spexcode 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -44
- package/package.json +3 -3
- package/spec-cli/bin/spex.mjs +2 -2
- package/spec-cli/hooks/dispatch.sh +1 -1
- package/spec-cli/hooks/harness.sh +26 -6
- package/spec-cli/src/attach.ts +2 -2
- package/spec-cli/src/cli.ts +695 -536
- package/spec-cli/src/client.ts +31 -30
- package/spec-cli/src/contract-filter.ts +1 -1
- package/spec-cli/src/doctor.ts +40 -13
- package/spec-cli/src/gateway.ts +11 -7
- package/spec-cli/src/git.ts +2 -2
- package/spec-cli/src/{board.ts → graph.ts} +44 -14
- package/spec-cli/src/{boardCache.ts → graphCache.ts} +41 -21
- package/spec-cli/src/{boardDelta.ts → graphDelta.ts} +1 -1
- package/spec-cli/src/graphStream.ts +288 -0
- package/spec-cli/src/guide.ts +123 -96
- package/spec-cli/src/harness-select.ts +2 -2
- package/spec-cli/src/harness.ts +30 -14
- package/spec-cli/src/help.ts +289 -384
- package/spec-cli/src/hooks.ts +1 -1
- package/spec-cli/src/index.ts +130 -103
- package/spec-cli/src/init.ts +9 -9
- package/spec-cli/src/issues.ts +89 -33
- package/spec-cli/src/layout.ts +5 -5
- package/spec-cli/src/lint.ts +73 -8
- package/spec-cli/src/localIssues.ts +42 -60
- package/spec-cli/src/materialize.ts +1 -1
- package/spec-cli/src/mentions.ts +15 -15
- package/spec-cli/src/migrate-table.ts +397 -0
- package/spec-cli/src/migrate.ts +386 -0
- package/spec-cli/src/ranker.ts +30 -4
- package/spec-cli/src/reaper.ts +117 -0
- package/spec-cli/src/search.bench.mjs +10 -10
- package/spec-cli/src/search.ts +1 -1
- package/spec-cli/src/sessions.ts +244 -138
- package/spec-cli/src/specs.ts +25 -15
- package/spec-cli/src/supervise.ts +2 -2
- package/spec-cli/src/tree.ts +5 -5
- package/spec-cli/src/uninstall.ts +4 -4
- package/spec-cli/templates/hooks/post-checkout +1 -1
- package/spec-cli/templates/hooks/post-merge +3 -3
- package/spec-cli/templates/hooks/pre-commit +9 -9
- package/spec-cli/templates/hooks/prepare-commit-msg +1 -1
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/idle/idle.sh +2 -2
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/mark-active/mark-active.sh +7 -0
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/mark-active/spec.md +2 -0
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/session-fail/fail.sh +1 -1
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-first/spec-first.sh +1 -1
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-of-file/spec-of-file.sh +2 -2
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +21 -0
- package/spec-cli/templates/spec/project/{.config → .plugins}/core/stop-gate/stop-gate.sh +21 -21
- package/spec-cli/templates/spec/project/{.config → .plugins}/distill/spec.md +2 -2
- package/spec-cli/templates/spec/project/{.config → .plugins}/extract/spec.md +4 -4
- package/spec-cli/templates/spec/project/{.config → .plugins}/forge-link/spec.md +1 -1
- package/spec-cli/templates/spec/project/{.config → .plugins}/regroup/spec.md +2 -2
- package/spec-cli/templates/spec/project/{.config → .plugins}/reproduce-before-fix/spec.md +3 -3
- package/spec-cli/templates/spec/project/{.config → .plugins}/spec.md +4 -4
- package/spec-cli/templates/spec/project/.plugins/supervisor/spec.md +8 -0
- package/spec-cli/templates/spec/project/{.config → .plugins}/tidy/spec.md +1 -1
- package/spec-cli/templates/spec/project/spec.md +1 -1
- package/spec-dashboard/dist/assets/{Dashboard-P0B9ukSG.js → Dashboard-C7Bzsv86.js} +9 -9
- package/spec-dashboard/dist/assets/EvalsPage-DKZZIdHq.js +2 -0
- package/spec-dashboard/dist/assets/{FoldToggle-BuQ0lokE.js → FoldToggle-D5iB4Ac2.js} +1 -1
- package/spec-dashboard/dist/assets/{IssuesPage-H-D8aHEl.js → IssuesPage-CMFTsQhg.js} +1 -1
- package/spec-dashboard/dist/assets/{MobileApp-oZXIeCPb.js → MobileApp-DwuTKgdP.js} +1 -1
- package/spec-dashboard/dist/assets/{SessionInterface-Blr_MEdU.js → SessionInterface-CBS5_cmK.js} +3 -3
- package/spec-dashboard/dist/assets/{SessionWindow-LcCzBMU7.js → SessionWindow-CqAnjWfI.js} +2 -2
- package/spec-dashboard/dist/assets/{Settings-_yOye-In.js → Settings-BW5f0OaW.js} +1 -1
- package/spec-dashboard/dist/assets/{index-uGs9v_9o.css → index-Cc26X4ce.css} +1 -1
- package/spec-dashboard/dist/assets/{index-BhIslAau.js → index-Ce0wDyQS.js} +8 -8
- package/spec-dashboard/dist/index.html +2 -2
- package/{spec-yatsu → spec-eval}/src/cache.ts +8 -5
- package/{spec-yatsu → spec-eval}/src/cli.ts +164 -95
- package/{spec-yatsu → spec-eval}/src/evaltab.ts +24 -24
- package/{spec-yatsu → spec-eval}/src/filing.ts +7 -5
- package/{spec-yatsu → spec-eval}/src/freshness.ts +44 -22
- package/{spec-yatsu → spec-eval}/src/scenariofresh.ts +22 -17
- package/{spec-yatsu/src/yatsu.ts → spec-eval/src/scenarios.ts} +41 -26
- package/{spec-yatsu/src/proof.ts → spec-eval/src/sessioneval.ts} +59 -59
- package/{spec-yatsu → spec-eval}/src/sidecar.ts +7 -1
- package/{spec-yatsu → spec-eval}/src/timeline.ts +1 -1
- package/spec-forge/src/__fixtures__/github-forge.json +9 -9
- package/spec-forge/src/cli.ts +14 -13
- package/spec-forge/src/{needs-yatsu-eval.ts → needs-eval.ts} +6 -6
- package/spec-cli/src/boardStream.ts +0 -179
- package/spec-cli/templates/spec/project/.config/core/spec.md +0 -17
- package/spec-cli/templates/spec/project/.config/supervisor/spec.md +0 -8
- package/spec-dashboard/dist/assets/EvalsPage-BrvAGyc4.js +0 -2
- /package/spec-cli/templates/presets/careful/{.config → .plugins}/clarify-before-code/spec.md +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/core/idle/spec.md +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/core/session-fail/spec.md +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-first/spec.md +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-of-file/spec.md +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/core/stop-gate/spec.md +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/distill/digest.mjs +0 -0
- /package/spec-cli/templates/spec/project/{.config → .plugins}/memory-hygiene/spec.md +0 -0
package/spec-cli/src/guide.ts
CHANGED
|
@@ -10,7 +10,8 @@ the rest, you don't hand-author the spec tree or wire the dashboard yourself.
|
|
|
10
10
|
the \`spexcode\` package itself, never the internal @spexcode/spec-cli. Both paths own the same
|
|
11
11
|
\`spex\` bin, so uninstall one before switching (\`npm rm -g spexcode\`; a legacy link of the
|
|
12
12
|
inner package uninstalls as \`@spexcode/spec-cli\`). A source link ships no
|
|
13
|
-
prebuilt dashboard dist — \`spex
|
|
13
|
+
prebuilt dashboard dist — \`spex serve ui\` builds it once on first run (needs spec-dashboard's
|
|
14
|
+
npm deps installed), or use the dev server.)
|
|
14
15
|
|
|
15
16
|
2. Adopt a repo
|
|
16
17
|
cd <your-repo> && spex init # seeds .spec/ + git hooks (additive, never overwrites)
|
|
@@ -18,13 +19,13 @@ the rest, you don't hand-author the spec tree or wire the dashboard yourself.
|
|
|
18
19
|
(each a dir with a spec.md + a \`code:\` list of the files it governs).
|
|
19
20
|
|
|
20
21
|
3. Run the backend — it reads .spec + git from the cwd repo
|
|
21
|
-
spex serve # http://localhost:8787 (
|
|
22
|
-
Serve a different repo by running it from there; two repos at once = two \`spex serve\` on two
|
|
22
|
+
spex serve # http://localhost:8787 (--port <n> for another endpoint)
|
|
23
|
+
Serve a different repo by running it from there; two repos at once = two \`spex serve\` on two ports.
|
|
23
24
|
|
|
24
|
-
4. Open the dashboard — the SAME
|
|
25
|
-
spex
|
|
25
|
+
4. Open the dashboard — the SAME dashboard for every project, pointed per project
|
|
26
|
+
spex serve ui # serves the bundled dashboard on :5173, proxying /api
|
|
26
27
|
Point it at another backend with --api-port (pairs with \`spex serve --port\`); one dashboard per
|
|
27
|
-
project. The
|
|
28
|
+
project. The dashboard is a viewer — which backend it proxies is the only "which project" knob.
|
|
28
29
|
Loopback-only by default: viewing from another machine needs \`--host 0.0.0.0\` (or a specific
|
|
29
30
|
interface) — still plain HTTP with no gate, so bind wide only on a LAN/tailnet you trust (for
|
|
30
31
|
the internet, use \`spex serve --public\` instead).
|
|
@@ -33,12 +34,12 @@ the rest, you don't hand-author the spec tree or wire the dashboard yourself.
|
|
|
33
34
|
|
|
34
35
|
5. Govern your layout (optional)
|
|
35
36
|
spexcode.json sets lint's governedRoots/sourceExtensions and any non-default worktree layout.
|
|
36
|
-
\`spex lint\` must report 0 errors; coverage warnings are your adoption TODO (files no node claims yet).
|
|
37
|
+
\`spex spec lint\` must report 0 errors; coverage warnings are your adoption TODO (files no node claims yet).
|
|
37
38
|
|
|
38
39
|
Look these up on demand — the formats an agent authors, and the settings it configures:
|
|
39
40
|
spex guide spec the spec.md format (frontmatter + body + the rules lint enforces)
|
|
40
|
-
spex guide
|
|
41
|
-
spex guide
|
|
41
|
+
spex guide eval the eval.md format (scenario schema + how loss is measured and filed)
|
|
42
|
+
spex guide settings the spexcode.json / spexcode.local.json settings (launchers, dashboard icon, lint
|
|
42
43
|
budgets, layout) — every field, and which of the two files it belongs in
|
|
43
44
|
spex guide footprint the footprint model — what SpexCode plants in a repo, and who sees it
|
|
44
45
|
(committed | ignored | hidden), and every migration recipe`
|
|
@@ -47,26 +48,30 @@ const SPEC = `spex guide spec — the spec.md file format
|
|
|
47
48
|
|
|
48
49
|
A spec node is a DIRECTORY under .spec/<project>/…/<id>/ holding a spec.md. The node's id is its leaf dir
|
|
49
50
|
name when that is globally unique, else the shortest parent-qualified path-suffix that disambiguates (so ids
|
|
50
|
-
are unique by construction) — the same id \`spex
|
|
51
|
+
are unique by construction) — the same id \`spex graph\`, \`spec ack\`, and a node/<id> branch use. A spec states a node's PRESENT
|
|
51
52
|
intent at CONTRACT altitude — what it guarantees and why — and is rewritten in place as intent changes;
|
|
52
53
|
version history is git's job, never a changelog in the body.
|
|
53
54
|
|
|
54
55
|
FRONTMATTER (YAML between the opening and closing --- lines; every field optional, sensible defaults):
|
|
55
56
|
title display name. Defaults to the dir id.
|
|
56
|
-
desc one-line summary shown on the
|
|
57
|
-
hue
|
|
57
|
+
desc one-line summary shown on the graph.
|
|
58
|
+
hue node colour on the graph, 0–360. Default 210.
|
|
58
59
|
status pending | active | merged | drift. Usually DERIVED from git state — rarely hand-set.
|
|
59
|
-
code:
|
|
60
|
-
|
|
60
|
+
code: the file this node GOVERNS (is source of truth for) — a YAML list, but AT MOST ONE entry
|
|
61
|
+
(the \`one-govern\` lint error otherwise: keep the true subject, move the rest to related:).
|
|
62
|
+
Drives drift + eval freshness. Many nodes MAY govern the same file (ordinary
|
|
61
63
|
composition); a file governed by > maxOwners nodes warns (the \`owners\` rule — split it). Omit
|
|
62
64
|
for a pure-prose node: a cross-cutting contract no file owns.
|
|
63
65
|
related: files this node REFERENCES but does not own — a YAML list, same path forms. Carries coverage
|
|
64
|
-
(never drift, never
|
|
66
|
+
(never drift, never eval freshness, nothing to ack); it is the many-to-many net that claims the files
|
|
65
67
|
govern doesn't. Every listed path must exist (lint integrity error otherwise).
|
|
66
|
-
surface
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
surface plugin-system/.plugins nodes only: one or MORE of system (folded into every agent's prompt) |
|
|
69
|
+
command (a /command) | skill (an on-demand SKILL.md the harness loads when a task matches the
|
|
70
|
+
node's desc) | agent (a spawnable sub-agent definition; its \`tools:\` list is the spawned
|
|
71
|
+
agent's tool allowlist) | hook (a lifecycle hook handler — a co-located script the dispatcher
|
|
72
|
+
runs on the harness events in events:, ordered by order:, blocking when block: true).
|
|
73
|
+
Comma-list several to plug into each. hook nodes may nest under a grouping
|
|
74
|
+
plugin (e.g. .plugins/core/<id>); surface is a field, discovered recursively.
|
|
70
75
|
events hook surface only: harness lifecycle events this node binds (YAML list — PreToolUse, Stop, …).
|
|
71
76
|
order hook surface only: integer; the dispatcher runs same-event hooks low to high.
|
|
72
77
|
block hook surface only: true if the hook may block its event (honored only on block-capable events).
|
|
@@ -75,31 +80,41 @@ BODY (Markdown after the frontmatter): the contract — intent, invariants, outw
|
|
|
75
80
|
code does it. Two optional level-2 headings split ground truth from detail:
|
|
76
81
|
## raw source human-authored, rarely-changed intent — the loss function's target.
|
|
77
82
|
## expanded spec agent-authored detail that must keep serving the raw source.
|
|
78
|
-
Bodies without those headings are read whole. Link sibling nodes with [[node-id]]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
WHAT lint CHECKS (spex lint; the pre-commit hook gates on errors):
|
|
82
|
-
integrity
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
83
|
+
Bodies without those headings are read whole. Link sibling nodes with [[node-id]] — every link must name
|
|
84
|
+
a REAL node (lint's mention rule; backtick a placeholder like \`[[node]]\` so it reads as sample text).
|
|
85
|
+
|
|
86
|
+
WHAT lint CHECKS (spex spec lint; the pre-commit hook gates on errors):
|
|
87
|
+
integrity (error) every code:/related: path exists.
|
|
88
|
+
one-govern (error) a node governs (code:) at most ONE file — keep the true subject, move the rest
|
|
89
|
+
to related:.
|
|
90
|
+
living (error) no "## vN" changelog headings — the body is current-state.
|
|
91
|
+
id-format (error) each id char is ascii [a-z0-9-] or a non-ascii unicode letter/number (CJK ok; one
|
|
92
|
+
optional leading dot; no space / '/' / '_' / uppercase Latin), and its leaf dir name
|
|
93
|
+
is unique tree-wide.
|
|
94
|
+
mention (error) every [[node-id]] in prose names a real node (fenced/backticked samples exempt).
|
|
95
|
+
altitude (warn) the body stays high-altitude: line/char budgets (~50 lines / 4200 chars), low
|
|
96
|
+
code-identifier density, no step-by-step phrasing. Over budget = rewrite higher.
|
|
97
|
+
breadth (warn) a node with >= maxChildren direct children (default 8) — altitude's structural
|
|
98
|
+
twin; is an intermediate grouping layer missing?
|
|
99
|
+
coverage (warn) every source file is claimed by ≥1 node — via code: OR related: (related is the net).
|
|
100
|
+
drift (warn) a governed file has commits newer than the node's spec version — it may be stale.
|
|
101
|
+
Remedy: edit the spec to the new intent (re-versions the node), OR \`spex spec ack <node>
|
|
102
|
+
--reason "…"\` when only mechanics changed and the contract still holds.
|
|
103
|
+
related-drift (warn) a related: file moved ahead of the node — a soft nudge, one summary line, never blocks.
|
|
104
|
+
owners (warn) a file governed by > maxOwners nodes (default 3) does too much — SPLIT it so each
|
|
105
|
+
governor owns its own module (or merge the nodes, or give it one foundation owner).
|
|
106
|
+
confusable-id (warn) two leaf ids one edit apart read as the same word — rename one to read apart.
|
|
107
|
+
|
|
108
|
+
LIFECYCLE: author each node on a node/<id> branch, one node per commit; \`spex spec lint\` must reach 0 errors
|
|
109
|
+
before merge. \`spex init\` seeds the first tree; \`spex guide eval\` covers the sibling eval.md, the measurement file.`
|
|
110
|
+
|
|
111
|
+
const EVAL = `spex guide eval — the eval.md file format
|
|
112
|
+
|
|
113
|
+
An eval.md sits BESIDE a node's spec.md and says how to MEASURE the node's loss — the gap between live
|
|
99
114
|
behaviour and the spec. It is optional, but a node that governs SOURCE code (its code: includes a file whose extension is in
|
|
100
|
-
\`lint.sourceExtensions\` — default .ts/.tsx/.js/.jsx, set it for a Rust/Go/Python tree) with no
|
|
101
|
-
a blind spot: \`spex
|
|
102
|
-
agent measures;
|
|
115
|
+
\`lint.sourceExtensions\` — default .ts/.tsx/.js/.jsx, set it for a Rust/Go/Python tree) with no eval.md is
|
|
116
|
+
a blind spot: \`spex eval lint\` flags it \`eval-coverage\`. The eval system defines no DSL and RUNS
|
|
117
|
+
NOTHING — the agent measures; eval keeps score.
|
|
103
118
|
|
|
104
119
|
FRONTMATTER: a \`scenarios:\` list (a YAML block sequence of mappings). Each scenario:
|
|
105
120
|
name REQUIRED. Unique within the file — it keys the sidecar and \`--scenario <name>\`.
|
|
@@ -111,28 +126,29 @@ FRONTMATTER: a \`scenarios:\` list (a YAML block sequence of mappings). Each sce
|
|
|
111
126
|
use an existing one, or add it to the library to mint it. Tags classify a scenario (surface,
|
|
112
127
|
device) so it can be filtered and, later, routed to the right driver.
|
|
113
128
|
test optional. A repo path to a co-located runnable file (a playwright.spec.ts, a script)
|
|
114
|
-
the agent MAY run by hand. Not a driver —
|
|
129
|
+
the agent MAY run by hand. Not a driver — eval never executes it.
|
|
115
130
|
code optional. The file THIS scenario GOVERNS, ideally one (a comma list / flow list \`[a, b]\` is
|
|
116
131
|
allowed) — its own slice of the code freshness axis, so scenarios on one node go stale
|
|
117
132
|
independently. Absent → it inherits the node's \`code:\` list. A file governed by > maxOwners
|
|
118
|
-
scenarios warns \`
|
|
133
|
+
scenarios warns \`eval-owners\` (split it). Each path must exist (a ghost → \`eval-schema\`).
|
|
119
134
|
related optional. Files this scenario REFERENCES but does not govern — same path forms. They do NOT
|
|
120
135
|
stale it (the freshness mirror of a spec node's govern/related). Each path must exist.
|
|
121
136
|
Multi-line prose uses YAML block scalars: \`|\` keeps newlines, \`>\` folds wrapped lines to spaces.
|
|
122
|
-
|
|
137
|
+
An eval.md OWNS nothing — only its scenarios govern and relate (see governed-related).
|
|
123
138
|
|
|
124
139
|
THE SCHEMA IS ENFORCED (closed field set, four required fields, unique names, tags within the library). A
|
|
125
140
|
missing required field, an unknown key (a typo like \`descripton:\`), a duplicate name, an out-of-library
|
|
126
|
-
tag, or no scenarios at all is rejected LOUD: \`spex
|
|
127
|
-
pre-commit \`
|
|
141
|
+
tag, or no scenarios at all is rejected LOUD: \`spex eval lint\` reports it as \`eval-schema\`, and the
|
|
142
|
+
pre-commit \`internal check-staged\` BLOCKS the commit.
|
|
128
143
|
|
|
129
144
|
BODY (after the frontmatter): prose naming the measurement method — YATU ("You As The User"): the agent
|
|
130
|
-
looks at / calls the real product surface, not an internal helper chosen to make the
|
|
145
|
+
looks at / calls the real product surface, not an internal helper chosen to make the evidence easy.
|
|
131
146
|
|
|
132
147
|
MEASURING AND FILING: the agent runs the scenario however it likes (a browser run, an API
|
|
133
148
|
transcript, a by-hand pass), compares the result to \`expected\`, and files it:
|
|
134
|
-
spex
|
|
135
|
-
[--image <png> …repeatable] [--result <txt>|-] [--video <webm|mp4> [--timeline <json>]
|
|
149
|
+
spex eval add <node> [--scenario <name>] (--pass | --fail) [--note <text>]
|
|
150
|
+
[--image <png> …repeatable] [--result <txt>|-] [--video <webm|mp4>] [--timeline <json>]
|
|
151
|
+
(--scenario may be omitted only when the node declares exactly one scenario.)
|
|
136
152
|
The verdict is \`--pass\` or \`--fail\` (a measurement must commit to one — an unmeasured scenario is \`missing\`,
|
|
137
153
|
not a hedged fail). \`--note <text>\` is an OPTIONAL one-line annotation on either (why it failed, how far a
|
|
138
154
|
pass sits from ideal); it does NOT replace evidence — the image/video/transcript is the captured actual behaviour.
|
|
@@ -145,10 +161,11 @@ PICK THE EVIDENCE KIND BY WHAT THE BEHAVIOUR DOES OVER TIME:
|
|
|
145
161
|
— never a value the agent eyeballs off the finished artefact afterwards (that's
|
|
146
162
|
misaligned and dishonest, worse than none). \`--timeline <json>\` carries one; its \`axis\`
|
|
147
163
|
is the evidence's: a video is \`time\` (ms), a transcript \`line\`, a still SEQUENCE \`frame\`,
|
|
148
|
-
|
|
164
|
+
a structured data export \`index\` (record ordinals; the format's axis set is open — an
|
|
165
|
+
unknown axis just renders as a bare number).
|
|
149
166
|
\`at\` = the position on that axis, \`step\` = a short name for that moment; copy this shape:
|
|
150
167
|
{ "v": 2, "axis": "time",
|
|
151
|
-
"events": [ { "at": 0, "step": "open
|
|
168
|
+
"events": [ { "at": 0, "step": "open graph" },
|
|
152
169
|
{ "at": 1200, "step": "type query" } ] }
|
|
153
170
|
The run exports it: in whatever drives the evidence — Playwright, a computer-use hand, a
|
|
154
171
|
CLI harness stamping line numbers — take a baseline and at EACH real step push
|
|
@@ -157,36 +174,40 @@ PICK THE EVIDENCE KIND BY WHAT THE BEHAVIOUR DOES OVER TIME:
|
|
|
157
174
|
skip it for a short single-step artefact. (Legacy \`{ "v": 1, "events": [{ "tMs" }] }\` — the
|
|
158
175
|
time axis with \`tMs\` — is still accepted, read as \`axis: "time"\`.)
|
|
159
176
|
STATIC end state → \`--image <png>\` (repeatable — N stills). Layout, an icon, copy, one rendered frame.
|
|
160
|
-
backend / CLI → \`--result <txt>\` (a transcript; \`-\` reads stdin). A STRUCTURED export (a
|
|
161
|
-
\`--export-json
|
|
177
|
+
backend / CLI → \`--result <txt>\` (a transcript; \`-\` reads stdin). A STRUCTURED export (a tool's
|
|
178
|
+
\`--export-json\` dump, an API payload, a metrics dump) is recognized BY CONTENT and kept as
|
|
162
179
|
\`data\` — rendered as a validatable data block, not flattened into scrolling transcript
|
|
163
180
|
text; free-form output stays a transcript. You pick the flag; the KIND follows the bytes.
|
|
164
181
|
The flags combine in ONE filing — several stills can ride beside the clip of the same run.
|
|
165
|
-
ANCHOR DISCIPLINE:
|
|
182
|
+
ANCHOR DISCIPLINE: an eval's \`codeSha\` is HEAD at filing time, and a git sha names only a COMMIT — an
|
|
166
183
|
uncommitted change has none. So measure the tree you are about to commit, COMMIT it, then file; confidence
|
|
167
184
|
is earned on the working tree, but the anchor can only land after the commit. Filing from a dirty tree
|
|
168
|
-
mis-anchors the
|
|
185
|
+
mis-anchors the eval (its sha lacks the change it measured) and it goes stale the moment you commit.
|
|
169
186
|
|
|
170
187
|
A botched filing (a junk e2e/smoke run, a wrong verdict) is undone through the SAME surface:
|
|
171
|
-
spex
|
|
188
|
+
spex eval retract <node> [--scenario <name>] [--last | --ts <iso>] [--note <why>]
|
|
172
189
|
retract APPENDS a retraction event to the sidecar (never deletes a line — the trace stays, git records
|
|
173
|
-
who/when/why); the scoreboard then drops the retracted
|
|
190
|
+
who/when/why); the scoreboard then drops the retracted eval everywhere: the previous eval becomes
|
|
174
191
|
the latest again, or the scenario honestly returns to \`missing\`. Default target is the scenario's latest
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
THE SCOREBOARD:
|
|
178
|
-
(a second git-as-database axis). Freshness is derived live from git:
|
|
179
|
-
code file or the scenario (the
|
|
180
|
-
spex
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
eval (\`--last\` makes that explicit; repeat to peel junk back one filing at a time); \`--ts\` pins one.
|
|
193
|
+
|
|
194
|
+
THE SCOREBOARD: evals live in evals.ndjson beside the eval.md — one JSON line per measurement
|
|
195
|
+
(a second git-as-database axis). Freshness is derived live from git: an eval goes STALE when a governed
|
|
196
|
+
code file or the scenario (the eval.md) moves since it was filed.
|
|
197
|
+
spex eval lint [--changed] the measurement layer's findings — PURE ADVISORY, always exit 0 (spec
|
|
198
|
+
lint's errors block commits; a measurement gap never blocks anyone):
|
|
199
|
+
eval-schema (malformed) · eval-drift (stale) · eval-missing (never
|
|
200
|
+
measured) · eval-dangling (orphaned remark track) · eval-coverage
|
|
201
|
+
(governed source, no eval.md — spec lint's coverage, one rule per layer) ·
|
|
202
|
+
eval-owners (a file governed by > maxOwners scenarios — split it)
|
|
203
|
+
spex eval ls <node> the eval timeline (verdict · freshness · evidence), newest first
|
|
204
|
+
spex eval scenario ls [<node>] the declared contracts; --unmeasured = the blind-spot worklist
|
|
205
|
+
spex eval clean GC the content-addressed evidence cache`
|
|
206
|
+
|
|
207
|
+
const SETTINGS = `spex guide settings — SpexCode's runtime settings (spexcode.json / spexcode.local.json)
|
|
187
208
|
|
|
188
209
|
SpexCode reads its runtime settings from TWO optional JSON files at the repo root. There is no imperative
|
|
189
|
-
|
|
210
|
+
settings verb — an agent CONFIGURES SpexCode by EDITING these files directly. The two split by
|
|
190
211
|
PORTABILITY, and picking the right one is the whole discipline:
|
|
191
212
|
|
|
192
213
|
spexcode.json COMMITTED — portable, shared by everyone on the repo. Layout, policy, dashboard
|
|
@@ -200,7 +221,7 @@ Rule of thumb — is the value TRUE FOR THE PROJECT or TRUE FOR THIS MACHINE? A
|
|
|
200
221
|
icon, a lint budget, a launcher's name+harness are project facts → committed spexcode.json. The ABSOLUTE
|
|
201
222
|
PATH of a launcher wrapper or a TLS cert path are machine facts → gitignored spexcode.local.json.
|
|
202
223
|
Both files are optional; omit any field to take its default, except \`sessions.defaultLauncher\` when using
|
|
203
|
-
\`spex new\` or the dashboard without an explicit launcher choice.
|
|
224
|
+
\`spex session new\` or the dashboard without an explicit launcher choice.
|
|
204
225
|
|
|
205
226
|
MERGE: spexcode.local.json is layered over spexcode.json ONE LEVEL DEEP — per top-level section (dashboard,
|
|
206
227
|
sessions, …), the two objects are shallow-merged with LOCAL WINNING per key; sections only one file names
|
|
@@ -218,14 +239,16 @@ Example — a repo whose trunk is \`staging\`, not \`main\`:
|
|
|
218
239
|
── DASHBOARD (spexcode.json — portable project identity) ──
|
|
219
240
|
dashboard.title browser-tab name. Default: the repo-root basename.
|
|
220
241
|
dashboard.icon browser-tab favicon: an emoji ("🔭") OR an Iconify name ("mdi:rocket-launch").
|
|
221
|
-
dashboard.apiUrl the per-project backend the
|
|
222
|
-
install prefer the API_URL env var; apiUrl here is the default only when the
|
|
242
|
+
dashboard.apiUrl the per-project backend the dashboard proxies to (read frontend-side). For a SHARED
|
|
243
|
+
install prefer the API_URL env var; apiUrl here is the default only when the dashboard
|
|
223
244
|
lives inside the project.
|
|
224
245
|
Example:
|
|
225
246
|
{ "dashboard": { "title": "MyApp specs", "icon": "mdi:rocket-launch" } }
|
|
226
247
|
|
|
227
248
|
── SESSIONS / WORKERS ──
|
|
228
|
-
sessions.maxActive concurrency cap — max agents AUTONOMOUSLY PROGRESSING at once (default 8
|
|
249
|
+
sessions.maxActive concurrency cap — max agents AUTONOMOUSLY PROGRESSING at once (default 8;
|
|
250
|
+
precedence: spexcode.json → SPEXCODE_MAX_ACTIVE env → default; read live, so
|
|
251
|
+
an edit applies without a restart).
|
|
229
252
|
Counts compute slots, not total sessions: idle/asking/review/done do not
|
|
230
253
|
occupy one. A policy number → committed spexcode.json; omit it to use the
|
|
231
254
|
default, or tune higher/lower for the project's usual host.
|
|
@@ -265,12 +288,13 @@ in the committed spexcode.json — the merge keeps both:
|
|
|
265
288
|
}
|
|
266
289
|
}
|
|
267
290
|
|
|
268
|
-
── SERVE (spexcode.json — public-exposure for \`spex serve --public
|
|
291
|
+
── SERVE (spexcode.json ONLY — public-exposure for \`spex serve --public\`; this section is read straight
|
|
292
|
+
from spexcode.json, the local overlay is NOT consulted here) ──
|
|
269
293
|
serve.public.enabled turn public mode on without the --public flag.
|
|
270
294
|
serve.public.http drop TLS (the --http escape hatch) — the password then travels in cleartext.
|
|
271
295
|
serve.public.tls { "cert": "<path>", "key": "<path>" } — PATHS to your own cert/key; omit for a
|
|
272
|
-
cached self-signed default.
|
|
273
|
-
|
|
296
|
+
cached self-signed default. Host-specific paths can instead ride the
|
|
297
|
+
--tls-cert/--tls-key flags or SPEXCODE_TLS_CERT/SPEXCODE_TLS_KEY env (which win).
|
|
274
298
|
The gateway password is NEVER read from these files (flag/env only), so serve.public stays committable.
|
|
275
299
|
|
|
276
300
|
── BACKEND ROUTING (not a config field — how a \`spex\` command picks its backend) ──
|
|
@@ -286,20 +310,23 @@ resolves its backend per this ladder, flag first:
|
|
|
286
310
|
health-probes before trusting (a dead record is ignored).
|
|
287
311
|
3. the other side as fallback (human with no live record → env; worker with no env → record).
|
|
288
312
|
4. default http://127.0.0.1:$PORT||8787.
|
|
289
|
-
WRITES are project-bound: every mutating verb (new/merge/send/close/rename/
|
|
313
|
+
WRITES are project-bound: every mutating verb (new/merge/send/close/rename/resume/stop) refuses
|
|
290
314
|
loudly when the resolved backend serves a DIFFERENT same-host project — an explicit --api/--port skips
|
|
291
|
-
the guard (the flag is the
|
|
315
|
+
the guard (the flag is the declaration of intent). Reads point anywhere.
|
|
292
316
|
|
|
293
317
|
── ISSUES (spexcode.json — portable policy) ──
|
|
294
318
|
issues.enabled the issues-workflow on/off switch (default ON). OFF silences the post-merge nudge and
|
|
295
|
-
hides the dashboard view
|
|
319
|
+
hides the dashboard view. Flip it by editing the JSON — there is no CLI toggle verb;
|
|
320
|
+
\`spex doctor\` reports the current state (and flags a legacy \`proposals.enabled\` key,
|
|
321
|
+
which is no longer read).
|
|
296
322
|
|
|
297
323
|
── FORGE (spexcode.json — which forge this repo's remote is; a project fact, so committed) ──
|
|
298
324
|
forge.host explicit forge host id ('github' | 'gitlab' | …) overriding the automatic derivation.
|
|
299
325
|
Normally OMIT it: spec-forge resolves the host from the origin remote's hostname —
|
|
300
|
-
github.com → github,
|
|
301
|
-
self-hosted domain the heuristic misreads needs the override.
|
|
302
|
-
registered driver degrades to an EMPTY forge slice (local
|
|
326
|
+
github.com → github, bitbucket → bitbucket, any other remote → gitlab (the common
|
|
327
|
+
self-hosted shape) — and only a domain the heuristic misreads needs the override.
|
|
328
|
+
A resolved host with no registered driver degrades to an EMPTY forge slice (local
|
|
329
|
+
issues still work, no error).
|
|
303
330
|
|
|
304
331
|
── LINT (spexcode.json — a top-level "lint" key; budgets are portable, so committed only) ──
|
|
305
332
|
lint.governedRoots dirs whose source files must each be governed by a spec (coverage).
|
|
@@ -314,13 +341,13 @@ the guard (the flag is the proof of intent). Reads point anywhere.
|
|
|
314
341
|
lint.driftErrorThreshold commit-local gate HARD-BLOCKS a commit touching a node >= this many commits
|
|
315
342
|
behind (default 3).
|
|
316
343
|
lint.maxOwners warn when a file is governed by > this many nodes (default 3).
|
|
317
|
-
lint.scenarioTags the closed vocabulary
|
|
344
|
+
lint.scenarioTags the closed vocabulary an eval scenario's tags: must draw from (default
|
|
318
345
|
["frontend-e2e","backend-api","cli","desktop","mobile"]); extend to mint a tag.
|
|
319
346
|
Example — govern your own source dir and loosen the altitude budget:
|
|
320
347
|
{ "lint": { "governedRoots": ["src"], "altitude": { "lineBudget": 70 } } }
|
|
321
348
|
|
|
322
349
|
── OTHER (spexcode.json unless noted) ──
|
|
323
|
-
preset the SELECTED init preset — which cumulative .
|
|
350
|
+
preset the SELECTED init preset — which cumulative .plugins tier \`spex init\` seeds (default
|
|
324
351
|
'default'; seed-time only, read by init.ts).
|
|
325
352
|
harnesses which harness targets \`spex materialize\` delivers into — native ids ("claude"|"codex") or a
|
|
326
353
|
{ "plugin": "<folder>" } bundle. Default (omitted): all native harnesses. PERSISTENT and
|
|
@@ -333,18 +360,18 @@ const FOOTPRINT = `spex guide footprint — what SpexCode plants in a repo, and
|
|
|
333
360
|
SpexCode claims software engineering's HEAD (the recording of intent) and TAIL (the storage of
|
|
334
361
|
measurement) and leaves the MIDDLE — construction — to the harness/agent/test framework; freshness
|
|
335
362
|
stitches the two ends into a closed loop. The footprint follows: the head+tail (.spec, spexcode.json,
|
|
336
|
-
|
|
363
|
+
evals) is the ASSET and lives in git like source; everything else is derived wiring or a machine fact.
|
|
337
364
|
Materialized artifacts carry no facts, so they are NEVER tracked — there is exactly one residence
|
|
338
365
|
behavior, decided per KIND (and, for a contract file, by its live CONTENT).
|
|
339
366
|
|
|
340
367
|
── THE FOUR KINDS (all fixed) ──
|
|
341
|
-
spec data .spec/ (incl .
|
|
368
|
+
spec data .spec/ (incl .plugins/) + spexcode.json — ALWAYS tracked. Git is the database; there is
|
|
342
369
|
deliberately NO way to say "untrack the spec" in this schema.
|
|
343
370
|
machine facts spexcode.local.json, the hook shims (.claude/settings.json, .codex/hooks.json), plugin
|
|
344
371
|
bundles — NEVER tracked; always in the per-clone exclude.
|
|
345
372
|
artifacts the CLAUDE.md/AGENTS.md contract blocks + materialized skills/agents — derived, NEVER
|
|
346
373
|
tracked; hidden via .git/info/exclude. The host's tracked .gitignore is never touched.
|
|
347
|
-
run residue .worktrees/, the global store (~/.spexcode), .git/spexcode
|
|
374
|
+
run residue .worktrees/, the global store (~/.spexcode), .git/spexcode evidence — never tracked;
|
|
348
375
|
out-of-tree, or exclude-ruled where in-tree.
|
|
349
376
|
|
|
350
377
|
── A CONTRACT FILE'S RESIDENCE IS A LIVE CONTENT FACT (re-judged at every materialize) ──
|
|
@@ -360,10 +387,10 @@ behavior, decided per KIND (and, for a contract file, by its live CONTENT).
|
|
|
360
387
|
── THE GIT-NATIVE ANCHORS (no harness event ever triggers a materialize) ──
|
|
361
388
|
spex init / spex materialize / session-worktree creation — the explicit passes;
|
|
362
389
|
pre-commit the correctness anchor: an UNCONDITIONAL materialize (masks provably fresh at the only
|
|
363
|
-
moment history is written) + staged-index surgery — a staged
|
|
364
|
-
block is cleaned IN PLACE (partial staging survives; source is the staged
|
|
390
|
+
moment history is written) + staged-index surgery — a staged file carrying the sentinel
|
|
391
|
+
block is cleaned IN PLACE (partial staging survives; source is the staged content), a
|
|
365
392
|
HEAD-untracked generated artifact is unstaged. Repairs and proceeds, never rejects.
|
|
366
|
-
post-checkout/post-merge freshness anchors: .spec/.
|
|
393
|
+
post-checkout/post-merge freshness anchors: .spec/.plugins edits are git-transactional — they take
|
|
367
394
|
effect at the commit/checkout/merge that carries them, like any other source change.
|
|
368
395
|
An environment with no spex-planted hooks (CI, a cloud agent's fresh clone, a teammate who hasn't
|
|
369
396
|
installed) simply runs \`spex materialize\` in its setup step — there is no committed-artifact mode.
|
|
@@ -373,7 +400,7 @@ TRACK ≠ PUSH: none of this ever touches remotes; where commits GO is branch/re
|
|
|
373
400
|
materialize(P₂) ∘ materialize(P₁) = materialize(P₂): every materialize first ERASES all landing points by
|
|
374
401
|
SpexCode's own identity stamps, then re-asserts — legacy states (a .gitignore managed block, a committed
|
|
375
402
|
artifact) are forgotten by the same pass. \`spex uninstall\` is the empty
|
|
376
|
-
materialize plus the global store: a total backout that never touches your .spec/.
|
|
403
|
+
materialize plus the global store: a total backout that never touches your .spec/.plugins or prose. Fresh
|
|
377
404
|
clones and session worktrees are self-sufficient: data by checkout, materialized artifacts by
|
|
378
405
|
re-materialize, the machine snapshot (spexcode.local.json) by copy.
|
|
379
406
|
|
|
@@ -393,7 +420,7 @@ remote, give it a different git HOME instead of untracking it. The manual recipe
|
|
|
393
420
|
(e.g. .git/spexcode/<name>.git) — the dir then holds only a one-line .git pointer file, so the
|
|
394
421
|
spec loader never walks an object store;
|
|
395
422
|
4. commit the node's changes through that inner repo.
|
|
396
|
-
The effect, honestly: filesystem-derived surfaces see the node (
|
|
423
|
+
The effect, honestly: filesystem-derived surfaces see the node (graph, search, lint); git-derived
|
|
397
424
|
views are blind to it (version count, the history tab, drift), and a dispatched worker's worktree
|
|
398
425
|
checkout does not contain it. Those gaps are what the pending spec-local design (a first-class
|
|
399
426
|
private overlay root) closes — not built yet. Cautions: \`git clean -fdx\` in the outer repo deletes
|
|
@@ -407,7 +434,7 @@ not a flag flip.
|
|
|
407
434
|
elsewhere cannot be recalled.
|
|
408
435
|
back out entirely \`spex uninstall\` (add --hooks to also remove the spexcode git hooks).`
|
|
409
436
|
|
|
410
|
-
const TOPICS: Record<string, string> = { spec: SPEC,
|
|
437
|
+
const TOPICS: Record<string, string> = { spec: SPEC, eval: EVAL, settings: SETTINGS, footprint: FOOTPRINT }
|
|
411
438
|
|
|
412
439
|
// every guide page ends by naming the OTHER help layer, so a reader never dead-ends here: guide is
|
|
413
440
|
// the skill layer (workflows · formats · settings); command usage lives in help.ts's two layers.
|
|
@@ -30,14 +30,14 @@ export function resolveHarnessTargets(raw: unknown): HarnessTarget[] {
|
|
|
30
30
|
for (const m of raw) {
|
|
31
31
|
if (typeof m === 'string') {
|
|
32
32
|
if (m === 'plugin')
|
|
33
|
-
throw new Error(`spexcode.json "harnesses": a plugin target needs an EXPLICIT landing folder — write {"plugin":"<folder>"} (e.g. {"plugin":".zcode"}), not the bare string "plugin", because each host agent
|
|
33
|
+
throw new Error(`spexcode.json "harnesses": a plugin target needs an EXPLICIT landing folder — write {"plugin":"<folder>"} (e.g. {"plugin":".zcode"}), not the bare string "plugin", because each host agent reads a different plugins dir.`)
|
|
34
34
|
if (!KNOWN.includes(m))
|
|
35
35
|
throw new Error(`spexcode.json "harnesses": unknown harness id "${m}" — known native ids are ${KNOWN.join(', ')}, or use {"plugin":"<folder>"}.`)
|
|
36
36
|
targets.push({ kind: 'native', id: m as HarnessId })
|
|
37
37
|
} else if (m && typeof m === 'object' && !Array.isArray(m) && 'plugin' in m) {
|
|
38
38
|
const folder = (m as { plugin?: unknown }).plugin
|
|
39
39
|
if (typeof folder !== 'string' || !folder.trim())
|
|
40
|
-
throw new Error(`spexcode.json "harnesses": a {"plugin":…} target needs a NON-EMPTY folder string (e.g. {"plugin":".zcode"}) — each host agent
|
|
40
|
+
throw new Error(`spexcode.json "harnesses": a {"plugin":…} target needs a NON-EMPTY folder string (e.g. {"plugin":".zcode"}) — each host agent reads a different plugins dir, so the folder must be explicit.`)
|
|
41
41
|
targets.push({ kind: 'plugin', folder: folder.trim() })
|
|
42
42
|
} else {
|
|
43
43
|
throw new Error(`spexcode.json "harnesses": each member must be a native id string (${KNOWN.join(', ')}) or a {"plugin":"<folder>"} object — got ${JSON.stringify(m)}.`)
|
package/spec-cli/src/harness.ts
CHANGED
|
@@ -25,10 +25,17 @@ import { git } from './git.js'
|
|
|
25
25
|
export type HarnessId = 'claude' | 'codex'
|
|
26
26
|
export type HarnessLivenessRecord = { session: string; harnessSessionId?: string | null }
|
|
27
27
|
// the per-pane runtime probe the caller snapshots ONCE for the whole session list and hands liveness():
|
|
28
|
-
// the pane's root pid (tmux `#{pane_pid}`)
|
|
29
|
-
//
|
|
28
|
+
// the pane's root pid (tmux `#{pane_pid}`), the hot-tier `pidAlive` verdict, and — ONLY on the legacy path —
|
|
29
|
+
// one whole-box pid→(ppid, comm) table (a single `ps` spawn).
|
|
30
|
+
// `pidAlive` = the hot registry's verdict for THIS session's launch-registered `agent.pid`: true = the pid
|
|
31
|
+
// answers kill-0 (alive), false = proven dead (ESRCH, permanently latched per pid-reuse guard), undefined =
|
|
32
|
+
// NO agent.pid file (a pre-registration/old session). codex reads this as its liveness truth when present
|
|
33
|
+
// and falls back to `procs` (the whole-box tree walk) only when it is undefined; claude ignores it (its
|
|
34
|
+
// truth is the rendezvous socket).
|
|
35
|
+
// `procs` is gathered (the single `ps` spawn) ONLY when a pid-less codex session still needs the legacy
|
|
36
|
+
// tree-walk, so a box with no codex — or all pid-registered launches — never pays for it.
|
|
30
37
|
export type ProcTable = Map<number, { ppid: number; comm: string }>
|
|
31
|
-
export type PaneProbe = { panePid?: number; procs?: ProcTable }
|
|
38
|
+
export type PaneProbe = { panePid?: number; procs?: ProcTable; pidAlive?: boolean }
|
|
32
39
|
|
|
33
40
|
export interface Harness {
|
|
34
41
|
readonly id: HarnessId
|
|
@@ -116,11 +123,13 @@ export interface Harness {
|
|
|
116
123
|
// live listener (the caller probes all windowed sessions once per snapshot). The adapter adds only its own
|
|
117
124
|
// channel check. claude: online iff the window is up AND its reclaude rendezvous socket has a live LISTENER
|
|
118
125
|
// (`socketLive` — a connect that a live claude accepts and a stale socket FILE refuses; claude IGNORES the
|
|
119
|
-
// pane probe). codex: online iff the window is up AND
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
126
|
+
// pane probe). codex: online iff the window is up AND the launch-registered `agent.pid` is alive
|
|
127
|
+
// (`pane.pidAlive`, the hot-tier kill-0 verdict — zero ps scan); a pre-registration session with no agent.pid
|
|
128
|
+
// (`pidAlive` undefined) falls back to the LEGACY whole-box tree walk — a codex-ish process (`codex` by any
|
|
129
|
+
// name, or the `node` its CLI runs under) live in the pane pid's DESCENDANT tree, NOT the pane's foreground
|
|
130
|
+
// command name (that is `bash`, the launch wrapper, even while the TUI renders — field-confirmed), and NOT the
|
|
131
|
+
// SHARED per-project app-server socket (it stays bound after a failed `--remote resume` dropped the pane back
|
|
132
|
+
// to the shell). A missing probe (tmux/ps couldn't report) is not-live. The 'starting' boot
|
|
124
133
|
// grace lives in the caller (sessions.ts liveness), so a still-booting pane reads starting, not offline.
|
|
125
134
|
liveness(rec: HarnessLivenessRecord, tmuxAlive: boolean, runtimeDir?: string, pane?: PaneProbe, socketLive?: boolean): 'online' | 'offline'
|
|
126
135
|
// deliver a follow-up prompt to a LIVE session and report whether it landed. claude: through the rendezvous
|
|
@@ -1042,11 +1051,18 @@ export const codexHarness: Harness = {
|
|
|
1042
1051
|
removeTrust: (proj) => removeCodexTrust(mainCheckout(proj)),
|
|
1043
1052
|
clean(proj, arts) { cleanHarness(this, proj, arts) },
|
|
1044
1053
|
slashCommands: codexSlashCommands,
|
|
1045
|
-
// online iff the tmux window is up AND
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1049
|
-
|
|
1054
|
+
// online iff the tmux window is up AND the agent is live. PRIMARY: the launch-registered `agent.pid` hot-tier
|
|
1055
|
+
// verdict (`pidAlive`) — a 100ms syscall (kill-0), no ps scan. LEGACY: a pre-registration session has no
|
|
1056
|
+
// agent.pid (`pidAlive` undefined) → fall back to the whole-box ps DESCENDANT-tree walk (paneTreeRunsCodex):
|
|
1057
|
+
// a codex-ish process live below the pane pid, NOT the pane's foreground command (that is `bash`, the launch
|
|
1058
|
+
// wrapper, even while the TUI renders — the field-confirmed false-OFFLINE) and NOT the app-server socket
|
|
1059
|
+
// (SHARED per-project, it survives a failed `--remote resume` — the earlier false-ONLINE). The legacy path
|
|
1060
|
+
// self-extinguishes as pre-registration sessions close.
|
|
1061
|
+
liveness: (_rec, tmuxAlive, _runtimeDir, pane) => {
|
|
1062
|
+
if (!tmuxAlive) return 'offline'
|
|
1063
|
+
if (pane?.pidAlive !== undefined) return pane.pidAlive ? 'online' : 'offline'
|
|
1064
|
+
return paneTreeRunsCodex(pane) ? 'online' : 'offline'
|
|
1065
|
+
},
|
|
1050
1066
|
deliver: (rec, text) => deliverViaCodexAppServer(rec, text),
|
|
1051
1067
|
// owned thread id → `--resume <id>` MARKER the codex launch script reads to resume that thread DIRECTLY (NOT
|
|
1052
1068
|
// a tail handed to a bare `codex` — the script's final `codex … resume "$tid"` performs codex's own resume on
|
|
@@ -1088,7 +1104,7 @@ export function launcherList(root = mainCheckout()): Launcher[] {
|
|
|
1088
1104
|
export const MISSING_DEFAULT_LAUNCHER_ERROR =
|
|
1089
1105
|
'sessions.defaultLauncher is required for a launch without --launcher; set it in spexcode.json or spexcode.local.json (for example {"sessions":{"defaultLauncher":"claude"}})'
|
|
1090
1106
|
|
|
1091
|
-
// the configured default launcher NAME ([[launcher-select]]) — the profile `spex new`/a dropdown pick with no
|
|
1107
|
+
// the configured default launcher NAME ([[launcher-select]]) — the profile `spex session new`/a dropdown pick with no
|
|
1092
1108
|
// explicit choice resolves. Missing config is a fail-loud setup error, never an implicit fallthrough to a
|
|
1093
1109
|
// `claude` launcher (which `spex init` seeds by name, so a default can point at it explicitly).
|
|
1094
1110
|
export function defaultLauncher(root = mainCheckout()): string {
|