@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.
Files changed (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +2 -2
  3. package/config/rules/README.md +1 -1
  4. package/config/rules/boundary/config-loading-ownership.yaml +1 -0
  5. package/config/rules/boundary/env-var-hygiene.yaml +0 -1
  6. package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
  7. package/config/rules/boundary/sp-script-placement.yaml +3 -3
  8. package/config/rules/docs/sourced-api-claims.yaml +29 -64
  9. package/config/rules/strict/runtime-boundaries.yaml +40 -1
  10. package/config/rules/structure/protected-files.yaml +1 -1
  11. package/config/rules/surface/check-cli-surface.yaml +33 -0
  12. package/config/rules/typescript/no-debugger.yaml +3 -0
  13. package/config/workflow-candidates.json +2 -2
  14. package/config/workflows/feature-verification.yaml +6 -19
  15. package/config/workflows/history-anatomy.yaml +14 -28
  16. package/config/workflows/idea-pipeline.yaml +6 -19
  17. package/config/workflows/pr-review.yaml +9 -16
  18. package/config/workflows/task-pipeline.yaml +18 -25
  19. package/config/workflows/wrapup-pipeline.yaml +21 -63
  20. package/package.json +3 -3
  21. package/plugins/sp/README.md +70 -37
  22. package/plugins/sp/agents/expert-spur.md +0 -1
  23. package/plugins/sp/agents/super-planner.md +5 -5
  24. package/plugins/sp/agents/super-reviewer.md +2 -2
  25. package/plugins/sp/commands/dev-changelog.md +1 -1
  26. package/plugins/sp/commands/dev-daily.md +1 -1
  27. package/plugins/sp/commands/dev-pr-review.md +1 -1
  28. package/plugins/sp/commands/dev-review-session.md +1 -1
  29. package/plugins/sp/commands/dev-runall.md +4 -4
  30. package/plugins/sp/commands/dev-verify.md +4 -6
  31. package/plugins/sp/commands/dev-verifyall.md +4 -5
  32. package/plugins/sp/hooks/hooks.json +11 -1
  33. package/plugins/sp/lib/context-post-tool.ts +1 -1
  34. package/plugins/sp/lib/context-session-start.ts +1 -1
  35. package/plugins/sp/lib/context-session-stop.ts +1 -1
  36. package/plugins/sp/lib/destructive-policy.ts +65 -1
  37. package/plugins/sp/plugin.json +2 -5
  38. package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
  39. package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
  40. package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
  41. package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
  42. package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
  43. package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
  44. package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
  45. package/plugins/sp/skills/brainstorm/SKILL.md +12 -11
  46. package/plugins/sp/skills/brainstorm/agents/openai.yaml +1 -1
  47. package/plugins/sp/skills/brainstorm/examples/ideation-example.md +0 -4
  48. package/plugins/sp/skills/brainstorm/metadata.openclaw +1 -1
  49. package/plugins/sp/skills/brainstorm/references/workflows.md +4 -14
  50. package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
  51. package/plugins/sp/skills/code-verification/SKILL.md +8 -8
  52. package/plugins/sp/skills/code-verification/references/secu-review.md +4 -4
  53. package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
  54. package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
  55. package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
  56. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  57. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
  58. package/plugins/sp/skills/history-anatomy/references/operations.md +3 -4
  59. package/plugins/sp/skills/history-anatomy/references/report-contract.md +2 -3
  60. package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
  61. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  62. package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
  63. package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +9 -10
  64. package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
  65. package/plugins/sp/skills/reverse-engineering/SKILL.md +4 -6
  66. package/plugins/sp/skills/session-review/SKILL.md +13 -29
  67. package/plugins/sp/skills/source-driven-development/SKILL.md +26 -27
  68. package/plugins/sp/skills/spur-check/SKILL.md +14 -13
  69. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  70. package/plugins/sp/skills/spur-cli/references/agent.md +17 -2
  71. package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
  72. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  73. package/plugins/sp/skills/spur-cli/references/history.md +3 -0
  74. package/plugins/sp/skills/spur-cli/references/self.md +5 -3
  75. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
  76. package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
  77. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
  78. package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
  79. package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
  80. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
  81. package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -13
  82. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
  83. package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
  84. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
  85. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +9 -12
  86. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
  87. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  88. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
  89. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
  90. package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
  91. package/spur.js +12726 -1456
  92. package/web/_astro/{BoardApp.CaNvQBD_.js → BoardApp.B5rTVNd_.js} +1 -1
  93. package/web/_astro/{BoardApp.C5jnkkKP.js → BoardApp.BMprmPRc.js} +141 -141
  94. package/web/_astro/{TaskDetail.DGKndhI8.js → TaskDetail.Ddsur8qd.js} +1 -1
  95. package/web/_astro/{arc.DHrwdg3G.js → arc.DLqVVBxP.js} +1 -1
  96. package/web/_astro/{architectureDiagram-3BPJPVTR.pvKpK1s5.js → architectureDiagram-3BPJPVTR.C-Pk3uAi.js} +1 -1
  97. package/web/_astro/{blockDiagram-GPEHLZMM.BQmRcZsS.js → blockDiagram-GPEHLZMM.BRO1i_Nm.js} +1 -1
  98. package/web/_astro/{c4Diagram-AAUBKEIU.CCLjHeH1.js → c4Diagram-AAUBKEIU.BU_cWu6e.js} +1 -1
  99. package/web/_astro/channel.Bs3Illhy.js +1 -0
  100. package/web/_astro/{chunk-2J33WTMH.DL6Yuoma.js → chunk-2J33WTMH.D2VXiQ2t.js} +1 -1
  101. package/web/_astro/{chunk-4BX2VUAB.CzlyEI7l.js → chunk-4BX2VUAB.BcHT772J.js} +1 -1
  102. package/web/_astro/{chunk-55IACEB6.B60EWxp1.js → chunk-55IACEB6.DhDtICmo.js} +1 -1
  103. package/web/_astro/{chunk-727SXJPM.c7v_fRLo.js → chunk-727SXJPM.BoKHp7gV.js} +1 -1
  104. package/web/_astro/{chunk-AQP2D5EJ.8BxLWdDc.js → chunk-AQP2D5EJ.Chu4JRpE.js} +1 -1
  105. package/web/_astro/{chunk-FMBD7UC4.DmKuxGFT.js → chunk-FMBD7UC4.wHdwKPlq.js} +1 -1
  106. package/web/_astro/{chunk-ND2GUHAM.1Zwd4mqV.js → chunk-ND2GUHAM.ffN-M1JB.js} +1 -1
  107. package/web/_astro/{chunk-QZHKN3VN.BvYLu-A1.js → chunk-QZHKN3VN.FRubwg4S.js} +1 -1
  108. package/web/_astro/{classDiagram-4FO5ZUOK.CEqRt0WU.js → classDiagram-4FO5ZUOK.c22tQsc_.js} +1 -1
  109. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CEqRt0WU.js → classDiagram-v2-Q7XG4LA2.c22tQsc_.js} +1 -1
  110. package/web/_astro/{cose-bilkent-S5V4N54A.C7JiUdj2.js → cose-bilkent-S5V4N54A.B9c6fiSj.js} +1 -1
  111. package/web/_astro/{cynefin-OW5HDTMX.B6mZ-1be.js → cynefin-OW5HDTMX.DrZsrUHl.js} +1 -1
  112. package/web/_astro/{dagre-BM42HDAG.BuryfDy6.js → dagre-BM42HDAG.DG1U4b3y.js} +1 -1
  113. package/web/_astro/{diagram-2AECGRRQ.dao39YQM.js → diagram-2AECGRRQ.CH15b-K3.js} +1 -1
  114. package/web/_astro/{diagram-5GNKFQAL.lnGZe4At.js → diagram-5GNKFQAL.BH1j7twR.js} +1 -1
  115. package/web/_astro/{diagram-KO2AKTUF.uWfhzEfT.js → diagram-KO2AKTUF.q2zCkbAj.js} +1 -1
  116. package/web/_astro/{diagram-LMA3HP47.ppq8-198.js → diagram-LMA3HP47.BLaaZnHV.js} +1 -1
  117. package/web/_astro/{diagram-OG6HWLK6.CKSCqDt5.js → diagram-OG6HWLK6.BcpRdQwI.js} +1 -1
  118. package/web/_astro/{erDiagram-TEJ5UH35.sFSf2yBN.js → erDiagram-TEJ5UH35.D7KBlqfM.js} +1 -1
  119. package/web/_astro/{flowDiagram-I6XJVG4X.BJQjKLbz.js → flowDiagram-I6XJVG4X.D_gmuRqg.js} +1 -1
  120. package/web/_astro/{ganttDiagram-6RSMTGT7.DAHKNj2N.js → ganttDiagram-6RSMTGT7.DzgWYniD.js} +1 -1
  121. package/web/_astro/{gitGraphDiagram-PVQCEYII.BlmzcW_O.js → gitGraphDiagram-PVQCEYII.576i3hm3.js} +1 -1
  122. package/web/_astro/index.DiC1a6lh.css +1 -0
  123. package/web/_astro/{infoDiagram-5YYISTIA.DGhjaqsi.js → infoDiagram-5YYISTIA.DMCclBiz.js} +1 -1
  124. package/web/_astro/{ishikawaDiagram-YF4QCWOH.gKIpgt6F.js → ishikawaDiagram-YF4QCWOH.9Kyca4SH.js} +1 -1
  125. package/web/_astro/{journeyDiagram-JHISSGLW.DG3tmP0R.js → journeyDiagram-JHISSGLW.chbdG-8q.js} +1 -1
  126. package/web/_astro/{kanban-definition-UN3LZRKU.CgHDSxkR.js → kanban-definition-UN3LZRKU.Bds54-e0.js} +1 -1
  127. package/web/_astro/{linear.BuGEpcuM.js → linear.D5ND9GN_.js} +1 -1
  128. package/web/_astro/{mermaid.core.DBVqkWBp.js → mermaid.core.D9N_PGbj.js} +4 -4
  129. package/web/_astro/{mindmap-definition-RKZ34NQL.D-kOcji9.js → mindmap-definition-RKZ34NQL.t0xop5vp.js} +1 -1
  130. package/web/_astro/{pieDiagram-4H26LBE5.BvGPwtZt.js → pieDiagram-4H26LBE5.DqgKh6hM.js} +1 -1
  131. package/web/_astro/{quadrantDiagram-W4KKPZXB.BKb_9FV6.js → quadrantDiagram-W4KKPZXB.DeuHVi2t.js} +1 -1
  132. package/web/_astro/{requirementDiagram-4Y6WPE33.Cx0fvD8v.js → requirementDiagram-4Y6WPE33.BdYFSG4W.js} +1 -1
  133. package/web/_astro/{sankeyDiagram-5OEKKPKP.fatULi0W.js → sankeyDiagram-5OEKKPKP.k1LPPETJ.js} +1 -1
  134. package/web/_astro/{sequenceDiagram-3UESZ5HK.ChHyKqco.js → sequenceDiagram-3UESZ5HK.DdyfpzoG.js} +1 -1
  135. package/web/_astro/{stateDiagram-AJRCARHV.BdUpKWyj.js → stateDiagram-AJRCARHV.DSEI8il0.js} +1 -1
  136. package/web/_astro/{stateDiagram-v2-BHNVJYJU.fmJxqIm5.js → stateDiagram-v2-BHNVJYJU.CoMnQRKo.js} +1 -1
  137. package/web/_astro/{timeline-definition-PNZ67QCA.BayDn5-V.js → timeline-definition-PNZ67QCA.DM1AHNm-.js} +1 -1
  138. package/web/_astro/{vennDiagram-CIIHVFJN.Bm2Aggwa.js → vennDiagram-CIIHVFJN.C-cJmVtl.js} +1 -1
  139. package/web/_astro/{wardleyDiagram-YWT4CUSO.DI75O-uG.js → wardleyDiagram-YWT4CUSO.D0xMzKPb.js} +1 -1
  140. package/web/_astro/{xychartDiagram-2RQKCTM6.DL90E7Q7.js → xychartDiagram-2RQKCTM6.BH5xJcEq.js} +1 -1
  141. package/web/index.html +2 -2
  142. package/config/plugin-scripts.json +0 -116
  143. package/config/script-placement-baseline.json +0 -33
  144. package/plugins/sp/hooks/careful-guard.ts +0 -74
  145. package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
  146. package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
  147. package/plugins/sp/lib/dogfood-report.ts +0 -278
  148. package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
  149. package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
  150. package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
  151. package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
  152. package/plugins/sp/lib/inline-run-app.ts +0 -68
  153. package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
  154. package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
  155. package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
  156. package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
  157. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
  158. package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
  159. package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
  160. package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
  161. package/plugins/sp/lib/run-summary-core.ts +0 -225
  162. package/plugins/sp/lib/spur-bin.ts +0 -36
  163. package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
  164. package/plugins/sp/lib/step-profile.generated.mjs +0 -174
  165. package/plugins/sp/lib/transcript.ts +0 -112
  166. package/plugins/sp/scripts/batch-plan.mjs +0 -1096
  167. package/plugins/sp/scripts/batch-plan.ts +0 -145
  168. package/plugins/sp/scripts/batch-preflight.mjs +0 -423
  169. package/plugins/sp/scripts/batch-preflight.ts +0 -270
  170. package/plugins/sp/scripts/commit-guard.mjs +0 -226
  171. package/plugins/sp/scripts/commit-guard.ts +0 -248
  172. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
  173. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
  174. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
  175. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
  176. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
  177. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
  178. package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
  179. package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
  180. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
  181. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
  182. package/plugins/sp/scripts/idea-handoff.mjs +0 -32
  183. package/plugins/sp/scripts/idea-handoff.ts +0 -47
  184. package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
  185. package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
  186. package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
  187. package/plugins/sp/scripts/inline-run-setup.ts +0 -198
  188. package/plugins/sp/scripts/persist-out-check.mjs +0 -217
  189. package/plugins/sp/scripts/persist-out-check.ts +0 -249
  190. package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
  191. package/plugins/sp/scripts/pr-reviewing.ts +0 -952
  192. package/plugins/sp/scripts/quality-gate.mjs +0 -30491
  193. package/plugins/sp/scripts/quality-gate.ts +0 -80
  194. package/plugins/sp/scripts/residual-scan.mjs +0 -634
  195. package/plugins/sp/scripts/residual-scan.ts +0 -248
  196. package/plugins/sp/scripts/run-summary.mjs +0 -758
  197. package/plugins/sp/scripts/run-summary.ts +0 -107
  198. package/plugins/sp/scripts/script-root.mjs +0 -129
  199. package/plugins/sp/scripts/script-root.ts +0 -199
  200. package/plugins/sp/scripts/session-timeline.mjs +0 -359
  201. package/plugins/sp/scripts/session-timeline.ts +0 -190
  202. package/plugins/sp/scripts/task-diffstat.mjs +0 -181
  203. package/plugins/sp/scripts/task-diffstat.ts +0 -219
  204. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
  205. package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
  206. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  207. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
  208. package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
  209. package/plugins/sp/scripts/wrapup-steps.ts +0 -891
  210. package/web/_astro/channel.C7wG9RxA.js +0 -1
  211. package/web/_astro/index.BCY1JJHd.css +0 -1
