kith-coord 0.1.5 → 0.1.7

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.
package/dist/index.js CHANGED
@@ -1,40 +1,7 @@
1
- // src/core/claim-wait.ts
2
- async function waitForClaim(params) {
3
- const { waitTimeoutMs, signal } = params;
4
- if (!Number.isSafeInteger(waitTimeoutMs) || waitTimeoutMs < 0 || waitTimeoutMs > 6e5) {
5
- throw new Error("waitTimeoutMs must be an integer between 0 and 600000");
6
- }
7
- const started = performance.now();
8
- let previous;
9
- for (; ; ) {
10
- signal?.throwIfAborted();
11
- const elapsed = Math.round(performance.now() - started);
12
- if (previous && elapsed >= waitTimeoutMs) return { ...previous, waitedMs: elapsed, waitTimedOut: true };
13
- params.heartbeat();
14
- const result = params.attempt();
15
- const waitedMs = Math.round(performance.now() - started);
16
- if (result.ok || waitedMs >= waitTimeoutMs) return { ...result, waitedMs, waitTimedOut: !result.ok };
17
- previous = result;
18
- await new Promise((resolve, reject) => {
19
- const abort = () => {
20
- clearTimeout(timer);
21
- reject(signal.reason);
22
- };
23
- const timer = setTimeout(() => {
24
- signal?.removeEventListener("abort", abort);
25
- resolve();
26
- }, Math.min(100, waitTimeoutMs - waitedMs));
27
- signal?.addEventListener("abort", abort, { once: true });
28
- });
29
- }
30
- }
1
+ import { createRequire as __kithRequire } from 'node:module'; const require = __kithRequire(import.meta.url);
31
2
 
32
- // src/version.ts
33
- var KITH_VERSION = "0.1.5";
34
- var KITH_PROTOCOL_VERSION = "1.3.0";
35
-
36
- // src/core/workspace-observation.ts
37
- import { execFileSync as execFileSync2, spawnSync } from "child_process";
3
+ // src/core/engine.ts
4
+ import { execFileSync as execFileSync8 } from "child_process";
38
5
 
39
6
  // src/core/repository.ts
40
7
  import { execFileSync } from "child_process";
@@ -86,16 +53,16 @@ function discoverRepository(input) {
86
53
  throw new Error("Cannot coordinate a filesystem root; select an explicit project with --repo.");
87
54
  }
88
55
  if (!fs.statSync(directory).isDirectory()) throw new Error(`Not a directory: ${input}`);
