kith-coord 0.1.0 → 0.1.2
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 +18 -1
- package/README.md +6 -14
- package/dist/cli/index.js +105 -71
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +82 -60
- package/dist/index.js.map +1 -1
- package/docs/client-validation.md +21 -4
- package/docs/integrations.md +9 -4
- package/docs/path-and-scope-rules.md +1 -1
- package/docs/protocol/tools.v1.json +6 -2
- package/docs/protocol.md +6 -4
- package/docs/reliability.md +3 -1
- package/docs/roadmap.md +1 -1
- package/docs/usage.md +9 -1
- package/docs/validation/2026-10-20-cursor-cli.json +38 -0
- package/docs/validation/2026-10-20-cursor-hook-unavailable.json +18 -0
- package/docs/validation/2026-10-20-windows-node-22.13.0.json +17 -0
- package/docs/validation/2026-10-20-windows-node-24.json +17 -0
- package/package.json +4 -2
- package/server.json +52 -0
package/dist/index.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ declare class FencingAndAgentManager {
|
|
|
73
73
|
private readonly coordDb;
|
|
74
74
|
static readonly DEFAULT_LEASE_MS: number;
|
|
75
75
|
private readonly capabilities;
|
|
76
|
+
private static ownWindowsStart;
|
|
76
77
|
constructor(coordDb: CoordDatabase);
|
|
77
78
|
static validateLease(duration: number): number;
|
|
78
79
|
/** A real kernel process identity; tests must not supply liveness sentinels. */
|
|
@@ -977,7 +978,7 @@ declare function recoveryReport(directory?: string): {
|
|
|
977
978
|
error: string | undefined;
|
|
978
979
|
};
|
|
979
980
|
|
|
980
|
-
declare const KITH_VERSION = "0.1.
|
|
981
|
-
declare const KITH_PROTOCOL_VERSION = "1.
|
|
981
|
+
declare const KITH_VERSION = "0.1.2";
|
|
982
|
+
declare const KITH_PROTOCOL_VERSION = "1.1.0";
|
|
982
983
|
|
|
983
984
|
export { type AcquireLockResult, type AgentRecord, type AgentUnreadDelta, AstAnalyzer, type BashGuardPostReport, BashMerkleGuard, type BlastRadiusAlert, CoWWorktreeManager, type ConflictRecord, type ContractRecord, CoordDatabase, type CoordEvent, type DeclareIntentResult, DependencyAndContractEngine, type DirtyMergeProbeResult, type ExtractedImport, type ExtractedSymbol, FencingAndAgentManager, type FileAstAnalysis, type FileModificationReport, type FileSnapshotEntry, IntentAndTaskEngine, KITH_PROTOCOL_VERSION, KITH_VERSION, KithEngine, type LockConflict, LockEngine, type LockRecord, type MergeTrainEntry, type PostFileEditHookResult, type QuarantineRecord, type RepositoryLayout, ResourceBroker, type ResourceRecord, SessionOwnershipError, type SessionRegistration, ShadowGitEngine, type SpawnedWorktree, type SpeculativeMergeResult, SpeculativeMergeTrain, StaleFencingTokenError, type SymbolKind, type TaskRecord, type WorkspaceObservation, type WorktreeChange, caseSensitivePaths, discoverRepository, isWithin, observeWorkspaces, physicalPath, previewIntegration, readCoordinationState, recoveryReport, repositoryFile, worktreeChanges };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var KITH_VERSION = "0.1.
|
|
3
|
-
var KITH_PROTOCOL_VERSION = "1.
|
|
2
|
+
var KITH_VERSION = "0.1.2";
|
|
3
|
+
var KITH_PROTOCOL_VERSION = "1.1.0";
|
|
4
4
|
|
|
5
5
|
// src/core/workspace-observation.ts
|
|
6
6
|
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
@@ -10,6 +10,7 @@ import { execFileSync } from "child_process";
|
|
|
10
10
|
import fs from "fs";
|
|
11
11
|
import path from "path";
|
|
12
12
|
function physicalPath(input) {
|
|
13
|
+
if (process.platform === "win32") input = input.replaceAll("/", path.sep);
|
|
13
14
|
const absolute = path.isAbsolute(input) ? input : process.cwd() + path.sep + input;
|
|
14
15
|
let current = path.parse(absolute).root;
|
|
15
16
|
for (const component of absolute.slice(current.length).split(path.sep)) {
|
|
@@ -40,7 +41,7 @@ function isWithin(root, target) {
|
|
|
40
41
|
return relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative);
|
|
41
42
|
}
|
|
42
43
|
function repositoryFile(root, file) {
|
|
43
|
-
if (!file || /[
|
|
44
|
+
if (!file || /[\x00-\x1f\x7f]/u.test(file) || process.platform !== "win32" && file.includes("\\")) throw new Error(`Invalid file path: ${file}`);
|
|
44
45
|
const canonicalRoot = physicalPath(root);
|
|
45
46
|
const target = physicalPath(path.isAbsolute(file) ? file : canonicalRoot + path.sep + file);
|
|
46
47
|
if (!isWithin(canonicalRoot, target) || target === canonicalRoot) {
|
|
@@ -230,7 +231,7 @@ function previewIntegration(repoRoot, agent) {
|
|
|
230
231
|
|
|
231
232
|
// src/core/engine.ts
|
|
232
233
|
import fs12 from "fs";
|
|
233
|
-
import
|
|
234
|
+
import path11 from "path";
|
|
234
235
|
|
|
235
236
|
// src/core/ast-analyzer.ts
|
|
236
237
|
import crypto from "crypto";
|
|
@@ -511,12 +512,12 @@ var AstAnalyzer = class _AstAnalyzer {
|
|
|
511
512
|
// src/core/bash-guard.ts
|
|
512
513
|
import crypto4 from "crypto";
|
|
513
514
|
import fs5 from "fs";
|
|
514
|
-
import
|
|
515
|
+
import path5 from "path";
|
|
515
516
|
|
|
516
517
|
// src/core/locks.ts
|
|
517
518
|
import crypto3 from "crypto";
|
|
518
519
|
import fs4 from "fs";
|
|
519
|
-
import
|
|
520
|
+
import path4 from "path";
|
|
520
521
|
|
|
521
522
|
// src/core/glob.ts
|
|
522
523
|
function scopeSegments(pattern, literal = false) {
|
|
@@ -587,6 +588,7 @@ function scopesOverlap(a, b, literalA = false, literalB = false) {
|
|
|
587
588
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
588
589
|
import crypto2 from "crypto";
|
|
589
590
|
import fs3 from "fs";
|
|
591
|
+
import path3 from "path";
|
|
590
592
|
var SessionOwnershipError = class extends Error {
|
|
591
593
|
constructor(agentId) {
|
|
592
594
|
super(`No valid active session capability for '${agentId}'. Start a session, or resume it with its session token.`);
|
|
@@ -612,6 +614,7 @@ var FencingAndAgentManager = class _FencingAndAgentManager {
|
|
|
612
614
|
coordDb;
|
|
613
615
|
static DEFAULT_LEASE_MS = 5 * 60 * 1e3;
|
|
614
616
|
capabilities = /* @__PURE__ */ new Map();
|
|
617
|
+
static ownWindowsStart = null;
|
|
615
618
|
static validateLease(duration) {
|
|
616
619
|
if (!Number.isSafeInteger(duration) || duration < 100 || duration > 864e5) {
|
|
617
620
|
throw new Error("Lease duration must be an integer between 100 and 86400000 milliseconds");
|
|
@@ -626,6 +629,24 @@ var FencingAndAgentManager = class _FencingAndAgentManager {
|
|
|
626
629
|
const stat = fs3.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
627
630
|
return `linux:${stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]}`;
|
|
628
631
|
}
|
|
632
|
+
if (process.platform === "win32") {
|
|
633
|
+
if (pid === process.pid && this.ownWindowsStart) return this.ownWindowsStart;
|
|
634
|
+
const powershell = path3.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
635
|
+
const ticks = execFileSync3(
|
|
636
|
+
powershell,
|
|
637
|
+
[
|
|
638
|
+
"-NoProfile",
|
|
639
|
+
"-NonInteractive",
|
|
640
|
+
"-Command",
|
|
641
|
+
`(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture)`
|
|
642
|
+
],
|
|
643
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3, windowsHide: true }
|
|
644
|
+
).trim();
|
|
645
|
+
if (!/^\d+$/.test(ticks)) return null;
|
|
646
|
+
const identity = `windows:${ticks}`;
|
|
647
|
+
if (pid === process.pid) this.ownWindowsStart = identity;
|
|
648
|
+
return identity;
|
|
649
|
+
}
|
|
629
650
|
const out = execFileSync3("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
630
651
|
encoding: "utf8",
|
|
631
652
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -815,9 +836,9 @@ var LockEngine = class _LockEngine {
|
|
|
815
836
|
const parts = rawPattern.split("/");
|
|
816
837
|
const magic = parts.findIndex((p) => /[?*]/u.test(p));
|
|
817
838
|
const prefix = magic < 0 ? rawPattern : parts.slice(0, magic).join("/") || ".";
|
|
818
|
-
const absolute = physicalPath(
|
|
839
|
+
const absolute = physicalPath(path4.isAbsolute(prefix) ? prefix : root + "/" + prefix);
|
|
819
840
|
if (!isWithin(root, absolute)) throw new Error(`Scope is outside repository: ${rawPattern}`);
|
|
820
|
-
const relative =
|
|
841
|
+
const relative = path4.relative(root, absolute).split(path4.sep).join("/");
|
|
821
842
|
const suffix = magic < 0 ? "" : parts.slice(magic).join("/");
|
|
822
843
|
const normalized = [relative, suffix].filter(Boolean).join("/");
|
|
823
844
|
scopeSegments(normalized);
|
|
@@ -852,7 +873,7 @@ var LockEngine = class _LockEngine {
|
|
|
852
873
|
let budget = 1e4;
|
|
853
874
|
while (pending.length) {
|
|
854
875
|
const { root, relative } = pending.pop();
|
|
855
|
-
const dir =
|
|
876
|
+
const dir = path4.join(root, relative);
|
|
856
877
|
let entries;
|
|
857
878
|
try {
|
|
858
879
|
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
@@ -1111,7 +1132,7 @@ var BashMerkleGuard = class {
|
|
|
1111
1132
|
getSnapshotFilePath(agentId) {
|
|
1112
1133
|
const epoch = this.fencing.assertSession(agentId);
|
|
1113
1134
|
const safeId = crypto4.createHash("sha256").update(`${agentId}:${epoch}`).digest("hex");
|
|
1114
|
-
return
|
|
1135
|
+
return path5.join(this.coordDb.coordDir, "snapshots", `${safeId}.json`);
|
|
1115
1136
|
}
|
|
1116
1137
|
/**
|
|
1117
1138
|
* Captures a Merkle snapshot of workspace files before a Bash tool execution.
|
|
@@ -1123,7 +1144,7 @@ var BashMerkleGuard = class {
|
|
|
1123
1144
|
const entries = {};
|
|
1124
1145
|
const files = this.walkWorkspaceFiles(rootDir);
|
|
1125
1146
|
for (const absPath of files) {
|
|
1126
|
-
const relPath =
|
|
1147
|
+
const relPath = path5.relative(rootDir, absPath).replace(/\\/g, "/");
|
|
1127
1148
|
const stat = fs5.statSync(absPath);
|
|
1128
1149
|
if (stat.size > 5 * 1024 * 1024) continue;
|
|
1129
1150
|
const buf = fs5.readFileSync(absPath);
|
|
@@ -1166,7 +1187,7 @@ var BashMerkleGuard = class {
|
|
|
1166
1187
|
const authorizedModifiedFiles = [];
|
|
1167
1188
|
const quarantinedViolations = [];
|
|
1168
1189
|
for (const absPath of currentFiles) {
|
|
1169
|
-
const relPath =
|
|
1190
|
+
const relPath = path5.relative(rootDir, absPath).replace(/\\/g, "/");
|
|
1170
1191
|
seenCurrentRelPaths.add(relPath);
|
|
1171
1192
|
const stat = fs5.statSync(absPath);
|
|
1172
1193
|
if (stat.size > 5 * 1024 * 1024) continue;
|
|
@@ -1228,7 +1249,7 @@ var BashMerkleGuard = class {
|
|
|
1228
1249
|
}
|
|
1229
1250
|
if (authorizedModifiedFiles.length > 0) {
|
|
1230
1251
|
for (const relFile of authorizedModifiedFiles) {
|
|
1231
|
-
const absFile =
|
|
1252
|
+
const absFile = path5.join(rootDir, relFile);
|
|
1232
1253
|
if (fs5.existsSync(absFile) && /\.(ts|tsx|js|jsx|mjs|py)$/.test(relFile)) {
|
|
1233
1254
|
this.depGraph.recordFileModification(params.agentId, relFile, fs5.readFileSync(absFile, "utf8"));
|
|
1234
1255
|
}
|
|
@@ -1249,7 +1270,7 @@ var BashMerkleGuard = class {
|
|
|
1249
1270
|
const id = `quar_${crypto4.randomBytes(6).toString("hex")}`;
|
|
1250
1271
|
const now = Date.now();
|
|
1251
1272
|
const safeName = params.relPath.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1252
|
-
const patchPath =
|
|
1273
|
+
const patchPath = path5.join(
|
|
1253
1274
|
this.coordDb.coordDir,
|
|
1254
1275
|
"quarantine",
|
|
1255
1276
|
`${id}_${safeName}.quarantine`
|
|
@@ -1263,13 +1284,13 @@ var BashMerkleGuard = class {
|
|
|
1263
1284
|
if (params.prevEntry?.backupContentBase64 !== void 0) {
|
|
1264
1285
|
fs5.writeFileSync(patchPath + ".before", Buffer.from(params.prevEntry.backupContentBase64, "base64"), { mode: 384 });
|
|
1265
1286
|
}
|
|
1266
|
-
const absTarget =
|
|
1287
|
+
const absTarget = path5.join(params.rootDir, params.relPath);
|
|
1267
1288
|
if (params.restoreLockedFiles && params.prevEntry === null) {
|
|
1268
1289
|
if (fs5.existsSync(absTarget)) {
|
|
1269
1290
|
fs5.unlinkSync(absTarget);
|
|
1270
1291
|
}
|
|
1271
1292
|
} else if (params.restoreLockedFiles && params.prevEntry?.backupContentBase64 !== void 0) {
|
|
1272
|
-
fs5.mkdirSync(
|
|
1293
|
+
fs5.mkdirSync(path5.dirname(absTarget), { recursive: true });
|
|
1273
1294
|
fs5.writeFileSync(
|
|
1274
1295
|
absTarget,
|
|
1275
1296
|
Buffer.from(params.prevEntry.backupContentBase64, "base64")
|
|
@@ -1313,7 +1334,7 @@ var BashMerkleGuard = class {
|
|
|
1313
1334
|
if (entry.name === ".git" || entry.name === ".coord" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === ".next" || entry.name === "target") {
|
|
1314
1335
|
continue;
|
|
1315
1336
|
}
|
|
1316
|
-
const fullPath =
|
|
1337
|
+
const fullPath = path5.join(dir, entry.name);
|
|
1317
1338
|
if (entry.isDirectory()) {
|
|
1318
1339
|
this.walkWorkspaceFiles(fullPath, acc);
|
|
1319
1340
|
} else if (entry.isFile()) {
|
|
@@ -1750,7 +1771,7 @@ function delay(ms) {
|
|
|
1750
1771
|
// src/core/db.ts
|
|
1751
1772
|
import { createRequire } from "module";
|
|
1752
1773
|
import fs7 from "fs";
|
|
1753
|
-
import
|
|
1774
|
+
import path6 from "path";
|
|
1754
1775
|
var require2 = createRequire(import.meta.url);
|
|
1755
1776
|
var { DatabaseSync } = require2("node:sqlite");
|
|
1756
1777
|
var CoordDatabase = class _CoordDatabase {
|
|
@@ -1766,19 +1787,19 @@ var CoordDatabase = class _CoordDatabase {
|
|
|
1766
1787
|
this.repoRoot = layout.checkoutRoot;
|
|
1767
1788
|
this.commonGitDir = layout.commonGitDir;
|
|
1768
1789
|
this.isLinkedWorktree = layout.isLinkedWorktree;
|
|
1769
|
-
for (let current = fs7.realpathSync.native(repoRoot); isWithin(this.repoRoot, current); current =
|
|
1770
|
-
const local =
|
|
1771
|
-
if (local !== layout.coordDir && fs7.existsSync(
|
|
1790
|
+
for (let current = fs7.realpathSync.native(repoRoot); isWithin(this.repoRoot, current); current = path6.dirname(current)) {
|
|
1791
|
+
const local = path6.join(current, ".coord");
|
|
1792
|
+
if (local !== layout.coordDir && fs7.existsSync(path6.join(local, "state.db"))) {
|
|
1772
1793
|
throw new Error(`Legacy checkout-local coordination state found at ${local}. Stop clients and preserve that directory before consolidating shared state.`);
|
|
1773
1794
|
}
|
|
1774
1795
|
if (current === this.repoRoot) break;
|
|
1775
1796
|
}
|
|
1776
1797
|
this.coordDir = layout.coordDir;
|
|
1777
1798
|
if (physicalPath(this.coordDir) !== this.coordDir) throw new Error("Coordination directory must not be a symlink");
|
|
1778
|
-
this.dbPath = customDbPath ??
|
|
1799
|
+
this.dbPath = customDbPath ?? path6.join(this.coordDir, "state.db");
|
|
1779
1800
|
if (!customDbPath && physicalPath(this.dbPath) !== this.dbPath) throw new Error("Coordination database must not be a symlink");
|
|
1780
|
-
const recoveryFiles = !customDbPath && (fs7.existsSync(
|
|
1781
|
-
const dir =
|
|
1801
|
+
const recoveryFiles = !customDbPath && (fs7.existsSync(path6.join(this.coordDir, "merge-train.lock")) || fs7.existsSync(path6.join(this.coordDir, "setup.lock")) || ["shadow-index", "quarantine", "snapshots", "worktrees", "setup"].some((name) => {
|
|
1802
|
+
const dir = path6.join(this.coordDir, name);
|
|
1782
1803
|
return fs7.existsSync(dir) && (!fs7.lstatSync(dir).isDirectory() || fs7.readdirSync(dir).length > 0);
|
|
1783
1804
|
}));
|
|
1784
1805
|
const sidecars = ["-wal", "-shm", "-journal"].some((suffix) => fs7.existsSync(this.dbPath + suffix));
|
|
@@ -1798,7 +1819,7 @@ var CoordDatabase = class _CoordDatabase {
|
|
|
1798
1819
|
}
|
|
1799
1820
|
fs7.mkdirSync(this.coordDir, { recursive: true, mode: 448 });
|
|
1800
1821
|
for (const name of ["shadow-index", "quarantine", "snapshots", "worktrees"]) {
|
|
1801
|
-
const target =
|
|
1822
|
+
const target = path6.join(this.coordDir, name);
|
|
1802
1823
|
if (!isWithin(this.coordDir, physicalPath(target)) || physicalPath(target) !== target) {
|
|
1803
1824
|
throw new Error(`Coordination subdirectory must not be a symlink: ${name}`);
|
|
1804
1825
|
}
|
|
@@ -2159,7 +2180,7 @@ var CoordDatabase = class _CoordDatabase {
|
|
|
2159
2180
|
// src/core/dep-graph.ts
|
|
2160
2181
|
import crypto6 from "crypto";
|
|
2161
2182
|
import fs8 from "fs";
|
|
2162
|
-
import
|
|
2183
|
+
import path7 from "path";
|
|
2163
2184
|
var DependencyAndContractEngine = class {
|
|
2164
2185
|
constructor(coordDb, lockEngine, fencing) {
|
|
2165
2186
|
this.coordDb = coordDb;
|
|
@@ -2182,13 +2203,13 @@ var DependencyAndContractEngine = class {
|
|
|
2182
2203
|
return indexedCount;
|
|
2183
2204
|
}
|
|
2184
2205
|
walkSourceFiles(dir, acc = []) {
|
|
2185
|
-
if (!fs8.existsSync(dir) || dir !== this.coordDb.repoRoot && fs8.existsSync(
|
|
2206
|
+
if (!fs8.existsSync(dir) || dir !== this.coordDb.repoRoot && fs8.existsSync(path7.join(dir, ".git"))) return acc;
|
|
2186
2207
|
const entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
2187
2208
|
for (const entry of entries) {
|
|
2188
2209
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target" || entry.name === "coverage") {
|
|
2189
2210
|
continue;
|
|
2190
2211
|
}
|
|
2191
|
-
const fullPath =
|
|
2212
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2192
2213
|
if (entry.isDirectory()) {
|
|
2193
2214
|
this.walkSourceFiles(fullPath, acc);
|
|
2194
2215
|
} else if (entry.isFile() && /\.(ts|tsx|js|jsx|mjs|py)$/.test(entry.name) && !entry.name.endsWith(".d.ts")) {
|
|
@@ -2249,6 +2270,7 @@ var DependencyAndContractEngine = class {
|
|
|
2249
2270
|
}
|
|
2250
2271
|
for (const exp of analysis.exports) {
|
|
2251
2272
|
const prev = existingBySymbol.get(exp.symbolName);
|
|
2273
|
+
if (prev && prev.kind === exp.kind && prev.signature === exp.signature && prev.ast_hash === exp.astHash && prev.params_fingerprint === exp.paramsFingerprint && prev.doc_comment === exp.docComment) continue;
|
|
2252
2274
|
const isBreaking = prev !== void 0 && prev.ast_hash !== exp.astHash;
|
|
2253
2275
|
if (isBreaking && prev) {
|
|
2254
2276
|
changedSymbols.push({
|
|
@@ -2720,7 +2742,7 @@ var IntentAndTaskEngine = class {
|
|
|
2720
2742
|
import { execFileSync as execFileSync4, execSync } from "child_process";
|
|
2721
2743
|
import crypto8 from "crypto";
|
|
2722
2744
|
import fs9 from "fs";
|
|
2723
|
-
import
|
|
2745
|
+
import path8 from "path";
|
|
2724
2746
|
var SpeculativeMergeTrain = class {
|
|
2725
2747
|
constructor(coordDb, fencing) {
|
|
2726
2748
|
this.coordDb = coordDb;
|
|
@@ -2759,8 +2781,8 @@ var SpeculativeMergeTrain = class {
|
|
|
2759
2781
|
now
|
|
2760
2782
|
);
|
|
2761
2783
|
});
|
|
2762
|
-
const stageDir =
|
|
2763
|
-
const lockPath =
|
|
2784
|
+
const stageDir = path8.join(this.coordDb.coordDir, "worktrees", `_merge_train_${id}`);
|
|
2785
|
+
const lockPath = path8.join(this.coordDb.coordDir, "merge-train.lock");
|
|
2764
2786
|
let lockFd;
|
|
2765
2787
|
try {
|
|
2766
2788
|
try {
|
|
@@ -2811,8 +2833,8 @@ var SpeculativeMergeTrain = class {
|
|
|
2811
2833
|
cwd: this.coordDb.repoRoot,
|
|
2812
2834
|
stdio: "ignore"
|
|
2813
2835
|
});
|
|
2814
|
-
const rootNodeModules =
|
|
2815
|
-
const stageNodeModules =
|
|
2836
|
+
const rootNodeModules = path8.join(this.coordDb.repoRoot, "node_modules");
|
|
2837
|
+
const stageNodeModules = path8.join(stageDir, "node_modules");
|
|
2816
2838
|
const hydratedNodeModules = fs9.existsSync(rootNodeModules) && !fs9.existsSync(stageNodeModules);
|
|
2817
2839
|
if (hydratedNodeModules) {
|
|
2818
2840
|
fs9.symlinkSync(rootNodeModules, stageNodeModules, "dir");
|
|
@@ -2977,7 +2999,7 @@ ${combined}`
|
|
|
2977
2999
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
2978
3000
|
import crypto9 from "crypto";
|
|
2979
3001
|
import fs10 from "fs";
|
|
2980
|
-
import
|
|
3002
|
+
import path9 from "path";
|
|
2981
3003
|
var ShadowGitEngine = class {
|
|
2982
3004
|
constructor(coordDb, fencing) {
|
|
2983
3005
|
this.coordDb = coordDb;
|
|
@@ -3010,7 +3032,7 @@ var ShadowGitEngine = class {
|
|
|
3010
3032
|
getShadowIndexPath(agentId) {
|
|
3011
3033
|
const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3012
3034
|
const epoch = this.fencing.assertSession(agentId);
|
|
3013
|
-
return
|
|
3035
|
+
return path9.join(this.coordDb.coordDir, "shadow-index", `${safeId}-${epoch}.index`);
|
|
3014
3036
|
}
|
|
3015
3037
|
/**
|
|
3016
3038
|
* Updates an agent's ephemeral shadow Git index (`.coord/shadow-index/<agent-id>.index`)
|
|
@@ -3047,7 +3069,7 @@ var ShadowGitEngine = class {
|
|
|
3047
3069
|
} else if (params.modifiedFiles && params.modifiedFiles.length > 0) {
|
|
3048
3070
|
for (const mod of params.modifiedFiles) {
|
|
3049
3071
|
const relPath = LockEngine.normalizeFile(targetDir, mod.filePath);
|
|
3050
|
-
const absPath =
|
|
3072
|
+
const absPath = path9.join(targetDir, relPath);
|
|
3051
3073
|
const content = mod.content ?? (fs10.existsSync(absPath) ? fs10.readFileSync(absPath, "utf8") : null);
|
|
3052
3074
|
if (content === null) {
|
|
3053
3075
|
{
|
|
@@ -3248,7 +3270,7 @@ var ShadowGitEngine = class {
|
|
|
3248
3270
|
// src/core/worktree-cow.ts
|
|
3249
3271
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
3250
3272
|
import fs11 from "fs";
|
|
3251
|
-
import
|
|
3273
|
+
import path10 from "path";
|
|
3252
3274
|
var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
3253
3275
|
constructor(coordDb, fencing) {
|
|
3254
3276
|
this.coordDb = coordDb;
|
|
@@ -3268,13 +3290,13 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3268
3290
|
const safeId = params.agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3269
3291
|
const branch = params.branch ?? `kith/${safeId}`;
|
|
3270
3292
|
const baseRef = params.baseRef ?? "HEAD";
|
|
3271
|
-
const worktreePath =
|
|
3293
|
+
const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
|
|
3272
3294
|
if (!this.fencing.getAgent(params.agentId)) throw new Error(`Unknown agent: ${params.agentId}`);
|
|
3273
3295
|
if (!safeId || safeId === "." || safeId === "..") throw new Error("Invalid agent ID");
|
|
3274
3296
|
if (fs11.existsSync(worktreePath)) {
|
|
3275
3297
|
throw new Error(`Worktree already exists: ${worktreePath}. Commit or preserve its work before removing it.`);
|
|
3276
3298
|
}
|
|
3277
|
-
fs11.mkdirSync(
|
|
3299
|
+
fs11.mkdirSync(path10.dirname(worktreePath), { recursive: true });
|
|
3278
3300
|
execFileSync6("git", ["worktree", "add", "-b", branch, worktreePath, baseRef], {
|
|
3279
3301
|
cwd: this.coordDb.repoRoot,
|
|
3280
3302
|
stdio: "ignore"
|
|
@@ -3282,16 +3304,16 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3282
3304
|
const hydratedDirs = [];
|
|
3283
3305
|
let hydrationMethod = "none";
|
|
3284
3306
|
for (const dirName of _CoWWorktreeManager.HYDRATABLE_DIRS) {
|
|
3285
|
-
const srcDir =
|
|
3286
|
-
const destDir =
|
|
3307
|
+
const srcDir = path10.join(this.coordDb.repoRoot, dirName);
|
|
3308
|
+
const destDir = path10.join(worktreePath, dirName);
|
|
3287
3309
|
if (!fs11.existsSync(srcDir) || fs11.existsSync(destDir)) continue;
|
|
3288
3310
|
const method = this.cloneDirectoryCoW(srcDir, destDir);
|
|
3289
3311
|
hydrationMethod = method;
|
|
3290
3312
|
hydratedDirs.push(dirName);
|
|
3291
3313
|
}
|
|
3292
3314
|
for (const envName of _CoWWorktreeManager.ENV_FILES) {
|
|
3293
|
-
const srcEnv =
|
|
3294
|
-
const destEnv =
|
|
3315
|
+
const srcEnv = path10.join(this.coordDb.repoRoot, envName);
|
|
3316
|
+
const destEnv = path10.join(worktreePath, envName);
|
|
3295
3317
|
if (fs11.existsSync(srcEnv) && !fs11.existsSync(destEnv)) {
|
|
3296
3318
|
fs11.copyFileSync(srcEnv, destEnv);
|
|
3297
3319
|
}
|
|
@@ -3342,7 +3364,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3342
3364
|
removeWorktree(agentId) {
|
|
3343
3365
|
this.fencing.assertSession(agentId);
|
|
3344
3366
|
const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3345
|
-
const worktreePath =
|
|
3367
|
+
const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
|
|
3346
3368
|
if (!fs11.existsSync(worktreePath)) return false;
|
|
3347
3369
|
const registered = this.fencing.getAgent(agentId)?.worktree_path;
|
|
3348
3370
|
if (!registered || physicalPath(registered) !== physicalPath(worktreePath) || this.fencing.listAgents(true).some((agent) => agent.id !== agentId && agent.worktree_path && physicalPath(agent.worktree_path) === physicalPath(worktreePath))) {
|
|
@@ -3433,7 +3455,7 @@ var KithEngine = class {
|
|
|
3433
3455
|
const file = LockEngine.normalizeFile(targetRoot, params.filePath);
|
|
3434
3456
|
const access = this.locks.checkFileWriteAccess({ agentId: params.agentId, filePath: file });
|
|
3435
3457
|
if (!access.allowed) throw new Error(access.reason);
|
|
3436
|
-
const content = params.content ?? (fs12.existsSync(
|
|
3458
|
+
const content = params.content ?? (fs12.existsSync(path11.join(targetRoot, file)) ? fs12.readFileSync(path11.join(targetRoot, file), "utf8") : "");
|
|
3437
3459
|
const contractReport = this.depGraph.recordFileModification(
|
|
3438
3460
|
params.agentId,
|
|
3439
3461
|
file,
|
|
@@ -3573,7 +3595,7 @@ ${lines.join("\n")}` : "";
|
|
|
3573
3595
|
// src/core/observation.ts
|
|
3574
3596
|
import fs13 from "fs";
|
|
3575
3597
|
import os from "os";
|
|
3576
|
-
import
|
|
3598
|
+
import path12 from "path";
|
|
3577
3599
|
import { createRequire as createRequire2 } from "module";
|
|
3578
3600
|
var { DatabaseSync: DatabaseSync2 } = createRequire2(import.meta.url)("node:sqlite");
|
|
3579
3601
|
function readCoordinationState(directory = process.cwd()) {
|
|
@@ -3601,16 +3623,16 @@ function readCoordinationState(directory = process.cwd()) {
|
|
|
3601
3623
|
};
|
|
3602
3624
|
let scratch, db;
|
|
3603
3625
|
try {
|
|
3604
|
-
const file =
|
|
3626
|
+
const file = path12.join(layout.coordDir, "state.db");
|
|
3605
3627
|
if (physicalPath(layout.coordDir) !== layout.coordDir || physicalPath(file) !== file) throw new Error("Coordination state must not be a symlink");
|
|
3606
|
-
for (let current = fs13.realpathSync.native(directory); isWithin(layout.checkoutRoot, current); current =
|
|
3607
|
-
const local =
|
|
3608
|
-
if (local !== layout.coordDir && fs13.existsSync(
|
|
3628
|
+
for (let current = fs13.realpathSync.native(directory); isWithin(layout.checkoutRoot, current); current = path12.dirname(current)) {
|
|
3629
|
+
const local = path12.join(current, ".coord");
|
|
3630
|
+
if (local !== layout.coordDir && fs13.existsSync(path12.join(local, "state.db"))) throw new Error(`Legacy independent state at ${local}; preserve it before consolidation`);
|
|
3609
3631
|
if (current === layout.checkoutRoot) break;
|
|
3610
3632
|
}
|
|
3611
3633
|
if (!fs13.existsSync(file)) {
|
|
3612
|
-
if (["setup.lock", "merge-train.lock"].some((name) => fs13.existsSync(
|
|
3613
|
-
const p =
|
|
3634
|
+
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) => {
|
|
3635
|
+
const p = path12.join(layout.coordDir, name);
|
|
3614
3636
|
return fs13.existsSync(p) && (!fs13.lstatSync(p).isDirectory() || fs13.readdirSync(p).length > 0);
|
|
3615
3637
|
})) throw new Error("Missing database with retained sidecars; preserve the recovery set");
|
|
3616
3638
|
return empty;
|
|
@@ -3623,14 +3645,14 @@ function readCoordinationState(directory = process.cwd()) {
|
|
|
3623
3645
|
return [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs].join(":");
|
|
3624
3646
|
};
|
|
3625
3647
|
const before = files.map(identity);
|
|
3626
|
-
scratch = fs13.mkdtempSync(
|
|
3648
|
+
scratch = fs13.mkdtempSync(path12.join(os.tmpdir(), "kith-observe-"));
|
|
3627
3649
|
for (let i = 0; i < files.length; i++) if (before[i]) {
|
|
3628
|
-
const target =
|
|
3650
|
+
const target = path12.join(scratch, path12.basename(files[i]));
|
|
3629
3651
|
fs13.copyFileSync(files[i], target);
|
|
3630
3652
|
fs13.chmodSync(target, 384);
|
|
3631
3653
|
}
|
|
3632
3654
|
if (JSON.stringify(before) !== JSON.stringify(files.map(identity))) throw new Error("Coordination state changed during snapshot; stop writers or retry");
|
|
3633
|
-
db = new DatabaseSync2(
|
|
3655
|
+
db = new DatabaseSync2(path12.join(scratch, "state.db"), { readOnly: true });
|
|
3634
3656
|
db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000; BEGIN");
|
|
3635
3657
|
if (CoordDatabase.inspectSchema(db) !== 2) throw new Error("Offline observation requires schema 2; preserve state and upgrade with clients stopped");
|
|
3636
3658
|
const all = (sql) => db.prepare(sql).all();
|
|
@@ -3662,19 +3684,19 @@ function readCoordinationState(directory = process.cwd()) {
|
|
|
3662
3684
|
}
|
|
3663
3685
|
}
|
|
3664
3686
|
function recoveryReport(directory = process.cwd()) {
|
|
3665
|
-
const state = readCoordinationState(directory), retainedRoot =
|
|
3687
|
+
const state = readCoordinationState(directory), retainedRoot = path12.join(state.repository.coordDir, "worktrees");
|
|
3666
3688
|
const retained = physicalPath(retainedRoot) === retainedRoot && fs13.existsSync(retainedRoot) && fs13.lstatSync(retainedRoot).isDirectory() && !fs13.lstatSync(retainedRoot).isSymbolicLink() ? fs13.readdirSync(retainedRoot, { withFileTypes: true }).map((entry) => ({
|
|
3667
|
-
path:
|
|
3689
|
+
path: path12.join(retainedRoot, entry.name),
|
|
3668
3690
|
symlink: entry.isSymbolicLink(),
|
|
3669
|
-
agentIds: state.agents.filter((a) => a.worktree_path ===
|
|
3691
|
+
agentIds: state.agents.filter((a) => a.worktree_path === path12.join(retainedRoot, entry.name)).map((a) => a.id)
|
|
3670
3692
|
})) : [];
|
|
3671
3693
|
return {
|
|
3672
3694
|
...state,
|
|
3673
3695
|
retainedWorktrees: retained,
|
|
3674
3696
|
expiredClaims: state.locks.filter((lock) => Number(lock.expires_at) <= state.observedAt),
|
|
3675
3697
|
unfinishedIntegrations: state.mergeQueue.filter((entry) => entry.status !== "merged"),
|
|
3676
|
-
setupLock: { path:
|
|
3677
|
-
integrationLock: { path:
|
|
3698
|
+
setupLock: { path: path12.join(state.repository.coordDir, "setup.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "setup.lock")) },
|
|
3699
|
+
integrationLock: { path: path12.join(state.repository.coordDir, "merge-train.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "merge-train.lock")) }
|
|
3678
3700
|
};
|
|
3679
3701
|
}
|
|
3680
3702
|
export {
|