@@ -1,93 +0,0 @@
1
- /**
2
- * GENERATED by scripts/commands/bundle-plugin-lib.ts — do not edit.
3
- * Source of truth: packages/app/src/services/residual-scan.ts (task 1003 R5).
4
- */
5
- export declare type ResidualCategory =
6
- | 'review-finding'
7
- | 'diff-marker'
8
- | 'unchecked-box'
9
- | 'staging-residue'
10
- | 'review-contract';
11
- export declare type ResidualClass = 'blocking' | 'deferrable' | 'advisory' | 'housekeeping';
12
- export declare interface ResidualItem {
13
- id: string;
14
- category: ResidualCategory;
15
- class: ResidualClass;
16
- priority?: string;
17
- location: string;
18
- text: string;
19
- }
20
- export declare interface ResidualArtifact {
21
- wbs: string;
22
- base: string | null;
23
- scanned: Record<ResidualCategory, boolean>;
24
- items: ResidualItem[];
25
- counts: { blocking: number; deferrable: number; advisory: number; housekeeping: number };
26
- }
27
- export declare interface Deferral {
28
- id: string;
29
- reason: string;
30
- }
31
- export declare interface ResidualScanInputs {
32
- wbs: string;
33
- base: string | null;
34
- taskContent: string;
35
- addedLines: Array<{ file: string; line: number; text: string }>;
36
- stagingResidue: string[];
37
- deferrals: Deferral[];
38
- }
39
- export declare interface FoldResult {
40
- verdict: 'PASS' | 'PARTIAL' | 'FAIL';
41
- checks: Array<{ name: string; status: string; evidence: string }>;
42
- findings: string;
43
- }
44
- export declare interface ParsedScanArgs {
45
- mode: string;
46
- wbs: string;
47
- spurBin?: string;
48
- root: string;
49
- tmpDir: string;
50
- }
51
- export declare const ALLOW_PRAGMA: 'residual-scan:allow';
52
- export declare function recordedVerdictPath(runDir: string, wbs: string, fs: Pick<typeof import('node:fs'), 'lstatSync' | 'existsSync'>): string;
53
- export declare function makeItemId(category: ResidualCategory, location: string, text: string): string;
54
- export declare function normalizeAnchor(location: string): string;
55
- export declare function locationOf(locationCell: string, finding: string): string;
56
- export declare function parseReviewFindings(
57
- taskContent: string,
58
- ): Array<{ priority: string; location: string; text: string; deferral?: string }>;
59
- export declare function parseDiffMarkers(
60
- addedLines: Array<{ file: string; line: number; text: string }>,
61
- ): Array<{ location: string; text: string }>;
62
- export declare function findUncheckedBoxes(taskContent: string): Array<{ location: string; text: string }>;
63
- export declare function classify(
64
- items: Array<Omit<ResidualItem, 'id' | 'class'> & { category: ResidualCategory }>,
65
- deferrals: Deferral[],
66
- ): ResidualItem[];
67
- export declare function scanResiduals(inputs: ResidualScanInputs): ResidualArtifact;
68
- export declare function parseDeferralEntries(raw: unknown): Deferral[];
69
- export declare function blockingReviewFindings(taskContent: string, deferrals: Deferral[]): ResidualItem[];
70
- export declare function reviewContractViolations(taskContent: string): Array<{ location: string; text: string }>;
71
- export declare function buildReviewGateArtifact(
72
- wbs: string,
73
- taskContent: string,
74
- deferrals: Deferral[],
75
- ): { artifact: ResidualArtifact; note: string };
76
- export declare function parseScanArgs(
77
- argv: readonly (string | undefined)[],
78
- cwd: string,
79
- defaultTmpDir: string,
80
- ): ParsedScanArgs | null;
81
- export declare function blockingAnchors(items: ResidualItem[]): string[];
82
- export declare function foldVerdict(
83
- verdict: { verdict: string; checks: Array<{ name: string; status: string; evidence: string }> },
84
- scan: ResidualArtifact,
85
- existingFindings: string,
86
- maxFindings?: number,
87
- ): FoldResult;
88
- export declare function renderReport(wbs: string, items: ResidualItem[], attemptCount: number): string;
89
- export declare function verdictDisagreementNote(
90
- runDir: string,
91
- wbs: string,
92
- fs: Pick<typeof import('node:fs'), 'existsSync' | 'readFileSync' | 'statSync'>,
93
- ): string | null;
@@ -1,383 +0,0 @@
1
- // packages/app/src/services/residual-scan.ts
2
- import { join } from "node:path";
3
- import { createHash } from "node:crypto";
4
-
5
- // packages/app/src/services/verify-answer-lint.ts
6
- var CLAIM_MARKERS = [
7
- /@[a-z0-9._-]+\/[a-z0-9._-]+/i,
8
- /\bnode_modules\//,
9
- /\b\d+\.\d+\.\d+\b/,
10
- /`[A-Za-z_$][\w.$]*\s*\([^`]*\)`/
11
- ];
12
- var CITATION_FORMS = [
13
- /`[^`\s]+:\d+(?:-\d+)?`/,
14
- /`[^`]+`\s+lines?\s+\d+/i,
15
- /\b[\w./-]+\.[A-Za-z0-9]+:\d+(?:-\d+)?\b/,
16
- /https?:\/\/\S+/i
17
- ];
18
- function hasClaimMarker(evidence) {
19
- return CLAIM_MARKERS.some((re) => re.test(evidence));
20
- }
21
- function hasCitationForm(evidence) {
22
- return CITATION_FORMS.some((re) => re.test(evidence));
23
- }
24
-
25
- // packages/app/src/services/residual-scan.ts
26
- var MARKER_PATTERN = /TODO|FIXME|XXX|HACK/;
27
- var PRIORITY_PATTERN = /^P[1-4]/;
28
- var RANGE_PRIORITY = /^P[1-4]\s*[\u2013\u2014-]\s*P?[1-4]/;
29
- var NONE_FINDING = /^(none( found)?|no (findings?|issues?)( found)?|\u2014)\s*(\(.*\))?\.?$/i;
30
- var DISPOSITION_HEADER = /^(Disposition|Action|Status|Resolution|Fixed)$/i;
31
- var RESOLVED_DISPOSITION = /^(FIXED|RESOLVED|DONE)\b/i;
32
- var DEFERRED_DISPOSITION = /^DEFER(RED)?\b/i;
33
- var ANCHOR_PATTERN = /[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+/g;
34
- var RANGE_ANCHOR = /([A-Za-z0-9_./-]+\.[A-Za-z]+):([0-9]+)-[0-9]+/g;
35
- var EXCLUDED_PATHS = ["docs/tasks", "docs/features/", ".spur/"];
36
- var ALLOW_PRAGMA = "residual-scan:allow";
37
- function makeItemId(category, location, text) {
38
- const normalized = text.trim().replace(/\s+/g, " ");
39
- const hex = createHash("sha256").update(`${location}${normalized}`).digest("hex");
40
- return `${category}:${hex.slice(0, 8)}`;
41
- }
42
- function normalizeAnchor(location) {
43
- return location.replace(RANGE_ANCHOR, "$1:$2");
44
- }
45
- function locationOf(locationCell, finding) {
46
- const cell = locationCell.trim().replace(/`/g, "");
47
- if (cell.length > 0 && cell !== "—")
48
- return normalizeAnchor(cell);
49
- const backtick = finding.match(/`([^`]+)`/)?.[1];
50
- return backtick === undefined ? "" : normalizeAnchor(backtick);
51
- }
52
- function reviewBody(taskContent) {
53
- const section = taskContent.split(/^### Review\b/m)[1];
54
- return section === undefined ? null : section.split(/^### /m)[0] ?? "";
55
- }
56
- function parseReviewFindings(taskContent) {
57
- const section = reviewBody(taskContent);
58
- if (section === null)
59
- return [];
60
- const body = section;
61
- const out = [];
62
- const lines = body.split(`
63
- `);
64
- for (let i = 0;i < lines.length; i++) {
65
- const line = lines[i];
66
- if (line === undefined || !line.trimStart().startsWith("|"))
67
- continue;
68
- const header = splitRow(line);
69
- const priorityCol = header.findIndex((h) => h.trim() === "Priority");
70
- if (priorityCol === -1) {
71
- while (i + 1 < lines.length && lines[i + 1]?.trimStart().startsWith("|"))
72
- i++;
73
- continue;
74
- }
75
- const findingCol = header.findIndex((h) => h.trim() === "Finding");
76
- const locationCol = header.findIndex((h) => h.trim() === "Location");
77
- const dispositionCol = header.findIndex((h) => DISPOSITION_HEADER.test(h.trim()));
78
- i++;
79
- const sep = lines[i];
80
- if (sep !== undefined && /^\s*\|[\s:|-]+\|\s*$/.test(sep))
81
- i++;
82
- while (i < lines.length) {
83
- const row = lines[i];
84
- if (row === undefined || !row.trimStart().startsWith("|"))
85
- break;
86
- const cells = splitRow(row);
87
- const priority = (cells[priorityCol] ?? "").trim();
88
- const finding = (cells[findingCol] ?? "").trim();
89
- const disposition = dispositionCol === -1 ? "" : (cells[dispositionCol] ?? "").trim();
90
- if (PRIORITY_PATTERN.test(priority) && !RANGE_PRIORITY.test(priority) && !NONE_FINDING.test(finding) && finding.length > 0 && !RESOLVED_DISPOSITION.test(disposition)) {
91
- const location = locationOf(cells[locationCol] ?? "", finding);
92
- out.push(DEFERRED_DISPOSITION.test(disposition) ? { priority, location, text: finding, deferral: disposition } : { priority, location, text: finding });
93
- }
94
- i++;
95
- }
96
- }
97
- return out;
98
- }
99
- function splitRow(line) {
100
- return line.trim().replace(/^\|/, "").replace(/\|$/, "").split(/(?<!\\)\|/).map((c) => c.trim());
101
- }
102
- function parseDiffMarkers(addedLines) {
103
- return addedLines.filter((l) => !EXCLUDED_PATHS.some((p) => l.file.startsWith(p))).filter((l) => !l.text.includes(ALLOW_PRAGMA)).filter((l) => MARKER_PATTERN.test(l.text)).map((l) => ({ location: `${l.file}:${l.line}`, text: l.text.trim() }));
104
- }
105
- function findUncheckedBoxes(taskContent) {
106
- const path = "task-file";
107
- return taskContent.split(`
108
- `).map((text, idx) => ({ text: text.trim(), line: idx + 1 })).filter((l) => l.text.startsWith("- [ ]")).map((l) => ({ location: `${path}:${l.line}`, text: l.text }));
109
- }
110
- function classify(items, deferrals) {
111
- const deferred = new Map(deferrals.map((d) => [d.id, d.reason]));
112
- return items.map((item) => {
113
- const id = makeItemId(item.category, item.location, item.text);
114
- let klass;
115
- if (item.category === "review-finding")
116
- klass = item.priority?.startsWith("P4") ? "advisory" : "blocking";
117
- else if (item.category === "staging-residue")
118
- klass = "housekeeping";
119
- else
120
- klass = "blocking";
121
- if (klass === "blocking" && item.category !== "unchecked-box") {
122
- const p3Like = item.category === "diff-marker" || item.category === "review-finding" && (item.priority ?? "").startsWith("P3");
123
- const reason = deferred.get(id);
124
- if (p3Like && reason !== undefined && reason.trim().length > 0)
125
- klass = "deferrable";
126
- }
127
- return {
128
- id,
129
- category: item.category,
130
- class: klass,
131
- priority: item.priority,
132
- location: item.location,
133
- text: item.text
134
- };
135
- });
136
- }
137
- function scanResiduals(inputs) {
138
- const { wbs, base, taskContent, addedLines, stagingResidue, deferrals } = inputs;
139
- const reviewRows = parseReviewFindings(taskContent);
140
- const tableDeferrals = reviewRows.flatMap((r) => r.deferral === undefined ? [] : [{ id: makeItemId("review-finding", r.location, r.text), reason: r.deferral }]);
141
- const review = reviewRows.map((r) => ({
142
- category: "review-finding",
143
- priority: r.priority,
144
- location: r.location,
145
- text: r.text
146
- }));
147
- const markers = base === null ? [] : parseDiffMarkers(addedLines).map((m) => ({
148
- category: "diff-marker",
149
- location: m.location,
150
- text: m.text
151
- }));
152
- const boxes = findUncheckedBoxes(taskContent).map((b) => ({
153
- category: "unchecked-box",
154
- location: b.location,
155
- text: b.text
156
- }));
157
- const residue = stagingResidue.map((p) => ({
158
- category: "staging-residue",
159
- location: p,
160
- text: p
161
- }));
162
- const items = classify([...review, ...markers, ...boxes, ...residue], [...tableDeferrals, ...deferrals]);
163
- const counts = { blocking: 0, deferrable: 0, advisory: 0, housekeeping: 0 };
164
- for (const item of items)
165
- counts[item.class]++;
166
- return {
167
- wbs,
168
- base,
169
- scanned: {
170
- "review-finding": true,
171
- "diff-marker": base !== null,
172
- "unchecked-box": true,
173
- "staging-residue": true,
174
- "review-contract": false
175
- },
176
- items,
177
- counts
178
- };
179
- }
180
- function parseDeferralEntries(raw) {
181
- const isDeferral = (e) => typeof e === "object" && e !== null && typeof e.id === "string" && typeof e.reason === "string" && e.reason.trim().length > 0;
182
- return (Array.isArray(raw) ? raw : []).filter(isDeferral);
183
- }
184
- function blockingReviewFindings(taskContent, deferrals) {
185
- const rows = parseReviewFindings(taskContent);
186
- const tableDeferrals = rows.flatMap((r) => r.deferral === undefined ? [] : [{ id: makeItemId("review-finding", r.location, r.text), reason: r.deferral }]);
187
- const items = rows.map((r) => ({
188
- category: "review-finding",
189
- priority: r.priority,
190
- location: r.location,
191
- text: r.text
192
- }));
193
- return classify(items, [...tableDeferrals, ...deferrals]);
194
- }
195
- function reviewContractViolations(taskContent) {
196
- const violations = [];
197
- for (const row of parseReviewFindings(taskContent)) {
198
- if (hasClaimMarker(row.text) && !hasCitationForm(row.text)) {
199
- violations.push({
200
- location: row.location.length > 0 ? row.location : "task-file:Review",
201
- text: `${row.priority} finding asserts an external API/library/version claim without a ` + "same-cell citation (add a repo `path:line`, an external named-origin form, or a URL " + `to the Finding cell): ${row.text.slice(0, 80)}`
202
- });
203
- }
204
- }
205
- return violations;
206
- }
207
- function buildReviewGateArtifact(wbs, taskContent, deferrals) {
208
- const contractViolations = reviewContractViolations(taskContent);
209
- const items = [
210
- ...blockingReviewFindings(taskContent, deferrals),
211
- ...contractViolations.map((v) => ({
212
- id: makeItemId("review-contract", v.location, v.text),
213
- category: "review-contract",
214
- class: "blocking",
215
- location: v.location,
216
- text: v.text
217
- }))
218
- ];
219
- const blocking = items.filter((i) => i.class === "blocking");
220
- const counts = { blocking: 0, deferrable: 0, advisory: 0, housekeeping: 0 };
221
- for (const item of items)
222
- counts[item.class]++;
223
- const artifact = {
224
- wbs,
225
- base: null,
226
- scanned: {
227
- "review-finding": true,
228
- "diff-marker": false,
229
- "unchecked-box": false,
230
- "staging-residue": false,
231
- "review-contract": true
232
- },
233
- items,
234
- counts
235
- };
236
- const note = `residual-review-gate: ${wbs} blocking=${counts.blocking}` + (blocking.length > 0 ? ` ids=${blocking.map((i) => i.id).join(",")} anchors=${blockingAnchors(blocking).join(",")}` : "") + `
237
- `;
238
- return { artifact, note };
239
- }
240
- function blockingAnchors(items) {
241
- const anchors = new Set;
242
- for (const item of items) {
243
- if (item.class !== "blocking")
244
- continue;
245
- for (const m of normalizeAnchor(item.location).matchAll(ANCHOR_PATTERN))
246
- anchors.add(m[0]);
247
- }
248
- return [...anchors];
249
- }
250
- function foldVerdict(verdict, scan, existingFindings, maxFindings = 20) {
251
- const blocking = scan.items.filter((i) => i.class === "blocking");
252
- const deferrable = scan.items.filter((i) => i.class === "deferrable");
253
- const evidence = `blocking=${blocking.length} deferrable=${deferrable.length} advisory=${scan.counts.advisory} housekeeping=${scan.counts.housekeeping}` + (blocking.length > 0 ? `; blocking ids: ${blocking.map((i) => i.id).join(", ")}` : "") + (deferrable.length > 0 ? `; deferrable ids: ${deferrable.map((i) => i.id).join(", ")}` : "");
254
- const checks = verdict.checks.filter((c) => c.name !== "residual-sweep");
255
- checks.push({ name: "residual-sweep", status: blocking.length > 0 ? "fail" : "pass", evidence });
256
- const merged = new Set([
257
- ...existingFindings.split(/\s+/).filter((a) => a.length > 0),
258
- ...blockingAnchors(scan.items)
259
- ]);
260
- const findings = [...merged].sort().slice(0, maxFindings).map((a) => `${a} `).join("");
261
- let verdictStatus = verdict.verdict === "PASS" ? "PASS" : "FAIL";
262
- if (verdict.verdict === "PARTIAL")
263
- verdictStatus = "PARTIAL";
264
- else if (verdict.verdict === "FAIL")
265
- verdictStatus = "FAIL";
266
- else if (blocking.length > 0)
267
- verdictStatus = "PARTIAL";
268
- return { verdict: verdictStatus, checks, findings };
269
- }
270
- function renderReport(wbs, items, attemptCount) {
271
- const lines = [
272
- `# Residual report — ${wbs}`,
273
- "",
274
- `Attempt: ${attemptCount}`,
275
- "",
276
- "| Category | Class | Location | Text |",
277
- "| --- | --- | --- | --- |"
278
- ];
279
- for (const item of items) {
280
- lines.push(`| ${item.category} | ${item.class} | ${item.location} | ${item.text.replace(/\|/g, "\\|")} |`);
281
- }
282
- return `${lines.join(`
283
- `)}
284
- `;
285
- }
286
- function recordedVerdictPath(runDir, wbs, fs) {
287
- const evidence = join(runDir, "..", "memory", "evidence");
288
- const durable = join(evidence, `${wbs}-verdict.json`);
289
- const run = join(runDir, `${wbs}-verdict.json`);
290
- for (const path of [join(runDir, ".."), join(evidence, ".."), evidence, durable, run]) {
291
- try {
292
- if (fs.lstatSync(path).isSymbolicLink())
293
- throw new Error(`residual-scan: symlink evidence path: ${path}`);
294
- } catch (error) {
295
- if (error.code !== "ENOENT")
296
- throw error;
297
- }
298
- }
299
- if (!fs.existsSync(durable))
300
- return run;
301
- if (!fs.existsSync(run))
302
- return durable;
303
- const newer = (a, b) => mtimeOf(fs, b) > mtimeOf(fs, a) ? b : a;
304
- return newer(run, durable);
305
- }
306
- function mtimeOf(fs, path) {
307
- try {
308
- return fs.statSync(path).mtimeMs;
309
- } catch {
310
- return 0;
311
- }
312
- }
313
- function verdictDisagreementNote(runDir, wbs, fs) {
314
- const run = join(runDir, `${wbs}-verdict.json`);
315
- const durable = join(runDir, "..", "memory", "evidence", `${wbs}-verdict.json`);
316
- if (!fs.existsSync(run) || !fs.existsSync(durable))
317
- return null;
318
- const bytes = (path) => {
319
- try {
320
- return fs.statSync(path).isFile() ? fs.readFileSync(path, "utf8") : null;
321
- } catch {
322
- return null;
323
- }
324
- };
325
- const runBytes = bytes(run);
326
- if (runBytes === null || runBytes === bytes(durable))
327
- return null;
328
- const value = (path) => {
329
- try {
330
- return JSON.parse(bytes(path) ?? "{}").verdict ?? "?";
331
- } catch {
332
- return "?";
333
- }
334
- };
335
- const winner = mtimeOf(fs, durable) > mtimeOf(fs, run) ? "durable" : "run";
336
- return `residual-fold: ${wbs} verdict copies disagree — run=${run} (${value(run)}) durable=${durable} (${value(durable)})` + ` → chose ${winner} (newer mtime)`;
337
- }
338
- function parseScanArgs(argv, cwd, defaultTmpDir) {
339
- let mode = "";
340
- let wbs = "";
341
- let spurBin;
342
- let root = cwd;
343
- let tmpDir = defaultTmpDir;
344
- for (let i = 0;i < argv.length; i++) {
345
- const a = argv[i];
346
- if (a === undefined)
347
- break;
348
- if (a === "--spur-bin")
349
- spurBin = argv[++i];
350
- else if (a === "--root")
351
- root = argv[++i] ?? root;
352
- else if (a === "--tmp-dir")
353
- tmpDir = argv[++i] ?? tmpDir;
354
- else if (a === "--help" || a === "-h")
355
- return null;
356
- else if (mode === "")
357
- mode = a;
358
- else if (wbs === "")
359
- wbs = a;
360
- }
361
- return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir };
362
- }
363
- export {
364
- ALLOW_PRAGMA,
365
- blockingAnchors,
366
- blockingReviewFindings,
367
- buildReviewGateArtifact,
368
- classify,
369
- findUncheckedBoxes,
370
- foldVerdict,
371
- locationOf,
372
- makeItemId,
373
- normalizeAnchor,
374
- parseDeferralEntries,
375
- parseDiffMarkers,
376
- parseReviewFindings,
377
- parseScanArgs,
378
- recordedVerdictPath,
379
- renderReport,
380
- reviewContractViolations,
381
- scanResiduals,
382
- verdictDisagreementNote
383
- };