spexcode 0.4.1 → 0.4.3

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 (106) hide show
  1. package/README.md +37 -17
  2. package/package.json +9 -5
  3. package/spec-cli/README.md +23 -14
  4. package/spec-cli/hooks/dispatch.sh +7 -5
  5. package/spec-cli/hooks/harness.sh +19 -8
  6. package/spec-cli/src/anchors.ts +110 -18
  7. package/spec-cli/src/cli.ts +88 -37
  8. package/spec-cli/src/client.ts +34 -11
  9. package/spec-cli/src/doctor.ts +174 -36
  10. package/spec-cli/src/gateway-auth.ts +194 -0
  11. package/spec-cli/src/gateway-hub.ts +360 -0
  12. package/spec-cli/src/gateway.ts +27 -19
  13. package/spec-cli/src/git.ts +12 -5
  14. package/spec-cli/src/graph.ts +54 -43
  15. package/spec-cli/src/graphStream.ts +265 -34
  16. package/spec-cli/src/guide.ts +105 -55
  17. package/spec-cli/src/harness-select.ts +20 -6
  18. package/spec-cli/src/harness.ts +181 -33
  19. package/spec-cli/src/help.ts +64 -27
  20. package/spec-cli/src/host.ts +610 -0
  21. package/spec-cli/src/identity-presets.d.ts +13 -0
  22. package/spec-cli/src/identity-presets.js +128 -0
  23. package/spec-cli/src/index.ts +145 -86
  24. package/spec-cli/src/init.ts +53 -25
  25. package/spec-cli/src/layout.ts +5 -5
  26. package/spec-cli/src/lint.ts +25 -121
  27. package/spec-cli/src/login-page.ts +12 -5
  28. package/spec-cli/src/materialize.ts +39 -13
  29. package/spec-cli/src/mentions.ts +16 -11
  30. package/spec-cli/src/opencode.ts +145 -0
  31. package/spec-cli/src/pi-harness.ts +124 -0
  32. package/spec-cli/src/project-identity.ts +73 -0
  33. package/spec-cli/src/pty-bridge.ts +321 -521
  34. package/spec-cli/src/pty-helper.mjs +86 -0
  35. package/spec-cli/src/reaper.ts +16 -8
  36. package/spec-cli/src/reviewSnapshot.ts +23 -0
  37. package/spec-cli/src/reviews.ts +347 -0
  38. package/spec-cli/src/session-timeline.ts +14 -0
  39. package/spec-cli/src/sessions.ts +188 -66
  40. package/spec-cli/src/shim-runtime.ts +169 -0
  41. package/spec-cli/src/slash-commands.ts +76 -0
  42. package/spec-cli/src/source-files.ts +79 -0
  43. package/spec-cli/src/specs.ts +12 -7
  44. package/spec-cli/src/supervise.ts +37 -15
  45. package/spec-cli/src/tree.ts +8 -8
  46. package/spec-cli/src/uninstall.ts +56 -23
  47. package/spec-cli/templates/hooks/prepare-commit-msg +32 -18
  48. package/spec-cli/templates/spec/project/.plugins/commands/extract/spec.md +24 -18
  49. package/spec-cli/templates/spec/project/.plugins/commands/regroup/spec.md +3 -3
  50. package/spec-cli/templates/spec/project/.plugins/commands/rename/spec.md +11 -0
  51. package/spec-cli/templates/spec/project/.plugins/commands/spec.md +22 -8
  52. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
  53. package/spec-cli/templates/spec/project/.plugins/commands/tidy/spec.md +29 -11
  54. package/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
  55. package/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +2 -0
  56. package/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +1 -1
  57. package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec-first.sh +28 -39
  58. package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec.md +5 -3
  59. package/spec-cli/templates/spec/project/.plugins/core/spec-of-file/spec.md +1 -1
  60. package/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -2
  61. package/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +7 -1
  62. package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +2 -2
  63. package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +5 -5
  64. package/spec-cli/templates/spec/project/.plugins/prompts/spec.md +10 -6
  65. package/spec-cli/templates/spec/project/.plugins/skills/distill/spec.md +38 -50
  66. package/spec-cli/templates/spec/project/.plugins/skills/spec.md +9 -7
  67. package/spec-cli/templates/spec/project/.plugins/spec.md +17 -12
  68. package/spec-cli/templates/spec/project/spec.md +6 -5
  69. package/spec-cli/templates/spexcode.json +4 -2
  70. package/spec-dashboard/dist/assets/Dashboard-CiHh-gLD.js +27 -0
  71. package/spec-dashboard/dist/assets/EvalsPage-DmiX3rdU.js +2 -0
  72. package/spec-dashboard/dist/assets/IssuesPage-CIbVGRUJ.js +1 -0
  73. package/spec-dashboard/dist/assets/MobileApp-D-N9_eh0.js +2 -0
  74. package/spec-dashboard/dist/assets/Modal-DHMzSFJ4.js +1 -0
  75. package/spec-dashboard/dist/assets/PageScroll-C15adEYI.js +1 -0
  76. package/spec-dashboard/dist/assets/ProjectsPage-sQpzglp5.js +1 -0
  77. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +32 -0
  78. package/spec-dashboard/dist/assets/SessionInterface-B8pGU7Rg.js +39 -0
  79. package/spec-dashboard/dist/assets/SessionWindow-CuDO_67z.js +13 -0
  80. package/spec-dashboard/dist/assets/Settings-C_N1wX1f.js +1 -0
  81. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +1 -0
  82. package/spec-dashboard/dist/assets/index-DmWbmvCq.js +41 -0
  83. package/spec-dashboard/dist/assets/index-GGIVdKwH.css +1 -0
  84. package/spec-dashboard/dist/assets/terminalFont-DmTPNdbU.js +1 -0
  85. package/spec-dashboard/dist/index.html +10 -10
  86. package/spec-dashboard/src/reviewFilters.js +279 -0
  87. package/spec-dashboard/src/reviewQuery.js +174 -0
  88. package/spec-dashboard/src/session.js +167 -0
  89. package/spec-eval/src/cli.ts +49 -28
  90. package/spec-eval/src/humanok.ts +1 -1
  91. package/spec-eval/src/matrix.ts +693 -0
  92. package/spec-eval/src/sessioneval.ts +726 -66
  93. package/spec-cli/src/migrate-table.ts +0 -408
  94. package/spec-cli/src/migrate.ts +0 -386
  95. package/spec-dashboard/dist/assets/Dashboard-C_fGmOKK.js +0 -27
  96. package/spec-dashboard/dist/assets/EvalsPage-Cnr1s3bq.js +0 -2
  97. package/spec-dashboard/dist/assets/FoldToggle-x9gtO1OQ.js +0 -1
  98. package/spec-dashboard/dist/assets/IssuesPage-5f_vL-JV.js +0 -1
  99. package/spec-dashboard/dist/assets/MobileApp-DEO1jgGM.js +0 -1
  100. package/spec-dashboard/dist/assets/SessionInterface-CAlbMOFR.js +0 -66
  101. package/spec-dashboard/dist/assets/SessionInterface-DYP7pi_n.css +0 -32
  102. package/spec-dashboard/dist/assets/SessionWindow-JYbpPwNB.js +0 -13
  103. package/spec-dashboard/dist/assets/Settings-DKb5Ji_X.js +0 -1
  104. package/spec-dashboard/dist/assets/index-BQu-oJ8J.js +0 -41
  105. package/spec-dashboard/dist/assets/index-BbMkwuix.css +0 -1
  106. package/spec-dashboard/dist/assets/launch-BM9GgvkX.js +0 -6
