spexcode 0.5.2 → 0.5.4
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 +1 -1
- package/package.json +1 -1
- package/spec-cli/bin/spex.mjs +25 -1
- package/spec-cli/hooks/dispatch.sh +24 -1
- package/spec-cli/src/anchors.ts +192 -58
- package/spec-cli/src/attach.ts +11 -8
- package/spec-cli/src/claude-headless.ts +13 -52
- package/spec-cli/src/cli.ts +209 -71
- package/spec-cli/src/client.ts +143 -5
- package/spec-cli/src/gateway-hub.ts +2 -6
- package/spec-cli/src/gateway.ts +96 -11
- package/spec-cli/src/git.ts +1395 -96
- package/spec-cli/src/graph.ts +21 -17
- package/spec-cli/src/graphCache.ts +405 -61
- package/spec-cli/src/graphStream.ts +581 -182
- package/spec-cli/src/guide.ts +15 -4
- package/spec-cli/src/harness.ts +1133 -71
- package/spec-cli/src/headless-controller.ts +63 -0
- package/spec-cli/src/help.ts +120 -64
- package/spec-cli/src/host-resources.ts +698 -0
- package/spec-cli/src/index.ts +95 -21
- package/spec-cli/src/init.ts +73 -15
- package/spec-cli/src/issues.ts +36 -3
- package/spec-cli/src/layout.ts +189 -41
- package/spec-cli/src/lint.ts +152 -27
- package/spec-cli/src/localIssues.ts +5 -1
- package/spec-cli/src/maintenance-wrapper.ts +276 -0
- package/spec-cli/src/materialize.ts +6 -7
- package/spec-cli/src/opencode-headless.ts +4 -8
- package/spec-cli/src/pi-headless.ts +17 -60
- package/spec-cli/src/process-identity.ts +174 -0
- package/spec-cli/src/project-store.ts +17 -0
- package/spec-cli/src/pty-bridge.ts +6 -3
- package/spec-cli/src/reviews.ts +17 -13
- package/spec-cli/src/runtime-ownership.ts +88 -0
- package/spec-cli/src/session-maintenance.ts +750 -0
- package/spec-cli/src/session-timeline.ts +6 -6
- package/spec-cli/src/sessions.ts +1251 -208
- package/spec-cli/src/sh.ts +3 -0
- package/spec-cli/src/source-files.ts +27 -2
- package/spec-cli/src/specs.ts +58 -25
- package/spec-cli/src/supervise.ts +31 -11
- package/spec-cli/src/uninstall.ts +7 -6
- package/spec-cli/templates/hooks/commit-msg +14 -0
- package/spec-cli/templates/hooks/pre-commit +29 -15
- package/spec-cli/templates/hooks/prepare-commit-msg +20 -48
- package/spec-cli/templates/hooks/reference-transaction +105 -0
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +23 -21
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +12 -10
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +15 -6
- package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +32 -0
- package/spec-dashboard/dist/assets/App-C5vbTw8Q.js +2 -0
- package/spec-dashboard/dist/assets/Dashboard-u8RIS3NY.js +27 -0
- package/spec-dashboard/dist/assets/EvalsPage-BS7ITcNo.js +2 -0
- package/spec-dashboard/dist/assets/IssuesPage-DXbqQFW_.js +1 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/spec-dashboard/dist/assets/MobileApp-DVLnk9hz.js +2 -0
- package/spec-dashboard/dist/assets/Modal-6mHq6fbZ.js +1 -0
- package/spec-dashboard/dist/assets/{PageScroll-CadAKuSy.js → PageScroll-CAY4S4g4.js} +1 -1
- package/spec-dashboard/dist/assets/ProjectsPage-UQyzsTWN.js +1 -0
- package/spec-dashboard/dist/assets/SessionInterface-DKU4c1Z-.js +39 -0
- package/spec-dashboard/dist/assets/SessionWindow-zGwJaGbR.js +1 -0
- package/spec-dashboard/dist/assets/Settings-igR17pns.js +1 -0
- package/spec-dashboard/dist/assets/Thread-B-ZUarN1.js +13 -0
- package/spec-dashboard/dist/assets/TimelineChat-Cp579UoJ.css +1 -0
- package/spec-dashboard/dist/assets/TimelineChat-sc49Qj5d.js +276 -0
- package/spec-dashboard/dist/assets/data-B1ot4PF0.js +1 -0
- package/spec-dashboard/dist/assets/index-BqBNCa1V.js +41 -0
- package/spec-dashboard/dist/assets/index-CzutlTDf.css +1 -0
- package/spec-dashboard/dist/assets/{terminalFont-DmTPNdbU.js → terminalFont-C6oGNKrf.js} +1 -1
- package/spec-dashboard/dist/index.html +5 -4
- package/spec-dashboard/src/reviewFilters.js +46 -8
- package/spec-dashboard/src/session.js +64 -9
- package/spec-eval/src/cli.ts +144 -32
- package/spec-eval/src/evaltab.ts +16 -9
- package/spec-eval/src/freshness.ts +336 -54
- package/spec-eval/src/scenariofresh.ts +51 -1
- package/spec-eval/src/scenarios.ts +324 -25
- package/spec-eval/src/sessioneval.ts +969 -173
- package/spec-cli/src/message-stream.ts +0 -147
- package/spec-dashboard/dist/assets/Dashboard-CTAuTyZ3.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-KbMMownG.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-DmyLb9Rj.js +0 -1
- package/spec-dashboard/dist/assets/MobileApp-D2RZGt4Z.js +0 -2
- package/spec-dashboard/dist/assets/Modal-3brXUhM0.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-DU3x4Y8l.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-BtrzlOPs.js +0 -39
- package/spec-dashboard/dist/assets/SessionWindow-BWH5O0jh.js +0 -13
- package/spec-dashboard/dist/assets/Settings-COgdKTJB.js +0 -1
- package/spec-dashboard/dist/assets/TimelineChat-DQ21GSJK.js +0 -1
- package/spec-dashboard/dist/assets/index-D6HBvKkJ.js +0 -41
- package/spec-dashboard/dist/assets/index-DFdlYy4H.css +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createConnection, type Socket } from 'node:net'
|
|
2
|
+
import type { DispatchResult } from './harness.js'
|
|
3
|
+
|
|
4
|
+
export type ControlRequestLabel = {
|
|
5
|
+
name: string
|
|
6
|
+
session: string
|
|
7
|
+
timeoutMs: number
|
|
8
|
+
rejected: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const timer = setTimeout(() => reject(new Error(message)), ms)
|
|
14
|
+
promise.then(
|
|
15
|
+
(value) => { clearTimeout(timer); resolve(value) },
|
|
16
|
+
(error) => { clearTimeout(timer); reject(error) },
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function controlRequest(
|
|
22
|
+
sockPath: string,
|
|
23
|
+
request: object,
|
|
24
|
+
label: ControlRequestLabel,
|
|
25
|
+
): Promise<DispatchResult> {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
let socket: Socket | undefined
|
|
28
|
+
let buffer = ''
|
|
29
|
+
let settled = false
|
|
30
|
+
const finish = (result: DispatchResult) => {
|
|
31
|
+
if (settled) return
|
|
32
|
+
settled = true
|
|
33
|
+
clearTimeout(timer)
|
|
34
|
+
socket?.destroy()
|
|
35
|
+
resolve(result)
|
|
36
|
+
}
|
|
37
|
+
const timer = setTimeout(
|
|
38
|
+
() => finish({ ok: false, error: `${label.name} control timed out for session ${label.session}` }),
|
|
39
|
+
label.timeoutMs,
|
|
40
|
+
)
|
|
41
|
+
try {
|
|
42
|
+
socket = createConnection(sockPath)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
finish({ ok: false, error: `${label.name} controller connect failed for session ${label.session}: ${(error as Error).message}` })
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
socket.setEncoding('utf8')
|
|
48
|
+
socket.on('connect', () => socket!.write(`${JSON.stringify(request)}\n`))
|
|
49
|
+
socket.on('data', (chunk) => {
|
|
50
|
+
buffer += chunk
|
|
51
|
+
const nl = buffer.indexOf('\n')
|
|
52
|
+
if (nl < 0) return
|
|
53
|
+
try {
|
|
54
|
+
const response = JSON.parse(buffer.slice(0, nl)) as DispatchResult
|
|
55
|
+
finish(response.ok ? { ok: true } : { ok: false, error: response.error || label.rejected })
|
|
56
|
+
} catch (error) {
|
|
57
|
+
finish({ ok: false, error: `${label.name} returned an invalid control response: ${(error as Error).message}` })
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
socket.on('error', (error) => finish({ ok: false, error: `${label.name} controller unreachable for session ${label.session}: ${error.message}` }))
|
|
61
|
+
socket.on('close', () => finish({ ok: false, error: `${label.name} controller closed before confirming session ${label.session}` }))
|
|
62
|
+
})
|
|
63
|
+
}
|
package/spec-cli/src/help.ts
CHANGED
|
@@ -29,6 +29,97 @@ const MENTION_NOTE = `Mentions: @session · [[node]] · @new / @new:<launcher> w
|
|
|
29
29
|
text passed as a CLI arg included. [[node]] names the topic node; @session hands the text to that live agent;
|
|
30
30
|
@new spawns a fresh worker on the thread's node (bare = configured default; :<launcher> = that named profile).`
|
|
31
31
|
|
|
32
|
+
type SessionVerbNote = 'selector' | 'project-bound'
|
|
33
|
+
type SessionVerbHelp = readonly [usage: string | readonly string[], detail: string, notes?: readonly SessionVerbNote[]]
|
|
34
|
+
|
|
35
|
+
// One definition feeds both the complete session drawer and exact `session <verb> --help` projections.
|
|
36
|
+
// Keeping the behavioral prose here prevents a compact probe from becoming a second, drifting manual.
|
|
37
|
+
function sessionHelpDefinitions(): Record<string, SessionVerbHelp> {
|
|
38
|
+
return {
|
|
39
|
+
new: ['spex session new "<prompt>" [--prompt-file <path>|-] [--launcher <name>]',
|
|
40
|
+
`Launch a worker in its own node worktree. The materialized system contract reaches it
|
|
41
|
+
automatically; the prompt supplies the task context. Its first [[id]] mention binds the
|
|
42
|
+
session to that node. --prompt-file <path>|- carries a long prompt without shell quoting
|
|
43
|
+
(exclusive with the inline prompt). The successful receipt names what to read, monitor, and reply on.`],
|
|
44
|
+
ls: ['spex session ls [SEL…] [--status a,b] [--all] [--json]',
|
|
45
|
+
'One-shot table of living sessions. Shelved sessions ([[archive]]) are hidden; --all includes them, and naming one explicitly always shows it.', ['selector']],
|
|
46
|
+
resources: ['spex session resources [--json]', 'Read-only host/process ownership, budgets, shared refs, and findings.'],
|
|
47
|
+
watch: ['spex session watch [SEL…] [--as NAME] [--idle] [--interval N=5]',
|
|
48
|
+
'Streams lifecycle transitions and blocks until killed; `session wait` is the one-shot alternative.', ['selector']],
|
|
49
|
+
wait: ['spex session wait <SEL> [--timeout S=1200] [--interval S=2] [--idle]',
|
|
50
|
+
`EDGE-TRIGGERED wait on one session. Prints the current status immediately (stderr), then
|
|
51
|
+
exits 0 only when it OBSERVES
|
|
52
|
+
the session TRANSITION from a non-actionable status into an actionable one, printing the
|
|
53
|
+
observed path on stdout (e.g. working→review — read the LAST token as the status reached).
|
|
54
|
+
It NEVER returns just because the session is actionable ALREADY — for "what is it right NOW"
|
|
55
|
+
use \`session ls\` / \`session review\` instead. --timeout is the guaranteed exit (code 1,
|
|
56
|
+
observed path on stderr).`, ['selector']],
|
|
57
|
+
review: ['spex session review <SEL> [--json]', 'Reports ahead · uncommitted · proposal · gates · merge-base diff.', ['selector']],
|
|
58
|
+
merge: ['spex session merge <SEL>', 'Dispatches a gated merge to the session\'s own agent; it does not close the session.', ['selector', 'project-bound']],
|
|
59
|
+
send: [['spex session send <SEL> "<msg>"', 'spex session send <SEL> --keys "<keys>"'],
|
|
60
|
+
`Plain send delivers a message and fails loud when dispatch is dead. --keys is the LAST RESORT:
|
|
61
|
+
raw nav-mode keystrokes to a TUI dialog ("Up Up Enter", C-/M-/S- combos). The raw key surface
|
|
62
|
+
is UNSTABLE and can confirm dangerous dialogs — try a plain send first; use keys only when text
|
|
63
|
+
provably cannot land.`, ['selector', 'project-bound']],
|
|
64
|
+
interrupt: ['spex session interrupt <SEL>', 'Hard-interrupt the current turn through native harness control.', ['selector', 'project-bound']],
|
|
65
|
+
rename: ['spex session rename <SEL> "<name>"', 'Set the display name; an empty name clears it.', ['selector', 'project-bound']],
|
|
66
|
+
show: ['spex session show <SEL> [--capture] [--json]', `The session record: status · node · branch · launcher · the full originating prompt.
|
|
67
|
+
--capture prints the LIVE PANE as text instead (empty pane = exit 0; unknown session = exit 2).`, ['selector']],
|
|
68
|
+
resume: ['spex session resume <SEL> [--force]', 'Relaunch ONLY if confirmed offline; --force is for a wedged session.', ['selector', 'project-bound']],
|
|
69
|
+
stop: ['spex session stop <SEL>', 'Soft stop: kill the exact agent and KEEP the worktree resumable.', ['selector', 'project-bound']],
|
|
70
|
+
archive: ['spex session archive <SEL>', 'Cold-archive it: exact leaf/runtime stopped, worktree and conversation kept.', ['selector']],
|
|
71
|
+
unarchive: ['spex session unarchive <SEL>', 'Deprecated compatibility spelling: same behavior as resume, relaunching the same conversation.', ['selector']],
|
|
72
|
+
close: ['spex session close <SEL>', 'Retire the session and its worktree.', ['selector', 'project-bound']],
|
|
73
|
+
maintain: [[
|
|
74
|
+
'spex session maintain --allow-stop <SEL> [--allow-resume <SEL>[:force]] … -- <command> [args…]',
|
|
75
|
+
'spex session maintain --status',
|
|
76
|
+
], `Hold one project maintenance lease around a bounded operator command. Only the exact one-shot
|
|
77
|
+
stop/resume plan is brokered; ordinary session writes stay closed. The bearer never prints or enters
|
|
78
|
+
argv/environment. --status is the sanitized read-only lease view.`, ['selector', 'project-bound']],
|
|
79
|
+
done: ['spex session done --propose merge|nothing|close [--note T]', 'Declare your own work committed and stop.'],
|
|
80
|
+
park: ['spex session park --note <what-you-await>', 'Declare that a real background task will wake your own session.'],
|
|
81
|
+
ask: ['spex session ask --note <your-question>', 'Declare that your own session is stopped on the human and resumes on reply.'],
|
|
82
|
+
attach: ['spex session attach <SEL>', `Attaches the current terminal to the worker's tmux (detach: C-b d) and blocks until detached.
|
|
83
|
+
LOCAL-only (fails loud on a remote backend); show --capture and send are non-interactive.`, ['selector']],
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const SESSION_HELP_GROUPS = [
|
|
88
|
+
{ title: 'Manager verbs (dispatch, monitor, land)', verbs: ['new', 'ls', 'resources', 'watch', 'wait', 'review', 'merge'] },
|
|
89
|
+
{ title: 'Control another session', verbs: ['send', 'interrupt', 'rename', 'show', 'resume', 'stop', 'archive', 'unarchive', 'close', 'maintain'] },
|
|
90
|
+
{ title: 'Worker verbs (declare YOUR OWN state — a claim the graph and your supervisor act on)', verbs: ['done', 'park', 'ask'] },
|
|
91
|
+
{ title: 'Human escape hatch', verbs: ['attach'] },
|
|
92
|
+
] as const
|
|
93
|
+
|
|
94
|
+
const SESSION_WRITE_NOTE = `Manager verbs that WRITE (send/interrupt/rename/resume/stop/close/maintain/merge) are PROJECT-BOUND: a backend serving
|
|
95
|
+
another project's repo refuses loudly — name the target with --api <url> to drive it on purpose.`
|
|
96
|
+
|
|
97
|
+
function indent(text: string, spaces: number): string {
|
|
98
|
+
const prefix = ' '.repeat(spaces)
|
|
99
|
+
return text.split('\n').map((line) => `${prefix}${line}`).join('\n')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function sessionDrawerHelp(): string {
|
|
103
|
+
const definitions = sessionHelpDefinitions()
|
|
104
|
+
const groups = SESSION_HELP_GROUPS.map(({ title, verbs }) => `${title}:\n${verbs.map((verb) => {
|
|
105
|
+
const [rawUsage, detail] = definitions[verb]
|
|
106
|
+
const usages = typeof rawUsage === 'string' ? [rawUsage] : rawUsage
|
|
107
|
+
return `${usages.map((usage) => ` ${usage}`).join('\n')}\n${indent(detail, 6)}`
|
|
108
|
+
}).join('\n')}`).join('\n\n')
|
|
109
|
+
return `${groups}\n\n${SEL_NOTE}\n${SESSION_WRITE_NOTE}\n${MENTION_NOTE}`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sessionVerbHelp(verb: string): string | null {
|
|
113
|
+
const entry = sessionHelpDefinitions()[verb]
|
|
114
|
+
if (!entry) return null
|
|
115
|
+
const [rawUsage, detail, sharedNotes = []] = entry
|
|
116
|
+
const usages = typeof rawUsage === 'string' ? [rawUsage] : rawUsage
|
|
117
|
+
const notes = [detail]
|
|
118
|
+
if (sharedNotes.includes('selector')) notes.push(SEL_NOTE)
|
|
119
|
+
if (sharedNotes.includes('project-bound')) notes.push(SESSION_WRITE_NOTE)
|
|
120
|
+
return `${usages.map((line, index) => `${index === 0 ? 'Usage: ' : ' '}${line}`).join('\n')}\n\n${notes.join('\n\n')}`
|
|
121
|
+
}
|
|
122
|
+
|
|
32
123
|
const ENTRIES: Record<string, Entry> = {
|
|
33
124
|
// ── project verbs (implicit object = this project) ────────────────────────
|
|
34
125
|
graph: {
|
|
@@ -164,7 +255,7 @@ leaf id reused) · mention (a [[id]] naming no node). Warns: coverage · drift
|
|
|
164
255
|
(UNANCHORED drift — always advisory, never blocks; on a scoped file's MISS, \`lint.scopedCodeMiss:
|
|
165
256
|
"ignore"\` may silence it) · anchor (anchoring a type) · related-drift (a scoped related row warns
|
|
166
257
|
per selector HIT, misses silent) · owners (whole-file governors only; scoped don't count) ·
|
|
167
|
-
confusable-id (two leaf ids one edit apart). spec lint's errors BLOCK commits (the
|
|
258
|
+
confusable-id (two leaf ids one edit apart). spec lint's errors BLOCK commits (the prepared reference hook; bypass SPEXCODE_SKIP_LINT=1);
|
|
168
259
|
contrast \`spex eval lint\`, which is pure advisory and never blocks anyone.
|
|
169
260
|
|
|
170
261
|
ack — stamp Spec-OK on HEAD (an empty stamp commit): the drift remedy when only MECHANICS changed
|
|
@@ -175,70 +266,18 @@ edit the spec instead — same commit as the code.`,
|
|
|
175
266
|
},
|
|
176
267
|
session: {
|
|
177
268
|
line: 'session <verb> the worktree state machine: new · ls · watch · wait · review · merge · send · …',
|
|
178
|
-
body:
|
|
179
|
-
spex session new "<prompt>" [--prompt-file <path>|-] [--launcher <name>]
|
|
180
|
-
Launch a worker in its own node worktree. Give it ONLY its task — the dev-flow contract
|
|
181
|
-
reaches it through the materialized system prompt. The prompt's first [[id]] mention binds
|
|
182
|
-
the session to that node. --prompt-file <path>|- carries a long prompt
|
|
183
|
-
without shell quoting (exclusive with the inline prompt). Then MONITOR it (wait/watch below).
|
|
184
|
-
spex session ls [SEL…] [--status a,b] [--json] one-shot table of living sessions
|
|
185
|
-
spex session watch [SEL…] [--as NAME] [--idle] [--interval N=5]
|
|
186
|
-
Streams lifecycle transitions until killed — it NEVER EXITS; the human's forever stream. An
|
|
187
|
-
agent must background it or use wait; blocking a turn on watch freezes you.
|
|
188
|
-
spex session wait <SEL> [--timeout S=1200] [--interval S=2] [--idle]
|
|
189
|
-
EDGE-TRIGGERED sleep on one session — ALWAYS run it in the BACKGROUND; its exit is your wake-up.
|
|
190
|
-
Prints the session's current status immediately (stderr), then exits 0 only when it OBSERVES
|
|
191
|
-
the session TRANSITION from a non-actionable status into an actionable one, printing the
|
|
192
|
-
observed path on stdout (e.g. working→review — read the LAST token as the status reached).
|
|
193
|
-
USE IT to sleep until a dispatched worker next needs you — including a dispatched MERGE
|
|
194
|
-
actually landing (review→working while the merge runs, then the edge back is your wake-up).
|
|
195
|
-
It NEVER returns just because the session is actionable ALREADY — for "what is it right NOW"
|
|
196
|
-
use \`session ls\` / \`session review\` instead. --timeout is the guaranteed exit (code 1,
|
|
197
|
-
observed path on stderr). Background one wait per worker.
|
|
198
|
-
spex session review <SEL> [--json] the merge cockpit: ahead · uncommitted · proposal · gates ·
|
|
199
|
-
merge-base diff — decide from this, don't hand-run git
|
|
200
|
-
spex session merge <SEL> gated merge, dispatched to the session's OWN agent; confirm
|
|
201
|
-
HEAD advanced before closing — closing unmerged discards work
|
|
202
|
-
|
|
203
|
-
Control another session (all take SEL):
|
|
204
|
-
spex session send <SEL> "<msg>" deliver a message (fail-loud: a dead dispatch exits non-zero)
|
|
205
|
-
spex session interrupt <SEL> hard-interrupt the current turn through native harness control
|
|
206
|
-
spex session send <SEL> --keys "<keys>"
|
|
207
|
-
LAST RESORT: raw nav-mode keystrokes to a TUI dialog ("Up Up Enter", C-/M-/S- combos). The raw
|
|
208
|
-
key surface is UNSTABLE and can confirm dangerous dialogs — don't reach for it unless a plain
|
|
209
|
-
\`session send\` text provably cannot land.
|
|
210
|
-
spex session rename <SEL> "<name>" set the display name ("" clears)
|
|
211
|
-
spex session show <SEL> [--capture] [--json]
|
|
212
|
-
The session record: status · node · branch · launcher · the full originating prompt.
|
|
213
|
-
--capture prints the LIVE PANE as text instead (empty pane = exit 0; unknown session = exit 2).
|
|
214
|
-
spex session resume <SEL> [--force] relaunch ONLY if confirmed offline (--force for a wedged one)
|
|
215
|
-
spex session stop <SEL> soft stop: kill the agent, KEEP the worktree (resumable)
|
|
216
|
-
spex session close <SEL> retire the session and its worktree
|
|
217
|
-
|
|
218
|
-
Worker verbs (declare YOUR OWN state — a claim the graph and your supervisor act on):
|
|
219
|
-
spex session done --propose merge|nothing|close [--note T] committed and stopping
|
|
220
|
-
spex session park --note <what-you-await> a real background task will wake you
|
|
221
|
-
spex session ask --note <your-question> stopped on the human; resumes on reply
|
|
222
|
-
|
|
223
|
-
Human escape hatch:
|
|
224
|
-
spex session attach <SEL> sit in the worker's REAL tmux (detach: C-b d). INTERACTIVE
|
|
225
|
-
AND BLOCKING — an agent must NEVER run it in a turn: use
|
|
226
|
-
show --capture / send. LOCAL-only (fails loud on a remote backend).
|
|
227
|
-
|
|
228
|
-
${SEL_NOTE}
|
|
229
|
-
Manager verbs that WRITE (send/interrupt/rename/resume/stop/close/merge) are PROJECT-BOUND: a backend serving
|
|
230
|
-
another project's repo refuses loudly — name the target with --api <url> to drive it on purpose.
|
|
231
|
-
${MENTION_NOTE}`,
|
|
269
|
+
body: sessionDrawerHelp(),
|
|
232
270
|
see: 'spex eval ls --session <SEL> (the session’s measured loss) · spex help eval',
|
|
233
271
|
},
|
|
234
272
|
eval: {
|
|
235
|
-
line: 'eval <verb> the measurement system: add · ls · scenario ls · matrix · lint · ok · retract · clean',
|
|
273
|
+
line: 'eval <verb> the measurement system: add · ls · scenario ls/write · matrix · lint · ok · retract · clean',
|
|
236
274
|
body: `Usage: spex eval add [<node>|.] [--scenario <name>] (--pass|--fail) [--note <text>]
|
|
237
275
|
[--image <png> …repeatable] [--result <path|->] [--video <webm|mp4>] [--timeline <json>]
|
|
238
276
|
spex eval ls [<node>|.] [--json] a node's eval timeline, newest first
|
|
239
277
|
spex eval ls --session <SEL> [--json] a session's aggregate: its changed nodes' scores
|
|
240
278
|
spex eval ls --session <SEL> --export [--open | --out <path>]
|
|
241
|
-
spex eval scenario ls [<node>|.] [--unmeasured] [--json] declared scenarios;
|
|
279
|
+
spex eval scenario ls [<node>|.] [--unmeasured] [--json] declared scenarios; JSON = canonical index
|
|
280
|
+
spex eval scenario write --mutation <json> < eval.md propose one canonical metadata mutation
|
|
242
281
|
spex eval matrix <launcher> [--node <id>] [--rows k1,k2] the harness live-behavior matrix
|
|
243
282
|
spex eval lint [--changed] measurement-layer findings (advisory, always exit 0)
|
|
244
283
|
spex eval ok <node> [--scenario <name>] the HUMAN sign-off on the scenario's latest measurement
|
|
@@ -251,12 +290,17 @@ the behaviour: MOVING/timed behaviour records a --video; a STATIC end state scre
|
|
|
251
290
|
backend/CLI files a --result transcript. A fix's evidence is a fail→pass pair on the SAME scenario.
|
|
252
291
|
|
|
253
292
|
ls — node-scoped bare (its per-scenario eval history); session-scoped with an EXPLICIT --session
|
|
254
|
-
(never type-sniffed):
|
|
255
|
-
✦-marked
|
|
293
|
+
(never type-sniffed): filed evals are newest-first across nodes and source ownership; own measurements
|
|
294
|
+
are ✦-marked, evals filed by other sessions are unmarked, and blind spots follow measured rows. --export writes ONE self-contained
|
|
256
295
|
HTML artifact (diff · evidence inlined · gates) for CI/sharing.
|
|
257
296
|
|
|
258
|
-
scenario ls — the DECLARED contracts
|
|
259
|
-
|
|
297
|
+
scenario ls — the DECLARED contracts, no evals: text may show the latest verdict and --unmeasured is its blind-spot
|
|
298
|
+
worklist; --json emits the complete canonical semantic/measurement index with stable hashes and fixed-tree provenance
|
|
299
|
+
(--unmeasured is text-only because the JSON projection never reads the eval sidecar).
|
|
300
|
+
|
|
301
|
+
scenario write — the fixed-tree declaration writer for an external measurement guard: stdin is the authoritative
|
|
302
|
+
eval.md, --mutation is one closed JSON insert/delete request for one scenario's test metadata, and stdout is only
|
|
303
|
+
the proposed eval.md bytes. It reads no worktree or runner and fails without stdout on malformed or ambiguous input.
|
|
260
304
|
|
|
261
305
|
matrix — run the eight-row harness live-behavior matrix against a REAL dispatched session of the named
|
|
262
306
|
launcher (the harness-adapter acceptance rule, defined once in spec-eval/src/matrix.ts): it syncs the
|
|
@@ -359,6 +403,7 @@ Machine plumbing — called by generated hooks and launch scripts, never typed b
|
|
|
359
403
|
session-idle --session <id> the idle-prompt hook marks an active session idle
|
|
360
404
|
commit-gate the Stop gate's deterministic commit check (exit 0 = ready to declare done)
|
|
361
405
|
nudge <node> the post-merge hook prints the issue nudge for a merged node
|
|
406
|
+
shared-runtime-spawn <cwd> <log> <pid> <scope> <cmd> [args…] detached adapter control plane (launch script)
|
|
362
407
|
codex-launch <sock> <cwd> [prompt…] backend-owned codex thread/start + first turn (launch script)
|
|
363
408
|
codex-turn <sock> <threadId> <text…> fire a follow-up turn on an owned thread (tests/scripts)
|
|
364
409
|
claude-headless-run <id> <runtime> <cmd> -- <tail…> resident stream-json controller (launch script)
|
|
@@ -377,9 +422,13 @@ your own state is declared with spex session done|park|ask.`,
|
|
|
377
422
|
},
|
|
378
423
|
}
|
|
379
424
|
|
|
380
|
-
// `spex <cmd> --help` must meet the user wherever they typed it: cli.ts intercepts the probe pre-verb
|
|
381
|
-
//
|
|
382
|
-
export function commandHelp(name: string): string | null {
|
|
425
|
+
// `spex <cmd> --help` must meet the user wherever they typed it: cli.ts intercepts the probe pre-verb.
|
|
426
|
+
// Session's noun-verb shape gets one extra projection; a bare noun still returns the complete drawer.
|
|
427
|
+
export function commandHelp(name: string, verb?: string): string | null {
|
|
428
|
+
if (name === 'session' && verb) {
|
|
429
|
+
const exact = sessionVerbHelp(verb)
|
|
430
|
+
if (exact) return `${exact}\n\nsee also: spex session (the complete drawer)\n\nmap: spex help · skills: spex guide`
|
|
431
|
+
}
|
|
383
432
|
const e = ENTRIES[name]
|
|
384
433
|
if (!e) return null
|
|
385
434
|
const oneLiner = e.line.replace(/^\S+(\s+\S+)*?\s{2,}/, '') // the map line minus its "cmd args" column
|
|
@@ -387,6 +436,13 @@ export function commandHelp(name: string): string | null {
|
|
|
387
436
|
return `${header}${e.body}${e.see ? `\n\nsee also: ${e.see}` : ''}\n\nmap: spex help · skills: spex guide`
|
|
388
437
|
}
|
|
389
438
|
|
|
439
|
+
export function sessionLaunchReceipt(id: string): string {
|
|
440
|
+
return `spex: launched session ${id}
|
|
441
|
+
current result: the session JSON is on stdout now; \`spex session ls ${id}\` is the later one-shot snapshot
|
|
442
|
+
next lifecycle change: background \`spex session wait ${id}\` (edge-triggered; exits on the next non-actionable→actionable transition); \`spex session watch ${id}\` streams and NEVER EXITS
|
|
443
|
+
response channel: \`spex session send ${id} "<msg>"\`; \`send --keys\` is an UNSTABLE LAST RESORT after a plain send cannot land`
|
|
444
|
+
}
|
|
445
|
+
|
|
390
446
|
export function overviewHelp(): string {
|
|
391
447
|
return `spex — SpexCode CLI (spec↔code graph + worktree session state machine)
|
|
392
448
|
|