kith-coord 0.1.6 → 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.6";
34
- var KITH_PROTOCOL_VERSION = "1.4.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 });
@@ -2718,10 +2732,21 @@ var IntentAndTaskEngine = class {
2718
2732
  return this.coordDb.withTransaction(() => {
2719
2733
  this.fencing.assertSession(agentId);
2720
2734
  const allTasks = this.listTasks();
2735
+ const activeSiblingIntents = allTasks.filter(
2736
+ (t) => t.status === "active" && t.owner_agent_id !== agentId
2737
+ );
2721
2738
  const taskById = new Map(allTasks.map((t) => [t.id, t]));
2722
2739
  const queued = allTasks.filter((t) => t.status === "queued").sort((a, b) => b.priority - a.priority || a.created_at - b.created_at);
2723
2740
  if (queued.length === 0) {
2724
- 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
+ };
2725
2750
  }
2726
2751
  for (const candidate of queued) {
2727
2752
  const depsSatisfied = candidate.depends_on.every(
@@ -2757,17 +2782,21 @@ var IntentAndTaskEngine = class {
2757
2782
  fencingToken
2758
2783
  });
2759
2784
  return {
2785
+ ok: true,
2760
2786
  claimed: true,
2761
2787
  task: this.getTask(candidate.id),
2762
2788
  fencingToken,
2763
- locks
2789
+ locks,
2790
+ activeSiblingIntents
2764
2791
  };
2765
2792
  }
2766
2793
  return {
2794
+ ok: false,
2767
2795
  claimed: false,
2768
2796
  task: null,
2769
2797
  fencingToken: null,
2770
2798
  locks: [],
2799
+ activeSiblingIntents,
2771
2800
  reason: "all_queued_tasks_blocked_by_dependencies_or_locks"
2772
2801
  };
2773
2802
  });
@@ -3362,10 +3391,418 @@ var ShadowGitEngine = class {
3362
3391
  }
3363
3392
  };
3364
3393
 
3365
- // src/core/worktree-cow.ts
3394
+ // src/core/workspace-contracts.ts
3366
3395
  import { execFileSync as execFileSync6 } from "child_process";
3396
+ import fs12 from "fs";
3397
+ import path11 from "path";
3398
+
3399
+ // src/core/observation.ts
3367
3400
  import fs11 from "fs";
3401
+ import os from "os";
3368
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";
3369
3806
  var CoWWorktreeManager = class _CoWWorktreeManager {
3370
3807
  constructor(coordDb, fencing) {
3371
3808
  this.coordDb = coordDb;
@@ -3385,32 +3822,32 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3385
3822
  const safeId = params.agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
3386
3823
  const branch = params.branch ?? `kith/${safeId}`;
3387
3824
  const baseRef = params.baseRef ?? "HEAD";
3388
- const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
3825
+ const worktreePath = path12.join(this.coordDb.coordDir, "worktrees", safeId);
3389
3826
  if (!this.fencing.getAgent(params.agentId)) throw new Error(`Unknown agent: ${params.agentId}`);
3390
3827
  if (!safeId || safeId === "." || safeId === "..") throw new Error("Invalid agent ID");
3391
- if (fs11.existsSync(worktreePath)) {
3828
+ if (fs13.existsSync(worktreePath)) {
3392
3829
  throw new Error(`Worktree already exists: ${worktreePath}. Commit or preserve its work before removing it.`);
3393
3830
  }
3394
- fs11.mkdirSync(path10.dirname(worktreePath), { recursive: true });
3395
- 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], {
3396
3833
  cwd: this.coordDb.repoRoot,
3397
3834
  stdio: "ignore"
3398
3835
  });
3399
3836
  const hydratedDirs = [];
3400
3837
  let hydrationMethod = "none";
3401
3838
  for (const dirName of _CoWWorktreeManager.HYDRATABLE_DIRS) {
3402
- const srcDir = path10.join(this.coordDb.repoRoot, dirName);
3403
- const destDir = path10.join(worktreePath, dirName);
3404
- 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;
3405
3842
  const method = this.cloneDirectoryCoW(srcDir, destDir);
3406
3843
  hydrationMethod = method;
3407
3844
  hydratedDirs.push(dirName);
3408
3845
  }
3409
3846
  for (const envName of _CoWWorktreeManager.ENV_FILES) {
3410
- const srcEnv = path10.join(this.coordDb.repoRoot, envName);
3411
- const destEnv = path10.join(worktreePath, envName);
3412
- if (fs11.existsSync(srcEnv) && !fs11.existsSync(destEnv)) {
3413
- 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);
3414
3851
  }
3415
3852
  }
