martin-loop 0.1.2 → 0.1.4

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -189
  3. package/dist/bin/martin-loop.js +23 -0
  4. package/dist/index.d.ts +22 -0
  5. package/dist/index.js +31 -0
  6. package/dist/vendor/adapters/claude-cli.d.ts +89 -0
  7. package/dist/vendor/adapters/claude-cli.js +555 -0
  8. package/dist/vendor/adapters/cli-bridge.d.ts +28 -0
  9. package/dist/vendor/adapters/cli-bridge.js +127 -0
  10. package/dist/vendor/adapters/direct-provider.d.ts +10 -0
  11. package/dist/vendor/adapters/direct-provider.js +41 -0
  12. package/dist/vendor/adapters/index.d.ts +5 -0
  13. package/dist/vendor/adapters/index.js +5 -0
  14. package/dist/vendor/adapters/runtime-support.d.ts +14 -0
  15. package/dist/vendor/adapters/runtime-support.js +52 -0
  16. package/dist/vendor/adapters/stub-agent-cli.d.ts +8 -0
  17. package/dist/vendor/adapters/stub-agent-cli.js +41 -0
  18. package/dist/vendor/adapters/stub-direct-provider.d.ts +8 -0
  19. package/dist/vendor/adapters/stub-direct-provider.js +10 -0
  20. package/dist/vendor/cli/bin/martin.d.ts +2 -0
  21. package/dist/vendor/cli/bin/martin.js +19 -0
  22. package/dist/vendor/cli/index.d.ts +39 -0
  23. package/dist/vendor/cli/index.js +634 -0
  24. package/dist/vendor/cli/persistence.d.ts +34 -0
  25. package/dist/vendor/cli/persistence.js +71 -0
  26. package/dist/vendor/contracts/governance.d.ts +21 -0
  27. package/dist/vendor/contracts/governance.js +12 -0
  28. package/dist/vendor/contracts/index.d.ts +330 -0
  29. package/dist/vendor/contracts/index.js +203 -0
  30. package/dist/vendor/core/compiler.d.ts +50 -0
  31. package/dist/vendor/core/compiler.js +47 -0
  32. package/dist/vendor/core/grounding.d.ts +37 -0
  33. package/dist/vendor/core/grounding.js +270 -0
  34. package/dist/vendor/core/index.d.ts +145 -0
  35. package/dist/vendor/core/index.js +1099 -0
  36. package/dist/vendor/core/leash.d.ts +48 -0
  37. package/dist/vendor/core/leash.js +408 -0
  38. package/dist/vendor/core/persistence/compiler.d.ts +18 -0
  39. package/dist/vendor/core/persistence/compiler.js +35 -0
  40. package/dist/vendor/core/persistence/index.d.ts +6 -0
  41. package/dist/vendor/core/persistence/index.js +4 -0
  42. package/dist/vendor/core/persistence/ledger.d.ts +23 -0
  43. package/dist/vendor/core/persistence/ledger.js +10 -0
  44. package/dist/vendor/core/persistence/store.d.ts +77 -0
  45. package/dist/vendor/core/persistence/store.js +84 -0
  46. package/dist/vendor/core/policy.d.ts +126 -0
  47. package/dist/vendor/core/policy.js +625 -0
  48. package/dist/vendor/core/rollback.d.ts +11 -0
  49. package/dist/vendor/core/rollback.js +219 -0
  50. package/docs/oss/OSS-BOUNDARY-REPORT.json +1 -1
  51. package/docs/oss/OSS-BOUNDARY-REPORT.md +1 -1
  52. package/docs/oss/RELEASE-SURFACE-REPORT.json +1 -1
  53. package/docs/oss/RELEASE-SURFACE-REPORT.md +1 -1
  54. package/package.json +54 -54
