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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 — 2026-10-20
4
+
5
+ - Validate the real Cursor CLI model/editor/MCP workflow and missing-guard refusal, with redacted reusable proof.
6
+ - Identify native Windows session owners through process creation time; accept native and Git-style concrete paths while preserving link containment.
7
+ - Generate Windows MCP launchers with the current Node executable and built CLI, avoiding npm `.cmd` spawning. Refuse unsupported native shell-hook setup before writes.
8
+ - Exercise native Windows worktrees, conflict refusal, ports, verification/retry, crash recovery and conservative setup/uninstall on hosted runners against clean installed packages (registry installs on release tags). Supervised mutex commands remain refused on native Windows.
9
+ - Flush setup backup files on Windows without unsupported directory fsync; directory-entry durability across power loss is not promised there.
10
+
3
11
  ## 0.1.0 — 2026-10-20
4
12
 
5
13
  - Use the npm identity `kith-coord` with CLI `kith`. Protocol version 1.0.0 and storage schema 2 are independent. Earlier source manifests used 1.0.0 without a registry release.
@@ -9,7 +17,7 @@
9
17
  - Save private per-checkout setup backups before configuration writes. Uninstall restores only unchanged tracked writes, retaining edited configuration, state, worktrees, recovery copies and ignore protection.
10
18
  - Export the 13 canonical MCP input schemas from actual stdio discovery and check snapshot drift in CI; document protocol ownership and integration semantics. No database tables or runtime dependencies added.
11
19
 
12
- ## Unreleased
20
+ ## Pre-release development history
13
21
 
14
22
  ### Coordination positioning
15
23
 
package/README.md CHANGED
@@ -35,12 +35,12 @@ The [reliability contract](docs/reliability.md) defines ownership, verification,
35
35
 
36
36
  ## Quick start
37
37
 
