spexcode 0.1.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/bin/spex.mjs +15 -0
- package/dashboard-dist/assets/index-B60MILFg.js +139 -0
- package/dashboard-dist/assets/index-Cq7hwngj.css +32 -0
- package/dashboard-dist/index.html +16 -0
- package/package.json +35 -0
- package/src/board.ts +119 -0
- package/src/cli.ts +487 -0
- package/src/client.ts +102 -0
- package/src/gateway.ts +241 -0
- package/src/git.ts +492 -0
- package/src/guide.ts +134 -0
- package/src/harness.ts +674 -0
- package/src/hooks.ts +41 -0
- package/src/index.ts +233 -0
- package/src/init.ts +120 -0
- package/src/layout.ts +246 -0
- package/src/lint.ts +206 -0
- package/src/login-page.ts +79 -0
- package/src/materialize.ts +85 -0
- package/src/pty-bridge.ts +235 -0
- package/src/ranker.ts +129 -0
- package/src/resilience.ts +41 -0
- package/src/search.bench.mjs +47 -0
- package/src/search.ts +24 -0
- package/src/self.ts +256 -0
- package/src/sessions.ts +1469 -0
- package/src/slash-commands.ts +242 -0
- package/src/specs.ts +331 -0
- package/src/supervise.ts +158 -0
- package/src/uploads.ts +31 -0
- package/templates/hooks/pre-commit +57 -0
- package/templates/hooks/prepare-commit-msg +14 -0
- package/templates/spec/project/.config/core/idle/idle.sh +15 -0
- package/templates/spec/project/.config/core/idle/spec.md +13 -0
- package/templates/spec/project/.config/core/mark-active/mark-active.sh +46 -0
- package/templates/spec/project/.config/core/mark-active/spec.md +16 -0
- package/templates/spec/project/.config/core/session-fail/fail.sh +12 -0
- package/templates/spec/project/.config/core/session-fail/spec.md +13 -0
- package/templates/spec/project/.config/core/spec-first/spec-first.sh +54 -0
- package/templates/spec/project/.config/core/spec-first/spec.md +15 -0
- package/templates/spec/project/.config/core/spec-of-file/spec-of-file.sh +42 -0
- package/templates/spec/project/.config/core/spec-of-file/spec.md +15 -0
- package/templates/spec/project/.config/core/spec.md +13 -0
- package/templates/spec/project/.config/core/stop-gate/spec.md +17 -0
- package/templates/spec/project/.config/core/stop-gate/stop-gate.sh +108 -0
- package/templates/spec/project/.config/extract/spec.md +60 -0
- package/templates/spec/project/.config/forge-link/spec.md +9 -0
- package/templates/spec/project/.config/memory-hygiene/spec.md +15 -0
- package/templates/spec/project/.config/regroup/spec.md +25 -0
- package/templates/spec/project/.config/scenario/spec.md +32 -0
- package/templates/spec/project/.config/spec.md +15 -0
- package/templates/spec/project/.config/supervisor/spec.md +8 -0
- package/templates/spec/project/.config/tidy/spec.md +25 -0
- package/templates/spec/project/spec.md +19 -0
- package/templates/spexcode.json +5 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# @@@ stop-gate - a blocking Stop hook with TWO jobs, each with a HARD loop-break (never blocks more than
|
|
3
|
+
# once on the same cause, never leaks a dishonest stop):
|
|
4
|
+
# (A) COMMIT GATE — a done/merge proposal (awaiting + merge|nothing) is rejected while the node branch has
|
|
5
|
+
# uncommitted work or 0 commits ahead of main; the dogfood ritual commits BEFORE proposing. Clean ->
|
|
6
|
+
# allow; dirty -> block once with the reason, escape on the continuation to `asking` (needs the human).
|
|
7
|
+
# (B) DECLARE GATE — a session may not stop in an undeclared (`active`) state:
|
|
8
|
+
# declared (awaiting/parked/error/asking) . allow (the agent reported; nothing to do)
|
|
9
|
+
# active, first stop (stop_hook_active false) .. block ONCE — instruct the agent to declare
|
|
10
|
+
# active, the continuation (stop_hook_active true) auto-declare a safe default (awaiting/nothing if
|
|
11
|
+
# committed, else `asking` — needs the human) and allow. Guaranteed to end.
|
|
12
|
+
# $SPEX is the PATH-independent CLI invocation (abs tsx + cli) injected by settingsArg, so the gate's own
|
|
13
|
+
# auto-default AND the command it shows the agent both work even when `spex` is absent from PATH.
|
|
14
|
+
# @@@ global store + governed gate - state lives in the per-session GLOBAL record session.json (keyed by the
|
|
15
|
+
# harness session_id from the payload, grouped per-project — mirrors spec-cli/src/layout.ts). The gate acts
|
|
16
|
+
# ONLY on a GOVERNED (dashboard-launched) session: a user-self-launched agent has no board to feed, so an
|
|
17
|
+
# undeclared stop is none of our business — we exit 0 SILENTLY (the bug this fixes: the declare-demand
|
|
18
|
+
# misfiring on a self-launched codex/claude). cwd = the session worktree (resolves the project key + the
|
|
19
|
+
# commit-gate's git); state writes go through `$SPEX session … --session <id>` (TS owns the JSON).
|
|
20
|
+
. "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
|
|
21
|
+
S="${SPEX:-spex}"
|
|
22
|
+
input=$(cat 2>/dev/null || true)
|
|
23
|
+
sid=$(hp_session_id "$input"); [ -n "$sid" ] || exit 0
|
|
24
|
+
sdir=$(hp_store_dir "$sid") || exit 0
|
|
25
|
+
rec="$sdir/session.json"
|
|
26
|
+
# non-governed (or no record) → silently let the stop through. THIS is the self-launch fix.
|
|
27
|
+
grep -q '"governed"[[:space:]]*:[[:space:]]*true' "$rec" 2>/dev/null || exit 0
|
|
28
|
+
|
|
29
|
+
jget() { sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$rec" 2>/dev/null | head -1; }
|
|
30
|
+
status=$(jget status)
|
|
31
|
+
proposal=$(jget proposal)
|
|
32
|
+
|
|
33
|
+
# the value of the payload's structured `stop_hook_active` field (true on the hook-forced continuation),
|
|
34
|
+
# read by field name rather than substring-sniffing the JSON blob. ([a-z]* captures true/false portably —
|
|
35
|
+
# BSD sed has no \| alternation.)
|
|
36
|
+
cont=$(printf '%s' "$input" | sed -n 's/.*"stop_hook_active"[[:space:]]*:[[:space:]]*\([a-z]*\).*/\1/p')
|
|
37
|
+
|
|
38
|
+
# @@@ yatsu advisory - a nudge (never a gate) emitted when a session stops CLEAN-DONE (committed work + a
|
|
39
|
+
# done/awaiting declaration): the agent IS yatsu's evaluator, so a yatsu gap in what it just changed is a
|
|
40
|
+
# blind spot to flag the moment work lands. SCOPED via `spex yatsu scan --changed` to the nodes THIS branch
|
|
41
|
+
# touched — so an agent is never nagged about a score that went stale in a node it never opened (the bug
|
|
42
|
+
# that made three workers ask "is this mine?"). Three gap classes it surfaces: yatsu-drift / yatsu-missing
|
|
43
|
+
# (a node with a yatsu.md whose score is stale / unmeasured) and yatsu-uncovered (a FRONTEND node with no
|
|
44
|
+
# yatsu.md — an obvious UI change carrying no loss signal). Delivered via the Stop hook's additionalContext
|
|
45
|
+
# (NEVER a block decision: a gap is a heads-up, not a wall). FIRES ONCE: the additionalContext itself forces
|
|
46
|
+
# one continuation, so the CALLER guards it on stop_hook_active — re-emitting on the forced re-stop is what
|
|
47
|
+
# looped 31 turns and tripped the Stop-hook block cap. Called only on ALLOW paths, never alongside a block.
|
|
48
|
+
yatsu_advisory() {
|
|
49
|
+
local out ids n msg esc
|
|
50
|
+
out=$($S yatsu scan --changed 2>&1)
|
|
51
|
+
n=$(printf '%s\n' "$out" | grep -cE 'yatsu-(drift|missing|uncovered):')
|
|
52
|
+
[ "${n:-0}" -gt 0 ] || return 0 # no gap in what you changed (or scan unavailable) -> nothing to nudge
|
|
53
|
+
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.)"
|
|
55
|
+
esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
56
|
+
printf '{"hookSpecificOutput":{"hookEventName":"Stop","additionalContext":"%s"}}\n' "$esc"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# @@@ commit gate - a declaration of done/merge (awaiting + proposal merge|nothing) is only honest once the
|
|
60
|
+
# node branch carries the work as COMMITS: the dogfood ritual commits spec+code BEFORE any proposal, yet a
|
|
61
|
+
# dashboard-launched agent kept proposing merge with 0 commits / a dirty tree. So before allowing such a
|
|
62
|
+
# declaration we run the deterministic check (`spex session commit-gate`, which goes through git.ts's git()
|
|
63
|
+
# so the hook's GIT_DIR/GIT_INDEX_FILE can't misdirect repo discovery). Clean -> allow. Dirty/0-ahead ->
|
|
64
|
+
# block ONCE with the specific reason + commit instructions; on the forced continuation (the agent ignored
|
|
65
|
+
# it) escape the loop by downgrading to `asking` (needs the human) with a clear note, so a FALSE "ready to
|
|
66
|
+
# merge" never stands. (A propose-close declaration is exempt — it discards the worktree, so commits are moot.)
|
|
67
|
+
if [ "${status:-active}" = awaiting ] && { [ "$proposal" = merge ] || [ "$proposal" = nothing ]; }; then
|
|
68
|
+
if gatemsg=$($S session commit-gate 2>&1); then
|
|
69
|
+
# nudge ONCE: emit on the natural stop, but STAY SILENT on the forced re-stop the additionalContext
|
|
70
|
+
# itself causes (stop_hook_active=true). Without this guard the advisory re-fired every clean-done stop
|
|
71
|
+
# and looped — the bug a prior change DESCRIBED in a comment but never actually implemented at the call.
|
|
72
|
+
[ "$cont" != true ] && yatsu_advisory
|
|
73
|
+
exit 0 # work is committed and ahead of main -> the proposal is honest, let it stop.
|
|
74
|
+
fi
|
|
75
|
+
if [ "$cont" = true ]; then
|
|
76
|
+
$S session ask --session "$sid" --note "stopped with uncommitted work — commit your spec+code on the node branch, then re-declare done" >/dev/null 2>&1 || true
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
esc=$(printf '%s' "$gatemsg" | sed 's/[\\"]/\\&/g')
|
|
80
|
+
printf '{"decision":"block","reason":"Not ready to declare done: %s. The dogfood ritual lands every change as a git commit on your node branch BEFORE you propose. Commit your spec.md + code on this node branch (spec: <id> — <reason>, with a Session: trailer), then re-run %s session done --propose %s."}\n' "$esc" "$S" "$proposal"
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# any OTHER already-declared state (parked / error / asking / awaiting+close) -> let it stop.
|
|
85
|
+
[ "${status:-active}" != "active" ] && exit 0
|
|
86
|
+
|
|
87
|
+
if [ "$cont" = true ]; then
|
|
88
|
+
# the forced continuation also stopped without declaring -> escape the loop, don't block. Keep the commit
|
|
89
|
+
# gate airtight: default to awaiting/nothing only when the branch is actually committed+ahead; otherwise an
|
|
90
|
+
# undeclared stop with uncommitted work becomes `asking` (needs the human), never a false awaiting/done.
|
|
91
|
+
if $S session commit-gate >/dev/null 2>&1; then
|
|
92
|
+
$S session state awaiting --session "$sid" --propose nothing --note "auto: stopped without declaring" >/dev/null 2>&1 || true
|
|
93
|
+
# NOTE: no yatsu nudge on the auto-declare path. It only runs on the forced continuation (cont=true),
|
|
94
|
+
# where a guarded advisory could never fire anyway, and an unguarded one was a second loop vector (a
|
|
95
|
+
# mark-active tool call could re-enter this branch). The clean-done path above is the single nudge site.
|
|
96
|
+
else
|
|
97
|
+
$S session ask --session "$sid" --note "auto: stopped without declaring and with uncommitted work — commit your spec+code on the node branch, then declare" >/dev/null 2>&1 || true
|
|
98
|
+
fi
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
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
|
|
104
|
+
# precise APPLICATION CONDITION for each — so the agent picks the TRUE one. park is policed hardest because
|
|
105
|
+
# a false park (no real background task) reads on the board as "fine, self-resuming" when the agent actually
|
|
106
|
+
# 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"
|
|
108
|
+
exit 0
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: extract
|
|
3
|
+
surface: slash
|
|
4
|
+
status: active
|
|
5
|
+
hue: 30
|
|
6
|
+
desc: Reverse-engineer a faithful spec tree out of existing code — responsibility nodes at contract altitude, intent never fabricated.
|
|
7
|
+
kind: mutating
|
|
8
|
+
---
|
|
9
|
+
Reverse-engineer a spec tree for the target source area(s) below — code that has no specs yet. Aim for a
|
|
10
|
+
tree where every governed file is claimed and `spex lint` is clean, with bodies at contract altitude and in
|
|
11
|
+
the codebase's own primary language (a predominantly-Chinese repo → Chinese specs).
|
|
12
|
+
|
|
13
|
+
{{targets}}
|
|
14
|
+
|
|
15
|
+
**Find the spine the code already declares first** — a barrel of exports, a README or design doc, the
|
|
16
|
+
dependency direction between modules — and adopt it as the top-level shape, refined where the code reveals
|
|
17
|
+
finer responsibilities. Only when the code declares no architecture is the top-level cut a judgment call
|
|
18
|
+
worth raising with the human.
|
|
19
|
+
|
|
20
|
+
Then grow nodes under that spine:
|
|
21
|
+
|
|
22
|
+
- **Decompose by responsibility, not by file.** A node is one job the code does. A fat file split across
|
|
23
|
+
several jobs becomes several nodes that each claim it; one job spanning several files becomes one node
|
|
24
|
+
claiming them all. Every governed file is claimed by at least one node; nest into subtrees where warranted.
|
|
25
|
+
- **Group wide layers; don't mirror the file tree — at every level, the root included.** One-node-per-folder
|
|
26
|
+
is a smell. If a node would have more than ~7 direct children you're under-grouping: add intermediate
|
|
27
|
+
**sub-domain** nodes that cluster siblings serving one concern (e.g. model-config + selection + auth +
|
|
28
|
+
provider-compat → a *model* domain), and recurse until every level reads as a handful of siblings, not a
|
|
29
|
+
flat wall. Fold cross-cutting substrate (design system, dialogs, i18n, utilities, platform glue) under one
|
|
30
|
+
*foundation* node. A sub-domain node claims the cluster's barrel/wiring files (so it isn't pure-prose);
|
|
31
|
+
equally, split a fat folder holding several distinct jobs. Group by responsibility, never to hit a number.
|
|
32
|
+
- **Stay at contract altitude.** State each node's intent, invariants, and outward behavior — what it
|
|
33
|
+
guarantees and why — not how the code does it.
|
|
34
|
+
- **Never fabricate intent.** Code shows *what it does*, rarely *why*. Read any README/design docs for real
|
|
35
|
+
intent; where you can still only see behavior, state the behavior and mark the intent as inferred rather
|
|
36
|
+
than inventing a rationale.
|
|
37
|
+
- **Reserve pure-prose nodes** (no `code:`) for a genuine cross-cutting contract no single file owns. Use
|
|
38
|
+
sparingly.
|
|
39
|
+
- **Mind the scope boundary.** A file that looks like a thin wrapper may be the foot of a feature defined
|
|
40
|
+
outside the target area — flag it instead of mis-homing it, and prefer extracting the whole repo so
|
|
41
|
+
cross-cutting features stay visible. If nothing reaches a file, say it's likely dead rather than
|
|
42
|
+
dignifying it with a confident spec.
|
|
43
|
+
|
|
44
|
+
**Give every frontend node a loss signal.** A node that governs UI or visual code (`.tsx`/`.jsx`/`.vue`/
|
|
45
|
+
`.svelte`/`.css`, or the dashboard) is a blind spot until it carries a `yatsu.md` — so write one as you
|
|
46
|
+
extract it: a **real user-path** scenario — a goal and the steps to reach it through the running app (never a
|
|
47
|
+
bare render-check), covering a failure/empty/edge state — with a **description** of those steps and the
|
|
48
|
+
**expected** zero-loss result. Frontend scenarios are measured by looking (YATU) — a screenshot filed with
|
|
49
|
+
`spex yatsu eval <node> --image <png> --pass`. Backend nodes don't need one yet; run `spex yatsu scan` to
|
|
50
|
+
list the frontend nodes still uncovered.
|
|
51
|
+
|
|
52
|
+
**Extract incrementally — don't plan the whole tree before writing.** For a large area (hundreds of files),
|
|
53
|
+
don't enumerate the whole partition up front or script a generator to emit it at once — that burns context
|
|
54
|
+
before a node lands and loses everything to one interruption. Fix the top-level cut and commit it, then take
|
|
55
|
+
ONE subtree at a time (write the leaf, list its files, lint, COMMIT) before the next — never holding more than
|
|
56
|
+
one subtree uncommitted, so progress survives context limits.
|
|
57
|
+
|
|
58
|
+
Confirm `spexcode.json`'s `governedRoots` points at the real source dirs first — lint reads silently empty
|
|
59
|
+
otherwise. Commit one node per commit (`spec: <id> — extract from <area>`) with a `Session:` trailer, and
|
|
60
|
+
run `spex lint` after each: it must reach 0 errors, 0 coverage warnings, 0 altitude warnings.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: forge-link
|
|
3
|
+
surface: system
|
|
4
|
+
status: active
|
|
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.
|
|
7
|
+
code:
|
|
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 board` lists them). A pull request needs no marker: opening it from your `node/<id>` branch links it for free.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: memory-hygiene
|
|
3
|
+
surface: system
|
|
4
|
+
status: active
|
|
5
|
+
hue: 200
|
|
6
|
+
desc: A config plugin — keep the project-keyed agent memory free of session- and role-specific facts, so N agents in one folder never inherit a confused identity.
|
|
7
|
+
code:
|
|
8
|
+
---
|
|
9
|
+
## Memory hygiene — keep the shared store identity-clean
|
|
10
|
+
|
|
11
|
+
SpexCode's agent memory is keyed by the **main project**, so every agent running under this project — the main checkout AND every worktree — reads the **same** memory. That makes session- and role-specific facts toxic: one agent's note silently becomes every agent's belief. So, when deciding whether to record a memory:
|
|
12
|
+
|
|
13
|
+
- **Never record session-specific content** — this task, this worktree's transient state, a one-off decision, who you're talking to right now. Memory is ONLY for durable, cross-session project/user facts.
|
|
14
|
+
- **On a non-main worktree** (you are on a `node/<id>` branch, not the main checkout): do **not** record any memory for this session at all. Its work is transient and will merge or close; a durable lesson is recorded later, from main, once it has actually landed.
|
|
15
|
+
- **Even on main, never record a transient ROLE or IDENTITY** — "I am the supervisor", "I'm the coordinator", "I'm the agent doing X". These are per-launch facts, not durable ones. Recording one makes the next launched agent read *itself* as that role, and several agents in one folder dissolve into mutual-supervision confusion (everyone thinks they're the supervisor, everyone watches everyone).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: regroup
|
|
3
|
+
surface: slash
|
|
4
|
+
status: active
|
|
5
|
+
hue: 130
|
|
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.
|
|
7
|
+
kind: mutating
|
|
8
|
+
---
|
|
9
|
+
A node flagged for **breadth** has too many direct children: a flat fan-out the eye can't hold. Breadth is altitude's structural twin — the same comprehensibility limit, rotated from a body's depth onto the tree's width. Find the **natural seams** in the fan-out and lift the children onto them, *only where a real group exists*. Getting under the child budget is the floor, not the goal; never manufacture structure to hit a number.
|
|
10
|
+
|
|
11
|
+
{{targets}}
|
|
12
|
+
|
|
13
|
+
Read before you move: the over-broad node's spec, every child's spec, and the `[[links]]`/`related:` between them. A flat fan-out is rarely one undifferentiated list — some children are **misfiled** under the wrong parent, some **cohere into a surface no node owns yet**, and some are **genuine independent peers**. Sort each child into exactly one disposition:
|
|
14
|
+
|
|
15
|
+
- **Reparent under an existing sibling.** When a child's own spec says it is *part of* another child — its tab, its row, its input, a sub-surface of it — that sibling is its true owner. `git mv` it under that node. No new parent: the breadth was the symptom of a misparented child, and putting it under its real owner fixes the miscategorization for free. **Try this first** — it is the cheapest, most honest move and adds nothing to the tree. (Second-order case: if a reparent pushes the new host over budget, that host now needs its own seam.)
|
|
16
|
+
- **Group under a new intermediate parent.** When several children genuinely cohere — one surface, one concept, read together to understand one thing — but no existing node owns them. Create one parent along that seam. It must **earn its existence**: a body stating what the group *is* and why these children belong, a contract at altitude (it is a real node and must pass altitude lint itself) — never a hollow container echoing a table of contents. The two-for-one test: a true seam also makes the siblings around it read more clearly.
|
|
17
|
+
- **Leave it flat.** When a child shares no boundary with the rest. A flat list of genuine peers is sometimes right; refusing to force a "misc"/"everything-else" bucket is the correct move, not a failure. A couple of real groups plus a handful of still-flat peers is a good outcome.
|
|
18
|
+
|
|
19
|
+
Honor these:
|
|
20
|
+
|
|
21
|
+
- **Reparent, never rewrite.** Move a node by `git mv`-ing its folder; its id (= folder basename), its `[[links]]`, its `code:` governance, and its `yatsu.md` all ride along untouched. If a child needs its body edited to belong in a group, it doesn't belong.
|
|
22
|
+
- **Fewest, deepest-justified parents.** Don't trade one flat layer for six two-child wrappers — that relocates the sprawl instead of resolving it. Between two passing groupings, take the one that adds fewer nodes.
|
|
23
|
+
- Parent ids name the concept (kebab-case); give the parent a `hue` near its children's family.
|
|
24
|
+
|
|
25
|
+
Work the order: (1) read everything; (2) write each candidate seam with the one-sentence intent that justifies it — kill the unjustifiable, and mark each survivor as reparent-under-existing or new-parent; (3) make the moves, one reviewable commit per group (`spec: <parent> — regroup <children>`, with a `Session:` trailer); (4) run `spex lint` and confirm breadth is resolved with no new errors. Uncommitted `git mv`s churn the drift count transiently — committing settles it; don't chase it.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: scenario
|
|
3
|
+
surface: slash
|
|
4
|
+
status: active
|
|
5
|
+
hue: 160
|
|
6
|
+
kind: mutating
|
|
7
|
+
desc: Enter Scenario mode — author, refine, and re-measure a node's yatsu.md loss scenarios as first-class, file-scoped units.
|
|
8
|
+
---
|
|
9
|
+
You are in **Scenario mode** — a focused flow for managing a node's yatsu.md **scenarios** as first-class
|
|
10
|
+
units of loss. A scenario is one declared way to measure a node's loss: a `name`, a `description` of what to
|
|
11
|
+
check, the `expected` result that is zero loss, and optionally a `test` (a co-located runnable file) and
|
|
12
|
+
`code` (the concrete repo files THIS scenario depends on — its own freshness axis). Your job is to **edit,
|
|
13
|
+
create, and manage** these scenarios for the target node(s) — nothing else; do not write feature code.
|
|
14
|
+
|
|
15
|
+
Work through the real `spex` surface, never by reverse-engineering files:
|
|
16
|
+
|
|
17
|
+
1. **Read the schema and the node.** Run `spex guide yatsu` for the exact yatsu.md format. Read the target
|
|
18
|
+
node's `spec.md` (its present intent) and its governed `code:` files, plus its existing `yatsu.md` if any.
|
|
19
|
+
A scenario must measure what the SPEC promises, through the real product surface (YATU — You As The User),
|
|
20
|
+
not an internal helper chosen to make the proof easy.
|
|
21
|
+
2. **Author or refine scenarios.** For a node with no yatsu.md, create one with at least one scenario. For an
|
|
22
|
+
existing one, sharpen weak scenarios and add missing coverage. Give a scenario its own `code:` list when it
|
|
23
|
+
depends on a specific subset of the node's files, so it goes stale independently of its siblings. Keep
|
|
24
|
+
names unique and the schema valid — `spex yatsu scan <node>` reports violations (`yatsu-schema`), and the
|
|
25
|
+
pre-commit gate rejects a malformed file, so fix every finding before committing.
|
|
26
|
+
3. **Re-measure when the change warrants it.** If you changed a scenario or the code it measures, file a fresh
|
|
27
|
+
reading: measure through the product, then `spex yatsu eval <node> --scenario <name> (--pass|--fail|--note)
|
|
28
|
+
[--image <png>|--result <txt>]`. Use `spex yatsu show <node>` to read the timeline.
|
|
29
|
+
4. **Commit spec + yatsu.md together** on the node's branch, then report what scenarios now exist and their
|
|
30
|
+
satisfaction status. Do not bundle unrelated edits.
|
|
31
|
+
|
|
32
|
+
The node(s) to manage scenarios for: {{targets}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: .config
|
|
3
|
+
status: active
|
|
4
|
+
hue: 110
|
|
5
|
+
desc: The instance home — this repo's DIY dev-flow plugins live here as skill-shaped config nodes.
|
|
6
|
+
---
|
|
7
|
+
`.config/` is the **instance** of the config 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) — living as a flat child of `.config/` and carrying a `surface: slash|system` field
|
|
10
|
+
that names where it plugs in.
|
|
11
|
+
|
|
12
|
+
The launcher's system gather and the new-session dropdown read from here. Only **active** plugins
|
|
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.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: supervisor
|
|
3
|
+
surface: slash
|
|
4
|
+
status: active
|
|
5
|
+
hue: 280
|
|
6
|
+
desc: Launch a supervisor agent that manages other agents from the main checkout to drive a goal to completion.
|
|
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:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: tidy
|
|
3
|
+
surface: slash
|
|
4
|
+
status: active
|
|
5
|
+
hue: 140
|
|
6
|
+
desc: Rewrite a node's body to contract altitude — push mechanics into code comments, keep the contract, without going vague.
|
|
7
|
+
kind: mutating
|
|
8
|
+
---
|
|
9
|
+
Tidy each target spec node so its body reads as **contract**, not implementation — and not vague hand-waving either.
|
|
10
|
+
|
|
11
|
+
{{targets}}
|
|
12
|
+
|
|
13
|
+
The objective rule for every sentence is the contract-surface test: *"could a behavior-preserving refactor delete or change this?"*
|
|
14
|
+
|
|
15
|
+
- **Yes ⇒ it is implementation.** It leaves the body and becomes an `@@@title - explanation` comment at the code that owns it (operators, call names, data structures, "added a parameter", step-by-step how-to).
|
|
16
|
+
- **No ⇒ it is contract surface.** Keep it — public names, signatures, return types, invariants, edges/errors, the WHEN → outcome a caller observes.
|
|
17
|
+
|
|
18
|
+
For each target, in its own commit:
|
|
19
|
+
|
|
20
|
+
- **Preserve the contract** — never drop a requirement; tidying is rephrasing at the right altitude, not deletion of meaning.
|
|
21
|
+
- **Raise, don't hollow out** — the trap while shortening is going *too thin*: a body so vague a refactor couldn't violate it ("validates input appropriately") is as broken as a mechanics dump. Keep every testable specific; cut only the how.
|
|
22
|
+
- **Cut redundancy and narration** — say each thing once, at the altitude a maintainer needs.
|
|
23
|
+
- **Stay a living document** — rewrite in place; never add `## vN` history (git carries versions).
|
|
24
|
+
|
|
25
|
+
Commit per node (`spec: <id> — tidy to contract altitude`) with a `Session:` trailer. Run `spex lint` after each — it must stay at 0 errors.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: project
|
|
3
|
+
status: active
|
|
4
|
+
hue: 45
|
|
5
|
+
desc: The root spec node — the founding intent this repo's spec tree hangs from. Rewrite it to your own.
|
|
6
|
+
---
|
|
7
|
+
# project
|
|
8
|
+
|
|
9
|
+
The root of your spec tree. In SpexCode the spec tree IS ground truth and git is its database: every
|
|
10
|
+
node is a `spec.md` stating present intent, and each version change is attributed to the session that
|
|
11
|
+
made it. This node is the founding spec everything else hangs from — **rewrite this body to describe
|
|
12
|
+
your own project**, then grow child package/feature nodes beneath it (each its own directory with a
|
|
13
|
+
`spec.md`).
|
|
14
|
+
|
|
15
|
+
`.config/` holds the dev-flow plugins this instance ships — skill-shaped flat 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: slash` node is a prompt preset the
|
|
18
|
+
new-session dropdown composes over target nodes (the seed ships `tidy` and `health`). Add, edit, or
|
|
19
|
+
remove plugins by editing those spec nodes.
|