@martinloop/mcp 0.7.1 → 0.8.1

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 (77) hide show
  1. package/dist/package-version.d.ts +1 -1
  2. package/dist/package-version.js +1 -1
  3. package/dist/vendor/adapters/claude-cli.d.ts +16 -1
  4. package/dist/vendor/adapters/claude-cli.js +286 -71
  5. package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
  6. package/dist/vendor/adapters/cli-bridge.js +129 -149
  7. package/dist/vendor/adapters/codex-capabilities.d.ts +4 -1
  8. package/dist/vendor/adapters/codex-capabilities.js +36 -4
  9. package/dist/vendor/adapters/codex-cli.d.ts +10 -3
  10. package/dist/vendor/adapters/codex-cli.js +21 -24
  11. package/dist/vendor/adapters/codex-host.js +150 -132
  12. package/dist/vendor/adapters/index.d.ts +3 -2
  13. package/dist/vendor/adapters/index.js +3 -2
  14. package/dist/vendor/adapters/openai-compatible.js +88 -6
  15. package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
  16. package/dist/vendor/adapters/process-supervisor.js +541 -0
  17. package/dist/vendor/adapters/verifier-only.d.ts +10 -1
  18. package/dist/vendor/adapters/verifier-only.js +43 -1
  19. package/dist/vendor/contracts/external-outcome.d.ts +102 -0
  20. package/dist/vendor/contracts/external-outcome.js +226 -0
  21. package/dist/vendor/contracts/index.d.ts +12 -1
  22. package/dist/vendor/contracts/index.js +2 -0
  23. package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
  24. package/dist/vendor/contracts/swarm-hosted.js +629 -0
  25. package/dist/vendor/contracts/swarm.d.ts +449 -0
  26. package/dist/vendor/contracts/swarm.js +550 -0
  27. package/dist/vendor/core/external-outcome.d.ts +34 -0
  28. package/dist/vendor/core/external-outcome.js +515 -0
  29. package/dist/vendor/core/grounding.js +1 -1
  30. package/dist/vendor/core/index.d.ts +52 -1
  31. package/dist/vendor/core/index.js +55 -3
  32. package/dist/vendor/core/leash.d.ts +1 -0
  33. package/dist/vendor/core/leash.js +13 -0
  34. package/dist/vendor/core/persistence/integrity.d.ts +4 -0
  35. package/dist/vendor/core/persistence/integrity.js +19 -6
  36. package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
  37. package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
  38. package/dist/vendor/core/policy.d.ts +3 -0
  39. package/dist/vendor/core/policy.js +13 -0
  40. package/dist/vendor/core/swarm/candidates.d.ts +78 -0
  41. package/dist/vendor/core/swarm/candidates.js +810 -0
  42. package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
  43. package/dist/vendor/core/swarm/evidence-projection.js +128 -0
  44. package/dist/vendor/core/swarm/evidence.d.ts +40 -0
  45. package/dist/vendor/core/swarm/evidence.js +575 -0
  46. package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
  47. package/dist/vendor/core/swarm/hosted-export.js +362 -0
  48. package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
  49. package/dist/vendor/core/swarm/hosted-projection.js +258 -0
  50. package/dist/vendor/core/swarm/index.d.ts +110 -0
  51. package/dist/vendor/core/swarm/index.js +1060 -0
  52. package/dist/vendor/core/swarm/integration.d.ts +76 -0
  53. package/dist/vendor/core/swarm/integration.js +615 -0
  54. package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
  55. package/dist/vendor/core/swarm/lifecycle.js +252 -0
  56. package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
  57. package/dist/vendor/core/swarm/live-runtime.js +1482 -0
  58. package/dist/vendor/core/swarm/live-store.d.ts +99 -0
  59. package/dist/vendor/core/swarm/live-store.js +860 -0
  60. package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
  61. package/dist/vendor/core/swarm/operational-reader.js +96 -0
  62. package/dist/vendor/core/swarm/operations.d.ts +29 -0
  63. package/dist/vendor/core/swarm/operations.js +128 -0
  64. package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
  65. package/dist/vendor/core/swarm/parent-receipt.js +489 -0
  66. package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
  67. package/dist/vendor/core/swarm/receipt-projection.js +120 -0
  68. package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
  69. package/dist/vendor/core/swarm/scheduler.js +555 -0
  70. package/dist/vendor/core/swarm/verification.d.ts +70 -0
  71. package/dist/vendor/core/swarm/verification.js +295 -0
  72. package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
  73. package/dist/vendor/core/swarm/workspaces.js +488 -0
  74. package/dist/vendor/core/verified-handoff.d.ts +4 -0
  75. package/dist/vendor/core/verified-handoff.js +4 -0
  76. package/package.json +1 -1
  77. package/server.json +2 -2