3416
3853
  this.coordDb.withTransaction(() => {
@@ -3435,7 +3872,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3435
3872
  cloneDirectoryCoW(srcDir, destDir) {
3436
3873
  if (process.platform === "darwin") {
3437
3874
  try {
3438
- execFileSync6("cp", ["-cR", srcDir, destDir], {
3875
+ execFileSync7("cp", ["-cR", srcDir, destDir], {
3439
3876
  stdio: "ignore",
3440
3877
  timeout: 5e3
3441
3878
  });
@@ -3444,7 +3881,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3444
3881
  }
3445
3882
  } else if (process.platform === "linux") {
3446
3883
  try {
3447
- execFileSync6("cp", ["--reflink=always", "-R", srcDir, destDir], {
3884
+ execFileSync7("cp", ["--reflink=always", "-R", srcDir, destDir], {
3448
3885
  stdio: "ignore",
3449
3886
  timeout: 5e3
3450
3887
  });
@@ -3452,27 +3889,27 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3452
3889
  } catch {
3453
3890
  }
3454
3891
  }
3455
- fs11.rmSync(destDir, { recursive: true, force: true });
3456
- 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 });
3457
3894
  return "copy";
3458
3895
  }
3459
3896
  removeWorktree(agentId) {
3460
3897
  this.fencing.assertSession(agentId);
3461
3898
  const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
3462
- const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
3463
- if (!fs11.existsSync(worktreePath)) return false;
3899
+ const worktreePath = path12.join(this.coordDb.coordDir, "worktrees", safeId);
3900
+ if (!fs13.existsSync(worktreePath)) return false;
3464
3901
  const registered = this.fencing.getAgent(agentId)?.worktree_path;
3465
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))) {
3466
3903
  throw new Error("Refusing to remove a worktree that is not exclusively registered to this agent.");
3467
3904
  }
3468
- const status = execFileSync6("git", ["status", "--porcelain", "--untracked-files=normal", "--ignored=matching"], {
3905
+ const status = execFileSync7("git", ["status", "--porcelain", "--untracked-files=normal", "--ignored=matching"], {
3469
3906
  cwd: worktreePath,
3470
3907
  encoding: "utf8"
3471
3908
  });
3472
3909
  if (status.trim()) {
3473
3910
  throw new Error(`Refusing to remove dirty worktree: ${worktreePath}. Commit or preserve changed, untracked, and ignored files first.`);
3474
3911
  }
3475
- execFileSync6("git", ["worktree", "remove", worktreePath], {
3912
+ execFileSync7("git", ["worktree", "remove", worktreePath], {
3476
3913
  cwd: this.coordDb.repoRoot,
3477
3914
  stdio: ["ignore", "pipe", "pipe"]
3478
3915
  });
@@ -3484,7 +3921,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3484
3921
  }
3485
3922
  listWorktrees() {
3486
3923
  try {
3487
- const out = execFileSync6("git", ["worktree", "list", "--porcelain"], {
3924
+ const out = execFileSync7("git", ["worktree", "list", "--porcelain"], {
3488
3925
  cwd: this.coordDb.repoRoot,
3489
3926
  encoding: "utf8"
3490
3927
  });
@@ -3551,7 +3988,7 @@ var KithEngine = class {
3551
3988
  const file = LockEngine.normalizeFile(targetRoot, params.filePath);
3552
3989
  const access = this.locks.checkFileWriteAccess({ agentId: params.agentId, filePath: file });
3553
3990
  if (!access.allowed) throw new Error(access.reason);
3554
- 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") : "");
3555
3992
  const contractReport = this.depGraph.recordFileModification(
3556
3993
  params.agentId,
3557
3994
  file,
@@ -3585,6 +4022,13 @@ var KithEngine = class {
3585
4022
  `[LIVE DIRTY-TREE CONFLICT] Uncommitted changes in '${conf.file_path}' conflict with active agent '${peer}' (${conf.severity}: ${conf.details.reason}).`
3586
4023
  );
3587
4024
  }
4025
+ const crossRepoWarnings = verifyCrossRepoImportsForFile({
4026
+ repoRoot: this.coordDb.repoRoot,
4027
+ worktreeDir: targetRoot,
4028
+ filePath: file,
4029
+ content
4030
+ });
4031
+ warnings.push(...crossRepoWarnings);
3588
4032
  return {
3589
4033
  filePath: file,
3590
4034
  contractReport,
@@ -3636,6 +4080,7 @@ ${lines.join("\n")}` : "";
3636
4080
  }
3637
4081
  getStatus() {
3638
4082
  const agents = this.fencing.listAgents(true);
4083
+ const workspaceContracts = inspectWorkspaceContracts(this.coordDb.repoRoot);
3639
4084
  return {
3640
4085
  protocolVersion: KITH_PROTOCOL_VERSION,
3641
4086
  repository: { root: this.coordDb.repoRoot, commonGitDir: this.coordDb.commonGitDir, coordDir: this.coordDb.coordDir },
@@ -3646,6 +4091,9 @@ ${lines.join("\n")}` : "";
3646
4091
  tasks: this.intents.listTasks(),
3647
4092
  conflicts: this.shadowGit.listConflicts(),
3648
4093
  contracts: this.depGraph.listContracts(),
4094
+ linkedContracts: workspaceContracts.linkedContracts,
4095
+ crossRepoWarnings: workspaceContracts.warnings,
4096
+ crossRepoComplete: workspaceContracts.complete,
3649
4097
  resources: this.broker.listResources(),
3650
4098
  mergeQueue: this.mergeTrain.listQueue(),
3651
4099
  quarantined: this.bashGuard.listQuarantined(),
@@ -3659,9 +4107,70 @@ ${lines.join("\n")}` : "";
3659
4107
  return previewIntegration(this.coordDb.repoRoot, this.fencing.getAgent(agentId));
3660
4108
  }
3661
4109
  beginWork(params) {
3662
- const intent = this.intents.declareIntent(params);
3663
- const worktree = intent.ok && params.isolateWorktree ? this.worktrees.spawnWorktree({ agentId: params.agentId }) : null;
3664
- 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));
3665
4174
  }
3666
4175
  async beginWorkWhenAvailable(params) {
3667
4176
  return this.waitForOwnedClaim(params, () => this.beginWork(params));
@@ -3704,118 +4213,6 @@ ${lines.join("\n")}` : "";
3704
4213
  this.coordDb.close();
3705
4214
  }
3706
4215
  };
3707
-
3708
- // src/core/observation.ts
3709
- import fs13 from "fs";
3710
- import os from "os";
3711
- import path12 from "path";
3712
- import { createRequire as createRequire2 } from "module";
3713
- var { DatabaseSync: DatabaseSync2 } = createRequire2(import.meta.url)("node:sqlite");
3714
- function readCoordinationState(directory = process.cwd(), options = {}) {
3715
- const layout = discoverRepository(directory), observedAt = Date.now();
3716
- const empty = {
3717
- protocolVersion: KITH_PROTOCOL_VERSION,
3718
- observedAt,
3719
- repository: { root: layout.checkoutRoot, commonGitDir: layout.commonGitDir, coordDir: layout.coordDir },
3720
- runtime: { node: process.version },
3721
- state: "missing",
3722
- error: void 0,
3723
- fencingEpoch: 0,
3724
- agents: [],
3725
- tasks: [],
3726
- locks: [],
3727
- conflicts: [],
3728
- contracts: [],
3729
- resources: [],
3730
- mergeQueue: [],
3731
- quarantined: [],
3732
- mutexWaiters: [],
3733
- commandGuards: [],
3734
- events: [],
3735
- workspaces: []
3736
- };
3737
- let scratch, db;
3738
- try {
3739
- const file = path12.join(layout.coordDir, "state.db");
3740
- if (physicalPath(layout.coordDir) !== layout.coordDir || physicalPath(file) !== file) throw new Error("Coordination state must not be a symlink");
3741
- for (let current = fs13.realpathSync.native(directory); isWithin(layout.checkoutRoot, current); current = path12.dirname(current)) {
3742
- const local = path12.join(current, ".coord");
3743
- if (local !== layout.coordDir && fs13.existsSync(path12.join(local, "state.db"))) throw new Error(`Legacy independent state at ${local}; preserve it before consolidation`);
3744
- if (current === layout.checkoutRoot) break;
3745
- }
3746
- if (!fs13.existsSync(file)) {
3747
- 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) => {
3748
- const p = path12.join(layout.coordDir, name);
3749
- return fs13.existsSync(p) && (!fs13.lstatSync(p).isDirectory() || fs13.readdirSync(p).length > 0);
3750
- })) throw new Error("Missing database with retained sidecars; preserve the recovery set");
3751
- return empty;
3752
- }
3753
- const files = ["", "-wal", "-shm", "-journal"].map((s) => file + s);
3754
- const identity = (f) => {
3755
- if (!fs13.existsSync(f)) return null;
3756
- if (physicalPath(f) !== f || !fs13.lstatSync(f).isFile()) throw new Error("Database or sidecar is redirected or not a regular file");
3757
- const s = fs13.statSync(f, { bigint: true });
3758
- return [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs].join(":");
3759
- };
3760
- const before = files.map(identity);
3761
- scratch = fs13.mkdtempSync(path12.join(os.tmpdir(), "kith-observe-"));
3762
- for (let i = 0; i < files.length; i++) if (before[i]) {
3763
- const target = path12.join(scratch, path12.basename(files[i]));
3764
- fs13.copyFileSync(files[i], target);
3765
- fs13.chmodSync(target, 384);
3766
- }
3767
- if (JSON.stringify(before) !== JSON.stringify(files.map(identity))) throw new Error("Coordination state changed during snapshot; stop writers or retry");
3768
- db = new DatabaseSync2(path12.join(scratch, "state.db"), { readOnly: true });
3769
- db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000; BEGIN");
3770
- if (CoordDatabase.inspectSchema(db) < 2) throw new Error("Offline observation requires schema 2 or newer; preserve state and upgrade with clients stopped");
3771
- const all = (sql) => db.prepare(sql).all();
3772
- const agents = all("SELECT * FROM agents ORDER BY registered_at DESC");
3773
- if (options.ownersOnly) {
3774
- db.exec("COMMIT");
3775
- return { ...empty, state: "ready", agents };
3776
- }
3777
- const state = {
3778
- ...empty,
3779
- state: "ready",
3780
- fencingEpoch: Number(db.prepare("SELECT value FROM meta WHERE key='fencing_epoch'").get().value),
3781
- agents,
3782
- 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)) })),
3783
- locks: all("SELECT * FROM locks ORDER BY acquired_at"),
3784
- conflicts: all("SELECT * FROM conflicts ORDER BY detected_at DESC").map(({ details_json, ...r }) => ({ ...r, details: JSON.parse(String(details_json)) })),
3785
- contracts: all("SELECT * FROM contracts ORDER BY updated_at DESC").map((r) => ({ ...r, is_breaking: Boolean(r.is_breaking) })),
3786
- resources: all("SELECT * FROM resources ORDER BY acquired_at"),
3787
- mergeQueue: all("SELECT * FROM merge_queue ORDER BY created_at DESC"),
3788
- quarantined: all("SELECT * FROM quarantine ORDER BY created_at DESC"),
3789
- mutexWaiters: all("SELECT ticket,mutex_name,owner_agent_id,session_epoch,expires_at FROM mutex_waiters ORDER BY ticket"),
3790
- commandGuards: all("SELECT mutex_name,owner_agent_id,worker_pid,supervisor_pid FROM mutex_runs ORDER BY mutex_name"),
3791
- events: all("SELECT * FROM events ORDER BY id DESC LIMIT 100").map(({ payload_json, ...r }) => ({ ...r, payload: JSON.parse(String(payload_json)) })),
3792
- workspaces: observeWorkspaces(layout.checkoutRoot, agents, observedAt)
3793
- };
3794
- db.exec("COMMIT");
3795
- return state;
3796
- } catch (error) {
3797
- return { ...empty, state: "unavailable", error: error instanceof Error ? error.message : String(error) };
3798
- } finally {
3799
- db?.close();
3800
- if (scratch) fs13.rmSync(scratch, { recursive: true, force: true });
3801
- }
3802
- }
3803
- function recoveryReport(directory = process.cwd()) {
3804
- const state = readCoordinationState(directory), retainedRoot = path12.join(state.repository.coordDir, "worktrees");
3805
- const retained = physicalPath(retainedRoot) === retainedRoot && fs13.existsSync(retainedRoot) && fs13.lstatSync(retainedRoot).isDirectory() && !fs13.lstatSync(retainedRoot).isSymbolicLink() ? fs13.readdirSync(retainedRoot, { withFileTypes: true }).map((entry) => ({
3806
- path: path12.join(retainedRoot, entry.name),
3807
- symlink: entry.isSymbolicLink(),
3808
- agentIds: state.agents.filter((a) => a.worktree_path === path12.join(retainedRoot, entry.name)).map((a) => a.id)
3809
- })) : [];
3810
- return {
3811
- ...state,
3812
- retainedWorktrees: retained,
3813
- expiredClaims: state.locks.filter((lock) => Number(lock.expires_at) <= state.observedAt),
3814
- unfinishedIntegrations: state.mergeQueue.filter((entry) => entry.status !== "merged"),
3815
- setupLock: { path: path12.join(state.repository.coordDir, "setup.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "setup.lock")) },
3816
- integrationLock: { path: path12.join(state.repository.coordDir, "merge-train.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "merge-train.lock")) }
3817
- };
3818
- }
3819
4216
  export {
3820
4217
  AstAnalyzer,
3821
4218
  BashMerkleGuard,
@@ -3833,8 +4230,10 @@ export {
3833
4230
  ShadowGitEngine,
3834
4231
  SpeculativeMergeTrain,
3835
4232
  StaleFencingTokenError,
4233
+ buildSwarmPrompts,
3836
4234
  caseSensitivePaths,
3837
4235
  discoverRepository,
4236
+ inspectWorkspaceContracts,
3838
4237
  isWithin,
3839
4238
  observeWorkspaces,
3840
4239
  physicalPath,
@@ -3842,6 +4241,7 @@ export {
3842
4241
  readCoordinationState,
3843
4242
  recoveryReport,
3844
4243
  repositoryFile,
4244
+ verifyCrossRepoImportsForFile,
3845
4245
  worktreeChanges
3846
4246
  };
3847
4247
  //# sourceMappingURL=index.js.map