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.
- package/CODE_OF_CONDUCT.md +32 -0
- package/LICENSE +21 -21
- package/README.md +307 -398
- package/demo/seeded-workspace/README.md +35 -35
- package/demo/seeded-workspace/TASKS.md +29 -29
- package/demo/seeded-workspace/martin.config.yaml +11 -11
- package/demo/seeded-workspace/package.json +8 -8
- package/demo/seeded-workspace/src/invoice-summary.js +11 -11
- package/demo/seeded-workspace/test/invoice-summary.test.js +20 -20
- package/dist/bin/martin-loop.js +0 -0
- package/dist/vendor/adapters/counter.d.ts +1 -0
- package/dist/vendor/adapters/counter.js +4 -0
- package/dist/vendor/adapters/git-baseline.d.ts +50 -0
- package/dist/vendor/adapters/git-baseline.js +233 -0
- package/dist/vendor/adapters/openrouter-adapter.d.ts +15 -0
- package/dist/vendor/adapters/openrouter-adapter.js +302 -0
- package/dist/vendor/adapters/usage.d.ts +48 -0
- package/dist/vendor/adapters/usage.js +66 -0
- package/dist/vendor/cli/bin/exit.d.ts +12 -0
- package/dist/vendor/cli/bin/exit.js +28 -0
- package/dist/vendor/cli/commands/analyze.d.ts +5 -0
- package/dist/vendor/cli/commands/analyze.js +58 -0
- package/dist/vendor/cli/commands/audit-log-verify.d.ts +34 -0
- package/dist/vendor/cli/commands/audit-log-verify.js +99 -0
- package/dist/vendor/cli/commands/audit.d.ts +8 -0
- package/dist/vendor/cli/commands/audit.js +199 -0
- package/dist/vendor/cli/commands/corpus.d.ts +5 -0
- package/dist/vendor/cli/commands/corpus.js +60 -0
- package/dist/vendor/cli/commands/doctor.d.ts +8 -0
- package/dist/vendor/cli/commands/doctor.js +219 -0
- package/dist/vendor/cli/commands/explain.d.ts +17 -0
- package/dist/vendor/cli/commands/explain.js +176 -0
- package/dist/vendor/cli/commands/export.d.ts +5 -0
- package/dist/vendor/cli/commands/export.js +60 -0
- package/dist/vendor/cli/commands/governance.d.ts +8 -0
- package/dist/vendor/cli/commands/governance.js +95 -0
- package/dist/vendor/cli/commands/improve.d.ts +18 -0
- package/dist/vendor/cli/commands/improve.js +396 -0
- package/dist/vendor/cli/commands/init.d.ts +8 -0
- package/dist/vendor/cli/commands/init.js +281 -0
- package/dist/vendor/cli/commands/migration.d.ts +8 -0
- package/dist/vendor/cli/commands/migration.js +67 -0
- package/dist/vendor/cli/commands/prior.d.ts +23 -0
- package/dist/vendor/cli/commands/prior.js +145 -0
- package/dist/vendor/cli/commands/resume.d.ts +21 -0
- package/dist/vendor/cli/commands/resume.js +73 -0
- package/dist/vendor/cli/commands/verify.d.ts +6 -0
- package/dist/vendor/cli/commands/verify.js +43 -0
- package/dist/vendor/cli/research/public-corpus.d.ts +43 -0
- package/dist/vendor/cli/research/public-corpus.js +151 -0
- package/dist/vendor/cli/ui/error-card.d.ts +38 -0
- package/dist/vendor/cli/ui/error-card.js +103 -0
- package/dist/vendor/cli/ui/mission-brief.d.ts +41 -0
- package/dist/vendor/cli/ui/mission-brief.js +173 -0
- package/dist/vendor/cli/ui/summary-card.d.ts +34 -0
- package/dist/vendor/cli/ui/summary-card.js +102 -0
- package/dist/vendor/contracts/audit.d.ts +46 -0
- package/dist/vendor/contracts/audit.js +360 -0
- package/dist/vendor/contracts/post-phase15.d.ts +240 -0
- package/dist/vendor/contracts/post-phase15.js +166 -0
- package/dist/vendor/core/agent/mandates.d.ts +46 -0
- package/dist/vendor/core/agent/mandates.js +178 -0
- package/dist/vendor/core/agent/receipts.d.ts +38 -0
- package/dist/vendor/core/agent/receipts.js +131 -0
- package/dist/vendor/core/agent/signing.d.ts +17 -0
- package/dist/vendor/core/agent/signing.js +91 -0
- package/dist/vendor/core/attestation/sign.d.ts +25 -0
- package/dist/vendor/core/attestation/sign.js +216 -0
- package/dist/vendor/core/autonomy/autonomous-promotion.d.ts +120 -0
- package/dist/vendor/core/autonomy/autonomous-promotion.js +346 -0
- package/dist/vendor/core/autonomy/envelope-v2.d.ts +29 -0
- package/dist/vendor/core/autonomy/envelope-v2.js +60 -0
- package/dist/vendor/core/autonomy/envelope.d.ts +17 -0
- package/dist/vendor/core/autonomy/envelope.js +27 -0
- package/dist/vendor/core/autonomy/escalation-ledger.d.ts +20 -0
- package/dist/vendor/core/autonomy/escalation-ledger.js +18 -0
- package/dist/vendor/core/autonomy/resume.d.ts +15 -0
- package/dist/vendor/core/autonomy/resume.js +23 -0
- package/dist/vendor/core/circuit/circuit-breaker.d.ts +60 -0
- package/dist/vendor/core/circuit/circuit-breaker.js +143 -0
- package/dist/vendor/core/context-distillation.d.ts +3 -0
- package/dist/vendor/core/context-distillation.js +44 -0
- package/dist/vendor/core/context-flow/compile-context.d.ts +8 -0
- package/dist/vendor/core/context-flow/compile-context.js +111 -0
- package/dist/vendor/core/context-flow/entities.d.ts +2 -0
- package/dist/vendor/core/context-flow/entities.js +44 -0
- package/dist/vendor/core/context-flow/evaluate-policy.d.ts +2 -0
- package/dist/vendor/core/context-flow/evaluate-policy.js +42 -0
- package/dist/vendor/core/context-flow/index.d.ts +11 -0
- package/dist/vendor/core/context-flow/index.js +24 -0
- package/dist/vendor/core/context-flow/labels.d.ts +3 -0
- package/dist/vendor/core/context-flow/labels.js +17 -0
- package/dist/vendor/core/context-flow/normalizer.d.ts +9 -0
- package/dist/vendor/core/context-flow/normalizer.js +69 -0
- package/dist/vendor/core/context-flow/profiles.d.ts +33 -0
- package/dist/vendor/core/context-flow/profiles.js +36 -0
- package/dist/vendor/core/context-flow/redaction.d.ts +1 -0
- package/dist/vendor/core/context-flow/redaction.js +6 -0
- package/dist/vendor/core/context-flow/sensitivity.d.ts +2 -0
- package/dist/vendor/core/context-flow/sensitivity.js +27 -0
- package/dist/vendor/core/context-flow/sync-preview.d.ts +2 -0
- package/dist/vendor/core/context-flow/sync-preview.js +22 -0
- package/dist/vendor/core/context-flow/token-estimator.d.ts +3 -0
- package/dist/vendor/core/context-flow/token-estimator.js +13 -0
- package/dist/vendor/core/context-flow/types.d.ts +91 -0
- package/dist/vendor/core/context-flow/types.js +2 -0
- package/dist/vendor/core/context-utility.d.ts +47 -0
- package/dist/vendor/core/context-utility.js +405 -0
- package/dist/vendor/core/cost/pipeline.d.ts +92 -0
- package/dist/vendor/core/cost/pipeline.js +141 -0
- package/dist/vendor/core/cost/tagged-cost.d.ts +27 -0
- package/dist/vendor/core/cost/tagged-cost.js +55 -0
- package/dist/vendor/core/cost-governor.d.ts +2 -0
- package/dist/vendor/core/cost-governor.js +50 -0
- package/dist/vendor/core/cve/cve-check.d.ts +80 -0
- package/dist/vendor/core/cve/cve-check.js +172 -0
- package/dist/vendor/core/digital-twin/index.d.ts +27 -0
- package/dist/vendor/core/digital-twin/index.js +90 -0
- package/dist/vendor/core/drift/drift-graph.d.ts +47 -0
- package/dist/vendor/core/drift/drift-graph.js +100 -0
- package/dist/vendor/core/drift/objective-lock.d.ts +69 -0
- package/dist/vendor/core/drift/objective-lock.js +88 -0
- package/dist/vendor/core/drift/scope.d.ts +46 -0
- package/dist/vendor/core/drift/scope.js +102 -0
- package/dist/vendor/core/drift/signature-lock.d.ts +48 -0
- package/dist/vendor/core/drift/signature-lock.js +202 -0
- package/dist/vendor/core/drift/stale-proof-gate.d.ts +21 -0
- package/dist/vendor/core/drift/stale-proof-gate.js +19 -0
- package/dist/vendor/core/eval/known-bad-world-runner.d.ts +24 -0
- package/dist/vendor/core/eval/known-bad-world-runner.js +256 -0
- package/dist/vendor/core/evidence/claim-audit.d.ts +18 -0
- package/dist/vendor/core/evidence/claim-audit.js +89 -0
- package/dist/vendor/core/exit-intelligence.d.ts +2 -0
- package/dist/vendor/core/exit-intelligence.js +58 -0
- package/dist/vendor/core/explain/formatter.d.ts +42 -0
- package/dist/vendor/core/explain/formatter.js +171 -0
- package/dist/vendor/core/explain/timeline.d.ts +29 -0
- package/dist/vendor/core/explain/timeline.js +213 -0
- package/dist/vendor/core/failure-taxonomy.d.ts +2 -0
- package/dist/vendor/core/failure-taxonomy.js +76 -0
- package/dist/vendor/core/gateway/index.d.ts +10 -0
- package/dist/vendor/core/gateway/index.js +12 -0
- package/dist/vendor/core/gateway/registry.d.ts +40 -0
- package/dist/vendor/core/gateway/registry.js +97 -0
- package/dist/vendor/core/gateway/transport.d.ts +31 -0
- package/dist/vendor/core/gateway/transport.js +82 -0
- package/dist/vendor/core/gateway/vault.d.ts +19 -0
- package/dist/vendor/core/gateway/vault.js +29 -0
- package/dist/vendor/core/graph/adapters.d.ts +43 -0
- package/dist/vendor/core/graph/adapters.js +91 -0
- package/dist/vendor/core/graph/hotspots.d.ts +22 -0
- package/dist/vendor/core/graph/hotspots.js +30 -0
- package/dist/vendor/core/graph/index.d.ts +1 -0
- package/dist/vendor/core/graph/index.js +2 -0
- package/dist/vendor/core/honey/honey-tokens.d.ts +32 -0
- package/dist/vendor/core/honey/honey-tokens.js +44 -0
- package/dist/vendor/core/index.d.ts +2 -2
- package/dist/vendor/core/index.js +38 -12
- package/dist/vendor/core/learning/bayesian-update.d.ts +31 -0
- package/dist/vendor/core/learning/bayesian-update.js +60 -0
- package/dist/vendor/core/learning/prior-sets.d.ts +42 -0
- package/dist/vendor/core/learning/prior-sets.js +111 -0
- package/dist/vendor/core/learning/promotion-gate.d.ts +17 -0
- package/dist/vendor/core/learning/promotion-gate.js +23 -0
- package/dist/vendor/core/leash/blast-radius.d.ts +42 -0
- package/dist/vendor/core/leash/blast-radius.js +156 -0
- package/dist/vendor/core/leash/policy-leash.d.ts +31 -0
- package/dist/vendor/core/leash/policy-leash.js +117 -0
- package/dist/vendor/core/memo/memo.d.ts +63 -0
- package/dist/vendor/core/memo/memo.js +97 -0
- package/dist/vendor/core/memory/learning-pipeline.d.ts +154 -0
- package/dist/vendor/core/memory/learning-pipeline.js +391 -0
- package/dist/vendor/core/memory/palace.d.ts +84 -0
- package/dist/vendor/core/memory/palace.js +379 -0
- package/dist/vendor/core/merge/ast-merge.d.ts +22 -0
- package/dist/vendor/core/merge/ast-merge.js +350 -0
- package/dist/vendor/core/merge/text-merge.d.ts +12 -0
- package/dist/vendor/core/merge/text-merge.js +182 -0
- package/dist/vendor/core/otel/tracer.d.ts +45 -0
- package/dist/vendor/core/otel/tracer.js +116 -0
- package/dist/vendor/core/parallel/parallel-attempts.d.ts +28 -0
- package/dist/vendor/core/parallel/parallel-attempts.js +41 -0
- package/dist/vendor/core/parallel/scorer.d.ts +24 -0
- package/dist/vendor/core/parallel/scorer.js +65 -0
- package/dist/vendor/core/pattern-detection.d.ts +64 -0
- package/dist/vendor/core/pattern-detection.js +108 -0
- package/dist/vendor/core/persistence/checkpoint.d.ts +44 -0
- package/dist/vendor/core/persistence/checkpoint.js +156 -0
- package/dist/vendor/core/persistence/cleanup.d.ts +22 -0
- package/dist/vendor/core/persistence/cleanup.js +131 -0
- package/dist/vendor/core/persistence/index.d.ts +2 -0
- package/dist/vendor/core/persistence/index.js +1 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
- package/dist/vendor/core/persistence/runs-reader.js +84 -0
- package/dist/vendor/core/persistence/store.d.ts +6 -1
- package/dist/vendor/core/persistence/store.js +5 -0
- package/dist/vendor/core/policy/file-touch-quota.d.ts +60 -0
- package/dist/vendor/core/policy/file-touch-quota.js +105 -0
- package/dist/vendor/core/policy/policy-loader.d.ts +30 -0
- package/dist/vendor/core/policy/policy-loader.js +170 -0
- package/dist/vendor/core/policy/policy-schema.d.ts +55 -0
- package/dist/vendor/core/policy/policy-schema.js +78 -0
- package/dist/vendor/core/probe/probe.d.ts +49 -0
- package/dist/vendor/core/probe/probe.js +115 -0
- package/dist/vendor/core/proof/patch-proof.d.ts +58 -0
- package/dist/vendor/core/proof/patch-proof.js +84 -0
- package/dist/vendor/core/proof/semantic-probe.d.ts +25 -0
- package/dist/vendor/core/proof/semantic-probe.js +82 -0
- package/dist/vendor/core/recovery/failure-mode-runner.d.ts +29 -0
- package/dist/vendor/core/recovery/failure-mode-runner.js +39 -0
- package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
- package/dist/vendor/core/red-blue/red-phase.js +141 -0
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
- package/dist/vendor/core/red-blue/risk-tiers.js +33 -0
- package/dist/vendor/core/replay/replay.d.ts +85 -0
- package/dist/vendor/core/replay/replay.js +109 -0
- package/dist/vendor/core/router/engine.d.ts +54 -0
- package/dist/vendor/core/router/engine.js +131 -0
- package/dist/vendor/core/router/index.d.ts +1 -0
- package/dist/vendor/core/router/index.js +2 -0
- package/dist/vendor/core/router/trust-calibration.d.ts +57 -0
- package/dist/vendor/core/router/trust-calibration.js +127 -0
- package/dist/vendor/core/run-martin.d.ts +2 -0
- package/dist/vendor/core/run-martin.js +287 -0
- package/dist/vendor/core/security/cve-scanner.d.ts +62 -0
- package/dist/vendor/core/security/cve-scanner.js +178 -0
- package/dist/vendor/core/sentinel/efficiency-sentinel.d.ts +29 -0
- package/dist/vendor/core/sentinel/efficiency-sentinel.js +30 -0
- package/dist/vendor/core/sentinel/progress-guard.d.ts +35 -0
- package/dist/vendor/core/sentinel/progress-guard.js +46 -0
- package/dist/vendor/core/siem/siem-emitter.d.ts +49 -0
- package/dist/vendor/core/siem/siem-emitter.js +157 -0
- package/dist/vendor/core/strategy/attempt-brief.d.ts +22 -0
- package/dist/vendor/core/strategy/attempt-brief.js +89 -0
- package/dist/vendor/core/summarize/diff-summary.d.ts +35 -0
- package/dist/vendor/core/summarize/diff-summary.js +204 -0
- package/dist/vendor/core/surface-signals.d.ts +21 -0
- package/dist/vendor/core/surface-signals.js +139 -0
- package/dist/vendor/core/truth/truth-wall.d.ts +51 -0
- package/dist/vendor/core/truth/truth-wall.js +69 -0
- package/dist/vendor/core/truth-spine.d.ts +26 -0
- package/dist/vendor/core/truth-spine.js +62 -0
- package/dist/vendor/core/types.d.ts +115 -0
- package/dist/vendor/core/types.js +2 -0
- package/dist/vendor/core/verification/tiered-verify.d.ts +17 -0
- package/dist/vendor/core/verification/tiered-verify.js +29 -0
- package/dist/vendor/core/verifier-pyramid.d.ts +32 -0
- package/dist/vendor/core/verifier-pyramid.js +111 -0
- package/dist/vendor/core/workflow-artifacts.d.ts +99 -0
- package/dist/vendor/core/workflow-artifacts.js +668 -0
- package/dist/vendor/core/wrap/supervised-run.d.ts +96 -0
- package/dist/vendor/core/wrap/supervised-run.js +178 -0
- package/docs/assets/cli-animated.svg +139 -0
- package/docs/assets/cli-static.svg +34 -0
- package/docs/assets/github-hero-v2.svg +23 -0
- package/docs/assets/martin-raplph.png.jpg +0 -0
- package/docs/assets/martinloop-logo.png +0 -0
- package/docs/assets/nvidia-inception-program-light.png +0 -0
- package/docs/assets/nvidia-inception-program.png +0 -0
- package/docs/assets/phase3c-sidesidebyside-demo.html +228 -0
- package/docs/assets/side-by-side.svg +134 -0
- package/docs/oss/CLAUDE-CODE-WALKTHROUGH.md +142 -142
- package/docs/oss/EXAMPLES.md +134 -134
- package/docs/oss/OSS-BOUNDARY-REPORT.json +1 -1
- package/docs/oss/OSS-BOUNDARY-REPORT.md +1 -1
- package/docs/oss/QUICKSTART.md +170 -165
- package/docs/oss/RALPH-LOOP-SAFETY.md +113 -113
- package/docs/oss/README.md +96 -96
- package/docs/oss/RELEASE-SURFACE-REPORT.json +2 -1
- package/docs/oss/RELEASE-SURFACE-REPORT.md +2 -1
- package/package.json +130 -58
- package/docs/distribution/DIRECTORY-SUBMISSIONS.md +0 -89
- package/docs/distribution/INTEGRATION-OUTREACH.md +0 -61
- package/docs/distribution/UNDER-3-CHALLENGE.md +0 -65
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import * as readline from "node:readline/promises";
|
|
5
|
+
const POLICY_EXAMPLE = `# martin.policy.yaml — runtime governance rules for Martin Loop
|
|
6
|
+
# This file is safe to commit. It contains no secrets.
|
|
7
|
+
# Secrets (API keys) always go in environment variables.
|
|
8
|
+
|
|
9
|
+
budgetUsd:
|
|
10
|
+
perRun: 5.00 # hard cap per individual run
|
|
11
|
+
perDay: 50.00 # daily cap across all runs (informational)
|
|
12
|
+
|
|
13
|
+
allowedVerifiers:
|
|
14
|
+
- "pnpm test"
|
|
15
|
+
- "bun run test"
|
|
16
|
+
- "npm test"
|
|
17
|
+
- "cargo test"
|
|
18
|
+
- "pytest"
|
|
19
|
+
|
|
20
|
+
# Commands appended to the built-in leash block list
|
|
21
|
+
blockedCommands:
|
|
22
|
+
# - "npm publish"
|
|
23
|
+
# - "git push --force"
|
|
24
|
+
|
|
25
|
+
# Restrict which files martin may touch (glob, optional)
|
|
26
|
+
# fileScopeGlob: "src/**"
|
|
27
|
+
|
|
28
|
+
# Hard cap on attempts per run
|
|
29
|
+
maxAttempts: 5
|
|
30
|
+
|
|
31
|
+
# Pause and require human approval before spending above this
|
|
32
|
+
requireApprovalAboveUsd: 2.00
|
|
33
|
+
|
|
34
|
+
# SIEM integration (optional — requires SLICE-16)
|
|
35
|
+
# siem:
|
|
36
|
+
# endpoint: "https://your-siem.example.com/events"
|
|
37
|
+
# format: "ocsf" # or "cef"
|
|
38
|
+
# apiKey: "$SIEM_API_KEY" # env var reference
|
|
39
|
+
`;
|
|
40
|
+
const MARTIN_CONFIG_TEMPLATE = (adapter, model) => `# martin.config.yaml
|
|
41
|
+
# Core runtime configuration for Martin Loop.
|
|
42
|
+
# Do not commit this file if it contains any secrets.
|
|
43
|
+
|
|
44
|
+
policyProfile: balanced
|
|
45
|
+
|
|
46
|
+
budget:
|
|
47
|
+
maxUsd: 5.00
|
|
48
|
+
softLimitUsd: 3.75
|
|
49
|
+
maxIterations: 5
|
|
50
|
+
maxTokens: 50000
|
|
51
|
+
|
|
52
|
+
governance:
|
|
53
|
+
destructiveActionPolicy: approval
|
|
54
|
+
telemetryDestination: local-only
|
|
55
|
+
verifierRules:
|
|
56
|
+
- "pnpm test"
|
|
57
|
+
`;
|
|
58
|
+
const GH_ACTIONS_TEMPLATE = `# .github/workflows/martin-loop.yml
|
|
59
|
+
# Martin Loop governed AI coding runtime — GitHub Actions integration
|
|
60
|
+
# Docs: https://github.com/your-org/martin-loop
|
|
61
|
+
|
|
62
|
+
name: Martin Loop
|
|
63
|
+
|
|
64
|
+
on:
|
|
65
|
+
workflow_dispatch:
|
|
66
|
+
inputs:
|
|
67
|
+
objective:
|
|
68
|
+
description: "What should Martin Loop fix or implement?"
|
|
69
|
+
required: true
|
|
70
|
+
budget_usd:
|
|
71
|
+
description: "Hard budget cap for this run (USD)"
|
|
72
|
+
required: false
|
|
73
|
+
default: "5.00"
|
|
74
|
+
issue_comment:
|
|
75
|
+
types: [created]
|
|
76
|
+
|
|
77
|
+
jobs:
|
|
78
|
+
martin:
|
|
79
|
+
# Trigger on workflow_dispatch OR on /martin comment in a PR
|
|
80
|
+
if: |
|
|
81
|
+
github.event_name == 'workflow_dispatch' ||
|
|
82
|
+
(github.event_name == 'issue_comment' &&
|
|
83
|
+
github.event.issue.pull_request != null &&
|
|
84
|
+
startsWith(github.event.comment.body, '/martin'))
|
|
85
|
+
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
|
|
88
|
+
permissions:
|
|
89
|
+
contents: write
|
|
90
|
+
pull-requests: write
|
|
91
|
+
|
|
92
|
+
steps:
|
|
93
|
+
- name: Check out repository
|
|
94
|
+
uses: actions/checkout@v4
|
|
95
|
+
|
|
96
|
+
- name: Set up pnpm
|
|
97
|
+
uses: pnpm/action-setup@v4
|
|
98
|
+
with:
|
|
99
|
+
version: 10.17.1
|
|
100
|
+
|
|
101
|
+
- name: Set up Node.js
|
|
102
|
+
uses: actions/setup-node@v4
|
|
103
|
+
with:
|
|
104
|
+
node-version: 22
|
|
105
|
+
cache: pnpm
|
|
106
|
+
|
|
107
|
+
- name: Install martin-loop
|
|
108
|
+
run: pnpm add -g @martin/cli
|
|
109
|
+
|
|
110
|
+
# Pre-flight check — fails the job if environment is misconfigured
|
|
111
|
+
- name: Pre-flight check
|
|
112
|
+
run: martin-loop doctor
|
|
113
|
+
env:
|
|
114
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
115
|
+
|
|
116
|
+
- name: Run Martin Loop
|
|
117
|
+
id: martin
|
|
118
|
+
run: |
|
|
119
|
+
# Resolve objective from dispatch input or /martin comment
|
|
120
|
+
if [ "\${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
121
|
+
OBJECTIVE="\${{ github.event.inputs.objective }}"
|
|
122
|
+
BUDGET="\${{ github.event.inputs.budget_usd }}"
|
|
123
|
+
else
|
|
124
|
+
OBJECTIVE="\$(echo '\${{ github.event.comment.body }}' | sed 's|^/martin[[:space:]]*||')"
|
|
125
|
+
BUDGET="5.00"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
martin-loop run \\
|
|
129
|
+
--objective "\$OBJECTIVE" \\
|
|
130
|
+
--budget-usd "\$BUDGET" \\
|
|
131
|
+
--workspace "\${{ github.repository_owner }}" \\
|
|
132
|
+
--project "\${{ github.repository }}" \\
|
|
133
|
+
--json > martin-result.json
|
|
134
|
+
env:
|
|
135
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
136
|
+
MARTIN_NO_SUMMARY: "1"
|
|
137
|
+
|
|
138
|
+
- name: Upload audit artifacts
|
|
139
|
+
if: always()
|
|
140
|
+
uses: actions/upload-artifact@v4
|
|
141
|
+
with:
|
|
142
|
+
name: martin-loop-audit-\${{ github.run_id }}
|
|
143
|
+
path: ~/.martin/runs/
|
|
144
|
+
retention-days: 30
|
|
145
|
+
|
|
146
|
+
- name: Commit any changes from Martin Loop
|
|
147
|
+
run: |
|
|
148
|
+
git config user.name "martin-loop[bot]"
|
|
149
|
+
git config user.email "martin-loop[bot]@users.noreply.github.com"
|
|
150
|
+
git add -A
|
|
151
|
+
git diff --staged --quiet || git commit -m "feat(martin-loop): apply governed patch from run \${{ github.run_id }}"
|
|
152
|
+
git push
|
|
153
|
+
|
|
154
|
+
- name: Post PR comment with run summary
|
|
155
|
+
if: github.event_name == 'issue_comment'
|
|
156
|
+
uses: actions/github-script@v7
|
|
157
|
+
with:
|
|
158
|
+
script: |
|
|
159
|
+
const fs = require('fs');
|
|
160
|
+
let body;
|
|
161
|
+
try {
|
|
162
|
+
const result = JSON.parse(fs.readFileSync('martin-result.json', 'utf8'));
|
|
163
|
+
const decision = result.decision ?? {};
|
|
164
|
+
const loop = result.loop ?? {};
|
|
165
|
+
const succeeded = decision.lifecycleState === 'succeeded';
|
|
166
|
+
const outcome = succeeded ? '✅ SUCCEEDED' : '❌ FAILED';
|
|
167
|
+
const cost = loop.costUsd ? \`$\${loop.costUsd.toFixed(2)}\` : 'n/a';
|
|
168
|
+
const loopId = loop.loopId ?? 'unknown';
|
|
169
|
+
body = [
|
|
170
|
+
\`## Martin Loop — \${outcome}\`,
|
|
171
|
+
\`\`,
|
|
172
|
+
\`| Field | Value |\`,
|
|
173
|
+
\`|---|---|\`,
|
|
174
|
+
\`| Outcome | \${decision.lifecycleState ?? 'unknown'} |\`,
|
|
175
|
+
\`| Cost | \${cost} |\`,
|
|
176
|
+
\`| Loop ID | \${loopId} |\`,
|
|
177
|
+
\`\`,
|
|
178
|
+
succeeded
|
|
179
|
+
? \`Patch applied and committed. Audit artifacts uploaded to this run's Actions artifacts.\`
|
|
180
|
+
: \`Run failed. See Actions artifacts for the full audit trail.\\n\\nTip: martin-loop explain \${loopId}\`
|
|
181
|
+
].join('\\n');
|
|
182
|
+
} catch (e) {
|
|
183
|
+
body = '## Martin Loop\\n\\nRun completed — see Actions artifacts for details.';
|
|
184
|
+
}
|
|
185
|
+
github.rest.issues.createComment({
|
|
186
|
+
issue_number: context.issue.number,
|
|
187
|
+
owner: context.repo.owner,
|
|
188
|
+
repo: context.repo.repo,
|
|
189
|
+
body
|
|
190
|
+
});
|
|
191
|
+
`;
|
|
192
|
+
export async function handleInitCommand(args, options = {}) {
|
|
193
|
+
const cwd = options.cwd ?? process.cwd();
|
|
194
|
+
const force = args.includes("--force");
|
|
195
|
+
const nonInteractive = !process.stdin.isTTY || args.includes("--yes") || args.includes("-y");
|
|
196
|
+
const configPath = join(cwd, "martin.config.yaml");
|
|
197
|
+
const policyExamplePath = join(cwd, "martin.policy.yaml.example");
|
|
198
|
+
const ghWorkflowDir = join(cwd, ".github", "workflows");
|
|
199
|
+
const ghWorkflowPath = join(ghWorkflowDir, "martin-loop.yml");
|
|
200
|
+
// Guard against overwriting existing config without --force
|
|
201
|
+
if (!force && existsSync(configPath)) {
|
|
202
|
+
return {
|
|
203
|
+
exitCode: 1,
|
|
204
|
+
stdout: "",
|
|
205
|
+
stderr: [
|
|
206
|
+
`martin.config.yaml already exists in ${cwd}.`,
|
|
207
|
+
`Use --force to overwrite, or delete it manually first.`
|
|
208
|
+
].join("\n")
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// Detect adapter from environment
|
|
212
|
+
const hasAnthropic = !!(process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.length > 10);
|
|
213
|
+
const hasOpenAI = !!(process.env.OPENAI_API_KEY && process.env.OPENAI_API_KEY.length > 10);
|
|
214
|
+
let adapter = hasAnthropic ? "claude" : hasOpenAI ? "codex" : "claude";
|
|
215
|
+
let model = adapter === "claude" ? "claude-sonnet-4-6" : "gpt-4o";
|
|
216
|
+
// Ask for budget in interactive mode
|
|
217
|
+
let budget = "5.00";
|
|
218
|
+
let writeGhWorkflow = true;
|
|
219
|
+
if (!nonInteractive) {
|
|
220
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
221
|
+
try {
|
|
222
|
+
const budgetAnswer = await rl.question(` Budget cap per run (USD) [default: 5.00]: `);
|
|
223
|
+
if (budgetAnswer.trim()) {
|
|
224
|
+
const parsed = parseFloat(budgetAnswer.trim());
|
|
225
|
+
if (!isNaN(parsed) && parsed > 0)
|
|
226
|
+
budget = parsed.toFixed(2);
|
|
227
|
+
}
|
|
228
|
+
const ghAnswer = await rl.question(` Write GitHub Actions workflow? (Y/n) [default: Y]: `);
|
|
229
|
+
writeGhWorkflow = !ghAnswer.trim() || ghAnswer.trim().toLowerCase() !== "n";
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
rl.close();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const written = [];
|
|
236
|
+
// Write martin.config.yaml
|
|
237
|
+
await writeFile(configPath, MARTIN_CONFIG_TEMPLATE(adapter, model).replace("maxUsd: 5.00", `maxUsd: ${budget}`), "utf8");
|
|
238
|
+
written.push(" martin.config.yaml");
|
|
239
|
+
// Write martin.policy.yaml.example
|
|
240
|
+
await writeFile(policyExamplePath, POLICY_EXAMPLE, "utf8");
|
|
241
|
+
written.push(" martin.policy.yaml.example");
|
|
242
|
+
// Write GitHub Actions workflow
|
|
243
|
+
if (writeGhWorkflow) {
|
|
244
|
+
await mkdir(ghWorkflowDir, { recursive: true });
|
|
245
|
+
await writeFile(ghWorkflowPath, GH_ACTIONS_TEMPLATE, "utf8");
|
|
246
|
+
written.push(" .github/workflows/martin-loop.yml");
|
|
247
|
+
}
|
|
248
|
+
// Run doctor check after init
|
|
249
|
+
let doctorSummary = "";
|
|
250
|
+
try {
|
|
251
|
+
const { handleDoctorCommand } = await import("./doctor.js");
|
|
252
|
+
const doctorResult = await handleDoctorCommand([], { cwd });
|
|
253
|
+
doctorSummary = "\n" + doctorResult.stdout;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Non-fatal
|
|
257
|
+
}
|
|
258
|
+
const credWarning = !hasAnthropic && !hasOpenAI
|
|
259
|
+
? `\n ⚠ No API key found. Set ANTHROPIC_API_KEY before running martin-loop.\n`
|
|
260
|
+
: "";
|
|
261
|
+
const output = [
|
|
262
|
+
"Martin Loop initialized.",
|
|
263
|
+
"",
|
|
264
|
+
"Written:",
|
|
265
|
+
...written,
|
|
266
|
+
"",
|
|
267
|
+
"Add to .gitignore (optional — no secrets but env-specific):",
|
|
268
|
+
" martin.config.yaml",
|
|
269
|
+
"",
|
|
270
|
+
credWarning,
|
|
271
|
+
"Next steps:",
|
|
272
|
+
" 1. Set ANTHROPIC_API_KEY in your environment or CI secrets",
|
|
273
|
+
" 2. Run: martin-loop doctor",
|
|
274
|
+
" 3. Run: martin-loop run --objective \"your first task\"",
|
|
275
|
+
"",
|
|
276
|
+
"Docs: https://github.com/your-org/martin-loop",
|
|
277
|
+
doctorSummary
|
|
278
|
+
].filter((line) => line !== undefined).join("\n");
|
|
279
|
+
return { exitCode: 0, stdout: output, stderr: "" };
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { verifyMigrationPlan } from "@martin/sdk";
|
|
4
|
+
export async function handleMigrationCommand(args, options = {}) {
|
|
5
|
+
const subcommand = args[0];
|
|
6
|
+
try {
|
|
7
|
+
if (subcommand !== "verify") {
|
|
8
|
+
return {
|
|
9
|
+
stdout: "",
|
|
10
|
+
stderr: [
|
|
11
|
+
"Usage: martin migration verify --plan <path> [options]",
|
|
12
|
+
"",
|
|
13
|
+
"Options:",
|
|
14
|
+
" --now <timestamp>",
|
|
15
|
+
" --release-windows <start/end,start/end>",
|
|
16
|
+
" --output <path>"
|
|
17
|
+
].join("\n"),
|
|
18
|
+
exitCode: 1
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const planPath = parseFlag(args, "--plan");
|
|
22
|
+
if (!planPath) {
|
|
23
|
+
return { stdout: "", stderr: "Missing --plan", exitCode: 1 };
|
|
24
|
+
}
|
|
25
|
+
const cwd = options.cwd ?? process.cwd();
|
|
26
|
+
const resolvedPlanPath = resolve(cwd, planPath);
|
|
27
|
+
const rawPlan = await readFile(resolvedPlanPath, "utf8");
|
|
28
|
+
const plan = JSON.parse(rawPlan);
|
|
29
|
+
const releaseWindows = parseCsvFlag(args, "--release-windows");
|
|
30
|
+
const now = parseFlag(args, "--now");
|
|
31
|
+
const verificationOptions = {
|
|
32
|
+
...(now ? { now } : {}),
|
|
33
|
+
...(releaseWindows.length > 0 ? { releaseWindows } : {})
|
|
34
|
+
};
|
|
35
|
+
const readiness = verifyMigrationPlan(plan, verificationOptions);
|
|
36
|
+
const outputPath = resolve(cwd, parseFlag(args, "--output") ?? "cutover-readiness.json");
|
|
37
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
38
|
+
await writeFile(outputPath, JSON.stringify(readiness, null, 2), "utf8");
|
|
39
|
+
return {
|
|
40
|
+
stdout: JSON.stringify({
|
|
41
|
+
command: "migration.verify",
|
|
42
|
+
outputPath,
|
|
43
|
+
result: readiness
|
|
44
|
+
}, null, 2),
|
|
45
|
+
stderr: "",
|
|
46
|
+
exitCode: 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
return { stdout: "", stderr: message, exitCode: 1 };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function parseFlag(args, flag) {
|
|
55
|
+
const index = args.indexOf(flag);
|
|
56
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
57
|
+
}
|
|
58
|
+
function parseCsvFlag(args, flag) {
|
|
59
|
+
const value = parseFlag(args, flag);
|
|
60
|
+
return value
|
|
61
|
+
? value
|
|
62
|
+
.split(",")
|
|
63
|
+
.map((entry) => entry.trim())
|
|
64
|
+
.filter((entry) => entry.length > 0)
|
|
65
|
+
: [];
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PriorCommandResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolvePriorStoreDir(cwd?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Handles `martin prior <subcommand> [args]`
|
|
9
|
+
*
|
|
10
|
+
* Subcommands:
|
|
11
|
+
* list — list all PriorSets
|
|
12
|
+
* status --prior-set-id <id> — show current gate for a PriorSet
|
|
13
|
+
* promote --prior-set-id <id> --gate <gate> — advance gate (sequential only)
|
|
14
|
+
* rollback --prior-set-id <id> — revert to previous gate
|
|
15
|
+
*/
|
|
16
|
+
export declare function handlePriorCommand(args: string[], options?: {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}): Promise<PriorCommandResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Seeds a PriorSet into the store if it doesn't already exist.
|
|
21
|
+
* Used by tests and the CLI init flow.
|
|
22
|
+
*/
|
|
23
|
+
export declare function seedPriorSet(priorSetId: string, taskClass: string, priors: Record<string, number>, storeDir: string): Promise<void>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { listPriorSets, getPriorSet, promotePriorSet, rollbackPriorSet, createPriorSet, savePriorStore, loadPriorStore } from "../../core/index.js";
|
|
3
|
+
const VALID_GATES = ["development", "backtest", "shadow", "live"];
|
|
4
|
+
// ─── Store path resolution ────────────────────────────────────────────────────
|
|
5
|
+
export function resolvePriorStoreDir(cwd) {
|
|
6
|
+
return join(cwd ?? process.cwd(), ".martin");
|
|
7
|
+
}
|
|
8
|
+
// ─── Command handler ──────────────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Handles `martin prior <subcommand> [args]`
|
|
11
|
+
*
|
|
12
|
+
* Subcommands:
|
|
13
|
+
* list — list all PriorSets
|
|
14
|
+
* status --prior-set-id <id> — show current gate for a PriorSet
|
|
15
|
+
* promote --prior-set-id <id> --gate <gate> — advance gate (sequential only)
|
|
16
|
+
* rollback --prior-set-id <id> — revert to previous gate
|
|
17
|
+
*/
|
|
18
|
+
export async function handlePriorCommand(args, options = {}) {
|
|
19
|
+
const storeDir = resolvePriorStoreDir(options.cwd);
|
|
20
|
+
const subcommand = args[0];
|
|
21
|
+
try {
|
|
22
|
+
switch (subcommand) {
|
|
23
|
+
case "list": {
|
|
24
|
+
const priorSets = await listPriorSets(storeDir);
|
|
25
|
+
if (priorSets.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
stdout: JSON.stringify({ priorSets: [], message: "No PriorSets found." }, null, 2),
|
|
28
|
+
stderr: "",
|
|
29
|
+
exitCode: 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
stdout: JSON.stringify({ priorSets }, null, 2),
|
|
34
|
+
stderr: "",
|
|
35
|
+
exitCode: 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
case "status": {
|
|
39
|
+
const priorSetId = parseFlag(args, "--prior-set-id");
|
|
40
|
+
if (!priorSetId) {
|
|
41
|
+
return { stdout: "", stderr: "Missing --prior-set-id", exitCode: 1 };
|
|
42
|
+
}
|
|
43
|
+
const ps = await getPriorSet(priorSetId, storeDir);
|
|
44
|
+
if (!ps) {
|
|
45
|
+
return { stdout: "", stderr: `PriorSet not found: ${priorSetId}`, exitCode: 1 };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
stdout: JSON.stringify({
|
|
49
|
+
priorSetId: ps.priorSetId,
|
|
50
|
+
taskClass: ps.taskClass,
|
|
51
|
+
promotionGate: ps.promotionGate,
|
|
52
|
+
version: ps.version,
|
|
53
|
+
sampleCount: ps.sampleCount,
|
|
54
|
+
confidence: ps.confidence
|
|
55
|
+
}, null, 2),
|
|
56
|
+
stderr: "",
|
|
57
|
+
exitCode: 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
case "promote": {
|
|
61
|
+
const priorSetId = parseFlag(args, "--prior-set-id");
|
|
62
|
+
const gate = parseFlag(args, "--gate");
|
|
63
|
+
if (!priorSetId)
|
|
64
|
+
return { stdout: "", stderr: "Missing --prior-set-id", exitCode: 1 };
|
|
65
|
+
if (!gate)
|
|
66
|
+
return { stdout: "", stderr: "Missing --gate", exitCode: 1 };
|
|
67
|
+
if (!VALID_GATES.includes(gate)) {
|
|
68
|
+
return {
|
|
69
|
+
stdout: "",
|
|
70
|
+
stderr: `Invalid gate: ${gate}. Must be one of: ${VALID_GATES.join(", ")}`,
|
|
71
|
+
exitCode: 1
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const updated = await promotePriorSet(priorSetId, gate, storeDir);
|
|
75
|
+
return {
|
|
76
|
+
stdout: JSON.stringify({
|
|
77
|
+
message: `PriorSet ${priorSetId} promoted to ${gate}`,
|
|
78
|
+
priorSetId: updated.priorSetId,
|
|
79
|
+
promotionGate: updated.promotionGate,
|
|
80
|
+
version: updated.version
|
|
81
|
+
}, null, 2),
|
|
82
|
+
stderr: "",
|
|
83
|
+
exitCode: 0
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
case "rollback": {
|
|
87
|
+
const priorSetId = parseFlag(args, "--prior-set-id");
|
|
88
|
+
if (!priorSetId)
|
|
89
|
+
return { stdout: "", stderr: "Missing --prior-set-id", exitCode: 1 };
|
|
90
|
+
const reverted = await rollbackPriorSet(priorSetId, storeDir);
|
|
91
|
+
return {
|
|
92
|
+
stdout: JSON.stringify({
|
|
93
|
+
message: `PriorSet ${priorSetId} rolled back to ${reverted.promotionGate}`,
|
|
94
|
+
priorSetId: reverted.priorSetId,
|
|
95
|
+
promotionGate: reverted.promotionGate,
|
|
96
|
+
version: reverted.version
|
|
97
|
+
}, null, 2),
|
|
98
|
+
stderr: "",
|
|
99
|
+
exitCode: 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
return {
|
|
104
|
+
stdout: "",
|
|
105
|
+
stderr: [
|
|
106
|
+
"Usage: martin prior <subcommand> [options]",
|
|
107
|
+
"",
|
|
108
|
+
"Subcommands:",
|
|
109
|
+
" list List all PriorSets",
|
|
110
|
+
" status --prior-set-id <id> Show current gate",
|
|
111
|
+
" promote --prior-set-id <id> --gate <gate> Promote to next gate",
|
|
112
|
+
" rollback --prior-set-id <id> Revert to previous gate",
|
|
113
|
+
"",
|
|
114
|
+
"Gates (sequential): development → backtest → shadow → live"
|
|
115
|
+
].join("\n"),
|
|
116
|
+
exitCode: 1
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
122
|
+
return { stdout: "", stderr: message, exitCode: 1 };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ─── Seed helper (for testing / initial setup) ────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* Seeds a PriorSet into the store if it doesn't already exist.
|
|
128
|
+
* Used by tests and the CLI init flow.
|
|
129
|
+
*/
|
|
130
|
+
export async function seedPriorSet(priorSetId, taskClass, priors, storeDir) {
|
|
131
|
+
const store = await loadPriorStore(storeDir);
|
|
132
|
+
if (store.priorSets.some(p => p.priorSetId === priorSetId))
|
|
133
|
+
return;
|
|
134
|
+
const ps = createPriorSet({ priorSetId, taskClass, priors });
|
|
135
|
+
store.priorSets.push(ps);
|
|
136
|
+
await savePriorStore(store, storeDir);
|
|
137
|
+
}
|
|
138
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
139
|
+
function parseFlag(args, flag) {
|
|
140
|
+
const idx = args.indexOf(flag);
|
|
141
|
+
if (idx === -1 || idx >= args.length - 1)
|
|
142
|
+
return undefined;
|
|
143
|
+
return args[idx + 1];
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=prior.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resume.ts — SLICE-23: Resumable loops CLI handler
|
|
3
|
+
*
|
|
4
|
+
* Usage: martin resume <loop-id> [--force]
|
|
5
|
+
*
|
|
6
|
+
* 1. Reads checkpoint from ~/.martin/loops/<loop-id>/checkpoint.json
|
|
7
|
+
* 2. Validates workspace hashes
|
|
8
|
+
* 3. If modified and no --force: prints error and exits with code 1
|
|
9
|
+
* 4. If valid (or --force): prints resume state and exits with instructions
|
|
10
|
+
* (actual resume wiring into runMartin is future work — this slice ships
|
|
11
|
+
* the checkpoint infrastructure and the CLI entry point)
|
|
12
|
+
* 5. Prints: "Resuming loop <id> from attempt <N>. Cost so far: $<M>"
|
|
13
|
+
*/
|
|
14
|
+
export interface ResumeCommandResult {
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
exitCode: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function handleResumeCommand(args: string[], _options?: {
|
|
20
|
+
cwd?: string;
|
|
21
|
+
}): Promise<ResumeCommandResult>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resume.ts — SLICE-23: Resumable loops CLI handler
|
|
3
|
+
*
|
|
4
|
+
* Usage: martin resume <loop-id> [--force]
|
|
5
|
+
*
|
|
6
|
+
* 1. Reads checkpoint from ~/.martin/loops/<loop-id>/checkpoint.json
|
|
7
|
+
* 2. Validates workspace hashes
|
|
8
|
+
* 3. If modified and no --force: prints error and exits with code 1
|
|
9
|
+
* 4. If valid (or --force): prints resume state and exits with instructions
|
|
10
|
+
* (actual resume wiring into runMartin is future work — this slice ships
|
|
11
|
+
* the checkpoint infrastructure and the CLI entry point)
|
|
12
|
+
* 5. Prints: "Resuming loop <id> from attempt <N>. Cost so far: $<M>"
|
|
13
|
+
*/
|
|
14
|
+
export async function handleResumeCommand(args, _options = {}) {
|
|
15
|
+
const loopId = readPositional(args);
|
|
16
|
+
const force = args.includes("--force");
|
|
17
|
+
if (!loopId) {
|
|
18
|
+
return {
|
|
19
|
+
exitCode: 1,
|
|
20
|
+
stdout: "",
|
|
21
|
+
stderr: "Error: resume requires a loop ID. Usage: martin resume <loopId> [--force]"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const { getCheckpointStorageDir, readCheckpoint, validateWorkspaceHashes, WorkspaceModifiedError } = await import("../../core/index.js");
|
|
25
|
+
const storageDir = getCheckpointStorageDir();
|
|
26
|
+
const checkpoint = readCheckpoint(loopId, storageDir);
|
|
27
|
+
if (!checkpoint) {
|
|
28
|
+
return {
|
|
29
|
+
exitCode: 1,
|
|
30
|
+
stdout: "",
|
|
31
|
+
stderr: `Error: no checkpoint found for loop ${loopId}. Run has not been interrupted or checkpoint was cleaned up.`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const { valid, modifiedFiles } = validateWorkspaceHashes(checkpoint, force);
|
|
35
|
+
if (!valid) {
|
|
36
|
+
const fileList = modifiedFiles.join(", ");
|
|
37
|
+
return {
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
stdout: "",
|
|
40
|
+
stderr: [
|
|
41
|
+
`Error: workspace has been modified since checkpoint for loop ${loopId}.`,
|
|
42
|
+
`Modified files: ${fileList}`,
|
|
43
|
+
"Use --force to resume anyway."
|
|
44
|
+
].join("\n")
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const resumeFromAttempt = checkpoint.lastCompletedAttempt + 1;
|
|
48
|
+
const costFormatted = checkpoint.costSoFar.toFixed(4);
|
|
49
|
+
const summary = [
|
|
50
|
+
`Resuming loop ${loopId} from attempt ${resumeFromAttempt}. Cost so far: $${costFormatted}`,
|
|
51
|
+
`Phase at checkpoint: ${checkpoint.phase}`,
|
|
52
|
+
`Last attempt rolled back: ${String(checkpoint.lastAttemptRolledBack)}`,
|
|
53
|
+
...(force && modifiedFiles.length > 0
|
|
54
|
+
? [`Warning: resuming with --force despite ${modifiedFiles.length} modified file(s).`]
|
|
55
|
+
: []),
|
|
56
|
+
"",
|
|
57
|
+
"Note: this command prints resume state only. Pass the loop ID to martin run to re-execute."
|
|
58
|
+
].join("\n");
|
|
59
|
+
return {
|
|
60
|
+
exitCode: 0,
|
|
61
|
+
stdout: summary,
|
|
62
|
+
stderr: ""
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function readPositional(args) {
|
|
66
|
+
for (const arg of args) {
|
|
67
|
+
if (!arg.startsWith("--")) {
|
|
68
|
+
return arg;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=resume.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { resolveRunsRoot, verifyLoopRecordAttestation } from "../../core/index.js";
|
|
3
|
+
export async function handleVerifyCommand(args) {
|
|
4
|
+
const loopId = parseFlag(args, "--loop") ?? args[0];
|
|
5
|
+
if (!loopId) {
|
|
6
|
+
return {
|
|
7
|
+
stdout: "",
|
|
8
|
+
stderr: "Missing --loop. Usage: martin verify --loop <id>",
|
|
9
|
+
exitCode: 1
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const runRoot = join(resolveRunsRoot(process.env), loopId);
|
|
13
|
+
const verification = await verifyLoopRecordAttestation(runRoot);
|
|
14
|
+
if (verification.ok) {
|
|
15
|
+
return {
|
|
16
|
+
stdout: JSON.stringify({
|
|
17
|
+
command: "verify",
|
|
18
|
+
status: "passed",
|
|
19
|
+
loopId,
|
|
20
|
+
file: verification.file,
|
|
21
|
+
keyId: verification.keyId
|
|
22
|
+
}, null, 2),
|
|
23
|
+
stderr: "",
|
|
24
|
+
exitCode: 0
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
stdout: "",
|
|
29
|
+
stderr: JSON.stringify({
|
|
30
|
+
command: "verify",
|
|
31
|
+
status: "failed",
|
|
32
|
+
loopId,
|
|
33
|
+
file: verification.file,
|
|
34
|
+
reason: verification.reason
|
|
35
|
+
}, null, 2),
|
|
36
|
+
exitCode: 1
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseFlag(args, flag) {
|
|
40
|
+
const index = args.indexOf(flag);
|
|
41
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=verify.js.map
|