@martinloop/mcp 0.3.9 → 0.5.1
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 +82 -141
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.js +62 -98
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +16 -11
- package/dist/server.d.ts +23 -0
- package/dist/server.js +192 -15
- 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.d.ts +2 -0
- package/dist/tools/preflight.js +43 -3
- package/dist/tools/run-dossier.d.ts +3 -1
- package/dist/tools/run-dossier.js +40 -2
- package/dist/tools/run-loop.d.ts +5 -3
- package/dist/tools/run-loop.js +15 -9
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-response.js +9 -2
- package/dist/tools/tool-support.d.ts +4 -2
- package/dist/tools/tool-support.js +6 -15
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +97 -69
- package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
- package/dist/vendor/adapters/cli-bridge.js +54 -13
- package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
- package/dist/vendor/adapters/codex-launcher.js +118 -19
- package/dist/vendor/adapters/index.d.ts +2 -4
- package/dist/vendor/adapters/index.js +2 -4
- package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
- package/dist/vendor/adapters/openai-compatible.js +80 -15
- package/dist/vendor/adapters/verifier-only.js +9 -4
- package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
- package/dist/vendor/contracts/context-handoff.d.ts +100 -0
- package/dist/vendor/contracts/context-handoff.js +11 -0
- package/dist/vendor/contracts/context-manifest.d.ts +195 -0
- package/dist/vendor/contracts/context-manifest.js +17 -0
- package/dist/vendor/contracts/context-shadow.d.ts +75 -0
- package/dist/vendor/contracts/context-shadow.js +8 -0
- package/dist/vendor/contracts/delivery.d.ts +47 -0
- package/dist/vendor/contracts/delivery.js +18 -0
- package/dist/vendor/contracts/exits.d.ts +126 -0
- package/dist/vendor/contracts/exits.js +32 -0
- package/dist/vendor/contracts/index.d.ts +55 -6
- package/dist/vendor/contracts/index.js +24 -0
- package/dist/vendor/contracts/mission.d.ts +102 -0
- package/dist/vendor/contracts/mission.js +64 -0
- package/dist/vendor/contracts/operator.d.ts +3 -1
- package/dist/vendor/contracts/operator.js +2 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +106 -0
- package/dist/vendor/contracts/verified-handoff.js +40 -0
- package/dist/vendor/core/context-chain-gate.d.ts +89 -0
- package/dist/vendor/core/context-chain-gate.js +173 -0
- package/dist/vendor/core/context-compiler.d.ts +53 -0
- package/dist/vendor/core/context-compiler.js +261 -0
- package/dist/vendor/core/context-handoff.d.ts +42 -0
- package/dist/vendor/core/context-handoff.js +167 -0
- package/dist/vendor/core/context-shadow.d.ts +39 -0
- package/dist/vendor/core/context-shadow.js +200 -0
- package/dist/vendor/core/delivery/index.d.ts +6 -0
- package/dist/vendor/core/delivery/index.js +4 -0
- package/dist/vendor/core/delivery/message-client.d.ts +18 -0
- package/dist/vendor/core/delivery/message-client.js +38 -0
- package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
- package/dist/vendor/core/delivery/message-ledger.js +97 -0
- package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
- package/dist/vendor/core/delivery/message-schema.js +111 -0
- package/dist/vendor/core/delivery/update-check.d.ts +19 -0
- package/dist/vendor/core/delivery/update-check.js +107 -0
- package/dist/vendor/core/exit-signal.d.ts +88 -0
- package/dist/vendor/core/exit-signal.js +339 -0
- package/dist/vendor/core/exits.d.ts +41 -0
- package/dist/vendor/core/exits.js +258 -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 +47 -6
- package/dist/vendor/core/index.js +1102 -670
- package/dist/vendor/core/mission/aggregate.d.ts +27 -0
- package/dist/vendor/core/mission/aggregate.js +42 -0
- package/dist/vendor/core/mission/index.d.ts +2 -0
- package/dist/vendor/core/mission/index.js +1 -0
- package/dist/vendor/core/persistence/compiler.d.ts +8 -0
- package/dist/vendor/core/persistence/compiler.js +156 -0
- package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
- package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
- package/dist/vendor/core/persistence/index.d.ts +4 -0
- package/dist/vendor/core/persistence/index.js +2 -0
- package/dist/vendor/core/persistence/ledger.d.ts +1 -1
- package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
- package/dist/vendor/core/persistence/mission-store.js +281 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
- package/dist/vendor/core/policy.d.ts +2 -0
- package/dist/vendor/core/policy.js +11 -8
- 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/rollback.js +15 -1
- package/dist/vendor/core/routing.d.ts +0 -42
- package/dist/vendor/core/routing.js +4 -154
- package/dist/vendor/core/savings.d.ts +13 -0
- package/dist/vendor/core/savings.js +22 -0
- package/dist/vendor/core/termination-store.d.ts +15 -0
- package/dist/vendor/core/termination-store.js +44 -0
- package/dist/vendor/core/verified-handoff.d.ts +72 -0
- package/dist/vendor/core/verified-handoff.js +238 -0
- package/package.json +4 -10
- package/server.json +3 -3
- package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/stub-direct-provider.js +0 -12
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eight-Exit Runtime — pure deterministic evaluator.
|
|
3
|
+
*
|
|
4
|
+
* EXIT_PRECEDENCE rationale (product decision — see contracts/exits.ts for rationale;
|
|
5
|
+
* any change to this array requires updating that paragraph in the same commit):
|
|
6
|
+
* human_interrupt > external_event > wall_clock > budget_cap > turn_cap >
|
|
7
|
+
* goal_met > error_threshold > no_progress
|
|
8
|
+
*/
|
|
9
|
+
import type { ExitEvaluationV1, ExitPolicyV1, ExitSnapshotV1, LoopBudget, LoopLifecycleState, LoopStatus } from "../contracts/index.js";
|
|
10
|
+
export interface LegacyExitDecision {
|
|
11
|
+
shouldExit: boolean;
|
|
12
|
+
lifecycleState: LoopLifecycleState;
|
|
13
|
+
status: LoopStatus;
|
|
14
|
+
reason: string;
|
|
15
|
+
reasonCode?: string;
|
|
16
|
+
failureClass?: string;
|
|
17
|
+
safetySurface?: string;
|
|
18
|
+
exitEvaluation: ExitEvaluationV1;
|
|
19
|
+
}
|
|
20
|
+
export interface ExitPolicyOverrides {
|
|
21
|
+
goal?: Partial<ExitPolicyV1["goal"]>;
|
|
22
|
+
turns?: Partial<ExitPolicyV1["turns"]>;
|
|
23
|
+
budget?: Partial<ExitPolicyV1["budget"]>;
|
|
24
|
+
wallClock?: Partial<ExitPolicyV1["wallClock"]>;
|
|
25
|
+
progress?: Partial<ExitPolicyV1["progress"]>;
|
|
26
|
+
errors?: Partial<ExitPolicyV1["errors"]>;
|
|
27
|
+
humanInterrupt?: Partial<ExitPolicyV1["humanInterrupt"]>;
|
|
28
|
+
externalEvent?: Partial<ExitPolicyV1["externalEvent"]>;
|
|
29
|
+
}
|
|
30
|
+
export declare function createDefaultExitPolicy(budget: LoopBudget, overrides?: ExitPolicyOverrides): ExitPolicyV1;
|
|
31
|
+
export declare function validateExitPolicy(policy: ExitPolicyV1): void;
|
|
32
|
+
export declare function evaluateExitPolicy(policy: ExitPolicyV1, snapshot: ExitSnapshotV1): ExitEvaluationV1;
|
|
33
|
+
export declare function toLegacyExitDecision(evaluation: ExitEvaluationV1, externalDisposition?: "satisfied" | "superseded" | "cancelled"): LegacyExitDecision;
|
|
34
|
+
/**
|
|
35
|
+
* Hash the meaningful workspace/verification state for no-progress detection.
|
|
36
|
+
* Excludes timestamps, cost counters, prose summaries — only structural changes count.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hashProgressState(value: unknown): string;
|
|
39
|
+
/** @deprecated Use evaluateExitPolicy instead. Retained for downstream compatibility. */
|
|
40
|
+
export { inferExitCompat as inferExitCompat };
|
|
41
|
+
declare function inferExitCompat(_unused: unknown): never;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eight-Exit Runtime — pure deterministic evaluator.
|
|
3
|
+
*
|
|
4
|
+
* EXIT_PRECEDENCE rationale (product decision — see contracts/exits.ts for rationale;
|
|
5
|
+
* any change to this array requires updating that paragraph in the same commit):
|
|
6
|
+
* human_interrupt > external_event > wall_clock > budget_cap > turn_cap >
|
|
7
|
+
* goal_met > error_threshold > no_progress
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
// Precedence: human authority first, resource limits before goal success,
|
|
11
|
+
// resource exits ranked by urgency (wall_clock > budget_cap > turn_cap).
|
|
12
|
+
// Note: turn_cap is suppressed at signal-generation time when goalMet is true
|
|
13
|
+
// so goal_met and turn_cap cannot co-occur in practice.
|
|
14
|
+
const EXIT_PRECEDENCE = [
|
|
15
|
+
"human_interrupt",
|
|
16
|
+
"external_event",
|
|
17
|
+
"wall_clock",
|
|
18
|
+
"budget_cap",
|
|
19
|
+
"turn_cap",
|
|
20
|
+
"goal_met",
|
|
21
|
+
"error_threshold",
|
|
22
|
+
"no_progress"
|
|
23
|
+
];
|
|
24
|
+
export function createDefaultExitPolicy(budget, overrides = {}) {
|
|
25
|
+
const base = {
|
|
26
|
+
schemaVersion: "exit-policy/1",
|
|
27
|
+
goal: { verifierRequired: true, minimumScore: 1 },
|
|
28
|
+
turns: { max: budget.maxIterations },
|
|
29
|
+
budget: { maxUsd: budget.maxUsd, maxTokens: budget.maxTokens },
|
|
30
|
+
wallClock: { maxElapsedMs: 30 * 60 * 1000 },
|
|
31
|
+
progress: { windowSize: 3, unchangedStateLimit: 3 },
|
|
32
|
+
errors: { maxConsecutive: 3 },
|
|
33
|
+
humanInterrupt: { enabled: true },
|
|
34
|
+
externalEvent: { enabled: true }
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
...overrides,
|
|
39
|
+
goal: { ...base.goal, ...overrides.goal },
|
|
40
|
+
turns: { ...base.turns, ...overrides.turns },
|
|
41
|
+
budget: { ...base.budget, ...overrides.budget },
|
|
42
|
+
wallClock: { ...base.wallClock, ...overrides.wallClock },
|
|
43
|
+
progress: { ...base.progress, ...overrides.progress },
|
|
44
|
+
errors: { ...base.errors, ...overrides.errors },
|
|
45
|
+
humanInterrupt: { ...base.humanInterrupt, ...overrides.humanInterrupt },
|
|
46
|
+
externalEvent: { ...base.externalEvent, ...overrides.externalEvent }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function validateExitPolicy(policy) {
|
|
50
|
+
assertFinitePositive("turns.max", policy.turns.max);
|
|
51
|
+
assertFinitePositive("budget.maxUsd", policy.budget.maxUsd);
|
|
52
|
+
assertFinitePositive("budget.maxTokens", policy.budget.maxTokens);
|
|
53
|
+
assertFinitePositive("wallClock.maxElapsedMs", policy.wallClock.maxElapsedMs);
|
|
54
|
+
assertFinitePositive("progress.windowSize", policy.progress.windowSize);
|
|
55
|
+
assertFinitePositive("progress.unchangedStateLimit", policy.progress.unchangedStateLimit);
|
|
56
|
+
assertFinitePositive("errors.maxConsecutive", policy.errors.maxConsecutive);
|
|
57
|
+
if (policy.goal.minimumScore < 0 || policy.goal.minimumScore > 1) {
|
|
58
|
+
throw new RangeError("goal.minimumScore must be between 0 and 1");
|
|
59
|
+
}
|
|
60
|
+
if (policy.progress.unchangedStateLimit > policy.progress.windowSize) {
|
|
61
|
+
throw new RangeError("progress.unchangedStateLimit cannot exceed progress.windowSize");
|
|
62
|
+
}
|
|
63
|
+
if (policy.wallClock.deadlineAt !== undefined) {
|
|
64
|
+
const parsed = Date.parse(policy.wallClock.deadlineAt);
|
|
65
|
+
if (!Number.isFinite(parsed)) {
|
|
66
|
+
throw new RangeError("wallClock.deadlineAt must be an ISO timestamp");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function evaluateExitPolicy(policy, snapshot) {
|
|
71
|
+
validateExitPolicy(policy);
|
|
72
|
+
const matches = [];
|
|
73
|
+
const push = (kind, reason, evidence) => {
|
|
74
|
+
matches.push({ kind, reason, evidence });
|
|
75
|
+
};
|
|
76
|
+
// 1. Human interrupt (highest authority — cannot be argued with)
|
|
77
|
+
if (policy.humanInterrupt.enabled && snapshot.humanInterrupt !== undefined) {
|
|
78
|
+
push("human_interrupt", snapshot.humanInterrupt.reason ?? "Human interrupt requested.", {
|
|
79
|
+
requestedAt: snapshot.humanInterrupt.requestedAt,
|
|
80
|
+
requestedBy: snapshot.humanInterrupt.requestedBy
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// 2. External event
|
|
84
|
+
if (policy.externalEvent.enabled && snapshot.externalEvent !== undefined) {
|
|
85
|
+
push("external_event", snapshot.externalEvent.reason ?? "External terminal event observed.", {
|
|
86
|
+
source: snapshot.externalEvent.source,
|
|
87
|
+
event: snapshot.externalEvent.event,
|
|
88
|
+
disposition: snapshot.externalEvent.disposition,
|
|
89
|
+
subject: snapshot.externalEvent.subject,
|
|
90
|
+
evidenceUri: snapshot.externalEvent.evidenceUri
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// 3. Goal met (verified success outranks resource exhaustion)
|
|
94
|
+
const result = snapshot.result;
|
|
95
|
+
const goalMet = result !== undefined &&
|
|
96
|
+
result.status === "completed" &&
|
|
97
|
+
(!policy.goal.verifierRequired || result.verificationPassed) &&
|
|
98
|
+
result.verifierScore >= policy.goal.minimumScore;
|
|
99
|
+
if (goalMet) {
|
|
100
|
+
push("goal_met", "Configured verification goal passed.", {
|
|
101
|
+
verifierScore: result.verifierScore,
|
|
102
|
+
minimumScore: policy.goal.minimumScore
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// 4. Wall clock
|
|
106
|
+
const elapsedMs = Math.max(0, snapshot.nowMs - snapshot.runStartedAtMs);
|
|
107
|
+
const deadlineMs = policy.wallClock.deadlineAt === undefined
|
|
108
|
+
? undefined
|
|
109
|
+
: Date.parse(policy.wallClock.deadlineAt);
|
|
110
|
+
if (elapsedMs >= policy.wallClock.maxElapsedMs ||
|
|
111
|
+
(deadlineMs !== undefined && snapshot.nowMs >= deadlineMs)) {
|
|
112
|
+
push("wall_clock", "Run wall-clock limit reached.", {
|
|
113
|
+
elapsedMs,
|
|
114
|
+
maxElapsedMs: policy.wallClock.maxElapsedMs,
|
|
115
|
+
deadlineAt: policy.wallClock.deadlineAt
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// 5. Budget cap
|
|
119
|
+
const usdExceeded = snapshot.actualUsd >= policy.budget.maxUsd;
|
|
120
|
+
const tokensExceeded = snapshot.tokensUsed >= policy.budget.maxTokens;
|
|
121
|
+
if (usdExceeded || tokensExceeded) {
|
|
122
|
+
push("budget_cap", "Run token or dollar budget reached.", {
|
|
123
|
+
actualUsd: snapshot.actualUsd,
|
|
124
|
+
maxUsd: policy.budget.maxUsd,
|
|
125
|
+
tokensUsed: snapshot.tokensUsed,
|
|
126
|
+
maxTokens: policy.budget.maxTokens,
|
|
127
|
+
usdExceeded,
|
|
128
|
+
tokensExceeded
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// 6. Turn cap — suppressed when goalMet: completing on the final allowed
|
|
132
|
+
// iteration is legitimate success, not an overrun. turn_cap fires only when
|
|
133
|
+
// iterations are exhausted without verified completion.
|
|
134
|
+
if (!goalMet && snapshot.turnsUsed >= policy.turns.max) {
|
|
135
|
+
push("turn_cap", "Run iteration limit reached.", {
|
|
136
|
+
turnsUsed: snapshot.turnsUsed,
|
|
137
|
+
maxTurns: policy.turns.max
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// 7. Error threshold
|
|
141
|
+
if (snapshot.consecutiveErrors >= policy.errors.maxConsecutive) {
|
|
142
|
+
push("error_threshold", "Consecutive error threshold reached.", {
|
|
143
|
+
consecutiveErrors: snapshot.consecutiveErrors,
|
|
144
|
+
maxConsecutive: policy.errors.maxConsecutive
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// 8. No progress — only fires when the window is FULL (A3 fix: sub-window must not fire)
|
|
148
|
+
const requiredHashes = policy.progress.unchangedStateLimit;
|
|
149
|
+
const recentHashes = snapshot.recentStateHashes.slice(-requiredHashes);
|
|
150
|
+
const hashStall = recentHashes.length === requiredHashes && new Set(recentHashes).size === 1;
|
|
151
|
+
if (hashStall || snapshot.trajectoryStop?.shouldStop === true) {
|
|
152
|
+
push("no_progress", snapshot.trajectoryStop?.reason ?? "Canonical run state stopped changing.", {
|
|
153
|
+
unchangedStateCount: hashStall ? recentHashes.length : 0,
|
|
154
|
+
stateHash: hashStall ? recentHashes[0] : undefined,
|
|
155
|
+
trajectoryStop: snapshot.trajectoryStop?.shouldStop ?? false
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const matched = EXIT_PRECEDENCE.filter((kind) => matches.some((m) => m.kind === kind));
|
|
159
|
+
return {
|
|
160
|
+
schemaVersion: "exit-evaluation/1",
|
|
161
|
+
policyVersion: policy.schemaVersion,
|
|
162
|
+
shouldExit: matched.length > 0,
|
|
163
|
+
...(matched[0] === undefined ? {} : { primary: matched[0] }),
|
|
164
|
+
matched,
|
|
165
|
+
phase: snapshot.phase,
|
|
166
|
+
evaluatedAt: snapshot.evaluatedAt,
|
|
167
|
+
matches
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function toLegacyExitDecision(evaluation, externalDisposition) {
|
|
171
|
+
const primary = evaluation.primary;
|
|
172
|
+
if (primary === undefined) {
|
|
173
|
+
return {
|
|
174
|
+
shouldExit: false,
|
|
175
|
+
lifecycleState: "running",
|
|
176
|
+
status: "running",
|
|
177
|
+
reason: "No exit condition matched.",
|
|
178
|
+
exitEvaluation: evaluation
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const reason = evaluation.matches.find((m) => m.kind === primary)?.reason ??
|
|
182
|
+
`MartinLoop exited because ${primary} fired.`;
|
|
183
|
+
if (primary === "goal_met") {
|
|
184
|
+
return completed(reason, primary, evaluation);
|
|
185
|
+
}
|
|
186
|
+
if (primary === "external_event" && externalDisposition === "satisfied") {
|
|
187
|
+
return completed(reason, primary, evaluation);
|
|
188
|
+
}
|
|
189
|
+
if (primary === "human_interrupt") {
|
|
190
|
+
return exited("human_escalation", reason, primary, evaluation);
|
|
191
|
+
}
|
|
192
|
+
if (primary === "budget_cap" || primary === "turn_cap") {
|
|
193
|
+
return exited("budget_exit", reason, primary, evaluation);
|
|
194
|
+
}
|
|
195
|
+
if (primary === "no_progress") {
|
|
196
|
+
return exited("diminishing_returns", reason, primary, evaluation);
|
|
197
|
+
}
|
|
198
|
+
if (primary === "error_threshold") {
|
|
199
|
+
return exited("error_threshold", reason, primary, evaluation);
|
|
200
|
+
}
|
|
201
|
+
if (primary === "wall_clock") {
|
|
202
|
+
return exited("wall_clock", reason, primary, evaluation);
|
|
203
|
+
}
|
|
204
|
+
if (primary === "external_event") {
|
|
205
|
+
return exited("external_event", reason, primary, evaluation);
|
|
206
|
+
}
|
|
207
|
+
return exited("stuck_exit", reason, primary, evaluation);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Hash the meaningful workspace/verification state for no-progress detection.
|
|
211
|
+
* Excludes timestamps, cost counters, prose summaries — only structural changes count.
|
|
212
|
+
*/
|
|
213
|
+
export function hashProgressState(value) {
|
|
214
|
+
return createHash("sha256").update(stableJson(value)).digest("hex");
|
|
215
|
+
}
|
|
216
|
+
function stableJson(value) {
|
|
217
|
+
if (value === null || typeof value !== "object") {
|
|
218
|
+
return JSON.stringify(value) ?? "null";
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(value)) {
|
|
221
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
222
|
+
}
|
|
223
|
+
const obj = value;
|
|
224
|
+
return `{${Object.keys(obj)
|
|
225
|
+
.sort()
|
|
226
|
+
.map((k) => `${JSON.stringify(k)}:${stableJson(obj[k])}`)
|
|
227
|
+
.join(",")}}`;
|
|
228
|
+
}
|
|
229
|
+
function completed(reason, primary, evaluation) {
|
|
230
|
+
return {
|
|
231
|
+
shouldExit: true,
|
|
232
|
+
lifecycleState: "completed",
|
|
233
|
+
status: "completed",
|
|
234
|
+
reason,
|
|
235
|
+
reasonCode: primary,
|
|
236
|
+
exitEvaluation: evaluation
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function exited(lifecycleState, reason, primary, evaluation) {
|
|
240
|
+
return {
|
|
241
|
+
shouldExit: true,
|
|
242
|
+
lifecycleState,
|
|
243
|
+
status: "exited",
|
|
244
|
+
reason,
|
|
245
|
+
reasonCode: primary,
|
|
246
|
+
exitEvaluation: evaluation
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function assertFinitePositive(name, value) {
|
|
250
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
251
|
+
throw new RangeError(`${name} must be a finite number greater than zero`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** @deprecated Use evaluateExitPolicy instead. Retained for downstream compatibility. */
|
|
255
|
+
export { inferExitCompat as inferExitCompat };
|
|
256
|
+
function inferExitCompat(_unused) {
|
|
257
|
+
throw new Error("inferExitCompat is a compatibility stub — call evaluateExitPolicy directly");
|
|
258
|
+
}
|
|
@@ -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)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { type ApprovalPolicy, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
|
|
1
|
+
import { type ApprovalPolicy, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
|
|
2
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
|
|
3
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
4
4
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
5
5
|
import { captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
6
6
|
import { type RunStore } from "./persistence/index.js";
|
|
7
|
+
import { type ExitPolicyOverrides } from "./exits.js";
|
|
8
|
+
import { type ExitSignalSource } from "./exit-signal.js";
|
|
9
|
+
import { type VerifierExecutionBinding } from "./verified-handoff.js";
|
|
7
10
|
export type { ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
|
|
8
11
|
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
9
12
|
export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
|
|
@@ -14,8 +17,10 @@ export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, ver
|
|
|
14
17
|
export type { LocalIdentityAuthority } from "./identity.js";
|
|
15
18
|
export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
16
19
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
17
|
-
export {
|
|
18
|
-
export type {
|
|
20
|
+
export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
21
|
+
export type { AvoidedUsdInput } from "./savings.js";
|
|
22
|
+
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
23
|
+
export type { RouteDecision, RouteClassificationInput } from "./routing.js";
|
|
19
24
|
export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
|
|
20
25
|
export type { TestIntegrityReport, TestIntegrityFinding } from "./test-integrity.js";
|
|
21
26
|
export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
|
|
@@ -24,16 +29,27 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
24
29
|
export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
|
|
25
30
|
export { compilePromptPacket } from "./compiler.js";
|
|
26
31
|
export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
|
|
32
|
+
export { buildVerifiedHandoff, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
|
|
33
|
+
export type { BoundVerifierEvidence, BuildVerifiedHandoffInput, VerifierExecutionBinding, } from "./verified-handoff.js";
|
|
27
34
|
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
28
35
|
export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
29
36
|
export { compileAndPersistContext } from "./persistence/index.js";
|
|
30
37
|
export type { CompileResult } from "./persistence/index.js";
|
|
38
|
+
export { compileContextShadow, estimateContextTokens } from "./context-shadow.js";
|
|
39
|
+
export type { CompileContextShadowInput, CompileContextShadowResult, ContextShadowSegment } from "./context-shadow.js";
|
|
40
|
+
export { compileContext, HEURISTIC_ADAPTER, MAX_RENDER_PASSES } from "./context-compiler.js";
|
|
41
|
+
export type { CompileContextInput, CompileContextOutput, ContextAdapter } from "./context-compiler.js";
|
|
42
|
+
export { verifyContextHandoff, decideContextCircuitBreak } from "./context-handoff.js";
|
|
43
|
+
export type { VerifyContextHandoffInput } from "./context-handoff.js";
|
|
44
|
+
export { evaluateChainGate, renderGatePrComment } from "./context-chain-gate.js";
|
|
45
|
+
export type { ChainGateConfig, ChainGateCost, ChainGateConclusion, ChainGateInput, ChainGateResult, GatePrCommentOptions } from "./context-chain-gate.js";
|
|
31
46
|
export { appendMemory, readMemoryEntries, getPreference, buildMemorySummary, recordPreference, recordConsent } from "./persistence/memory-store.js";
|
|
32
47
|
export type { MemoryEntry, MemoryKind, MemorySummary } from "./persistence/memory-store.js";
|
|
33
48
|
export { appendTraceEntry, readTraceEntries, aggregateTraces, getHistoricalDirectSuccessRate } from "./persistence/trace-store.js";
|
|
34
49
|
export type { TraceEntry, TraceAggregation } from "./persistence/trace-store.js";
|
|
35
50
|
export interface MartinAdapterRequest {
|
|
36
51
|
loopId: string;
|
|
52
|
+
workspaceId: string;
|
|
37
53
|
attemptId: string;
|
|
38
54
|
context: {
|
|
39
55
|
taskTitle: string;
|
|
@@ -41,6 +57,7 @@ export interface MartinAdapterRequest {
|
|
|
41
57
|
verificationPlan: string[];
|
|
42
58
|
verificationStack?: LoopTask["verificationStack"];
|
|
43
59
|
mutationMode?: MutationMode;
|
|
60
|
+
definitionOfDonePreSatisfied?: boolean;
|
|
44
61
|
/** Absolute path to the repository root. */
|
|
45
62
|
repoRoot?: string;
|
|
46
63
|
/** Glob patterns for files the agent may modify. Empty = no restriction. */
|
|
@@ -58,10 +75,14 @@ export interface MartinAdapterRequest {
|
|
|
58
75
|
remainingTokens: number;
|
|
59
76
|
};
|
|
60
77
|
previousAttempts: LoopAttempt[];
|
|
78
|
+
/** Abort signal propagated from the harness — adapters should honour it. */
|
|
79
|
+
signal?: AbortSignal;
|
|
61
80
|
}
|
|
62
81
|
export interface MartinVerificationStep {
|
|
63
82
|
command: string;
|
|
64
83
|
launched: boolean;
|
|
84
|
+
completed?: boolean;
|
|
85
|
+
crashed?: boolean;
|
|
65
86
|
exitCode?: number;
|
|
66
87
|
timedOut: boolean;
|
|
67
88
|
fastFail?: boolean;
|
|
@@ -72,6 +93,7 @@ export interface MartinVerificationOutcome {
|
|
|
72
93
|
summary: string;
|
|
73
94
|
steps?: MartinVerificationStep[];
|
|
74
95
|
warnings?: string[];
|
|
96
|
+
binding?: VerifierExecutionBinding;
|
|
75
97
|
}
|
|
76
98
|
export interface MartinAdapterResult {
|
|
77
99
|
status: "completed" | "failed";
|
|
@@ -91,6 +113,7 @@ export interface MartinAdapterResult {
|
|
|
91
113
|
summary: string;
|
|
92
114
|
steps?: MartinVerificationStep[];
|
|
93
115
|
warnings?: string[];
|
|
116
|
+
binding?: VerifierExecutionBinding;
|
|
94
117
|
};
|
|
95
118
|
execution?: {
|
|
96
119
|
changedFiles?: string[];
|
|
@@ -119,7 +142,7 @@ export interface MartinAdapter {
|
|
|
119
142
|
label: string;
|
|
120
143
|
metadata: {
|
|
121
144
|
providerId: string;
|
|
122
|
-
model
|
|
145
|
+
model?: string;
|
|
123
146
|
transport?: "cli" | "http" | "routed_http";
|
|
124
147
|
capabilities?: {
|
|
125
148
|
preflight?: boolean;
|
|
@@ -132,7 +155,6 @@ export interface MartinAdapter {
|
|
|
132
155
|
[key: string]: unknown;
|
|
133
156
|
};
|
|
134
157
|
execute(request: MartinAdapterRequest): Promise<MartinAdapterResult>;
|
|
135
|
-
withModel?(model: string): MartinAdapter;
|
|
136
158
|
}
|
|
137
159
|
export interface DistilledContext {
|
|
138
160
|
focus: string;
|
|
@@ -170,11 +192,24 @@ export interface RunMartinInput {
|
|
|
170
192
|
now?: () => string;
|
|
171
193
|
idFactory?: (prefix: string) => string;
|
|
172
194
|
maxRecentAttempts?: number;
|
|
173
|
-
fallbackModels?: string[];
|
|
174
195
|
fallbackAdapters?: MartinAdapter[];
|
|
175
196
|
receiptScope?: ReceiptScope;
|
|
176
197
|
/** Optional persistence store. When provided, runMartin writes artifacts on each lifecycle event. */
|
|
177
198
|
store?: RunStore;
|
|
199
|
+
/** Overrides for the default eight-exit policy derived from budget. */
|
|
200
|
+
exitPolicy?: ExitPolicyOverrides;
|
|
201
|
+
/** Source for durable cancellation/external-event signals. */
|
|
202
|
+
exitSignalSource?: ExitSignalSource;
|
|
203
|
+
/** Poll interval for the signal monitor in ms (default 250). */
|
|
204
|
+
exitSignalPollIntervalMs?: number;
|
|
205
|
+
/** Clock override for testing (default Date.now). */
|
|
206
|
+
nowMs?: () => number;
|
|
207
|
+
/** When provided, runMartin verifies the handoff before invoking the adapter. */
|
|
208
|
+
contextHandoff?: ContextHandoffReceipt;
|
|
209
|
+
/** true when the producer receipt file hash has been independently confirmed. */
|
|
210
|
+
producerReceiptVerified?: boolean;
|
|
211
|
+
/** Map of sha256 → true for every artifact available to the verifier. */
|
|
212
|
+
availableArtifacts?: ReadonlyMap<string, true>;
|
|
178
213
|
}
|
|
179
214
|
export interface RunMartinResult {
|
|
180
215
|
loop: LoopRecord;
|
|
@@ -184,3 +219,9 @@ export declare function distillContext(loop: Pick<LoopRecord, "task" | "budget"
|
|
|
184
219
|
maxRecentAttempts?: number;
|
|
185
220
|
}): DistilledContext;
|
|
186
221
|
export declare function runMartin(input: RunMartinInput): Promise<RunMartinResult>;
|
|
222
|
+
export { createDefaultExitPolicy, evaluateExitPolicy, hashProgressState, toLegacyExitDecision, validateExitPolicy } from "./exits.js";
|
|
223
|
+
export type { ExitPolicyOverrides, LegacyExitDecision } from "./exits.js";
|
|
224
|
+
export { SignalDiagnosticError, createFileExitSignalSource, exitSignalPath, readAllExitSignals, readExitSignal, startExitSignalMonitor, writeExitSignal } from "./exit-signal.js";
|
|
225
|
+
export type { ExitSignalSource, SignalDiagnostic, SignalReadResult } from "./exit-signal.js";
|
|
226
|
+
export { cacheMessage, fetchSelectedMessage, getCliInstalledVersion, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, parseMessageSelectionResponse, recordDismissed, recordShown, resolveDefaultLedgerPath, saveDeliveryRecord, } from "./delivery/index.js";
|
|
227
|
+
export type { MessageClientOptions, MessageSelectRequest, ParseFailure, ParseResult, ValidationError, } from "./delivery/index.js";
|