@@ -10,6 +10,6 @@ block: false
10
10
  ---
11
11
  A non-blocking per-edit annotation. The first time a session edits a given file, it names the spec node(s) that GOVERN that file — and, when a file is over-owned, flags that it is doing too much and points at the split — so the contract is in view at the very moment of the edit, not only later at commit or drift time.
12
12
 
13
- It never renders a verdict: it only adds context, so it can inform without interrupting. It is deduplicated once per file via a session ledger, so a fifty-edit refactor annotates each file once rather than on every write — the discipline that keeps a pervasive signal from decaying into the noise it is meant to cure. It speaks only when there is something to say: a sanely-owned file draws silence, an over-owned or uncovered one draws the pointer.
13
+ It never renders a verdict: it only adds context, so it can inform without interrupting. Like [[spec-first]], spec-awareness is universal — it is NOT gated on `governed` and runs for any agent. It is deduplicated once per normalized repo-relative file via a ledger that lives as a sibling file in the session's global store dir (keyed by the payload's `session_id`), so a fifty-edit refactor annotates each file once rather than on every write — the discipline that keeps a pervasive signal from decaying into the noise it is meant to cure. It speaks only when there is something to say: a sanely-owned file draws silence, an over-owned or uncovered one draws the pointer. It only speaks for Git-relevant files inside the current repo: tracked files, plus new untracked files that are not ignored. Paths outside the repo, `.spec`, `.git`, and ignored artifacts stay silent.
14
14
 
15
15
  This is the at-the-keystroke companion to the read-first gate [[spec-first]] and the commit-time checks: together they keep the [[core]] rule — code must not silently diverge from its spec — visible across the whole edit loop.
@@ -6,7 +6,7 @@ hue: 200
6
6
  desc: A config plugin — the minimal spec-discipline contract folded into every launched agent.
7
7
  code:
8
8
  ---