89
- const git2 = (...args) => execFileSync("git", ["rev-parse", ...args], {
56
+ const git3 = (...args) => execFileSync("git", ["rev-parse", ...args], {
90
57
  cwd: directory,
91
58
  encoding: "utf8",
92
59
  stdio: ["ignore", "pipe", "pipe"]
93
60
  }).replace(/\r?\n$/, "");
94
61
  try {
95
- const checkoutRoot = fs.realpathSync(git2("--show-toplevel"));
62
+ const checkoutRoot = fs.realpathSync(git3("--show-toplevel"));
96
63
  if (!isWithin(checkoutRoot, directory)) throw new Error("Git routing environment selects a different checkout");
97
- const commonGitDir = fs.realpathSync(git2("--path-format=absolute", "--git-common-dir"));
98
- const isLinkedWorktree = fs.realpathSync(git2("--absolute-git-dir")) !== commonGitDir;
64
+ const commonGitDir = fs.realpathSync(git3("--path-format=absolute", "--git-common-dir"));
65
+ const isLinkedWorktree = fs.realpathSync(git3("--absolute-git-dir")) !== commonGitDir;
99
66
  const coordDir = path.basename(commonGitDir) === ".git" ? path.join(path.dirname(commonGitDir), ".coord") : path.join(commonGitDir, "kith");
100
67
  return { checkoutRoot, commonGitDir, coordDir, isLinkedWorktree };
101
68
  } catch (error) {
@@ -135,7 +102,43 @@ function caseSensitivePaths(root) {
135
102
  return false;
136
103
  }
137
104
 
105
+ // src/core/claim-wait.ts
106
+ async function waitForClaim(params) {
107
+ const { waitTimeoutMs, signal } = params;
108
+ if (!Number.isSafeInteger(waitTimeoutMs) || waitTimeoutMs < 0 || waitTimeoutMs > 6e5) {
109
+ throw new Error("waitTimeoutMs must be an integer between 0 and 600000");
110
+ }
111
+ const started = performance.now();
112
+ let previous;
113
+ for (; ; ) {
114
+ signal?.throwIfAborted();
115
+ const elapsed = Math.round(performance.now() - started);
116
+ if (previous && elapsed >= waitTimeoutMs) return { ...previous, waitedMs: elapsed, waitTimedOut: true };
117
+ params.heartbeat();
118
+ const result = params.attempt();
119
+ const waitedMs = Math.round(performance.now() - started);
120
+ if (result.ok || waitedMs >= waitTimeoutMs) return { ...result, waitedMs, waitTimedOut: !result.ok };
121
+ previous = result;
122
+ await new Promise((resolve, reject) => {
123
+ const abort = () => {
124
+ clearTimeout(timer);
125
+ reject(signal.reason);
126
+ };
127
+ const timer = setTimeout(() => {
128
+ signal?.removeEventListener("abort", abort);
129
+ resolve();
130
+ }, Math.min(100, waitTimeoutMs - waitedMs));
131
+ signal?.addEventListener("abort", abort, { once: true });
132
+ });
133
+ }
134
+ }
135
+
136
+ // src/version.ts
137
+ var KITH_VERSION = "0.1.7";
138
+ var KITH_PROTOCOL_VERSION = "1.5.0";
139
+
138
140
  // src/core/workspace-observation.ts
141
+ import { execFileSync as execFileSync2, spawnSync } from "child_process";
139
142
  function git(cwd, ...args) {
140
143
  return execFileSync2("git", ["-c", "core.fsmonitor=false", ...args], {
141
144
  cwd,
@@ -264,8 +267,8 @@ function previewIntegration(repoRoot, agent) {
264
267
  }
265
268
 
266
269
  // src/core/engine.ts
267
- import fs12 from "fs";
268
- import path11 from "path";
270
+ import fs14 from "fs";
271
+ import path13 from "path";
269
272
 
270
273
  // src/core/ast-analyzer.ts
271
274
  import crypto from "crypto";
@@ -909,6 +912,7 @@ var LockEngine = class _LockEngine {
909
912
  const first = segments.findIndex((s) => /[?*]/u.test(s));
910
913
  if (first < 0) return false;
911
914
  const prefix = segments.slice(0, first).join("/");
915
+ const recursivePrefix = Boolean(prefix) && segments.length === first + 1 && segments[first] === "**";
912
916
  const roots = /* @__PURE__ */ new Set([this.coordDb.repoRoot, ...this.fencing.listAgents(true).map((a) => a.worktree_path).filter((p) => Boolean(p))]);
913
917
  const pending = [...roots].map((root) => ({ root, relative: prefix }));
914
918
  let budget = 1e4;
@@ -925,7 +929,17 @@ var LockEngine = class _LockEngine {
925
929
  for (const entry of entries) {
926
930
  if (--budget < 0) return true;
927
931
  const file = relative ? relative + "/" + entry.name : entry.name;
928
- if (entry.isSymbolicLink()) return true;
932
+ if (entry.isSymbolicLink()) {
933
+ if (!recursivePrefix) return true;
934
+ try {
935
+ const scopeRoot = physicalPath(path4.join(root, prefix));
936
+ const linkTarget = fs4.realpathSync(path4.join(dir, entry.name));
937
+ if (!isWithin(scopeRoot, linkTarget)) return true;
938
+ continue;
939
+ } catch {
940
+ return true;
941
+ }
942
+ }
929
943
  const fold = (value) => value.normalize("NFC").toLowerCase().replace(/ς/g, "\u03C3");
930
944
  if (!_LockEngine.patternsOverlap(fold(pattern), fold(file)) && !_LockEngine.patternsOverlap(fold(pattern), fold(file + "/**"))) continue;
931
945
  if (entry.isDirectory()) pending.push({ root, relative: file });
@@ -1725,7 +1739,7 @@ var ResourceBroker = class _ResourceBroker {
1725
1739
  worker.once("error", reject);
1726
1740
  }), stopped]);
1727
1741
  if (params.signal?.aborted) failure ??= abortError();
1728
- if (failure) throw failure;
1742
+ if (failure) throw Object.assign(failure, { stdout: Buffer.concat(stdout).toString(), stderr: Buffer.concat(stderr).toString() });
1729
1743
  if (!outcome) throw new Error("Command worker exited without a result");
1730
1744
  if (outcome.error) throw new Error(outcome.error);
1731
1745
  return { stdout: Buffer.concat(stdout).toString(), stderr: Buffer.concat(stderr).toString(), exitCode: outcome.exitCode, waitedMs };
@@ -1941,7 +1955,7 @@ var CoordDatabase = class _CoordDatabase {
1941
1955
  }
1942
1956
  if (!tables.some((table) => table.name === "meta")) throw new Error("Missing coordination schema metadata. Preserve the database before repair.");
1943
1957
  const row = db.prepare("SELECT value FROM meta WHERE key='schema_version'").get();
1944
- if (row?.value !== "1" && row?.value !== "2") {
1958
+ if (row?.value !== "1" && row?.value !== "2" && row?.value !== "3") {
1945
1959
  throw new Error(`Unsupported coordination schema version: ${String(row?.value)}. Preserve the database before upgrading or repairing it.`);
1946
1960
  }
1947
1961
  const version = Number(row.value);
@@ -1956,7 +1970,8 @@ var CoordDatabase = class _CoordDatabase {
1956
1970
  "resources",
1957
1971
  "quarantine",
1958
1972
  "events",
1959
- ...version === 2 ? ["agent_sessions", "mutex_waiters", "mutex_runs"] : []
1973
+ ...version >= 2 ? ["agent_sessions", "mutex_waiters", "mutex_runs"] : [],
1974
+ ...version >= 3 ? ["mcp_operations"] : []
1960
1975
  ];
1961
1976
  if (required.some((name) => !tables.some((table) => table.name === name))) {
1962
1977
  throw new Error("Incomplete coordination schema. Preserve the database before repair.");
@@ -1966,11 +1981,12 @@ var CoordDatabase = class _CoordDatabase {
1966
1981
  tasks: "status,owner_agent_id,fencing_token,scope_globs_json,depends_on_json",
1967
1982
  locks: "owner_agent_id,pattern,mode,fencing_token,expires_at",
1968
1983
  resources: "type,name,owner_agent_id,fencing_token,expires_at",
1969
- ...version === 2 ? {
1984
+ ...version >= 2 ? {
1970
1985
  agent_sessions: "agent_id,token_hash,epoch",
1971
1986
  mutex_waiters: "ticket,mutex_name,request_id,owner_agent_id,session_epoch,waiter_pid,waiter_start,expires_at",
1972
1987
  mutex_runs: "mutex_name,run_id,owner_agent_id,session_epoch,fencing_token,supervisor_pid,supervisor_start,worker_pid,worker_start"
1973
- } : {}
1988
+ } : {},
1989
+ ...version >= 3 ? { mcp_operations: "operation_key,request_hash,agent_id,session_epoch,capability_hash,owner_pid,owner_start,result_json,created_at" } : {}
1974
1990
  };
1975
1991
  for (const [table, fields] of Object.entries(columns)) {
1976
1992
  try {
@@ -1985,7 +2001,8 @@ var CoordDatabase = class _CoordDatabase {
1985
2001
  "SELECT fencing_token FROM tasks",
1986
2002
  "SELECT fencing_token FROM resources",
1987
2003
  "SELECT fencing_token FROM merge_queue",
1988
- ...version === 2 ? ["SELECT epoch FROM agent_sessions", "SELECT session_epoch FROM mutex_waiters", "SELECT fencing_token FROM mutex_runs"] : []
2004
+ ...version >= 2 ? ["SELECT epoch FROM agent_sessions", "SELECT session_epoch FROM mutex_waiters", "SELECT fencing_token FROM mutex_runs"] : [],
2005
+ ...version >= 3 ? ["SELECT session_epoch FROM mcp_operations"] : []
1989
2006
  ];
1990
2007
  if (db.prepare(`SELECT 1 FROM (${tokens.join(" UNION ALL ")}) WHERE token IS NOT NULL AND (typeof(token)!='integer' OR token<0 OR token>?) LIMIT 1`).get(epoch)) {
1991
2008
  throw new Error("Invalid or regressed fencing counter. Preserve the database before repair.");
@@ -2163,6 +2180,14 @@ var CoordDatabase = class _CoordDatabase {
2163
2180
  worker_pid INTEGER, worker_start TEXT
2164
2181
  );
2165
2182
  `);
2183
+ this.db.exec(`
2184
+ CREATE TABLE IF NOT EXISTS mcp_operations (
2185
+ operation_key TEXT PRIMARY KEY, request_hash TEXT NOT NULL,
2186
+ agent_id TEXT NOT NULL, session_epoch INTEGER NOT NULL, capability_hash TEXT NOT NULL,
2187
+ owner_pid INTEGER NOT NULL, owner_start TEXT NOT NULL,
2188
+ result_json TEXT, created_at INTEGER NOT NULL
2189
+ );
2190
+ `);
2166
2191
  if (version < 2) {
2167
2192
  this.db.exec(`
2168
2193
  UPDATE tasks SET status='queued', owner_agent_id=NULL, fencing_token=NULL
@@ -2174,6 +2199,7 @@ var CoordDatabase = class _CoordDatabase {
2174
2199
  UPDATE meta SET value='2' WHERE key='schema_version';
2175
2200
  `);
2176
2201
  }
2202
+ this.db.exec("UPDATE meta SET value='3' WHERE key='schema_version'");
2177
2203
  });
2178
2204
  }
2179
2205
  /**
@@ -2706,10 +2732,21 @@ var IntentAndTaskEngine = class {
2706
2732
  return this.coordDb.withTransaction(() => {
2707
2733
  this.fencing.assertSession(agentId);
2708
2734
  const allTasks = this.listTasks();
2735
+ const activeSiblingIntents = allTasks.filter(
2736
+ (t) => t.status === "active" && t.owner_agent_id !== agentId
2737
+ );
2709
2738
  const taskById = new Map(allTasks.map((t) => [t.id, t]));
2710
2739
  const queued = allTasks.filter((t) => t.status === "queued").sort((a, b) => b.priority - a.priority || a.created_at - b.created_at);
2711
2740
  if (queued.length === 0) {
2712
- return { claimed: false, task: null, fencingToken: null, locks: [], reason: "no_queued_tasks" };
2741
+ return {
2742
+ ok: false,
2743
+ claimed: false,
2744
+ task: null,
2745
+ fencingToken: null,
2746
+ locks: [],
2747
+ activeSiblingIntents,
2748
+ reason: "no_queued_tasks"
2749
+ };
2713
2750
  }
2714
2751
  for (const candidate of queued) {
2715
2752
  const depsSatisfied = candidate.depends_on.every(
@@ -2745,17 +2782,21 @@ var IntentAndTaskEngine = class {
2745
2782
  fencingToken
2746
2783
  });
2747
2784
  return {
2785
+ ok: true,
2748
2786
  claimed: true,
2749
2787
  task: this.getTask(candidate.id),
2750
2788
  fencingToken,
2751
- locks
2789
+ locks,
2790
+ activeSiblingIntents
2752
2791
  };
2753
2792
  }
2754
2793
  return {
2794
+ ok: false,
2755
2795
  claimed: false,
2756
2796
  task: null,
2757
2797
  fencingToken: null,
2758
2798
  locks: [],
2799
+ activeSiblingIntents,
2759
2800
  reason: "all_queued_tasks_blocked_by_dependencies_or_locks"
2760
2801
  };
2761
2802
  });
@@ -3350,10 +3391,418 @@ var ShadowGitEngine = class {
3350
3391
  }
3351
3392
  };
3352
3393
 
3353
- // src/core/worktree-cow.ts
3394
+ // src/core/workspace-contracts.ts
3354
3395
  import { execFileSync as execFileSync6 } from "child_process";
3396
+ import fs12 from "fs";
3397
+ import path11 from "path";
3398
+
3399
+ // src/core/observation.ts
3355
3400
  import fs11 from "fs";
3401
+ import os from "os";
3356
3402
  import path10 from "path";
3403
+ import { createRequire as createRequire2 } from "module";
3404
+ var { DatabaseSync: DatabaseSync2 } = createRequire2(import.meta.url)("node:sqlite");
3405
+ function readCoordinationState(directory = process.cwd(), options = {}) {
3406
+ const layout = discoverRepository(directory), observedAt = Date.now();
3407
+ const empty = {
3408
+ protocolVersion: KITH_PROTOCOL_VERSION,
3409
+ observedAt,
3410
+ repository: { root: layout.checkoutRoot, commonGitDir: layout.commonGitDir, coordDir: layout.coordDir },
3411
+ runtime: { node: process.version },
3412
+ state: "missing",
3413
+ error: void 0,
3414
+ sessionEpochs: {},
3415
+ truncated: false,
3416
+ schemaVersion: 0,
3417
+ fencingEpoch: 0,
3418
+ agents: [],
3419
+ tasks: [],
3420
+ locks: [],
3421
+ conflicts: [],
3422
+ contracts: [],
3423
+ resources: [],
3424
+ mergeQueue: [],
3425
+ quarantined: [],
3426
+ mutexWaiters: [],
3427
+ commandGuards: [],
3428
+ events: [],
3429
+ workspaces: []
3430
+ };
3431
+ let scratch, db;
3432
+ try {
3433
+ const file = path10.join(layout.coordDir, "state.db");
3434
+ if (physicalPath(layout.coordDir) !== layout.coordDir || physicalPath(file) !== file) throw new Error("Coordination state must not be a symlink");
3435
+ for (let current = fs11.realpathSync.native(directory); isWithin(layout.checkoutRoot, current); current = path10.dirname(current)) {
3436
+ const local = path10.join(current, ".coord");
3437
+ if (local !== layout.coordDir && fs11.existsSync(path10.join(local, "state.db"))) throw new Error(`Legacy independent state at ${local}; preserve it before consolidation`);
3438
+ if (current === layout.checkoutRoot) break;
3439
+ }
3440
+ if (!fs11.existsSync(file)) {
3441
+ if (["setup.lock", "merge-train.lock"].some((name) => fs11.existsSync(path10.join(layout.coordDir, name))) || ["-wal", "-shm", "-journal"].some((s) => fs11.existsSync(file + s)) || ["worktrees", "quarantine", "snapshots", "shadow-index", "setup"].some((name) => {
3442
+ const p = path10.join(layout.coordDir, name);
3443
+ return fs11.existsSync(p) && (!fs11.lstatSync(p).isDirectory() || fs11.readdirSync(p).length > 0);
3444
+ })) throw new Error("Missing database with retained sidecars; preserve the recovery set");
3445
+ return empty;
3446
+ }
3447
+ const files = ["", "-wal", "-shm", "-journal"].map((s) => file + s);
3448
+ const identity = (f) => {
3449
+ if (!fs11.existsSync(f)) return null;
3450
+ if (physicalPath(f) !== f || !fs11.lstatSync(f).isFile()) throw new Error("Database or sidecar is redirected or not a regular file");
3451
+ const s = fs11.statSync(f, { bigint: true });
3452
+ return [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs].join(":");
3453
+ };
3454
+ const before = files.map(identity);
3455
+ scratch = fs11.mkdtempSync(path10.join(os.tmpdir(), "kith-observe-"));
3456
+ for (let i = 0; i < files.length; i++) if (before[i]) {
3457
+ const target = path10.join(scratch, path10.basename(files[i]));
3458
+ fs11.copyFileSync(files[i], target);
3459
+ fs11.chmodSync(target, 384);
3460
+ }
3461
+ if (JSON.stringify(before) !== JSON.stringify(files.map(identity))) throw new Error("Coordination state changed during snapshot; stop writers or retry");
3462
+ db = new DatabaseSync2(path10.join(scratch, "state.db"), { readOnly: true });
3463
+ db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000; BEGIN");
3464
+ const schemaVersion = CoordDatabase.inspectSchema(db);
3465
+ if (schemaVersion < 2) throw new Error("Offline observation requires schema 2 or newer; preserve state and upgrade with clients stopped");
3466
+ const all = (sql) => db.prepare(sql).all();
3467
+ const agents = all("SELECT * FROM agents ORDER BY registered_at DESC" + (options.coordinationOnly ? " LIMIT 201" : ""));
3468
+ if (options.coordinationOnly) {
3469
+ const tasks = all("SELECT * FROM tasks WHERE status IN ('active','blocked','queued') ORDER BY priority DESC, updated_at DESC LIMIT 201").map(({ depends_on_json, scope_globs_json, ...r }) => ({ ...r, depends_on: JSON.parse(String(depends_on_json)), scope_globs: JSON.parse(String(scope_globs_json)) }));
3470
+ const locks = all("SELECT * FROM locks WHERE expires_at > " + observedAt + " ORDER BY acquired_at DESC LIMIT 501");
3471
+ const resources = all("SELECT * FROM resources ORDER BY acquired_at DESC LIMIT 201");
3472
+ const mutexWaiters = all("SELECT ticket,mutex_name,owner_agent_id,session_epoch,expires_at FROM mutex_waiters ORDER BY ticket LIMIT 201");
3473
+ const commandGuards = all("SELECT mutex_name,owner_agent_id,session_epoch,fencing_token FROM mutex_runs LIMIT 201");
3474
+ const mergeQueue = all("SELECT agent_id,branch,worktree_path,status,fencing_token,created_at,updated_at FROM merge_queue WHERE rowid IN (SELECT MAX(rowid) FROM merge_queue GROUP BY agent_id,branch) ORDER BY created_at DESC LIMIT 201");
3475
+ const sessionEpochs = Object.fromEntries(all("SELECT s.agent_id,s.epoch FROM agent_sessions s JOIN agents a ON s.agent_id=a.id ORDER BY a.registered_at DESC LIMIT 201").map((r) => [r.agent_id, r.epoch]));
3476
+ db.exec("COMMIT");
3477
+ return {
3478
+ ...empty,
3479
+ state: "ready",
3480
+ schemaVersion,
3481
+ agents,
3482
+ sessionEpochs,
3483
+ tasks,
3484
+ locks,
3485
+ resources,
3486
+ mutexWaiters,
3487
+ commandGuards,
3488
+ mergeQueue,
3489
+ truncated: [agents, tasks, resources, mutexWaiters, commandGuards, mergeQueue].some((rows) => rows.length > 200) || locks.length > 500
3490
+ };
3491
+ }
3492
+ if (options.ownersOnly) {
3493
+ db.exec("COMMIT");
3494
+ return { ...empty, state: "ready", schemaVersion, agents };
3495
+ }
3496
+ const state = {
3497
+ ...empty,
3498
+ state: "ready",
3499
+ schemaVersion,
3500
+ fencingEpoch: Number(db.prepare("SELECT value FROM meta WHERE key='fencing_epoch'").get().value),
3501
+ agents,
3502
+ tasks: all("SELECT * FROM tasks ORDER BY priority DESC,created_at").map(({ depends_on_json, scope_globs_json, ...r }) => ({ ...r, depends_on: JSON.parse(String(depends_on_json)), scope_globs: JSON.parse(String(scope_globs_json)) })),
3503
+ locks: all("SELECT * FROM locks ORDER BY acquired_at"),
3504
+ conflicts: all("SELECT * FROM conflicts ORDER BY detected_at DESC").map(({ details_json, ...r }) => ({ ...r, details: JSON.parse(String(details_json)) })),
3505
+ contracts: all("SELECT * FROM contracts ORDER BY updated_at DESC").map((r) => ({ ...r, is_breaking: Boolean(r.is_breaking) })),
3506
+ resources: all("SELECT * FROM resources ORDER BY acquired_at"),
3507
+ mergeQueue: all("SELECT * FROM merge_queue ORDER BY created_at DESC"),
3508
+ quarantined: all("SELECT * FROM quarantine ORDER BY created_at DESC"),
3509
+ mutexWaiters: all("SELECT ticket,mutex_name,owner_agent_id,session_epoch,expires_at FROM mutex_waiters ORDER BY ticket"),
3510
+ commandGuards: all("SELECT mutex_name,owner_agent_id,worker_pid,supervisor_pid FROM mutex_runs ORDER BY mutex_name"),
3511
+ events: all("SELECT * FROM events ORDER BY id DESC LIMIT 100").map(({ payload_json, ...r }) => ({ ...r, payload: JSON.parse(String(payload_json)) })),
3512
+ workspaces: options.coordinationOnly ? [] : observeWorkspaces(layout.checkoutRoot, agents, observedAt)
3513
+ };
3514
+ db.exec("COMMIT");
3515
+ return state;
3516
+ } catch (error) {
3517
+ return { ...empty, state: "unavailable", error: error instanceof Error ? error.message : String(error) };
3518
+ } finally {
3519
+ db?.close();
3520
+ if (scratch) fs11.rmSync(scratch, { recursive: true, force: true });
3521
+ }
3522
+ }
3523
+ function recoveryReport(directory = process.cwd()) {
3524
+ const state = readCoordinationState(directory), retainedRoot = path10.join(state.repository.coordDir, "worktrees");
3525
+ const retained = physicalPath(retainedRoot) === retainedRoot && fs11.existsSync(retainedRoot) && fs11.lstatSync(retainedRoot).isDirectory() && !fs11.lstatSync(retainedRoot).isSymbolicLink() ? fs11.readdirSync(retainedRoot, { withFileTypes: true }).map((entry) => ({
3526
+ path: path10.join(retainedRoot, entry.name),
3527
+ symlink: entry.isSymbolicLink(),
3528
+ agentIds: state.agents.filter((a) => a.worktree_path === path10.join(retainedRoot, entry.name)).map((a) => a.id)
3529
+ })) : [];
3530
+ return {
3531
+ ...state,
3532
+ retainedWorktrees: retained,
3533
+ expiredClaims: state.locks.filter((lock) => Number(lock.expires_at) <= state.observedAt),
3534
+ unfinishedIntegrations: state.mergeQueue.filter((entry) => entry.status !== "merged"),
3535
+ setupLock: { path: path10.join(state.repository.coordDir, "setup.lock"), present: fs11.existsSync(path10.join(state.repository.coordDir, "setup.lock")) },
3536
+ integrationLock: { path: path10.join(state.repository.coordDir, "merge-train.lock"), present: fs11.existsSync(path10.join(state.repository.coordDir, "merge-train.lock")) }
3537
+ };
3538
+ }
3539
+
3540
+ // src/core/workspace-contracts.ts
3541
+ function buildSwarmPrompts(repoRoot) {
3542
+ const layout = discoverRepository(repoRoot);
3543
+ if (!layout.commonGitDir) throw new Error("Select a Git checkout for task prompts, not a parent workspace.");
3544
+ const cleanRoot = layout.checkoutRoot;
3545
+ return {
3546
+ architect: `Use Kith on repository ${cleanRoot} to plan parallel multi-agent work:
3547
+ 1. Call kith_start_session with a unique agentId (e.g. "architect-1") and repository "${cleanRoot}".
3548
+ 2. Publish any shared interfaces, routes, or schemas upfront via kith_publish_contract.
3549
+ 3. Enqueue parallelizable tasks with disjoint file scopes using kith_add_task (passing title, intentSummary, scopeGlobs, priority, and dependsOn for sequential steps).
3550
+ 4. Summarize the queued task IDs and scopes so worker agents can claim them in parallel.`,
3551
+ worker: `Use Kith on repository ${cleanRoot} to claim and execute the next queued task:
3552
+ 1. Call kith_claim_next_task with a unique agentId, repository "${cleanRoot}", and isolateWorktree: true.
3553
+ 2. Edit only inside the returned worktree.worktreePath and within the task's locked scopeGlobs, respecting publishedContracts and linkedContracts.
3554
+ 3. After each file edit, call kith_record_file_edit to check contract blast radius and live dirty-tree conflicts.
3555
+ 4. Commit your changes in the worktree and call kith_complete_work with a read-only verificationCmd to verify and merge.`
3556
+ };
3557
+ }
3558
+ function parseJsoncObject(raw) {
3559
+ let out = "";
3560
+ let inString = false;
3561
+ let stringQuote = '"';
3562
+ let i = 0;
3563
+ while (i < raw.length) {
3564
+ const ch = raw[i];
3565
+ const next = raw[i + 1];
3566
+ if (inString) {
3567
+ out += ch;
3568
+ if (ch === "\\" && i + 1 < raw.length) {
3569
+ out += raw[i + 1];
3570
+ i += 2;
3571
+ continue;
3572
+ }
3573
+ if (ch === stringQuote) {
3574
+ inString = false;
3575
+ }
3576
+ i++;
3577
+ continue;
3578
+ }
3579
+ if (ch === '"' || ch === "'") {
3580
+ inString = true;
3581
+ stringQuote = ch;
3582
+ out += ch;
3583
+ i++;
3584
+ continue;
3585
+ }
3586
+ if (ch === "/" && next === "/") {
3587
+ i += 2;
3588
+ while (i < raw.length && raw[i] !== "\n") i++;
3589
+ continue;
3590
+ }
3591
+ if (ch === "/" && next === "*") {
3592
+ i += 2;
3593
+ while (i + 1 < raw.length && !(raw[i] === "*" && raw[i + 1] === "/")) i++;
3594
+ i += 2;
3595
+ continue;
3596
+ }
3597
+ out += ch;
3598
+ i++;
3599
+ }
3600
+ const withoutTrailingCommas = out.replace(/,\s*([}\]])/g, "$1");
3601
+ try {
3602
+ const parsed = JSON.parse(withoutTrailingCommas);
3603
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
3604
+ } catch {
3605
+ return null;
3606
+ }
3607
+ }
3608
+ var MAX_SOURCE_BYTES = 64 * 1024;
3609
+ var MAX_REPLY_BYTES = 16 * 1024;
3610
+ var MAX_LINKS = 8;
3611
+ var MAX_SYMBOLS = 64;
3612
+ function git2(root, ...args) {
3613
+ return execFileSync6("git", ["-c", "core.fsmonitor=false", ...args], {
3614
+ cwd: root,
3615
+ encoding: "utf8",
3616
+ stdio: ["ignore", "pipe", "pipe"],
3617
+ timeout: 2e3,
3618
+ maxBuffer: MAX_SOURCE_BYTES,
3619
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }
3620
+ }).trim();
3621
+ }
3622
+ function readBounded(file) {
3623
+ const fd = fs12.openSync(file, "r");
3624
+ try {
3625
+ const stat = fs12.fstatSync(fd);
3626
+ if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) throw new Error("Contract/config file exceeds inspection limit or is not a regular file");
3627
+ const bytes = Buffer.alloc(MAX_SOURCE_BYTES + 1);
3628
+ const size = fs12.readSync(fd, bytes, 0, bytes.length, 0);
3629
+ if (size > MAX_SOURCE_BYTES) throw new Error("Contract/config file exceeds inspection limit");
3630
+ return bytes.subarray(0, size).toString("utf8");
3631
+ } finally {
3632
+ fs12.closeSync(fd);
3633
+ }
3634
+ }
3635
+ function resolveFile(base) {
3636
+ for (const name of [base, `${base}.ts`, `${base}.tsx`, `${base}.d.ts`, `${base}.js`, `${base}.jsx`, path11.join(base, "index.ts"), path11.join(base, "index.js")]) {
3637
+ if (fs12.existsSync(name) && fs12.statSync(name).isFile()) return physicalPath(name);
3638
+ }
3639
+ }
3640
+ function inspectWorkspaceContracts(repoRoot, worktreeDir, imports) {
3641
+ const report = { linkedContracts: [], warnings: [], complete: true };
3642
+ const unavailable = (detail) => {
3643
+ report.complete = false;
3644
+ if (report.warnings.length < 8) report.warnings.push(`[CROSS-REPO UNAVAILABLE] ${detail.slice(0, 300)}`);
3645
+ };
3646
+ try {
3647
+ const layout = discoverRepository(worktreeDir ?? repoRoot);
3648
+ if (!layout.commonGitDir) return report;
3649
+ const activeRoot = layout.checkoutRoot;
3650
+ let config = null;
3651
+ for (const name of ["tsconfig.json", "jsconfig.json"]) {
3652
+ const file = path11.join(activeRoot, name);
3653
+ if (!fs12.existsSync(file)) continue;
3654
+ repositoryFile(activeRoot, file);
3655
+ config = parseJsoncObject(readBounded(file));
3656
+ if (!config) unavailable(`Cannot parse ${name}; aliases were not inspected.`);
3657
+ break;
3658
+ }
3659
+ if (!config) return report;
3660
+ const options = config.compilerOptions;
3661
+ if (config.extends || config.references) {
3662
+ unavailable("Inherited/referenced configuration is not inspected; use your normal compiler checks.");
3663
+ return report;
3664
+ }
3665
+ if (!options?.paths || typeof options.paths !== "object") return report;
3666
+ const caller = discoverRepository(repoRoot);
3667
+ if (caller.commonGitDir !== layout.commonGitDir) {
3668
+ unavailable("Worktree repository identity differs from the caller.");
3669
+ return report;
3670
+ }
3671
+ const bases = [.../* @__PURE__ */ new Set([activeRoot, caller.checkoutRoot])].map((root) => path11.resolve(root, typeof options.baseUrl === "string" ? options.baseUrl : "."));
3672
+ let symbolBudget = MAX_SYMBOLS;
3673
+ const aliases = Object.entries(options.paths).sort(([a], [b]) => b.length - a.length);
3674
+ if (aliases.length > 32) unavailable("Alias limit reached; inspection is partial.");
3675
+ const externalTargets = (targets) => Array.isArray(targets) && targets.slice(0, 8).some((target) => typeof target === "string" && !isWithin(caller.checkoutRoot, physicalPath(path11.resolve(bases[bases.length - 1], target.replace("*", "")))));
3676
+ const selected = imports ? [...new Set(imports)].slice(0, 32) : aliases.slice(0, 32).filter(([a]) => !a.includes("*")).map(([a]) => a);
3677
+ if (imports && imports.length > 32) unavailable("Import limit reached; inspection is partial.");
3678
+ if (!imports && aliases.some(([a, targets]) => a.includes("*") && externalTargets(targets))) unavailable("Wildcard aliases are checked for concrete imports after edits; not enumerated by status.");
3679
+ for (const specifier of selected) {
3680
+ const match = aliases.slice(0, 32).find(([alias2]) => {
3681
+ if (!alias2.includes("*")) return alias2 === specifier;
3682
+ const [prefix, suffix, extra] = alias2.split("*");
3683
+ return extra === void 0 && specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length;
3684
+ });
3685
+ if (!match || !Array.isArray(match[1])) continue;
3686
+ const [alias, targets] = match;
3687
+ if (!externalTargets(targets)) continue;
3688
+ const star = alias.indexOf("*");
3689
+ const substitution = star < 0 ? "" : specifier.slice(star, specifier.length - (alias.length - star - 1));
3690
+ if (substitution.split(/[\\/]/).some((part) => part === "..")) {
3691
+ unavailable("Alias traversal was not inspected.");
3692
+ continue;
3693
+ }
3694
+ let externalFile;
3695
+ for (const target of targets.slice(0, 8)) {
3696
+ if (typeof target !== "string" || (target.match(/\*/g)?.length ?? 0) > 1) continue;
3697
+ for (const base of bases) {
3698
+ externalFile = resolveFile(path11.resolve(base, target.replace("*", substitution)));
3699
+ if (externalFile) break;
3700
+ }
3701
+ if (externalFile) break;
3702
+ }
3703
+ if (!externalFile) {
3704
+ unavailable(`Cannot resolve '${specifier}'; no compatibility verdict available.`);
3705
+ continue;
3706
+ }
3707
+ if (isWithin(activeRoot, externalFile)) continue;
3708
+ const sibling = discoverRepository(path11.dirname(externalFile));
3709
+ if (!sibling.commonGitDir || sibling.commonGitDir === layout.commonGitDir) {
3710
+ unavailable(`'${specifier}' does not select a separate Git repository; no external source was read.`);
3711
+ continue;
3712
+ }
3713
+ const siblingRoot = sibling.checkoutRoot;
3714
+ const relative = repositoryFile(siblingRoot, externalFile);
3715
+ git2(siblingRoot, "ls-files", "--error-unmatch", "--", relative);
3716
+ if (report.linkedContracts.length >= MAX_LINKS || symbolBudget <= 0) {
3717
+ unavailable("Contract/symbol limit reached; inspection is partial.");
3718
+ break;
3719
+ }
3720
+ const content = readBounded(externalFile);
3721
+ const extracted = AstAnalyzer.extractExports(content);
3722
+ const symbols = extracted.slice(0, symbolBudget).map((s) => ({
3723
+ symbolName: s.symbolName.slice(0, 128),
3724
+ kind: s.kind,
3725
+ signature: `${s.kind} ${s.symbolName}`.slice(0, 160),
3726
+ astHash: s.astHash
3727
+ }));
3728
+ symbolBudget -= symbols.length;
3729
+ const exportsComplete = extracted.length === symbols.length && !/export\s+(?:\*|\{)/.test(content);
3730
+ if (!exportsComplete) unavailable(`'${specifier}' exports are partial or re-exported; missing-export checks are unavailable.`);
3731
+ let dirtyInCheckout = null;
3732
+ try {
3733
+ dirtyInCheckout = Boolean(git2(siblingRoot, "status", "--porcelain=v1", "--", relative));
3734
+ } catch {
3735
+ unavailable(`'${specifier}' Git status is unavailable.`);
3736
+ }
3737
+ const state = readCoordinationState(siblingRoot, { coordinationOnly: true });
3738
+ const ownershipAvailable = state.state === "missing" || state.state === "ready" && state.schemaVersion === 3 && !state.truncated;
3739
+ if (!ownershipAvailable) unavailable(`'${specifier}' sibling ownership is unavailable or partial; inspect its stopped state before recovery.`);
3740
+ const agents = state.state === "ready" ? state.agents.filter((a) => a.status === "active" && a.lease_expires_at > state.observedAt && state.sessionEpochs[a.id] !== void 0) : [];
3741
+ const locked = state.locks.find((l) => agents.some((a) => a.id === l.owner_agent_id) && Number(l.expires_at) > state.observedAt && Number(l.fencing_token) >= state.sessionEpochs[String(l.owner_agent_id)] && LockEngine.patternsOverlap(relative, String(l.pattern)));
3742
+ const drifts = [];
3743
+ for (const agent of agents.slice(0, 8)) {
3744
+ if (!agent.worktree_path || physicalPath(agent.worktree_path) === siblingRoot) continue;
3745
+ try {
3746
+ const wt = discoverRepository(agent.worktree_path);
3747
+ if (wt.checkoutRoot !== physicalPath(agent.worktree_path) || wt.commonGitDir !== sibling.commonGitDir) throw new Error("Worktree identity changed");
3748
+ const file = path11.join(wt.checkoutRoot, relative);
3749
+ repositoryFile(wt.checkoutRoot, file);
3750
+ const current = AstAnalyzer.extractExports(readBounded(file));
3751
+ const original = new Map(extracted.map((s) => [s.symbolName, s.astHash]));
3752
+ const updated = new Map(current.map((s) => [s.symbolName, s.astHash]));
3753
+ const changed = [...updated].filter(([name, hash]) => original.get(name) !== hash).map(([name]) => name);
3754
+ const removed = [...original.keys()].filter((name) => !updated.has(name));
3755
+ if (changed.length || removed.length) drifts.push({ agentId: agent.id, worktreePath: wt.checkoutRoot, changedSymbols: changed.slice(0, 16), removedSymbols: removed.slice(0, 16) });
3756
+ if (changed.length > 16 || removed.length > 16) unavailable(`'${specifier}' drift detail is partial.`);
3757
+ } catch {
3758
+ unavailable(`'${specifier}' recorded worktree is unavailable; drift has not been verified.`);
3759
+ }
3760
+ }
3761
+ if (agents.length > 8) unavailable(`'${specifier}' owner/worktree inspection limit reached.`);
3762
+ const link = { alias: specifier, sourceRepo: siblingRoot, sourceRepoName: path11.basename(siblingRoot), filePath: relative, absolutePath: externalFile, symbols, exportsComplete, ownershipAvailable, dirtyInCheckout, lockedByAgent: locked ? String(locked.owner_agent_id) : null, activeWorktreeDrifts: drifts };
3763
+ report.linkedContracts.push(link);
3764
+ if (Buffer.byteLength(JSON.stringify(report)) > MAX_REPLY_BYTES - 3e3) {
3765
+ report.linkedContracts.pop();
3766
+ unavailable("Reply byte limit reached; inspection is partial.");
3767
+ break;
3768
+ }
3769
+ if (locked) report.warnings.push(`[CROSS-REPO ACTIVE LOCK] '${specifier}' is claimed by '${locked.owner_agent_id}'.`.slice(0, 300));
3770
+ if (dirtyInCheckout) report.warnings.push(`[CROSS-REPO CONTRACT DRIFT] '${specifier}' has uncommitted changes.`.slice(0, 300));
3771
+ for (const drift of drifts) report.warnings.push(`[CROSS-REPO WORKTREE DRIFT] '${specifier}' has unmerged signature changes in '${drift.agentId}'.`.slice(0, 300));
3772
+ if (report.warnings.length > 8) {
3773
+ report.warnings = report.warnings.slice(0, 7);
3774
+ unavailable("Warning limit reached; inspection is partial.");
3775
+ }
3776
+ }
3777
+ } catch {
3778
+ unavailable("Contract inspection could not finish; existing claims/work remain valid. Use normal project verification.");
3779
+ }
3780
+ return report;
3781
+ }
3782
+ function verifyCrossRepoImportsForFile(params) {
3783
+ if (Buffer.byteLength(params.content) > MAX_SOURCE_BYTES) return ["[CROSS-REPO UNAVAILABLE] Edited file exceeds inspection limit; use normal project verification."];
3784
+ const imports = AstAnalyzer.extractImports(params.worktreeDir ?? params.repoRoot, params.filePath, params.content);
3785
+ const report = inspectWorkspaceContracts(params.repoRoot, params.worktreeDir, imports.map((i) => i.rawSpecifier));
3786
+ const warnings = [...report.warnings];
3787
+ const namedImports = [...params.content.matchAll(/import\s+(?:type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g)];
3788
+ for (const imp of namedImports.slice(0, 32)) {
3789
+ const link = report.linkedContracts.find((l) => l.alias === imp[2]);
3790
+ if (!link?.exportsComplete) continue;
3791
+ const exported = new Set(link.symbols.map((s) => s.symbolName));
3792
+ for (const binding of imp[1].split(",")) {
3793
+ const symbol = binding.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
3794
+ if (!symbol || exported.has(symbol)) continue;
3795
+ if (warnings.length >= 8) break;
3796
+ warnings.push(`[CROSS-REPO CONTRACT MISMATCH] '${params.filePath.slice(0, 120)}' imports '${symbol.slice(0, 120)}' from '${imp[2].slice(0, 120)}', but that module has no such recorded export.`);
3797
+ }
3798
+ }
3799
+ return warnings;
3800
+ }
3801
+
3802
+ // src/core/worktree-cow.ts
3803
+ import { execFileSync as execFileSync7 } from "child_process";
3804
+ import fs13 from "fs";
3805
+ import path12 from "path";
3357
3806
  var CoWWorktreeManager = class _CoWWorktreeManager {
3358
3807
  constructor(coordDb, fencing) {
3359
3808
  this.coordDb = coordDb;
@@ -3373,32 +3822,32 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3373
3822
  const safeId = params.agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
3374
3823
  const branch = params.branch ?? `kith/${safeId}`;
3375
3824
  const baseRef = params.baseRef ?? "HEAD";
3376
- const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
3825
+ const worktreePath = path12.join(this.coordDb.coordDir, "worktrees", safeId);
3377
3826
  if (!this.fencing.getAgent(params.agentId)) throw new Error(`Unknown agent: ${params.agentId}`);
3378
3827
  if (!safeId || safeId === "." || safeId === "..") throw new Error("Invalid agent ID");
3379
- if (fs11.existsSync(worktreePath)) {
3828
+ if (fs13.existsSync(worktreePath)) {
3380
3829
  throw new Error(`Worktree already exists: ${worktreePath}. Commit or preserve its work before removing it.`);
3381
3830
  }
3382
- fs11.mkdirSync(path10.dirname(worktreePath), { recursive: true });
3383
- execFileSync6("git", ["worktree", "add", "-b", branch, worktreePath, baseRef], {
3831
+ fs13.mkdirSync(path12.dirname(worktreePath), { recursive: true });
3832
+ execFileSync7("git", ["worktree", "add", "-b", branch, worktreePath, baseRef], {
3384
3833
  cwd: this.coordDb.repoRoot,
3385
3834
  stdio: "ignore"
3386
3835
  });
3387
3836
  const hydratedDirs = [];
3388
3837
  let hydrationMethod = "none";
3389
3838
  for (const dirName of _CoWWorktreeManager.HYDRATABLE_DIRS) {
3390
- const srcDir = path10.join(this.coordDb.repoRoot, dirName);
3391
- const destDir = path10.join(worktreePath, dirName);
3392
- if (!fs11.existsSync(srcDir) || fs11.existsSync(destDir)) continue;
3839
+ const srcDir = path12.join(this.coordDb.repoRoot, dirName);
3840
+ const destDir = path12.join(worktreePath, dirName);
3841
+ if (!fs13.existsSync(srcDir) || fs13.existsSync(destDir)) continue;
3393
3842
  const method = this.cloneDirectoryCoW(srcDir, destDir);
3394
3843
  hydrationMethod = method;
3395
3844
  hydratedDirs.push(dirName);
3396
3845
  }
3397
3846
  for (const envName of _CoWWorktreeManager.ENV_FILES) {
3398
- const srcEnv = path10.join(this.coordDb.repoRoot, envName);
3399
- const destEnv = path10.join(worktreePath, envName);
3400
- if (fs11.existsSync(srcEnv) && !fs11.existsSync(destEnv)) {
3401
- fs11.copyFileSync(srcEnv, destEnv);
3847
+ const srcEnv = path12.join(this.coordDb.repoRoot, envName);
3848
+ const destEnv = path12.join(worktreePath, envName);
3849
+ if (fs13.existsSync(srcEnv) && !fs13.existsSync(destEnv)) {
3850
+ fs13.copyFileSync(srcEnv, destEnv);
3402
3851
  }
3403
3852
  }
3404
3853
  this.coordDb.withTransaction(() => {
@@ -3423,7 +3872,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3423
3872
  cloneDirectoryCoW(srcDir, destDir) {
3424
3873
  if (process.platform === "darwin") {
3425
3874
  try {
3426
- execFileSync6("cp", ["-cR", srcDir, destDir], {
3875
+ execFileSync7("cp", ["-cR", srcDir, destDir], {
3427
3876
  stdio: "ignore",
3428
3877
  timeout: 5e3
3429
3878
  });
@@ -3432,7 +3881,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3432
3881
  }
3433
3882
  } else if (process.platform === "linux") {
3434
3883
  try {
3435
- execFileSync6("cp", ["--reflink=always", "-R", srcDir, destDir], {
3884
+ execFileSync7("cp", ["--reflink=always", "-R", srcDir, destDir], {
3436
3885
  stdio: "ignore",
3437
3886
  timeout: 5e3
3438
3887
  });
@@ -3440,27 +3889,27 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3440
3889
  } catch {
3441
3890
  }
3442
3891
  }
3443
- fs11.rmSync(destDir, { recursive: true, force: true });
3444
- fs11.cpSync(srcDir, destDir, { recursive: true, dereference: false });
3892
+ fs13.rmSync(destDir, { recursive: true, force: true });
3893
+ fs13.cpSync(srcDir, destDir, { recursive: true, dereference: false });
3445
3894
  return "copy";
3446
3895
  }
3447
3896
  removeWorktree(agentId) {
3448
3897
  this.fencing.assertSession(agentId);
3449
3898
  const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
3450
- const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
3451
- if (!fs11.existsSync(worktreePath)) return false;
3899
+ const worktreePath = path12.join(this.coordDb.coordDir, "worktrees", safeId);
3900
+ if (!fs13.existsSync(worktreePath)) return false;
3452
3901
  const registered = this.fencing.getAgent(agentId)?.worktree_path;
3453
3902
  if (!registered || physicalPath(registered) !== physicalPath(worktreePath) || this.fencing.listAgents(true).some((agent) => agent.id !== agentId && agent.worktree_path && physicalPath(agent.worktree_path) === physicalPath(worktreePath))) {
3454
3903
  throw new Error("Refusing to remove a worktree that is not exclusively registered to this agent.");
3455
3904
  }
3456
- const status = execFileSync6("git", ["status", "--porcelain", "--untracked-files=normal", "--ignored=matching"], {
3905
+ const status = execFileSync7("git", ["status", "--porcelain", "--untracked-files=normal", "--ignored=matching"], {
3457
3906
  cwd: worktreePath,
3458
3907
  encoding: "utf8"
3459
3908
  });
3460
3909
  if (status.trim()) {
3461
3910
  throw new Error(`Refusing to remove dirty worktree: ${worktreePath}. Commit or preserve changed, untracked, and ignored files first.`);
3462
3911
  }
3463
- execFileSync6("git", ["worktree", "remove", worktreePath], {
3912
+ execFileSync7("git", ["worktree", "remove", worktreePath], {
3464
3913
  cwd: this.coordDb.repoRoot,
3465
3914
  stdio: ["ignore", "pipe", "pipe"]
3466
3915
  });
@@ -3472,7 +3921,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3472
3921
  }
3473
3922
  listWorktrees() {
3474
3923
  try {
3475
- const out = execFileSync6("git", ["worktree", "list", "--porcelain"], {
3924
+ const out = execFileSync7("git", ["worktree", "list", "--porcelain"], {
3476
3925
  cwd: this.coordDb.repoRoot,
3477
3926
  encoding: "utf8"
3478
3927
  });
@@ -3539,7 +3988,7 @@ var KithEngine = class {
3539
3988
  const file = LockEngine.normalizeFile(targetRoot, params.filePath);
3540
3989
  const access = this.locks.checkFileWriteAccess({ agentId: params.agentId, filePath: file });
3541
3990
  if (!access.allowed) throw new Error(access.reason);
3542
- const content = params.content ?? (fs12.existsSync(path11.join(targetRoot, file)) ? fs12.readFileSync(path11.join(targetRoot, file), "utf8") : "");
3991
+ const content = params.content ?? (fs14.existsSync(path13.join(targetRoot, file)) ? fs14.readFileSync(path13.join(targetRoot, file), "utf8") : "");
3543
3992
  const contractReport = this.depGraph.recordFileModification(
3544
3993
  params.agentId,
3545
3994
  file,
@@ -3573,6 +4022,13 @@ var KithEngine = class {
3573
4022
  `[LIVE DIRTY-TREE CONFLICT] Uncommitted changes in '${conf.file_path}' conflict with active agent '${peer}' (${conf.severity}: ${conf.details.reason}).`
3574
4023
  );
3575
4024
  }
4025
+ const crossRepoWarnings = verifyCrossRepoImportsForFile({
4026
+ repoRoot: this.coordDb.repoRoot,
4027
+ worktreeDir: targetRoot,
4028
+ filePath: file,
4029
+ content
4030
+ });
4031
+ warnings.push(...crossRepoWarnings);
3576
4032
  return {
3577
4033
  filePath: file,
3578
4034
  contractReport,
@@ -3624,6 +4080,7 @@ ${lines.join("\n")}` : "";
3624
4080
  }
3625
4081
  getStatus() {
3626
4082
  const agents = this.fencing.listAgents(true);
4083
+ const workspaceContracts = inspectWorkspaceContracts(this.coordDb.repoRoot);
3627
4084
  return {
3628
4085
  protocolVersion: KITH_PROTOCOL_VERSION,
3629
4086
  repository: { root: this.coordDb.repoRoot, commonGitDir: this.coordDb.commonGitDir, coordDir: this.coordDb.coordDir },
@@ -3634,6 +4091,9 @@ ${lines.join("\n")}` : "";
3634
4091
  tasks: this.intents.listTasks(),
3635
4092
  conflicts: this.shadowGit.listConflicts(),
3636
4093
  contracts: this.depGraph.listContracts(),
4094
+ linkedContracts: workspaceContracts.linkedContracts,
4095
+ crossRepoWarnings: workspaceContracts.warnings,
4096
+ crossRepoComplete: workspaceContracts.complete,
3637
4097
  resources: this.broker.listResources(),
3638
4098
  mergeQueue: this.mergeTrain.listQueue(),
3639
4099
  quarantined: this.bashGuard.listQuarantined(),
@@ -3647,9 +4107,70 @@ ${lines.join("\n")}` : "";
3647
4107
  return previewIntegration(this.coordDb.repoRoot, this.fencing.getAgent(agentId));
3648
4108
  }
3649
4109
  beginWork(params) {
3650
- const intent = this.intents.declareIntent(params);
3651
- const worktree = intent.ok && params.isolateWorktree ? this.worktrees.spawnWorktree({ agentId: params.agentId }) : null;
3652
- return { ...intent, worktree };
4110
+ const { intent, worktree } = this.coordDb.withTransaction(() => {
4111
+ const intent2 = this.intents.declareIntent(params);
4112
+ const worktree2 = intent2.ok && params.isolateWorktree ? this.worktrees.spawnWorktree({ agentId: params.agentId }) : null;
4113
+ return { intent: intent2, worktree: worktree2 };
4114
+ });
4115
+ const workspaceContracts = inspectWorkspaceContracts(
4116
+ this.coordDb.repoRoot,
4117
+ worktree?.worktreePath ?? this.fencing.getAgent(params.agentId)?.worktree_path ?? void 0
4118
+ );
4119
+ return {
4120
+ ...intent,
4121
+ worktree,
4122
+ linkedContracts: workspaceContracts.linkedContracts,
4123
+ crossRepoWarnings: workspaceContracts.warnings,
4124
+ crossRepoComplete: workspaceContracts.complete
4125
+ };
4126
+ }
4127
+ claimNextTask(params) {
4128
+ const { claimedRes, worktree } = this.coordDb.withTransaction(() => {
4129
+ const claimedRes2 = this.intents.claimNextTask(params.agentId);
4130
+ const worktree2 = claimedRes2.claimed && params.isolateWorktree ? this.claimWorktree(params.agentId) : null;
4131
+ return { claimedRes: claimedRes2, worktree: worktree2 };
4132
+ });
4133
+ const publishedContracts = this.depGraph.listContracts().slice(0, 30);
4134
+ const workspaceContracts = inspectWorkspaceContracts(
4135
+ this.coordDb.repoRoot,
4136
+ worktree?.worktreePath ?? this.fencing.getAgent(params.agentId)?.worktree_path ?? void 0
4137
+ );
4138
+ return {
4139
+ ...claimedRes,
4140
+ worktree,
4141
+ publishedContracts,
4142
+ linkedContracts: workspaceContracts.linkedContracts,
4143
+ crossRepoWarnings: workspaceContracts.warnings,
4144
+ crossRepoComplete: workspaceContracts.complete
4145
+ };
4146
+ }
4147
+ claimWorktree(agentId) {
4148
+ this.fencing.assertSession(agentId);
4149
+ const agent = this.fencing.getAgent(agentId);
4150
+ if (!agent.worktree_path) return this.worktrees.spawnWorktree({ agentId });
4151
+ const root = physicalPath(agent.worktree_path);
4152
+ const owner = discoverRepository(this.coordDb.repoRoot);
4153
+ const layout = discoverRepository(root);
4154
+ const git3 = (...args) => execFileSync8("git", args, {
4155
+ cwd: root,
4156
+ encoding: "utf8",
4157
+ stdio: ["ignore", "pipe", "pipe"],
4158
+ timeout: 5e3
4159
+ }).trim();
4160
+ if (!isWithin(physicalPath(path13.join(this.coordDb.coordDir, "worktrees")), root) || !layout.isLinkedWorktree || layout.checkoutRoot !== root || layout.commonGitDir !== owner.commonGitDir || !agent.branch || git3("symbolic-ref", "--short", "HEAD") !== agent.branch || !this.worktrees.listWorktrees().some((w) => physicalPath(w.path) === root && w.branch === agent.branch) || this.fencing.listAgents(true).some((a) => a.id !== agentId && a.worktree_path && physicalPath(a.worktree_path) === root)) {
4161
+ throw new Error("Saved worktree identity or ownership changed; inspect and preserve it before claiming another task.");
4162
+ }
4163
+ if (git3("status", "--porcelain", "--untracked-files=normal")) {
4164
+ throw new Error("Saved worktree has unfinished changes; preserve or finish them before claiming another task.");
4165
+ }
4166
+ const head = execFileSync8("git", ["rev-parse", "HEAD"], { cwd: this.coordDb.repoRoot, encoding: "utf8" }).trim();
4167
+ if (git3("rev-parse", "HEAD") !== head) {
4168
+ throw new Error("Saved worktree is ahead or behind the current checkout; integrate or replace it explicitly before claiming another task.");
4169
+ }
4170
+ return { agentId, worktreePath: root, branch: agent.branch, hydrationMethod: "none", hydratedDirs: [], durationMs: 0 };
4171
+ }
4172
+ async claimNextTaskWhenAvailable(params) {
4173
+ return this.waitForOwnedClaim(params, () => this.claimNextTask(params));
3653
4174
  }
3654
4175
  async beginWorkWhenAvailable(params) {
3655
4176
  return this.waitForOwnedClaim(params, () => this.beginWork(params));
@@ -3692,118 +4213,6 @@ ${lines.join("\n")}` : "";
3692
4213
  this.coordDb.close();
3693
4214
  }
3694
4215
  };
3695
-
3696
- // src/core/observation.ts
3697
- import fs13 from "fs";
3698
- import os from "os";
3699
- import path12 from "path";
3700
- import { createRequire as createRequire2 } from "module";
3701
- var { DatabaseSync: DatabaseSync2 } = createRequire2(import.meta.url)("node:sqlite");
3702
- function readCoordinationState(directory = process.cwd(), options = {}) {
3703
- const layout = discoverRepository(directory), observedAt = Date.now();
3704
- const empty = {
3705
- protocolVersion: KITH_PROTOCOL_VERSION,
3706
- observedAt,
3707
- repository: { root: layout.checkoutRoot, commonGitDir: layout.commonGitDir, coordDir: layout.coordDir },
3708
- runtime: { node: process.version },
3709
- state: "missing",
3710
- error: void 0,
3711
- fencingEpoch: 0,
3712
- agents: [],
3713
- tasks: [],
3714
- locks: [],
3715
- conflicts: [],
3716
- contracts: [],
3717
- resources: [],
3718
- mergeQueue: [],
3719
- quarantined: [],
3720
- mutexWaiters: [],
3721
- commandGuards: [],
3722
- events: [],
3723
- workspaces: []
3724
- };
3725
- let scratch, db;
3726
- try {
3727
- const file = path12.join(layout.coordDir, "state.db");
3728
- if (physicalPath(layout.coordDir) !== layout.coordDir || physicalPath(file) !== file) throw new Error("Coordination state must not be a symlink");
3729
- for (let current = fs13.realpathSync.native(directory); isWithin(layout.checkoutRoot, current); current = path12.dirname(current)) {
3730
- const local = path12.join(current, ".coord");
3731
- if (local !== layout.coordDir && fs13.existsSync(path12.join(local, "state.db"))) throw new Error(`Legacy independent state at ${local}; preserve it before consolidation`);
3732
- if (current === layout.checkoutRoot) break;
3733
- }
3734
- if (!fs13.existsSync(file)) {
3735
- if (["setup.lock", "merge-train.lock"].some((name) => fs13.existsSync(path12.join(layout.coordDir, name))) || ["-wal", "-shm", "-journal"].some((s) => fs13.existsSync(file + s)) || ["worktrees", "quarantine", "snapshots", "shadow-index", "setup"].some((name) => {
3736
- const p = path12.join(layout.coordDir, name);
3737
- return fs13.existsSync(p) && (!fs13.lstatSync(p).isDirectory() || fs13.readdirSync(p).length > 0);
3738
- })) throw new Error("Missing database with retained sidecars; preserve the recovery set");
3739
- return empty;
3740
- }
3741
- const files = ["", "-wal", "-shm", "-journal"].map((s) => file + s);
3742
- const identity = (f) => {
3743
- if (!fs13.existsSync(f)) return null;
3744
- if (physicalPath(f) !== f || !fs13.lstatSync(f).isFile()) throw new Error("Database or sidecar is redirected or not a regular file");
3745
- const s = fs13.statSync(f, { bigint: true });
3746
- return [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs].join(":");
3747
- };
3748
- const before = files.map(identity);
3749
- scratch = fs13.mkdtempSync(path12.join(os.tmpdir(), "kith-observe-"));
3750
- for (let i = 0; i < files.length; i++) if (before[i]) {
3751
- const target = path12.join(scratch, path12.basename(files[i]));
3752
- fs13.copyFileSync(files[i], target);
3753
- fs13.chmodSync(target, 384);
3754
- }
3755
- if (JSON.stringify(before) !== JSON.stringify(files.map(identity))) throw new Error("Coordination state changed during snapshot; stop writers or retry");
3756
- db = new DatabaseSync2(path12.join(scratch, "state.db"), { readOnly: true });
3757
- db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000; BEGIN");
3758
- if (CoordDatabase.inspectSchema(db) !== 2) throw new Error("Offline observation requires schema 2; preserve state and upgrade with clients stopped");
3759
- const all = (sql) => db.prepare(sql).all();
3760
- const agents = all("SELECT * FROM agents ORDER BY registered_at DESC");
3761
- if (options.ownersOnly) {
3762
- db.exec("COMMIT");
3763
- return { ...empty, state: "ready", agents };
3764
- }
3765
- const state = {
3766
- ...empty,
3767
- state: "ready",
3768
- fencingEpoch: Number(db.prepare("SELECT value FROM meta WHERE key='fencing_epoch'").get().value),
3769
- agents,
3770
- tasks: all("SELECT * FROM tasks ORDER BY priority DESC,created_at").map(({ depends_on_json, scope_globs_json, ...r }) => ({ ...r, depends_on: JSON.parse(String(depends_on_json)), scope_globs: JSON.parse(String(scope_globs_json)) })),
3771
- locks: all("SELECT * FROM locks ORDER BY acquired_at"),
3772
- conflicts: all("SELECT * FROM conflicts ORDER BY detected_at DESC").map(({ details_json, ...r }) => ({ ...r, details: JSON.parse(String(details_json)) })),
3773
- contracts: all("SELECT * FROM contracts ORDER BY updated_at DESC").map((r) => ({ ...r, is_breaking: Boolean(r.is_breaking) })),
3774
- resources: all("SELECT * FROM resources ORDER BY acquired_at"),
3775
- mergeQueue: all("SELECT * FROM merge_queue ORDER BY created_at DESC"),
3776
- quarantined: all("SELECT * FROM quarantine ORDER BY created_at DESC"),
3777
- mutexWaiters: all("SELECT ticket,mutex_name,owner_agent_id,session_epoch,expires_at FROM mutex_waiters ORDER BY ticket"),
3778
- commandGuards: all("SELECT mutex_name,owner_agent_id,worker_pid,supervisor_pid FROM mutex_runs ORDER BY mutex_name"),
3779
- events: all("SELECT * FROM events ORDER BY id DESC LIMIT 100").map(({ payload_json, ...r }) => ({ ...r, payload: JSON.parse(String(payload_json)) })),
3780
- workspaces: observeWorkspaces(layout.checkoutRoot, agents, observedAt)
3781
- };
3782
- db.exec("COMMIT");
3783
- return state;
3784
- } catch (error) {
3785
- return { ...empty, state: "unavailable", error: error instanceof Error ? error.message : String(error) };
3786
- } finally {
3787
- db?.close();
3788
- if (scratch) fs13.rmSync(scratch, { recursive: true, force: true });
3789
- }
3790
- }
3791
- function recoveryReport(directory = process.cwd()) {
3792
- const state = readCoordinationState(directory), retainedRoot = path12.join(state.repository.coordDir, "worktrees");
3793
- const retained = physicalPath(retainedRoot) === retainedRoot && fs13.existsSync(retainedRoot) && fs13.lstatSync(retainedRoot).isDirectory() && !fs13.lstatSync(retainedRoot).isSymbolicLink() ? fs13.readdirSync(retainedRoot, { withFileTypes: true }).map((entry) => ({
3794
- path: path12.join(retainedRoot, entry.name),
3795
- symlink: entry.isSymbolicLink(),
3796
- agentIds: state.agents.filter((a) => a.worktree_path === path12.join(retainedRoot, entry.name)).map((a) => a.id)
3797
- })) : [];
3798
- return {
3799
- ...state,
3800
- retainedWorktrees: retained,
3801
- expiredClaims: state.locks.filter((lock) => Number(lock.expires_at) <= state.observedAt),
3802
- unfinishedIntegrations: state.mergeQueue.filter((entry) => entry.status !== "merged"),
3803
- setupLock: { path: path12.join(state.repository.coordDir, "setup.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "setup.lock")) },
3804
- integrationLock: { path: path12.join(state.repository.coordDir, "merge-train.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "merge-train.lock")) }
3805
- };
3806
- }
3807
4216
  export {
3808
4217
  AstAnalyzer,
3809
4218
  BashMerkleGuard,
@@ -3821,8 +4230,10 @@ export {
3821
4230
  ShadowGitEngine,
3822
4231
  SpeculativeMergeTrain,
3823
4232
  StaleFencingTokenError,
4233
+ buildSwarmPrompts,
3824
4234
  caseSensitivePaths,
3825
4235
  discoverRepository,
4236
+ inspectWorkspaceContracts,
3826
4237
  isWithin,
3827
4238
  observeWorkspaces,
3828
4239
  physicalPath,
@@ -3830,6 +4241,7 @@ export {
3830
4241
  readCoordinationState,
3831
4242
  recoveryReport,
3832
4243
  repositoryFile,
4244
+ verifyCrossRepoImportsForFile,
3833
4245
  worktreeChanges
3834
4246
  };
3835
4247
  //# sourceMappingURL=index.js.map