@gobing-ai/spur 0.3.60 → 0.3.62

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 (110) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +2 -2
  3. package/config/config.global.yaml +68 -57
  4. package/config/corpus-baseline.json +505 -57
  5. package/config/plugin-scripts.json +2 -2
  6. package/config/rules/boundary/config-loading-ownership.yaml +21 -0
  7. package/config/workflow-composition-baseline.json +1 -1
  8. package/config/workflows/history-anatomy.yaml +366 -0
  9. package/config/workflows/wayfinder-resolution.yaml +4 -6
  10. package/package.json +1 -1
  11. package/plugins/README.md +5 -4
  12. package/plugins/sp/README.md +22 -9
  13. package/plugins/sp/agents/super-planner.md +1 -1
  14. package/plugins/sp/commands/dev-find-issue.md +25 -46
  15. package/plugins/sp/commands/dev-pr-review.md +1 -1
  16. package/plugins/sp/commands/dev-run.md +22 -3
  17. package/plugins/sp/commands/dev-wrap.md +2 -2
  18. package/plugins/sp/commands/dev-wrapall.md +2 -2
  19. package/plugins/sp/lib/artifact-digest.generated.d.mts +7 -0
  20. package/plugins/sp/lib/artifact-digest.generated.mjs +48 -0
  21. package/plugins/sp/plugin.json +1 -1
  22. package/plugins/sp/references/roles.md +17 -14
  23. package/plugins/sp/scripts/history-anatomy-cache.mjs +669 -0
  24. package/plugins/sp/scripts/history-anatomy-cache.ts +818 -0
  25. package/plugins/sp/scripts/surface-drift-inventory.ts +51 -17
  26. package/plugins/sp/scripts/validate-commands.ts +11 -4
  27. package/plugins/sp/skills/code-verification/SKILL.md +1 -1
  28. package/plugins/sp/skills/code-verification/references/verdict-schema.md +1 -1
  29. package/plugins/sp/skills/history-anatomy/SKILL.md +67 -0
  30. package/plugins/sp/skills/history-anatomy/references/modes.md +82 -0
  31. package/plugins/sp/skills/history-anatomy/references/operations.md +67 -0
  32. package/plugins/sp/skills/history-anatomy/references/report-contract.md +133 -0
  33. package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
  34. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  35. package/plugins/sp/skills/pr-reviewing/SKILL.md +2 -2
  36. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +1 -1
  37. package/plugins/sp/skills/spur-cli/references/init.md +1 -1
  38. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +1 -1
  39. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +1 -1
  40. package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
  41. package/plugins/sp/skills/spur-dev/SKILL.md +4 -4
  42. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -4
  43. package/plugins/sp/skills/spur-dev/references/dev-operations.md +11 -11
  44. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  45. package/plugins/sp/skills/spur-dev/references/execution-batch.md +17 -4
  46. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -1
  47. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -2
  48. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +25 -13
  49. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  50. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +5 -2
  51. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +2 -2
  52. package/schemas/spur-config.schema.json +1 -1
  53. package/spur.js +1278 -890
  54. package/web/_astro/BoardApp.Z9jMwI9G.js +1 -0
  55. package/web/_astro/BoardApp.rkWGVwwK.js +178 -0
  56. package/web/_astro/{TaskDetail.B0l1uzsB.js → TaskDetail.B7ODt_bA.js} +1 -1
  57. package/web/_astro/{arc.DPruHC3P.js → arc.C5QRz6AQ.js} +1 -1
  58. package/web/_astro/{architectureDiagram-3BPJPVTR.CuOqAck6.js → architectureDiagram-3BPJPVTR.CHptn8gZ.js} +1 -1
  59. package/web/_astro/{blockDiagram-GPEHLZMM.Cxb7HDIA.js → blockDiagram-GPEHLZMM.b1nEDkDw.js} +1 -1
  60. package/web/_astro/{c4Diagram-AAUBKEIU.Dw7wa-0S.js → c4Diagram-AAUBKEIU.BWj8S_Qq.js} +1 -1
  61. package/web/_astro/channel.BJtn6CGV.js +1 -0
  62. package/web/_astro/{chunk-2J33WTMH.MECQitzo.js → chunk-2J33WTMH.q0BBuE1n.js} +1 -1
  63. package/web/_astro/{chunk-4BX2VUAB.p3ofhIGE.js → chunk-4BX2VUAB.DP2KDkAU.js} +1 -1
  64. package/web/_astro/{chunk-55IACEB6.BmQGsPZP.js → chunk-55IACEB6.CkCId72p.js} +1 -1
  65. package/web/_astro/{chunk-727SXJPM.CwrK23OA.js → chunk-727SXJPM.oHWzxMot.js} +1 -1
  66. package/web/_astro/{chunk-AQP2D5EJ.CUKI_QXp.js → chunk-AQP2D5EJ.DkkgxfMP.js} +1 -1
  67. package/web/_astro/{chunk-FMBD7UC4.7AtDgShM.js → chunk-FMBD7UC4.lmFJDvRK.js} +1 -1
  68. package/web/_astro/{chunk-ND2GUHAM.f_YJbTs3.js → chunk-ND2GUHAM.52jt0eHK.js} +1 -1
  69. package/web/_astro/{chunk-QZHKN3VN.0q7V3gEg.js → chunk-QZHKN3VN.Cg8KEFoD.js} +1 -1
  70. package/web/_astro/{classDiagram-4FO5ZUOK.C_hG3HMK.js → classDiagram-4FO5ZUOK.BuOUhxcD.js} +1 -1
  71. package/web/_astro/{classDiagram-v2-Q7XG4LA2.C_hG3HMK.js → classDiagram-v2-Q7XG4LA2.BuOUhxcD.js} +1 -1
  72. package/web/_astro/{cose-bilkent-S5V4N54A.vsaaaEMy.js → cose-bilkent-S5V4N54A.BRUU8E_0.js} +1 -1
  73. package/web/_astro/{dagre-BM42HDAG.BN7XFqQ4.js → dagre-BM42HDAG.GDbfFYVV.js} +1 -1
  74. package/web/_astro/{diagram-2AECGRRQ.ED6AfryZ.js → diagram-2AECGRRQ.CvIDBeJF.js} +1 -1
  75. package/web/_astro/{diagram-5GNKFQAL.Dxllzg8R.js → diagram-5GNKFQAL.DMgJjWOX.js} +1 -1
  76. package/web/_astro/{diagram-KO2AKTUF.RuWppieD.js → diagram-KO2AKTUF.njjl-0AP.js} +1 -1
  77. package/web/_astro/{diagram-LMA3HP47.CL32Xn6s.js → diagram-LMA3HP47.CcCqgP8M.js} +1 -1
  78. package/web/_astro/{diagram-OG6HWLK6.CUneEhqB.js → diagram-OG6HWLK6.Btqd-YVE.js} +1 -1
  79. package/web/_astro/{erDiagram-TEJ5UH35.DI-92Lec.js → erDiagram-TEJ5UH35.BbhML_Xo.js} +1 -1
  80. package/web/_astro/{flowDiagram-I6XJVG4X.69Aqa1FF.js → flowDiagram-I6XJVG4X.CkvsIgY8.js} +1 -1
  81. package/web/_astro/{ganttDiagram-6RSMTGT7.DdpClZpa.js → ganttDiagram-6RSMTGT7.m8IWD_wW.js} +1 -1
  82. package/web/_astro/{gitGraphDiagram-PVQCEYII.xqWfkvB4.js → gitGraphDiagram-PVQCEYII.5z87HXO-.js} +1 -1
  83. package/web/_astro/index.nWve6EHS.css +1 -0
  84. package/web/_astro/{infoDiagram-5YYISTIA.B3G5sfKm.js → infoDiagram-5YYISTIA.Bvyvsd7Q.js} +1 -1
  85. package/web/_astro/{ishikawaDiagram-YF4QCWOH.ahVNsoAQ.js → ishikawaDiagram-YF4QCWOH.DdWVYO87.js} +1 -1
  86. package/web/_astro/{journeyDiagram-JHISSGLW.PmFHLc-X.js → journeyDiagram-JHISSGLW.C3kOlYH1.js} +1 -1
  87. package/web/_astro/{kanban-definition-UN3LZRKU.Co1qOPTd.js → kanban-definition-UN3LZRKU.CfKTUHVC.js} +1 -1
  88. package/web/_astro/{linear.DoHZ-iRt.js → linear.CMDHnzgX.js} +1 -1
  89. package/web/_astro/{mermaid.core.DNRElYN2.js → mermaid.core.DN7-WrsP.js} +4 -4
  90. package/web/_astro/{mindmap-definition-RKZ34NQL.CdfJZidJ.js → mindmap-definition-RKZ34NQL.DcQxLvqG.js} +1 -1
  91. package/web/_astro/{pieDiagram-4H26LBE5.C1A8dby4.js → pieDiagram-4H26LBE5.Nu-Inbw6.js} +1 -1
  92. package/web/_astro/{quadrantDiagram-W4KKPZXB.NmZqjG_c.js → quadrantDiagram-W4KKPZXB.Dck2EChs.js} +1 -1
  93. package/web/_astro/{requirementDiagram-4Y6WPE33.tY36zYtK.js → requirementDiagram-4Y6WPE33.C--E5XuW.js} +1 -1
  94. package/web/_astro/{sankeyDiagram-5OEKKPKP.rOEGZYjo.js → sankeyDiagram-5OEKKPKP.B_0iRNna.js} +1 -1
  95. package/web/_astro/{sequenceDiagram-3UESZ5HK.Dpnw3S9J.js → sequenceDiagram-3UESZ5HK.PKuKr9mk.js} +1 -1
  96. package/web/_astro/{stateDiagram-AJRCARHV.DGpvQ3Wz.js → stateDiagram-AJRCARHV.ClhGHb-O.js} +1 -1
  97. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CbwQdEfu.js → stateDiagram-v2-BHNVJYJU.Bv6BBnvg.js} +1 -1
  98. package/web/_astro/{timeline-definition-PNZ67QCA.DAYUpUJT.js → timeline-definition-PNZ67QCA.BZm2ZvAH.js} +1 -1
  99. package/web/_astro/{vennDiagram-CIIHVFJN.C7JCCd6-.js → vennDiagram-CIIHVFJN.Dt6yZXry.js} +1 -1
  100. package/web/_astro/{wardley-L42UT6IY.BGgwOH5_.js → wardley-L42UT6IY.rUs-E00M.js} +1 -1
  101. package/web/_astro/{wardleyDiagram-YWT4CUSO.oIZqzI2l.js → wardleyDiagram-YWT4CUSO.BcWgv4cA.js} +1 -1
  102. package/web/_astro/{xychartDiagram-2RQKCTM6.DohcJzqm.js → xychartDiagram-2RQKCTM6.DH-M-cX9.js} +1 -1
  103. package/web/index.html +2 -2
  104. package/plugins/sp/commands/dev-history-load.md +0 -63
  105. package/plugins/sp/scripts/history-load.mjs +0 -268
  106. package/plugins/sp/scripts/history-load.ts +0 -400
  107. package/web/_astro/BoardApp.BJs3l7O8.js +0 -179
  108. package/web/_astro/BoardApp.D8InuCYx.js +0 -1
  109. package/web/_astro/channel.XCf1DcsL.js +0 -1
  110. package/web/_astro/index.DJl2RAVZ.css +0 -1