@@ -0,0 +1,488 @@
1
+ import { execFile } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { lstat, mkdir, realpath, rm } from "node:fs/promises";
4
+ import { isAbsolute, join, relative, resolve } from "node:path";
5
+ import { SWARM_SCHEMA_VERSION } from "../../contracts/index.js";
6
+ const MAX_GIT_OUTPUT_BYTES = 8 * 1024 * 1024;
7
+ const FILENAME_SAFE_ID = /^[a-z0-9](?:[a-z0-9_-]|\.(?=[a-z0-9_-])){0,127}$/iu;
8
+ export class SwarmWorkspaceError extends Error {
9
+ code;
10
+ constructor(code, message, options) {
11
+ super(message, options);
12
+ this.name = "SwarmWorkspaceError";
13
+ this.code = code;
14
+ }
15
+ }
16
+ export class SwarmWorkspaceCreationError extends SwarmWorkspaceError {
17
+ originalCode;
18
+ cleanup;
19
+ recoveryHandle;
20
+ constructor(input) {
21
+ super("WORKTREE_VALIDATION_CLEANUP_PENDING", `Workspace validation failed (${input.original.code}) and exact-path rollback is pending.`, { cause: input.original });
22
+ this.name = "SwarmWorkspaceCreationError";
23
+ this.originalCode = input.original.code;
24
+ this.cleanup = input.cleanup;
25
+ this.recoveryHandle = input.recoveryHandle;
26
+ }
27
+ }
28
+ export async function createSwarmWorkspaceManager(input) {
29
+ assertSwarmPathIdentifier(input.swarmId, "swarm ID");
30
+ const gitRunner = input.gitRunner ?? runGit;
31
+ const fs = input.fs ?? defaultFs;
32
+ const now = input.now ?? (() => new Date().toISOString());
33
+ const createCleanupId = input.createCleanupId ?? (() => `cleanup-${randomUUID()}`);
34
+ const isolationMode = input.isolationMode ?? "worktree";
35
+ let canonicalRoot;
36
+ try {
37
+ canonicalRoot = await fs.realpath(resolve(input.canonicalRoot));
38
+ }
39
+ catch (error) {
40
+ throw new SwarmWorkspaceError("CANONICAL_ROOT_UNAVAILABLE", "Canonical root is unavailable or is not a Git repository.", { cause: error });
41
+ }
42
+ const topLevel = await gitScalarOrThrow(gitRunner, canonicalRoot, ["rev-parse", "--show-toplevel"], "NOT_A_GIT_REPOSITORY", "Canonical root must be a Git repository.");
43
+ const realTopLevel = await fs.realpath(resolve(topLevel));
44
+ if (!pathsEqual(canonicalRoot, realTopLevel)) {
45
+ throw new SwarmWorkspaceError("CANONICAL_ROOT_NOT_TOP_LEVEL", "Canonical root must be the real Git repository top level.");
46
+ }
47
+ await assertCanonicalClean(gitRunner, canonicalRoot);
48
+ const baselineCommit = await gitScalarOrThrow(gitRunner, canonicalRoot, ["rev-parse", "--verify", "HEAD^{commit}"], "BASELINE_UNAVAILABLE", "Unable to resolve one immutable canonical baseline commit.");
49
+ await fs.mkdir(resolve(input.ownedRoot));
50
+ const ownedRoot = await fs.realpath(resolve(input.ownedRoot));
51
+ if (pathsEqual(ownedRoot, canonicalRoot) || isPathContained(ownedRoot, canonicalRoot)) {
52
+ throw new SwarmWorkspaceError("UNSAFE_OWNED_ROOT", "Swarm-owned root must not be the canonical checkout or one of its ancestors.");
53
+ }
54
+ // Creating an in-repository owned root is safe only when repository ignore
55
+ // policy keeps that runtime state out of the canonical worktree.
56
+ await assertCanonicalClean(gitRunner, canonicalRoot);
57
+ const registered = new Map();
58
+ const registeredIds = new Set();
59
+ let operationTail = Promise.resolve();
60
+ const runExclusively = async (operation) => {
61
+ const pending = operationTail.then(operation, operation);
62
+ operationTail = pending.then(() => undefined, () => undefined);
63
+ return pending;
64
+ };
65
+ const createWorkspace = async (spec) => runExclusively(async () => {
66
+ assertSwarmPathIdentifier(spec.workspaceId, "workspace ID");
67
+ if (registeredIds.has(spec.workspaceId)) {
68
+ throw new SwarmWorkspaceError("DUPLICATE_WORKSPACE_ID", `Workspace ID is already registered: ${spec.workspaceId}`);
69
+ }
70
+ validateSpec(spec);
71
+ await assertCanonicalClean(gitRunner, canonicalRoot);
72
+ const currentHead = await gitScalarOrThrow(gitRunner, canonicalRoot, ["rev-parse", "--verify", "HEAD^{commit}"], "BASELINE_UNAVAILABLE", "Unable to verify the canonical baseline commit.");
73
+ if (currentHead !== baselineCommit) {
74
+ throw new SwarmWorkspaceError("CANONICAL_BASELINE_CHANGED", "Canonical HEAD no longer matches the captured swarm baseline.");
75
+ }
76
+ const workspacePath = resolve(join(ownedRoot, spec.workspaceId));
77
+ if (!isStrictlyContained(ownedRoot, workspacePath)) {
78
+ throw new SwarmWorkspaceError("WORKSPACE_PATH_ESCAPE", "Workspace path escapes the real swarm-owned root.");
79
+ }
80
+ if (await fs.pathExists(workspacePath)) {
81
+ throw new SwarmWorkspaceError("WORKSPACE_PATH_EXISTS", `Workspace path already exists for ID: ${spec.workspaceId}`);
82
+ }
83
+ if (isolationMode === "independent_clone") {
84
+ await runGitOrThrow(gitRunner, canonicalRoot, ["clone", "--no-hardlinks", "--no-checkout", canonicalRoot, workspacePath], "CLONE_CREATE_FAILED", `Git could not create isolated clone ${spec.workspaceId}.`);
85
+ await runGitOrThrow(gitRunner, workspacePath, ["-c", "core.autocrlf=false", "checkout", "--detach", baselineCommit], "CLONE_CHECKOUT_FAILED", `Git could not checkout the immutable baseline for ${spec.workspaceId}.`);
86
+ }
87
+ else {
88
+ await runGitOrThrow(gitRunner, canonicalRoot, ["-c", "core.autocrlf=false", "worktree", "add", "--detach", workspacePath, baselineCommit], "WORKTREE_CREATE_FAILED", `Git could not create detached workspace ${spec.workspaceId}.`);
89
+ }
90
+ let workspaceRealPath = workspacePath;
91
+ try {
92
+ workspaceRealPath = await fs.realpath(workspacePath);
93
+ if (!isStrictlyContained(ownedRoot, workspaceRealPath)) {
94
+ throw new SwarmWorkspaceError("WORKSPACE_REALPATH_ESCAPE", "Created workspace resolves outside the real swarm-owned root.");
95
+ }
96
+ if (pathsEqual(workspaceRealPath, canonicalRoot)) {
97
+ throw new SwarmWorkspaceError("CANONICAL_WORKSPACE_FORBIDDEN", "A mutating swarm workspace cannot be the canonical checkout.");
98
+ }
99
+ if (isolationMode === "independent_clone") {
100
+ await assertIndependentCloneIdentity({
101
+ gitRunner,
102
+ fs,
103
+ workspaceRealPath,
104
+ baselineCommit
105
+ });
106
+ }
107
+ else {
108
+ await assertWorktreeIdentity({
109
+ gitRunner,
110
+ fs,
111
+ canonicalRoot,
112
+ workspaceRealPath,
113
+ baselineCommit
114
+ });
115
+ }
116
+ }
117
+ catch (validationFailure) {
118
+ const original = asWorkspaceError(validationFailure);
119
+ const attemptedAt = now();
120
+ try {
121
+ if (isolationMode === "independent_clone") {
122
+ await removeOwnedDirectory(fs, workspacePath);
123
+ }
124
+ else {
125
+ await gitRunner({
126
+ cwd: canonicalRoot,
127
+ args: ["worktree", "remove", "--force", workspacePath]
128
+ });
129
+ }
130
+ }
131
+ catch (rollbackFailure) {
132
+ const errorCode = safeErrorCode(rollbackFailure);
133
+ const cleanupPending = errorCode === "EPERM" || errorCode === "EBUSY";
134
+ const record = freezeWorkspaceRecord(makeWorkspaceRecord({
135
+ spec,
136
+ swarmId: input.swarmId,
137
+ baselineCommit,
138
+ createdAt: attemptedAt,
139
+ state: "cleanup_pending"
140
+ }));
141
+ const recoveryHandle = Object.freeze({
142
+ path: workspaceRealPath,
143
+ record,
144
+ isolationMode
145
+ });
146
+ registered.set(recoveryHandle, {
147
+ handle: recoveryHandle,
148
+ path: workspacePath,
149
+ realPath: workspaceRealPath,
150
+ record,
151
+ validated: false,
152
+ isolationMode
153
+ });
154
+ registeredIds.add(spec.workspaceId);
155
+ const cleanup = freezeCleanupRecord({
156
+ schemaVersion: SWARM_SCHEMA_VERSION,
157
+ cleanupId: createCleanupId(),
158
+ swarmId: input.swarmId,
159
+ workspaceId: spec.workspaceId,
160
+ workspaceKind: spec.kind,
161
+ evidencePersisted: false,
162
+ processCloseState: "not_required",
163
+ removalState: "failed",
164
+ state: cleanupPending ? "cleanup_pending" : "failed",
165
+ attemptedAt,
166
+ errorCode
167
+ });
168
+ throw new SwarmWorkspaceCreationError({ original, cleanup, recoveryHandle });
169
+ }
170
+ throw original;
171
+ }
172
+ const record = freezeWorkspaceRecord(makeWorkspaceRecord({
173
+ spec,
174
+ swarmId: input.swarmId,
175
+ baselineCommit,
176
+ createdAt: now()
177
+ }));
178
+ const handle = Object.freeze({
179
+ path: workspaceRealPath,
180
+ record,
181
+ isolationMode
182
+ });
183
+ registered.set(handle, {
184
+ handle,
185
+ path: workspacePath,
186
+ realPath: workspaceRealPath,
187
+ record,
188
+ validated: true,
189
+ isolationMode
190
+ });
191
+ registeredIds.add(spec.workspaceId);
192
+ return handle;
193
+ });
194
+ const removeWorkspace = async (handle, gate) => runExclusively(async () => {
195
+ const entry = registered.get(handle);
196
+ if (entry === undefined || entry.handle !== handle) {
197
+ throw new SwarmWorkspaceError("UNREGISTERED_WORKSPACE_HANDLE", "Cleanup requires the exact manager-issued registered workspace handle.");
198
+ }
199
+ if (!gate.evidencePersisted) {
200
+ throw new SwarmWorkspaceError("EVIDENCE_NOT_PERSISTED", "Workspace cleanup cannot begin before evidence is persisted.");
201
+ }
202
+ if (!gate.processTreeClosed) {
203
+ throw new SwarmWorkspaceError("PROCESS_TREE_ACTIVE", "Workspace cleanup cannot begin before its process tree is closed.");
204
+ }
205
+ if (pathsEqual(entry.realPath, canonicalRoot)) {
206
+ throw new SwarmWorkspaceError("CANONICAL_REMOVE_FORBIDDEN", "Refusing to remove the canonical checkout.");
207
+ }
208
+ const currentRealPath = await fs.realpath(entry.path);
209
+ if (!pathsEqual(currentRealPath, entry.realPath) || !isStrictlyContained(ownedRoot, currentRealPath)) {
210
+ throw new SwarmWorkspaceError("WORKSPACE_IDENTITY_CHANGED", "Registered workspace path no longer resolves to the owned workspace.");
211
+ }
212
+ if (entry.isolationMode === "independent_clone") {
213
+ if (entry.validated) {
214
+ await assertIndependentCloneIdentity({
215
+ gitRunner,
216
+ fs,
217
+ workspaceRealPath: entry.realPath,
218
+ baselineCommit
219
+ });
220
+ }
221
+ }
222
+ else {
223
+ await assertListedWorktree(gitRunner, canonicalRoot, entry.realPath, entry.validated ? baselineCommit : undefined);
224
+ }
225
+ const attemptedAt = now();
226
+ try {
227
+ if (entry.isolationMode === "independent_clone") {
228
+ await removeOwnedDirectory(fs, entry.path);
229
+ }
230
+ else {
231
+ const args = [
232
+ "worktree",
233
+ "remove",
234
+ ...(gate.force === true ? ["--force"] : []),
235
+ entry.path
236
+ ];
237
+ await gitRunner({ cwd: canonicalRoot, args });
238
+ }
239
+ registered.delete(handle);
240
+ registeredIds.delete(entry.record.workspaceId);
241
+ return freezeCleanupRecord({
242
+ schemaVersion: SWARM_SCHEMA_VERSION,
243
+ cleanupId: createCleanupId(),
244
+ swarmId: input.swarmId,
245
+ workspaceId: entry.record.workspaceId,
246
+ workspaceKind: entry.record.kind,
247
+ ...(gate.ownedProcessId === undefined ? {} : { ownedProcessId: gate.ownedProcessId }),
248
+ evidencePersisted: true,
249
+ processCloseState: "closed",
250
+ removalState: "removed",
251
+ state: "completed",
252
+ attemptedAt,
253
+ completedAt: now()
254
+ });
255
+ }
256
+ catch (error) {
257
+ const errorCode = safeErrorCode(error);
258
+ const cleanupPending = errorCode === "EPERM" || errorCode === "EBUSY";
259
+ return freezeCleanupRecord({
260
+ schemaVersion: SWARM_SCHEMA_VERSION,
261
+ cleanupId: createCleanupId(),
262
+ swarmId: input.swarmId,
263
+ workspaceId: entry.record.workspaceId,
264
+ workspaceKind: entry.record.kind,
265
+ ...(gate.ownedProcessId === undefined ? {} : { ownedProcessId: gate.ownedProcessId }),
266
+ evidencePersisted: true,
267
+ processCloseState: "closed",
268
+ removalState: "failed",
269
+ state: cleanupPending ? "cleanup_pending" : "failed",
270
+ attemptedAt,
271
+ errorCode
272
+ });
273
+ }
274
+ });
275
+ return Object.freeze({
276
+ baselineCommit,
277
+ canonicalRoot,
278
+ ownedRoot,
279
+ createWorkspace,
280
+ removeWorkspace
281
+ });
282
+ }
283
+ async function removeOwnedDirectory(fs, path) {
284
+ if (fs.rm) {
285
+ await fs.rm(path);
286
+ return;
287
+ }
288
+ await rm(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
289
+ }
290
+ const defaultFs = {
291
+ async mkdir(path) {
292
+ await mkdir(path, { recursive: true });
293
+ },
294
+ realpath,
295
+ async pathExists(path) {
296
+ try {
297
+ await lstat(path);
298
+ return true;
299
+ }
300
+ catch (error) {
301
+ if (isNodeError(error) && error.code === "ENOENT")
302
+ return false;
303
+ throw error;
304
+ }
305
+ },
306
+ async rm(path) {
307
+ await rm(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
308
+ }
309
+ };
310
+ function runGit(command) {
311
+ return new Promise((resolvePromise, rejectPromise) => {
312
+ execFile("git", [...command.args], {
313
+ cwd: command.cwd,
314
+ encoding: "utf8",
315
+ windowsHide: true,
316
+ maxBuffer: MAX_GIT_OUTPUT_BYTES
317
+ }, (error, stdout, stderr) => {
318
+ if (error) {
319
+ rejectPromise(error);
320
+ return;
321
+ }
322
+ resolvePromise({ stdout: String(stdout), stderr: String(stderr) });
323
+ });
324
+ });
325
+ }
326
+ async function assertCanonicalClean(gitRunner, canonicalRoot) {
327
+ const result = await runGitOrThrow(gitRunner, canonicalRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], "CANONICAL_STATUS_FAILED", "Unable to inspect canonical checkout status.");
328
+ if (result.stdout.length > 0) {
329
+ throw new SwarmWorkspaceError("CANONICAL_CHECKOUT_DIRTY", "Swarm isolation requires a clean canonical checkout.");
330
+ }
331
+ }
332
+ async function assertIndependentCloneIdentity(input) {
333
+ const topLevel = await gitScalarOrThrow(input.gitRunner, input.workspaceRealPath, ["rev-parse", "--show-toplevel"], "CLONE_TOP_LEVEL_FAILED", "Created isolated clone is not a valid Git repository.");
334
+ const realTopLevel = await input.fs.realpath(resolve(topLevel));
335
+ if (!pathsEqual(realTopLevel, input.workspaceRealPath)) {
336
+ throw new SwarmWorkspaceError("CLONE_TOP_LEVEL_MISMATCH", "Created isolated clone top level does not match its registered real path.");
337
+ }
338
+ const head = await gitScalarOrThrow(input.gitRunner, input.workspaceRealPath, ["rev-parse", "--verify", "HEAD^{commit}"], "CLONE_HEAD_FAILED", "Unable to verify isolated clone HEAD.");
339
+ if (head !== input.baselineCommit) {
340
+ throw new SwarmWorkspaceError("CLONE_BASELINE_MISMATCH", "Created isolated clone HEAD does not match the captured baseline.");
341
+ }
342
+ const gitDir = await gitScalarOrThrow(input.gitRunner, input.workspaceRealPath, ["rev-parse", "--git-dir"], "CLONE_GIT_DIR_FAILED", "Unable to resolve isolated clone Git metadata.");
343
+ const gitDirRealPath = await input.fs.realpath(resolve(input.workspaceRealPath, gitDir));
344
+ if (!isStrictlyContained(input.workspaceRealPath, gitDirRealPath)) {
345
+ throw new SwarmWorkspaceError("CLONE_GIT_DIR_ESCAPE", "Isolated clone Git metadata must remain inside the clone workspace.");
346
+ }
347
+ }
348
+ async function assertWorktreeIdentity(input) {
349
+ const topLevel = await gitScalarOrThrow(input.gitRunner, input.workspaceRealPath, ["rev-parse", "--show-toplevel"], "WORKTREE_TOP_LEVEL_FAILED", "Created workspace is not a valid Git worktree.");
350
+ const realTopLevel = await input.fs.realpath(resolve(topLevel));
351
+ if (!pathsEqual(realTopLevel, input.workspaceRealPath)) {
352
+ throw new SwarmWorkspaceError("WORKTREE_TOP_LEVEL_MISMATCH", "Created workspace top level does not match its registered real path.");
353
+ }
354
+ const head = await gitScalarOrThrow(input.gitRunner, input.workspaceRealPath, ["rev-parse", "--verify", "HEAD^{commit}"], "WORKTREE_HEAD_FAILED", "Unable to verify created workspace HEAD.");
355
+ if (head !== input.baselineCommit) {
356
+ throw new SwarmWorkspaceError("WORKTREE_BASELINE_MISMATCH", "Created workspace HEAD does not match the captured baseline.");
357
+ }
358
+ await assertListedWorktree(input.gitRunner, input.canonicalRoot, input.workspaceRealPath, input.baselineCommit);
359
+ }
360
+ async function assertListedWorktree(gitRunner, canonicalRoot, expectedRealPath, baselineCommit) {
361
+ const result = await runGitOrThrow(gitRunner, canonicalRoot, ["worktree", "list", "--porcelain", "-z"], "WORKTREE_LIST_FAILED", "Unable to verify registered Git worktrees.");
362
+ const listed = parseWorktreeList(result.stdout).find(({ path }) => pathsEqual(path, expectedRealPath));
363
+ if (listed === undefined) {
364
+ throw new SwarmWorkspaceError("WORKTREE_NOT_REGISTERED", "Workspace is absent from Git worktree registration.");
365
+ }
366
+ if (baselineCommit !== undefined && listed.head !== baselineCommit) {
367
+ throw new SwarmWorkspaceError("WORKTREE_LIST_BASELINE_MISMATCH", "Registered worktree HEAD does not match the captured baseline.");
368
+ }
369
+ }
370
+ function parseWorktreeList(stdout) {
371
+ const records = [];
372
+ let current;
373
+ for (const field of stdout.split("\0")) {
374
+ if (field.length === 0) {
375
+ if (current !== undefined)
376
+ records.push(current);
377
+ current = undefined;
378
+ continue;
379
+ }
380
+ if (field.startsWith("worktree ")) {
381
+ if (current !== undefined)
382
+ records.push(current);
383
+ current = { path: field.slice("worktree ".length) };
384
+ continue;
385
+ }
386
+ if (field.startsWith("HEAD ") && current !== undefined) {
387
+ current.head = field.slice("HEAD ".length);
388
+ }
389
+ }
390
+ if (current !== undefined)
391
+ records.push(current);
392
+ return records;
393
+ }
394
+ function makeWorkspaceRecord(input) {
395
+ const base = {
396
+ schemaVersion: SWARM_SCHEMA_VERSION,
397
+ workspaceId: input.spec.workspaceId,
398
+ swarmId: input.swarmId,
399
+ baselineCommit: input.baselineCommit,
400
+ state: input.state ?? "active",
401
+ createdAt: input.createdAt
402
+ };
403
+ if (input.spec.kind === "child") {
404
+ return {
405
+ ...base,
406
+ kind: "child",
407
+ childRunId: input.spec.childRunId,
408
+ agentId: input.spec.agentId,
409
+ taskIds: Object.freeze([...input.spec.taskIds])
410
+ };
411
+ }
412
+ return { ...base, kind: input.spec.kind };
413
+ }
414
+ function freezeWorkspaceRecord(record) {
415
+ return Object.freeze(record);
416
+ }
417
+ function freezeCleanupRecord(record) {
418
+ return Object.freeze(record);
419
+ }
420
+ function validateSpec(spec) {
421
+ if (spec.kind !== "child")
422
+ return;
423
+ if (!spec.childRunId.trim() || !spec.agentId.trim() || spec.taskIds.length === 0) {
424
+ throw new SwarmWorkspaceError("INVALID_CHILD_WORKSPACE_IDENTITY", "Child workspaces require child run, agent, and task identities.");
425
+ }
426
+ if (spec.taskIds.some((taskId) => !taskId.trim())) {
427
+ throw new SwarmWorkspaceError("INVALID_CHILD_TASK_ID", "Child workspace task identities must be non-empty.");
428
+ }
429
+ }
430
+ /** Non-authority path guard shared by production composition callers before any ID-derived join. */
431
+ export function assertSwarmPathIdentifier(value, label = "swarm path identifier") {
432
+ const windowsBaseName = value.split(".", 1)[0]?.toUpperCase() ?? "";
433
+ const windowsReserved = windowsBaseName === "CON"
434
+ || windowsBaseName === "PRN"
435
+ || windowsBaseName === "AUX"
436
+ || windowsBaseName === "NUL"
437
+ || /^COM[1-9]$/u.test(windowsBaseName)
438
+ || /^LPT[1-9]$/u.test(windowsBaseName);
439
+ if (!FILENAME_SAFE_ID.test(value) || value.endsWith(".") || windowsReserved) {
440
+ throw new SwarmWorkspaceError("UNSAFE_FILENAME_ID", `${label} must be a filename-safe workspace ID.`);
441
+ }
442
+ }
443
+ async function gitScalarOrThrow(gitRunner, cwd, args, code, message) {
444
+ const result = await runGitOrThrow(gitRunner, cwd, args, code, message);
445
+ const value = result.stdout.trim();
446
+ if (!value)
447
+ throw new SwarmWorkspaceError(code, message);
448
+ return value;
449
+ }
450
+ async function runGitOrThrow(gitRunner, cwd, args, code, message) {
451
+ try {
452
+ return await gitRunner({ cwd, args: [...args] });
453
+ }
454
+ catch (error) {
455
+ throw new SwarmWorkspaceError(code, message, { cause: error });
456
+ }
457
+ }
458
+ function pathsEqual(left, right) {
459
+ return pathKey(left) === pathKey(right);
460
+ }
461
+ function pathKey(path) {
462
+ const normalized = resolve(path).replaceAll("\\", "/");
463
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
464
+ }
465
+ function isStrictlyContained(root, candidate) {
466
+ const rel = relative(root, candidate);
467
+ return rel.length > 0 && rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !isAbsolute(rel);
468
+ }
469
+ function isPathContained(root, candidate) {
470
+ return pathsEqual(root, candidate) || isStrictlyContained(root, candidate);
471
+ }
472
+ function safeErrorCode(error) {
473
+ if (isNodeError(error) && typeof error.code === "string" && /^[A-Z0-9_]+$/u.test(error.code)) {
474
+ return error.code;
475
+ }
476
+ if (typeof error?.code === "number") {
477
+ return `GIT_EXIT_${String(error.code)}`;
478
+ }
479
+ return "GIT_WORKTREE_REMOVE_FAILED";
480
+ }
481
+ function asWorkspaceError(error) {
482
+ if (error instanceof SwarmWorkspaceError)
483
+ return error;
484
+ return new SwarmWorkspaceError("WORKTREE_POST_CREATE_VALIDATION_FAILED", "Created worktree failed post-add validation.", { cause: error });
485
+ }
486
+ function isNodeError(error) {
487
+ return error instanceof Error;
488
+ }
@@ -3,7 +3,11 @@ export declare function toTestIntegrityVerdict(status: TestIntegrityStatus): Tes
3
3
  export interface VerifierExecutionBinding {
4
4
  runId: string;
5
5
  workspaceId: string;
6
+ attemptId?: string;
6
7
  cwd: string;
8
+ runsRoot?: string;
9
+ executionProfile?: "strict_local" | "ci_safe" | "staging_controlled" | "research_untrusted";
10
+ allowedNetworkDomains?: string[];
7
11
  commands: string[];
8
12
  }
9
13
  export interface BoundVerifierEvidence {
@@ -38,7 +38,11 @@ export function verifierActuallyPassed(evidence, expected) {
38
38
  }
39
39
  if (evidence.binding.runId !== expected.runId ||
40
40
  evidence.binding.workspaceId !== expected.workspaceId ||
41
+ (evidence.binding.attemptId ?? undefined) !== (expected.attemptId ?? undefined) ||
41
42
  evidence.binding.cwd !== expected.cwd ||
43
+ (evidence.binding.runsRoot ?? undefined) !== (expected.runsRoot ?? undefined) ||
44
+ (evidence.binding.executionProfile ?? undefined) !== (expected.executionProfile ?? undefined) ||
45
+ JSON.stringify(evidence.binding.allowedNetworkDomains ?? []) !== JSON.stringify(expected.allowedNetworkDomains ?? []) ||
42
46
  JSON.stringify(evidence.binding.commands) !== JSON.stringify(expected.commands)) {
43
47
  return false;
44
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martinloop/mcp",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "mcpName": "io.github.Keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/Keesan12/martin-loop",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.7.1",
10
+ "version": "0.8.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@martinloop/mcp",
15
- "version": "0.7.1",
15
+ "version": "0.8.1",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }