@gobing-ai/spur 0.4.8 → 0.4.10
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/pipeline-budgets.json +2 -2
- package/config/rules/README.md +1 -1
- package/config/rules/boundary/config-loading-ownership.yaml +1 -0
- package/config/rules/boundary/env-var-hygiene.yaml +0 -1
- package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
- package/config/rules/boundary/sp-script-placement.yaml +3 -3
- package/config/rules/docs/sourced-api-claims.yaml +29 -64
- package/config/rules/strict/runtime-boundaries.yaml +40 -1
- package/config/rules/structure/protected-files.yaml +1 -1
- package/config/rules/surface/check-cli-surface.yaml +33 -0
- package/config/rules/typescript/no-debugger.yaml +3 -0
- package/config/workflow-candidates.json +2 -2
- package/config/workflows/feature-verification.yaml +6 -19
- package/config/workflows/history-anatomy.yaml +14 -28
- package/config/workflows/idea-pipeline.yaml +6 -19
- package/config/workflows/pr-review.yaml +9 -16
- package/config/workflows/task-pipeline.yaml +18 -25
- package/config/workflows/wrapup-pipeline.yaml +21 -63
- package/package.json +3 -3
- package/plugins/sp/README.md +70 -37
- package/plugins/sp/agents/expert-spur.md +0 -1
- package/plugins/sp/agents/super-planner.md +5 -5
- package/plugins/sp/agents/super-reviewer.md +2 -2
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/commands/dev-daily.md +1 -1
- package/plugins/sp/commands/dev-pr-review.md +1 -1
- package/plugins/sp/commands/dev-review-session.md +1 -1
- package/plugins/sp/commands/dev-runall.md +4 -4
- package/plugins/sp/commands/dev-verify.md +4 -6
- package/plugins/sp/commands/dev-verifyall.md +4 -5
- package/plugins/sp/hooks/hooks.json +11 -1
- package/plugins/sp/lib/context-post-tool.ts +1 -1
- package/plugins/sp/lib/context-session-start.ts +1 -1
- package/plugins/sp/lib/context-session-stop.ts +1 -1
- package/plugins/sp/lib/destructive-policy.ts +65 -1
- package/plugins/sp/plugin.json +2 -5
- package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
- package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
- package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
- package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
- package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
- package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
- package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
- package/plugins/sp/skills/brainstorm/SKILL.md +12 -11
- package/plugins/sp/skills/brainstorm/agents/openai.yaml +1 -1
- package/plugins/sp/skills/brainstorm/examples/ideation-example.md +0 -4
- package/plugins/sp/skills/brainstorm/metadata.openclaw +1 -1
- package/plugins/sp/skills/brainstorm/references/workflows.md +4 -14
- package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +8 -8
- package/plugins/sp/skills/code-verification/references/secu-review.md +4 -4
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
- package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
- package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
- package/plugins/sp/skills/history-anatomy/references/operations.md +3 -4
- package/plugins/sp/skills/history-anatomy/references/report-contract.md +2 -3
- package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +9 -10
- package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
- package/plugins/sp/skills/reverse-engineering/SKILL.md +4 -6
- package/plugins/sp/skills/session-review/SKILL.md +13 -29
- package/plugins/sp/skills/source-driven-development/SKILL.md +26 -27
- package/plugins/sp/skills/spur-check/SKILL.md +14 -13
- package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
- package/plugins/sp/skills/spur-cli/references/agent.md +17 -2
- package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
- package/plugins/sp/skills/spur-cli/references/features.md +6 -1
- package/plugins/sp/skills/spur-cli/references/history.md +3 -0
- package/plugins/sp/skills/spur-cli/references/self.md +5 -3
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
- package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -13
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +9 -12
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
- package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
- package/spur.js +12726 -1456
- package/web/_astro/{BoardApp.CaNvQBD_.js → BoardApp.B5rTVNd_.js} +1 -1
- package/web/_astro/{BoardApp.C5jnkkKP.js → BoardApp.BMprmPRc.js} +141 -141
- package/web/_astro/{TaskDetail.DGKndhI8.js → TaskDetail.Ddsur8qd.js} +1 -1
- package/web/_astro/{arc.DHrwdg3G.js → arc.DLqVVBxP.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.pvKpK1s5.js → architectureDiagram-3BPJPVTR.C-Pk3uAi.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BQmRcZsS.js → blockDiagram-GPEHLZMM.BRO1i_Nm.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.CCLjHeH1.js → c4Diagram-AAUBKEIU.BU_cWu6e.js} +1 -1
- package/web/_astro/channel.Bs3Illhy.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.DL6Yuoma.js → chunk-2J33WTMH.D2VXiQ2t.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.CzlyEI7l.js → chunk-4BX2VUAB.BcHT772J.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.B60EWxp1.js → chunk-55IACEB6.DhDtICmo.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.c7v_fRLo.js → chunk-727SXJPM.BoKHp7gV.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.8BxLWdDc.js → chunk-AQP2D5EJ.Chu4JRpE.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DmKuxGFT.js → chunk-FMBD7UC4.wHdwKPlq.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.1Zwd4mqV.js → chunk-ND2GUHAM.ffN-M1JB.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BvYLu-A1.js → chunk-QZHKN3VN.FRubwg4S.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CEqRt0WU.js → classDiagram-4FO5ZUOK.c22tQsc_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CEqRt0WU.js → classDiagram-v2-Q7XG4LA2.c22tQsc_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.C7JiUdj2.js → cose-bilkent-S5V4N54A.B9c6fiSj.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.B6mZ-1be.js → cynefin-OW5HDTMX.DrZsrUHl.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BuryfDy6.js → dagre-BM42HDAG.DG1U4b3y.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.dao39YQM.js → diagram-2AECGRRQ.CH15b-K3.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.lnGZe4At.js → diagram-5GNKFQAL.BH1j7twR.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.uWfhzEfT.js → diagram-KO2AKTUF.q2zCkbAj.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.ppq8-198.js → diagram-LMA3HP47.BLaaZnHV.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.CKSCqDt5.js → diagram-OG6HWLK6.BcpRdQwI.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.sFSf2yBN.js → erDiagram-TEJ5UH35.D7KBlqfM.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.BJQjKLbz.js → flowDiagram-I6XJVG4X.D_gmuRqg.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DAHKNj2N.js → ganttDiagram-6RSMTGT7.DzgWYniD.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BlmzcW_O.js → gitGraphDiagram-PVQCEYII.576i3hm3.js} +1 -1
- package/web/_astro/index.DiC1a6lh.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DGhjaqsi.js → infoDiagram-5YYISTIA.DMCclBiz.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.gKIpgt6F.js → ishikawaDiagram-YF4QCWOH.9Kyca4SH.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DG3tmP0R.js → journeyDiagram-JHISSGLW.chbdG-8q.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CgHDSxkR.js → kanban-definition-UN3LZRKU.Bds54-e0.js} +1 -1
- package/web/_astro/{linear.BuGEpcuM.js → linear.D5ND9GN_.js} +1 -1
- package/web/_astro/{mermaid.core.DBVqkWBp.js → mermaid.core.D9N_PGbj.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.D-kOcji9.js → mindmap-definition-RKZ34NQL.t0xop5vp.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.BvGPwtZt.js → pieDiagram-4H26LBE5.DqgKh6hM.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.BKb_9FV6.js → quadrantDiagram-W4KKPZXB.DeuHVi2t.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.Cx0fvD8v.js → requirementDiagram-4Y6WPE33.BdYFSG4W.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.fatULi0W.js → sankeyDiagram-5OEKKPKP.k1LPPETJ.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.ChHyKqco.js → sequenceDiagram-3UESZ5HK.DdyfpzoG.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BdUpKWyj.js → stateDiagram-AJRCARHV.DSEI8il0.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.fmJxqIm5.js → stateDiagram-v2-BHNVJYJU.CoMnQRKo.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BayDn5-V.js → timeline-definition-PNZ67QCA.DM1AHNm-.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.Bm2Aggwa.js → vennDiagram-CIIHVFJN.C-cJmVtl.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.DI75O-uG.js → wardleyDiagram-YWT4CUSO.D0xMzKPb.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.DL90E7Q7.js → xychartDiagram-2RQKCTM6.BH5xJcEq.js} +1 -1
- package/web/index.html +2 -2
- package/config/plugin-scripts.json +0 -116
- package/config/script-placement-baseline.json +0 -33
- package/plugins/sp/hooks/careful-guard.ts +0 -74
- package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
- package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
- package/plugins/sp/lib/dogfood-report.ts +0 -278
- package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
- package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
- package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
- package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
- package/plugins/sp/lib/inline-run-app.ts +0 -68
- package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
- package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
- package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
- package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
- package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
- package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
- package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
- package/plugins/sp/lib/run-summary-core.ts +0 -225
- package/plugins/sp/lib/spur-bin.ts +0 -36
- package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
- package/plugins/sp/lib/step-profile.generated.mjs +0 -174
- package/plugins/sp/lib/transcript.ts +0 -112
- package/plugins/sp/scripts/batch-plan.mjs +0 -1096
- package/plugins/sp/scripts/batch-plan.ts +0 -145
- package/plugins/sp/scripts/batch-preflight.mjs +0 -423
- package/plugins/sp/scripts/batch-preflight.ts +0 -270
- package/plugins/sp/scripts/commit-guard.mjs +0 -226
- package/plugins/sp/scripts/commit-guard.ts +0 -248
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
- package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
- package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
- package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
- package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
- package/plugins/sp/scripts/idea-handoff.mjs +0 -32
- package/plugins/sp/scripts/idea-handoff.ts +0 -47
- package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
- package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
- package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
- package/plugins/sp/scripts/inline-run-setup.ts +0 -198
- package/plugins/sp/scripts/persist-out-check.mjs +0 -217
- package/plugins/sp/scripts/persist-out-check.ts +0 -249
- package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
- package/plugins/sp/scripts/pr-reviewing.ts +0 -952
- package/plugins/sp/scripts/quality-gate.mjs +0 -30491
- package/plugins/sp/scripts/quality-gate.ts +0 -80
- package/plugins/sp/scripts/residual-scan.mjs +0 -634
- package/plugins/sp/scripts/residual-scan.ts +0 -248
- package/plugins/sp/scripts/run-summary.mjs +0 -758
- package/plugins/sp/scripts/run-summary.ts +0 -107
- package/plugins/sp/scripts/script-root.mjs +0 -129
- package/plugins/sp/scripts/script-root.ts +0 -199
- package/plugins/sp/scripts/session-timeline.mjs +0 -359
- package/plugins/sp/scripts/session-timeline.ts +0 -190
- package/plugins/sp/scripts/task-diffstat.mjs +0 -181
- package/plugins/sp/scripts/task-diffstat.ts +0 -219
- package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
- package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
- package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
- package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
- package/plugins/sp/scripts/wrapup-steps.ts +0 -891
- package/web/_astro/channel.C7wG9RxA.js +0 -1
- package/web/_astro/index.BCY1JJHd.css +0 -1
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
// plugins/sp/lib/dogfood-pipeline.ts
|
|
6
|
-
var PIPELINE_TOKENS = [
|
|
7
|
-
"--next",
|
|
8
|
-
"dev-runall",
|
|
9
|
-
"dev-wrapall",
|
|
10
|
-
"dev-refineall",
|
|
11
|
-
"dev-verifyall",
|
|
12
|
-
"dev-run",
|
|
13
|
-
"dev-wrap",
|
|
14
|
-
"dev-idea",
|
|
15
|
-
"refineall",
|
|
16
|
-
"verifyall",
|
|
17
|
-
"runall",
|
|
18
|
-
"wrapall",
|
|
19
|
-
"run",
|
|
20
|
-
"wrap",
|
|
21
|
-
"idea"
|
|
22
|
-
];
|
|
23
|
-
var PIPELINE_DRIVING_REFUSE_MESSAGE = "⚠ pipeline-driving testee detected; pass --max-retry 0 (observe-only) or --max-retry N (fix mode, tree mutation acknowledged)";
|
|
24
|
-
var MUTATING_FIX_REFUSE_MESSAGE = "⚠ mutating --fix mode detected (--fix all | --fix blockers-first); pass --max-retry 0 (observe-only for the driver; the testee still mutates the tree) or --max-retry N (fix mode, driver + testee both mutate)";
|
|
25
|
-
var IMPLEMENT_HEAVY_ADVISORY_MESSAGE = "⚠ implement-heavy pipeline dogfood: prefer --max-retry 0 (observe-only) or step-split; operator --max-retry N overrides";
|
|
26
|
-
var IMPLEMENT_HEAVY_TOKENS = [
|
|
27
|
-
"dev-runall",
|
|
28
|
-
"dev-wrapall",
|
|
29
|
-
"dev-run",
|
|
30
|
-
"dev-wrap",
|
|
31
|
-
"dev-idea",
|
|
32
|
-
"runall",
|
|
33
|
-
"wrapall",
|
|
34
|
-
"run",
|
|
35
|
-
"wrap",
|
|
36
|
-
"idea",
|
|
37
|
-
"implement"
|
|
38
|
-
];
|
|
39
|
-
function hasMutatingFixMode(step) {
|
|
40
|
-
return /(?<![\w-])--fix[=\s]+(all|blockers-first)(?![\w-])/i.test(step);
|
|
41
|
-
}
|
|
42
|
-
function tokenMatches(testee, token) {
|
|
43
|
-
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44
|
-
const re = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i");
|
|
45
|
-
return re.test(testee);
|
|
46
|
-
}
|
|
47
|
-
function detectPipelineDriving(testee) {
|
|
48
|
-
if (typeof testee !== "string" || testee.length === 0) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return PIPELINE_TOKENS.some((token) => tokenMatches(testee, token));
|
|
52
|
-
}
|
|
53
|
-
function isImplementHeavyStep(step) {
|
|
54
|
-
if (typeof step !== "string" || step.length === 0)
|
|
55
|
-
return false;
|
|
56
|
-
const nonMutatingOnly = tokenMatches(step, "dev-verify") || tokenMatches(step, "dev-review") || tokenMatches(step, "dev-unit");
|
|
57
|
-
const hasMutating = IMPLEMENT_HEAVY_TOKENS.some((token) => tokenMatches(step, token)) || hasMutatingFixMode(step);
|
|
58
|
-
if (nonMutatingOnly && !hasMutating)
|
|
59
|
-
return false;
|
|
60
|
-
if (tokenMatches(step, "dev-refine") && !hasMutating && !tokenMatches(step, "--next")) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
if (tokenMatches(step, "dev-refine") && tokenMatches(step, "--next")) {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
return hasMutating;
|
|
67
|
-
}
|
|
68
|
-
function detectImplementHeavy(testee, derivedSteps = []) {
|
|
69
|
-
if (isImplementHeavyStep(testee))
|
|
70
|
-
return true;
|
|
71
|
-
if (!detectPipelineDriving(testee))
|
|
72
|
-
return false;
|
|
73
|
-
return derivedSteps.some((step) => isImplementHeavyStep(step));
|
|
74
|
-
}
|
|
75
|
-
function evaluateDogfoodGate(testee, options = {}) {
|
|
76
|
-
const maxRetryPresent = options.maxRetryPresent === true;
|
|
77
|
-
const steps = options.steps ?? [];
|
|
78
|
-
const pipelineDriving = detectPipelineDriving(testee);
|
|
79
|
-
const mutatingFix = hasMutatingFixMode(testee);
|
|
80
|
-
const implementHeavy = detectImplementHeavy(testee, steps);
|
|
81
|
-
if (pipelineDriving && !maxRetryPresent) {
|
|
82
|
-
return {
|
|
83
|
-
pipelineDriving,
|
|
84
|
-
mutatingFix,
|
|
85
|
-
maxRetryPresent,
|
|
86
|
-
implementHeavy,
|
|
87
|
-
refuse: true,
|
|
88
|
-
advisory: false,
|
|
89
|
-
message: PIPELINE_DRIVING_REFUSE_MESSAGE,
|
|
90
|
-
exitCode: 2
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
if (mutatingFix && !maxRetryPresent) {
|
|
94
|
-
return {
|
|
95
|
-
pipelineDriving,
|
|
96
|
-
mutatingFix,
|
|
97
|
-
maxRetryPresent,
|
|
98
|
-
implementHeavy,
|
|
99
|
-
refuse: true,
|
|
100
|
-
advisory: false,
|
|
101
|
-
message: MUTATING_FIX_REFUSE_MESSAGE,
|
|
102
|
-
exitCode: 2
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
if (implementHeavy) {
|
|
106
|
-
return {
|
|
107
|
-
pipelineDriving,
|
|
108
|
-
mutatingFix,
|
|
109
|
-
maxRetryPresent,
|
|
110
|
-
implementHeavy,
|
|
111
|
-
refuse: false,
|
|
112
|
-
advisory: true,
|
|
113
|
-
message: IMPLEMENT_HEAVY_ADVISORY_MESSAGE,
|
|
114
|
-
exitCode: 0
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
pipelineDriving,
|
|
119
|
-
mutatingFix,
|
|
120
|
-
maxRetryPresent,
|
|
121
|
-
implementHeavy,
|
|
122
|
-
refuse: false,
|
|
123
|
-
advisory: false,
|
|
124
|
-
message: null,
|
|
125
|
-
exitCode: 0
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
// plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts
|
|
129
|
-
function parseCliArgs(argv) {
|
|
130
|
-
let testee = null;
|
|
131
|
-
let maxRetryPresent = false;
|
|
132
|
-
let steps = [];
|
|
133
|
-
let json = false;
|
|
134
|
-
let help = false;
|
|
135
|
-
for (let i = 0;i < argv.length; i++) {
|
|
136
|
-
const a = argv[i];
|
|
137
|
-
if (a === undefined)
|
|
138
|
-
continue;
|
|
139
|
-
if (a === "--help" || a === "-h") {
|
|
140
|
-
help = true;
|
|
141
|
-
} else if (a === "--json") {
|
|
142
|
-
json = true;
|
|
143
|
-
} else if (a === "--max-retry-present") {
|
|
144
|
-
maxRetryPresent = true;
|
|
145
|
-
} else if (a === "--testee") {
|
|
146
|
-
testee = argv[++i] ?? null;
|
|
147
|
-
} else if (a === "--steps") {
|
|
148
|
-
const raw = argv[++i] ?? "";
|
|
149
|
-
steps = raw.length === 0 ? [] : raw.split("||").map((s) => s.trim()).filter(Boolean);
|
|
150
|
-
} else if (a === "--") {
|
|
151
|
-
testee = argv.slice(i + 1).join(" ");
|
|
152
|
-
break;
|
|
153
|
-
} else if (!a.startsWith("-") && testee === null) {
|
|
154
|
-
testee = a;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return { testee, maxRetryPresent, steps, json, help };
|
|
158
|
-
}
|
|
159
|
-
var CLI_USAGE = `Usage:
|
|
160
|
-
bun plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts \\
|
|
161
|
-
--testee "<testee string>" [--max-retry-present] [--steps "s1||s2"] [--json]
|
|
162
|
-
|
|
163
|
-
Exit codes:
|
|
164
|
-
0 proceed (stdout may carry implement-heavy advisory)
|
|
165
|
-
2 refuse — pipeline-driving OR mutating --fix mode without --max-retry
|
|
166
|
-
1 usage error
|
|
167
|
-
|
|
168
|
-
Phase 1.0: run BEFORE deriving steps (omit --steps).
|
|
169
|
-
Phase 1 W8: re-run AFTER step derivation with --steps "label1||label2".`;
|
|
170
|
-
function runCli(argv) {
|
|
171
|
-
const { testee, maxRetryPresent, steps, json, help } = parseCliArgs(argv);
|
|
172
|
-
if (help) {
|
|
173
|
-
return { exitCode: 0, stdout: "", stderr: CLI_USAGE };
|
|
174
|
-
}
|
|
175
|
-
if (testee === null || testee.length === 0) {
|
|
176
|
-
return { exitCode: 1, stdout: "", stderr: CLI_USAGE };
|
|
177
|
-
}
|
|
178
|
-
const result = evaluateDogfoodGate(testee, { maxRetryPresent, steps });
|
|
179
|
-
if (json) {
|
|
180
|
-
return { exitCode: result.exitCode, stdout: `${JSON.stringify(result, null, 2)}
|
|
181
|
-
`, stderr: "" };
|
|
182
|
-
}
|
|
183
|
-
if (result.message) {
|
|
184
|
-
return { exitCode: result.exitCode, stdout: `${result.message}
|
|
185
|
-
`, stderr: "" };
|
|
186
|
-
}
|
|
187
|
-
return { exitCode: result.exitCode, stdout: "", stderr: "" };
|
|
188
|
-
}
|
|
189
|
-
{
|
|
190
|
-
const { exitCode, stdout, stderr } = runCli(process.argv.slice(2));
|
|
191
|
-
if (stdout)
|
|
192
|
-
process.stdout.write(stdout);
|
|
193
|
-
if (stderr)
|
|
194
|
-
process.stderr.write(`${stderr}
|
|
195
|
-
`);
|
|
196
|
-
process.exit(exitCode);
|
|
197
|
-
}
|
|
198
|
-
export {
|
|
199
|
-
CLI_USAGE,
|
|
200
|
-
IMPLEMENT_HEAVY_ADVISORY_MESSAGE,
|
|
201
|
-
MUTATING_FIX_REFUSE_MESSAGE,
|
|
202
|
-
PIPELINE_DRIVING_REFUSE_MESSAGE,
|
|
203
|
-
PIPELINE_TOKENS,
|
|
204
|
-
detectImplementHeavy,
|
|
205
|
-
detectPipelineDriving,
|
|
206
|
-
evaluateDogfoodGate,
|
|
207
|
-
hasMutatingFixMode,
|
|
208
|
-
isImplementHeavyStep,
|
|
209
|
-
parseCliArgs,
|
|
210
|
-
runCli
|
|
211
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* detect-pipeline-driving — live Phase 1.0 CLI gate over the @1.2 detector (task 0277 W7).
|
|
3
|
-
*
|
|
4
|
-
* The detector and its policy helpers are application-owned in
|
|
5
|
-
* `plugins/sp/lib/dogfood-pipeline.ts` (task 1173); this script keeps the shell entry the dogfood
|
|
6
|
-
* driver calls, with argv, usage, exit codes and refusal messages unchanged. Callers and tests may
|
|
7
|
-
* keep importing this path — every former export is re-exported.
|
|
8
|
-
*
|
|
9
|
-
* bun plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts \
|
|
10
|
-
* --testee "/sp:dev-run 0125 --auto --next" [--max-retry-present] \
|
|
11
|
-
* [--steps "step1||step2"] [--json]
|
|
12
|
-
*
|
|
13
|
-
* Exit codes:
|
|
14
|
-
* 0 — proceed (may print implement-heavy advisory on stderr)
|
|
15
|
-
* 2 — refuse: pipeline-driving without explicit --max-retry
|
|
16
|
-
* 1 — usage / argument error
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { evaluateDogfoodGate } from '../../lib/dogfood-pipeline';
|
|
20
|
-
|
|
21
|
-
export * from '../../lib/dogfood-pipeline';
|
|
22
|
-
|
|
23
|
-
export interface CliArgs {
|
|
24
|
-
testee: string | null;
|
|
25
|
-
maxRetryPresent: boolean;
|
|
26
|
-
steps: string[];
|
|
27
|
-
json: boolean;
|
|
28
|
-
help: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Parse CLI argv (everything after the script name). Exported for unit tests. */
|
|
32
|
-
export function parseCliArgs(argv: string[]): CliArgs {
|
|
33
|
-
let testee: string | null = null;
|
|
34
|
-
let maxRetryPresent = false;
|
|
35
|
-
let steps: string[] = [];
|
|
36
|
-
let json = false;
|
|
37
|
-
let help = false;
|
|
38
|
-
|
|
39
|
-
for (let i = 0; i < argv.length; i++) {
|
|
40
|
-
const a = argv[i];
|
|
41
|
-
if (a === undefined) continue;
|
|
42
|
-
if (a === '--help' || a === '-h') {
|
|
43
|
-
help = true;
|
|
44
|
-
} else if (a === '--json') {
|
|
45
|
-
json = true;
|
|
46
|
-
} else if (a === '--max-retry-present') {
|
|
47
|
-
maxRetryPresent = true;
|
|
48
|
-
} else if (a === '--testee') {
|
|
49
|
-
testee = argv[++i] ?? null;
|
|
50
|
-
} else if (a === '--steps') {
|
|
51
|
-
const raw = argv[++i] ?? '';
|
|
52
|
-
steps =
|
|
53
|
-
raw.length === 0
|
|
54
|
-
? []
|
|
55
|
-
: raw
|
|
56
|
-
.split('||')
|
|
57
|
-
.map((s) => s.trim())
|
|
58
|
-
.filter(Boolean);
|
|
59
|
-
} else if (a === '--') {
|
|
60
|
-
testee = argv.slice(i + 1).join(' ');
|
|
61
|
-
break;
|
|
62
|
-
} else if (!a.startsWith('-') && testee === null) {
|
|
63
|
-
testee = a;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { testee, maxRetryPresent, steps, json, help };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const CLI_USAGE = `Usage:
|
|
71
|
-
bun plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts \\
|
|
72
|
-
--testee "<testee string>" [--max-retry-present] [--steps "s1||s2"] [--json]
|
|
73
|
-
|
|
74
|
-
Exit codes:
|
|
75
|
-
0 proceed (stdout may carry implement-heavy advisory)
|
|
76
|
-
2 refuse — pipeline-driving OR mutating --fix mode without --max-retry
|
|
77
|
-
1 usage error
|
|
78
|
-
|
|
79
|
-
Phase 1.0: run BEFORE deriving steps (omit --steps).
|
|
80
|
-
Phase 1 W8: re-run AFTER step derivation with --steps "label1||label2".`;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Pure CLI runner for tests: returns { exitCode, stdout, stderr } without
|
|
84
|
-
* process.exit / console I/O side effects.
|
|
85
|
-
*/
|
|
86
|
-
export function runCli(argv: string[]): { exitCode: number; stdout: string; stderr: string } {
|
|
87
|
-
const { testee, maxRetryPresent, steps, json, help } = parseCliArgs(argv);
|
|
88
|
-
if (help) {
|
|
89
|
-
return { exitCode: 0, stdout: '', stderr: CLI_USAGE };
|
|
90
|
-
}
|
|
91
|
-
if (testee === null || testee.length === 0) {
|
|
92
|
-
return { exitCode: 1, stdout: '', stderr: CLI_USAGE };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const result = evaluateDogfoodGate(testee, { maxRetryPresent, steps });
|
|
96
|
-
if (json) {
|
|
97
|
-
return { exitCode: result.exitCode, stdout: `${JSON.stringify(result, null, 2)}\n`, stderr: '' };
|
|
98
|
-
}
|
|
99
|
-
if (result.message) {
|
|
100
|
-
return { exitCode: result.exitCode, stdout: `${result.message}\n`, stderr: '' };
|
|
101
|
-
}
|
|
102
|
-
return { exitCode: result.exitCode, stdout: '', stderr: '' };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (import.meta.main) {
|
|
106
|
-
const { exitCode, stdout, stderr } = runCli(process.argv.slice(2));
|
|
107
|
-
if (stdout) process.stdout.write(stdout);
|
|
108
|
-
if (stderr) process.stderr.write(`${stderr}\n`);
|
|
109
|
-
process.exit(exitCode);
|
|
110
|
-
}
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
// plugins/sp/scripts/dogfood-testing/validate-report.ts
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
7
|
-
|
|
8
|
-
// plugins/sp/lib/dogfood-report.ts
|
|
9
|
-
var REQUIRED_SECTIONS = [1, 2, 3, 4, 5, 6];
|
|
10
|
-
var CANONICAL_PROTOCOL = "sp:dogfood-testing@1.2";
|
|
11
|
-
function countSectionHeadings(markdown, section) {
|
|
12
|
-
const re = new RegExp(`^### ${section}\\.`, "gm");
|
|
13
|
-
return (markdown.match(re) ?? []).length;
|
|
14
|
-
}
|
|
15
|
-
function countLedgerDataRows(markdown) {
|
|
16
|
-
const heading = markdown.match(/^### 3\. Monitor Ledger\s*$/m);
|
|
17
|
-
if (!heading || heading.index === undefined)
|
|
18
|
-
return null;
|
|
19
|
-
const after = markdown.slice(heading.index + heading[0].length);
|
|
20
|
-
const nextHeading = after.search(/^### /m);
|
|
21
|
-
const body = nextHeading === -1 ? after : after.slice(0, nextHeading);
|
|
22
|
-
const rows = body.split(`
|
|
23
|
-
`).filter((line) => line.trim().startsWith("|")).filter((line) => !/^\|[\s:|-]+\|?\s*$/.test(line.trim())).filter((line) => !/^\|\s*`?drift:/.test(line.trim()));
|
|
24
|
-
return Math.max(rows.length - 1, 0);
|
|
25
|
-
}
|
|
26
|
-
function declaredExecutedSteps(markdown) {
|
|
27
|
-
const match = markdown.match(/\*\*Steps:\*\*\s*\d+\s+derived,\s*(\d+)\s+executed/)?.[1];
|
|
28
|
-
return match ? Number.parseInt(match, 10) : null;
|
|
29
|
-
}
|
|
30
|
-
var UNKNOWN_CELLS = new Set(["~unknown", "unknown", "—", "-", "?", "n/a", "~n/a"]);
|
|
31
|
-
var CACHE_SHARE_TOLERANCE = 1;
|
|
32
|
-
function sectionBody(markdown, headingRe) {
|
|
33
|
-
const heading = markdown.match(headingRe);
|
|
34
|
-
if (!heading || heading.index === undefined)
|
|
35
|
-
return null;
|
|
36
|
-
const after = markdown.slice(heading.index + heading[0].length);
|
|
37
|
-
const nextHeading = after.search(/^###? /m);
|
|
38
|
-
return nextHeading === -1 ? after : after.slice(0, nextHeading);
|
|
39
|
-
}
|
|
40
|
-
function parseTokensLine(line) {
|
|
41
|
-
const match = line.match(/~(?<total>n\/a|\d+)\s+total\s*\|\s*~(?<cached>n\/a|\d+)\s+cached(?:\s*\(\s*~?(?<pct>n\/a|\d+(?:\.\d+)?)%\s*hit rate\))?/i);
|
|
42
|
-
if (!match)
|
|
43
|
-
return null;
|
|
44
|
-
const num = (v) => v === undefined ? null : v === "n/a" ? "n/a" : Number.parseInt(v, 10);
|
|
45
|
-
return { total: num(match.groups?.total), cached: num(match.groups?.cached), pct: num(match.groups?.pct) };
|
|
46
|
-
}
|
|
47
|
-
function parseTokenCell(cell) {
|
|
48
|
-
const t = cell.trim();
|
|
49
|
-
if (UNKNOWN_CELLS.has(t))
|
|
50
|
-
return null;
|
|
51
|
-
const m = t.match(/~?(\d+)/)?.[1];
|
|
52
|
-
return m ? Number.parseInt(m, 10) : null;
|
|
53
|
-
}
|
|
54
|
-
function parsePctCell(cell) {
|
|
55
|
-
const t = cell.trim();
|
|
56
|
-
if (UNKNOWN_CELLS.has(t))
|
|
57
|
-
return null;
|
|
58
|
-
const m = t.match(/(\d+(?:\.\d+)?)\s*%/)?.[1];
|
|
59
|
-
return m ? Number.parseFloat(m) : null;
|
|
60
|
-
}
|
|
61
|
-
function ledgerCostRows(markdown) {
|
|
62
|
-
const body = sectionBody(markdown, /^### 3\. Monitor Ledger\s*$/m);
|
|
63
|
-
if (body === null)
|
|
64
|
-
return [];
|
|
65
|
-
return body.split(`
|
|
66
|
-
`).filter((line) => line.trim().startsWith("|")).filter((line) => !/^\|[\s:|-]+\|?\s*$/.test(line.trim())).filter((line) => !/^\|\s*`?drift:/.test(line.trim())).slice(1).map((line) => {
|
|
67
|
-
const cells = line.split("|").map((c) => c.trim()).filter((c, i, a) => !(i === 0 && c === "") && !(i === a.length - 1 && c === ""));
|
|
68
|
-
const freshCell = cells[5] ?? "";
|
|
69
|
-
const cachedCell = cells[6] ?? "";
|
|
70
|
-
return {
|
|
71
|
-
step: cells[0] ?? "",
|
|
72
|
-
fresh: parseTokenCell(freshCell),
|
|
73
|
-
cached: parseTokenCell(cachedCell),
|
|
74
|
-
cachePct: parsePctCell(cells[7] ?? ""),
|
|
75
|
-
unknownCell: UNKNOWN_CELLS.has(freshCell) || UNKNOWN_CELLS.has(cachedCell)
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
function validateCostEvidence(markdown, errors) {
|
|
80
|
-
const costBlock = sectionBody(markdown, /^#### Cost\s*$/m);
|
|
81
|
-
if (costBlock === null) {
|
|
82
|
-
errors.push("missing_cost_block");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
for (const field of ["Ledger estimate", "Method", "Meter"]) {
|
|
86
|
-
if (!costBlock.includes(`**${field}:**`))
|
|
87
|
-
errors.push(`missing_cost_field:${field}`);
|
|
88
|
-
}
|
|
89
|
-
const ledgerLine = costBlock.split(`
|
|
90
|
-
`).find((l) => l.includes("**Ledger estimate:**")) ?? "";
|
|
91
|
-
const costTotals = parseTokensLine(ledgerLine);
|
|
92
|
-
if (ledgerLine !== "" && costTotals === null)
|
|
93
|
-
errors.push("malformed_cost_line");
|
|
94
|
-
const footerLine = markdown.split(`
|
|
95
|
-
`).find((l) => l.trim().startsWith("Tokens:"));
|
|
96
|
-
const footerTotals = footerLine ? parseTokensLine(footerLine) : null;
|
|
97
|
-
if (footerLine !== undefined && footerTotals === null)
|
|
98
|
-
errors.push("malformed_footer");
|
|
99
|
-
if (footerLine === undefined && markdown.includes("── Dogfood Summary ──")) {
|
|
100
|
-
errors.push("missing_footer_tokens");
|
|
101
|
-
}
|
|
102
|
-
const pctScopes = [costBlock, footerLine ?? ""];
|
|
103
|
-
for (const row of ledgerCostRows(markdown)) {
|
|
104
|
-
if (row.cachePct !== null)
|
|
105
|
-
pctScopes.push(`${row.cachePct}%`);
|
|
106
|
-
}
|
|
107
|
-
for (const scope of pctScopes) {
|
|
108
|
-
for (const m of scope.matchAll(/(\d+(?:\.\d+)?)\s*%/g)) {
|
|
109
|
-
const pct = Number.parseFloat(m[1] ?? "");
|
|
110
|
-
if (pct < 0 || pct > 100)
|
|
111
|
-
errors.push(`impossible_percentage:${m[0].trim()}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const rows = ledgerCostRows(markdown);
|
|
115
|
-
for (const row of rows) {
|
|
116
|
-
if (row.unknownCell && row.cachePct !== null)
|
|
117
|
-
errors.push("unknown_row_with_numeric_cache");
|
|
118
|
-
}
|
|
119
|
-
for (const row of rows) {
|
|
120
|
-
if (row.fresh === null || row.cached === null || row.cachePct === null)
|
|
121
|
-
continue;
|
|
122
|
-
const basis = row.fresh + row.cached;
|
|
123
|
-
if (basis === 0)
|
|
124
|
-
continue;
|
|
125
|
-
const expected = Math.round(row.cached / basis * 100);
|
|
126
|
-
if (Math.abs(row.cachePct - expected) > CACHE_SHARE_TOLERANCE) {
|
|
127
|
-
errors.push(`cache_pct_mismatch:${row.step}_expected_${expected}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const observable = rows.filter((r) => r.fresh !== null && r.cached !== null);
|
|
131
|
-
const sumFresh = observable.reduce((acc, r) => acc + (r.fresh ?? 0), 0);
|
|
132
|
-
const sumCached = observable.reduce((acc, r) => acc + (r.cached ?? 0), 0);
|
|
133
|
-
const checkTotals = (label, totals) => {
|
|
134
|
-
if (totals === null)
|
|
135
|
-
return;
|
|
136
|
-
if (observable.length > 0) {
|
|
137
|
-
const expectedTotal = sumFresh + sumCached;
|
|
138
|
-
if (typeof totals.total === "number" && totals.total !== expectedTotal) {
|
|
139
|
-
errors.push(`cost_total_mismatch:${label}_expected_${expectedTotal}`);
|
|
140
|
-
}
|
|
141
|
-
if (typeof totals.cached === "number" && totals.cached !== sumCached) {
|
|
142
|
-
errors.push(`cost_total_mismatch:${label}_cached_expected_${sumCached}`);
|
|
143
|
-
}
|
|
144
|
-
const expectedPct = expectedTotal > 0 ? Math.round(sumCached / expectedTotal * 100) : null;
|
|
145
|
-
if (expectedPct !== null) {
|
|
146
|
-
if (totals.pct === "n/a" || totals.pct === null) {
|
|
147
|
-
errors.push(`missing_cache_share:${label}`);
|
|
148
|
-
} else if (typeof totals.pct === "number" && Math.abs(totals.pct - expectedPct) > CACHE_SHARE_TOLERANCE) {
|
|
149
|
-
errors.push(`cache_share_mismatch:${label}_expected_${expectedPct}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} else if (typeof totals.total === "number" || typeof totals.cached === "number" || typeof totals.pct === "number") {
|
|
153
|
-
errors.push(`fabricated_cache_share:${label}`);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
checkTotals("cost", costTotals);
|
|
157
|
-
checkTotals("footer", footerTotals);
|
|
158
|
-
}
|
|
159
|
-
function validateReport(markdown) {
|
|
160
|
-
const errors = [];
|
|
161
|
-
if (!markdown.includes("── Dogfood Summary ──"))
|
|
162
|
-
errors.push("missing_footer");
|
|
163
|
-
if (!markdown.includes("[Live:"))
|
|
164
|
-
errors.push("missing_live_path");
|
|
165
|
-
if (!markdown.includes("[Report:"))
|
|
166
|
-
errors.push("missing_report_path");
|
|
167
|
-
for (const section of REQUIRED_SECTIONS) {
|
|
168
|
-
const count = countSectionHeadings(markdown, section);
|
|
169
|
-
if (count === 0)
|
|
170
|
-
errors.push(`missing_section:${section}`);
|
|
171
|
-
if (count > 1)
|
|
172
|
-
errors.push(`duplicate_section:${section}`);
|
|
173
|
-
}
|
|
174
|
-
if (!markdown.includes("#### Fixed") || !markdown.includes("#### Unresolved")) {
|
|
175
|
-
errors.push("missing_issues_subheads");
|
|
176
|
-
}
|
|
177
|
-
const protocolMatch = markdown.match(/^protocol:\s*(\S+)\s*$/m);
|
|
178
|
-
if (!protocolMatch || protocolMatch[1] !== CANONICAL_PROTOCOL) {
|
|
179
|
-
errors.push("protocol_string");
|
|
180
|
-
}
|
|
181
|
-
const executed = declaredExecutedSteps(markdown);
|
|
182
|
-
if (executed === null) {
|
|
183
|
-
errors.push("missing_steps_declared");
|
|
184
|
-
} else {
|
|
185
|
-
const rows = countLedgerDataRows(markdown);
|
|
186
|
-
if (rows === null || rows !== executed) {
|
|
187
|
-
errors.push("ledger_cardinality");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
validateCostEvidence(markdown, errors);
|
|
191
|
-
return { ok: errors.length === 0, errors };
|
|
192
|
-
}
|
|
193
|
-
// plugins/sp/scripts/dogfood-testing/validate-report.ts
|
|
194
|
-
function parseValidateCliArgs(argv) {
|
|
195
|
-
let file = null;
|
|
196
|
-
let json = false;
|
|
197
|
-
let help = false;
|
|
198
|
-
for (let i = 0;i < argv.length; i++) {
|
|
199
|
-
const a = argv[i];
|
|
200
|
-
if (a === undefined)
|
|
201
|
-
continue;
|
|
202
|
-
if (a === "--help" || a === "-h")
|
|
203
|
-
help = true;
|
|
204
|
-
else if (a === "--json")
|
|
205
|
-
json = true;
|
|
206
|
-
else if (a === "--file")
|
|
207
|
-
file = argv[++i] ?? null;
|
|
208
|
-
else if (!a.startsWith("-") && file === null)
|
|
209
|
-
file = a;
|
|
210
|
-
}
|
|
211
|
-
return { file, json, help };
|
|
212
|
-
}
|
|
213
|
-
var VALIDATE_CLI_USAGE = `Usage:
|
|
214
|
-
bun plugins/sp/scripts/dogfood-testing/validate-report.ts --file <report.md> [--json]
|
|
215
|
-
|
|
216
|
-
Exit codes:
|
|
217
|
-
0 report validates clean (complete-report shape)
|
|
218
|
-
2 validation failed (errors on stdout / --json)
|
|
219
|
-
1 usage error
|
|
220
|
-
|
|
221
|
-
Cost evidence (task 0913): a complete report must carry the Cost block
|
|
222
|
-
(Ledger estimate / Method / Meter), percentages within 0..100, totals equal to
|
|
223
|
-
the ledger sums of observable rows (fresh + cached), and 'n/a' (never a
|
|
224
|
-
fabricated number) when no observable rows exist. Display-rounding tolerance
|
|
225
|
-
for the cache share is ±1 percentage point.
|
|
226
|
-
|
|
227
|
-
Phase 4 finalize MUST run this before status: complete (task 0278 R6).
|
|
228
|
-
On exit 2: set status: aborted and list error codes under #### Unresolved.`;
|
|
229
|
-
function runValidateCli(argv, readFile) {
|
|
230
|
-
const { file, json, help } = parseValidateCliArgs(argv);
|
|
231
|
-
if (help)
|
|
232
|
-
return { exitCode: 0, stdout: "", stderr: VALIDATE_CLI_USAGE };
|
|
233
|
-
if (file === null || file.length === 0) {
|
|
234
|
-
return { exitCode: 1, stdout: "", stderr: VALIDATE_CLI_USAGE };
|
|
235
|
-
}
|
|
236
|
-
let markdown;
|
|
237
|
-
try {
|
|
238
|
-
markdown = readFile(file);
|
|
239
|
-
} catch (err) {
|
|
240
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
241
|
-
return { exitCode: 1, stdout: "", stderr: `Failed to read ${file}: ${msg}` };
|
|
242
|
-
}
|
|
243
|
-
const result = validateReport(markdown);
|
|
244
|
-
if (json) {
|
|
245
|
-
return {
|
|
246
|
-
exitCode: result.ok ? 0 : 2,
|
|
247
|
-
stdout: `${JSON.stringify(result, null, 2)}
|
|
248
|
-
`,
|
|
249
|
-
stderr: ""
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
if (result.ok) {
|
|
253
|
-
return { exitCode: 0, stdout: `ok
|
|
254
|
-
`, stderr: "" };
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
exitCode: 2,
|
|
258
|
-
stdout: `${result.errors.join(`
|
|
259
|
-
`)}
|
|
260
|
-
`,
|
|
261
|
-
stderr: ""
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function mainCli(argv = process.argv.slice(2)) {
|
|
265
|
-
const { exitCode, stdout, stderr } = runValidateCli(argv, (p) => readFileSync(p, "utf8"));
|
|
266
|
-
if (stdout)
|
|
267
|
-
process.stdout.write(stdout);
|
|
268
|
-
if (stderr)
|
|
269
|
-
process.stderr.write(`${stderr}
|
|
270
|
-
`);
|
|
271
|
-
return exitCode;
|
|
272
|
-
}
|
|
273
|
-
{
|
|
274
|
-
process.exit(mainCli());
|
|
275
|
-
}
|
|
276
|
-
export {
|
|
277
|
-
VALIDATE_CLI_USAGE,
|
|
278
|
-
mainCli,
|
|
279
|
-
parseValidateCliArgs,
|
|
280
|
-
runValidateCli,
|
|
281
|
-
validateReport
|
|
282
|
-
};
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* validate-report — CLI entry for the @1.2 dogfood report contract checker (task 0276 W6, 0278 R6).
|
|
3
|
-
*
|
|
4
|
-
* The validation logic is application-owned in `plugins/sp/lib/dogfood-report.ts` (task 1173); this
|
|
5
|
-
* script opens the file, runs the check and prints — argv, usage, exit codes and every exported
|
|
6
|
-
* symbol stay as they were, so callers and tests keep importing this path.
|
|
7
|
-
*
|
|
8
|
-
* CLI (task 0278 R6): `bun …/validate-report.ts --file <report.md> [--json]`
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { readFileSync } from 'node:fs';
|
|
12
|
-
import { validateReport } from '../../lib/dogfood-report';
|
|
13
|
-
|
|
14
|
-
export * from '../../lib/dogfood-report';
|
|
15
|
-
|
|
16
|
-
export interface ValidateCliArgs {
|
|
17
|
-
file: string | null;
|
|
18
|
-
json: boolean;
|
|
19
|
-
help: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function parseValidateCliArgs(argv: string[]): ValidateCliArgs {
|
|
23
|
-
let file: string | null = null;
|
|
24
|
-
let json = false;
|
|
25
|
-
let help = false;
|
|
26
|
-
for (let i = 0; i < argv.length; i++) {
|
|
27
|
-
const a = argv[i];
|
|
28
|
-
if (a === undefined) continue;
|
|
29
|
-
if (a === '--help' || a === '-h') help = true;
|
|
30
|
-
else if (a === '--json') json = true;
|
|
31
|
-
else if (a === '--file') file = argv[++i] ?? null;
|
|
32
|
-
else if (!a.startsWith('-') && file === null) file = a;
|
|
33
|
-
}
|
|
34
|
-
return { file, json, help };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const VALIDATE_CLI_USAGE = `Usage:
|
|
38
|
-
bun plugins/sp/scripts/dogfood-testing/validate-report.ts --file <report.md> [--json]
|
|
39
|
-
|
|
40
|
-
Exit codes:
|
|
41
|
-
0 report validates clean (complete-report shape)
|
|
42
|
-
2 validation failed (errors on stdout / --json)
|
|
43
|
-
1 usage error
|
|
44
|
-
|
|
45
|
-
Cost evidence (task 0913): a complete report must carry the Cost block
|
|
46
|
-
(Ledger estimate / Method / Meter), percentages within 0..100, totals equal to
|
|
47
|
-
the ledger sums of observable rows (fresh + cached), and 'n/a' (never a
|
|
48
|
-
fabricated number) when no observable rows exist. Display-rounding tolerance
|
|
49
|
-
for the cache share is ±1 percentage point.
|
|
50
|
-
|
|
51
|
-
Phase 4 finalize MUST run this before status: complete (task 0278 R6).
|
|
52
|
-
On exit 2: set status: aborted and list error codes under #### Unresolved.`;
|
|
53
|
-
|
|
54
|
-
export function runValidateCli(
|
|
55
|
-
argv: string[],
|
|
56
|
-
readFile: (path: string) => string,
|
|
57
|
-
): { exitCode: number; stdout: string; stderr: string } {
|
|
58
|
-
const { file, json, help } = parseValidateCliArgs(argv);
|
|
59
|
-
if (help) return { exitCode: 0, stdout: '', stderr: VALIDATE_CLI_USAGE };
|
|
60
|
-
if (file === null || file.length === 0) {
|
|
61
|
-
return { exitCode: 1, stdout: '', stderr: VALIDATE_CLI_USAGE };
|
|
62
|
-
}
|
|
63
|
-
let markdown: string;
|
|
64
|
-
try {
|
|
65
|
-
markdown = readFile(file);
|
|
66
|
-
} catch (err) {
|
|
67
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
-
return { exitCode: 1, stdout: '', stderr: `Failed to read ${file}: ${msg}` };
|
|
69
|
-
}
|
|
70
|
-
const result = validateReport(markdown);
|
|
71
|
-
if (json) {
|
|
72
|
-
return {
|
|
73
|
-
exitCode: result.ok ? 0 : 2,
|
|
74
|
-
stdout: `${JSON.stringify(result, null, 2)}\n`,
|
|
75
|
-
stderr: '',
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
if (result.ok) {
|
|
79
|
-
return { exitCode: 0, stdout: 'ok\n', stderr: '' };
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
exitCode: 2,
|
|
83
|
-
stdout: `${result.errors.join('\n')}\n`,
|
|
84
|
-
stderr: '',
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** CLI entry for Phase 4 self-validate (import.meta.main). */
|
|
89
|
-
export function mainCli(argv: string[] = process.argv.slice(2)): number {
|
|
90
|
-
const { exitCode, stdout, stderr } = runValidateCli(argv, (p) => readFileSync(p, 'utf8'));
|
|
91
|
-
if (stdout) process.stdout.write(stdout);
|
|
92
|
-
if (stderr) process.stderr.write(`${stderr}\n`);
|
|
93
|
-
return exitCode;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (import.meta.main) {
|
|
97
|
-
process.exit(mainCli());
|
|
98
|
-
}
|