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,48 @@
1
+ import type { ApprovalPolicy, ExecutionProfile, LoopTask } from "../contracts/index.js";
2
+ export type SafetySurface = "command" | "filesystem" | "secret" | "spend" | "network" | "dependency";
3
+ export type SafetyViolationKind = "command_blocked" | "path_not_allowed" | "path_denied" | "path_outside_repo" | "network_blocked" | "dependency_approval_required" | "migration_approval_required" | "config_change_approval_required" | "secret_value" | "protected_path";
4
+ export interface SafetyViolation {
5
+ kind: SafetyViolationKind;
6
+ message: string;
7
+ command?: string;
8
+ file?: string;
9
+ match?: string;
10
+ }
11
+ export interface SafetyLeashDecision {
12
+ allowed: boolean;
13
+ blocked: boolean;
14
+ riskLevel: "safe" | "blocked";
15
+ surface: SafetySurface;
16
+ profile?: ExecutionProfile;
17
+ blockedCommands: string[];
18
+ violations: SafetyViolation[];
19
+ reason?: string;
20
+ }
21
+ export interface ResolvedExecutionProfile {
22
+ name: ExecutionProfile;
23
+ networkMode: "off" | "allowlisted" | "open";
24
+ allowedNetworkDomains: string[];
25
+ requireDependencyApproval: boolean;
26
+ requireMigrationApproval: boolean;
27
+ requireConfigApproval: boolean;
28
+ }
29
+ export declare function evaluateVerificationLeash(task: Pick<LoopTask, "verificationPlan" | "verificationStack" | "executionProfile" | "allowedNetworkDomains">): SafetyLeashDecision;
30
+ export declare function evaluateFilesystemLeash(input: {
31
+ repoRoot?: string;
32
+ changedFiles: string[];
33
+ allowedPaths?: string[];
34
+ deniedPaths?: string[];
35
+ }): SafetyLeashDecision;
36
+ export declare function evaluateSecretLeash(input: {
37
+ values: string[];
38
+ }): SafetyLeashDecision;
39
+ export declare function resolveExecutionProfile(input: {
40
+ executionProfile?: ExecutionProfile;
41
+ allowedNetworkDomains?: string[];
42
+ }): ResolvedExecutionProfile;
43
+ export declare function evaluateChangeApprovalLeash(input: {
44
+ changedFiles: string[];
45
+ executionProfile?: ExecutionProfile;
46
+ approvalPolicy?: ApprovalPolicy;
47
+ }): SafetyLeashDecision;
48
+ export declare function redactSecretsFromText(input: string): string;
@@ -0,0 +1,408 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ const BLOCKED_PATTERNS = [
3
+ /(^|\s)rm\s+-rf(\s|$)/u,
4
+ /git\s+reset\s+--hard/iu,
5
+ /git\s+clean\s+-fd/iu,
6
+ /curl\b[^\n|]*\|\s*(sh|bash)/iu,
7
+ /wget\b[^\n|]*\|\s*(sh|bash)/iu,
8
+ /(^|\s)sudo(\s|$)/u,
9
+ /(^|\s)mkfs(\.|\s|$)/u,
10
+ /(^|\s)dd\s+if=/u,
11
+ /(shutdown|reboot)(\s|$)/iu,
12
+ /:\(\)\{:\|:&\};:/u,
13
+ /chmod\s+-R\s+777\s+\//iu,
14
+ /(kubectl|docker)\s+.*\b(delete|prune|rm)\b/iu,
15
+ /ssh\s+/iu,
16
+ /scp\s+/iu
17
+ ];
18
+ const SECRET_PATTERNS = [
19
+ {
20
+ kind: "secret_value",
21
+ pattern: /\bOPENAI_API_KEY\s*=\s*[^\s"'`]+/giu,
22
+ replacement: "OPENAI_API_KEY=[REDACTED_SECRET]"
23
+ },
24
+ {
25
+ kind: "secret_value",
26
+ pattern: /\bsk-[A-Za-z0-9_-]{8,}\b/gu,
27
+ replacement: "[REDACTED_SECRET]"
28
+ },
29
+ {
30
+ kind: "secret_value",
31
+ pattern: /\bghp_[A-Za-z0-9_]{8,}\b/gu,
32
+ replacement: "[REDACTED_SECRET]"
33
+ }
34
+ ];
35
+ const PROTECTED_PATH_PATTERNS = [
36
+ {
37
+ kind: "protected_path",
38
+ pattern: /\B\.env(?!\.example\b)(?:\.[A-Za-z0-9._-]+)?\b/giu,
39
+ replacement: "[REDACTED_PATH]"
40
+ },
41
+ {
42
+ kind: "protected_path",
43
+ pattern: /\bid_rsa\b/giu,
44
+ replacement: "[REDACTED_PATH]"
45
+ },
46
+ {
47
+ kind: "protected_path",
48
+ pattern: /\b[A-Za-z0-9._/-]+\.(pem|p12|key)\b/giu,
49
+ replacement: "[REDACTED_PATH]"
50
+ }
51
+ ];
52
+ export function evaluateVerificationLeash(task) {
53
+ const commands = [
54
+ ...(task.verificationPlan ?? []),
55
+ ...((task.verificationStack ?? []).map((step) => step.command))
56
+ ].filter(Boolean);
57
+ const profile = resolveExecutionProfile(task);
58
+ const blockedCommands = commands.filter((command) => BLOCKED_PATTERNS.some((pattern) => pattern.test(command)));
59
+ const violations = blockedCommands.map((command) => ({
60
+ kind: "command_blocked",
61
+ command,
62
+ message: `Blocked destructive verifier command: ${command}`
63
+ }));
64
+ if (blockedCommands.length > 0) {
65
+ return {
66
+ allowed: false,
67
+ blocked: true,
68
+ riskLevel: "blocked",
69
+ surface: "command",
70
+ profile: profile.name,
71
+ blockedCommands,
72
+ violations,
73
+ reason: "Safety leash blocked destructive or unbounded verifier commands."
74
+ };
75
+ }
76
+ const networkViolations = commands
77
+ .map((command) => buildNetworkViolation(command, profile))
78
+ .filter((violation) => Boolean(violation));
79
+ if (networkViolations.length > 0) {
80
+ return {
81
+ allowed: false,
82
+ blocked: true,
83
+ riskLevel: "blocked",
84
+ surface: "network",
85
+ profile: profile.name,
86
+ blockedCommands: commands.filter((command) => buildNetworkViolation(command, profile)),
87
+ violations: networkViolations,
88
+ reason: `Safety leash blocked outbound network access for the ${profile.name} profile.`
89
+ };
90
+ }
91
+ return {
92
+ allowed: true,
93
+ blocked: false,
94
+ riskLevel: "safe",
95
+ surface: "command",
96
+ profile: profile.name,
97
+ blockedCommands: [],
98
+ violations: []
99
+ };
100
+ }
101
+ export function evaluateFilesystemLeash(input) {
102
+ const violations = [];
103
+ for (const rawFile of input.changedFiles) {
104
+ const normalized = normalizeChangedFile(rawFile, input.repoRoot);
105
+ if (normalized.outsideRepo) {
106
+ violations.push({
107
+ kind: "path_outside_repo",
108
+ file: rawFile,
109
+ message: `Changed file is outside the configured repo root: ${rawFile}`
110
+ });
111
+ continue;
112
+ }
113
+ const file = normalized.file;
114
+ if (!file) {
115
+ continue;
116
+ }
117
+ if ((input.deniedPaths ?? []).some((pattern) => matchesPathPattern(file, pattern))) {
118
+ violations.push({
119
+ kind: "path_denied",
120
+ file,
121
+ message: `Changed file matches a denylisted path: ${file}`
122
+ });
123
+ continue;
124
+ }
125
+ const allowedPaths = input.allowedPaths ?? [];
126
+ if (allowedPaths.length > 0 && !allowedPaths.some((pattern) => matchesPathPattern(file, pattern))) {
127
+ violations.push({
128
+ kind: "path_not_allowed",
129
+ file,
130
+ message: `Changed file is outside the allowed paths: ${file}`
131
+ });
132
+ }
133
+ }
134
+ if (violations.length > 0) {
135
+ return {
136
+ allowed: false,
137
+ blocked: true,
138
+ riskLevel: "blocked",
139
+ surface: "filesystem",
140
+ blockedCommands: [],
141
+ violations,
142
+ reason: "Safety leash blocked file changes outside the allowed repo contract."
143
+ };
144
+ }
145
+ return {
146
+ allowed: true,
147
+ blocked: false,
148
+ riskLevel: "safe",
149
+ surface: "filesystem",
150
+ blockedCommands: [],
151
+ violations: []
152
+ };
153
+ }
154
+ export function evaluateSecretLeash(input) {
155
+ const violations = [];
156
+ for (const value of input.values) {
157
+ if (!value)
158
+ continue;
159
+ for (const rule of SECRET_PATTERNS) {
160
+ for (const match of value.matchAll(rule.pattern)) {
161
+ const matched = match[0];
162
+ if (!matched)
163
+ continue;
164
+ violations.push({
165
+ kind: rule.kind,
166
+ match: matched,
167
+ message: "Secret-like credential value detected in the runtime context."
168
+ });
169
+ }
170
+ }
171
+ }
172
+ if (violations.length > 0) {
173
+ return {
174
+ allowed: false,
175
+ blocked: true,
176
+ riskLevel: "blocked",
177
+ surface: "secret",
178
+ blockedCommands: [],
179
+ violations,
180
+ reason: "Safety leash blocked secret-like credential values from entering the runtime context."
181
+ };
182
+ }
183
+ return {
184
+ allowed: true,
185
+ blocked: false,
186
+ riskLevel: "safe",
187
+ surface: "secret",
188
+ blockedCommands: [],
189
+ violations: []
190
+ };
191
+ }
192
+ export function resolveExecutionProfile(input) {
193
+ const name = input.executionProfile ?? "strict_local";
194
+ const allowedNetworkDomains = [...(input.allowedNetworkDomains ?? [])];
195
+ switch (name) {
196
+ case "ci_safe":
197
+ return {
198
+ name,
199
+ networkMode: "off",
200
+ allowedNetworkDomains,
201
+ requireDependencyApproval: true,
202
+ requireMigrationApproval: true,
203
+ requireConfigApproval: true
204
+ };
205
+ case "staging_controlled":
206
+ return {
207
+ name,
208
+ networkMode: allowedNetworkDomains.length > 0 ? "allowlisted" : "off",
209
+ allowedNetworkDomains,
210
+ requireDependencyApproval: true,
211
+ requireMigrationApproval: true,
212
+ requireConfigApproval: true
213
+ };
214
+ case "research_untrusted":
215
+ return {
216
+ name,
217
+ networkMode: allowedNetworkDomains.length > 0 ? "allowlisted" : "open",
218
+ allowedNetworkDomains,
219
+ requireDependencyApproval: true,
220
+ requireMigrationApproval: true,
221
+ requireConfigApproval: true
222
+ };
223
+ case "strict_local":
224
+ default:
225
+ return {
226
+ name: "strict_local",
227
+ networkMode: "off",
228
+ allowedNetworkDomains,
229
+ requireDependencyApproval: true,
230
+ requireMigrationApproval: true,
231
+ requireConfigApproval: true
232
+ };
233
+ }
234
+ }
235
+ export function evaluateChangeApprovalLeash(input) {
236
+ const profile = resolveExecutionProfile({
237
+ executionProfile: input.executionProfile
238
+ });
239
+ const violations = [];
240
+ for (const file of input.changedFiles.map((entry) => entry.replace(/\\/gu, "/"))) {
241
+ if (isDependencyFile(file) && profile.requireDependencyApproval && !input.approvalPolicy?.dependencyAdds) {
242
+ violations.push({
243
+ kind: "dependency_approval_required",
244
+ file,
245
+ message: `Dependency-related file change requires approval in the ${profile.name} profile: ${file}`
246
+ });
247
+ continue;
248
+ }
249
+ if (isMigrationFile(file) && profile.requireMigrationApproval && !input.approvalPolicy?.migrations) {
250
+ violations.push({
251
+ kind: "migration_approval_required",
252
+ file,
253
+ message: `Migration change requires approval in the ${profile.name} profile: ${file}`
254
+ });
255
+ continue;
256
+ }
257
+ if (isConfigChangeFile(file) && profile.requireConfigApproval && !input.approvalPolicy?.configChanges) {
258
+ violations.push({
259
+ kind: "config_change_approval_required",
260
+ file,
261
+ message: `Deployment or configuration change requires approval in the ${profile.name} profile: ${file}`
262
+ });
263
+ }
264
+ }
265
+ if (violations.length > 0) {
266
+ return {
267
+ allowed: false,
268
+ blocked: true,
269
+ riskLevel: "blocked",
270
+ surface: "dependency",
271
+ profile: profile.name,
272
+ blockedCommands: [],
273
+ violations,
274
+ reason: "Safety leash blocked dependency, migration, or configuration changes that require approval."
275
+ };
276
+ }
277
+ return {
278
+ allowed: true,
279
+ blocked: false,
280
+ riskLevel: "safe",
281
+ surface: "dependency",
282
+ profile: profile.name,
283
+ blockedCommands: [],
284
+ violations: []
285
+ };
286
+ }
287
+ export function redactSecretsFromText(input) {
288
+ let output = input;
289
+ for (const rule of [...SECRET_PATTERNS, ...PROTECTED_PATH_PATTERNS]) {
290
+ output = output.replace(rule.pattern, rule.replacement);
291
+ }
292
+ return output;
293
+ }
294
+ function normalizeChangedFile(file, repoRoot) {
295
+ const normalizedInput = file.replace(/\\/gu, "/");
296
+ if (!repoRoot) {
297
+ return {
298
+ file: normalizedInput,
299
+ outsideRepo: normalizedInput.startsWith("../") || normalizedInput.includes("/../")
300
+ };
301
+ }
302
+ const absoluteRoot = resolve(repoRoot);
303
+ const absoluteFile = isAbsolute(file) ? resolve(file) : resolve(absoluteRoot, file);
304
+ const repoRelative = relative(absoluteRoot, absoluteFile).replace(/\\/gu, "/");
305
+ if (repoRelative.length === 0 ||
306
+ repoRelative === ".." ||
307
+ repoRelative.startsWith("../") ||
308
+ isAbsolute(repoRelative)) {
309
+ return { outsideRepo: true };
310
+ }
311
+ return {
312
+ file: repoRelative,
313
+ outsideRepo: false
314
+ };
315
+ }
316
+ function matchesPathPattern(file, pattern) {
317
+ const normalizedFile = file.replace(/\\/gu, "/");
318
+ const normalizedPattern = pattern.replace(/\\/gu, "/");
319
+ if (normalizedPattern.includes("**")) {
320
+ const prefix = normalizedPattern.split("**")[0] ?? normalizedPattern;
321
+ return normalizedFile.startsWith(prefix.replace(/\/$/u, ""));
322
+ }
323
+ if (normalizedPattern.endsWith("*")) {
324
+ return normalizedFile.startsWith(normalizedPattern.replace(/\*+$/u, ""));
325
+ }
326
+ return (normalizedFile === normalizedPattern ||
327
+ normalizedFile.startsWith(`${normalizedPattern.replace(/\/$/u, "")}/`));
328
+ }
329
+ function buildNetworkViolation(command, profile) {
330
+ const targets = extractNetworkTargets(command);
331
+ if (targets.length === 0) {
332
+ return undefined;
333
+ }
334
+ if (profile.networkMode === "open") {
335
+ return undefined;
336
+ }
337
+ if (profile.networkMode === "allowlisted") {
338
+ const blockedTarget = targets.find((target) => !profile.allowedNetworkDomains.some((domain) => target === domain || target.endsWith(`.${domain}`)));
339
+ if (!blockedTarget) {
340
+ return undefined;
341
+ }
342
+ return {
343
+ kind: "network_blocked",
344
+ command,
345
+ match: blockedTarget,
346
+ message: `Network target is not allowlisted for the ${profile.name} profile: ${blockedTarget}`
347
+ };
348
+ }
349
+ return {
350
+ kind: "network_blocked",
351
+ command,
352
+ match: targets[0],
353
+ message: `Outbound network access is blocked for the ${profile.name} profile.`
354
+ };
355
+ }
356
+ function extractNetworkTargets(command) {
357
+ if (!/\b(curl|wget|invoke-webrequest|iwr|httpie|http)\b/iu.test(command)) {
358
+ return [];
359
+ }
360
+ return [...command.matchAll(/https?:\/\/([^/\s"'`]+)/giu)]
361
+ .map((match) => match[1]?.toLowerCase())
362
+ .filter((value) => Boolean(value));
363
+ }
364
+ function isDependencyFile(file) {
365
+ const normalized = file.replace(/\\/gu, "/");
366
+ return [
367
+ "package.json",
368
+ "package-lock.json",
369
+ "pnpm-lock.yaml",
370
+ "yarn.lock",
371
+ "bun.lock",
372
+ "bun.lockb",
373
+ "pyproject.toml",
374
+ "requirements.txt",
375
+ "Cargo.toml",
376
+ "Cargo.lock"
377
+ ].includes(normalized.split("/").at(-1) ?? normalized);
378
+ }
379
+ function isMigrationFile(file) {
380
+ const normalized = file.replace(/\\/gu, "/");
381
+ return (normalized.includes("/migrations/") ||
382
+ normalized.startsWith("migrations/") ||
383
+ normalized.includes("prisma/migrations/"));
384
+ }
385
+ function isConfigChangeFile(file) {
386
+ const normalized = file.replace(/\\/gu, "/");
387
+ const leaf = normalized.split("/").at(-1) ?? normalized;
388
+ if ([
389
+ "vercel.json",
390
+ "netlify.toml",
391
+ "wrangler.toml",
392
+ "docker-compose.yml",
393
+ "docker-compose.yaml",
394
+ "compose.yml",
395
+ "compose.yaml",
396
+ "fly.toml",
397
+ "railway.json"
398
+ ].includes(leaf)) {
399
+ return true;
400
+ }
401
+ return (normalized.startsWith(".github/workflows/") ||
402
+ normalized.startsWith("deploy/") ||
403
+ normalized.startsWith("deployment/") ||
404
+ normalized.startsWith("infra/") ||
405
+ normalized.startsWith("infrastructure/") ||
406
+ normalized.startsWith("ops/"));
407
+ }
408
+ //# sourceMappingURL=leash.js.map
@@ -0,0 +1,18 @@
1
+ import { type CompilerAdapterRequest, type PromptPacket } from "../compiler.js";
2
+ import type { RunStore } from "./store.js";
3
+ export interface CompileResult {
4
+ packet: PromptPacket;
5
+ }
6
+ /**
7
+ * Compile a deterministic PromptPacket from the request state and, when a
8
+ * store is provided, persist it as compiled-context.json in the attempt
9
+ * artifact directory.
10
+ *
11
+ * R3.7: Context compiler produces deterministic compiled-context.json.
12
+ * R3.8: Any attempt prompt can be reconstructed from disk artifacts alone.
13
+ */
14
+ export declare function compileAndPersistContext(request: CompilerAdapterRequest, options: {
15
+ attemptIndex: number;
16
+ store?: RunStore;
17
+ now?: string;
18
+ }): Promise<CompileResult>;
@@ -0,0 +1,35 @@
1
+ import { compilePromptPacket } from "../compiler.js";
2
+ import { makeLedgerEvent } from "./ledger.js";
3
+ /**
4
+ * Compile a deterministic PromptPacket from the request state and, when a
5
+ * store is provided, persist it as compiled-context.json in the attempt
6
+ * artifact directory.
7
+ *
8
+ * R3.7: Context compiler produces deterministic compiled-context.json.
9
+ * R3.8: Any attempt prompt can be reconstructed from disk artifacts alone.
10
+ */
11
+ export async function compileAndPersistContext(request, options) {
12
+ const packet = compilePromptPacket(request);
13
+ const ts = options.now ?? new Date().toISOString();
14
+ if (options.store) {
15
+ // Write compiled-context.json to attempt artifact directory
16
+ await options.store.writeAttemptArtifacts(request.loopId, options.attemptIndex, {
17
+ compiledContext: packet
18
+ });
19
+ // Append prompt.compiled ledger event
20
+ await options.store.appendLedger(request.loopId, makeLedgerEvent({
21
+ kind: "prompt.compiled",
22
+ runId: request.loopId,
23
+ attemptIndex: options.attemptIndex,
24
+ payload: {
25
+ attemptId: request.attemptId,
26
+ attemptNumber: packet.attemptNumber,
27
+ priorFailurePatterns: packet.priorFailurePatterns,
28
+ budgetEnvelope: packet.budgetEnvelope
29
+ },
30
+ timestamp: ts
31
+ }));
32
+ }
33
+ return { packet };
34
+ }
35
+ //# sourceMappingURL=compiler.js.map
@@ -0,0 +1,6 @@
1
+ export { makeLedgerEvent } from "./ledger.js";
2
+ export type { LedgerEvent, LedgerEventDraft, LedgerEventKind } from "./ledger.js";
3
+ export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
4
+ export type { AttemptArtifacts, RunContract, RunStore } from "./store.js";
5
+ export { compileAndPersistContext } from "./compiler.js";
6
+ export type { CompileResult } from "./compiler.js";
@@ -0,0 +1,4 @@
1
+ export { makeLedgerEvent } from "./ledger.js";
2
+ export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
3
+ export { compileAndPersistContext } from "./compiler.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Ledger event types for the Phase 3 persistence stack.
3
+ * These are WRITE-side events that go into ledger.jsonl — separate from the
4
+ * read-model LoopEventType used by the control plane.
5
+ */
6
+ export type LedgerEventKind = "contract.created" | "attempt.admitted" | "attempt.rejected" | "prompt.compiled" | "patch.generated" | "verification.completed" | "grounding.violations_found" | "safety.violations_found" | "budget.settled" | "attempt.kept" | "attempt.discarded" | "run.exited";
7
+ export interface LedgerEvent {
8
+ kind: LedgerEventKind;
9
+ runId: string;
10
+ attemptIndex?: number;
11
+ timestamp: string;
12
+ payload: Record<string, unknown>;
13
+ }
14
+ export interface LedgerEventDraft {
15
+ kind: LedgerEventKind;
16
+ runId: string;
17
+ attemptIndex?: number;
18
+ payload: Record<string, unknown>;
19
+ timestamp?: string;
20
+ }
21
+ export declare function makeLedgerEvent(draft: LedgerEventDraft, options?: {
22
+ now?: string;
23
+ }): LedgerEvent;
@@ -0,0 +1,10 @@
1
+ export function makeLedgerEvent(draft, options = {}) {
2
+ return {
3
+ kind: draft.kind,
4
+ runId: draft.runId,
5
+ timestamp: draft.timestamp ?? options.now ?? new Date().toISOString(),
6
+ payload: draft.payload,
7
+ ...(draft.attemptIndex !== undefined ? { attemptIndex: draft.attemptIndex } : {})
8
+ };
9
+ }
10
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1,77 @@
1
+ import type { LoopBudget, LoopTask, MachineState } from "../../contracts/index.js";
2
+ import { type LedgerEvent } from "./ledger.js";
3
+ export interface RunContract {
4
+ runId: string;
5
+ workspaceId: string;
6
+ projectId: string;
7
+ task: LoopTask;
8
+ budget: LoopBudget;
9
+ createdAt: string;
10
+ metadata?: Record<string, string>;
11
+ }
12
+ export interface AttemptArtifacts {
13
+ /** Compiled PromptPacket written as compiled-context.json */
14
+ compiledContext: unknown;
15
+ /** Unified diff string from the patch (optional) */
16
+ diff?: string;
17
+ /** Raw verifier command output (optional) */
18
+ verifierOutput?: string;
19
+ /** Grounding scan result (optional) */
20
+ groundingScan?: unknown;
21
+ /** Safety leash artifact captured for a blocked or escalated attempt (optional) */
22
+ leash?: unknown;
23
+ /** Patch score artifact captured for Phase 10 patch-truth decisions (optional) */
24
+ patchScore?: unknown;
25
+ /** Patch keep/discard/escalate decision artifact (optional) */
26
+ patchDecision?: unknown;
27
+ /** Rollback boundary captured before an attempt mutates the repo (optional) */
28
+ rollbackBoundary?: unknown;
29
+ /** Rollback restore outcome captured after discard/escalation/failure (optional) */
30
+ rollbackOutcome?: unknown;
31
+ }
32
+ /**
33
+ * RunStore isolates all filesystem persistence from orchestration logic.
34
+ * runMartin accepts an optional store; when provided, every lifecycle event
35
+ * is durably written before the run proceeds to the next step.
36
+ */
37
+ export interface RunStore {
38
+ /**
39
+ * Write contract.json for a new run. Called once at run start.
40
+ * The contract is immutable after this point.
41
+ */
42
+ initRun(contract: RunContract): Promise<void>;
43
+ /**
44
+ * Overwrite state.json with the current MachineState.
45
+ * Called on every phase transition.
46
+ */
47
+ updateState(runId: string, state: MachineState): Promise<void>;
48
+ /**
49
+ * Append one event line to ledger.jsonl. Append-only — never rewrites.
50
+ */
51
+ appendLedger(runId: string, event: LedgerEvent): Promise<void>;
52
+ /**
53
+ * Write artifacts for a completed attempt to artifacts/attempt-<n>/.
54
+ */
55
+ writeAttemptArtifacts(runId: string, attemptIndex: number, artifacts: AttemptArtifacts): Promise<void>;
56
+ }
57
+ export declare function resolveRunsRoot(env?: NodeJS.ProcessEnv): string;
58
+ export declare function runDir(runsRoot: string, runId: string): string;
59
+ export declare function artifactDir(runsRoot: string, runId: string, attemptIndex: number): string;
60
+ /**
61
+ * Filesystem-backed RunStore. Writes to:
62
+ * <runsRoot>/<runId>/contract.json
63
+ * <runsRoot>/<runId>/state.json
64
+ * <runsRoot>/<runId>/ledger.jsonl
65
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/compiled-context.json
66
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/diff.patch (if diff provided)
67
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/verifier-output.txt (if provided)
68
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/grounding-scan.json (if provided)
69
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/leash.json (if leash provided)
70
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/patch-score.json (if patchScore provided)
71
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/patch-decision.json (if patchDecision provided)
72
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/rollback-boundary.json (if rollbackBoundary provided)
73
+ * <runsRoot>/<runId>/artifacts/attempt-<n>/rollback-outcome.json (if rollbackOutcome provided)
74
+ */
75
+ export declare function createFileRunStore(options?: {
76
+ runsRoot?: string;
77
+ }): RunStore;