@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.
Files changed (113) hide show
  1. package/README.md +82 -141
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +1 -1
  4. package/dist/resources.d.ts +1 -2
  5. package/dist/resources.js +62 -98
  6. package/dist/server-validation.d.ts +1 -1
  7. package/dist/server-validation.js +16 -11
  8. package/dist/server.d.ts +23 -0
  9. package/dist/server.js +192 -15
  10. package/dist/tools/doctor.js +5 -4
  11. package/dist/tools/get-status.d.ts +2 -0
  12. package/dist/tools/get-status.js +1 -0
  13. package/dist/tools/preflight.d.ts +2 -0
  14. package/dist/tools/preflight.js +43 -3
  15. package/dist/tools/run-dossier.d.ts +3 -1
  16. package/dist/tools/run-dossier.js +40 -2
  17. package/dist/tools/run-loop.d.ts +5 -3
  18. package/dist/tools/run-loop.js +15 -9
  19. package/dist/tools/tool-errors.js +1 -1
  20. package/dist/tools/tool-response.js +9 -2
  21. package/dist/tools/tool-support.d.ts +4 -2
  22. package/dist/tools/tool-support.js +6 -15
  23. package/dist/vendor/adapters/claude-cli.d.ts +2 -2
  24. package/dist/vendor/adapters/claude-cli.js +97 -69
  25. package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
  26. package/dist/vendor/adapters/cli-bridge.js +54 -13
  27. package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
  28. package/dist/vendor/adapters/codex-launcher.js +118 -19
  29. package/dist/vendor/adapters/index.d.ts +2 -4
  30. package/dist/vendor/adapters/index.js +2 -4
  31. package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
  32. package/dist/vendor/adapters/openai-compatible.js +80 -15
  33. package/dist/vendor/adapters/verifier-only.js +9 -4
  34. package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
  35. package/dist/vendor/contracts/context-handoff.d.ts +100 -0
  36. package/dist/vendor/contracts/context-handoff.js +11 -0
  37. package/dist/vendor/contracts/context-manifest.d.ts +195 -0
  38. package/dist/vendor/contracts/context-manifest.js +17 -0
  39. package/dist/vendor/contracts/context-shadow.d.ts +75 -0
  40. package/dist/vendor/contracts/context-shadow.js +8 -0
  41. package/dist/vendor/contracts/delivery.d.ts +47 -0
  42. package/dist/vendor/contracts/delivery.js +18 -0
  43. package/dist/vendor/contracts/exits.d.ts +126 -0
  44. package/dist/vendor/contracts/exits.js +32 -0
  45. package/dist/vendor/contracts/index.d.ts +55 -6
  46. package/dist/vendor/contracts/index.js +24 -0
  47. package/dist/vendor/contracts/mission.d.ts +102 -0
  48. package/dist/vendor/contracts/mission.js +64 -0
  49. package/dist/vendor/contracts/operator.d.ts +3 -1
  50. package/dist/vendor/contracts/operator.js +2 -1
  51. package/dist/vendor/contracts/verified-handoff.d.ts +106 -0
  52. package/dist/vendor/contracts/verified-handoff.js +40 -0
  53. package/dist/vendor/core/context-chain-gate.d.ts +89 -0
  54. package/dist/vendor/core/context-chain-gate.js +173 -0
  55. package/dist/vendor/core/context-compiler.d.ts +53 -0
  56. package/dist/vendor/core/context-compiler.js +261 -0
  57. package/dist/vendor/core/context-handoff.d.ts +42 -0
  58. package/dist/vendor/core/context-handoff.js +167 -0
  59. package/dist/vendor/core/context-shadow.d.ts +39 -0
  60. package/dist/vendor/core/context-shadow.js +200 -0
  61. package/dist/vendor/core/delivery/index.d.ts +6 -0
  62. package/dist/vendor/core/delivery/index.js +4 -0
  63. package/dist/vendor/core/delivery/message-client.d.ts +18 -0
  64. package/dist/vendor/core/delivery/message-client.js +38 -0
  65. package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
  66. package/dist/vendor/core/delivery/message-ledger.js +97 -0
  67. package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
  68. package/dist/vendor/core/delivery/message-schema.js +111 -0
  69. package/dist/vendor/core/delivery/update-check.d.ts +19 -0
  70. package/dist/vendor/core/delivery/update-check.js +107 -0
  71. package/dist/vendor/core/exit-signal.d.ts +88 -0
  72. package/dist/vendor/core/exit-signal.js +339 -0
  73. package/dist/vendor/core/exits.d.ts +41 -0
  74. package/dist/vendor/core/exits.js +258 -0
  75. package/dist/vendor/core/grounding.d.ts +2 -1
  76. package/dist/vendor/core/grounding.js +40 -5
  77. package/dist/vendor/core/index.d.ts +47 -6
  78. package/dist/vendor/core/index.js +1102 -670
  79. package/dist/vendor/core/mission/aggregate.d.ts +27 -0
  80. package/dist/vendor/core/mission/aggregate.js +42 -0
  81. package/dist/vendor/core/mission/index.d.ts +2 -0
  82. package/dist/vendor/core/mission/index.js +1 -0
  83. package/dist/vendor/core/persistence/compiler.d.ts +8 -0
  84. package/dist/vendor/core/persistence/compiler.js +156 -0
  85. package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
  86. package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
  87. package/dist/vendor/core/persistence/index.d.ts +4 -0
  88. package/dist/vendor/core/persistence/index.js +2 -0
  89. package/dist/vendor/core/persistence/ledger.d.ts +1 -1
  90. package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
  91. package/dist/vendor/core/persistence/mission-store.js +281 -0
  92. package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
  93. package/dist/vendor/core/policy.d.ts +2 -0
  94. package/dist/vendor/core/policy.js +11 -8
  95. package/dist/vendor/core/red-blue/red-phase.d.ts +2 -1
  96. package/dist/vendor/core/red-blue/red-phase.js +3 -3
  97. package/dist/vendor/core/red-blue/risk-tiers.d.ts +2 -4
  98. package/dist/vendor/core/red-blue/risk-tiers.js +1 -3
  99. package/dist/vendor/core/rollback.js +15 -1
  100. package/dist/vendor/core/routing.d.ts +0 -42
  101. package/dist/vendor/core/routing.js +4 -154
  102. package/dist/vendor/core/savings.d.ts +13 -0
  103. package/dist/vendor/core/savings.js +22 -0
  104. package/dist/vendor/core/termination-store.d.ts +15 -0
  105. package/dist/vendor/core/termination-store.js +44 -0
  106. package/dist/vendor/core/verified-handoff.d.ts +72 -0
  107. package/dist/vendor/core/verified-handoff.js +238 -0
  108. package/package.json +4 -10
  109. package/server.json +3 -3
  110. package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
  111. package/dist/vendor/adapters/stub-agent-cli.js +0 -40
  112. package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
  113. 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 { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
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 { classifyRoute, evaluatePreworkBurnPolicy, resolveModelForTier, selectBestEngine } from "./routing.js";
18
- export type { RouteDecision, RouteClassificationInput, AvailableEngine } from "./routing.js";
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: string;
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";