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
|
@@ -13,7 +13,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
13
13
|
// ─── Prompt packet compiler ──────────────────────────────────────────────────
|
|
14
14
|
export { compilePromptPacket } from "./compiler.js";
|
|
15
15
|
// ─── Persistence (RunStore, LedgerEvent, FileRunStore) ──────────────────────
|
|
16
|
-
export { createFileRunStore, makeLedgerEvent, resolveRunsRoot } from "./persistence/index.js";
|
|
16
|
+
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot } from "./persistence/index.js";
|
|
17
17
|
export { compileAndPersistContext } from "./persistence/index.js";
|
|
18
18
|
/**
|
|
19
19
|
* Admission gate — must pass before any attempt is executed.
|
|
@@ -129,6 +129,7 @@ export async function runMartin(input) {
|
|
|
129
129
|
runId: loop.loopId,
|
|
130
130
|
payload: { workspaceId: input.workspaceId, projectId: input.projectId }
|
|
131
131
|
}));
|
|
132
|
+
await persistLoopRecordIfSupported(input.store, loop);
|
|
132
133
|
}
|
|
133
134
|
const DEFAULT_FALLBACK_MODELS = [
|
|
134
135
|
"claude-haiku-4-5",
|
|
@@ -177,8 +178,10 @@ export async function runMartin(input) {
|
|
|
177
178
|
payload: createRunExitPayload(leashExitDecision)
|
|
178
179
|
}));
|
|
179
180
|
}
|
|
181
|
+
const finalizedLoop = finalizeLoop(loop, leashExitDecision, now(), idFactory);
|
|
182
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
180
183
|
return {
|
|
181
|
-
loop:
|
|
184
|
+
loop: finalizedLoop,
|
|
182
185
|
decision: leashExitDecision
|
|
183
186
|
};
|
|
184
187
|
}
|
|
@@ -213,8 +216,10 @@ export async function runMartin(input) {
|
|
|
213
216
|
payload: createRunExitPayload(secretExitDecision)
|
|
214
217
|
}));
|
|
215
218
|
}
|
|
219
|
+
const finalizedLoop = finalizeLoop(loop, secretExitDecision, now(), idFactory);
|
|
220
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
216
221
|
return {
|
|
217
|
-
loop:
|
|
222
|
+
loop: finalizedLoop,
|
|
218
223
|
decision: secretExitDecision
|
|
219
224
|
};
|
|
220
225
|
}
|
|
@@ -255,8 +260,10 @@ export async function runMartin(input) {
|
|
|
255
260
|
payload: createRunExitPayload(preflightExitDecision)
|
|
256
261
|
}));
|
|
257
262
|
}
|
|
263
|
+
const finalizedLoop = finalizeLoop(loop, preflightExitDecision, now(), idFactory);
|
|
264
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
258
265
|
return {
|
|
259
|
-
loop:
|
|
266
|
+
loop: finalizedLoop,
|
|
260
267
|
decision: preflightExitDecision
|
|
261
268
|
};
|
|
262
269
|
}
|
|
@@ -289,8 +296,10 @@ export async function runMartin(input) {
|
|
|
289
296
|
}
|
|
290
297
|
}));
|
|
291
298
|
}
|
|
299
|
+
const finalizedLoop = finalizeLoop(loop, poisoningExitDecision, now(), idFactory);
|
|
300
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
292
301
|
return {
|
|
293
|
-
loop:
|
|
302
|
+
loop: finalizedLoop,
|
|
294
303
|
decision: poisoningExitDecision
|
|
295
304
|
};
|
|
296
305
|
}
|
|
@@ -344,8 +353,10 @@ export async function runMartin(input) {
|
|
|
344
353
|
payload: createRunExitPayload(exitDecision)
|
|
345
354
|
}));
|
|
346
355
|
}
|
|
356
|
+
const finalizedLoop = finalizeLoop(loop, exitDecision, now(), idFactory);
|
|
357
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
347
358
|
return {
|
|
348
|
-
loop:
|
|
359
|
+
loop: finalizedLoop,
|
|
349
360
|
decision: exitDecision
|
|
350
361
|
};
|
|
351
362
|
}
|
|
@@ -641,8 +652,10 @@ export async function runMartin(input) {
|
|
|
641
652
|
payload: createRunExitPayload(verifyOnlyExitDecision)
|
|
642
653
|
}));
|
|
643
654
|
}
|
|
655
|
+
const finalizedLoop = finalizeLoop(loop, verifyOnlyExitDecision, now(), idFactory);
|
|
656
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
644
657
|
return {
|
|
645
|
-
loop:
|
|
658
|
+
loop: finalizedLoop,
|
|
646
659
|
decision: verifyOnlyExitDecision
|
|
647
660
|
};
|
|
648
661
|
}
|
|
@@ -714,8 +727,10 @@ export async function runMartin(input) {
|
|
|
714
727
|
payload: createRunExitPayload(filesystemExitDecision)
|
|
715
728
|
}));
|
|
716
729
|
}
|
|
730
|
+
const finalizedLoop = finalizeLoop(loop, filesystemExitDecision, now(), idFactory);
|
|
731
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
717
732
|
return {
|
|
718
|
-
loop:
|
|
733
|
+
loop: finalizedLoop,
|
|
719
734
|
decision: filesystemExitDecision
|
|
720
735
|
};
|
|
721
736
|
}
|
|
@@ -789,8 +804,10 @@ export async function runMartin(input) {
|
|
|
789
804
|
payload: createRunExitPayload(approvalExitDecision)
|
|
790
805
|
}));
|
|
791
806
|
}
|
|
807
|
+
const finalizedLoop = finalizeLoop(loop, approvalExitDecision, now(), idFactory);
|
|
808
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
792
809
|
return {
|
|
793
|
-
loop:
|
|
810
|
+
loop: finalizedLoop,
|
|
794
811
|
decision: approvalExitDecision
|
|
795
812
|
};
|
|
796
813
|
}
|
|
@@ -926,8 +943,10 @@ export async function runMartin(input) {
|
|
|
926
943
|
payload: createRunExitPayload(patchExitDecision)
|
|
927
944
|
}));
|
|
928
945
|
}
|
|
946
|
+
const finalizedLoop = finalizeLoop(loop, patchExitDecision, now(), idFactory);
|
|
947
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
929
948
|
return {
|
|
930
|
-
loop:
|
|
949
|
+
loop: finalizedLoop,
|
|
931
950
|
decision: patchExitDecision
|
|
932
951
|
};
|
|
933
952
|
}
|
|
@@ -970,8 +989,10 @@ export async function runMartin(input) {
|
|
|
970
989
|
payload: createRunExitPayload(decision)
|
|
971
990
|
}));
|
|
972
991
|
}
|
|
992
|
+
const finalizedLoop = finalizeLoop(loop, decision, now(), idFactory);
|
|
993
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
973
994
|
return {
|
|
974
|
-
loop:
|
|
995
|
+
loop: finalizedLoop,
|
|
975
996
|
decision
|
|
976
997
|
};
|
|
977
998
|
}
|
|
@@ -989,8 +1010,10 @@ export async function runMartin(input) {
|
|
|
989
1010
|
payload: createRunExitPayload(decision)
|
|
990
1011
|
}));
|
|
991
1012
|
}
|
|
1013
|
+
const finalizedLoop = finalizeLoop(loop, decision, now(), idFactory);
|
|
1014
|
+
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
992
1015
|
return {
|
|
993
|
-
loop:
|
|
1016
|
+
loop: finalizedLoop,
|
|
994
1017
|
decision
|
|
995
1018
|
};
|
|
996
1019
|
}
|
|
@@ -1050,6 +1073,9 @@ function finalizeLoop(loop, decision, timestamp, idFactory) {
|
|
|
1050
1073
|
updatedAt: timestamp
|
|
1051
1074
|
};
|
|
1052
1075
|
}
|
|
1076
|
+
async function persistLoopRecordIfSupported(store, loop) {
|
|
1077
|
+
await store?.writeLoopRecord?.(loop.loopId, loop);
|
|
1078
|
+
}
|
|
1053
1079
|
function getAdapterTransport(adapter) {
|
|
1054
1080
|
return adapter.metadata.transport ?? (adapter.kind === "agent-cli" ? "cli" : "http");
|
|
1055
1081
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies a single-step Bayesian update to a prior probability.
|
|
3
|
+
*
|
|
4
|
+
* Formula: posterior = (prior * likelihood) / normalizing_constant
|
|
5
|
+
*
|
|
6
|
+
* The normalizing constant ensures the result stays in [0, 1]:
|
|
7
|
+
* Z = prior * likelihood + (1 - prior) * (1 - likelihood)
|
|
8
|
+
*
|
|
9
|
+
* This is the standard Beta-Bernoulli update for binary outcomes.
|
|
10
|
+
* - likelihood > 0.5 → positive evidence → posterior increases
|
|
11
|
+
* - likelihood < 0.5 → negative evidence → posterior decreases
|
|
12
|
+
* - likelihood = 0.5 → no information → posterior unchanged
|
|
13
|
+
*
|
|
14
|
+
* @param prior Current prior probability (0.0 – 1.0)
|
|
15
|
+
* @param likelihood Likelihood of observation given the hypothesis (0.0 – 1.0)
|
|
16
|
+
* @returns Updated posterior probability (0.0 – 1.0)
|
|
17
|
+
*/
|
|
18
|
+
export declare function updatePriorBayesian(prior: number, likelihood: number): number;
|
|
19
|
+
/**
|
|
20
|
+
* Applies a Bayesian update to a PriorSet's individual symbol priors
|
|
21
|
+
* using confidence deltas from the hypothesis ledger.
|
|
22
|
+
*
|
|
23
|
+
* confidenceDelta > 0 → validated evidence → likelihood = 0.5 + delta/2
|
|
24
|
+
* confidenceDelta < 0 → invalidated evidence → likelihood = 0.5 + delta/2
|
|
25
|
+
*
|
|
26
|
+
* @param priors Symbol → prior probability map
|
|
27
|
+
* @param symbol The specific symbol to update
|
|
28
|
+
* @param confidenceDelta From hypothesis ledger (−1.0 to +1.0)
|
|
29
|
+
* @returns New priors map with updated value for symbol
|
|
30
|
+
*/
|
|
31
|
+
export declare function updateSymbolPrior(priors: Record<string, number>, symbol: string, confidenceDelta: number): Record<string, number>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// ─── Bayesian Update ─────────────────────────────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* Applies a single-step Bayesian update to a prior probability.
|
|
4
|
+
*
|
|
5
|
+
* Formula: posterior = (prior * likelihood) / normalizing_constant
|
|
6
|
+
*
|
|
7
|
+
* The normalizing constant ensures the result stays in [0, 1]:
|
|
8
|
+
* Z = prior * likelihood + (1 - prior) * (1 - likelihood)
|
|
9
|
+
*
|
|
10
|
+
* This is the standard Beta-Bernoulli update for binary outcomes.
|
|
11
|
+
* - likelihood > 0.5 → positive evidence → posterior increases
|
|
12
|
+
* - likelihood < 0.5 → negative evidence → posterior decreases
|
|
13
|
+
* - likelihood = 0.5 → no information → posterior unchanged
|
|
14
|
+
*
|
|
15
|
+
* @param prior Current prior probability (0.0 – 1.0)
|
|
16
|
+
* @param likelihood Likelihood of observation given the hypothesis (0.0 – 1.0)
|
|
17
|
+
* @returns Updated posterior probability (0.0 – 1.0)
|
|
18
|
+
*/
|
|
19
|
+
export function updatePriorBayesian(prior, likelihood) {
|
|
20
|
+
if (prior < 0 || prior > 1)
|
|
21
|
+
throw new Error(`prior must be in [0, 1], got ${prior}`);
|
|
22
|
+
if (likelihood < 0 || likelihood > 1)
|
|
23
|
+
throw new Error(`likelihood must be in [0, 1], got ${likelihood}`);
|
|
24
|
+
// P(H|E) = P(E|H) * P(H) / P(E)
|
|
25
|
+
// P(E) = P(E|H)*P(H) + P(E|¬H)*P(¬H)
|
|
26
|
+
const pEgivenH = likelihood;
|
|
27
|
+
const pEgivenNotH = 1 - likelihood;
|
|
28
|
+
const pH = prior;
|
|
29
|
+
const pNotH = 1 - prior;
|
|
30
|
+
const pE = pEgivenH * pH + pEgivenNotH * pNotH;
|
|
31
|
+
// Avoid division by zero (shouldn't happen with valid inputs)
|
|
32
|
+
if (pE === 0)
|
|
33
|
+
return prior;
|
|
34
|
+
return (pEgivenH * pH) / pE;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Applies a Bayesian update to a PriorSet's individual symbol priors
|
|
38
|
+
* using confidence deltas from the hypothesis ledger.
|
|
39
|
+
*
|
|
40
|
+
* confidenceDelta > 0 → validated evidence → likelihood = 0.5 + delta/2
|
|
41
|
+
* confidenceDelta < 0 → invalidated evidence → likelihood = 0.5 + delta/2
|
|
42
|
+
*
|
|
43
|
+
* @param priors Symbol → prior probability map
|
|
44
|
+
* @param symbol The specific symbol to update
|
|
45
|
+
* @param confidenceDelta From hypothesis ledger (−1.0 to +1.0)
|
|
46
|
+
* @returns New priors map with updated value for symbol
|
|
47
|
+
*/
|
|
48
|
+
export function updateSymbolPrior(priors, symbol, confidenceDelta) {
|
|
49
|
+
const currentPrior = priors[symbol] ?? 0.5;
|
|
50
|
+
// Map confidenceDelta (−1..+1) to likelihood (0..1)
|
|
51
|
+
// delta=+1.0 → likelihood=1.0 (strong positive evidence)
|
|
52
|
+
// delta=0.0 → likelihood=0.5 (no information)
|
|
53
|
+
// delta=−1.0 → likelihood=0.0 (strong negative evidence)
|
|
54
|
+
const likelihood = Math.max(0, Math.min(1, 0.5 + confidenceDelta / 2));
|
|
55
|
+
return {
|
|
56
|
+
...priors,
|
|
57
|
+
[symbol]: updatePriorBayesian(currentPrior, likelihood)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=bayesian-update.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type PromotionGate = "development" | "backtest" | "shadow" | "live";
|
|
2
|
+
export interface PriorSet {
|
|
3
|
+
priorSetId: string;
|
|
4
|
+
taskClass: string;
|
|
5
|
+
version: number;
|
|
6
|
+
confidence: number;
|
|
7
|
+
priors: Record<string, number>;
|
|
8
|
+
lastUpdatedAt: string;
|
|
9
|
+
promotionGate: PromotionGate;
|
|
10
|
+
sampleCount: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PriorStore {
|
|
13
|
+
priorSets: PriorSet[];
|
|
14
|
+
}
|
|
15
|
+
export interface CreatePriorSetInput {
|
|
16
|
+
priorSetId: string;
|
|
17
|
+
taskClass: string;
|
|
18
|
+
priors: Record<string, number>;
|
|
19
|
+
}
|
|
20
|
+
export declare function createPriorSet(input: CreatePriorSetInput): PriorSet;
|
|
21
|
+
export declare function loadPriorStore(storeDir: string): Promise<PriorStore>;
|
|
22
|
+
export declare function savePriorStore(store: PriorStore, storeDir: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Promotes a PriorSet to the next gate in the sequence.
|
|
25
|
+
* Gates must advance sequentially: development → backtest → shadow → live.
|
|
26
|
+
* Throws if the target gate is not the immediate next gate.
|
|
27
|
+
*/
|
|
28
|
+
export declare function promotePriorSet(priorSetId: string, targetGate: PromotionGate, storeDir: string): Promise<PriorSet>;
|
|
29
|
+
/**
|
|
30
|
+
* Rolls back a PriorSet to the previous gate.
|
|
31
|
+
* Rollback is always available from any gate above development.
|
|
32
|
+
* Throws if already at development.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rollbackPriorSet(priorSetId: string, storeDir: string): Promise<PriorSet>;
|
|
35
|
+
/**
|
|
36
|
+
* Returns all PriorSets from the store.
|
|
37
|
+
*/
|
|
38
|
+
export declare function listPriorSets(storeDir: string): Promise<PriorSet[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Returns a single PriorSet by ID, or null if not found.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getPriorSet(priorSetId: string, storeDir: string): Promise<PriorSet | null>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
// ─── Gate ordering ────────────────────────────────────────────────────────────
|
|
5
|
+
const GATE_ORDER = ["development", "backtest", "shadow", "live"];
|
|
6
|
+
function gateIndex(gate) {
|
|
7
|
+
return GATE_ORDER.indexOf(gate);
|
|
8
|
+
}
|
|
9
|
+
function nextGate(current) {
|
|
10
|
+
const idx = gateIndex(current);
|
|
11
|
+
return idx < GATE_ORDER.length - 1 ? GATE_ORDER[idx + 1] ?? null : null;
|
|
12
|
+
}
|
|
13
|
+
function prevGate(current) {
|
|
14
|
+
const idx = gateIndex(current);
|
|
15
|
+
return idx > 0 ? GATE_ORDER[idx - 1] ?? null : null;
|
|
16
|
+
}
|
|
17
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
18
|
+
export function createPriorSet(input) {
|
|
19
|
+
const confidence = Object.keys(input.priors).length > 0
|
|
20
|
+
? Object.values(input.priors).reduce((a, b) => a + b, 0) / Object.keys(input.priors).length
|
|
21
|
+
: 0;
|
|
22
|
+
return {
|
|
23
|
+
priorSetId: input.priorSetId,
|
|
24
|
+
taskClass: input.taskClass,
|
|
25
|
+
version: 1,
|
|
26
|
+
confidence,
|
|
27
|
+
priors: { ...input.priors },
|
|
28
|
+
lastUpdatedAt: new Date().toISOString(),
|
|
29
|
+
promotionGate: "development",
|
|
30
|
+
sampleCount: 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// ─── Persistence ─────────────────────────────────────────────────────────────
|
|
34
|
+
const STORE_FILE = "prior-sets.json";
|
|
35
|
+
export async function loadPriorStore(storeDir) {
|
|
36
|
+
const path = join(storeDir, STORE_FILE);
|
|
37
|
+
if (!existsSync(path))
|
|
38
|
+
return { priorSets: [] };
|
|
39
|
+
const raw = await readFile(path, "utf8");
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
export async function savePriorStore(store, storeDir) {
|
|
43
|
+
await writeFile(join(storeDir, STORE_FILE), JSON.stringify(store, null, 2), "utf8");
|
|
44
|
+
}
|
|
45
|
+
// ─── Operations ───────────────────────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Promotes a PriorSet to the next gate in the sequence.
|
|
48
|
+
* Gates must advance sequentially: development → backtest → shadow → live.
|
|
49
|
+
* Throws if the target gate is not the immediate next gate.
|
|
50
|
+
*/
|
|
51
|
+
export async function promotePriorSet(priorSetId, targetGate, storeDir) {
|
|
52
|
+
const store = await loadPriorStore(storeDir);
|
|
53
|
+
const idx = store.priorSets.findIndex(p => p.priorSetId === priorSetId);
|
|
54
|
+
if (idx === -1)
|
|
55
|
+
throw new Error(`PriorSet not found: ${priorSetId}`);
|
|
56
|
+
const current = store.priorSets[idx];
|
|
57
|
+
const expected = nextGate(current.promotionGate);
|
|
58
|
+
if (expected === null || targetGate !== expected) {
|
|
59
|
+
throw new Error(`Invalid promotion: ${current.promotionGate} → ${targetGate}. ` +
|
|
60
|
+
`Expected next gate: ${expected ?? "none (already at live)"}. Gates must advance sequentially.`);
|
|
61
|
+
}
|
|
62
|
+
const updated = {
|
|
63
|
+
...current,
|
|
64
|
+
promotionGate: targetGate,
|
|
65
|
+
version: current.version + 1,
|
|
66
|
+
lastUpdatedAt: new Date().toISOString()
|
|
67
|
+
};
|
|
68
|
+
store.priorSets[idx] = updated;
|
|
69
|
+
await savePriorStore(store, storeDir);
|
|
70
|
+
return updated;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Rolls back a PriorSet to the previous gate.
|
|
74
|
+
* Rollback is always available from any gate above development.
|
|
75
|
+
* Throws if already at development.
|
|
76
|
+
*/
|
|
77
|
+
export async function rollbackPriorSet(priorSetId, storeDir) {
|
|
78
|
+
const store = await loadPriorStore(storeDir);
|
|
79
|
+
const idx = store.priorSets.findIndex(p => p.priorSetId === priorSetId);
|
|
80
|
+
if (idx === -1)
|
|
81
|
+
throw new Error(`PriorSet not found: ${priorSetId}`);
|
|
82
|
+
const current = store.priorSets[idx];
|
|
83
|
+
const prev = prevGate(current.promotionGate);
|
|
84
|
+
if (prev === null) {
|
|
85
|
+
throw new Error(`Cannot rollback ${priorSetId}: already at development gate.`);
|
|
86
|
+
}
|
|
87
|
+
const updated = {
|
|
88
|
+
...current,
|
|
89
|
+
promotionGate: prev,
|
|
90
|
+
version: current.version + 1,
|
|
91
|
+
lastUpdatedAt: new Date().toISOString()
|
|
92
|
+
};
|
|
93
|
+
store.priorSets[idx] = updated;
|
|
94
|
+
await savePriorStore(store, storeDir);
|
|
95
|
+
return updated;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns all PriorSets from the store.
|
|
99
|
+
*/
|
|
100
|
+
export async function listPriorSets(storeDir) {
|
|
101
|
+
const store = await loadPriorStore(storeDir);
|
|
102
|
+
return store.priorSets;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Returns a single PriorSet by ID, or null if not found.
|
|
106
|
+
*/
|
|
107
|
+
export async function getPriorSet(priorSetId, storeDir) {
|
|
108
|
+
const store = await loadPriorStore(storeDir);
|
|
109
|
+
return store.priorSets.find(p => p.priorSetId === priorSetId) ?? null;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=prior-sets.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface GuardedPromotionInput {
|
|
2
|
+
candidateId: string;
|
|
3
|
+
holdoutPassed: number;
|
|
4
|
+
holdoutTotal: number;
|
|
5
|
+
regressionCount: number;
|
|
6
|
+
rollbackPlan: string;
|
|
7
|
+
humanApproved: boolean;
|
|
8
|
+
permanentChange: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface GuardedPromotionDecision {
|
|
11
|
+
candidateId: string;
|
|
12
|
+
decision: "promote" | "reject";
|
|
13
|
+
reasons: string[];
|
|
14
|
+
allowedClaimWording: string;
|
|
15
|
+
nonClaims: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function evaluateGuardedPromotion(input: GuardedPromotionInput): GuardedPromotionDecision;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function evaluateGuardedPromotion(input) {
|
|
2
|
+
const reasons = [];
|
|
3
|
+
if (input.holdoutTotal <= 0 || input.holdoutPassed !== input.holdoutTotal) {
|
|
4
|
+
reasons.push("holdout suite did not pass completely");
|
|
5
|
+
}
|
|
6
|
+
if (input.regressionCount > 0) {
|
|
7
|
+
reasons.push("regressions detected");
|
|
8
|
+
}
|
|
9
|
+
if (input.rollbackPlan.trim().length === 0) {
|
|
10
|
+
reasons.push("rollback plan missing");
|
|
11
|
+
}
|
|
12
|
+
if (input.permanentChange && !input.humanApproved) {
|
|
13
|
+
reasons.push("human approval required for permanent change");
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
candidateId: input.candidateId,
|
|
17
|
+
decision: reasons.length === 0 ? "promote" : "reject",
|
|
18
|
+
reasons,
|
|
19
|
+
allowedClaimWording: "guarded self-improvement candidate promotion only after holdout, rollback, and human approval gates pass.",
|
|
20
|
+
nonClaims: ["unqualified self-improving", "silent self-modification"]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=promotion-gate.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blast Radius Estimator — pre-execution risk scoring.
|
|
3
|
+
*
|
|
4
|
+
* Before any attempt runs, estimates the potential scope of impact (0–100)
|
|
5
|
+
* based on:
|
|
6
|
+
* - Objective language (refactor/migrate/delete keywords increase radius)
|
|
7
|
+
* - Known hotspot files in the likely path (files with regression history)
|
|
8
|
+
* - Repository size (larger repos have wider potential impact)
|
|
9
|
+
* - Files explicitly in scope (each hotspot-adjacent file adds risk)
|
|
10
|
+
*
|
|
11
|
+
* The router uses this score to gate high-risk tasks onto high-trust models.
|
|
12
|
+
* The CLI displays it in the pre-run Mission Brief.
|
|
13
|
+
*/
|
|
14
|
+
import type { RepoHotspot } from "../graph/hotspots.js";
|
|
15
|
+
export interface BlastRadiusInput {
|
|
16
|
+
/** The task objective text, used for keyword analysis */
|
|
17
|
+
objective: string;
|
|
18
|
+
/** Absolute path to the repository root */
|
|
19
|
+
repoRoot: string;
|
|
20
|
+
/** Files the task has explicitly identified as targets (optional) */
|
|
21
|
+
currentFiles?: string[];
|
|
22
|
+
/** Hotspot data from MartinGraph.assembleHotspotContext() (optional) */
|
|
23
|
+
hotspots?: RepoHotspot[];
|
|
24
|
+
}
|
|
25
|
+
export interface BlastRadiusResult {
|
|
26
|
+
/** Risk score 0–100. >70 triggers high-trust route enforcement. */
|
|
27
|
+
score: number;
|
|
28
|
+
/** Files identified as likely to be touched based on scope + hotspot data */
|
|
29
|
+
filesAtRisk: string[];
|
|
30
|
+
/** Subset of filesAtRisk that have recorded regression history */
|
|
31
|
+
hotspotsInPath: string[];
|
|
32
|
+
/** Categorical risk label derived from score */
|
|
33
|
+
regressionRisk: "low" | "medium" | "high";
|
|
34
|
+
/** Human-readable explanation of what drove the score */
|
|
35
|
+
rationale: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Estimates the blast radius of a planned task before execution.
|
|
39
|
+
* Non-throwing: if directory reads fail, falls back gracefully with
|
|
40
|
+
* a conservative medium score.
|
|
41
|
+
*/
|
|
42
|
+
export declare function estimateBlastRadius(input: BlastRadiusInput): Promise<BlastRadiusResult>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blast Radius Estimator — pre-execution risk scoring.
|
|
3
|
+
*
|
|
4
|
+
* Before any attempt runs, estimates the potential scope of impact (0–100)
|
|
5
|
+
* based on:
|
|
6
|
+
* - Objective language (refactor/migrate/delete keywords increase radius)
|
|
7
|
+
* - Known hotspot files in the likely path (files with regression history)
|
|
8
|
+
* - Repository size (larger repos have wider potential impact)
|
|
9
|
+
* - Files explicitly in scope (each hotspot-adjacent file adds risk)
|
|
10
|
+
*
|
|
11
|
+
* The router uses this score to gate high-risk tasks onto high-trust models.
|
|
12
|
+
* The CLI displays it in the pre-run Mission Brief.
|
|
13
|
+
*/
|
|
14
|
+
import { readdir } from "node:fs/promises";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
/** Keywords that strongly suggest broad file scope changes */
|
|
17
|
+
const HIGH_BLAST_KEYWORDS = [
|
|
18
|
+
"refactor",
|
|
19
|
+
"migrate",
|
|
20
|
+
"restructure",
|
|
21
|
+
"rename",
|
|
22
|
+
"move",
|
|
23
|
+
"delete",
|
|
24
|
+
"remove all",
|
|
25
|
+
"overhaul",
|
|
26
|
+
"rewrite",
|
|
27
|
+
"reorganize",
|
|
28
|
+
"consolidate"
|
|
29
|
+
];
|
|
30
|
+
/** Keywords that suggest narrow, test-scoped changes (reduce risk) */
|
|
31
|
+
const LOW_BLAST_KEYWORDS = [
|
|
32
|
+
"test",
|
|
33
|
+
"spec",
|
|
34
|
+
"fixture",
|
|
35
|
+
"mock",
|
|
36
|
+
"stub",
|
|
37
|
+
"snapshot",
|
|
38
|
+
"add test",
|
|
39
|
+
"write test"
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Estimates the blast radius of a planned task before execution.
|
|
43
|
+
* Non-throwing: if directory reads fail, falls back gracefully with
|
|
44
|
+
* a conservative medium score.
|
|
45
|
+
*/
|
|
46
|
+
export async function estimateBlastRadius(input) {
|
|
47
|
+
const { objective, repoRoot, currentFiles = [], hotspots = [] } = input;
|
|
48
|
+
const objectiveLower = objective.toLowerCase();
|
|
49
|
+
const rationale = [];
|
|
50
|
+
let score = 10; // Every task has nonzero blast radius
|
|
51
|
+
// ── Keyword analysis ──────────────────────────────────────────────────────
|
|
52
|
+
const highKeywordsFound = HIGH_BLAST_KEYWORDS.filter((k) => objectiveLower.includes(k));
|
|
53
|
+
if (highKeywordsFound.length > 0) {
|
|
54
|
+
const delta = Math.min(25, highKeywordsFound.length * 12);
|
|
55
|
+
score += delta;
|
|
56
|
+
rationale.push(`Objective contains high-scope keywords: ${highKeywordsFound.join(", ")} (+${String(delta)})`);
|
|
57
|
+
}
|
|
58
|
+
const lowKeywordsFound = LOW_BLAST_KEYWORDS.filter((k) => objectiveLower.includes(k));
|
|
59
|
+
if (lowKeywordsFound.length > 0 && highKeywordsFound.length === 0) {
|
|
60
|
+
score -= 10;
|
|
61
|
+
rationale.push(`Objective is test-scoped (${lowKeywordsFound[0]}), reducing radius (-10)`);
|
|
62
|
+
}
|
|
63
|
+
// ── Repository size ───────────────────────────────────────────────────────
|
|
64
|
+
const repoFileCount = await countFiles(repoRoot);
|
|
65
|
+
if (repoFileCount > 500) {
|
|
66
|
+
score += 20;
|
|
67
|
+
rationale.push(`Large repository (${String(repoFileCount)} files, +20)`);
|
|
68
|
+
}
|
|
69
|
+
else if (repoFileCount > 100) {
|
|
70
|
+
score += 8;
|
|
71
|
+
rationale.push(`Medium repository (${String(repoFileCount)} files, +8)`);
|
|
72
|
+
}
|
|
73
|
+
// ── Hotspot overlap ───────────────────────────────────────────────────────
|
|
74
|
+
const filesAtRisk = [...currentFiles];
|
|
75
|
+
const hotspotsInPath = [];
|
|
76
|
+
// Add hotspot files that are likely in scope (high regression count)
|
|
77
|
+
const criticalHotspots = hotspots.filter((h) => h.regressionCount > 2);
|
|
78
|
+
for (const hotspot of criticalHotspots) {
|
|
79
|
+
const delta = 15;
|
|
80
|
+
score += delta;
|
|
81
|
+
hotspotsInPath.push(hotspot.file);
|
|
82
|
+
if (!filesAtRisk.includes(hotspot.file))
|
|
83
|
+
filesAtRisk.push(hotspot.file);
|
|
84
|
+
rationale.push(`Hotspot in path: ${hotspot.file} (${String(hotspot.regressionCount)} regressions, +${String(delta)})`);
|
|
85
|
+
}
|
|
86
|
+
// Files with any regression history add smaller penalty
|
|
87
|
+
const mildHotspots = hotspots.filter((h) => h.regressionCount > 0 && h.regressionCount <= 2);
|
|
88
|
+
for (const hotspot of mildHotspots) {
|
|
89
|
+
score += 5;
|
|
90
|
+
if (!filesAtRisk.includes(hotspot.file))
|
|
91
|
+
filesAtRisk.push(hotspot.file);
|
|
92
|
+
}
|
|
93
|
+
if (mildHotspots.length > 0) {
|
|
94
|
+
rationale.push(`${String(mildHotspots.length)} files with minor regression history (+${String(mildHotspots.length * 5)})`);
|
|
95
|
+
}
|
|
96
|
+
// ── Explicit files in scope ────────────────────────────────────────────────
|
|
97
|
+
if (currentFiles.length > 0) {
|
|
98
|
+
const adjacentHotspots = currentFiles.filter((f) => hotspots.some((h) => h.file === f && h.regressionCount > 0));
|
|
99
|
+
if (adjacentHotspots.length > 0) {
|
|
100
|
+
score += adjacentHotspots.length * 10;
|
|
101
|
+
rationale.push(`${String(adjacentHotspots.length)} explicitly targeted files have regression history (+${String(adjacentHotspots.length * 10)})`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ── Clamp and label ───────────────────────────────────────────────────────
|
|
105
|
+
const clampedScore = Math.max(0, Math.min(100, score));
|
|
106
|
+
const regressionRisk = clampedScore >= 70 ? "high" : clampedScore >= 40 ? "medium" : "low";
|
|
107
|
+
if (rationale.length === 0) {
|
|
108
|
+
rationale.push("No elevated risk signals detected");
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
score: clampedScore,
|
|
112
|
+
filesAtRisk,
|
|
113
|
+
hotspotsInPath,
|
|
114
|
+
regressionRisk,
|
|
115
|
+
rationale: rationale.join("; ")
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Counts source files in a directory (non-recursive for performance).
|
|
120
|
+
* Only counts .ts, .tsx, .js, .jsx, .py, .go, .rs files — not config/docs.
|
|
121
|
+
* Returns 0 on any error (missing dir, permission denied, etc.).
|
|
122
|
+
*/
|
|
123
|
+
async function countFiles(dir) {
|
|
124
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
125
|
+
".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".cs", ".rb"
|
|
126
|
+
]);
|
|
127
|
+
async function countRecursive(current, depth) {
|
|
128
|
+
if (depth > 6)
|
|
129
|
+
return 0; // don't recurse into deep vendor trees
|
|
130
|
+
try {
|
|
131
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
132
|
+
let count = 0;
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
count += await countRecursive(join(current, entry.name), depth + 1);
|
|
139
|
+
}
|
|
140
|
+
else if (SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
141
|
+
count += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return count;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return countRecursive(dir, 0);
|
|
151
|
+
}
|
|
152
|
+
function extname(name) {
|
|
153
|
+
const dot = name.lastIndexOf(".");
|
|
154
|
+
return dot >= 0 ? name.slice(dot) : "";
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=blast-radius.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type LeashInput, type PolicyResult } from "@martin/policy";
|
|
2
|
+
import type { SafetyLeashDecision } from "../leash.js";
|
|
3
|
+
export interface PolicyLeashOptions {
|
|
4
|
+
/** Explicit compiled policy path. Raw Rego fails closed inside @martin/policy. */
|
|
5
|
+
policyPath?: string;
|
|
6
|
+
/** Explicit compiled WASM policy path. Takes precedence over policyPath. */
|
|
7
|
+
policyWasmPath?: string;
|
|
8
|
+
/** Repository root used for .martin/policy.wasm discovery. */
|
|
9
|
+
repoRoot?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PolicyDeniedInput {
|
|
13
|
+
surface: string;
|
|
14
|
+
command?: string | null;
|
|
15
|
+
path?: string;
|
|
16
|
+
value?: string | null;
|
|
17
|
+
reasons: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface PolicyLeashVerdict {
|
|
20
|
+
allow: boolean;
|
|
21
|
+
reasons: string[];
|
|
22
|
+
deniedInputs: PolicyDeniedInput[];
|
|
23
|
+
inputsEvaluated: number;
|
|
24
|
+
source?: PolicyResult["source"];
|
|
25
|
+
policyPath?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
export type PolicyBackedSafetyLeashDecision = SafetyLeashDecision & {
|
|
29
|
+
policyVerdict: PolicyLeashVerdict;
|
|
30
|
+
};
|
|
31
|
+
export declare function applyPolicyToLeashDecision(decision: SafetyLeashDecision, inputs: LeashInput[], options?: PolicyLeashOptions): Promise<PolicyBackedSafetyLeashDecision>;
|