spexcode 0.2.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +56 -44
  2. package/package.json +3 -3
  3. package/spec-cli/bin/spex.mjs +2 -2
  4. package/spec-cli/hooks/dispatch.sh +1 -1
  5. package/spec-cli/hooks/harness.sh +26 -6
  6. package/spec-cli/src/anchors.ts +300 -0
  7. package/spec-cli/src/attach.ts +2 -2
  8. package/spec-cli/src/cli.ts +691 -536
  9. package/spec-cli/src/client.ts +31 -30
  10. package/spec-cli/src/contract-filter.ts +1 -1
  11. package/spec-cli/src/doctor.ts +40 -13
  12. package/spec-cli/src/gateway.ts +11 -7
  13. package/spec-cli/src/git.ts +23 -15
  14. package/spec-cli/src/{board.ts → graph.ts} +44 -14
  15. package/spec-cli/src/{boardCache.ts → graphCache.ts} +41 -21
  16. package/spec-cli/src/{boardDelta.ts → graphDelta.ts} +1 -1
  17. package/spec-cli/src/graphStream.ts +288 -0
  18. package/spec-cli/src/guide.ts +142 -98
  19. package/spec-cli/src/harness-select.ts +2 -2
  20. package/spec-cli/src/harness.ts +30 -14
  21. package/spec-cli/src/help.ts +300 -385
  22. package/spec-cli/src/hooks.ts +1 -1
  23. package/spec-cli/src/index.ts +144 -103
  24. package/spec-cli/src/init.ts +14 -13
  25. package/spec-cli/src/issues.ts +89 -33
  26. package/spec-cli/src/layout.ts +5 -5
  27. package/spec-cli/src/lint.ts +128 -30
  28. package/spec-cli/src/localIssues.ts +61 -60
  29. package/spec-cli/src/materialize.ts +1 -1
  30. package/spec-cli/src/mentions.ts +15 -15
  31. package/spec-cli/src/migrate-table.ts +398 -0
  32. package/spec-cli/src/migrate.ts +386 -0
  33. package/spec-cli/src/ranker.ts +30 -4
  34. package/spec-cli/src/reaper.ts +117 -0
  35. package/spec-cli/src/search.bench.mjs +12 -12
  36. package/spec-cli/src/search.ts +1 -1
  37. package/spec-cli/src/sessions.ts +244 -138
  38. package/spec-cli/src/specs.ts +40 -25
  39. package/spec-cli/src/supervise.ts +2 -2
  40. package/spec-cli/src/tree.ts +5 -5
  41. package/spec-cli/src/uninstall.ts +4 -4
  42. package/spec-cli/templates/hooks/post-checkout +1 -1
  43. package/spec-cli/templates/hooks/post-merge +3 -3
  44. package/spec-cli/templates/hooks/pre-commit +9 -9
  45. package/spec-cli/templates/hooks/prepare-commit-msg +1 -1
  46. package/spec-cli/templates/spec/project/{.config → .plugins}/core/idle/idle.sh +2 -2
  47. package/spec-cli/templates/spec/project/{.config → .plugins}/core/mark-active/mark-active.sh +7 -0
  48. package/spec-cli/templates/spec/project/{.config → .plugins}/core/mark-active/spec.md +2 -0
  49. package/spec-cli/templates/spec/project/{.config → .plugins}/core/session-fail/fail.sh +1 -1
  50. package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-first/spec-first.sh +1 -1
  51. package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-of-file/spec-of-file.sh +2 -2
  52. package/spec-cli/templates/spec/project/.plugins/core/spec.md +21 -0
  53. package/spec-cli/templates/spec/project/{.config → .plugins}/core/stop-gate/stop-gate.sh +21 -21
  54. package/spec-cli/templates/spec/project/{.config → .plugins}/distill/spec.md +2 -2
  55. package/spec-cli/templates/spec/project/{.config → .plugins}/extract/spec.md +5 -5
  56. package/spec-cli/templates/spec/project/{.config → .plugins/prompts}/forge-link/spec.md +1 -1
  57. package/spec-cli/templates/spec/project/{.config → .plugins/prompts}/reproduce-before-fix/spec.md +3 -3
  58. package/spec-cli/templates/spec/project/.plugins/prompts/spec.md +20 -0
  59. package/spec-cli/templates/spec/project/{.config → .plugins}/regroup/spec.md +2 -2
  60. package/spec-cli/templates/spec/project/.plugins/spec.md +18 -0
  61. package/spec-cli/templates/spec/project/.plugins/supervisor/spec.md +8 -0
  62. package/spec-cli/templates/spec/project/{.config → .plugins}/tidy/spec.md +1 -1
  63. package/spec-cli/templates/spec/project/spec.md +1 -1
  64. package/spec-dashboard/dist/assets/{Dashboard-P0B9ukSG.js → Dashboard-CTcH2eW9.js} +9 -9
  65. package/spec-dashboard/dist/assets/EvalsPage-CJNKwHLN.js +2 -0
  66. package/spec-dashboard/dist/assets/{FoldToggle-BuQ0lokE.js → FoldToggle-CVFbBpyW.js} +1 -1
  67. package/spec-dashboard/dist/assets/{IssuesPage-H-D8aHEl.js → IssuesPage-kULjonqj.js} +1 -1
  68. package/spec-dashboard/dist/assets/{MobileApp-oZXIeCPb.js → MobileApp-B0ZJju8K.js} +1 -1
  69. package/spec-dashboard/dist/assets/{SessionInterface-Blr_MEdU.js → SessionInterface-BRKJqU2U.js} +3 -3
  70. package/spec-dashboard/dist/assets/{SessionWindow-LcCzBMU7.js → SessionWindow-CDhEL7wO.js} +7 -7
  71. package/spec-dashboard/dist/assets/{Settings-_yOye-In.js → Settings-BL6FV_8S.js} +1 -1
  72. package/spec-dashboard/dist/assets/{index-uGs9v_9o.css → index-DmQsNYKK.css} +1 -1
  73. package/spec-dashboard/dist/assets/{index-BhIslAau.js → index-DulGPk6A.js} +8 -8
  74. package/spec-dashboard/dist/index.html +2 -2
  75. package/{spec-yatsu → spec-eval}/src/cache.ts +8 -5
  76. package/{spec-yatsu → spec-eval}/src/cli.ts +212 -96
  77. package/{spec-yatsu → spec-eval}/src/evaltab.ts +35 -27
  78. package/{spec-yatsu → spec-eval}/src/filing.ts +7 -5
  79. package/{spec-yatsu → spec-eval}/src/freshness.ts +44 -22
  80. package/spec-eval/src/humanok.ts +43 -0
  81. package/{spec-yatsu → spec-eval}/src/scenariofresh.ts +22 -17
  82. package/{spec-yatsu/src/yatsu.ts → spec-eval/src/scenarios.ts} +41 -26
  83. package/{spec-yatsu/src/proof.ts → spec-eval/src/sessioneval.ts} +59 -59
  84. package/{spec-yatsu → spec-eval}/src/sidecar.ts +42 -10
  85. package/{spec-yatsu → spec-eval}/src/timeline.ts +1 -1
  86. package/spec-forge/src/__fixtures__/github-forge.json +9 -9
  87. package/spec-forge/src/cli.ts +14 -13
  88. package/spec-forge/src/{needs-yatsu-eval.ts → needs-eval.ts} +6 -6
  89. package/spec-cli/src/boardStream.ts +0 -179
  90. package/spec-cli/templates/presets/careful/.config/clarify-before-code/spec.md +0 -11
  91. package/spec-cli/templates/spec/project/.config/core/spec.md +0 -17
  92. package/spec-cli/templates/spec/project/.config/spec.md +0 -15
  93. package/spec-cli/templates/spec/project/.config/supervisor/spec.md +0 -8
  94. package/spec-dashboard/dist/assets/EvalsPage-BrvAGyc4.js +0 -2
  95. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/idle/spec.md +0 -0
  96. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/session-fail/spec.md +0 -0
  97. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-first/spec.md +0 -0
  98. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-of-file/spec.md +0 -0
  99. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/stop-gate/spec.md +0 -0
  100. /package/spec-cli/templates/spec/project/{.config → .plugins}/distill/digest.mjs +0 -0
  101. /package/spec-cli/templates/spec/project/{.config → .plugins/prompts}/memory-hygiene/spec.md +0 -0
