martin-loop 0.1.1 → 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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +331 -58
  3. package/dist/bin/martin-loop.js +12 -8
  4. package/dist/index.d.ts +21 -8
  5. package/dist/index.js +31 -9
  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.js +19 -0
  21. package/dist/vendor/cli/index.d.ts +39 -0
  22. package/dist/vendor/cli/index.js +634 -0
  23. package/dist/vendor/cli/persistence.d.ts +34 -0
  24. package/dist/vendor/cli/persistence.js +71 -0
  25. package/dist/vendor/contracts/governance.d.ts +21 -0
  26. package/dist/vendor/contracts/governance.js +12 -0
  27. package/dist/vendor/contracts/index.d.ts +330 -0
  28. package/dist/vendor/contracts/index.js +203 -0
  29. package/dist/vendor/core/compiler.d.ts +50 -0
  30. package/dist/vendor/core/compiler.js +47 -0
  31. package/dist/vendor/core/grounding.d.ts +37 -0
  32. package/dist/vendor/core/grounding.js +270 -0
  33. package/dist/vendor/core/index.d.ts +145 -0
  34. package/dist/vendor/core/index.js +1099 -0
  35. package/dist/vendor/core/leash.d.ts +48 -0
  36. package/dist/vendor/core/leash.js +408 -0
  37. package/dist/vendor/core/persistence/compiler.d.ts +18 -0
  38. package/dist/vendor/core/persistence/compiler.js +35 -0
  39. package/dist/vendor/core/persistence/index.d.ts +6 -0
  40. package/dist/vendor/core/persistence/index.js +4 -0
  41. package/dist/vendor/core/persistence/ledger.d.ts +23 -0
  42. package/dist/vendor/core/persistence/ledger.js +10 -0
  43. package/dist/vendor/core/persistence/store.d.ts +77 -0
  44. package/dist/vendor/core/persistence/store.js +84 -0
  45. package/dist/vendor/core/policy.d.ts +126 -0
  46. package/dist/vendor/core/policy.js +625 -0
  47. package/dist/vendor/core/rollback.d.ts +11 -0
  48. package/dist/vendor/core/rollback.js +219 -0
  49. package/docs/oss/EXAMPLES.md +126 -0
  50. package/docs/oss/OSS-BOUNDARY-REPORT.json +113 -0
  51. package/docs/oss/OSS-BOUNDARY-REPORT.md +48 -0
  52. package/docs/oss/QUICKSTART.md +135 -0
  53. package/docs/{README.md → oss/README.md} +17 -13
  54. package/docs/oss/RELEASE-SURFACE-REPORT.json +45 -0
  55. package/docs/oss/RELEASE-SURFACE-REPORT.md +35 -0
  56. package/package.json +27 -35
  57. package/dist/bin/martin-loop.js.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/docs/EXAMPLES.md +0 -96
  60. package/docs/QUICKSTART.md +0 -127
  61. package/docs/release/CLAIM-TO-CAPABILITY.md +0 -19
  62. /package/dist/{bin/martin-loop.d.ts → vendor/cli/bin/martin.d.ts} +0 -0
