@gobing-ai/spur 0.3.53 → 0.3.55
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/corpus-baseline.json +2783 -2783
- package/config/pipeline-budgets.json +42 -0
- package/config/plugin-scripts.json +73 -0
- package/config/workflow-composition-baseline.json +561 -0
- package/config/workflows/docs-pipeline.yaml +29 -13
- package/config/workflows/feature-dev.yaml +54 -2
- package/config/workflows/idea-pipeline.yaml +15 -0
- package/config/workflows/task-pipeline.yaml +77 -65
- package/config/workflows/wrapup-pipeline.yaml +40 -45
- package/package.json +9 -9
- package/plugins/README.md +1 -1
- package/plugins/sp/README.md +3 -3
- package/plugins/sp/agents/super-planner.md +1 -1
- package/plugins/sp/commands/dev-daily.md +1 -1
- package/plugins/sp/commands/dev-history-load.md +1 -1
- package/plugins/sp/commands/dev-idea.md +10 -2
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +160 -0
- package/plugins/sp/scripts/batch-preflight.ts +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +479 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +75 -37
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +204 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -1
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +141 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +1 -1
- package/plugins/sp/scripts/feature-sync-bounded.mjs +290 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +9 -8
- package/plugins/sp/scripts/history-load.mjs +268 -0
- package/plugins/sp/scripts/pr-reviewing.mjs +685 -0
- package/plugins/sp/scripts/pr-reviewing.ts +1 -1
- package/plugins/sp/scripts/script-contract-check.ts +319 -0
- package/plugins/sp/skills/daily-summary/SKILL.md +3 -3
- package/plugins/sp/skills/dogfood-testing/SKILL.md +5 -5
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
- package/plugins/sp/skills/pr-reviewing/SKILL.md +3 -3
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +3 -4
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
- package/spur.js +4270 -1323
- package/web/_astro/{BoardApp.plVXYkKo.js → BoardApp.DCLSB3Zs.js} +63 -63
- package/web/_astro/BoardApp.Dx5gzAhb.js +1 -0
- package/web/_astro/{TaskDetail.5DIyVQ_N.js → TaskDetail.DN_RxS-2.js} +1 -1
- package/web/_astro/{arc.BnUgL7ho.js → arc.CldTHRg-.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B9YMJau-.js → architectureDiagram-3BPJPVTR.DQ4T9oeU.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Dw2qgvWJ.js → blockDiagram-GPEHLZMM.CGDaXH8J.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.jTbU0-Nk.js → c4Diagram-AAUBKEIU.Cqsc2iub.js} +1 -1
- package/web/_astro/channel.CrBJYpxo.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.Di46EtkU.js → chunk-2J33WTMH.CQSKsRmq.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DweHBwqQ.js → chunk-4BX2VUAB.Pv1HCtp8.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.rRjTKaa7.js → chunk-55IACEB6.-9MseOIk.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BkEQIkSV.js → chunk-727SXJPM.M3OI1DF7.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.Cfe9IvAd.js → chunk-AQP2D5EJ.NfHka5Ca.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.fI6tLbeN.js → chunk-FMBD7UC4.B2suYe3s.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BIjgpq0O.js → chunk-ND2GUHAM.BfJ7aucP.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.ZwLnVxyz.js → chunk-QZHKN3VN._582hZVc.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BJAGYidE.js → classDiagram-4FO5ZUOK._tttU_jk.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BJAGYidE.js → classDiagram-v2-Q7XG4LA2._tttU_jk.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.B5y3nz26.js → cose-bilkent-S5V4N54A.C4rVATLQ.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.DHmw0ZBC.js → dagre-BM42HDAG.8BLRi7f9.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.10gapJfY.js → diagram-2AECGRRQ.D6lwUZos.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Gh33_drM.js → diagram-5GNKFQAL.Cl_rMiVT.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.8ROWlfcX.js → diagram-KO2AKTUF.DhMExHLp.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BsP3tKmg.js → diagram-LMA3HP47.3OAxzH_o.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DM2WFDhU.js → diagram-OG6HWLK6.Uh9RLucG.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.royaC_lH.js → erDiagram-TEJ5UH35.izzElG0c.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.C9GEvIie.js → flowDiagram-I6XJVG4X.BlGwp5qM.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.CXO4T2U0.js → ganttDiagram-6RSMTGT7.je7Vf8dN.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Q02KzYH7.js → gitGraphDiagram-PVQCEYII.DNk0Ycop.js} +1 -1
- package/web/_astro/{index.DAxu50UF.css → index.V6Q7nhed.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DNfSDKJt.js → infoDiagram-5YYISTIA.K2HhGyWH.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BFttvtgJ.js → ishikawaDiagram-YF4QCWOH.BbbOW4IN.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.Cfm-2SbG.js → journeyDiagram-JHISSGLW.B1UaZKqN.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BWGNW6fV.js → kanban-definition-UN3LZRKU.9tsw5QFd.js} +1 -1
- package/web/_astro/{linear.BprdUk2Y.js → linear.I9dvtu-j.js} +1 -1
- package/web/_astro/{mermaid.core.B2zRNxtX.js → mermaid.core.5bKJsMgf.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.g9jij8sw.js → mindmap-definition-RKZ34NQL.BC5MqSn6.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CbzR4n5b.js → pieDiagram-4H26LBE5.B8GIMyxD.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DxV3viWt.js → quadrantDiagram-W4KKPZXB.K_aZSM_u.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DvAb90JS.js → requirementDiagram-4Y6WPE33.B_V5kzfT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.CIFz5t4D.js → sankeyDiagram-5OEKKPKP.DyV2quLS.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DzlEQynd.js → sequenceDiagram-3UESZ5HK.BlKvPnBv.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Dph2V-ts.js → stateDiagram-AJRCARHV.C1lYUMdS.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.wJhfrLAy.js → stateDiagram-v2-BHNVJYJU.DJWjr3QV.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BrSiyubX.js → timeline-definition-PNZ67QCA.CYYiXVlv.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.COobPrUX.js → vennDiagram-CIIHVFJN.zYjQKa_S.js} +1 -1
- package/web/_astro/{wardley-L42UT6IY.CrjeTx8A.js → wardley-L42UT6IY.BGrWTY3D.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B8_fTQ8E.js → wardleyDiagram-YWT4CUSO.CkXxtcI1.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.BPpokgVv.js → xychartDiagram-2RQKCTM6.BhKH3KG3.js} +1 -1
- package/web/index.html +2 -2
- package/config/workflows/planning-pipeline.yaml +0 -249
- package/config/workflows/task-pipeline2.yaml +0 -778
- package/web/_astro/BoardApp.pdtYu-yW.js +0 -1
- package/web/_astro/channel.DEqvaz-I.js +0 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* script-contract-check — two-sided gate over plugins/sp script entrypoint contracts
|
|
4
|
+
* (task 0600, feature I, ADR-065).
|
|
5
|
+
*
|
|
6
|
+
* Enforces superskill's standard plugin script contract across plugins/sp:
|
|
7
|
+
* 1. A 'standard' entry must have a valid .mjs twin not older than its .ts source.
|
|
8
|
+
* 2. A .mjs twin on disk must be registered under a 'standard' entry (never 'repo-only' or unlisted).
|
|
9
|
+
* 3. Every script file under plugins/sp/scripts/ must have a manifest entry (two-sided).
|
|
10
|
+
* 4. No shipped surface (commands/, skills/, agents/, README.md) may reference 'bun plugins/sp/scripts/'.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* bun plugins/sp/scripts/script-contract-check.ts
|
|
14
|
+
* [--manifest <path>] default: config/plugin-scripts.json
|
|
15
|
+
* [--scripts-dir <path>] default: plugins/sp/scripts
|
|
16
|
+
* [--plugin-dir <path>] default: plugins/sp
|
|
17
|
+
*
|
|
18
|
+
* Exit code: 0 on success, 1 on any violation.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
22
|
+
import { join, resolve } from 'node:path';
|
|
23
|
+
|
|
24
|
+
export type ScriptContractType = 'standard' | 'repo-only';
|
|
25
|
+
|
|
26
|
+
export interface ScriptManifestEntry {
|
|
27
|
+
rel: string;
|
|
28
|
+
contract: ScriptContractType;
|
|
29
|
+
twin?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ScriptManifest {
|
|
33
|
+
schema_version?: number;
|
|
34
|
+
description?: string;
|
|
35
|
+
entries: ScriptManifestEntry[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Violation {
|
|
39
|
+
kind:
|
|
40
|
+
| 'missing_twin'
|
|
41
|
+
| 'stale_twin'
|
|
42
|
+
| 'unexpected_twin'
|
|
43
|
+
| 'unregistered_script'
|
|
44
|
+
| 'forbidden_invocation'
|
|
45
|
+
| 'incomplete';
|
|
46
|
+
target: string;
|
|
47
|
+
message: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseArgs(argv: string[]): {
|
|
51
|
+
manifest: string;
|
|
52
|
+
scriptsDir: string;
|
|
53
|
+
pluginDir: string;
|
|
54
|
+
cwd: string;
|
|
55
|
+
} {
|
|
56
|
+
let manifest = 'config/plugin-scripts.json';
|
|
57
|
+
let scriptsDir = 'plugins/sp/scripts';
|
|
58
|
+
let pluginDir = 'plugins/sp';
|
|
59
|
+
for (let i = 0; i < argv.length; i++) {
|
|
60
|
+
if (argv[i] === '--manifest') manifest = argv[++i] ?? manifest;
|
|
61
|
+
else if (argv[i] === '--scripts-dir') scriptsDir = argv[++i] ?? scriptsDir;
|
|
62
|
+
else if (argv[i] === '--plugin-dir') pluginDir = argv[++i] ?? pluginDir;
|
|
63
|
+
}
|
|
64
|
+
return { manifest, scriptsDir, pluginDir, cwd: process.cwd() };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function loadManifest(path: string): { manifest: ScriptManifest | null; error: string | null } {
|
|
68
|
+
if (!existsSync(path)) return { manifest: null, error: `manifest not found at ${path}` };
|
|
69
|
+
try {
|
|
70
|
+
const raw = readFileSync(path, 'utf8');
|
|
71
|
+
const parsed = JSON.parse(raw) as ScriptManifest;
|
|
72
|
+
if (!Array.isArray(parsed.entries)) {
|
|
73
|
+
return { manifest: null, error: `${path}: missing "entries" array` };
|
|
74
|
+
}
|
|
75
|
+
return { manifest: parsed, error: null };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return { manifest: null, error: `malformed JSON at ${path}: ${String(err)}` };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function listDiskScripts(scriptsDir: string): { tsFiles: string[]; mjsFiles: string[] } {
|
|
82
|
+
const tsFiles: string[] = [];
|
|
83
|
+
const mjsFiles: string[] = [];
|
|
84
|
+
|
|
85
|
+
function walk(dir: string, base: string): void {
|
|
86
|
+
let entries: string[];
|
|
87
|
+
try {
|
|
88
|
+
entries = readdirSync(dir);
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
const fullPath = join(dir, entry);
|
|
94
|
+
let st: ReturnType<typeof statSync>;
|
|
95
|
+
try {
|
|
96
|
+
st = statSync(fullPath);
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (st.isDirectory()) {
|
|
101
|
+
walk(fullPath, base ? `${base}/${entry}` : entry);
|
|
102
|
+
} else if (st.isFile()) {
|
|
103
|
+
const rel = base ? `${base}/${entry}` : entry;
|
|
104
|
+
if (entry.endsWith('.ts')) {
|
|
105
|
+
tsFiles.push(rel);
|
|
106
|
+
} else if (entry.endsWith('.mjs')) {
|
|
107
|
+
mjsFiles.push(rel);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
walk(scriptsDir, '');
|
|
114
|
+
return { tsFiles: tsFiles.sort(), mjsFiles: mjsFiles.sort() };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function scanShippedSurfaces(pluginDir: string): Array<{ file: string; line: number; content: string }> {
|
|
118
|
+
const matches: Array<{ file: string; line: number; content: string }> = [];
|
|
119
|
+
const forbiddenPattern = 'bun plugins/sp/scripts/';
|
|
120
|
+
|
|
121
|
+
const searchDirs = ['commands', 'skills', 'agents'];
|
|
122
|
+
const singleFiles = ['README.md'];
|
|
123
|
+
|
|
124
|
+
function scanFile(filePath: string): void {
|
|
125
|
+
if (!existsSync(filePath)) return;
|
|
126
|
+
let text: string;
|
|
127
|
+
try {
|
|
128
|
+
text = readFileSync(filePath, 'utf8');
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const lines = text.split('\n');
|
|
133
|
+
for (let i = 0; i < lines.length; i++) {
|
|
134
|
+
const line = lines[i] ?? '';
|
|
135
|
+
if (line.includes(forbiddenPattern)) {
|
|
136
|
+
matches.push({
|
|
137
|
+
file: filePath,
|
|
138
|
+
line: i + 1,
|
|
139
|
+
content: line.trim(),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function walk(dir: string): void {
|
|
146
|
+
let entries: string[];
|
|
147
|
+
try {
|
|
148
|
+
entries = readdirSync(dir);
|
|
149
|
+
} catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
const fullPath = join(dir, entry);
|
|
154
|
+
let st: ReturnType<typeof statSync>;
|
|
155
|
+
try {
|
|
156
|
+
st = statSync(fullPath);
|
|
157
|
+
} catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (st.isDirectory()) {
|
|
161
|
+
walk(fullPath);
|
|
162
|
+
} else if (
|
|
163
|
+
st.isFile() &&
|
|
164
|
+
(entry.endsWith('.md') || entry.endsWith('.json') || entry.endsWith('.yaml') || entry.endsWith('.yml'))
|
|
165
|
+
) {
|
|
166
|
+
scanFile(fullPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const d of searchDirs) {
|
|
172
|
+
walk(join(pluginDir, d));
|
|
173
|
+
}
|
|
174
|
+
for (const f of singleFiles) {
|
|
175
|
+
scanFile(join(pluginDir, f));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return matches;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function validateContract(manifest: ScriptManifest, scriptsDir: string, pluginDir: string): Violation[] {
|
|
182
|
+
const violations: Violation[] = [];
|
|
183
|
+
const { tsFiles, mjsFiles } = listDiskScripts(scriptsDir);
|
|
184
|
+
|
|
185
|
+
const manifestMap = new Map<string, ScriptManifestEntry>();
|
|
186
|
+
for (const entry of manifest.entries) {
|
|
187
|
+
if (!entry.rel || !entry.contract) {
|
|
188
|
+
violations.push({
|
|
189
|
+
kind: 'incomplete',
|
|
190
|
+
target: entry.rel ?? '<missing>',
|
|
191
|
+
message: `manifest entry missing required fields (rel, contract)`,
|
|
192
|
+
});
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
manifestMap.set(entry.rel, entry);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Rule 1: standard entries must have valid .mjs twins not older than the .ts source.
|
|
199
|
+
// mtime is only meaningful when a build genuinely ran: a `git worktree add` stamps
|
|
200
|
+
// every checked-out file within the same millisecond, and the lexicographic write
|
|
201
|
+
// order (`tool.mjs` < `tool.ts`) makes the twin spuriously "older" by <1ms. Tolerate
|
|
202
|
+
// sub-second deltas so a fresh checkout is never flagged; real build staleness is
|
|
203
|
+
// seconds-to-minutes, far beyond this window (task 0606 R1 eval-worktree blocker).
|
|
204
|
+
const STALE_TWIN_TOLERANCE_MS = 1000;
|
|
205
|
+
for (const entry of manifest.entries) {
|
|
206
|
+
if (entry.rel && entry.contract === 'standard') {
|
|
207
|
+
const expectedTwinRel = entry.twin ?? entry.rel.replace(/\.ts$/, '.mjs');
|
|
208
|
+
const twinPath = join(scriptsDir, expectedTwinRel);
|
|
209
|
+
const tsPath = join(scriptsDir, entry.rel);
|
|
210
|
+
|
|
211
|
+
if (!existsSync(twinPath)) {
|
|
212
|
+
violations.push({
|
|
213
|
+
kind: 'missing_twin',
|
|
214
|
+
target: entry.rel,
|
|
215
|
+
message: `standard script ${entry.rel} is missing its .mjs twin (${expectedTwinRel})`,
|
|
216
|
+
});
|
|
217
|
+
} else if (existsSync(tsPath)) {
|
|
218
|
+
const tsStat = statSync(tsPath);
|
|
219
|
+
const twinStat = statSync(twinPath);
|
|
220
|
+
if (twinStat.mtimeMs < tsStat.mtimeMs - STALE_TWIN_TOLERANCE_MS) {
|
|
221
|
+
violations.push({
|
|
222
|
+
kind: 'stale_twin',
|
|
223
|
+
target: entry.rel,
|
|
224
|
+
message: `standard script .mjs twin ${expectedTwinRel} is older than source ${entry.rel}`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Rule 2: committed .mjs files must belong to a 'standard' entry
|
|
232
|
+
for (const mjsRel of mjsFiles) {
|
|
233
|
+
const correspondingTsRel = mjsRel.replace(/\.mjs$/, '.ts');
|
|
234
|
+
const entry = manifestMap.get(correspondingTsRel);
|
|
235
|
+
if (!entry) {
|
|
236
|
+
violations.push({
|
|
237
|
+
kind: 'unexpected_twin',
|
|
238
|
+
target: mjsRel,
|
|
239
|
+
message: `.mjs twin ${mjsRel} exists on disk but has no manifest entry for ${correspondingTsRel}`,
|
|
240
|
+
});
|
|
241
|
+
} else if (entry.contract === 'repo-only') {
|
|
242
|
+
violations.push({
|
|
243
|
+
kind: 'unexpected_twin',
|
|
244
|
+
target: mjsRel,
|
|
245
|
+
message: `repo-only script ${correspondingTsRel} must not have a .mjs twin (${mjsRel})`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Rule 3: all .ts files on disk must have a manifest entry
|
|
251
|
+
const diskTsSet = new Set(tsFiles);
|
|
252
|
+
for (const tsRel of tsFiles) {
|
|
253
|
+
if (!manifestMap.has(tsRel)) {
|
|
254
|
+
violations.push({
|
|
255
|
+
kind: 'unregistered_script',
|
|
256
|
+
target: tsRel,
|
|
257
|
+
message: `script ${tsRel} exists in scripts dir but is not registered in manifest`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Also verify manifest entries exist on disk
|
|
263
|
+
for (const [rel] of manifestMap) {
|
|
264
|
+
if (!diskTsSet.has(rel)) {
|
|
265
|
+
violations.push({
|
|
266
|
+
kind: 'unregistered_script',
|
|
267
|
+
target: rel,
|
|
268
|
+
message: `manifest entry ${rel} does not exist on disk in ${scriptsDir}`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Rule 4: scan shipped surfaces for forbidden invocation
|
|
274
|
+
const forbiddenHits = scanShippedSurfaces(pluginDir);
|
|
275
|
+
for (const hit of forbiddenHits) {
|
|
276
|
+
violations.push({
|
|
277
|
+
kind: 'forbidden_invocation',
|
|
278
|
+
target: `${hit.file}:${hit.line}`,
|
|
279
|
+
message: `shipped surface ${hit.file}:${hit.line} contains forbidden invocation 'bun plugins/sp/scripts/': ${hit.content}`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return violations;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function run(argv: string[] = process.argv.slice(2)): number {
|
|
287
|
+
const { manifest, scriptsDir, pluginDir, cwd } = parseArgs(argv);
|
|
288
|
+
const manifestPath = resolve(cwd, manifest);
|
|
289
|
+
const absScriptsDir = resolve(cwd, scriptsDir);
|
|
290
|
+
const absPluginDir = resolve(cwd, pluginDir);
|
|
291
|
+
|
|
292
|
+
const { manifest: parsed, error } = loadManifest(manifestPath);
|
|
293
|
+
if (error || !parsed) {
|
|
294
|
+
console.error(`script-contract-check: FAIL - ${error}`);
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const violations = validateContract(parsed, absScriptsDir, absPluginDir);
|
|
299
|
+
const ok = violations.length === 0;
|
|
300
|
+
|
|
301
|
+
const standardCount = parsed.entries.filter((e) => e.contract === 'standard').length;
|
|
302
|
+
const repoOnlyCount = parsed.entries.filter((e) => e.contract === 'repo-only').length;
|
|
303
|
+
|
|
304
|
+
console.log(
|
|
305
|
+
`script-contract-check: ${parsed.entries.length} script(s) baselined (${standardCount} standard, ${repoOnlyCount} repo-only), ` +
|
|
306
|
+
`${violations.length} violation(s) — ${ok ? 'PASS' : 'FAIL'}`,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
if (ok) return 0;
|
|
310
|
+
|
|
311
|
+
for (const v of violations) {
|
|
312
|
+
console.error(`script-contract-check: FAIL (${v.kind}) - ${v.message}`);
|
|
313
|
+
}
|
|
314
|
+
return 1;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (import.meta.main) {
|
|
318
|
+
process.exit(run());
|
|
319
|
+
}
|
|
@@ -52,13 +52,13 @@ The daily-summary skill:
|
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
# Generate summary for today
|
|
55
|
-
|
|
55
|
+
node "$(superskill script path sp daily-summary/daily-summary.mjs)"
|
|
56
56
|
|
|
57
57
|
# Generate summary for a specific date
|
|
58
|
-
|
|
58
|
+
node "$(superskill script path sp daily-summary/daily-summary.mjs)" --date 2026-04-17
|
|
59
59
|
|
|
60
60
|
# Show summary without writing file
|
|
61
|
-
|
|
61
|
+
node "$(superskill script path sp daily-summary/daily-summary.mjs)" --dry-run
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
## Workflows
|
|
@@ -96,9 +96,9 @@ The command forwards these via `$ARGUMENTS`:
|
|
|
96
96
|
in-agent):
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
|
|
99
|
+
node "$(superskill script path sp dogfood-testing/detect-pipeline-driving.mjs)" \
|
|
100
100
|
--testee "<raw testee string>" \
|
|
101
|
-
[--max-retry-present]
|
|
101
|
+
[--max-retry-present]
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
- Exit **2** → print the stdout refuse line and **stop** (do not plan). The CLI refuses on either
|
|
@@ -118,7 +118,7 @@ The command forwards these via `$ARGUMENTS`:
|
|
|
118
118
|
re-run the gate with the derived step labels:
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
|
|
121
|
+
node "$(superskill script path sp dogfood-testing/detect-pipeline-driving.mjs)" \
|
|
122
122
|
--testee "<raw testee string>" \
|
|
123
123
|
--max-retry-present \
|
|
124
124
|
--steps "step1 label||step2 label||..."
|
|
@@ -226,7 +226,7 @@ contract violation**.
|
|
|
226
226
|
**before** claiming `status: complete`:
|
|
227
227
|
|
|
228
228
|
```bash
|
|
229
|
-
|
|
229
|
+
node "$(superskill script path sp dogfood-testing/validate-report.mjs)" --file <report-path>
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
Exit **0** → proceed. Exit **2** → set `status: aborted`, list every error code under §5
|
|
@@ -328,7 +328,7 @@ Do **not** use this skill for:
|
|
|
328
328
|
Pipeline-driving detection is **word-boundary**, not leading-space substring. The live gate is:
|
|
329
329
|
|
|
330
330
|
```bash
|
|
331
|
-
|
|
331
|
+
node "$(superskill script path sp dogfood-testing/detect-pipeline-driving.mjs)" --testee "<testee>" [--max-retry-present] [--steps "…"] [--json]
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
| Token shape | Examples | Matches | Rejects |
|
|
@@ -278,7 +278,7 @@ Before the skill may stop (success, partial, fail, observe-only end, or abort),
|
|
|
278
278
|
and mirror the footer block at the **end of the report file**. A report whose body lacks the
|
|
279
279
|
footer cannot set `status: complete`.
|
|
280
280
|
8. **Self-validate (task 0278 R6):** run
|
|
281
|
-
`
|
|
281
|
+
`node "$(superskill script path sp dogfood-testing/validate-report.mjs)" --file <report-path>` before
|
|
282
282
|
claiming `status: complete`. Exit 2 → `status: aborted` + list error codes under
|
|
283
283
|
`#### Unresolved` (never force complete on a non-@1.2 shape).
|
|
284
284
|
9. **Refusal rule (@1.2):** when any check above fails, set `status: aborted` and list every
|
|
@@ -129,11 +129,11 @@ staged entrypoint below, supports `--json`, and must be parsed. Stop at the firs
|
|
|
129
129
|
report its artifact. The canonical installed invocation is:
|
|
130
130
|
|
|
131
131
|
```sh
|
|
132
|
-
|
|
132
|
+
node "$(superskill script path sp pr-reviewing.mjs)" <subcommand> [flags]
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
Installed targets resolve the staged
|
|
136
|
-
|
|
135
|
+
Installed targets resolve the staged `.mjs` twin and execute it with Node, matching the standard
|
|
136
|
+
superskill script contract.
|
|
137
137
|
|
|
138
138
|
1. **Preflight** — `<script> preflight --base "$base" --json`. Hard-fails on a
|
|
139
139
|
detached HEAD, missing `gh` auth, no GitHub remote, a dirty tree, or the current branch
|
|
@@ -219,7 +219,8 @@ for "what's actually in file Y" or for resources that sit outside the step seque
|
|
|
219
219
|
- `plugins/sp/references/roles.md` — the Layer-1 role→tier table (`scribe` / `coder` / `reviewer` /
|
|
220
220
|
`planner`, one per tier); the role vocabulary this spine's operations route under.
|
|
221
221
|
- `.spur/workflows/task-pipeline.yaml` — the execution pipeline definition.
|
|
222
|
-
- `.spur/workflows/
|
|
222
|
+
- `.spur/workflows/idea-pipeline.yaml` — the front-half state machine (absorbed the retired
|
|
223
|
+
planning-pipeline in D5-K; `/sp:dev-plan` routes here, ADR-072).
|
|
223
224
|
- `.spur/templates/bdd/gherkin.md` — the BDD scenario template.
|
|
224
225
|
|
|
225
226
|
## Platform Notes
|
|
@@ -538,8 +538,7 @@ invariants that keep the pipeline set coherent as new ones are added.
|
|
|
538
538
|
|
|
539
539
|
| Pipeline | Lifecycle phase | Entry point | Terminal states |
|
|
540
540
|
| --- | --- | --- | --- |
|
|
541
|
-
| `idea-pipeline.yaml` | Ideation (vague idea → feature + AC + task batch) | `/sp:dev-idea` | `handoff`, `cancelled` |
|
|
542
|
-
| `planning-pipeline.yaml` | Design (known slug/task → design handoff) | `/sp:dev-plan` | `handoff`, `cancelled` |
|
|
541
|
+
| `idea-pipeline.yaml` | Ideation + planning (vague idea or known slug → feature + AC + task batch) | `/sp:dev-idea`, `/sp:dev-plan` | `handoff`, `cancelled` |
|
|
543
542
|
| `task-pipeline.yaml` | Execution (one task → done) | `/sp:dev-run` | `done`, `failed` |
|
|
544
543
|
| `wrapup-pipeline.yaml` | Wrap-up (completed tasks → learning + metrics + doc-sync) | `/sp:dev-wrap`, `/sp:dev-wrapall` | `done`, `skipped` |
|
|
545
544
|
| `feature-dev.yaml` | Umbrella (brainstorm → plan → execute → feature-verify) | `/sp:dev-runall --feature <id>` (or `--tasks feature:<id>`) | `done`, `failed` |
|
|
@@ -644,8 +643,8 @@ next_action: "run verification"
|
|
|
644
643
|
**Write checkpoints after:**
|
|
645
644
|
|
|
646
645
|
- Every HITL gate decision (approved/rejected/deferred).
|
|
647
|
-
- Every phase transition in `
|
|
648
|
-
|
|
646
|
+
- Every phase transition in `task-pipeline`, `feature-dev`, `idea-pipeline`, and
|
|
647
|
+
`wrapup-pipeline`.
|
|
649
648
|
- Every terminal state (`done`, `failed`, `cancelled`, `skipped`).
|
|
650
649
|
|
|
651
650
|
**Read checkpoints when:**
|
|
@@ -82,7 +82,7 @@ each would be scope creep for one-liner procedures.
|
|
|
82
82
|
| 13a | parallel | `dev-parallel` | `Skill()` | `sp:parallel-execution` | `--tasks <selector> [--feature <id>] [--mode <fan-out\|review-panel\|investigation>] [--agent <inline\|auto\|name>] [--json]` |
|
|
83
83
|
| 14 | wrap | `dev-wrap` | `Skill()` | `spur workflow run` (wrapup-pipeline) | `<wbs> [--agent <inline\|auto\|name>] [--auto] [--merge] [--dry-run]` |
|
|
84
84
|
| 15 | wrapall | `dev-wrapall` | `Skill()` | `spur workflow run` (wrapup-pipeline) | `[--since <iso>] [--feature <id>] [--status <s>] [--agent <inline\|auto\|name>] [--auto] [--merge] [--dry-run]` |
|
|
85
|
-
| 16 | idea | `dev-idea` | `Skill()` | `spur workflow run` (idea-pipeline) | `"<idea>" [--auto] [--skip-design] [--approve-taste]` |
|
|
85
|
+
| 16 | idea | `dev-idea` | `Skill()` | `spur workflow run` (idea-pipeline) | `"<idea>" [--auto] [--skip-design] [--approve-taste] [--agent <inline\|auto\|name>]` |
|
|
86
86
|
|
|
87
87
|
## Skill-backed operations
|
|
88
88
|
|
|
@@ -179,7 +179,7 @@ The ordered execution plan: a WBS-ascending-topological list of tasks to run, pl
|
|
|
179
179
|
helper (preferred) or `sp:next-router` dry-run:
|
|
180
180
|
|
|
181
181
|
```bash
|
|
182
|
-
|
|
182
|
+
node "$(superskill script path sp batch-preflight.mjs)" \
|
|
183
183
|
--wbs <wbs> --status <status> \
|
|
184
184
|
--deps <comma-deps> --dep-status <wbs:status,...> --json
|
|
185
185
|
```
|
|
@@ -278,7 +278,7 @@ After a non-PASS terminal state (or when the task status is stuck at `wip`/`test
|
|
|
278
278
|
verdict), consult **one** recovery hop:
|
|
279
279
|
|
|
280
280
|
```bash
|
|
281
|
-
|
|
281
|
+
node "$(superskill script path sp batch-preflight.mjs)" --wbs <wbs> --status <status> --recovery
|
|
282
282
|
# → e.g. /sp:dev-verify 0042 --auto --next
|
|
283
283
|
```
|
|
284
284
|
|
|
@@ -306,7 +306,7 @@ Both `task-pipeline.yaml` (`record` step) and `wrapup-pipeline.yaml` (`feature-t
|
|
|
306
306
|
invoke the bounded wrapper instead of raw `feature sync`:
|
|
307
307
|
|
|
308
308
|
```bash
|
|
309
|
-
|
|
309
|
+
node "$(superskill script path sp feature-sync-bounded.mjs)" <feature-id> --spur-bin "<spurBin>" --json
|
|
310
310
|
```
|
|
311
311
|
|
|
312
312
|
The wrapper:
|
|
@@ -36,8 +36,8 @@ SSOT: `.spur/workflows/task-lifecycle.yaml`.
|
|
|
36
36
|
|
|
37
37
|
## feature-check gate
|
|
38
38
|
|
|
39
|
-
Entered before `spur feature check <id> --strict` (idea-pipeline `feature-check` state
|
|
40
|
-
|
|
39
|
+
Entered before `spur feature check <id> --strict` (idea-pipeline `feature-check` state — the
|
|
40
|
+
AC-validation gate `/sp:dev-plan` also routes through since D5-K).
|
|
41
41
|
|
|
42
42
|
- [ ] Feature file has a non-placeholder `## Goal` and `## Scope` section.
|
|
43
43
|
- [ ] Acceptance criteria are R-numbered scenarios in Gherkin `Feature/Scenario` form (see `ac-style-guide.md`).
|
|
@@ -48,8 +48,8 @@ planning-pipeline AC validation).
|
|
|
48
48
|
|
|
49
49
|
## batch-create gate
|
|
50
50
|
|
|
51
|
-
Entered before `spur task batch-create --file <json>` (idea-pipeline `batch-create` state
|
|
52
|
-
|
|
51
|
+
Entered before `spur task batch-create --file <json>` (idea-pipeline `batch-create` state — the
|
|
52
|
+
decomposition gate `/sp:dev-plan` also routes through since D5-K).
|
|
53
53
|
|
|
54
54
|
- [ ] The batch JSON is a bare array (not an object with a `tasks` key).
|
|
55
55
|
- [ ] Each entry validates locally against `apps/cli/schemas/task-batch.schema.json` (`additionalProperties: false` — no unknown keys).
|