@@ -1,345 +1,64 @@
1
1
  // @@@ help journey - the CLI's three help layers, each pointing at the next so no probe dead-ends:
2
- // 1. `spex help` → the MAP: every porcelain command, grouped by the loop it serves.
3
- // 2. `spex help <cmd>` → ONE command's usage (also `spex <cmd> --help`, intercepted pre-verb).
2
+ // 1. `spex help` → the MAP: the whole noun-first surface, one line per drawer/verb.
3
+ // 2. `spex help <cmd>` → ONE drawer/command's usage (also `spex <cmd> --help`, intercepted pre-verb).
4
4
  // 3. `spex guide [topic]` → the SKILL layer: workflows, file formats, best practice (guide.ts).
5
5
  // help answers "what do I type"; guide answers "how do I work". Machine plumbing (hook/launch-script
6
6
  // callees) lives under `spex internal` and is deliberately absent from the map — an agent scanning
7
7
  // `spex help` sees only verbs meant for it. Governed by the cli-surface spec node.
8
+ //
9
+ // The grammar the map teaches: `spex <noun> <verb> [object] [flags]` — the verb is always the second
10
+ // token after its noun; a bare noun prints its drawer's help; a bare verb exists only where the object is
11
+ // invariably THIS PROJECT. One verb, one spelling: there are no aliases and no promoted twins.
8
12
 
9
13
  // One command's help entry. `see` renders as a trailing "see also:" journey pointer.
10
14
  type Entry = { line: string; body: string; see?: string }
11
15
 
