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,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
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-04-21T09:43:03.509Z",
2
+ "generatedAt": "2026-04-23T15:03:09.849Z",
3
3
  "verdict": "go",
4
4
  "publicSurface": {
5
5
  "packageName": "martin-loop",
@@ -1,6 +1,6 @@
1
1
  # Martin Loop Phase 13 OSS Core Boundary
2
2
 
3
- Generated: 2026-04-21T09:43:03.509Z
3
+ Generated: 2026-04-23T15:03:09.849Z
4
4
 
5
5
  ## Verdict
6
6
  **GO**
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-04-21T09:43:03.931Z",
2
+ "generatedAt": "2026-04-23T15:03:11.006Z",
3
3
  "publicSurface": {
4
4
  "packageName": "martin-loop",
5
5
  "installCommand": "npm install martin-loop",
@@ -1,6 +1,6 @@
1
1
  # Martin Loop Phase 13 Release Surface Audit
2
2
 
3
- Generated: 2026-04-21T09:43:03.931Z
3
+ Generated: 2026-04-23T15:03:11.006Z
4
4
 
5
5
  ## Verdict
6
6
  **GO**
package/package.json CHANGED
@@ -1,54 +1,54 @@
1
- {
2
- "name": "martin-loop",
3
- "private": false,
4
- "version": "0.1.2",
5
- "type": "module",
6
- "description": "Martin Loop dual-track monorepo for the OSS runtime and hosted SaaS control plane.",
7
- "packageManager": "pnpm@10.17.1",
8
- "main": "./dist/index.js",
9
- "types": "./dist/index.d.ts",
10
- "bin": {
11
- "martin-loop": "./dist/bin/martin-loop.js",
12
- "martin": "./dist/bin/martin-loop.js"
13
- },
14
- "exports": {
15
- ".": {
16
- "types": "./dist/index.d.ts",
17
- "default": "./dist/index.js"
18
- },
19
- "./package.json": "./package.json"
20
- },
21
- "files": [
22
- "dist",
23
- "README.md",
24
- "docs/oss"
25
- ],
26
- "publishConfig": {
27
- "access": "public"
28
- },
29
- "scripts": {
30
- "build": "pnpm -r build && node ./scripts/build-public-facade.mjs",
31
- "test": "pnpm -r test",
32
- "lint": "pnpm -r lint",
33
- "oss:validate": "node ./scripts/oss-boundary.mjs",
34
- "release:surface:validate": "node ./scripts/release-surface-audit.mjs",
35
- "pilot:prep:validate": "node ./scripts/pilot-prep-audit.mjs",
36
- "public:smoke": "node ./scripts/public-facade-smoke.mjs",
37
- "repo:smoke": "node ./scripts/repo-backed-smoke.mjs",
38
- "rc:validate": "node ./scripts/rc-validation.mjs",
39
- "rc:validate:install": "node ./scripts/rc-validation.mjs --install",
40
- "release:matrix:local": "node ./scripts/release-matrix.mjs",
41
- "dev:control-plane": "pnpm --filter @martin/control-plane dev",
42
- "run:cli": "pnpm --filter @martin/cli dev",
43
- "eval": "pnpm --filter @martin/benchmarks eval",
44
- "dev:dashboard": "node apps/local-dashboard/server.js"
45
- },
46
- "devDependencies": {
47
- "@types/node": "^22.13.10",
48
- "@types/react": "^19.0.12",
49
- "@types/react-dom": "^19.0.5",
50
- "tsx": "^4.19.3",
51
- "typescript": "^5.8.2",
52
- "vitest": "^3.0.8"
53
- }
54
- }
1
+ {
2
+ "name": "martin-loop",
3
+ "private": false,
4
+ "version": "0.1.4",
5
+ "type": "module",
6
+ "description": "Martin Loop dual-track monorepo for the OSS runtime and hosted SaaS control plane.",
7
+ "license": "MIT",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "bin": {
11
+ "martin-loop": "./dist/bin/martin-loop.js",
12
+ "martin": "./dist/bin/martin-loop.js"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "docs/oss"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.13.10",
31
+ "@types/react": "^19.0.12",
32
+ "@types/react-dom": "^19.0.5",
33
+ "tsx": "^4.19.3",
34
+ "typescript": "^5.8.2",
35
+ "vitest": "^3.0.8"
36
+ },
37
+ "scripts": {
38
+ "build": "pnpm -r build && node ./scripts/build-public-facade.mjs",
39
+ "test": "pnpm -r test",
40
+ "lint": "pnpm -r lint",
41
+ "oss:validate": "node ./scripts/oss-boundary.mjs",
42
+ "release:surface:validate": "node ./scripts/release-surface-audit.mjs",
43
+ "pilot:prep:validate": "node ./scripts/pilot-prep-audit.mjs",
44
+ "public:smoke": "node ./scripts/public-facade-smoke.mjs",
45
+ "repo:smoke": "node ./scripts/repo-backed-smoke.mjs",
46
+ "rc:validate": "node ./scripts/rc-validation.mjs",
47
+ "rc:validate:install": "node ./scripts/rc-validation.mjs --install",
48
+ "release:matrix:local": "node ./scripts/release-matrix.mjs",
49
+ "dev:control-plane": "pnpm --filter @martin/control-plane dev",
50
+ "run:cli": "pnpm --filter @martin/cli dev",
51
+ "eval": "pnpm --filter @martin/benchmarks eval",
52
+ "dev:dashboard": "node apps/local-dashboard/server.js"
53
+ }
54
+ }