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.
- package/CODE_OF_CONDUCT.md +32 -0
- package/README.md +172 -227
- package/demo/seeded-workspace/README.md +35 -0
- package/demo/seeded-workspace/TASKS.md +29 -0
- package/demo/seeded-workspace/martin.config.yaml +11 -0
- package/demo/seeded-workspace/package.json +8 -0
- package/demo/seeded-workspace/src/invoice-summary.js +11 -0
- package/demo/seeded-workspace/test/invoice-summary.test.js +20 -0
- package/dist/bin/martin-loop.js +0 -0
- package/dist/vendor/adapters/claude-cli.d.ts +19 -4
- package/dist/vendor/adapters/claude-cli.js +55 -24
- package/dist/vendor/adapters/cli-bridge.d.ts +1 -0
- package/dist/vendor/adapters/cli-bridge.js +154 -28
- 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/index.d.ts +1 -0
- package/dist/vendor/adapters/index.js +1 -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/adapters/verifier-only.d.ts +7 -0
- package/dist/vendor/adapters/verifier-only.js +57 -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/index.d.ts +6 -1
- package/dist/vendor/cli/index.js +124 -7
- 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/index.d.ts +3 -1
- 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/compiler.d.ts +2 -0
- package/dist/vendor/core/compiler.js +10 -4
- 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-integrity.d.ts +26 -0
- package/dist/vendor/core/context-integrity.js +56 -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 +7 -4
- package/dist/vendor/core/index.js +222 -64
- 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/policy.d.ts +6 -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 -0
- package/docs/oss/EXAMPLES.md +9 -1
- package/docs/oss/OSS-BOUNDARY-REPORT.json +109 -113
- package/docs/oss/OSS-BOUNDARY-REPORT.md +48 -48
- package/docs/oss/QUICKSTART.md +39 -4
- package/docs/oss/RALPH-LOOP-SAFETY.md +113 -0
- package/docs/oss/README.md +7 -4
- package/docs/oss/RELEASE-SURFACE-REPORT.json +46 -45
- package/docs/oss/RELEASE-SURFACE-REPORT.md +36 -35
- package/package.json +129 -49
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type LoopBudget } from "../contracts/index.js";
|
|
1
|
+
import { type LoopBudget, type MutationMode } from "../contracts/index.js";
|
|
2
2
|
export type RunCommandRequest = {
|
|
3
3
|
workspaceId: string;
|
|
4
4
|
projectId: string;
|
|
@@ -11,6 +11,7 @@ export type RunCommandRequest = {
|
|
|
11
11
|
cwd?: string;
|
|
12
12
|
model?: string;
|
|
13
13
|
engine?: string;
|
|
14
|
+
mutationMode?: MutationMode;
|
|
14
15
|
allowedPaths?: string[];
|
|
15
16
|
deniedPaths?: string[];
|
|
16
17
|
acceptanceCriteria?: string[];
|
|
@@ -23,6 +24,10 @@ export type ParsedCliArguments = {
|
|
|
23
24
|
} | {
|
|
24
25
|
command: "bench";
|
|
25
26
|
suiteId: string;
|
|
27
|
+
} | {
|
|
28
|
+
command: "demo";
|
|
29
|
+
directory: string;
|
|
30
|
+
force: boolean;
|
|
26
31
|
} | {
|
|
27
32
|
command: "inspect";
|
|
28
33
|
file: string;
|
package/dist/vendor/cli/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, cp, mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createClaudeCliAdapter, createCodexCliAdapter, createStubDirectProviderAdapter, createVerifierOnlyAdapter } from "../adapters/index.js";
|
|
5
6
|
import { runMartin } from "../core/index.js";
|
|
6
7
|
import { buildPortfolioSnapshot, createLoopRecord } from "../contracts/index.js";
|
|
7
8
|
export async function executeCli(args) {
|
|
@@ -30,7 +31,7 @@ export async function executeCli(args) {
|
|
|
30
31
|
}
|
|
31
32
|
};
|
|
32
33
|
const workingDirectory = parsed.request.cwd ?? readOption(args, "--cwd") ?? process.cwd();
|
|
33
|
-
const adapter = selectAdapter(args, workingDirectory, parsed.request.model, parsed.request.engine);
|
|
34
|
+
const adapter = selectAdapter(args, workingDirectory, parsed.request.model, parsed.request.engine, parsed.request.mutationMode);
|
|
34
35
|
let result;
|
|
35
36
|
try {
|
|
36
37
|
result = await runMartin({
|
|
@@ -40,6 +41,7 @@ export async function executeCli(args) {
|
|
|
40
41
|
title: resolvedRequest.title,
|
|
41
42
|
objective: resolvedRequest.objective,
|
|
42
43
|
verificationPlan: resolvedRequest.verificationPlan,
|
|
44
|
+
...(resolvedRequest.mutationMode ? { mutationMode: resolvedRequest.mutationMode } : {}),
|
|
43
45
|
repoRoot: workingDirectory,
|
|
44
46
|
...(resolvedRequest.allowedPaths?.length ? { allowedPaths: resolvedRequest.allowedPaths } : {}),
|
|
45
47
|
...(resolvedRequest.deniedPaths?.length ? { deniedPaths: resolvedRequest.deniedPaths } : {}),
|
|
@@ -58,6 +60,7 @@ export async function executeCli(args) {
|
|
|
58
60
|
title: resolvedRequest.title,
|
|
59
61
|
objective: resolvedRequest.objective,
|
|
60
62
|
verificationPlan: resolvedRequest.verificationPlan,
|
|
63
|
+
...(resolvedRequest.mutationMode ? { mutationMode: resolvedRequest.mutationMode } : {}),
|
|
61
64
|
repoRoot: workingDirectory
|
|
62
65
|
},
|
|
63
66
|
budget: resolvedRequest.budget,
|
|
@@ -115,6 +118,27 @@ export async function executeCli(args) {
|
|
|
115
118
|
stderr: "The benchmark harness remains a workspace-only RC surface and is not part of the publishable @martin/cli boundary yet. Use pnpm --filter @martin/benchmarks test or pnpm --filter @martin/benchmarks eval:phase12 from the repo root instead."
|
|
116
119
|
};
|
|
117
120
|
}
|
|
121
|
+
case "demo": {
|
|
122
|
+
try {
|
|
123
|
+
const targetDirectory = await createDemoWorkspace({
|
|
124
|
+
targetDirectory: parsed.directory,
|
|
125
|
+
force: parsed.force
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
exitCode: 0,
|
|
129
|
+
stdout: renderDemoInstructions(targetDirectory),
|
|
130
|
+
stderr: ""
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
return {
|
|
136
|
+
exitCode: 1,
|
|
137
|
+
stdout: "",
|
|
138
|
+
stderr: `Error: ${message}`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
118
142
|
case "inspect": {
|
|
119
143
|
try {
|
|
120
144
|
const contents = await readFile(parsed.file, "utf8");
|
|
@@ -295,6 +319,9 @@ export function parseCliArguments(args) {
|
|
|
295
319
|
request.cwd = next;
|
|
296
320
|
index += 1;
|
|
297
321
|
break;
|
|
322
|
+
case "--verify-only":
|
|
323
|
+
request.mutationMode = "verify_only";
|
|
324
|
+
break;
|
|
298
325
|
case "--allow-path":
|
|
299
326
|
if (next) {
|
|
300
327
|
request.allowedPaths = [...(request.allowedPaths ?? []), next];
|
|
@@ -339,6 +366,7 @@ export function parseCliArguments(args) {
|
|
|
339
366
|
...(request.cwd ? { cwd: request.cwd } : {}),
|
|
340
367
|
...(request.model ? { model: request.model } : {}),
|
|
341
368
|
...(request.engine ? { engine: request.engine } : {}),
|
|
369
|
+
...(request.mutationMode ? { mutationMode: request.mutationMode } : {}),
|
|
342
370
|
...(request.allowedPaths?.length ? { allowedPaths: request.allowedPaths } : {}),
|
|
343
371
|
...(request.deniedPaths?.length ? { deniedPaths: request.deniedPaths } : {}),
|
|
344
372
|
...(request.acceptanceCriteria?.length ? { acceptanceCriteria: request.acceptanceCriteria } : {})
|
|
@@ -351,6 +379,13 @@ export function parseCliArguments(args) {
|
|
|
351
379
|
suiteId: readOption(rest, "--suite") ?? "ralphy-smoke"
|
|
352
380
|
};
|
|
353
381
|
}
|
|
382
|
+
if (command === "demo") {
|
|
383
|
+
return {
|
|
384
|
+
command: "demo",
|
|
385
|
+
directory: resolve(readOption(rest, "--dir") ?? join(process.cwd(), "martin-loop-demo")),
|
|
386
|
+
force: hasFlag(rest, "--force")
|
|
387
|
+
};
|
|
388
|
+
}
|
|
354
389
|
if (command === "inspect") {
|
|
355
390
|
return {
|
|
356
391
|
command: "inspect",
|
|
@@ -373,12 +408,14 @@ export function renderCliHelp() {
|
|
|
373
408
|
" martin-loop run <objective> [options]",
|
|
374
409
|
" martin run <objective> [options] (alias)",
|
|
375
410
|
" martin-loop run --objective <text> [options]",
|
|
411
|
+
" martin-loop demo [--dir <path>] [--force]",
|
|
376
412
|
" martin-loop inspect --file <path>",
|
|
377
413
|
" martin-loop resume <loopId>",
|
|
378
414
|
" martin-loop bench --suite <suiteId>",
|
|
379
415
|
"",
|
|
380
416
|
"Commands:",
|
|
381
417
|
" run Execute a bounded Martin loop against the current repository.",
|
|
418
|
+
" demo Copy a safe local sandbox so you can try MartinLoop outside your own repo.",
|
|
382
419
|
" inspect Read a persisted loop record and summarize its portfolio metrics.",
|
|
383
420
|
" resume Load a persisted loop record by loop ID from ~/.martin/runs/.",
|
|
384
421
|
" bench Redirect to the workspace-only RC benchmark harness.",
|
|
@@ -390,12 +427,19 @@ export function renderCliHelp() {
|
|
|
390
427
|
" --cwd <path> Set the repo root used for repo-backed runs.",
|
|
391
428
|
" --budget <n> Set the hard cost cap in USD (subprocess killed at limit).",
|
|
392
429
|
" --budget-usd <n> Alias for --budget.",
|
|
393
|
-
" --
|
|
430
|
+
" --soft-limit-usd <n> Soft budget warning threshold in USD.",
|
|
394
431
|
" --max-iterations <n> Set the maximum number of attempts.",
|
|
432
|
+
" --max-tokens <n> Set the maximum total token budget.",
|
|
433
|
+
" --verify <cmd> Shell command to run as the verifier after each attempt.",
|
|
434
|
+
" --verify-only Skip the coding adapter and run the verifier only.",
|
|
395
435
|
" --allow-path <glob> Restrict agent writes to this path pattern (repeatable).",
|
|
396
436
|
" --deny-path <glob> Block agent from this path pattern (repeatable).",
|
|
397
437
|
" --accept <criterion> Add an acceptance criterion to the prompt (repeatable).",
|
|
398
|
-
" --config <path> Path to martin.config.yaml."
|
|
438
|
+
" --config <path> Path to martin.config.yaml.",
|
|
439
|
+
"",
|
|
440
|
+
"Demo options:",
|
|
441
|
+
" --dir <path> Target directory for the copied demo sandbox.",
|
|
442
|
+
" --force Replace an existing non-empty demo target."
|
|
399
443
|
].join("\n");
|
|
400
444
|
}
|
|
401
445
|
function readOption(tokens, flag) {
|
|
@@ -418,6 +462,76 @@ function parseLoopRecords(contents) {
|
|
|
418
462
|
return lines.map((line) => JSON.parse(line));
|
|
419
463
|
}
|
|
420
464
|
}
|
|
465
|
+
async function createDemoWorkspace(input) {
|
|
466
|
+
const rootDir = await findMartinPackageRoot();
|
|
467
|
+
const sourceDirectory = join(rootDir, "demo", "seeded-workspace");
|
|
468
|
+
try {
|
|
469
|
+
await readdir(sourceDirectory);
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
if (isNodeErrorWithCode(error, "ENOENT")) {
|
|
473
|
+
throw new Error(`Demo assets are missing from this install: ${sourceDirectory}`);
|
|
474
|
+
}
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
const targetDirectory = resolve(input.targetDirectory);
|
|
478
|
+
const existingEntries = await readdir(targetDirectory).catch((error) => {
|
|
479
|
+
if (isNodeErrorWithCode(error, "ENOENT")) {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
throw error;
|
|
483
|
+
});
|
|
484
|
+
if (existingEntries) {
|
|
485
|
+
if (existingEntries.length > 0 && !input.force) {
|
|
486
|
+
throw new Error(`Demo target already exists and is not empty: ${targetDirectory}. Re-run with --force to replace it.`);
|
|
487
|
+
}
|
|
488
|
+
await rm(targetDirectory, { force: true, recursive: true });
|
|
489
|
+
}
|
|
490
|
+
await mkdir(dirname(targetDirectory), { recursive: true });
|
|
491
|
+
await cp(sourceDirectory, targetDirectory, { recursive: true });
|
|
492
|
+
return targetDirectory;
|
|
493
|
+
}
|
|
494
|
+
async function findMartinPackageRoot() {
|
|
495
|
+
let currentDirectory = dirname(fileURLToPath(import.meta.url));
|
|
496
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
497
|
+
const manifestPath = join(currentDirectory, "package.json");
|
|
498
|
+
try {
|
|
499
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
500
|
+
if (manifest.name === "martin-loop") {
|
|
501
|
+
return currentDirectory;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
if (!isNodeErrorWithCode(error, "ENOENT")) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const parentDirectory = dirname(currentDirectory);
|
|
510
|
+
if (parentDirectory === currentDirectory) {
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
currentDirectory = parentDirectory;
|
|
514
|
+
}
|
|
515
|
+
throw new Error("Unable to resolve the martin-loop package root for demo assets.");
|
|
516
|
+
}
|
|
517
|
+
function renderDemoInstructions(targetDirectory) {
|
|
518
|
+
return [
|
|
519
|
+
`MartinLoop demo sandbox created at ${targetDirectory}`,
|
|
520
|
+
"",
|
|
521
|
+
"Next steps:",
|
|
522
|
+
` cd ${targetDirectory}`,
|
|
523
|
+
" npm install",
|
|
524
|
+
" npm test",
|
|
525
|
+
"",
|
|
526
|
+
"Safe first run (no provider spend):",
|
|
527
|
+
' MARTIN_LIVE=false npx martin-loop run "Summarize the demo workspace and confirm the verifier is green" --verify "npm test"',
|
|
528
|
+
"",
|
|
529
|
+
"Optional live run:",
|
|
530
|
+
' npx martin-loop run "Add support for a discount percentage to summarizeInvoice and update the tests" --verify "npm test" --engine codex',
|
|
531
|
+
"",
|
|
532
|
+
`Task ideas live in ${join(targetDirectory, "TASKS.md")}`
|
|
533
|
+
].join("\n");
|
|
534
|
+
}
|
|
421
535
|
async function resolveGuardrails(request, rawArgs) {
|
|
422
536
|
const tokens = rawArgs.slice(1);
|
|
423
537
|
const { config, configPath } = await loadGuardrailsConfig(request.configPath);
|
|
@@ -615,7 +729,10 @@ function isNodeErrorWithCode(error, code) {
|
|
|
615
729
|
* --engine codex — real Codex CLI subprocess
|
|
616
730
|
* MARTIN_LIVE=false — stub adapter (for tests / dry-runs)
|
|
617
731
|
*/
|
|
618
|
-
function selectAdapter(rawArgs, workingDirectory, modelOverride, engineOverride) {
|
|
732
|
+
function selectAdapter(rawArgs, workingDirectory, modelOverride, engineOverride, mutationMode) {
|
|
733
|
+
if (mutationMode === "verify_only") {
|
|
734
|
+
return createVerifierOnlyAdapter({ workingDirectory });
|
|
735
|
+
}
|
|
619
736
|
if (process.env.MARTIN_LIVE === "false") {
|
|
620
737
|
return createStubDirectProviderAdapter({
|
|
621
738
|
label: "Stub adapter (MARTIN_LIVE=false)",
|