spexcode 0.4.0 → 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.
- package/README.md +37 -17
- package/package.json +9 -5
- package/spec-cli/README.md +23 -14
- package/spec-cli/hooks/dispatch.sh +7 -5
- package/spec-cli/hooks/harness.sh +19 -8
- package/spec-cli/src/anchors.ts +163 -30
- package/spec-cli/src/cli.ts +97 -42
- package/spec-cli/src/client.ts +34 -11
- package/spec-cli/src/doctor.ts +174 -36
- package/spec-cli/src/gateway-auth.ts +194 -0
- package/spec-cli/src/gateway-hub.ts +360 -0
- package/spec-cli/src/gateway.ts +31 -16
- package/spec-cli/src/git.ts +12 -5
- package/spec-cli/src/graph.ts +53 -36
- package/spec-cli/src/graphStream.ts +265 -34
- package/spec-cli/src/guide.ts +157 -76
- package/spec-cli/src/harness-select.ts +20 -6
- package/spec-cli/src/harness.ts +266 -67
- package/spec-cli/src/help.ts +84 -41
- package/spec-cli/src/host.ts +610 -0
- package/spec-cli/src/identity-presets.d.ts +13 -0
- package/spec-cli/src/identity-presets.js +128 -0
- package/spec-cli/src/index.ts +163 -86
- package/spec-cli/src/init.ts +53 -25
- package/spec-cli/src/layout.ts +5 -5
- package/spec-cli/src/lint.ts +115 -169
- package/spec-cli/src/login-page.ts +12 -5
- package/spec-cli/src/materialize.ts +39 -13
- package/spec-cli/src/mentions.ts +16 -11
- package/spec-cli/src/opencode.ts +145 -0
- package/spec-cli/src/pi-harness.ts +124 -0
- package/spec-cli/src/project-identity.ts +73 -0
- package/spec-cli/src/pty-bridge.ts +321 -521
- package/spec-cli/src/pty-helper.mjs +86 -0
- package/spec-cli/src/reaper.ts +16 -8
- package/spec-cli/src/reviewSnapshot.ts +23 -0
- package/spec-cli/src/reviews.ts +347 -0
- package/spec-cli/src/session-timeline.ts +162 -0
- package/spec-cli/src/sessions.ts +210 -63
- package/spec-cli/src/shim-runtime.ts +169 -0
- package/spec-cli/src/slash-commands.ts +76 -0
- package/spec-cli/src/source-files.ts +79 -0
- package/spec-cli/src/specs.ts +47 -24
- package/spec-cli/src/supervise.ts +37 -15
- package/spec-cli/src/tree.ts +8 -8
- package/spec-cli/src/uninstall.ts +56 -23
- package/spec-cli/templates/hooks/prepare-commit-msg +32 -13
- package/spec-cli/templates/spec/project/.plugins/{extract → commands/extract}/spec.md +24 -18
- package/spec-cli/templates/spec/project/.plugins/{regroup → commands/regroup}/spec.md +3 -3
- package/spec-cli/templates/spec/project/.plugins/commands/rename/spec.md +11 -0
- package/spec-cli/templates/spec/project/.plugins/commands/spec.md +30 -0
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
- package/spec-cli/templates/spec/project/.plugins/commands/tidy/spec.md +43 -0
- package/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +2 -0
- package/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec-first.sh +28 -39
- package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec.md +5 -3
- package/spec-cli/templates/spec/project/.plugins/core/spec-of-file/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -2
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +7 -1
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +2 -2
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +5 -5
- package/spec-cli/templates/spec/project/.plugins/prompts/spec.md +10 -6
- package/spec-cli/templates/spec/project/.plugins/skills/distill/spec.md +62 -0
- package/spec-cli/templates/spec/project/.plugins/skills/spec.md +19 -0
- package/spec-cli/templates/spec/project/.plugins/spec.md +17 -12
- package/spec-cli/templates/spec/project/spec.md +6 -5
- package/spec-cli/templates/spexcode.json +4 -2
- package/spec-dashboard/dist/assets/Dashboard-CiHh-gLD.js +27 -0
- package/spec-dashboard/dist/assets/EvalsPage-DmiX3rdU.js +2 -0
- package/spec-dashboard/dist/assets/IssuesPage-CIbVGRUJ.js +1 -0
- package/spec-dashboard/dist/assets/MobileApp-D-N9_eh0.js +2 -0
- package/spec-dashboard/dist/assets/Modal-DHMzSFJ4.js +1 -0
- package/spec-dashboard/dist/assets/PageScroll-C15adEYI.js +1 -0
- package/spec-dashboard/dist/assets/ProjectsPage-sQpzglp5.js +1 -0
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +32 -0
- package/spec-dashboard/dist/assets/SessionInterface-B8pGU7Rg.js +39 -0
- package/spec-dashboard/dist/assets/SessionWindow-CuDO_67z.js +13 -0
- package/spec-dashboard/dist/assets/Settings-C_N1wX1f.js +1 -0
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +1 -0
- package/spec-dashboard/dist/assets/index-DmWbmvCq.js +41 -0
- package/spec-dashboard/dist/assets/index-GGIVdKwH.css +1 -0
- package/spec-dashboard/dist/assets/terminalFont-DmTPNdbU.js +1 -0
- package/spec-dashboard/dist/index.html +10 -10
- package/spec-dashboard/src/reviewFilters.js +279 -0
- package/spec-dashboard/src/reviewQuery.js +174 -0
- package/spec-dashboard/src/session.js +167 -0
- package/spec-eval/src/cli.ts +80 -45
- package/spec-eval/src/evaltab.ts +4 -3
- package/spec-eval/src/humanok.ts +1 -1
- package/spec-eval/src/matrix.ts +693 -0
- package/spec-eval/src/scenarios.ts +116 -4
- package/spec-eval/src/sessioneval.ts +726 -66
- package/spec-cli/src/migrate-table.ts +0 -398
- package/spec-cli/src/migrate.ts +0 -386
- package/spec-cli/templates/spec/project/.plugins/distill/spec.md +0 -74
- package/spec-cli/templates/spec/project/.plugins/supervisor/spec.md +0 -8
- package/spec-cli/templates/spec/project/.plugins/tidy/spec.md +0 -25
- package/spec-dashboard/dist/assets/Dashboard-CTcH2eW9.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-CJNKwHLN.js +0 -2
- package/spec-dashboard/dist/assets/FoldToggle-CVFbBpyW.js +0 -1
- package/spec-dashboard/dist/assets/IssuesPage-kULjonqj.js +0 -1
- package/spec-dashboard/dist/assets/MobileApp-B0ZJju8K.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-BRKJqU2U.js +0 -71
- package/spec-dashboard/dist/assets/SessionInterface-DYP7pi_n.css +0 -32
- package/spec-dashboard/dist/assets/SessionWindow-CDhEL7wO.js +0 -9
- package/spec-dashboard/dist/assets/Settings-BL6FV_8S.js +0 -1
- package/spec-dashboard/dist/assets/index-DmQsNYKK.css +0 -1
- package/spec-dashboard/dist/assets/index-DulGPk6A.js +0 -41
- /package/spec-cli/templates/spec/project/.plugins/{distill → skills/distill}/digest.mjs +0 -0
package/spec-cli/src/migrate.ts
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
// @@@ spex doctor --migrate - the ONE-SHOT 0.2.x → 0.3.0 migrator for an ADOPTER repo ([[migrate]]).
|
|
2
|
-
// Ships with 0.3.0, deleted in 0.4.0 (a released, named, term-limited migrator — not a runtime residue).
|
|
3
|
-
// It performs the v0.3.0 vocabulary migration in one run: tree renames (.config → .plugins, config →
|
|
4
|
-
// plugin-system, yatsu.md → eval.md, yatsu.evals.ndjson → evals.ndjson), template hook-asset upgrades,
|
|
5
|
-
// body vocabulary rewrites (only what is EXECUTED or PARSED — command spellings, lint labels, mentions,
|
|
6
|
-
// routes, .spec paths, issue-frontmatter bindings — never conceptual prose), and the legacy settings key.
|
|
7
|
-
//
|
|
8
|
-
// Iron rules, enforced structurally:
|
|
9
|
-
// • NO GUESSING: a hook asset is replaced only when its git blob sha matches a KNOWN historical stock
|
|
10
|
-
// template version (migrate-table.ts, generated from this repo's own git history). Anything else is
|
|
11
|
-
// flagged for human review and left byte-identical.
|
|
12
|
-
// • FAIL LOUD, NEVER HALF-MIGRATE: every precondition (clean tree, drained sessions, tracked .spec,
|
|
13
|
-
// main checkout, no rename collisions) is checked BEFORE the first write; any failure prints ALL
|
|
14
|
-
// refusals and exits 2 with the tree untouched.
|
|
15
|
-
// • EVERYTHING STAGED, NOTHING COMMITTED: the operator reviews the staged diff + flagged items and
|
|
16
|
-
// commits through the ritual; `git reset --hard` undoes the whole run.
|
|
17
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, renameSync } from 'node:fs'
|
|
18
|
-
import { join, relative, dirname, basename } from 'node:path'
|
|
19
|
-
import { fileURLToPath } from 'node:url'
|
|
20
|
-
import { execFileSync } from 'node:child_process'
|
|
21
|
-
import { TEMPLATE_ASSETS, RETIRED_ASSETS, CONFIG_NODE_SHAS } from './migrate-table.js'
|
|
22
|
-
|
|
23
|
-
const PKG_ROOT = fileURLToPath(new URL('..', import.meta.url))
|
|
24
|
-
|
|
25
|
-
// loud git: any failure throws with git's own stderr. GIT_DIR/GIT_INDEX_FILE are stripped (same reason as
|
|
26
|
-
// git.ts's git(): an inherited hook env would silently point repo discovery at the wrong place).
|
|
27
|
-
function git(cwd: string, args: string[]): string {
|
|
28
|
-
const env = { ...process.env }
|
|
29
|
-
delete env.GIT_DIR; delete env.GIT_INDEX_FILE; delete env.GIT_WORK_TREE
|
|
30
|
-
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', env, maxBuffer: 64 * 1024 * 1024 }).trim()
|
|
31
|
-
}
|
|
32
|
-
function tryGit(cwd: string, args: string[]): string | null {
|
|
33
|
-
try { return git(cwd, args) } catch { return null }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function walk(dir: string): string[] {
|
|
37
|
-
let out: string[] = []
|
|
38
|
-
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
-
const p = join(dir, e.name)
|
|
40
|
-
if (e.isDirectory()) out = out.concat(walk(p))
|
|
41
|
-
else if (e.isFile()) out.push(p)
|
|
42
|
-
}
|
|
43
|
-
return out
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---------- the vocabulary tables (§3.1/§3.2 of the v0.3.0 plan — old spelling → its ONE new home) ----------
|
|
47
|
-
// Only surfaces that are executed or parsed. Ordered: longer/more-specific rules run before their prefixes.
|
|
48
|
-
const COMMAND_REWRITES: [RegExp, string][] = [
|
|
49
|
-
// eval domain (né yatsu)
|
|
50
|
-
[/\bspex yatsu check-staged\b/g, 'spex internal check-staged'],
|
|
51
|
-
[/\bspex yatsu eval\b/g, 'spex eval add'],
|
|
52
|
-
[/\bspex yatsu show\b/g, 'spex eval ls'],
|
|
53
|
-
[/\bspex yatsu scan\b/g, 'spex eval lint'],
|
|
54
|
-
[/\bspex yatsu retract\b/g, 'spex eval retract'],
|
|
55
|
-
[/\bspex yatsu clean\b/g, 'spex eval clean'],
|
|
56
|
-
[/\bspex guide yatsu\b/g, 'spex guide eval'],
|
|
57
|
-
[/\bspex guide config\b/g, 'spex guide settings'],
|
|
58
|
-
[/\bspex blob put\b/g, 'spex evidence put'],
|
|
59
|
-
[/\bspex blob get\b/g, 'spex evidence get'],
|
|
60
|
-
// issue domain (né issues/forge drawers)
|
|
61
|
-
[/\bspex issues nudge\b/g, 'spex internal nudge'],
|
|
62
|
-
[/\bspex issues open\b/g, 'spex issue open'],
|
|
63
|
-
[/\bspex issues reply\b/g, 'spex issue reply'],
|
|
64
|
-
[/\bspex issues close\b/g, 'spex issue close'],
|
|
65
|
-
[/\bspex issues promote\b/g, 'spex issue promote'],
|
|
66
|
-
[/\bspex issues ls\b/g, 'spex issue ls'],
|
|
67
|
-
[/\bspex issues\b(?!\s+(?:on|off|status)\b)/g, 'spex issue ls'], // bare `spex issues` listed; on|off|status are DELETED → flagged below
|
|
68
|
-
[/\bspex forge links\b/g, 'spex issue links'],
|
|
69
|
-
[/\bspex forge eval-pending\b/g, 'spex issue links --pending'],
|
|
70
|
-
// remark domain (né bare remark/resolve/retract)
|
|
71
|
-
[/\bspex resolve\b/g, 'spex remark resolve'],
|
|
72
|
-
[/\bspex retract\b/g, 'spex remark retract'],
|
|
73
|
-
[/\bspex remark\b(?!\s+(?:add|resolve|retract)\b)/g, 'spex remark add'],
|
|
74
|
-
// session drawer: renamed subs first, then the retired promoted spellings
|
|
75
|
-
[/\bspex session reopen\b/g, 'spex session resume'],
|
|
76
|
-
[/\bspex session exit\b/g, 'spex session stop'],
|
|
77
|
-
[/\bspex session rawkey\b/g, 'spex session send --keys'],
|
|
78
|
-
[/\bspex session capture\b/g, 'spex session show --capture'],
|
|
79
|
-
[/\bspex session prompt\b/g, 'spex session show'],
|
|
80
|
-
[/\bspex session state\b/g, 'spex internal session-state'],
|
|
81
|
-
[/\bspex session fail\b/g, 'spex internal session-fail'],
|
|
82
|
-
[/\bspex session idle\b/g, 'spex internal session-idle'],
|
|
83
|
-
[/\bspex session commit-gate\b/g, 'spex internal commit-gate'],
|
|
84
|
-
[/\bspex reopen\b/g, 'spex session resume'],
|
|
85
|
-
[/\bspex exit\b/g, 'spex session stop'],
|
|
86
|
-
[/\bspex rawkey\b/g, 'spex session send --keys'],
|
|
87
|
-
[/\bspex capture\b/g, 'spex session show --capture'],
|
|
88
|
-
[/\bspex prompt\b/g, 'spex session show'],
|
|
89
|
-
[/\bspex new\b/g, 'spex session new'],
|
|
90
|
-
[/\bspex ls\b/g, 'spex session ls'],
|
|
91
|
-
[/\bspex watch\b/g, 'spex session watch'],
|
|
92
|
-
[/\bspex wait\b/g, 'spex session wait'],
|
|
93
|
-
[/\bspex review\b(?!\s+proof\b)/g, 'spex session review'], // `spex review proof` is DELETED → flagged below
|
|
94
|
-
[/\bspex merge\b/g, 'spex session merge'],
|
|
95
|
-
[/\bspex done\b/g, 'spex session done'],
|
|
96
|
-
[/\bspex park\b/g, 'spex session park'],
|
|
97
|
-
[/\bspex ask\b/g, 'spex session ask'],
|
|
98
|
-
[/\bspex send\b/g, 'spex session send'],
|
|
99
|
-
[/\bspex close\b/g, 'spex session close'],
|
|
100
|
-
[/\bspex attach\b/g, 'spex session attach'],
|
|
101
|
-
[/\bspex rename\b/g, 'spex session rename'],
|
|
102
|
-
// spec drawer (né promoted verbs)
|
|
103
|
-
[/\bspex search\b/g, 'spex spec search'],
|
|
104
|
-
[/\bspex owner\b/g, 'spex spec owner'],
|
|
105
|
-
[/\bspex lint\b/g, 'spex spec lint'],
|
|
106
|
-
[/\bspex ack\b/g, 'spex spec ack'],
|
|
107
|
-
// project verbs
|
|
108
|
-
[/\bspex board\b/g, 'spex graph --json'],
|
|
109
|
-
[/\bspex tree\b/g, 'spex graph'],
|
|
110
|
-
[/\bspex dashboard\b/g, 'spex serve ui'],
|
|
111
|
-
[/\bspex self\b/g, 'spex doctor'],
|
|
112
|
-
[/\bspex doctor contract\b/g, 'spex doctor --contract'],
|
|
113
|
-
[/\bspex doctor conflicts\b/g, 'spex doctor --conflicts'],
|
|
114
|
-
]
|
|
115
|
-
const ROUTE_REWRITES: [RegExp, string][] = [
|
|
116
|
-
[/\/api\/board\/stream\b/g, '/api/graph/stream'],
|
|
117
|
-
[/\/api\/board\b/g, '/api/graph'],
|
|
118
|
-
[/\/api\/sessions\/graph\b/g, '/api/sessions/edges'],
|
|
119
|
-
[/\/api\/yatsu\/blob\b/g, '/api/evidence'],
|
|
120
|
-
[/\/yatsu\/eval\b/g, '/evals'],
|
|
121
|
-
[/\/api\/config\b/g, '/api/plugins'],
|
|
122
|
-
[/\/api\/layout\b/g, '/api/settings'],
|
|
123
|
-
[/\/api\/launchers\b/g, '/api/settings'],
|
|
124
|
-
]
|
|
125
|
-
const LABEL_REWRITES: [RegExp, string][] = [
|
|
126
|
-
[/\byatsu-uncovered:/g, 'eval-coverage:'],
|
|
127
|
-
[/\byatsu-(schema|missing|drift|dangling|owners):/g, 'eval-$1:'],
|
|
128
|
-
]
|
|
129
|
-
const FILE_REWRITES: [RegExp, string][] = [
|
|
130
|
-
[/\byatsu\.evals\.ndjson\b/g, 'evals.ndjson'], // also rewrites the `.yatsu.evals.ndjson` suffix form
|
|
131
|
-
[/\byatsu\.md\b/g, 'eval.md'],
|
|
132
|
-
]
|
|
133
|
-
// dead spellings that survive every rewrite rule (no deterministic new home) → review items, never guessed at.
|
|
134
|
-
const MD_FLAG_PATTERNS: [RegExp, string][] = [
|
|
135
|
-
[/\bspex yatsu\b/, 'unmapped `spex yatsu …` spelling'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
136
|
-
[/\byatsu (scan|eval|show|retract|clean)\b/, 'un-prefixed yatsu verb (script-style invocation)'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
137
|
-
[/\bspex forge\b/, 'unmapped `spex forge …` spelling (drawer dissolved into `spex issue links`)'],
|
|
138
|
-
[/\bspex issues (on|off|status)\b/, '`spex issues on|off|status` was deleted — the switch is the spexcode.json `issues.enabled` key'],
|
|
139
|
-
[/\bspex review proof\b/, '`spex review proof` was deleted — use `spex eval ls --session <SEL> --export`'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
140
|
-
[/\bspex eval\b(?!\s+(?:add|ls|scenario|lint|retract|clean)\b|\s+--|\s*[.,;:)`'"]|\s*$)/m, 'old top-level `spex eval <SEL>` session read — now `spex eval ls --session <SEL>`'],
|
|
141
|
-
[/\byatsu-[a-z]+:/, 'unmapped yatsu-* lint label'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
142
|
-
[/\byatsu\.md\b/, 'a yatsu.md reference survived rewriting'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
143
|
-
]
|
|
144
|
-
// executable patterns that make an UNKNOWN (non-template) script a review item — scripts are never rewritten.
|
|
145
|
-
const SCRIPT_FLAG_PATTERNS: [RegExp, string][] = [
|
|
146
|
-
[/\byatsu\b/, 'invokes/greps the yatsu vocabulary'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
147
|
-
[/\bsession (state|fail|idle|commit-gate)\b/, 'calls a hook verb that moved to `spex internal …`'],
|
|
148
|
-
[/\bblob (put|get)\b/, 'calls the blob verbs (now `spex evidence put|get`)'], // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
149
|
-
[/\bspex (board|tree|dashboard|search|owner|lint|ack|new|ls|watch|wait|review|merge)\b/, 'calls a retired top-level spelling'],
|
|
150
|
-
[/\/api\/(board|config|layout|launchers|yatsu)\b/, 'hits a renamed API route'],
|
|
151
|
-
]
|
|
152
|
-
|
|
153
|
-
type Flag = { file: string; line: number | null; reason: string }
|
|
154
|
-
|
|
155
|
-
// ---------- preconditions: check EVERYTHING before the first write ----------
|
|
156
|
-
type World = {
|
|
157
|
-
root: string
|
|
158
|
-
specRoots: string[] // .spec/<root> dirs that are spec roots (have spec.md)
|
|
159
|
-
configRoots: string[] // spec roots carrying an old-world .config
|
|
160
|
-
yatsuFiles: string[] // absolute paths of yatsu.md / *yatsu.evals.ndjson under .spec
|
|
161
|
-
configNode: string | null // .spec/<root>/config dir to rename, iff hash-gated as spexcode's plugin-system spec
|
|
162
|
-
configNodeFlag: Flag | null // a node named `config` that is NOT stock → flagged, not renamed
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// inspectAsync (bottom of file) gathers the world and appends every refusal — ALL preconditions are
|
|
166
|
-
// evaluated before runMigrate performs its first write.
|
|
167
|
-
|
|
168
|
-
export async function runMigrate(): Promise<number> {
|
|
169
|
-
const refusals: string[] = []
|
|
170
|
-
let world: World | null = null
|
|
171
|
-
try { world = await inspectAsync(process.cwd(), refusals) } catch (e) { refusals.push((e as Error).message) }
|
|
172
|
-
if (refusals.length || !world) {
|
|
173
|
-
console.error('spex doctor --migrate: REFUSED — nothing was changed. Fix these and re-run:')
|
|
174
|
-
for (const r of refusals) console.error(` ✗ ${r}`)
|
|
175
|
-
return 2
|
|
176
|
-
}
|
|
177
|
-
const { root } = world
|
|
178
|
-
const rel = (f: string) => relative(root, f)
|
|
179
|
-
const flags: Flag[] = world.configNodeFlag ? [world.configNodeFlag] : []
|
|
180
|
-
const summary: string[] = []
|
|
181
|
-
const renamedNodes: [string, string][] = [] // node-id renames actually performed → drives mention re-pointing
|
|
182
|
-
|
|
183
|
-
console.log('spex doctor --migrate — one-shot 0.2.x → 0.3.0 migration (everything staged, nothing committed)\n')
|
|
184
|
-
|
|
185
|
-
// ---------- 1. tree renames (git mv — history-preserving) ----------
|
|
186
|
-
for (const dir of world.configRoots) {
|
|
187
|
-
git(root, ['mv', join(dir, '.config'), join(dir, '.plugins')])
|
|
188
|
-
summary.push(`renamed ${rel(dir)}/.config → .plugins (git mv)`)
|
|
189
|
-
}
|
|
190
|
-
renamedNodes.push(['.config', '.plugins'])
|
|
191
|
-
if (world.configNode) {
|
|
192
|
-
git(root, ['mv', world.configNode, join(dirname(world.configNode), 'plugin-system')])
|
|
193
|
-
renamedNodes.push(['config', 'plugin-system'])
|
|
194
|
-
summary.push(`renamed node ${rel(world.configNode)} → plugin-system (git mv; stock plugin-system spec, hash-verified)`)
|
|
195
|
-
}
|
|
196
|
-
// yatsu file renames — re-walk (paths moved under .plugins)
|
|
197
|
-
const yatsuNow = walk(join(root, '.spec')).filter((f) => basename(f) === 'yatsu.md' || basename(f).endsWith('yatsu.evals.ndjson')) // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
198
|
-
for (const f of yatsuNow) {
|
|
199
|
-
const target = basename(f) === 'yatsu.md' ? join(dirname(f), 'eval.md') : join(dirname(f), basename(f).replace(/yatsu\.evals\.ndjson$/, 'evals.ndjson')) // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
200
|
-
git(root, ['mv', f, target])
|
|
201
|
-
}
|
|
202
|
-
if (yatsuNow.length) summary.push(`renamed ${yatsuNow.length} measurement file(s): yatsu.md → eval.md, *yatsu.evals.ndjson → *evals.ndjson (git mv)`) // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
203
|
-
|
|
204
|
-
// ---------- 2. template hook-asset upgrade (exact-match replace or flag — NEVER a silent rewrite) ----------
|
|
205
|
-
let replaced = 0, current = 0, retiredStock = 0
|
|
206
|
-
const touched = new Set<string>() // files replaced (skip vocab) …
|
|
207
|
-
const frozen = new Set<string>() // … and files flagged (must stay byte-identical)
|
|
208
|
-
for (const dir of world.configRoots) {
|
|
209
|
-
const plugRoot = join(dir, '.plugins')
|
|
210
|
-
for (const f of walk(plugRoot)) {
|
|
211
|
-
const r = relative(plugRoot, f)
|
|
212
|
-
const asset = TEMPLATE_ASSETS.find((a) => a.rel === r)
|
|
213
|
-
if (asset) {
|
|
214
|
-
const newContent = readFileSync(join(PKG_ROOT, asset.template), 'utf8')
|
|
215
|
-
if (readFileSync(f, 'utf8') === newContent) { current++; continue }
|
|
216
|
-
const sha = git(root, ['hash-object', f])
|
|
217
|
-
if (asset.oldShas.includes(sha)) {
|
|
218
|
-
writeFileSync(f, newContent); git(root, ['add', f]); replaced++; touched.add(f)
|
|
219
|
-
} else {
|
|
220
|
-
flags.push({ file: rel(f), line: null, reason: `differs from EVERY known stock template version (hand-customized?) — left untouched; port your customization onto the new template by hand (shipped at ${join('spec-cli', asset.template)})` })
|
|
221
|
-
frozen.add(f)
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
// not shipped any more: a stock copy of a retired template is reported; an unknown script with
|
|
225
|
-
// executable old vocabulary is flagged. Unknown .md bodies fall through to the vocabulary pass.
|
|
226
|
-
const oldNames = [r, r.replace(/eval\.md$/, 'yatsu.md'), r.replace(/evals\.ndjson$/, 'yatsu.evals.ndjson')] // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
227
|
-
const retiredKey = oldNames.find((n) => RETIRED_ASSETS[n])
|
|
228
|
-
if (retiredKey && RETIRED_ASSETS[retiredKey].includes(git(root, ['hash-object', f]))) {
|
|
229
|
-
retiredStock++
|
|
230
|
-
flags.push({ file: rel(f), line: null, reason: `stock copy of a template the current release no longer ships (${retiredKey}) — kept; review whether to keep or delete it` })
|
|
231
|
-
// hash-verified STOCK content: a retired .md still gets the vocabulary pass (we know every byte
|
|
232
|
-
// is ours, so rewriting its executable surfaces is not a guess); a retired SCRIPT has no shipped
|
|
233
|
-
// successor and no rewrite table, so it stays frozen byte-identical.
|
|
234
|
-
if (!f.endsWith('.md')) frozen.add(f)
|
|
235
|
-
} else if (!f.endsWith('.md')) {
|
|
236
|
-
const text = readFileSync(f, 'utf8')
|
|
237
|
-
for (const [re, why] of SCRIPT_FLAG_PATTERNS) {
|
|
238
|
-
const i = text.split('\n').findIndex((l) => re.test(l))
|
|
239
|
-
if (i >= 0) { flags.push({ file: rel(f), line: i + 1, reason: `unknown script ${why} — scripts are never auto-rewritten; update it by hand` }); frozen.add(f); break }
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
summary.push(`template assets: ${replaced} upgraded (stock, hash-verified), ${current} already current, ${retiredStock} retired-stock, ${flags.length} flagged for review`)
|
|
246
|
-
|
|
247
|
-
// ---------- 3. body vocabulary pass (every .spec .md not replaced/frozen above) ----------
|
|
248
|
-
let rewrote = 0
|
|
249
|
-
const mentionRules: [RegExp, string][] = renamedNodes.map(([o, n]) => [new RegExp(`\\[\\[${o.replace('.', '\\.')}\\]\\]`, 'g'), `[[${n}]]`])
|
|
250
|
-
for (const f of walk(join(root, '.spec'))) {
|
|
251
|
-
if (!f.endsWith('.md') || touched.has(f) || frozen.has(f)) continue
|
|
252
|
-
const before = readFileSync(f, 'utf8')
|
|
253
|
-
let text = before
|
|
254
|
-
for (const [re, to] of [...FILE_REWRITES, ...LABEL_REWRITES, ...COMMAND_REWRITES, ...ROUTE_REWRITES, ...mentionRules]) text = text.replace(re, to)
|
|
255
|
-
// .spec-internal path strings (code: frontmatter, prose paths): /.config/ → /.plugins/ on lines that name .spec
|
|
256
|
-
text = text.split('\n').map((l) => l.includes('.spec/') ? l.replaceAll('/.config/', '/.plugins/') : l).join('\n')
|
|
257
|
-
// issue-thread frontmatter `nodes:` bindings follow the performed node renames
|
|
258
|
-
if (renamedNodes.some(([o]) => o === 'config'))
|
|
259
|
-
text = text.replace(/^(nodes:.*)$/m, (line) => line.replace(/\bconfig\b/g, 'plugin-system'))
|
|
260
|
-
if (text !== before) { writeFileSync(f, text); git(root, ['add', f]); rewrote++ }
|
|
261
|
-
// whatever old vocabulary SURVIVES the rewrite has no deterministic home → review, never guess
|
|
262
|
-
const lines = text.split('\n')
|
|
263
|
-
for (const [re, why] of MD_FLAG_PATTERNS) {
|
|
264
|
-
const i = lines.findIndex((l) => re.test(l))
|
|
265
|
-
if (i >= 0) flags.push({ file: rel(f), line: i + 1, reason: why })
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
summary.push(`bodies: ${rewrote} .md file(s) rewritten (command spellings · lint labels · file/route names · [[mention]] re-pointing · .spec paths)`)
|
|
269
|
-
|
|
270
|
-
// ---------- 4. legacy settings key: proposals.enabled → issues.enabled ----------
|
|
271
|
-
for (const name of ['spexcode.json', 'spexcode.local.json']) {
|
|
272
|
-
const p = join(root, name)
|
|
273
|
-
if (!existsSync(p)) continue
|
|
274
|
-
let cfg: Record<string, unknown>
|
|
275
|
-
try { cfg = JSON.parse(readFileSync(p, 'utf8')) } catch { flags.push({ file: name, line: null, reason: 'unparseable JSON — legacy `proposals` key (if any) not migrated' }); continue }
|
|
276
|
-
if (!('proposals' in cfg)) continue
|
|
277
|
-
const prop = cfg.proposals as { enabled?: unknown }
|
|
278
|
-
const issues = (cfg.issues ?? {}) as Record<string, unknown>
|
|
279
|
-
if (typeof prop?.enabled === 'boolean' && !('enabled' in issues)) cfg.issues = { ...issues, enabled: prop.enabled }
|
|
280
|
-
delete cfg.proposals
|
|
281
|
-
writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n')
|
|
282
|
-
if (tryGit(root, ['ls-files', '--error-unmatch', name])) git(root, ['add', p])
|
|
283
|
-
summary.push(`${name}: legacy \`proposals\` key rewritten to \`issues.enabled\` (v0.3.0 reads only the new key)`)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ---------- 5. per-clone evidence cache: yatsu-blobs → evidence (lossless dir rename) ----------
|
|
287
|
-
const commonDir = git(root, ['rev-parse', '--path-format=absolute', '--git-common-dir'])
|
|
288
|
-
const oldCache = join(commonDir, 'spexcode', 'yatsu-blobs'), newCache = join(commonDir, 'spexcode', 'evidence') // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
289
|
-
if (existsSync(oldCache) && !existsSync(newCache)) { renameSync(oldCache, newCache); summary.push('evidence cache: .git/spexcode/yatsu-blobs → evidence (bytes preserved)') } // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
290
|
-
|
|
291
|
-
// ---------- 6. post-checks: the new CLI must actually work on the migrated tree ----------
|
|
292
|
-
console.log('— migration applied; running post-checks —\n')
|
|
293
|
-
const post: string[] = []
|
|
294
|
-
try {
|
|
295
|
-
const { specLint } = await import('./lint.js')
|
|
296
|
-
const findings = await specLint()
|
|
297
|
-
const errs = findings.filter((x) => x.level === 'error')
|
|
298
|
-
for (const x of findings) console.error(` ${x.level === 'error' ? '✗' : '•'} ${x.rule}: ${x.msg}`)
|
|
299
|
-
post.push(`spec lint: ${errs.length} error(s), ${findings.length - errs.length} warning(s)${errs.length ? ' — FIX BEFORE COMMITTING' : ''}`)
|
|
300
|
-
} catch (e) { post.push(`spec lint FAILED to run: ${(e as Error).message}`) }
|
|
301
|
-
try {
|
|
302
|
-
const { runEval } = await import('../../spec-eval/src/cli.js')
|
|
303
|
-
await runEval(['lint'])
|
|
304
|
-
post.push('eval lint: ran (advisory — findings above, if any)')
|
|
305
|
-
} catch (e) { post.push(`eval lint FAILED to run: ${(e as Error).message}`) }
|
|
306
|
-
try {
|
|
307
|
-
const { materialize } = await import('./materialize.js')
|
|
308
|
-
post.push(`materialize: ok (content-hash ${materialize(root)}) — hook manifest + contract now speak .plugins`)
|
|
309
|
-
} catch (e) { post.push(`materialize FAILED: ${(e as Error).message}`) }
|
|
310
|
-
|
|
311
|
-
// ---------- summary ----------
|
|
312
|
-
console.log('\n===== migration summary =====')
|
|
313
|
-
for (const s of summary) console.log(` ✓ ${s}`)
|
|
314
|
-
for (const s of post) console.log(` · ${s}`)
|
|
315
|
-
if (flags.length) {
|
|
316
|
-
console.log(`\n NEEDS REVIEW (${flags.length}) — reported, deliberately NOT rewritten:`)
|
|
317
|
-
for (const fl of flags) console.log(` ! ${fl.file}${fl.line ? `:${fl.line}` : ''} — ${fl.reason}`)
|
|
318
|
-
}
|
|
319
|
-
console.log(`\n Everything is STAGED, nothing committed. Undo entirely with: git reset --hard
|
|
320
|
-
Next steps:
|
|
321
|
-
1. review the staged diff (git diff --cached) and every NEEDS REVIEW item above
|
|
322
|
-
2. commit through the ritual (on a trunk this is topology surgery: SPEXCODE_ALLOW_MAIN=1 git commit)
|
|
323
|
-
3. npm run hooks — in EVERY clone of this repo (git hooks are per-clone copies)
|
|
324
|
-
4. full backend restart (the supervisor env must be respawned), then rebuild the dashboard dist
|
|
325
|
-
5. if a node was renamed above, retarget OPEN forge issues' \`Spec:\` lines via your forge (closed ones are archive — leave them)`)
|
|
326
|
-
return 0
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// the precondition gatherer — every check appends a refusal; the caller only proceeds on an empty list.
|
|
330
|
-
async function inspectAsync(cwd: string, refusals: string[]): Promise<World | null> {
|
|
331
|
-
const root = tryGit(cwd, ['rev-parse', '--show-toplevel'])
|
|
332
|
-
if (!root) { refusals.push('not inside a git repository.'); return null }
|
|
333
|
-
const gitDir = git(root, ['rev-parse', '--path-format=absolute', '--git-dir'])
|
|
334
|
-
const commonDir = git(root, ['rev-parse', '--path-format=absolute', '--git-common-dir'])
|
|
335
|
-
if (gitDir !== commonDir) refusals.push(`this is a linked worktree (${root}) — run the migration from the main checkout.`)
|
|
336
|
-
const specDir = join(root, '.spec')
|
|
337
|
-
if (!existsSync(specDir)) { refusals.push(`no .spec/ at ${root} — this repo has not adopted SpexCode; nothing to migrate.`); return null }
|
|
338
|
-
|
|
339
|
-
const specRoots: string[] = [], configRoots: string[] = [], pluginRoots: string[] = []
|
|
340
|
-
for (const e of readdirSync(specDir, { withFileTypes: true })) {
|
|
341
|
-
if (!e.isDirectory() || e.name === '.issues') continue
|
|
342
|
-
const dir = join(specDir, e.name)
|
|
343
|
-
if (!existsSync(join(dir, 'spec.md'))) continue
|
|
344
|
-
specRoots.push(dir)
|
|
345
|
-
if (existsSync(join(dir, '.config'))) configRoots.push(dir)
|
|
346
|
-
if (existsSync(join(dir, '.plugins'))) pluginRoots.push(dir)
|
|
347
|
-
}
|
|
348
|
-
for (const p of pluginRoots) refusals.push(`${relative(root, p)}/.plugins already exists — this tree is already migrated${configRoots.length ? ' (and a .config root ALSO exists — half-migrated state, resolve by hand)' : ''}.`)
|
|
349
|
-
|
|
350
|
-
const yatsuFiles = walk(specDir).filter((f) => basename(f) === 'yatsu.md' || basename(f).endsWith('yatsu.evals.ndjson')) // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
351
|
-
if (!configRoots.length && !yatsuFiles.length && !pluginRoots.length)
|
|
352
|
-
refusals.push('nothing to migrate — no .config plugin root and no yatsu.md/yatsu.evals.ndjson files under .spec/ (this tree already speaks 0.3.0, or never had the 0.2.x vocabulary).') // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
353
|
-
|
|
354
|
-
if (!git(root, ['ls-files', '.spec']).length)
|
|
355
|
-
refusals.push('.spec is not tracked by git (an old `/.spec` exclude line?) — remove the exclude, `git add .spec` and commit it first, then re-run.')
|
|
356
|
-
|
|
357
|
-
const dirty = git(root, ['status', '--porcelain']).split('\n').filter((l) => l && !l.startsWith('??'))
|
|
358
|
-
if (dirty.length) refusals.push(`working tree not clean — commit or stash first:\n${dirty.slice(0, 10).map((l) => ' ' + l).join('\n')}${dirty.length > 10 ? `\n … +${dirty.length - 10} more` : ''}`)
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
const { listSessionIds, readRawRecord } = await import('./layout.js')
|
|
362
|
-
const ids = listSessionIds()
|
|
363
|
-
if (ids.length) {
|
|
364
|
-
const rows = ids.map((id) => { const r = readRawRecord(id); return ` ${id} (${r?.status ?? 'unreadable'})` })
|
|
365
|
-
refusals.push(`${ids.length} session(s) still exist for this project — drain them first (merge, then \`spex session close <id>\`; a worktree branched pre-migration would conflict):\n${rows.join('\n')}`)
|
|
366
|
-
}
|
|
367
|
-
} catch (e) { refusals.push(`could not enumerate this project's sessions (${(e as Error).message}) — refusing to migrate blind.`) }
|
|
368
|
-
|
|
369
|
-
for (const dir of configRoots) if (existsSync(join(dir, '.plugins'))) refusals.push(`${relative(root, dir)}/.plugins already exists beside .config — resolve by hand.`)
|
|
370
|
-
for (const f of yatsuFiles) {
|
|
371
|
-
const target = basename(f) === 'yatsu.md' ? join(dirname(f), 'eval.md') : join(dirname(f), basename(f).replace(/yatsu\.evals\.ndjson$/, 'evals.ndjson')) // dead-words-ok: the migrator names the old vocabulary it migrates from
|
|
372
|
-
if (existsSync(target)) refusals.push(`rename collision: ${relative(root, target)} already exists beside ${relative(root, f)}.`)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
let configNode: string | null = null, configNodeFlag: Flag | null = null
|
|
376
|
-
for (const dir of specRoots) {
|
|
377
|
-
const cand = join(dir, 'config')
|
|
378
|
-
if (!existsSync(join(cand, 'spec.md'))) continue
|
|
379
|
-
if (existsSync(join(dir, 'plugin-system'))) { refusals.push(`${relative(root, dir)}/plugin-system already exists beside config/ — resolve by hand.`); continue }
|
|
380
|
-
const sha = git(root, ['hash-object', join(cand, 'spec.md')])
|
|
381
|
-
if (CONFIG_NODE_SHAS.includes(sha)) configNode = cand
|
|
382
|
-
else configNodeFlag = { file: relative(root, join(cand, 'spec.md')), line: null, reason: 'a node named `config` exists but its spec.md matches no stock version of spexcode\'s plugin-system spec — left untouched (rename it by hand ONLY if it really is the plugin-system spec)' }
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return { root, specRoots, configRoots, yatsuFiles, configNode, configNodeFlag }
|
|
386
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: distill
|
|
3
|
-
surface: skill, command
|
|
4
|
-
status: active
|
|
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.
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# distill
|
|
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.
|
|
14
|
-
|
|
15
|
-
## 1 · resolve the session
|
|
16
|
-
|
|
17
|
-
Input: a session id — SpexCode's, a bare harness id (claude / codex thread), or a transcript `.jsonl` path.
|
|
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.
|
|
26
|
-
|
|
27
|
-
## 2 · digest the transcript — mechanical first, model second
|
|
28
|
-
|
|
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.
|
|
34
|
-
|
|
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.
|
|
37
|
-
|
|
38
|
-
## 3 · distill — forward-looking, not narrative
|
|
39
|
-
|
|
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:
|
|
42
|
-
|
|
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.
|
|
51
|
-
|
|
52
|
-
## 4 · salvage the desk
|
|
53
|
-
|
|
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.
|
|
58
|
-
|
|
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.
|
|
67
|
-
|
|
68
|
-
## 5 · clean up — only after the salvage LANDED
|
|
69
|
-
|
|
70
|
-
Cleanup discards state — verify the salvaged commits are in your tree (or the branch genuinely merged) first.
|
|
71
|
-
|
|
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.
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: supervisor
|
|
3
|
-
surface: command
|
|
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 the project's agent notes (`<root>/CLAUDE.md` / `AGENTS.md`, including the materialized `<!-- spexcode -->` contract block)** for project-specific rules; the loop below (dispatch → monitor → review → merge → close, parallelizing independent tasks) is your playbook. Then drive the goal: decompose it into worker-sized tasks and dispatch one worker per independent task (`spex session new "<task>"` — give each ONLY its task; a task about one specific node mentions it as `[[<id>]]`, which only sets the branch name and board attribution; the session's real node links come from what it edits), monitor with `spex session watch`, review proposals with `spex session review <id>`, merge good ones with `git -C <root> merge --no-ff <branch>`, then close. Never let a worker self-merge; keep `spex spec lint` at 0 errors. To WAIT on a worker, POLL one-shot (`spex session review <id>` or `spex session ls` — both return immediately); never block on `spex session 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,25 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: tidy
|
|
3
|
-
surface: command
|
|
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 spec lint` after each — it must stay at 0 errors.
|