@martinloop/mcp 0.5.0 → 0.5.3
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/README.md +52 -14
- package/dist/resources.js +15 -3
- package/dist/server.js +40 -8
- package/dist/tools/doctor.js +5 -4
- package/dist/tools/get-status.d.ts +2 -0
- package/dist/tools/get-status.js +1 -0
- package/dist/tools/preflight.js +1 -1
- package/dist/tools/run-loop.d.ts +6 -1
- package/dist/tools/run-loop.js +22 -7
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-response.js +9 -2
- package/dist/tools/tool-support.d.ts +3 -2
- package/dist/tools/tool-support.js +5 -3
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +59 -34
- package/dist/vendor/adapters/codex-capabilities.d.ts +60 -0
- package/dist/vendor/adapters/codex-capabilities.js +237 -0
- package/dist/vendor/adapters/codex-cli.d.ts +22 -0
- package/dist/vendor/adapters/codex-cli.js +62 -0
- package/dist/vendor/adapters/codex-host.d.ts +104 -0
- package/dist/vendor/adapters/codex-host.js +577 -0
- package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
- package/dist/vendor/adapters/codex-launcher.js +17 -766
- package/dist/vendor/adapters/index.d.ts +4 -3
- package/dist/vendor/adapters/index.js +4 -3
- package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
- package/dist/vendor/adapters/openai-compatible.js +33 -7
- package/dist/vendor/adapters/verifier-only.d.ts +9 -0
- package/dist/vendor/adapters/verifier-only.js +68 -0
- package/dist/vendor/contracts/index.d.ts +16 -6
- package/dist/vendor/contracts/index.js +1 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +6 -0
- package/dist/vendor/contracts/verified-handoff.js +5 -0
- package/dist/vendor/core/grounding.d.ts +2 -1
- package/dist/vendor/core/grounding.js +40 -5
- package/dist/vendor/core/index.d.ts +4 -6
- package/dist/vendor/core/index.js +25 -19
- package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
- package/dist/vendor/core/policy.js +6 -6
- package/dist/vendor/core/red-blue/red-phase.d.ts +2 -1
- package/dist/vendor/core/red-blue/red-phase.js +3 -3
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +2 -4
- package/dist/vendor/core/red-blue/risk-tiers.js +1 -3
- package/dist/vendor/core/routing.d.ts +0 -42
- package/dist/vendor/core/routing.js +4 -154
- package/dist/vendor/core/verified-handoff.d.ts +6 -1
- package/dist/vendor/core/verified-handoff.js +28 -0
- package/package.json +6 -2
- package/server.json +2 -2
- package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/stub-direct-provider.js +0 -12
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type GeminiCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
|
|
3
|
+
export { createCodexCliAdapter, type CodexCliAdapterOptions } from "./codex-cli.js";
|
|
4
4
|
export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
|
|
5
|
-
export {
|
|
5
|
+
export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
|
|
6
|
+
export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexApprovalCapability, type CodexCapabilityFlag, type CodexCapabilityProfile, type CodexFlagScope, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult, type CodexPromptTransport, type CodexSandboxCapability, checkCodexSandboxPreflight, probeFilesystemWriteCapability, type CodexSandboxPreflightOk, type CodexSandboxPreflightOutcome, type CodexSandboxPreflightReadOnly } from "./codex-launcher.js";
|
|
6
7
|
export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
|
|
3
|
+
export { createCodexCliAdapter } from "./codex-cli.js";
|
|
4
4
|
export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
|
|
5
|
-
export {
|
|
5
|
+
export { createVerifierOnlyAdapter } from "./verifier-only.js";
|
|
6
|
+
export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, checkCodexSandboxPreflight, probeFilesystemWriteCapability } from "./codex-launcher.js";
|
|
6
7
|
export { createSpawnPlan } from "./cli-bridge.js";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Usage:
|
|
15
15
|
* # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
|
|
16
16
|
* MARTIN_OPENAI_API_KEY=sk-...
|
|
17
|
-
* MARTIN_OPENAI_MODEL
|
|
17
|
+
* MARTIN_OPENAI_MODEL=<provider-model-id>
|
|
18
18
|
* martin-loop run "fix the bug" --engine openai
|
|
19
19
|
*
|
|
20
20
|
* # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
|
|
@@ -51,10 +51,9 @@ export interface OpenAiCompatibleAdapterOptions {
|
|
|
51
51
|
fetchImpl?: typeof fetch;
|
|
52
52
|
}
|
|
53
53
|
export declare const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
54
|
-
export declare const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
55
54
|
export declare function resolveOpenAiCompatibleRuntimeConfig(env?: NodeJS.ProcessEnv): {
|
|
56
55
|
baseUrl: string;
|
|
57
|
-
model
|
|
56
|
+
model?: string;
|
|
58
57
|
apiKey: string;
|
|
59
58
|
apiKeyConfigured: boolean;
|
|
60
59
|
authPosture: "api_key" | "anonymous_or_local";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Usage:
|
|
15
15
|
* # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
|
|
16
16
|
* MARTIN_OPENAI_API_KEY=sk-...
|
|
17
|
-
* MARTIN_OPENAI_MODEL
|
|
17
|
+
* MARTIN_OPENAI_MODEL=<provider-model-id>
|
|
18
18
|
* martin-loop run "fix the bug" --engine openai
|
|
19
19
|
*
|
|
20
20
|
* # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
|
|
@@ -77,13 +77,27 @@ function normalizeOpenAiCompatibleUsage(input) {
|
|
|
77
77
|
};
|
|
78
78
|
const actualUsd = (input.tokensIn / 1000) * pricing.inputPer1K +
|
|
79
79
|
(input.tokensOut / 1000) * pricing.outputPer1K;
|
|
80
|
-
const provenance = input.usageWasFullyProviderReported && hasKnownPricing ? "
|
|
80
|
+
const provenance = input.usageWasFullyProviderReported && hasKnownPricing ? "calculated" : "estimated";
|
|
81
81
|
return normalizeUsage({
|
|
82
82
|
actualUsd,
|
|
83
83
|
...(provenance === "estimated" ? { estimatedUsd: actualUsd } : {}),
|
|
84
84
|
tokensIn: input.tokensIn,
|
|
85
85
|
tokensOut: input.tokensOut,
|
|
86
|
-
provenance
|
|
86
|
+
provenance,
|
|
87
|
+
providerSettlement: {
|
|
88
|
+
providerId: "openai-compatible",
|
|
89
|
+
model: input.model,
|
|
90
|
+
transport: "http",
|
|
91
|
+
source: input.usageWasFullyProviderReported ? "openai_compatible_json" : "estimated_fallback",
|
|
92
|
+
inputTokens: input.tokensIn,
|
|
93
|
+
outputTokens: input.tokensOut,
|
|
94
|
+
billingMode: input.billingMode,
|
|
95
|
+
modelSource: input.modelSource,
|
|
96
|
+
pricingSource: hasKnownPricing ? "static_catalog" : "blended_fallback",
|
|
97
|
+
pricingVersion: "embedded-v1",
|
|
98
|
+
rawUsageAvailable: input.usageWasFullyProviderReported,
|
|
99
|
+
settledAt: new Date().toISOString()
|
|
100
|
+
}
|
|
87
101
|
});
|
|
88
102
|
}
|
|
89
103
|
// ---------------------------------------------------------------------------
|
|
@@ -97,12 +111,11 @@ Follow these rules exactly:
|
|
|
97
111
|
- Be precise, minimal, and test-backed in all changes.
|
|
98
112
|
- State what you changed and why at the end of your response.`;
|
|
99
113
|
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
100
|
-
export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
101
114
|
export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
|
|
102
115
|
const apiKey = env["MARTIN_OPENAI_API_KEY"] ?? "";
|
|
103
116
|
return {
|
|
104
117
|
baseUrl: env["MARTIN_OPENAI_BASE_URL"] ?? DEFAULT_OPENAI_BASE_URL,
|
|
105
|
-
model: env["MARTIN_OPENAI_MODEL"]
|
|
118
|
+
...(env["MARTIN_OPENAI_MODEL"] ? { model: env["MARTIN_OPENAI_MODEL"] } : {}),
|
|
106
119
|
apiKey,
|
|
107
120
|
apiKeyConfigured: apiKey.length > 0,
|
|
108
121
|
authPosture: apiKey.length > 0 ? "api_key" : "anonymous_or_local"
|
|
@@ -143,7 +156,16 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
143
156
|
const runtimeConfig = resolveOpenAiCompatibleRuntimeConfig();
|
|
144
157
|
const baseUrl = (options.baseUrl ?? runtimeConfig.baseUrl).replace(/\/$/, "");
|
|
145
158
|
const model = options.model ?? runtimeConfig.model;
|
|
159
|
+
if (!model) {
|
|
160
|
+
throw new Error("MODEL_CONFIGURATION_REQUIRED: set --model or MARTIN_OPENAI_MODEL for direct OpenAI-compatible execution.");
|
|
161
|
+
}
|
|
146
162
|
const apiKey = options.apiKey ?? runtimeConfig.apiKey;
|
|
163
|
+
const modelSource = options.model ? "explicit_override" : "provider_configured";
|
|
164
|
+
const billingMode = /^(?:https?:\/\/)?(?:localhost|127\.0\.0\.1|\[::1\])/iu.test(baseUrl)
|
|
165
|
+
? "local_unmetered"
|
|
166
|
+
: apiKey
|
|
167
|
+
? "metered_api"
|
|
168
|
+
: "unknown";
|
|
147
169
|
return {
|
|
148
170
|
adapterId: `openai-compatible:${model}`,
|
|
149
171
|
kind: "direct-provider",
|
|
@@ -306,7 +328,9 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
306
328
|
model,
|
|
307
329
|
tokensIn,
|
|
308
330
|
tokensOut: 0,
|
|
309
|
-
usageWasFullyProviderReported
|
|
331
|
+
usageWasFullyProviderReported,
|
|
332
|
+
modelSource,
|
|
333
|
+
billingMode
|
|
310
334
|
}),
|
|
311
335
|
verification: { passed: false, summary: "Empty response — nothing to verify." },
|
|
312
336
|
failure: { message: "empty_response" }
|
|
@@ -332,7 +356,9 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
332
356
|
model,
|
|
333
357
|
tokensIn,
|
|
334
358
|
tokensOut,
|
|
335
|
-
usageWasFullyProviderReported
|
|
359
|
+
usageWasFullyProviderReported,
|
|
360
|
+
modelSource,
|
|
361
|
+
billingMode
|
|
336
362
|
}),
|
|
337
363
|
verification,
|
|
338
364
|
execution,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MartinAdapter } from "../core/index.js";
|
|
2
|
+
import { type SpawnLike } from "./cli-bridge.js";
|
|
3
|
+
export interface VerifierOnlyAdapterOptions {
|
|
4
|
+
workingDirectory?: string;
|
|
5
|
+
verifyTimeoutMs?: number;
|
|
6
|
+
label?: string;
|
|
7
|
+
spawnImpl?: SpawnLike;
|
|
8
|
+
}
|
|
9
|
+
export declare function createVerifierOnlyAdapter(options?: VerifierOnlyAdapterOptions): MartinAdapter;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
|
|
2
|
+
import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
|
|
3
|
+
export function createVerifierOnlyAdapter(options = {}) {
|
|
4
|
+
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
5
|
+
const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
|
|
6
|
+
return {
|
|
7
|
+
adapterId: "direct:verifier:verify-only",
|
|
8
|
+
kind: "direct-provider",
|
|
9
|
+
label: options.label ?? "Verifier-only adapter",
|
|
10
|
+
metadata: {
|
|
11
|
+
providerId: "verifier",
|
|
12
|
+
model: "verify-only",
|
|
13
|
+
transport: "cli",
|
|
14
|
+
capabilities: createAdapterCapabilities({
|
|
15
|
+
usageSettlement: true,
|
|
16
|
+
diffArtifacts: true,
|
|
17
|
+
workspaceMutations: false
|
|
18
|
+
})
|
|
19
|
+
},
|
|
20
|
+
async execute(request) {
|
|
21
|
+
const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
|
|
22
|
+
(request.context.verificationStack?.length ?? 0) > 0;
|
|
23
|
+
const baselineChangedFiles = shouldTrackVerifierWrites
|
|
24
|
+
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
25
|
+
: new Set();
|
|
26
|
+
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl, {
|
|
27
|
+
runId: request.loopId,
|
|
28
|
+
workspaceId: request.workspaceId,
|
|
29
|
+
cwd: workingDirectory
|
|
30
|
+
});
|
|
31
|
+
const changedFiles = shouldTrackVerifierWrites
|
|
32
|
+
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
33
|
+
: [];
|
|
34
|
+
const execution = { changedFiles };
|
|
35
|
+
if (verification.passed) {
|
|
36
|
+
return {
|
|
37
|
+
status: "completed",
|
|
38
|
+
summary: changedFiles.length > 0
|
|
39
|
+
? `Verifier-only run completed but modified files: ${changedFiles.join(", ")}`
|
|
40
|
+
: "Verifier-only run completed without file edits.",
|
|
41
|
+
usage: normalizeUsage({
|
|
42
|
+
actualUsd: 0,
|
|
43
|
+
tokensIn: 0,
|
|
44
|
+
tokensOut: 0,
|
|
45
|
+
provenance: "actual"
|
|
46
|
+
}),
|
|
47
|
+
verification,
|
|
48
|
+
execution
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
status: "failed",
|
|
53
|
+
summary: "Verifier-only run failed.",
|
|
54
|
+
usage: normalizeUsage({
|
|
55
|
+
actualUsd: 0,
|
|
56
|
+
tokensIn: 0,
|
|
57
|
+
tokensOut: 0,
|
|
58
|
+
provenance: "actual"
|
|
59
|
+
}),
|
|
60
|
+
verification,
|
|
61
|
+
execution,
|
|
62
|
+
failure: {
|
|
63
|
+
message: verification.summary
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -66,17 +66,27 @@ export interface LoopCost {
|
|
|
66
66
|
estimatedUsd?: number;
|
|
67
67
|
provenance?: CostProvenance;
|
|
68
68
|
providerSettlement?: ProviderUsageSettlement;
|
|
69
|
+
savingsBaseline?: {
|
|
70
|
+
usd: number;
|
|
71
|
+
source: "measured_control" | "operator_supplied" | "fixture";
|
|
72
|
+
provenance: CostProvenance;
|
|
73
|
+
};
|
|
69
74
|
}
|
|
70
|
-
export type UsageSettlementSource = "claude_json" | "codex_jsonl" | "gemini_json" | "estimated_fallback" | "unavailable";
|
|
75
|
+
export type UsageSettlementSource = "claude_json" | "codex_jsonl" | "gemini_json" | "openai_compatible_json" | "estimated_fallback" | "unavailable";
|
|
71
76
|
export interface ProviderUsageSettlement {
|
|
72
77
|
providerId: string;
|
|
73
|
-
model
|
|
78
|
+
model?: string;
|
|
74
79
|
transport?: "cli" | "http" | "routed_http";
|
|
75
80
|
source: UsageSettlementSource;
|
|
76
81
|
inputTokens: number;
|
|
77
82
|
cachedInputTokens?: number;
|
|
78
83
|
outputTokens: number;
|
|
79
84
|
reasoningOutputTokens?: number;
|
|
85
|
+
cacheCreationInputTokens?: number;
|
|
86
|
+
billingMode?: "metered_api" | "subscription" | "local_unmetered" | "unknown";
|
|
87
|
+
modelSource?: "provider_reported" | "explicit_override" | "provider_configured" | "agent_default" | "unavailable";
|
|
88
|
+
pricingSource?: "provider_reported_total" | "static_catalog" | "blended_fallback" | "none";
|
|
89
|
+
pricingVersion?: string;
|
|
80
90
|
rawUsageAvailable: boolean;
|
|
81
91
|
settledAt: string;
|
|
82
92
|
}
|
|
@@ -141,7 +151,7 @@ export interface LoopAttempt {
|
|
|
141
151
|
attemptId: string;
|
|
142
152
|
index: number;
|
|
143
153
|
adapterId: string;
|
|
144
|
-
model
|
|
154
|
+
model?: string;
|
|
145
155
|
startedAt: string;
|
|
146
156
|
completedAt?: string;
|
|
147
157
|
summary?: string;
|
|
@@ -335,7 +345,7 @@ export interface MachineState {
|
|
|
335
345
|
* Cost provenance label — every budget metric must carry this.
|
|
336
346
|
* Never conflate actual with estimated or unavailable.
|
|
337
347
|
*/
|
|
338
|
-
export type CostProvenance = "actual" | "estimated" | "unavailable";
|
|
348
|
+
export type CostProvenance = "actual" | "calculated" | "estimated" | "unavailable";
|
|
339
349
|
/**
|
|
340
350
|
* Per-attempt budget preflight estimate produced before admission.
|
|
341
351
|
* Used to gate attempts before any tokens are spent.
|
|
@@ -443,8 +453,8 @@ export { CONTEXT_SHADOW_MANIFEST_VERSION, CONTEXT_C5_VERSION } from "./context-s
|
|
|
443
453
|
export type { ContextC5EnvelopeV1, ContextEvidence, ContextShadowDecisionV1, ContextShadowManifestV1, ContextShadowSegmentInput, ContextShadowSegmentKind } from "./context-shadow.js";
|
|
444
454
|
export { CONTEXT_MANIFEST_VERSION, CONTEXT_LEDGER_VERSION } from "./context-manifest.js";
|
|
445
455
|
export type { ContinuationCheckpoint, ContextBudget, ContextCandidateDecision, ContextFaultRequest, ContextFaultResult, ContextKind, ContextLedgerEntry, ContextManifest, ContextObject, ContextPolicy, ContextPriority, ContextSensitivity, ContextTrust, TaskItem, UsageEvidence } from "./context-manifest.js";
|
|
446
|
-
export { EVIDENCE_STATUSES, TEST_INTEGRITY_STATUSES, TEST_INTEGRITY_VERDICTS, VERIFIED_HANDOFF_OUTCOMES, } from "./verified-handoff.js";
|
|
447
|
-
export type { EvidenceStatus, TestIntegrityStatus, TestIntegrityVerdict, VerifiedHandoffCheckV1, VerifiedHandoffOutcome, VerifiedHandoffRecoveryV1, VerifiedHandoffRequirementV1, VerifiedHandoffScopeV1, VerifiedHandoffTestIntegrityV1, VerifiedHandoffV1, } from "./verified-handoff.js";
|
|
456
|
+
export { EVIDENCE_STATUSES, EXECUTION_MODES, TEST_INTEGRITY_STATUSES, TEST_INTEGRITY_VERDICTS, VERIFIED_HANDOFF_OUTCOMES, } from "./verified-handoff.js";
|
|
457
|
+
export type { EvidenceStatus, ExecutionMode, TestIntegrityStatus, TestIntegrityVerdict, VerifiedHandoffCheckV1, VerifiedHandoffOutcome, VerifiedHandoffRecoveryV1, VerifiedHandoffRequirementV1, VerifiedHandoffScopeV1, VerifiedHandoffTestIntegrityV1, VerifiedHandoffV1, } from "./verified-handoff.js";
|
|
448
458
|
export { HANDOFF_SCHEMA_VERSION } from "./context-handoff.js";
|
|
449
459
|
export type { ChainIntegrityState, ContextCircuitBreakResult, ContextExclusionDecision, ContextHandoffArtifact, ContextHandoffClaim, ContextHandoffReceipt, ContextHandoffVerification, HandoffClaimState } from "./context-handoff.js";
|
|
450
460
|
export { MISSION_SCHEMA_VERSION, MISSION_STATUSES, ALLOWED_MISSION_TRANSITIONS, createMissionRecord, isMissionTransitionAllowed } from './mission.js';
|
|
@@ -243,7 +243,7 @@ export { CONTEXT_SHADOW_MANIFEST_VERSION, CONTEXT_C5_VERSION } from "./context-s
|
|
|
243
243
|
// ─── Context Runtime — A-CTX-1 ───────────────────────────────────────────────
|
|
244
244
|
export { CONTEXT_MANIFEST_VERSION, CONTEXT_LEDGER_VERSION } from "./context-manifest.js";
|
|
245
245
|
// ─── Track A — Verified Handoff ───────────────────────────────────────────────
|
|
246
|
-
export { EVIDENCE_STATUSES, TEST_INTEGRITY_STATUSES, TEST_INTEGRITY_VERDICTS, VERIFIED_HANDOFF_OUTCOMES, } from "./verified-handoff.js";
|
|
246
|
+
export { EVIDENCE_STATUSES, EXECUTION_MODES, TEST_INTEGRITY_STATUSES, TEST_INTEGRITY_VERDICTS, VERIFIED_HANDOFF_OUTCOMES, } from "./verified-handoff.js";
|
|
247
247
|
// ─── Context Handoff — A-CTX-2 ───────────────────────────────────────────────
|
|
248
248
|
export { HANDOFF_SCHEMA_VERSION } from "./context-handoff.js";
|
|
249
249
|
export { MISSION_SCHEMA_VERSION, MISSION_STATUSES, ALLOWED_MISSION_TRANSITIONS, createMissionRecord, isMissionTransitionAllowed } from './mission.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CostProvenance, LoopLifecycleState, LoopStatus, ReceiptIntegritySummary } from "./index.js";
|
|
2
2
|
export declare const VERIFIED_HANDOFF_OUTCOMES: readonly ["VERIFIED", "STOPPED", "NEEDS_REVIEW"];
|
|
3
3
|
export type VerifiedHandoffOutcome = (typeof VERIFIED_HANDOFF_OUTCOMES)[number];
|
|
4
|
+
export declare const EXECUTION_MODES: readonly ["governed", "verification_only", "simulated"];
|
|
5
|
+
export type ExecutionMode = (typeof EXECUTION_MODES)[number];
|
|
4
6
|
export declare const EVIDENCE_STATUSES: readonly ["PASSED", "FAILED", "CONTRADICTED", "NOT_RUN", "NOT_EVALUATED"];
|
|
5
7
|
export type EvidenceStatus = (typeof EVIDENCE_STATUSES)[number];
|
|
6
8
|
/**
|
|
@@ -71,6 +73,10 @@ export interface VerifiedHandoffV1 {
|
|
|
71
73
|
verificationPlan: string[];
|
|
72
74
|
};
|
|
73
75
|
outcome: VerifiedHandoffOutcome;
|
|
76
|
+
/** Added compatibly in 1.0.0; missing legacy values must be treated as non-governed. */
|
|
77
|
+
executionMode?: ExecutionMode;
|
|
78
|
+
/** Only an explicit true value is eligible for a governed VERIFIED claim. */
|
|
79
|
+
governanceClaimEligible?: boolean;
|
|
74
80
|
sourceStatus: {
|
|
75
81
|
status: LoopStatus;
|
|
76
82
|
lifecycleState: LoopLifecycleState;
|
|
@@ -9,6 +9,7 @@ export interface RepoGroundingIndex {
|
|
|
9
9
|
createdAt: string;
|
|
10
10
|
fileCount: number;
|
|
11
11
|
files: RepoGroundingFile[];
|
|
12
|
+
repositoryFingerprint?: string;
|
|
12
13
|
}
|
|
13
14
|
export interface RepoGroundingHit {
|
|
14
15
|
path: string;
|
|
@@ -18,7 +19,7 @@ export interface RepoGroundingHit {
|
|
|
18
19
|
}
|
|
19
20
|
export declare function resolveGroundingRoot(env?: NodeJS.ProcessEnv): string;
|
|
20
21
|
export declare function loadOrBuildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
|
|
21
|
-
export declare function buildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
|
|
22
|
+
export declare function buildRepoGroundingIndex(repoRoot: string, repositoryFingerprint?: string): Promise<RepoGroundingIndex>;
|
|
22
23
|
export declare function queryRepoGroundingIndex(index: RepoGroundingIndex, query: string, limit?: number): RepoGroundingHit[];
|
|
23
24
|
export type GroundingViolationKind = "file_not_found" | "symbol_not_found" | "import_not_found" | "patch_outside_allowed_paths";
|
|
24
25
|
export interface GroundingViolation {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { extname, join, relative } from "node:path";
|
|
4
5
|
const TEXT_EXTENSIONS = new Set([
|
|
@@ -18,14 +19,16 @@ export function resolveGroundingRoot(env = process.env) {
|
|
|
18
19
|
}
|
|
19
20
|
export async function loadOrBuildRepoGroundingIndex(repoRoot) {
|
|
20
21
|
const cachePath = getGroundingCachePath(repoRoot);
|
|
22
|
+
const repositoryFingerprint = await computeRepositoryFingerprint(repoRoot);
|
|
21
23
|
try {
|
|
22
24
|
const cached = JSON.parse(await readFile(cachePath, "utf8"));
|
|
23
|
-
if (cached?.schemaVersion === "martin.grounding.v1"
|
|
25
|
+
if (cached?.schemaVersion === "martin.grounding.v1" &&
|
|
26
|
+
cached.repositoryFingerprint === repositoryFingerprint) {
|
|
24
27
|
return cached;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
catch { }
|
|
28
|
-
const index = await buildRepoGroundingIndex(repoRoot);
|
|
31
|
+
const index = await buildRepoGroundingIndex(repoRoot, repositoryFingerprint);
|
|
29
32
|
try {
|
|
30
33
|
await mkdir(resolveGroundingRoot(), { recursive: true });
|
|
31
34
|
await writeFile(cachePath, JSON.stringify(index, null, 2), "utf8");
|
|
@@ -36,7 +39,7 @@ export async function loadOrBuildRepoGroundingIndex(repoRoot) {
|
|
|
36
39
|
}
|
|
37
40
|
return index;
|
|
38
41
|
}
|
|
39
|
-
export async function buildRepoGroundingIndex(repoRoot) {
|
|
42
|
+
export async function buildRepoGroundingIndex(repoRoot, repositoryFingerprint) {
|
|
40
43
|
const files = [];
|
|
41
44
|
const discovered = await walk(repoRoot, repoRoot, files, { count: 0 });
|
|
42
45
|
return {
|
|
@@ -44,9 +47,41 @@ export async function buildRepoGroundingIndex(repoRoot) {
|
|
|
44
47
|
repoRoot,
|
|
45
48
|
createdAt: new Date().toISOString(),
|
|
46
49
|
fileCount: discovered.count,
|
|
47
|
-
files
|
|
50
|
+
files,
|
|
51
|
+
repositoryFingerprint: repositoryFingerprint ?? await computeRepositoryFingerprint(repoRoot)
|
|
48
52
|
};
|
|
49
53
|
}
|
|
54
|
+
async function computeRepositoryFingerprint(repoRoot) {
|
|
55
|
+
const entries = [];
|
|
56
|
+
await collectFingerprintEntries(repoRoot, repoRoot, entries, { count: 0 });
|
|
57
|
+
entries.sort();
|
|
58
|
+
return createHash("sha256").update(entries.join("\n")).digest("hex");
|
|
59
|
+
}
|
|
60
|
+
async function collectFingerprintEntries(repoRoot, currentDir, entries, state) {
|
|
61
|
+
if (state.count >= MAX_FILES)
|
|
62
|
+
return;
|
|
63
|
+
const directoryEntries = await readdir(currentDir, { withFileTypes: true });
|
|
64
|
+
for (const entry of directoryEntries) {
|
|
65
|
+
if (state.count >= MAX_FILES)
|
|
66
|
+
break;
|
|
67
|
+
const absPath = join(currentDir, entry.name);
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
70
|
+
await collectFingerprintEntries(repoRoot, absPath, entries, state);
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
|
75
|
+
continue;
|
|
76
|
+
try {
|
|
77
|
+
const metadata = await stat(absPath);
|
|
78
|
+
const relPath = relative(repoRoot, absPath).replace(/\\/g, "/");
|
|
79
|
+
entries.push(`${relPath}:${metadata.size}:${metadata.mtimeMs}`);
|
|
80
|
+
state.count += 1;
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
50
85
|
export function queryRepoGroundingIndex(index, query, limit = 6) {
|
|
51
86
|
const terms = tokenize(query);
|
|
52
87
|
if (terms.length === 0)
|
|
@@ -19,8 +19,8 @@ export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
|
19
19
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
20
20
|
export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
21
21
|
export type { AvoidedUsdInput } from "./savings.js";
|
|
22
|
-
export { classifyRoute, evaluatePreworkBurnPolicy
|
|
23
|
-
export type { RouteDecision, RouteClassificationInput
|
|
22
|
+
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
23
|
+
export type { RouteDecision, RouteClassificationInput } from "./routing.js";
|
|
24
24
|
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
|
|
25
25
|
export type { TestIntegrityReport, TestIntegrityFinding } from "./test-integrity.js";
|
|
26
26
|
export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
|
|
@@ -29,7 +29,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
29
29
|
export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
|
|
30
30
|
export { compilePromptPacket } from "./compiler.js";
|
|
31
31
|
export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
|
|
32
|
-
export { buildVerifiedHandoff, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
32
|
+
export { buildVerifiedHandoff, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
33
33
|
export type { BoundVerifierEvidence, BuildVerifiedHandoffInput, VerifierExecutionBinding, } from "./verified-handoff.js";
|
|
34
34
|
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
35
35
|
export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
@@ -142,7 +142,7 @@ export interface MartinAdapter {
|
|
|
142
142
|
label: string;
|
|
143
143
|
metadata: {
|
|
144
144
|
providerId: string;
|
|
145
|
-
model
|
|
145
|
+
model?: string;
|
|
146
146
|
transport?: "cli" | "http" | "routed_http";
|
|
147
147
|
capabilities?: {
|
|
148
148
|
preflight?: boolean;
|
|
@@ -155,7 +155,6 @@ export interface MartinAdapter {
|
|
|
155
155
|
[key: string]: unknown;
|
|
156
156
|
};
|
|
157
157
|
execute(request: MartinAdapterRequest): Promise<MartinAdapterResult>;
|
|
158
|
-
withModel?(model: string): MartinAdapter;
|
|
159
158
|
}
|
|
160
159
|
export interface DistilledContext {
|
|
161
160
|
focus: string;
|
|
@@ -193,7 +192,6 @@ export interface RunMartinInput {
|
|
|
193
192
|
now?: () => string;
|
|
194
193
|
idFactory?: (prefix: string) => string;
|
|
195
194
|
maxRecentAttempts?: number;
|
|
196
|
-
fallbackModels?: string[];
|
|
197
195
|
fallbackAdapters?: MartinAdapter[];
|
|
198
196
|
receiptScope?: ReceiptScope;
|
|
199
197
|
/** Optional persistence store. When provided, runMartin writes artifacts on each lifecycle event. */
|
|
@@ -22,7 +22,7 @@ export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
|
22
22
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
23
23
|
export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
24
24
|
// ─── Routing economics ─────────────────────────────────────────────────────
|
|
25
|
-
export { classifyRoute, evaluatePreworkBurnPolicy
|
|
25
|
+
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
26
26
|
// ─── Test Integrity Validation ──────────────────────────────────────────────
|
|
27
27
|
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
|
|
28
28
|
// ─── Repo Style Analyzer ────────────────────────────────────────────────────
|
|
@@ -32,7 +32,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
32
32
|
// ─── Prompt packet compiler ──────────────────────────────────────────────────
|
|
33
33
|
export { compilePromptPacket } from "./compiler.js";
|
|
34
34
|
// ─── Verified Handoff builder ────────────────────────────────────────────────
|
|
35
|
-
export { buildVerifiedHandoff, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
35
|
+
export { buildVerifiedHandoff, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
36
36
|
// ─── Persistence (RunStore, LedgerEvent, FileRunStore) ──────────────────────
|
|
37
37
|
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
38
38
|
export { compileAndPersistContext } from "./persistence/index.js";
|
|
@@ -265,11 +265,6 @@ export async function runMartin(input) {
|
|
|
265
265
|
}));
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
-
const DEFAULT_FALLBACK_MODELS = [
|
|
269
|
-
"claude-haiku-4-5",
|
|
270
|
-
"claude-sonnet-4-6",
|
|
271
|
-
"claude-opus-4-6"
|
|
272
|
-
];
|
|
273
268
|
const adapterChain = [input.adapter, ...(input.fallbackAdapters ?? [])];
|
|
274
269
|
let currentAdapterIndex = 0;
|
|
275
270
|
let currentAdapter = adapterChain[currentAdapterIndex] ?? input.adapter;
|
|
@@ -803,7 +798,7 @@ export async function runMartin(input) {
|
|
|
803
798
|
attemptId,
|
|
804
799
|
index: currentAttemptIndex,
|
|
805
800
|
adapterId: executingAdapter.adapterId,
|
|
806
|
-
model: executingAdapter.metadata.model,
|
|
801
|
+
...(executingAdapter.metadata.model ? { model: executingAdapter.metadata.model } : {}),
|
|
807
802
|
startedAt: attemptStartedAt,
|
|
808
803
|
completedAt: attemptCompletedAt,
|
|
809
804
|
summary: result.summary,
|
|
@@ -864,13 +859,6 @@ export async function runMartin(input) {
|
|
|
864
859
|
adapterSwitched = true;
|
|
865
860
|
}
|
|
866
861
|
}
|
|
867
|
-
if (failure.recommendedIntervention === "change_model" && currentAdapter.withModel) {
|
|
868
|
-
const fallbackModels = input.fallbackModels ?? DEFAULT_FALLBACK_MODELS;
|
|
869
|
-
const nextModel = fallbackModels[loop.attempts.length % fallbackModels.length];
|
|
870
|
-
if (nextModel) {
|
|
871
|
-
currentAdapter = currentAdapter.withModel(nextModel);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
862
|
loop = appendLoopEvent(loop, {
|
|
875
863
|
type: "failure.classified",
|
|
876
864
|
lifecycleState: "running",
|
|
@@ -1138,12 +1126,17 @@ export async function runMartin(input) {
|
|
|
1138
1126
|
// Uses the task's repoRoot to build/load the grounding index, then scans any diff
|
|
1139
1127
|
let groundingScanResult;
|
|
1140
1128
|
const patchDiff = buildPatchDiff(result, changedFiles);
|
|
1141
|
-
|
|
1129
|
+
const workspaceMutationCapable = executingAdapter.metadata.capabilities?.workspaceMutations !== false;
|
|
1130
|
+
const hasReportedOrObservedChanges = changedFiles.length > 0 || (result.execution?.changedFiles?.length ?? 0) > 0;
|
|
1131
|
+
const groundingEvidenceRequired = result.status === "completed" && workspaceMutationCapable && hasReportedOrObservedChanges;
|
|
1132
|
+
let groundingEvidenceAvailable = !groundingEvidenceRequired;
|
|
1133
|
+
if (groundingEvidenceRequired && patchDiff && input.task.repoRoot) {
|
|
1142
1134
|
try {
|
|
1143
1135
|
const groundingIndex = await loadOrBuildRepoGroundingIndex(input.task.repoRoot);
|
|
1144
1136
|
groundingScanResult = reconcileExistingGroundingFiles(scanPatchForGroundingViolations(patchDiff, groundingIndex, {
|
|
1145
1137
|
allowedPaths: input.task.allowedPaths
|
|
1146
1138
|
}), input.task.repoRoot, rollbackBoundary);
|
|
1139
|
+
groundingEvidenceAvailable = true;
|
|
1147
1140
|
if (input.store && groundingScanResult.violations.length > 0) {
|
|
1148
1141
|
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
1149
1142
|
kind: "grounding.violations_found",
|
|
@@ -1159,10 +1152,22 @@ export async function runMartin(input) {
|
|
|
1159
1152
|
}
|
|
1160
1153
|
}
|
|
1161
1154
|
catch {
|
|
1162
|
-
//
|
|
1155
|
+
// Availability is persisted below so scanner failures and missing
|
|
1156
|
+
// grounding inputs follow the same recover-forward trust contract.
|
|
1163
1157
|
}
|
|
1164
1158
|
}
|
|
1165
|
-
|
|
1159
|
+
if (groundingEvidenceRequired && !groundingEvidenceAvailable) {
|
|
1160
|
+
// The completed work remains useful, but unavailable grounding evidence
|
|
1161
|
+
// cannot be converted into a zero-violation governance claim.
|
|
1162
|
+
loop = {
|
|
1163
|
+
...loop,
|
|
1164
|
+
metadata: {
|
|
1165
|
+
...loop.metadata,
|
|
1166
|
+
groundingEvidenceStatus: "unavailable"
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && groundingEvidenceAvailable;
|
|
1166
1171
|
let patchDecision;
|
|
1167
1172
|
if (shouldEvaluatePatchTruth) {
|
|
1168
1173
|
patchDecision = evaluatePatchDecision({
|
|
@@ -1548,7 +1553,8 @@ function getUsageProvenance(usage) {
|
|
|
1548
1553
|
const COST_PROVENANCE_RANK = {
|
|
1549
1554
|
unavailable: 0,
|
|
1550
1555
|
estimated: 1,
|
|
1551
|
-
|
|
1556
|
+
calculated: 2,
|
|
1557
|
+
actual: 3
|
|
1552
1558
|
};
|
|
1553
1559
|
/**
|
|
1554
1560
|
* Aggregates cost provenance across attempts. The cumulative loop provenance
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - legacy root JSONL files: <runsRoot>/*.jsonl
|
|
7
7
|
* - canonical run trees: <runsRoot>/<loopId>/loop-record.json
|
|
8
8
|
*/
|
|
9
|
+
import type { CostProvenance } from "../../contracts/index.js";
|
|
9
10
|
export interface LoopAttemptRecord {
|
|
10
11
|
index: number;
|
|
11
12
|
model?: string;
|
|
@@ -32,6 +33,7 @@ export interface LoopRunRecord {
|
|
|
32
33
|
tokensIn: number;
|
|
33
34
|
tokensOut: number;
|
|
34
35
|
avoidedUsd?: number;
|
|
36
|
+
provenance?: CostProvenance;
|
|
35
37
|
};
|
|
36
38
|
attempts: LoopAttemptRecord[];
|
|
37
39
|
task: {
|
|
@@ -152,14 +152,14 @@ export function classifyFailure(input) {
|
|
|
152
152
|
failureClass: "logic_error",
|
|
153
153
|
rationale: "Oscillating failure pattern detected — switching strategy to break the cycle.",
|
|
154
154
|
retryable: true,
|
|
155
|
-
recommendedIntervention: "
|
|
155
|
+
recommendedIntervention: "compress_context"
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
return {
|
|
159
159
|
failureClass: repeatedFailure ?? "logic_error",
|
|
160
160
|
rationale: "The loop is still failing to produce a correct implementation.",
|
|
161
161
|
retryable: true,
|
|
162
|
-
recommendedIntervention:
|
|
162
|
+
recommendedIntervention: "compress_context"
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
165
|
export function evaluateCostGovernor(input) {
|
|
@@ -318,7 +318,7 @@ function mapClassHintToAssessment(classHint, attempts) {
|
|
|
318
318
|
failureClass: "hallucination",
|
|
319
319
|
rationale: "Structural evidence: suspiciously short or trivial agent response.",
|
|
320
320
|
retryable: true,
|
|
321
|
-
recommendedIntervention: repeatedFailure === "hallucination" ? "
|
|
321
|
+
recommendedIntervention: repeatedFailure === "hallucination" ? "compress_context" : "run_verifier"
|
|
322
322
|
};
|
|
323
323
|
case "verification_failure":
|
|
324
324
|
case "test_regression":
|
|
@@ -333,7 +333,7 @@ function mapClassHintToAssessment(classHint, attempts) {
|
|
|
333
333
|
failureClass: classHint,
|
|
334
334
|
rationale: "Structural hint provided by adapter.",
|
|
335
335
|
retryable: true,
|
|
336
|
-
recommendedIntervention:
|
|
336
|
+
recommendedIntervention: "compress_context"
|
|
337
337
|
};
|
|
338
338
|
}
|
|
339
339
|
}
|
|
@@ -472,7 +472,7 @@ export function selectRecoveryRecipe(evidence) {
|
|
|
472
472
|
return {
|
|
473
473
|
recipe: "strategy_swap",
|
|
474
474
|
rationale: "Very low diff novelty across repeated retries. Swap strategy.",
|
|
475
|
-
intervention: "
|
|
475
|
+
intervention: "compress_context"
|
|
476
476
|
};
|
|
477
477
|
}
|
|
478
478
|
return {
|
|
@@ -485,7 +485,7 @@ export function selectRecoveryRecipe(evidence) {
|
|
|
485
485
|
return {
|
|
486
486
|
recipe: "downgrade_model",
|
|
487
487
|
rationale: "Cost efficiency is degrading or retries are exhausted. Downgrade the model.",
|
|
488
|
-
intervention: "
|
|
488
|
+
intervention: "compress_context"
|
|
489
489
|
};
|
|
490
490
|
}
|
|
491
491
|
return {
|