spexcode 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +86 -25
  2. package/package.json +5 -6
  3. package/spec-cli/README.md +86 -0
  4. package/spec-cli/bin/spex.mjs +15 -3
  5. package/spec-cli/hooks/dispatch.sh +20 -8
  6. package/spec-cli/hooks/harness.sh +18 -11
  7. package/spec-cli/src/board.ts +47 -18
  8. package/spec-cli/src/boardCache.ts +70 -0
  9. package/spec-cli/src/boardDelta.ts +90 -0
  10. package/spec-cli/src/boardStream.ts +178 -0
  11. package/spec-cli/src/cli.ts +172 -119
  12. package/spec-cli/src/client.ts +6 -4
  13. package/spec-cli/src/gateway.ts +49 -13
  14. package/spec-cli/src/git.ts +105 -92
  15. package/spec-cli/src/guide.ts +175 -12
  16. package/spec-cli/src/harness-select.ts +63 -0
  17. package/spec-cli/src/harness.ts +506 -100
  18. package/spec-cli/src/help.ts +360 -0
  19. package/spec-cli/src/hooks.ts +0 -14
  20. package/spec-cli/src/index.ts +272 -32
  21. package/spec-cli/src/init.ts +41 -1
  22. package/spec-cli/src/issues.ts +250 -0
  23. package/spec-cli/src/layout.ts +70 -28
  24. package/spec-cli/src/lint.ts +12 -10
  25. package/spec-cli/src/listen.ts +28 -0
  26. package/spec-cli/src/localIssues.ts +683 -0
  27. package/spec-cli/src/materialize.ts +182 -27
  28. package/spec-cli/src/mentions.ts +192 -0
  29. package/spec-cli/src/plugin-harness.ts +145 -0
  30. package/spec-cli/src/pty-bridge.ts +378 -81
  31. package/spec-cli/src/self.ts +123 -20
  32. package/spec-cli/src/sessions.ts +461 -298
  33. package/spec-cli/src/specs.ts +55 -14
  34. package/spec-cli/src/supervise.ts +23 -3
  35. package/spec-cli/src/tsx-bin.ts +14 -5
  36. package/spec-cli/src/uninstall.ts +146 -0
  37. package/spec-cli/templates/hooks/post-merge +27 -0
  38. package/spec-cli/templates/hooks/pre-commit +51 -31
  39. package/spec-cli/templates/hooks/prepare-commit-msg +31 -8
  40. package/spec-cli/templates/presets/careful/.config/clarify-before-code/spec.md +11 -0
  41. package/spec-cli/templates/spec/project/.config/core/spec-of-file/spec-of-file.sh +26 -3
  42. package/spec-cli/templates/spec/project/.config/core/spec.md +4 -0
  43. package/spec-cli/templates/spec/project/.config/core/stop-gate/stop-gate.sh +16 -4
  44. package/spec-cli/templates/spec/project/.config/extract/spec.md +1 -1
  45. package/spec-cli/templates/spec/project/.config/regroup/spec.md +1 -1
  46. package/spec-cli/templates/spec/project/.config/reproduce-before-fix/spec.md +18 -0
  47. package/spec-cli/templates/spec/project/.config/spec.md +3 -3
  48. package/spec-cli/templates/spec/project/.config/supervisor/spec.md +2 -2
  49. package/spec-cli/templates/spec/project/.config/tidy/spec.md +1 -1
  50. package/spec-cli/templates/spec/project/spec.md +2 -2
  51. package/spec-dashboard/dist/assets/index-B0tgHeEQ.js +145 -0
  52. package/spec-dashboard/dist/assets/index-BTU-44Os.css +32 -0
  53. package/spec-dashboard/dist/index.html +17 -5
  54. package/spec-forge/src/cache.ts +16 -0
  55. package/spec-forge/src/drivers/github.ts +74 -4
  56. package/spec-forge/src/port.ts +25 -0
  57. package/spec-forge/src/resident.ts +40 -6
  58. package/spec-yatsu/src/cli.ts +227 -38
  59. package/spec-yatsu/src/evaltab.ts +169 -19
  60. package/spec-yatsu/src/filing.ts +48 -0
  61. package/spec-yatsu/src/freshness.ts +55 -20
  62. package/spec-yatsu/src/proof.ts +89 -3
  63. package/spec-yatsu/src/scenariofresh.ts +92 -0
  64. package/spec-yatsu/src/sidecar.ts +75 -11
  65. package/spec-yatsu/src/timeline.ts +47 -0
  66. package/spec-yatsu/src/yatsu.ts +47 -3
  67. package/spec-cli/src/relay.ts +0 -28
  68. package/spec-cli/templates/spec/project/.config/scenario/spec.md +0 -32
  69. package/spec-dashboard/dist/assets/index-Bk4E1EQy.js +0 -139
  70. package/spec-dashboard/dist/assets/index-Cq7hwngj.css +0 -32
