martin-loop 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -344
  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/EXAMPLES.md +126 -126
  51. package/docs/oss/OSS-BOUNDARY-REPORT.json +113 -113
  52. package/docs/oss/OSS-BOUNDARY-REPORT.md +48 -48
  53. package/docs/oss/QUICKSTART.md +135 -135
  54. package/docs/oss/README.md +93 -93
  55. package/docs/oss/RELEASE-SURFACE-REPORT.json +45 -45
  56. package/docs/oss/RELEASE-SURFACE-REPORT.md +35 -35
  57. package/package.json +56 -54
@@ -0,0 +1,127 @@
1
+ import { spawn } from "node:child_process";
2
+ import { isAbsolute } from "node:path";
3
+ import { diffStatsFromNumstat } from "./runtime-support.js";
4
+ export async function runSubprocess(command, args, options) {
5
+ return new Promise((resolve) => {
6
+ let timedOut = false;
7
+ const stdoutChunks = [];
8
+ const stderrChunks = [];
9
+ const stdinMode = options.stdinData !== undefined ? "pipe" : "ignore";
10
+ let proc;
11
+ try {
12
+ proc = (options.spawnImpl ?? spawn)(command, args, {
13
+ cwd: options.cwd,
14
+ stdio: [stdinMode, "pipe", "pipe"],
15
+ env: process.env,
16
+ // shell: true is required on Windows to resolve PATH shims (e.g. claude.cmd).
17
+ // Avoid it for absolute .exe paths because cmd.exe can split paths with spaces.
18
+ // Prompt content is never passed as a shell argument, it goes via stdin, so
19
+ // injection risk from the DEP0190 warning does not apply here.
20
+ shell: shouldUseWindowsShell(command)
21
+ });
22
+ }
23
+ catch (error) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ resolve({
26
+ exitCode: 1,
27
+ stdout: "",
28
+ stderr: message,
29
+ timedOut: false
30
+ });
31
+ return;
32
+ }
33
+ if (options.stdinData !== undefined && proc.stdin) {
34
+ proc.stdin.write(options.stdinData, "utf8");
35
+ proc.stdin.end();
36
+ }
37
+ proc.stdout?.on("data", (chunk) => {
38
+ stdoutChunks.push(chunk);
39
+ });
40
+ proc.stderr?.on("data", (chunk) => {
41
+ stderrChunks.push(chunk);
42
+ });
43
+ const timer = setTimeout(() => {
44
+ timedOut = true;
45
+ proc.kill("SIGTERM");
46
+ }, options.timeoutMs);
47
+ proc.on("close", (code) => {
48
+ clearTimeout(timer);
49
+ resolve({
50
+ exitCode: code ?? 1,
51
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
52
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
53
+ timedOut
54
+ });
55
+ });
56
+ proc.on("error", (error) => {
57
+ clearTimeout(timer);
58
+ resolve({
59
+ exitCode: 1,
60
+ stdout: "",
61
+ stderr: error.message,
62
+ timedOut: false
63
+ });
64
+ });
65
+ });
66
+ }
67
+ export async function runVerification(commands, cwd, timeoutMs, verificationStack, spawnImpl) {
68
+ const steps = verificationStack && verificationStack.length > 0
69
+ ? verificationStack.map((step) => ({
70
+ command: step.command,
71
+ fastFail: step.fastFail !== false
72
+ }))
73
+ : commands.map((command) => ({ command, fastFail: true }));
74
+ if (steps.length === 0) {
75
+ return { passed: true, summary: "No verification commands specified." };
76
+ }
77
+ const failedSteps = [];
78
+ for (const step of steps) {
79
+ const parts = step.command.trim().split(/\s+/u);
80
+ const bin = parts[0];
81
+ const args = parts.slice(1);
82
+ if (!bin) {
83
+ continue;
84
+ }
85
+ const result = await runSubprocess(bin, args, { cwd, timeoutMs, spawnImpl });
86
+ if (result.timedOut) {
87
+ return { passed: false, summary: `Verification timed out: ${step.command}` };
88
+ }
89
+ if (result.exitCode !== 0) {
90
+ const detail = truncate(result.stderr.trim() || result.stdout.trim(), 500);
91
+ const summary = `Verification failed: ${step.command}\n${detail}`;
92
+ if (step.fastFail) {
93
+ return { passed: false, summary };
94
+ }
95
+ failedSteps.push(step.command);
96
+ }
97
+ }
98
+ if (failedSteps.length > 0) {
99
+ return { passed: false, summary: `Failed steps: ${failedSteps.join(", ")}` };
100
+ }
101
+ return { passed: true, summary: `All ${String(steps.length)} verification step(s) passed.` };
102
+ }
103
+ export async function readGitExecutionArtifacts(repoRoot, timeoutMs, spawnImpl) {
104
+ const changedFilesResult = await runSubprocess("git", ["diff", "--name-only", "HEAD"], { cwd: repoRoot, timeoutMs, spawnImpl });
105
+ const numstatResult = await runSubprocess("git", ["diff", "--numstat", "HEAD"], { cwd: repoRoot, timeoutMs, spawnImpl });
106
+ const changedFiles = changedFilesResult.exitCode === 0
107
+ ? changedFilesResult.stdout
108
+ .split(/\r?\n/u)
109
+ .map((entry) => entry.trim())
110
+ .filter(Boolean)
111
+ : [];
112
+ const diffStats = numstatResult.exitCode === 0 ? diffStatsFromNumstat(numstatResult.stdout) : undefined;
113
+ return {
114
+ ...(changedFiles.length > 0 ? { changedFiles } : {}),
115
+ ...(diffStats ? { diffStats } : {})
116
+ };
117
+ }
118
+ function shouldUseWindowsShell(command) {
119
+ return process.platform === "win32" && !isAbsolute(command);
120
+ }
121
+ function truncate(text, maxLength) {
122
+ if (text.length <= maxLength) {
123
+ return text;
124
+ }
125
+ return `...${text.slice(-(maxLength - 3))}`;
126
+ }
127
+ //# sourceMappingURL=cli-bridge.js.map
@@ -0,0 +1,10 @@
1
+ import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
2
+ export interface DirectProviderAdapterOptions {
3
+ providerId: string;
4
+ model: string;
5
+ label?: string;
6
+ transport?: "http" | "routed_http";
7
+ capabilities?: Partial<NonNullable<MartinAdapter["metadata"]["capabilities"]>>;
8
+ responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
9
+ }
10
+ export declare function createDirectProviderAdapter(options: DirectProviderAdapterOptions): MartinAdapter;
@@ -0,0 +1,41 @@
1
+ import { createAdapterCapabilities } from "./runtime-support.js";
2
+ export function createDirectProviderAdapter(options) {
3
+ return {
4
+ adapterId: `direct:${options.providerId}:${options.model}`,
5
+ kind: "direct-provider",
6
+ label: options.label ?? `Direct provider (${options.providerId}/${options.model})`,
7
+ metadata: {
8
+ providerId: options.providerId,
9
+ model: options.model,
10
+ transport: options.transport ?? "http",
11
+ capabilities: createAdapterCapabilities({
12
+ usageSettlement: true,
13
+ structuredErrors: true,
14
+ ...options.capabilities
15
+ })
16
+ },
17
+ async execute(request) {
18
+ if (options.responder) {
19
+ return await options.responder(request);
20
+ }
21
+ return {
22
+ status: "failed",
23
+ summary: `Direct provider ${options.providerId}/${options.model} is not configured for live inference.`,
24
+ usage: {
25
+ actualUsd: 0,
26
+ tokensIn: 0,
27
+ tokensOut: 0,
28
+ provenance: "unavailable"
29
+ },
30
+ verification: {
31
+ passed: false,
32
+ summary: "No live provider request was attempted."
33
+ },
34
+ failure: {
35
+ message: `Direct provider ${options.providerId}/${options.model} is not configured for live inference. environment_mismatch`
36
+ }
37
+ };
38
+ }
39
+ };
40
+ }
41
+ //# sourceMappingURL=direct-provider.js.map
@@ -0,0 +1,5 @@
1
+ export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
2
+ export { createStubDirectProviderAdapter, type StubDirectProviderAdapterOptions } from "./stub-direct-provider.js";
3
+ export { createStubAgentCliAdapter, type StubAgentCliAdapterOptions } from "./stub-agent-cli.js";
4
+ export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type CodexCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
5
+ export type { SpawnLike, SubprocessResult, VerificationOutcome } from "./cli-bridge.js";
@@ -0,0 +1,5 @@
1
+ export { createDirectProviderAdapter } from "./direct-provider.js";
2
+ export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
3
+ export { createStubAgentCliAdapter } from "./stub-agent-cli.js";
4
+ export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter } from "./claude-cli.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ import type { MartinAdapter, MartinAdapterResult } from "../core/index.js";
2
+ type StructuredError = NonNullable<NonNullable<MartinAdapterResult["execution"]>["structuredErrors"]>[number];
3
+ type DiffStats = NonNullable<NonNullable<MartinAdapterResult["execution"]>["diffStats"]>;
4
+ export declare function createAdapterCapabilities(overrides?: Partial<NonNullable<MartinAdapter["metadata"]["capabilities"]>>): NonNullable<MartinAdapter["metadata"]["capabilities"]>;
5
+ export declare function normalizeUsage(input: {
6
+ actualUsd?: number;
7
+ estimatedUsd?: number;
8
+ tokensIn?: number;
9
+ tokensOut?: number;
10
+ provenance?: MartinAdapterResult["usage"]["provenance"];
11
+ }): MartinAdapterResult["usage"];
12
+ export declare function diffStatsFromNumstat(stdout: string): DiffStats | undefined;
13
+ export declare function normalizeStructuredErrors(errors: StructuredError[] | undefined): StructuredError[];
14
+ export {};
@@ -0,0 +1,52 @@
1
+ export function createAdapterCapabilities(overrides = {}) {
2
+ return {
3
+ preflight: true,
4
+ usageSettlement: false,
5
+ diffArtifacts: false,
6
+ structuredErrors: false,
7
+ cachingSignals: false,
8
+ ...overrides
9
+ };
10
+ }
11
+ export function normalizeUsage(input) {
12
+ const provenance = input.provenance ??
13
+ (input.estimatedUsd !== undefined ? "estimated" : "actual");
14
+ const actualUsd = provenance === "estimated"
15
+ ? input.estimatedUsd ?? input.actualUsd ?? 0
16
+ : input.actualUsd ?? 0;
17
+ return {
18
+ actualUsd: roundUsd(actualUsd),
19
+ ...(input.estimatedUsd !== undefined
20
+ ? { estimatedUsd: roundUsd(input.estimatedUsd) }
21
+ : {}),
22
+ tokensIn: input.tokensIn ?? 0,
23
+ tokensOut: input.tokensOut ?? 0,
24
+ provenance
25
+ };
26
+ }
27
+ export function diffStatsFromNumstat(stdout) {
28
+ const lines = stdout
29
+ .split(/\r?\n/u)
30
+ .map((line) => line.trim())
31
+ .filter(Boolean);
32
+ if (lines.length === 0) {
33
+ return undefined;
34
+ }
35
+ let filesChanged = 0;
36
+ let addedLines = 0;
37
+ let deletedLines = 0;
38
+ for (const line of lines) {
39
+ const [added, deleted] = line.split(/\s+/u);
40
+ filesChanged += 1;
41
+ addedLines += Number(added) || 0;
42
+ deletedLines += Number(deleted) || 0;
43
+ }
44
+ return { filesChanged, addedLines, deletedLines };
45
+ }
46
+ export function normalizeStructuredErrors(errors) {
47
+ return errors?.slice(0, 10) ?? [];
48
+ }
49
+ function roundUsd(value) {
50
+ return Math.round(value * 1_000_000) / 1_000_000;
51
+ }
52
+ //# sourceMappingURL=runtime-support.js.map
@@ -0,0 +1,8 @@
1
+ import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
2
+ export interface StubAgentCliAdapterOptions {
3
+ command: string[];
4
+ profile?: string;
5
+ label?: string;
6
+ responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
7
+ }
8
+ export declare function createStubAgentCliAdapter(options: StubAgentCliAdapterOptions): MartinAdapter;
@@ -0,0 +1,41 @@
1
+ import { createAdapterCapabilities } from "./runtime-support.js";
2
+ export function createStubAgentCliAdapter(options) {
3
+ const metadata = {
4
+ command: options.command.join(" "),
5
+ providerId: "agent-cli",
6
+ model: options.profile ?? options.command[0] ?? "default",
7
+ transport: "cli",
8
+ capabilities: createAdapterCapabilities(),
9
+ ...(options.profile ? { profile: options.profile } : {})
10
+ };
11
+ return {
12
+ adapterId: `agent-cli:${options.command.join(":")}`,
13
+ kind: "agent-cli",
14
+ label: options.label ?? `Stub agent CLI (${options.command.join(" ")})`,
15
+ metadata,
16
+ async execute(request) {
17
+ if (options.responder) {
18
+ return await options.responder(request);
19
+ }
20
+ return {
21
+ status: "failed",
22
+ summary: `Stub agent CLI ${options.command.join(" ")} did not execute a live session.`,
23
+ usage: {
24
+ actualUsd: 0,
25
+ tokensIn: 0,
26
+ tokensOut: 0,
27
+ provenance: "unavailable"
28
+ },
29
+ verification: {
30
+ passed: false,
31
+ summary: "No CLI execution was attempted."
32
+ },
33
+ failure: {
34
+ message: `Agent CLI ${options.command.join(" ")} is not configured for live execution.`,
35
+ classHint: "environment_mismatch"
36
+ }
37
+ };
38
+ }
39
+ };
40
+ }
41
+ //# sourceMappingURL=stub-agent-cli.js.map
@@ -0,0 +1,8 @@
1
+ import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
2
+ export interface StubDirectProviderAdapterOptions {
3
+ providerId: string;
4
+ model: string;
5
+ label?: string;
6
+ responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
7
+ }
8
+ export declare function createStubDirectProviderAdapter(options: StubDirectProviderAdapterOptions): MartinAdapter;
@@ -0,0 +1,10 @@
1
+ import { createDirectProviderAdapter } from "./direct-provider.js";
2
+ export function createStubDirectProviderAdapter(options) {
3
+ return createDirectProviderAdapter({
4
+ providerId: options.providerId,
5
+ model: options.model,
6
+ label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
7
+ responder: options.responder
8
+ });
9
+ }
10
+ //# sourceMappingURL=stub-direct-provider.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { executeCli } from "../index.js";
3
+ const args = process.argv.slice(2);
4
+ executeCli(args)
5
+ .then((result) => {
6
+ if (result.stdout) {
7
+ process.stdout.write(`${result.stdout}\n`);
8
+ }
9
+ if (result.stderr) {
10
+ process.stderr.write(`${result.stderr}\n`);
11
+ }
12
+ process.exitCode = result.exitCode;
13
+ })
14
+ .catch((error) => {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ process.stderr.write(`${message}\n`);
17
+ process.exitCode = 1;
18
+ });
19
+ //# sourceMappingURL=martin.js.map
@@ -0,0 +1,39 @@
1
+ import { type LoopBudget } from "../contracts/index.js";
2
+ export type RunCommandRequest = {
3
+ workspaceId: string;
4
+ projectId: string;
5
+ title: string;
6
+ objective: string;
7
+ verificationPlan: string[];
8
+ metadata: Record<string, string>;
9
+ budget: LoopBudget;
10
+ configPath?: string;
11
+ cwd?: string;
12
+ model?: string;
13
+ engine?: string;
14
+ allowedPaths?: string[];
15
+ deniedPaths?: string[];
16
+ acceptanceCriteria?: string[];
17
+ };
18
+ export type ParsedCliArguments = {
19
+ command: "help";
20
+ } | {
21
+ command: "run";
22
+ request: RunCommandRequest;
23
+ } | {
24
+ command: "bench";
25
+ suiteId: string;
26
+ } | {
27
+ command: "inspect";
28
+ file: string;
29
+ } | {
30
+ command: "resume";
31
+ loopId: string;
32
+ };
33
+ export declare function executeCli(args: string[]): Promise<{
34
+ exitCode: number;
35
+ stdout: string;
36
+ stderr: string;
37
+ }>;
38
+ export declare function parseCliArguments(args: string[]): ParsedCliArguments;
39
+ export declare function renderCliHelp(): string;