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/CHANGELOG.md +13 -0
- package/README.md +16 -31
- package/bin/kith-menubar +0 -0
- package/dist/THIRD_PARTY_NOTICES.txt +80 -0
- package/dist/cli/index.js +29338 -3955
- package/dist/cli/index.js.map +1 -1
- package/dist/command-worker.js +2 -0
- package/dist/command-worker.js.map +1 -1
- package/dist/index.d.ts +195 -3
- package/dist/index.js +582 -182
- package/dist/index.js.map +1 -1
- package/docs/integrations.md +5 -0
- package/docs/protocol/tools.v1.json +15 -1
- package/docs/protocol.md +10 -2
- package/docs/reliability.md +14 -0
- package/docs/roadmap.md +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,40 +1,7 @@
|
|
|
1
|
-
|
|
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/
|
|
33
|
-
|
|
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
|
|
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(
|
|
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(
|
|
98
|
-
const isLinkedWorktree = fs.realpathSync(
|
|
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
|
|
268
|
-
import
|
|
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())
|
|
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 {
|
|
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/
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
3395
|
-
|
|
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 =
|
|
3403
|
-
const destDir =
|
|
3404
|
-
if (!
|
|
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 =
|
|
3411
|
-
const destEnv =
|
|
3412
|
-
if (
|
|
3413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3456
|
-
|
|
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 =
|
|
3463
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 ?? (
|
|
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.
|
|
3663
|
-
|
|
3664
|
-
|
|
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
|