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.
Files changed (97) hide show
  1. package/README.md +56 -44
  2. package/package.json +3 -3
  3. package/spec-cli/bin/spex.mjs +2 -2
  4. package/spec-cli/hooks/dispatch.sh +1 -1
  5. package/spec-cli/hooks/harness.sh +26 -6
  6. package/spec-cli/src/attach.ts +2 -2
  7. package/spec-cli/src/cli.ts +695 -536
  8. package/spec-cli/src/client.ts +31 -30
  9. package/spec-cli/src/contract-filter.ts +1 -1
  10. package/spec-cli/src/doctor.ts +40 -13
  11. package/spec-cli/src/gateway.ts +11 -7
  12. package/spec-cli/src/git.ts +2 -2
  13. package/spec-cli/src/{board.ts → graph.ts} +44 -14
  14. package/spec-cli/src/{boardCache.ts → graphCache.ts} +41 -21
  15. package/spec-cli/src/{boardDelta.ts → graphDelta.ts} +1 -1
  16. package/spec-cli/src/graphStream.ts +288 -0
  17. package/spec-cli/src/guide.ts +123 -96
  18. package/spec-cli/src/harness-select.ts +2 -2
  19. package/spec-cli/src/harness.ts +30 -14
  20. package/spec-cli/src/help.ts +289 -384
  21. package/spec-cli/src/hooks.ts +1 -1
  22. package/spec-cli/src/index.ts +130 -103
  23. package/spec-cli/src/init.ts +9 -9
  24. package/spec-cli/src/issues.ts +89 -33
  25. package/spec-cli/src/layout.ts +5 -5
  26. package/spec-cli/src/lint.ts +73 -8
  27. package/spec-cli/src/localIssues.ts +42 -60
  28. package/spec-cli/src/materialize.ts +1 -1
  29. package/spec-cli/src/mentions.ts +15 -15
  30. package/spec-cli/src/migrate-table.ts +397 -0
  31. package/spec-cli/src/migrate.ts +386 -0
  32. package/spec-cli/src/ranker.ts +30 -4
  33. package/spec-cli/src/reaper.ts +117 -0
  34. package/spec-cli/src/search.bench.mjs +10 -10
  35. package/spec-cli/src/search.ts +1 -1
  36. package/spec-cli/src/sessions.ts +244 -138
  37. package/spec-cli/src/specs.ts +25 -15
  38. package/spec-cli/src/supervise.ts +2 -2
  39. package/spec-cli/src/tree.ts +5 -5
  40. package/spec-cli/src/uninstall.ts +4 -4
  41. package/spec-cli/templates/hooks/post-checkout +1 -1
  42. package/spec-cli/templates/hooks/post-merge +3 -3
  43. package/spec-cli/templates/hooks/pre-commit +9 -9
  44. package/spec-cli/templates/hooks/prepare-commit-msg +1 -1
  45. package/spec-cli/templates/spec/project/{.config → .plugins}/core/idle/idle.sh +2 -2
  46. package/spec-cli/templates/spec/project/{.config → .plugins}/core/mark-active/mark-active.sh +7 -0
  47. package/spec-cli/templates/spec/project/{.config → .plugins}/core/mark-active/spec.md +2 -0
  48. package/spec-cli/templates/spec/project/{.config → .plugins}/core/session-fail/fail.sh +1 -1
  49. package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-first/spec-first.sh +1 -1
  50. package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-of-file/spec-of-file.sh +2 -2
  51. package/spec-cli/templates/spec/project/.plugins/core/spec.md +21 -0
  52. package/spec-cli/templates/spec/project/{.config → .plugins}/core/stop-gate/stop-gate.sh +21 -21
  53. package/spec-cli/templates/spec/project/{.config → .plugins}/distill/spec.md +2 -2
  54. package/spec-cli/templates/spec/project/{.config → .plugins}/extract/spec.md +4 -4
  55. package/spec-cli/templates/spec/project/{.config → .plugins}/forge-link/spec.md +1 -1
  56. package/spec-cli/templates/spec/project/{.config → .plugins}/regroup/spec.md +2 -2
  57. package/spec-cli/templates/spec/project/{.config → .plugins}/reproduce-before-fix/spec.md +3 -3
  58. package/spec-cli/templates/spec/project/{.config → .plugins}/spec.md +4 -4
  59. package/spec-cli/templates/spec/project/.plugins/supervisor/spec.md +8 -0
  60. package/spec-cli/templates/spec/project/{.config → .plugins}/tidy/spec.md +1 -1
  61. package/spec-cli/templates/spec/project/spec.md +1 -1
  62. package/spec-dashboard/dist/assets/{Dashboard-P0B9ukSG.js → Dashboard-C7Bzsv86.js} +9 -9
  63. package/spec-dashboard/dist/assets/EvalsPage-DKZZIdHq.js +2 -0
  64. package/spec-dashboard/dist/assets/{FoldToggle-BuQ0lokE.js → FoldToggle-D5iB4Ac2.js} +1 -1
  65. package/spec-dashboard/dist/assets/{IssuesPage-H-D8aHEl.js → IssuesPage-CMFTsQhg.js} +1 -1
  66. package/spec-dashboard/dist/assets/{MobileApp-oZXIeCPb.js → MobileApp-DwuTKgdP.js} +1 -1
  67. package/spec-dashboard/dist/assets/{SessionInterface-Blr_MEdU.js → SessionInterface-CBS5_cmK.js} +3 -3
  68. package/spec-dashboard/dist/assets/{SessionWindow-LcCzBMU7.js → SessionWindow-CqAnjWfI.js} +2 -2
  69. package/spec-dashboard/dist/assets/{Settings-_yOye-In.js → Settings-BW5f0OaW.js} +1 -1
  70. package/spec-dashboard/dist/assets/{index-uGs9v_9o.css → index-Cc26X4ce.css} +1 -1
  71. package/spec-dashboard/dist/assets/{index-BhIslAau.js → index-Ce0wDyQS.js} +8 -8
  72. package/spec-dashboard/dist/index.html +2 -2
  73. package/{spec-yatsu → spec-eval}/src/cache.ts +8 -5
  74. package/{spec-yatsu → spec-eval}/src/cli.ts +164 -95
  75. package/{spec-yatsu → spec-eval}/src/evaltab.ts +24 -24
  76. package/{spec-yatsu → spec-eval}/src/filing.ts +7 -5
  77. package/{spec-yatsu → spec-eval}/src/freshness.ts +44 -22
  78. package/{spec-yatsu → spec-eval}/src/scenariofresh.ts +22 -17
  79. package/{spec-yatsu/src/yatsu.ts → spec-eval/src/scenarios.ts} +41 -26
  80. package/{spec-yatsu/src/proof.ts → spec-eval/src/sessioneval.ts} +59 -59
  81. package/{spec-yatsu → spec-eval}/src/sidecar.ts +7 -1
  82. package/{spec-yatsu → spec-eval}/src/timeline.ts +1 -1
  83. package/spec-forge/src/__fixtures__/github-forge.json +9 -9
  84. package/spec-forge/src/cli.ts +14 -13
  85. package/spec-forge/src/{needs-yatsu-eval.ts → needs-eval.ts} +6 -6
  86. package/spec-cli/src/boardStream.ts +0 -179
  87. package/spec-cli/templates/spec/project/.config/core/spec.md +0 -17
  88. package/spec-cli/templates/spec/project/.config/supervisor/spec.md +0 -8
  89. package/spec-dashboard/dist/assets/EvalsPage-BrvAGyc4.js +0 -2
  90. /package/spec-cli/templates/presets/careful/{.config → .plugins}/clarify-before-code/spec.md +0 -0
  91. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/idle/spec.md +0 -0
  92. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/session-fail/spec.md +0 -0
  93. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-first/spec.md +0 -0
  94. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/spec-of-file/spec.md +0 -0
  95. /package/spec-cli/templates/spec/project/{.config → .plugins}/core/stop-gate/spec.md +0 -0
  96. /package/spec-cli/templates/spec/project/{.config → .plugins}/distill/digest.mjs +0 -0
  97. /package/spec-cli/templates/spec/project/{.config → .plugins}/memory-hygiene/spec.md +0 -0
