kith-coord 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/README.md +3 -3
- package/dist/cli/index.js +91 -64
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +80 -59
- package/dist/index.js.map +1 -1
- package/docs/client-validation.md +15 -4
- package/docs/integrations.md +9 -4
- package/docs/path-and-scope-rules.md +1 -1
- package/docs/reliability.md +3 -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 +1 -1
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_VERSION = "0.1.1";
|
|
981
982
|
declare const KITH_PROTOCOL_VERSION = "1.0.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,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var KITH_VERSION = "0.1.
|
|
2
|
+
var KITH_VERSION = "0.1.1";
|
|
3
3
|
var KITH_PROTOCOL_VERSION = "1.0.0";
|
|
4
4
|
|
|
5
5
|
// src/core/workspace-observation.ts
|
|
@@ -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")) {
|
|
@@ -2720,7 +2741,7 @@ var IntentAndTaskEngine = class {
|
|
|
2720
2741
|
import { execFileSync as execFileSync4, execSync } from "child_process";
|
|
2721
2742
|
import crypto8 from "crypto";
|
|
2722
2743
|
import fs9 from "fs";
|
|
2723
|
-
import
|
|
2744
|
+
import path8 from "path";
|
|
2724
2745
|
var SpeculativeMergeTrain = class {
|
|
2725
2746
|
constructor(coordDb, fencing) {
|
|
2726
2747
|
this.coordDb = coordDb;
|
|
@@ -2759,8 +2780,8 @@ var SpeculativeMergeTrain = class {
|
|
|
2759
2780
|
now
|
|
2760
2781
|
);
|
|
2761
2782
|
});
|
|
2762
|
-
const stageDir =
|
|
2763
|
-
const lockPath =
|
|
2783
|
+
const stageDir = path8.join(this.coordDb.coordDir, "worktrees", `_merge_train_${id}`);
|
|
2784
|
+
const lockPath = path8.join(this.coordDb.coordDir, "merge-train.lock");
|
|
2764
2785
|
let lockFd;
|
|
2765
2786
|
try {
|
|
2766
2787
|
try {
|
|
@@ -2811,8 +2832,8 @@ var SpeculativeMergeTrain = class {
|
|
|
2811
2832
|
cwd: this.coordDb.repoRoot,
|
|
2812
2833
|
stdio: "ignore"
|
|
2813
2834
|
});
|
|
2814
|
-
const rootNodeModules =
|
|
2815
|
-
const stageNodeModules =
|
|
2835
|
+
const rootNodeModules = path8.join(this.coordDb.repoRoot, "node_modules");
|
|
2836
|
+
const stageNodeModules = path8.join(stageDir, "node_modules");
|
|
2816
2837
|
const hydratedNodeModules = fs9.existsSync(rootNodeModules) && !fs9.existsSync(stageNodeModules);
|
|
2817
2838
|
if (hydratedNodeModules) {
|
|
2818
2839
|
fs9.symlinkSync(rootNodeModules, stageNodeModules, "dir");
|
|
@@ -2977,7 +2998,7 @@ ${combined}`
|
|
|
2977
2998
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
2978
2999
|
import crypto9 from "crypto";
|
|
2979
3000
|
import fs10 from "fs";
|
|
2980
|
-
import
|
|
3001
|
+
import path9 from "path";
|
|
2981
3002
|
var ShadowGitEngine = class {
|
|
2982
3003
|
constructor(coordDb, fencing) {
|
|
2983
3004
|
this.coordDb = coordDb;
|
|
@@ -3010,7 +3031,7 @@ var ShadowGitEngine = class {
|
|
|
3010
3031
|
getShadowIndexPath(agentId) {
|
|
3011
3032
|
const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3012
3033
|
const epoch = this.fencing.assertSession(agentId);
|
|
3013
|
-
return
|
|
3034
|
+
return path9.join(this.coordDb.coordDir, "shadow-index", `${safeId}-${epoch}.index`);
|
|
3014
3035
|
}
|
|
3015
3036
|
/**
|
|
3016
3037
|
* Updates an agent's ephemeral shadow Git index (`.coord/shadow-index/<agent-id>.index`)
|
|
@@ -3047,7 +3068,7 @@ var ShadowGitEngine = class {
|
|
|
3047
3068
|
} else if (params.modifiedFiles && params.modifiedFiles.length > 0) {
|
|
3048
3069
|
for (const mod of params.modifiedFiles) {
|
|
3049
3070
|
const relPath = LockEngine.normalizeFile(targetDir, mod.filePath);
|
|
3050
|
-
const absPath =
|
|
3071
|
+
const absPath = path9.join(targetDir, relPath);
|
|
3051
3072
|
const content = mod.content ?? (fs10.existsSync(absPath) ? fs10.readFileSync(absPath, "utf8") : null);
|
|
3052
3073
|
if (content === null) {
|
|
3053
3074
|
{
|
|
@@ -3248,7 +3269,7 @@ var ShadowGitEngine = class {
|
|
|
3248
3269
|
// src/core/worktree-cow.ts
|
|
3249
3270
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
3250
3271
|
import fs11 from "fs";
|
|
3251
|
-
import
|
|
3272
|
+
import path10 from "path";
|
|
3252
3273
|
var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
3253
3274
|
constructor(coordDb, fencing) {
|
|
3254
3275
|
this.coordDb = coordDb;
|
|
@@ -3268,13 +3289,13 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3268
3289
|
const safeId = params.agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3269
3290
|
const branch = params.branch ?? `kith/${safeId}`;
|
|
3270
3291
|
const baseRef = params.baseRef ?? "HEAD";
|
|
3271
|
-
const worktreePath =
|
|
3292
|
+
const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
|
|
3272
3293
|
if (!this.fencing.getAgent(params.agentId)) throw new Error(`Unknown agent: ${params.agentId}`);
|
|
3273
3294
|
if (!safeId || safeId === "." || safeId === "..") throw new Error("Invalid agent ID");
|
|
3274
3295
|
if (fs11.existsSync(worktreePath)) {
|
|
3275
3296
|
throw new Error(`Worktree already exists: ${worktreePath}. Commit or preserve its work before removing it.`);
|
|
3276
3297
|
}
|
|
3277
|
-
fs11.mkdirSync(
|
|
3298
|
+
fs11.mkdirSync(path10.dirname(worktreePath), { recursive: true });
|
|
3278
3299
|
execFileSync6("git", ["worktree", "add", "-b", branch, worktreePath, baseRef], {
|
|
3279
3300
|
cwd: this.coordDb.repoRoot,
|
|
3280
3301
|
stdio: "ignore"
|
|
@@ -3282,16 +3303,16 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3282
3303
|
const hydratedDirs = [];
|
|
3283
3304
|
let hydrationMethod = "none";
|
|
3284
3305
|
for (const dirName of _CoWWorktreeManager.HYDRATABLE_DIRS) {
|
|
3285
|
-
const srcDir =
|
|
3286
|
-
const destDir =
|
|
3306
|
+
const srcDir = path10.join(this.coordDb.repoRoot, dirName);
|
|
3307
|
+
const destDir = path10.join(worktreePath, dirName);
|
|
3287
3308
|
if (!fs11.existsSync(srcDir) || fs11.existsSync(destDir)) continue;
|
|
3288
3309
|
const method = this.cloneDirectoryCoW(srcDir, destDir);
|
|
3289
3310
|
hydrationMethod = method;
|
|
3290
3311
|
hydratedDirs.push(dirName);
|
|
3291
3312
|
}
|
|
3292
3313
|
for (const envName of _CoWWorktreeManager.ENV_FILES) {
|
|
3293
|
-
const srcEnv =
|
|
3294
|
-
const destEnv =
|
|
3314
|
+
const srcEnv = path10.join(this.coordDb.repoRoot, envName);
|
|
3315
|
+
const destEnv = path10.join(worktreePath, envName);
|
|
3295
3316
|
if (fs11.existsSync(srcEnv) && !fs11.existsSync(destEnv)) {
|
|
3296
3317
|
fs11.copyFileSync(srcEnv, destEnv);
|
|
3297
3318
|
}
|
|
@@ -3342,7 +3363,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
|
|
|
3342
3363
|
removeWorktree(agentId) {
|
|
3343
3364
|
this.fencing.assertSession(agentId);
|
|
3344
3365
|
const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3345
|
-
const worktreePath =
|
|
3366
|
+
const worktreePath = path10.join(this.coordDb.coordDir, "worktrees", safeId);
|
|
3346
3367
|
if (!fs11.existsSync(worktreePath)) return false;
|
|
3347
3368
|
const registered = this.fencing.getAgent(agentId)?.worktree_path;
|
|
3348
3369
|
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 +3454,7 @@ var KithEngine = class {
|
|
|
3433
3454
|
const file = LockEngine.normalizeFile(targetRoot, params.filePath);
|
|
3434
3455
|
const access = this.locks.checkFileWriteAccess({ agentId: params.agentId, filePath: file });
|
|
3435
3456
|
if (!access.allowed) throw new Error(access.reason);
|
|
3436
|
-
const content = params.content ?? (fs12.existsSync(
|
|
3457
|
+
const content = params.content ?? (fs12.existsSync(path11.join(targetRoot, file)) ? fs12.readFileSync(path11.join(targetRoot, file), "utf8") : "");
|
|
3437
3458
|
const contractReport = this.depGraph.recordFileModification(
|
|
3438
3459
|
params.agentId,
|
|
3439
3460
|
file,
|
|
@@ -3573,7 +3594,7 @@ ${lines.join("\n")}` : "";
|
|
|
3573
3594
|
// src/core/observation.ts
|
|
3574
3595
|
import fs13 from "fs";
|
|
3575
3596
|
import os from "os";
|
|
3576
|
-
import
|
|
3597
|
+
import path12 from "path";
|
|
3577
3598
|
import { createRequire as createRequire2 } from "module";
|
|
3578
3599
|
var { DatabaseSync: DatabaseSync2 } = createRequire2(import.meta.url)("node:sqlite");
|
|
3579
3600
|
function readCoordinationState(directory = process.cwd()) {
|
|
@@ -3601,16 +3622,16 @@ function readCoordinationState(directory = process.cwd()) {
|
|
|
3601
3622
|
};
|
|
3602
3623
|
let scratch, db;
|
|
3603
3624
|
try {
|
|
3604
|
-
const file =
|
|
3625
|
+
const file = path12.join(layout.coordDir, "state.db");
|
|
3605
3626
|
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(
|
|
3627
|
+
for (let current = fs13.realpathSync.native(directory); isWithin(layout.checkoutRoot, current); current = path12.dirname(current)) {
|
|
3628
|
+
const local = path12.join(current, ".coord");
|
|
3629
|
+
if (local !== layout.coordDir && fs13.existsSync(path12.join(local, "state.db"))) throw new Error(`Legacy independent state at ${local}; preserve it before consolidation`);
|
|
3609
3630
|
if (current === layout.checkoutRoot) break;
|
|
3610
3631
|
}
|
|
3611
3632
|
if (!fs13.existsSync(file)) {
|
|
3612
|
-
if (["setup.lock", "merge-train.lock"].some((name) => fs13.existsSync(
|
|
3613
|
-
const p =
|
|
3633
|
+
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) => {
|
|
3634
|
+
const p = path12.join(layout.coordDir, name);
|
|
3614
3635
|
return fs13.existsSync(p) && (!fs13.lstatSync(p).isDirectory() || fs13.readdirSync(p).length > 0);
|
|
3615
3636
|
})) throw new Error("Missing database with retained sidecars; preserve the recovery set");
|
|
3616
3637
|
return empty;
|
|
@@ -3623,14 +3644,14 @@ function readCoordinationState(directory = process.cwd()) {
|
|
|
3623
3644
|
return [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs].join(":");
|
|
3624
3645
|
};
|
|
3625
3646
|
const before = files.map(identity);
|
|
3626
|
-
scratch = fs13.mkdtempSync(
|
|
3647
|
+
scratch = fs13.mkdtempSync(path12.join(os.tmpdir(), "kith-observe-"));
|
|
3627
3648
|
for (let i = 0; i < files.length; i++) if (before[i]) {
|
|
3628
|
-
const target =
|
|
3649
|
+
const target = path12.join(scratch, path12.basename(files[i]));
|
|
3629
3650
|
fs13.copyFileSync(files[i], target);
|
|
3630
3651
|
fs13.chmodSync(target, 384);
|
|
3631
3652
|
}
|
|
3632
3653
|
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(
|
|
3654
|
+
db = new DatabaseSync2(path12.join(scratch, "state.db"), { readOnly: true });
|
|
3634
3655
|
db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000; BEGIN");
|
|
3635
3656
|
if (CoordDatabase.inspectSchema(db) !== 2) throw new Error("Offline observation requires schema 2; preserve state and upgrade with clients stopped");
|
|
3636
3657
|
const all = (sql) => db.prepare(sql).all();
|
|
@@ -3662,19 +3683,19 @@ function readCoordinationState(directory = process.cwd()) {
|
|
|
3662
3683
|
}
|
|
3663
3684
|
}
|
|
3664
3685
|
function recoveryReport(directory = process.cwd()) {
|
|
3665
|
-
const state = readCoordinationState(directory), retainedRoot =
|
|
3686
|
+
const state = readCoordinationState(directory), retainedRoot = path12.join(state.repository.coordDir, "worktrees");
|
|
3666
3687
|
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:
|
|
3688
|
+
path: path12.join(retainedRoot, entry.name),
|
|
3668
3689
|
symlink: entry.isSymbolicLink(),
|
|
3669
|
-
agentIds: state.agents.filter((a) => a.worktree_path ===
|
|
3690
|
+
agentIds: state.agents.filter((a) => a.worktree_path === path12.join(retainedRoot, entry.name)).map((a) => a.id)
|
|
3670
3691
|
})) : [];
|
|
3671
3692
|
return {
|
|
3672
3693
|
...state,
|
|
3673
3694
|
retainedWorktrees: retained,
|
|
3674
3695
|
expiredClaims: state.locks.filter((lock) => Number(lock.expires_at) <= state.observedAt),
|
|
3675
3696
|
unfinishedIntegrations: state.mergeQueue.filter((entry) => entry.status !== "merged"),
|
|
3676
|
-
setupLock: { path:
|
|
3677
|
-
integrationLock: { path:
|
|
3697
|
+
setupLock: { path: path12.join(state.repository.coordDir, "setup.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "setup.lock")) },
|
|
3698
|
+
integrationLock: { path: path12.join(state.repository.coordDir, "merge-train.lock"), present: fs13.existsSync(path12.join(state.repository.coordDir, "merge-train.lock")) }
|
|
3678
3699
|
};
|
|
3679
3700
|
}
|
|
3680
3701
|
export {
|