spexcode 0.2.7 → 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 +50 -24
- 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/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) · [
|
|
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:`
|
|
34
|
-
supposed to do, right now. Nodes nest, so the tree
|
|
35
|
-
|
|
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
|
|
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/
|
|
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
|
|
60
|
-
it's the half a human signs off on. Commits are the optimizer. **
|
|
61
|
-
subsystem,
|
|
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
|
|
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
|
|
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
|
|
84
|
-
|
|
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:`
|
|
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-
|
|
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,
|
|
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:
|
|
149
|
+
## Measuring behavior: eval
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
[the loop](#the-optimization-loop)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
159
|
+
actual to expected and files the eval with evidence:
|
|
151
160
|
|
|
152
161
|
```sh
|
|
153
|
-
spex
|
|
162
|
+
spex eval add settings --scenario remembers-tab --pass --image evidence.png
|
|
154
163
|
```
|
|
155
164
|
|
|
156
|
-
|
|
157
|
-
and history as spec versions. Bug fixes are expected to bracket: file a failing
|
|
158
|
-
reproduces the bug, fix, then file a passing
|
|
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
|
|
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-
|
|
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,
|
|
190
|
-
|
|
191
|
-
to), and `spex guide
|
|
192
|
-
manuals are `spex guide` (the workflow), `spex guide spec`,
|
|
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
|
|
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.
|
|
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-
|
|
17
|
-
"!spec-
|
|
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",
|
package/spec-cli/bin/spex.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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). .
|
|
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
|
|
63
|
-
#
|
|
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
|
|
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 .
|
|
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 (.
|
|
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/*/.
|
|
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
|
package/spec-cli/src/attach.ts
CHANGED
|
@@ -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`
|
|
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
|
|
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)`)
|