package/README.md CHANGED
@@ -9,6 +9,13 @@
9
9
  <a href="https://spexcode.net"><img alt="docs" src="https://img.shields.io/badge/docs-spexcode.net-8957e5"></a>
10
10
  </p>
11
11
 
12
+ <p>
13
+ <img alt="Linux" src="https://img.shields.io/badge/Linux-supported-success?logo=linux&logoColor=white">
14
+ <img alt="macOS" src="https://img.shields.io/badge/macOS-supported-success?logo=apple&logoColor=white">
15
+ <img alt="Windows: via WSL2" src="https://img.shields.io/badge/Windows-WSL2-success">
16
+ <img alt="database: git" src="https://img.shields.io/badge/database-git-f05032?logo=git&logoColor=white">
17
+ </p>
18
+
12
19
  </div>
13
20
 
14
21
  Spec-driven development with AI agents in the loop. SpexCode keeps a versioned tree of specs inside
@@ -23,16 +30,17 @@ implementation from drifting apart.
23
30
  English | [中文](./docs/README.zh-CN.md) · Docs: [spexcode.net](https://spexcode.net) · License: MIT
24
31
 
25
32
  Quick links: [the model](#the-model) · [quick start](#quick-start) ·
26
- [agents](#working-with-agents) · [yatsu](#measuring-behavior-yatsu) · [config](#configuration)
33
+ [agents](#working-with-agents) · [eval](#measuring-behavior-eval) · [config](#configuration)
27
34
 
28
35
  ## The model
29
36
 
30
37
  <div align="center"><img src="docs/sdd-tuxedo-pooh.png" alt="spec-driven development meme" width="260"></div>
31
38
 
32
39
  A spec node is a directory under `.spec/` containing a `spec.md`: frontmatter (title, status, a
33
- `code:` list of the files it governs) plus a prose body stating what that part of the system is
34
- supposed to do, right now. Nodes nest, so the tree mirrors how you think about the project rather
35
- than the file layout. The body has two parts. The short **raw source** states the intent; changing it takes explicit
40
+ `code:` pointer to the file it governs, a `related:` list for files it references) plus a prose
41
+ body stating what that part of the system is supposed to do, right now. Nodes nest, so the tree
42
+ mirrors how you think about the project rather than the file layout. The body can split into two
43
+ labelled parts. The short **raw source** states the intent; changing it takes explicit
36
44
  human approval (an agent can draft it, a human signs off). The **expanded spec** is the agent's
37
45
  detailed reading of that intent; it iterates freely but must always match the raw source.
38
46
 
@@ -41,7 +49,7 @@ detailed reading of that intent; it iterates freely but must always match the ra
41
49
  Two rules make this workable:
42
50
 
43
51
  1. **Git is the database.** There is no separate store. A node's version count is the number of
44
- commits that touched its `spec.md`, its history view is `git log` on that file, and each version
52
+ commits that changed its `spec.md`, its history view is `git log` on that file, and each version
45
53
  is attributed to an agent session through a `Session:` commit trailer. This is also why a spec
46
54
  body always describes present intent and gets rewritten in place: changelog headings inside the
47
55
  body are banned (the linter enforces it), because git already keeps the history.
@@ -49,23 +57,23 @@ Two rules make this workable:
49
57
  code it justifies. When code moves without its spec, the linter flags it,
50
58
 
51
59
  ```
52
- drift: spec-cli/src/board.ts is 1 commit(s) ahead of spec 'board-lean' (v8) — may be stale
60
+ drift: spec-cli/src/graph.ts is 1 commit(s) ahead of spec 'graph-lean' (v12) — may be stale
53
61
  ```
54
62
 
55
63
  and keeps flagging until the spec catches up.
56
64
 
57
65
  ## The optimization loop
58
66
 
59
- Specs, commits, and yatsu readings compose into one loop. The spec is the loss function: it states what you want, and
60
- it's the half a human signs off on. Commits are the optimizer. **yatsu**, the measurement
61
- subsystem, is the eval: it scores how far live behavior currently sits from the spec, and the
67
+ Specs, commits, and evals compose into one loop. The spec is the loss function: it states what you
68
+ want, and it's the half a human signs off on. Commits are the optimizer. **eval**, the measurement
69
+ subsystem, scores how far live behavior currently sits from the spec, and the
62
70
  score's history lives in git like everything else.
63
71
 
64
72
  <img src="docs/readme-loop.png" alt="the spec/code optimization loop">
65
73
 
66
74
  It also settles where the human stands day to day: nobody reads a neural net by staring at its
67
75
  weights, and between merge gates you don't have to stare at agent diffs either. Attention goes to
68
- the spec and the eval readings; the diff gets read once, at merge time.
76
+ the spec and the evals; the diff gets read once, at merge time.
69
77
 
70
78
  ## Quick start
71
79
 
@@ -76,20 +84,20 @@ npm i -g spexcode # installs the `spex` command
76
84
  cd your-repo
77
85
  spex init # seeds .spec/, installs git hooks, materializes the agent contract
78
86
  spex serve # API backend on :8787
79
- spex dashboard # board UI on :5173, proxying to the backend
87
+ spex serve ui # dashboard on :5173, proxying to the backend
80
88
  ```
81
89
 
82
90
  `spex init` is additive. It works on any existing git repo and never overwrites your files: it
83
- creates a root `.spec/project/spec.md` and a starter `spexcode.json`, installs the pre-commit
84
- hooks, and writes a managed block into `CLAUDE.md`/`AGENTS.md` so any agent working in the repo
91
+ creates a root `.spec/project/spec.md` and a starter `spexcode.json`, installs the git hooks, and
92
+ writes a managed block into `CLAUDE.md`/`AGENTS.md` so any agent working in the repo
85
93
  discovers the workflow on its own.
86
94
 
87
95
  Then grow the tree:
88
96
 
89
97
  1. Edit `.spec/project/spec.md` to describe the project.
90
- 2. Add child nodes for the parts you want governed, each with a `code:` list pointing at existing
91
- files.
92
- 3. Run `spex lint`. Coverage warnings list the source files no spec claims yet; that list is your
98
+ 2. Add child nodes for the parts you want governed, each with a `code:` entry pointing at an
99
+ existing file (`related:` for the files it touches but doesn't own).
100
+ 3. Run `spex spec lint`. Coverage warnings list the source files no spec claims yet; that list is your
93
101
  adoption TODO.
94
102
 
95
103
  You are not expected to hand-author all of this. The intended workflow is to have an agent do most
@@ -97,7 +105,7 @@ of the spec writing; `spex guide spec` prints the exact file format it needs.
97
105
  [Getting started](https://spexcode.net/getting-started/) on the docs site walks the setup end to
98
106
  end.
99
107
 
100
- <img src="docs/readme-board.png" alt="dashboard screenshot">
108
+ <img src="docs/readme-graph.png" alt="dashboard screenshot">
101
109
 
102
110
  *SpexCode's own repo on its own board; the sessions top-left are agents building the tool.*
103
111
 
@@ -107,7 +115,7 @@ This part needs tmux and a logged-in [Claude Code](https://www.anthropic.com/cla
107
115
  on the machine.
108
116
 
109
117
  ```sh
110
- spex new "make the settings page remember the last tab" --node settings
118
+ spex session new "make the settings page remember the last tab" --node settings
111
119
  ```
112
120
 
113
121
  launches a worker session in its own worktree on branch `node/settings-…`. The `--node` flag (or a
@@ -123,9 +131,9 @@ themselves use when they delegate.
123
131
  You supervise from outside — on the board, or with the same commands your agent uses:
124
132
 
125
133
  ```sh
126
- spex watch # stream session transitions: launched / review / done / needs-input ...
127
- spex review settings # commits ahead of trunk, merge-base diff, typecheck/lint gates
128
- spex merge settings # gated merge into the trunk
134
+ spex session watch # stream session transitions: launched / review / done / needs-input ...
135
+ spex session review settings # commits ahead of trunk, merge-base diff, merge-conflict/lint gates
136
+ spex session merge settings # gated merge into the trunk
129
137
  spex session close settings
130
138
  ```
131
139
 
@@ -138,28 +146,29 @@ hook stamps the attribution; the materialized contract block carries the rest, s
138
146
  prompt stays task-only. More on this mode of working:
139
147
  [working with agents](https://spexcode.net/working-with-agents/).
140
148
 
141
- ## Measuring behavior: yatsu
149
+ ## Measuring behavior: eval
142
150
 
143
- yatsu — short for **You As The Stupid User** — is the measuring half of
144
- [the loop](#the-optimization-loop): you measure behavior from the product's real surface, the way a
145
- clueless real end user would touch it, not through an internal helper or shortcut that makes the
146
- proof easy. A spec says what a part should do; a
147
- `yatsu.md` beside it says how to check. Each scenario is a plain description plus an expected
148
- result. yatsu itself runs nothing (no DSL, no runner). An agent runs the scenario however it can:
151
+ eval is the measuring half of
152
+ [the loop](#the-optimization-loop), built on the YATU discipline (**You As The User**): you measure
153
+ behavior from the product's real surface, the way a real end user would touch it, not through an
154
+ internal helper or shortcut that makes the
155
+ proof easy. A spec says what a part should do; an
156
+ `eval.md` beside it says how to check. Each scenario is a plain description plus an expected
157
+ result. eval itself runs nothing (no DSL, no runner). An agent runs the scenario however it can:
149
158
  a test file, a real browser, or just clicking through by hand and screenshotting. It compares
150
- actual to expected and files the reading with evidence:
159
+ actual to expected and files the eval with evidence:
151
160
 
152
161
  ```sh
153
- spex yatsu eval settings --scenario remembers-tab --pass --image proof.png
162
+ spex eval add settings --scenario remembers-tab --pass --image evidence.png
154
163
  ```
155
164
 
156
- Readings live in a git-tracked ndjson next to the spec, so measurements get the same attribution
157
- and history as spec versions. Bug fixes are expected to bracket: file a failing reading that
158
- reproduces the bug, fix, then file a passing reading on the same scenario.
165
+ Evals live in a git-tracked ndjson next to the spec, so measurements get the same attribution
166
+ and history as spec versions. Bug fixes are expected to bracket: file a failing eval that
167
+ reproduces the bug, fix, then file a passing eval on the same scenario.
159
168
 
160
169
  <img src="docs/readme-eval.png" alt="eval view screenshot">
161
170
 
162
- *The eval view: scenario readings on the left; the selected reading's expected result, staleness,
171
+ *The eval view: scenario evals on the left; the selected eval's expected result, staleness,
163
172
  and recorded video evidence in the middle.*
164
173
 
165
174
  ## What's in the repo
@@ -168,14 +177,14 @@ and recorded video evidence in the middle.*
168
177
  |---|---|
169
178
  | `spec-cli` | The `spex` CLI and the HTTP backend (Hono, runs via tsx, no build step). Reads `.spec` and git live; owns the session state machine and the linter. |
170
179
  | `spec-dashboard` | React board: the node graph, per-node spec/history/issues panes, and a real terminal onto each live agent session. |
171
- | `spec-yatsu` | Scenario definitions, readings, evidence blobs. |
172
- | `spec-forge` | Read-only tracer that resolves a forge's open issues and PRs to the spec nodes they serve (GitHub today). An issue links itself with a `Spec: <node-id>` line in its body; a PR from a `node/<id>` branch links for free. |
180
+ | `spec-eval` | Scenario definitions, evals, evidence. |
181
+ | `spec-forge` | Read-only tracer that resolves a forge's open issues and PRs to the spec nodes they serve (GitHub and GitLab drivers today). An issue links itself with a `Spec: <node-id>` line in its body; a PR from a `node/<id>` branch links for free. |
173
182
 
174
183
  ## The linter
175
184
 
176
- `spex lint` checks the spec↔code graph and is the real gate (the git hook is fast local feedback):
185
+ `spex spec lint` checks the spec↔code graph and is the real gate (the git hook is fast local feedback):
177
186
 
178
- - **integrity** (error): a `code:` path that doesn't exist
187
+ - **integrity** (error): a `code:` or `related:` path that doesn't exist
179
188
  - **living** (error): a changelog heading in a spec body
180
189
  - **altitude** (warn): a body that slid from contract prose into an implementation dump. The usual
181
190
  smell is a numbered step list or a wall of function names; this rule is why spec bodies stay
@@ -183,20 +192,23 @@ and recorded video evidence in the middle.*
183
192
  - **coverage** (warn): unclaimed source files
184
193
  - **drift** (warn): governed code changed after its spec's last version, derived live from git
185
194
 
195
+ plus naming and ownership rules (`one-govern`, `id-format`, `mention` as errors; `breadth`,
196
+ `related-drift`, `owners`, `confusable-id` as warns) — `spex guide spec` lists them all.
197
+
186
198
  ## Configuration
187
199
 
188
200
  `spexcode.json` (committed, portable: layout, lint budgets, dashboard identity, launcher names) and
189
- `spexcode.local.json` (gitignored, host-specific: absolute launcher paths, plus a `private: true`
190
- overlay for repos you use but don't own) cover every setting. No `spex config set` yet: you edit the two files by hand (or ask your agent
191
- to), and `spex guide config` documents every field. The other
192
- manuals are `spex guide` (the workflow), `spex guide spec`, and `spex guide yatsu`; `spex help`
193
- maps the commands.
201
+ `spexcode.local.json` (gitignored, host-specific: absolute launcher paths, cert paths) cover every
202
+ setting. There is no imperative settings verb: you edit the two files by hand (or ask your agent
203
+ to), and `spex guide settings` documents every field. The other
204
+ manuals are `spex guide` (the workflow), `spex guide spec`, `spex guide eval`, and
205
+ `spex guide footprint`; `spex help` maps the commands.
194
206
 
195
207
  ## Contributing
196
208
 
197
209
  [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) gets you from a clone to a first merged change.
198
210
  [`docs/AGENT_GUIDE.md`](docs/AGENT_GUIDE.md) has the full mechanics of the node model and the
199
- reflexive config system.
211
+ reflexive plugin system.
200
212
 
201
213
  ## Credit
202
214
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spexcode",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "SpexCode — a spec-driven, self-developing dev tool. The `spex` CLI + spec server reads the .spec tree and its git history, and serves the dashboard.",
6
6
  "license": "MIT",
@@ -13,8 +13,8 @@
13
13
  "spec-cli/bin",
14
14
  "spec-cli/templates",
15
15
  "spec-cli/hooks",
16
- "spec-yatsu/src",
17
- "!spec-yatsu/src/**/*.test.ts",
16
+ "spec-eval/src",
17
+ "!spec-eval/src/**/*.test.ts",
18
18
  "spec-forge/src",
19
19
  "!spec-forge/src/**/*.test.ts",
20
20
  "spec-dashboard/dist",
@@ -17,9 +17,9 @@ const cli = join(pkg, 'src', 'cli.ts')
17
17
  // while a merge conflict is being resolved in the checkout that hosts it, the source holds conflict
18
18
  // markers and tsx dies with a raw esbuild stacktrace — on EVERY call, including the Stop hook and an
19
19
  // agent's `spex session done`. Catch that one transient state up front: scan the source trees the CLI
20
- // imports (spec-cli ←→ spec-yatsu ←→ spec-forge), and if any file carries a marker, print one actionable
20
+ // imports (spec-cli ←→ spec-eval ←→ spec-forge), and if any file carries a marker, print one actionable
21
21
  // line and exit 75 (EX_TEMPFAIL: transient, retry) instead of spawning tsx into the stacktrace.
22
- const srcRoots = [join(pkg, 'src'), join(pkg, '..', 'spec-yatsu', 'src'), join(pkg, '..', 'spec-forge', 'src')]
22
+ const srcRoots = [join(pkg, 'src'), join(pkg, '..', 'spec-eval', 'src'), join(pkg, '..', 'spec-forge', 'src')]
23
23
  const conflicted = srcRoots.flatMap((root) => {
24
24
  if (!existsSync(root)) return []
25
25
  return readdirSync(root, { recursive: true })
@@ -13,7 +13,7 @@
13
13
  #
14
14
  # The old (1) GATE — an auto-materialize when the config content-hash moved — is RETIRED ([[commit-surgery]]):
15
15
  # a harness event is never a materialize trigger; the materialize anchors are git-native only (spex verbs,
16
- # session-worktree creation, and the pre-commit/post-checkout/post-merge hooks). .config edits are
16
+ # session-worktree creation, and the pre-commit/post-checkout/post-merge hooks). .plugins edits are
17
17
  # git-transactional: they take effect at the commit/checkout/merge that carries them, like any other source.
18
18
  set -u
19
19
  # args: `<harness> <Event>`. A harness id as $1 (claude|codex|plugin) is consumed; otherwise we keep $1 as the
@@ -59,15 +59,35 @@ hp_field() {
59
59
  # the session id from a payload (both harnesses use session_id).
60
60
  # Codex hooks run inside ONE shared per-project app-server. That process can inherit the FIRST launched
61
61
  # session's SPEXCODE_SESSION_ID, so on codex the payload session_id (the acting thread id) must win and then
62
- # hp_store_dir aliases it to the governed record. Claude's payload id already equals its governed record id,
63
- # so preferring SPEXCODE_SESSION_ID there preserves the launcher's pinned id path.
62
+ # hp_store_dir aliases it to the governed record. Claude's payload id equals its governed record id, so the
63
+ # PAYLOAD is the acting identity; the inherited env is only a fallback for payload-less events. Env-first was
64
+ # a live bug: a nested subagent (Task tool) inherits the parent's SPEXCODE_SESSION_ID, so with env winning,
65
+ # every child tool call fired mark-active against the PARENT's record — the parent read `working` forever and
66
+ # every park/done declaration was clobbered within seconds (measured). Same staleness class the codex branch
67
+ # already guards against (payload-first below); claude now follows the same rule.
64
68
  hp_session_id() {
69
+ local pid
65
70
  case "$SPEXCODE_HARNESS" in
66
71
  codex) hp_field "$1" session_id ;;
67
- *) printf '%s' "${SPEXCODE_SESSION_ID:-$(hp_field "$1" session_id)}" ;;
72
+ *) pid=$(hp_field "$1" session_id); printf '%s' "${pid:-$SPEXCODE_SESSION_ID}" ;;
68
73
  esac
69
74
  }
70
75
 
76
+ # is THIS payload a tool call executed by an IN-PROCESS SUBAGENT (Claude's Task tool — a sub-conversation
77
+ # inside the SAME harness process) rather than the session's own top-level agent? The subagent's payload
78
+ # carries the PARENT's session_id/transcript_path (measured live, claude 2.1.207), so hp_session_id cannot
79
+ # tell them apart — but the harness stamps every subagent-executed call with a top-level `agent_id`
80
+ # (+ `agent_type`) field that a top-level call NEVER carries. The check is structural, never a heuristic:
81
+ # scan ONLY the payload prefix before the first `"tool_input"` (every harness-stamped top-level field
82
+ # precedes tool_input), and require the key shape `"agent_id":`. Inside a JSON string value every quote is
83
+ # escaped (\"), so a bare-quoted `"agent_id"` followed by a colon in that prefix can only be the harness's
84
+ # own stamp — a tool parameter literally NAMED agent_id (an MCP tool) sits inside tool_input, past the
85
+ # truncation, and file/prompt content can never fake an unescaped quote. Codex payloads carry no such field
86
+ # (its verified field set) so this never matches there. Echoes "1" when yes, else nothing.
87
+ hp_is_subagent() {
88
+ printf '%s' "${1%%\"tool_input\"*}" | grep -q '"agent_id"[[:space:]]*:' && printf 1
89
+ }
90
+
71
91
  # the per-PROJECT GLOBAL runtime dir (mirrors spec-cli/src/layout.ts `runtimeRoot`): <store>/projects/<enc>,
72
92
  # keyed by the project (dirname of the ABSOLUTE git-common-dir, so the answer is identical from main or any
73
93
  # worktree). The per-session dirs and the per-tree materialize slots (hp_tree_dir) live under it.
@@ -114,7 +134,7 @@ hp_store_dir() {
114
134
 
115
135
  # the TOOLCHAIN's own version fingerprint — the toolchain side of the content key. The materialized artifacts
116
136
  # are a function of (config content, toolchain), so a TOOLCHAIN update must move the key too, or an updated
117
- # deploy never self-heals its stale contract/shims/manifest until someone happens to edit .config (the field
137
+ # deploy never self-heals its stale contract/shims/manifest until someone happens to edit .plugins (the field
118
138
  # lesson: a toolchain update does NOT self-heal). A source checkout answers with the git TREE hash of the
119
139
  # package dir (moves exactly when the toolchain's content moves, not on every repo commit); an npm install
120
140
  # (no .git) answers with the package.json hash (npm bumps the version). env-stripped git — a git hook's
@@ -127,7 +147,7 @@ hp_toolchain_version() {
127
147
  }
128
148
 
129
149
  # the deterministic content fingerprint of EVERYTHING the materialize is a function of: the EDITABLE config
130
- # roots (.config + config md/sh), the PERSISTED POLICY files (the MAIN checkout's spexcode.json +
150
+ # roots (.plugins + plugin-system md/sh), the PERSISTED POLICY files (the MAIN checkout's spexcode.json +
131
151
  # spexcode.local.json — the `harnesses` set materialize reads via readConfig(mainCheckout)), and the
132
152
  # toolchain version above. Since the dispatch-gate retired ([[commit-surgery]] — materialize anchors on
133
153
  # git-native events only), this is a FRESHNESS STAMP materialize records after each pass, a diagnostic
@@ -138,7 +158,7 @@ hp_config_hash() {
138
158
  gcd=$(env -u GIT_DIR -u GIT_INDEX_FILE git rev-parse --path-format=absolute --git-common-dir 2>/dev/null) \
139
159
  || gcd=$(realpath "$(env -u GIT_DIR -u GIT_INDEX_FILE git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null)
140
160
  { hp_toolchain_version
141
- find .spec/*/.config .spec/*/config \( -name '*.md' -o -name '*.sh' \) -type f -print0 2>/dev/null \
161
+ find .spec/*/.plugins .spec/*/plugin-system \( -name '*.md' -o -name '*.sh' \) -type f -print0 2>/dev/null \
142
162
  | sort -z | xargs -0 cat 2>/dev/null
143
163
  [ -n "$gcd" ] && cat "$(dirname "$gcd")/spexcode.json" "$(dirname "$gcd")/spexcode.local.json" 2>/dev/null
144
164
  } | sha256sum | cut -d' ' -f1
@@ -9,7 +9,7 @@ import { spawnSync } from 'node:child_process'
9
9
  import { networkInterfaces } from 'node:os'
10
10
  import { alive, apiBase, TMUX_SOCK } from './sessions.js'
11
11
 
12
- const AGENT_ALTERNATIVES = 'read the pane with `spex session capture`, drive it with `session send` / `session rawkey`'
12
+ const AGENT_ALTERNATIVES = 'read the pane with `spex session show <SEL> --capture`, drive it with `session send` (plain text first; `--keys` only as a last resort)'
13
13
 
14
14
  // attach only makes sense on the machine that runs the tmux server — the backend's. The board the selector
15
15
  // resolved against IS that backend, so the test is: does the RESOLVED backend (see [[remote-client]]'s
@@ -41,7 +41,7 @@ An agent must not run it inside a turn (it freezes you); ${AGENT_ALTERNATIVES}.`
41
41
  }
42
42
  if (!(await alive(id))) {
43
43
  console.error(`spex session attach: ${id} is offline — no live tmux session to attach.
44
- Bring it back with \`spex session reopen ${id}\`, or read its record with \`spex ls ${id}\`.`)
44
+ Bring it back with \`spex session resume ${id}\`, or read its record with \`spex session show ${id}\`.`)
45
45
  process.exit(1)
46
46
  }
47
47
  console.log(`attaching to ${id} — detach with C-b d (the session keeps running)`)