martin-loop 0.1.5 → 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 (274) hide show
  1. package/CODE_OF_CONDUCT.md +32 -0
  2. package/LICENSE +21 -21
  3. package/README.md +307 -398
  4. package/demo/seeded-workspace/README.md +35 -35
  5. package/demo/seeded-workspace/TASKS.md +29 -29
  6. package/demo/seeded-workspace/martin.config.yaml +11 -11
  7. package/demo/seeded-workspace/package.json +8 -8
  8. package/demo/seeded-workspace/src/invoice-summary.js +11 -11
  9. package/demo/seeded-workspace/test/invoice-summary.test.js +20 -20
  10. package/dist/bin/martin-loop.js +0 -0
  11. package/dist/vendor/adapters/counter.d.ts +1 -0
  12. package/dist/vendor/adapters/counter.js +4 -0
  13. package/dist/vendor/adapters/git-baseline.d.ts +50 -0
  14. package/dist/vendor/adapters/git-baseline.js +233 -0
  15. package/dist/vendor/adapters/openrouter-adapter.d.ts +15 -0
  16. package/dist/vendor/adapters/openrouter-adapter.js +302 -0
  17. package/dist/vendor/adapters/usage.d.ts +48 -0
  18. package/dist/vendor/adapters/usage.js +66 -0
  19. package/dist/vendor/cli/bin/exit.d.ts +12 -0
  20. package/dist/vendor/cli/bin/exit.js +28 -0
  21. package/dist/vendor/cli/commands/analyze.d.ts +5 -0
  22. package/dist/vendor/cli/commands/analyze.js +58 -0
  23. package/dist/vendor/cli/commands/audit-log-verify.d.ts +34 -0
  24. package/dist/vendor/cli/commands/audit-log-verify.js +99 -0
  25. package/dist/vendor/cli/commands/audit.d.ts +8 -0
  26. package/dist/vendor/cli/commands/audit.js +199 -0
  27. package/dist/vendor/cli/commands/corpus.d.ts +5 -0
  28. package/dist/vendor/cli/commands/corpus.js +60 -0
  29. package/dist/vendor/cli/commands/doctor.d.ts +8 -0
  30. package/dist/vendor/cli/commands/doctor.js +219 -0
  31. package/dist/vendor/cli/commands/explain.d.ts +17 -0
  32. package/dist/vendor/cli/commands/explain.js +176 -0
  33. package/dist/vendor/cli/commands/export.d.ts +5 -0
  34. package/dist/vendor/cli/commands/export.js +60 -0
  35. package/dist/vendor/cli/commands/governance.d.ts +8 -0
  36. package/dist/vendor/cli/commands/governance.js +95 -0
  37. package/dist/vendor/cli/commands/improve.d.ts +18 -0
  38. package/dist/vendor/cli/commands/improve.js +396 -0
  39. package/dist/vendor/cli/commands/init.d.ts +8 -0
  40. package/dist/vendor/cli/commands/init.js +281 -0
  41. package/dist/vendor/cli/commands/migration.d.ts +8 -0
  42. package/dist/vendor/cli/commands/migration.js +67 -0
  43. package/dist/vendor/cli/commands/prior.d.ts +23 -0
  44. package/dist/vendor/cli/commands/prior.js +145 -0
  45. package/dist/vendor/cli/commands/resume.d.ts +21 -0
  46. package/dist/vendor/cli/commands/resume.js +73 -0
  47. package/dist/vendor/cli/commands/verify.d.ts +6 -0
  48. package/dist/vendor/cli/commands/verify.js +43 -0
  49. package/dist/vendor/cli/research/public-corpus.d.ts +43 -0
  50. package/dist/vendor/cli/research/public-corpus.js +151 -0
  51. package/dist/vendor/cli/ui/error-card.d.ts +38 -0
  52. package/dist/vendor/cli/ui/error-card.js +103 -0
  53. package/dist/vendor/cli/ui/mission-brief.d.ts +41 -0
  54. package/dist/vendor/cli/ui/mission-brief.js +173 -0
  55. package/dist/vendor/cli/ui/summary-card.d.ts +34 -0
  56. package/dist/vendor/cli/ui/summary-card.js +102 -0
  57. package/dist/vendor/contracts/audit.d.ts +46 -0
  58. package/dist/vendor/contracts/audit.js +360 -0
  59. package/dist/vendor/contracts/post-phase15.d.ts +240 -0
  60. package/dist/vendor/contracts/post-phase15.js +166 -0
  61. package/dist/vendor/core/agent/mandates.d.ts +46 -0
  62. package/dist/vendor/core/agent/mandates.js +178 -0
  63. package/dist/vendor/core/agent/receipts.d.ts +38 -0
  64. package/dist/vendor/core/agent/receipts.js +131 -0
  65. package/dist/vendor/core/agent/signing.d.ts +17 -0
  66. package/dist/vendor/core/agent/signing.js +91 -0
  67. package/dist/vendor/core/attestation/sign.d.ts +25 -0
  68. package/dist/vendor/core/attestation/sign.js +216 -0
  69. package/dist/vendor/core/autonomy/autonomous-promotion.d.ts +120 -0
  70. package/dist/vendor/core/autonomy/autonomous-promotion.js +346 -0
  71. package/dist/vendor/core/autonomy/envelope-v2.d.ts +29 -0
  72. package/dist/vendor/core/autonomy/envelope-v2.js +60 -0
  73. package/dist/vendor/core/autonomy/envelope.d.ts +17 -0
  74. package/dist/vendor/core/autonomy/envelope.js +27 -0
  75. package/dist/vendor/core/autonomy/escalation-ledger.d.ts +20 -0
  76. package/dist/vendor/core/autonomy/escalation-ledger.js +18 -0
  77. package/dist/vendor/core/autonomy/resume.d.ts +15 -0
  78. package/dist/vendor/core/autonomy/resume.js +23 -0
  79. package/dist/vendor/core/circuit/circuit-breaker.d.ts +60 -0
  80. package/dist/vendor/core/circuit/circuit-breaker.js +143 -0
  81. package/dist/vendor/core/context-distillation.d.ts +3 -0
  82. package/dist/vendor/core/context-distillation.js +44 -0
  83. package/dist/vendor/core/context-flow/compile-context.d.ts +8 -0
  84. package/dist/vendor/core/context-flow/compile-context.js +111 -0
  85. package/dist/vendor/core/context-flow/entities.d.ts +2 -0
  86. package/dist/vendor/core/context-flow/entities.js +44 -0
  87. package/dist/vendor/core/context-flow/evaluate-policy.d.ts +2 -0
  88. package/dist/vendor/core/context-flow/evaluate-policy.js +42 -0
  89. package/dist/vendor/core/context-flow/index.d.ts +11 -0
  90. package/dist/vendor/core/context-flow/index.js +24 -0
  91. package/dist/vendor/core/context-flow/labels.d.ts +3 -0
  92. package/dist/vendor/core/context-flow/labels.js +17 -0
  93. package/dist/vendor/core/context-flow/normalizer.d.ts +9 -0
  94. package/dist/vendor/core/context-flow/normalizer.js +69 -0
  95. package/dist/vendor/core/context-flow/profiles.d.ts +33 -0
  96. package/dist/vendor/core/context-flow/profiles.js +36 -0
  97. package/dist/vendor/core/context-flow/redaction.d.ts +1 -0
  98. package/dist/vendor/core/context-flow/redaction.js +6 -0
  99. package/dist/vendor/core/context-flow/sensitivity.d.ts +2 -0
  100. package/dist/vendor/core/context-flow/sensitivity.js +27 -0
  101. package/dist/vendor/core/context-flow/sync-preview.d.ts +2 -0
  102. package/dist/vendor/core/context-flow/sync-preview.js +22 -0
  103. package/dist/vendor/core/context-flow/token-estimator.d.ts +3 -0
  104. package/dist/vendor/core/context-flow/token-estimator.js +13 -0
  105. package/dist/vendor/core/context-flow/types.d.ts +91 -0
  106. package/dist/vendor/core/context-flow/types.js +2 -0
  107. package/dist/vendor/core/context-utility.d.ts +47 -0
  108. package/dist/vendor/core/context-utility.js +405 -0
  109. package/dist/vendor/core/cost/pipeline.d.ts +92 -0
  110. package/dist/vendor/core/cost/pipeline.js +141 -0
  111. package/dist/vendor/core/cost/tagged-cost.d.ts +27 -0
  112. package/dist/vendor/core/cost/tagged-cost.js +55 -0
  113. package/dist/vendor/core/cost-governor.d.ts +2 -0
  114. package/dist/vendor/core/cost-governor.js +50 -0
  115. package/dist/vendor/core/cve/cve-check.d.ts +80 -0
  116. package/dist/vendor/core/cve/cve-check.js +172 -0
  117. package/dist/vendor/core/digital-twin/index.d.ts +27 -0
  118. package/dist/vendor/core/digital-twin/index.js +90 -0
  119. package/dist/vendor/core/drift/drift-graph.d.ts +47 -0
  120. package/dist/vendor/core/drift/drift-graph.js +100 -0
  121. package/dist/vendor/core/drift/objective-lock.d.ts +69 -0
  122. package/dist/vendor/core/drift/objective-lock.js +88 -0
  123. package/dist/vendor/core/drift/scope.d.ts +46 -0
  124. package/dist/vendor/core/drift/scope.js +102 -0
  125. package/dist/vendor/core/drift/signature-lock.d.ts +48 -0
  126. package/dist/vendor/core/drift/signature-lock.js +202 -0
  127. package/dist/vendor/core/drift/stale-proof-gate.d.ts +21 -0
  128. package/dist/vendor/core/drift/stale-proof-gate.js +19 -0
  129. package/dist/vendor/core/eval/known-bad-world-runner.d.ts +24 -0
  130. package/dist/vendor/core/eval/known-bad-world-runner.js +256 -0
  131. package/dist/vendor/core/evidence/claim-audit.d.ts +18 -0
  132. package/dist/vendor/core/evidence/claim-audit.js +89 -0
  133. package/dist/vendor/core/exit-intelligence.d.ts +2 -0
  134. package/dist/vendor/core/exit-intelligence.js +58 -0
  135. package/dist/vendor/core/explain/formatter.d.ts +42 -0
  136. package/dist/vendor/core/explain/formatter.js +171 -0
  137. package/dist/vendor/core/explain/timeline.d.ts +29 -0
  138. package/dist/vendor/core/explain/timeline.js +213 -0
  139. package/dist/vendor/core/failure-taxonomy.d.ts +2 -0
  140. package/dist/vendor/core/failure-taxonomy.js +76 -0
  141. package/dist/vendor/core/gateway/index.d.ts +10 -0
  142. package/dist/vendor/core/gateway/index.js +12 -0
  143. package/dist/vendor/core/gateway/registry.d.ts +40 -0
  144. package/dist/vendor/core/gateway/registry.js +97 -0
  145. package/dist/vendor/core/gateway/transport.d.ts +31 -0
  146. package/dist/vendor/core/gateway/transport.js +82 -0
  147. package/dist/vendor/core/gateway/vault.d.ts +19 -0
  148. package/dist/vendor/core/gateway/vault.js +29 -0
  149. package/dist/vendor/core/graph/adapters.d.ts +43 -0
  150. package/dist/vendor/core/graph/adapters.js +91 -0
  151. package/dist/vendor/core/graph/hotspots.d.ts +22 -0
  152. package/dist/vendor/core/graph/hotspots.js +30 -0
  153. package/dist/vendor/core/graph/index.d.ts +1 -0
  154. package/dist/vendor/core/graph/index.js +2 -0
  155. package/dist/vendor/core/honey/honey-tokens.d.ts +32 -0
  156. package/dist/vendor/core/honey/honey-tokens.js +44 -0
  157. package/dist/vendor/core/index.d.ts +2 -2
  158. package/dist/vendor/core/index.js +38 -12
  159. package/dist/vendor/core/learning/bayesian-update.d.ts +31 -0
  160. package/dist/vendor/core/learning/bayesian-update.js +60 -0
  161. package/dist/vendor/core/learning/prior-sets.d.ts +42 -0
  162. package/dist/vendor/core/learning/prior-sets.js +111 -0
  163. package/dist/vendor/core/learning/promotion-gate.d.ts +17 -0
  164. package/dist/vendor/core/learning/promotion-gate.js +23 -0
  165. package/dist/vendor/core/leash/blast-radius.d.ts +42 -0
  166. package/dist/vendor/core/leash/blast-radius.js +156 -0
  167. package/dist/vendor/core/leash/policy-leash.d.ts +31 -0
  168. package/dist/vendor/core/leash/policy-leash.js +117 -0
  169. package/dist/vendor/core/memo/memo.d.ts +63 -0
  170. package/dist/vendor/core/memo/memo.js +97 -0
  171. package/dist/vendor/core/memory/learning-pipeline.d.ts +154 -0
  172. package/dist/vendor/core/memory/learning-pipeline.js +391 -0
  173. package/dist/vendor/core/memory/palace.d.ts +84 -0
  174. package/dist/vendor/core/memory/palace.js +379 -0
  175. package/dist/vendor/core/merge/ast-merge.d.ts +22 -0
  176. package/dist/vendor/core/merge/ast-merge.js +350 -0
  177. package/dist/vendor/core/merge/text-merge.d.ts +12 -0
  178. package/dist/vendor/core/merge/text-merge.js +182 -0
  179. package/dist/vendor/core/otel/tracer.d.ts +45 -0
  180. package/dist/vendor/core/otel/tracer.js +116 -0
  181. package/dist/vendor/core/parallel/parallel-attempts.d.ts +28 -0
  182. package/dist/vendor/core/parallel/parallel-attempts.js +41 -0
  183. package/dist/vendor/core/parallel/scorer.d.ts +24 -0
  184. package/dist/vendor/core/parallel/scorer.js +65 -0
  185. package/dist/vendor/core/pattern-detection.d.ts +64 -0
  186. package/dist/vendor/core/pattern-detection.js +108 -0
  187. package/dist/vendor/core/persistence/checkpoint.d.ts +44 -0
  188. package/dist/vendor/core/persistence/checkpoint.js +156 -0
  189. package/dist/vendor/core/persistence/cleanup.d.ts +22 -0
  190. package/dist/vendor/core/persistence/cleanup.js +131 -0
  191. package/dist/vendor/core/persistence/index.d.ts +2 -0
  192. package/dist/vendor/core/persistence/index.js +1 -0
  193. package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
  194. package/dist/vendor/core/persistence/runs-reader.js +84 -0
  195. package/dist/vendor/core/persistence/store.d.ts +6 -1
  196. package/dist/vendor/core/persistence/store.js +5 -0
  197. package/dist/vendor/core/policy/file-touch-quota.d.ts +60 -0
  198. package/dist/vendor/core/policy/file-touch-quota.js +105 -0
  199. package/dist/vendor/core/policy/policy-loader.d.ts +30 -0
  200. package/dist/vendor/core/policy/policy-loader.js +170 -0
  201. package/dist/vendor/core/policy/policy-schema.d.ts +55 -0
  202. package/dist/vendor/core/policy/policy-schema.js +78 -0
  203. package/dist/vendor/core/probe/probe.d.ts +49 -0
  204. package/dist/vendor/core/probe/probe.js +115 -0
  205. package/dist/vendor/core/proof/patch-proof.d.ts +58 -0
  206. package/dist/vendor/core/proof/patch-proof.js +84 -0
  207. package/dist/vendor/core/proof/semantic-probe.d.ts +25 -0
  208. package/dist/vendor/core/proof/semantic-probe.js +82 -0
  209. package/dist/vendor/core/recovery/failure-mode-runner.d.ts +29 -0
  210. package/dist/vendor/core/recovery/failure-mode-runner.js +39 -0
  211. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  212. package/dist/vendor/core/red-blue/red-phase.js +141 -0
  213. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  214. package/dist/vendor/core/red-blue/risk-tiers.js +33 -0
  215. package/dist/vendor/core/replay/replay.d.ts +85 -0
  216. package/dist/vendor/core/replay/replay.js +109 -0
  217. package/dist/vendor/core/router/engine.d.ts +54 -0
  218. package/dist/vendor/core/router/engine.js +131 -0
  219. package/dist/vendor/core/router/index.d.ts +1 -0
  220. package/dist/vendor/core/router/index.js +2 -0
  221. package/dist/vendor/core/router/trust-calibration.d.ts +57 -0
  222. package/dist/vendor/core/router/trust-calibration.js +127 -0
  223. package/dist/vendor/core/run-martin.d.ts +2 -0
  224. package/dist/vendor/core/run-martin.js +287 -0
  225. package/dist/vendor/core/security/cve-scanner.d.ts +62 -0
  226. package/dist/vendor/core/security/cve-scanner.js +178 -0
  227. package/dist/vendor/core/sentinel/efficiency-sentinel.d.ts +29 -0
  228. package/dist/vendor/core/sentinel/efficiency-sentinel.js +30 -0
  229. package/dist/vendor/core/sentinel/progress-guard.d.ts +35 -0
  230. package/dist/vendor/core/sentinel/progress-guard.js +46 -0
  231. package/dist/vendor/core/siem/siem-emitter.d.ts +49 -0
  232. package/dist/vendor/core/siem/siem-emitter.js +157 -0
  233. package/dist/vendor/core/strategy/attempt-brief.d.ts +22 -0
  234. package/dist/vendor/core/strategy/attempt-brief.js +89 -0
  235. package/dist/vendor/core/summarize/diff-summary.d.ts +35 -0
  236. package/dist/vendor/core/summarize/diff-summary.js +204 -0
  237. package/dist/vendor/core/surface-signals.d.ts +21 -0
  238. package/dist/vendor/core/surface-signals.js +139 -0
  239. package/dist/vendor/core/truth/truth-wall.d.ts +51 -0
  240. package/dist/vendor/core/truth/truth-wall.js +69 -0
  241. package/dist/vendor/core/truth-spine.d.ts +26 -0
  242. package/dist/vendor/core/truth-spine.js +62 -0
  243. package/dist/vendor/core/types.d.ts +115 -0
  244. package/dist/vendor/core/types.js +2 -0
  245. package/dist/vendor/core/verification/tiered-verify.d.ts +17 -0
  246. package/dist/vendor/core/verification/tiered-verify.js +29 -0
  247. package/dist/vendor/core/verifier-pyramid.d.ts +32 -0
  248. package/dist/vendor/core/verifier-pyramid.js +111 -0
  249. package/dist/vendor/core/workflow-artifacts.d.ts +99 -0
  250. package/dist/vendor/core/workflow-artifacts.js +668 -0
  251. package/dist/vendor/core/wrap/supervised-run.d.ts +96 -0
  252. package/dist/vendor/core/wrap/supervised-run.js +178 -0
  253. package/docs/assets/cli-animated.svg +139 -0
  254. package/docs/assets/cli-static.svg +34 -0
  255. package/docs/assets/github-hero-v2.svg +23 -0
  256. package/docs/assets/martin-raplph.png.jpg +0 -0
  257. package/docs/assets/martinloop-logo.png +0 -0
  258. package/docs/assets/nvidia-inception-program-light.png +0 -0
  259. package/docs/assets/nvidia-inception-program.png +0 -0
  260. package/docs/assets/phase3c-sidesidebyside-demo.html +228 -0
  261. package/docs/assets/side-by-side.svg +134 -0
  262. package/docs/oss/CLAUDE-CODE-WALKTHROUGH.md +142 -142
  263. package/docs/oss/EXAMPLES.md +134 -134
  264. package/docs/oss/OSS-BOUNDARY-REPORT.json +1 -1
  265. package/docs/oss/OSS-BOUNDARY-REPORT.md +1 -1
  266. package/docs/oss/QUICKSTART.md +170 -165
  267. package/docs/oss/RALPH-LOOP-SAFETY.md +113 -113
  268. package/docs/oss/README.md +96 -96
  269. package/docs/oss/RELEASE-SURFACE-REPORT.json +2 -1
  270. package/docs/oss/RELEASE-SURFACE-REPORT.md +2 -1
  271. package/package.json +130 -58
  272. package/docs/distribution/DIRECTORY-SUBMISSIONS.md +0 -89
  273. package/docs/distribution/INTEGRATION-OUTREACH.md +0 -61
  274. package/docs/distribution/UNDER-3-CHALLENGE.md +0 -65