@@ -0,0 +1,219 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ export async function captureRollbackBoundary(input) {
5
+ if (!input.repoRoot) {
6
+ return undefined;
7
+ }
8
+ const repoState = readRepoState(input.repoRoot);
9
+ const snapshotPaths = uniqueSorted([
10
+ ...repoState.trackedDirtyFiles,
11
+ ...repoState.untrackedFiles
12
+ ]);
13
+ const snapshots = [];
14
+ for (const filePath of snapshotPaths) {
15
+ snapshots.push(await readRollbackSnapshot(input.repoRoot, filePath));
16
+ }
17
+ return {
18
+ strategy: "git_head_plus_snapshot",
19
+ capturedAt: input.capturedAt,
20
+ ...(readGitScalar(input.repoRoot, ["rev-parse", "HEAD"])
21
+ ? { headRef: readGitScalar(input.repoRoot, ["rev-parse", "HEAD"]) }
22
+ : {}),
23
+ trackedDirtyFiles: repoState.trackedDirtyFiles,
24
+ untrackedFiles: repoState.untrackedFiles,
25
+ snapshots
26
+ };
27
+ }
28
+ export async function restoreRollbackBoundary(input) {
29
+ if (!input.repoRoot) {
30
+ return undefined;
31
+ }
32
+ if (!input.boundary) {
33
+ return {
34
+ attempted: false,
35
+ status: "unavailable",
36
+ restoredAt: input.restoredAt,
37
+ decision: input.decision,
38
+ before: emptyRepoState(),
39
+ after: emptyRepoState(),
40
+ restoredFiles: [],
41
+ deletedFiles: [],
42
+ error: "Rollback boundary was unavailable for this attempt."
43
+ };
44
+ }
45
+ const before = readRepoState(input.repoRoot);
46
+ if (repoStateMatchesBoundary(before, input.boundary)) {
47
+ return {
48
+ attempted: false,
49
+ status: "not_required",
50
+ restoredAt: input.restoredAt,
51
+ decision: input.decision,
52
+ before,
53
+ after: before,
54
+ restoredFiles: [],
55
+ deletedFiles: []
56
+ };
57
+ }
58
+ const restoredFiles = new Set();
59
+ const deletedFiles = new Set();
60
+ try {
61
+ const baselineTracked = new Set(input.boundary.trackedDirtyFiles);
62
+ const baselineUntracked = new Set(input.boundary.untrackedFiles);
63
+ for (const filePath of before.trackedDirtyFiles) {
64
+ if (!baselineTracked.has(filePath)) {
65
+ restoreTrackedFileFromHead(input.repoRoot, filePath);
66
+ restoredFiles.add(filePath);
67
+ }
68
+ }
69
+ for (const filePath of before.untrackedFiles) {
70
+ if (!baselineUntracked.has(filePath)) {
71
+ await removeRepoPath(input.repoRoot, filePath);
72
+ deletedFiles.add(filePath);
73
+ }
74
+ }
75
+ for (const snapshot of input.boundary.snapshots) {
76
+ await restoreRollbackSnapshot(input.repoRoot, snapshot);
77
+ if (snapshot.existed) {
78
+ restoredFiles.add(snapshot.path);
79
+ }
80
+ else {
81
+ deletedFiles.add(snapshot.path);
82
+ }
83
+ }
84
+ const after = readRepoState(input.repoRoot);
85
+ const restored = repoStateMatchesBoundary(after, input.boundary);
86
+ return {
87
+ attempted: true,
88
+ status: restored ? "restored" : "failed",
89
+ restoredAt: input.restoredAt,
90
+ decision: input.decision,
91
+ before,
92
+ after,
93
+ restoredFiles: [...restoredFiles].sort(),
94
+ deletedFiles: [...deletedFiles].sort(),
95
+ ...(restored
96
+ ? {}
97
+ : { error: "Repo state still diverged from the recorded rollback boundary after restore." })
98
+ };
99
+ }
100
+ catch (error) {
101
+ const after = readRepoState(input.repoRoot);
102
+ return {
103
+ attempted: true,
104
+ status: "failed",
105
+ restoredAt: input.restoredAt,
106
+ decision: input.decision,
107
+ before,
108
+ after,
109
+ restoredFiles: [...restoredFiles].sort(),
110
+ deletedFiles: [...deletedFiles].sort(),
111
+ error: toErrorMessage(error)
112
+ };
113
+ }
114
+ }
115
+ function readRepoState(repoRoot) {
116
+ return {
117
+ trackedDirtyFiles: readGitLines(repoRoot, ["diff", "--name-only", "HEAD"]),
118
+ untrackedFiles: readGitLines(repoRoot, ["ls-files", "--others", "--exclude-standard"])
119
+ };
120
+ }
121
+ function readGitLines(repoRoot, args) {
122
+ const result = spawnSync("git", args, {
123
+ cwd: repoRoot,
124
+ encoding: "utf8"
125
+ });
126
+ if (result.status !== 0 || typeof result.stdout !== "string") {
127
+ return [];
128
+ }
129
+ return uniqueSorted(result.stdout
130
+ .split(/\r?\n/u)
131
+ .map((line) => normalizeRepoPath(line))
132
+ .filter(Boolean));
133
+ }
134
+ function readGitScalar(repoRoot, args) {
135
+ const result = spawnSync("git", args, {
136
+ cwd: repoRoot,
137
+ encoding: "utf8"
138
+ });
139
+ if (result.status !== 0 || typeof result.stdout !== "string") {
140
+ return undefined;
141
+ }
142
+ const value = result.stdout.trim();
143
+ return value.length > 0 ? value : undefined;
144
+ }
145
+ async function readRollbackSnapshot(repoRoot, filePath) {
146
+ const absolutePath = resolveRepoPath(repoRoot, filePath);
147
+ try {
148
+ const contents = await readFile(absolutePath);
149
+ return {
150
+ path: normalizeRepoPath(filePath),
151
+ existed: true,
152
+ encoding: "base64",
153
+ contentBase64: contents.toString("base64")
154
+ };
155
+ }
156
+ catch {
157
+ return {
158
+ path: normalizeRepoPath(filePath),
159
+ existed: false,
160
+ encoding: "base64"
161
+ };
162
+ }
163
+ }
164
+ async function restoreRollbackSnapshot(repoRoot, snapshot) {
165
+ const absolutePath = resolveRepoPath(repoRoot, snapshot.path);
166
+ if (!snapshot.existed || !snapshot.contentBase64) {
167
+ await rm(absolutePath, { recursive: true, force: true });
168
+ return;
169
+ }
170
+ await mkdir(dirname(absolutePath), { recursive: true });
171
+ await writeFile(absolutePath, Buffer.from(snapshot.contentBase64, "base64"));
172
+ }
173
+ function restoreTrackedFileFromHead(repoRoot, filePath) {
174
+ const result = spawnSync("git", ["restore", "--staged", "--worktree", "--source=HEAD", "--", filePath], {
175
+ cwd: repoRoot,
176
+ encoding: "utf8"
177
+ });
178
+ if (result.status !== 0) {
179
+ throw new Error(result.stderr?.trim() || `git restore failed for ${filePath}`);
180
+ }
181
+ }
182
+ async function removeRepoPath(repoRoot, filePath) {
183
+ await rm(resolveRepoPath(repoRoot, filePath), { recursive: true, force: true });
184
+ }
185
+ function resolveRepoPath(repoRoot, filePath) {
186
+ const resolvedRoot = resolve(repoRoot);
187
+ const resolvedPath = resolve(resolvedRoot, filePath);
188
+ const relativePath = relative(resolvedRoot, resolvedPath);
189
+ if (relativePath.startsWith("..") || relativePath === "") {
190
+ throw new Error(`Refusing to access a rollback path outside repo root: ${filePath}`);
191
+ }
192
+ return resolvedPath;
193
+ }
194
+ function repoStateMatchesBoundary(state, boundary) {
195
+ return arraysEqual(state.trackedDirtyFiles, boundary.trackedDirtyFiles) &&
196
+ arraysEqual(state.untrackedFiles, boundary.untrackedFiles);
197
+ }
198
+ function arraysEqual(left, right) {
199
+ if (left.length !== right.length) {
200
+ return false;
201
+ }
202
+ return left.every((value, index) => value === right[index]);
203
+ }
204
+ function uniqueSorted(values) {
205
+ return [...new Set(values.map((value) => normalizeRepoPath(value)).filter(Boolean))].sort();
206
+ }
207
+ function normalizeRepoPath(value) {
208
+ return value.trim().replace(/\\/gu, "/");
209
+ }
210
+ function emptyRepoState() {
211
+ return {
212
+ trackedDirtyFiles: [],
213
+ untrackedFiles: []
214
+ };
215
+ }
216
+ function toErrorMessage(error) {
217
+ return error instanceof Error ? error.message : String(error);
218
+ }
219
+ //# sourceMappingURL=rollback.js.map
@@ -0,0 +1,126 @@
1
+ # Examples
2
+
3
+ These examples are grounded in the current CLI and MCP surfaces in this repo. Where an example depends on a real provider path, it is labeled that way explicitly.
4
+
5
+ These are still primarily repo-local RC examples. The root `martin-loop` package facade is now real and smoke-validated, but registry publication remains a later release step.
6
+
7
+ ## 1. Stub-backed hello world
8
+
9
+ Use this when you want a safe first pass through the loop without real model spend.
10
+
11
+ ### PowerShell
12
+
13
+ ```powershell
14
+ $env:MARTIN_LIVE='false'
15
+ pnpm run:cli -- run `
16
+ --workspace ws_demo `
17
+ --project proj_demo `
18
+ --objective "Describe the current Martin run lifecycle in one paragraph" `
19
+ --verify "pnpm --filter @martin/core test"
20
+ Remove-Item Env:MARTIN_LIVE
21
+ ```
22
+
23
+ Why this is useful:
24
+
25
+ - exercises `runMartin`
26
+ - writes a real loop record and artifacts
27
+ - avoids external provider dependencies
28
+
29
+ ## 2. Repo-backed task with explicit scope
30
+
31
+ Use allow and deny paths so the task contract is narrow and reviewable.
32
+
33
+ ```bash
34
+ pnpm run:cli -- run \
35
+ --cwd . \
36
+ --objective "Tighten README wording for the OSS quickstart" \
37
+ --verify "pnpm --filter @martin/core test" \
38
+ --allow-path README.md \
39
+ --allow-path docs/oss/** \
40
+ --deny-path apps/control-plane/** \
41
+ --accept "Only update documentation files" \
42
+ --accept "Do not modify runtime code"
43
+ ```
44
+
45
+ What this demonstrates:
46
+
47
+ - repo root selection with `--cwd`
48
+ - scoped file-edit boundaries
49
+ - acceptance criteria injection into the task contract
50
+
51
+ ## 3. Safety-block example
52
+
53
+ This example is expected to block before execution because the verifier command is unsafe.
54
+
55
+ ```bash
56
+ pnpm run:cli -- run \
57
+ --objective "Try to run an unsafe verifier" \
58
+ --verify "rm -rf ."
59
+ ```
60
+
61
+ Expected behavior:
62
+
63
+ - the leash blocks the verifier command before adapter execution
64
+ - the run exits through a safety-oriented path rather than pretending the command was acceptable
65
+ - the attempt artifact set includes a persisted leash artifact when applicable
66
+
67
+ The point of this example is not that `rm` exists on every machine. The point is that the raw verifier text is evaluated before the process would be allowed to run.
68
+
69
+ ## 4. Budget-constrained live run
70
+
71
+ This is a live-provider example. Only use it when you have the relevant CLI and credentials configured.
72
+
73
+ ```bash
74
+ pnpm run:cli -- run \
75
+ --engine codex \
76
+ --model o3 \
77
+ --objective "Refactor the CLI argument parser for clarity" \
78
+ --verify "pnpm --filter @martin/cli test" \
79
+ --budget-usd 2 \
80
+ --soft-limit-usd 1 \
81
+ --max-iterations 2
82
+ ```
83
+
84
+ What to review afterward:
85
+
86
+ - admission and settlement events in `ledger.jsonl`
87
+ - cost provenance labels in the run artifacts
88
+ - whether the loop stopped for completion, budget pressure, or lack of progress
89
+
90
+ ## 5. MCP invocation shape
91
+
92
+ The MCP server exposes `martin_run`, `martin_inspect`, and `martin_status`.
93
+
94
+ Example `martin_run` payload:
95
+
96
+ ```json
97
+ {
98
+ "objective": "Tighten the local dashboard copy",
99
+ "workingDirectory": ".",
100
+ "engine": "claude",
101
+ "verificationPlan": ["pnpm --filter @martin/control-plane test"],
102
+ "maxUsd": 5,
103
+ "maxIterations": 2,
104
+ "maxTokens": 20000,
105
+ "workspaceId": "ws_mcp",
106
+ "projectId": "proj_mcp"
107
+ }
108
+ ```
109
+
110
+ ## 6. What to inspect in artifacts
111
+
112
+ For a repo-backed attempt, look at:
113
+
114
+ - `contract.json`
115
+ - `state.json`
116
+ - `ledger.jsonl`
117
+ - `artifacts/attempt-XXX/compiled-context.json`
118
+ - `artifacts/attempt-XXX/diff.patch`
119
+ - `artifacts/attempt-XXX/grounding-scan.json`
120
+ - `artifacts/attempt-XXX/leash.json`
121
+ - `artifacts/attempt-XXX/patch-score.json`
122
+ - `artifacts/attempt-XXX/patch-decision.json`
123
+ - `artifacts/attempt-XXX/rollback-boundary.json`
124
+ - `artifacts/attempt-XXX/rollback-outcome.json`
125
+
126
+ Those files are the evidence trail that backs the runtime’s claims.
@@ -0,0 +1,113 @@
1
+ {
2
+ "generatedAt": "2026-04-22T11:35:54.851Z",
3
+ "verdict": "go",
4
+ "publicSurface": {
5
+ "packageName": "martin-loop",
6
+ "canonicalPackageManager": "npm",
7
+ "installCommand": "npm install martin-loop",
8
+ "npxCommand": "npx martin-loop",
9
+ "sdkImportPath": "martin-loop",
10
+ "supportsNpxCommand": true,
11
+ "supportsSdkImport": true
12
+ },
13
+ "ossCorePackages": [
14
+ {
15
+ "name": "@martin/contracts",
16
+ "path": "packages/contracts",
17
+ "private": true,
18
+ "publishAccess": null,
19
+ "workspaceDependencies": [],
20
+ "classification": "oss_core",
21
+ "classificationReason": "Intended Phase 13 OSS core surface."
22
+ },
23
+ {
24
+ "name": "@martin/core",
25
+ "path": "packages/core",
26
+ "private": true,
27
+ "publishAccess": null,
28
+ "workspaceDependencies": [
29
+ "@martin/contracts"
30
+ ],
31
+ "classification": "oss_core",
32
+ "classificationReason": "Intended Phase 13 OSS core surface."
33
+ },
34
+ {
35
+ "name": "@martin/adapters",
36
+ "path": "packages/adapters",
37
+ "private": true,
38
+ "publishAccess": null,
39
+ "workspaceDependencies": [
40
+ "@martin/core"
41
+ ],
42
+ "classification": "oss_core",
43
+ "classificationReason": "Intended Phase 13 OSS core surface."
44
+ },
45
+ {
46
+ "name": "@martin/cli",
47
+ "path": "packages/cli",
48
+ "private": false,
49
+ "publishAccess": "public",
50
+ "workspaceDependencies": [
51
+ "@martin/adapters",
52
+ "@martin/contracts",
53
+ "@martin/core"
54
+ ],
55
+ "classification": "oss_core",
56
+ "classificationReason": "Intended Phase 13 OSS core surface."
57
+ },
58
+ {
59
+ "name": "@martin/mcp",
60
+ "path": "packages/mcp",
61
+ "private": false,
62
+ "publishAccess": "public",
63
+ "workspaceDependencies": [
64
+ "@martin/adapters",
65
+ "@martin/contracts",
66
+ "@martin/core"
67
+ ],
68
+ "classification": "oss_core",
69
+ "classificationReason": "Intended Phase 13 OSS core surface."
70
+ }
71
+ ],
72
+ "nonOssWorkspacePackages": [
73
+ {
74
+ "name": "@martin/control-plane",
75
+ "path": "apps/control-plane",
76
+ "private": true,
77
+ "publishAccess": null,
78
+ "workspaceDependencies": [
79
+ "@martin/contracts"
80
+ ],
81
+ "classification": "non_oss_workspace",
82
+ "classificationReason": "Managed or RC-only workspace surface that stays out of the initial OSS boundary."
83
+ },
84
+ {
85
+ "name": "@martin/benchmarks",
86
+ "path": "benchmarks",
87
+ "private": true,
88
+ "publishAccess": null,
89
+ "workspaceDependencies": [
90
+ "@martin/adapters",
91
+ "@martin/contracts",
92
+ "@martin/core"
93
+ ],
94
+ "classification": "non_oss_workspace",
95
+ "classificationReason": "Managed or RC-only workspace surface that stays out of the initial OSS boundary."
96
+ }
97
+ ],
98
+ "localOnlySurfaces": [
99
+ {
100
+ "path": "apps/local-dashboard",
101
+ "reason": "Local read-model viewer that is not yet packaged as a publishable OSS workspace."
102
+ }
103
+ ],
104
+ "dependencyLeaks": [],
105
+ "summary": {
106
+ "ossCoreCount": 5,
107
+ "nonOssWorkspaceCount": 2,
108
+ "localOnlySurfaceCount": 1,
109
+ "dependencyLeakCount": 0,
110
+ "privateOssCoreCount": 3,
111
+ "publishReadyOssCoreCount": 2
112
+ }
113
+ }
@@ -0,0 +1,48 @@
1
+ # Martin Loop Phase 13 OSS Core Boundary
2
+
3
+ Generated: 2026-04-22T11:35:54.851Z
4
+
5
+ ## Verdict
6
+ **GO**
7
+
8
+ ## Summary
9
+ - Public package target: martin-loop
10
+ - Canonical public package manager: npm
11
+ - Intended OSS core packages: 5
12
+ - Non-OSS workspace packages: 2
13
+ - Local-only surfaces: 1
14
+ - Private OSS-core packages still gated from publish: 3
15
+ - OSS-core packages already publish-configured: 2
16
+ - Dependency leaks: 0
17
+ - No workspace dependency leaks detected between the intended OSS core and the non-OSS workspace surfaces.
18
+
19
+ ## Public Package Surface
20
+ - Install target: `npm install martin-loop`
21
+ - CLI target: `npx martin-loop`
22
+ - SDK target: `import { MartinLoop } from 'martin-loop'`
23
+ - Root `npx martin-loop` support shipped: yes
24
+ - Root SDK import shipped: yes
25
+
26
+ ## Intended OSS Core Packages
27
+ | Package | Path | Private | Publish Access | Workspace Deps |
28
+ |---|---|---|---|---|
29
+ | @martin/contracts | packages/contracts | yes | n/a | none |
30
+ | @martin/core | packages/core | yes | n/a | @martin/contracts |
31
+ | @martin/adapters | packages/adapters | yes | n/a | @martin/core |
32
+ | @martin/cli | packages/cli | no | public | @martin/adapters, @martin/contracts, @martin/core |
33
+ | @martin/mcp | packages/mcp | no | public | @martin/adapters, @martin/contracts, @martin/core |
34
+
35
+ ## Non-OSS Workspace Packages
36
+ | Package | Path | Reason |
37
+ |---|---|---|
38
+ | @martin/control-plane | apps/control-plane | Managed or RC-only workspace surface that stays out of the initial OSS boundary. |
39
+ | @martin/benchmarks | benchmarks | Managed or RC-only workspace surface that stays out of the initial OSS boundary. |
40
+
41
+ ## Local-Only Surfaces
42
+ | Path | Reason |
43
+ |---|---|
44
+ | apps/local-dashboard | Local read-model viewer that is not yet packaged as a publishable OSS workspace. |
45
+
46
+ ## Dependency Leak Review
47
+ - No workspace dependency leaks detected.
48
+
@@ -0,0 +1,135 @@
1
+ # Quickstart
2
+
3
+ This quickstart is intentionally conservative. It is written for a fresh engineer validating the current Phase 13 release-candidate state, not for a hypothetical future public release.
4
+
5
+ ## Public launch target vs current RC path
6
+
7
+ The frozen public launch target is:
8
+
9
+ - `npm install martin-loop`
10
+ - `npx martin-loop ...`
11
+ - `import { MartinLoop } from "martin-loop"`
12
+
13
+ That launch surface is now implemented in the root package facade and smoke-validated from a clean temporary install. This quickstart still documents the honest RC-from-source path because public registry publication is a later release step.
14
+
15
+ ## Prerequisites
16
+
17
+ - Node.js 20+ recommended
18
+ - `pnpm` 10.x
19
+ - A clean local checkout of this repo
20
+
21
+ Optional for live runs:
22
+
23
+ - Claude Code CLI for the Claude adapter path
24
+ - OpenAI Codex CLI plus credentials for the Codex adapter path
25
+
26
+ ## Install and build
27
+
28
+ From the repo root:
29
+
30
+ ```bash
31
+ pnpm install
32
+ pnpm build
33
+ ```
34
+
35
+ ## Run the RC validation matrix
36
+
37
+ ```bash
38
+ pnpm rc:validate
39
+ ```
40
+
41
+ What this does:
42
+
43
+ - creates an isolated temporary home or profile directory
44
+ - points Martin run artifacts at that clean location
45
+ - runs the current build, lint, test, benchmark, and certification matrix
46
+ - writes step logs into a temp `martin-rc-validation-*` directory
47
+
48
+ Use this when you want to answer, "Can a fresh environment still reproduce the current RC baseline?"
49
+
50
+ ## RC gate commands
51
+
52
+ The current Phase 13 RC gate is made of these commands:
53
+
54
+ - `pnpm oss:validate`
55
+ - `pnpm public:smoke`
56
+ - `pnpm repo:smoke`
57
+ - `pnpm rc:validate`
58
+ - `pnpm pilot:prep:validate`
59
+ - `pnpm release:matrix:local`
60
+
61
+ Recommended order for a fresh local reviewer:
62
+
63
+ ```bash
64
+ pnpm oss:validate
65
+ pnpm public:smoke
66
+ pnpm repo:smoke
67
+ pnpm rc:validate
68
+ pnpm release:matrix:local
69
+ ```
70
+
71
+ `pnpm release:matrix:local` runs the full local OS lane for the current machine. The repository also defines Windows, macOS, and Linux CI lanes in `.github/workflows/phase13-release-matrix.yml`.
72
+
73
+ ## Stub-safe CLI run
74
+
75
+ This is the safest first run because it avoids real provider spend.
76
+
77
+ ### PowerShell
78
+
79
+ ```powershell
80
+ $env:MARTIN_LIVE='false'
81
+ pnpm run:cli -- run --objective "Summarize the current runtime state" --verify "pnpm --filter @martin/core test"
82
+ Remove-Item Env:MARTIN_LIVE
83
+ ```
84
+
85
+ ### Bash
86
+
87
+ ```bash
88
+ MARTIN_LIVE=false pnpm run:cli -- run --objective "Summarize the current runtime state" --verify "pnpm --filter @martin/core test"
89
+ ```
90
+
91
+ This path uses the stub adapter and still exercises the loop, persistence, and policy surfaces.
92
+
93
+ ## Config-driven run
94
+
95
+ The repo ships an example config at `martin.config.example.yaml`.
96
+
97
+ Martin auto-looks for `martin.config.yaml` in the invocation root, or you can pass `--config <path>`.
98
+
99
+ Example:
100
+
101
+ ```bash
102
+ pnpm run:cli -- run --config martin.config.example.yaml --objective "Run with repo defaults" --verify "pnpm --filter @martin/core test"
103
+ ```
104
+
105
+ ## Inspect a saved run
106
+
107
+ Martin persists runs under `~/.martin/runs/` by default, or under `MARTIN_RUNS_DIR` if you override it.
108
+
109
+ ```bash
110
+ pnpm run:cli -- inspect --file path/to/loop-record.json
111
+ ```
112
+
113
+ For persisted run folders, inspect the `contract.json`, `state.json`, `ledger.jsonl`, and `artifacts/attempt-XXX/` files together. Those artifacts are the source of truth for runtime behavior.
114
+
115
+ ## MCP server
116
+
117
+ Build first, then start the server from the workspace:
118
+
119
+ ```bash
120
+ pnpm --filter @martin/mcp build
121
+ node packages/mcp/dist/server.js
122
+ ```
123
+
124
+ The current MCP tools are:
125
+
126
+ - `martin_run`
127
+ - `martin_inspect`
128
+ - `martin_status`
129
+
130
+ ## Notes for reviewers
131
+
132
+ - Fresh-home behavior matters. Do not rely only on a long-lived `~/.martin` directory.
133
+ - Exact-versus-estimated cost labels are meaningful and should not be merged in docs or dashboards.
134
+ - The repo contains control-plane code, but the public OSS boundary is still being finalized during Phase 13.
135
+ - The benchmark harness remains a workspace-level RC surface; `martin bench` is not part of the publishable CLI boundary yet.