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
@@ -0,0 +1,199 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { generateAuditPackFromRunDirectory, verifyAuditPack } from "@martin/contracts/audit";
4
+ import { resolveRunsRoot } from "../../core/index.js";
5
+ export async function handleAuditCommand(args, options = {}) {
6
+ const subcommand = args[0];
7
+ try {
8
+ switch (subcommand) {
9
+ case "generate": {
10
+ const runId = parseFlag(args, "--run-id");
11
+ const output = parseFlag(args, "--output") ?? join(options.cwd ?? process.cwd(), "audit-pack");
12
+ if (!runId) {
13
+ return { stdout: "", stderr: "Missing --run-id", exitCode: 1 };
14
+ }
15
+ const result = await generateAuditPackFromRunDirectory({
16
+ runId,
17
+ runDir: join(resolveRunsRoot(process.env), runId),
18
+ outputDir: output,
19
+ generatedBy: "martin"
20
+ });
21
+ return {
22
+ stdout: JSON.stringify({
23
+ command: "audit.generate",
24
+ runId,
25
+ packPath: result.packPath,
26
+ manifest: result.manifest
27
+ }, null, 2),
28
+ stderr: "",
29
+ exitCode: 0
30
+ };
31
+ }
32
+ case "verify": {
33
+ const pack = parseFlag(args, "--pack");
34
+ if (!pack) {
35
+ return { stdout: "", stderr: "Missing --pack", exitCode: 1 };
36
+ }
37
+ const result = await verifyAuditPack(pack);
38
+ return formatVerificationResult(result);
39
+ }
40
+ case "show": {
41
+ const runId = parseFlag(args, "--run-id");
42
+ if (!runId) {
43
+ return { stdout: "", stderr: "Missing --run-id", exitCode: 1 };
44
+ }
45
+ const runsRoot = parseFlag(args, "--runs-root") ?? resolveRunsRoot(process.env);
46
+ const summary = await summarizeRunAudit(join(runsRoot, runId), runId);
47
+ return {
48
+ stdout: JSON.stringify({
49
+ command: "audit.show",
50
+ ...summary
51
+ }, null, 2),
52
+ stderr: "",
53
+ exitCode: 0
54
+ };
55
+ }
56
+ case "log-verify": {
57
+ const { runAuditLogVerify } = await import("./audit-log-verify.js");
58
+ const from = parseFlag(args, "--from");
59
+ const to = parseFlag(args, "--to");
60
+ const runsRoot = parseFlag(args, "--runs-root");
61
+ const result = await runAuditLogVerify({ from, to, runsRoot });
62
+ const output = JSON.stringify({
63
+ command: "audit.log-verify",
64
+ ...result,
65
+ }, null, 2);
66
+ if (result.verdict === "PASS") {
67
+ return { stdout: output, stderr: "", exitCode: 0 };
68
+ }
69
+ return { stdout: "", stderr: output, exitCode: 1 };
70
+ }
71
+ default:
72
+ return {
73
+ stdout: "",
74
+ stderr: [
75
+ "Usage: martin audit <subcommand> [options]",
76
+ "",
77
+ "Subcommands:",
78
+ " generate --run-id <id> --output <dir> Generate audit pack",
79
+ " verify --pack <dir> Verify audit pack manifest",
80
+ " show --run-id <id> [--runs-root <dir>] Summarize persisted run audit evidence",
81
+ " log-verify [--from ISO] [--to ISO] Verify Ed25519 signatures across run log"
82
+ ].join("\n"),
83
+ exitCode: 1
84
+ };
85
+ }
86
+ }
87
+ catch (error) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ return { stdout: "", stderr: message, exitCode: 1 };
90
+ }
91
+ }
92
+ async function summarizeRunAudit(runDir, runId) {
93
+ const [contract, state, loop, ledger, attemptDirs] = await Promise.all([
94
+ readJsonIfExists(join(runDir, "contract.json")),
95
+ readJsonIfExists(join(runDir, "state.json")),
96
+ readJsonIfExists(join(runDir, "loop-record.json")),
97
+ readLedgerIfExists(join(runDir, "ledger.jsonl")),
98
+ readAttemptDirs(join(runDir, "artifacts"))
99
+ ]);
100
+ const eventKinds = ledger.reduce((counts, event) => {
101
+ const kind = typeof event.kind === "string" ? event.kind : "unknown";
102
+ counts[kind] = (counts[kind] ?? 0) + 1;
103
+ return counts;
104
+ }, {});
105
+ const loopRecord = isRecord(loop) ? loop : undefined;
106
+ const contractRecord = isRecord(contract) ? contract : undefined;
107
+ const stateRecord = isRecord(state) ? state : undefined;
108
+ const task = isRecord(loopRecord?.task)
109
+ ? loopRecord.task
110
+ : isRecord(contractRecord?.task)
111
+ ? contractRecord.task
112
+ : undefined;
113
+ return {
114
+ runId,
115
+ runDir,
116
+ status: asString(loopRecord?.status) ?? "unknown",
117
+ lifecycleState: asString(loopRecord?.lifecycleState) ?? asString(stateRecord?.phase) ?? null,
118
+ taskTitle: asString(task?.title) ?? null,
119
+ attempts: Array.isArray(loopRecord?.attempts) ? loopRecord.attempts.length : attemptDirs.length,
120
+ ledgerEvents: ledger.length,
121
+ eventKinds,
122
+ cost: isRecord(loopRecord?.cost) ? loopRecord.cost : null,
123
+ artifacts: {
124
+ hasContract: contract !== null,
125
+ hasState: state !== null,
126
+ hasLoopRecord: loop !== null,
127
+ attemptDirs
128
+ }
129
+ };
130
+ }
131
+ async function readJsonIfExists(path) {
132
+ try {
133
+ return JSON.parse(await readFile(path, "utf8"));
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ async function readLedgerIfExists(path) {
140
+ try {
141
+ const contents = await readFile(path, "utf8");
142
+ return contents
143
+ .trim()
144
+ .split(/\r?\n/u)
145
+ .filter(Boolean)
146
+ .map((line) => JSON.parse(line));
147
+ }
148
+ catch {
149
+ return [];
150
+ }
151
+ }
152
+ async function readAttemptDirs(path) {
153
+ try {
154
+ const entries = await readdir(path, { withFileTypes: true });
155
+ return entries
156
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith("attempt-"))
157
+ .map((entry) => entry.name)
158
+ .sort();
159
+ }
160
+ catch {
161
+ return [];
162
+ }
163
+ }
164
+ function isRecord(value) {
165
+ return typeof value === "object" && value !== null && !Array.isArray(value);
166
+ }
167
+ function asString(value) {
168
+ return typeof value === "string" ? value : undefined;
169
+ }
170
+ function formatVerificationResult(result) {
171
+ if (result.ok) {
172
+ return {
173
+ stdout: JSON.stringify({
174
+ command: "audit.verify",
175
+ status: "passed",
176
+ manifest: result.manifest,
177
+ checkedFiles: result.checkedFiles.length
178
+ }, null, 2),
179
+ stderr: "",
180
+ exitCode: 0
181
+ };
182
+ }
183
+ return {
184
+ stdout: "",
185
+ stderr: JSON.stringify({
186
+ command: "audit.verify",
187
+ status: "failed",
188
+ failures: result.failures
189
+ }, null, 2),
190
+ exitCode: 1
191
+ };
192
+ }
193
+ function parseFlag(args, flag) {
194
+ const idx = args.indexOf(flag);
195
+ if (idx === -1 || idx >= args.length - 1)
196
+ return undefined;
197
+ return args[idx + 1];
198
+ }
199
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1,5 @@
1
+ export declare function handleCorpusCommand(args: string[]): Promise<{
2
+ stdout: string;
3
+ stderr: string;
4
+ exitCode: number;
5
+ }>;
@@ -0,0 +1,60 @@
1
+ import { computeCorpusStats, loadCorpus, resolveEntry, resolveCorpusPath, saveCorpus } from "@martin/trace-intelligence";
2
+ export async function handleCorpusCommand(args) {
3
+ const subcommand = args[0];
4
+ const corpusPath = resolveCorpusPath();
5
+ try {
6
+ const corpus = await loadCorpus(corpusPath);
7
+ switch (subcommand) {
8
+ case "show":
9
+ case undefined: {
10
+ const stats = computeCorpusStats(corpus);
11
+ const lines = [
12
+ `Corpus: ${corpusPath}`,
13
+ `Entries: ${stats.totalEntries} (${stats.resolvedEntries} resolved, ${stats.activeEntries} open)`,
14
+ ""
15
+ ];
16
+ if (corpus.entries.length === 0) {
17
+ lines.push("No patterns in corpus yet. Run martin analyze to populate.");
18
+ }
19
+ else {
20
+ for (const entry of corpus.entries) {
21
+ const resolved = entry.resolved ? "[resolved]" : "[open] ";
22
+ lines.push(`${resolved} ${entry.fingerprint.slice(0, 12)} ${entry.kind} (${entry.severity}) — seen ${entry.occurrenceCount}x, ${entry.totalAffectedRuns} runs`);
23
+ }
24
+ }
25
+ return { exitCode: 0, stdout: lines.join("\n"), stderr: "" };
26
+ }
27
+ case "clear": {
28
+ const cleared = { ...corpus, entries: [], updatedAt: new Date().toISOString() };
29
+ await saveCorpus(cleared, corpusPath);
30
+ return { exitCode: 0, stdout: `Corpus cleared (${corpus.entries.length} entries removed).`, stderr: "" };
31
+ }
32
+ case "resolve": {
33
+ const fingerprint = args[1];
34
+ if (!fingerprint) {
35
+ return { exitCode: 1, stdout: "", stderr: "Usage: martin corpus resolve <fingerprint>" };
36
+ }
37
+ try {
38
+ const updated = resolveEntry(corpus, fingerprint);
39
+ await saveCorpus(updated, corpusPath);
40
+ return { exitCode: 0, stdout: `Entry ${fingerprint} marked as resolved.`, stderr: "" };
41
+ }
42
+ catch (resolveErr) {
43
+ const msg = resolveErr instanceof Error ? resolveErr.message : String(resolveErr);
44
+ return { exitCode: 1, stdout: "", stderr: msg };
45
+ }
46
+ }
47
+ default:
48
+ return {
49
+ exitCode: 1,
50
+ stdout: "",
51
+ stderr: `Unknown corpus subcommand: ${subcommand}\nUsage: martin corpus [show|clear|resolve <fingerprint>]`
52
+ };
53
+ }
54
+ }
55
+ catch (err) {
56
+ const message = err instanceof Error ? err.message : String(err);
57
+ return { exitCode: 1, stdout: "", stderr: `Error: ${message}` };
58
+ }
59
+ }
60
+ //# sourceMappingURL=corpus.js.map
@@ -0,0 +1,8 @@
1
+ export interface DoctorCommandResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ exitCode: number;
5
+ }
6
+ export declare function handleDoctorCommand(args: string[], options?: {
7
+ cwd?: string;
8
+ }): Promise<DoctorCommandResult>;
@@ -0,0 +1,219 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { basename, join } from "node:path";
4
+ const RUNS_ROOT = join(homedir(), ".martin", "runs");
5
+ const CONFIG_SEARCH_PATHS = [
6
+ join(process.cwd(), "martin.config.yaml"),
7
+ join(homedir(), ".martin", "martin.config.yaml")
8
+ ];
9
+ export async function handleDoctorCommand(args, options = {}) {
10
+ const checkFilter = parseFlag(args, "--check");
11
+ const allMode = args.includes("--all");
12
+ const checks = await runChecks(checkFilter, options.cwd ?? process.cwd());
13
+ const failed = checks.filter((c) => c.status === "fail");
14
+ const passed = checks.filter((c) => c.status === "pass");
15
+ if (args.includes("--json")) {
16
+ return {
17
+ exitCode: failed.length > 0 ? 1 : 0,
18
+ stdout: JSON.stringify({ checks, passed: passed.length, failed: failed.length }, null, 2),
19
+ stderr: ""
20
+ };
21
+ }
22
+ const lines = [
23
+ "martin doctor",
24
+ "─".repeat(54)
25
+ ];
26
+ for (const check of checks) {
27
+ const icon = check.status === "pass" ? "✓" : check.status === "skip" ? "─" : "✗";
28
+ const label = check.name.padEnd(34);
29
+ const detail = check.detail;
30
+ lines.push(`${icon} ${label} ${detail}`);
31
+ if (check.status === "fail" && check.hint) {
32
+ lines.push(` ${" ".repeat(34)} Hint: ${check.hint}`);
33
+ }
34
+ }
35
+ lines.push("─".repeat(54));
36
+ if (failed.length === 0) {
37
+ lines.push(`All ${passed.length} checks passed. Martin Loop is ready.`);
38
+ }
39
+ else {
40
+ const plural = failed.length === 1 ? "check" : "checks";
41
+ lines.push(`${failed.length} ${plural} failed. Fix the above and re-run martin doctor.`);
42
+ }
43
+ return {
44
+ exitCode: failed.length > 0 ? 1 : 0,
45
+ stdout: lines.join("\n"),
46
+ stderr: ""
47
+ };
48
+ }
49
+ async function runChecks(filter, cwd) {
50
+ const allChecks = [
51
+ { id: "credentials", fn: checkAdapterCredentials },
52
+ { id: "budget", fn: () => checkBudgetConfig(cwd) },
53
+ { id: "policy", fn: () => checkPolicyFile(cwd) },
54
+ { id: "leash", fn: checkLeashConfig },
55
+ { id: "store", fn: checkRunStore },
56
+ ];
57
+ const selected = filter
58
+ ? allChecks.filter((c) => c.id === filter)
59
+ : allChecks;
60
+ const results = [];
61
+ for (const check of selected) {
62
+ try {
63
+ results.push(await check.fn());
64
+ }
65
+ catch (err) {
66
+ results.push({
67
+ name: check.id,
68
+ status: "fail",
69
+ detail: "FAIL — unexpected error",
70
+ hint: err instanceof Error ? err.message : String(err)
71
+ });
72
+ }
73
+ }
74
+ return results;
75
+ }
76
+ async function checkAdapterCredentials() {
77
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
78
+ const openaiKey = process.env.OPENAI_API_KEY;
79
+ if (anthropicKey && anthropicKey.length > 10) {
80
+ return {
81
+ name: "Adapter credentials",
82
+ status: "pass",
83
+ detail: "OK (ANTHROPIC_API_KEY set)"
84
+ };
85
+ }
86
+ if (openaiKey && openaiKey.length > 10) {
87
+ return {
88
+ name: "Adapter credentials",
89
+ status: "pass",
90
+ detail: "OK (OPENAI_API_KEY set)"
91
+ };
92
+ }
93
+ return {
94
+ name: "Adapter credentials",
95
+ status: "fail",
96
+ detail: "FAIL — no API key found",
97
+ hint: "Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your environment"
98
+ };
99
+ }
100
+ async function checkBudgetConfig(cwd) {
101
+ // Check for config file with a budget set
102
+ for (const configPath of [join(cwd, "martin.config.yaml"), ...CONFIG_SEARCH_PATHS]) {
103
+ try {
104
+ const contents = await readFile(configPath, "utf8");
105
+ const maxUsdMatch = contents.match(/maxUsd:\s*([0-9.]+)/);
106
+ const maxUsdValue = maxUsdMatch?.[1];
107
+ if (maxUsdValue) {
108
+ const val = parseFloat(maxUsdValue);
109
+ if (val > 0) {
110
+ return {
111
+ name: "Budget configured",
112
+ status: "pass",
113
+ detail: `OK ($${val.toFixed(2)} max per run in ${basename(configPath)})`
114
+ };
115
+ }
116
+ }
117
+ }
118
+ catch { /* not found — continue */ }
119
+ }
120
+ return {
121
+ name: "Budget configured",
122
+ status: "pass",
123
+ detail: "OK (using default $10.00 — set maxUsd in martin.config.yaml to override)"
124
+ };
125
+ }
126
+ async function checkPolicyFile(cwd) {
127
+ const policyPaths = [
128
+ join(cwd, "martin.policy.yaml"),
129
+ join(homedir(), ".martin", "martin.policy.yaml")
130
+ ];
131
+ for (const policyPath of policyPaths) {
132
+ try {
133
+ const contents = await readFile(policyPath, "utf8");
134
+ // Basic YAML validation: check for obvious typos in known fields
135
+ const knownFields = ["budgetUsd", "allowedVerifiers", "blockedCommands", "fileScopeGlob", "maxAttempts", "requireApprovalAboveUsd", "siem", "cveCheck", "circuitBreaker", "output"];
136
+ const lines = contents.split("\n");
137
+ for (const line of lines) {
138
+ const topLevelMatch = line.match(/^([A-Za-z][\w-]*):/);
139
+ const key = topLevelMatch?.[1];
140
+ if (key) {
141
+ if (!knownFields.includes(key)) {
142
+ return {
143
+ name: "Policy file",
144
+ status: "fail",
145
+ detail: `FAIL — ${basename(policyPath)}: unknown field '${key}'`,
146
+ hint: `Known fields: ${knownFields.join(", ")}`
147
+ };
148
+ }
149
+ }
150
+ }
151
+ return {
152
+ name: "Policy file",
153
+ status: "pass",
154
+ detail: `OK (${basename(policyPath)} valid)`
155
+ };
156
+ }
157
+ catch { /* not found — continue */ }
158
+ }
159
+ return {
160
+ name: "Policy file",
161
+ status: "pass",
162
+ detail: "OK (no policy file — using built-in defaults)"
163
+ };
164
+ }
165
+ async function checkLeashConfig() {
166
+ // Leash is part of core — if the relevant exports are importable, the guard is active.
167
+ try {
168
+ const core = await import("../../core/index.js");
169
+ if (typeof core.evaluateVerificationLeash === "function") {
170
+ return {
171
+ name: "Leash config",
172
+ status: "pass",
173
+ detail: "OK (verification leash loaded)"
174
+ };
175
+ }
176
+ return {
177
+ name: "Leash config",
178
+ status: "fail",
179
+ detail: "FAIL — leash export missing from @martin/core",
180
+ hint: "Run: pnpm --filter @martin/core build"
181
+ };
182
+ }
183
+ catch (err) {
184
+ return {
185
+ name: "Leash config",
186
+ status: "fail",
187
+ detail: "FAIL — @martin/core failed to import",
188
+ hint: "Run: pnpm build in the repo root"
189
+ };
190
+ }
191
+ }
192
+ async function checkRunStore() {
193
+ try {
194
+ await mkdir(RUNS_ROOT, { recursive: true });
195
+ // Test write access by creating and removing a temp file
196
+ const testPath = join(RUNS_ROOT, `.doctor-check-${Date.now()}`);
197
+ await writeFile(testPath, "ok", "utf8");
198
+ const { unlink } = await import("node:fs/promises");
199
+ await unlink(testPath);
200
+ return {
201
+ name: "Run store writable",
202
+ status: "pass",
203
+ detail: `OK (${RUNS_ROOT.replace(homedir(), "~")})`
204
+ };
205
+ }
206
+ catch (err) {
207
+ return {
208
+ name: "Run store writable",
209
+ status: "fail",
210
+ detail: `FAIL — cannot write to ${RUNS_ROOT}`,
211
+ hint: `Check permissions on ${RUNS_ROOT} or set MARTIN_RUNS_ROOT env var`
212
+ };
213
+ }
214
+ }
215
+ function parseFlag(args, flag) {
216
+ const index = args.indexOf(flag);
217
+ return index >= 0 ? args[index + 1] : undefined;
218
+ }
219
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * explain.ts — SLICE-25
3
+ *
4
+ * CLI command: martin explain <loop-id>
5
+ *
6
+ * Reads stored run artifacts from ~/.martin/loops/<loop-id>/ and produces
7
+ * plain-English Markdown (or JSON) explaining every gate decision.
8
+ * No model calls — pure artifact reading.
9
+ */
10
+ export interface ExplainCommandResult {
11
+ stdout: string;
12
+ stderr: string;
13
+ exitCode: number;
14
+ }
15
+ export declare function handleExplainCommand(args: string[], options?: {
16
+ cwd?: string;
17
+ }): Promise<ExplainCommandResult>;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * explain.ts — SLICE-25
3
+ *
4
+ * CLI command: martin explain <loop-id>
5
+ *
6
+ * Reads stored run artifacts from ~/.martin/loops/<loop-id>/ and produces
7
+ * plain-English Markdown (or JSON) explaining every gate decision.
8
+ * No model calls — pure artifact reading.
9
+ */
10
+ import { readdir, readFile } from "node:fs/promises";
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { formatRunExplanation, formatRunTimeline, resolveRunsRoot } from "../../core/index.js";
14
+ export async function handleExplainCommand(args, options = {}) {
15
+ // Extract loop-id — first positional argument
16
+ const loopId = args.find(a => !a.startsWith("--")) ?? "";
17
+ if (!loopId) {
18
+ return {
19
+ exitCode: 1,
20
+ stdout: "",
21
+ stderr: "Error: explain requires a loop ID. Usage: martin explain <loop-id> [--attempt N] [--decision <gate>] [--timeline] [--html] [--json] [--debug]"
22
+ };
23
+ }
24
+ const attemptFlag = parseFlag(args, "--attempt");
25
+ const decisionFlag = parseFlag(args, "--decision");
26
+ const timelineMode = args.includes("--timeline");
27
+ const htmlMode = args.includes("--html");
28
+ const jsonMode = args.includes("--json");
29
+ const debugMode = args.includes("--debug");
30
+ const loopDir = await resolveExplainDirectory(loopId);
31
+ // Attempt to load artifacts
32
+ let artifacts;
33
+ try {
34
+ artifacts = await loadArtifacts(loopDir, debugMode);
35
+ }
36
+ catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ const isNotFound = message.includes("ENOENT") || message.includes("no such file");
39
+ if (isNotFound) {
40
+ return {
41
+ exitCode: 1,
42
+ stdout: "",
43
+ stderr: `No artifacts found for loop ${loopId}. Run \`martin doctor\` to check your storage path.`
44
+ };
45
+ }
46
+ return {
47
+ exitCode: 1,
48
+ stdout: "",
49
+ stderr: `Error reading artifacts: ${message}`
50
+ };
51
+ }
52
+ if (timelineMode) {
53
+ try {
54
+ const timelineData = await loadTimelineData(loopDir);
55
+ const result = formatRunTimeline({
56
+ loopId,
57
+ objective: timelineData.objective,
58
+ lifecycleState: timelineData.lifecycleState,
59
+ events: timelineData.events,
60
+ ledgerEvents: timelineData.ledgerEvents
61
+ });
62
+ if (jsonMode) {
63
+ return {
64
+ exitCode: 0,
65
+ stdout: JSON.stringify(result.json, null, 2),
66
+ stderr: ""
67
+ };
68
+ }
69
+ return {
70
+ exitCode: 0,
71
+ stdout: htmlMode ? result.html : result.markdown,
72
+ stderr: ""
73
+ };
74
+ }
75
+ catch (err) {
76
+ const message = err instanceof Error ? err.message : String(err);
77
+ return {
78
+ exitCode: 1,
79
+ stdout: "",
80
+ stderr: `Error building timeline: ${message}`
81
+ };
82
+ }
83
+ }
84
+ if (artifacts.length === 0) {
85
+ return {
86
+ exitCode: 1,
87
+ stdout: "",
88
+ stderr: `No artifacts found for loop ${loopId}. Run \`martin doctor\` to check your storage path.`
89
+ };
90
+ }
91
+ const result = formatRunExplanation(artifacts, {
92
+ loopId,
93
+ attemptFilter: attemptFlag != null ? parseInt(attemptFlag, 10) : undefined,
94
+ decisionFilter: decisionFlag ?? undefined,
95
+ json: jsonMode
96
+ });
97
+ if (jsonMode) {
98
+ return {
99
+ exitCode: 0,
100
+ stdout: JSON.stringify(result.json, null, 2),
101
+ stderr: ""
102
+ };
103
+ }
104
+ return {
105
+ exitCode: 0,
106
+ stdout: result.markdown,
107
+ stderr: ""
108
+ };
109
+ }
110
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
111
+ async function loadArtifacts(loopDir, debug) {
112
+ const entries = await readdir(loopDir);
113
+ const artifactFiles = entries.filter(f => f.endsWith(".json") && (f.startsWith("attempt-") || f.includes("artifact")));
114
+ const artifacts = [];
115
+ for (const file of artifactFiles.sort()) {
116
+ try {
117
+ const raw = await readFile(join(loopDir, file), "utf8");
118
+ const parsed = JSON.parse(raw);
119
+ artifacts.push(parsed);
120
+ }
121
+ catch (err) {
122
+ if (debug) {
123
+ const msg = err instanceof Error ? err.message : String(err);
124
+ process.stderr.write(`[debug] skipped ${file}: ${msg}\n`);
125
+ }
126
+ // Non-fatal — skip malformed artifact files
127
+ }
128
+ }
129
+ // Also accept a single loop-record.json that may contain an attempts array
130
+ if (artifacts.length === 0 && entries.includes("loop-record.json")) {
131
+ const raw = await readFile(join(loopDir, "loop-record.json"), "utf8");
132
+ const record = JSON.parse(raw);
133
+ if (Array.isArray(record.attempts)) {
134
+ return record.attempts;
135
+ }
136
+ }
137
+ return artifacts;
138
+ }
139
+ async function loadTimelineData(loopDir) {
140
+ const loopRecordRaw = await readFile(join(loopDir, "loop-record.json"), "utf8");
141
+ const loopRecord = JSON.parse(loopRecordRaw);
142
+ const ledgerPath = join(loopDir, "ledger.jsonl");
143
+ const ledgerRaw = await readFile(ledgerPath, "utf8");
144
+ const ledgerEvents = ledgerRaw
145
+ .split(/\r?\n/u)
146
+ .filter((line) => line.trim().length > 0)
147
+ .map((line) => JSON.parse(line));
148
+ return {
149
+ objective: loopRecord.task?.objective ?? "unknown",
150
+ ...(loopRecord.lifecycleState ? { lifecycleState: loopRecord.lifecycleState } : {}),
151
+ events: loopRecord.events ?? [],
152
+ ledgerEvents
153
+ };
154
+ }
155
+ async function resolveExplainDirectory(loopId) {
156
+ const roots = [
157
+ resolveRunsRoot(process.env),
158
+ join(homedir(), ".martin", "loops")
159
+ ];
160
+ for (const root of roots) {
161
+ const candidate = join(root, loopId);
162
+ try {
163
+ await readdir(candidate);
164
+ return candidate;
165
+ }
166
+ catch {
167
+ // Try the next legacy root.
168
+ }
169
+ }
170
+ return join(roots[0] ?? join(homedir(), ".martin", "runs"), loopId);
171
+ }
172
+ function parseFlag(args, flag) {
173
+ const idx = args.indexOf(flag);
174
+ return idx >= 0 ? args[idx + 1] : undefined;
175
+ }
176
+ //# sourceMappingURL=explain.js.map