12
16
  const SEL_NOTE = `SEL = session id (or unique id-prefix) | node id | branch — every session read/control verb
13
- accepts any of the three; none (or @all) means every session.`
14
-
15
- const ROUTING_NOTE = `Backend routing: every session verb accepts --api <url> (--port <n> = localhost sugar) to name its
16
- backend explicitly — the flag always wins. Bare, it resolves: worker env / the cwd project's live
17
- recorded backend / fallback / :8787 (spex guide config → BACKEND ROUTING).`
18
-
19
- // aliases resolve to a canonical entry so `spex help session` and `spex session new --help` meet the same text.
20
- // The session-sub tokens mirror the CLI's verb-mirror rule: each typeable sub also answers bare at the top
21
- // level, so its help probe (`spex send --help`, `spex help send`) must land on the session entry, not dead-end.
22
- const SESSION_SUB_TOKENS = ['reopen', 'done', 'park', 'ask', 'exit', 'close', 'send', 'capture', 'attach', 'rename', 'rawkey', 'prompt']
23
- const ALIAS: Record<string, string> = {
24
- 'review-proof': 'eval',
25
- help: 'help',
26
- ...Object.fromEntries(SESSION_SUB_TOKENS.map((t) => [t, 'session'])),
27
- }
17
+ accepts any of the three; on the list verbs (ls/watch), none (or @all) means every session.`
28
18
 
29
- const ENTRIES: Record<string, Entry> = {
30
- // ── find & read the graph ─────────────────────────────────────────────────
31
- search: {
32
- line: 'search <query> which spec node GOVERNS a topic — ranked by user-story, not grep',
33
- body: `Usage: spex search <query…> [--limit N=10] [--json]
34
-
35
- Finds the spec node(s) whose INTENT matches your topic — ranked by user-story relevance, which
36
- surfaces user-facing behaviour a code-grep misses. Run it BEFORE touching code: the returned node's
37
- spec.md body is the current contract for that area. Prints title, id, path, snippet per hit.
38
- The corpus is English — query in English (translate first if your question isn't).`,
39
- see: 'spex owner (file → node, the reverse edge) · spex guide spec (what a node is)',
40
- },
41
- owner: {
42
- line: 'owner <path> the reverse edge: which spec node(s) govern or reference a file',
43
- body: `Usage: spex owner <path> [--actionable]
44
-
45
- Maps a source file to BOTH spec relations: its GOVERNORS (code: — the source of truth; drives
46
- drift/yatsu) and its REFERENCERS (related: — coverage only), with the verdict spelled out:
47
- uncovered ("give it a home"), related-only (covered, but nothing tracks its drift), sanely
48
- governed (read/honor that spec), or over-owned (> maxOwners — split the file). --actionable
49
- prints NOTHING unless action is needed (hook use): only uncovered / over-owned fire.`,
50
- see: 'spex search (topic → node) · spex lint (coverage over the whole tree)',
51
- },
52
- tree: {
53
- line: 'tree the graph as a human-readable tree (status-coloured, badges)',
54
- body: `Usage: spex tree [--node <id>] [--depth N] [--json]
55
-
56
- Prints the assembled spec graph as an indented tree — the CLI twin of the dashboard's tidy-tree,
57
- built from the same board (merged tree + worktree overlay). One line per node: id, derived status
58
- (coloured when stdout is a tty; NO_COLOR respected — the status word always prints), title, and
59
- attention badges: drift:N (drifted files), stale:N (yatsu scenarios whose latest reading aged),
60
- issues:N (open issues), ghost (being added by a worktree).
61
- --node <id> render just that subtree (unknown id fails loud)
62
- --depth N limit levels below the shown root; prunes are counted, never silent
63
- --json the same filtered subtree as nested objects, badge counts precomputed`,
64
- see: 'spex board (the full flat JSON payload) · spex search (find one node by intent)',
65
- },
66
- board: {
67
- line: 'board dump the assembled board as JSON (tree · overlay · sessions)',
68
- body: `Usage: spex board
19
+ const ROUTING_NOTE = `Backend routing: every backend-touching verb accepts --api <url> (--port <n> = localhost sugar) to name
20
+ its backend explicitly the flag always wins. Bare, it resolves: worker env / the cwd project's live
21
+ recorded backend / fallback / :8787 (spex guide settings → BACKEND ROUTING).`
69
22
 
70
- Prints the full dashboard board state as JSON the merged spec tree, per-worktree overlay, and the
71
- session list. Identical to GET /api/board; needs the backend (spex serve) reachable.`,
72
- see: 'spex tree (the same graph, human-readable) · spex ls (just the sessions, as a table) · spex search (find one node instead of dumping all)',
73
- },
74
- guide: {
75
- line: 'guide [topic] the manuals: setup workflow · spec/yatsu file formats · spexcode.json · footprint',
76
- body: `Usage: spex guide the human setup workflow (install once, adopt a repo, serve)
77
- spex guide spec the spec.md file format + every lint rule
78
- spex guide yatsu the yatsu.md scenario format + how loss is measured and filed
79
- spex guide config every spexcode.json / spexcode.local.json field, and which file it belongs in
80
- spex guide footprint the footprint model: never-tracked artifacts, exclude + content filter, anchors
23
+ const DOT_NOTE = `\`.\` as a node argument means the node THIS worktree works on (the session's bound node, else the
24
+ node/<id> branch). One-shot payload reads (graph · spec search · session ls/show/review · eval ls ·
25
+ scenario ls · issue ls/show/links) take --json.`
81
26
 
82
- guide is the SKILL layer workflows and formats. Command usage lives here in help
83
- (\`spex help <cmd>\`); guide carries what the commands assume you know.`,
84
- },
85
-
86
- // ── author & verify (worker loop) ─────────────────────────────────────────
87
- lint: {
88
- line: 'lint check the spec↔code graph (integrity·living·altitude·coverage·drift·owners)',
89
- body: `Usage: spex lint
90
-
91
- Checks the whole graph and exits non-zero on errors (or a blocked commit-local drift gate):
92
- integrity (error) a code:/related: path doesn't exist
93
- living (error) a body accretes a "## vN" changelog instead of staying current-state
94
- altitude (warn) a body slid below contract altitude into a mechanics dump
95
- coverage (warn) a governed source file no node claims yet
96
- drift (warn) a governed file changed after its spec's last version
97
- owners (warn) a file governed by more than maxOwners nodes — split it
98
- When run from the pre-commit hook, a staged commit touching a heavily-drifted node BLOCKS
99
- (bypass: SPEXCODE_SKIP_LINT=1); CI/manual runs are advisory beyond errors.`,
100
- see: 'spex guide spec (each rule explained) · spex ack (drift that is mechanics-only)',
101
- },
102
- ack: {
103
- line: 'ack <node>… --reason stamp Spec-OK on HEAD: this change keeps those specs valid',
104
- body: `Usage: spex ack <node-id>… --reason "<why the contract still holds>"
105
-
106
- Amends HEAD with a Spec-OK trailer per node — the drift remedy when only MECHANICS changed and the
107
- spec's contract still holds. --reason is required (it forces the check) but NOT stored; git keeps
108
- only the trailer. If the intent DID change, edit the spec instead — same commit as the code.`,
109
- see: 'spex lint (where drift is reported) · spex guide spec (drift remedies)',
110
- },
111
- yatsu: {
112
- line: 'yatsu <sub> measure a node’s scenarios & file the loss signal: scan | eval | retract | show | clean',
113
- body: `Usage: spex yatsu scan [--changed] list nodes/scenarios missing readings
114
- spex yatsu eval [.|<node>] [--scenario <name>] (--pass|--fail) [--note <text>]
115
- [--image <png> …] [--result <path|->] [--video <webm|mp4>] [--timeline <json>]
116
- spex yatsu retract [.|<node>] [--scenario <name>] [--last | --ts <iso>] [--note <why>]
117
- spex yatsu show [.|<node>] [--json] readings history for a node
118
- spex yatsu clean [--keep-latest | --all] prune stored readings
119
-
120
- Files a reading of a scenario against its expected — the loss signal the optimizer reads. Measure
121
- through the REAL product surface (run it, drive a browser, capture), never by reasoning about
122
- the code. Evidence kind follows the behaviour: MOVING/timed behaviour (scroll, animation,
123
- playback, a multi-step flow) records a \`--video\`; a STATIC end state screenshots \`--image\`;
124
- backend/CLI files a \`--result\` transcript. A fix's proof is a fail→pass pair on the SAME
125
- scenario. \`retract\` is the sanctioned undo for a botched filing: it APPENDS a retraction
126
- event (traceable, never deletes a line).`,
127
- see: 'spex guide yatsu (yatsu.md format + evidence rules) · spex blob (stash evidence bytes)',
128
- },
129
- blob: {
130
- line: 'blob put|get evidence bytes ⇄ content hash: put stashes & prints the hash, get reads back',
131
- body: `Usage: spex blob put <file|->
132
- spex blob get <hash> [-o <file>]
133
-
134
- put writes bytes into the shared content-addressed evidence cache and prints the hash — transport
135
- only, no reading filed. Use the hash with --evidence on issues/remarks; re-putting the same content
136
- restores a pruned or cloned-away blob.
137
-
138
- get is the symmetric read: hash in, bytes out. Local cache first (no backend needed — the evidence
139
- is usually on this disk), then the backend's /api/yatsu/blob/:hash on a local miss; both missing
140
- fails loud naming each path. Bytes go to stdout by default (pipe-friendly); -o writes a file.`,
141
- see: 'spex yatsu eval (file a reading with evidence) · spex issues open --evidence <hash>',
142
- },
143
- issues: {
144
- line: 'issues … THE issue surface: one merged local+forge list, plus all write verbs',
145
- body: `Usage: spex issues the merged read (local + forge, store-tagged)
146
- [--node <id>] [--store local|github] [--all] [--json]
147
- spex issues open "<concern>" [--store local|<host>] [--node <id>…] [--evidence <hash>…] [--body -|<text>]
148
- spex issues reply <id> --body -|<text> [--evidence <hash>…] (routes by the issue's store)
149
- spex issues close <id> close by the issue's store: local lands, forge closes remote
150
- spex issues promote <id> move an OPEN local issue to the forge (one recorded action)
151
- spex issues on|off|status toggle/inspect the local-issue workflow
152
-
153
- Bare \`spex issues\` is the drain view a supervisor reads. \`open\` welcomes taste, annotations, and
154
- off-mainline smells — not only bugs; \`--store <host>\` opens straight on the forge (the same port the
155
- dashboard's New form uses). \`close\` and \`reply\` route by the issue's store — one verb, local or forge,
156
- the same routing as the dashboard. (\`nudge\` exists but is fired by the post-merge hook, not typed.)
157
- Mentions: @session · [[node]] work in any concern/body — CLI args included. [[node]] links the topic
158
- node (it also tags the issue, like --node); @session hands the text to that live agent; @new spawns
159
- a fresh worker on the thread's node.`,
160
- see: 'spex remark (pin a resolvable concern to an issue or scenario) · spex forge (trace forge → nodes)',
161
- },
162
- remark: {
163
- line: 'remark / resolve / retract pin a resolvable concern to a host; a peer resolves, the author retracts',
164
- body: `Usage: spex remark <issue-id | <node> --scenario <name>> --body -|<text> [--code-sha <sha>] [--evidence <hash>…]
165
- spex resolve <remark-ref> (the <thread-id>#<rid> that \`spex remark\` printed)
166
- spex retract <remark-ref>
167
-
168
- The resolvable interaction primitive: pin a concern to a HOST — a local issue or a yatsu scenario —
169
- that a second agent can \`resolve\` and the author can \`retract\`. The whole loop is CLI-first; the
170
- dashboard adds no capability.`,
171
- see: 'spex issues (the hosts) · spex yatsu show (scenario hosts)',
172
- },
173
- forge: {
174
- line: 'forge <sub> read-only trace of forge issues/PRs onto spec nodes: links | eval-pending',
175
- body: `Usage: spex forge links [--host github] [--node <id>] [--json]
176
- spex forge eval-pending [--host github] [--node <id>] [--json]
177
-
178
- Resolves a forge's open issues/PRs to the spec nodes they serve (the Spec: marker in an issue body;
179
- a node/<id> branch links its PR for free). Read-only — git/.spec stays the single source of truth.`,
180
- see: 'spex issues (the merged read that includes forge threads)',
181
- },
182
- // ── dispatch & manage sessions (manager loop) ─────────────────────────────
183
- new: {
184
- line: 'new "<prompt>" launch a worker session in its own node worktree [--prompt-file <path>|-] [--node <id>] [--launcher <name>]',
185
- body: `Usage: spex new "<task prompt>" [--node <id>] [--launcher <name>]
186
- spex new --prompt-file <path>|- [--node <id>] [--launcher <name>]
187
-
188
- Creates a session: node branch + worktree + a launched agent carrying your prompt (= session new).
189
- Give it ONLY its task — the dev-flow contract reaches it through the materialized system prompt.
190
- --node <id> (or the prompt's first [[id]] mention, same effect) binds the session to that node:
191
- branch name node/<id>-<shortid>, board attribution, and one appended line with the node's spec.md
192
- path when the node exists. With neither, the session is bound to no node.
193
- --prompt-file <path> reads the prompt from a file (- = stdin), so a long multi-paragraph prompt never
194
- fights shell quoting; it is exclusive with the inline prompt (both given = error), and an unreadable
195
- or empty file refuses the launch.
196
- The launcher name selects both the agent harness and the command/auth profile (built-ins: claude, codex);
197
- omitting it requires sessions.defaultLauncher in spexcode.json or spexcode.local.json.
198
- Routes through the running backend (auth env + concurrency cap); prints the created session JSON.
199
- Then MONITOR it: background \`spex wait <id>\`, or \`spex watch\` for the whole stream.
200
- Talk to it with \`spex send <id> "<msg>"\` — never raw tmux keystrokes.`,
201
- see: 'spex wait / spex watch (monitor) · spex send (talk to it) · spex review (when it proposes) · ' + SEL_NOTE.split('\n')[0],
202
- },
203
- ls: {
204
- line: 'ls [SEL…] living-sessions table [--status a,b] [--json] [--api URL]',
205
- body: `Usage: spex ls [SEL…] [--status a,b] [--json] [--api <url> | --port <n>]
206
-
207
- One-shot table of living sessions and their states, from the resolved backend — bare \`spex ls\` in a
208
- project's tree hits THAT project's live backend; --api <url> (or --port <n>) points it anywhere,
209
- including a remote machine's. ${SEL_NOTE}
210
- ${ROUTING_NOTE}`,
211
- see: 'spex watch (the live stream) · spex wait (block on one session)',
212
- },
213
- watch: {
214
- line: 'watch [SEL…] stream actionable transitions — NEVER EXITS; background it, never block on it',
215
- body: `Usage: spex watch [SEL…] [--as NAME] [--status a,b] [--idle] [--interval N=5]
216
-
217
- Streams session lifecycle transitions (launched → review/done/offline/error/needs-input → closed)
218
- until killed. It NEVER EXITS — it is the human's forever stream. An agent must background it or use
219
- \`spex wait <SEL>\` (one-shot) instead; blocking a turn on watch freezes you. Watching draws a
220
- supervision edge on the session graph and greets the watched sessions once.
221
- ${SEL_NOTE}`,
222
- see: 'spex wait (the one-shot, guaranteed-to-exit counterpart)',
223
- },
224
- wait: {
225
- line: 'wait <SEL> block until <SEL> is actionable, print the status, exit',
226
- body: `Usage: spex wait <SEL> [--timeout S=1200] [--interval S=2] [--idle]
227
-
228
- Blocks until the session reaches an actionable status, prints it, and EXITS — the supervisor's
229
- per-worker monitor (background one wait per worker; the exit is your wake-up). Guaranteed to
230
- terminate: --timeout is the hard wall (exit 1); a vanished session exits 2; a down backend fails
231
- loud (exit 1), never a false timeout. Draws the watcher→worker edge for the whole wait.
232
- ${SEL_NOTE}`,
233
- see: 'spex watch (the forever stream) · spex review (what to run when it prints review/done)',
234
- },
235
- review: {
236
- line: 'review <SEL> manager cockpit: ahead · merge-base diff · gates · proposal [--json]',
237
- body: `Usage: spex review <SEL> [--json]
238
-
239
- The ONE review payload for a session: commits ahead of the trunk, uncommitted files, its proposal,
240
- the gates (conflicts with the trunk, lint), and the merge-base diff — decide from this, don't
241
- hand-run git. The MEASURED side of the decision is \`spex eval <SEL>\`: the changed nodes' eval
242
- readings, and (--export) the self-contained HTML export.
243
- ${SEL_NOTE}`,
244
- see: 'spex eval (the session’s measured loss) · spex merge (act on an approved review)',
245
- },
246
- eval: {
247
- line: 'eval <SEL> the session’s eval readings: its changed nodes’ measured loss [--export]',
248
- body: `Usage: spex eval <SEL> [--json]
249
- spex eval <SEL> --export [--open | --out <path> | --json]
250
-
251
- The session's evaluation, read from the backend (the dashboard Eval tab's CLI twin): every spec node
252
- the session's diff touches, each DECLARED scenario at its CURRENT score (latest reading, rooted at
253
- the session's worktree). Blind spots lead (declared, never measured — the outstanding loss), then
254
- the session's OWN measurements ✦-marked, then the inherited baseline (other sessions' latest
255
- readings) under an explicit divider. A frontend change with no yatsu.md is flagged, never hidden.
256
- --export writes the evaluation as ONE self-contained HTML artifact instead (diff · evidence
257
- inlined · gates; --json = the model) for CI/sharing. (\`spex review proof\` is its deprecated alias.)
258
- This is the READ; filing a reading stays \`spex yatsu eval\`.
259
- ${SEL_NOTE}`,
260
- see: 'spex review (gates + diff — the merge decision) · spex yatsu eval (FILE a reading, the write verb)',
261
- },
262
- merge: {
263
- line: 'merge <SEL> gated merge into the trunk — dispatched to the session’s own agent',
264
- body: `Usage: spex merge <SEL>
265
-
266
- Dispatches the merge to the session's OWN agent (it knows the work's intent and resolves conflicts);
267
- the server never touches the trunk's tree. Gates re-check first. After it lands, confirm HEAD
268
- advanced before closing the session — closing an unmerged branch discards the work.
269
- Mutating verbs are PROJECT-BOUND: a backend serving another project's repo refuses the write loudly
270
- (name the target with --api <url> to write cross-project on purpose).
271
- ${SEL_NOTE}`,
272
- see: 'spex review (before) · spex session close (after the merge is confirmed)',
273
- },
274
- session: {
275
- line: 'session <sub> every session verb answers here: new·ls·watch·wait·review·merge·reopen·done·park·ask·exit·close·send·capture·attach·rename·rawkey·prompt',
276
- body: `Worker verbs (declare YOUR OWN state — a claim the board and your supervisor act on):
277
- spex session done --propose merge|nothing|close [--note T] committed and stopping; merge = ready for review
278
- spex session park --note <what-you-await> a real background task will wake you
279
- spex session ask --note <your-question> stopped on the human; resumes when they reply
280
-
281
- Manager verbs (control another session; all take SEL):
282
- spex session send <SEL> "<msg>" deliver a message (fail-loud: a dead dispatch exits non-zero)
283
- spex session capture <SEL> the live pane as text
284
- spex session rename <SEL> "<name>" set the display name ("" clears; the right-click rename, as a verb)
285
- spex session rawkey <SEL> "<keys>" raw nav keys to a TUI dialog, in strike order (e.g. "Up Up Enter";
286
- named keys · single chars · C-/M-/S- combos; fail-loud)
287
- spex session prompt <SEL> the session's originating prompt
288
- spex session reopen <SEL> [--force] relaunch ONLY if confirmed offline (--force for a wedged live one)
289
- spex session exit <SEL> soft stop: kill the agent, KEEP the worktree (resumable)
290
- spex session close <SEL> retire the session and its worktree
291
-
292
- Promoted verbs — they answer in this drawer too (same verb, either drawer):
293
- spex session new|ls|watch|wait|review|merge … ≡ spex new|ls|watch|wait|review|merge …
294
- And the reverse holds for every sub above: it also answers bare at the top level
295
- (spex send <SEL> "…" ≡ spex session send <SEL> "…"). One implementation, two spellings —
296
- you never have to guess which drawer a session verb lives in.
27
+ const MENTION_NOTE = `Mentions: @session · [[node]] · @new work in ANY prompt, issue, or remark body — text passed as a CLI
28
+ arg included. [[node]] names the topic node; @session hands the text to that live agent; @new spawns a
29
+ fresh worker on the thread's node.`
297
30
 
298
- Human escape hatch:
299
- spex session attach <SEL> sit in the worker's REAL tmux (detach: C-b d; the session keeps
300
- running). INTERACTIVE AND BLOCKING — like watch, an agent must
301
- NEVER run it in a turn (it freezes you): use capture/send/rawkey.
302
- LOCAL-only the tmux server is the backend machine's, so it fails
303
- loud when the resolved backend is remote. Offline session → loud.
304
-
305
- Mentions: @session · [[node]] work in ANY prompt, issue, or remark body text passed as a CLI arg
306
- included. [[node]] names the topic (a new session derives its node from the prompt's first one);
307
- @session hands the surrounding text to that live agent; @new dispatches a fresh worker.
308
-
309
- (state · fail · idle · commit-gate also exist but are hook-driven the lifecycle hooks call them;
310
- never type them.) ${SEL_NOTE}
311
- Manager verbs that WRITE (send/rename/rawkey/reopen/exit/close) are PROJECT-BOUND: a backend serving
312
- another project's repo refuses loudly — name the target with --api <url> to drive it on purpose.
313
- ${ROUTING_NOTE}`,
314
- see: 'spex new (launch) · spex wait/watch (monitor) · spex review/merge (land)',
31
+ const ENTRIES: Record<string, Entry> = {
32
+ // ── project verbs (implicit object = this project) ────────────────────────
33
+ graph: {
34
+ line: 'graph the assembled view: bare = readable tree · --json = the full payload',
35
+ body: `Usage: spex graph [--focus <id>] [--depth N] [--json]
36
+
37
+ The ONE assembled view — merged spec tree + worktree overlay + sessions. Bare it renders the
38
+ status-coloured tree (coloured when stdout is a tty; NO_COLOR respected), one line per node: id,
39
+ derived status, title, and attention badges (drift:N · stale:N · issues:N · ghost).
40
+ --focus <id> render just that subtree (unknown id fails loud)
41
+ --depth N limit levels below the shown root; prunes are counted, never silent
42
+ --json the full payload (tree · overlay · sessions), identical to GET /api/graphmachine
43
+ food; with --focus/--depth it is that filtered subtree as nested objects instead`,
44
+ see: 'spex spec search (find one node by intent) · spex session ls (just the sessions, as a table)',
315
45
  },
316
-
317
- // ── install & serve (operator) ────────────────────────────────────────────
318
46
  init: {
319
47
  line: 'init [dir] adopt SpexCode on a repo: seed .spec + hooks + materialize [--preset name]',
320
- body: `Usage: spex init [dir=cwd] [--preset default|careful]
48
+ body: `Usage: spex init [dir=cwd] [--preset default]
321
49
 
322
- Scaffolds adoption in one shot: seeds a starter .spec tree (project root + .config plugins), plants
50
+ Scaffolds adoption in one shot: seeds a starter .spec tree (project root + .plugins plugins), plants
323
51
  spexcode.json, installs the git hooks, and materializes the harness artifacts (contract block +
324
- shims). Additive — never overwrites your files. --preset picks the .config plugin tier (cumulative).
52
+ shims). Additive — never overwrites your files. --preset picks the .plugins plugin tier (cumulative).
325
53
  Footprint is fixed: materialized artifacts are never tracked — hidden via the per-clone .git/info/exclude, with
326
54
  a tracked/mixed CLAUDE.md/AGENTS.md covered by the clean/smudge filter (see spex guide footprint).`,
327
- see: 'spex guide (the full setup workflow) · spex uninstall (the inverse) · spex lint (adoption TODO)',
328
- },
329
- uninstall: {
330
- line: 'uninstall [dir] surgical inverse of init — removes generated artifacts, keeps your .spec [--hooks]',
331
- body: `Usage: spex uninstall [dir=cwd] [--hooks]
332
-
333
- Removes every SpexCode-GENERATED artifact (harness shims · contract blocks · trust entries ·
334
- exclude/ignore blocks · global store · plugin bundle) and never your .spec/.config data or your own
335
- prose. Git hooks are preserved unless --hooks.`,
336
- see: 'spex init (re-adopt later — your .spec survives)',
55
+ see: 'spex guide (the full setup workflow) · spex uninstall (the inverse) · spex spec lint (adoption TODO)',
337
56
  },
338
57
  materialize: {
339
58
  line: 'materialize re-materialize the harness artifacts (contract block · shims) for cwd’s project',
340
59
  body: `Usage: spex materialize
341
60
 
342
- Materializes the surface:system config nodes into the managed <!-- spexcode --> block of
61
+ Materializes the surface:system plugin nodes into the managed <!-- spexcode --> block of
343
62
  CLAUDE.md/AGENTS.md plus the .claude/.codex shims, and prints the content hash. The materialize anchors on
344
63
  git-native events only (init · this verb · session-worktree creation · the pre-commit/post-checkout/
345
64
  post-merge hooks) — run it by hand after a toolchain update, or in the setup step of any clone that
@@ -348,118 +67,314 @@ never arrive via git.`,
348
67
  see: 'spex doctor (verify the materialized artifacts actually reach an agent)',
349
68
  },
350
69
  doctor: {
351
- line: 'doctor diagnose whether the workflow actually reaches this agent — per-layer, per-harness',
352
- body: `Usage: spex doctor per-layer coverage report: preconditions · git-hook floor · contract ·
353
- hooks + handler existence · backend — for every harness materialize delivers to
354
- spex doctor contract print the composed surface:system text any agent here reads
355
- spex doctor conflicts detect double-delivery (loose artifacts beside the managed ones)
70
+ line: 'doctor diagnose whether the workflow actually reaches this agent [--contract|--conflicts]',
71
+ body: `Usage: spex doctor per-layer coverage report: preconditions · git-hook floor · contract ·
72
+ hooks + handler existence · backend — for every harness materialize delivers to
73
+ spex doctor --contract print the composed surface:system text any agent here reads
74
+ spex doctor --conflicts detect double-delivery (loose artifacts beside the managed ones)
356
75
 
357
76
  Run it when a worker seems to be missing its contract or hooks — it names the broken layer and the
358
77
  repair, instead of you diffing materialized files by hand.`,
359
78
  see: 'spex materialize (re-materialize the artifacts doctor checks)',
360
79
  },
80
+ uninstall: {
81
+ line: 'uninstall [dir] surgical inverse of init — removes generated artifacts, keeps your .spec [--hooks]',
82
+ body: `Usage: spex uninstall [dir=cwd] [--hooks]
83
+
84
+ Removes every SpexCode-GENERATED artifact (harness shims · contract blocks · trust entries ·
85
+ exclude/ignore blocks · global store · plugin bundle) and never your .spec/.plugins data or your own
86
+ prose. Git hooks are preserved unless --hooks.`,
87
+ see: 'spex init (re-adopt later — your .spec survives)',
88
+ },
361
89
  serve: {
362
- line: 'serve run the API backend (default :8787) [--port N] [--public --password pw]',
363
- body: `Usage: spex serve [--port N=8787]
364
- spex serve --public --password <pw> [--tls-cert F --tls-key F] [--http]
365
-
366
- Runs the backend for the repo at cwd behind a zero-downtime supervisor (hot-reloads on source
367
- change; the public port never gaps). --port pairs with \`spex dashboard --api-port\`, so many
368
- projects coexist on one host. On a successful bind it RECORDS its endpoint in the per-project
369
- runtime tier — that's how a bare \`spex\` run from this project's tree finds this backend (see
370
- spex guide config → BACKEND ROUTING). --public exposes it on a public IP behind a password +
371
- self-signed TLS (own cert via --tls-cert/--tls-key; --http drops TLS).`,
372
- see: 'spex dashboard (the UI on top) · GET /health (liveness probe)',
90
+ line: 'serve [api|ui] api (default) = the backend :8787 · ui = the dashboard :5173 on top of it',
91
+ body: `Usage: spex serve [api] [--port N=8787]
92
+ spex serve [api] --public --password <pw> [--tls-cert F --tls-key F] [--http]
93
+ spex serve ui [--port N=5173] [--api-port N=8787] [--host H=127.0.0.1]
94
+
95
+ \`serve\` (or \`serve api\`) runs the backend for the repo at cwd behind a zero-downtime supervisor
96
+ (hot-reloads on source change; the public port never gaps). On a successful bind it RECORDS its
97
+ endpoint in the per-project runtime tier — that's how a bare \`spex\` run from this project's tree
98
+ finds this backend (spex guide settings → BACKEND ROUTING). --public exposes it on a public IP behind
99
+ a password + self-signed TLS (own cert via --tls-cert/--tls-key; --http drops TLS).
100
+
101
+ \`serve ui\` is a SEPARATE process: it serves the bundled dashboard on its own port and proxies /api +
102
+ the terminal socket to a running backend (--api-port pairs with the backend's --port, so many
103
+ projects coexist on one host). Loopback-only by default; --host 0.0.0.0 opens it to a LAN/tailnet —
104
+ still plain HTTP with no gate, so bind wide only on a network you trust.`,
105
+ see: 'GET /health (backend liveness probe)',
106
+ },
107
+
108
+ // ── the noun drawers ──────────────────────────────────────────────────────
109
+ spec: {
110
+ line: 'spec <verb> the governance graph: search · owner · lint · ack',
111
+ body: `Usage: spex spec search <query…> [--limit N=10] [--json]
112
+ spex spec owner <path> [--actionable]
113
+ spex spec lint
114
+ spex spec ack <node-id>… --reason "<why the contract still holds>"
115
+
116
+ search — which spec node GOVERNS a topic, ranked by user-story relevance (which surfaces user-facing
117
+ behaviour a code-grep misses). Run it BEFORE touching code: the node's spec.md body is the current
118
+ contract. The corpus is English — query in English.
119
+
120
+ owner — the reverse edge: a file's GOVERNORS (code: — drives drift + eval freshness) and REFERENCERS (related:
121
+ — coverage only), with the verdict spelled out (uncovered / related-only / sanely governed /
122
+ over-owned → split the file). --actionable prints NOTHING unless action is needed (hook use).
123
+
124
+ lint — checks the whole spec↔code graph and exits non-zero on errors. Errors: integrity (a
125
+ code:/related: file does not exist; a dead/ambiguous/unverifiable \`path#symbol\` anchor; an anchor
126
+ whose language has no designated extractor or whose extractor can't run here) · anchor-drift (a
127
+ commit since the spec's version touched the ANCHORED unit's lines, unacked — the blocking tier of
128
+ drift) · one-govern (a node governs >1 file) · living (a "## vN" changelog heading) · id-format (an
129
+ id char outside the whitelist — ascii [a-z0-9-] or a non-ascii unicode letter/number, CJK ok — or a
130
+ leaf id reused) · mention (a [[id]] naming no node). Warns: altitude · breadth · coverage · drift
131
+ (UNANCHORED drift — always advisory, never blocks) · anchor (anchoring a type) · related-drift ·
132
+ owners · confusable-id (two leaf ids one edit apart). spec lint's errors BLOCK commits (the pre-commit shim; bypass SPEXCODE_SKIP_LINT=1);
133
+ contrast \`spex eval lint\`, which is pure advisory and never blocks anyone.
134
+
135
+ ack — stamp Spec-OK on HEAD (an empty stamp commit): the drift remedy when only MECHANICS changed
136
+ and the spec's contract still holds. --reason is required and recorded in the ack commit's body
137
+ (quieting an anchor hit is a strong claim — the why must be durable). If the intent DID change,
138
+ edit the spec instead — same commit as the code.`,
139
+ see: 'spex guide spec (the file format + every lint rule) · spex graph (browse the whole tree)',
140
+ },
141
+ session: {
142
+ line: 'session <verb> the worktree state machine: new · ls · watch · wait · review · merge · send · …',
143
+ body: `Manager verbs (dispatch, monitor, land):
144
+ spex session new "<prompt>" [--prompt-file <path>|-] [--node <id>] [--launcher <name>]
145
+ Launch a worker in its own node worktree. Give it ONLY its task — the dev-flow contract
146
+ reaches it through the materialized system prompt. --node (or the prompt's first [[id]],
147
+ same effect) binds the session to that node. --prompt-file <path>|- carries a long prompt
148
+ without shell quoting (exclusive with the inline prompt). Then MONITOR it (wait/watch below).
149
+ spex session ls [SEL…] [--status a,b] [--json] one-shot table of living sessions
150
+ spex session watch [SEL…] [--as NAME] [--idle] [--interval N=5]
151
+ Streams lifecycle transitions until killed — it NEVER EXITS; the human's forever stream. An
152
+ agent must background it or use wait; blocking a turn on watch freezes you.
153
+ spex session wait <SEL> [--timeout S=1200] [--interval S=2] [--idle]
154
+ EDGE-TRIGGERED sleep on one session — ALWAYS run it in the BACKGROUND; its exit is your wake-up.
155
+ Prints the session's current status immediately (stderr), then exits 0 only when it OBSERVES
156
+ the session TRANSITION from a non-actionable status into an actionable one, printing the
157
+ observed path on stdout (e.g. working→review — read the LAST token as the status reached).
158
+ USE IT to sleep until a dispatched worker next needs you — including a dispatched MERGE
159
+ actually landing (review→working while the merge runs, then the edge back is your wake-up).
160
+ It NEVER returns just because the session is actionable ALREADY — for "what is it right NOW"
161
+ use \`session ls\` / \`session review\` instead. --timeout is the guaranteed exit (code 1,
162
+ observed path on stderr). Background one wait per worker.
163
+ spex session review <SEL> [--json] the merge cockpit: ahead · uncommitted · proposal · gates ·
164
+ merge-base diff — decide from this, don't hand-run git
165
+ spex session merge <SEL> gated merge, dispatched to the session's OWN agent; confirm
166
+ HEAD advanced before closing — closing unmerged discards work
167
+
168
+ Control another session (all take SEL):
169
+ spex session send <SEL> "<msg>" deliver a message (fail-loud: a dead dispatch exits non-zero)
170
+ spex session send <SEL> --keys "<keys>"
171
+ LAST RESORT: raw nav-mode keystrokes to a TUI dialog ("Up Up Enter", C-/M-/S- combos). The raw
172
+ key surface is UNSTABLE and can confirm dangerous dialogs — don't reach for it unless a plain
173
+ \`session send\` text provably cannot land.
174
+ spex session rename <SEL> "<name>" set the display name ("" clears)
175
+ spex session show <SEL> [--capture] [--json]
176
+ The session record: status · node · branch · launcher · the full originating prompt.
177
+ --capture prints the LIVE PANE as text instead (empty pane = exit 0; unknown session = exit 2).
178
+ spex session resume <SEL> [--force] relaunch ONLY if confirmed offline (--force for a wedged one)
179
+ spex session stop <SEL> soft stop: kill the agent, KEEP the worktree (resumable)
180
+ spex session close <SEL> retire the session and its worktree
181
+
182
+ Worker verbs (declare YOUR OWN state — a claim the graph and your supervisor act on):
183
+ spex session done --propose merge|nothing|close [--note T] committed and stopping
184
+ spex session park --note <what-you-await> a real background task will wake you
185
+ spex session ask --note <your-question> stopped on the human; resumes on reply
186
+
187
+ Human escape hatch:
188
+ spex session attach <SEL> sit in the worker's REAL tmux (detach: C-b d). INTERACTIVE
189
+ AND BLOCKING — an agent must NEVER run it in a turn: use
190
+ show --capture / send. LOCAL-only (fails loud on a remote backend).
191
+
192
+ ${SEL_NOTE}
193
+ Manager verbs that WRITE (send/rename/resume/stop/close/merge) are PROJECT-BOUND: a backend serving
194
+ another project's repo refuses loudly — name the target with --api <url> to drive it on purpose.
195
+ ${MENTION_NOTE}`,
196
+ see: 'spex eval ls --session <SEL> (the session’s measured loss) · spex help eval',
197
+ },
198
+ eval: {
199
+ line: 'eval <verb> the measurement system: add · ls · scenario ls · lint · ok · retract · clean',
200
+ body: `Usage: spex eval add [<node>|.] [--scenario <name>] (--pass|--fail) [--note <text>]
201
+ [--image <png> …repeatable] [--result <path|->] [--video <webm|mp4>] [--timeline <json>]
202
+ spex eval ls [<node>|.] [--json] a node's eval timeline, newest first
203
+ spex eval ls --session <SEL> [--json] a session's aggregate: its changed nodes' scores
204
+ spex eval ls --session <SEL> --export [--open | --out <path>]
205
+ spex eval scenario ls [<node>|.] [--unmeasured] [--json] declared scenarios; bare = every node
206
+ spex eval lint [--changed] measurement-layer findings (advisory, always exit 0)
207
+ spex eval ok <node> [--scenario <name>] the HUMAN sign-off on the scenario's latest reading
208
+ spex eval retract [<node>|.] [--scenario <name>] [--last | --ts <iso>] [--note <why>]
209
+ spex eval clean [--keep-latest | --all] GC the content-addressed evidence cache
210
+
211
+ add — file an eval of a scenario against its expected: the loss signal the optimizer reads.
212
+ Measure through the REAL product surface, never by reasoning about the code. Evidence kind follows
213
+ the behaviour: MOVING/timed behaviour records a --video; a STATIC end state screenshots --image;
214
+ backend/CLI files a --result transcript. A fix's evidence is a fail→pass pair on the SAME scenario.
215
+
216
+ ls — node-scoped bare (its per-scenario eval history); session-scoped with an EXPLICIT --session
217
+ (never type-sniffed): every node the session's diff touches, blind spots first, its OWN measurements
218
+ ✦-marked ahead of the inherited baseline. --export writes that evaluation as ONE self-contained
219
+ HTML artifact (diff · evidence inlined · gates) for CI/sharing.
220
+
221
+ scenario ls — the DECLARED contracts (name · tags · latest verdict), no evals: bare lists every
222
+ measurable node's scenarios; --unmeasured keeps only the never-measured — the blind-spot worklist.
223
+
224
+ lint — the measurement layer's findings: malformed eval.md (eval-schema) · unmeasured (eval-missing) ·
225
+ stale (eval-drift) · orphaned remark tracks (eval-dangling) · governed source with no eval.md
226
+ (eval-coverage — the same name and shape as spec lint's coverage, one rule per layer) · over-owned
227
+ files (eval-owners). --changed scopes to the nodes THIS branch touched. spec lint's errors block
228
+ commits; eval lint is PURE ADVISORY, always exit 0 — a measurement gap never blocks anyone.
229
+
230
+ ok — the human's reviewed-and-agreed mark on the scenario's LATEST reading: an appended, monotonic
231
+ sign-off bound to that one immutable reading (a newer reading or staleness releases it on its own —
232
+ no un-ok exists). The evals feed default-hides a fresh, ok'd scenario; a governed session is refused
233
+ (an agent's judgment on a reading is a remark, never a self-blessing).
234
+
235
+ retract — the sanctioned undo for a botched filing: APPENDS a retraction event (traceable, never
236
+ deletes a line); the previous eval becomes latest again, or the scenario honestly returns to
237
+ unmeasured.
238
+
239
+ ${DOT_NOTE}`,
240
+ see: 'spex guide eval (the eval.md scenario format + evidence rules) · spex evidence (bare byte transport)',
241
+ },
242
+ issue: {
243
+ line: 'issue <verb> concern threads, local + forge merged: ls · show · open · reply · close · promote · links',
244
+ body: `Usage: spex issue ls [--node <id>] [--store local|<host>] [--all] [--json]
245
+ spex issue show <id> [--json]
246
+ spex issue open "<concern>" [--store local|<host>] [--node <id>…] [--evidence <hash>…] [--body -|<text>]
247
+ spex issue reply <id> --body -|<text> [--evidence <hash>…]
248
+ spex issue close <id>
249
+ spex issue promote <id>
250
+ spex issue links [--pending] [--store <host>] [--node <id>] [--json]
251
+
252
+ ls is the drain view a supervisor reads: ONE store-tagged list, local + forge interleaved by
253
+ creation time. \`show <id>\` is the single-thread detail — the whole thread with its replies (a local
254
+ id, or a forge id like github#12). \`open\` welcomes taste, annotations, and off-mainline smells —
255
+ not only bugs; --store <host> opens straight on the forge. \`reply\` and \`close\` route by the
256
+ issue's store — one verb, local or forge. \`promote\` moves an OPEN local issue to the forge as one
257
+ recorded action. \`links\` is the read-only forge trace: which open forge issues/PRs serve which
258
+ spec node (--pending narrows to threads still awaiting an eval). The issues workflow's
259
+ on/off switch is the \`issues.enabled\` key in spexcode.json (no CLI toggle verb — edit the JSON;
260
+ \`spex doctor\` reports its state).
261
+ ${MENTION_NOTE}`,
262
+ see: 'spex remark (pin a resolvable concern to an issue or scenario) · spex evidence put (stash evidence bytes)',
263
+ },
264
+ remark: {
265
+ line: 'remark <verb> resolvable pins on a host: add · resolve · retract',
266
+ body: `Usage: spex remark add <issue-id | <node> --scenario <name>> --body -|<text> [--code-sha <sha>] [--evidence <hash>…]
267
+ spex remark resolve <ref> (the <thread-id>#<rid> that \`spex remark add\` printed)
268
+ spex remark retract <ref>
269
+
270
+ The resolvable interaction primitive: \`add\` pins a concern to a HOST — a local issue, or a scenario
271
+ (\`--scenario\` present ⇒ the positional is a node; otherwise it is an issue id — the flag decides,
272
+ never type-sniffing). A SECOND agent \`resolve\`s it (never the author); the AUTHOR \`retract\`s their
273
+ own. The whole loop is CLI-first; the dashboard adds no capability.`,
274
+ see: 'spex issue (the thread hosts) · spex eval ls (scenario hosts)',
275
+ },
276
+ evidence: {
277
+ line: 'evidence put|get content-addressed bytes: put stashes & prints the hash, get reads back',
278
+ body: `Usage: spex evidence put <file|->
279
+ spex evidence get <hash> [-o <file>]
280
+
281
+ put writes bytes into the shared content-addressed evidence cache and prints the hash — transport
282
+ only, no eval filed. Use the hash with --evidence on issues/remarks; re-putting the same content
283
+ restores pruned or cloned-away evidence.
284
+
285
+ get is the symmetric read: hash in, bytes out. Local cache first (no backend needed — the evidence
286
+ is usually on this disk), then the backend on a local miss; both missing fails loud naming each
287
+ path. Bytes go to stdout by default (pipe-friendly); -o writes a file.`,
288
+ see: 'spex eval add (file an eval WITH evidence) · spex issue open --evidence <hash>',
373
289
  },
374
- dashboard: {
375
- line: 'dashboard serve the dashboard UI (default :5173), proxying /api to a running serve',
376
- body: `Usage: spex dashboard [--port N=5173] [--api-port N=8787] [--host H=127.0.0.1]
377
-
378
- Serves the bundled dashboard on its own port and proxies /api + the terminal socket to a running
379
- \`spex serve\`. The installed replacement for the dev-only \`npm run web\`. Loopback-only by default;
380
- --host 0.0.0.0 (or a specific interface) opens it to a LAN/tailnet still plain HTTP with no gate,
381
- so bind wide only on a network you trust (for the internet, use \`spex serve --public\`).`,
382
- see: 'spex serve (must be running first)',
290
+
291
+ // ── help & guide ──────────────────────────────────────────────────────────
292
+ guide: {
293
+ line: 'guide [topic] the manuals: setup workflow · spec/eval file formats · spexcode.json · footprint',
294
+ body: `Usage: spex guide the human setup workflow (install once, adopt a repo, serve)
295
+ spex guide spec the spec.md file format + every lint rule
296
+ spex guide eval the eval.md scenario format + how loss is measured and filed
297
+ spex guide settings every spexcode.json / spexcode.local.json field, and which file it belongs in
298
+ spex guide footprint the footprint model: never-tracked artifacts, exclude + content filter, anchors
299
+
300
+ guide is the SKILL layer — workflows and formats. Command usage lives here in help
301
+ (\`spex help <cmd>\`); guide carries what the commands assume you know.`,
383
302
  },
384
303
 
385
304
  // ── plumbing ──────────────────────────────────────────────────────────────
386
305
  internal: {
387
306
  line: '', // deliberately not on the map
388
- body: `Usage: spex internal <trunk | commit-surgery | refresh-footprint | codex-launch | codex-turn>
307
+ body: `Usage: spex internal <sub>
389
308
 
390
309
  Machine plumbing — called by generated hooks and launch scripts, never typed by a human or agent:
391
310
  trunk print the resolved source-of-truth branch (the pre-commit main-guard captures it)
392
311
  commit-surgery pre-commit footprint anchor: unconditional materialize + staged-index repair
393
312
  refresh-footprint quiet materialize — the post-checkout/post-merge freshness anchor
394
- codex-launch <sock> <cwd> [prompt…] backend-owned codex thread/start + first turn (launch script)
395
- codex-turn <sock> <threadId> <text…> fire a follow-up turn on an owned thread (tests/scripts)
313
+ check-staged pre-commit eval backstop: reject staged stray evidence files / malformed eval.md
314
+ session-state <st> --session <id> a lifecycle hook authors the session's state
315
+ session-fail --session <id> the StopFailure hook marks the session errored
316
+ session-idle --session <id> the idle-prompt hook marks an active session idle
317
+ commit-gate the Stop gate's deterministic commit check (exit 0 = ready to declare done)
318
+ nudge <node> the post-merge hook prints the issue nudge for a merged node
319
+ codex-launch <sock> <cwd> [prompt…] backend-owned codex thread/start + first turn (launch script)
320
+ codex-turn <sock> <threadId> <text…> fire a follow-up turn on an owned thread (tests/scripts)
396
321
 
397
322
  If you reached for one of these by hand, the porcelain you want is probably elsewhere: the trunk
398
- name also lives at GET /api/layout; sessions are driven with spex new / session send.`,
323
+ name also lives at GET /api/settings (.layout); sessions are driven with spex session new / session send;
324
+ your own state is declared with spex session done|park|ask.`,
399
325
  see: 'spex help (the porcelain map)',
400
326
  },
401
327
  help: {
402
328
  line: '',
403
- body: `Usage: spex help the command map, grouped by the loop each verb serves
404
- spex help <command> one command's usage (same as spex <command> --help)
329
+ body: `Usage: spex help the command map
330
+ spex help <command> one command/drawer's usage (same as spex <command> --help)
405
331
  spex guide [topic] the skill layer: workflows, file formats, best practice`,
406
332
  },
407
333
  }
408
334
 
409
- // `spex <cmd> --help` must meet the user wherever they typed it: sub-namespace tokens map to their
410
- // canonical entry, so \`spex session send --help\` and \`spex help session\` print the same text.
335
+ // `spex <cmd> --help` must meet the user wherever they typed it: cli.ts intercepts the probe pre-verb and
336
+ // asks for the FIRST token's entry, so `spex session send --help` answers with the session drawer's entry.
411
337
  export function commandHelp(name: string): string | null {
412
- const key = ALIAS[name] ?? (name === 'resolve' || name === 'retract' ? 'remark' : name)
413
- const e = ENTRIES[key]
338
+ const e = ENTRIES[name]
414
339
  if (!e) return null
415
340
  const oneLiner = e.line.replace(/^\S+(\s+\S+)*?\s{2,}/, '') // the map line minus its "cmd args" column
416
- const header = oneLiner ? `spex ${key} — ${oneLiner}\n\n` : '' // unlisted entries (internal, help) lead with their own Usage
341
+ const header = oneLiner ? `spex ${name} — ${oneLiner}\n\n` : '' // unlisted entries (internal, help) lead with their own Usage
417
342
  return `${header}${e.body}${e.see ? `\n\nsee also: ${e.see}` : ''}\n\nmap: spex help · skills: spex guide`
418
343
  }
419
344
 
420
345
  export function overviewHelp(): string {
421
346
  return `spex — SpexCode CLI (spec↔code graph + worktree session state machine)
422
347
 
423
- Usage: spex <command> [args] one command's usage: spex help <command> (or spex <command> --help)
424
-
425
- Find & read the graph
426
- ${ENTRIES.search.line}
427
- ${ENTRIES.owner.line}
428
- ${ENTRIES.tree.line}
429
- ${ENTRIES.board.line}
430
- ${ENTRIES.guide.line}
348
+ Usage: spex <noun> <verb> [object] [flags] the verb is always the token after its noun;
349
+ a bare noun prints that drawer's help
350
+ spex help <command> one command's usage (or spex <command> --help — always
351
+ safe: a help probe never runs the verb)
431
352
 
432
- Author & verify (the worker loop)
433
- ${ENTRIES.lint.line}
434
- ${ENTRIES.ack.line}
435
- ${ENTRIES.yatsu.line}
436
- ${ENTRIES.blob.line}
437
- ${ENTRIES.issues.line}
438
- ${ENTRIES.remark.line}
439
- ${ENTRIES.forge.line}
440
-
441
- Dispatch & manage sessions (the manager loop)
442
- ${ENTRIES.new.line}
443
- ${ENTRIES.ls.line}
444
- ${ENTRIES.watch.line}
445
- ${ENTRIES.wait.line}
446
- ${ENTRIES.review.line}
447
- ${ENTRIES.eval.line}
448
- ${ENTRIES.merge.line}
449
- ${ENTRIES.session.line}
450
-
451
- Install & serve (the operator loop)
353
+ Project verbs (implicit object = this project)
354
+ ${ENTRIES.graph.line}
452
355
  ${ENTRIES.init.line}
453
- ${ENTRIES.uninstall.line}
454
356
  ${ENTRIES.materialize.line}
455
357
  ${ENTRIES.doctor.line}
358
+ ${ENTRIES.uninstall.line}
456
359
  ${ENTRIES.serve.line}
457
- ${ENTRIES.dashboard.line}
458
360
 
459
- ${SEL_NOTE}
361
+ Noun drawers
362
+ ${ENTRIES.spec.line}
363
+ ${ENTRIES.session.line}
364
+ ${ENTRIES.eval.line}
365
+ ${ENTRIES.issue.line}
366
+ ${ENTRIES.remark.line}
367
+ ${ENTRIES.evidence.line}
368
+
369
+ Manuals
370
+ ${ENTRIES.guide.line}
460
371
 
461
- ${ROUTING_NOTE}
372
+ Conventions (stated once, hold everywhere)
373
+ ${SEL_NOTE.split('\n').join('\n ')}
374
+ ${DOT_NOTE.split('\n').join('\n ')}
375
+ ${ROUTING_NOTE.split('\n').join('\n ')}
376
+ ${MENTION_NOTE.split('\n').join('\n ')}
462
377
 
463
- Concepts & best practice live in the guide: spex guide (setup) · guide spec · guide yatsu · guide config.
378
+ Concepts & best practice live in the guide: spex guide (setup) · guide spec · guide eval · guide settings · guide footprint.
464
379
  Machine plumbing (hook/launch-script callees) lives under \`spex internal\` — not part of your vocabulary.`
465
380
  }