martin-loop 0.1.4 → 1.3.0

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 (286) hide show
  1. package/CODE_OF_CONDUCT.md +32 -0
  2. package/README.md +172 -227
  3. package/demo/seeded-workspace/README.md +35 -0
  4. package/demo/seeded-workspace/TASKS.md +29 -0
  5. package/demo/seeded-workspace/martin.config.yaml +11 -0
  6. package/demo/seeded-workspace/package.json +8 -0
  7. package/demo/seeded-workspace/src/invoice-summary.js +11 -0
  8. package/demo/seeded-workspace/test/invoice-summary.test.js +20 -0
  9. package/dist/bin/martin-loop.js +0 -0
  10. package/dist/vendor/adapters/claude-cli.d.ts +19 -4
  11. package/dist/vendor/adapters/claude-cli.js +55 -24
  12. package/dist/vendor/adapters/cli-bridge.d.ts +1 -0
  13. package/dist/vendor/adapters/cli-bridge.js +154 -28
  14. package/dist/vendor/adapters/counter.d.ts +1 -0
  15. package/dist/vendor/adapters/counter.js +4 -0
  16. package/dist/vendor/adapters/git-baseline.d.ts +50 -0
  17. package/dist/vendor/adapters/git-baseline.js +233 -0
  18. package/dist/vendor/adapters/index.d.ts +1 -0
  19. package/dist/vendor/adapters/index.js +1 -0
  20. package/dist/vendor/adapters/openrouter-adapter.d.ts +15 -0
  21. package/dist/vendor/adapters/openrouter-adapter.js +302 -0
  22. package/dist/vendor/adapters/usage.d.ts +48 -0
  23. package/dist/vendor/adapters/usage.js +66 -0
  24. package/dist/vendor/adapters/verifier-only.d.ts +7 -0
  25. package/dist/vendor/adapters/verifier-only.js +57 -0
  26. package/dist/vendor/cli/bin/exit.d.ts +12 -0
  27. package/dist/vendor/cli/bin/exit.js +28 -0
  28. package/dist/vendor/cli/commands/analyze.d.ts +5 -0
  29. package/dist/vendor/cli/commands/analyze.js +58 -0
  30. package/dist/vendor/cli/commands/audit-log-verify.d.ts +34 -0
  31. package/dist/vendor/cli/commands/audit-log-verify.js +99 -0
  32. package/dist/vendor/cli/commands/audit.d.ts +8 -0
  33. package/dist/vendor/cli/commands/audit.js +199 -0
  34. package/dist/vendor/cli/commands/corpus.d.ts +5 -0
  35. package/dist/vendor/cli/commands/corpus.js +60 -0
  36. package/dist/vendor/cli/commands/doctor.d.ts +8 -0
  37. package/dist/vendor/cli/commands/doctor.js +219 -0
  38. package/dist/vendor/cli/commands/explain.d.ts +17 -0
  39. package/dist/vendor/cli/commands/explain.js +176 -0
  40. package/dist/vendor/cli/commands/export.d.ts +5 -0
  41. package/dist/vendor/cli/commands/export.js +60 -0
  42. package/dist/vendor/cli/commands/governance.d.ts +8 -0
  43. package/dist/vendor/cli/commands/governance.js +95 -0
  44. package/dist/vendor/cli/commands/improve.d.ts +18 -0
  45. package/dist/vendor/cli/commands/improve.js +396 -0
  46. package/dist/vendor/cli/commands/init.d.ts +8 -0
  47. package/dist/vendor/cli/commands/init.js +281 -0
  48. package/dist/vendor/cli/commands/migration.d.ts +8 -0
  49. package/dist/vendor/cli/commands/migration.js +67 -0
  50. package/dist/vendor/cli/commands/prior.d.ts +23 -0
  51. package/dist/vendor/cli/commands/prior.js +145 -0
  52. package/dist/vendor/cli/commands/resume.d.ts +21 -0
  53. package/dist/vendor/cli/commands/resume.js +73 -0
  54. package/dist/vendor/cli/commands/verify.d.ts +6 -0
  55. package/dist/vendor/cli/commands/verify.js +43 -0
  56. package/dist/vendor/cli/index.d.ts +6 -1
  57. package/dist/vendor/cli/index.js +124 -7
  58. package/dist/vendor/cli/research/public-corpus.d.ts +43 -0
  59. package/dist/vendor/cli/research/public-corpus.js +151 -0
  60. package/dist/vendor/cli/ui/error-card.d.ts +38 -0
  61. package/dist/vendor/cli/ui/error-card.js +103 -0
  62. package/dist/vendor/cli/ui/mission-brief.d.ts +41 -0
  63. package/dist/vendor/cli/ui/mission-brief.js +173 -0
  64. package/dist/vendor/cli/ui/summary-card.d.ts +34 -0
  65. package/dist/vendor/cli/ui/summary-card.js +102 -0
  66. package/dist/vendor/contracts/audit.d.ts +46 -0
  67. package/dist/vendor/contracts/audit.js +360 -0
  68. package/dist/vendor/contracts/index.d.ts +3 -1
  69. package/dist/vendor/contracts/post-phase15.d.ts +240 -0
  70. package/dist/vendor/contracts/post-phase15.js +166 -0
  71. package/dist/vendor/core/agent/mandates.d.ts +46 -0
  72. package/dist/vendor/core/agent/mandates.js +178 -0
  73. package/dist/vendor/core/agent/receipts.d.ts +38 -0
  74. package/dist/vendor/core/agent/receipts.js +131 -0
  75. package/dist/vendor/core/agent/signing.d.ts +17 -0
  76. package/dist/vendor/core/agent/signing.js +91 -0
  77. package/dist/vendor/core/attestation/sign.d.ts +25 -0
  78. package/dist/vendor/core/attestation/sign.js +216 -0
  79. package/dist/vendor/core/autonomy/autonomous-promotion.d.ts +120 -0
  80. package/dist/vendor/core/autonomy/autonomous-promotion.js +346 -0
  81. package/dist/vendor/core/autonomy/envelope-v2.d.ts +29 -0
  82. package/dist/vendor/core/autonomy/envelope-v2.js +60 -0
  83. package/dist/vendor/core/autonomy/envelope.d.ts +17 -0
  84. package/dist/vendor/core/autonomy/envelope.js +27 -0
  85. package/dist/vendor/core/autonomy/escalation-ledger.d.ts +20 -0
  86. package/dist/vendor/core/autonomy/escalation-ledger.js +18 -0
  87. package/dist/vendor/core/autonomy/resume.d.ts +15 -0
  88. package/dist/vendor/core/autonomy/resume.js +23 -0
  89. package/dist/vendor/core/circuit/circuit-breaker.d.ts +60 -0
  90. package/dist/vendor/core/circuit/circuit-breaker.js +143 -0
  91. package/dist/vendor/core/compiler.d.ts +2 -0
  92. package/dist/vendor/core/compiler.js +10 -4
  93. package/dist/vendor/core/context-distillation.d.ts +3 -0
  94. package/dist/vendor/core/context-distillation.js +44 -0
  95. package/dist/vendor/core/context-flow/compile-context.d.ts +8 -0
  96. package/dist/vendor/core/context-flow/compile-context.js +111 -0
  97. package/dist/vendor/core/context-flow/entities.d.ts +2 -0
  98. package/dist/vendor/core/context-flow/entities.js +44 -0
  99. package/dist/vendor/core/context-flow/evaluate-policy.d.ts +2 -0
  100. package/dist/vendor/core/context-flow/evaluate-policy.js +42 -0
  101. package/dist/vendor/core/context-flow/index.d.ts +11 -0
  102. package/dist/vendor/core/context-flow/index.js +24 -0
  103. package/dist/vendor/core/context-flow/labels.d.ts +3 -0
  104. package/dist/vendor/core/context-flow/labels.js +17 -0
  105. package/dist/vendor/core/context-flow/normalizer.d.ts +9 -0
  106. package/dist/vendor/core/context-flow/normalizer.js +69 -0
  107. package/dist/vendor/core/context-flow/profiles.d.ts +33 -0
  108. package/dist/vendor/core/context-flow/profiles.js +36 -0
  109. package/dist/vendor/core/context-flow/redaction.d.ts +1 -0
  110. package/dist/vendor/core/context-flow/redaction.js +6 -0
  111. package/dist/vendor/core/context-flow/sensitivity.d.ts +2 -0
  112. package/dist/vendor/core/context-flow/sensitivity.js +27 -0
  113. package/dist/vendor/core/context-flow/sync-preview.d.ts +2 -0
  114. package/dist/vendor/core/context-flow/sync-preview.js +22 -0
  115. package/dist/vendor/core/context-flow/token-estimator.d.ts +3 -0
  116. package/dist/vendor/core/context-flow/token-estimator.js +13 -0
  117. package/dist/vendor/core/context-flow/types.d.ts +91 -0
  118. package/dist/vendor/core/context-flow/types.js +2 -0
  119. package/dist/vendor/core/context-integrity.d.ts +26 -0
  120. package/dist/vendor/core/context-integrity.js +56 -0
  121. package/dist/vendor/core/context-utility.d.ts +47 -0
  122. package/dist/vendor/core/context-utility.js +405 -0
  123. package/dist/vendor/core/cost/pipeline.d.ts +92 -0
  124. package/dist/vendor/core/cost/pipeline.js +141 -0
  125. package/dist/vendor/core/cost/tagged-cost.d.ts +27 -0
  126. package/dist/vendor/core/cost/tagged-cost.js +55 -0
  127. package/dist/vendor/core/cost-governor.d.ts +2 -0
  128. package/dist/vendor/core/cost-governor.js +50 -0
  129. package/dist/vendor/core/cve/cve-check.d.ts +80 -0
  130. package/dist/vendor/core/cve/cve-check.js +172 -0
  131. package/dist/vendor/core/digital-twin/index.d.ts +27 -0
  132. package/dist/vendor/core/digital-twin/index.js +90 -0
  133. package/dist/vendor/core/drift/drift-graph.d.ts +47 -0
  134. package/dist/vendor/core/drift/drift-graph.js +100 -0
  135. package/dist/vendor/core/drift/objective-lock.d.ts +69 -0
  136. package/dist/vendor/core/drift/objective-lock.js +88 -0
  137. package/dist/vendor/core/drift/scope.d.ts +46 -0
  138. package/dist/vendor/core/drift/scope.js +102 -0
  139. package/dist/vendor/core/drift/signature-lock.d.ts +48 -0
  140. package/dist/vendor/core/drift/signature-lock.js +202 -0
  141. package/dist/vendor/core/drift/stale-proof-gate.d.ts +21 -0
  142. package/dist/vendor/core/drift/stale-proof-gate.js +19 -0
  143. package/dist/vendor/core/eval/known-bad-world-runner.d.ts +24 -0
  144. package/dist/vendor/core/eval/known-bad-world-runner.js +256 -0
  145. package/dist/vendor/core/evidence/claim-audit.d.ts +18 -0
  146. package/dist/vendor/core/evidence/claim-audit.js +89 -0
  147. package/dist/vendor/core/exit-intelligence.d.ts +2 -0
  148. package/dist/vendor/core/exit-intelligence.js +58 -0
  149. package/dist/vendor/core/explain/formatter.d.ts +42 -0
  150. package/dist/vendor/core/explain/formatter.js +171 -0
  151. package/dist/vendor/core/explain/timeline.d.ts +29 -0
  152. package/dist/vendor/core/explain/timeline.js +213 -0
  153. package/dist/vendor/core/failure-taxonomy.d.ts +2 -0
  154. package/dist/vendor/core/failure-taxonomy.js +76 -0
  155. package/dist/vendor/core/gateway/index.d.ts +10 -0
  156. package/dist/vendor/core/gateway/index.js +12 -0
  157. package/dist/vendor/core/gateway/registry.d.ts +40 -0
  158. package/dist/vendor/core/gateway/registry.js +97 -0
  159. package/dist/vendor/core/gateway/transport.d.ts +31 -0
  160. package/dist/vendor/core/gateway/transport.js +82 -0
  161. package/dist/vendor/core/gateway/vault.d.ts +19 -0
  162. package/dist/vendor/core/gateway/vault.js +29 -0
  163. package/dist/vendor/core/graph/adapters.d.ts +43 -0
  164. package/dist/vendor/core/graph/adapters.js +91 -0
  165. package/dist/vendor/core/graph/hotspots.d.ts +22 -0
  166. package/dist/vendor/core/graph/hotspots.js +30 -0
  167. package/dist/vendor/core/graph/index.d.ts +1 -0
  168. package/dist/vendor/core/graph/index.js +2 -0
  169. package/dist/vendor/core/honey/honey-tokens.d.ts +32 -0
  170. package/dist/vendor/core/honey/honey-tokens.js +44 -0
  171. package/dist/vendor/core/index.d.ts +7 -4
  172. package/dist/vendor/core/index.js +222 -64
  173. package/dist/vendor/core/learning/bayesian-update.d.ts +31 -0
  174. package/dist/vendor/core/learning/bayesian-update.js +60 -0
  175. package/dist/vendor/core/learning/prior-sets.d.ts +42 -0
  176. package/dist/vendor/core/learning/prior-sets.js +111 -0
  177. package/dist/vendor/core/learning/promotion-gate.d.ts +17 -0
  178. package/dist/vendor/core/learning/promotion-gate.js +23 -0
  179. package/dist/vendor/core/leash/blast-radius.d.ts +42 -0
  180. package/dist/vendor/core/leash/blast-radius.js +156 -0
  181. package/dist/vendor/core/leash/policy-leash.d.ts +31 -0
  182. package/dist/vendor/core/leash/policy-leash.js +117 -0
  183. package/dist/vendor/core/memo/memo.d.ts +63 -0
  184. package/dist/vendor/core/memo/memo.js +97 -0
  185. package/dist/vendor/core/memory/learning-pipeline.d.ts +154 -0
  186. package/dist/vendor/core/memory/learning-pipeline.js +391 -0
  187. package/dist/vendor/core/memory/palace.d.ts +84 -0
  188. package/dist/vendor/core/memory/palace.js +379 -0
  189. package/dist/vendor/core/merge/ast-merge.d.ts +22 -0
  190. package/dist/vendor/core/merge/ast-merge.js +350 -0
  191. package/dist/vendor/core/merge/text-merge.d.ts +12 -0
  192. package/dist/vendor/core/merge/text-merge.js +182 -0
  193. package/dist/vendor/core/otel/tracer.d.ts +45 -0
  194. package/dist/vendor/core/otel/tracer.js +116 -0
  195. package/dist/vendor/core/parallel/parallel-attempts.d.ts +28 -0
  196. package/dist/vendor/core/parallel/parallel-attempts.js +41 -0
  197. package/dist/vendor/core/parallel/scorer.d.ts +24 -0
  198. package/dist/vendor/core/parallel/scorer.js +65 -0
  199. package/dist/vendor/core/pattern-detection.d.ts +64 -0
  200. package/dist/vendor/core/pattern-detection.js +108 -0
  201. package/dist/vendor/core/persistence/checkpoint.d.ts +44 -0
  202. package/dist/vendor/core/persistence/checkpoint.js +156 -0
  203. package/dist/vendor/core/persistence/cleanup.d.ts +22 -0
  204. package/dist/vendor/core/persistence/cleanup.js +131 -0
  205. package/dist/vendor/core/persistence/index.d.ts +2 -0
  206. package/dist/vendor/core/persistence/index.js +1 -0
  207. package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
  208. package/dist/vendor/core/persistence/runs-reader.js +84 -0
  209. package/dist/vendor/core/persistence/store.d.ts +6 -1
  210. package/dist/vendor/core/persistence/store.js +5 -0
  211. package/dist/vendor/core/policy/file-touch-quota.d.ts +60 -0
  212. package/dist/vendor/core/policy/file-touch-quota.js +105 -0
  213. package/dist/vendor/core/policy/policy-loader.d.ts +30 -0
  214. package/dist/vendor/core/policy/policy-loader.js +170 -0
  215. package/dist/vendor/core/policy/policy-schema.d.ts +55 -0
  216. package/dist/vendor/core/policy/policy-schema.js +78 -0
  217. package/dist/vendor/core/policy.d.ts +6 -0
  218. package/dist/vendor/core/probe/probe.d.ts +49 -0
  219. package/dist/vendor/core/probe/probe.js +115 -0
  220. package/dist/vendor/core/proof/patch-proof.d.ts +58 -0
  221. package/dist/vendor/core/proof/patch-proof.js +84 -0
  222. package/dist/vendor/core/proof/semantic-probe.d.ts +25 -0
  223. package/dist/vendor/core/proof/semantic-probe.js +82 -0
  224. package/dist/vendor/core/recovery/failure-mode-runner.d.ts +29 -0
  225. package/dist/vendor/core/recovery/failure-mode-runner.js +39 -0
  226. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  227. package/dist/vendor/core/red-blue/red-phase.js +141 -0
  228. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  229. package/dist/vendor/core/red-blue/risk-tiers.js +33 -0
  230. package/dist/vendor/core/replay/replay.d.ts +85 -0
  231. package/dist/vendor/core/replay/replay.js +109 -0
  232. package/dist/vendor/core/router/engine.d.ts +54 -0
  233. package/dist/vendor/core/router/engine.js +131 -0
  234. package/dist/vendor/core/router/index.d.ts +1 -0
  235. package/dist/vendor/core/router/index.js +2 -0
  236. package/dist/vendor/core/router/trust-calibration.d.ts +57 -0
  237. package/dist/vendor/core/router/trust-calibration.js +127 -0
  238. package/dist/vendor/core/run-martin.d.ts +2 -0
  239. package/dist/vendor/core/run-martin.js +287 -0
  240. package/dist/vendor/core/security/cve-scanner.d.ts +62 -0
  241. package/dist/vendor/core/security/cve-scanner.js +178 -0
  242. package/dist/vendor/core/sentinel/efficiency-sentinel.d.ts +29 -0
  243. package/dist/vendor/core/sentinel/efficiency-sentinel.js +30 -0
  244. package/dist/vendor/core/sentinel/progress-guard.d.ts +35 -0
  245. package/dist/vendor/core/sentinel/progress-guard.js +46 -0
  246. package/dist/vendor/core/siem/siem-emitter.d.ts +49 -0
  247. package/dist/vendor/core/siem/siem-emitter.js +157 -0
  248. package/dist/vendor/core/strategy/attempt-brief.d.ts +22 -0
  249. package/dist/vendor/core/strategy/attempt-brief.js +89 -0
  250. package/dist/vendor/core/summarize/diff-summary.d.ts +35 -0
  251. package/dist/vendor/core/summarize/diff-summary.js +204 -0
  252. package/dist/vendor/core/surface-signals.d.ts +21 -0
  253. package/dist/vendor/core/surface-signals.js +139 -0
  254. package/dist/vendor/core/truth/truth-wall.d.ts +51 -0
  255. package/dist/vendor/core/truth/truth-wall.js +69 -0
  256. package/dist/vendor/core/truth-spine.d.ts +26 -0
  257. package/dist/vendor/core/truth-spine.js +62 -0
  258. package/dist/vendor/core/types.d.ts +115 -0
  259. package/dist/vendor/core/types.js +2 -0
  260. package/dist/vendor/core/verification/tiered-verify.d.ts +17 -0
  261. package/dist/vendor/core/verification/tiered-verify.js +29 -0
  262. package/dist/vendor/core/verifier-pyramid.d.ts +32 -0
  263. package/dist/vendor/core/verifier-pyramid.js +111 -0
  264. package/dist/vendor/core/workflow-artifacts.d.ts +99 -0
  265. package/dist/vendor/core/workflow-artifacts.js +668 -0
  266. package/dist/vendor/core/wrap/supervised-run.d.ts +96 -0
  267. package/dist/vendor/core/wrap/supervised-run.js +178 -0
  268. package/docs/assets/cli-animated.svg +139 -0
  269. package/docs/assets/cli-static.svg +34 -0
  270. package/docs/assets/github-hero-v2.svg +23 -0
  271. package/docs/assets/martin-raplph.png.jpg +0 -0
  272. package/docs/assets/martinloop-logo.png +0 -0
  273. package/docs/assets/nvidia-inception-program-light.png +0 -0
  274. package/docs/assets/nvidia-inception-program.png +0 -0
  275. package/docs/assets/phase3c-sidesidebyside-demo.html +228 -0
  276. package/docs/assets/side-by-side.svg +134 -0
  277. package/docs/oss/CLAUDE-CODE-WALKTHROUGH.md +142 -0
  278. package/docs/oss/EXAMPLES.md +9 -1
  279. package/docs/oss/OSS-BOUNDARY-REPORT.json +109 -113
  280. package/docs/oss/OSS-BOUNDARY-REPORT.md +48 -48
  281. package/docs/oss/QUICKSTART.md +39 -4
  282. package/docs/oss/RALPH-LOOP-SAFETY.md +113 -0
  283. package/docs/oss/README.md +7 -4
  284. package/docs/oss/RELEASE-SURFACE-REPORT.json +46 -45
  285. package/docs/oss/RELEASE-SURFACE-REPORT.md +36 -35
  286. package/package.json +129 -49
