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,178 @@
1
+ /**
2
+ * CVE Patch Scanner — Phase 37.
3
+ *
4
+ * Parses a unified diff for newly added package dependencies and queries the
5
+ * OSV.dev API (https://api.osv.dev) to check for known CVEs. Blocks the
6
+ * attempt if any discovered package has severity HIGH or CRITICAL.
7
+ *
8
+ * Supported manifest formats:
9
+ * - package.json (npm/Node.js) — ecosystem: "npm"
10
+ * - requirements.txt (Python) — ecosystem: "PyPI"
11
+ * - Cargo.toml (Rust) — ecosystem: "crates.io"
12
+ * - go.mod (Go) — ecosystem: "Go"
13
+ *
14
+ * Design rules:
15
+ * - Advisory when OSV.dev is unreachable (never hard-fail on network error)
16
+ * - Only checks ADDED lines (+ prefix) — not removed packages
17
+ * - Deduplicates package names before querying
18
+ * - MAX_PACKAGES_PER_SCAN = 20 to bound latency
19
+ */
20
+ // ─── Constants ────────────────────────────────────────────────────────────────
21
+ const OSV_API = "https://api.osv.dev/v1/query";
22
+ const MAX_PACKAGES_PER_SCAN = 20;
23
+ const BLOCKING_SEVERITIES = ["HIGH", "CRITICAL"];
24
+ // ─── Diff parsing ─────────────────────────────────────────────────────────────
25
+ /**
26
+ * Extract newly added package dependencies from a unified diff string.
27
+ * Only examines added lines (starting with +) to avoid flagging removals.
28
+ */
29
+ export function extractPackageCandidates(diff) {
30
+ const candidates = [];
31
+ const seen = new Set();
32
+ const addedLines = diff
33
+ .split("\n")
34
+ .filter(line => line.startsWith("+") && !line.startsWith("+++"));
35
+ for (const line of addedLines) {
36
+ const content = line.slice(1).trim();
37
+ // package.json dependency: "name": "^1.2.3"
38
+ const npmMatch = content.match(/^"([@\w][\w\-./@]*)"\s*:\s*"([^"]+)"/);
39
+ if (npmMatch && !content.includes("description") && !content.includes("license")) {
40
+ const name = npmMatch[1];
41
+ const raw = npmMatch[2];
42
+ const version = raw.replace(/^[\^~>=<]+/, "").split(" ")[0];
43
+ const key = `npm:${name}`;
44
+ if (!seen.has(key)) {
45
+ seen.add(key);
46
+ candidates.push({ name, version, ecosystem: "npm" });
47
+ }
48
+ continue;
49
+ }
50
+ // requirements.txt: package==1.2.3 or package>=1.0
51
+ const pypiMatch = content.match(/^([\w\-\.]+)\s*[>=<!~^]+\s*([\d.]+)/);
52
+ if (pypiMatch) {
53
+ const name = pypiMatch[1];
54
+ const version = pypiMatch[2];
55
+ const key = `PyPI:${name.toLowerCase()}`;
56
+ if (!seen.has(key)) {
57
+ seen.add(key);
58
+ candidates.push({ name, version, ecosystem: "PyPI" });
59
+ }
60
+ continue;
61
+ }
62
+ // Cargo.toml: name = "1.2.3" or name = { version = "1.2.3" }
63
+ const cargoMatch = content.match(/^([\w\-]+)\s*=\s*"([\d.]+)"/);
64
+ if (cargoMatch) {
65
+ const name = cargoMatch[1];
66
+ const version = cargoMatch[2];
67
+ const key = `crates.io:${name}`;
68
+ if (!seen.has(key)) {
69
+ seen.add(key);
70
+ candidates.push({ name, version, ecosystem: "crates.io" });
71
+ }
72
+ continue;
73
+ }
74
+ // go.mod: require package/path v1.2.3
75
+ const goMatch = content.match(/^(?:require\s+)?([\w.\-/]+)\s+v([\d.]+(?:-\w+)?)/);
76
+ if (goMatch) {
77
+ const name = goMatch[1];
78
+ const version = goMatch[2];
79
+ const key = `Go:${name}`;
80
+ if (!seen.has(key)) {
81
+ seen.add(key);
82
+ candidates.push({ name, version, ecosystem: "Go" });
83
+ }
84
+ continue;
85
+ }
86
+ }
87
+ return candidates.slice(0, MAX_PACKAGES_PER_SCAN);
88
+ }
89
+ function resolveSeverity(vuln) {
90
+ // Try database_specific.severity first (most common)
91
+ const dbSev = vuln.database_specific?.severity?.toUpperCase();
92
+ if (dbSev === "CRITICAL")
93
+ return "CRITICAL";
94
+ if (dbSev === "HIGH")
95
+ return "HIGH";
96
+ if (dbSev === "MEDIUM")
97
+ return "MEDIUM";
98
+ if (dbSev === "LOW")
99
+ return "LOW";
100
+ // Fall back to CVSS score — OSV may return a numeric score string ("7.5") or a
101
+ // CVSS vector string ("CVSS:3.1/AV:N/..."). Vector strings are NOT numeric scores;
102
+ // skip them to avoid over-classification.
103
+ const cvss = vuln.severity?.find(s => s.type === "CVSS_V3")?.score ?? "";
104
+ if (cvss && !cvss.startsWith("CVSS:")) {
105
+ const baseScore = parseFloat(cvss);
106
+ if (!isNaN(baseScore) && baseScore >= 0 && baseScore <= 10) {
107
+ if (baseScore >= 9.0)
108
+ return "CRITICAL";
109
+ if (baseScore >= 7.0)
110
+ return "HIGH";
111
+ if (baseScore >= 4.0)
112
+ return "MEDIUM";
113
+ return "LOW";
114
+ }
115
+ }
116
+ return "UNKNOWN";
117
+ }
118
+ async function queryOsv(candidate) {
119
+ const body = JSON.stringify({
120
+ version: candidate.version,
121
+ package: { name: candidate.name, ecosystem: candidate.ecosystem }
122
+ });
123
+ const res = await fetch(OSV_API, {
124
+ method: "POST",
125
+ headers: { "Content-Type": "application/json" },
126
+ body,
127
+ signal: AbortSignal.timeout(5_000)
128
+ });
129
+ if (!res.ok)
130
+ return [];
131
+ const data = (await res.json());
132
+ return (data.vulns ?? []).map(v => ({
133
+ packageName: candidate.name,
134
+ version: candidate.version,
135
+ ecosystem: candidate.ecosystem,
136
+ vulnId: v.id,
137
+ summary: v.summary ?? "No summary available",
138
+ severity: resolveSeverity(v),
139
+ url: `https://osv.dev/vulnerability/${v.id}`
140
+ }));
141
+ }
142
+ // ─── Public API ───────────────────────────────────────────────────────────────
143
+ /**
144
+ * Scan a unified diff for new package dependencies and check them against
145
+ * the OSV.dev vulnerability database.
146
+ *
147
+ * Returns immediately (advisory mode) if OSV.dev is unreachable.
148
+ * Blocks the attempt if any package has severity HIGH or CRITICAL.
149
+ */
150
+ export async function scanDiffForCves(diff) {
151
+ const packageCandidates = extractPackageCandidates(diff);
152
+ if (packageCandidates.length === 0) {
153
+ return { packageCandidates: [], matches: [], blocked: false };
154
+ }
155
+ let matches = [];
156
+ let networkError = false;
157
+ try {
158
+ const results = await Promise.all(packageCandidates.map(queryOsv));
159
+ matches = results.flat();
160
+ }
161
+ catch {
162
+ // OSV.dev unreachable — advisory mode, do not block
163
+ networkError = true;
164
+ return { packageCandidates, matches: [], blocked: false, networkError };
165
+ }
166
+ const blockingMatches = matches.filter(m => BLOCKING_SEVERITIES.includes(m.severity));
167
+ const blocked = blockingMatches.length > 0;
168
+ let blockReason;
169
+ if (blocked) {
170
+ const summary = blockingMatches
171
+ .slice(0, 3)
172
+ .map(m => `${m.packageName} (${m.vulnId} ${m.severity})`)
173
+ .join(", ");
174
+ blockReason = `CVE scan blocked: ${summary}${blockingMatches.length > 3 ? ` +${blockingMatches.length - 3} more` : ""}`;
175
+ }
176
+ return { packageCandidates, matches, blocked, blockReason };
177
+ }
178
+ //# sourceMappingURL=cve-scanner.js.map
@@ -0,0 +1,29 @@
1
+ export interface CostBaseline {
2
+ samples: number;
3
+ p25CostUsd: number;
4
+ p50CostUsd: number;
5
+ lastUpdated: string;
6
+ }
7
+ export interface EfficiencyAnomaly {
8
+ trapId: "T14";
9
+ runId: string;
10
+ actualCostUsd: number;
11
+ p25BaselineCostUsd: number;
12
+ deviationPct: number;
13
+ /** T14 is always "logged" — never "block". Warn-only trap. */
14
+ action: "logged";
15
+ }
16
+ export interface CheckEfficiencyInput {
17
+ runId: string;
18
+ actualCostUsd: number;
19
+ baseline: CostBaseline;
20
+ }
21
+ /**
22
+ * T14 — Efficiency Anomaly Detection
23
+ *
24
+ * Detection logic:
25
+ * - IF baseline.samples < 20 → DO NOT FIRE (cold-start guard)
26
+ * - IF actualCost < (baseline.p25 * 0.75) → fire T14 anomaly
27
+ * - T14 is ALWAYS warn-only (action: "logged") — never hard-rejects a run
28
+ */
29
+ export declare function checkEfficiencyAnomaly(input: CheckEfficiencyInput): EfficiencyAnomaly | null;
@@ -0,0 +1,30 @@
1
+ // ─── Types ────────────────────────────────────────────────────────────────────
2
+ // ─── Detection ────────────────────────────────────────────────────────────────
3
+ /**
4
+ * T14 — Efficiency Anomaly Detection
5
+ *
6
+ * Detection logic:
7
+ * - IF baseline.samples < 20 → DO NOT FIRE (cold-start guard)
8
+ * - IF actualCost < (baseline.p25 * 0.75) → fire T14 anomaly
9
+ * - T14 is ALWAYS warn-only (action: "logged") — never hard-rejects a run
10
+ */
11
+ export function checkEfficiencyAnomaly(input) {
12
+ // Cold-start guard: never fire before 20 samples
13
+ if (input.baseline.samples < 20) {
14
+ return null;
15
+ }
16
+ const threshold = input.baseline.p25CostUsd * 0.75;
17
+ if (input.actualCostUsd >= threshold) {
18
+ return null;
19
+ }
20
+ const deviationPct = ((input.baseline.p25CostUsd - input.actualCostUsd) / input.baseline.p25CostUsd) * 100;
21
+ return {
22
+ trapId: "T14",
23
+ runId: input.runId,
24
+ actualCostUsd: input.actualCostUsd,
25
+ p25BaselineCostUsd: input.baseline.p25CostUsd,
26
+ deviationPct,
27
+ action: "logged"
28
+ };
29
+ }
30
+ //# sourceMappingURL=efficiency-sentinel.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Stagnation detection — monotonic progress guard.
3
+ *
4
+ * Detects when an agent loop is spinning without making forward progress:
5
+ * the last N attempts all produced the same failure classification and
6
+ * the same intervention response. When stagnation is detected the caller
7
+ * should exit the loop with lifecycleState "stagnation_detected".
8
+ *
9
+ * This prevents the "$500 infinite loop" scenario where Claude Code
10
+ * enters a retry cycle for hours without human awareness.
11
+ */
12
+ import type { LoopAttempt } from "../../contracts/index.js";
13
+ export interface StagnationInput {
14
+ readonly attempts: LoopAttempt[];
15
+ /** Number of consecutive attempts to inspect. Defaults to 3. */
16
+ readonly windowSize?: number;
17
+ }
18
+ export interface StagnationResult {
19
+ readonly stagnant: boolean;
20
+ readonly reason?: string;
21
+ readonly windowSize: number;
22
+ }
23
+ /**
24
+ * Return `stagnant: true` when the last `windowSize` attempts all share:
25
+ * - the same `adapterId` (same adapter/model — not a recovery escalation)
26
+ * - the same `failureClass` value (including both-undefined)
27
+ * - the same `intervention` value (including both-undefined)
28
+ *
29
+ * Requiring the same adapterId prevents false positives when the runtime
30
+ * is legitimately cycling through different recovery paths (which leads to
31
+ * `diminishing_returns`, not stagnation).
32
+ *
33
+ * When fewer attempts than `windowSize` exist, always returns `stagnant: false`.
34
+ */
35
+ export declare function detectStagnation(input: StagnationInput): StagnationResult;
@@ -0,0 +1,46 @@
1
+ // ---------------------------------------------------------------------------
2
+ // detectStagnation
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Return `stagnant: true` when the last `windowSize` attempts all share:
6
+ * - the same `adapterId` (same adapter/model — not a recovery escalation)
7
+ * - the same `failureClass` value (including both-undefined)
8
+ * - the same `intervention` value (including both-undefined)
9
+ *
10
+ * Requiring the same adapterId prevents false positives when the runtime
11
+ * is legitimately cycling through different recovery paths (which leads to
12
+ * `diminishing_returns`, not stagnation).
13
+ *
14
+ * When fewer attempts than `windowSize` exist, always returns `stagnant: false`.
15
+ */
16
+ export function detectStagnation(input) {
17
+ const windowSize = input.windowSize ?? 3;
18
+ if (input.attempts.length < windowSize) {
19
+ return { stagnant: false, windowSize };
20
+ }
21
+ // Take the last `windowSize` attempts
22
+ const window = input.attempts.slice(-windowSize);
23
+ const first = window[0];
24
+ const referenceAdapter = first.adapterId;
25
+ const referenceClass = first.failureClass;
26
+ const referenceIntervention = first.intervention;
27
+ for (const attempt of window) {
28
+ // Different adapter = legitimate recovery escalation, not stagnation
29
+ if (attempt.adapterId !== referenceAdapter) {
30
+ return { stagnant: false, windowSize };
31
+ }
32
+ if (attempt.failureClass !== referenceClass) {
33
+ return { stagnant: false, windowSize };
34
+ }
35
+ if (attempt.intervention !== referenceIntervention) {
36
+ return { stagnant: false, windowSize };
37
+ }
38
+ }
39
+ const classLabel = referenceClass ?? "(none)";
40
+ const interventionLabel = referenceIntervention ?? "(none)";
41
+ const reason = `Stagnation detected: last ${windowSize} attempts on adapter "${referenceAdapter}" all produced ` +
42
+ `failureClass="${classLabel}" with intervention="${interventionLabel}". ` +
43
+ `Loop is not making forward progress.`;
44
+ return { stagnant: true, reason, windowSize };
45
+ }
46
+ //# sourceMappingURL=progress-guard.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * siem-emitter.ts — SLICE-16
3
+ *
4
+ * SIEM integration: emits Martin Loop gate events in OCSF or CEF format.
5
+ *
6
+ * Events are batched (up to 50 events or 5s) then POSTed to the configured
7
+ * endpoint. On transport failure the events are written to
8
+ * ~/.martin/siem-queue/ for later retry via `martin siem drain`.
9
+ *
10
+ * Configuration (policy file or env):
11
+ * siem.endpoint — HTTP POST target (required to enable)
12
+ * siem.format — "ocsf" | "cef" (default: "ocsf")
13
+ * siem.apiKey — Bearer token for endpoint auth (optional)
14
+ */
15
+ export type SiemEventType = "attempt_started" | "attempt_completed" | "leash_blocked" | "policy_violated" | "budget_exceeded" | "patch_accepted" | "patch_rejected" | "rollback_triggered";
16
+ export type SiemFormat = "ocsf" | "cef";
17
+ export interface SiemEventInput {
18
+ type: SiemEventType;
19
+ loopId: string;
20
+ attemptId?: string;
21
+ message: string;
22
+ severity?: "info" | "low" | "medium" | "high" | "critical";
23
+ metadata?: Record<string, string | number | boolean>;
24
+ }
25
+ export declare function formatOcsf(event: SiemEventInput, timestamp: number): object;
26
+ export declare function formatCef(event: SiemEventInput, timestamp: number): string;
27
+ export interface SiemConfig {
28
+ endpoint: string;
29
+ format: SiemFormat;
30
+ apiKey?: string;
31
+ flushIntervalMs?: number;
32
+ maxBatchSize?: number;
33
+ }
34
+ export declare class SiemEmitter {
35
+ private readonly config;
36
+ private queue;
37
+ private flushTimer;
38
+ private fetchFn;
39
+ constructor(config: SiemConfig, fetchFn?: typeof fetch);
40
+ emit(event: SiemEventInput): void;
41
+ flush(): Promise<{
42
+ sent: number;
43
+ queued: number;
44
+ }>;
45
+ /** Returns all pending (unflushed) events. Used in tests. */
46
+ pendingCount(): number;
47
+ destroy(): void;
48
+ }
49
+ export declare function createSiemEmitter(config: Partial<SiemConfig> | undefined, fetchFn?: typeof fetch): SiemEmitter | null;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * siem-emitter.ts — SLICE-16
3
+ *
4
+ * SIEM integration: emits Martin Loop gate events in OCSF or CEF format.
5
+ *
6
+ * Events are batched (up to 50 events or 5s) then POSTed to the configured
7
+ * endpoint. On transport failure the events are written to
8
+ * ~/.martin/siem-queue/ for later retry via `martin siem drain`.
9
+ *
10
+ * Configuration (policy file or env):
11
+ * siem.endpoint — HTTP POST target (required to enable)
12
+ * siem.format — "ocsf" | "cef" (default: "ocsf")
13
+ * siem.apiKey — Bearer token for endpoint auth (optional)
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ // ---------------------------------------------------------------------------
17
+ // OCSF formatter
18
+ // ---------------------------------------------------------------------------
19
+ const OCSF_SEVERITY_MAP = {
20
+ info: 1, low: 2, medium: 4, high: 6, critical: 8
21
+ };
22
+ const OCSF_ACTIVITY_MAP = {
23
+ attempt_started: 1,
24
+ attempt_completed: 2,
25
+ leash_blocked: 3,
26
+ policy_violated: 4,
27
+ budget_exceeded: 5,
28
+ patch_accepted: 6,
29
+ patch_rejected: 7,
30
+ rollback_triggered: 8
31
+ };
32
+ export function formatOcsf(event, timestamp) {
33
+ return {
34
+ class_uid: 300101,
35
+ category_uid: 3,
36
+ type_uid: 300101,
37
+ time: timestamp,
38
+ severity_id: OCSF_SEVERITY_MAP[event.severity ?? "info"] ?? 1,
39
+ actor: { user: { name: "martin-loop" } },
40
+ metadata: {
41
+ version: "1.0.0",
42
+ product: { name: "Martin Loop" },
43
+ uid: createHash("sha256")
44
+ .update(`${event.loopId}:${event.attemptId ?? ""}:${timestamp}`)
45
+ .digest("hex")
46
+ .slice(0, 16)
47
+ },
48
+ activity_id: OCSF_ACTIVITY_MAP[event.type] ?? 0,
49
+ message: event.message,
50
+ unmapped: {
51
+ event_type: event.type,
52
+ loop_id: event.loopId,
53
+ attempt_id: event.attemptId ?? "",
54
+ ...(event.metadata ?? {})
55
+ }
56
+ };
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // CEF formatter
60
+ // ---------------------------------------------------------------------------
61
+ const CEF_SEVERITY_MAP = {
62
+ info: 0, low: 2, medium: 5, high: 7, critical: 10
63
+ };
64
+ function cefEscape(val) {
65
+ return val.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/=/g, "\\=");
66
+ }
67
+ export function formatCef(event, timestamp) {
68
+ const sev = CEF_SEVERITY_MAP[event.severity ?? "info"] ?? 0;
69
+ const ext = [
70
+ `rt=${timestamp}`,
71
+ `loopId=${cefEscape(event.loopId)}`,
72
+ `attemptId=${cefEscape(event.attemptId ?? "")}`,
73
+ `msg=${cefEscape(event.message)}`
74
+ ];
75
+ if (event.metadata) {
76
+ for (const [k, v] of Object.entries(event.metadata)) {
77
+ ext.push(`${cefEscape(String(k))}=${cefEscape(String(v))}`);
78
+ }
79
+ }
80
+ return `CEF:0|MartinLoop|martin-loop|1.0|${event.type}|${event.message}|${sev}| ${ext.join(" ")}`;
81
+ }
82
+ export class SiemEmitter {
83
+ config;
84
+ queue = [];
85
+ flushTimer = null;
86
+ fetchFn;
87
+ constructor(config, fetchFn) {
88
+ this.config = {
89
+ endpoint: config.endpoint,
90
+ format: config.format,
91
+ apiKey: config.apiKey,
92
+ flushIntervalMs: config.flushIntervalMs ?? 5000,
93
+ maxBatchSize: config.maxBatchSize ?? 50
94
+ };
95
+ this.fetchFn = fetchFn ?? fetch;
96
+ }
97
+ emit(event) {
98
+ this.queue.push({ event, timestamp: Date.now() });
99
+ if (this.queue.length >= this.config.maxBatchSize) {
100
+ void this.flush();
101
+ return;
102
+ }
103
+ if (this.config.flushIntervalMs > 0 && !this.flushTimer) {
104
+ this.flushTimer = setTimeout(() => {
105
+ void this.flush();
106
+ }, this.config.flushIntervalMs);
107
+ }
108
+ }
109
+ async flush() {
110
+ if (this.flushTimer) {
111
+ clearTimeout(this.flushTimer);
112
+ this.flushTimer = null;
113
+ }
114
+ const batch = this.queue.splice(0, this.config.maxBatchSize);
115
+ if (batch.length === 0)
116
+ return { sent: 0, queued: 0 };
117
+ const formatted = batch.map(({ event, timestamp }) => this.config.format === "cef"
118
+ ? formatCef(event, timestamp)
119
+ : formatOcsf(event, timestamp));
120
+ const headers = { "Content-Type": "application/json" };
121
+ if (this.config.apiKey)
122
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
123
+ try {
124
+ const res = await this.fetchFn(this.config.endpoint, {
125
+ method: "POST",
126
+ headers,
127
+ body: JSON.stringify({ events: formatted }),
128
+ signal: AbortSignal.timeout(10_000)
129
+ });
130
+ if (!res.ok)
131
+ throw new Error(`SIEM endpoint returned HTTP ${res.status}`);
132
+ return { sent: batch.length, queued: 0 };
133
+ }
134
+ catch {
135
+ // On failure: keep events in front of queue for retry
136
+ this.queue.unshift(...batch);
137
+ return { sent: 0, queued: this.queue.length };
138
+ }
139
+ }
140
+ /** Returns all pending (unflushed) events. Used in tests. */
141
+ pendingCount() {
142
+ return this.queue.length;
143
+ }
144
+ destroy() {
145
+ if (this.flushTimer)
146
+ clearTimeout(this.flushTimer);
147
+ }
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Factory — returns null when siem.endpoint is not configured
151
+ // ---------------------------------------------------------------------------
152
+ export function createSiemEmitter(config, fetchFn) {
153
+ if (!config?.endpoint)
154
+ return null;
155
+ return new SiemEmitter({ endpoint: config.endpoint, format: config.format ?? "ocsf", ...config }, fetchFn);
156
+ }
157
+ //# sourceMappingURL=siem-emitter.js.map
@@ -0,0 +1,22 @@
1
+ import type { LoopAttempt, LoopTask } from "../../contracts/index.js";
2
+ export interface AttemptFocusInput {
3
+ task: Pick<LoopTask, "objective" | "acceptanceCriteria">;
4
+ attempts: LoopAttempt[];
5
+ remainingBudgetUsd: number;
6
+ }
7
+ export interface RetryEconomicsInput {
8
+ previousAttempts: LoopAttempt[];
9
+ projectedUsd: number;
10
+ remainingBudgetUsd: number;
11
+ remainingIterations: number;
12
+ novelRecoveryPath?: boolean;
13
+ }
14
+ export interface RetryEconomicsDecision {
15
+ allowRetry: boolean;
16
+ reason?: string;
17
+ repeatedVerifierSignature?: string;
18
+ }
19
+ export declare function buildAttemptFocus(input: AttemptFocusInput): string;
20
+ export declare function evaluateRetryEconomics(input: RetryEconomicsInput): RetryEconomicsDecision;
21
+ export declare function detectRepeatedVerifierSignature(attempts: Pick<LoopAttempt, "verifierSummary">[], window?: number): string | undefined;
22
+ export declare function normalizeVerifierSignature(summary?: string): string | undefined;
@@ -0,0 +1,89 @@
1
+ export function buildAttemptFocus(input) {
2
+ const parts = [`Primary objective: ${input.task.objective}.`];
3
+ const acceptanceCriteria = input.task.acceptanceCriteria ?? [];
4
+ const lastAttempt = input.attempts.at(-1);
5
+ const repeatedSignature = detectRepeatedVerifierSignature(input.attempts);
6
+ if (acceptanceCriteria.length > 0) {
7
+ parts.push(`Acceptance criteria: ${acceptanceCriteria.join("; ")}.`);
8
+ }
9
+ if (lastAttempt?.failureClass) {
10
+ parts.push(`Last failure class: ${lastAttempt.failureClass}. Resolve that exact gap before widening scope.`);
11
+ }
12
+ if (lastAttempt?.summary) {
13
+ parts.push(`Do not repeat this failed pattern: ${truncateSentence(lastAttempt.summary, 160)}.`);
14
+ }
15
+ if (repeatedSignature) {
16
+ parts.push(`Repeated verifier signal: ${repeatedSignature}. Target the smallest change that clears this exact failure.`);
17
+ }
18
+ else if (lastAttempt?.verifierSummary) {
19
+ parts.push(`Verifier focus: ${normalizeVerifierSignature(lastAttempt.verifierSummary)}.`);
20
+ }
21
+ if (input.remainingBudgetUsd <= 2) {
22
+ parts.push("Budget is tight. Prefer one narrow, verifier-led fix over a broad rewrite.");
23
+ }
24
+ return parts.join(" ");
25
+ }
26
+ export function evaluateRetryEconomics(input) {
27
+ if (input.novelRecoveryPath) {
28
+ return { allowRetry: true };
29
+ }
30
+ const lastTwo = input.previousAttempts.slice(-2);
31
+ const repeatedFailureClass = lastTwo.length === 2 &&
32
+ lastTwo[0]?.failureClass &&
33
+ lastTwo[0].failureClass === lastTwo[1]?.failureClass;
34
+ const repeatedVerifierSignature = detectRepeatedVerifierSignature(input.previousAttempts);
35
+ const remainingAfterRetry = roundUsd(input.remainingBudgetUsd - input.projectedUsd);
36
+ const tightBudget = remainingAfterRetry < Math.max(input.projectedUsd * 0.5, 0.25);
37
+ if (repeatedFailureClass && repeatedVerifierSignature && tightBudget && input.remainingIterations <= 2) {
38
+ return {
39
+ allowRetry: false,
40
+ reason: `Repeated verifier signature "${repeatedVerifierSignature}" under tight budget. ` +
41
+ "Escalate instead of paying for another near-identical retry.",
42
+ repeatedVerifierSignature
43
+ };
44
+ }
45
+ return {
46
+ allowRetry: true,
47
+ ...(repeatedVerifierSignature ? { repeatedVerifierSignature } : {})
48
+ };
49
+ }
50
+ export function detectRepeatedVerifierSignature(attempts, window = 2) {
51
+ const signatures = attempts
52
+ .map((attempt) => normalizeVerifierSignature(attempt.verifierSummary))
53
+ .filter((signature) => Boolean(signature));
54
+ if (signatures.length < window) {
55
+ return undefined;
56
+ }
57
+ const recent = signatures.slice(-window);
58
+ const first = recent[0];
59
+ if (!first) {
60
+ return undefined;
61
+ }
62
+ return recent.every((signature) => signature === first) ? first : undefined;
63
+ }
64
+ export function normalizeVerifierSignature(summary) {
65
+ if (!summary) {
66
+ return undefined;
67
+ }
68
+ const normalized = summary
69
+ .toLowerCase()
70
+ .replace(/\s+/g, " ")
71
+ .replace(/\b\d+\b/g, "#")
72
+ .replace(/[^a-z0-9# :._-]/g, "")
73
+ .trim();
74
+ if (!normalized) {
75
+ return undefined;
76
+ }
77
+ const firstClause = normalized.split(/(?:;|\.|,|\n)/u)[0]?.trim();
78
+ return firstClause ? truncateSentence(firstClause, 96) : undefined;
79
+ }
80
+ function truncateSentence(value, maxLength) {
81
+ if (value.length <= maxLength) {
82
+ return value;
83
+ }
84
+ return `${value.slice(0, maxLength - 1).trimEnd()}...`;
85
+ }
86
+ function roundUsd(value) {
87
+ return Math.round(value * 100) / 100;
88
+ }
89
+ //# sourceMappingURL=attempt-brief.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * diff-summary.ts
3
+ *
4
+ * Produces a one-line human-readable summary of a unified diff relative to the
5
+ * loop objective. Uses a cheap haiku-tier LLM call when an adapter is available;
6
+ * falls back to a deterministic heuristic when no adapter is configured or the
7
+ * model call fails.
8
+ *
9
+ * Quality gate: the generated one-liner MUST contain at least one concrete
10
+ * identifier (file name, function name, symbol) extracted from the diff itself.
11
+ * If the model output fails that gate the fallback is used instead.
12
+ */
13
+ export interface DiffSummaryInput {
14
+ /** The raw unified diff text (output of git diff or similar). */
15
+ diff: string;
16
+ /** The original loop objective supplied by the user. */
17
+ objective: string;
18
+ }
19
+ export interface DiffSummary {
20
+ /** Single-line summary suitable for a commit message or PR description. */
21
+ oneLiner: string;
22
+ /** How the summary was produced. */
23
+ method: "model" | "heuristic";
24
+ /** Concrete identifier extracted from the diff that validates the summary. */
25
+ anchorIdentifier: string;
26
+ }
27
+ /**
28
+ * Summarize a unified diff in the context of a loop objective.
29
+ *
30
+ * @param input Diff text + objective string.
31
+ * @param callModelFn Optional async function that accepts a prompt string and
32
+ * returns a model response string. When omitted, or when
33
+ * the call throws, the deterministic heuristic is used.
34
+ */
35
+ export declare function summarizeDiff(input: DiffSummaryInput, callModelFn?: (prompt: string) => Promise<string>): Promise<DiffSummary>;