Skip to content

15. Commands

Run in the composer inside a session (draft screens send text as a message — FAQ). Registered in src/commands.ts; the two deterministic archive doors (/chapters-fork, /chapters-continue) in src/tools.ts.

Form Effect
/chapters-link show current upstream + mirror status
/chapters-link <local-path> bind a shared local git repo (bare or worktree; file:///, ~, and relative paths all resolve) — a bare pool repo is created on demand, no credentials needed
/chapters-link <https-url> <token> bind a smart-HTTP pool; the token is required unless the host is loopback (127.0.0.1 / localhost / ::1), and is stored 0600 under the DSH home, never in the project
/chapters-link treedx+<url>/<repo> <token> bind a TreeDX service (token always required — bearer API); the managed repository is created server-side if absent

The no-args answer is a git-status-like block: Provider, Upstream, Bound (slug · project key · since), Mirror (branch/head, plus an origin-drift note when it will rebuild), Sync (last run + detail), Pending (files not yet mirrored), Harness (this machine’s id). Relinking to a different target — or a push that finds the mirror diverged — rebuilds the mirror from the store (the store is the truth).

Per linked project: Project / Remote / Provider lines, then Sync: a push is pending (debounced) or the last sync mode; then Last sync: <time> — synced (or local-only: <reason>) with the step trail behind it — cloned → published N new file(s), M already mirrored → pushed — and the enrichment status line. With no project and no history it says so in one line and points at /chapters-link. Pending counts, vocabulary curation notes, and failure details are all printed — no silence.

/chapters-models # every registered provider + its advertised models, as paste-ready ids
/chapters-models <provider> # one provider's advertised models
/chapters-models <provider> <model> # one exact route: context window, reasoning efforts

The discovery surface for the three model settings — enrichment.model, rules.model, and the prompt model — because a setter you cannot feed correct identifiers is a setter you cannot use. Every model line is the exact provider/model string those setters accept, and the output ends with what each of the three is currently set to (and the command that changes it). The exact-route form resolves through the host’s llm service and adds what only resolution knows: the context window and the selectable reasoning efforts, when the adapter advertises them.

Two honesty rules this command never drops: catalog membership is advisory (the host routes unlisted ids too — an empty catalog means “no selection offered”, not “no models work”), and a provider or capability the host does not expose is reported as absent, never guessed. Like every /chapters-* command it renders as a command flow node — zero model-context cost (#173).

/chapters-enrich run # process one batch now (up to `enrichmentBatchCap` chapters)
/chapters-enrich model # show the current annotator route
/chapters-enrich model <provider/model|clear> # override it, or reset to the conversation route
/chapters-enrich report # enrichment status line + pending chapter count
/chapters-rule add <category> "<text>" # write-once proposal; binds nothing yet
/chapters-rule list [--all|--proposed|--category <c>] # review surface
/chapters-rule view <id> # ONE rule, completely — full text, status, basis
/chapters-rule approve <id> | revoke <id> # YOUR machine's curation fact — never a shared rewrite

list shows titles, and a title is the first sentence of the body cut at 60 characters — enough to find a rule, never enough to judge one. view is the reading surface: the FULL untruncated text plus everything known about the rule on this machine (status, pin, and for session rules whether they are active, held for review, and whether their basis is stated — your own words — or inferred by the model). Approving a rule you have not read is approving nothing; view is what makes approve a judgment instead of a guess (#174, at the maintainer’s direction).

The session tier and the audit surface, so automatic capture is never opaque (#50):

/chapters-rule list --scope project|session # which tier a rule belongs to
/chapters-rule active # exactly what a rebuilt head will show, + withheld counts + the mining posture
/chapters-rule approve <id> # the one action that turns a held mined rule on
/chapters-rule pin <id> | unpin <id> # the always-rendered class (human-set only)
/chapters-rule model [<provider/model> | clear] # a dedicated model for mining; costs one call per chapter
/chapters-rule conflicts # report contradictory pairs — never edits a rule

ADD RULE: <text> at the start of a line in your own message is the deterministic capture path — zero model tokens, active for your session tree, exactly the words you wrote. It is deliberately not a command: nothing to remember, no review step before it takes effect. Everything the model mines instead is labelled stated or inferred in the head, so the two paths are never confused. See Rules & governance.

The deterministic door: /chapters-fork [title] and /chapters-continue [title]

Section titled “The deterministic door: /chapters-fork [title] and /chapters-continue [title]”

One door, two intents — not two mechanisms. Both commands come from the same builder (deterministicCommand, src/tools.ts), and a test pins that given the same events they archive identically: same ranges, same titles, same plan facts. Both buttons on the assistant row execute these same commands through the host’s command wire, from one shared client runner (src/client/doors.ts — a table of two rows, deliberately not two implementations).

What the door does, either way: everything after the last archive watermark up to the last completed turn is archived verbatim, ranges derived by the plugin itself — zero model involvement — and a titled child opens whose entire inherited history is the cumulative TOC. If nothing new was said since the last archive, both degrade to a pure citation child: created, citing the existing chapters unchanged, nothing written.

The only difference is what the child is told about plans:

intent command button what the child’s notice carries
branch /chapters-fork Fork with chapters zero plan bytes — this child splits a new direction; its plans stay reachable as history, in the cited chapters and as searchable kind: plan documents
continue /chapters-continue Continue with chapters ## Active plan + ## Previous plans on this path (marked history) with the verdict, chapter, blob and plan-document paths; and if the session was still planning, the child is genuinely in plan mode because it is seeded with its own plan/mode fact. A plan already approved is not seeded — approving exits plan mode, and that child’s job is to implement

Why commands and buttons, when the chapters_continue tool exists: a tool call must be authored by the model, which means (a) it has to chain chapters_segment → chapters_continue with correct ranges, and (b) any recorded test of it freezes those machine-specific numbers. The door removes both dependencies — which is how the continuation became testable in a browser at all, and why a click works on a small local model that may not chain two tool calls.

Continue with chapters also got its button late: the fork button had always driven the UI to the child it created, while the continuation’s only feedback was a session id inside command text — a created child the user could not see read as a broken door. Both rows now switch you to the child (with a sidebar fallback and an inline note if the switch cannot be made).

One visible side effect, stated plainly because users notice it: the plugin hides the host’s native Branch into a new conversation action in that same message row (src/client/index.ts), the two chapter doors replacing it. It is a cosmetic rule matched on the button’s accessible label, so if a future host rewords that label the native button simply reappears — fails open, visibly, never silently.

/chapters-save — archive without creating a child session

Section titled “/chapters-save — archive without creating a child session”
/chapters-save # archive completed turns into searchable chapters
/chapters-save [title] # optional title prefix for the saved chapters

Archives everything after the last archive watermark up to the last completed turn into verbatim chapters on disk, updates the search index, and triggers enrichment — without creating a child session or triggering compaction. The parent session remains fully functional.

This is the side-effect-only half of the deterministic door: same archive pipeline as /chapters-fork and /chapters-continue (composeChapters + writeArchive, zero model involvement), but stops before agents.create. Useful for sharing session context with parallel sessions via chapters_search without disrupting the active conversation.

Idempotent: re-running saves only what has not yet been archived (respects the watermark). If nothing new was said since the last save, reports “nothing to save” without error.

/chapters-repair — re-derive the archive from the durable log

Section titled “/chapters-repair — re-derive the archive from the durable log”

The durable session log is append-only and complete (invariant 1), which is the only reason any of the defects in issues #58/#59 are recoverable at all. /chapters-repair audits the three things that can disagree — the log, the store’s files, and the registry’s records — and repairs what the current code can now produce correctly.

/chapters-repair status # last journal, findings count by class
/chapters-repair audit [--json] # read-only; reports every finding and its remedy
/chapters-repair dry-run [class] # the exact mutations a run would make, with numbers
/chapters-repair apply hollow|gaps|citations|bookkeeping|all
/chapters-repair journal [<id>] # list applied journals / print one
/chapters-repair restore <journal-id> # undo a whole applied run, newest action first

What it fixes, by class:

class what it was remedy
hollow-payload a chapter that rendered nothing (**unknown** / 0 B), from a payload-less read (#58) re-render the span from the log under a NEW number; supersede the old record
unsiged-completed-turn a completed turn inside no chapter, from a lost signature (#59) archive the gap span through the normal save core
never-archived-subagent a settled child that tripped no archive moment archive it from its durable log, with subagent lineage
dead-citation a checkpoint that cites a chapter file never written (#59) materialize the file at the cited path and reserved number — the lie becomes true, no text is edited
ghost-plan-manifest / unreleased-reservation a manifest with no body; a retry key that can never land expire/release, keeping every spent number spent
orphan-file / duplicate-number / countervalue-drift files no record cites; a number spent twice; a counter behind its own max classify, supersede, raise the counter upward only
test-litter chapters written into a real store by the acceptance rig quarantine, never delete

The repair contract (why it never edits a chapter)

Section titled “The repair contract (why it never edits a chapter)”

Damage is superseded, never repaired in place, and a body-hash guard is the only sanctioned mutation of a chapter file — the one deletion class is operator removal of bad data, /chapters-remove, and repair itself never deletes. So repair supersedes: it writes a new chapter number carrying repairs: <n>, marks the old registry record status: 'superseded', and copies the corrupt file into <tree>/.repair-quarantine/. Nothing is deleted, nothing is rewritten, and every applied run is journalled to <tree>/repairs/<utc-stamp>.json — one record per move, with hashes, so restore can walk it backwards. Derived layers (TOC, index shards, vocabulary, plan status, counters) are re-derivable and repair may rewrite those; verbatim bytes are read-only input.

Refusals, deliberately: it will not merge, split or renumber existing chapters; will not touch a session log or a checkpoint’s text; will not re-cite a file it cannot verify byte-for-byte; and will not run against a workspace whose registry row it cannot serialize through the #59 write discipline.

/chapters-remove — delete a bad chapter for real

Section titled “/chapters-remove — delete a bad chapter for real”

The mistake this door exists for: a prompt meant for another project, typed into this project’s session. The contaminated turns archive, and from that boundary on every TOC, every search, and every enrichment result carries the wrong project’s context. /chapters-repair supersedes damage and keeps the bytes; removal is for bytes that should not exist at all — another project’s content, junk, a secret. The rule it ships with: bad bytes leave the memory; the durable log is what lets you admit you were wrong.

/chapters-remove list # the session's active TOC with removable ids
/chapters-remove find <query> # search this project's pool; names ids
/chapters-remove <id ...> # dry-run: the exact deletion set, nothing written
/chapters-remove apply <id ...> # delete for real — journal first
/chapters-remove report # missing-access log: who still cites removed chapters
/chapters-remove restore <journal-id> # re-derive the removed chapter from the durable log

What apply deletes, and what it deliberately keeps:

layer removed chapter’s fate
chapter file deleted from the store and from the pool mirror — the deletion is committed and pushed, so index shards regenerate without it
artifacts & plan documents deleted when orphaned — a content-addressed blob survives if any active chapter still names it
registry record a stub stays: number, seq span, path, hash, artifact manifest, status: 'removed' — the archive watermark reads it, so no auto-archive moment can re-capture the poisoned span
derived surfaces TOC notices, index shards, chapters_search, enrichment: gone from the next build onward, through the one isActiveChapter predicate
the durable session log never touched. Removal is a store operation; the log stays append-only and complete
already-seeded child TOCs still name the chapter (recorded text is never rewritten) — the dangling-reference report names them, and later misses land in removals/missing-access.log
git history of the pool unchanged — the deletion is a new commit, not a rewrite

Identifiers: the canonical id is the registry path printed by list; find results, <sessionId>:<number>, and a bare TOC ordinal from list all resolve through the same door. An unknown id refuses and names near candidates; an ambiguous one refuses rather than guessing; an id pointing outside this project’s store refuses — removal is same-project only, from the contaminated session or any other session of the project.

Reversal is re-derivation, not resurrection: restore <journal-id> re-renders the journaled span from the session’s durable log through the same save core every door uses, under a new number — so a removed number stays spent forever, and a restore that would render nothing refuses rather than writing a husk. Safety order is the #60 discipline: the journal under <tree>/removals/ is written before the first unlink; if it cannot be written, nothing is deleted.

Known scope limit, stated rather than hidden: v1 is single-machine correct. If the same pool is linked on a second machine that still holds the file locally, that machine can re-publish it until cross-machine removal tombstones land (follow-up issue). Dry-run is the default: list and find write nothing, and the apply word is the only thing that deletes.

No command. With subagentAutoSave on (the default), every subagent run that carries the chapters preset is archived the moment it ends: the plugin subscribes to the host’s subagent/end lifecycle event, reads the child’s completed turns, and runs the same save pipeline as /chapters-save — composeChapters + writeArchive, zero model involvement — with enrichment queued rather than run (a ten-child fanout must not hammer the annotator). A continuable child that is woken again archives again: the watermark makes the repeat save only its new turns.

The child’s chapters live under its own session tree, exactly like every other session — searchable and pool-published on its own. Its frontmatter says where it came from: subagent: true plus parentSession: <id>, read from the child’s own durable session header (the host stamps parentSession, origin: subagent, delegationDepth, and agentPreset at creation — measured, spikes/subagent-end/FINDINGS.md). Nothing links a subagent into its spawner’s ancestry; parent and children share a workspace, not a TOC.

Honest limits, stated because silence here would be the kind of unstated loss this project refuses: only completed turns are archived (the anchor is the child’s last turn/end, so an interrupted tail is skipped, never half-saved); a child on a different preset is left alone; and if a child’s log can be read neither live nor from disk at settlement, the archive is skipped with a loud diagnostic rather than guessed at.

Auto-archive when a continuation child lags behind

Section titled “Auto-archive when a continuation child lags behind”

A chapters_continue child is not a subagent, so the settlement door above does not cover it. As of issue #61 a turn/end listener archives it through the same save core when its session is a chapters-preset session with lineage — read from the registry row, the header, or the ch- id our own continue door mints — and either it has never archived at all, or childAutoArchiveMinTurns completed turns have piled up past its watermark. It creates no session and compacts nothing, so nothing happens mid-step; a session that never trips a moment is unretrievable, and the field case was 2,363 events with zero chapters. Close it with childAutoArchive: false.

/chapters-prompt # usage + the current state (ON with model / OFF)
/chapters-prompt model # show the effective model and its source (override or config)
/chapters-prompt model <provider/model> # set it — setting a model is what ENABLES the feature
/chapters-prompt model clear # drop the override; falls back to `promptModel` ('' ⇒ off)
/chapters-prompt off # disable regardless of config (stores the empty override)
/chapters-prompt suggest # machine verb: the composer ghost pulls this; ≤ one bounded call per turn boundary

suggest is the client’s door: it answers {kind:'success', text:<one prompt>} when a recommendation is current or computable, and refuses with the remedy (off, no route, model failure, over-cap answer) when it is not. A refusal latches the ghost hidden for the turn boundary — the disabled state shows nothing, ever. /chapters-status prints the feature’s state beside enrichment and rules.

With the chapters agent preset mounted, /compact runs through the plugin’s deterministic engine: checkpoint summary with carried plot, zero inference tokens, append-only shadowing. (The harness’s own command surface, ours underneath.)

Next: 16. Agent tools.