9
- The CLI speaks ONE grammar: `spex <noun> <verb> [object] [flags]`. Six nouns — `spec` (the governance graph), `session` (the worktree state machine), `eval` (the measurement system), `issue` (concern threads), `remark` (resolvable pins), `evidence` (content-addressed bytes) — plus a few bare project verbs (`graph` · `init` · `materialize` · `doctor` · `serve` · `uninstall`) whose object is always this project. The verb is always the token right after its noun; a bare noun prints its drawer's help; a `--help` probe is always safe (it prints and exits before the verb runs). `SEL` = session id | unique id-prefix | node id | branch; `.` as a node argument = the node this worktree works on. When unsure of a spelling, ask the tool — `spex help`, `spex help <command>` — never guess from memory: removed spellings only report their replacement and exit.
9
+ The CLI speaks ONE grammar: `spex <noun> <verb> [object] [flags]`. Six nouns — `spec` (the governance graph), `session` (the worktree state machine), `eval` (the measurement system), `issue` (concern threads), `remark` (resolvable pins), `evidence` (content-addressed bytes) — plus a few bare project verbs (`graph` · `init` · `materialize` · `doctor` · `serve` · `uninstall`) whose object is always this project. The verb is always the token right after its noun; a bare noun prints its drawer's help; a `--help` probe is always safe (it prints and exits before the verb runs). `SEL` = session id | unique id-prefix | node id | branch; inside a session worktree, `.` as a session selector = that worktree's own session, while `.` as a node argument = the node this worktree works on. When unsure of a spelling, ask the tool — `spex help`, `spex help <command>` — never guess from memory: removed spellings only report their replacement and exit.
10
10
 
11
11
  Four disciplines, non-negotiable:
12
12
 
@@ -16,6 +16,6 @@ Four disciplines, non-negotiable:
16
16
 
17
17
  3. THE BODY IS A LIVING CURRENT-STATE DOCUMENT. It states the node's PRESENT intent and is rewritten in place. Never accrete a "## vN" changelog heading, and never add current-state or verdict sections — version history is git's job, not the body's.
18
18
 
19
- 4. KEEP THE LOSS SIGNAL HONEST for what you changed — eval readings are the signal the optimizer reads, so a gap is a blind spot. Changed a node that carries a `eval.md`? Re-measure it: run its scenario, compare to the expected, and file the result with `spex eval add <node>`. Made an obvious frontend change to a node with NO `eval.md`? Give it one a scenario (description + expected) — so its loss can be measured. A frontend scenario is measured through the **actual running product** drive a real browser, read the real DOM and capture a screenshot (or video), never reason about the code and that real observation is filed as the reading, not left as an ad-hoc check you ran but never recorded. `spex eval lint --changed` shows the gaps in exactly the nodes you touched.
19
+ 4. KEEP THE LOSS SIGNAL HONEST for what you changed — eval readings are the signal the optimizer reads, so a gap is a blind spot. Changed a node that carries a `eval.md`? Re-measure it: run its scenario, compare to the expected, and file the result with `spex eval add <node>`. Match the evidence to the behaviour: a DYNAMIC scenario anything that moves or is timed (terminal scroll/redraw, an animation or transition, media playback, a multi-step interaction flow) — records a video of the run and files it with `--video`; a STATIC end state screenshots with `--image`; a backend/CLI scenario files its transcript with `--result`. A still of a moving thing proves the wrong thing. File the reading only AFTER the change it measures is committed a reading's `codeSha` anchors to HEAD at filing time, so verify on the working tree, commit the verified tree, then file; a dirty-tree filing names a commit that lacks your change. Made an obvious frontend change to a node with NO `eval.md`? Give it one — a scenario (description + expected) — so its loss can be measured. `spex eval lint --changed` shows the gaps in exactly the nodes you touched.
20
20
 
21
21
  Don't reverse-engineer the file formats: `spex guide spec` and `spex guide eval` print the full spec.md and scenario schema on demand. This prompt is the clue; that manual carries the detail.
@@ -12,6 +12,12 @@ The blocking stop gate, with two jobs, each holding a hard loop-break so it neve
12
12
 
13
13
  The COMMIT gate keeps a done/merge proposal honest: such a proposal is rejected while the branch still carries uncommitted work or is zero commits ahead of main, because the ritual commits the spec and code BEFORE proposing. Clean work is allowed to stop; a dirty proposal blocks once with the reason, and if the agent ignores it the gate escapes by downgrading to `asking` so a false "ready to merge" can never stand.
14
14
 