@@ -0,0 +1,669 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // plugins/sp/scripts/history-anatomy-cache.ts
6
+ import { createHash as createHash2 } from "node:crypto";
7
+ import {
8
+ closeSync,
9
+ existsSync,
10
+ fsyncSync,
11
+ openSync,
12
+ readdirSync,
13
+ readFileSync,
14
+ renameSync,
15
+ rmSync,
16
+ statSync,
17
+ writeFileSync
18
+ } from "node:fs";
19
+ import { join } from "node:path";
20
+
21
+ // plugins/sp/lib/artifact-digest.generated.mjs
22
+ import { createHash } from "node:crypto";
23
+ var ARTIFACT_ARRAY_CLASSIFICATION = {
24
+ byTool: "ranked",
25
+ bySession: "ranked",
26
+ topStepsByTokens: "ranked",
27
+ topStepsByDuration: "ranked",
28
+ topSteps: "ranked",
29
+ bottlenecks: "ranked",
30
+ coverage: "set",
31
+ daily: "set",
32
+ loops: "set",
33
+ warnings: "set",
34
+ pairings: "set",
35
+ ladderSnapshot: "set",
36
+ stepSupport: "set",
37
+ phases: "set",
38
+ tools: "set",
39
+ skills: "set",
40
+ sources: "set",
41
+ models: "set"
42
+ };
43
+ var RANKED_ARTIFACT_KEYS = new Set(Object.entries(ARTIFACT_ARRAY_CLASSIFICATION).filter(([, kind]) => kind === "ranked").map(([key]) => key));
44
+ function canonicalize(value, key) {
45
+ if (key === "generatedAt" || key === "validatedAt" || key === "baselineArtifactDigest")
46
+ return null;
47
+ if (Array.isArray(value)) {
48
+ const raw = value.map((v) => JSON.stringify(canonicalize(v, "")));
49
+ return RANKED_ARTIFACT_KEYS.has(key) ? raw : [...raw].sort();
50
+ }
51
+ if (value !== null && typeof value === "object") {
52
+ const out = {};
53
+ for (const k of Object.keys(value).sort()) {
54
+ out[k] = canonicalize(value[k], k);
55
+ }
56
+ return out;
57
+ }
58
+ return value;
59
+ }
60
+ function semanticArtifactDigest(artifactJson) {
61
+ const material = JSON.stringify(canonicalize(artifactJson, "root"));
62
+ return createHash("sha256").update(material).digest("hex");
63
+ }
64
+
65
+ // plugins/sp/scripts/history-anatomy-cache.ts
66
+ var ELEVEN_SECTIONS = [
67
+ "Scope and provenance",
68
+ "Executive summary",
69
+ "Baseline comparison",
70
+ "Findings",
71
+ "Recurrence ledger",
72
+ "Telemetry gaps",
73
+ "Remediation options",
74
+ "Performance analysis",
75
+ "Workflow and process improvements",
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
+ ];
90
+ function parseScalar(raw) {
91
+ const t = raw.trim();
92
+ if (t.startsWith('"') && t.endsWith('"'))
93
+ return t.slice(1, -1).replaceAll("\\\"", '"');
94
+ if (t === "null")
95
+ return null;
96
+ return t;
97
+ }
98
+ function parseBlock(text) {
99
+ const obj = {};
100
+ const lines = text.split(`
101
+ `);
102
+ for (let i = 0;i < lines.length; i++) {
103
+ const line = lines[i] ?? "";
104
+ if (/^\s*$/.test(line) || /^\s*#/.test(line) || /^-\s+/.test(line.trim()))
105
+ continue;
106
+ const indent = line.search(/\S/);
107
+ const eq = line.indexOf(":");
108
+ if (eq === -1)
109
+ continue;
110
+ const key = line.slice(0, eq).trim();
111
+ const val = parseScalar(line.slice(eq + 1).trim());
112
+ let consumed = 0;
113
+ const next = lines[i + 1];
114
+ if ((val === "" || val === undefined) && /^\s*-\s+/.test(next ?? "")) {
115
+ const items = [];
116
+ let j = i + 1;
117
+ while (j < lines.length && /^\s*-\s+/.test(lines[j] ?? "")) {
118
+ const entry = {};
119
+ for (const part of (lines[j] ?? "").trim().replace(/^-\s+/, "").split(",")) {
120
+ const e = part.indexOf(":");
121
+ if (e === -1)
122
+ continue;
123
+ entry[part.slice(0, e).trim()] = parseScalar(part.slice(e + 1).trim());
124
+ }
125
+ items.push(entry);
126
+ j++;
127
+ }
128
+ obj[key] = items;
129
+ consumed = j - i - 1;
130
+ } else if (val === "" || val === undefined) {
131
+ const block = [];
132
+ let j = i + 1;
133
+ while (j < lines.length) {
134
+ const nl = lines[j] ?? "";
135
+ if (nl.trim() === "") {
136
+ block.push("");
137
+ j++;
138
+ continue;
139
+ }
140
+ const nind = nl.search(/\S/);
141
+ if (nind <= indent)
142
+ break;
143
+ block.push(nl);
144
+ j++;
145
+ }
146
+ obj[key] = parseBlock(block.join(`
147
+ `));
148
+ consumed = j - i - 1;
149
+ } else {
150
+ obj[key] = val;
151
+ }
152
+ i += consumed;
153
+ }
154
+ return obj;
155
+ }
156
+ function readSourceName(s) {
157
+ if (s !== null && typeof s === "object")
158
+ return String(s.source ?? "");
159
+ return String(s);
160
+ }
161
+ var DISPOSITIONS = ["hit", "miss", "forced-recompute"];
162
+ function optionalString(v) {
163
+ return v == null || v === "" ? undefined : String(v);
164
+ }
165
+ function parseProvenance(reportMarkdown) {
166
+ const match = reportMarkdown.match(/^---\n([\s\S]*?)\n---/);
167
+ if (match === null)
168
+ return null;
169
+ try {
170
+ const obj = parseBlock(match[1] ?? "");
171
+ const identity = obj.identity;
172
+ const coverage = obj.coverage;
173
+ if (identity === undefined || !Array.isArray(coverage))
174
+ return null;
175
+ const bounds = identity.bounds;
176
+ if (bounds === undefined || typeof bounds.since !== "string" || typeof bounds.until !== "string") {
177
+ return null;
178
+ }
179
+ return {
180
+ identity: {
181
+ contractVersion: String(identity.contractVersion ?? ""),
182
+ mode: identity.mode === "ad-hoc" ? "ad-hoc" : "daily",
183
+ date: String(identity.date ?? ""),
184
+ timezone: String(identity.timezone ?? ""),
185
+ bounds: { since: bounds.since, until: bounds.until },
186
+ sources: Array.isArray(identity.sources) ? identity.sources.map(readSourceName) : []
187
+ },
188
+ windowState: obj.windowState === "closed" ? "closed" : "provisional",
189
+ generatedAt: String(obj.generatedAt ?? ""),
190
+ validatedAt: String(obj.validatedAt ?? ""),
191
+ artifactDigest: String(obj.artifactDigest ?? ""),
192
+ baselineArtifactDigest: obj.baselineArtifactDigest == null ? null : String(obj.baselineArtifactDigest),
193
+ contractDigest: String(obj.contractDigest ?? ""),
194
+ skillDigest: String(obj.skillDigest ?? ""),
195
+ workflowDigest: String(obj.workflowDigest ?? ""),
196
+ coverage: coverage.map((c) => ({
197
+ source: String(c.source ?? ""),
198
+ status: String(c.status ?? ""),
199
+ lastImportedAt: c.lastImportedAt == null ? null : String(c.lastImportedAt)
200
+ })),
201
+ runId: optionalString(obj.runId),
202
+ currentArtifactPath: optionalString(obj.currentArtifactPath),
203
+ baselineArtifactPath: obj.baselineArtifactPath == null ? null : String(obj.baselineArtifactPath),
204
+ spurVersion: optionalString(obj.spurVersion),
205
+ schemaVersion: obj.schemaVersion == null ? undefined : Number(obj.schemaVersion),
206
+ executor: optionalString(obj.executor),
207
+ model: optionalString(obj.model),
208
+ cacheDisposition: DISPOSITIONS.includes(obj.cacheDisposition) ? obj.cacheDisposition : undefined
209
+ };
210
+ } catch {
211
+ return null;
212
+ }
213
+ }
214
+ function decideCache(cached, current, opts) {
215
+ if (opts.recompute)
216
+ return { disposition: "forced-recompute", reasons: ["recompute"] };
217
+ if (cached === null)
218
+ return { disposition: "miss", reasons: ["no-cache"] };
219
+ const reasons = [];
220
+ const id = cached.identity;
221
+ const cur = current.identity;
222
+ if (id.contractVersion !== cur.contractVersion)
223
+ reasons.push("identity:contractVersion");
224
+ if (id.mode !== cur.mode)
225
+ reasons.push("identity:mode");
226
+ if (id.date !== cur.date)
227
+ reasons.push("identity:date");
228
+ if (id.timezone !== cur.timezone)
229
+ reasons.push("identity:timezone");
230
+ if (id.bounds.since !== cur.bounds.since || id.bounds.until !== cur.bounds.until)
231
+ reasons.push("identity:bounds");
232
+ if ([...id.sources].sort().join("\x00") !== [...cur.sources].sort().join("\x00"))
233
+ reasons.push("identity:sources");
234
+ if (cached.artifactDigest !== current.artifactDigest)
235
+ reasons.push("data-changed");
236
+ if (cached.contractDigest !== current.contractDigest)
237
+ reasons.push("logic-changed:contract");
238
+ if (cached.skillDigest !== current.skillDigest)
239
+ reasons.push("logic-changed:skill");
240
+ if (cached.workflowDigest !== current.workflowDigest)
241
+ reasons.push("logic-changed:workflow");
242
+ const currentSources = new Set(current.coverage.map((c) => c.source));
243
+ if (cached.coverage.some((c) => !currentSources.has(c.source)))
244
+ reasons.push("coverage-degraded");
245
+ if (cached.windowState === "provisional" && opts.dayClosed)
246
+ reasons.push("window-closed");
247
+ return { disposition: reasons.length === 0 ? "hit" : "miss", reasons };
248
+ }
249
+ function escapeRe(s) {
250
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
251
+ }
252
+ function checkReportStructure(reportMarkdown) {
253
+ const problems = [];
254
+ if (/TODO|PLACEHOLDER|FIXME|^\|\s*\|/im.test(reportMarkdown))
255
+ problems.push("placeholder-or-todo-present");
256
+ let lastIdx = -1;
257
+ for (const section of ELEVEN_SECTIONS) {
258
+ const re = new RegExp(`^#{2,3}\\s+${escapeRe(section)}\\s*$`, "m");
259
+ const m = reportMarkdown.match(re);
260
+ if (m === null || (m.index ?? -1) <= lastIdx) {
261
+ problems.push(`section-missing-or-out-of-order:${section}`);
262
+ } else if (m.index !== undefined) {
263
+ lastIdx = m.index;
264
+ }
265
+ }
266
+ const findingRows = reportMarkdown.match(/^\|\s*(reliability|repetition|workflow|performance|coverage|telemetry|positive):[^|]+/gm);
267
+ for (const row of findingRows ?? []) {
268
+ for (const field of FINDING_FIELDS) {
269
+ if (!row.toLowerCase().includes(field))
270
+ problems.push(`finding-missing-field:${field}`);
271
+ }
272
+ }
273
+ const ledgerIdx = reportMarkdown.search(/^#{2,3}\s+Evidence\s+ledger/im);
274
+ if (ledgerIdx !== -1) {
275
+ const ledgerSection = reportMarkdown.slice(ledgerIdx);
276
+ const sep = ledgerSection.match(/^\|[\s:|-]+\|[ \t]*$/m);
277
+ const body = sep?.index === undefined ? ledgerSection : ledgerSection.slice(sep.index + sep[0].length);
278
+ const claimRows = body.match(/^[|>]\s+\S.*$/gm) ?? [];
279
+ for (const row of claimRows) {
280
+ const hasAnchor = /`[^`]+:\d+`|`[^`]+\.(md|ts|json)`|[a-z][a-z0-9_-]*\/[a-z][a-z0-9_./-]*:[0-9]+/i.test(row);
281
+ if (!hasAnchor) {
282
+ problems.push("evidence-claim-without-anchor");
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ return { ok: problems.length === 0, problems };
288
+ }
289
+ var NOT_AVAILABLE = "not available";
290
+ function logicDigest(path) {
291
+ if (path === undefined || path === "" || !existsSync(path))
292
+ return NOT_AVAILABLE;
293
+ try {
294
+ const h = createHash2("sha256");
295
+ if (statSync(path).isDirectory()) {
296
+ 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();
297
+ for (const f of walk(path)) {
298
+ h.update(f.slice(path.length));
299
+ h.update(readFileSync(f));
300
+ }
301
+ } else {
302
+ h.update(readFileSync(path));
303
+ }
304
+ return h.digest("hex");
305
+ } catch {
306
+ return NOT_AVAILABLE;
307
+ }
308
+ }
309
+ function importedSnapshotAsOf(coverage) {
310
+ const stamps = coverage.map((c) => c.lastImportedAt).filter((v) => typeof v === "string" && v !== "");
311
+ if (stamps.length === 0 || stamps.length !== coverage.length)
312
+ return NOT_AVAILABLE;
313
+ return [...stamps].sort()[0] ?? NOT_AVAILABLE;
314
+ }
315
+ function localDay(tz, at = new Date) {
316
+ try {
317
+ return new Intl.DateTimeFormat("en-CA", { timeZone: tz, dateStyle: "short" }).format(at);
318
+ } catch {
319
+ return at.toISOString().slice(0, 10);
320
+ }
321
+ }
322
+ function resolvePaths(opts) {
323
+ const pluginRoot = opts.helper.replace(/\/scripts\/[^/]+$/, "");
324
+ const skill = `${pluginRoot}/skills/history-anatomy`;
325
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
326
+ const date = opts.date !== undefined && opts.date !== "" ? opts.date : localDay(tz, opts.now ?? new Date);
327
+ const target = opts.output !== undefined && opts.output !== "" ? opts.output : `${opts.reportDir}/${date}-history-anatomy.md`;
328
+ return `HA_HELPER=${opts.helper}
329
+ HA_SKILL=${skill}
330
+ HA_TARGET=${target}
331
+ HA_DATE=${date}
332
+ `;
333
+ }
334
+ function buildProvenance(opts) {
335
+ let raw;
336
+ try {
337
+ raw = JSON.parse(readFileSync(opts.artifact, "utf8"));
338
+ } catch (err) {
339
+ throw new Error(`could not parse fresh analyze artifact at ${opts.artifact}: ${err.message}`);
340
+ }
341
+ const coverage = (raw.coverage ?? []).map((c) => ({
342
+ source: String(c.source ?? ""),
343
+ status: String(c.status ?? ""),
344
+ lastImportedAt: c.lastImportedAt == null ? null : String(c.lastImportedAt)
345
+ }));
346
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
347
+ const now = opts.now ?? new Date;
348
+ const date = opts.date !== undefined && opts.date !== "" ? opts.date : localDay(tz, now);
349
+ const windowState = opts.mode === "ad-hoc" || date < localDay(tz, now) ? "closed" : "provisional";
350
+ const nowIso = now.toISOString();
351
+ return {
352
+ identity: {
353
+ contractVersion: opts.contractVersion ?? "1",
354
+ mode: opts.mode,
355
+ date,
356
+ timezone: tz,
357
+ bounds: { since: String(raw.selector?.since ?? ""), until: String(raw.selector?.until ?? "") },
358
+ sources: coverage.map((c) => c.source).sort()
359
+ },
360
+ windowState,
361
+ generatedAt: nowIso,
362
+ validatedAt: nowIso,
363
+ artifactDigest: semanticArtifactDigest(raw),
364
+ baselineArtifactDigest: (() => {
365
+ if (!(opts.baseline !== undefined && existsSync(opts.baseline)))
366
+ return null;
367
+ try {
368
+ return semanticArtifactDigest(JSON.parse(readFileSync(opts.baseline, "utf8")));
369
+ } catch (err) {
370
+ throw new Error(`could not parse baseline artifact at ${opts.baseline}: ${err.message}`);
371
+ }
372
+ })(),
373
+ contractDigest: logicDigest(opts.contractFile),
374
+ skillDigest: logicDigest(opts.skillDir),
375
+ workflowDigest: logicDigest(opts.workflowFile),
376
+ coverage,
377
+ runId: opts.runId,
378
+ currentArtifactPath: opts.artifact,
379
+ baselineArtifactPath: opts.baseline ?? null,
380
+ spurVersion: opts.spurVersion ?? NOT_AVAILABLE,
381
+ schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : undefined,
382
+ executor: opts.executor ?? NOT_AVAILABLE,
383
+ model: opts.model ?? NOT_AVAILABLE
384
+ };
385
+ }
386
+ function probe(opts) {
387
+ const current = buildProvenance(opts);
388
+ const cachedText = existsSync(opts.target) ? readFileSync(opts.target, "utf8") : null;
389
+ const cached = cachedText === null ? null : parseProvenance(cachedText);
390
+ const decision = opts.mode === "ad-hoc" ? { disposition: "miss", reasons: ["ad-hoc-never-cached"] } : decideCache(cached, current, {
391
+ recompute: opts.recompute,
392
+ dayClosed: current.windowState === "closed"
393
+ });
394
+ current.cacheDisposition = decision.disposition;
395
+ return { decision, current };
396
+ }
397
+ var YAML_KEYS = [
398
+ "windowState",
399
+ "generatedAt",
400
+ "validatedAt",
401
+ "artifactDigest",
402
+ "baselineArtifactDigest",
403
+ "contractDigest",
404
+ "skillDigest",
405
+ "workflowDigest",
406
+ "runId",
407
+ "currentArtifactPath",
408
+ "baselineArtifactPath",
409
+ "spurVersion",
410
+ "schemaVersion",
411
+ "executor",
412
+ "model",
413
+ "cacheDisposition"
414
+ ];
415
+ function yamlScalar(v) {
416
+ if (v === null || v === undefined)
417
+ return "null";
418
+ if (typeof v === "number" || typeof v === "boolean")
419
+ return String(v);
420
+ return `"${String(v).replaceAll('"', "\\\"")}"`;
421
+ }
422
+ function renderProvenanceFrontmatter(p) {
423
+ const lines = [
424
+ "---",
425
+ "identity:",
426
+ ` contractVersion: ${yamlScalar(p.identity.contractVersion)}`,
427
+ ` mode: ${p.identity.mode}`,
428
+ ` date: ${yamlScalar(p.identity.date)}`,
429
+ ` timezone: ${p.identity.timezone}`,
430
+ " bounds:",
431
+ ` since: ${p.identity.bounds.since}`,
432
+ ` until: ${p.identity.bounds.until}`,
433
+ " sources:"
434
+ ];
435
+ for (const s of p.identity.sources)
436
+ lines.push(` - source: ${s}`);
437
+ for (const k of YAML_KEYS) {
438
+ if (p[k] === undefined)
439
+ continue;
440
+ lines.push(`${k}: ${yamlScalar(p[k])}`);
441
+ }
442
+ lines.push("coverage:");
443
+ for (const c of p.coverage) {
444
+ lines.push(` - source: ${c.source}, status: ${c.status}, lastImportedAt: ${c.lastImportedAt ?? "null"}`);
445
+ }
446
+ lines.push("---");
447
+ return lines.join(`
448
+ `);
449
+ }
450
+ function bannerLine(p) {
451
+ return `> imported snapshot as of ${importedSnapshotAsOf(p.coverage)} · window ${p.windowState} · cache ${p.cacheDisposition ?? NOT_AVAILABLE}`;
452
+ }
453
+ function stripHeader(md) {
454
+ const body = md.replace(/^---\n[\s\S]*?\n---\n?/, "").replace(/^\n+/, "");
455
+ return body.replace(/^> imported snapshot as of [^\n]*\n+/, "");
456
+ }
457
+ function stampReport(candidateMarkdown, p) {
458
+ return `${renderProvenanceFrontmatter(p)}
459
+
460
+ ${bannerLine(p)}
461
+
462
+ ${stripHeader(candidateMarkdown).replace(/^\n+/, "")}`;
463
+ }
464
+ function refreshReport(publishedMarkdown, validatedAt, disposition) {
465
+ const cached = parseProvenance(publishedMarkdown);
466
+ if (cached === null)
467
+ return publishedMarkdown;
468
+ const refreshed = { ...cached, validatedAt, cacheDisposition: disposition };
469
+ return stampReport(publishedMarkdown, refreshed);
470
+ }
471
+ function publishAtomically(candidatePath, targetPath) {
472
+ const tmpPath = `${targetPath}.tmp`;
473
+ try {
474
+ writeFileSync(tmpPath, readFileSync(candidatePath));
475
+ const fd = openSync(tmpPath, "r");
476
+ try {
477
+ fsyncSync(fd);
478
+ } finally {
479
+ closeSync(fd);
480
+ }
481
+ renameSync(tmpPath, targetPath);
482
+ } catch (err) {
483
+ try {
484
+ rmSync(tmpPath, { force: true });
485
+ } catch {}
486
+ throw err;
487
+ }
488
+ }
489
+ var VALID_COMMANDS = "digest, check, paths, probe, stamp, refresh, publish";
490
+ 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>]";
491
+ function parseFlags(args) {
492
+ const out = {};
493
+ for (let i = 0;i < args.length; i++) {
494
+ const a = args[i] ?? "";
495
+ if (!a.startsWith("--"))
496
+ continue;
497
+ const key = a.slice(2);
498
+ const next = args[i + 1];
499
+ if (next === undefined || next.startsWith("--")) {
500
+ out[key] = "true";
501
+ } else {
502
+ out[key] = next;
503
+ i++;
504
+ }
505
+ }
506
+ return out;
507
+ }
508
+ function runCacheCli(argv) {
509
+ const [cmd, a, b] = argv;
510
+ switch (cmd) {
511
+ case "digest": {
512
+ if (a === undefined) {
513
+ return { exitCode: 1, stdout: "", stderr: `usage: <script> digest <artifact.json>
514
+ ` };
515
+ }
516
+ let artifact;
517
+ try {
518
+ artifact = JSON.parse(readFileSync(a, "utf8"));
519
+ } catch {
520
+ return { exitCode: 1, stdout: "", stderr: `could not parse artifact at ${a}
521
+ ` };
522
+ }
523
+ const digest = semanticArtifactDigest(artifact);
524
+ return { exitCode: 0, stdout: `${digest}
525
+ `, stderr: "" };
526
+ }
527
+ case "check": {
528
+ if (a === undefined) {
529
+ return { exitCode: 1, stdout: "", stderr: `usage: <script> check <report.md>
530
+ ` };
531
+ }
532
+ const result = checkReportStructure(readFileSync(a, "utf8"));
533
+ const stdout = `${result.ok ? "PASS" : "FAIL"}
534
+ ${result.problems.map((p) => `- ${p}
535
+ `).join("")}`;
536
+ return { exitCode: result.ok ? 0 : 1, stdout, stderr: "" };
537
+ }
538
+ case "publish": {
539
+ if (a === undefined || b === undefined) {
540
+ return { exitCode: 1, stdout: "", stderr: `usage: <script> publish <candidate.md> <target.md>
541
+ ` };
542
+ }
543
+ publishAtomically(a, b);
544
+ return { exitCode: 0, stdout: "", stderr: "" };
545
+ }
546
+ case "paths": {
547
+ const f = parseFlags(argv.slice(1));
548
+ if (f.helper === undefined || f.out === undefined) {
549
+ return {
550
+ exitCode: 1,
551
+ stdout: "",
552
+ stderr: `usage: <script> paths --helper <p> --out <env> [--report-dir <d>] [--date <d>] [--output <p>]
553
+ `
554
+ };
555
+ }
556
+ writeFileSync(f.out, resolvePaths({
557
+ helper: f.helper,
558
+ reportDir: f["report-dir"] ?? "docs/report",
559
+ date: f.date,
560
+ output: f.output
561
+ }));
562
+ return { exitCode: 0, stdout: "", stderr: "" };
563
+ }
564
+ case "probe": {
565
+ const f = parseFlags(argv.slice(1));
566
+ if (f.artifact === undefined || f.target === undefined) {
567
+ return { exitCode: 1, stdout: "", stderr: `usage: ${PROBE_USAGE}
568
+ ` };
569
+ }
570
+ let result;
571
+ try {
572
+ result = probe({
573
+ artifact: f.artifact,
574
+ target: f.target,
575
+ baseline: f.baseline,
576
+ mode: f.mode === "ad-hoc" ? "ad-hoc" : "daily",
577
+ date: f.date,
578
+ recompute: f.recompute === "true",
579
+ executor: f.executor,
580
+ model: f.model,
581
+ skillDir: f["skill-dir"],
582
+ contractFile: f.contract,
583
+ workflowFile: f.workflow,
584
+ contractVersion: f["contract-version"],
585
+ runId: f["run-id"],
586
+ spurVersion: f["spur-version"]
587
+ });
588
+ } catch {
589
+ return { exitCode: 1, stdout: "", stderr: `could not read artifact at ${f.artifact}
590
+ ` };
591
+ }
592
+ if (f.out !== undefined)
593
+ writeFileSync(f.out, `${JSON.stringify(result.current, null, 2)}
594
+ `);
595
+ const reasons = result.decision.reasons.map((r) => `- ${r}
596
+ `).join("");
597
+ return { exitCode: 0, stdout: `${result.decision.disposition}
598
+ ${reasons}`, stderr: "" };
599
+ }
600
+ case "stamp": {
601
+ const f = parseFlags(argv.slice(1));
602
+ if (f.candidate === undefined || f.provenance === undefined || f.out === undefined) {
603
+ return {
604
+ exitCode: 1,
605
+ stdout: "",
606
+ stderr: `usage: <script> stamp --candidate <c.md> --provenance <p.json> --out <o.md>
607
+ `
608
+ };
609
+ }
610
+ try {
611
+ const p = JSON.parse(readFileSync(f.provenance, "utf8"));
612
+ writeFileSync(f.out, `${stampReport(readFileSync(f.candidate, "utf8"), p)}
613
+ `);
614
+ } catch {
615
+ return { exitCode: 1, stdout: "", stderr: `stamp: could not read candidate or provenance
616
+ ` };
617
+ }
618
+ return { exitCode: 0, stdout: "", stderr: "" };
619
+ }
620
+ case "refresh": {
621
+ const f = parseFlags(argv.slice(1));
622
+ if (f.report === undefined || f.out === undefined) {
623
+ return {
624
+ exitCode: 1,
625
+ stdout: "",
626
+ stderr: `usage: <script> refresh --report <published.md> --out <o.md> [--disposition hit]
627
+ `
628
+ };
629
+ }
630
+ try {
631
+ const disposition = f.disposition ?? "hit";
632
+ const refreshed = refreshReport(readFileSync(f.report, "utf8"), f["validated-at"] ?? new Date().toISOString(), disposition);
633
+ writeFileSync(f.out, refreshed.endsWith(`
634
+ `) ? refreshed : `${refreshed}
635
+ `);
636
+ } catch {
637
+ return { exitCode: 1, stdout: "", stderr: `refresh: could not read report at ${f.report}
638
+ ` };
639
+ }
640
+ return { exitCode: 0, stdout: "", stderr: "" };
641
+ }
642
+ default:
643
+ return { exitCode: 1, stdout: "", stderr: `valid commands: ${VALID_COMMANDS}
644
+ ` };
645
+ }
646
+ }
647
+ {
648
+ const { exitCode, stdout, stderr } = runCacheCli(process.argv.slice(2));
649
+ process.stdout.write(stdout);
650
+ process.stderr.write(stderr);
651
+ process.exitCode = exitCode;
652
+ }
653
+ export {
654
+ stampReport,
655
+ semanticArtifactDigest,
656
+ runCacheCli,
657
+ resolvePaths,
658
+ renderProvenanceFrontmatter,
659
+ refreshReport,
660
+ publishAtomically,
661
+ probe,
662
+ parseProvenance,
663
+ logicDigest,
664
+ importedSnapshotAsOf,
665
+ decideCache,
666
+ checkReportStructure,
667
+ buildProvenance,
668
+ bannerLine
669
+ };