@gobing-ai/spur 0.3.94 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +42 -0
- package/board/index.d.ts +38 -0
- package/config/plugin-scripts.json +3 -52
- package/config/rules/boundary/sp-script-placement.yaml +19 -0
- package/config/rules/strict/runtime-boundaries.yaml +5 -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/workflows/feature-verification.yaml +7 -4
- package/config/workflows/history-anatomy.yaml +23 -11
- package/config/workflows/idea-pipeline.yaml +87 -86
- package/config/workflows/pr-review.yaml +18 -9
- package/config/workflows/task-pipeline.yaml +84 -109
- package/config/workflows/wrapup-pipeline.yaml +173 -46
- package/package.json +15 -11
- package/plugins/sp/README.md +8 -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-plan.md +8 -9
- package/plugins/sp/commands/dev-review.md +22 -13
- package/plugins/sp/commands/dev-verify.md +7 -4
- package/plugins/sp/commands/dev-verifyall.md +5 -4
- package/plugins/sp/commands/dev-wrap.md +1 -1
- package/plugins/sp/commands/spur-init.md +2 -2
- package/plugins/sp/hooks/context-post-tool.ts +2 -2
- package/plugins/sp/hooks/context-session-start.ts +2 -1
- package/plugins/sp/hooks/context-session-stop.ts +22 -14
- package/plugins/sp/hooks/pi/guard-extension.ts +109 -150
- 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 +6 -5
- package/plugins/sp/lib/inline-run.generated.d.mts +13 -2
- package/plugins/sp/lib/inline-run.generated.mjs +33 -16
- package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +438 -0
- package/plugins/sp/lib/residual-scan.generated.d.mts +62 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +210 -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 +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +3 -3
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +3 -3
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +9 -7
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +8 -7
- package/plugins/sp/scripts/feature-verification-steps.ts +6 -6
- package/plugins/sp/scripts/history-anatomy-cache.mjs +23 -22
- package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
- package/plugins/sp/scripts/inline-run-setup.mjs +90 -315
- package/plugins/sp/scripts/inline-run-setup.ts +109 -639
- package/plugins/sp/scripts/pr-reviewing.mjs +2 -0
- package/plugins/sp/scripts/pr-reviewing.ts +1 -0
- package/plugins/sp/scripts/quality-gate.mjs +38 -21
- package/plugins/sp/scripts/quality-gate.ts +20 -660
- package/plugins/sp/scripts/residual-scan.mjs +138 -159
- package/plugins/sp/scripts/residual-scan.ts +102 -499
- package/plugins/sp/scripts/script-root.mjs +129 -0
- package/plugins/sp/scripts/script-root.ts +199 -0
- package/plugins/sp/scripts/workflow-step-profile.mjs +27 -16
- package/plugins/sp/scripts/workflow-step-profile.ts +22 -314
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +12 -3
- package/plugins/sp/scripts/wrapup-drift-probe.ts +6 -3
- package/plugins/sp/scripts/wrapup-steps.mjs +42 -40
- package/plugins/sp/scripts/wrapup-steps.ts +64 -46
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
- package/plugins/sp/skills/code-verification/SKILL.md +46 -16
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +13 -2
- package/plugins/sp/skills/functional-review/SKILL.md +8 -5
- 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 +2 -2
- 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/verbs.md +28 -5
- package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +11 -9
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +86 -70
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +107 -44
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -9
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +31 -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 +35 -6
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
- package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
- package/plugins/sp/tsconfig.json +8 -0
- package/spur.js +6638 -4775
- package/web/_astro/BoardApp.BIjMatT1.js +1 -0
- package/web/_astro/{BoardApp.FTEs3-N8.js → BoardApp.GvjIe9Z6.js} +150 -153
- package/web/_astro/TaskDetail.BZM3EAFx.js +1 -0
- package/web/_astro/_commonjsHelpers.CqkleIqs.js +1 -0
- package/web/_astro/{arc.uuAf51IT.js → arc.HkRiZnoI.js} +1 -1
- package/web/_astro/architectureDiagram-3BPJPVTR.BDdK-tgZ.js +36 -0
- package/web/_astro/{blockDiagram-GPEHLZMM.D1mGCq3p.js → blockDiagram-GPEHLZMM.CZ9kOBxx.js} +1 -1
- package/web/_astro/board-facade-react-dom-client.B2U5wMvk.js +1 -0
- package/web/_astro/board-facade-react-dom.Cd2MuMhF.js +1 -0
- package/web/_astro/board-facade-react-jsx-dev-runtime.B_L3JzfC.js +1 -0
- package/web/_astro/board-facade-react-jsx-runtime.BY2yfHRi.js +1 -0
- package/web/_astro/board-facade-react-router-dom.Dy5pjzOw.js +1 -0
- package/web/_astro/board-facade-react-router.DjjqU27_.js +1 -0
- package/web/_astro/board-facade-react.Dgf4GRyj.js +1 -0
- package/web/_astro/{c4Diagram-AAUBKEIU.CMsolcde.js → c4Diagram-AAUBKEIU.DXK9qeRD.js} +1 -1
- package/web/_astro/channel.DlhX2MEt.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CrGA3fik.js → chunk-2J33WTMH.BTD2WeX5.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DsLVVla0.js → chunk-4BX2VUAB.Ci6Qbcvk.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.Dxc59Tfi.js → chunk-55IACEB6.yl3zsj7p.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CaEVE2Wy.js → chunk-727SXJPM.ClXZsfyR.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.BiJ4HXeI.js → chunk-AQP2D5EJ.BmKWZBcP.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.Mxf1fru5.js → chunk-FMBD7UC4.By30fcb7.js} +1 -1
- package/web/_astro/chunk-JMJ3UQ3L.BQJEJHu4.js +34 -0
- package/web/_astro/{chunk-ND2GUHAM.pyOWQixH.js → chunk-ND2GUHAM.24NmrD-k.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BmEsg4vr.js → chunk-QZHKN3VN.BN4sKdcS.js} +1 -1
- package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +7 -0
- package/web/_astro/{classDiagram-4FO5ZUOK.BHhhMFTO.js → classDiagram-4FO5ZUOK.CJpoMPb5.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BHhhMFTO.js → classDiagram-v2-Q7XG4LA2.CJpoMPb5.js} +1 -1
- package/web/_astro/client.BhluLQqe.js +1 -0
- package/web/_astro/client.DBpSRLnx.js +9 -0
- package/web/_astro/cose-bilkent-S5V4N54A.BraxQ2Nt.js +1 -0
- package/web/_astro/{cynefin-OW5HDTMX.C2j1_lKL.js → cynefin-OW5HDTMX.gzoU73oL.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.hZ2NCTdT.js → dagre-BM42HDAG.wyDfWySU.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.Bfw5_EzK.js → diagram-2AECGRRQ.h2_EbMxV.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bt1V_Tmk.js → diagram-5GNKFQAL.Db6dcs89.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.DEk-YFwp.js → diagram-KO2AKTUF.NVwzxu_U.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.CDjndtQm.js → diagram-LMA3HP47.B-eNgDQk.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.cFnUHScG.js → diagram-OG6HWLK6.Bsa8ZRbJ.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DlhYp7NV.js → erDiagram-TEJ5UH35.BospRE5Q.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DNTpsxfx.js → flowDiagram-I6XJVG4X.DMzvHwAA.js} +1 -1
- package/web/_astro/ganttDiagram-6RSMTGT7.Dmdxgd-M.js +292 -0
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DkEqNI0P.js → gitGraphDiagram-PVQCEYII.CaqdO0rL.js} +1 -1
- package/web/_astro/index.5dn-Zm-2.js +1 -0
- package/web/_astro/index.C-IqPpW2.js +1 -0
- package/web/_astro/index.Cyc_GA4f.js +1 -0
- package/web/_astro/index.EoZlzLK-.css +1 -0
- package/web/_astro/index.tZfqwSut.js +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CrjioCTG.js → infoDiagram-5YYISTIA.DDVD9Wn7.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BcK0CN8n.js → ishikawaDiagram-YF4QCWOH.EKyCUFsL.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.p0CbBDX1.js → journeyDiagram-JHISSGLW.By2-RkYB.js} +1 -1
- package/web/_astro/jsx-runtime.DWOiEOII.js +1 -0
- package/web/_astro/{kanban-definition-UN3LZRKU.puHIFt6J.js → kanban-definition-UN3LZRKU.ar-WmlyR.js} +1 -1
- package/web/_astro/{linear.Bb-3a1d7.js → linear.BnzMBgo_.js} +1 -1
- package/web/_astro/{mermaid.core.CJDgXOJs.js → mermaid.core.DSqeFu2Y.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BoAZEI9t.js → mindmap-definition-RKZ34NQL.1XcF8Wh2.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CJNSdqY7.js → pieDiagram-4H26LBE5.DnItHOdK.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DtTy7_0Z.js → quadrantDiagram-W4KKPZXB.BQQyrnBt.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BNYV98Xg.js → requirementDiagram-4Y6WPE33.Dmzxun82.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DRq9DGHp.js → sankeyDiagram-5OEKKPKP.BBIeRKYB.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B7KdBFCm.js → sequenceDiagram-3UESZ5HK.CUNVdBBf.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CD62ZJ2H.js → stateDiagram-AJRCARHV.DlFf2sJm.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CeVk1TAj.js → stateDiagram-v2-BHNVJYJU.DXR7I1H5.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.C_ZPA5tT.js → timeline-definition-PNZ67QCA.BcO-GyX2.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.CrThO_FQ.js → vennDiagram-CIIHVFJN.ChCECmmW.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.DSZCA5nl.js → wardleyDiagram-YWT4CUSO.CRaT12mD.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.KI7baTuk.js → xychartDiagram-2RQKCTM6.B3lsHVpj.js} +1 -1
- package/web/board-runtime.json +69 -0
- 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 -298
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -479
- 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 -377
- 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 -187
- package/plugins/sp/scripts/task-size-precheck.ts +0 -210
- 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 -878
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -547
- package/web/_astro/BoardApp.C02hAHPO.js +0 -1
- package/web/_astro/TaskDetail.C-GdsS-t.js +0 -1
- package/web/_astro/architectureDiagram-3BPJPVTR.CGe629A1.js +0 -36
- package/web/_astro/channel.fsgl7o5j.js +0 -1
- package/web/_astro/client.yhYJvxCU.js +0 -9
- package/web/_astro/cose-bilkent-S5V4N54A.2fH4YOlp.js +0 -1
- package/web/_astro/ganttDiagram-6RSMTGT7.DC_p36PI.js +0 -292
- package/web/_astro/index.De90oHcH.js +0 -1
- package/web/_astro/index.Hjbr15fG.css +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// plugins/sp/scripts/script-root.ts
|
|
5
|
+
import { spawnSync } from "child_process";
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
|
|
10
|
+
// plugins/sp/lib/env.ts
|
|
11
|
+
function getEnvVars() {
|
|
12
|
+
return process.env;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// plugins/sp/scripts/script-root.ts
|
|
16
|
+
var SOURCE_REPO_MARKER = join("config", "plugin-scripts.json");
|
|
17
|
+
var PROJECT_SCRIPTS_DIR = join("plugins", "sp", "scripts");
|
|
18
|
+
var SCRIPT_ROOT_TWIN_REL = "script-root.mjs";
|
|
19
|
+
function defaultResolveScript(cwd) {
|
|
20
|
+
return (rel) => {
|
|
21
|
+
const run = spawnSync("superskill", ["script", "path", "sp", rel, "--json"], {
|
|
22
|
+
cwd,
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
env: getEnvVars()
|
|
25
|
+
});
|
|
26
|
+
if (run.status !== 0 || !run.stdout)
|
|
27
|
+
return null;
|
|
28
|
+
const lastLine = run.stdout.trim().split(`
|
|
29
|
+
`).pop() ?? "";
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(lastLine);
|
|
32
|
+
return typeof parsed.path === "string" && parsed.path.length > 0 ? parsed : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function digestScriptSet(dir, cwd) {
|
|
39
|
+
const abs = cwd ? join(cwd, dir) : dir;
|
|
40
|
+
const names = readdirSync(abs).filter((name) => {
|
|
41
|
+
try {
|
|
42
|
+
return statSync(join(abs, name)).isFile();
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}).sort();
|
|
47
|
+
const list = names.map((name) => `${name}:${createHash("sha256").update(readFileSync(join(abs, name))).digest("hex")}`);
|
|
48
|
+
return `sha256:${createHash("sha256").update(list.join(`
|
|
49
|
+
`)).digest("hex")}`;
|
|
50
|
+
}
|
|
51
|
+
function runScriptRoot(env, options = {}) {
|
|
52
|
+
const cwd = options.cwd;
|
|
53
|
+
const runId = env.__runId ?? env.runId ?? "";
|
|
54
|
+
const relResult = join(".spur", "run", `${runId}-script-root.json`);
|
|
55
|
+
const abs = (p) => cwd ? join(cwd, p) : p;
|
|
56
|
+
const warn = options.warn ?? ((message) => void process.stderr.write(`${message}
|
|
57
|
+
`));
|
|
58
|
+
mkdirSync(abs(join(".spur", "run")), { recursive: true });
|
|
59
|
+
const write = (row) => {
|
|
60
|
+
writeFileSync(abs(relResult), `${JSON.stringify(row)}
|
|
61
|
+
`);
|
|
62
|
+
return { ...row, resultFile: relResult, exitCode: 0 };
|
|
63
|
+
};
|
|
64
|
+
if (existsSync(abs(SOURCE_REPO_MARKER))) {
|
|
65
|
+
try {
|
|
66
|
+
return write({
|
|
67
|
+
mode: "source-repo",
|
|
68
|
+
source: "project",
|
|
69
|
+
dir: PROJECT_SCRIPTS_DIR,
|
|
70
|
+
scriptSetDigest: digestScriptSet(PROJECT_SCRIPTS_DIR, cwd)
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return write({ mode: "unresolved", error: `source-repo digest failed: ${String(error)}` });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (existsSync(abs(PROJECT_SCRIPTS_DIR))) {
|
|
77
|
+
warn(`script-root: ignoring vendored ${PROJECT_SCRIPTS_DIR}/ (no ${SOURCE_REPO_MARKER} marker) \u2014 ` + `steps resolve the installed twin via 'superskill script path sp <rel>'`);
|
|
78
|
+
}
|
|
79
|
+
const resolve = options.resolveScript ?? defaultResolveScript(cwd);
|
|
80
|
+
const resolved = resolve(SCRIPT_ROOT_TWIN_REL);
|
|
81
|
+
if (!resolved?.path) {
|
|
82
|
+
return write({
|
|
83
|
+
mode: "unresolved",
|
|
84
|
+
error: `'superskill script path sp ${SCRIPT_ROOT_TWIN_REL}' resolved nothing`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const dir = dirname(resolved.path);
|
|
88
|
+
try {
|
|
89
|
+
return write({
|
|
90
|
+
mode: "installed",
|
|
91
|
+
source: resolved.source ?? "unknown",
|
|
92
|
+
dir,
|
|
93
|
+
scriptSetDigest: digestScriptSet(dir)
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return write({ mode: "unresolved", error: `installed digest failed: ${String(error)}` });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
var SCRIPT_ROOT_USAGE = "usage: script-root --run-id <id> [--spur-bin <path>]";
|
|
100
|
+
function main(argv, env = getEnvVars(), options = {}) {
|
|
101
|
+
let runId = env.__runId ?? "";
|
|
102
|
+
for (let i = 0;i < argv.length; i++) {
|
|
103
|
+
if (argv[i] === "--run-id") {
|
|
104
|
+
runId = argv[i + 1] ?? "";
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (argv[i] === "--spur-bin") {
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
process.stderr.write(`${SCRIPT_ROOT_USAGE}
|
|
113
|
+
`);
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
return runScriptRoot({ ...env, __runId: runId }, options).exitCode;
|
|
117
|
+
}
|
|
118
|
+
{
|
|
119
|
+
process.exit(main(process.argv.slice(2)));
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
runScriptRoot,
|
|
123
|
+
main,
|
|
124
|
+
digestScriptSet,
|
|
125
|
+
SOURCE_REPO_MARKER,
|
|
126
|
+
SCRIPT_ROOT_USAGE,
|
|
127
|
+
SCRIPT_ROOT_TWIN_REL,
|
|
128
|
+
PROJECT_SCRIPTS_DIR
|
|
129
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* script-root — run-start script-resolution identity for the sp pipelines (task 0960 R2).
|
|
4
|
+
*
|
|
5
|
+
* WHY: the pipeline YAML ships to every consumer, but its script probes resolved
|
|
6
|
+
* `plugins/sp/scripts/` **project-first**. A consumer that hand-vendored a stale copy then ran
|
|
7
|
+
* a different revision than the installed twin, mixing revisions across steps that write and
|
|
8
|
+
* read the same cross-step artifacts (status files, verdicts, proof digests). The probe is
|
|
9
|
+
* dogfooding-only, so it is now gated on `config/plugin-scripts.json` — a marker that lives
|
|
10
|
+
* outside `plugins/sp/`, so copying the plugin tree cannot reproduce it.
|
|
11
|
+
*
|
|
12
|
+
* This script records *which* tree a run resolved, once, before any step executes:
|
|
13
|
+
* `.spur/run/<runId>-script-root.json` `{ mode, source, dir, scriptSetDigest }`
|
|
14
|
+
*
|
|
15
|
+
* - `source-repo` — the marker is present; the project's live `plugins/sp/scripts/` is the
|
|
16
|
+
* resolved tree (the dogfood path).
|
|
17
|
+
* - `installed` — no marker; the tree is `dirname(superskill script path sp script-root.mjs
|
|
18
|
+
* --json .path)`, with `source` from the same probe. One stderr warning names a dead
|
|
19
|
+
* vendored `plugins/sp/scripts/` dir that is no longer executed.
|
|
20
|
+
* - `unresolved` — nothing resolved. Written with an `error` and **exit 0**: a missing
|
|
21
|
+
* optional script must stay a per-step fail-closed outcome, never a run-wide abort (R3).
|
|
22
|
+
*
|
|
23
|
+
* `scriptSetDigest` is one sha256 over the sorted `name:sha256` list of the resolved dir's
|
|
24
|
+
* regular files (non-recursive) — the installed staging has no version file, so this answers
|
|
25
|
+
* "which revision produced this run?" without a Superskill change.
|
|
26
|
+
*
|
|
27
|
+
* Node/bun builtins and the vendored `../lib/env` gateway only — bundled standalone by
|
|
28
|
+
* `superskill install`, so no `@gobing-ai/*` value imports (task 0669).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { spawnSync } from 'node:child_process';
|
|
32
|
+
import { createHash } from 'node:crypto';
|
|
33
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
34
|
+
import { dirname, join } from 'node:path';
|
|
35
|
+
import { getEnvVars } from '../lib/env';
|
|
36
|
+
|
|
37
|
+
export type ScriptRootMode = 'source-repo' | 'installed' | 'unresolved';
|
|
38
|
+
|
|
39
|
+
export interface ScriptRoot {
|
|
40
|
+
mode: ScriptRootMode;
|
|
41
|
+
source?: string;
|
|
42
|
+
dir?: string;
|
|
43
|
+
scriptSetDigest?: string;
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ScriptRootResult extends ScriptRoot {
|
|
48
|
+
resultFile: string;
|
|
49
|
+
exitCode: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ScriptRootEnv {
|
|
53
|
+
__runId?: string;
|
|
54
|
+
[key: string]: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Resolved script descriptor from `superskill script path sp <rel> --json`. */
|
|
58
|
+
export interface ResolvedScript {
|
|
59
|
+
path?: string;
|
|
60
|
+
source?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ScriptRootOptions {
|
|
64
|
+
/** Base directory for `.spur/run` and the project marker; defaults to process cwd. */
|
|
65
|
+
cwd?: string;
|
|
66
|
+
/** Test seam: run `superskill script path sp script-root.mjs --json`. */
|
|
67
|
+
resolveScript?: (rel: string) => ResolvedScript | null;
|
|
68
|
+
/** Test seam: the one-warning sink. */
|
|
69
|
+
warn?: (message: string) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Marker that identifies the Spur source repo (owned by script-contract-check, outside plugins/sp). */
|
|
73
|
+
export const SOURCE_REPO_MARKER = join('config', 'plugin-scripts.json');
|
|
74
|
+
/** The project-first tree the dogfood probes execute. */
|
|
75
|
+
export const PROJECT_SCRIPTS_DIR = join('plugins', 'sp', 'scripts');
|
|
76
|
+
/** The rel the installed twin resolves under (its own staging name). */
|
|
77
|
+
export const SCRIPT_ROOT_TWIN_REL = 'script-root.mjs';
|
|
78
|
+
|
|
79
|
+
function defaultResolveScript(cwd?: string): (rel: string) => ResolvedScript | null {
|
|
80
|
+
return (rel: string): ResolvedScript | null => {
|
|
81
|
+
const run = spawnSync('superskill', ['script', 'path', 'sp', rel, '--json'], {
|
|
82
|
+
cwd,
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
env: getEnvVars(),
|
|
85
|
+
});
|
|
86
|
+
if (run.status !== 0 || !run.stdout) return null;
|
|
87
|
+
const lastLine = run.stdout.trim().split('\n').pop() ?? '';
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(lastLine) as ResolvedScript;
|
|
90
|
+
return typeof parsed.path === 'string' && parsed.path.length > 0 ? parsed : null;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** sha256 over the sorted `name:sha256` list of `dir`'s regular files (non-recursive). */
|
|
98
|
+
export function digestScriptSet(dir: string, cwd?: string): string {
|
|
99
|
+
const abs = cwd ? join(cwd, dir) : dir;
|
|
100
|
+
const names = readdirSync(abs)
|
|
101
|
+
.filter((name) => {
|
|
102
|
+
try {
|
|
103
|
+
return statSync(join(abs, name)).isFile();
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
.sort();
|
|
109
|
+
const list = names.map(
|
|
110
|
+
(name) =>
|
|
111
|
+
`${name}:${createHash('sha256')
|
|
112
|
+
.update(readFileSync(join(abs, name)))
|
|
113
|
+
.digest('hex')}`,
|
|
114
|
+
);
|
|
115
|
+
return `sha256:${createHash('sha256').update(list.join('\n')).digest('hex')}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the run's script root and write `.spur/run/<runId>-script-root.json`.
|
|
120
|
+
* Never aborts: every failure is recorded as `mode: "unresolved"` with exit 0 (R3).
|
|
121
|
+
*/
|
|
122
|
+
export function runScriptRoot(env: ScriptRootEnv, options: ScriptRootOptions = {}): ScriptRootResult {
|
|
123
|
+
const cwd = options.cwd;
|
|
124
|
+
const runId = env.__runId ?? env.runId ?? '';
|
|
125
|
+
const relResult = join('.spur', 'run', `${runId}-script-root.json`);
|
|
126
|
+
const abs = (p: string): string => (cwd ? join(cwd, p) : p);
|
|
127
|
+
const warn = options.warn ?? ((message: string): void => void process.stderr.write(`${message}\n`));
|
|
128
|
+
mkdirSync(abs(join('.spur', 'run')), { recursive: true });
|
|
129
|
+
|
|
130
|
+
const write = (row: ScriptRoot): ScriptRootResult => {
|
|
131
|
+
writeFileSync(abs(relResult), `${JSON.stringify(row)}\n`);
|
|
132
|
+
return { ...row, resultFile: relResult, exitCode: 0 };
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Source repo: the project's live tree is the resolved tree (R6).
|
|
136
|
+
if (existsSync(abs(SOURCE_REPO_MARKER))) {
|
|
137
|
+
try {
|
|
138
|
+
return write({
|
|
139
|
+
mode: 'source-repo',
|
|
140
|
+
source: 'project',
|
|
141
|
+
dir: PROJECT_SCRIPTS_DIR,
|
|
142
|
+
scriptSetDigest: digestScriptSet(PROJECT_SCRIPTS_DIR, cwd),
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return write({ mode: 'unresolved', error: `source-repo digest failed: ${String(error)}` });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Installed: resolve the twin, and name a dead vendored dir that is no longer executed.
|
|
150
|
+
if (existsSync(abs(PROJECT_SCRIPTS_DIR))) {
|
|
151
|
+
warn(
|
|
152
|
+
`script-root: ignoring vendored ${PROJECT_SCRIPTS_DIR}/ (no ${SOURCE_REPO_MARKER} marker) — ` +
|
|
153
|
+
`steps resolve the installed twin via 'superskill script path sp <rel>'`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const resolve = options.resolveScript ?? defaultResolveScript(cwd);
|
|
157
|
+
const resolved = resolve(SCRIPT_ROOT_TWIN_REL);
|
|
158
|
+
if (!resolved?.path) {
|
|
159
|
+
return write({
|
|
160
|
+
mode: 'unresolved',
|
|
161
|
+
error: `'superskill script path sp ${SCRIPT_ROOT_TWIN_REL}' resolved nothing`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const dir = dirname(resolved.path);
|
|
165
|
+
try {
|
|
166
|
+
return write({
|
|
167
|
+
mode: 'installed',
|
|
168
|
+
source: resolved.source ?? 'unknown',
|
|
169
|
+
dir,
|
|
170
|
+
scriptSetDigest: digestScriptSet(dir),
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return write({ mode: 'unresolved', error: `installed digest failed: ${String(error)}` });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const SCRIPT_ROOT_USAGE = 'usage: script-root --run-id <id> [--spur-bin <path>]';
|
|
178
|
+
|
|
179
|
+
export function main(argv: string[], env: ScriptRootEnv = getEnvVars(), options: ScriptRootOptions = {}): number {
|
|
180
|
+
let runId = env.__runId ?? '';
|
|
181
|
+
for (let i = 0; i < argv.length; i++) {
|
|
182
|
+
if (argv[i] === '--run-id') {
|
|
183
|
+
runId = argv[i + 1] ?? '';
|
|
184
|
+
i++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (argv[i] === '--spur-bin') {
|
|
188
|
+
i++;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
process.stderr.write(`${SCRIPT_ROOT_USAGE}\n`);
|
|
192
|
+
return 2;
|
|
193
|
+
}
|
|
194
|
+
return runScriptRoot({ ...env, __runId: runId }, options).exitCode;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (import.meta.main) {
|
|
198
|
+
process.exit(main(process.argv.slice(2)));
|
|
199
|
+
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
// plugins/sp/scripts/workflow-step-profile.ts
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
// plugins/sp/lib/spur-bin.ts
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
8
10
|
|
|
9
11
|
// plugins/sp/lib/env.ts
|
|
10
12
|
function getEnvVar(name, fallback) {
|
|
@@ -12,7 +14,22 @@ function getEnvVar(name, fallback) {
|
|
|
12
14
|
return raw === undefined ? fallback : raw;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
// plugins/sp/
|
|
17
|
+
// plugins/sp/lib/spur-bin.ts
|
|
18
|
+
function spurCommand(spurBin) {
|
|
19
|
+
const parts = (spurBin ?? "spur").trim().split(/\s+/).filter((p) => p.length > 0);
|
|
20
|
+
return { cmd: parts[0] ?? "spur", prefix: parts.slice(1) };
|
|
21
|
+
}
|
|
22
|
+
function defaultSpurBin() {
|
|
23
|
+
const fromEnv = getEnvVar("SPUR_BIN");
|
|
24
|
+
if (fromEnv)
|
|
25
|
+
return fromEnv;
|
|
26
|
+
const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
27
|
+
if (existsSync(local))
|
|
28
|
+
return `bun ${local}`;
|
|
29
|
+
return "spur";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// plugins/sp/lib/step-profile.generated.mjs
|
|
16
33
|
var DEFAULT_LAST = 20;
|
|
17
34
|
var DEFAULT_WINDOW_SEC = 300;
|
|
18
35
|
function nearestRankP50(values) {
|
|
@@ -156,7 +173,7 @@ function ratio(value) {
|
|
|
156
173
|
}
|
|
157
174
|
function formatStepProfileHuman(profile) {
|
|
158
175
|
const lines = [
|
|
159
|
-
`step profile
|
|
176
|
+
`step profile — ${profile.workflow} (window ${profile.windowSec}s, ${profile.sampledRuns} sampled runs)`
|
|
160
177
|
];
|
|
161
178
|
for (const row of profile.rows) {
|
|
162
179
|
lines.push([
|
|
@@ -175,14 +192,8 @@ function formatStepProfileHuman(profile) {
|
|
|
175
192
|
`)}
|
|
176
193
|
`;
|
|
177
194
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return getEnvVar("SPUR_BIN");
|
|
181
|
-
const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
182
|
-
if (existsSync(local))
|
|
183
|
-
return `bun ${local}`;
|
|
184
|
-
return "spur";
|
|
185
|
-
}
|
|
195
|
+
|
|
196
|
+
// plugins/sp/scripts/workflow-step-profile.ts
|
|
186
197
|
function positiveInt(raw, fallback) {
|
|
187
198
|
const parsed = Number.parseInt(raw ?? "", 10);
|
|
188
199
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
@@ -196,6 +207,8 @@ function parseStepProfileCliArgs(argv) {
|
|
|
196
207
|
let help = false;
|
|
197
208
|
for (let i = 0;i < argv.length; i++) {
|
|
198
209
|
const a = argv[i];
|
|
210
|
+
if (a === undefined)
|
|
211
|
+
continue;
|
|
199
212
|
if (a === "--help" || a === "-h")
|
|
200
213
|
help = true;
|
|
201
214
|
else if (a === "--json")
|
|
@@ -212,9 +225,8 @@ function parseStepProfileCliArgs(argv) {
|
|
|
212
225
|
return { workflow, last, windowSec, spurBin, json, help };
|
|
213
226
|
}
|
|
214
227
|
function runSpurJson(spurBin, args) {
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const cmdArgs = [...binParts.slice(1), ...args];
|
|
228
|
+
const { cmd, prefix } = spurCommand(spurBin);
|
|
229
|
+
const cmdArgs = [...prefix, ...args];
|
|
218
230
|
const r = spawnSync(cmd, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
|
|
219
231
|
const decode = (b) => typeof b === "string" ? b : Buffer.from(b ?? []).toString("utf8");
|
|
220
232
|
return {
|
|
@@ -318,7 +330,6 @@ export {
|
|
|
318
330
|
main,
|
|
319
331
|
formatStepProfileHuman,
|
|
320
332
|
extractExecutions,
|
|
321
|
-
defaultSpurBin,
|
|
322
333
|
buildStepProfile,
|
|
323
334
|
buildRows,
|
|
324
335
|
STEP_PROFILE_USAGE,
|