spexcode 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -17
- package/package.json +9 -5
- package/spec-cli/README.md +23 -14
- package/spec-cli/hooks/dispatch.sh +7 -5
- package/spec-cli/hooks/harness.sh +19 -8
- package/spec-cli/src/anchors.ts +110 -18
- package/spec-cli/src/cli.ts +88 -37
- 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 +27 -19
- package/spec-cli/src/git.ts +12 -5
- package/spec-cli/src/graph.ts +54 -43
- package/spec-cli/src/graphStream.ts +265 -34
- package/spec-cli/src/guide.ts +105 -55
- package/spec-cli/src/harness-select.ts +20 -6
- package/spec-cli/src/harness.ts +181 -33
- package/spec-cli/src/help.ts +64 -27
- 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 +145 -86
- package/spec-cli/src/init.ts +53 -25
- package/spec-cli/src/layout.ts +5 -5
- package/spec-cli/src/lint.ts +25 -121
- package/spec-cli/src/login-page.ts +12 -5
- package/spec-cli/src/materialize.ts +39 -13
- package/spec-cli/src/mentions.ts +16 -11
- package/spec-cli/src/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 +14 -0
- package/spec-cli/src/sessions.ts +188 -66
- package/spec-cli/src/shim-runtime.ts +169 -0
- package/spec-cli/src/slash-commands.ts +76 -0
- package/spec-cli/src/source-files.ts +79 -0
- package/spec-cli/src/specs.ts +12 -7
- package/spec-cli/src/supervise.ts +37 -15
- package/spec-cli/src/tree.ts +8 -8
- package/spec-cli/src/uninstall.ts +56 -23
- package/spec-cli/templates/hooks/prepare-commit-msg +32 -18
- package/spec-cli/templates/spec/project/.plugins/commands/extract/spec.md +24 -18
- package/spec-cli/templates/spec/project/.plugins/commands/regroup/spec.md +3 -3
- package/spec-cli/templates/spec/project/.plugins/commands/rename/spec.md +11 -0
- package/spec-cli/templates/spec/project/.plugins/commands/spec.md +22 -8
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/commands/tidy/spec.md +29 -11
- package/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +2 -0
- package/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec-first.sh +28 -39
- package/spec-cli/templates/spec/project/.plugins/core/spec-first/spec.md +5 -3
- package/spec-cli/templates/spec/project/.plugins/core/spec-of-file/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -2
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +7 -1
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +2 -2
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +5 -5
- package/spec-cli/templates/spec/project/.plugins/prompts/spec.md +10 -6
- package/spec-cli/templates/spec/project/.plugins/skills/distill/spec.md +38 -50
- package/spec-cli/templates/spec/project/.plugins/skills/spec.md +9 -7
- package/spec-cli/templates/spec/project/.plugins/spec.md +17 -12
- package/spec-cli/templates/spec/project/spec.md +6 -5
- package/spec-cli/templates/spexcode.json +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 +49 -28
- package/spec-eval/src/humanok.ts +1 -1
- package/spec-eval/src/matrix.ts +693 -0
- package/spec-eval/src/sessioneval.ts +726 -66
- package/spec-cli/src/migrate-table.ts +0 -408
- package/spec-cli/src/migrate.ts +0 -386
- package/spec-dashboard/dist/assets/Dashboard-C_fGmOKK.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-Cnr1s3bq.js +0 -2
- package/spec-dashboard/dist/assets/FoldToggle-x9gtO1OQ.js +0 -1
- package/spec-dashboard/dist/assets/IssuesPage-5f_vL-JV.js +0 -1
- package/spec-dashboard/dist/assets/MobileApp-DEO1jgGM.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-CAlbMOFR.js +0 -66
- package/spec-dashboard/dist/assets/SessionInterface-DYP7pi_n.css +0 -32
- package/spec-dashboard/dist/assets/SessionWindow-JYbpPwNB.js +0 -13
- package/spec-dashboard/dist/assets/Settings-DKb5Ji_X.js +0 -1
- package/spec-dashboard/dist/assets/index-BQu-oJ8J.js +0 -41
- package/spec-dashboard/dist/assets/index-BbMkwuix.css +0 -1
- package/spec-dashboard/dist/assets/launch-BM9GgvkX.js +0 -6
package/spec-cli/src/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
|
-
}
|