@@ -0,0 +1,47 @@
1
+ import { redactSecretsFromText } from "./leash.js";
2
+ /**
3
+ * Compiles a deterministic PromptPacket from a MartinAdapterRequest.
4
+ * This is the context compiler — takes structured request state and produces
5
+ * a reconstructable packet (no chat history required).
6
+ */
7
+ export function compilePromptPacket(request) {
8
+ const priorFailurePatterns = request.previousAttempts
9
+ .filter((a) => a.failureClass && a.intervention)
10
+ .map((a) => `${a.failureClass}:${a.intervention}`);
11
+ const guidanceParts = [
12
+ "Only modify files directly required to satisfy the contract.",
13
+ "Do not touch files outside the allowed paths."
14
+ ];
15
+ if (request.context.allowedPaths && request.context.allowedPaths.length > 0) {
16
+ guidanceParts.push(`Allowed paths: ${request.context.allowedPaths.join(", ")}.`);
17
+ }
18
+ if (request.context.deniedPaths && request.context.deniedPaths.length > 0) {
19
+ guidanceParts.push(`Denied paths (never touch): ${request.context.deniedPaths.join(", ")}.`);
20
+ }
21
+ if (priorFailurePatterns.length > 0) {
22
+ guidanceParts.push(`Prior failure patterns: ${priorFailurePatterns.join(", ")}. Adjust strategy accordingly.`);
23
+ }
24
+ return {
25
+ loopId: request.loopId,
26
+ attemptNumber: request.previousAttempts.length + 1,
27
+ contract: {
28
+ objective: redactSecretsFromText(request.context.objective),
29
+ verificationPlan: request.context.verificationPlan,
30
+ ...(request.context.allowedPaths ? { allowedPaths: request.context.allowedPaths } : {}),
31
+ ...(request.context.deniedPaths ? { deniedPaths: request.context.deniedPaths } : {}),
32
+ ...(request.context.acceptanceCriteria
33
+ ? {
34
+ acceptanceCriteria: request.context.acceptanceCriteria.map((criterion) => redactSecretsFromText(criterion))
35
+ }
36
+ : {})
37
+ },
38
+ priorFailurePatterns,
39
+ guidance: guidanceParts.join(" "),
40
+ budgetEnvelope: {
41
+ remainingBudgetUsd: request.context.remainingBudgetUsd,
42
+ remainingIterations: request.context.remainingIterations,
43
+ remainingTokens: request.context.remainingTokens
44
+ }
45
+ };
46
+ }
47
+ //# sourceMappingURL=compiler.js.map
@@ -0,0 +1,37 @@
1
+ export interface RepoGroundingFile {
2
+ path: string;
3
+ symbols: string[];
4
+ keywords: string[];
5
+ }
6
+ export interface RepoGroundingIndex {
7
+ schemaVersion: "martin.grounding.v1";
8
+ repoRoot: string;
9
+ createdAt: string;
10
+ fileCount: number;
11
+ files: RepoGroundingFile[];
12
+ }
13
+ export interface RepoGroundingHit {
14
+ path: string;
15
+ score: number;
16
+ matchedTerms: string[];
17
+ symbols: string[];
18
+ }
19
+ export declare function loadOrBuildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
20
+ export declare function buildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
21
+ export declare function queryRepoGroundingIndex(index: RepoGroundingIndex, query: string, limit?: number): RepoGroundingHit[];
22
+ export type GroundingViolationKind = "file_not_found" | "symbol_not_found" | "import_not_found" | "patch_outside_allowed_paths";
23
+ export interface GroundingViolation {
24
+ kind: GroundingViolationKind;
25
+ reference: string;
26
+ sourceHint?: string;
27
+ }
28
+ export interface GroundingScanResult {
29
+ violations: GroundingViolation[];
30
+ scannedReferences: number;
31
+ resolvedFiles: string[];
32
+ /** True when the diff adds only comments, whitespace, or empty lines — no substantive code. */
33
+ contentOnly: boolean;
34
+ }
35
+ export declare function scanPatchForGroundingViolations(diff: string, index: RepoGroundingIndex, options?: {
36
+ allowedPaths?: string[];
37
+ }): GroundingScanResult;
@@ -0,0 +1,270 @@
1
+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { extname, join, relative } from "node:path";
4
+ const TEXT_EXTENSIONS = new Set([
5
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
6
+ ".json", ".md", ".yaml", ".yml", ".py", ".go",
7
+ ".rs", ".java", ".sh"
8
+ ]);
9
+ const IGNORED_DIRS = new Set([
10
+ ".git", "node_modules", ".next", "dist", "build",
11
+ ".turbo", "coverage", ".npm-cache", ".pnpm-store"
12
+ ]);
13
+ const MAX_FILE_BYTES = 64_000;
14
+ const MAX_FILES = 500;
15
+ export async function loadOrBuildRepoGroundingIndex(repoRoot) {
16
+ const cachePath = getGroundingCachePath(repoRoot);
17
+ try {
18
+ const cached = JSON.parse(await readFile(cachePath, "utf8"));
19
+ if (cached?.schemaVersion === "martin.grounding.v1") {
20
+ return cached;
21
+ }
22
+ }
23
+ catch { }
24
+ const index = await buildRepoGroundingIndex(repoRoot);
25
+ try {
26
+ await mkdir(join(homedir(), ".martin", "grounding"), { recursive: true });
27
+ await writeFile(cachePath, JSON.stringify(index, null, 2), "utf8");
28
+ }
29
+ catch {
30
+ // Cache persistence is best-effort; runtime grounding must still work even
31
+ // when the local filesystem blocks writes to ~/.martin/grounding.
32
+ }
33
+ return index;
34
+ }
35
+ export async function buildRepoGroundingIndex(repoRoot) {
36
+ const files = [];
37
+ const discovered = await walk(repoRoot, repoRoot, files, { count: 0 });
38
+ return {
39
+ schemaVersion: "martin.grounding.v1",
40
+ repoRoot,
41
+ createdAt: new Date().toISOString(),
42
+ fileCount: discovered.count,
43
+ files
44
+ };
45
+ }
46
+ export function queryRepoGroundingIndex(index, query, limit = 6) {
47
+ const terms = tokenize(query);
48
+ if (terms.length === 0)
49
+ return [];
50
+ return index.files
51
+ .map((file) => {
52
+ let score = 0;
53
+ const matched = new Set();
54
+ for (const term of terms) {
55
+ if (file.path.toLowerCase().includes(term)) {
56
+ score += 5;
57
+ matched.add(term);
58
+ }
59
+ if (file.keywords.some((keyword) => keyword === term)) {
60
+ score += 3;
61
+ matched.add(term);
62
+ }
63
+ if (file.symbols.some((symbol) => symbol.toLowerCase().includes(term))) {
64
+ score += 4;
65
+ matched.add(term);
66
+ }
67
+ }
68
+ if (score === 0)
69
+ return undefined;
70
+ return {
71
+ path: file.path,
72
+ score,
73
+ matchedTerms: [...matched],
74
+ symbols: file.symbols.slice(0, 5)
75
+ };
76
+ })
77
+ .filter((item) => Boolean(item))
78
+ .sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
79
+ .slice(0, limit);
80
+ }
81
+ function getGroundingCachePath(repoRoot) {
82
+ return join(homedir(), ".martin", "grounding", `${Buffer.from(repoRoot).toString("base64url")}.json`);
83
+ }
84
+ async function walk(repoRoot, currentDir, files, state) {
85
+ if (state.count >= MAX_FILES)
86
+ return state;
87
+ const entries = await readdir(currentDir, { withFileTypes: true });
88
+ for (const entry of entries) {
89
+ if (state.count >= MAX_FILES)
90
+ break;
91
+ const absPath = join(currentDir, entry.name);
92
+ if (entry.isDirectory()) {
93
+ if (!IGNORED_DIRS.has(entry.name)) {
94
+ await walk(repoRoot, absPath, files, state);
95
+ }
96
+ continue;
97
+ }
98
+ if (!entry.isFile())
99
+ continue;
100
+ if (!TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
101
+ continue;
102
+ try {
103
+ const content = await readFile(absPath, "utf8");
104
+ if (content.length > MAX_FILE_BYTES)
105
+ continue;
106
+ const relPath = relative(repoRoot, absPath).replace(/\\/g, "/");
107
+ files.push({
108
+ path: relPath,
109
+ symbols: extractSymbols(content),
110
+ keywords: extractKeywords(relPath, content)
111
+ });
112
+ state.count += 1;
113
+ }
114
+ catch { }
115
+ }
116
+ return state;
117
+ }
118
+ function extractSymbols(content) {
119
+ const out = new Set();
120
+ const patterns = [
121
+ /export\s+(?:async\s+)?function\s+([A-Za-z0-9_]+)/g,
122
+ /export\s+class\s+([A-Za-z0-9_]+)/g,
123
+ /export\s+const\s+([A-Za-z0-9_]+)/g,
124
+ /function\s+([A-Za-z0-9_]+)/g,
125
+ /class\s+([A-Za-z0-9_]+)/g,
126
+ /def\s+([A-Za-z0-9_]+)/g
127
+ ];
128
+ for (const pattern of patterns) {
129
+ for (const match of content.matchAll(pattern)) {
130
+ if (match[1])
131
+ out.add(match[1]);
132
+ }
133
+ }
134
+ return [...out].slice(0, 12);
135
+ }
136
+ function extractKeywords(relPath, content) {
137
+ const out = new Set();
138
+ for (const part of relPath.split(/[\/.\-_]+/u)) {
139
+ if (part.length >= 3)
140
+ out.add(part.toLowerCase());
141
+ }
142
+ for (const term of tokenize(content.split("\n").slice(0, 10).join(" "))) {
143
+ out.add(term);
144
+ }
145
+ return [...out].slice(0, 20);
146
+ }
147
+ function tokenize(input) {
148
+ const stopWords = new Set([
149
+ "the", "and", "for", "with", "from", "that",
150
+ "this", "into", "only", "keep", "fix", "make", "loop"
151
+ ]);
152
+ return [...new Set(input.toLowerCase().match(/[a-z0-9_]{3,}/g) ?? [])].filter((term) => !stopWords.has(term));
153
+ }
154
+ export function scanPatchForGroundingViolations(diff, index, options = {}) {
155
+ const violations = [];
156
+ const resolvedFiles = [];
157
+ const indexedPaths = new Set(index.files.map((file) => file.path));
158
+ const indexedSymbols = new Set(index.files.flatMap((file) => file.symbols.map((symbol) => symbol.toLowerCase())));
159
+ const referencedFiles = new Set();
160
+ const diffFilePattern = /^(?:---|\+\+\+)\s+[ab]\/(.+)$/gm;
161
+ for (const match of diff.matchAll(diffFilePattern)) {
162
+ const filePath = match[1]?.trim();
163
+ if (filePath && filePath !== "/dev/null") {
164
+ referencedFiles.add(filePath);
165
+ }
166
+ }
167
+ for (const filePath of referencedFiles) {
168
+ if (indexedPaths.has(filePath)) {
169
+ resolvedFiles.push(filePath);
170
+ }
171
+ else {
172
+ violations.push({
173
+ kind: "file_not_found",
174
+ reference: filePath,
175
+ sourceHint: `diff header references ${filePath}`
176
+ });
177
+ }
178
+ if (options.allowedPaths?.length) {
179
+ const isAllowed = options.allowedPaths.some((pattern) => matchesGlobPattern(filePath, pattern));
180
+ if (!isAllowed) {
181
+ violations.push({
182
+ kind: "patch_outside_allowed_paths",
183
+ reference: filePath,
184
+ sourceHint: `${filePath} is outside allowed paths: ${options.allowedPaths.join(", ")}`
185
+ });
186
+ }
187
+ }
188
+ }
189
+ const addedLinePattern = /^\+(?!\+\+)(.+)$/gm;
190
+ const importPattern = /(?:import|require)\s*(?:.*\s+from\s+)?['"]([^'"]+)['"]/g;
191
+ const identifierPattern = /\b([A-Za-z_][A-Za-z0-9_]{2,})\b/g;
192
+ const symbolKeywords = new Set([
193
+ "const",
194
+ "let",
195
+ "var",
196
+ "function",
197
+ "class",
198
+ "return",
199
+ "export",
200
+ "import",
201
+ "from",
202
+ "await",
203
+ "async",
204
+ "new",
205
+ "true",
206
+ "false",
207
+ "null",
208
+ "undefined"
209
+ ]);
210
+ for (const lineMatch of diff.matchAll(addedLinePattern)) {
211
+ const line = lineMatch[1] ?? "";
212
+ for (const importMatch of line.matchAll(importPattern)) {
213
+ const reference = importMatch[1] ?? "";
214
+ if (!reference.startsWith(".") && !reference.startsWith("/")) {
215
+ continue;
216
+ }
217
+ const normalized = reference
218
+ .replace(/^\.\//, "")
219
+ .replace(/\.(js|ts|tsx|jsx)$/, "");
220
+ const hasMatch = index.files.some((file) => {
221
+ const withoutExt = file.path.replace(/\.(js|ts|tsx|jsx)$/, "");
222
+ return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
223
+ });
224
+ if (!hasMatch) {
225
+ violations.push({
226
+ kind: "import_not_found",
227
+ reference,
228
+ sourceHint: `import in added line: ${line.trim().slice(0, 80)}`
229
+ });
230
+ }
231
+ }
232
+ for (const symbolMatch of line.matchAll(identifierPattern)) {
233
+ const symbol = symbolMatch[1];
234
+ if (!symbol)
235
+ continue;
236
+ if (symbolKeywords.has(symbol))
237
+ continue;
238
+ if (symbol.toUpperCase() === symbol)
239
+ continue;
240
+ if (indexedSymbols.has(symbol.toLowerCase()))
241
+ continue;
242
+ if (/^[a-z]+$/.test(symbol) && symbol.length <= 4)
243
+ continue;
244
+ violations.push({
245
+ kind: "symbol_not_found",
246
+ reference: symbol,
247
+ sourceHint: `symbol in added line: ${line.trim().slice(0, 80)}`
248
+ });
249
+ }
250
+ }
251
+ // Detect content-only diff (only comments, whitespace, empty lines added)
252
+ const substantiveLinePattern = /^\+(?!\+\+)\s*(?!\/\/|\/\*|\*|#).*\S/gm;
253
+ const hasSubstantiveLines = substantiveLinePattern.test(diff);
254
+ const contentOnly = !hasSubstantiveLines && diff.includes("+");
255
+ return {
256
+ violations,
257
+ scannedReferences: referencedFiles.size,
258
+ resolvedFiles,
259
+ contentOnly
260
+ };
261
+ }
262
+ function matchesGlobPattern(filePath, pattern) {
263
+ const regexStr = pattern
264
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
265
+ .replace(/\*\*/g, "__DOUBLESTAR__")
266
+ .replace(/\*/g, "[^/]*")
267
+ .replace(/__DOUBLESTAR__/g, ".*");
268
+ return new RegExp(`^${regexStr}$`).test(filePath);
269
+ }
270
+ //# sourceMappingURL=grounding.js.map
@@ -0,0 +1,145 @@
1
+ import { type ApprovalPolicy, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type LoopRecord, type LoopTask } from "../contracts/index.js";
2
+ import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
3
+ import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
4
+ import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
5
+ import { captureRollbackBoundary, restoreRollbackBoundary } from "./rollback.js";
6
+ import { type RunStore } from "./persistence/index.js";
7
+ export type { ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase } from "../contracts/index.js";
8
+ export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, restoreRollbackBoundary };
9
+ export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
10
+ export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
11
+ export type { GroundingScanResult, GroundingViolation, GroundingViolationKind, RepoGroundingHit, RepoGroundingIndex } from "./grounding.js";
12
+ export { compilePromptPacket } from "./compiler.js";
13
+ export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
14
+ export { createFileRunStore, makeLedgerEvent, resolveRunsRoot } from "./persistence/index.js";
15
+ export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, RunContract, RunStore } from "./persistence/index.js";
16
+ export { compileAndPersistContext } from "./persistence/index.js";
17
+ export type { CompileResult } from "./persistence/index.js";
18
+ export interface MartinAdapterRequest {
19
+ loopId: string;
20
+ attemptId: string;
21
+ context: {
22
+ taskTitle: string;
23
+ objective: string;
24
+ verificationPlan: string[];
25
+ verificationStack?: LoopTask["verificationStack"];
26
+ /** Absolute path to the repository root. */
27
+ repoRoot?: string;
28
+ /** Glob patterns for files the agent may modify. Empty = no restriction. */
29
+ allowedPaths?: string[];
30
+ /** Glob patterns for files the agent must never modify. */
31
+ deniedPaths?: string[];
32
+ /** Human-readable acceptance criteria injected into the prompt. */
33
+ acceptanceCriteria?: string[];
34
+ executionProfile?: ExecutionProfile;
35
+ allowedNetworkDomains?: string[];
36
+ approvalPolicy?: ApprovalPolicy;
37
+ focus: string;
38
+ remainingBudgetUsd: number;
39
+ remainingIterations: number;
40
+ remainingTokens: number;
41
+ };
42
+ previousAttempts: LoopAttempt[];
43
+ }
44
+ export interface MartinAdapterResult {
45
+ status: "completed" | "failed";
46
+ summary: string;
47
+ usage: {
48
+ actualUsd: number;
49
+ estimatedUsd?: number;
50
+ tokensIn: number;
51
+ tokensOut: number;
52
+ provenance?: CostProvenance;
53
+ };
54
+ verification: {
55
+ passed: boolean;
56
+ summary: string;
57
+ };
58
+ execution?: {
59
+ changedFiles?: string[];
60
+ diffStats?: {
61
+ filesChanged: number;
62
+ addedLines: number;
63
+ deletedLines: number;
64
+ };
65
+ structuredErrors?: Array<{
66
+ file: string;
67
+ line?: number;
68
+ col?: number;
69
+ code?: string;
70
+ message: string;
71
+ }>;
72
+ };
73
+ artifacts?: LoopArtifact[];
74
+ failure?: {
75
+ message: string;
76
+ classHint?: FailureClass;
77
+ };
78
+ }
79
+ export interface MartinAdapter {
80
+ adapterId: string;
81
+ kind: "direct-provider" | "agent-cli";
82
+ label: string;
83
+ metadata: {
84
+ providerId: string;
85
+ model: string;
86
+ transport?: "cli" | "http" | "routed_http";
87
+ capabilities?: {
88
+ preflight?: boolean;
89
+ usageSettlement?: boolean;
90
+ diffArtifacts?: boolean;
91
+ structuredErrors?: boolean;
92
+ cachingSignals?: boolean;
93
+ };
94
+ [key: string]: unknown;
95
+ };
96
+ execute(request: MartinAdapterRequest): Promise<MartinAdapterResult>;
97
+ withModel?(model: string): MartinAdapter;
98
+ }
99
+ export interface DistilledContext {
100
+ focus: string;
101
+ recentAttempts: LoopAttempt[];
102
+ constraints: {
103
+ remainingBudgetUsd: number;
104
+ remainingIterations: number;
105
+ remainingTokens: number;
106
+ };
107
+ }
108
+ export interface AttemptPolicyDecision {
109
+ allowed: boolean;
110
+ reason: string;
111
+ recommendedIntervention?: InterventionType;
112
+ }
113
+ /**
114
+ * Admission gate — must pass before any attempt is executed.
115
+ * Evaluates budget headroom, oscillation, and repetitive failure patterns.
116
+ * PolicyPhase remains explicit: attempts are only admitted in ADMIT phase.
117
+ */
118
+ export declare function evaluateAttemptPolicy(input: {
119
+ request: MartinAdapterRequest;
120
+ projectedUsd: number;
121
+ }): AttemptPolicyDecision;
122
+ export interface RunMartinInput {
123
+ workspaceId: string;
124
+ projectId: string;
125
+ teamId?: string;
126
+ task: LoopTask;
127
+ budget: LoopBudget;
128
+ metadata?: Record<string, string>;
129
+ adapter: MartinAdapter;
130
+ now?: () => string;
131
+ idFactory?: (prefix: string) => string;
132
+ maxRecentAttempts?: number;
133
+ fallbackModels?: string[];
134
+ fallbackAdapters?: MartinAdapter[];
135
+ /** Optional persistence store. When provided, runMartin writes artifacts on each lifecycle event. */
136
+ store?: RunStore;
137
+ }
138
+ export interface RunMartinResult {
139
+ loop: LoopRecord;
140
+ decision: ExitDecision;
141
+ }
142
+ export declare function distillContext(loop: Pick<LoopRecord, "task" | "budget" | "cost" | "attempts">, options?: {
143
+ maxRecentAttempts?: number;
144
+ }): DistilledContext;
145
+ export declare function runMartin(input: RunMartinInput): Promise<RunMartinResult>;