15
- The DECLARE gate refuses to let a session stop in an undeclared `active` state, since a state is a claim the board and other agents act on, not a box ticked to end a turn. A declared state stops freely; an undeclared first stop blocks once to make the agent pick the true state; on the forced continuation it auto-declares a safe default — committed work becomes `awaiting`, otherwise `asking` — so the loop is guaranteed to end. The full block text (choices, each with its application condition, plus the discipline of declaring as the turn's LAST call — any later tool call re-flips the record to active) prints once per session, marked by a sentinel file beside the session record; later undeclared stops get a one-line reminder that stays self-explanatory — menu, declare-last, and the `spex help session` entry that recovers the full conditions.
15
+ The DECLARE gate refuses to let a session stop in an undeclared `active` state, since a state is a claim the board and other agents act on, not a box ticked to end a turn. A declared state stops freely; an undeclared first stop emits `{"decision":"block"}` and the dispatcher exits 2 so the harness actually interrupts the stop and shows the reason; on the forced continuation it auto-declares a safe default — committed work becomes `awaiting`, otherwise `asking` — so the loop is guaranteed to end.
16
+
17
+ The block text is where the declaration ritual is taught, so it is written to be read at two depths. The FULL teaching text prints once per session: it names the PATH-independent CLI once as a shared prefix, lists the five choices as a compact menu each with its application condition (park policed hardest — a false park is the most damaging mislabel), and ends with the ordering discipline: declare LAST, then stop — a declaration followed by more tool calls honestly re-flips the record to active ([[mark-active]], by design), so making the declaration the turn's final call is what eliminates the park→block→re-park loop at its source. Every later undeclared stop in the same session gets a ONE-LINE version instead (a heavy session hits the gate 15-20 times a night; re-printing the full menu is token noise). The once-sentinel is a plain file beside the session record in the global store — the same per-session-sentinel mechanism as the CLI's note-truncation notice, never a second scheme. The terse line stays self-explanatory: it carries the command menu, the declare-LAST reminder, and the `spex help session` recovery entry, so an agent that never saw the full text (a compacted context) recovers every choice's condition from the entry rather than from memory — the whole full-to-terse information gap is closable from the line itself.
18
+
19
+ The clean-done eval nudge is advisory only and must never corrupt the Stop hook protocol. Claude-family hooks can receive it as `hookSpecificOutput.additionalContext`; Codex Stop allows are silent because Codex treats unsupported non-block stdout as invalid hook JSON. Blocking decisions stay shared across harnesses through `{"decision":"block"}` plus the dispatcher’s Codex stderr bridge.
20
+
21
+ Both gates act ONLY on a GOVERNED (dashboard-launched) session: the gate resolves the session's record in the global store from the payload's `session_id`, and on a non-governed (user-self-launched) record — or none — it exits 0 SILENTLY. A self-launched agent has no board to feed, so the declare-demand must never misfire on it. Its own state writes go through `spex session … --session <id>`, passing the id explicitly since there is no worktree file to read it from.
16
22
 
17
23
  It is the enforcement edge of [[core]]: nothing leaves a session except as committed work under a truthful declaration. The freshness it reads is set by [[mark-active]].
@@ -3,7 +3,7 @@ title: forge-link
3
3
  surface: system
4
4
  status: active
5
5
  hue: 280
6
- desc: A config plugin — agents link an issue they open to the spec node it serves via a `Spec: <id>` body line.
6
+ desc: A config plugin — agents link an issue or change request opened through the resolved forge to the spec node it serves via one `Spec: <id>` body line.
7
7
  code:
8
8
  ---
9
- When you open a GitHub issue, link it to the spec node(s) it serves by adding a line to the issue **body**: `Spec: <node-id>` (comma-separate several). The id is the node's **leaf** name — the folder under `.spec/…/<id>/spec.md`, e.g. `sessions`, never the slash-path. An unrecognized id silently links nothing, so use a real node id (`spex graph --json` lists them). A pull request needs no marker: opening it from your `node/<id>` branch links it for free.
9
+ When you open an issue or change request through the repository's resolved forge, link it to the spec node(s) it serves by adding one line to its **body**: `Spec: <node-id>` (comma-separate several). The id is the node's **leaf** name — the folder under `.spec/…/<id>/spec.md`, e.g. `sessions`, never the slash-path. An unrecognized id silently links nothing, so use a real node id (`spex graph --json` lists them). This is the one linking marker on every forge.
@@ -3,16 +3,16 @@ title: reproduce-before-fix
3
3
  surface: system
4
4
  status: active
5
5
  hue: 140
6
- desc: A config plugin — a bug fix must first REPRODUCE the failure as a failing eval, then fix, then file the passing eval. The fail→pass pair on one scenario is the fix's proof (the A/B).
6
+ desc: A config plugin — a bug fix must first REPRODUCE the failure as a failing eval, then fix, verify, commit, and file the passing eval. The fail→pass pair on one scenario is the fix's proof (the A/B).
7
7
  code:
8
8
  ---
9
9
  ## Reproduce before you fix — the fix's proof is a fail→pass pair
10
10
 
11
11
  If your task is to FIX A BUG, reproduce it *first*, as a measurement — before you touch the fix. A claim that something is broken is worth nothing until the loss signal shows it broken; a claim that you fixed it is worth nothing until the same signal shows it passing. So a bug fix is bracketed by two readings of ONE scenario:
12
12
 
13
- - **A — reproduce (fail).** Find the eval scenario whose expected the bug violates (if none fits, ADD one to the node's `eval.md` — a description + the expected correct behaviour), run it, and file the failing reading with evidence that SHOWS the bug: `spex eval add <node> --scenario <s> --fail --note "<what's wrong>"` plus an `--image`/`--video` of the actual broken behaviour. This is not ceremony — reproducing is how you learn what actually breaks, and a fix aimed at an unreproduced bug aims at a guess.
14
- - **B — fix, then re-measure (pass).** Make the code honor the spec, run the SAME scenario again, and file the passing reading with evidence of the corrected behaviour: `spex eval add <node> --scenario <s> --pass`.
13
+ - **A — reproduce (fail).** Find the eval-core scenario whose expected the bug violates (if none fits, ADD one to the node's `eval.md` — a description + the expected correct behaviour), run it, and file the failing reading with evidence that SHOWS the bug: `spex eval add <node> --scenario <s> --fail --note "<what's wrong>"` plus an `--image`/`--video` of the actual broken behaviour. This is not ceremony — reproducing is how you learn what actually breaks, and a fix aimed at an unreproduced bug aims at a guess.
14
+ - **B — fix, verify, commit, then file (pass).** Four moves, in order: make the code honor the spec; run the SAME scenario against your **working tree** until it actually passes — confidence is earned here, before any commit; **commit** the verified tree; only then file the passing reading, `spex eval add <node> --scenario <s> --pass`, so its `codeSha` names the very commit you measured (the sidecar append lands last, on top of it). Confidence and anchoring are two different acts: you test the uncommitted tree, but the reading's sha anchor can only land after the commit — this is NOT "commit first, then test" (blind commits with routine rollbacks are bad git). A git sha names only a commit and an uncommitted change has none, so a reading filed from a dirty tree is born mis-anchored — its `codeSha` is a HEAD that does not contain the change it claims to measure — and freshness later marking it stale is the mis-anchor being correctly exposed, not an engine bug. (A needs no such care: it measures the bug on the old, already-committed HEAD, so its anchor is honest as-is.)
15
15
 
16
- The two readings on the same scenario are the **A/B** — the error→correct transition, the fix's proof-of-work. eval keeps per-scenario reading history, so the pair is durable and navigable end to end.
16
+ The two readings on the same scenario are the **A/B** — the error→correct transition, the fix's proof-of-work. eval already keeps per-scenario reading history, so the pair is durable and navigable end to end: the dashboard annotator flips between A (fail) and B (pass), and a trunk delivery (deliver-port) carries that pair as the evidence the fix is real. Don't skip A because the fix looks obvious — an obvious fix with no reproduced failure leaves the loss signal blind to exactly the regression you just closed.
17
17
 
18
- Don't skip A because the fix looks obvious — an obvious fix with no reproduced failure leaves the loss signal blind to exactly the regression you just closed. This does not apply to building new intent (there is no prior failure to reproduce) — it is the discipline for **repair**: keep the loss signal honest across a bug's whole lifecycle, not just at the end.
18
+ This does not apply to building new intent (there is no prior failure to reproduce) — it is the discipline for **repair**: keep the loss signal honest across a bug's whole lifecycle, not just at the end.
@@ -2,19 +2,23 @@
2
2
  title: prompts
3
3
  status: active
4
4
  hue: 110
5
- desc: Grouping shelf for the auxiliary `surface: system` prompt contracts — single-body always-on prose. `core` is not a resident — the core contract subsystem is a direct `.plugins` child. A shelf, not a surface — routing stays field-driven.
5
+ desc: Grouping shelf for the auxiliary `surface: system` prompt contracts — single-body always-on prose. Core is NOT a resident — the core contract subsystem is a direct `.plugins` child. A shelf, not a surface — routing stays field-driven per surface.
6
6
  ---
7
7
  # prompts
8
8
 
9
9
  The **auxiliary** `surface: system` prompt contracts live here: leaf plugins whose whole substance is one
10
10
  prose body an agent must always carry — materialized (in name order, together with every other system
11
11
  body) into the `<!-- spexcode -->` contract block — rather than a verb it invokes. Grouping them keeps
12
- `.plugins/` legible at a glance: peripheral prompt contracts on this shelf; the invocable surfaces
13
- (command/skill/agent) and `core` as flat children beside it.
12
+ `.plugins/` legible at a glance: peripheral prompt contracts on this shelf; the command presets on
13
+ [[commands]], the skill plugins on [[skills]], and [[core]] as a flat child beside it.
14
14
 
15
15
  The shelf boundary is **leafness, not surface**: a single-body prose contract shelves here, while a
16
- contract that anchors its own subtree outranks the shelf — `core`, the contract subsystem whose children
17
- are the `surface: hook` gates, is a *peer* of this shelf, never a resident.
16
+ contract that anchors its own subtree outranks the shelf — [[core]], the dev-flow contract subsystem
17
+ whose children are the `surface: hook` gates, is a *peer* of this shelf, never a resident.
18
18
 
19
19
  This node is a **shelf, not a surface**: it declares no `surface` field and gathers nothing itself.
20
- Discovery is recursive and field-driven, so residents plug in exactly as they would at the root.
20
+ Discovery is recursive and field-driven (surface), so residents plug in exactly as they would at the
21
+ root — nothing about materialization order (name order, unchanged names) or gathering changes with the
22
+ path. The init templates mirror this layout — a fresh `spex init` (per init-preset) seeds `core`
23
+ flat and its auxiliary system plugins under the same `prompts/` shelf. The shelf stays pure presentation:
24
+ changing this path changes neither a plugin's identity nor its gathered surface.
@@ -3,72 +3,60 @@ title: distill
3
3
  surface: skill, command
4
4
  status: active
5
5
  hue: 210
6
- desc: Use when the human wants to inherit a past or dead session's knowledge and work — "distill session X / 继承那个 session 的经验 / 接手它的工作 / 把之前 session 的东西捞回来 / harvest, salvage a finished session". Given a session id, read its transcript from disk (NEVER resume or re-prompt it its cache is cold and a re-prime is expensive), distill goal · decisions · traps · next steps into the current session, and if its worktree/branch never merged, carry the work over and retire the resources.
6
+ desc: Use when the user wants to inherit a finished, dead, or abandoned session — "distill session X / 继承那个 session 的经验 / 接手它的工作 / harvest or salvage a session". Given a SpexCode session id, harness thread id, or transcript path, read it without resuming, extract decisions, traps, and next actions, salvage unmerged work, retire only resources proven safe to remove, then rename the inheriting session to mark the handoff.
7
7
  ---
8
8
 
9
9
  # distill
10
10
 
11
- Inherit a finished (or dead) session's **mind and desk** without waking it mind = its transcript on disk,
12
- desk = its worktree/branch. The one iron rule: **never resume, reopen, send to, or otherwise re-prompt the
13
- old session** (cold cache: any turn pays a full re-prime). Everything below is read-only files and plain git.
11
+ Inherit a finished session's **mind and desk** without waking it: mind is its transcript; desk is its
12
+ worktree and branch. **Never resume, reopen, send to, or otherwise re-prompt the old session.** Read files
13
+ and git state only until its work has landed.
14
14
 
15
- ## 1 · resolve the session
15
+ ## Resolve and digest
16
16
 
17
- Input: a session id — SpexCode's, a bare harness id (claude / codex thread), or a transcript `.jsonl` path.
17
+ Accept a SpexCode session id, a Claude/Codex thread id, or a transcript `.jsonl` path.
18
18
 
19
- - **SpexCode session** (first choice the join is first-class): its record is
20
- `~/.spexcode/projects/*/sessions/<id>/session.json` glob for the id, prefix ok. Take `worktree_path`,
21
- `branch`, `harness`, `harness_session_id`, `status`, `title`; the originating goal is
22
- `spex session show <id>` (the record's prompt). For a claude-harness session the transcript id IS the SpexCode session id;
23
- for codex it is `harness_session_id`.
24
- - **Any other session**: treat the arg as the harness's own id. The transcript carries `cwd` (and, unless
25
- the worktree was detached, a branch) — the digest header surfaces them; that is your join to its desk.
19
+ - For a SpexCode id, find `~/.spexcode/projects/*/sessions/<id>/session.json` (a unique prefix is enough).
20
+ Keep its prompt, `worktree_path`, `branch`, `harness`, and `harness_session_id`; Claude uses the session
21
+ id as its transcript id, while Codex uses `harness_session_id`.
22
+ - Otherwise pass the harness id or transcript path directly. The digest header supplies its `cwd` and branch
23
+ when recorded; those locate the desk even when it belongs to another repository.
26
24
 
27
- ## 2 · digest the transcript mechanical first, model second
25
+ From the repository root run the plugin's co-located digest:
28
26
 
29
- `node .spec/<root>/.plugins/distill/digest.mjs <id-or-path>` locates the transcript (claude:
30
- `$CLAUDE_CONFIG_DIR` and every `~/.claude*` config dir → `projects/*/<id>.jsonl`; codex: `$CODEX_HOME` or
31
- `~/.codex` → `sessions/**/rollout-*<id>.jsonl`) and prints a compact digest: the human's prompts in full,
32
- the agent's own text, tool calls as one-liners, error results, and a footer with the files it edited and
33
- the raw transcript path. It exits loud when nothing is found — do not fall back to resuming the session.
27
+ `node .spec/*/.plugins/skills/distill/digest.mjs <id-or-path>`
34
28
 
35
- Read the digest yourself when small; big (>~100 KB) a subagent returns only the distillation below, so
36
- the inheritance never floods your own context. Its error lines and footer are step 3's trap material.
29
+ It searches the harness's normal local transcript stores and prints human prompts, agent text, concise tool
30
+ calls, errors, metadata, edited files, and the raw path. A miss fails loudly; never recover by waking the old
31
+ session. For output above about 100 KB, have a subagent return only the distillation below.
37
32
 
38
- ## 3 · distill — forward-looking, not narrative
33
+ ## Distill forward
39
34
 
40
- Completed work is git's job to remember; do not re-narrate it and never paste raw transcript. State in
41
- your reply, and work from, what the transcript knows that git does not:
35
+ Do not paste the transcript or retell changes git already records. Preserve what git does not:
42
36
 
43
- - **Goal & landing** — what it set out to do, and where it actually stopped (merged? proposal pending?
44
- abandoned mid-flight?).
45
- - **Decisions & why** — the direction that was settled, including options weighed and rejected.
46
- - **Traps** failures, dead ends, gotchas, and every correction the human made. These are the
47
- highest-value lines in the whole transcript.
48
- - **Unfinished / next actions** — what it would have done next.
49
- - **Pointers** — files edited, spec nodes touched, and the raw transcript path itself, so later questions
50
- drill into the source instead of inheriting everything up front.
37
+ - goal, actual stopping point, and whether anything landed;
38
+ - decisions and rejected alternatives, with reasons;
39
+ - failures, dead ends, and user corrections;
40
+ - unfinished work and next actions;
41
+ - edited files, relevant spec nodes, and the raw transcript path.
51
42
 
52
- ## 4 · salvage the desk
43
+ ## Salvage, then retire
53
44
 
54
- The SpexCode record names the worktree/branch; otherwise the digest's `cwd` may be a linked worktree
55
- (`git -C <cwd> rev-parse --git-common-dir`). Salvage inside that repo it need not be the one you sit in.
56
- Cross-check the digest's files-edited footer against that worktree: a manager-style session's edits often
57
- live OUTSIDE it (main-checkout config, other repos) — those need a by-hand look, not the recipe below.
45
+ Use the record or digest to inspect the old repository's worktree, branch, merge base, commits, dirty files,
46
+ and untracked files. Cross-check the digest's edited-file list because some work may live outside that
47
+ worktree. Distinguish an already-merged branch from a branch equal to its merge base, which never committed.
58
48
 
59
- - **Already merged** (`git merge-base --is-ancestor <branch> <trunk>`) nothing to salvage; note it and
60
- go to cleanup. A tip that EQUALS the merge-base carried no commits say "never committed", not "merged".
61
- - **Unmerged commits** carry them onto your current branch: `git cherry-pick <base>..<branch>` (keeps
62
- authorship and `Session:` trailers); fall back to applying `git diff <base> <branch>` when the history
63
- is too messy to replay.
64
- - **Uncommitted changes** in the old worktree → `git -C <wt> status --porcelain`; apply its diff to your
65
- tree and copy untracked files over. Commit the salvage in your own tree, naming the origin session in
66
- the message.
49
+ Carry unmerged commits onto the current branch with `git cherry-pick <base>..<branch>`; if replay is
50
+ unsuitable, apply the branch diff. Apply dirty changes and copy needed untracked files, then commit them
51
+ with the source session named. Keep authorship and `Session:` trailers where possible.
67
52
 
68
- ## 5 · clean up only after the salvage LANDED
53
+ Only after verifying the salvage is present (or the branch truly merged), retire a SpexCode session with
54
+ `spex session close <id>`, or remove a bare worktree and then its branch. If proof is incomplete, keep the
55
+ resources and report why.
69
56
 
70
- Cleanup discards state — verify the salvaged commits are in your tree (or the branch genuinely merged) first.
57
+ ## Rename yourself
71
58
 
72
- - SpexCode session: `spex session close <id>` retires the session and its worktree in one verb.
73
- - Bare worktree: `git worktree remove <wt>`, + `git branch -D <branch>` once confirmed carried or merged.
74
- - In doubt, keep the resources and say so a kept worktree costs disk; a wrong cleanup costs the work.
59
+ Last step: mark the inheritance on the board. If you run as a SpexCode session (inside a session
60
+ worktree), `spex session rename . "<name>"` `.` selects this worktree's own session. Name what this
61
+ session now carries the inherited goal going forward, not the old session's id and no `distill:`
62
+ prefix. Outside a SpexCode session there is nothing to rename; skip this step.
@@ -2,16 +2,18 @@
2
2
  title: skills
3
3
  status: active
4
4
  hue: 280
5
- desc: Grouping shelf for the `surface: skill` plugins — the harness skills an agent invokes on demand. A shelf, not a surface — routing stays field-driven.
5
+ desc: Grouping shelf for the `surface: skill` plugins — the harness skills an agent invokes on demand. A shelf, not a surface — routing stays field-driven per surface.
6
6
  ---
7
7
  # skills
8
8
 
9
9
  The invocable **skill** plugins live here: leaf plugins that materialize into the harness's skill dir,
10
10
  where an agent invokes them on demand, each carrying `surface: skill`. Grouping them keeps `.plugins/`
11
- legible at a glance — the skill plugins on this shelf, the command presets on `commands/`, the auxiliary
12
- system contracts on `prompts/`, with `core` a flat child beside them.
11
+ legible at a glance — the skill plugins on this shelf, the command presets on [[commands]], the auxiliary
12
+ system contracts on [[prompts]], with [[core]] a flat child beside them.
13
13
 
14
- This node is a **shelf, not a surface**: it declares no `surface` field and gathers nothing itself.
15
- Discovery is recursive and field-driven, so a resident plugs in exactly as it would at the root. A plugin
16
- that serves both surfaces shelves here by its primary (skill) identity and still gathers as a command
17
- through its field.
14
+ This node is a **shelf, not a surface** (the [[prompts]] shape): it declares no `surface` field and
15
+ gathers nothing itself. Discovery is recursive and field-driven (surface), so a resident plugs in
16
+ exactly as it would at the root. A plugin that serves BOTH surfaces [[distill]] is skill and command
17
+ shelves here by its primary (skill) identity and still gathers as a command through its field. The init
18
+ templates mirror this layout. The shelf stays pure presentation: moving a resident beneath it changes
19
+ neither that plugin's identity nor the surfaces gathered from its frontmatter.
@@ -2,17 +2,22 @@
2
2
  title: .plugins
3
3
  status: active
4
4
  hue: 110
5
- desc: The instance home — this repo's DIY dev-flow plugins live here as skill-shaped plugin nodes.
5
+ desc: The instance home — SpexCode's DIY dev-flow plugins live here as skill-shaped plugin nodes.
6
6
  ---
7
- `.plugins/` is the **instance** of the plugin system: the concrete dev-flow plugins this repo ships for
8
- working in it. Each plugin is a skill-shaped node — its folder *is* the unit (a `spec.md` plus any
9
- co-located scripts) — carrying a `surface: command|system|…` field that names where it plugs in.
10
- Discovery is recursive and field-driven, so plugins may sit under a grouping shelf: the auxiliary
11
- `surface: system` prompt contracts live under `prompts/`, the `surface: command` presets under
12
- `commands/`, the `surface: skill` plugins under `skills/`, while `core` — the contract subsystem whose
13
- children are the `surface: hook` gatessits as a flat child here.
7
+ `.plugins/` is the **instance** of the plugin system: the concrete dev-flow plugins SpexCode ships for
8
+ working in this repo. Each plugin is a skill-shaped node — its folder *is* the unit (a `spec.md` plus any
9
+ co-located scripts) — carrying a `surface: command|system|…` field that names where it plugs in, per
10
+ plugin-system's surface field-driven routing. Discovery is recursive, so a plugin may sit under a
11
+ grouping shelf: the auxiliary `surface: system` prompt contracts live under [[prompts]], the `surface:
12
+ command` presets under [[commands]], the `surface: skill` plugins under [[skills]], the `surface: review`
13
+ remark presets under review, while [[core]]the dev-flow contract subsystem whose children are the
14
+ `surface: hook` gates — sits as a flat child beside them.
14
15
 
15
- The launcher's system gather and the new-session dropdown read from here. Only **active** plugins
16
- gather: a `pending` node is declared intent, not yet an active plugin. The seed ships `core`
17
- (`surface: system` the spec-discipline contract folded into every agent) plus command presets like
18
- `tidy`; add your own by creating a sibling node with a `surface` field.
16
+ `/api/plugins` and the launcher's system gather read from here, not from plugin-system (which holds the
17
+ *spec of the plugin system* itself). Only **built/active** plugins gather a `pending` node is declared
18
+ intent, not yet an active plugin, so it renders on the board but is neither offered as a command preset nor
19
+ materialized into the agent's contract.
20
+
21
+ Which plugins `spex init` ships is the init-preset rule. `seed: false` excludes a plugin subtree; shared
22
+ plugins have one body and one helper set — there is no separately authored adopter variant. Dogfood eval
23
+ scenarios/readings remain with the implementation and git history they measure.
@@ -12,8 +12,9 @@ made it. This node is the founding spec everything else hangs from — **rewrite
12
12
  your own project**, then grow child package/feature nodes beneath it (each its own directory with a
13
13
  `spec.md`).
14
14
 
15
- `.plugins/` holds the dev-flow plugins this instance ships — skill-shaped child nodes, each tagged
16
- with a `surface` field: a `surface: system` node folds into every launched agent's system prompt as
17
- always-on contract (the seed ships `core`), and a `surface: command` node is a prompt preset the
18
- new-session dropdown composes over target nodes (the seed ships `tidy`). Add, edit, or
19
- remove plugins by editing those spec nodes.
15
+ `.plugins/` holds the dev-flow plugins this instance ships — spec-shaped child nodes whose `surface`
16
+ field says how they enter the product. The initialized set includes always-on `system` contracts,
17
+ lifecycle `hook` handlers, new-session `command` presets, and an on-demand `skill`; one plugin may serve
18
+ more than one surface. Grouping shelves carry no surface themselves, and discovery is recursive, so the
19
+ folder layout stays readable without deciding behavior. Add, edit, or remove plugins by editing those
20
+ spec nodes.
@@ -4,8 +4,10 @@
4
4
  },
5
5
  "sessions": {
6
6
  "launchers": {
7
- "claude": { "harness": "claude", "cmd": "claude --dangerously-skip-permissions" },
8
- "codex": { "harness": "codex", "cmd": "codex --yolo" }
7
+ "claude": { "harness": "claude", "cmd": "claude" },
8
+ "codex": { "harness": "codex", "cmd": "codex" },
9
+ "opencode": { "harness": "opencode", "cmd": "opencode" },
10
+ "pi": { "harness": "pi", "cmd": "pi" }
9
11
  },
10
12
  "defaultLauncher": "claude"
11
13
  }