@@ -13,7 +13,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
13
13
  // ─── Prompt packet compiler ──────────────────────────────────────────────────
14
14
  export { compilePromptPacket } from "./compiler.js";
15
15
  // ─── Persistence (RunStore, LedgerEvent, FileRunStore) ──────────────────────
16
- export { createFileRunStore, makeLedgerEvent, resolveRunsRoot } from "./persistence/index.js";
16
+ export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot } from "./persistence/index.js";
17
17
  export { compileAndPersistContext } from "./persistence/index.js";
18
18
  /**
19
19
  * Admission gate — must pass before any attempt is executed.
@@ -129,6 +129,7 @@ export async function runMartin(input) {
129
129
  runId: loop.loopId,
130
130
  payload: { workspaceId: input.workspaceId, projectId: input.projectId }
131
131
  }));
132
+ await persistLoopRecordIfSupported(input.store, loop);
132
133
  }
133
134
  const DEFAULT_FALLBACK_MODELS = [
134
135
  "claude-haiku-4-5",
@@ -177,8 +178,10 @@ export async function runMartin(input) {
177
178
  payload: createRunExitPayload(leashExitDecision)
178
179
  }));
179
180
  }
181
+ const finalizedLoop = finalizeLoop(loop, leashExitDecision, now(), idFactory);
182
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
180
183
  return {
181
- loop: finalizeLoop(loop, leashExitDecision, now(), idFactory),
184
+ loop: finalizedLoop,
182
185
  decision: leashExitDecision
183
186
  };
184
187
  }
@@ -213,8 +216,10 @@ export async function runMartin(input) {
213
216
  payload: createRunExitPayload(secretExitDecision)
214
217
  }));
215
218
  }
219
+ const finalizedLoop = finalizeLoop(loop, secretExitDecision, now(), idFactory);
220
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
216
221
  return {
217
- loop: finalizeLoop(loop, secretExitDecision, now(), idFactory),
222
+ loop: finalizedLoop,
218
223
  decision: secretExitDecision
219
224
  };
220
225
  }
@@ -255,8 +260,10 @@ export async function runMartin(input) {
255
260
  payload: createRunExitPayload(preflightExitDecision)
256
261
  }));
257
262
  }
263
+ const finalizedLoop = finalizeLoop(loop, preflightExitDecision, now(), idFactory);
264
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
258
265
  return {
259
- loop: finalizeLoop(loop, preflightExitDecision, now(), idFactory),
266
+ loop: finalizedLoop,
260
267
  decision: preflightExitDecision
261
268
  };
262
269
  }
@@ -289,8 +296,10 @@ export async function runMartin(input) {
289
296
  }
290
297
  }));
291
298
  }
299
+ const finalizedLoop = finalizeLoop(loop, poisoningExitDecision, now(), idFactory);
300
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
292
301
  return {
293
- loop: finalizeLoop(loop, poisoningExitDecision, now(), idFactory),
302
+ loop: finalizedLoop,
294
303
  decision: poisoningExitDecision
295
304
  };
296
305
  }
@@ -344,8 +353,10 @@ export async function runMartin(input) {
344
353
  payload: createRunExitPayload(exitDecision)
345
354
  }));
346
355
  }
356
+ const finalizedLoop = finalizeLoop(loop, exitDecision, now(), idFactory);
357
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
347
358
  return {
348
- loop: finalizeLoop(loop, exitDecision, now(), idFactory),
359
+ loop: finalizedLoop,
349
360
  decision: exitDecision
350
361
  };
351
362
  }
@@ -641,8 +652,10 @@ export async function runMartin(input) {
641
652
  payload: createRunExitPayload(verifyOnlyExitDecision)
642
653
  }));
643
654
  }
655
+ const finalizedLoop = finalizeLoop(loop, verifyOnlyExitDecision, now(), idFactory);
656
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
644
657
  return {
645
- loop: finalizeLoop(loop, verifyOnlyExitDecision, now(), idFactory),
658
+ loop: finalizedLoop,
646
659
  decision: verifyOnlyExitDecision
647
660
  };
648
661
  }
@@ -714,8 +727,10 @@ export async function runMartin(input) {
714
727
  payload: createRunExitPayload(filesystemExitDecision)
715
728
  }));
716
729
  }
730
+ const finalizedLoop = finalizeLoop(loop, filesystemExitDecision, now(), idFactory);
731
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
717
732
  return {
718
- loop: finalizeLoop(loop, filesystemExitDecision, now(), idFactory),
733
+ loop: finalizedLoop,
719
734
  decision: filesystemExitDecision
720
735
  };
721
736
  }
@@ -789,8 +804,10 @@ export async function runMartin(input) {
789
804
  payload: createRunExitPayload(approvalExitDecision)
790
805
  }));
791
806
  }
807
+ const finalizedLoop = finalizeLoop(loop, approvalExitDecision, now(), idFactory);
808
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
792
809
  return {
793
- loop: finalizeLoop(loop, approvalExitDecision, now(), idFactory),
810
+ loop: finalizedLoop,
794
811
  decision: approvalExitDecision
795
812
  };
796
813
  }
@@ -926,8 +943,10 @@ export async function runMartin(input) {
926
943
  payload: createRunExitPayload(patchExitDecision)
927
944
  }));
928
945
  }
946
+ const finalizedLoop = finalizeLoop(loop, patchExitDecision, now(), idFactory);
947
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
929
948
  return {
930
- loop: finalizeLoop(loop, patchExitDecision, now(), idFactory),
949
+ loop: finalizedLoop,
931
950
  decision: patchExitDecision
932
951
  };
933
952
  }
@@ -970,8 +989,10 @@ export async function runMartin(input) {
970
989
  payload: createRunExitPayload(decision)
971
990
  }));
972
991
  }
992
+ const finalizedLoop = finalizeLoop(loop, decision, now(), idFactory);
993
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
973
994
  return {
974
- loop: finalizeLoop(loop, decision, now(), idFactory),
995
+ loop: finalizedLoop,
975
996
  decision
976
997
  };
977
998
  }
@@ -989,8 +1010,10 @@ export async function runMartin(input) {
989
1010
  payload: createRunExitPayload(decision)
990
1011
  }));
991
1012
  }
1013
+ const finalizedLoop = finalizeLoop(loop, decision, now(), idFactory);
1014
+ await persistLoopRecordIfSupported(input.store, finalizedLoop);
992
1015
  return {
993
- loop: finalizeLoop(loop, decision, now(), idFactory),
1016
+ loop: finalizedLoop,
994
1017
  decision
995
1018
  };
996
1019
  }
@@ -1050,6 +1073,9 @@ function finalizeLoop(loop, decision, timestamp, idFactory) {
1050
1073
  updatedAt: timestamp
1051
1074
  };
1052
1075
  }
1076
+ async function persistLoopRecordIfSupported(store, loop) {
1077
+ await store?.writeLoopRecord?.(loop.loopId, loop);
1078
+ }
1053
1079
  function getAdapterTransport(adapter) {
1054
1080
  return adapter.metadata.transport ?? (adapter.kind === "agent-cli" ? "cli" : "http");
1055
1081
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Applies a single-step Bayesian update to a prior probability.
3
+ *
4
+ * Formula: posterior = (prior * likelihood) / normalizing_constant
5
+ *
6
+ * The normalizing constant ensures the result stays in [0, 1]:
7
+ * Z = prior * likelihood + (1 - prior) * (1 - likelihood)
8
+ *
9
+ * This is the standard Beta-Bernoulli update for binary outcomes.
10
+ * - likelihood > 0.5 → positive evidence → posterior increases
11
+ * - likelihood < 0.5 → negative evidence → posterior decreases
12
+ * - likelihood = 0.5 → no information → posterior unchanged
13
+ *
14
+ * @param prior Current prior probability (0.0 – 1.0)
15
+ * @param likelihood Likelihood of observation given the hypothesis (0.0 – 1.0)
16
+ * @returns Updated posterior probability (0.0 – 1.0)
17
+ */
18
+ export declare function updatePriorBayesian(prior: number, likelihood: number): number;
19
+ /**
20
+ * Applies a Bayesian update to a PriorSet's individual symbol priors
21
+ * using confidence deltas from the hypothesis ledger.
22
+ *
23
+ * confidenceDelta > 0 → validated evidence → likelihood = 0.5 + delta/2
24
+ * confidenceDelta < 0 → invalidated evidence → likelihood = 0.5 + delta/2
25
+ *
26
+ * @param priors Symbol → prior probability map
27
+ * @param symbol The specific symbol to update
28
+ * @param confidenceDelta From hypothesis ledger (−1.0 to +1.0)
29
+ * @returns New priors map with updated value for symbol
30
+ */
31
+ export declare function updateSymbolPrior(priors: Record<string, number>, symbol: string, confidenceDelta: number): Record<string, number>;
@@ -0,0 +1,60 @@
1
+ // ─── Bayesian Update ─────────────────────────────────────────────────────────
2
+ /**
3
+ * Applies a single-step Bayesian update to a prior probability.
4
+ *
5
+ * Formula: posterior = (prior * likelihood) / normalizing_constant
6
+ *
7
+ * The normalizing constant ensures the result stays in [0, 1]:
8
+ * Z = prior * likelihood + (1 - prior) * (1 - likelihood)
9
+ *
10
+ * This is the standard Beta-Bernoulli update for binary outcomes.
11
+ * - likelihood > 0.5 → positive evidence → posterior increases
12
+ * - likelihood < 0.5 → negative evidence → posterior decreases
13
+ * - likelihood = 0.5 → no information → posterior unchanged
14
+ *
15
+ * @param prior Current prior probability (0.0 – 1.0)
16
+ * @param likelihood Likelihood of observation given the hypothesis (0.0 – 1.0)
17
+ * @returns Updated posterior probability (0.0 – 1.0)
18
+ */
19
+ export function updatePriorBayesian(prior, likelihood) {
20
+ if (prior < 0 || prior > 1)
21
+ throw new Error(`prior must be in [0, 1], got ${prior}`);
22
+ if (likelihood < 0 || likelihood > 1)
23
+ throw new Error(`likelihood must be in [0, 1], got ${likelihood}`);
24
+ // P(H|E) = P(E|H) * P(H) / P(E)
25
+ // P(E) = P(E|H)*P(H) + P(E|¬H)*P(¬H)
26
+ const pEgivenH = likelihood;
27
+ const pEgivenNotH = 1 - likelihood;
28
+ const pH = prior;
29
+ const pNotH = 1 - prior;
30
+ const pE = pEgivenH * pH + pEgivenNotH * pNotH;
31
+ // Avoid division by zero (shouldn't happen with valid inputs)
32
+ if (pE === 0)
33
+ return prior;
34
+ return (pEgivenH * pH) / pE;
35
+ }
36
+ /**
37
+ * Applies a Bayesian update to a PriorSet's individual symbol priors
38
+ * using confidence deltas from the hypothesis ledger.
39
+ *
40
+ * confidenceDelta > 0 → validated evidence → likelihood = 0.5 + delta/2
41
+ * confidenceDelta < 0 → invalidated evidence → likelihood = 0.5 + delta/2
42
+ *
43
+ * @param priors Symbol → prior probability map
44
+ * @param symbol The specific symbol to update
45
+ * @param confidenceDelta From hypothesis ledger (−1.0 to +1.0)
46
+ * @returns New priors map with updated value for symbol
47
+ */
48
+ export function updateSymbolPrior(priors, symbol, confidenceDelta) {
49
+ const currentPrior = priors[symbol] ?? 0.5;
50
+ // Map confidenceDelta (−1..+1) to likelihood (0..1)
51
+ // delta=+1.0 → likelihood=1.0 (strong positive evidence)
52
+ // delta=0.0 → likelihood=0.5 (no information)
53
+ // delta=−1.0 → likelihood=0.0 (strong negative evidence)
54
+ const likelihood = Math.max(0, Math.min(1, 0.5 + confidenceDelta / 2));
55
+ return {
56
+ ...priors,
57
+ [symbol]: updatePriorBayesian(currentPrior, likelihood)
58
+ };
59
+ }
60
+ //# sourceMappingURL=bayesian-update.js.map
@@ -0,0 +1,42 @@
1
+ export type PromotionGate = "development" | "backtest" | "shadow" | "live";
2
+ export interface PriorSet {
3
+ priorSetId: string;
4
+ taskClass: string;
5
+ version: number;
6
+ confidence: number;
7
+ priors: Record<string, number>;
8
+ lastUpdatedAt: string;
9
+ promotionGate: PromotionGate;
10
+ sampleCount: number;
11
+ }
12
+ export interface PriorStore {
13
+ priorSets: PriorSet[];
14
+ }
15
+ export interface CreatePriorSetInput {
16
+ priorSetId: string;
17
+ taskClass: string;
18
+ priors: Record<string, number>;
19
+ }
20
+ export declare function createPriorSet(input: CreatePriorSetInput): PriorSet;
21
+ export declare function loadPriorStore(storeDir: string): Promise<PriorStore>;
22
+ export declare function savePriorStore(store: PriorStore, storeDir: string): Promise<void>;
23
+ /**
24
+ * Promotes a PriorSet to the next gate in the sequence.
25
+ * Gates must advance sequentially: development → backtest → shadow → live.
26
+ * Throws if the target gate is not the immediate next gate.
27
+ */
28
+ export declare function promotePriorSet(priorSetId: string, targetGate: PromotionGate, storeDir: string): Promise<PriorSet>;
29
+ /**
30
+ * Rolls back a PriorSet to the previous gate.
31
+ * Rollback is always available from any gate above development.
32
+ * Throws if already at development.
33
+ */
34
+ export declare function rollbackPriorSet(priorSetId: string, storeDir: string): Promise<PriorSet>;
35
+ /**
36
+ * Returns all PriorSets from the store.
37
+ */
38
+ export declare function listPriorSets(storeDir: string): Promise<PriorSet[]>;
39
+ /**
40
+ * Returns a single PriorSet by ID, or null if not found.
41
+ */
42
+ export declare function getPriorSet(priorSetId: string, storeDir: string): Promise<PriorSet | null>;
@@ -0,0 +1,111 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ // ─── Gate ordering ────────────────────────────────────────────────────────────
5
+ const GATE_ORDER = ["development", "backtest", "shadow", "live"];
6
+ function gateIndex(gate) {
7
+ return GATE_ORDER.indexOf(gate);
8
+ }
9
+ function nextGate(current) {
10
+ const idx = gateIndex(current);
11
+ return idx < GATE_ORDER.length - 1 ? GATE_ORDER[idx + 1] ?? null : null;
12
+ }
13
+ function prevGate(current) {
14
+ const idx = gateIndex(current);
15
+ return idx > 0 ? GATE_ORDER[idx - 1] ?? null : null;
16
+ }
17
+ // ─── Factory ──────────────────────────────────────────────────────────────────
18
+ export function createPriorSet(input) {
19
+ const confidence = Object.keys(input.priors).length > 0
20
+ ? Object.values(input.priors).reduce((a, b) => a + b, 0) / Object.keys(input.priors).length
21
+ : 0;
22
+ return {
23
+ priorSetId: input.priorSetId,
24
+ taskClass: input.taskClass,
25
+ version: 1,
26
+ confidence,
27
+ priors: { ...input.priors },
28
+ lastUpdatedAt: new Date().toISOString(),
29
+ promotionGate: "development",
30
+ sampleCount: 0
31
+ };
32
+ }
33
+ // ─── Persistence ─────────────────────────────────────────────────────────────
34
+ const STORE_FILE = "prior-sets.json";
35
+ export async function loadPriorStore(storeDir) {
36
+ const path = join(storeDir, STORE_FILE);
37
+ if (!existsSync(path))
38
+ return { priorSets: [] };
39
+ const raw = await readFile(path, "utf8");
40
+ return JSON.parse(raw);
41
+ }
42
+ export async function savePriorStore(store, storeDir) {
43
+ await writeFile(join(storeDir, STORE_FILE), JSON.stringify(store, null, 2), "utf8");
44
+ }
45
+ // ─── Operations ───────────────────────────────────────────────────────────────
46
+ /**
47
+ * Promotes a PriorSet to the next gate in the sequence.
48
+ * Gates must advance sequentially: development → backtest → shadow → live.
49
+ * Throws if the target gate is not the immediate next gate.
50
+ */
51
+ export async function promotePriorSet(priorSetId, targetGate, storeDir) {
52
+ const store = await loadPriorStore(storeDir);
53
+ const idx = store.priorSets.findIndex(p => p.priorSetId === priorSetId);
54
+ if (idx === -1)
55
+ throw new Error(`PriorSet not found: ${priorSetId}`);
56
+ const current = store.priorSets[idx];
57
+ const expected = nextGate(current.promotionGate);
58
+ if (expected === null || targetGate !== expected) {
59
+ throw new Error(`Invalid promotion: ${current.promotionGate} → ${targetGate}. ` +
60
+ `Expected next gate: ${expected ?? "none (already at live)"}. Gates must advance sequentially.`);
61
+ }
62
+ const updated = {
63
+ ...current,
64
+ promotionGate: targetGate,
65
+ version: current.version + 1,
66
+ lastUpdatedAt: new Date().toISOString()
67
+ };
68
+ store.priorSets[idx] = updated;
69
+ await savePriorStore(store, storeDir);
70
+ return updated;
71
+ }
72
+ /**
73
+ * Rolls back a PriorSet to the previous gate.
74
+ * Rollback is always available from any gate above development.
75
+ * Throws if already at development.
76
+ */
77
+ export async function rollbackPriorSet(priorSetId, storeDir) {
78
+ const store = await loadPriorStore(storeDir);
79
+ const idx = store.priorSets.findIndex(p => p.priorSetId === priorSetId);
80
+ if (idx === -1)
81
+ throw new Error(`PriorSet not found: ${priorSetId}`);
82
+ const current = store.priorSets[idx];
83
+ const prev = prevGate(current.promotionGate);
84
+ if (prev === null) {
85
+ throw new Error(`Cannot rollback ${priorSetId}: already at development gate.`);
86
+ }
87
+ const updated = {
88
+ ...current,
89
+ promotionGate: prev,
90
+ version: current.version + 1,
91
+ lastUpdatedAt: new Date().toISOString()
92
+ };
93
+ store.priorSets[idx] = updated;
94
+ await savePriorStore(store, storeDir);
95
+ return updated;
96
+ }
97
+ /**
98
+ * Returns all PriorSets from the store.
99
+ */
100
+ export async function listPriorSets(storeDir) {
101
+ const store = await loadPriorStore(storeDir);
102
+ return store.priorSets;
103
+ }
104
+ /**
105
+ * Returns a single PriorSet by ID, or null if not found.
106
+ */
107
+ export async function getPriorSet(priorSetId, storeDir) {
108
+ const store = await loadPriorStore(storeDir);
109
+ return store.priorSets.find(p => p.priorSetId === priorSetId) ?? null;
110
+ }
111
+ //# sourceMappingURL=prior-sets.js.map
@@ -0,0 +1,17 @@
1
+ export interface GuardedPromotionInput {
2
+ candidateId: string;
3
+ holdoutPassed: number;
4
+ holdoutTotal: number;
5
+ regressionCount: number;
6
+ rollbackPlan: string;
7
+ humanApproved: boolean;
8
+ permanentChange: boolean;
9
+ }
10
+ export interface GuardedPromotionDecision {
11
+ candidateId: string;
12
+ decision: "promote" | "reject";
13
+ reasons: string[];
14
+ allowedClaimWording: string;
15
+ nonClaims: string[];
16
+ }
17
+ export declare function evaluateGuardedPromotion(input: GuardedPromotionInput): GuardedPromotionDecision;
@@ -0,0 +1,23 @@
1
+ export function evaluateGuardedPromotion(input) {
2
+ const reasons = [];
3
+ if (input.holdoutTotal <= 0 || input.holdoutPassed !== input.holdoutTotal) {
4
+ reasons.push("holdout suite did not pass completely");
5
+ }
6
+ if (input.regressionCount > 0) {
7
+ reasons.push("regressions detected");
8
+ }
9
+ if (input.rollbackPlan.trim().length === 0) {
10
+ reasons.push("rollback plan missing");
11
+ }
12
+ if (input.permanentChange && !input.humanApproved) {
13
+ reasons.push("human approval required for permanent change");
14
+ }
15
+ return {
16
+ candidateId: input.candidateId,
17
+ decision: reasons.length === 0 ? "promote" : "reject",
18
+ reasons,
19
+ allowedClaimWording: "guarded self-improvement candidate promotion only after holdout, rollback, and human approval gates pass.",
20
+ nonClaims: ["unqualified self-improving", "silent self-modification"]
21
+ };
22
+ }
23
+ //# sourceMappingURL=promotion-gate.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Blast Radius Estimator — pre-execution risk scoring.
3
+ *
4
+ * Before any attempt runs, estimates the potential scope of impact (0–100)
5
+ * based on:
6
+ * - Objective language (refactor/migrate/delete keywords increase radius)
7
+ * - Known hotspot files in the likely path (files with regression history)
8
+ * - Repository size (larger repos have wider potential impact)
9
+ * - Files explicitly in scope (each hotspot-adjacent file adds risk)
10
+ *
11
+ * The router uses this score to gate high-risk tasks onto high-trust models.
12
+ * The CLI displays it in the pre-run Mission Brief.
13
+ */
14
+ import type { RepoHotspot } from "../graph/hotspots.js";
15
+ export interface BlastRadiusInput {
16
+ /** The task objective text, used for keyword analysis */
17
+ objective: string;
18
+ /** Absolute path to the repository root */
19
+ repoRoot: string;
20
+ /** Files the task has explicitly identified as targets (optional) */
21
+ currentFiles?: string[];
22
+ /** Hotspot data from MartinGraph.assembleHotspotContext() (optional) */
23
+ hotspots?: RepoHotspot[];
24
+ }
25
+ export interface BlastRadiusResult {
26
+ /** Risk score 0–100. >70 triggers high-trust route enforcement. */
27
+ score: number;
28
+ /** Files identified as likely to be touched based on scope + hotspot data */
29
+ filesAtRisk: string[];
30
+ /** Subset of filesAtRisk that have recorded regression history */
31
+ hotspotsInPath: string[];
32
+ /** Categorical risk label derived from score */
33
+ regressionRisk: "low" | "medium" | "high";
34
+ /** Human-readable explanation of what drove the score */
35
+ rationale: string;
36
+ }
37
+ /**
38
+ * Estimates the blast radius of a planned task before execution.
39
+ * Non-throwing: if directory reads fail, falls back gracefully with
40
+ * a conservative medium score.
41
+ */
42
+ export declare function estimateBlastRadius(input: BlastRadiusInput): Promise<BlastRadiusResult>;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Blast Radius Estimator — pre-execution risk scoring.
3
+ *
4
+ * Before any attempt runs, estimates the potential scope of impact (0–100)
5
+ * based on:
6
+ * - Objective language (refactor/migrate/delete keywords increase radius)
7
+ * - Known hotspot files in the likely path (files with regression history)
8
+ * - Repository size (larger repos have wider potential impact)
9
+ * - Files explicitly in scope (each hotspot-adjacent file adds risk)
10
+ *
11
+ * The router uses this score to gate high-risk tasks onto high-trust models.
12
+ * The CLI displays it in the pre-run Mission Brief.
13
+ */
14
+ import { readdir } from "node:fs/promises";
15
+ import { join } from "node:path";
16
+ /** Keywords that strongly suggest broad file scope changes */
17
+ const HIGH_BLAST_KEYWORDS = [
18
+ "refactor",
19
+ "migrate",
20
+ "restructure",
21
+ "rename",
22
+ "move",
23
+ "delete",
24
+ "remove all",
25
+ "overhaul",
26
+ "rewrite",
27
+ "reorganize",
28
+ "consolidate"
29
+ ];
30
+ /** Keywords that suggest narrow, test-scoped changes (reduce risk) */
31
+ const LOW_BLAST_KEYWORDS = [
32
+ "test",
33
+ "spec",
34
+ "fixture",
35
+ "mock",
36
+ "stub",
37
+ "snapshot",
38
+ "add test",
39
+ "write test"
40
+ ];
41
+ /**
42
+ * Estimates the blast radius of a planned task before execution.
43
+ * Non-throwing: if directory reads fail, falls back gracefully with
44
+ * a conservative medium score.
45
+ */
46
+ export async function estimateBlastRadius(input) {
47
+ const { objective, repoRoot, currentFiles = [], hotspots = [] } = input;
48
+ const objectiveLower = objective.toLowerCase();
49
+ const rationale = [];
50
+ let score = 10; // Every task has nonzero blast radius
51
+ // ── Keyword analysis ──────────────────────────────────────────────────────
52
+ const highKeywordsFound = HIGH_BLAST_KEYWORDS.filter((k) => objectiveLower.includes(k));
53
+ if (highKeywordsFound.length > 0) {
54
+ const delta = Math.min(25, highKeywordsFound.length * 12);
55
+ score += delta;
56
+ rationale.push(`Objective contains high-scope keywords: ${highKeywordsFound.join(", ")} (+${String(delta)})`);
57
+ }
58
+ const lowKeywordsFound = LOW_BLAST_KEYWORDS.filter((k) => objectiveLower.includes(k));
59
+ if (lowKeywordsFound.length > 0 && highKeywordsFound.length === 0) {
60
+ score -= 10;
61
+ rationale.push(`Objective is test-scoped (${lowKeywordsFound[0]}), reducing radius (-10)`);
62
+ }
63
+ // ── Repository size ───────────────────────────────────────────────────────
64
+ const repoFileCount = await countFiles(repoRoot);
65
+ if (repoFileCount > 500) {
66
+ score += 20;
67
+ rationale.push(`Large repository (${String(repoFileCount)} files, +20)`);
68
+ }
69
+ else if (repoFileCount > 100) {
70
+ score += 8;
71
+ rationale.push(`Medium repository (${String(repoFileCount)} files, +8)`);
72
+ }
73
+ // ── Hotspot overlap ───────────────────────────────────────────────────────
74
+ const filesAtRisk = [...currentFiles];
75
+ const hotspotsInPath = [];
76
+ // Add hotspot files that are likely in scope (high regression count)
77
+ const criticalHotspots = hotspots.filter((h) => h.regressionCount > 2);
78
+ for (const hotspot of criticalHotspots) {
79
+ const delta = 15;
80
+ score += delta;
81
+ hotspotsInPath.push(hotspot.file);
82
+ if (!filesAtRisk.includes(hotspot.file))
83
+ filesAtRisk.push(hotspot.file);
84
+ rationale.push(`Hotspot in path: ${hotspot.file} (${String(hotspot.regressionCount)} regressions, +${String(delta)})`);
85
+ }
86
+ // Files with any regression history add smaller penalty
87
+ const mildHotspots = hotspots.filter((h) => h.regressionCount > 0 && h.regressionCount <= 2);
88
+ for (const hotspot of mildHotspots) {
89
+ score += 5;
90
+ if (!filesAtRisk.includes(hotspot.file))
91
+ filesAtRisk.push(hotspot.file);
92
+ }
93
+ if (mildHotspots.length > 0) {
94
+ rationale.push(`${String(mildHotspots.length)} files with minor regression history (+${String(mildHotspots.length * 5)})`);
95
+ }
96
+ // ── Explicit files in scope ────────────────────────────────────────────────
97
+ if (currentFiles.length > 0) {
98
+ const adjacentHotspots = currentFiles.filter((f) => hotspots.some((h) => h.file === f && h.regressionCount > 0));
99
+ if (adjacentHotspots.length > 0) {
100
+ score += adjacentHotspots.length * 10;
101
+ rationale.push(`${String(adjacentHotspots.length)} explicitly targeted files have regression history (+${String(adjacentHotspots.length * 10)})`);
102
+ }
103
+ }
104
+ // ── Clamp and label ───────────────────────────────────────────────────────
105
+ const clampedScore = Math.max(0, Math.min(100, score));
106
+ const regressionRisk = clampedScore >= 70 ? "high" : clampedScore >= 40 ? "medium" : "low";
107
+ if (rationale.length === 0) {
108
+ rationale.push("No elevated risk signals detected");
109
+ }
110
+ return {
111
+ score: clampedScore,
112
+ filesAtRisk,
113
+ hotspotsInPath,
114
+ regressionRisk,
115
+ rationale: rationale.join("; ")
116
+ };
117
+ }
118
+ /**
119
+ * Counts source files in a directory (non-recursive for performance).
120
+ * Only counts .ts, .tsx, .js, .jsx, .py, .go, .rs files — not config/docs.
121
+ * Returns 0 on any error (missing dir, permission denied, etc.).
122
+ */
123
+ async function countFiles(dir) {
124
+ const SOURCE_EXTENSIONS = new Set([
125
+ ".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".cs", ".rb"
126
+ ]);
127
+ async function countRecursive(current, depth) {
128
+ if (depth > 6)
129
+ return 0; // don't recurse into deep vendor trees
130
+ try {
131
+ const entries = await readdir(current, { withFileTypes: true });
132
+ let count = 0;
133
+ for (const entry of entries) {
134
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
135
+ continue;
136
+ }
137
+ if (entry.isDirectory()) {
138
+ count += await countRecursive(join(current, entry.name), depth + 1);
139
+ }
140
+ else if (SOURCE_EXTENSIONS.has(extname(entry.name))) {
141
+ count += 1;
142
+ }
143
+ }
144
+ return count;
145
+ }
146
+ catch {
147
+ return 0;
148
+ }
149
+ }
150
+ return countRecursive(dir, 0);
151
+ }
152
+ function extname(name) {
153
+ const dot = name.lastIndexOf(".");
154
+ return dot >= 0 ? name.slice(dot) : "";
155
+ }
156
+ //# sourceMappingURL=blast-radius.js.map
@@ -0,0 +1,31 @@
1
+ import { type LeashInput, type PolicyResult } from "@martin/policy";
2
+ import type { SafetyLeashDecision } from "../leash.js";
3
+ export interface PolicyLeashOptions {
4
+ /** Explicit compiled policy path. Raw Rego fails closed inside @martin/policy. */
5
+ policyPath?: string;
6
+ /** Explicit compiled WASM policy path. Takes precedence over policyPath. */
7
+ policyWasmPath?: string;
8
+ /** Repository root used for .martin/policy.wasm discovery. */
9
+ repoRoot?: string;
10
+ timeoutMs?: number;
11
+ }
12
+ export interface PolicyDeniedInput {
13
+ surface: string;
14
+ command?: string | null;
15
+ path?: string;
16
+ value?: string | null;
17
+ reasons: string[];
18
+ }
19
+ export interface PolicyLeashVerdict {
20
+ allow: boolean;
21
+ reasons: string[];
22
+ deniedInputs: PolicyDeniedInput[];
23
+ inputsEvaluated: number;
24
+ source?: PolicyResult["source"];
25
+ policyPath?: string;
26
+ error?: string;
27
+ }
28
+ export type PolicyBackedSafetyLeashDecision = SafetyLeashDecision & {
29
+ policyVerdict: PolicyLeashVerdict;
30
+ };
31
+ export declare function applyPolicyToLeashDecision(decision: SafetyLeashDecision, inputs: LeashInput[], options?: PolicyLeashOptions): Promise<PolicyBackedSafetyLeashDecision>;