@spexcode/spec-cli 0.6.5
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.
- package/README.md +95 -0
- package/bin/spex.mjs +76 -0
- package/dist/attach.d.ts +2 -0
- package/dist/attach.js +48 -0
- package/dist/claude-headless.d.ts +33 -0
- package/dist/claude-headless.js +307 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1859 -0
- package/dist/client.d.ts +111 -0
- package/dist/client.js +476 -0
- package/dist/cockpit.d.ts +7 -0
- package/dist/cockpit.js +26 -0
- package/dist/codex-headless.d.ts +1 -0
- package/dist/codex-headless.js +7 -0
- package/dist/codex-runtime-generations.d.ts +53 -0
- package/dist/codex-runtime-generations.js +686 -0
- package/dist/commit-surgery.d.ts +1 -0
- package/dist/commit-surgery.js +90 -0
- package/dist/contract-filter.d.ts +14 -0
- package/dist/contract-filter.js +219 -0
- package/dist/dashboard-assets.d.ts +6 -0
- package/dist/dashboard-assets.js +50 -0
- package/dist/delivery-queue.d.ts +23 -0
- package/dist/delivery-queue.js +179 -0
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.js +547 -0
- package/dist/eval-host.d.ts +1 -0
- package/dist/eval-host.js +19 -0
- package/dist/execution-trace.d.ts +26 -0
- package/dist/execution-trace.js +475 -0
- package/dist/file-write.d.ts +2 -0
- package/dist/file-write.js +26 -0
- package/dist/flat.d.ts +89 -0
- package/dist/flat.js +1076 -0
- package/dist/gateway-auth.d.ts +56 -0
- package/dist/gateway-auth.js +168 -0
- package/dist/gateway-hub.d.ts +28 -0
- package/dist/gateway-hub.js +361 -0
- package/dist/gateway.d.ts +37 -0
- package/dist/gateway.js +503 -0
- package/dist/graphCache.d.ts +27 -0
- package/dist/graphCache.js +716 -0
- package/dist/graphSnapshot.d.ts +14 -0
- package/dist/graphSnapshot.js +35 -0
- package/dist/graphStream.d.ts +85 -0
- package/dist/graphStream.js +1195 -0
- package/dist/guidance-catalog.d.ts +64 -0
- package/dist/guidance-catalog.js +166 -0
- package/dist/guide.d.ts +8 -0
- package/dist/guide.js +684 -0
- package/dist/harness-select.d.ts +18 -0
- package/dist/harness-select.js +62 -0
- package/dist/harness.d.ts +325 -0
- package/dist/harness.js +3026 -0
- package/dist/headless-controller.d.ts +9 -0
- package/dist/headless-controller.js +47 -0
- package/dist/help.d.ts +10 -0
- package/dist/help.js +574 -0
- package/dist/hook-prompts.d.ts +18 -0
- package/dist/hook-prompts.js +122 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +27 -0
- package/dist/host-resources.d.ts +97 -0
- package/dist/host-resources.js +795 -0
- package/dist/host.d.ts +88 -0
- package/dist/host.js +663 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +899 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +284 -0
- package/dist/issues-cli.d.ts +6 -0
- package/dist/issues-cli.js +334 -0
- package/dist/issues.d.ts +69 -0
- package/dist/issues.js +189 -0
- package/dist/lint.d.ts +34 -0
- package/dist/lint.js +476 -0
- package/dist/listen.d.ts +7 -0
- package/dist/listen.js +24 -0
- package/dist/localIssues.d.ts +65 -0
- package/dist/localIssues.js +582 -0
- package/dist/login-page.d.ts +5 -0
- package/dist/login-page.js +86 -0
- package/dist/loop-in.d.ts +23 -0
- package/dist/loop-in.js +68 -0
- package/dist/machine-peer.d.ts +69 -0
- package/dist/machine-peer.js +603 -0
- package/dist/materialize.d.ts +18 -0
- package/dist/materialize.js +468 -0
- package/dist/mentions.d.ts +50 -0
- package/dist/mentions.js +117 -0
- package/dist/opencode-headless.d.ts +4 -0
- package/dist/opencode-headless.js +208 -0
- package/dist/opencode.d.ts +3 -0
- package/dist/opencode.js +142 -0
- package/dist/pi-harness.d.ts +3 -0
- package/dist/pi-harness.js +129 -0
- package/dist/pi-headless.d.ts +25 -0
- package/dist/pi-headless.js +196 -0
- package/dist/plugin-harness.d.ts +21 -0
- package/dist/plugin-harness.js +146 -0
- package/dist/pty-bridge.d.ts +10 -0
- package/dist/pty-bridge.js +404 -0
- package/dist/pty-helper.d.mts +1 -0
- package/dist/pty-helper.mjs +101 -0
- package/dist/pty-native-helper.d.mts +2 -0
- package/dist/pty-native-helper.mjs +23 -0
- package/dist/public-graph.d.ts +46 -0
- package/dist/public-graph.js +56 -0
- package/dist/ranker.d.ts +15 -0
- package/dist/ranker.js +188 -0
- package/dist/reaper.d.ts +6 -0
- package/dist/reaper.js +81 -0
- package/dist/reviews.d.ts +189 -0
- package/dist/reviews.js +350 -0
- package/dist/runtime-guard.d.ts +6 -0
- package/dist/runtime-guard.js +37 -0
- package/dist/runtime-ownership.d.ts +21 -0
- package/dist/runtime-ownership.js +84 -0
- package/dist/search.bench.d.mts +1 -0
- package/dist/search.bench.mjs +75 -0
- package/dist/search.d.ts +20 -0
- package/dist/search.js +61 -0
- package/dist/session-cursors.d.ts +14 -0
- package/dist/session-cursors.js +82 -0
- package/dist/session-declarations.d.ts +13 -0
- package/dist/session-declarations.js +126 -0
- package/dist/session-execution.d.ts +10 -0
- package/dist/session-execution.js +70 -0
- package/dist/session-files.d.ts +28 -0
- package/dist/session-files.js +130 -0
- package/dist/session-follow.d.ts +31 -0
- package/dist/session-follow.js +179 -0
- package/dist/session-reparent.d.ts +10 -0
- package/dist/session-reparent.js +33 -0
- package/dist/session-timeline.d.ts +51 -0
- package/dist/session-timeline.js +233 -0
- package/dist/session-web.d.ts +24 -0
- package/dist/session-web.js +141 -0
- package/dist/sessions.d.ts +412 -0
- package/dist/sessions.js +4239 -0
- package/dist/sh.d.ts +1 -0
- package/dist/sh.js +3 -0
- package/dist/shim-runtime.d.ts +1 -0
- package/dist/shim-runtime.js +161 -0
- package/dist/slash-commands.d.ts +9 -0
- package/dist/slash-commands.js +307 -0
- package/dist/source-files.d.ts +9 -0
- package/dist/source-files.js +113 -0
- package/dist/supervise.d.ts +1 -0
- package/dist/supervise.js +308 -0
- package/dist/tree.d.ts +25 -0
- package/dist/tree.js +98 -0
- package/dist/tsx-bin.d.ts +3 -0
- package/dist/tsx-bin.js +41 -0
- package/dist/uninstall.d.ts +3 -0
- package/dist/uninstall.js +177 -0
- package/dist/uploads.d.ts +36 -0
- package/dist/uploads.js +243 -0
- package/dist/worktree-sources.d.ts +1 -0
- package/dist/worktree-sources.js +47 -0
- package/hooks/compat/mark-active-0.5.2-eef1.fixture +53 -0
- package/hooks/compat/mark-active-sed-v0.fixture +46 -0
- package/hooks/dispatch.sh +105 -0
- package/hooks/harness.sh +272 -0
- package/package.json +47 -0
- package/templates/hooks/commit-msg +14 -0
- package/templates/hooks/post-checkout +25 -0
- package/templates/hooks/post-merge +34 -0
- package/templates/hooks/pre-commit +120 -0
- package/templates/hooks/prepare-commit-msg +35 -0
- package/templates/hooks/reference-transaction +105 -0
- package/templates/spec/project/.plugins/commands/extract/spec.md +66 -0
- package/templates/spec/project/.plugins/commands/regroup/spec.md +25 -0
- package/templates/spec/project/.plugins/commands/rename/spec.md +11 -0
- package/templates/spec/project/.plugins/commands/spec.md +30 -0
- package/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
- package/templates/spec/project/.plugins/commands/tidy/spec.md +43 -0
- package/templates/spec/project/.plugins/core/comment-altitude/spec.md +9 -0
- package/templates/spec/project/.plugins/core/idle/idle.sh +15 -0
- package/templates/spec/project/.plugins/core/idle/spec.md +13 -0
- package/templates/spec/project/.plugins/core/mark-active/mark-active.sh +59 -0
- package/templates/spec/project/.plugins/core/mark-active/spec.md +22 -0
- package/templates/spec/project/.plugins/core/session-fail/fail.sh +12 -0
- package/templates/spec/project/.plugins/core/session-fail/spec.md +15 -0
- package/templates/spec/project/.plugins/core/spec-first/spec-first.sh +43 -0
- package/templates/spec/project/.plugins/core/spec-first/spec.md +17 -0
- package/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +66 -0
- package/templates/spec/project/.plugins/core/spec-of-file/spec.md +15 -0
- package/templates/spec/project/.plugins/core/spec.md +22 -0
- package/templates/spec/project/.plugins/core/stop-gate/spec.md +23 -0
- package/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +149 -0
- package/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +15 -0
- package/templates/spec/project/.plugins/prompts/forge-link/spec.md +10 -0
- package/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +13 -0
- package/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +12 -0
- package/templates/spec/project/.plugins/prompts/spec.md +24 -0
- package/templates/spec/project/.plugins/skills/distill/digest.mjs +136 -0
- package/templates/spec/project/.plugins/skills/distill/spec.md +67 -0
- package/templates/spec/project/.plugins/skills/spec.md +19 -0
- package/templates/spec/project/.plugins/spec.md +23 -0
- package/templates/spec/project/spec.md +20 -0
package/dist/help.js
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
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: 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
|
+
// 3. `spex guide [topic]` → the SKILL layer: workflows, file formats, best practice (guide.ts).
|
|
5
|
+
// help answers "what do I type"; guide answers "how do I work". Machine plumbing (hook/launch-script
|
|
6
|
+
// callees) lives under `spex internal` and is deliberately absent from the map — an agent scanning
|
|
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.
|
|
12
|
+
const SEL_NOTE = `SEL = session id (or unique id-prefix) | node id | branch — every session read/control verb
|
|
13
|
+
accepts any of the four; inside a session worktree, . means that worktree's session. On the list verbs (ls/watch),
|
|
14
|
+
none (or @all) means every session.`;
|
|
15
|
+
const ROUTING_NOTE = `Backend routing: every backend-touching verb accepts --api <url> (--port <n> = localhost sugar) to name
|
|
16
|
+
its backend explicitly — the flag always wins. Bare, it resolves: worker env / the cwd project's live
|
|
17
|
+
recorded backend / fallback / :8787 (spex guide settings → BACKEND ROUTING). A password-gated explicit
|
|
18
|
+
gateway accepts --password <pw> (or SPEXCODE_PASSWORD); its self-signed certificate needs explicit --insecure.`;
|
|
19
|
+
const DOT_NOTE = `\`.\` as a node argument means the node THIS worktree works on (the session's bound node, else the
|
|
20
|
+
node/<id> branch). One-shot payload reads (graph · spec search · session ls/show/review · eval ls ·
|
|
21
|
+
scenario ls · issue ls/show/links) take --json.`;
|
|
22
|
+
const MENTION_NOTE = `Mentions: @session · [[node]] work in ANY prompt, issue, or remark body — text passed as a CLI arg included.
|
|
23
|
+
[[node]] names a topic; @session names a retained session for the receiving agent to inspect, contact with
|
|
24
|
+
\`spex session send\`, or inherit with \`/distill\`. Mentions are references only: they never send or spawn.`;
|
|
25
|
+
// One definition feeds both the complete session drawer and exact `session <verb> --help` projections.
|
|
26
|
+
// Keeping the behavioral prose here prevents a compact probe from becoming a second, drifting manual.
|
|
27
|
+
function sessionHelpDefinitions() {
|
|
28
|
+
return {
|
|
29
|
+
new: [['spex session new "<prompt>" [--prompt-file <path>|-] [--launcher <name>] [--name <name>] [--base <commit-ish>]',
|
|
30
|
+
'spex session new --ssh <address> <FULL-SESSION-ID> "<prompt>" [--prompt-file <path>|-] [--launcher <name>] [--name <name>] [--base <commit-ish>]'],
|
|
31
|
+
`Launch a worker in its own node worktree. The materialized system contract reaches it
|
|
32
|
+
automatically; the prompt supplies the task context. Its first [[id]] mention binds the
|
|
33
|
+
session to that node. --prompt-file <path>|- carries a long prompt without shell quoting
|
|
34
|
+
(exclusive with the inline prompt). --name sets the session's initial display name without changing the prompt.
|
|
35
|
+
--base <commit-ish> pins the fork point instead of the source-of-truth branch's current head, so a run can be
|
|
36
|
+
reproduced against a frozen commit; a base that names no commit is refused before anything is created.
|
|
37
|
+
The successful receipt names what to read, monitor, and reply on. --ssh uses an existing gateway-to-gateway
|
|
38
|
+
communication tunnel: its full id anchors the remote project, creation stays parentless and remote, and its
|
|
39
|
+
prompt carries a runnable reply path over that same tunnel.`],
|
|
40
|
+
ls: [['spex session ls [SEL…] [--children[=<PARENT-SEL>]] [--status a,b] [--all] [--json]', 'spex session ls --ssh <address> <FULL-SESSION-ID> [--children=<PARENT-SEL>] [--status a,b] [--json]'],
|
|
41
|
+
'One-shot table of this project\'s live sessions, with each direct parent beside the row. --children scopes it to the caller\'s direct children; --children=<PARENT-SEL> names another parent without changing positional selector grammar. The heading summarizes the displayed scope by status. Shelved sessions ([[archive]]) are hidden; --all includes them, and naming one explicitly always shows it. An explicit id missing from the session list is diagnosed from terminal-close history: closed is a successful answer, while no live, archived, or closed history is a named miss. --ssh uses an existing gateway-to-gateway communication tunnel; its full id anchors one remote project rather than filtering the table, and archive projection stays unavailable on that peer route.', ['selector']],
|
|
42
|
+
resources: ['spex session resources [--json]', 'Read-only host/process ownership, budgets, shared refs, and findings.'],
|
|
43
|
+
files: [['spex session files add <path>', 'spex session files ls', 'spex session files retract <path>'],
|
|
44
|
+
'Publish, list, or withdraw YOUR session’s live file paths. Posting stores an absolute path beside the session record without copying bytes; the dashboard downloads it only when the human clicks.'],
|
|
45
|
+
web: [['spex session web add <url>', 'spex session web ls', 'spex session web retract <url>'],
|
|
46
|
+
'Publish, list, or withdraw YOUR session’s live loopback web-service URLs. Posting records no bytes and does not contact the service; the dashboard proxies the current HTTP/WebSocket service only when the human opens its preview.'],
|
|
47
|
+
watch: [['spex session watch <SEL…>', 'spex session watch list', 'spex session watch cancel <SEL…>', 'spex session watch stream [SEL…] [--as NAME] [--idle] [--interval N=1]'],
|
|
48
|
+
'With a governed caller, watch registers durable send-backed state delivery and exits. list/cancel manage those relations. Without a governed caller it names the background `session wait` fallback. stream is the human-only continuous log view and blocks until killed.', ['selector']],
|
|
49
|
+
wait: ['spex session wait [SEL…] [--timeout S=1200] [--interval S=1] [--idle]',
|
|
50
|
+
`EDGE-TRIGGERED wait: follows the selected sessions' logs AND your own log, and exits 0 on
|
|
51
|
+
the FIRST thing worth waking for — a followed session TRANSITIONING from a non-actionable
|
|
52
|
+
status into an actionable one (stdout = the observed path, e.g. working→review; read the LAST
|
|
53
|
+
token as the status reached), or a message arriving for you (stdout = message). The arrival
|
|
54
|
+
status is printed immediately on stderr but never returns it: it NEVER returns just because a
|
|
55
|
+
session is actionable ALREADY — for "what is it right NOW" use \`session ls\` / \`session review\`.
|
|
56
|
+
It reads files only, so it needs no \`spex serve\` and costs the sessions it follows nothing.
|
|
57
|
+
--timeout is the guaranteed exit (code 1, observed path on stderr); code 2 = a followed
|
|
58
|
+
session's store is gone.`, ['selector']],
|
|
59
|
+
review: ['spex session review <SEL> [--json]', 'Reports ahead · uncommitted · proposal · gates · merge-base diff.', ['selector']],
|
|
60
|
+
merge: ['spex session merge <SEL>', 'Dispatches a gated merge to the session\'s own agent; it does not close the session.', ['selector', 'project-bound']],
|
|
61
|
+
reparent: ['spex session reparent <child-SEL...> --to <parent-SEL>',
|
|
62
|
+
'Move one or more governed children to a replacement parent, replacing only the former parent\'s managed watch relation. It never restarts a child and works when the former parent is offline.', ['selector', 'project-bound']],
|
|
63
|
+
send: [['spex session send <SEL> "<msg>"', 'spex session send <SEL> [--api <url> | --port <n>] -- <option-shaped-msg>', 'spex session send --ssh <address> <FULL-SESSION-ID> "<msg>"', 'spex session send <SEL> --keys "<keys>"'],
|
|
64
|
+
`Plain send delivers a message once its timeline append succeeds; a dead adapter only delays its context. Routing flags may precede or follow ordinary text; use -- before a message that begins with --. --ssh uses an existing gateway-to-gateway communication tunnel and requires a full session id; no tunnel fails loud so an agent may run \`spex peer connect <address>\` then retry. --keys is the LAST RESORT:
|
|
65
|
+
raw nav-mode keystrokes to a TUI dialog ("Up Up Enter", C-/M-/S- combos). The raw key surface
|
|
66
|
+
is UNSTABLE and can confirm dangerous dialogs — try a plain send first; use keys only when text
|
|
67
|
+
provably cannot land.`, ['selector', 'project-bound']],
|
|
68
|
+
interrupt: ['spex session interrupt <SEL>', 'Hard-interrupt the current turn through native harness control.', ['selector', 'project-bound']],
|
|
69
|
+
rename: ['spex session rename <SEL> "<name>"', 'Set the display name; an empty name clears it.', ['selector', 'project-bound']],
|
|
70
|
+
show: [['spex session show <SEL> [--capture] [--json]', 'spex session show --ssh <address> <FULL-SESSION-ID> [--json]'], `The session record: status · node · branch · launcher · the full originating prompt.
|
|
71
|
+
--capture prints the LIVE PANE as text instead (empty pane = exit 0; unknown session = exit 2). --ssh uses an
|
|
72
|
+
existing gateway-to-gateway communication tunnel and requires a full session id; live-pane capture stays local.`, ['selector']],
|
|
73
|
+
resume: ['spex session resume <SEL> [--force]', 'Relaunch ONLY if confirmed offline; --force is for a wedged session.', ['selector', 'project-bound']],
|
|
74
|
+
stop: ['spex session stop <SEL>', 'Soft stop: kill the exact agent and KEEP the worktree resumable.', ['selector', 'project-bound']],
|
|
75
|
+
archive: ['spex session archive <SEL>', 'Cold-archive it: exact leaf/runtime stopped, worktree and conversation kept.', ['selector']],
|
|
76
|
+
unarchive: ['spex session unarchive <SEL>', 'Deprecated compatibility spelling: same behavior as resume, relaunching the same conversation.', ['selector']],
|
|
77
|
+
close: [['spex session close <SEL>', 'spex session close --ssh <address> <FULL-SESSION-ID>'], `Retire ANOTHER session — one you dispatched — deleting its worktree, branch and record.
|
|
78
|
+
<SEL> names that session; it is never \`.\` and never your own id. Closing yourself deletes the worktree
|
|
79
|
+
you are running in, mid-turn. Your own ending is a declaration: \`done --propose close\`. --ssh uses an existing
|
|
80
|
+
gateway-to-gateway communication tunnel and requires a full session id.`, ['selector', 'project-bound']],
|
|
81
|
+
quarantine: ['spex session quarantine <ID> --adapter <harness> [--thread <native-id>] --tmux <id> --worktree <absent-path> --branch <absent-branch> [--restore]',
|
|
82
|
+
'Move only an unreadable record after the backend proves every named residue absent. --thread is an adapter-native conversation id, never the SpexCode session id; omit it for Claude. Quarantine and --restore both require the original exact id because corrupt rows are outside selectors.', ['project-bound']],
|
|
83
|
+
done: ['spex session done --propose merge|nothing|close [--note T]',
|
|
84
|
+
'`merge` declares review: committed work ready for human review, and it is the ONLY declaration that offers a clickable merge. `nothing` is an intended trap: it writes no state and sends the agent to the true merge, close, ask, or park destination. `close` declares close-pending only for settled work: discardable worktree and no outstanding human decision, follow-up, or inspection. The human closes it. This declaration is how a session ends itself; never run `session close` on your own id.'],
|
|
85
|
+
park: ['spex session park --note <what-you-await>',
|
|
86
|
+
'Declare parked only when a managed watch delivery or real background task will wake your own session. It self-resumes; waiting for a human is asking, not parked.'],
|
|
87
|
+
ask: ['spex session ask --note <what-you-await>',
|
|
88
|
+
'Declare asking when a human reply, direction, or decision is needed, including a reported finding/recommendation, handoff, or posted-artifact inspection. It resumes only when the human replies; a background wake-up is parked instead.'],
|
|
89
|
+
attach: ['spex session attach <SEL>', `Attaches the current terminal to the worker's tmux (detach: C-b d) and blocks until detached.
|
|
90
|
+
LOCAL-only (fails loud on a remote backend); show --capture and send are non-interactive.`, ['selector']],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const SESSION_HELP_GROUPS = [
|
|
94
|
+
{ title: 'Manager verbs (dispatch, monitor, land)', verbs: ['new', 'ls', 'resources', 'watch', 'wait', 'review', 'merge', 'reparent'] },
|
|
95
|
+
{ title: 'Control another session', verbs: ['send', 'interrupt', 'rename', 'show', 'resume', 'stop', 'archive', 'unarchive', 'close', 'quarantine'] },
|
|
96
|
+
{ title: 'Worker verbs (declare YOUR OWN state — a claim the graph and your supervisor act on)', verbs: ['done', 'park', 'ask', 'files', 'web'] },
|
|
97
|
+
{ title: 'Human escape hatch', verbs: ['attach'] },
|
|
98
|
+
];
|
|
99
|
+
const SESSION_WRITE_NOTE = `Manager verbs that WRITE (send/interrupt/rename/resume/stop/close/merge/reparent) are PROJECT-BOUND: a backend serving
|
|
100
|
+
another project's repo refuses loudly — name the target with --api <url> to drive it on purpose.`;
|
|
101
|
+
function indent(text, spaces) {
|
|
102
|
+
const prefix = ' '.repeat(spaces);
|
|
103
|
+
return text.split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
104
|
+
}
|
|
105
|
+
function sessionDrawerHelp() {
|
|
106
|
+
const definitions = sessionHelpDefinitions();
|
|
107
|
+
const groups = SESSION_HELP_GROUPS.map(({ title, verbs }) => `${title}:\n${verbs.map((verb) => {
|
|
108
|
+
const [rawUsage, detail] = definitions[verb];
|
|
109
|
+
const usages = typeof rawUsage === 'string' ? [rawUsage] : rawUsage;
|
|
110
|
+
return `${usages.map((usage) => ` ${usage}`).join('\n')}\n${indent(detail, 6)}`;
|
|
111
|
+
}).join('\n')}`).join('\n\n');
|
|
112
|
+
return `${groups}\n\n${SEL_NOTE}\n${SESSION_WRITE_NOTE}\n${MENTION_NOTE}`;
|
|
113
|
+
}
|
|
114
|
+
function sessionVerbHelp(verb) {
|
|
115
|
+
const entry = sessionHelpDefinitions()[verb];
|
|
116
|
+
if (!entry)
|
|
117
|
+
return null;
|
|
118
|
+
const [rawUsage, detail, sharedNotes = []] = entry;
|
|
119
|
+
const usages = typeof rawUsage === 'string' ? [rawUsage] : rawUsage;
|
|
120
|
+
const notes = [detail];
|
|
121
|
+
if (sharedNotes.includes('selector'))
|
|
122
|
+
notes.push(SEL_NOTE);
|
|
123
|
+
if (sharedNotes.includes('project-bound'))
|
|
124
|
+
notes.push(SESSION_WRITE_NOTE);
|
|
125
|
+
return `${usages.map((line, index) => `${index === 0 ? 'Usage: ' : ' '}${line}`).join('\n')}\n\n${notes.join('\n\n')}`;
|
|
126
|
+
}
|
|
127
|
+
const ENTRIES = {
|
|
128
|
+
// ── project verbs (implicit object = this project) ────────────────────────
|
|
129
|
+
graph: {
|
|
130
|
+
line: 'graph the assembled view: bare = readable tree · --json = full payload · --public = static graph snapshot',
|
|
131
|
+
body: `Usage: spex graph [--focus <id>] [--depth N] [--json] | spex graph --public [--out <path>] [--content-dir <path>]
|
|
132
|
+
|
|
133
|
+
The normal assembled view is merged spec tree + worktree overlay + sessions. Bare it renders the
|
|
134
|
+
status-coloured tree (coloured when stdout is a tty; NO_COLOR respected), one line per node: id,
|
|
135
|
+
derived status, title, and attention badges (drift:N · stale:N · issues:N · ghost).
|
|
136
|
+
--focus <id> render just that subtree (unknown id fails loud)
|
|
137
|
+
--depth N limit levels below the shown root; prunes are counted, never silent
|
|
138
|
+
--json the full payload (tree · overlay · sessions), identical to GET /api/graph — machine
|
|
139
|
+
food; with --focus/--depth it is that filtered subtree as nested objects instead
|
|
140
|
+
--public deterministic read-only Spec Graph payload; excludes sessions, issues, evals, and write state
|
|
141
|
+
--out <path> write the public graph index to a file instead of stdout (only with --public)
|
|
142
|
+
--content-dir <path> write one read-only spec document per node for a static host (only with --public)`,
|
|
143
|
+
see: 'spex spec search (find one node by intent) · spex session ls (just the sessions, as a table)',
|
|
144
|
+
},
|
|
145
|
+
init: {
|
|
146
|
+
line: 'init [dir] adopt SpexCode on a repo: seed .spec + hooks + materialize --harness <ids> [--preset name]',
|
|
147
|
+
body: `Usage: spex init [dir=cwd] --harness <id[,id]|plugin:<folder>> [--preset default]
|
|
148
|
+
|
|
149
|
+
Scaffolds adoption in one shot: seeds a starter .spec tree (project root + .plugins plugins), plants
|
|
150
|
+
spexcode.json, installs the git hooks, and materializes the harness artifacts (contract block +
|
|
151
|
+
shims). --harness is REQUIRED — the explicit choice of which harnesses materialize delivers into
|
|
152
|
+
(stamped as spexcode.json "harnesses"; only their launchers are seeded); a pre-existing "harnesses"
|
|
153
|
+
field satisfies it. Additive — never overwrites your files. --preset picks the .plugins plugin tier (cumulative).
|
|
154
|
+
Footprint is fixed: materialized artifacts are never tracked — hidden via the per-clone .git/info/exclude, with
|
|
155
|
+
a tracked/mixed CLAUDE.md/AGENTS.md covered by the clean/smudge filter (see spex guide footprint).`,
|
|
156
|
+
see: 'spex guide (the full setup workflow) · spex uninstall (the inverse) · spex spec lint (adoption TODO)',
|
|
157
|
+
},
|
|
158
|
+
materialize: {
|
|
159
|
+
line: 'materialize the base pass of harness adaptation: render .spec/.plugins into your harness’s artifacts',
|
|
160
|
+
body: `Usage: spex materialize
|
|
161
|
+
|
|
162
|
+
The base operation of HARNESS ADAPTATION: one pass renders the spec tree's surface nodes into the
|
|
163
|
+
artifacts each selected harness auto-discovers — the managed <!-- spexcode --> block of
|
|
164
|
+
CLAUDE.md/AGENTS.md, the .claude/.codex shims, the skills/agents — and prints the content hash.
|
|
165
|
+
The outputs are derived and never tracked: to change one, edit its source (.plugins, spexcode.json)
|
|
166
|
+
and re-materialize — never the artifact. Not a one-time setup: it anchors on git-native events
|
|
167
|
+
(init · this verb · session-worktree creation · the pre-commit/post-checkout/post-merge hooks) —
|
|
168
|
+
run it by hand after a toolchain update, or in the setup step of any clone that has no spex-planted
|
|
169
|
+
hooks yet (CI, a cloud agent): generated and excluded, the artifacts never arrive via git.`,
|
|
170
|
+
see: 'spex doctor (verify the materialized artifacts actually reach an agent)',
|
|
171
|
+
},
|
|
172
|
+
doctor: {
|
|
173
|
+
line: 'doctor diagnose spec health and whether the workflow reaches this agent [--contract|--conflicts]',
|
|
174
|
+
body: `Usage: spex doctor spec-health findings + delivery report: preconditions · git-hook floor ·
|
|
175
|
+
contract · hooks + handler existence · backend · footprint
|
|
176
|
+
spex doctor --contract print the composed surface:system text any agent here reads
|
|
177
|
+
spex doctor --conflicts detect double-delivery (loose artifacts beside the managed ones)
|
|
178
|
+
|
|
179
|
+
Bare doctor is the opt-in, read-only health surface: it reports altitude and breadth findings without
|
|
180
|
+
putting them in the lint gate, then audits workflow delivery. Run it directly or let the tidy workflow
|
|
181
|
+
consume the same visible diagnosis.`,
|
|
182
|
+
see: 'spex spec lint (deterministic graph/contract gate) · spex materialize (repair delivery artifacts)',
|
|
183
|
+
},
|
|
184
|
+
flat: {
|
|
185
|
+
line: 'flat new|site|gallery Flatcode — flatten any repository into a converged .spec tree, then preview it',
|
|
186
|
+
body: `Usage: spex flat new <repo-url|path> [--out <dir>] [--launcher <name>] [--rounds <n>] [--coverage <pct>] [--lang <code>]
|
|
187
|
+
spex flat site <flat-dir>
|
|
188
|
+
spex flat gallery --out <dir> <flat-dir>…
|
|
189
|
+
|
|
190
|
+
For a repository URL, clones the target into an isolated flat. For a local path, operates directly in that clean
|
|
191
|
+
repository and adds only \`.spec\` work. A new local repository receives the ordinary \`.spec\` adoption seed;
|
|
192
|
+
an initialized one retains its \`.spec\`, configuration, and launcher. It then runs an agent round after round
|
|
193
|
+
until the spec tree passes a gate: zero \`spex spec lint\` errors, coverage at or above --coverage (default
|
|
194
|
+
90%), with \`spex doctor\`'s altitude findings fed back as the next round's instructions. The agent saying it
|
|
195
|
+
finished is not the signal — the measurement is.
|
|
196
|
+
|
|
197
|
+
Rounds are bounded (--rounds, default 6). Exhausting the budget reports a PARTIAL flat naming what still
|
|
198
|
+
fails and exits non-zero; it never reports a pass it did not measure.
|
|
199
|
+
|
|
200
|
+
The product is a sibling \`<out>/flat.json\` result record. A URL flat additionally holds its clone at \`<out>/repo\`
|
|
201
|
+
on the \`flatcode\` branch. A local flat commits only \`.spec\` in its source repository's current branch;
|
|
202
|
+
Flatcode rejects a dirty repository and fails if the agent touches anything outside \`.spec\`. Nothing is pushed,
|
|
203
|
+
nothing is served, and no session or project is registered.
|
|
204
|
+
|
|
205
|
+
--launcher picks the agent exactly like a session does when the current directory already has launcher
|
|
206
|
+
profiles. From a fresh directory, it accepts the built-in names \`claude\`, \`codex\`, \`opencode\`, or \`pi\` and
|
|
207
|
+
uses that choice both for the conversion turn and for \`spex init\` on a new target. An initialized local
|
|
208
|
+
project uses its own configured launcher. Without a configured default or --launcher, an interactive terminal
|
|
209
|
+
asks which agent to use; a non-interactive call must pass --launcher. A launcher whose harness has no
|
|
210
|
+
non-interactive turn is refused by name rather than quietly swapped.
|
|
211
|
+
|
|
212
|
+
--lang <code> writes the spec PROSE in that language (\`--lang zh\`, \`--lang ja\`, or a spelled-out name):
|
|
213
|
+
every node's title, desc and body, since a spec is written for whoever maintains that repository. Node ids
|
|
214
|
+
stay lowercase ascii-kebab — they are directory names and part of URLs — and so do code paths and mentions.
|
|
215
|
+
|
|
216
|
+
\`spex flat site <flat-dir>\` writes <flat-dir>/site — the graph-only dashboard over that flat's spec tree,
|
|
217
|
+
plus the node documents, the .spec archive, and a release manifest with a SHA-256 per file. It is plain
|
|
218
|
+
static files with no backend, so any static host serves it and a partial flat still previews (its About
|
|
219
|
+
panel carries the coverage, so a partial tree never reads as a finished one). The directory is relocatable:
|
|
220
|
+
it names everything relative to itself, so the same bytes serve from a domain root or any path prefix.
|
|
221
|
+
|
|
222
|
+
\`spex flat gallery --out <dir> <flat-dir>…\` assembles many flats into one static tree — each flat's site at
|
|
223
|
+
<out>/<owner>/<repo>/ (the slug comes from the source it read, not from your --out name), a self-contained
|
|
224
|
+
index page listing them, and gallery.json naming every entry with a SHA-256 of its release manifest, so what
|
|
225
|
+
landed on a host can be checked against what was built.`,
|
|
226
|
+
see: 'spex guide spec (the authoring format the rounds follow) · spex spec lint · spex doctor',
|
|
227
|
+
},
|
|
228
|
+
uninstall: {
|
|
229
|
+
line: 'uninstall [dir] remove all derived artifacts + local state; preserve tracked intent [--hooks]',
|
|
230
|
+
body: `Usage: spex uninstall [dir=cwd] [--hooks]
|
|
231
|
+
|
|
232
|
+
Removes all SpexCode-derived wiring and project-local state: contract blocks, harness shims,
|
|
233
|
+
generated skills/agents, plugin bundles, trust/filter/exclude entries, and the global per-project
|
|
234
|
+
store. Your tracked intent (.spec including .plugins, plus spexcode.json) and surrounding user prose
|
|
235
|
+
are preserved. Git hooks remain unless --hooks; that flag removes only unmodified canonical copies.`,
|
|
236
|
+
see: 'spex init (re-adopt later — your tracked intent survives)',
|
|
237
|
+
},
|
|
238
|
+
serve: {
|
|
239
|
+
line: 'serve [api|ui] api (default) = the backend :8787 · ui = the dashboard :5173 on top of it',
|
|
240
|
+
body: `Usage: spex serve [api] [--port N=8787]
|
|
241
|
+
spex serve [api] --public --password <pw> [--tls-cert F --tls-key F] [--http]
|
|
242
|
+
spex serve ui [--port N=5173] [--api-port N=8787] [--host H=127.0.0.1]
|
|
243
|
+
|
|
244
|
+
\`serve\` (or \`serve api\`) runs the backend for the repo at cwd behind a zero-downtime supervisor
|
|
245
|
+
(hot-reloads on source change; the public port never gaps). On a successful bind it RECORDS its
|
|
246
|
+
endpoint in the per-project runtime tier — that's how a bare \`spex\` run from this project's tree
|
|
247
|
+
finds this backend (spex guide settings → BACKEND ROUTING). --public exposes it on a public IP behind
|
|
248
|
+
a password + self-signed TLS (own cert via --tls-cert/--tls-key; --http drops TLS).
|
|
249
|
+
|
|
250
|
+
\`serve ui\` is a SEPARATE process: it serves the bundled dashboard on its own port and proxies /api +
|
|
251
|
+
the terminal socket to a running backend (--api-port pairs with the backend's --port, so many
|
|
252
|
+
projects coexist on one host). Loopback-only by default; --host 0.0.0.0 opens it to a LAN/tailnet —
|
|
253
|
+
still plain HTTP with no gate, so bind wide only on a network you trust.`,
|
|
254
|
+
see: 'GET /health (backend liveness probe)',
|
|
255
|
+
},
|
|
256
|
+
dashboard: {
|
|
257
|
+
line: 'dashboard ONE dashboard for every project you serve — no --api-port pairing [--port N=5173]',
|
|
258
|
+
body: `Usage: spex dashboard [--port N=5173] [--host H=127.0.0.1]
|
|
259
|
+
|
|
260
|
+
The HOST gateway: serves the built dashboard once and routes to EVERY backend the current user runs —
|
|
261
|
+
the multi-project hub engine plus the host registry on top. It continuously reconciles the per-project
|
|
262
|
+
endpoint records each \`spex serve\` publishes (validating each against the live backend's /api/instance
|
|
263
|
+
identity), keeps a durable known-project catalog, and proxies each project's API + SSE + terminal
|
|
264
|
+
socket under /p/<projectId>/* — the project is named in the path, so nothing is "current" and no
|
|
265
|
+
pairing flag exists.
|
|
266
|
+
|
|
267
|
+
Admin surface (hub-authorized: implicit from loopback until an admin password is set, then cookie
|
|
268
|
+
sessions): GET /projects (the validated list + gating state) · GET /projects/stream (SSE) ·
|
|
269
|
+
GET /projects/browse?path=… (read-only host folder picker) · POST /projects {root, initGit?, init?}
|
|
270
|
+
(explicit setup, then register) · GET|PUT /projects/<id>/config (raw portable
|
|
271
|
+
spexcode.json, revision-guarded) · POST /projects/<id>/init|doctor|serve
|
|
272
|
+
(run the real \`spex init\`/\`spex doctor\`, or start an offline project's backend, detached — a
|
|
273
|
+
backend never depends on this gateway staying up) · PUT|DELETE /projects/admin-password and
|
|
274
|
+
/projects/<id>/password (the gates). A gated project answers /p/<id>/login with the designed page.
|
|
275
|
+
|
|
276
|
+
Loopback-only by default; --host widens the bind — the admin surface stays locked to loopback until
|
|
277
|
+
an admin password exists, and ungated projects serve open.`,
|
|
278
|
+
see: 'spex serve (each project\'s backend) · spex serve ui (explicit one-backend pairing)',
|
|
279
|
+
},
|
|
280
|
+
guidance: {
|
|
281
|
+
line: 'guidance export the immutable guidance catalog index [--out <path>]',
|
|
282
|
+
body: `Usage: spex guidance [--out <path>] [--json]
|
|
283
|
+
|
|
284
|
+
Prints the deterministic, schema-versioned guidance index. Entries point at the authoritative active plugin,
|
|
285
|
+
help, and guide sources with exact rendered content, content hashes, and git revision provenance. The derived
|
|
286
|
+
effective system-contract view follows materialization order; no separate authoring source is created.
|
|
287
|
+
--out writes the same bytes to a file instead of stdout.`,
|
|
288
|
+
see: 'spex help · spex guide (the source guidance surfaces)',
|
|
289
|
+
},
|
|
290
|
+
// ── the noun drawers ──────────────────────────────────────────────────────
|
|
291
|
+
peer: {
|
|
292
|
+
line: 'peer <verb> gateway-owned SSH communication tunnels between machines',
|
|
293
|
+
body: `Usage: spex peer connect <SSH-ADDRESS>
|
|
294
|
+
spex peer ls [--json]
|
|
295
|
+
spex peer disconnect <SSH-ADDRESS>
|
|
296
|
+
|
|
297
|
+
connect asks the local host gateway to own one bidirectional SSH tunnel to the opaque address. The
|
|
298
|
+
same tunnel survives session closure and is reused by later sessions. The gateway must already be
|
|
299
|
+
running (\`spex dashboard\`); no command leaves an SSH child owned by the CLI. ls reports known peers;
|
|
300
|
+
disconnect retires the local peer and asks its remote counterpart to do the same.`,
|
|
301
|
+
see: 'spex session send --ssh <address> <full-session-id> "<msg>" · spex dashboard (the host gateway)',
|
|
302
|
+
},
|
|
303
|
+
spec: {
|
|
304
|
+
line: 'spec <verb> the governance graph: search · owner · lint · ack',
|
|
305
|
+
body: `Usage: spex spec search <query…> [--limit N=10] [--json]
|
|
306
|
+
spex spec owner <path> [--actionable]
|
|
307
|
+
spex spec lint [--json]
|
|
308
|
+
spex spec ack <node-id>… --reason "<why the contract still holds>"
|
|
309
|
+
|
|
310
|
+
search — which spec node GOVERNS a topic, ranked by user-story relevance (which surfaces user-facing
|
|
311
|
+
behaviour a code-grep misses). Run it BEFORE touching code: the node's spec.md body is the current
|
|
312
|
+
contract. The corpus is English — query in English.
|
|
313
|
+
|
|
314
|
+
owner — the reverse edge: a file's GOVERNORS (code: — drives drift + eval freshness) and REFERENCERS (related:
|
|
315
|
+
— coverage only), with the verdict spelled out (uncovered / related-only / sanely governed /
|
|
316
|
+
over-owned → split the file). --actionable prints NOTHING unless action is needed (hook use).
|
|
317
|
+
|
|
318
|
+
lint — checks the whole spec↔code graph and exits non-zero on errors. Errors: integrity (a
|
|
319
|
+
code:/related: file does not exist; a dead/ambiguous/unverifiable \`path#symbol\` selector; a selector
|
|
320
|
+
whose language has no designated extractor or whose extractor can't run here; a duplicate entry, a
|
|
321
|
+
base path both bare and scoped, or a selector on a glob/directory) · anchor-drift (a
|
|
322
|
+
commit since the spec's version touched an ANCHORED unit's lines, unacked — the blocking tier of
|
|
323
|
+
drift; same-file selectors OR'd, one error naming the hit selectors) · one-govern (a node
|
|
324
|
+
governs >1 DISTINCT file) · living (a "## vN" changelog heading) · id-format (an
|
|
325
|
+
id char outside the whitelist — ascii [a-z0-9-] or a non-ascii unicode letter/number, CJK ok — or a
|
|
326
|
+
leaf id reused) · mention (a [[id]] naming no node). Warns: coverage · drift
|
|
327
|
+
(UNANCHORED drift — always advisory, never blocks; on a scoped file's MISS, \`lint.scopedCodeMiss:
|
|
328
|
+
"ignore"\` may silence it) · anchor (anchoring a type) · related-drift (a scoped related row warns
|
|
329
|
+
per selector HIT, misses silent) · owners (whole-file governors only; scoped don't count) ·
|
|
330
|
+
confusable-id (two leaf ids one edit apart). spec lint's errors BLOCK commits (the prepared reference hook; bypass SPEXCODE_SKIP_LINT=1);
|
|
331
|
+
contrast \`spex eval lint\`, which is pure advisory and never blocks anyone. \`--json\` writes the
|
|
332
|
+
versioned machine report to stdout (source candidates + structured findings); its exit code still blocks
|
|
333
|
+
on errors.
|
|
334
|
+
|
|
335
|
+
ack — stamp Spec-OK on HEAD (an empty stamp commit): the drift remedy when only MECHANICS changed
|
|
336
|
+
and the spec's contract still holds. --reason is required and recorded in the ack commit's body
|
|
337
|
+
(quieting an anchor hit is a strong claim — the why must be durable). If the intent DID change,
|
|
338
|
+
edit the spec instead — same commit as the code.`,
|
|
339
|
+
see: 'spex guide spec (the file format + every lint rule) · spex graph (browse the whole tree)',
|
|
340
|
+
},
|
|
341
|
+
session: {
|
|
342
|
+
line: 'session <verb> the worktree state machine: new · ls · watch · wait · review · merge · send · …',
|
|
343
|
+
body: sessionDrawerHelp(),
|
|
344
|
+
see: 'spex eval ls --session <SEL> (the session’s measured loss) · spex help eval',
|
|
345
|
+
},
|
|
346
|
+
eval: {
|
|
347
|
+
line: 'eval <verb> the measurement system: add · ls · scenario ls/write · lint · ok · retract · clean',
|
|
348
|
+
body: `Usage: spex eval add [<node>|.] [--scenario <name>] (--pass|--fail) [--note <text>]
|
|
349
|
+
[--image <png> …repeatable] [--result <path|->] [--video <webm|mp4>] [--timeline <json>]
|
|
350
|
+
spex eval ls [<node>|.] [--json] a node's eval timeline, newest first
|
|
351
|
+
spex eval ls --session <SEL> [--json] a session's aggregate: its changed nodes' scores
|
|
352
|
+
spex eval ls --session <SEL> --export [--open | --out <path>]
|
|
353
|
+
spex eval scenario ls [<node>|.] [--unmeasured] [--json] declared scenarios; JSON = canonical index
|
|
354
|
+
spex eval scenario write --mutation <json> < eval.md propose one canonical metadata mutation
|
|
355
|
+
spex eval lint [--changed] measurement-layer findings (advisory, always exit 0)
|
|
356
|
+
spex eval ok <node> [--scenario <name>] the HUMAN sign-off on the scenario's latest measurement
|
|
357
|
+
spex eval retract [<node>|.] [--scenario <name>] [--last | --ts <iso>] [--note <why>]
|
|
358
|
+
spex eval clean [--keep-latest | --all] GC the content-addressed evidence cache
|
|
359
|
+
|
|
360
|
+
add — file an eval of a scenario against its expected: the loss signal the optimizer reads.
|
|
361
|
+
Measure through the REAL product surface, never by reasoning about the code. Evidence kind follows
|
|
362
|
+
the behaviour: MOVING/timed behaviour records a --video; a STATIC end state screenshots --image;
|
|
363
|
+
backend/CLI files a --result transcript. A fix's evidence is a fail→pass pair on the SAME scenario.
|
|
364
|
+
|
|
365
|
+
ls — node-scoped bare (its per-scenario eval history); session-scoped with an EXPLICIT --session
|
|
366
|
+
(never type-sniffed): filed evals are newest-first across nodes and source ownership; own measurements
|
|
367
|
+
are ✦-marked, evals filed by other sessions are unmarked, and blind spots follow measured rows. --export writes ONE self-contained
|
|
368
|
+
HTML artifact (diff · evidence inlined · gates) for CI/sharing.
|
|
369
|
+
|
|
370
|
+
scenario ls — the DECLARED contracts, no evals: text may show the latest verdict and --unmeasured is its blind-spot
|
|
371
|
+
worklist; --json emits the complete canonical semantic/measurement index with stable hashes and fixed-tree provenance
|
|
372
|
+
(--unmeasured is text-only because the JSON projection never reads the eval sidecar).
|
|
373
|
+
|
|
374
|
+
scenario write — the fixed-tree declaration writer for an external measurement guard: stdin is the authoritative
|
|
375
|
+
eval.md, --mutation is one closed JSON insert/delete request for one scenario's test metadata, and stdout is only
|
|
376
|
+
the proposed eval.md bytes. It reads no worktree or runner and fails without stdout on malformed or ambiguous input.
|
|
377
|
+
|
|
378
|
+
lint — the measurement layer's findings: malformed eval.md (eval-schema) · unmeasured (eval-missing) ·
|
|
379
|
+
stale (eval-drift) · orphaned remark tracks (eval-dangling) · governed source with no eval.md
|
|
380
|
+
(eval-coverage — the same name and shape as spec lint's coverage, one rule per layer) · over-owned
|
|
381
|
+
files (eval-owners). --changed scopes to the nodes THIS branch touched. spec lint's errors block
|
|
382
|
+
commits; eval lint is PURE ADVISORY, always exit 0 — a measurement gap never blocks anyone.
|
|
383
|
+
|
|
384
|
+
ok — the human's reviewed-and-agreed mark on the scenario's LATEST measurement: an appended, monotonic
|
|
385
|
+
sign-off bound to that one immutable measurement (a newer measurement or staleness releases it on its own —
|
|
386
|
+
no un-ok exists). The evals feed default-hides a fresh, ok'd scenario; a governed session is refused
|
|
387
|
+
(an agent's judgment on a measurement is a remark, never a self-blessing).
|
|
388
|
+
|
|
389
|
+
retract — the sanctioned undo for a botched filing: APPENDS a retraction event (traceable, never
|
|
390
|
+
deletes a line); the previous eval becomes latest again, or the scenario honestly returns to
|
|
391
|
+
unmeasured.
|
|
392
|
+
|
|
393
|
+
${DOT_NOTE}`,
|
|
394
|
+
see: 'spex guide eval (the eval.md scenario format + evidence rules) · spex evidence (bare byte transport)',
|
|
395
|
+
},
|
|
396
|
+
issue: {
|
|
397
|
+
line: 'issue <verb> concern threads, local + forge merged: ls · show · open · reply · close · promote · links',
|
|
398
|
+
body: `Usage: spex issue ls [--node <id>] [--store local|<host>] [--all] [--json]
|
|
399
|
+
spex issue show <id> [--json]
|
|
400
|
+
spex issue open "<concern>" [--store local|<host>] [--node <id>…] [--evidence <hash>…] [--body -|<text>]
|
|
401
|
+
spex issue reply <id> --body -|<text> [--evidence <hash>…]
|
|
402
|
+
spex issue close <id>
|
|
403
|
+
spex issue promote <id>
|
|
404
|
+
spex issue links [--pending] [--store <host>] [--node <id>] [--json]
|
|
405
|
+
|
|
406
|
+
ls is the drain view a supervisor reads: ONE store-tagged list, local + forge interleaved by
|
|
407
|
+
creation time. \`show <id>\` is the single-thread detail — the whole thread with its replies (a local
|
|
408
|
+
id, or a forge id like github#12). \`open\` welcomes taste, annotations, and off-mainline smells —
|
|
409
|
+
not only bugs; --store <host> opens straight on the forge. \`reply\` and \`close\` route by the
|
|
410
|
+
issue's store — one verb, local or forge. \`promote\` moves an OPEN local issue to the forge as one
|
|
411
|
+
recorded action. \`links\` is the read-only forge trace: which open forge issues/PRs serve which
|
|
412
|
+
spec node (--pending narrows to threads still awaiting an eval). The issues workflow's
|
|
413
|
+
on/off switch is the \`issues.enabled\` key in spexcode.json (no CLI toggle verb — edit the JSON;
|
|
414
|
+
\`spex doctor\` reports its state).
|
|
415
|
+
${MENTION_NOTE}`,
|
|
416
|
+
see: 'spex remark (pin a resolvable concern to an issue or scenario) · spex evidence put (stash evidence bytes)',
|
|
417
|
+
},
|
|
418
|
+
remark: {
|
|
419
|
+
line: 'remark <verb> resolvable pins on a host: add · resolve · retract',
|
|
420
|
+
body: `Usage: spex remark add <issue-id | <node> --scenario <name>> --body -|<text> [--code-sha <sha>] [--evidence <hash>…]
|
|
421
|
+
spex remark resolve <ref> (the <thread-id>#<rid> that \`spex remark add\` printed)
|
|
422
|
+
spex remark retract <ref>
|
|
423
|
+
|
|
424
|
+
The resolvable interaction primitive: \`add\` pins a concern to a HOST — a local issue, or a scenario
|
|
425
|
+
(\`--scenario\` present ⇒ the positional is a node; otherwise it is an issue id — the flag decides,
|
|
426
|
+
never type-sniffing). A SECOND agent \`resolve\`s it (never the author); the AUTHOR \`retract\`s their
|
|
427
|
+
own. The whole loop is CLI-first; the dashboard adds no capability.`,
|
|
428
|
+
see: 'spex issue (the thread hosts) · spex eval ls (scenario hosts)',
|
|
429
|
+
},
|
|
430
|
+
evidence: {
|
|
431
|
+
line: 'evidence put|get content-addressed bytes: put stashes & prints the hash, get reads back',
|
|
432
|
+
body: `Usage: spex evidence put <file|->
|
|
433
|
+
spex evidence get <hash> [-o <file>]
|
|
434
|
+
|
|
435
|
+
put writes bytes into the shared content-addressed evidence cache and prints the hash — transport
|
|
436
|
+
only, no eval filed. Use the hash with --evidence on issues/remarks; re-putting the same content
|
|
437
|
+
restores pruned or cloned-away evidence.
|
|
438
|
+
|
|
439
|
+
get is the symmetric read: hash in, bytes out. Local cache first (no backend needed — the evidence
|
|
440
|
+
is usually on this disk), then the backend on a local miss; both missing fails loud naming each
|
|
441
|
+
path. Bytes go to stdout by default (pipe-friendly); -o writes a file.`,
|
|
442
|
+
see: 'spex eval add (file an eval WITH evidence) · spex issue open --evidence <hash>',
|
|
443
|
+
},
|
|
444
|
+
// ── help & guide ──────────────────────────────────────────────────────────
|
|
445
|
+
guide: {
|
|
446
|
+
line: 'guide [topic] the manuals: setup workflow · spec/eval file formats · spexcode.json · footprint',
|
|
447
|
+
body: `Usage: spex guide the human setup workflow (install once, adopt a repo, serve)
|
|
448
|
+
spex guide spec the spec.md file format + every lint rule
|
|
449
|
+
spex guide eval the eval.md scenario format + how loss is measured and filed
|
|
450
|
+
spex guide settings every spexcode.json / spexcode.local.json field, and which file it belongs in
|
|
451
|
+
spex guide footprint the footprint model: never-tracked artifacts, exclude + content filter, anchors
|
|
452
|
+
|
|
453
|
+
guide is the SKILL layer — workflows and formats. Command usage lives here in help
|
|
454
|
+
(\`spex help <cmd>\`); guide carries what the commands assume you know.`,
|
|
455
|
+
},
|
|
456
|
+
// ── plumbing ──────────────────────────────────────────────────────────────
|
|
457
|
+
internal: {
|
|
458
|
+
line: '', // deliberately not on the map
|
|
459
|
+
body: `Usage: spex internal <sub>
|
|
460
|
+
|
|
461
|
+
Machine plumbing — called by generated hooks and launch scripts, never typed by a human or agent:
|
|
462
|
+
trunk print the resolved source-of-truth branch (the pre-commit main-guard captures it)
|
|
463
|
+
commit-surgery pre-commit footprint anchor: unconditional materialize + staged-index repair
|
|
464
|
+
refresh-footprint quiet materialize — the post-checkout/post-merge freshness anchor
|
|
465
|
+
check-staged pre-commit eval backstop: reject staged stray evidence files / malformed eval.md
|
|
466
|
+
session-state <st> --session <id> a lifecycle hook authors the session's state
|
|
467
|
+
session-fail --session <id> the StopFailure hook marks the session errored
|
|
468
|
+
session-idle --session <id> the idle-prompt hook marks an active session idle
|
|
469
|
+
commit-gate the Stop gate's deterministic commit check (exit 0 = ready to declare done)
|
|
470
|
+
hook-prompt <name> render canonical model-facing text for a runtime hook
|
|
471
|
+
nudge <node> the post-merge hook prints the issue nudge for a merged node
|
|
472
|
+
shared-runtime-spawn <cwd> <log> <pid> <scope> <cmd> [args…] detached adapter control plane (launch script)
|
|
473
|
+
codex-launch <sock> <cwd> [prompt…] backend-owned codex thread/start + first turn (launch script)
|
|
474
|
+
codex-turn <sock> <threadId> <text…> fire a follow-up turn on an owned thread (tests/scripts)
|
|
475
|
+
claude-headless-run <id> <runtime> <cmd> -- <tail…> resident stream-json controller (launch script)
|
|
476
|
+
pi-headless-run <id> <runtime> <cmd> -- <tail…> resident pi text-mode controller (launch script)
|
|
477
|
+
|
|
478
|
+
If you reached for one of these by hand, the porcelain you want is probably elsewhere: the trunk
|
|
479
|
+
name also lives at GET /api/settings (.layout); sessions are driven with spex session new / session send;
|
|
480
|
+
your own state is declared with spex session done|park|ask.`,
|
|
481
|
+
see: 'spex help (the porcelain map)',
|
|
482
|
+
},
|
|
483
|
+
help: {
|
|
484
|
+
line: '',
|
|
485
|
+
body: `Usage: spex help the command map
|
|
486
|
+
spex help <command> one command/drawer's usage (same as spex <command> --help)
|
|
487
|
+
spex guide [topic] the skill layer: workflows, file formats, best practice`,
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
// `spex <cmd> --help` must meet the user wherever they typed it: cli.ts intercepts the probe pre-verb.
|
|
491
|
+
// Session's noun-verb shape gets one extra projection; a bare noun still returns the complete drawer.
|
|
492
|
+
export function commandHelp(name, verb) {
|
|
493
|
+
if (name === 'session' && verb) {
|
|
494
|
+
const exact = sessionVerbHelp(verb);
|
|
495
|
+
if (exact)
|
|
496
|
+
return `${exact}\n\nsee also: spex session (the complete drawer)\n\nmap: spex help · skills: spex guide`;
|
|
497
|
+
}
|
|
498
|
+
const e = ENTRIES[name];
|
|
499
|
+
if (!e)
|
|
500
|
+
return null;
|
|
501
|
+
const oneLiner = e.line.replace(/^\S+(\s+\S+)*?\s{2,}/, ''); // the map line minus its "cmd args" column
|
|
502
|
+
const header = oneLiner ? `spex ${name} — ${oneLiner}\n\n` : ''; // unlisted entries (internal, help) lead with their own Usage
|
|
503
|
+
return `${header}${e.body}${e.see ? `\n\nsee also: ${e.see}` : ''}\n\nmap: spex help · skills: spex guide`;
|
|
504
|
+
}
|
|
505
|
+
// The guidance catalog consumes this registry projection rather than copying help prose or maintaining a
|
|
506
|
+
// second command list. Each returned text is exactly what the corresponding public help probe prints.
|
|
507
|
+
export function helpCatalogEntries() {
|
|
508
|
+
const entries = [{ id: 'overview', title: 'spex help', text: overviewHelp() }];
|
|
509
|
+
for (const name of Object.keys(ENTRIES).sort()) {
|
|
510
|
+
const text = commandHelp(name);
|
|
511
|
+
if (text)
|
|
512
|
+
entries.push({ id: name, title: `spex help ${name}`, text });
|
|
513
|
+
}
|
|
514
|
+
for (const verb of Object.keys(sessionHelpDefinitions()).sort()) {
|
|
515
|
+
const text = commandHelp('session', verb);
|
|
516
|
+
if (text)
|
|
517
|
+
entries.push({ id: `session.${verb}`, title: `spex session ${verb} --help`, text });
|
|
518
|
+
}
|
|
519
|
+
return entries;
|
|
520
|
+
}
|
|
521
|
+
export function sessionLaunchReceipt(id, managedWatch = false) {
|
|
522
|
+
return `spex: launched session ${id}
|
|
523
|
+
current result: the session JSON is on stdout now; \`spex session ls ${id}\` is the later one-shot snapshot
|
|
524
|
+
next lifecycle change: ${managedWatch
|
|
525
|
+
? `managed watch registered (parent source) — this parent receives ${id}'s state changes through its normal send queue; \`spex session reparent ${id} --to <parent>\` moves it, while \`watch cancel\` affects only manual watches`
|
|
526
|
+
: `background \`spex session wait ${id}\` (edge-triggered; exits on the next non-actionable→actionable transition); \`spex session watch ${id}\` registers send-backed delivery when the caller is governed`}; \`spex session watch stream ${id}\` NEVER EXITS
|
|
527
|
+
response channel: \`spex session send ${id} "<msg>"\`; \`send --keys\` is an UNSTABLE LAST RESORT after a plain send cannot land`;
|
|
528
|
+
}
|
|
529
|
+
export function peerSessionLaunchReceipt(id, sshAddress) {
|
|
530
|
+
return `spex: launched remote session ${id}
|
|
531
|
+
current result: the session JSON is on stdout now; \`spex session ls --ssh ${sshAddress} ${id}\` is the later one-shot snapshot
|
|
532
|
+
next lifecycle change: no managed watch crosses this machine peer; query the remote snapshot when needed
|
|
533
|
+
response channel: the launched prompt carries its runnable peer reply path; \`spex session send --ssh ${sshAddress} ${id} "<msg>"\` reaches this remote session`;
|
|
534
|
+
}
|
|
535
|
+
export function overviewHelp() {
|
|
536
|
+
return `spex — SpexCode CLI (spec↔code graph + worktree session state machine)
|
|
537
|
+
|
|
538
|
+
Usage: spex <noun> <verb> [object] [flags] the verb is always the token after its noun;
|
|
539
|
+
a bare noun prints that drawer's help
|
|
540
|
+
spex help <command> one command's usage (or spex <command> --help — always
|
|
541
|
+
safe: a help probe never runs the verb)
|
|
542
|
+
|
|
543
|
+
Project verbs (implicit object = this project)
|
|
544
|
+
${ENTRIES.graph.line}
|
|
545
|
+
${ENTRIES.init.line}
|
|
546
|
+
${ENTRIES.materialize.line}
|
|
547
|
+
${ENTRIES.doctor.line}
|
|
548
|
+
${ENTRIES.uninstall.line}
|
|
549
|
+
${ENTRIES.serve.line}
|
|
550
|
+
${ENTRIES.dashboard.line}
|
|
551
|
+
${ENTRIES.guidance.line}
|
|
552
|
+
|
|
553
|
+
Noun drawers
|
|
554
|
+
${ENTRIES.spec.line}
|
|
555
|
+
${ENTRIES.session.line}
|
|
556
|
+
${ENTRIES.peer.line}
|
|
557
|
+
${ENTRIES.eval.line}
|
|
558
|
+
${ENTRIES.issue.line}
|
|
559
|
+
${ENTRIES.remark.line}
|
|
560
|
+
${ENTRIES.evidence.line}
|
|
561
|
+
${ENTRIES.flat.line}
|
|
562
|
+
|
|
563
|
+
Manuals
|
|
564
|
+
${ENTRIES.guide.line}
|
|
565
|
+
|
|
566
|
+
Conventions (stated once, hold everywhere)
|
|
567
|
+
${SEL_NOTE.split('\n').join('\n ')}
|
|
568
|
+
${DOT_NOTE.split('\n').join('\n ')}
|
|
569
|
+
${ROUTING_NOTE.split('\n').join('\n ')}
|
|
570
|
+
${MENTION_NOTE.split('\n').join('\n ')}
|
|
571
|
+
|
|
572
|
+
Concepts & best practice live in the guide: spex guide (setup) · guide spec · guide eval · guide settings · guide footprint.
|
|
573
|
+
Machine plumbing (hook/launch-script callees) lives under \`spex internal\` — not part of your vocabulary.`;
|
|
574
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const HOOK_PROMPT_SOURCE: "spec-cli/src/hook-prompts.ts";
|
|
2
|
+
export type HookPromptRole = 'prompt' | 'signal';
|
|
3
|
+
export type HookPromptParams = Readonly<Record<string, string | number>>;
|
|
4
|
+
export type HookPromptEntry = Readonly<{
|
|
5
|
+
name: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
role: HookPromptRole;
|
|
9
|
+
content: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** The single authoring registry for text emitted by runtime hooks and published prompt catalogs. */
|
|
12
|
+
export declare class HookPromptCatalog {
|
|
13
|
+
readonly entries: readonly HookPromptEntry[];
|
|
14
|
+
private readonly definitions;
|
|
15
|
+
constructor();
|
|
16
|
+
entry(name: string): HookPromptEntry;
|
|
17
|
+
render(name: string, params?: HookPromptParams): string;
|
|
18
|
+
}
|