@gobing-ai/spur 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +2 -2
  3. package/config/rules/boundary/config-loading-ownership.yaml +1 -0
  4. package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
  5. package/config/rules/boundary/sp-script-placement.yaml +1 -1
  6. package/config/rules/docs/sourced-api-claims.yaml +45 -7
  7. package/config/rules/strict/runtime-boundaries.yaml +40 -1
  8. package/config/rules/structure/protected-files.yaml +1 -1
  9. package/config/rules/surface/check-cli-surface.yaml +33 -0
  10. package/config/rules/typescript/no-debugger.yaml +3 -0
  11. package/config/workflow-candidates.json +2 -2
  12. package/config/workflows/feature-verification.yaml +6 -11
  13. package/config/workflows/history-anatomy.yaml +14 -28
  14. package/config/workflows/idea-pipeline.yaml +6 -19
  15. package/config/workflows/pr-review.yaml +9 -16
  16. package/config/workflows/task-pipeline.yaml +16 -23
  17. package/config/workflows/wrapup-pipeline.yaml +21 -63
  18. package/package.json +3 -3
  19. package/plugins/sp/README.md +70 -37
  20. package/plugins/sp/agents/super-planner.md +5 -5
  21. package/plugins/sp/agents/super-reviewer.md +2 -2
  22. package/plugins/sp/commands/dev-changelog.md +1 -1
  23. package/plugins/sp/commands/dev-daily.md +1 -1
  24. package/plugins/sp/commands/dev-pr-review.md +1 -1
  25. package/plugins/sp/commands/dev-review-session.md +1 -1
  26. package/plugins/sp/commands/dev-runall.md +4 -4
  27. package/plugins/sp/commands/dev-verify.md +4 -6
  28. package/plugins/sp/commands/dev-verifyall.md +4 -5
  29. package/plugins/sp/hooks/hooks.json +11 -1
  30. package/plugins/sp/lib/context-post-tool.ts +1 -1
  31. package/plugins/sp/lib/context-session-start.ts +1 -1
  32. package/plugins/sp/lib/context-session-stop.ts +1 -1
  33. package/plugins/sp/lib/destructive-policy.ts +65 -1
  34. package/plugins/sp/plugin.json +2 -5
  35. package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
  36. package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
  37. package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
  38. package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
  39. package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
  40. package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
  41. package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
  42. package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
  43. package/plugins/sp/skills/code-verification/SKILL.md +8 -8
  44. package/plugins/sp/skills/code-verification/references/secu-review.md +4 -3
  45. package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
  46. package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
  47. package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
  48. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  49. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
  50. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  51. package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
  52. package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
  53. package/plugins/sp/skills/session-review/SKILL.md +3 -3
  54. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  55. package/plugins/sp/skills/spur-check/SKILL.md +14 -13
  56. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  57. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  58. package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
  59. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  60. package/plugins/sp/skills/spur-cli/references/history.md +3 -0
  61. package/plugins/sp/skills/spur-cli/references/self.md +5 -3
  62. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
  63. package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
  64. package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
  65. package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
  66. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
  67. package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -12
  68. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
  69. package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
  70. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +8 -11
  72. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
  73. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
  75. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
  76. package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
  77. package/spur.js +41901 -30598
  78. package/config/plugin-scripts.json +0 -116
  79. package/config/script-placement-baseline.json +0 -33
  80. package/plugins/sp/hooks/careful-guard.ts +0 -74
  81. package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
  82. package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
  83. package/plugins/sp/lib/dogfood-report.ts +0 -278
  84. package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
  85. package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
  86. package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
  87. package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
  88. package/plugins/sp/lib/inline-run-app.ts +0 -68
  89. package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
  90. package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
  91. package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
  92. package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
  93. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
  94. package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
  95. package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
  96. package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
  97. package/plugins/sp/lib/run-summary-core.ts +0 -225
  98. package/plugins/sp/lib/spur-bin.ts +0 -36
  99. package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
  100. package/plugins/sp/lib/step-profile.generated.mjs +0 -174
  101. package/plugins/sp/lib/transcript.ts +0 -112
  102. package/plugins/sp/scripts/batch-plan.mjs +0 -1096
  103. package/plugins/sp/scripts/batch-plan.ts +0 -145
  104. package/plugins/sp/scripts/batch-preflight.mjs +0 -423
  105. package/plugins/sp/scripts/batch-preflight.ts +0 -270
  106. package/plugins/sp/scripts/commit-guard.mjs +0 -226
  107. package/plugins/sp/scripts/commit-guard.ts +0 -248
  108. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
  109. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
  110. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
  111. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
  112. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
  113. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
  114. package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
  115. package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
  116. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
  117. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
  118. package/plugins/sp/scripts/idea-handoff.mjs +0 -32
  119. package/plugins/sp/scripts/idea-handoff.ts +0 -47
  120. package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
  121. package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
  122. package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
  123. package/plugins/sp/scripts/inline-run-setup.ts +0 -198
  124. package/plugins/sp/scripts/persist-out-check.mjs +0 -217
  125. package/plugins/sp/scripts/persist-out-check.ts +0 -249
  126. package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
  127. package/plugins/sp/scripts/pr-reviewing.ts +0 -952
  128. package/plugins/sp/scripts/quality-gate.mjs +0 -30491
  129. package/plugins/sp/scripts/quality-gate.ts +0 -80
  130. package/plugins/sp/scripts/residual-scan.mjs +0 -634
  131. package/plugins/sp/scripts/residual-scan.ts +0 -248
  132. package/plugins/sp/scripts/run-summary.mjs +0 -758
  133. package/plugins/sp/scripts/run-summary.ts +0 -107
  134. package/plugins/sp/scripts/script-root.mjs +0 -129
  135. package/plugins/sp/scripts/script-root.ts +0 -199
  136. package/plugins/sp/scripts/session-timeline.mjs +0 -359
  137. package/plugins/sp/scripts/session-timeline.ts +0 -190
  138. package/plugins/sp/scripts/task-diffstat.mjs +0 -181
  139. package/plugins/sp/scripts/task-diffstat.ts +0 -219
  140. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
  141. package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
  142. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  143. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
  144. package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
  145. package/plugins/sp/scripts/wrapup-steps.ts +0 -891
@@ -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
- }