@gobing-ai/spur 0.3.95 → 0.3.97
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/.claude-plugin/marketplace.json +1 -1
- package/config/plugin-scripts.json +0 -54
- package/config/rules/boundary/sp-script-placement.yaml +19 -0
- package/config/rules/strict/runtime-boundaries.yaml +6 -0
- package/config/rules/structure/test-location.yaml +2 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
- package/config/rules/typescript/output-boundaries.yaml +15 -2
- package/config/script-placement-baseline.json +51 -0
- package/config/templates/AGENTS.md +3 -3
- package/config/workflows/feature-verification.yaml +1 -1
- package/config/workflows/history-anatomy.yaml +23 -11
- package/config/workflows/idea-pipeline.yaml +80 -85
- package/config/workflows/pr-review.yaml +18 -9
- package/config/workflows/task-pipeline.yaml +42 -95
- package/config/workflows/wrapup-pipeline.yaml +91 -38
- package/package.json +2 -2
- package/plugins/sp/README.md +14 -10
- package/plugins/sp/agents/super-reviewer.md +43 -18
- package/plugins/sp/commands/dev-fixgha.md +83 -0
- package/plugins/sp/commands/dev-gitmsg.md +8 -6
- package/plugins/sp/commands/dev-gtd.md +2 -2
- package/plugins/sp/commands/dev-idea.md +22 -12
- package/plugins/sp/commands/dev-job-dump.md +29 -0
- package/plugins/sp/commands/dev-job-resume.md +29 -0
- package/plugins/sp/commands/dev-plan.md +8 -9
- package/plugins/sp/commands/dev-review.md +22 -13
- package/plugins/sp/commands/dev-verifyall.md +1 -1
- package/plugins/sp/commands/spur-init.md +2 -2
- package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
- package/plugins/sp/lib/inline-run.generated.mjs +39 -13
- package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +445 -0
- package/plugins/sp/lib/residual-scan.generated.d.mts +63 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +226 -0
- package/plugins/sp/lib/spur-bin.ts +36 -0
- package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
- package/plugins/sp/lib/step-profile.generated.mjs +174 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +3 -3
- package/plugins/sp/scripts/feature-verification-steps.mjs +14 -1
- package/plugins/sp/scripts/feature-verification-steps.ts +14 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
- package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
- package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
- package/plugins/sp/scripts/inline-run-setup.ts +104 -667
- package/plugins/sp/scripts/quality-gate.mjs +46 -24
- package/plugins/sp/scripts/quality-gate.ts +13 -659
- package/plugins/sp/scripts/residual-scan.mjs +179 -174
- package/plugins/sp/scripts/residual-scan.ts +125 -517
- package/plugins/sp/scripts/script-root.mjs +5 -1
- package/plugins/sp/scripts/script-root.ts +5 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
- package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +72 -33
- package/plugins/sp/scripts/wrapup-steps.ts +128 -37
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
- package/plugins/sp/skills/code-verification/SKILL.md +34 -9
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
- package/plugins/sp/skills/functional-review/SKILL.md +7 -4
- package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/source-driven-development/SKILL.md +11 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
- package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
- package/plugins/sp/skills/spur-cli/references/features.md +17 -6
- package/plugins/sp/skills/spur-cli/references/init.md +17 -16
- package/plugins/sp/skills/spur-cli/references/self.md +3 -2
- package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +9 -5
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
- package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
- package/plugins/sp/skills/spur-cli/references/workflows.md +17 -14
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -10
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +165 -75
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +7 -5
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +84 -39
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -12
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +40 -21
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +72 -15
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
- package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
- package/spur.js +4461 -2085
- package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.Bmv5WkJ9.js} +1 -1
- package/web/_astro/BoardApp.vveLOdDq.js +322 -0
- package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.IKc3uDYV.js} +1 -1
- package/web/_astro/{arc.CyjRvNMY.js → arc.C63Ufg35.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.CP8Hyldk.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.Bo_48MRA.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.Ch61sw-O.js} +1 -1
- package/web/_astro/channel.BKCmJpWM.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.CHpzS7xf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.DzMNNWIw.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.BqPrDw_Q.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ZzBHhNw-.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.1_O_LLkx.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.GS4UHg7_.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.BnqotN4g.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.Ghuh8zoJ.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.BW9vC3zg.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.BW9vC3zg.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.D6jNC0ti.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.BOGoCs4L.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.8iU5VcWi.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.CVt88HwH.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.C9HxUUMl.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.4ywkQN3t.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.C48A6gpz.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.DxFieQW4.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.CtjtQqYs.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.C5zAiahU.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.D_L6zsny.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.Ignnrk3V.js} +1 -1
- package/web/_astro/index.CbNz17Sx.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DLv8-P5u.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.p0OTpwFO.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.CIaBt-DD.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.DGySIntq.js} +1 -1
- package/web/_astro/{linear.zRsuuDTE.js → linear.BATV4RXu.js} +1 -1
- package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DzkwM3VX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.3piBRRzA.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DUNueLub.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.9bihVbrt.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.CkjsC-YT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.Dbq4vaoQ.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.BivrH99P.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.CqLQ1Vzz.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DvvoLD3b.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.B-KR78gv.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.Ck0Eieb5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRfdd_do.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.Ck_Jxk9C.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
- package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
- package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
- package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
- package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
- package/plugins/sp/scripts/record-feature-sync.ts +0 -84
- package/plugins/sp/scripts/script-contract-check.ts +0 -506
- package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
- package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
- package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
- package/plugins/sp/scripts/task-size-precheck.ts +0 -212
- package/plugins/sp/scripts/transition-shim-check.ts +0 -238
- package/plugins/sp/scripts/validate-commands.ts +0 -689
- package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
- package/web/_astro/BoardApp.BxJuwD7I.js +0 -191
- package/web/_astro/channel.CI6N_tCg.js +0 -1
- 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();
|