@@ -45,13 +45,23 @@ cont=$(printf '%s' "$input" | sed -n 's/.*"stop_hook_active"[[:space:]]*:[[:spac
45
45
  # (NEVER a block decision: a gap is a heads-up, not a wall). FIRES ONCE: the additionalContext itself forces
46
46
  # one continuation, so the CALLER guards it on stop_hook_active — re-emitting on the forced re-stop is what
47
47
  # looped 31 turns and tripped the Stop-hook block cap. Called only on ALLOW paths, never alongside a block.
48
+ #
49
+ # SURFACE-NEUTRAL: a stale/unmeasured score is refreshed only by PRODUCING the measurement on the scenario's
50
+ # OWN surface — a real run, never a desk check and never deferring to review a recording after the fact. The
51
+ # nudge privileges NO surface: `scan --changed` carries each drift/missing scenario's tag on its finding line
52
+ # ([[yatsu-core]]'s lint.scenarioTags — frontend-e2e / backend-api / cli / desktop / mobile), so the agent
53
+ # reads there WHICH surface to run. One line covers all five surfaces; there is no per-surface branch.
48
54
  yatsu_advisory() {
49
55
  local out ids n msg esc
56
+ # Codex Stop hooks reject the Claude-family `hookSpecificOutput.additionalContext` shape on allow paths.
57
+ # Keep Codex Stop stdout empty unless it is a real block decision; the dispatcher still bridges block
58
+ # reasons to Codex stderr.
59
+ [ "${SPEXCODE_HARNESS:-claude}" = codex ] && return 0
50
60
  out=$($S yatsu scan --changed 2>&1)
51
61
  n=$(printf '%s\n' "$out" | grep -cE 'yatsu-(drift|missing|uncovered):')
52
62
  [ "${n:-0}" -gt 0 ] || return 0 # no gap in what you changed (or scan unavailable) -> nothing to nudge
53
63
  ids=$(printf '%s\n' "$out" | sed -n "s/.*yatsu-[a-z]*: '\([^']*\)'.*/\1/p" | awk '!seen[$0]++' | head -6 | paste -sd' ' -)
54
- msg="yatsu — the loss signal the optimizer reads — flags ${n} gap(s) in nodes you changed: ${ids}. A node whose score went stale/unmeasured: re-measure it (run the scenario, compare to expected, \`spex yatsu eval <node>\`). A FRONTEND node with no yatsu.md: give it one (a scenario — description + expected), since an obvious UI change should carry a loss signal. \`spex yatsu scan --changed\` lists them. (Advisory — fires once, not a gate.)"
64
+ msg="yatsu — the loss signal the optimizer reads — flags ${n} gap(s) in nodes you changed: ${ids}. A node whose score went stale/unmeasured: re-measure it — PRODUCE the measurement YOURSELF with a real run of the scenario's actual surface (its tag on the \`spex yatsu scan --changed\` line tells you WHICH surface to run), compare to expected, and file it with \`spex yatsu eval <node>\`; don't desk-check it, and don't defer to reviewing a recording after the fact. A FRONTEND node with no yatsu.md: give it one (a scenario — description + expected), since an obvious UI change should carry a loss signal. \`spex yatsu scan --changed\` lists them. (Advisory — fires once, not a gate.)"
55
65
  esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
56
66
  printf '{"hookSpecificOutput":{"hookEventName":"Stop","additionalContext":"%s"}}\n' "$esc"
57
67
  }
@@ -99,10 +109,12 @@ if [ "$cont" = true ]; then
99
109
  exit 0
100
110
  fi
101
111
 
102
- # first stop in an undeclared state -> nudge exactly once with PATH-independent commands. The reason
103
- # EMPHASIZES that each state is a CLAIM others act on (not a box to tick to end the turn) and gives the
112
+ # first stop in an undeclared state -> nudge exactly once. The reason names the PATH-independent CLI ($S)
113
+ # ONCE as a shared `<CLI> session <choice>` prefix, then lists the five choices as a compact newline menu of
114
+ # bare subcommands — so the terminal output stays legible instead of repeating the long abs path per option.
115
+ # It EMPHASIZES that each state is a CLAIM others act on (not a box to tick to end the turn) and gives the
104
116
  # precise APPLICATION CONDITION for each — so the agent picks the TRUE one. park is policed hardest because
105
117
  # a false park (no real background task) reads on the board as "fine, self-resuming" when the agent actually
106
118
  # needs the human, which is the most damaging mislabel.
107
- printf '{"decision":"block","reason":"Your session state is a CLAIM the board, your supervisor, and other agents act on — not a box to tick to end the turn. Stopping undeclared makes your outcome a guess. Pick the ONE that is TRUE right now, by its condition, then stop. %s session done --propose merge = your spec+code are COMMITTED on the branch and genuinely ready for a human to review/merge (not just probably-done). %s session done --propose nothing = committed, but you are NOT proposing a merge; paused for the human to look. %s session park --note <what-you-await> = use ONLY when a real BACKGROUND TASK will wake you (a spex wait you backgrounded, a running build/job). If nothing is actually running to resume you, you are NOT parked — you are waiting on the human, so use ask. park claims leave-me-alone-I-self-resume; do not use it as a default to clear this gate. %s session done --propose close = you propose discarding this worktree. %s session ask --note <your-question> = you need the human: a real question, OR you are simply stopped awaiting their direction; you resume only when they reply. Choose by what is true."}\n' "$S" "$S" "$S" "$S" "$S"
119
+ printf '{"decision":"block","reason":"Your session state is a CLAIM the board, your supervisor, and other agents act on — not a box to tick to end the turn. Stopping undeclared makes your outcome a guess. Pick the ONE that is TRUE right now and run `%s session <choice>`, choosing the <choice> whose condition holds:\\n • done --propose merge spec+code COMMITTED on the branch and genuinely ready for a human to review/merge (not just probably-done).\\n • done --propose nothing committed, but you are NOT proposing a merge; paused for the human to look.\\n • park --note <what-you-await> ONLY when a real BACKGROUND TASK will wake you (a spex wait you backgrounded, a running build/job). If nothing is actually running to resume you, you are NOT parked — you are waiting on the human, so use ask; never use park as a default to clear this gate.\\n • done --propose close you propose discarding this worktree.\\n • ask --note <your-question> you need the human: a real question, or you are simply stopped awaiting direction; you resume only when they reply."}\n' "$S"
108
120
  exit 0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: extract
3
- surface: slash
3
+ surface: command
4
4
  status: active
5
5
  hue: 30
6
6
  desc: Reverse-engineer a faithful spec tree out of existing code — responsibility nodes at contract altitude, intent never fabricated.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: regroup
3
- surface: slash
3
+ surface: command
4
4
  status: active
5
5
  hue: 130
6
6
  desc: Resolve a node's breadth — a flat fan-out of too many children — by lifting children onto their true owner or a new grouping layer, only along real seams.
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: reproduce-before-fix
3
+ surface: system
4
+ status: active
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).
7
+ code:
8
+ ---
9
+ ## Reproduce before you fix — the fix's proof is a fail→pass pair
10
+
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
+
13
+ - **A — reproduce (fail).** Find the yatsu scenario whose expected the bug violates (if none fits, ADD one to the node's `yatsu.md` — a description + the expected correct behaviour), run it, and file the failing reading with evidence that SHOWS the bug: `spex yatsu eval <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 yatsu eval <node> --scenario <s> --pass`.
15
+
16
+ The two readings on the same scenario are the **A/B** — the error→correct transition, the fix's proof-of-work. yatsu keeps per-scenario reading history, so the pair is durable and navigable end to end.
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.
@@ -6,10 +6,10 @@ desc: The instance home — this repo's DIY dev-flow plugins live here as skill-
6
6
  ---
7
7
  `.config/` is the **instance** of the config system: the concrete dev-flow plugins this repo ships for
8
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) — living as a flat child of `.config/` and carrying a `surface: slash|system` field
9
+ co-located scripts) — living as a flat child of `.config/` and carrying a `surface: command|system` field
10
10
  that names where it plugs in.