@@ -0,0 +1,668 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join, relative } from "node:path";
4
+ import { reportedTotalUsd } from "../contracts/index.js";
5
+ import { signAgentReceipt } from "./agent/receipts.js";
6
+ import { assertSafeLoopId } from "./persistence/checkpoint.js";
7
+ import { signFileAttestation, verifyFileAttestation } from "./attestation/sign.js";
8
+ import { computeContextUtilityScore } from "./context-utility.js";
9
+ export function resolveWorkflowArtifactPaths(runsRoot, loopId) {
10
+ assertSafeLoopId(loopId);
11
+ const runRoot = join(runsRoot, loopId);
12
+ return {
13
+ runRoot,
14
+ contractPath: join(runRoot, "contract.json"),
15
+ statePath: join(runRoot, "state.json"),
16
+ loopRecordPath: join(runRoot, "loop-record.json"),
17
+ loopRecordSignaturePath: join(runRoot, "loop-record.signature.json"),
18
+ loopRecordPublicKeyPath: join(runRoot, "loop-record.public.pem"),
19
+ ledgerPath: join(runRoot, "ledger.jsonl"),
20
+ artifactsRoot: join(runRoot, "artifacts"),
21
+ contextUtilityScorePath: join(runRoot, "context-utility-score.json"),
22
+ workflowReceiptPath: join(runRoot, "workflow-receipt.json"),
23
+ workflowHandoffJsonPath: join(runRoot, "workflow-handoff.json"),
24
+ workflowHandoffMarkdownPath: join(runRoot, "workflow-handoff.md")
25
+ };
26
+ }
27
+ export async function persistWorkflowArtifacts(input) {
28
+ const paths = resolveWorkflowArtifactPaths(input.runsRoot, input.loop.loopId);
29
+ await persistContextUtilityScore(paths, input.martinHome, input.loop.updatedAt);
30
+ const ledgerHash = await computeLedgerHash(paths.ledgerPath);
31
+ const compaction = await compactWorkflowHandoffSources(paths);
32
+ const receipt = await signAgentReceipt(buildLocalWorkflowReceipt(input.loop, ledgerHash), {
33
+ martinHome: input.martinHome
34
+ });
35
+ const handoff = buildWorkflowHandoffArtifact(input.loop, paths, ledgerHash, compaction);
36
+ await mkdir(paths.runRoot, { recursive: true });
37
+ await writeJson(paths.workflowReceiptPath, receipt);
38
+ await writeJson(paths.workflowHandoffJsonPath, handoff);
39
+ await writeFile(paths.workflowHandoffMarkdownPath, renderWorkflowHandoffMarkdown(handoff), "utf8");
40
+ await signFileAttestation(paths.workflowHandoffJsonPath, {
41
+ martinHome: input.martinHome,
42
+ now: input.loop.updatedAt
43
+ });
44
+ return {
45
+ paths,
46
+ ledgerHash,
47
+ receipt,
48
+ handoff
49
+ };
50
+ }
51
+ export function buildWorkflowHandoffArtifact(loop, paths, ledgerHash, compaction) {
52
+ return {
53
+ schemaVersion: "martin.workflow.handoff.v1",
54
+ loopId: loop.loopId,
55
+ workspaceId: loop.workspaceId,
56
+ projectId: loop.projectId,
57
+ status: loop.status,
58
+ lifecycleState: loop.lifecycleState,
59
+ objective: loop.task.objective,
60
+ verificationPlan: resolveRenderedVerificationPlan(loop, compaction),
61
+ attemptCount: loop.attempts.length,
62
+ actualUsd: loop.cost.actualUsd,
63
+ reportedTotalUsd: reportedTotalUsd(loop.cost),
64
+ ledgerHash,
65
+ verificationStatus: loop.lifecycleState === "completed" ? "passed" : "blocked",
66
+ nextAction: determineNextAction(loop.lifecycleState),
67
+ runRoot: paths.runRoot,
68
+ loopRecordPath: paths.loopRecordPath,
69
+ workflowReceiptPath: paths.workflowReceiptPath,
70
+ ledgerPath: paths.ledgerPath,
71
+ compaction,
72
+ createdAt: loop.createdAt,
73
+ updatedAt: loop.updatedAt
74
+ };
75
+ }
76
+ export function renderWorkflowHandoffMarkdown(handoff) {
77
+ const verificationPlan = handoff.compaction.verificationPlan;
78
+ return [
79
+ "# Martin Loop Workflow Handoff",
80
+ "",
81
+ `- Loop: \`${handoff.loopId}\``,
82
+ `- Workspace: \`${handoff.workspaceId}\``,
83
+ `- Project: \`${handoff.projectId}\``,
84
+ `- Status: \`${handoff.status}\` / \`${handoff.lifecycleState}\``,
85
+ `- Verification: \`${handoff.verificationStatus}\``,
86
+ `- Attempts: ${String(handoff.attemptCount)}`,
87
+ `- Spend: $${handoff.actualUsd.toFixed(4)} actual / $${handoff.reportedTotalUsd.toFixed(4)} reported`,
88
+ `- Next action: ${handoff.nextAction}`,
89
+ "",
90
+ "## Objective",
91
+ handoff.objective,
92
+ "",
93
+ "## Verification Plan",
94
+ ...(verificationPlan
95
+ ? [
96
+ `- Tier: \`${verificationPlan.tier ?? "standard"}\``,
97
+ `- Must pass all: \`${verificationPlan.mustPassAll ? "yes" : "no"}\``,
98
+ `- Summary: ${verificationPlan.summary}`
99
+ ]
100
+ : []),
101
+ ...(handoff.verificationPlan.length > 0
102
+ ? handoff.verificationPlan.map((command) => `- \`${command}\``)
103
+ : ["- No verification plan recorded."]),
104
+ "",
105
+ "## Touched Surfaces",
106
+ ...(handoff.compaction.touchedSurfaces.length > 0
107
+ ? handoff.compaction.touchedSurfaces.map((surface) => `- \`${surface}\``)
108
+ : ["- No touched surfaces recorded."]),
109
+ "",
110
+ "## Blockers",
111
+ ...(handoff.compaction.blockers.length > 0
112
+ ? handoff.compaction.blockers.map((blocker) => `- ${blocker}`)
113
+ : ["- No blockers recorded."]),
114
+ "",
115
+ "## Verification State",
116
+ `- Status: \`${handoff.compaction.verification.status}\``,
117
+ `- Summary: ${handoff.compaction.verification.summary}`,
118
+ "",
119
+ "## Context Utility",
120
+ ...(handoff.compaction.contextUtility
121
+ ? renderContextUtilityMarkdown(handoff.compaction.contextUtility)
122
+ : ["- No context utility score recorded."]),
123
+ "",
124
+ "## Next Prompt",
125
+ "```text",
126
+ handoff.compaction.nextPrompt,
127
+ "```",
128
+ "",
129
+ "## Canonical Paths",
130
+ `- Run root: \`${handoff.runRoot}\``,
131
+ `- Contract: \`${normalizePath(join(handoff.runRoot, "contract.json"))}\``,
132
+ `- Loop record: \`${handoff.loopRecordPath}\``,
133
+ `- Workflow receipt: \`${handoff.workflowReceiptPath}\``,
134
+ `- Ledger: \`${handoff.ledgerPath}\``,
135
+ "",
136
+ `Ledger hash: \`${handoff.ledgerHash}\``,
137
+ ""
138
+ ].join("\n");
139
+ }
140
+ export async function computeLedgerHash(ledgerPath) {
141
+ const raw = await readFile(ledgerPath, "utf8").catch(() => "");
142
+ return createHash("sha256").update(raw, "utf8").digest("hex");
143
+ }
144
+ export async function compactWorkflowHandoffSources(paths) {
145
+ const [contract, state, ledger, sources, touchedSurfaces, verificationPlan] = await Promise.all([
146
+ readJsonIfExists(paths.contractPath),
147
+ readJsonIfExists(paths.statePath),
148
+ readLedgerEvents(paths.ledgerPath),
149
+ collectWorkflowSourceDigests(paths),
150
+ collectTouchedSurfaces(paths.artifactsRoot),
151
+ loadLatestVerificationPlan(paths)
152
+ ]);
153
+ const contextUtility = await loadLatestContextUtilityScore(paths);
154
+ const verification = deriveVerification(ledger);
155
+ const blockers = deriveBlockers({ state, ledger, verification });
156
+ return {
157
+ schemaVersion: "martin.workflow.handoff.compaction.v1",
158
+ sourceManifestDigest: hashCanonicalJson(sources),
159
+ sources,
160
+ state: {
161
+ phase: state?.phase,
162
+ currentAttempt: state?.currentAttempt,
163
+ activeModel: state?.activeModel,
164
+ lastFailureSurface: state?.lastFailureSurface,
165
+ openAlerts: [...(state?.openAlerts ?? [])]
166
+ },
167
+ verification,
168
+ ...(verificationPlan ? { verificationPlan } : {}),
169
+ ...(contextUtility ? { contextUtility } : {}),
170
+ touchedSurfaces,
171
+ blockers,
172
+ nextPrompt: buildNextPrompt({
173
+ contract,
174
+ state,
175
+ verification,
176
+ verificationPlan,
177
+ contextUtility,
178
+ touchedSurfaces,
179
+ blockers
180
+ })
181
+ };
182
+ }
183
+ export async function verifyWorkflowHandoffForResume(runRoot) {
184
+ const paths = resolveWorkflowArtifactPathsFromRunRoot(runRoot);
185
+ const attestation = await verifyFileAttestation(paths.workflowHandoffJsonPath);
186
+ if (!attestation.ok) {
187
+ return {
188
+ ok: false,
189
+ reason: attestation.reason ?? "Workflow handoff attestation failed.",
190
+ changedSources: [],
191
+ attestation
192
+ };
193
+ }
194
+ const handoff = await readJsonIfExists(paths.workflowHandoffJsonPath);
195
+ if (!handoff?.compaction) {
196
+ return {
197
+ ok: false,
198
+ reason: "Workflow handoff compaction is missing.",
199
+ changedSources: [],
200
+ attestation
201
+ };
202
+ }
203
+ let currentCompaction;
204
+ try {
205
+ currentCompaction = await compactWorkflowHandoffSources(paths);
206
+ }
207
+ catch (error) {
208
+ return {
209
+ ok: false,
210
+ reason: error instanceof Error ? error.message : "Workflow handoff compaction could not be rebuilt.",
211
+ changedSources: [],
212
+ attestation
213
+ };
214
+ }
215
+ const changedSources = diffWorkflowSources(handoff.compaction.sources, currentCompaction.sources);
216
+ if (handoff.compaction.sourceManifestDigest !== currentCompaction.sourceManifestDigest ||
217
+ changedSources.length > 0) {
218
+ return {
219
+ ok: false,
220
+ reason: changedSources.length > 0
221
+ ? `Workflow handoff pack is stale relative to persisted workflow sources: ${changedSources.join(", ")}`
222
+ : "Workflow handoff pack is stale relative to persisted workflow sources.",
223
+ changedSources,
224
+ attestation
225
+ };
226
+ }
227
+ if (hashCanonicalJson(handoff.compaction) !== hashCanonicalJson(currentCompaction)) {
228
+ return {
229
+ ok: false,
230
+ reason: "Workflow handoff compaction no longer matches the rebuilt persisted workflow state.",
231
+ changedSources: [],
232
+ attestation
233
+ };
234
+ }
235
+ return {
236
+ ok: true,
237
+ changedSources: [],
238
+ attestation
239
+ };
240
+ }
241
+ function buildLocalWorkflowReceipt(loop, ledgerHash) {
242
+ return {
243
+ receiptId: `receipt_${loop.loopId}`,
244
+ runId: loop.loopId,
245
+ quoteId: `local-quote:${loop.workspaceId}`,
246
+ mandateId: `local-operator:${loop.projectId}`,
247
+ status: mapReceiptStatus(loop.lifecycleState),
248
+ ledgerHash,
249
+ createdAt: loop.updatedAt,
250
+ actualUsd: loop.cost.actualUsd,
251
+ attempts: loop.attempts.length
252
+ };
253
+ }
254
+ function mapReceiptStatus(lifecycleState) {
255
+ switch (lifecycleState) {
256
+ case "completed":
257
+ return "completed";
258
+ case "human_escalation":
259
+ return "denied";
260
+ case "session_timeout":
261
+ case "wall_clock_exceeded":
262
+ return "expired";
263
+ default:
264
+ return "failed";
265
+ }
266
+ }
267
+ function determineNextAction(lifecycleState) {
268
+ switch (lifecycleState) {
269
+ case "completed":
270
+ return "Review receipt and merge only after verifier evidence is accepted.";
271
+ case "human_escalation":
272
+ return "Human review required before any follow-up run.";
273
+ case "session_timeout":
274
+ case "wall_clock_exceeded":
275
+ return "Resume from checkpoint or narrow the task before re-running.";
276
+ case "stagnation_detected":
277
+ case "diminishing_returns":
278
+ case "stuck_exit":
279
+ return "Tighten the task or switch the route before retrying.";
280
+ default:
281
+ return "Inspect the handoff, verify the loop record, and decide whether to retry.";
282
+ }
283
+ }
284
+ async function writeJson(path, value) {
285
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
286
+ }
287
+ async function readJsonIfExists(path) {
288
+ try {
289
+ const raw = await readFile(path, "utf8");
290
+ return JSON.parse(raw);
291
+ }
292
+ catch {
293
+ return undefined;
294
+ }
295
+ }
296
+ async function readLedgerEvents(path) {
297
+ const raw = await readFile(path, "utf8").catch(() => "");
298
+ if (raw.trim().length === 0) {
299
+ return [];
300
+ }
301
+ return raw
302
+ .split("\n")
303
+ .map((line) => line.trim())
304
+ .filter(Boolean)
305
+ .flatMap((line) => {
306
+ try {
307
+ return [JSON.parse(line)];
308
+ }
309
+ catch {
310
+ return [];
311
+ }
312
+ });
313
+ }
314
+ async function collectWorkflowSourceDigests(paths) {
315
+ const sources = [];
316
+ await pushSourceDigest(sources, paths.runRoot, paths.contractPath, "contract");
317
+ await pushSourceDigest(sources, paths.runRoot, paths.statePath, "state");
318
+ await pushSourceDigest(sources, paths.runRoot, paths.ledgerPath, "ledger");
319
+ await pushSourceDigest(sources, paths.runRoot, paths.contextUtilityScorePath, "artifact");
320
+ const artifactFiles = await collectArtifactFiles(paths.artifactsRoot);
321
+ for (const artifactPath of artifactFiles) {
322
+ await pushSourceDigest(sources, paths.runRoot, artifactPath, "artifact");
323
+ }
324
+ return sources.sort((left, right) => left.path.localeCompare(right.path));
325
+ }
326
+ async function pushSourceDigest(sources, runRoot, filePath, kind) {
327
+ const raw = await readFile(filePath).catch(() => undefined);
328
+ if (!raw) {
329
+ return;
330
+ }
331
+ sources.push({
332
+ path: normalizeRelativePath(runRoot, filePath),
333
+ kind,
334
+ sha256: createHash("sha256").update(raw).digest("hex")
335
+ });
336
+ }
337
+ async function collectArtifactFiles(root) {
338
+ const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
339
+ const files = [];
340
+ for (const entry of entries) {
341
+ const entryPath = join(root, entry.name);
342
+ if (entry.isDirectory()) {
343
+ files.push(...(await collectArtifactFiles(entryPath)));
344
+ continue;
345
+ }
346
+ if (entry.isFile() &&
347
+ !entry.name.endsWith(".signature.json") &&
348
+ !entry.name.endsWith(".public.pem")) {
349
+ files.push(entryPath);
350
+ }
351
+ }
352
+ return files.sort((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
353
+ }
354
+ async function collectTouchedSurfaces(artifactsRoot) {
355
+ const artifactFiles = await collectArtifactFiles(artifactsRoot);
356
+ const surfaces = new Set();
357
+ for (const artifactPath of artifactFiles) {
358
+ if (!artifactPath.endsWith("scope-surface.json")) {
359
+ continue;
360
+ }
361
+ const scopeSurface = await readJsonIfExists(artifactPath);
362
+ if (!scopeSurface) {
363
+ continue;
364
+ }
365
+ for (const surface of [
366
+ ...(scopeSurface.allChangedPaths ?? []),
367
+ ...(scopeSurface.trackedChangedPaths ?? []),
368
+ ...(scopeSurface.deletedPaths ?? []),
369
+ ...(scopeSurface.newUntrackedPaths ?? [])
370
+ ]) {
371
+ if (typeof surface === "string" && surface.trim().length > 0) {
372
+ surfaces.add(normalizePath(surface));
373
+ }
374
+ }
375
+ }
376
+ return [...surfaces].sort((left, right) => left.localeCompare(right));
377
+ }
378
+ function deriveVerification(ledger) {
379
+ const event = [...ledger]
380
+ .reverse()
381
+ .find((entry) => entry.kind === "verification.completed");
382
+ const passed = event?.payload["passed"] === true;
383
+ const summary = asText(event?.payload["summary"]) ??
384
+ (passed ? "Verification passed." : "No verification result recorded.");
385
+ return {
386
+ status: passed ? "verified" : "unverified",
387
+ summary,
388
+ ...(event?.attemptIndex !== undefined ? { attemptIndex: event.attemptIndex } : {})
389
+ };
390
+ }
391
+ function deriveBlockers(input) {
392
+ const blockers = new Map();
393
+ const lastDecisionEvent = [...input.ledger]
394
+ .reverse()
395
+ .find((event) => event.kind === "attempt.discarded" || event.kind === "attempt.rejected");
396
+ const exitEvent = [...input.ledger]
397
+ .reverse()
398
+ .find((event) => event.kind === "run.exited");
399
+ if (input.verification.status !== "verified") {
400
+ const decisionReason = asText(lastDecisionEvent?.payload["reason"]);
401
+ if (decisionReason) {
402
+ blockers.set(decisionReason, true);
403
+ }
404
+ const exitReason = asText(exitEvent?.payload["reason"]);
405
+ if (exitReason) {
406
+ blockers.set(exitReason, true);
407
+ }
408
+ for (const alert of input.state?.openAlerts ?? []) {
409
+ blockers.set(alert, true);
410
+ }
411
+ const lastPolicyReason = input.state?.policyHistory.at(-1)?.reason;
412
+ if (lastPolicyReason) {
413
+ blockers.set(lastPolicyReason, true);
414
+ }
415
+ }
416
+ return [...blockers.keys()];
417
+ }
418
+ function buildNextPrompt(input) {
419
+ const objective = input.contract?.task.objective ?? "Objective unavailable.";
420
+ const verificationPlan = input.verificationPlan;
421
+ const phase = input.state?.phase ?? "UNKNOWN";
422
+ const nextStep = input.verification.status === "verified"
423
+ ? "Review the persisted evidence and prepare the verified handoff."
424
+ : "Resume from persisted evidence only, keep the patch bounded to the touched surfaces, and clear the highest-priority blocker before re-running verification.";
425
+ return [
426
+ "Resume Martin Loop from persisted workflow evidence only.",
427
+ `Objective: ${objective}`,
428
+ `Phase: ${phase}`,
429
+ `Verification: ${input.verification.status} - ${input.verification.summary}`,
430
+ ...(verificationPlan
431
+ ? [
432
+ `Verification tier: ${verificationPlan.tier ?? "standard"} (${verificationPlan.mustPassAll ? "must-pass-all" : "best-effort"})`,
433
+ `Verification summary: ${verificationPlan.summary}`
434
+ ]
435
+ : []),
436
+ `Context utility: ${formatContextUtilityPromptLine(input.contextUtility)}`,
437
+ ...formatContextUtilityActionLines(input.contextUtility),
438
+ `Touched surfaces: ${input.touchedSurfaces.length > 0 ? input.touchedSurfaces.join(", ") : "none recorded"}`,
439
+ `Blockers: ${input.blockers.length > 0 ? input.blockers.join("; ") : "none recorded"}`,
440
+ "Verification plan:",
441
+ ...(verificationPlan
442
+ ? [
443
+ ...verificationPlan.requiredChecks.map((command) => `- required: ${command}`),
444
+ ...verificationPlan.optionalChecks.map((command) => `- optional: ${command}`)
445
+ ]
446
+ : (input.contract?.task.verificationPlan ?? []).map((command) => `- ${command}`)),
447
+ ...(verificationPlan || (input.contract?.task.verificationPlan?.length ?? 0) > 0
448
+ ? []
449
+ : ["- No verification plan recorded."]),
450
+ `Next step: ${nextStep}`
451
+ ].join("\n");
452
+ }
453
+ function resolveRenderedVerificationPlan(loop, compaction) {
454
+ if (!compaction.verificationPlan) {
455
+ return [...loop.task.verificationPlan];
456
+ }
457
+ return [
458
+ ...compaction.verificationPlan.requiredChecks,
459
+ ...compaction.verificationPlan.optionalChecks.map((command) => `${command} (optional)`)
460
+ ];
461
+ }
462
+ function diffWorkflowSources(expected, actual) {
463
+ const actualByPath = new Map(actual.map((entry) => [entry.path, entry]));
464
+ const changed = new Set();
465
+ for (const expectedEntry of expected) {
466
+ const actualEntry = actualByPath.get(expectedEntry.path);
467
+ if (!actualEntry ||
468
+ actualEntry.kind !== expectedEntry.kind ||
469
+ actualEntry.sha256 !== expectedEntry.sha256) {
470
+ changed.add(expectedEntry.path);
471
+ }
472
+ }
473
+ const expectedPaths = new Set(expected.map((entry) => entry.path));
474
+ for (const actualEntry of actual) {
475
+ if (!expectedPaths.has(actualEntry.path)) {
476
+ changed.add(actualEntry.path);
477
+ }
478
+ }
479
+ return [...changed].sort((left, right) => left.localeCompare(right));
480
+ }
481
+ function resolveWorkflowArtifactPathsFromRunRoot(runRoot) {
482
+ const normalizedRunRoot = runRoot.replace(/[\\\/]+$/u, "");
483
+ return {
484
+ runRoot: normalizedRunRoot,
485
+ contractPath: join(normalizedRunRoot, "contract.json"),
486
+ statePath: join(normalizedRunRoot, "state.json"),
487
+ loopRecordPath: join(normalizedRunRoot, "loop-record.json"),
488
+ loopRecordSignaturePath: join(normalizedRunRoot, "loop-record.signature.json"),
489
+ loopRecordPublicKeyPath: join(normalizedRunRoot, "loop-record.public.pem"),
490
+ ledgerPath: join(normalizedRunRoot, "ledger.jsonl"),
491
+ artifactsRoot: join(normalizedRunRoot, "artifacts"),
492
+ contextUtilityScorePath: join(normalizedRunRoot, "context-utility-score.json"),
493
+ workflowReceiptPath: join(normalizedRunRoot, "workflow-receipt.json"),
494
+ workflowHandoffJsonPath: join(normalizedRunRoot, "workflow-handoff.json"),
495
+ workflowHandoffMarkdownPath: join(normalizedRunRoot, "workflow-handoff.md")
496
+ };
497
+ }
498
+ function renderContextUtilityMarkdown(contextUtility) {
499
+ return [
500
+ `- Score: ${String(contextUtility.utilityScore)}/100 (\`${contextUtility.utilityBand}\`)`,
501
+ `- Summary: ${contextUtility.summary}`,
502
+ `- Evidence: ${String(contextUtility.evidenceCounts.memoryEntryCount)} memory, ${String(contextUtility.evidenceCounts.requiredCheckCount)} required checks, ${String(contextUtility.evidenceCounts.optionalCheckCount)} optional checks, ${String(contextUtility.evidenceCounts.anomalyCount)} anomalies, ${String(contextUtility.evidenceCounts.touchedSurfaceCount)} touched surfaces`,
503
+ ...contextUtility.actions.map((action) => `- ${formatContextUtilityAction(action)}`)
504
+ ];
505
+ }
506
+ function formatContextUtilityPromptLine(contextUtility) {
507
+ if (!contextUtility) {
508
+ return "not recorded";
509
+ }
510
+ return `${String(contextUtility.utilityScore)}/100 (${contextUtility.utilityBand}) - ${contextUtility.summary}`;
511
+ }
512
+ function formatContextUtilityActionLines(contextUtility) {
513
+ if (!contextUtility || contextUtility.actions.length === 0) {
514
+ return [];
515
+ }
516
+ return contextUtility.actions.map((action) => `Context action: ${formatContextUtilityAction(action)}`);
517
+ }
518
+ function formatContextUtilityAction(action) {
519
+ const drawerIds = action.drawerIds && action.drawerIds.length > 0
520
+ ? ` [${action.drawerIds.join(", ")}]`
521
+ : "";
522
+ return `${action.type}${drawerIds}: ${action.message}`;
523
+ }
524
+ async function persistContextUtilityScore(paths, martinHome, now) {
525
+ const latestAttemptIndex = await resolveLatestAttemptIndex(paths.artifactsRoot);
526
+ if (latestAttemptIndex === undefined) {
527
+ return;
528
+ }
529
+ const attemptRoot = join(paths.artifactsRoot, formatAttemptDir(latestAttemptIndex));
530
+ const compiledContext = await readRequiredJson(join(attemptRoot, "compiled-context.json"), `compiled context for latest attempt ${formatAttemptLabel(latestAttemptIndex)}`);
531
+ const score = computeContextUtilityScore({
532
+ attemptIndex: latestAttemptIndex,
533
+ compiledContext: compiledContext,
534
+ verificationPlan: await readJsonIfExists(join(attemptRoot, "verification-plan.json")),
535
+ patterns: await readJsonIfExists(join(attemptRoot, "patterns.json")),
536
+ scopeSurface: await readJsonIfExists(join(attemptRoot, "scope-surface.json"))
537
+ });
538
+ await writeJson(paths.contextUtilityScorePath, score);
539
+ await signFileAttestation(paths.contextUtilityScorePath, {
540
+ martinHome,
541
+ now
542
+ });
543
+ }
544
+ async function loadLatestContextUtilityScore(paths) {
545
+ const latestAttemptIndex = await resolveLatestAttemptIndex(paths.artifactsRoot);
546
+ if (latestAttemptIndex === undefined) {
547
+ return undefined;
548
+ }
549
+ const score = await readRequiredJson(paths.contextUtilityScorePath, `context utility score for latest attempt ${formatAttemptLabel(latestAttemptIndex)}`);
550
+ if (score.schemaVersion !== "martin.context-utility-score.v1") {
551
+ throw new Error("Context utility score artifact has an unsupported schema version.");
552
+ }
553
+ if (score.attemptIndex !== latestAttemptIndex) {
554
+ throw new Error(`Context utility score does not match the latest attempt ${formatAttemptLabel(latestAttemptIndex)}; refusing to reuse a stale older score.`);
555
+ }
556
+ return score;
557
+ }
558
+ async function loadLatestVerificationPlan(paths) {
559
+ const latestAttemptIndex = await resolveLatestAttemptIndex(paths.artifactsRoot);
560
+ if (latestAttemptIndex === undefined) {
561
+ return undefined;
562
+ }
563
+ const attemptRoot = join(paths.artifactsRoot, formatAttemptDir(latestAttemptIndex));
564
+ const rawPlan = await readJsonIfExists(join(attemptRoot, "verification-plan.json"));
565
+ return normalizeVerificationPlanSummary(rawPlan);
566
+ }
567
+ async function resolveLatestAttemptIndex(artifactsRoot) {
568
+ const entries = await readdir(artifactsRoot, { withFileTypes: true }).catch(() => []);
569
+ const attemptIndexes = entries
570
+ .filter((entry) => entry.isDirectory())
571
+ .map((entry) => parseAttemptIndex(entry.name))
572
+ .filter((value) => value !== undefined);
573
+ if (attemptIndexes.length === 0) {
574
+ return undefined;
575
+ }
576
+ return Math.max(...attemptIndexes);
577
+ }
578
+ function parseAttemptIndex(name) {
579
+ const match = /^attempt-(\d+)$/u.exec(name);
580
+ return match ? Number.parseInt(match[1] ?? "", 10) : undefined;
581
+ }
582
+ function formatAttemptDir(attemptIndex) {
583
+ return `attempt-${String(attemptIndex).padStart(3, "0")}`;
584
+ }
585
+ function formatAttemptLabel(attemptIndex) {
586
+ return formatAttemptDir(attemptIndex);
587
+ }
588
+ async function readRequiredJson(path, label) {
589
+ try {
590
+ const raw = await readFile(path, "utf8");
591
+ return JSON.parse(raw);
592
+ }
593
+ catch (error) {
594
+ const detail = error instanceof Error ? error.message : String(error);
595
+ throw new Error(`Unable to read ${label}: ${detail}`);
596
+ }
597
+ }
598
+ function normalizeRelativePath(root, path) {
599
+ return normalizePath(relative(root, path));
600
+ }
601
+ function normalizePath(value) {
602
+ return value.replace(/\\/g, "/");
603
+ }
604
+ function hashCanonicalJson(value) {
605
+ return createHash("sha256")
606
+ .update(JSON.stringify(value), "utf8")
607
+ .digest("hex");
608
+ }
609
+ function asText(value) {
610
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
611
+ }
612
+ function normalizeVerificationPlanSummary(value) {
613
+ if (!value || typeof value !== "object") {
614
+ return undefined;
615
+ }
616
+ const candidate = value;
617
+ const requiredChecks = uniqueStrings([
618
+ ...readStringArray(candidate["requiredChecks"]),
619
+ ...readStringArray(candidate["requiredCommands"]),
620
+ ...readStringArray(candidate["verificationPlan"])
621
+ ]);
622
+ const optionalChecks = uniqueStrings([
623
+ ...readStringArray(candidate["optionalChecks"]),
624
+ ...readStringArray(candidate["optionalCommands"])
625
+ ]).filter((command) => !requiredChecks.includes(command));
626
+ const requiredVerifiers = uniqueStrings(readStringArray(candidate["requiredVerifiers"]));
627
+ const tier = asText(candidate["tier"]);
628
+ const mustPassAll = candidate["mustPassAll"] === true;
629
+ const summary = asText(candidate["summary"]) ??
630
+ buildVerificationPlanSummary({
631
+ tier,
632
+ mustPassAll,
633
+ requiredChecks,
634
+ optionalChecks,
635
+ requiredVerifiers
636
+ });
637
+ if (requiredChecks.length === 0 &&
638
+ optionalChecks.length === 0 &&
639
+ requiredVerifiers.length === 0) {
640
+ return undefined;
641
+ }
642
+ return {
643
+ ...(tier ? { tier } : {}),
644
+ mustPassAll,
645
+ requiredChecks,
646
+ optionalChecks,
647
+ requiredVerifiers,
648
+ summary
649
+ };
650
+ }
651
+ function buildVerificationPlanSummary(input) {
652
+ return [
653
+ `${input.tier ?? "standard"} verification`,
654
+ input.mustPassAll ? "must pass all required checks" : "permits best-effort optional checks",
655
+ `${String(input.requiredChecks.length)} required command(s)`,
656
+ `${String(input.optionalChecks.length)} optional command(s)`,
657
+ `${String(input.requiredVerifiers.length)} verifier role(s)`
658
+ ].join(", ");
659
+ }
660
+ function uniqueStrings(values) {
661
+ return [...new Set(values.filter((value) => value.trim().length > 0))];
662
+ }
663
+ function readStringArray(value) {
664
+ return Array.isArray(value)
665
+ ? value.filter((entry) => typeof entry === "string")
666
+ : [];
667
+ }
668
+ //# sourceMappingURL=workflow-artifacts.js.map