38
- Requires **Git 2.38+** and **Node.js 22.13+ on 22.x or Node.js 24+**. Development targets macOS and Linux. Windows is unvalidated.
38
+ Requires **Git 2.38+** and **Node.js 22.13+ on 22.x or Node.js 24+**. Full workflows target macOS and Linux. Version 0.1.1 also validates native Windows MCP claims, worktrees, ports, integration and recovery; supervised mutex commands and shell hooks require macOS/Linux or WSL. See the [support evidence](docs/client-validation.md#native-windows).
39
39
 
40
40
  Install the release, then configure your project:
41
41
 
42
42
  ```sh
43
- npm install --global kith-coord@0.1.0
43
+ npm install --global kith-coord@0.1.1
44
44
  cd /path/to/your/project
45
45
  kith init
46
46
  ```
@@ -96,7 +96,7 @@ Default setup installs MCP configuration for Claude, Codex and Cursor plus agent
96
96
  | --- | --- |
97
97
  | Claude Code | MCP; optional edit hooks need shared session setup; recorded native CLI runs |
98
98
  | Codex | Project MCP configuration; recorded native CLI runs with configuration overrides |
99
- | Cursor | MCP and optional native hooks; generated-command fixtures tested; native application behavior unvalidated |
99
+ | Cursor | Native CLI workflow and fail-closed edit hooks validated; GUI discovery/trust unvalidated |
100
100
  | Other MCP clients | stdio server via `kith mcp`; client-specific validation required |
101
101
 
102
102
  Native checks are version-specific. GUI discovery and persisted project trust need separate checks. See [integration setup](docs/integrations.md), [recorded validation](docs/client-validation.md), and [session ownership](docs/sessions-and-mutexes.md) before mixing hooks, CLI commands, and MCP clients.
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var KITH_VERSION = "0.1.0";
4
+ var KITH_VERSION = "0.1.1";
5
5
  var KITH_PROTOCOL_VERSION = "1.0.0";
6
6
 
7
7
  // src/cli/setup-ledger.ts
@@ -14,6 +14,7 @@ import { execFileSync } from "child_process";
14
14
  import fs from "fs";
15
15
  import path from "path";
16
16
  function physicalPath(input) {
17
+ if (process.platform === "win32") input = input.replaceAll("/", path.sep);
17
18
  const absolute = path.isAbsolute(input) ? input : process.cwd() + path.sep + input;
18
19
  let current = path.parse(absolute).root;
19
20
  for (const component of absolute.slice(current.length).split(path.sep)) {
@@ -44,7 +45,7 @@ function isWithin(root, target) {
44
45
  return relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative);
45
46
  }
46
47
  function repositoryFile(root, file) {
47
- if (!file || /[\\\x00-\x1f\x7f]/u.test(file)) throw new Error(`Invalid file path: ${file}`);
48
+ if (!file || /[\x00-\x1f\x7f]/u.test(file) || process.platform !== "win32" && file.includes("\\")) throw new Error(`Invalid file path: ${file}`);
48
49
  const canonicalRoot = physicalPath(root);
49
50
  const target = physicalPath(path.isAbsolute(file) ? file : canonicalRoot + path.sep + file);
50
51
  if (!isWithin(canonicalRoot, target) || target === canonicalRoot) {
@@ -140,7 +141,7 @@ function persist(root, coordDir, ledger) {
140
141
  fs2.closeSync(fd);
141
142
  }
142
143
  fs2.renameSync(temp, file);
143
- for (const parent of [path2.dirname(file), coordDir]) {
144
+ for (const parent of process.platform === "win32" ? [] : [path2.dirname(file), coordDir]) {
144
145
  const dir = fs2.openSync(parent, "r");
145
146
  try {
146
147
  fs2.fsyncSync(dir);
@@ -174,7 +175,7 @@ function applySetupPlan(root, coordDir, plan, warnings) {
174
175
  before: previous ? previous.before : current,
175
176
  after: Buffer.from(planned.content).toString("base64"),
176
177
  beforeMode: previous ? previous.beforeMode : mode,
177
- afterMode: mode ?? (planned.mode ?? 438) & ~process.umask()
178
+ afterMode: mode ?? (process.platform === "win32" ? 438 : (planned.mode ?? 438) & ~process.umask())
178
179
  };
179
180
  }
180
181
  persist(root, coordDir, ledger);
@@ -892,7 +893,7 @@ function doctorReport(directory = process.cwd()) {
892
893
  const check = (name, ok, detail) => checks.push({ name, ok, detail });
893
894
  const [major, minor] = process.versions.node.split(".").map(Number);
894
895
  check("node", major === 22 && minor >= 13 || major >= 24, process.version);
895
- check("platform", process.platform === "darwin" || process.platform === "linux", process.platform === "win32" ? "Windows ownership and process-group supervision are not validated" : process.platform);
896
+ check("platform", process.platform === "darwin" || process.platform === "linux", process.platform === "win32" ? "Native Windows is limited to MCP coordination; supervised mutex commands and shell hooks require macOS/Linux or WSL" : process.platform);
896
897
  try {
897
898
  const version = execFileSync3("git", ["--version"], { encoding: "utf8", timeout: 2e3 }).trim();
898
899
  const parts = /\b(\d+)\.(\d+)/.exec(version);
@@ -960,6 +961,7 @@ function doctorReport(directory = process.cwd()) {
960
961
  import { execFileSync as execFileSync4 } from "child_process";
961
962
  import crypto2 from "crypto";
962
963
  import fs6 from "fs";
964
+ import path6 from "path";
963
965
  var SessionOwnershipError = class extends Error {
964
966
  constructor(agentId) {
965
967
  super(`No valid active session capability for '${agentId}'. Start a session, or resume it with its session token.`);
@@ -985,6 +987,7 @@ var FencingAndAgentManager = class _FencingAndAgentManager {
985
987
  coordDb;
986
988
  static DEFAULT_LEASE_MS = 5 * 60 * 1e3;
987
989
  capabilities = /* @__PURE__ */ new Map();
990
+ static ownWindowsStart = null;
988
991
  static validateLease(duration) {
989
992
  if (!Number.isSafeInteger(duration) || duration < 100 || duration > 864e5) {
990
993
  throw new Error("Lease duration must be an integer between 100 and 86400000 milliseconds");
@@ -999,6 +1002,24 @@ var FencingAndAgentManager = class _FencingAndAgentManager {
999
1002
  const stat = fs6.readFileSync(`/proc/${pid}/stat`, "utf8");
1000
1003
  return `linux:${stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]}`;
1001
1004
  }
1005
+ if (process.platform === "win32") {
1006
+ if (pid === process.pid && this.ownWindowsStart) return this.ownWindowsStart;
1007
+ const powershell = path6.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
1008
+ const ticks = execFileSync4(
1009
+ powershell,
1010
+ [
1011
+ "-NoProfile",
1012
+ "-NonInteractive",
1013
+ "-Command",
1014
+ `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture)`
1015
+ ],
1016
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3, windowsHide: true }
1017
+ ).trim();
1018
+ if (!/^\d+$/.test(ticks)) return null;
1019
+ const identity = `windows:${ticks}`;
1020
+ if (pid === process.pid) this.ownWindowsStart = identity;
1021
+ return identity;
1022
+ }
1002
1023
  const out = execFileSync4("ps", ["-o", "lstart=", "-p", String(pid)], {
1003
1024
  encoding: "utf8",
1004
1025
  stdio: ["ignore", "pipe", "ignore"],
@@ -1167,12 +1188,12 @@ var FencingAndAgentManager = class _FencingAndAgentManager {
1167
1188
 
1168
1189
  // src/core/engine.ts
1169
1190
  import fs15 from "fs";
1170
- import path13 from "path";
1191
+ import path14 from "path";
1171
1192
 
1172
1193
  // src/core/ast-analyzer.ts
1173
1194
  import crypto3 from "crypto";
1174
1195
  import fs7 from "fs";
1175
- import path6 from "path";
1196
+ import path7 from "path";
1176
1197
  var AstAnalyzer = class _AstAnalyzer {
1177
1198
  /**
1178
1199
  * Analyzes a source file's content to extract exported contracts (functions, types,
@@ -1180,7 +1201,7 @@ var AstAnalyzer = class _AstAnalyzer {
1180
1201
  */
1181
1202
  static analyzeSource(repoRoot, filePath, sourceContent) {
1182
1203
  const relPath = repositoryFile(repoRoot, filePath);
1183
- const absPath = path6.join(repoRoot, relPath);
1204
+ const absPath = path7.join(repoRoot, relPath);
1184
1205
  const content = sourceContent ?? (fs7.existsSync(absPath) ? fs7.readFileSync(absPath, "utf8") : "");
1185
1206
  const fileHash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
1186
1207
  const exports = _AstAnalyzer.extractExports(content);
@@ -1373,17 +1394,17 @@ var AstAnalyzer = class _AstAnalyzer {
1373
1394
  if (!specifier.startsWith(".")) {
1374
1395
  return null;
1375
1396
  }
1376
- const sourceDir = path6.dirname(path6.join(repoRoot, sourceRelPath));
1377
- const baseTarget = path6.resolve(sourceDir, specifier.replace(/\.(js|jsx|mjs|cjs)$/, ""));
1397
+ const sourceDir = path7.dirname(path7.join(repoRoot, sourceRelPath));
1398
+ const baseTarget = path7.resolve(sourceDir, specifier.replace(/\.(js|jsx|mjs|cjs)$/, ""));
1378
1399
  const candidates = [
1379
- path6.resolve(sourceDir, specifier),
1400
+ path7.resolve(sourceDir, specifier),
1380
1401
  `${baseTarget}.ts`,
1381
1402
  `${baseTarget}.tsx`,
1382
1403
  `${baseTarget}.js`,
1383
1404
  `${baseTarget}.mjs`,
1384
- path6.join(baseTarget, "index.ts"),
1385
- path6.join(baseTarget, "index.tsx"),
1386
- path6.join(baseTarget, "index.js")
1405
+ path7.join(baseTarget, "index.ts"),
1406
+ path7.join(baseTarget, "index.tsx"),
1407
+ path7.join(baseTarget, "index.js")
1387
1408
  ];
1388
1409
  for (const candidate of candidates) {
1389
1410
  if (fs7.existsSync(candidate) && fs7.statSync(candidate).isFile()) {
@@ -1448,12 +1469,12 @@ var AstAnalyzer = class _AstAnalyzer {
1448
1469
  // src/core/bash-guard.ts
1449
1470
  import crypto5 from "crypto";
1450
1471
  import fs9 from "fs";
1451
- import path8 from "path";
1472
+ import path9 from "path";
1452
1473
 
1453
1474
  // src/core/locks.ts
1454
1475
  import crypto4 from "crypto";
1455
1476
  import fs8 from "fs";
1456
- import path7 from "path";
1477
+ import path8 from "path";
1457
1478
 
1458
1479
  // src/core/glob.ts
1459
1480
  function scopeSegments(pattern, literal = false) {
@@ -1543,9 +1564,9 @@ var LockEngine = class _LockEngine {
1543
1564
  const parts = rawPattern.split("/");
1544
1565
  const magic = parts.findIndex((p) => /[?*]/u.test(p));
1545
1566
  const prefix = magic < 0 ? rawPattern : parts.slice(0, magic).join("/") || ".";
1546
- const absolute = physicalPath(path7.isAbsolute(prefix) ? prefix : root + "/" + prefix);
1567
+ const absolute = physicalPath(path8.isAbsolute(prefix) ? prefix : root + "/" + prefix);
1547
1568
  if (!isWithin(root, absolute)) throw new Error(`Scope is outside repository: ${rawPattern}`);
1548
- const relative = path7.relative(root, absolute).split(path7.sep).join("/");
1569
+ const relative = path8.relative(root, absolute).split(path8.sep).join("/");
1549
1570
  const suffix = magic < 0 ? "" : parts.slice(magic).join("/");
1550
1571
  const normalized = [relative, suffix].filter(Boolean).join("/");
1551
1572
  scopeSegments(normalized);
@@ -1580,7 +1601,7 @@ var LockEngine = class _LockEngine {
1580
1601
  let budget = 1e4;
1581
1602
  while (pending.length) {
1582
1603
  const { root, relative } = pending.pop();
1583
- const dir = path7.join(root, relative);
1604
+ const dir = path8.join(root, relative);
1584
1605
  let entries;
1585
1606
  try {
1586
1607
  entries = fs8.readdirSync(dir, { withFileTypes: true });
@@ -1839,7 +1860,7 @@ var BashMerkleGuard = class {
1839
1860
  getSnapshotFilePath(agentId) {
1840
1861
  const epoch = this.fencing.assertSession(agentId);
1841
1862
  const safeId = crypto5.createHash("sha256").update(`${agentId}:${epoch}`).digest("hex");
1842
- return path8.join(this.coordDb.coordDir, "snapshots", `${safeId}.json`);
1863
+ return path9.join(this.coordDb.coordDir, "snapshots", `${safeId}.json`);
1843
1864
  }
1844
1865
  /**
1845
1866
  * Captures a Merkle snapshot of workspace files before a Bash tool execution.
@@ -1851,7 +1872,7 @@ var BashMerkleGuard = class {
1851
1872
  const entries = {};
1852
1873
  const files = this.walkWorkspaceFiles(rootDir);
1853
1874
  for (const absPath of files) {
1854
- const relPath = path8.relative(rootDir, absPath).replace(/\\/g, "/");
1875
+ const relPath = path9.relative(rootDir, absPath).replace(/\\/g, "/");
1855
1876
  const stat = fs9.statSync(absPath);
1856
1877
  if (stat.size > 5 * 1024 * 1024) continue;
1857
1878
  const buf = fs9.readFileSync(absPath);
@@ -1894,7 +1915,7 @@ var BashMerkleGuard = class {
1894
1915
  const authorizedModifiedFiles = [];
1895
1916
  const quarantinedViolations = [];
1896
1917
  for (const absPath of currentFiles) {
1897
- const relPath = path8.relative(rootDir, absPath).replace(/\\/g, "/");
1918
+ const relPath = path9.relative(rootDir, absPath).replace(/\\/g, "/");
1898
1919
  seenCurrentRelPaths.add(relPath);
1899
1920
  const stat = fs9.statSync(absPath);
1900
1921
  if (stat.size > 5 * 1024 * 1024) continue;
@@ -1956,7 +1977,7 @@ var BashMerkleGuard = class {
1956
1977
  }
1957
1978
  if (authorizedModifiedFiles.length > 0) {
1958
1979
  for (const relFile of authorizedModifiedFiles) {
1959
- const absFile = path8.join(rootDir, relFile);
1980
+ const absFile = path9.join(rootDir, relFile);
1960
1981
  if (fs9.existsSync(absFile) && /\.(ts|tsx|js|jsx|mjs|py)$/.test(relFile)) {
1961
1982
  this.depGraph.recordFileModification(params.agentId, relFile, fs9.readFileSync(absFile, "utf8"));
1962
1983
  }
@@ -1977,7 +1998,7 @@ var BashMerkleGuard = class {
1977
1998
  const id = `quar_${crypto5.randomBytes(6).toString("hex")}`;
1978
1999
  const now = Date.now();
1979
2000
  const safeName = params.relPath.replace(/[^a-zA-Z0-9._-]/g, "_");
1980
- const patchPath = path8.join(
2001
+ const patchPath = path9.join(
1981
2002
  this.coordDb.coordDir,
1982
2003
  "quarantine",
1983
2004
  `${id}_${safeName}.quarantine`
@@ -1991,13 +2012,13 @@ var BashMerkleGuard = class {
1991
2012
  if (params.prevEntry?.backupContentBase64 !== void 0) {
1992
2013
  fs9.writeFileSync(patchPath + ".before", Buffer.from(params.prevEntry.backupContentBase64, "base64"), { mode: 384 });
1993
2014
  }
1994
- const absTarget = path8.join(params.rootDir, params.relPath);
2015
+ const absTarget = path9.join(params.rootDir, params.relPath);
1995
2016
  if (params.restoreLockedFiles && params.prevEntry === null) {
1996
2017
  if (fs9.existsSync(absTarget)) {
1997
2018
  fs9.unlinkSync(absTarget);
1998
2019
  }
1999
2020
  } else if (params.restoreLockedFiles && params.prevEntry?.backupContentBase64 !== void 0) {
2000
- fs9.mkdirSync(path8.dirname(absTarget), { recursive: true });
2021
+ fs9.mkdirSync(path9.dirname(absTarget), { recursive: true });
2001
2022
  fs9.writeFileSync(
2002
2023
  absTarget,
2003
2024
  Buffer.from(params.prevEntry.backupContentBase64, "base64")
@@ -2041,7 +2062,7 @@ var BashMerkleGuard = class {
2041
2062
  if (entry.name === ".git" || entry.name === ".coord" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === ".next" || entry.name === "target") {
2042
2063
  continue;
2043
2064
  }
2044
- const fullPath = path8.join(dir, entry.name);
2065
+ const fullPath = path9.join(dir, entry.name);
2045
2066
  if (entry.isDirectory()) {
2046
2067
  this.walkWorkspaceFiles(fullPath, acc);
2047
2068
  } else if (entry.isFile()) {
@@ -2478,7 +2499,7 @@ function delay(ms) {
2478
2499
  // src/core/dep-graph.ts
2479
2500
  import crypto7 from "crypto";
2480
2501
  import fs11 from "fs";
2481
- import path9 from "path";
2502
+ import path10 from "path";
2482
2503
  var DependencyAndContractEngine = class {
2483
2504
  constructor(coordDb, lockEngine, fencing) {
2484
2505
  this.coordDb = coordDb;
@@ -2501,13 +2522,13 @@ var DependencyAndContractEngine = class {
2501
2522
  return indexedCount;
2502
2523
  }
2503
2524
  walkSourceFiles(dir, acc = []) {
2504
- if (!fs11.existsSync(dir) || dir !== this.coordDb.repoRoot && fs11.existsSync(path9.join(dir, ".git"))) return acc;
2525
+ if (!fs11.existsSync(dir) || dir !== this.coordDb.repoRoot && fs11.existsSync(path10.join(dir, ".git"))) return acc;
2505
2526
  const entries = fs11.readdirSync(dir, { withFileTypes: true });
2506
2527
  for (const entry of entries) {
2507
2528
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target" || entry.name === "coverage") {
2508
2529
  continue;
2509
2530
  }
2510
- const fullPath = path9.join(dir, entry.name);
2531
+ const fullPath = path10.join(dir, entry.name);
2511
2532
  if (entry.isDirectory()) {
2512
2533
  this.walkSourceFiles(fullPath, acc);
2513
2534
  } else if (entry.isFile() && /\.(ts|tsx|js|jsx|mjs|py)$/.test(entry.name) && !entry.name.endsWith(".d.ts")) {
@@ -3039,7 +3060,7 @@ var IntentAndTaskEngine = class {
3039
3060
  import { execFileSync as execFileSync5, execSync } from "child_process";
3040
3061
  import crypto9 from "crypto";
3041
3062
  import fs12 from "fs";
3042
- import path10 from "path";
3063
+ import path11 from "path";
3043
3064
  var SpeculativeMergeTrain = class {
3044
3065
  constructor(coordDb, fencing) {
3045
3066
  this.coordDb = coordDb;
@@ -3078,8 +3099,8 @@ var SpeculativeMergeTrain = class {
3078
3099
  now
3079
3100
  );
3080
3101
  });
3081
- const stageDir = path10.join(this.coordDb.coordDir, "worktrees", `_merge_train_${id}`);
3082
- const lockPath = path10.join(this.coordDb.coordDir, "merge-train.lock");
3102
+ const stageDir = path11.join(this.coordDb.coordDir, "worktrees", `_merge_train_${id}`);
3103
+ const lockPath = path11.join(this.coordDb.coordDir, "merge-train.lock");
3083
3104
  let lockFd;
3084
3105
  try {
3085
3106
  try {
@@ -3130,8 +3151,8 @@ var SpeculativeMergeTrain = class {
3130
3151
  cwd: this.coordDb.repoRoot,
3131
3152
  stdio: "ignore"
3132
3153
  });
3133
- const rootNodeModules = path10.join(this.coordDb.repoRoot, "node_modules");
3134
- const stageNodeModules = path10.join(stageDir, "node_modules");
3154
+ const rootNodeModules = path11.join(this.coordDb.repoRoot, "node_modules");
3155
+ const stageNodeModules = path11.join(stageDir, "node_modules");
3135
3156
  const hydratedNodeModules = fs12.existsSync(rootNodeModules) && !fs12.existsSync(stageNodeModules);
3136
3157
  if (hydratedNodeModules) {
3137
3158
  fs12.symlinkSync(rootNodeModules, stageNodeModules, "dir");
@@ -3296,7 +3317,7 @@ ${combined}`
3296
3317
  import { execFileSync as execFileSync6 } from "child_process";
3297
3318
  import crypto10 from "crypto";
3298
3319
  import fs13 from "fs";
3299
- import path11 from "path";
3320
+ import path12 from "path";
3300
3321
  var ShadowGitEngine = class {
3301
3322
  constructor(coordDb, fencing) {
3302
3323
  this.coordDb = coordDb;
@@ -3329,7 +3350,7 @@ var ShadowGitEngine = class {
3329
3350
  getShadowIndexPath(agentId) {
3330
3351
  const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "_");
3331
3352
  const epoch = this.fencing.assertSession(agentId);
3332
- return path11.join(this.coordDb.coordDir, "shadow-index", `${safeId}-${epoch}.index`);
3353
+ return path12.join(this.coordDb.coordDir, "shadow-index", `${safeId}-${epoch}.index`);
3333
3354
  }
3334
3355
  /**
3335
3356
  * Updates an agent's ephemeral shadow Git index (`.coord/shadow-index/<agent-id>.index`)
@@ -3366,7 +3387,7 @@ var ShadowGitEngine = class {
3366
3387
  } else if (params.modifiedFiles && params.modifiedFiles.length > 0) {
3367
3388
  for (const mod of params.modifiedFiles) {
3368
3389
  const relPath = LockEngine.normalizeFile(targetDir, mod.filePath);
3369
- const absPath = path11.join(targetDir, relPath);
3390
+ const absPath = path12.join(targetDir, relPath);
3370
3391
  const content = mod.content ?? (fs13.existsSync(absPath) ? fs13.readFileSync(absPath, "utf8") : null);
3371
3392
  if (content === null) {
3372
3393
  {
@@ -3567,7 +3588,7 @@ var ShadowGitEngine = class {
3567
3588
  // src/core/worktree-cow.ts
3568
3589
  import { execFileSync as execFileSync7 } from "child_process";
3569
3590
  import fs14 from "fs";
3570
- import path12 from "path";
3591
+ import path13 from "path";
3571
3592
  var CoWWorktreeManager = class _CoWWorktreeManager {
3572
3593
  constructor(coordDb, fencing) {
3573
3594
  this.coordDb = coordDb;
@@ -3587,13 +3608,13 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3587
3608
  const safeId = params.agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
3588
3609
  const branch = params.branch ?? `kith/${safeId}`;
3589
3610
  const baseRef = params.baseRef ?? "HEAD";
3590
- const worktreePath = path12.join(this.coordDb.coordDir, "worktrees", safeId);
3611
+ const worktreePath = path13.join(this.coordDb.coordDir, "worktrees", safeId);
3591
3612
  if (!this.fencing.getAgent(params.agentId)) throw new Error(`Unknown agent: ${params.agentId}`);
3592
3613
  if (!safeId || safeId === "." || safeId === "..") throw new Error("Invalid agent ID");
3593
3614
  if (fs14.existsSync(worktreePath)) {
3594
3615
  throw new Error(`Worktree already exists: ${worktreePath}. Commit or preserve its work before removing it.`);
3595
3616
  }
3596
- fs14.mkdirSync(path12.dirname(worktreePath), { recursive: true });
3617
+ fs14.mkdirSync(path13.dirname(worktreePath), { recursive: true });
3597
3618
  execFileSync7("git", ["worktree", "add", "-b", branch, worktreePath, baseRef], {
3598
3619
  cwd: this.coordDb.repoRoot,
3599
3620
  stdio: "ignore"
@@ -3601,16 +3622,16 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3601
3622
  const hydratedDirs = [];
3602
3623
  let hydrationMethod = "none";
3603
3624
  for (const dirName of _CoWWorktreeManager.HYDRATABLE_DIRS) {
3604
- const srcDir = path12.join(this.coordDb.repoRoot, dirName);
3605
- const destDir = path12.join(worktreePath, dirName);
3625
+ const srcDir = path13.join(this.coordDb.repoRoot, dirName);
3626
+ const destDir = path13.join(worktreePath, dirName);
3606
3627
  if (!fs14.existsSync(srcDir) || fs14.existsSync(destDir)) continue;
3607
3628
  const method = this.cloneDirectoryCoW(srcDir, destDir);
3608
3629
  hydrationMethod = method;
3609
3630
  hydratedDirs.push(dirName);
3610
3631
  }
3611
3632
  for (const envName of _CoWWorktreeManager.ENV_FILES) {
3612
- const srcEnv = path12.join(this.coordDb.repoRoot, envName);
3613
- const destEnv = path12.join(worktreePath, envName);
3633
+ const srcEnv = path13.join(this.coordDb.repoRoot, envName);
3634
+ const destEnv = path13.join(worktreePath, envName);
3614
3635
  if (fs14.existsSync(srcEnv) && !fs14.existsSync(destEnv)) {
3615
3636
  fs14.copyFileSync(srcEnv, destEnv);
3616
3637
  }
@@ -3661,7 +3682,7 @@ var CoWWorktreeManager = class _CoWWorktreeManager {
3661
3682
  removeWorktree(agentId) {
3662
3683
  this.fencing.assertSession(agentId);
3663
3684
  const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
3664
- const worktreePath = path12.join(this.coordDb.coordDir, "worktrees", safeId);
3685
+ const worktreePath = path13.join(this.coordDb.coordDir, "worktrees", safeId);
3665
3686
  if (!fs14.existsSync(worktreePath)) return false;
3666
3687
  const registered = this.fencing.getAgent(agentId)?.worktree_path;
3667
3688
  if (!registered || physicalPath(registered) !== physicalPath(worktreePath) || this.fencing.listAgents(true).some((agent) => agent.id !== agentId && agent.worktree_path && physicalPath(agent.worktree_path) === physicalPath(worktreePath))) {
@@ -3752,7 +3773,7 @@ var KithEngine = class {
3752
3773
  const file = LockEngine.normalizeFile(targetRoot, params.filePath);
3753
3774
  const access = this.locks.checkFileWriteAccess({ agentId: params.agentId, filePath: file });
3754
3775
  if (!access.allowed) throw new Error(access.reason);
3755
- const content = params.content ?? (fs15.existsSync(path13.join(targetRoot, file)) ? fs15.readFileSync(path13.join(targetRoot, file), "utf8") : "");
3776
+ const content = params.content ?? (fs15.existsSync(path14.join(targetRoot, file)) ? fs15.readFileSync(path14.join(targetRoot, file), "utf8") : "");
3756
3777
  const contractReport = this.depGraph.recordFileModification(
3757
3778
  params.agentId,
3758
3779
  file,
@@ -3894,7 +3915,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3894
3915
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3895
3916
  import { z } from "zod";
3896
3917
  import fs16 from "fs";
3897
- import path14 from "path";
3918
+ import path15 from "path";
3898
3919
  function createKithMcpServer(repoRoot = process.cwd()) {
3899
3920
  const engine = new KithEngine(repoRoot);
3900
3921
  const server = new McpServer({
@@ -4112,9 +4133,9 @@ function createKithMcpServer(repoRoot = process.cwd()) {
4112
4133
  async (args, extra) => {
4113
4134
  authorize(args);
4114
4135
  const root = fs16.realpathSync(engine.fencing.getAgent(args.agentId)?.worktree_path ?? engine.coordDb.repoRoot);
4115
- const cwd = fs16.realpathSync(path14.resolve(root, args.cwd ?? "."));
4116
- const relative = path14.relative(root, cwd);
4117
- if (relative === ".." || relative.startsWith(".." + path14.sep) || path14.isAbsolute(relative)) {
4136
+ const cwd = fs16.realpathSync(path15.resolve(root, args.cwd ?? "."));
4137
+ const relative = path15.relative(root, cwd);
4138
+ if (relative === ".." || relative.startsWith(".." + path15.sep) || path15.isAbsolute(relative)) {
4118
4139
  throw new Error("Command cwd must be inside the registered worktree or repository");
4119
4140
  }
4120
4141
  const execution = engine.broker.executeWithMutex({
@@ -4384,7 +4405,8 @@ var HookHandler = class {
4384
4405
  // src/cli/init.ts
4385
4406
  import { execFileSync as execFileSync9 } from "child_process";
4386
4407
  import fs18 from "fs";
4387
- import path15 from "path";
4408
+ import path16 from "path";
4409
+ import { fileURLToPath as fileURLToPath2 } from "url";
4388
4410
  import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
4389
4411
  var INTEGRATIONS = ["claude", "codex", "cursor", "agents", "git", "shims"];
4390
4412
  function object(value, label) {
@@ -4416,11 +4438,14 @@ function initializeKithRepository(repoRoot = process.cwd(), cliBinaryCommand = "
4416
4438
  for (const integration of selected) {
4417
4439
  if (!INTEGRATIONS.includes(integration)) throw new Error(`Unknown integration: ${integration}`);
4418
4440
  }
4441
+ if (process.platform === "win32" && !mcpOnly && ["claude", "cursor", "git", "shims"].some((name) => selected.has(name))) {
4442
+ throw new Error("Native Windows setup supports MCP-only configuration; use --mcp-only or WSL for shell hooks and shims.");
4443
+ }
4419
4444
  if (!fs18.statSync(absRoot).isDirectory()) throw new Error(`Not a directory: ${absRoot}`);
4420
4445
  const plan = /* @__PURE__ */ new Map();
4421
4446
  const warnings = [];
4422
4447
  const add = (name, content, mode2) => {
4423
- const target = path15.isAbsolute(name) ? name : path15.join(absRoot, name);
4448
+ const target = path16.isAbsolute(name) ? name : path16.join(absRoot, name);
4424
4449
  if (!isWithin(absRoot, physicalPath(target))) throw new Error(`Configuration path leaves repository: ${name}`);
4425
4450
  if (!fs18.existsSync(target) || fs18.readFileSync(target, "utf8") !== content) {
4426
4451
  plan.set(name, { content, mode: mode2 });
@@ -4428,14 +4453,16 @@ function initializeKithRepository(repoRoot = process.cwd(), cliBinaryCommand = "
4428
4453
  };
4429
4454
  const json = (name, value) => add(name, JSON.stringify(value, null, 2) + "\n");
4430
4455
  const hookCommand = `${cliBinaryCommand} --repo ${shellQuote(absRoot)} hook`;
4431
- const server = { command: "kith", args: ["mcp", "--repo", absRoot] };
4432
- const ignorePath = path15.join(absRoot, ".gitignore");
4456
+ const windowsCli = process.platform === "win32" ? ["./cli/index.js", "./index.js", "../../dist/cli/index.js"].map((relative) => fileURLToPath2(new URL(relative, import.meta.url))).find((file) => fs18.existsSync(file)) : void 0;
4457
+ if (process.platform === "win32" && !windowsCli) throw new Error("Build Kith before generating native Windows MCP configuration");
4458
+ const server = windowsCli ? { command: process.execPath, args: [windowsCli, "mcp", "--repo", absRoot] } : { command: "kith", args: ["mcp", "--repo", absRoot] };
4459
+ const ignorePath = path16.join(absRoot, ".gitignore");
4433
4460
  const ignore = fs18.existsSync(ignorePath) ? fs18.readFileSync(ignorePath, "utf8") : "";
4434
4461
  if (!ignore.split(/\r?\n/).some((line) => [".coord/", "/.coord/"].includes(line.trim()))) {
4435
4462
  add(".gitignore", ignore + (ignore && !ignore.endsWith("\n") ? "\n" : "") + "\n# Kith local state and worktrees\n.coord/\n");
4436
4463
  }
4437
4464
  if (selected.has("claude")) {
4438
- const settings = readConfig(path15.join(absRoot, ".claude/settings.json"));
4465
+ const settings = readConfig(path16.join(absRoot, ".claude/settings.json"));
4439
4466
  const hooks = object(settings.hooks ?? {}, "Claude hooks");
4440
4467
  if (mcpOnly && ["PreToolUse", "PostToolUse"].some((event) => Array.isArray(hooks[event]) && hooks[event].length)) {
4441
4468
  warnings.push("Existing Claude tool hooks preserved. Review whether they require inherited KITH credentials; --mcp-only does not disable guards.");
@@ -4462,12 +4489,12 @@ function initializeKithRepository(repoRoot = process.cwd(), cliBinaryCommand = "
4462
4489
  settings.hooks = hooks;
4463
4490
  json(".claude/settings.json", settings);
4464
4491
  }
4465
- const mcp = readConfig(path15.join(absRoot, ".mcp.json"));
4492
+ const mcp = readConfig(path16.join(absRoot, ".mcp.json"));
4466
4493
  mcp.mcpServers = { ...object(mcp.mcpServers ?? {}, "Claude MCP servers"), kith: server };
4467
4494
  json(".mcp.json", mcp);
4468
4495
  }
4469
4496
  if (selected.has("codex")) {
4470
- const file = path15.join(absRoot, ".codex/config.toml");
4497
+ const file = path16.join(absRoot, ".codex/config.toml");
4471
4498
  const original = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "";
4472
4499
  let config;
4473
4500
  try {
@@ -4492,10 +4519,10 @@ function initializeKithRepository(repoRoot = process.cwd(), cliBinaryCommand = "
4492
4519
  }
4493
4520
  }
4494
4521
  if (selected.has("cursor")) {
4495
- const mcp = readConfig(path15.join(absRoot, ".cursor/mcp.json"));
4522
+ const mcp = readConfig(path16.join(absRoot, ".cursor/mcp.json"));
4496
4523
  mcp.mcpServers = { ...object(mcp.mcpServers ?? {}, "Cursor MCP servers"), kith: server };
4497
4524
  json(".cursor/mcp.json", mcp);
4498
- const existingCursorHooks = readConfig(path15.join(absRoot, ".cursor/hooks.json"));
4525
+ const existingCursorHooks = readConfig(path16.join(absRoot, ".cursor/hooks.json"));
4499
4526
  if (mcpOnly && ["preToolUse", "postToolUse"].some((event) => Array.isArray(existingCursorHooks.hooks?.[event]) && existingCursorHooks.hooks[event].length)) warnings.push("Existing Cursor hooks preserved; review their inherited session requirements.");
4500
4527
  if (!mcpOnly) {
4501
4528
  const config = existingCursorHooks;
@@ -4514,7 +4541,7 @@ function initializeKithRepository(repoRoot = process.cwd(), cliBinaryCommand = "
4514
4541
  }
4515
4542
  }
4516
4543
  if (selected.has("agents")) {
4517
- const file = path15.join(absRoot, "AGENTS.md");
4544
+ const file = path16.join(absRoot, "AGENTS.md");
4518
4545
  let content = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "# Repository Agent Instructions\n";
4519
4546
  const block = `<!-- KITH:START -->
4520
4547
  ## Parallel Agent Coordination (kith)
@@ -4547,10 +4574,10 @@ If Claude/Cursor edit hooks or CLI commands are also enabled, launch the client
4547
4574
  encoding: "utf8",
4548
4575
  stdio: ["ignore", "pipe", "ignore"]
4549
4576
  }).trim();
4550
- const hook = path15.join(hooksDir, "pre-commit");
4577
+ const hook = path16.join(hooksDir, "pre-commit");
4551
4578
  const physicalHooks = fs18.existsSync(hooksDir) ? fs18.realpathSync(hooksDir) : hooksDir;
4552
- const relativeHooks = path15.relative(fs18.realpathSync(absRoot), physicalHooks);
4553
- if (relativeHooks === ".." || relativeHooks.startsWith(".." + path15.sep) || path15.isAbsolute(relativeHooks)) {
4579
+ const relativeHooks = path16.relative(fs18.realpathSync(absRoot), physicalHooks);
4580
+ if (relativeHooks === ".." || relativeHooks.startsWith(".." + path16.sep) || path16.isAbsolute(relativeHooks)) {
4554
4581
  warnings.push("Git hook skipped: hooks directory is outside this repository. Integrate the guard manually.");
4555
4582
  } else {
4556
4583
  const script = (arg) => `#!/usr/bin/env sh