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,55 @@
1
+ // SLICE-17 — Per-run cost attribution by tag
2
+ // Groups cost ledger entries by arbitrary key-value tags for chargeback reporting.
3
+ /**
4
+ * Groups cost entries by the value of a specific tag key.
5
+ * Entries missing the tag key are grouped under "(untagged)".
6
+ * Results are sorted descending by totalUsd.
7
+ */
8
+ export function aggregateCostsByTag(entries, tagKey) {
9
+ const groups = new Map();
10
+ for (const entry of entries) {
11
+ const tagValue = entry.tags[tagKey] ?? "(untagged)";
12
+ const existing = groups.get(tagValue);
13
+ if (existing) {
14
+ existing.push(entry);
15
+ }
16
+ else {
17
+ groups.set(tagValue, [entry]);
18
+ }
19
+ }
20
+ const summaries = [];
21
+ for (const [value, groupEntries] of groups) {
22
+ const totalUsd = groupEntries.reduce((sum, e) => sum + e.totalUsd, 0);
23
+ summaries.push({
24
+ tag: tagKey,
25
+ value,
26
+ totalUsd,
27
+ runCount: groupEntries.length,
28
+ entries: groupEntries
29
+ });
30
+ }
31
+ // Sort descending by totalUsd
32
+ summaries.sort((a, b) => b.totalUsd - a.totalUsd);
33
+ return summaries;
34
+ }
35
+ /**
36
+ * Formats a list of TaggedCostSummary objects as a plain-text table.
37
+ * Columns: tag value | runs | total USD
38
+ * Sorted descending by totalUsd (aggregateCostsByTag already sorts).
39
+ */
40
+ export function formatCostReport(summaries) {
41
+ if (summaries.length === 0) {
42
+ return "No cost data available.";
43
+ }
44
+ const tagKey = summaries[0].tag;
45
+ const header = `${tagKey.padEnd(30)} | ${"runs".padStart(6)} | ${"total USD".padStart(10)}`;
46
+ const separator = "-".repeat(header.length);
47
+ const rows = summaries.map((s) => {
48
+ const value = s.value.padEnd(30);
49
+ const runs = String(s.runCount).padStart(6);
50
+ const cost = `$${s.totalUsd.toFixed(4)}`.padStart(10);
51
+ return `${value} | ${runs} | ${cost}`;
52
+ });
53
+ return [header, separator, ...rows].join("\n");
54
+ }
55
+ //# sourceMappingURL=tagged-cost.js.map
@@ -0,0 +1,2 @@
1
+ import type { CostGovernorInput, CostGovernorState } from "./types.js";
2
+ export declare function evaluateCostGovernor(input: CostGovernorInput): CostGovernorState;
@@ -0,0 +1,50 @@
1
+ export function evaluateCostGovernor(input) {
2
+ const totalTokens = input.cost.tokensIn + input.cost.tokensOut;
3
+ const projectedUsd = input.cost.actualUsd + (input.projectedUsage?.actualUsd ?? 0);
4
+ const projectedTokens = totalTokens +
5
+ (input.projectedUsage?.tokensIn ?? 0) +
6
+ (input.projectedUsage?.tokensOut ?? 0);
7
+ const remainingBudgetUsd = clamp(input.budget.maxUsd - input.cost.actualUsd);
8
+ const remainingIterations = Math.max(0, input.budget.maxIterations - input.attemptsUsed);
9
+ const remainingTokens = Math.max(0, input.budget.maxTokens - totalTokens);
10
+ const hardLimitReached = input.cost.actualUsd >= input.budget.maxUsd ||
11
+ projectedUsd > input.budget.maxUsd ||
12
+ totalTokens >= input.budget.maxTokens ||
13
+ projectedTokens > input.budget.maxTokens ||
14
+ input.attemptsUsed >= input.budget.maxIterations;
15
+ if (hardLimitReached) {
16
+ return {
17
+ pressure: "hard_limit",
18
+ shouldStop: true,
19
+ remainingBudgetUsd,
20
+ remainingIterations,
21
+ remainingTokens,
22
+ recommendedIntervention: "stop_loop"
23
+ };
24
+ }
25
+ const softLimitReached = input.cost.actualUsd >= input.budget.softLimitUsd ||
26
+ projectedUsd >= input.budget.softLimitUsd ||
27
+ totalTokens >= input.budget.maxTokens * 0.75 ||
28
+ remainingIterations <= 1;
29
+ if (softLimitReached) {
30
+ return {
31
+ pressure: "soft_limit",
32
+ shouldStop: false,
33
+ remainingBudgetUsd,
34
+ remainingIterations,
35
+ remainingTokens,
36
+ recommendedIntervention: "compress_context"
37
+ };
38
+ }
39
+ return {
40
+ pressure: "healthy",
41
+ shouldStop: false,
42
+ remainingBudgetUsd,
43
+ remainingIterations,
44
+ remainingTokens
45
+ };
46
+ }
47
+ function clamp(value) {
48
+ return value < 0 ? 0 : Number(value.toFixed(4));
49
+ }
50
+ //# sourceMappingURL=cost-governor.js.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * cve-check.ts — SLICE-22
3
+ *
4
+ * CVE-watch pre-execution hook.
5
+ *
6
+ * Before a patch is applied, scans the diff for any package.json changes that
7
+ * introduce or upgrade a dependency. Each new/upgraded package is checked against
8
+ * the OSV (Open Source Vulnerabilities) API.
9
+ *
10
+ * Blocking behavior (configurable via martin.policy.yaml):
11
+ * cveCheck:
12
+ * enabled: true # default: true
13
+ * blockSeverity: HIGH # CRITICAL | HIGH | MEDIUM | LOW
14
+ * failClosed: false # true = block on API error (default: false = warn)
15
+ *
16
+ * The result is written to cve-check.json in the attempt artifacts directory.
17
+ */
18
+ export type CveSeverity = "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "UNKNOWN";
19
+ export interface CveCheckPolicy {
20
+ enabled: boolean;
21
+ /** Block when any finding is at or above this severity. */
22
+ blockSeverity: CveSeverity;
23
+ /** When true, an OSV API error blocks the patch. Default false (fail-open). */
24
+ failClosed: boolean;
25
+ }
26
+ export interface PackageRef {
27
+ name: string;
28
+ version: string;
29
+ ecosystem: "npm" | "PyPI" | "crates.io" | "Go";
30
+ }
31
+ export interface CveFinding {
32
+ vulnerabilityId: string;
33
+ severity: CveSeverity;
34
+ packageName: string;
35
+ packageVersion: string;
36
+ summary?: string;
37
+ }
38
+ export interface CveCheckResult {
39
+ blocked: boolean;
40
+ /** Populated when blocked:true due to a CVE finding. */
41
+ reasonCode?: "cve_blocked" | "cve_api_error";
42
+ /** Human-readable explanation. */
43
+ reason?: string;
44
+ findings: CveFinding[];
45
+ /** Populated when the OSV call failed. */
46
+ apiError?: string;
47
+ /** true when cveCheck.enabled:false in policy. */
48
+ skipped?: boolean;
49
+ /** ISO timestamp of when the check ran. */
50
+ checkedAt: string;
51
+ /** Number of packages checked. */
52
+ checkedCount: number;
53
+ }
54
+ export interface OsvQueryResult {
55
+ results: Array<{
56
+ vulns?: Array<{
57
+ id: string;
58
+ summary?: string;
59
+ database_specific?: {
60
+ severity?: string;
61
+ };
62
+ severity?: Array<{
63
+ type: string;
64
+ score: string;
65
+ }>;
66
+ }>;
67
+ }>;
68
+ }
69
+ /**
70
+ * Extracts added/upgraded npm package name+version pairs from a unified diff.
71
+ *
72
+ * Looks for `+ "name": "version"` patterns on lines added within package.json hunks.
73
+ * Supports regular and scoped packages (@scope/name).
74
+ */
75
+ export declare function parseDependencyChanges(diff: string): PackageRef[];
76
+ export declare function checkCves(options: {
77
+ packages: PackageRef[];
78
+ policy: CveCheckPolicy;
79
+ fetchFn?: typeof fetch;
80
+ }): Promise<CveCheckResult>;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * cve-check.ts — SLICE-22
3
+ *
4
+ * CVE-watch pre-execution hook.
5
+ *
6
+ * Before a patch is applied, scans the diff for any package.json changes that
7
+ * introduce or upgrade a dependency. Each new/upgraded package is checked against
8
+ * the OSV (Open Source Vulnerabilities) API.
9
+ *
10
+ * Blocking behavior (configurable via martin.policy.yaml):
11
+ * cveCheck:
12
+ * enabled: true # default: true
13
+ * blockSeverity: HIGH # CRITICAL | HIGH | MEDIUM | LOW
14
+ * failClosed: false # true = block on API error (default: false = warn)
15
+ *
16
+ * The result is written to cve-check.json in the attempt artifacts directory.
17
+ */
18
+ // ---------------------------------------------------------------------------
19
+ // Diff parsing
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Extracts added/upgraded npm package name+version pairs from a unified diff.
23
+ *
24
+ * Looks for `+ "name": "version"` patterns on lines added within package.json hunks.
25
+ * Supports regular and scoped packages (@scope/name).
26
+ */
27
+ export function parseDependencyChanges(diff) {
28
+ const found = [];
29
+ let inPackageJson = false;
30
+ for (const line of diff.split("\n")) {
31
+ // Track when we're inside a package.json diff block
32
+ if (line.startsWith("diff --git")) {
33
+ inPackageJson = /\bpackage\.json\b/.test(line);
34
+ continue;
35
+ }
36
+ if (!inPackageJson)
37
+ continue;
38
+ // Only look at added lines (+ prefix, not +++ header)
39
+ if (!line.startsWith("+") || line.startsWith("+++"))
40
+ continue;
41
+ const content = line.slice(1); // strip leading +
42
+ // Match: "name": "version" or "name": "^version" etc.
43
+ // Supports scoped packages like @scope/name
44
+ const match = content.match(/"(@?[\w/.-]+)":\s*"[~^]?([0-9][^"]*|latest|next)"/u);
45
+ if (match?.[1] && match[2]) {
46
+ const name = match[1];
47
+ const version = match[2].replace(/^[~^]/, "");
48
+ // Skip non-package keys that might look like packages
49
+ const SKIP_KEYS = new Set(["version", "main", "module", "types", "license", "description"]);
50
+ if (!SKIP_KEYS.has(name)) {
51
+ found.push({ name, version, ecosystem: "npm" });
52
+ }
53
+ }
54
+ }
55
+ return found;
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // OSV query
59
+ // ---------------------------------------------------------------------------
60
+ const OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch";
61
+ const SEVERITY_ORDER = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
62
+ function severityFromString(raw) {
63
+ const upper = (raw ?? "").toUpperCase();
64
+ if (upper === "CRITICAL")
65
+ return "CRITICAL";
66
+ if (upper === "HIGH")
67
+ return "HIGH";
68
+ if (upper === "MEDIUM")
69
+ return "MEDIUM";
70
+ if (upper === "LOW")
71
+ return "LOW";
72
+ return "UNKNOWN";
73
+ }
74
+ function isSeverityAtOrAbove(finding, threshold) {
75
+ const fi = SEVERITY_ORDER.indexOf(finding);
76
+ const ti = SEVERITY_ORDER.indexOf(threshold);
77
+ if (fi === -1 || ti === -1)
78
+ return false;
79
+ return fi >= ti;
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // Main entry point
83
+ // ---------------------------------------------------------------------------
84
+ export async function checkCves(options) {
85
+ const checkedAt = new Date().toISOString();
86
+ const { packages, policy, fetchFn = fetch } = options;
87
+ // Fast exit: disabled by policy
88
+ if (!policy.enabled) {
89
+ return { blocked: false, findings: [], skipped: true, checkedAt, checkedCount: 0 };
90
+ }
91
+ // Fast exit: nothing to check
92
+ if (packages.length === 0) {
93
+ return { blocked: false, findings: [], checkedAt, checkedCount: 0 };
94
+ }
95
+ // Build OSV batch request
96
+ const queries = packages.map((pkg) => ({
97
+ package: { name: pkg.name, ecosystem: "npm" },
98
+ version: pkg.version
99
+ }));
100
+ let osvData;
101
+ try {
102
+ const response = await fetchFn(OSV_BATCH_URL, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify({ queries }),
106
+ signal: AbortSignal.timeout(10_000)
107
+ });
108
+ if (!response.ok) {
109
+ throw new Error(`OSV API returned HTTP ${response.status}`);
110
+ }
111
+ osvData = (await response.json());
112
+ }
113
+ catch (err) {
114
+ const apiError = err instanceof Error ? err.message : String(err);
115
+ if (policy.failClosed) {
116
+ return {
117
+ blocked: true,
118
+ reasonCode: "cve_api_error",
119
+ reason: `CVE check failed: OSV API error (failClosed:true). Error: ${apiError}`,
120
+ findings: [],
121
+ apiError,
122
+ checkedAt,
123
+ checkedCount: packages.length
124
+ };
125
+ }
126
+ // Fail-open: warn but do not block
127
+ return {
128
+ blocked: false,
129
+ findings: [],
130
+ apiError,
131
+ reason: `CVE check skipped (OSV API unavailable, failClosed:false): ${apiError}`,
132
+ checkedAt,
133
+ checkedCount: packages.length
134
+ };
135
+ }
136
+ // Parse findings
137
+ const findings = [];
138
+ for (let i = 0; i < (osvData.results ?? []).length; i++) {
139
+ const pkg = packages[i];
140
+ const result = osvData.results[i];
141
+ for (const vuln of result?.vulns ?? []) {
142
+ const severity = severityFromString(vuln.database_specific?.severity);
143
+ findings.push({
144
+ vulnerabilityId: vuln.id,
145
+ severity,
146
+ packageName: pkg?.name ?? "unknown",
147
+ packageVersion: pkg?.version ?? "unknown",
148
+ summary: vuln.summary
149
+ });
150
+ }
151
+ }
152
+ // Apply threshold
153
+ const blockingFindings = findings.filter((f) => isSeverityAtOrAbove(f.severity, policy.blockSeverity));
154
+ if (blockingFindings.length > 0) {
155
+ const names = blockingFindings.map((f) => `${f.packageName}@${f.packageVersion} (${f.severity})`).join(", ");
156
+ return {
157
+ blocked: true,
158
+ reasonCode: "cve_blocked",
159
+ reason: `CVE check blocked patch: ${blockingFindings.length} ${policy.blockSeverity}+ advisory(ies) found: ${names}`,
160
+ findings,
161
+ checkedAt,
162
+ checkedCount: packages.length
163
+ };
164
+ }
165
+ return {
166
+ blocked: false,
167
+ findings,
168
+ checkedAt,
169
+ checkedCount: packages.length
170
+ };
171
+ }
172
+ //# sourceMappingURL=cve-check.js.map
@@ -0,0 +1,27 @@
1
+ import type { ApprovalPolicy } from "../../contracts/index.js";
2
+ export interface DigitalTwinSimulationInput {
3
+ scenarioId: string;
4
+ objective?: string;
5
+ changedFiles: string[];
6
+ verificationPlan: string[];
7
+ requestedSurfaces: string[];
8
+ allowedSurfaces: string[];
9
+ approvalPolicy?: ApprovalPolicy;
10
+ allowedPaths?: string[];
11
+ deniedPaths?: string[];
12
+ allowedNetworkDomains?: string[];
13
+ requiresHumanApproval?: boolean;
14
+ }
15
+ export interface DigitalTwinSimulationReport {
16
+ scenarioId: string;
17
+ riskTier: "low" | "medium" | "high" | "critical";
18
+ blastRadiusScore: number;
19
+ rollbackConfidence: number;
20
+ protectedSurfaceTouched: boolean;
21
+ requiresIndependentVerification: boolean;
22
+ recommendedAction: "proceed" | "require_review" | "block";
23
+ reasons: string[];
24
+ allowedClaimWording: string;
25
+ nonClaims: string[];
26
+ }
27
+ export declare function runDigitalTwinSimulation(input: DigitalTwinSimulationInput): DigitalTwinSimulationReport;
@@ -0,0 +1,90 @@
1
+ import { evaluateAutonomyEnvelopeV2 } from "../autonomy/envelope-v2.js";
2
+ import { analyzeLoopSurface } from "../surface-signals.js";
3
+ export function runDigitalTwinSimulation(input) {
4
+ const envelopeDecision = evaluateAutonomyEnvelopeV2({
5
+ taskId: input.scenarioId,
6
+ taskFamily: "digital-twin",
7
+ requestedSurfaces: input.requestedSurfaces,
8
+ allowedSurfaces: input.allowedSurfaces,
9
+ verificationPlan: input.verificationPlan,
10
+ commands: input.verificationPlan,
11
+ changedFiles: input.changedFiles,
12
+ allowedPaths: input.allowedPaths,
13
+ deniedPaths: input.deniedPaths,
14
+ allowedNetworkDomains: input.allowedNetworkDomains,
15
+ approvalPolicy: input.approvalPolicy,
16
+ requiresHumanApproval: input.requiresHumanApproval
17
+ });
18
+ const surfaceSignals = analyzeLoopSurface({
19
+ objective: input.objective,
20
+ verificationPlan: input.verificationPlan,
21
+ changedFiles: input.changedFiles
22
+ });
23
+ const protectedSurfaceTouched = input.changedFiles.some(isProtectedSurface);
24
+ const blastRadiusScore = clampScore(10 +
25
+ surfaceSignals.workspaceGraphRiskScore * 25 +
26
+ surfaceSignals.crossBoundaryRiskScore * 25 +
27
+ (protectedSurfaceTouched ? 20 : 0) +
28
+ (envelopeDecision.blockedSurfaces.includes("command") ? 20 : 0) +
29
+ (envelopeDecision.blockedSurfaces.includes("dependency") ? 15 : 0) +
30
+ (input.requiresHumanApproval ? 15 : 0));
31
+ const riskTier = classifyRiskTier(blastRadiusScore);
32
+ const rollbackConfidence = clampRatio(0.92 -
33
+ surfaceSignals.workspaceGraphRiskScore * 0.25 -
34
+ surfaceSignals.crossBoundaryRiskScore * 0.2 -
35
+ (protectedSurfaceTouched ? 0.15 : 0) -
36
+ (input.requiresHumanApproval ? 0.12 : 0) -
37
+ (envelopeDecision.blockedSurfaces.includes("command") ? 0.1 : 0));
38
+ const reasons = [...envelopeDecision.reasons];
39
+ if (protectedSurfaceTouched) {
40
+ reasons.push("protected release or deployment surface touched");
41
+ }
42
+ if (surfaceSignals.workspaceGraphRiskScore >= 0.5) {
43
+ reasons.push("workspace graph risk is elevated");
44
+ }
45
+ if (surfaceSignals.crossBoundaryRiskScore >= 0.5) {
46
+ reasons.push("cross-boundary change risk is elevated");
47
+ }
48
+ return {
49
+ scenarioId: input.scenarioId,
50
+ riskTier,
51
+ blastRadiusScore,
52
+ rollbackConfidence,
53
+ protectedSurfaceTouched,
54
+ requiresIndependentVerification: riskTier === "high" || riskTier === "critical" || protectedSurfaceTouched,
55
+ recommendedAction: riskTier === "critical"
56
+ ? "block"
57
+ : riskTier === "high" || envelopeDecision.decision === "escalate"
58
+ ? "require_review"
59
+ : "proceed",
60
+ reasons,
61
+ allowedClaimWording: "High-risk runs are simulated in a pre-merge digital twin with blast-radius scoring, rollback confidence, and explicit review recommendations.",
62
+ nonClaims: ["perfect predictive safety", "simulation replaces human judgment"]
63
+ };
64
+ }
65
+ function classifyRiskTier(score) {
66
+ if (score >= 80)
67
+ return "critical";
68
+ if (score >= 55)
69
+ return "high";
70
+ if (score >= 30)
71
+ return "medium";
72
+ return "low";
73
+ }
74
+ function isProtectedSurface(file) {
75
+ const normalized = file.replace(/\\/gu, "/").toLowerCase();
76
+ return (normalized.startsWith(".github/workflows/") ||
77
+ normalized.startsWith("deploy/") ||
78
+ normalized.startsWith("infra/") ||
79
+ normalized.includes("/migrations/") ||
80
+ normalized.endsWith("/package.json") ||
81
+ normalized === "package.json" ||
82
+ normalized === "pnpm-lock.yaml");
83
+ }
84
+ function clampScore(value) {
85
+ return Math.max(0, Math.min(100, Math.round(value)));
86
+ }
87
+ function clampRatio(value) {
88
+ return Math.max(0, Math.min(1, Math.round(value * 100) / 100));
89
+ }
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Lightweight adjacency-list graph used by scope pruning (SLICE-02).
3
+ * Nodes are file paths (or symbol names). Edges represent import/reference
4
+ * relationships: for each node, the array contains its direct dependents.
5
+ */
6
+ export interface DriftGraph {
7
+ /** Complete set of known node identifiers (file paths or symbol names). */
8
+ nodes: Set<string>;
9
+ /** Adjacency list: node → list of directly connected nodes (1 hop). */
10
+ edges: Record<string, string[]>;
11
+ }
12
+ export interface DriftReport {
13
+ changedExports: string[];
14
+ affectedImporters: Array<{
15
+ file: string;
16
+ importedSymbol: string;
17
+ }>;
18
+ /** Importers with no test coverage for the changed symbol */
19
+ unvalidatedDownstream: string[];
20
+ /** true only if unvalidatedDownstream is empty */
21
+ safe: boolean;
22
+ }
23
+ export interface DriftInput {
24
+ /** Workspace root — ts-morph will glob all .ts files under it */
25
+ rootDir: string;
26
+ /** Path of the changed file, relative to rootDir (forward slashes) */
27
+ changedFile: string;
28
+ /** List of exported symbol names that changed */
29
+ changedExports: string[];
30
+ /** List of test file paths (absolute or relative to rootDir) that cover the changed symbols */
31
+ testFiles: string[];
32
+ }
33
+ /**
34
+ * Builds a DriftReport by walking the workspace with ts-morph.
35
+ *
36
+ * Algorithm:
37
+ * 1. If changedExports is empty → safe: true immediately (no public API touched)
38
+ * 2. Walk all .ts files (excluding node_modules) looking for imports of any changed symbol
39
+ * 3. For each importer, check if a test file also imports the same changed symbol
40
+ * 4. Importers without test coverage → unvalidatedDownstream → safe: false
41
+ */
42
+ export declare function buildDriftReport(input: DriftInput): Promise<DriftReport>;
43
+ /**
44
+ * Builds and writes drift-report.json to the run output directory.
45
+ * Only writes if changedExports is non-empty (no drift = no report needed).
46
+ */
47
+ export declare function emitDriftReport(input: DriftInput, runDir: string): Promise<DriftReport>;
@@ -0,0 +1,100 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { Project } from "ts-morph";
4
+ // ─── Core builder ─────────────────────────────────────────────────────────────
5
+ /**
6
+ * Builds a DriftReport by walking the workspace with ts-morph.
7
+ *
8
+ * Algorithm:
9
+ * 1. If changedExports is empty → safe: true immediately (no public API touched)
10
+ * 2. Walk all .ts files (excluding node_modules) looking for imports of any changed symbol
11
+ * 3. For each importer, check if a test file also imports the same changed symbol
12
+ * 4. Importers without test coverage → unvalidatedDownstream → safe: false
13
+ */
14
+ export async function buildDriftReport(input) {
15
+ if (input.changedExports.length === 0) {
16
+ return {
17
+ changedExports: [],
18
+ affectedImporters: [],
19
+ unvalidatedDownstream: [],
20
+ safe: true
21
+ };
22
+ }
23
+ const project = new Project({
24
+ skipAddingFilesFromTsConfig: true
25
+ });
26
+ // Add all .ts files from rootDir (not node_modules, not .d.ts)
27
+ project.addSourceFilesAtPaths([
28
+ normalizePath(join(input.rootDir, "**/*.ts")),
29
+ `!${normalizePath(join(input.rootDir, "**/node_modules/**"))}`,
30
+ `!${normalizePath(join(input.rootDir, "**/*.d.ts"))}`
31
+ ]);
32
+ const changedFilePath = normalizePath(join(input.rootDir, input.changedFile));
33
+ const affectedImporters = [];
34
+ for (const sourceFile of project.getSourceFiles()) {
35
+ const filePath = normalizePath(sourceFile.getFilePath());
36
+ // Skip the changed file itself
37
+ if (filePath === changedFilePath)
38
+ continue;
39
+ // Check import declarations for any of the changed symbols
40
+ for (const importDecl of sourceFile.getImportDeclarations()) {
41
+ const namedImports = importDecl.getNamedImports();
42
+ for (const namedImport of namedImports) {
43
+ const symbolName = namedImport.getName();
44
+ if (input.changedExports.includes(symbolName)) {
45
+ affectedImporters.push({
46
+ file: filePath,
47
+ importedSymbol: symbolName
48
+ });
49
+ }
50
+ }
51
+ }
52
+ }
53
+ // Determine which importers are test files (by path convention or explicit list)
54
+ const testFilePaths = new Set(input.testFiles.map(f => normalizePath(f.startsWith("/") || /^[A-Za-z]:/.test(f)
55
+ ? f
56
+ : join(input.rootDir, f))));
57
+ // Also treat files matching *.test.ts or *.spec.ts as test files
58
+ const testSymbolsCovered = new Set();
59
+ for (const importer of affectedImporters) {
60
+ const isTestFile = testFilePaths.has(importer.file) ||
61
+ /\.(test|spec)\.[tj]s$/.test(importer.file);
62
+ if (isTestFile) {
63
+ testSymbolsCovered.add(importer.importedSymbol);
64
+ }
65
+ }
66
+ // unvalidatedDownstream = non-test importers whose symbol has no test coverage
67
+ const unvalidatedDownstream = [];
68
+ for (const importer of affectedImporters) {
69
+ const isTestFile = testFilePaths.has(importer.file) ||
70
+ /\.(test|spec)\.[tj]s$/.test(importer.file);
71
+ if (!isTestFile && !testSymbolsCovered.has(importer.importedSymbol)) {
72
+ if (!unvalidatedDownstream.includes(importer.file)) {
73
+ unvalidatedDownstream.push(importer.file);
74
+ }
75
+ }
76
+ }
77
+ return {
78
+ changedExports: input.changedExports,
79
+ affectedImporters,
80
+ unvalidatedDownstream,
81
+ safe: unvalidatedDownstream.length === 0
82
+ };
83
+ }
84
+ // ─── Emitter ──────────────────────────────────────────────────────────────────
85
+ /**
86
+ * Builds and writes drift-report.json to the run output directory.
87
+ * Only writes if changedExports is non-empty (no drift = no report needed).
88
+ */
89
+ export async function emitDriftReport(input, runDir) {
90
+ const report = await buildDriftReport(input);
91
+ if (input.changedExports.length > 0) {
92
+ await writeFile(join(runDir, "drift-report.json"), JSON.stringify(report, null, 2), "utf8");
93
+ }
94
+ return report;
95
+ }
96
+ // ─── Internal helpers ─────────────────────────────────────────────────────────
97
+ function normalizePath(p) {
98
+ return p.replace(/\\/g, "/");
99
+ }
100
+ //# sourceMappingURL=drift-graph.js.map