@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,931 +0,0 @@
1
- #!/usr/bin/env node
2
- // @bun
3
-
4
- // plugins/sp/scripts/history-anatomy-cache.ts
5
- import { spawnSync } from "child_process";
6
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
7
-
8
- // plugins/sp/lib/history-anatomy.generated.mjs
9
- import { createHash as createHash2 } from "node:crypto";
10
- import {
11
- closeSync,
12
- existsSync,
13
- fsyncSync,
14
- openSync,
15
- readdirSync,
16
- readFileSync,
17
- renameSync,
18
- rmSync,
19
- statSync,
20
- writeFileSync
21
- } from "node:fs";
22
- import { join } from "node:path";
23
- import { createHash } from "node:crypto";
24
- var ARTIFACT_ARRAY_CLASSIFICATION = {
25
- byTool: "ranked",
26
- bySession: "ranked",
27
- topStepsByTokens: "ranked",
28
- topStepsByDuration: "ranked",
29
- topSteps: "ranked",
30
- bottlenecks: "ranked",
31
- coverage: "set",
32
- daily: "set",
33
- loops: "set",
34
- warnings: "set",
35
- pairings: "set",
36
- ladderSnapshot: "set",
37
- stepSupport: "set",
38
- phases: "set",
39
- tools: "set",
40
- skills: "set",
41
- sources: "set",
42
- models: "set"
43
- };
44
- var RANKED_ARTIFACT_KEYS = new Set(Object.entries(ARTIFACT_ARRAY_CLASSIFICATION).filter(([, kind]) => kind === "ranked").map(([key]) => key));
45
- function canonicalize(value, key) {
46
- if (key === "generatedAt" || key === "validatedAt" || key === "baselineArtifactDigest")
47
- return null;
48
- if (Array.isArray(value)) {
49
- const raw = value.map((v) => JSON.stringify(canonicalize(v, "")));
50
- return RANKED_ARTIFACT_KEYS.has(key) ? raw : [...raw].sort();
51
- }
52
- if (value !== null && typeof value === "object") {
53
- const out = {};
54
- for (const k of Object.keys(value).sort()) {
55
- out[k] = canonicalize(value[k], k);
56
- }
57
- return out;
58
- }
59
- return value;
60
- }
61
- function semanticArtifactDigest(artifactJson) {
62
- const material = JSON.stringify(canonicalize(artifactJson, "root"));
63
- return createHash("sha256").update(material).digest("hex");
64
- }
65
- var REPORT_SECTIONS = [
66
- "Scope and provenance",
67
- "Executive summary",
68
- "Baseline comparison",
69
- "Findings",
70
- "Recurrence ledger",
71
- "Telemetry gaps",
72
- "Remediation options",
73
- "Performance analysis",
74
- "Workflow and process improvements",
75
- "Report-only advisories",
76
- "Positive patterns",
77
- "Evidence ledger"
78
- ];
79
- var FINDING_FIELDS = [
80
- "key",
81
- "category",
82
- "impact",
83
- "trend",
84
- "observation",
85
- "inference",
86
- "confidence",
87
- "contradictions",
88
- "evidenceAnchor",
89
- "severity",
90
- "reproCommand",
91
- "ownerSurface"
92
- ];
93
- function parseScalar(raw) {
94
- const t = raw.trim();
95
- if (t.startsWith('"') && t.endsWith('"'))
96
- return t.slice(1, -1).replaceAll("\\\"", '"');
97
- if (t === "null")
98
- return null;
99
- return t;
100
- }
101
- function parseBlock(text) {
102
- const obj = {};
103
- const lines = text.split(`
104
- `);
105
- for (let i = 0;i < lines.length; i++) {
106
- const line = lines[i] ?? "";
107
- if (/^\s*$/.test(line) || /^\s*#/.test(line) || /^-\s+/.test(line.trim()))
108
- continue;
109
- const indent = line.search(/\S/);
110
- const eq = line.indexOf(":");
111
- if (eq === -1)
112
- continue;
113
- const key = line.slice(0, eq).trim();
114
- const val = parseScalar(line.slice(eq + 1).trim());
115
- let consumed = 0;
116
- const next = lines[i + 1];
117
- if ((val === "" || val === undefined) && /^\s*-\s+/.test(next ?? "")) {
118
- const items = [];
119
- let j = i + 1;
120
- while (j < lines.length && /^\s*-\s+/.test(lines[j] ?? "")) {
121
- const entry = {};
122
- for (const part of (lines[j] ?? "").trim().replace(/^-\s+/, "").split(",")) {
123
- const e = part.indexOf(":");
124
- if (e === -1)
125
- continue;
126
- entry[part.slice(0, e).trim()] = parseScalar(part.slice(e + 1).trim());
127
- }
128
- items.push(entry);
129
- j++;
130
- }
131
- obj[key] = items;
132
- consumed = j - i - 1;
133
- } else if (val === "" || val === undefined) {
134
- const block = [];
135
- let j = i + 1;
136
- while (j < lines.length) {
137
- const nl = lines[j] ?? "";
138
- if (nl.trim() === "") {
139
- block.push("");
140
- j++;
141
- continue;
142
- }
143
- const nind = nl.search(/\S/);
144
- if (nind <= indent)
145
- break;
146
- block.push(nl);
147
- j++;
148
- }
149
- obj[key] = parseBlock(block.join(`
150
- `));
151
- consumed = j - i - 1;
152
- } else {
153
- obj[key] = val;
154
- }
155
- i += consumed;
156
- }
157
- return obj;
158
- }
159
- function readSourceName(s) {
160
- if (s !== null && typeof s === "object")
161
- return String(s.source ?? "");
162
- return String(s);
163
- }
164
- var DISPOSITIONS = ["hit", "miss", "forced-recompute"];
165
- function optionalString(v) {
166
- return v == null || v === "" ? undefined : String(v);
167
- }
168
- function parseProvenance(reportMarkdown) {
169
- const match = reportMarkdown.match(/^---\n([\s\S]*?)\n---/);
170
- if (match === null)
171
- return null;
172
- try {
173
- const obj = parseBlock(match[1] ?? "");
174
- const identity = obj.identity;
175
- const coverage = obj.coverage;
176
- if (identity === undefined || !Array.isArray(coverage))
177
- return null;
178
- const bounds = identity.bounds;
179
- if (bounds === undefined || typeof bounds.since !== "string" || typeof bounds.until !== "string") {
180
- return null;
181
- }
182
- return {
183
- identity: {
184
- contractVersion: String(identity.contractVersion ?? ""),
185
- mode: identity.mode === "ad-hoc" ? "ad-hoc" : "daily",
186
- date: String(identity.date ?? ""),
187
- timezone: String(identity.timezone ?? ""),
188
- bounds: { since: bounds.since, until: bounds.until },
189
- sources: Array.isArray(identity.sources) ? identity.sources.map(readSourceName) : []
190
- },
191
- windowState: obj.windowState === "closed" ? "closed" : "provisional",
192
- generatedAt: String(obj.generatedAt ?? ""),
193
- validatedAt: String(obj.validatedAt ?? ""),
194
- artifactDigest: String(obj.artifactDigest ?? ""),
195
- baselineArtifactDigest: obj.baselineArtifactDigest == null ? null : String(obj.baselineArtifactDigest),
196
- contractDigest: String(obj.contractDigest ?? ""),
197
- skillDigest: String(obj.skillDigest ?? ""),
198
- workflowDigest: String(obj.workflowDigest ?? ""),
199
- helperDigest: String(obj.helperDigest ?? ""),
200
- coverage: coverage.map((c) => ({
201
- source: String(c.source ?? ""),
202
- status: String(c.status ?? ""),
203
- lastImportedAt: c.lastImportedAt == null ? null : String(c.lastImportedAt)
204
- })),
205
- runId: optionalString(obj.runId),
206
- currentArtifactPath: optionalString(obj.currentArtifactPath),
207
- baselineArtifactPath: obj.baselineArtifactPath == null ? null : String(obj.baselineArtifactPath),
208
- spurVersion: optionalString(obj.spurVersion),
209
- schemaVersion: obj.schemaVersion == null ? undefined : Number(obj.schemaVersion),
210
- executor: optionalString(obj.executor),
211
- model: optionalString(obj.model),
212
- cacheDisposition: DISPOSITIONS.includes(obj.cacheDisposition) ? obj.cacheDisposition : undefined
213
- };
214
- } catch {
215
- return null;
216
- }
217
- }
218
- function decideCache(cached, current, opts) {
219
- if (opts.recompute)
220
- return { disposition: "forced-recompute", reasons: ["recompute"] };
221
- if (cached === null)
222
- return { disposition: "miss", reasons: ["no-cache"] };
223
- const reasons = [];
224
- const id = cached.identity;
225
- const cur = current.identity;
226
- if (id.contractVersion !== cur.contractVersion)
227
- reasons.push("identity:contractVersion");
228
- if (id.mode !== cur.mode)
229
- reasons.push("identity:mode");
230
- if (id.date !== cur.date)
231
- reasons.push("identity:date");
232
- if (id.timezone !== cur.timezone)
233
- reasons.push("identity:timezone");
234
- if (id.bounds.since !== cur.bounds.since || id.bounds.until !== cur.bounds.until)
235
- reasons.push("identity:bounds");
236
- if ([...id.sources].sort().join("\x00") !== [...cur.sources].sort().join("\x00"))
237
- reasons.push("identity:sources");
238
- if (cached.artifactDigest !== current.artifactDigest)
239
- reasons.push("data-changed");
240
- if (cached.contractDigest !== current.contractDigest)
241
- reasons.push("logic-changed:contract");
242
- if (cached.skillDigest !== current.skillDigest)
243
- reasons.push("logic-changed:skill");
244
- if (cached.workflowDigest !== current.workflowDigest)
245
- reasons.push("logic-changed:workflow");
246
- if (cached.helperDigest !== current.helperDigest)
247
- reasons.push("logic-changed:helper");
248
- const currentSources = new Set(current.coverage.map((c) => c.source));
249
- if (cached.coverage.some((c) => !currentSources.has(c.source)))
250
- reasons.push("coverage-degraded");
251
- if (cached.windowState === "provisional" && opts.dayClosed)
252
- reasons.push("window-closed");
253
- return { disposition: reasons.length === 0 ? "hit" : "miss", reasons };
254
- }
255
- function escapeRe(s) {
256
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
257
- }
258
- var FINDING_CATEGORIES = [
259
- "reliability",
260
- "repetition",
261
- "workflow",
262
- "performance",
263
- "coverage",
264
- "telemetry",
265
- "positive"
266
- ];
267
- var FINDING_CONFIDENCES = ["high", "medium", "low"];
268
- function checkReportStructure(reportMarkdown) {
269
- const problems = [];
270
- if (/TODO|PLACEHOLDER|FIXME|^\|\s*\|/im.test(reportMarkdown))
271
- problems.push("placeholder-or-todo-present");
272
- let lastIdx = -1;
273
- for (const section of REPORT_SECTIONS) {
274
- const re = new RegExp(`^#{2,3}\\s+${escapeRe(section)}\\s*$`, "m");
275
- const m = reportMarkdown.match(re);
276
- if (m === null || (m.index ?? -1) <= lastIdx) {
277
- problems.push(`section-missing-or-out-of-order:${section}`);
278
- } else if (m.index !== undefined) {
279
- lastIdx = m.index;
280
- }
281
- }
282
- const findingsIdx = reportMarkdown.search(/^##\s+Findings\s*$/im);
283
- if (findingsIdx !== -1) {
284
- const tail = reportMarkdown.slice(findingsIdx);
285
- const nextSection = tail.slice(1).search(/^##\s+/im);
286
- const findingsBody = nextSection === -1 ? tail : tail.slice(0, nextSection + 1);
287
- const catAlt = FINDING_CATEGORIES.join("|");
288
- const knownRows = findingsBody.match(new RegExp(`^\\|\\s*(?:${catAlt}):[^|]+`, "gm")) ?? [];
289
- const invalidRows = findingsBody.match(/^\|\s*([^|:\s][^|:]*):[^|]+/gm) ?? [];
290
- for (const row of [...new Set([...knownRows, ...invalidRows])]) {
291
- const seg = row.match(/^\|\s*([^:|]+):/)?.[1];
292
- if (seg !== undefined && !FINDING_CATEGORIES.includes(seg.trim())) {
293
- problems.push(`finding-invalid-key-category:${seg.trim()}`);
294
- }
295
- const rowLower = row.toLowerCase();
296
- for (const field of FINDING_FIELDS) {
297
- if (!rowLower.includes(field.toLowerCase()))
298
- problems.push(`finding-missing-field:${field}`);
299
- }
300
- }
301
- const blocks = findingsBody.split(/^###\s+/m).slice(1);
302
- for (const block of blocks) {
303
- if (!block.includes("`key`") && !/\|\s*key\s*:/.test(block))
304
- continue;
305
- for (const field of FINDING_FIELDS) {
306
- if (!block.includes(field))
307
- problems.push(`finding-missing-field:${field}`);
308
- }
309
- if (!/(^|[\s`])P[123]([\s`.]|$)/.test(block) && !block.includes("symbolic-severity")) {
310
- problems.push("finding-invalid-severity");
311
- }
312
- const confValue = block.match(/`confidence`\s*[:=]\s*`?([^`\n]*?)`?\s*(?:\n|$)/)?.[1]?.trim();
313
- if (confValue !== undefined && confValue !== "" && !FINDING_CONFIDENCES.includes(confValue.toLowerCase())) {
314
- problems.push(`finding-invalid-confidence:${confValue}`);
315
- }
316
- const catValue = block.match(/`category`\s*[:=]\s*`?([^`\n]+?)`?\s*(?:\n|$)/)?.[1];
317
- if (catValue && !FINDING_CATEGORIES.includes(catValue.trim())) {
318
- problems.push(`finding-invalid-category:${catValue.trim()}`);
319
- }
320
- const keyValue = block.match(/`key`\s*[:=]\s*`?([^`\n]+?)`?\s*(?:\n|$)/)?.[1] ?? "";
321
- const firstSegment = keyValue.split(":")[0]?.trim() ?? "";
322
- if (keyValue !== "" && !FINDING_CATEGORIES.includes(firstSegment)) {
323
- problems.push(`finding-invalid-key-category:${firstSegment}`);
324
- }
325
- }
326
- }
327
- const ledgerIdx = reportMarkdown.search(/^#{2,3}\s+Evidence\s+ledger/im);
328
- if (ledgerIdx !== -1) {
329
- const ledgerSection = reportMarkdown.slice(ledgerIdx);
330
- const sep = ledgerSection.match(/^\|[\s:|-]+\|[ \t]*$/m);
331
- const body = sep?.index === undefined ? ledgerSection : ledgerSection.slice(sep.index + sep[0].length);
332
- const claimRows = body.match(/^[|>]\s+\S.*$/gm) ?? [];
333
- for (const row of claimRows) {
334
- const hasAnchor = /`[^`]+:\d+`|`[^`]+\.(md|ts|json)`|[a-z][a-z0-9_-]*\/[a-z][a-z0-9_./-]*:[0-9]+/i.test(row);
335
- if (!hasAnchor) {
336
- problems.push("evidence-claim-without-anchor");
337
- break;
338
- }
339
- }
340
- }
341
- return { ok: problems.length === 0, problems };
342
- }
343
- var NOT_AVAILABLE = "not available";
344
- function logicDigest(path) {
345
- if (path === undefined || path === "" || !existsSync(path))
346
- return NOT_AVAILABLE;
347
- try {
348
- const h = createHash2("sha256");
349
- if (statSync(path).isDirectory()) {
350
- const walk = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(join(dir, e.name)) : /\.(md|ya?ml)$/.test(e.name) ? [join(dir, e.name)] : []).sort();
351
- for (const f of walk(path)) {
352
- h.update(f.slice(path.length));
353
- h.update(readFileSync(f));
354
- }
355
- } else {
356
- h.update(readFileSync(path));
357
- }
358
- return h.digest("hex");
359
- } catch {
360
- return NOT_AVAILABLE;
361
- }
362
- }
363
- function importedSnapshotAsOf(coverage) {
364
- const stamps = coverage.map((c) => c.lastImportedAt).filter((v) => typeof v === "string" && v !== "");
365
- if (stamps.length === 0 || stamps.length !== coverage.length)
366
- return NOT_AVAILABLE;
367
- return [...stamps].sort()[0] ?? NOT_AVAILABLE;
368
- }
369
- function localDay(tz, at = new Date) {
370
- try {
371
- return new Intl.DateTimeFormat("en-CA", { timeZone: tz, dateStyle: "short" }).format(at);
372
- } catch {
373
- return at.toISOString().slice(0, 10);
374
- }
375
- }
376
- function tzOffsetMs(tz, at) {
377
- const parts = Object.fromEntries(new Intl.DateTimeFormat("en-US", {
378
- timeZone: tz,
379
- hour12: false,
380
- year: "numeric",
381
- month: "2-digit",
382
- day: "2-digit",
383
- hour: "2-digit",
384
- minute: "2-digit",
385
- second: "2-digit"
386
- }).formatToParts(at).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
387
- const asUtc = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), parts.hour === "24" ? 0 : Number(parts.hour), Number(parts.minute), Number(parts.second));
388
- const atSec = Math.floor(at.getTime() / 1000) * 1000;
389
- return asUtc - atSec;
390
- }
391
- function zonedDayStart(tz, ymd) {
392
- const [y, m, d] = ymd.split("-").map(Number);
393
- const utcMidnight = Date.UTC(y ?? 1970, (m ?? 1) - 1, d ?? 1);
394
- const guess = new Date(utcMidnight - tzOffsetMs(tz, new Date(utcMidnight)));
395
- return new Date(utcMidnight - tzOffsetMs(tz, guess));
396
- }
397
- function formatZonedIso(tz, at) {
398
- const off = tzOffsetMs(tz, at);
399
- const abs = Math.abs(off);
400
- const pad = (n) => String(n).padStart(2, "0");
401
- const offStr = `${off < 0 ? "-" : "+"}${pad(Math.floor(abs / 3600000))}:${pad(Math.floor(abs % 3600000 / 60000))}`;
402
- return `${new Date(at.getTime() + off).toISOString().slice(0, 23)}${offStr}`;
403
- }
404
- function dayBounds(tz, ymd) {
405
- const start = zonedDayStart(tz, ymd);
406
- const nextYmd = localDay(tz, new Date(start.getTime() + 30 * 3600000));
407
- return {
408
- since: formatZonedIso(tz, start),
409
- until: formatZonedIso(tz, new Date(zonedDayStart(tz, nextYmd).getTime() - 1))
410
- };
411
- }
412
- function resolvePaths(opts) {
413
- const m = opts.helper.match(/\/scripts\/(?:([^/]+)\/)?[^/]+$/);
414
- const pluginRoot = m ? opts.helper.slice(0, m.index) : opts.helper;
415
- const skill = `${pluginRoot}/skills/${m?.[1] ? `${m[1]}-history-anatomy` : "history-anatomy"}`;
416
- const tz = opts.tz ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
417
- const date = opts.date !== undefined && opts.date !== "" ? opts.date : localDay(tz, opts.now ?? new Date);
418
- const target = opts.output !== undefined && opts.output !== "" ? opts.output : `${opts.reportDir}/${date}-history-anatomy.md`;
419
- const adHoc = opts.mode === "ad-hoc" && !!opts.since && !!opts.until;
420
- let env = `HA_HELPER=${opts.helper}
421
- HA_SKILL=${skill}
422
- HA_TARGET=${target}
423
- HA_DATE=${date}
424
- `;
425
- if (adHoc) {
426
- return `${env}HA_SINCE=${opts.since}
427
- HA_UNTIL=${opts.until}
428
- `;
429
- }
430
- const current = dayBounds(tz, date);
431
- const baseline = dayBounds(tz, localDay(tz, new Date(zonedDayStart(tz, date).getTime() - 12 * 3600000)));
432
- env += `HA_SINCE=${current.since}
433
- HA_UNTIL=${current.until}
434
- `;
435
- env += `HA_BASELINE_SINCE=${baseline.since}
436
- HA_BASELINE_UNTIL=${baseline.until}
437
- `;
438
- return env;
439
- }
440
- function isRealDate(ymd) {
441
- const m = ymd.match(/^(\d{4})-(\d{2})-(\d{2})$/);
442
- if (!m)
443
- return false;
444
- const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
445
- const dt = new Date(Date.UTC(y, mo - 1, d));
446
- return dt.getUTCFullYear() === y && dt.getUTCMonth() === mo - 1 && dt.getUTCDate() === d;
447
- }
448
- function validateSelector(opts) {
449
- const errors = [];
450
- const has = (v) => v !== undefined && v !== "";
451
- const val = (v) => has(v) ? v : null;
452
- const mode = has(opts.mode) ? opts.mode : "daily";
453
- if (mode !== "daily" && mode !== "ad-hoc") {
454
- return { ok: false, errors: [`--mode must be "daily" or "ad-hoc", got "${opts.mode}"`] };
455
- }
456
- const recompute = opts.recompute ?? "";
457
- if (recompute !== "" && recompute !== "true" && recompute !== "false") {
458
- errors.push(`--recompute must be "true" or "false", got "${recompute}"`);
459
- }
460
- if (mode === "daily") {
461
- for (const [flag, v] of [
462
- ["--focus", opts.focus],
463
- ["--since", opts.since],
464
- ["--until", opts.until],
465
- ["--output", opts.output]
466
- ]) {
467
- if (has(v))
468
- errors.push(`daily mode rejects ${flag}`);
469
- }
470
- if (has(opts.date) && !isRealDate(opts.date)) {
471
- errors.push(`--date must be a real YYYY-MM-DD calendar day, got "${opts.date}"`);
472
- }
473
- } else {
474
- if (has(opts.date))
475
- errors.push("ad-hoc mode rejects --date");
476
- if (recompute === "true")
477
- errors.push("ad-hoc mode rejects --recompute");
478
- if (!has(opts.focus))
479
- errors.push("ad-hoc mode requires a non-empty --focus");
480
- const sinceOk = has(opts.since);
481
- const untilOk = has(opts.until);
482
- if (!sinceOk)
483
- errors.push("ad-hoc mode requires --since (inclusive ISO instant)");
484
- if (!untilOk)
485
- errors.push("ad-hoc mode requires --until (inclusive ISO instant)");
486
- if (sinceOk && untilOk) {
487
- const since = new Date(opts.since ?? "");
488
- const until = new Date(opts.until ?? "");
489
- if (Number.isNaN(since.getTime()))
490
- errors.push(`--since must be a parseable ISO instant, got "${opts.since}"`);
491
- if (Number.isNaN(until.getTime()))
492
- errors.push(`--until must be a parseable ISO instant, got "${opts.until}"`);
493
- if (!Number.isNaN(since.getTime()) && !Number.isNaN(until.getTime()) && since.getTime() > until.getTime()) {
494
- errors.push(`--since must not be after --until ("${opts.since}" > "${opts.until}")`);
495
- }
496
- }
497
- }
498
- if (errors.length)
499
- return { ok: false, errors };
500
- return {
501
- ok: true,
502
- mode,
503
- date: val(opts.date),
504
- focus: val(opts.focus),
505
- since: val(opts.since),
506
- until: val(opts.until)
507
- };
508
- }
509
- function buildProvenance(opts) {
510
- let raw;
511
- try {
512
- raw = JSON.parse(readFileSync(opts.artifact, "utf8"));
513
- } catch (err) {
514
- throw new Error(`could not parse fresh analyze artifact at ${opts.artifact}: ${err.message}`);
515
- }
516
- const coverage = (raw.coverage ?? []).map((c) => ({
517
- source: String(c.source ?? ""),
518
- status: String(c.status ?? ""),
519
- lastImportedAt: c.lastImportedAt == null ? null : String(c.lastImportedAt)
520
- }));
521
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
522
- const now = opts.now ?? new Date;
523
- const date = opts.date !== undefined && opts.date !== "" ? opts.date : localDay(tz, now);
524
- const windowState = opts.mode === "ad-hoc" || date < localDay(tz, now) ? "closed" : "provisional";
525
- const nowIso = now.toISOString();
526
- return {
527
- identity: {
528
- contractVersion: opts.contractVersion ?? "1",
529
- mode: opts.mode,
530
- date,
531
- timezone: tz,
532
- bounds: { since: String(raw.selector?.since ?? ""), until: String(raw.selector?.until ?? "") },
533
- sources: coverage.map((c) => c.source).sort()
534
- },
535
- windowState,
536
- generatedAt: nowIso,
537
- validatedAt: nowIso,
538
- artifactDigest: semanticArtifactDigest(raw),
539
- baselineArtifactDigest: (() => {
540
- if (!(opts.baseline !== undefined && existsSync(opts.baseline)))
541
- return null;
542
- try {
543
- return semanticArtifactDigest(JSON.parse(readFileSync(opts.baseline, "utf8")));
544
- } catch (err) {
545
- throw new Error(`could not parse baseline artifact at ${opts.baseline}: ${err.message}`);
546
- }
547
- })(),
548
- contractDigest: logicDigest(opts.contractFile),
549
- skillDigest: logicDigest(opts.skillDir),
550
- workflowDigest: logicDigest(opts.workflowFile),
551
- helperDigest: logicDigest(opts.helperFile),
552
- coverage,
553
- runId: opts.runId,
554
- currentArtifactPath: opts.artifact,
555
- baselineArtifactPath: opts.baseline ?? null,
556
- spurVersion: opts.spurVersion ?? NOT_AVAILABLE,
557
- schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : undefined,
558
- executor: opts.executor ?? NOT_AVAILABLE,
559
- model: opts.model ?? NOT_AVAILABLE
560
- };
561
- }
562
- function probe(opts) {
563
- const current = buildProvenance(opts);
564
- const cachedText = existsSync(opts.target) ? readFileSync(opts.target, "utf8") : null;
565
- const cached = cachedText === null ? null : parseProvenance(cachedText);
566
- const decision = opts.mode === "ad-hoc" ? { disposition: "miss", reasons: ["ad-hoc-never-cached"] } : decideCache(cached, current, {
567
- recompute: opts.recompute,
568
- dayClosed: current.windowState === "closed"
569
- });
570
- current.cacheDisposition = decision.disposition;
571
- return { decision, current };
572
- }
573
- var YAML_KEYS = [
574
- "windowState",
575
- "generatedAt",
576
- "validatedAt",
577
- "artifactDigest",
578
- "baselineArtifactDigest",
579
- "contractDigest",
580
- "skillDigest",
581
- "workflowDigest",
582
- "helperDigest",
583
- "runId",
584
- "currentArtifactPath",
585
- "baselineArtifactPath",
586
- "spurVersion",
587
- "schemaVersion",
588
- "executor",
589
- "model",
590
- "cacheDisposition"
591
- ];
592
- function yamlScalar(v) {
593
- if (v === null || v === undefined)
594
- return "null";
595
- if (typeof v === "number" || typeof v === "boolean")
596
- return String(v);
597
- return `"${String(v).replaceAll('"', "\\\"")}"`;
598
- }
599
- function renderProvenanceFrontmatter(p) {
600
- const lines = [
601
- "---",
602
- "identity:",
603
- ` contractVersion: ${yamlScalar(p.identity.contractVersion)}`,
604
- ` mode: ${p.identity.mode}`,
605
- ` date: ${yamlScalar(p.identity.date)}`,
606
- ` timezone: ${p.identity.timezone}`,
607
- " bounds:",
608
- ` since: ${p.identity.bounds.since}`,
609
- ` until: ${p.identity.bounds.until}`,
610
- " sources:"
611
- ];
612
- for (const s of p.identity.sources)
613
- lines.push(` - source: ${s}`);
614
- for (const k of YAML_KEYS) {
615
- if (p[k] === undefined)
616
- continue;
617
- lines.push(`${k}: ${yamlScalar(p[k])}`);
618
- }
619
- lines.push("coverage:");
620
- for (const c of p.coverage) {
621
- lines.push(` - source: ${c.source}, status: ${c.status}, lastImportedAt: ${c.lastImportedAt ?? "null"}`);
622
- }
623
- lines.push("---");
624
- return lines.join(`
625
- `);
626
- }
627
- function bannerLine(p) {
628
- return `> imported snapshot as of ${importedSnapshotAsOf(p.coverage)} · window ${p.windowState} · cache ${p.cacheDisposition ?? NOT_AVAILABLE}`;
629
- }
630
- function stripHeader(md) {
631
- const body = md.replace(/^---\n[\s\S]*?\n---\n?/, "").replace(/^\n+/, "");
632
- return body.replace(/^> imported snapshot as of [^\n]*\n+/, "");
633
- }
634
- function stampReport(candidateMarkdown, p) {
635
- return `${renderProvenanceFrontmatter(p)}
636
-
637
- ${bannerLine(p)}
638
-
639
- ${stripHeader(candidateMarkdown).replace(/^\n+/, "")}`;
640
- }
641
- function refreshReport(publishedMarkdown, validatedAt, disposition) {
642
- const cached = parseProvenance(publishedMarkdown);
643
- if (cached === null)
644
- return publishedMarkdown;
645
- const refreshed = { ...cached, validatedAt, cacheDisposition: disposition };
646
- return stampReport(publishedMarkdown, refreshed);
647
- }
648
- function publishAtomically(candidatePath, targetPath) {
649
- const tmpPath = `${targetPath}.tmp`;
650
- try {
651
- writeFileSync(tmpPath, readFileSync(candidatePath));
652
- const fd = openSync(tmpPath, "r");
653
- try {
654
- fsyncSync(fd);
655
- } finally {
656
- closeSync(fd);
657
- }
658
- renameSync(tmpPath, targetPath);
659
- } catch (err) {
660
- try {
661
- rmSync(tmpPath, { force: true });
662
- } catch {}
663
- throw err;
664
- }
665
- }
666
- function porcelainPaths(text) {
667
- return new Set(text.split(`
668
- `).map((line) => line.replace(/^\S+\s+/, "").trim()).filter((line) => line.length > 0));
669
- }
670
- function diffPorcelain(before, now, expects) {
671
- const beforePaths = porcelainPaths(before);
672
- return [...porcelainPaths(now)].filter((p) => !beforePaths.has(p) && !expects.has(p)).sort();
673
- }
674
-
675
- // plugins/sp/scripts/history-anatomy-cache.ts
676
- var VALID_COMMANDS = "digest, check, paths, assert-clean, probe, stamp, refresh, publish";
677
- var PROBE_USAGE = "<script> probe --artifact <a.json> --target <report.md> [--baseline <b.json>] [--mode daily|ad-hoc] " + "[--date <YYYY-MM-DD>] [--recompute true] [--out <prov.json>] [--skill-dir <d>] [--contract <f>] [--workflow <f>] [--helper <f>]";
678
- function parseFlags(args) {
679
- const out = {};
680
- for (let i = 0;i < args.length; i++) {
681
- const a = args[i] ?? "";
682
- if (!a.startsWith("--"))
683
- continue;
684
- const key = a.slice(2);
685
- const next = args[i + 1];
686
- if (next === undefined || next.startsWith("--")) {
687
- out[key] = "true";
688
- } else {
689
- out[key] = next;
690
- i++;
691
- }
692
- }
693
- return out;
694
- }
695
- function runCacheCli(argv) {
696
- const [cmd, a, b] = argv;
697
- switch (cmd) {
698
- case "digest": {
699
- if (a === undefined) {
700
- return { exitCode: 1, stdout: "", stderr: `usage: <script> digest <artifact.json>
701
- ` };
702
- }
703
- let artifact;
704
- try {
705
- artifact = JSON.parse(readFileSync2(a, "utf8"));
706
- } catch {
707
- return { exitCode: 1, stdout: "", stderr: `could not parse artifact at ${a}
708
- ` };
709
- }
710
- const digest = semanticArtifactDigest(artifact);
711
- return { exitCode: 0, stdout: `${digest}
712
- `, stderr: "" };
713
- }
714
- case "check": {
715
- if (a === undefined) {
716
- return { exitCode: 1, stdout: "", stderr: `usage: <script> check <report.md>
717
- ` };
718
- }
719
- const result = checkReportStructure(readFileSync2(a, "utf8"));
720
- const stdout = `${result.ok ? "PASS" : "FAIL"}
721
- ${result.problems.map((p) => `- ${p}
722
- `).join("")}`;
723
- return { exitCode: result.ok ? 0 : 1, stdout, stderr: "" };
724
- }
725
- case "publish": {
726
- if (a === undefined || b === undefined) {
727
- return { exitCode: 1, stdout: "", stderr: `usage: <script> publish <candidate.md> <target.md>
728
- ` };
729
- }
730
- publishAtomically(a, b);
731
- return { exitCode: 0, stdout: "", stderr: "" };
732
- }
733
- case "assert-clean": {
734
- const f = parseFlags(argv.slice(1));
735
- if (f.baseline === undefined) {
736
- return {
737
- exitCode: 1,
738
- stdout: "",
739
- stderr: `usage: <script> assert-clean --baseline <porcelain.txt> [--expect <path>]...
740
- `
741
- };
742
- }
743
- const expects = new Set;
744
- for (const arg of argv.slice(1)) {
745
- if (arg.startsWith("--expect="))
746
- expects.add(arg.slice("--expect=".length));
747
- }
748
- let now;
749
- try {
750
- now = spawnSync("git", ["status", "--porcelain"], {
751
- encoding: "utf8",
752
- ...f.cwd !== undefined ? { cwd: f.cwd } : {}
753
- }).stdout ?? "";
754
- } catch {
755
- return { exitCode: 0, stdout: "", stderr: `assert-clean: git unavailable; skipped
756
- ` };
757
- }
758
- const undeclared = diffPorcelain(readFileSync2(f.baseline, "utf8"), now, expects);
759
- if (undeclared.length > 0) {
760
- return {
761
- exitCode: 1,
762
- stdout: "",
763
- stderr: undeclared.map((p) => `undeclared write: ${p}
764
- `).join("")
765
- };
766
- }
767
- return { exitCode: 0, stdout: `clean
768
- `, stderr: "" };
769
- }
770
- case "paths": {
771
- const f = parseFlags(argv.slice(1));
772
- if (f.helper === undefined || f.out === undefined) {
773
- return {
774
- exitCode: 1,
775
- stdout: "",
776
- stderr: `usage: <script> paths --helper <p> --out <env> [--report-dir <d>] [--date <d>] [--output <p>] [--mode <m>] [--since <s>] [--until <u>] [--focus <text>] [--recompute true|false] [--run-id <id>]
777
- `
778
- };
779
- }
780
- const v = validateSelector({
781
- mode: f.mode,
782
- date: f.date,
783
- since: f.since,
784
- until: f.until,
785
- focus: f.focus,
786
- recompute: f.recompute,
787
- output: f.output
788
- });
789
- if (!v.ok)
790
- return { exitCode: 1, stdout: "", stderr: `${v.errors.join(`
791
- `)}
792
- ` };
793
- const env = resolvePaths({
794
- helper: f.helper,
795
- reportDir: f["report-dir"] ?? "docs/report",
796
- date: f.date,
797
- output: f.output,
798
- mode: v.mode,
799
- since: f.since,
800
- until: f.until
801
- });
802
- writeFileSync2(f.out, env);
803
- if (f["run-id"] !== undefined && f["run-id"] !== "") {
804
- mkdirSync(".spur/run", { recursive: true });
805
- const envVars = Object.fromEntries(env.trim().split(`
806
- `).map((l) => [l.slice(0, l.indexOf("=")), l.slice(l.indexOf("=") + 1)]));
807
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
808
- writeFileSync2(`.spur/run/${f["run-id"]}-selector.json`, `${JSON.stringify({
809
- mode: v.mode,
810
- date: envVars.HA_DATE ?? null,
811
- focus: v.focus,
812
- since: envVars.HA_SINCE ?? null,
813
- until: envVars.HA_UNTIL ?? null,
814
- timezone: tz
815
- }, null, 2)}
816
- `);
817
- }
818
- return { exitCode: 0, stdout: "", stderr: "" };
819
- }
820
- case "probe": {
821
- const f = parseFlags(argv.slice(1));
822
- if (f.artifact === undefined || f.target === undefined) {
823
- return { exitCode: 1, stdout: "", stderr: `usage: ${PROBE_USAGE}
824
- ` };
825
- }
826
- let result;
827
- try {
828
- result = probe({
829
- artifact: f.artifact,
830
- target: f.target,
831
- baseline: f.baseline,
832
- mode: f.mode === "ad-hoc" ? "ad-hoc" : "daily",
833
- date: f.date,
834
- recompute: f.recompute === "true",
835
- executor: f.executor,
836
- model: f.model,
837
- skillDir: f["skill-dir"],
838
- contractFile: f.contract,
839
- workflowFile: f.workflow,
840
- helperFile: f.helper,
841
- contractVersion: f["contract-version"],
842
- runId: f["run-id"],
843
- spurVersion: f["spur-version"]
844
- });
845
- } catch {
846
- return { exitCode: 1, stdout: "", stderr: `could not read artifact at ${f.artifact}
847
- ` };
848
- }
849
- if (f.out !== undefined)
850
- writeFileSync2(f.out, `${JSON.stringify(result.current, null, 2)}
851
- `);
852
- const reasons = result.decision.reasons.map((r) => `- ${r}
853
- `).join("");
854
- return { exitCode: 0, stdout: `${result.decision.disposition}
855
- ${reasons}`, stderr: "" };
856
- }
857
- case "stamp": {
858
- const f = parseFlags(argv.slice(1));
859
- if (f.candidate === undefined || f.provenance === undefined || f.out === undefined) {
860
- return {
861
- exitCode: 1,
862
- stdout: "",
863
- stderr: `usage: <script> stamp --candidate <c.md> --provenance <p.json> --out <o.md>
864
- `
865
- };
866
- }
867
- try {
868
- const p = JSON.parse(readFileSync2(f.provenance, "utf8"));
869
- writeFileSync2(f.out, `${stampReport(readFileSync2(f.candidate, "utf8"), p)}
870
- `);
871
- } catch {
872
- return { exitCode: 1, stdout: "", stderr: `stamp: could not read candidate or provenance
873
- ` };
874
- }
875
- return { exitCode: 0, stdout: "", stderr: "" };
876
- }
877
- case "refresh": {
878
- const f = parseFlags(argv.slice(1));
879
- if (f.report === undefined || f.out === undefined) {
880
- return {
881
- exitCode: 1,
882
- stdout: "",
883
- stderr: `usage: <script> refresh --report <published.md> --out <o.md> [--disposition hit]
884
- `
885
- };
886
- }
887
- try {
888
- const disposition = f.disposition ?? "hit";
889
- const refreshed = refreshReport(readFileSync2(f.report, "utf8"), f["validated-at"] ?? new Date().toISOString(), disposition);
890
- writeFileSync2(f.out, refreshed.endsWith(`
891
- `) ? refreshed : `${refreshed}
892
- `);
893
- } catch {
894
- return { exitCode: 1, stdout: "", stderr: `refresh: could not read report at ${f.report}
895
- ` };
896
- }
897
- return { exitCode: 0, stdout: "", stderr: "" };
898
- }
899
- default:
900
- return { exitCode: 1, stdout: "", stderr: `valid commands: ${VALID_COMMANDS}
901
- ` };
902
- }
903
- }
904
- {
905
- const { exitCode, stdout, stderr } = runCacheCli(process.argv.slice(2));
906
- process.stdout.write(stdout);
907
- process.stderr.write(stderr);
908
- process.exitCode = exitCode;
909
- }
910
- export {
911
- FINDING_FIELDS,
912
- REPORT_SECTIONS,
913
- bannerLine,
914
- buildProvenance,
915
- checkReportStructure,
916
- decideCache,
917
- diffPorcelain,
918
- importedSnapshotAsOf,
919
- logicDigest,
920
- parseProvenance,
921
- porcelainPaths,
922
- probe,
923
- publishAtomically,
924
- refreshReport,
925
- renderProvenanceFrontmatter,
926
- resolvePaths,
927
- runCacheCli,
928
- semanticArtifactDigest,
929
- stampReport,
930
- validateSelector
931
- };