spexcode 0.4.1 → 0.5.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 +37 -17
- package/package.json +9 -5
- package/spec-cli/README.md +23 -14
- package/spec-cli/hooks/dispatch.sh +7 -5
- package/spec-cli/hooks/harness.sh +19 -8
- package/spec-cli/src/anchors.ts +110 -18
- package/spec-cli/src/claude-headless.ts +271 -0
- package/spec-cli/src/cli.ts +112 -38
- package/spec-cli/src/client.ts +42 -11
- package/spec-cli/src/doctor.ts +174 -36
- package/spec-cli/src/gateway-auth.ts +194 -0
- package/spec-cli/src/gateway-hub.ts +360 -0
- package/spec-cli/src/gateway.ts +27 -19
- package/spec-cli/src/git.ts +12 -5
- package/spec-cli/src/graph.ts +54 -43
- package/spec-cli/src/graphStream.ts +265 -34
- package/spec-cli/src/guide.ts +115 -56
- package/spec-cli/src/harness-select.ts +20 -6
- package/spec-cli/src/harness.ts +295 -39
- package/spec-cli/src/help.ts +68 -28
- package/spec-cli/src/host.ts +610 -0
- package/spec-cli/src/identity-presets.d.ts +13 -0
- package/spec-cli/src/identity-presets.js +128 -0
- package/spec-cli/src/index.ts +158 -87
- package/spec-cli/src/init.ts +53 -25
- package/spec-cli/src/layout.ts +6 -5
- package/spec-cli/src/lint.ts +25 -121
- package/spec-cli/src/login-page.ts +12 -5
- package/spec-cli/src/materialize.ts +39 -13
- package/spec-cli/src/mentions.ts +16 -11
- package/spec-cli/src/message-stream.ts +147 -0
- package/spec-cli/src/opencode-headless.ts +95 -0
- package/spec-cli/src/opencode.ts +145 -0
- package/spec-cli/src/pi-harness.ts +124 -0
- package/spec-cli/src/pi-headless.ts +195 -0
- package/spec-cli/src/project-identity.ts +73 -0
- package/spec-cli/src/pty-bridge.ts +321 -521
- package/spec-cli/src/pty-helper.mjs +86 -0
- package/spec-cli/src/reaper.ts +16 -8
- package/spec-cli/src/reviewSnapshot.ts +23 -0
- package/spec-cli/src/reviews.ts +347 -0
- package/spec-cli/src/session-timeline.ts +14 -0
- package/spec-cli/src/sessions.ts +211 -75
- package/spec-cli/src/shim-runtime.ts +169 -0
- package/spec-cli/src/slash-commands.ts +76 -0
- package/spec-cli/src/source-files.ts +79 -0
- package/spec-cli/src/specs.ts +12 -7
- package/spec-cli/src/supervise.ts +37 -15
- package/spec-cli/src/tree.ts +8 -8
- package/spec-cli/src/uninstall.ts +56 -23
- package/spec-cli/templates/hooks/prepare-commit-msg +32 -18
- package/spec-cli/templates/spec/project/.plugins/commands/extract/spec.md +24 -18
- package/spec-cli/templates/spec/project/.plugins/commands/regroup/spec.md +3 -3
- package/spec-cli/templates/spec/project/.plugins/commands/rename/spec.md +11 -0
- package/spec-cli/templates/spec/project/.plugins/commands/spec.md +22 -8
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/commands/tidy/spec.md +29 -11
- package/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +2 -0
- package/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec-first.sh +28 -39
- package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec.md +5 -3
- package/spec-cli/templates/spec/project/.plugins/core/spec-of-file/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -2
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +7 -1
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +2 -2
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +5 -5
- package/spec-cli/templates/spec/project/.plugins/prompts/spec.md +10 -6
- package/spec-cli/templates/spec/project/.plugins/skills/distill/spec.md +38 -50
- package/spec-cli/templates/spec/project/.plugins/skills/spec.md +9 -7
- package/spec-cli/templates/spec/project/.plugins/spec.md +17 -12
- package/spec-cli/templates/spec/project/spec.md +6 -5
- package/spec-cli/templates/spexcode.json +10 -2
- package/spec-dashboard/dist/assets/Dashboard-C_w_wdk5.js +27 -0
- package/spec-dashboard/dist/assets/EvalsPage-5_nfIYll.js +2 -0
- package/spec-dashboard/dist/assets/IssuesPage-By-u--95.js +1 -0
- package/spec-dashboard/dist/assets/MobileApp-CVEwjHr9.js +2 -0
- package/spec-dashboard/dist/assets/Modal-BqgvzMJD.js +1 -0
- package/spec-dashboard/dist/assets/PageScroll-B_dKCuXx.js +1 -0
- package/spec-dashboard/dist/assets/ProjectsPage-RVP8AqK4.js +1 -0
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +32 -0
- package/spec-dashboard/dist/assets/SessionInterface-Bh3vq8SU.js +39 -0
- package/spec-dashboard/dist/assets/SessionWindow-BuJ5mzjC.js +13 -0
- package/spec-dashboard/dist/assets/Settings-B8KFocsz.js +1 -0
- package/spec-dashboard/dist/assets/TimelineChat-K0wdlweB.js +1 -0
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +1 -0
- package/spec-dashboard/dist/assets/index-BKaTHjmU.js +41 -0
- package/spec-dashboard/dist/assets/index-DcnCaBAC.css +1 -0
- package/spec-dashboard/dist/assets/terminalFont-DmTPNdbU.js +1 -0
- package/spec-dashboard/dist/index.html +10 -10
- package/spec-dashboard/src/reviewFilters.js +279 -0
- package/spec-dashboard/src/reviewQuery.js +174 -0
- package/spec-dashboard/src/session.js +167 -0
- package/spec-eval/src/cli.ts +49 -28
- package/spec-eval/src/humanok.ts +1 -1
- package/spec-eval/src/matrix.ts +693 -0
- package/spec-eval/src/sessioneval.ts +726 -66
- package/spec-cli/src/migrate-table.ts +0 -408
- package/spec-cli/src/migrate.ts +0 -386
- package/spec-dashboard/dist/assets/Dashboard-C_fGmOKK.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-Cnr1s3bq.js +0 -2
- package/spec-dashboard/dist/assets/FoldToggle-x9gtO1OQ.js +0 -1
- package/spec-dashboard/dist/assets/IssuesPage-5f_vL-JV.js +0 -1
- package/spec-dashboard/dist/assets/MobileApp-DEO1jgGM.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-CAlbMOFR.js +0 -66
- package/spec-dashboard/dist/assets/SessionInterface-DYP7pi_n.css +0 -32
- package/spec-dashboard/dist/assets/SessionWindow-JYbpPwNB.js +0 -13
- package/spec-dashboard/dist/assets/Settings-DKb5Ji_X.js +0 -1
- package/spec-dashboard/dist/assets/index-BQu-oJ8J.js +0 -41
- package/spec-dashboard/dist/assets/index-BbMkwuix.css +0 -1
- package/spec-dashboard/dist/assets/launch-BM9GgvkX.js +0 -6
package/spec-cli/src/guide.ts
CHANGED
|
@@ -5,35 +5,35 @@ the rest, you don't hand-author the spec tree or wire the dashboard yourself.
|
|
|
5
5
|
|
|
6
6
|
1. Install the CLI (one-time, global — ONE install serves every project)
|
|
7
7
|
npm i -g spexcode # ONE command lands on PATH: \`spex\` (Node ≥ 22)
|
|
8
|
-
|
|
8
|
+
Project-scoped commands operate on the repo of your current directory; the dashboard is host-scoped.
|
|
9
9
|
(Dogfooding an unpublished HEAD from a source checkout? \`npm link\` at the repo ROOT — that links
|
|
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
|
-
inner package uninstalls as \`@spexcode/spec-cli\`).
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
inner package uninstalls as \`@spexcode/spec-cli\`). Contributors run \`npm run api\` and
|
|
13
|
+
\`npm run web\` from the source root for backend reloads and Vite/HMR; those are not installed-user
|
|
14
|
+
startup commands.)
|
|
15
15
|
|
|
16
16
|
2. Adopt a repo
|
|
17
|
-
cd <your-repo> && spex init
|
|
17
|
+
cd <your-repo> && spex init --harness claude,codex,opencode,pi,claude-headless,opencode-headless,pi-headless # seeds .spec/ + git hooks (additive, never overwrites)
|
|
18
|
+
--harness is required and has no default — the explicit choice of which harness(es) materialize
|
|
19
|
+
delivers into. The example lists every built-in; drop the ones you don't use (any one id or
|
|
20
|
+
comma-separated subset is valid).
|
|
18
21
|
Works on any git repo. Edit .spec/project/spec.md to describe it, then grow child nodes
|
|
19
22
|
(each a dir with a spec.md + a \`code:\` list of the files it governs).
|
|
20
23
|
|
|
21
|
-
3. Run
|
|
24
|
+
3. Run this repo's backend — it reads .spec + git from cwd and registers in this user's host registry
|
|
22
25
|
spex serve # http://localhost:8787 (--port <n> for another endpoint)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
the internet, use \`spex serve --public\` instead).
|
|
32
|
-
(Dogfood/source alternative: API_URL=http://localhost:<port> npm run dev in spec-dashboard —
|
|
33
|
-
the dev server; "dashboard": { "apiUrl": "..." } in spexcode.json applies only to that layout.)
|
|
26
|
+
Run one \`spex serve\` from every project you want online. Give each additional backend a free
|
|
27
|
+
--port; the port only binds that backend and is never paired with a dashboard process.
|
|
28
|
+
|
|
29
|
+
4. Open the shared host dashboard — run this ONCE for the current user, from any directory
|
|
30
|
+
spex dashboard # host gateway + UI on http://localhost:5173
|
|
31
|
+
It discovers backends already running and any that start later. \`/projects\` is the global project
|
|
32
|
+
switcher and management surface; each project's dashboard lives under \`/p/:id/\`. The gateway is
|
|
33
|
+
loopback-only by default; \`--host\` widens its bind as described by \`spex dashboard --help\`.
|
|
34
34
|
|
|
35
35
|
5. Govern your layout (optional)
|
|
36
|
-
spexcode.json sets lint's governedRoots/
|
|
36
|
+
spexcode.json sets lint's governedRoots/include/exclude/test source policy and any non-default layout.
|
|
37
37
|
\`spex spec lint\` must report 0 errors; coverage warnings are your adoption TODO (files no node claims yet).
|
|
38
38
|
|
|
39
39
|
Look these up on demand — the formats an agent authors, and the settings it configures:
|
|
@@ -72,6 +72,11 @@ FRONTMATTER (YAML between the opening and closing --- lines; every field optiona
|
|
|
72
72
|
duplicates, globs/directories with a selector, and dead/ambiguous units all error loud. A
|
|
73
73
|
selector-scoped governor claims units, not the file, so it stays out of the \`owners\` bound
|
|
74
74
|
(spex spec owner still displays it, marked "(scoped)"). Anchors are optional.
|
|
75
|
+
Python \`.py\`/\`.pyi\` anchors recognize structural \`def\`, \`async def\`, and \`class\`
|
|
76
|
+
declarations. Methods and nested declarations use lexical qualified names such as
|
|
77
|
+
\`Class.method\`, \`outer.inner\`, or \`Outer.Inner.method\`; attached decorators belong to
|
|
78
|
+
the declaration's range. Runtime-created/assigned callables, imported aliases, and generated
|
|
79
|
+
names are outside this declaration extractor and therefore resolve as dead anchors.
|
|
75
80
|
related: files this node REFERENCES but does not own — a YAML list, same path forms. Carries coverage
|
|
76
81
|
(never drift, never eval freshness, nothing to ack); it is the many-to-many net that claims the files
|
|
77
82
|
govern doesn't. Every listed path must exist (lint integrity error otherwise). A related row
|
|
@@ -116,10 +121,6 @@ WHAT lint CHECKS (spex spec lint; the pre-commit hook gates on errors):
|
|
|
116
121
|
optional leading dot; no space / '/' / '_' / uppercase Latin), and its leaf dir name
|
|
117
122
|
is unique tree-wide.
|
|
118
123
|
mention (error) every [[node-id]] in prose names a real node (fenced/backticked samples exempt).
|
|
119
|
-
altitude (warn) the body stays high-altitude: line/char budgets (~50 lines / 4200 chars), low
|
|
120
|
-
code-identifier density, no step-by-step phrasing. Over budget = rewrite higher.
|
|
121
|
-
breadth (warn) a node with >= maxChildren direct children (default 8) — altitude's structural
|
|
122
|
-
twin; is an intermediate grouping layer missing?
|
|
123
124
|
coverage (warn) every source file is claimed by ≥1 node — via code: OR related: (related is the net).
|
|
124
125
|
drift (warn) a governed file has commits newer than the node's spec version — it may be stale.
|
|
125
126
|
ALWAYS advisory: unanchored drift never blocks a commit (the blocking tier is
|
|
@@ -139,14 +140,17 @@ WHAT lint CHECKS (spex spec lint; the pre-commit hook gates on errors):
|
|
|
139
140
|
foundation owner). Selector-scoped governors don't count toward the bound.
|
|
140
141
|
confusable-id (warn) two leaf ids one edit apart read as the same word — rename one to read apart.
|
|
141
142
|
|
|
143
|
+
Heuristic spec health is deliberately outside this production gate. Bare \`spex doctor\` reports opt-in
|
|
144
|
+
altitude and breadth findings; the tidy workflow consumes that report and adds semantic judgment.
|
|
145
|
+
|
|
142
146
|
LIFECYCLE: author each node on a node/<id> branch, one node per commit; \`spex spec lint\` must reach 0 errors
|
|
143
147
|
before merge. \`spex init\` seeds the first tree; \`spex guide eval\` covers the sibling eval.md, the measurement file.`
|
|
144
148
|
|
|
145
149
|
const EVAL = `spex guide eval — the eval.md file format
|
|
146
150
|
|
|
147
151
|
An eval.md sits BESIDE a node's spec.md and says how to MEASURE the node's loss — the gap between live
|
|
148
|
-
behaviour and the spec. It is optional, but a node that governs
|
|
149
|
-
|
|
152
|
+
behaviour and the spec. It is optional, but a node that governs a file admitted by lint's shared tracked-text
|
|
153
|
+
include-minus-exclude/test policy with no eval.md is
|
|
150
154
|
a blind spot: \`spex eval lint\` flags it \`eval-coverage\`. The eval system defines no DSL and RUNS
|
|
151
155
|
NOTHING — the agent measures; eval keeps score.
|
|
152
156
|
|
|
@@ -210,7 +214,12 @@ PICK THE EVIDENCE KIND BY WHAT THE BEHAVIOUR DOES OVER TIME:
|
|
|
210
214
|
CLI harness stamping line numbers — take a baseline and at EACH real step push
|
|
211
215
|
\`{ at: <position>, step: "…" }\`; dump that array as \`--timeline\`. Its \`axis\` MUST match the
|
|
212
216
|
evidence it rides (a \`line\` map needs a \`--result\` transcript, a \`time\` map a \`--video\`);
|
|
213
|
-
skip it for a short single-step artefact.
|
|
217
|
+
skip it for a short single-step artefact. A step name is a SHORT human label for its
|
|
218
|
+
moment ("open graph") — never a metadata channel: the run's identity, its verdict, and
|
|
219
|
+
the evidence's extent already have canonical homes (the scenario's \`test:\` field, the
|
|
220
|
+
measurement's --pass/--fail, the artefact itself), so a step like
|
|
221
|
+
"runner start: path/to/spec.ts :: <full case title>" smuggles data the measurement already
|
|
222
|
+
carries and turns the step ruler into noise. (Legacy \`{ "v": 1, "events": [{ "tMs" }] }\` — the
|
|
214
223
|
time axis with \`tMs\` — is still accepted, read as \`axis: "time"\`.)
|
|
215
224
|
STATIC end state → \`--image <png>\` (repeatable — N stills). Layout, an icon, copy, one rendered frame.
|
|
216
225
|
backend / CLI → \`--result <txt>\` (a transcript; \`-\` reads stdin). A STRUCTURED export (a tool's
|
|
@@ -245,23 +254,26 @@ code file or the scenario (the eval.md) moves since it was filed.
|
|
|
245
254
|
|
|
246
255
|
const SETTINGS = `spex guide settings — SpexCode's runtime settings (spexcode.json / spexcode.local.json)
|
|
247
256
|
|
|
248
|
-
SpexCode reads
|
|
257
|
+
SpexCode reads PROJECT runtime settings from TWO optional JSON files at the repo root. There is no imperative
|
|
249
258
|
settings verb — an agent CONFIGURES SpexCode by EDITING these files directly. The two split by
|
|
250
259
|
PORTABILITY, and picking the right one is the whole discipline:
|
|
251
260
|
|
|
252
261
|
spexcode.json COMMITTED — portable, shared by everyone on the repo. Layout, policy, dashboard
|
|
253
|
-
identity, lint budgets, launcher NAMES. "Git is the database": tracked so the
|
|
262
|
+
identity and launcher visibility, lint policy, doctor health budgets, launcher NAMES. "Git is the database": tracked so the
|
|
254
263
|
team shares ONE configuration.
|
|
255
264
|
spexcode.local.json GITIGNORED — host-specific, never committed. Absolute launcher paths, cert/secret
|
|
256
265
|
paths. Layered OVER spexcode.json (see MERGE
|
|
257
266
|
below); a targeted env override (SPEXCODE_CODEX_SERVER_CMD, …) still wins at its read site.
|
|
258
267
|
|
|
259
268
|
Rule of thumb — is the value TRUE FOR THE PROJECT or TRUE FOR THIS MACHINE? A branch name, a dashboard
|
|
260
|
-
icon,
|
|
269
|
+
icon or launcher-visibility policy, lint policy, doctor health budgets, and a launcher's name+harness are project facts → committed spexcode.json. The ABSOLUTE
|
|
261
270
|
PATH of a launcher wrapper or a TLS cert path are machine facts → gitignored spexcode.local.json.
|
|
262
271
|
Both files are optional; omit any field to take its default, except \`sessions.defaultLauncher\` when using
|
|
263
272
|
\`spex session new\` or the dashboard without an explicit launcher choice.
|
|
264
273
|
|
|
274
|
+
The host-wide gateway has one separate per-user setting, \`gateway.icon\` in
|
|
275
|
+
\`$SPEXCODE_HOME/config.json\`. It is documented below and never belongs to either project file.
|
|
276
|
+
|
|
265
277
|
MERGE: spexcode.local.json is layered over spexcode.json ONE LEVEL DEEP — per top-level section (dashboard,
|
|
266
278
|
sessions, …), the two objects are shallow-merged with LOCAL WINNING per key; sections only one file names
|
|
267
279
|
pass through untouched. This is exactly what lets a launcher's portable NAME reference (defaultLauncher)
|
|
@@ -277,12 +289,27 @@ Example — a repo whose trunk is \`staging\`, not \`main\`:
|
|
|
277
289
|
|
|
278
290
|
── DASHBOARD (spexcode.json — portable project identity) ──
|
|
279
291
|
dashboard.title browser-tab name. Default: the repo-root basename.
|
|
280
|
-
dashboard.icon
|
|
292
|
+
dashboard.icon the project's icon identity. The Projects UI writes one shared preset id:
|
|
293
|
+
spexcode | gateway | mdi:rocket-launch | compass | terminal | package | database | spark.
|
|
294
|
+
Existing emoji ("🔭"), arbitrary Iconify names ("lucide:radar"), and full URLs remain
|
|
295
|
+
supported; picking a preset migrates that one existing field, never creates another.
|
|
281
296
|
dashboard.apiUrl the per-project backend the dashboard proxies to (read frontend-side). For a SHARED
|
|
282
297
|
install prefer the API_URL env var; apiUrl here is the default only when the dashboard
|
|
283
298
|
lives inside the project.
|
|
299
|
+
dashboard.showHeadlessLaunchers
|
|
300
|
+
include launchers whose harness declares itself headless in the dashboard New Session
|
|
301
|
+
picker. Default: false. This changes dashboard visibility only; explicit CLI
|
|
302
|
+
--launcher selection can still use every configured launcher.
|
|
303
|
+
Example:
|
|
304
|
+
{ "dashboard": { "title": "MyApp specs", "icon": "mdi:rocket-launch",
|
|
305
|
+
"showHeadlessLaunchers": false } }
|
|
306
|
+
|
|
307
|
+
── HOST GATEWAY ($SPEXCODE_HOME/config.json — per-user host identity, never a project file) ──
|
|
308
|
+
gateway.icon the global /projects icon, using the same preset ids above. Default: "gateway".
|
|
309
|
+
The admin-only Projects picker writes this field atomically. It is the gateway's ONE
|
|
310
|
+
authored icon setting and is never copied into spexcode.json or spexcode.local.json.
|
|
284
311
|
Example:
|
|
285
|
-
{ "
|
|
312
|
+
{ "gateway": { "icon": "database" } }
|
|
286
313
|
|
|
287
314
|
── SESSIONS / WORKERS ──
|
|
288
315
|
sessions.maxActive concurrency cap — max agents AUTONOMOUSLY PROGRESSING at once (default 8;
|
|
@@ -291,8 +318,8 @@ Example:
|
|
|
291
318
|
Counts compute slots, not total sessions: idle/asking/review/done do not
|
|
292
319
|
occupy one. A policy number → committed spexcode.json; omit it to use the
|
|
293
320
|
default, or tune higher/lower for the project's usual host.
|
|
294
|
-
sessions.launchers the NAMED launcher profiles (see LAUNCHERS). \`spex init\` seeds
|
|
295
|
-
|
|
321
|
+
sessions.launchers the NAMED launcher profiles (see LAUNCHERS). \`spex init\` seeds one entry per
|
|
322
|
+
SELECTED harness (--harness); edit/add more like any other.
|
|
296
323
|
sessions.defaultLauncher the launcher name a create with no explicit --launcher/dropdown pick uses
|
|
297
324
|
(required for no-choice creates). A portable NAME → committed.
|
|
298
325
|
A launcher \`cmd\` that is a HOST-SPECIFIC ABSOLUTE PATH belongs in spexcode.local.json — the committed file
|
|
@@ -301,18 +328,28 @@ must stay free of machine paths.
|
|
|
301
328
|
── LAUNCHERS (the profile block, split across the two files) ──
|
|
302
329
|
A named launcher profile fixes BOTH a session's harness AND its exact launch command; a create picks one
|
|
303
330
|
by name with --launcher/the dashboard dropdown, and the chosen name is persisted on the record so a resume
|
|
304
|
-
reuses the same auth. There are NO magic built-ins: \`spex init\` SEEDS
|
|
305
|
-
|
|
306
|
-
"claude"
|
|
307
|
-
"
|
|
308
|
-
|
|
331
|
+
reuses the same auth. There are NO magic built-ins: \`spex init\` SEEDS an ordinary named launcher for each
|
|
332
|
+
harness the adopter SELECTED (--harness), from the template pool
|
|
333
|
+
"claude" → { "harness": "claude", "cmd": "claude" }
|
|
334
|
+
"claude-headless" → { "harness": "claude-headless", "cmd": "claude" }
|
|
335
|
+
"codex" → { "harness": "codex", "cmd": "codex" }
|
|
336
|
+
"opencode" → { "harness": "opencode", "cmd": "opencode" }
|
|
337
|
+
"opencode-headless" → { "harness": "opencode-headless", "cmd": "opencode --auto" }
|
|
338
|
+
"pi" → { "harness": "pi", "cmd": "pi" }
|
|
339
|
+
"pi-headless" → { "harness": "pi-headless", "cmd": "pi" }
|
|
340
|
+
The interactive profiles preserve each harness's normal permission model. \`opencode-headless\` is the one
|
|
341
|
+
deliberate seed exception: its terminal-free run requires \`opencode --auto\`; interactive \`opencode\` stays
|
|
342
|
+
plain. Other automatic-permission commands are NEVER clean-init defaults: define and select one explicitly
|
|
343
|
+
only when that access is intended. To run workers under an auth
|
|
309
344
|
wrapper (e.g. reclaude), point a launcher's \`cmd\` at it in spexcode.local.json — there is no environment
|
|
310
|
-
override that rewrites a launcher's command. Add more profiles when a project needs named auth/config-dir
|
|
311
|
-
variants. Shape:
|
|
312
|
-
"launchers": { "<name>": { "harness": "claude" | "codex"
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
in
|
|
345
|
+
override that rewrites a launcher's command. Add more profiles when a project needs named auth/config-dir or
|
|
346
|
+
permission variants. Shape:
|
|
347
|
+
"launchers": { "<name>": { "harness": "claude" | "codex" | "opencode" | "pi" | "claude-headless" | "opencode-headless" | "pi-headless",
|
|
348
|
+
"cmd": "<launch command>" } }
|
|
349
|
+
\`harness\` defaults to "claude"; \`cmd\` is required and embedded whole. A portable plain command may live
|
|
350
|
+
in committed spexcode.json (as the init seeds do). A host-specific command — an absolute wrapper path,
|
|
351
|
+
credential route, or personal permission choice — belongs in gitignored spexcode.local.json, while its
|
|
352
|
+
portable defaultLauncher NAME may stay in committed spexcode.json; the merge keeps both:
|
|
316
353
|
|
|
317
354
|
spexcode.json (committed — the portable name reference)
|
|
318
355
|
{ "sessions": { "defaultLauncher": "gpt5" } }
|
|
@@ -344,9 +381,11 @@ resolves its backend per this ladder, flag first:
|
|
|
344
381
|
sugar for --api http://127.0.0.1:<n>).
|
|
345
382
|
2a. worker (SPEXCODE_SESSION_ID set): env SPEXCODE_API_URL — the backend-injected lifeline; cwd
|
|
346
383
|
discovery never steals it.
|
|
347
|
-
2b. human (no session id): the cwd project's RECORDED live backend — \`spex serve\`
|
|
348
|
-
|
|
349
|
-
health-probes before trusting (a dead record is
|
|
384
|
+
2b. human (no session id): the cwd project's RECORDED live backend — \`spex serve\` publishes
|
|
385
|
+
{url,pid,instanceId,root} atomically to ~/.spexcode/projects/<enc>/backend.json
|
|
386
|
+
at bind time; the reader health-probes before trusting (a dead record is
|
|
387
|
+
ignored). The same record is what \`spex dashboard\` reconciles its
|
|
388
|
+
host-wide project list from.
|
|
350
389
|
3. the other side as fallback (human with no live record → env; worker with no env → record).
|
|
351
390
|
4. default http://127.0.0.1:$PORT||8787.
|
|
352
391
|
WRITES are project-bound: every mutating verb (new/merge/send/close/rename/resume/stop) refuses
|
|
@@ -371,12 +410,14 @@ the guard (the flag is the declaration of intent). Reads point anywhere.
|
|
|
371
410
|
lint.governedRoots dirs whose source files must each be governed by a spec (coverage).
|
|
372
411
|
'.' = the whole project (only git-TRACKED files). Default
|
|
373
412
|
["spec-dashboard/src", "spec-cli/src"].
|
|
374
|
-
lint.
|
|
375
|
-
|
|
376
|
-
lint.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
413
|
+
lint.sourceIncludeGlobs optional repo-relative includes. Omit = ALL tracked current regular text under
|
|
414
|
+
governedRoots; [] = intentionally none. Slash-less globs match at any depth.
|
|
415
|
+
lint.sourceExcludeGlobs repo-relative paths subtracted from the includes (default []). Nothing about
|
|
416
|
+
docs/vendor/build/config is guessed: exclude it here when that is project policy.
|
|
417
|
+
lint.sourceExtensions compatibility shorthand compiled into include globs (".py" → "**/*.py") and
|
|
418
|
+
unioned with sourceIncludeGlobs; it has no separate matching path.
|
|
419
|
+
lint.testGlobs globs EXCLUDED from coverage. Defaults cover .test/.spec names, test/tests/
|
|
420
|
+
directories, and test_* / *_test conventions; [] governs tests too.
|
|
380
421
|
lint.maxOwners warn when a file is governed WHOLE-FILE by > this many nodes (default 3).
|
|
381
422
|
Selector-scoped governors (code: path#symbol) don't count toward the bound.
|
|
382
423
|
(lint.driftErrorThreshold is RETIRED: the count-based commit gate is replaced
|
|
@@ -389,14 +430,32 @@ the guard (the flag is the declaration of intent). Reads point anywhere.
|
|
|
389
430
|
freshness. A project policy → committed spexcode.json.
|
|
390
431
|
lint.scenarioTags the closed vocabulary an eval scenario's tags: must draw from (default
|
|
391
432
|
["frontend-e2e","backend-api","cli","desktop","mobile"]); extend to mint a tag.
|
|
392
|
-
Example — govern your own source dir
|
|
393
|
-
{ "lint": { "governedRoots": ["src"]
|
|
433
|
+
Example — govern your own source dir:
|
|
434
|
+
{ "lint": { "governedRoots": ["src"] } }
|
|
435
|
+
Example — declare project-specific exclusions (nothing is guessed from these names):
|
|
436
|
+
{ "lint": { "governedRoots": ["."], "sourceExcludeGlobs": ["vendor/**", "dist/**", "docs/**"] } }
|
|
437
|
+
Migration: \`lint.maxChildren\` is RETIRED and no longer read; move its value to
|
|
438
|
+
\`doctor.breadth.maxChildren\`. Bare \`spex doctor\` flags a stale key.
|
|
439
|
+
|
|
440
|
+
── DOCTOR HEALTH (spexcode.json — portable advisory-diagnosis budgets) ──
|
|
441
|
+
doctor.altitude the one altitude proxy config consumed by bare \`spex doctor\`:
|
|
442
|
+
{ lineBudget, charBudget, sizeable, dense, steps, identifierExtensions }
|
|
443
|
+
Defaults: 50 / 4200 / 35 / 1.3 / 3 / []. Exact filename signals derive from
|
|
444
|
+
lint's tracked source candidates; identifierExtensions adds compatibility wildcard
|
|
445
|
+
rows (".legacy" → "*.legacy") to that same matcher.
|
|
446
|
+
doctor.breadth the one tree-breadth hypothesis consumed by bare \`spex doctor\`:
|
|
447
|
+
{ maxChildren }. Default: 8. Reports each affected node's direct child count and
|
|
448
|
+
points to the regroup workflow; genuine flat peers may remain flat.
|
|
449
|
+
Example — tune opt-in health diagnosis without changing the lint gate:
|
|
450
|
+
{ "doctor": { "altitude": { "lineBudget": 70 }, "breadth": { "maxChildren": 10 } } }
|
|
394
451
|
|
|
395
452
|
── OTHER (spexcode.json unless noted) ──
|
|
396
453
|
preset the SELECTED init preset — which cumulative .plugins tier \`spex init\` seeds (default
|
|
397
454
|
'default'; seed-time only, read by init.ts).
|
|
398
|
-
harnesses which harness targets \`spex materialize\` delivers into — native ids
|
|
399
|
-
|
|
455
|
+
harnesses which harness targets \`spex materialize\` delivers into — native ids
|
|
456
|
+
("claude"|"codex"|"opencode"|"pi"|"claude-headless"|"opencode-headless"|"pi-headless") or a
|
|
457
|
+
{ "plugin": "<folder>" } bundle. REQUIRED — there is no default set: \`spex init --harness\`
|
|
458
|
+
stamps the explicit choice, and a missing field fails materialize loud. PERSISTENT and
|
|
400
459
|
git-transactional: the edit takes effect at the next git-native materialize anchor (the commit
|
|
401
460
|
that carries it, a checkout/merge that receives it, or a manual \`spex materialize\`) — a
|
|
402
461
|
deselected harness's artifacts are pruned by that pass.`
|
|
@@ -13,19 +13,33 @@ export type HarnessTarget =
|
|
|
13
13
|
| { kind: 'native'; id: HarnessId }
|
|
14
14
|
| { kind: 'plugin'; folder: string }
|
|
15
15
|
|
|
16
|
-
// the
|
|
17
|
-
|
|
16
|
+
// the native vocabulary (for messages and init's flag parsing). There is NO default set: the delivery
|
|
17
|
+
// targets are an explicit adopter choice, stamped by `spex init --harness` and read back from config forever.
|
|
18
|
+
export const NATIVE_HARNESS_IDS: readonly HarnessId[] = HARNESSES.map((h) => h.id)
|
|
18
19
|
const KNOWN: readonly string[] = HARNESSES.map((h) => h.id)
|
|
19
20
|
|
|
21
|
+
// the one-line repair every missing-selection error carries — the field is REQUIRED, never defaulted,
|
|
22
|
+
// because with many harnesses a silent "deliver everywhere" would litter the adopter's tree with artifacts
|
|
23
|
+
// for tools they never installed.
|
|
24
|
+
const MISSING = `spexcode.json has no "harnesses" field — the delivery targets are an EXPLICIT choice, never a default. Declare it, e.g. "harnesses": ["claude"] — members are native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}. Fresh adoption: \`spex init --harness <ids>\` stamps it.`
|
|
25
|
+
|
|
26
|
+
// parse `spex init --harness <spec>` into the raw JSON members the `harnesses` field carries: a comma-
|
|
27
|
+
// separated list where `plugin:<folder>` means a plugin target and anything else is a native id. Pure
|
|
28
|
+
// spelling translation — legality (unknown ids, plugin exclusivity) stays with resolveHarnessTargets.
|
|
29
|
+
export function parseHarnessFlag(spec: string): unknown[] {
|
|
30
|
+
return spec.split(',').map((s) => s.trim()).filter(Boolean)
|
|
31
|
+
.map((s) => (s.startsWith('plugin:') ? { plugin: s.slice('plugin:'.length) } : s))
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
// parse + validate the spexcode.json `harnesses` field into resolved targets. FAIL LOUD on an illegal set —
|
|
21
35
|
// materialize and init both gate on this so a bad config never silently delivers the wrong thing. `raw` is the
|
|
22
|
-
// JSON value as written;
|
|
36
|
+
// JSON value as written; a MISSING field is itself illegal (the choice must exist, stamped by init).
|
|
23
37
|
export function resolveHarnessTargets(raw: unknown): HarnessTarget[] {
|
|
24
|
-
if (raw === undefined || raw === null)
|
|
38
|
+
if (raw === undefined || raw === null) throw new Error(MISSING)
|
|
25
39
|
if (!Array.isArray(raw))
|
|
26
|
-
throw new Error(`spexcode.json "harnesses" must be an ARRAY of targets (got ${typeof raw}). Members are native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}
|
|
40
|
+
throw new Error(`spexcode.json "harnesses" must be an ARRAY of targets (got ${typeof raw}). Members are native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}.`)
|
|
27
41
|
if (raw.length === 0)
|
|
28
|
-
throw new Error(`spexcode.json "harnesses" is EMPTY — list at least one target
|
|
42
|
+
throw new Error(`spexcode.json "harnesses" is EMPTY — list at least one target: native ids (${KNOWN.join(', ')}) or {"plugin":"<folder>"}.`)
|
|
29
43
|
const targets: HarnessTarget[] = []
|
|
30
44
|
for (const m of raw) {
|
|
31
45
|
if (typeof m === 'string') {
|