@gobing-ai/spur 0.3.95 → 0.3.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/plugin-scripts.json +0 -54
  3. package/config/rules/boundary/sp-script-placement.yaml +19 -0
  4. package/config/rules/strict/runtime-boundaries.yaml +5 -0
  5. package/config/rules/structure/test-location.yaml +2 -0
  6. package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
  7. package/config/rules/typescript/output-boundaries.yaml +15 -2
  8. package/config/script-placement-baseline.json +51 -0
  9. package/config/workflows/feature-verification.yaml +1 -1
  10. package/config/workflows/history-anatomy.yaml +23 -11
  11. package/config/workflows/idea-pipeline.yaml +80 -85
  12. package/config/workflows/pr-review.yaml +18 -9
  13. package/config/workflows/task-pipeline.yaml +42 -95
  14. package/config/workflows/wrapup-pipeline.yaml +89 -37
  15. package/package.json +1 -1
  16. package/plugins/sp/README.md +8 -10
  17. package/plugins/sp/agents/super-reviewer.md +43 -18
  18. package/plugins/sp/commands/dev-fixgha.md +83 -0
  19. package/plugins/sp/commands/dev-gitmsg.md +8 -6
  20. package/plugins/sp/commands/dev-gtd.md +2 -2
  21. package/plugins/sp/commands/dev-idea.md +22 -12
  22. package/plugins/sp/commands/dev-plan.md +8 -9
  23. package/plugins/sp/commands/dev-review.md +22 -13
  24. package/plugins/sp/commands/dev-verifyall.md +1 -1
  25. package/plugins/sp/commands/spur-init.md +2 -2
  26. package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
  27. package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
  28. package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
  29. package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
  30. package/plugins/sp/lib/inline-run.generated.mjs +25 -8
  31. package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
  32. package/plugins/sp/lib/quality-gate.generated.mjs +438 -0
  33. package/plugins/sp/lib/residual-scan.generated.d.mts +62 -0
  34. package/plugins/sp/lib/residual-scan.generated.mjs +210 -0
  35. package/plugins/sp/lib/spur-bin.ts +36 -0
  36. package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
  37. package/plugins/sp/lib/step-profile.generated.mjs +174 -0
  38. package/plugins/sp/plugin.json +1 -1
  39. package/plugins/sp/references/roles.md +1 -1
  40. package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
  41. package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
  42. package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
  43. package/plugins/sp/scripts/inline-run-setup.ts +104 -667
  44. package/plugins/sp/scripts/quality-gate.mjs +34 -19
  45. package/plugins/sp/scripts/quality-gate.ts +12 -658
  46. package/plugins/sp/scripts/residual-scan.mjs +135 -156
  47. package/plugins/sp/scripts/residual-scan.ts +102 -499
  48. package/plugins/sp/scripts/script-root.mjs +5 -1
  49. package/plugins/sp/scripts/script-root.ts +5 -1
  50. package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
  51. package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
  52. package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
  53. package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
  54. package/plugins/sp/scripts/wrapup-steps.mjs +10 -32
  55. package/plugins/sp/scripts/wrapup-steps.ts +8 -36
  56. package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
  57. package/plugins/sp/skills/code-verification/SKILL.md +34 -9
  58. package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
  59. package/plugins/sp/skills/functional-review/SKILL.md +7 -4
  60. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  61. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
  62. package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
  63. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  64. package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
  65. package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
  66. package/plugins/sp/skills/spur-cli/references/features.md +17 -6
  67. package/plugins/sp/skills/spur-cli/references/init.md +17 -16
  68. package/plugins/sp/skills/spur-cli/references/self.md +3 -2
  69. package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
  70. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
  71. package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
  72. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
  73. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +11 -9
  74. package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
  75. package/plugins/sp/skills/spur-dev/references/dev-operations.md +86 -70
  76. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -3
  77. package/plugins/sp/skills/spur-dev/references/execution-batch.md +77 -34
  78. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -9
  79. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
  80. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +31 -21
  81. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
  82. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
  83. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +29 -3
  84. package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
  85. package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
  86. package/spur.js +1495 -369
  87. package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.BIjMatT1.js} +1 -1
  88. package/web/_astro/{BoardApp.BxJuwD7I.js → BoardApp.GvjIe9Z6.js} +42 -42
  89. package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.BZM3EAFx.js} +1 -1
  90. package/web/_astro/{arc.CyjRvNMY.js → arc.HkRiZnoI.js} +1 -1
  91. package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.BDdK-tgZ.js} +1 -1
  92. package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.CZ9kOBxx.js} +1 -1
  93. package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.DXK9qeRD.js} +1 -1
  94. package/web/_astro/channel.DlhX2MEt.js +1 -0
  95. package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.BTD2WeX5.js} +1 -1
  96. package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.Ci6Qbcvk.js} +1 -1
  97. package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.yl3zsj7p.js} +1 -1
  98. package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ClXZsfyR.js} +1 -1
  99. package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.BmKWZBcP.js} +1 -1
  100. package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.By30fcb7.js} +1 -1
  101. package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.24NmrD-k.js} +1 -1
  102. package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.BN4sKdcS.js} +1 -1
  103. package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.CJpoMPb5.js} +1 -1
  104. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.CJpoMPb5.js} +1 -1
  105. package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.BraxQ2Nt.js} +1 -1
  106. package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.gzoU73oL.js} +1 -1
  107. package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.wyDfWySU.js} +1 -1
  108. package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.h2_EbMxV.js} +1 -1
  109. package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.Db6dcs89.js} +1 -1
  110. package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.NVwzxu_U.js} +1 -1
  111. package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.B-eNgDQk.js} +1 -1
  112. package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.Bsa8ZRbJ.js} +1 -1
  113. package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.BospRE5Q.js} +1 -1
  114. package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.DMzvHwAA.js} +1 -1
  115. package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.Dmdxgd-M.js} +1 -1
  116. package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.CaqdO0rL.js} +1 -1
  117. package/web/_astro/index.EoZlzLK-.css +1 -0
  118. package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DDVD9Wn7.js} +1 -1
  119. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.EKyCUFsL.js} +1 -1
  120. package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.By2-RkYB.js} +1 -1
  121. package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.ar-WmlyR.js} +1 -1
  122. package/web/_astro/{linear.zRsuuDTE.js → linear.BnzMBgo_.js} +1 -1
  123. package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DSqeFu2Y.js} +4 -4
  124. package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.1XcF8Wh2.js} +1 -1
  125. package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DnItHOdK.js} +1 -1
  126. package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.BQQyrnBt.js} +1 -1
  127. package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.Dmzxun82.js} +1 -1
  128. package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.BBIeRKYB.js} +1 -1
  129. package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.CUNVdBBf.js} +1 -1
  130. package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.DlFf2sJm.js} +1 -1
  131. package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DXR7I1H5.js} +1 -1
  132. package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.BcO-GyX2.js} +1 -1
  133. package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.ChCECmmW.js} +1 -1
  134. package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRaT12mD.js} +1 -1
  135. package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.B3lsHVpj.js} +1 -1
  136. package/web/index.html +2 -2
  137. package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
  138. package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
  139. package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
  140. package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
  141. package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
  142. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
  143. package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
  144. package/plugins/sp/scripts/record-feature-sync.ts +0 -84
  145. package/plugins/sp/scripts/script-contract-check.ts +0 -506
  146. package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
  147. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
  148. package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
  149. package/plugins/sp/scripts/task-size-precheck.ts +0 -212
  150. package/plugins/sp/scripts/transition-shim-check.ts +0 -238
  151. package/plugins/sp/scripts/validate-commands.ts +0 -689
  152. package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
  153. package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
  154. package/web/_astro/channel.CI6N_tCg.js +0 -1
  155. package/web/_astro/index.CENnIEqT.css +0 -1