11
11
 
12
12
  The launcher's system gather and the new-session dropdown read from here. Only **active** plugins
13
13
  gather: a `pending` node is declared intent, not yet an active plugin. The seed ships `core`
14
- (`surface: system` — the spec-discipline contract folded into every agent) plus `tidy` and `health`
15
- (`surface: slash` — prompt presets); add your own by creating a sibling node with a `surface` field.
14
+ (`surface: system` — the spec-discipline contract folded into every agent) plus command presets like
15
+ `tidy`; add your own by creating a sibling node with a `surface` field.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  title: supervisor
3
- surface: slash
3
+ surface: command
4
4
  status: active
5
5
  hue: 280
6
6
  desc: Launch a supervisor agent that manages other agents from the main checkout to drive a goal to completion.
7
7
  ---
8
- You are a SpexCode supervisor — a **manager**, not a feature worker. Your work base is the main checkout (the repository root), NOT your own worktree: do all git via `git -C <root>`, everything else via the `spex` CLI, and never write feature code. **FIRST, read `<root>/CLAUDE.md` — specifically its "Supervising — the manager loop" section** — that is your complete playbook (dispatch → monitor → review → merge → close, and how to parallelize). Then drive the goal: decompose it into spec-node tasks and dispatch one worker per independent task (`spex new "<task>" --node <id>` — give each ONLY its task), monitor with `spex watch`, review proposals with `spex review <id>`, merge good ones with `git -C <root> merge --no-ff <branch>`, then close. Never let a worker self-merge; keep `spex lint` at 0 errors. To WAIT on a worker, POLL one-shot (`spex review <id>` or `spex ls` — both return immediately); never block on `spex watch`, which STREAMS forever and will freeze your turn. Two footguns that bite a fresh supervisor: (1) `spex session new --help` is NOT help — it CREATES a stray session; always dispatch with `spex new`. (2) Before `spex session close <id>`, confirm the merge landed (`git -C <root> log -1` shows HEAD at the new merge commit) — closing an unmerged branch discards the work. Report progress as you go and when the goal is complete. Your goal follows:
8
+ You are a SpexCode supervisor — a **manager**, not a feature worker. Your work base is the main checkout (the repository root), NOT your own worktree: do all git via `git -C <root>`, everything else via the `spex` CLI, and never write feature code. **FIRST, read `<root>/CLAUDE.md` — specifically its "Supervising — the manager loop" section** — that is your complete playbook (dispatch → monitor → review → merge → close, and how to parallelize). Then drive the goal: decompose it into spec-node tasks and dispatch one worker per independent task (`spex new "<task>" --node <id>` — give each ONLY its task), monitor with `spex watch`, review proposals with `spex review <id>`, merge good ones with `git -C <root> merge --no-ff <branch>`, then close. Never let a worker self-merge; keep `spex lint` at 0 errors. To WAIT on a worker, POLL one-shot (`spex review <id>` or `spex ls` — both return immediately); never block on `spex watch`, which STREAMS forever and will freeze your turn. One footgun that bites a fresh supervisor: before `spex session close <id>`, confirm the merge landed (`git -C <root> log -1` shows HEAD at the new merge commit) — closing an unmerged branch discards the work. Report progress as you go and when the goal is complete. Your goal follows:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: tidy
3
- surface: slash
3
+ surface: command
4
4
  status: active
5
5
  hue: 140
6
6
  desc: Rewrite a node's body to contract altitude — push mechanics into code comments, keep the contract, without going vague.
@@ -14,6 +14,6 @@ your own project**, then grow child package/feature nodes beneath it (each its o
14
14
 
15
15
  `.config/` holds the dev-flow plugins this instance ships — skill-shaped flat child nodes, each tagged
16
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: slash` node is a prompt preset the
18
- new-session dropdown composes over target nodes (the seed ships `tidy` and `health`). Add, edit, or
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
19
  remove plugins by editing those spec nodes.