@@ -1,298 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * inline-pipeline-parity-check — two-sided gate between the inline pipeline
4
- * driver's documented action/guard set and the resolved action/guard sets in
5
- * `.spur/workflows/task-pipeline.yaml` and `.spur/workflows/idea-pipeline.yaml`
6
- * (task 0755 R2/R3).
7
- *
8
- * The driver reference at `plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md`
9
- * documents the set of action and guard kinds it implements. The two runtime
10
- * pipelines are the only consumers the driver needs to keep in step with. The
11
- * check is a symmetric set diff: an element present in one and absent in the
12
- * other fails the check and names the element.
13
- *
14
- * The set is defined in {@link DOCUMENTED} below. Task 0881 (0877 R7): the harness
15
- * enumerates state from BOTH reference sets — this constant and the driver
16
- * markdown's mirror list — plus the YAML union, as a three-way symmetric diff, so
17
- * removing a kind from either reference cannot escape parity. It also scans task
18
- * frontmatter `dependencies[]` for spurious edges: a referenced wbs with no task
19
- * file (post-0875, dependency edges no longer move the planning digest —
20
- * `packages/app/src/services/task-readiness.ts` — so over-declared edges would
21
- * otherwise be silently unbound).
22
- *
23
- * Usage:
24
- * bun plugins/sp/scripts/inline-pipeline-parity-check.ts
25
- * [--root <path>] default: repo root
26
- *
27
- * Exit code: 0 when the sets agree; 1 on any divergence. Violations are printed
28
- * to stderr; a summary to stdout.
29
- */
30
-
31
- import { readdirSync, readFileSync, statSync } from 'node:fs';
32
- import { join, resolve } from 'node:path';
33
- import { parse as parseYaml } from 'yaml';
34
-
35
- /** Documented action and guard set. Must stay in lockstep with the
36
- * "Supported action and guard set (0755 R2 parity contract)" section in
37
- * `plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md`. The
38
- * driver supports a kind if ANY workflow in `.spur/workflows/*.yaml` uses
39
- * it (the driver applies to any selected pipeline per its reference doc). */
40
- const DOCUMENTED = {
41
- actions: new Set([
42
- 'shell',
43
- 'note',
44
- 'doctor.probe',
45
- 'file.read.into-var',
46
- 'hitl.confirm',
47
- 'hitl.input',
48
- 'agent.run',
49
- 'proof.fingerprint',
50
- 'run.artifact',
51
- 'command.gate',
52
- 'decide',
53
- ]),
54
- guards: new Set(['always', 'shell', 'action-ok', 'contract-violation']),
55
- } as const;
56
-
57
- /** Directory of workflow definitions the driver is responsible for. */
58
- const WORKFLOW_DIR = join('config', 'workflows');
59
-
60
- /** The driver reference doc — the second reference set (0881). */
61
- const DRIVER_REF = join('plugins', 'sp', 'skills', 'spur-dev', 'references', 'inline-pipeline-driver.md');
62
-
63
- /** Task corpus dirs scanned for spurious `dependencies[]` edges (0881). */
64
- const TASK_DIRS = ['docs/tasks', 'docs/tasks5'];
65
-
66
- /**
67
- * Parse the driver reference's mirrored kind lists (`**Actions:** … · `kind` …`) into
68
- * reference sets. Returns null when the doc is absent (fixture roots).
69
- */
70
- function collectMarkdownReferenceKinds(root: string): { actions: Set<string>; guards: Set<string> } | null {
71
- let md: string;
72
- try {
73
- md = readFileSync(join(root, DRIVER_REF), 'utf8');
74
- } catch {
75
- return null;
76
- }
77
- const read = (label: RegExp): Set<string> => {
78
- const line = md.split('\n').find((l) => label.test(l));
79
- const out = new Set<string>();
80
- if (!line) return out;
81
- for (const m of line.matchAll(/`([^`]+)`/g)) out.add(m[1] ?? '');
82
- out.delete('');
83
- return out;
84
- };
85
- return { actions: read(/^\*\*Actions:/), guards: read(/^\*\*Guards/) };
86
- }
87
-
88
- /**
89
- * Scan task frontmatter `dependencies[]` for spurious edges: a referenced wbs that
90
- * resolves to no task file in the corpus (0881 — post-0875 these edges no longer
91
- * move the planning digest, so over-declared ones are otherwise silently unbound).
92
- */
93
- function checkTaskDependencyEdges(root: string, errors: string[]): number {
94
- const wbss = new Set<string>();
95
- const depFiles: { path: string; deps: string[] }[] = [];
96
- for (const dir of TASK_DIRS) {
97
- const abs = join(root, dir);
98
- let entries: string[] = [];
99
- try {
100
- entries = readdirSync(abs);
101
- } catch {
102
- continue;
103
- }
104
- for (const e of entries) {
105
- const m = /^(\d+)_.*\.md$/.exec(e);
106
- if (!m?.[1]) continue;
107
- wbss.add(m[1]);
108
- const raw = readFileSync(join(abs, e), 'utf8');
109
- const fm = /^---\n([\s\S]*?)\n---/.exec(raw);
110
- if (!fm?.[1]) continue;
111
- const deps: string[] = [];
112
- const inline = /^dependencies:\s*\[(.*)\]/m.exec(fm[1]);
113
- if (inline?.[1]) {
114
- for (const part of inline[1].split(',')) {
115
- const w = part.trim().replace(/["']/g, '');
116
- if (w !== '') deps.push(w);
117
- }
118
- } else {
119
- const block = /^dependencies:\s*$/m.exec(fm[1]);
120
- if (block) {
121
- for (const line of fm[1].split('\n')) {
122
- const item = /^\s*-\s*["']?(\d+)["']?\s*$/.exec(line);
123
- if (item?.[1]) deps.push(item[1]);
124
- }
125
- }
126
- }
127
- if (deps.length > 0) depFiles.push({ path: join(dir, e), deps });
128
- }
129
- }
130
- let count = 0;
131
- for (const { path, deps } of depFiles) {
132
- for (const dep of deps) {
133
- // Only wbs-shaped values are machine dependency edges; prose refs in the
134
- // legacy corpus are descriptive text, never consumed as edges.
135
- if (!/^\d{3,4}$/.test(dep)) continue;
136
- if (!wbss.has(dep)) {
137
- errors.push(`spurious dependency edge: ${path} depends on ${dep} but no task with wbs ${dep} exists`);
138
- count += 1;
139
- }
140
- }
141
- }
142
- return count;
143
- }
144
-
145
- /** Walk a state list and yield every `kind:` value found in `onEnter` action
146
- * lists. Skips the top-level workflow `kind:` (e.g. `state-machine`). */
147
- function collectActionKinds(states: unknown): Set<string> {
148
- const out = new Set<string>();
149
- if (!Array.isArray(states)) return out;
150
- for (const state of states) {
151
- if (typeof state !== 'object' || state === null) continue;
152
- const onEnter = (state as { onEnter?: unknown }).onEnter;
153
- if (!Array.isArray(onEnter)) continue;
154
- for (const action of onEnter) {
155
- if (typeof action !== 'object' || action === null) continue;
156
- const kind = (action as { kind?: unknown }).kind;
157
- if (typeof kind === 'string') out.add(kind);
158
- }
159
- }
160
- return out;
161
- }
162
-
163
- /** Walk a transition list and yield every `guard.kind` value. */
164
- function collectGuardKinds(transitions: unknown): Set<string> {
165
- const out = new Set<string>();
166
- if (!Array.isArray(transitions)) return out;
167
- for (const transition of transitions) {
168
- if (typeof transition !== 'object' || transition === null) continue;
169
- const guard = (transition as { guard?: { kind?: unknown } }).guard;
170
- const kind = guard?.kind;
171
- if (typeof kind === 'string') out.add(kind);
172
- }
173
- return out;
174
- }
175
-
176
- /** Symmetric set diff. Returns elements in `a` but not in `b`, and vice versa. */
177
- function diff<T>(a: Set<T>, b: Set<T>): { onlyInA: T[]; onlyInB: T[] } {
178
- const onlyInA: T[] = [];
179
- const onlyInB: T[] = [];
180
- for (const x of a) if (!b.has(x)) onlyInA.push(x);
181
- for (const x of b) if (!a.has(x)) onlyInB.push(x);
182
- return { onlyInA, onlyInB };
183
- }
184
-
185
- function parseArgs(argv: string[]): { root: string } {
186
- let root = resolve('.');
187
- for (let i = 0; i < argv.length; i += 1) {
188
- const arg = argv[i];
189
- if (arg === '--root' && i + 1 < argv.length) {
190
- root = resolve(argv[++i] ?? '.');
191
- } else if (arg === '--help' || arg === '-h') {
192
- process.stdout.write('Usage: bun inline-pipeline-parity-check.ts [--root <path>]\n');
193
- process.exit(0);
194
- }
195
- }
196
- return { root };
197
- }
198
-
199
- function listWorkflowFiles(dir: string): string[] {
200
- let entries: string[];
201
- try {
202
- entries = readdirSync(dir);
203
- } catch {
204
- return [];
205
- }
206
- return entries.filter((e) => e.endsWith('.yaml')).map((e) => join(dir, e));
207
- }
208
-
209
- async function main(): Promise<number> {
210
- const { root } = parseArgs(process.argv.slice(2));
211
- const errors: string[] = [];
212
-
213
- const workflowDir = join(root, WORKFLOW_DIR);
214
- if (!statSync(workflowDir, { throwIfNoEntry: false })) {
215
- process.stderr.write(`inline-pipeline-parity-check: workflow directory not found: ${workflowDir}\n`);
216
- return 1;
217
- }
218
-
219
- const files = listWorkflowFiles(workflowDir);
220
- if (files.length === 0) {
221
- process.stderr.write(`inline-pipeline-parity-check: no .yaml workflows found in ${workflowDir}\n`);
222
- return 1;
223
- }
224
-
225
- const unionActions = new Set<string>();
226
- const unionGuards = new Set<string>();
227
- const perFileKinds: { path: string; actions: Set<string>; guards: Set<string> }[] = [];
228
-
229
- for (const path of files) {
230
- let parsed: unknown;
231
- try {
232
- parsed = parseYaml(readFileSync(path, 'utf8'));
233
- } catch (err) {
234
- errors.push(`${path}: failed to parse (${err instanceof Error ? err.message : String(err)})`);
235
- continue;
236
- }
237
- if (typeof parsed !== 'object' || parsed === null) {
238
- continue;
239
- }
240
- const def = parsed as { states?: unknown; transitions?: unknown };
241
- const actions = collectActionKinds(def.states);
242
- const guards = collectGuardKinds(def.transitions);
243
- perFileKinds.push({ path, actions, guards });
244
- for (const a of actions) unionActions.add(a);
245
- for (const g of guards) unionGuards.add(g);
246
- }
247
-
248
- const actionDiff = diff(unionActions, DOCUMENTED.actions);
249
- const guardDiff = diff(unionGuards, DOCUMENTED.guards);
250
-
251
- for (const x of actionDiff.onlyInA) {
252
- const usedIn = perFileKinds.filter((f) => f.actions.has(x)).map((f) => f.path);
253
- errors.push(`action kind "${x}" used in YAML (${usedIn.join(', ')}) but absent from inline-pipeline-driver.md`);
254
- }
255
- for (const x of actionDiff.onlyInB) {
256
- errors.push(`action kind "${x}" documented in inline-pipeline-driver.md but never used in any workflow`);
257
- }
258
- for (const x of guardDiff.onlyInA) {
259
- const usedIn = perFileKinds.filter((f) => f.guards.has(x)).map((f) => f.path);
260
- errors.push(`guard kind "${x}" used in YAML (${usedIn.join(', ')}) but absent from inline-pipeline-driver.md`);
261
- }
262
- for (const x of guardDiff.onlyInB) {
263
- errors.push(`guard kind "${x}" documented in inline-pipeline-driver.md but never used in any workflow`);
264
- }
265
-
266
- // Three-way parity with the markdown mirror (0881): a kind dropped from either
267
- // reference set — constant or doc — is caught, not just YAML drift.
268
- const ref = collectMarkdownReferenceKinds(root);
269
- if (ref !== null) {
270
- for (const x of diff(ref.actions, DOCUMENTED.actions).onlyInA)
271
- errors.push(`action kind "${x}" in the driver markdown but absent from the DOCUMENTED set`);
272
- for (const x of diff(ref.actions, DOCUMENTED.actions).onlyInB)
273
- errors.push(`action kind "${x}" in DOCUMENTED but deleted from the driver markdown reference`);
274
- for (const x of diff(ref.guards, DOCUMENTED.guards).onlyInA)
275
- errors.push(`guard kind "${x}" in the driver markdown but absent from the DOCUMENTED set`);
276
- for (const x of diff(ref.guards, DOCUMENTED.guards).onlyInB)
277
- errors.push(`guard kind "${x}" in DOCUMENTED but deleted from the driver markdown reference`);
278
- for (const x of diff(ref.actions, unionActions).onlyInB)
279
- errors.push(`action kind "${x}" in the references but never used in any workflow`);
280
- for (const x of diff(ref.guards, unionGuards).onlyInB)
281
- errors.push(`guard kind "${x}" in the references but never used in any workflow`);
282
- }
283
-
284
- const spuriousEdges = checkTaskDependencyEdges(root, errors);
285
-
286
- if (errors.length > 0) {
287
- process.stderr.write(`inline-pipeline-parity-check: ${errors.length} divergence(s)\n`);
288
- for (const e of errors) process.stderr.write(` - ${e}\n`);
289
- return 1;
290
- }
291
-
292
- process.stdout.write(
293
- `inline-pipeline-parity-check: ok (${unionActions.size} actions, ${unionGuards.size} guards agree across ${files.length} workflows and both reference sets; ${spuriousEdges} spurious dependency edges)\n`,
294
- );
295
- return 0;
296
- }
297
-
298
- process.exit(await main());
@@ -1,63 +0,0 @@
1
- #!/usr/bin/env node
2
- // plugins/sp/scripts/record-feature-sync.ts
3
- import { spawnSync } from "node:child_process";
4
- import { appendFileSync, existsSync } from "node:fs";
5
-
6
- // plugins/sp/lib/env.ts
7
- function getEnvVar(name, fallback) {
8
- const raw = process.env[name];
9
- return raw === undefined ? fallback : raw;
10
- }
11
-
12
- // plugins/sp/scripts/record-feature-sync.ts
13
- function resolveSpurBin(argv) {
14
- const flag = argv.indexOf("--spur-bin");
15
- const value = flag !== -1 ? argv[flag + 1] : undefined;
16
- return value ?? getEnvVar("spurBin") ?? "spur";
17
- }
18
- function main() {
19
- const argv = process.argv.slice(2);
20
- const spurBin = resolveSpurBin(argv);
21
- const wbs = getEnvVar("wbs") ?? "";
22
- if (wbs.length === 0) {
23
- console.error("record-feature-sync: env `wbs` is required");
24
- process.exit(0);
25
- }
26
- const reportPath = `.spur/run/${wbs}-report.txt`;
27
- const report = (line) => {
28
- appendFileSync(reportPath, `${line}
29
- `);
30
- };
31
- const show = spawnSync(spurBin, ["task", "show", wbs, "--json"], {
32
- encoding: "utf8",
33
- stdio: ["ignore", "pipe", "ignore"]
34
- });
35
- let featureId = "";
36
- try {
37
- const parsed = JSON.parse(show.stdout ?? "");
38
- featureId = String(parsed.feature_id ?? parsed.frontmatter?.feature_id ?? "");
39
- } catch {
40
- featureId = "";
41
- }
42
- if (featureId.length === 0) {
43
- report(`Orphan task ${wbs} — no feature_id linked — proposal: consider linking to a parent feature.`);
44
- return;
45
- }
46
- if (existsSync("plugins/sp/scripts/feature-sync-bounded.ts")) {
47
- spawnSync("bun", ["plugins/sp/scripts/feature-sync-bounded.ts", featureId, "--spur-bin", spurBin, "--json"], {
48
- stdio: "inherit"
49
- });
50
- return;
51
- }
52
- const staged = spawnSync("superskill", ["script", "path", "sp", "feature-sync-bounded.mjs"], {
53
- encoding: "utf8",
54
- stdio: ["ignore", "pipe", "ignore"]
55
- });
56
- const stagedPath = (staged.stdout ?? "").trim();
57
- if (staged.status === 0 && stagedPath.length > 0 && existsSync(stagedPath)) {
58
- spawnSync("node", [stagedPath, featureId, "--spur-bin", spurBin, "--json"], { stdio: "inherit" });
59
- return;
60
- }
61
- spawnSync(spurBin, ["feature", "sync", featureId, "--json"], { stdio: "inherit" });
62
- }
63
- main();
@@ -1,84 +0,0 @@
1
- /**
2
- * record-feature-sync — record-step post-record feature sync owner (tasks 0328 / 0411 / 0931).
3
- *
4
- * Extracted from the record onEnter shell (0931 R5): ADR-115 caps one workflow shell at 10
5
- * logical commands and 800 chars, and the deferFeatureSync guard plus the sync chain can no
6
- * longer share it. The pipeline shell resolves this script (in-repo first, superskill-staged
7
- * .mjs fallback), notes a deferral, and delegates the rest here:
8
- *
9
- * - A task with no feature_id gets the orphan proposal note (0328).
10
- * - The sync routes through feature-sync-bounded.ts (0411 retry suppression), then the
11
- * superskill-staged module, then bare `spur feature sync` (0328).
12
- * - Best-effort by contract: a sync failure never fails the record step.
13
- *
14
- * Node-builtin imports only (ADR-065): this file is transpiled to a node-runnable .mjs twin
15
- * by `superskill script convert`. Env: wbs (required), spurBin (default "spur"); the
16
- * `--spur-bin <bin>` argument wins over the env var.
17
- */
18
-
19
- import { spawnSync } from 'node:child_process';
20
- import { appendFileSync, existsSync } from 'node:fs';
21
- import { getEnvVar } from '../lib/env';
22
-
23
- function resolveSpurBin(argv: string[]): string {
24
- const flag = argv.indexOf('--spur-bin');
25
- const value = flag !== -1 ? argv[flag + 1] : undefined;
26
- return value ?? getEnvVar('spurBin') ?? 'spur';
27
- }
28
-
29
- function main(): void {
30
- const argv = process.argv.slice(2);
31
- const spurBin = resolveSpurBin(argv);
32
- const wbs = getEnvVar('wbs') ?? '';
33
- if (wbs.length === 0) {
34
- console.error('record-feature-sync: env `wbs` is required');
35
- process.exit(0); // best-effort contract — never fail the record step
36
- }
37
-
38
- const reportPath = `.spur/run/${wbs}-report.txt`;
39
- const report = (line: string): void => {
40
- appendFileSync(reportPath, `${line}\n`);
41
- };
42
-
43
- const show = spawnSync(spurBin, ['task', 'show', wbs, '--json'], {
44
- encoding: 'utf8',
45
- stdio: ['ignore', 'pipe', 'ignore'],
46
- });
47
- let featureId = '';
48
- try {
49
- const parsed = JSON.parse(show.stdout ?? '') as {
50
- feature_id?: string;
51
- frontmatter?: { feature_id?: string };
52
- };
53
- featureId = String(parsed.feature_id ?? parsed.frontmatter?.feature_id ?? '');
54
- } catch {
55
- featureId = '';
56
- }
57
-
58
- if (featureId.length === 0) {
59
- report(`Orphan task ${wbs} — no feature_id linked — proposal: consider linking to a parent feature.`);
60
- return;
61
- }
62
-
63
- if (existsSync('plugins/sp/scripts/feature-sync-bounded.ts')) {
64
- spawnSync('bun', ['plugins/sp/scripts/feature-sync-bounded.ts', featureId, '--spur-bin', spurBin, '--json'], {
65
- stdio: 'inherit',
66
- });
67
- return;
68
- }
69
-
70
- const staged = spawnSync('superskill', ['script', 'path', 'sp', 'feature-sync-bounded.mjs'], {
71
- encoding: 'utf8',
72
- stdio: ['ignore', 'pipe', 'ignore'],
73
- });
74
- const stagedPath = (staged.stdout ?? '').trim();
75
- if (staged.status === 0 && stagedPath.length > 0 && existsSync(stagedPath)) {
76
- spawnSync('node', [stagedPath, featureId, '--spur-bin', spurBin, '--json'], { stdio: 'inherit' });
77
- return;
78
- }
79
-
80
- spawnSync(spurBin, ['feature', 'sync', featureId, '--json'], { stdio: 'inherit' });
81
- // Falls through with exit 0: a sync failure never fails the record step (0411).
82
- }
83
-
84
- main();