@kontextmind/kxm 0.7.86 → 0.7.88

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.
Files changed (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/configuration.md +2 -0
  3. package/package.json +1 -1
  4. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  5. package/plugins/kxm/dist/cli.js +897 -500
  6. package/plugins/kxm/dist/core.js +2 -2
  7. package/plugins/kxm/dist/extension.js +72 -63
  8. package/plugins/kxm/dist/mcp-server.js +3 -3
  9. package/plugins/kxm/dist/runtime.js +44 -26
  10. package/plugins/kxm/dist/server.js +56 -31
  11. package/plugins/kxm/package.json +1 -1
  12. package/plugins/kxm/src/cli/context-skills.ts +51 -6
  13. package/plugins/kxm/src/cli/hub.ts +39 -14
  14. package/plugins/kxm/src/cli/project.ts +78 -23
  15. package/plugins/kxm/src/cli/roles.ts +48 -4
  16. package/plugins/kxm/src/cli/system.ts +18 -1
  17. package/plugins/kxm/src/cli/tasks.ts +34 -5
  18. package/plugins/kxm/src/cli/types.ts +30 -0
  19. package/plugins/kxm/src/cli/workflows.ts +20 -4
  20. package/plugins/kxm/src/cli.ts +57 -0
  21. package/plugins/kxm/src/commands.ts +2 -2
  22. package/plugins/kxm/src/config.ts +4 -3
  23. package/plugins/kxm/src/context/providers.ts +4 -0
  24. package/plugins/kxm/src/database.ts +72 -33
  25. package/plugins/kxm/src/hub.ts +26 -8
  26. package/plugins/kxm/src/local-snapshot.ts +4 -4
  27. package/plugins/kxm/src/mcp-server.ts +1 -1
  28. package/plugins/kxm/src/memory.ts +10 -7
  29. package/plugins/kxm/src/role.ts +18 -6
  30. package/plugins/kxm/src/session-work.ts +4 -1
  31. package/plugins/kxm/src/skills.ts +29 -15
  32. package/plugins/kxm/src/sqlite.ts +18 -0
  33. package/plugins/kxm/src/state.ts +4 -1
  34. package/plugins/kxm/src/task-manager.ts +20 -13
  35. package/plugins/kxm/src/workflow-manager.ts +12 -4
@@ -2987,7 +2987,7 @@ var require_compile = __commonJS({
2987
2987
  const schOrFunc = root.refs[ref];
2988
2988
  if (schOrFunc)
2989
2989
  return schOrFunc;
2990
- let _sch = resolve30.call(this, root, ref);
2990
+ let _sch = resolve32.call(this, root, ref);
2991
2991
  if (_sch === void 0) {
2992
2992
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2993
2993
  const { schemaId } = this.opts;
@@ -3014,7 +3014,7 @@ var require_compile = __commonJS({
3014
3014
  function sameSchemaEnv(s1, s2) {
3015
3015
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3016
3016
  }
3017
- function resolve30(root, ref) {
3017
+ function resolve32(root, ref) {
3018
3018
  let sch;
3019
3019
  while (typeof (sch = this.refs[ref]) == "string")
3020
3020
  ref = sch;
@@ -3839,7 +3839,7 @@ var require_fast_uri = __commonJS({
3839
3839
  }
3840
3840
  return uri;
3841
3841
  }
3842
- function resolve30(baseURI, relativeURI, options) {
3842
+ function resolve32(baseURI, relativeURI, options) {
3843
3843
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3844
3844
  const {
3845
3845
  parsed: baseParsed,
@@ -4201,7 +4201,7 @@ var require_fast_uri = __commonJS({
4201
4201
  var fastUri = {
4202
4202
  SCHEMES,
4203
4203
  normalize,
4204
- resolve: resolve30,
4204
+ resolve: resolve32,
4205
4205
  resolveComponent,
4206
4206
  equal,
4207
4207
  serialize,
@@ -14779,7 +14779,7 @@ var require_dist = __commonJS({
14779
14779
  });
14780
14780
 
14781
14781
  // plugins/kxm/src/cli.ts
14782
- import { resolve as resolve29 } from "node:path";
14782
+ import { resolve as resolve31 } from "node:path";
14783
14783
  import { fileURLToPath as fileURLToPath4 } from "node:url";
14784
14784
 
14785
14785
  // node_modules/commander/lib/error.js
@@ -19501,7 +19501,7 @@ var HubClient = class {
19501
19501
  if (signal?.aborted) throw new MeshWaitError("aborted", messageId);
19502
19502
  const message = await this.getMessage(messageId);
19503
19503
  if (["replied", "cancelled", "expired", "error"].includes(message.status)) return message;
19504
- await new Promise((resolve30, reject) => {
19504
+ await new Promise((resolve32, reject) => {
19505
19505
  const onAbort = () => {
19506
19506
  signal?.removeEventListener("abort", onAbort);
19507
19507
  clearTimeout(timer);
@@ -19509,7 +19509,7 @@ var HubClient = class {
19509
19509
  };
19510
19510
  const timer = setTimeout(() => {
19511
19511
  signal?.removeEventListener("abort", onAbort);
19512
- resolve30();
19512
+ resolve32();
19513
19513
  }, Math.min(500, Math.max(1, deadline - Date.now())));
19514
19514
  signal?.addEventListener("abort", onAbort, { once: true });
19515
19515
  if (signal?.aborted) onAbort();
@@ -19564,7 +19564,7 @@ var HubClient = class {
19564
19564
  } catch (error) {
19565
19565
  if (this.stopped || error instanceof Error && error.name === "AbortError") return;
19566
19566
  }
19567
- if (!this.stopped) await new Promise((resolve30) => setTimeout(resolve30, this.options.reconnectMs));
19567
+ if (!this.stopped) await new Promise((resolve32) => setTimeout(resolve32, this.options.reconnectMs));
19568
19568
  }
19569
19569
  }
19570
19570
  headers(includeIdentity = true) {
@@ -20436,8 +20436,8 @@ var AGENT_COMMANDS = [
20436
20436
  summary: { type: "string", description: "Promotion summary" },
20437
20437
  authority: {
20438
20438
  type: "string",
20439
- enum: ["policy", "instruction", "evidence", "hypothesis"],
20440
- description: "Authority class"
20439
+ enum: ["evidence", "hypothesis"],
20440
+ description: "Authority class; an agent's proposal is peer origin, so evidence at most"
20441
20441
  },
20442
20442
  confidence: {
20443
20443
  type: "string",
@@ -21036,7 +21036,7 @@ function defaultSpawn(command, args, options) {
21036
21036
  if (options.signal?.aborted) {
21037
21037
  return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
21038
21038
  }
21039
- return new Promise((resolve30) => {
21039
+ return new Promise((resolve32) => {
21040
21040
  const stdout = [];
21041
21041
  const stderr = [];
21042
21042
  let outputBytes = 0;
@@ -21067,7 +21067,7 @@ function defaultSpawn(command, args, options) {
21067
21067
  const started = Boolean(child?.pid);
21068
21068
  if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
21069
21069
  const unverifiedDescendants = stopping || started && !observedChildExit;
21070
- resolve30({
21070
+ resolve32({
21071
21071
  stdout: Buffer.concat(stdout).toString("utf8"),
21072
21072
  stderr: Buffer.concat(stderr).toString("utf8"),
21073
21073
  code,
@@ -23280,13 +23280,13 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
23280
23280
  // plugins/kxm/src/runtime-supervisor.ts
23281
23281
  import { spawn as spawn2 } from "node:child_process";
23282
23282
  import { createHash as createHash12, createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
23283
- import { chmodSync as chmodSync4, existsSync as existsSync13, lstatSync as lstatSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
23283
+ import { chmodSync as chmodSync4, existsSync as existsSync14, lstatSync as lstatSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
23284
23284
  import { dirname as dirname9, isAbsolute as isAbsolute6, join as join13 } from "node:path";
23285
23285
 
23286
23286
  // plugins/kxm/src/runtime-store.ts
23287
23287
  import { createHash as createHash10, randomUUID as randomUUID4 } from "node:crypto";
23288
- import { existsSync as existsSync9, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync3, writeFileSync as writeFileSync6 } from "node:fs";
23289
- import { dirname as dirname7, join as join9, resolve as resolve6 } from "node:path";
23288
+ import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync3, writeFileSync as writeFileSync6 } from "node:fs";
23289
+ import { dirname as dirname7, join as join9, resolve as resolve7 } from "node:path";
23290
23290
 
23291
23291
  // plugins/kxm/src/bindings.ts
23292
23292
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -23294,7 +23294,7 @@ import { createHash as createHash7, randomUUID as randomUUID3 } from "node:crypt
23294
23294
  import {
23295
23295
  chmodSync as chmodSync2,
23296
23296
  closeSync,
23297
- existsSync as existsSync7,
23297
+ existsSync as existsSync8,
23298
23298
  fsyncSync,
23299
23299
  lstatSync as lstatSync2,
23300
23300
  mkdirSync as mkdirSync4,
@@ -23308,7 +23308,10 @@ import {
23308
23308
  import { homedir as homedir3 } from "node:os";
23309
23309
 
23310
23310
  // plugins/kxm/src/sqlite.ts
23311
+ import { existsSync as existsSync7 } from "node:fs";
23311
23312
  import { createRequire } from "node:module";
23313
+ import { resolve as resolve4 } from "node:path";
23314
+ import { pathToFileURL } from "node:url";
23312
23315
  var requireFromHere = createRequire(import.meta.url);
23313
23316
  function loadNative() {
23314
23317
  try {
@@ -23345,9 +23348,15 @@ var DatabaseSync = class {
23345
23348
  this.inner.close();
23346
23349
  }
23347
23350
  };
23351
+ function openReadOnlyDatabase(path4) {
23352
+ if (existsSync7(`${path4}-wal`)) return new DatabaseSync(path4, { readOnly: true });
23353
+ const uri = pathToFileURL(resolve4(path4));
23354
+ uri.searchParams.set("immutable", "1");
23355
+ return new DatabaseSync(uri.href, { readOnly: true });
23356
+ }
23348
23357
 
23349
23358
  // plugins/kxm/src/bindings.ts
23350
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, parse, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
23359
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, parse, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
23351
23360
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
23352
23361
  var BINDING_LABEL = "Runtime-local repository bindings";
23353
23362
  var activeLocks = /* @__PURE__ */ new WeakSet();
@@ -23358,7 +23367,7 @@ function bindingError(phase, code, message) {
23358
23367
  throw new KxmConfigError([bindingIssue(phase, code, message)]);
23359
23368
  }
23360
23369
  function canonicalHostPath2(path4) {
23361
- const absolute = resolve4(path4);
23370
+ const absolute = resolve5(path4);
23362
23371
  try {
23363
23372
  return realpathSync2.native(absolute);
23364
23373
  } catch {
@@ -23377,17 +23386,17 @@ function kxmUserStateRoot(options = {}) {
23377
23386
  const explicit = options.stateRoot ?? env.KXM_STATE_HOME?.trim();
23378
23387
  if (explicit) {
23379
23388
  if (!isAbsolute3(explicit)) bindingError("path", "local_state_root_not_absolute", "KXM_STATE_HOME must be an absolute host-local path");
23380
- return resolve4(explicit);
23389
+ return resolve5(explicit);
23381
23390
  }
23382
23391
  if (platform === "win32") {
23383
23392
  const localAppData = env.LOCALAPPDATA?.trim();
23384
23393
  const base2 = localAppData && isAbsolute3(localAppData) ? localAppData : join7(home, "AppData", "Local");
23385
- return resolve4(base2, "KXM");
23394
+ return resolve5(base2, "KXM");
23386
23395
  }
23387
- if (platform === "darwin") return resolve4(home, "Library", "Application Support", "KXM");
23396
+ if (platform === "darwin") return resolve5(home, "Library", "Application Support", "KXM");
23388
23397
  const xdgState = env.XDG_STATE_HOME?.trim();
23389
23398
  const base = xdgState && isAbsolute3(xdgState) ? xdgState : join7(home, ".local", "state");
23390
- return resolve4(base, "kxm");
23399
+ return resolve5(base, "kxm");
23391
23400
  }
23392
23401
  function projectBindingKey(projectRoot, platform = process.platform) {
23393
23402
  const canonical2 = canonicalHostPath2(projectRoot);
@@ -23399,7 +23408,7 @@ function kxmLocalBindingFile(projectRoot, options = {}) {
23399
23408
  return join7(stateRoot, "projects", projectBindingKey(projectRoot, options.platform), "repository-bindings.json");
23400
23409
  }
23401
23410
  function checkedDirectory(path4, description) {
23402
- if (!existsSync7(path4)) return false;
23411
+ if (!existsSync8(path4)) return false;
23403
23412
  const stat = lstatSync2(path4);
23404
23413
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
23405
23414
  bindingError("path", "local_binding_directory_invalid", `${description} must be a regular directory, not a link or file`);
@@ -23431,10 +23440,10 @@ function asRecord2(value, file, projectRoot, schemasDir) {
23431
23440
  };
23432
23441
  }
23433
23442
  function readKxmLocalBindings(projectRoot, options = {}) {
23434
- const root = resolve4(projectRoot);
23443
+ const root = resolve5(projectRoot);
23435
23444
  const stateRoot = kxmUserStateRoot(options);
23436
23445
  const file = kxmLocalBindingFile(root, options);
23437
- if (!existingBindingDirectory(file, stateRoot) || !existsSync7(file)) return void 0;
23446
+ if (!existingBindingDirectory(file, stateRoot) || !existsSync8(file)) return void 0;
23438
23447
  const stat = lstatSync2(file);
23439
23448
  if (stat.isSymbolicLink() || !stat.isFile()) {
23440
23449
  bindingError("path", "local_binding_file_invalid", "binding record must be a regular file, not a link or directory");
@@ -23482,7 +23491,7 @@ function syncDirectory(path4) {
23482
23491
  }
23483
23492
  }
23484
23493
  function createDirectoryIfMissing(path4) {
23485
- if (existsSync7(path4)) return;
23494
+ if (existsSync8(path4)) return;
23486
23495
  try {
23487
23496
  mkdirSync4(path4, { mode: 448 });
23488
23497
  syncDirectory(dirname5(path4));
@@ -23492,8 +23501,8 @@ function createDirectoryIfMissing(path4) {
23492
23501
  }
23493
23502
  function ensureDurableDirectory(path4, description) {
23494
23503
  const missing = [];
23495
- let current = resolve4(path4);
23496
- while (!existsSync7(current)) {
23504
+ let current = resolve5(path4);
23505
+ while (!existsSync8(current)) {
23497
23506
  missing.push(current);
23498
23507
  const parent = dirname5(current);
23499
23508
  if (parent === current) bindingError("path", "local_binding_directory_invalid", `${description} has no existing filesystem ancestor`);
@@ -23518,7 +23527,7 @@ function ensureBindingDirectory(file, stateRoot) {
23518
23527
  checkedDirectory(project, "local project binding directory");
23519
23528
  }
23520
23529
  function assertNoLinkedDirectoryComponents(path4, description) {
23521
- const absolute = resolve4(path4);
23530
+ const absolute = resolve5(path4);
23522
23531
  const filesystemRoot = parse(absolute).root;
23523
23532
  let current = filesystemRoot;
23524
23533
  const remainder = relative2(filesystemRoot, absolute);
@@ -23544,14 +23553,14 @@ function projectOperationLockFile(projectRoot) {
23544
23553
  if (result.status !== 0 || result.error || !result.stdout.trim()) {
23545
23554
  bindingError("discovery", "project_operation_lock_unavailable", "cannot resolve the authoritative Git metadata path for project locking");
23546
23555
  }
23547
- const gitDirectory = resolve4(result.stdout.trim());
23556
+ const gitDirectory = resolve5(result.stdout.trim());
23548
23557
  assertNoLinkedDirectoryComponents(gitDirectory, "authoritative Git metadata path");
23549
23558
  const gitStat = lstatSync2(gitDirectory);
23550
23559
  if (gitStat.isSymbolicLink() || !gitStat.isDirectory()) {
23551
23560
  bindingError("path", "project_operation_lock_parent_invalid", "authoritative Git metadata must resolve to a regular directory");
23552
23561
  }
23553
23562
  const lockDirectory = join7(gitDirectory, "kxm");
23554
- if (existsSync7(lockDirectory)) {
23563
+ if (existsSync8(lockDirectory)) {
23555
23564
  const stat = lstatSync2(lockDirectory);
23556
23565
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
23557
23566
  bindingError("path", "project_operation_lock_parent_invalid", "Git-local KXM lock directory must be regular, not a link or file");
@@ -23563,9 +23572,9 @@ function projectOperationLockFile(projectRoot) {
23563
23572
  return join7(lockDirectory, "project-operation-lock.sqlite");
23564
23573
  }
23565
23574
  function withKxmLocalBindingLock(projectRoot, options, callback) {
23566
- const root = resolve4(projectRoot);
23575
+ const root = resolve5(projectRoot);
23567
23576
  const file = projectOperationLockFile(root);
23568
- if (existsSync7(file)) {
23577
+ if (existsSync8(file)) {
23569
23578
  const stat = lstatSync2(file);
23570
23579
  if (stat.isSymbolicLink() || !stat.isFile()) {
23571
23580
  bindingError("path", "local_binding_lock_invalid", "project-operation lock must be a regular file, not a link or directory");
@@ -23598,7 +23607,7 @@ function withKxmLocalBindingLock(projectRoot, options, callback) {
23598
23607
  }
23599
23608
  }
23600
23609
  function planKxmLocalBindings(projectRoot, projectId, repositories, options = {}) {
23601
- const root = resolve4(projectRoot);
23610
+ const root = resolve5(projectRoot);
23602
23611
  const file = kxmLocalBindingFile(root, options);
23603
23612
  const record = normalizedRecord(root, projectId, repositories, options.schemasDir);
23604
23613
  const existing = readKxmLocalBindings(root, options);
@@ -23608,7 +23617,7 @@ function planKxmLocalBindings(projectRoot, projectId, repositories, options = {}
23608
23617
  return { file, written: !existing || !recordsEqual(existing, record), record };
23609
23618
  }
23610
23619
  function writeKxmLocalBindings(projectRoot, projectId, repositories, options = {}, lock) {
23611
- const root = resolve4(projectRoot);
23620
+ const root = resolve5(projectRoot);
23612
23621
  if (!lock) {
23613
23622
  return withKxmLocalBindingLock(root, options, (acquired) => writeKxmLocalBindings(root, projectId, repositories, options, acquired));
23614
23623
  }
@@ -23726,7 +23735,7 @@ import { createHash as createHash9, randomBytes } from "node:crypto";
23726
23735
  import {
23727
23736
  chmodSync as chmodSync3,
23728
23737
  copyFileSync,
23729
- existsSync as existsSync8,
23738
+ existsSync as existsSync9,
23730
23739
  lstatSync as lstatSync3,
23731
23740
  mkdirSync as mkdirSync5,
23732
23741
  readdirSync as readdirSync2,
@@ -23734,14 +23743,14 @@ import {
23734
23743
  unlinkSync as unlinkSync2,
23735
23744
  writeFileSync as writeFileSync5
23736
23745
  } from "node:fs";
23737
- import { basename as basename3, dirname as dirname6, join as join8, resolve as resolve5 } from "node:path";
23746
+ import { basename as basename3, dirname as dirname6, join as join8, resolve as resolve6 } from "node:path";
23738
23747
  function databaseError(code, file, message) {
23739
23748
  const issue3 = { phase: "semantic", code, file, message };
23740
23749
  return new KxmConfigError([issue3]);
23741
23750
  }
23742
23751
  function checkedParent(path4, description) {
23743
23752
  const parent = dirname6(path4);
23744
- if (!existsSync8(parent)) mkdirSync5(parent, { recursive: true, mode: 448 });
23753
+ if (!existsSync9(parent)) mkdirSync5(parent, { recursive: true, mode: 448 });
23745
23754
  const stat = lstatSync3(parent, { throwIfNoEntry: false });
23746
23755
  if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
23747
23756
  throw databaseError("runtime_path_invalid", description, `${description} parent must be a regular directory, not a link`);
@@ -23878,14 +23887,14 @@ function fileSha256(filePath) {
23878
23887
  return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
23879
23888
  }
23880
23889
  function backupDatabaseFile(sourcePath, targetPath, storeId) {
23881
- const resolvedSource = resolve5(sourcePath);
23882
- const resolvedTarget = resolve5(targetPath);
23890
+ const resolvedSource = resolve6(sourcePath);
23891
+ const resolvedTarget = resolve6(targetPath);
23883
23892
  const sourceStat = lstatSync3(resolvedSource, { throwIfNoEntry: false });
23884
23893
  if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
23885
23894
  throw databaseError("runtime_path_invalid", resolvedSource, `source database ${resolvedSource} must be a regular file, not a link or directory`);
23886
23895
  }
23887
23896
  checkedParent(resolvedTarget, "backup target");
23888
- if (existsSync8(resolvedTarget)) {
23897
+ if (existsSync9(resolvedTarget)) {
23889
23898
  unlinkSync2(resolvedTarget);
23890
23899
  }
23891
23900
  const sourceDb = new DatabaseSync(resolvedSource);
@@ -23929,8 +23938,8 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
23929
23938
  };
23930
23939
  }
23931
23940
  function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVersion, maxSupportedVersion) {
23932
- const resolvedBackup = resolve5(backupPath);
23933
- const resolvedTarget = resolve5(targetPath);
23941
+ const resolvedBackup = resolve6(backupPath);
23942
+ const resolvedTarget = resolve6(targetPath);
23934
23943
  const backupStat = lstatSync3(resolvedBackup, { throwIfNoEntry: false });
23935
23944
  if (!backupStat || !backupStat.isFile() || backupStat.isSymbolicLink()) {
23936
23945
  throw databaseError("runtime_path_invalid", resolvedBackup, `backup database ${resolvedBackup} must be a regular file, not a link or directory`);
@@ -23963,7 +23972,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
23963
23972
  }
23964
23973
  checkedParent(resolvedTarget, "restore target");
23965
23974
  for (const file of [resolvedTarget, `${resolvedTarget}-wal`, `${resolvedTarget}-shm`]) {
23966
- if (existsSync8(file)) {
23975
+ if (existsSync9(file)) {
23967
23976
  try {
23968
23977
  unlinkSync2(file);
23969
23978
  } catch {
@@ -24003,22 +24012,22 @@ function kxmBackupCeiling(storeId) {
24003
24012
  return KXM_BACKUP_CEILINGS[storeId] ?? KXM_BACKUP_CEILINGS["hub-store"];
24004
24013
  }
24005
24014
  function discoverProjectStores(projectRoot, options = {}) {
24006
- const root = resolve5(projectRoot);
24015
+ const root = resolve6(projectRoot);
24007
24016
  const stores = [];
24008
- const hubPath = options.hubDataPath ? resolve5(options.hubDataPath) : join8(root, ".kxm", "state", "kxm.db");
24009
- if (existsSync8(hubPath)) {
24017
+ const hubPath = options.hubDataPath ? resolve6(options.hubDataPath) : join8(root, ".kxm", "state", "kxm.db");
24018
+ if (existsSync9(hubPath)) {
24010
24019
  stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion: kxmBackupCeiling("hub-store") });
24011
24020
  }
24012
24021
  const registryPath = join8(root, ".kxm", "runtime", "registry.db");
24013
- if (existsSync8(registryPath)) {
24022
+ if (existsSync9(registryPath)) {
24014
24023
  stores.push({ storeId: "registry", sourcePath: registryPath, maxSupportedVersion: kxmBackupCeiling("registry") });
24015
24024
  }
24016
24025
  const bindingsPath = join8(root, ".kxm", "runtime", "bindings.db");
24017
- if (existsSync8(bindingsPath)) {
24026
+ if (existsSync9(bindingsPath)) {
24018
24027
  stores.push({ storeId: "binding-store", sourcePath: bindingsPath, maxSupportedVersion: kxmBackupCeiling("binding-store") });
24019
24028
  }
24020
24029
  const eventsDir = join8(root, ".kxm", "runtime", "events");
24021
- if (existsSync8(eventsDir)) {
24030
+ if (existsSync9(eventsDir)) {
24022
24031
  const entries = readdirSync2(eventsDir, { withFileTypes: true });
24023
24032
  for (const entry of entries) {
24024
24033
  if (entry.isFile() && entry.name.endsWith(".db")) {
@@ -24033,38 +24042,43 @@ function discoverProjectStores(projectRoot, options = {}) {
24033
24042
  }
24034
24043
  return stores;
24035
24044
  }
24036
- function createBackup(options = {}) {
24037
- const projectRoot = options.projectRoot ? resolve5(options.projectRoot) : process.cwd();
24045
+ function planBackup(options = {}) {
24046
+ const projectRoot = options.projectRoot ? resolve6(options.projectRoot) : process.cwd();
24038
24047
  const stores = discoverProjectStores(projectRoot, {
24039
24048
  ...options.hubDataPath !== void 0 ? { hubDataPath: options.hubDataPath } : {}
24040
24049
  });
24041
24050
  if (stores.length === 0) {
24042
24051
  throw databaseError("backup_no_stores", projectRoot, "no existing SQLite stores found to backup");
24043
24052
  }
24044
- const now = /* @__PURE__ */ new Date();
24045
- const timestamp = now.toISOString().replace(/[:.]/g, "-");
24046
- const backupId = `bk_${randomBytes(8).toString("hex")}`;
24047
- const outDir = options.outDir ? resolve5(options.outDir) : join8(projectRoot, ".kxm", "backups", `backup-${timestamp}`);
24048
- if (!existsSync8(outDir)) {
24049
- mkdirSync5(outDir, { recursive: true, mode: 448 });
24050
- }
24051
- const backedUpStores = [];
24053
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
24054
+ const timestamp = createdAt.replace(/[:.]/g, "-");
24055
+ const outDir = options.outDir ? resolve6(options.outDir) : join8(projectRoot, ".kxm", "backups", `backup-${timestamp}`);
24052
24056
  const usedFilenames = /* @__PURE__ */ new Set();
24053
- for (const store of stores) {
24057
+ const planned = stores.map((store) => {
24054
24058
  let filename = basename3(store.sourcePath);
24055
24059
  if (usedFilenames.has(filename)) {
24056
24060
  const sanitizedId = store.storeId.replace(/[^a-zA-Z0-9_.-]/g, "_");
24057
24061
  filename = `${sanitizedId}-${filename}`;
24058
24062
  }
24059
24063
  usedFilenames.add(filename);
24060
- const targetFile2 = join8(outDir, filename);
24061
- const record = backupDatabaseFile(store.sourcePath, targetFile2, store.storeId);
24062
- backedUpStores.push(record);
24064
+ return { storeId: store.storeId, sourcePath: store.sourcePath, backupFile: filename };
24065
+ });
24066
+ return { projectRoot, outDir, createdAt, stores: planned };
24067
+ }
24068
+ function createBackup(options = {}) {
24069
+ const { projectRoot, outDir, createdAt, stores } = planBackup(options);
24070
+ const backupId = `bk_${randomBytes(8).toString("hex")}`;
24071
+ if (!existsSync9(outDir)) {
24072
+ mkdirSync5(outDir, { recursive: true, mode: 448 });
24073
+ }
24074
+ const backedUpStores = [];
24075
+ for (const store of stores) {
24076
+ backedUpStores.push(backupDatabaseFile(store.sourcePath, join8(outDir, store.backupFile), store.storeId));
24063
24077
  }
24064
24078
  const manifest = {
24065
24079
  schema: "kxm.backup-manifest.v1",
24066
24080
  backupId,
24067
- createdAt: now.toISOString(),
24081
+ createdAt,
24068
24082
  projectRoot,
24069
24083
  stores: backedUpStores
24070
24084
  };
@@ -24076,8 +24090,8 @@ function createBackup(options = {}) {
24076
24090
  writeFileSync5(manifestPath, finalJson, "utf8");
24077
24091
  return { manifest, outDir };
24078
24092
  }
24079
- function restoreBackup(manifestPathOrDir, options = {}) {
24080
- let manifestPath = resolve5(manifestPathOrDir);
24093
+ function planRestore(manifestPathOrDir, options = {}) {
24094
+ let manifestPath = resolve6(manifestPathOrDir);
24081
24095
  const stat = lstatSync3(manifestPath, { throwIfNoEntry: false });
24082
24096
  if (!stat) {
24083
24097
  throw databaseError("runtime_path_invalid", manifestPath, `manifest path ${manifestPath} does not exist`);
@@ -24085,7 +24099,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
24085
24099
  if (stat.isDirectory()) {
24086
24100
  manifestPath = join8(manifestPath, "manifest.json");
24087
24101
  }
24088
- if (!existsSync8(manifestPath)) {
24102
+ if (!existsSync9(manifestPath)) {
24089
24103
  throw databaseError("runtime_path_invalid", manifestPath, `backup manifest ${manifestPath} not found`);
24090
24104
  }
24091
24105
  const manifestDir = dirname6(manifestPath);
@@ -24099,10 +24113,10 @@ function restoreBackup(manifestPathOrDir, options = {}) {
24099
24113
  if (manifest.schema !== "kxm.backup-manifest.v1" || !Array.isArray(manifest.stores) || manifest.stores.length === 0) {
24100
24114
  throw databaseError("restore_manifest_invalid", manifestPath, "manifest is not a valid kxm.backup-manifest.v1 document");
24101
24115
  }
24102
- const restoredStores = [];
24116
+ const stores = [];
24103
24117
  for (const store of manifest.stores) {
24104
24118
  const backupFilePath = join8(manifestDir, store.backupFile);
24105
- if (!existsSync8(backupFilePath)) {
24119
+ if (!existsSync9(backupFilePath)) {
24106
24120
  throw databaseError("restore_file_missing", backupFilePath, `backup file ${store.backupFile} missing from ${manifestDir}`);
24107
24121
  }
24108
24122
  const actualSha256 = fileSha256(backupFilePath);
@@ -24113,31 +24127,42 @@ function restoreBackup(manifestPathOrDir, options = {}) {
24113
24127
  `backup file ${store.backupFile} sha256 ${actualSha256} does not match manifest hash ${store.sha256}`
24114
24128
  );
24115
24129
  }
24116
- const maxSupported = kxmBackupCeiling(store.storeId);
24130
+ const maxSupportedVersion = kxmBackupCeiling(store.storeId);
24131
+ if (store.schemaVersion > maxSupportedVersion) {
24132
+ throw databaseError(
24133
+ "runtime_schema_newer",
24134
+ backupFilePath,
24135
+ `backup store ${store.storeId} schema version ${store.schemaVersion} is newer than supported maximum ${maxSupportedVersion}`
24136
+ );
24137
+ }
24117
24138
  let targetPath = store.sourcePath;
24118
24139
  if (options.projectRoot && manifest.projectRoot && targetPath.startsWith(manifest.projectRoot)) {
24119
24140
  const rel = targetPath.slice(manifest.projectRoot.length).replace(/^[\\/]+/, "");
24120
- targetPath = join8(resolve5(options.projectRoot), rel);
24121
- }
24122
- const result = restoreDatabaseFile(
24123
- backupFilePath,
24124
- targetPath,
24125
- store.storeId,
24126
- store.schemaVersion,
24127
- maxSupported
24128
- );
24129
- restoredStores.push(result);
24141
+ targetPath = join8(resolve6(options.projectRoot), rel);
24142
+ }
24143
+ stores.push({ storeId: store.storeId, backupFilePath, targetPath, schemaVersion: store.schemaVersion, maxSupportedVersion });
24130
24144
  }
24145
+ return { manifestPath, backupId: manifest.backupId, stores };
24146
+ }
24147
+ function restoreBackup(manifestPathOrDir, options = {}) {
24148
+ const plan = planRestore(manifestPathOrDir, options);
24149
+ const restoredStores = plan.stores.map((store) => restoreDatabaseFile(
24150
+ store.backupFilePath,
24151
+ store.targetPath,
24152
+ store.storeId,
24153
+ store.schemaVersion,
24154
+ store.maxSupportedVersion
24155
+ ));
24131
24156
  return {
24132
- manifestPath,
24133
- backupId: manifest.backupId,
24157
+ manifestPath: plan.manifestPath,
24158
+ backupId: plan.backupId,
24134
24159
  restoredStores
24135
24160
  };
24136
24161
  }
24137
24162
 
24138
24163
  // plugins/kxm/src/runtime-store.ts
24139
24164
  function kxmRuntimePaths(options = {}) {
24140
- const stateRoot = options.stateRoot ? resolve6(options.stateRoot) : kxmUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
24165
+ const stateRoot = options.stateRoot ? resolve7(options.stateRoot) : kxmUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
24141
24166
  const runtimeDir = join9(stateRoot, "runtime");
24142
24167
  return {
24143
24168
  stateRoot,
@@ -24155,9 +24180,9 @@ function runtimeError(code, file, message) {
24155
24180
  function projectRuntimeKey(projectRoot) {
24156
24181
  let canonical2;
24157
24182
  try {
24158
- canonical2 = realpathSync3.native(resolve6(projectRoot));
24183
+ canonical2 = realpathSync3.native(resolve7(projectRoot));
24159
24184
  } catch {
24160
- canonical2 = resolve6(projectRoot);
24185
+ canonical2 = resolve7(projectRoot);
24161
24186
  }
24162
24187
  const folded = process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
24163
24188
  return createHash10("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
@@ -24191,7 +24216,7 @@ var KxmRuntimeRegistry = class {
24191
24216
  path;
24192
24217
  database;
24193
24218
  constructor(path4) {
24194
- this.path = resolve6(path4);
24219
+ this.path = resolve7(path4);
24195
24220
  this.database = openDatabase(this.path, "runtime registry", {
24196
24221
  schema: REGISTRY_SCHEMA,
24197
24222
  version: KXM_REGISTRY_SCHEMA_VERSION,
@@ -24290,7 +24315,7 @@ var KxmRuntimeRegistry = class {
24290
24315
  return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
24291
24316
  }
24292
24317
  registerProject(registration) {
24293
- const projectRoot = resolve6(registration.projectRoot);
24318
+ const projectRoot = resolve7(registration.projectRoot);
24294
24319
  const projectKey = projectRuntimeKey(projectRoot);
24295
24320
  this.database.exec("BEGIN IMMEDIATE");
24296
24321
  try {
@@ -24994,7 +25019,7 @@ var RECORD_LIMIT = 16 * 1024 * 1024;
24994
25019
  // plugins/kxm/src/prices.ts
24995
25020
  var import_yaml3 = __toESM(require_dist(), 1);
24996
25021
  import { createHash as createHash11 } from "node:crypto";
24997
- import { existsSync as existsSync10, readFileSync as readFileSync9, statSync } from "node:fs";
25022
+ import { existsSync as existsSync11, readFileSync as readFileSync9, statSync } from "node:fs";
24998
25023
  import { join as join10 } from "node:path";
24999
25024
 
25000
25025
  // plugins/kxm/src/price-calc.ts
@@ -25147,8 +25172,8 @@ function parsePriceCatalog(text) {
25147
25172
  return catalog;
25148
25173
  }
25149
25174
  function loadPriceCatalog(rootOrPath) {
25150
- const candidatePath = existsSync10(join10(rootOrPath, ".kxm", "prices.yaml")) ? join10(rootOrPath, ".kxm", "prices.yaml") : existsSync10(join10(rootOrPath, "prices.yaml")) ? join10(rootOrPath, "prices.yaml") : existsSync10(rootOrPath) && statSync(rootOrPath).isFile() ? rootOrPath : void 0;
25151
- if (!candidatePath || !existsSync10(candidatePath)) {
25175
+ const candidatePath = existsSync11(join10(rootOrPath, ".kxm", "prices.yaml")) ? join10(rootOrPath, ".kxm", "prices.yaml") : existsSync11(join10(rootOrPath, "prices.yaml")) ? join10(rootOrPath, "prices.yaml") : existsSync11(rootOrPath) && statSync(rootOrPath).isFile() ? rootOrPath : void 0;
25176
+ if (!candidatePath || !existsSync11(candidatePath)) {
25152
25177
  return void 0;
25153
25178
  }
25154
25179
  const content = readFileSync9(candidatePath, "utf8");
@@ -25172,14 +25197,14 @@ var import_yaml5 = __toESM(require_dist(), 1);
25172
25197
 
25173
25198
  // plugins/kxm/src/routes.ts
25174
25199
  var import_yaml4 = __toESM(require_dist(), 1);
25175
- import { existsSync as existsSync11, readFileSync as readFileSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, readdirSync as readdirSync3 } from "node:fs";
25200
+ import { existsSync as existsSync12, readFileSync as readFileSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, readdirSync as readdirSync3 } from "node:fs";
25176
25201
  import { join as join11 } from "node:path";
25177
25202
  var RETIRED_POLICY = ".kxm/producers.yaml";
25178
25203
  var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
25179
25204
  function loadRoutePolicy(root) {
25180
- if (existsSync11(join11(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
25205
+ if (existsSync12(join11(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
25181
25206
  const path4 = join11(root, ".kxm", "routes.yaml");
25182
- if (!existsSync11(path4)) return empty();
25207
+ if (!existsSync12(path4)) return empty();
25183
25208
  const value = (0, import_yaml4.parse)(readFileSync10(path4, "utf8"));
25184
25209
  if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
25185
25210
  return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x2) => typeof x2 === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x2) => typeof x2 === "string") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v2]) => Array.isArray(v2)).map(([k, v2]) => [k, v2.filter((x2) => typeof x2 === "string")])) : {} };
@@ -25197,7 +25222,7 @@ function setRouteState(root, model, state, role, removeRole2 = false) {
25197
25222
  if (!/^[a-z][a-z0-9_-]{0,63}$/i.test(role)) throw new Error("invalid role id");
25198
25223
  const rolePath = join11(root, ".kxm", "roles", `${role}.yaml`);
25199
25224
  let roleFile = {};
25200
- if (existsSync11(rolePath)) {
25225
+ if (existsSync12(rolePath)) {
25201
25226
  const parsed = (0, import_yaml4.parse)(readFileSync10(rolePath, "utf8"));
25202
25227
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) roleFile = parsed;
25203
25228
  }
@@ -25220,7 +25245,7 @@ function setRouteState(root, model, state, role, removeRole2 = false) {
25220
25245
  function listRoleBindings(root) {
25221
25246
  const dir = join11(root, ".kxm", "roles");
25222
25247
  const result = {};
25223
- if (!existsSync11(dir)) return result;
25248
+ if (!existsSync12(dir)) return result;
25224
25249
  for (const file of readdirSync3(dir).filter((name) => name.endsWith(".yaml"))) {
25225
25250
  const role = file.slice(0, -5);
25226
25251
  const value = (0, import_yaml4.parse)(readFileSync10(join11(dir, file), "utf8"));
@@ -25230,21 +25255,21 @@ function listRoleBindings(root) {
25230
25255
  }
25231
25256
  function listInventoryModels(root) {
25232
25257
  const path4 = join11(root, ".kxm", "models", "inventory.yaml");
25233
- if (!existsSync11(path4)) return [];
25258
+ if (!existsSync12(path4)) return [];
25234
25259
  const value = (0, import_yaml4.parse)(readFileSync10(path4, "utf8"));
25235
25260
  return (value.models ?? []).map((m2) => m2.id).filter((id) => typeof id === "string");
25236
25261
  }
25237
25262
 
25238
25263
  // plugins/kxm/src/artifacts-exist.ts
25239
25264
  import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync2 } from "node:fs";
25240
- import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve7 } from "node:path";
25265
+ import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve8 } from "node:path";
25241
25266
  function staysUnder(root, candidate) {
25242
25267
  const child = relative3(root, candidate);
25243
25268
  return child === "" || !isAbsolute4(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
25244
25269
  }
25245
25270
  function verifyArtifactExists(rootInput, pathInput) {
25246
- const lexicalRoot = resolve7(rootInput);
25247
- const lexicalPath = resolve7(pathInput);
25271
+ const lexicalRoot = resolve8(rootInput);
25272
+ const lexicalPath = resolve8(pathInput);
25248
25273
  if (!staysUnder(lexicalRoot, lexicalPath)) {
25249
25274
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
25250
25275
  }
@@ -25863,9 +25888,9 @@ function formatRoutingReport(report2, options = {}) {
25863
25888
  }
25864
25889
 
25865
25890
  // plugins/kxm/src/hub-binding.ts
25866
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
25891
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
25867
25892
  import { homedir as homedir4 } from "node:os";
25868
- import { dirname as dirname8, isAbsolute as isAbsolute5, join as join12, resolve as resolve8 } from "node:path";
25893
+ import { dirname as dirname8, isAbsolute as isAbsolute5, join as join12, resolve as resolve9 } from "node:path";
25869
25894
  var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
25870
25895
  var HUB_HEALTH_PROBE_MS = 300;
25871
25896
  var HubBindingError = class extends Error {
@@ -25878,17 +25903,17 @@ function resolveUserStateRoot2(env) {
25878
25903
  const explicit = env.KXM_STATE_HOME?.trim();
25879
25904
  if (explicit) {
25880
25905
  if (!isAbsolute5(explicit)) throw new HubBindingError("local_state_root_not_absolute");
25881
- return resolve8(explicit);
25906
+ return resolve9(explicit);
25882
25907
  }
25883
25908
  if (process.platform === "win32") {
25884
25909
  const localAppData = env.LOCALAPPDATA?.trim();
25885
25910
  const base2 = localAppData && isAbsolute5(localAppData) ? localAppData : join12(homedir4(), "AppData", "Local");
25886
- return resolve8(base2, "KXM");
25911
+ return resolve9(base2, "KXM");
25887
25912
  }
25888
- if (process.platform === "darwin") return resolve8(homedir4(), "Library", "Application Support", "KXM");
25913
+ if (process.platform === "darwin") return resolve9(homedir4(), "Library", "Application Support", "KXM");
25889
25914
  const xdgState = env.XDG_STATE_HOME?.trim();
25890
25915
  const base = xdgState && isAbsolute5(xdgState) ? xdgState : join12(homedir4(), ".local", "state");
25891
- return resolve8(base, "kxm");
25916
+ return resolve9(base, "kxm");
25892
25917
  }
25893
25918
  function hubBindingFile(env = process.env) {
25894
25919
  return join12(resolveUserStateRoot2(env), "hub-binding.json");
@@ -25927,7 +25952,7 @@ function isAbortError(error) {
25927
25952
  }
25928
25953
  function readHubBinding(env = process.env) {
25929
25954
  const file = hubBindingFile(env);
25930
- if (!existsSync12(file)) return void 0;
25955
+ if (!existsSync13(file)) return void 0;
25931
25956
  let parsed;
25932
25957
  try {
25933
25958
  parsed = JSON.parse(readFileSync11(file, "utf8"));
@@ -26029,7 +26054,7 @@ function supervisorErrorFile(paths) {
26029
26054
  }
26030
26055
  function clearSupervisorError(paths) {
26031
26056
  const file = supervisorErrorFile(paths);
26032
- if (existsSync13(file)) rmSync4(file, { force: true });
26057
+ if (existsSync14(file)) rmSync4(file, { force: true });
26033
26058
  }
26034
26059
  function readRecentSupervisorError(paths) {
26035
26060
  const file = supervisorErrorFile(paths);
@@ -26044,7 +26069,7 @@ function readRecentSupervisorError(paths) {
26044
26069
  }
26045
26070
  }
26046
26071
  function kxmSupervisorStatus(paths) {
26047
- if (!existsSync13(paths.registryDb)) return { running: false };
26072
+ if (!existsSync14(paths.registryDb)) return { running: false };
26048
26073
  const registry = new KxmRuntimeRegistry(paths.registryDb);
26049
26074
  try {
26050
26075
  const record = registry.supervisor();
@@ -26168,7 +26193,7 @@ async function kxmRuntimeRequest(handle, method, path4, body) {
26168
26193
 
26169
26194
  // plugins/kxm/src/cli/types.ts
26170
26195
  import { spawn as spawn3 } from "node:child_process";
26171
- import { join as join15, resolve as resolve9 } from "node:path";
26196
+ import { join as join15, resolve as resolve10 } from "node:path";
26172
26197
 
26173
26198
  // plugins/kxm/src/telemetry.ts
26174
26199
  import { appendFileSync, mkdirSync as mkdirSync10, readFileSync as readFileSync13 } from "node:fs";
@@ -26314,6 +26339,17 @@ function print(io, jsonMode, payload, text) {
26314
26339
  if (tagged.ok === false) io.stderr(line);
26315
26340
  else io.stdout(line);
26316
26341
  }
26342
+ function printPlan(runtime, payload, planned, text) {
26343
+ print(runtime.io, runtime.json, { ok: true, ...payload, dryRun: true, planned }, [
26344
+ `dry run: ${text}`,
26345
+ ...planned.length === 0 ? [" no changes"] : planned.map((change) => ` would ${change.action} ${change.target}`)
26346
+ ].join("\n"));
26347
+ }
26348
+ var DRY_RUN_UNSUPPORTED = "dry_run_unsupported";
26349
+ function refuseDryRun(io, jsonMode, command, reason) {
26350
+ print(io, jsonMode, { ok: false, command, dryRun: true, error: DRY_RUN_UNSUPPORTED, detail: reason }, `kxm ${command} --dry-run refused: ${reason}`);
26351
+ return 2;
26352
+ }
26317
26353
  function printWorker(runtime, worker, payload, text, outcome) {
26318
26354
  const sessionId = runtime.env.KXM_SESSION_ID?.trim();
26319
26355
  const envelope = workerResult(worker, {
@@ -26365,16 +26401,16 @@ function redactCliValue(value, field = "") {
26365
26401
  return value;
26366
26402
  }
26367
26403
  function workspaceDirs(cwd, workspaceFlag, env) {
26368
- const workdir = resolve9(env.KXM_WORKDIR?.trim() || cwd);
26369
- const workspace = resolve9(workdir, workspaceFlag || env.KXM_WORKSPACE_DIR?.trim() || ".kxm");
26404
+ const workdir = resolve10(env.KXM_WORKDIR?.trim() || cwd);
26405
+ const workspace = resolve10(workdir, workspaceFlag || env.KXM_WORKSPACE_DIR?.trim() || ".kxm");
26370
26406
  const derive = workspaceFlag !== void 0;
26371
26407
  return {
26372
26408
  workdir,
26373
26409
  workspace,
26374
- config: derive ? join15(workspace, "config") : resolve9(workdir, env.KXM_CONFIG_DIR?.trim() || join15(workspace, "config")),
26375
- logs: derive ? join15(workspace, "logs") : resolve9(workdir, env.KXM_LOGS_DIR?.trim() || join15(workspace, "logs")),
26376
- assets: derive ? join15(workspace, "assets") : resolve9(workdir, env.KXM_ASSETS_DIR?.trim() || join15(workspace, "assets")),
26377
- state: derive ? join15(workspace, "state") : resolve9(workdir, env.KXM_STATE_DIR?.trim() || join15(workspace, "state"))
26410
+ config: derive ? join15(workspace, "config") : resolve10(workdir, env.KXM_CONFIG_DIR?.trim() || join15(workspace, "config")),
26411
+ logs: derive ? join15(workspace, "logs") : resolve10(workdir, env.KXM_LOGS_DIR?.trim() || join15(workspace, "logs")),
26412
+ assets: derive ? join15(workspace, "assets") : resolve10(workdir, env.KXM_ASSETS_DIR?.trim() || join15(workspace, "assets")),
26413
+ state: derive ? join15(workspace, "state") : resolve10(workdir, env.KXM_STATE_DIR?.trim() || join15(workspace, "state"))
26378
26414
  };
26379
26415
  }
26380
26416
  function maskEnvName(name) {
@@ -26442,20 +26478,20 @@ function workspaceEnv(runtime) {
26442
26478
  // plugins/kxm/src/cli/roles.ts
26443
26479
  var import_yaml8 = __toESM(require_dist(), 1);
26444
26480
  import { randomUUID as randomUUID5 } from "node:crypto";
26445
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "node:fs";
26446
- import { join as join18, resolve as resolve12 } from "node:path";
26481
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "node:fs";
26482
+ import { join as join18, resolve as resolve13 } from "node:path";
26447
26483
  import { createInterface } from "node:readline";
26448
26484
 
26449
26485
  // plugins/kxm/src/role.ts
26450
26486
  var import_yaml7 = __toESM(require_dist(), 1);
26451
- import { existsSync as existsSync15, mkdirSync as mkdirSync12, readdirSync as readdirSync4, readFileSync as readFileSync15, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "node:fs";
26487
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync4, readFileSync as readFileSync15, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "node:fs";
26452
26488
  import { join as join17 } from "node:path";
26453
26489
 
26454
26490
  // plugins/kxm/src/config.ts
26455
26491
  var import_yaml6 = __toESM(require_dist(), 1);
26456
- import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
26492
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
26457
26493
  import { homedir as homedir5 } from "node:os";
26458
- import { dirname as dirname11, join as join16, resolve as resolve10 } from "node:path";
26494
+ import { dirname as dirname11, join as join16, resolve as resolve11 } from "node:path";
26459
26495
  var KXM_CONFIG_SCHEMA = "kxm.config.v1";
26460
26496
  var DEFAULT_KXM_CONFIG = {
26461
26497
  schema: KXM_CONFIG_SCHEMA,
@@ -26508,11 +26544,11 @@ var DEFAULT_KXM_CONFIG = {
26508
26544
  }
26509
26545
  };
26510
26546
  function userConfigDirectory(overrideDir) {
26511
- if (overrideDir) return resolve10(overrideDir);
26512
- return resolve10(process.env.KXM_USER_CONFIG_DIR?.trim() || join16(homedir5(), ".config", "kxm"));
26547
+ if (overrideDir) return resolve11(overrideDir);
26548
+ return resolve11(process.env.KXM_USER_CONFIG_DIR?.trim() || join16(homedir5(), ".config", "kxm"));
26513
26549
  }
26514
26550
  function repoConfigDirectory(repoRoot) {
26515
- return resolve10(repoRoot, ".kxm");
26551
+ return resolve11(repoRoot, ".kxm");
26516
26552
  }
26517
26553
  function deepMerge(target, source) {
26518
26554
  const result = { ...target };
@@ -26537,7 +26573,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
26537
26573
  const repoConfigFile = join16(repoDir, "config.yaml");
26538
26574
  let userRaw = {};
26539
26575
  let userLoadedPath;
26540
- if (existsSync14(userConfigFile)) {
26576
+ if (existsSync15(userConfigFile)) {
26541
26577
  try {
26542
26578
  const text = readFileSync14(userConfigFile, "utf8");
26543
26579
  userRaw = (0, import_yaml6.parse)(text) ?? {};
@@ -26548,7 +26584,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
26548
26584
  }
26549
26585
  let repoRaw = {};
26550
26586
  let repoLoadedPath;
26551
- if (existsSync14(repoConfigFile)) {
26587
+ if (existsSync15(repoConfigFile)) {
26552
26588
  try {
26553
26589
  const text = readFileSync14(repoConfigFile, "utf8");
26554
26590
  repoRaw = (0, import_yaml6.parse)(text) ?? {};
@@ -26597,7 +26633,7 @@ function configKeyParts(keyPath) {
26597
26633
  }
26598
26634
  function readConfigFile(targetFile2) {
26599
26635
  let existing = {};
26600
- if (existsSync14(targetFile2)) {
26636
+ if (existsSync15(targetFile2)) {
26601
26637
  try {
26602
26638
  existing = (0, import_yaml6.parse)(readFileSync14(targetFile2, "utf8")) ?? {};
26603
26639
  } catch {
@@ -26623,7 +26659,8 @@ function setKxmConfigValue(repoRoot, keyPath, value, options = {}) {
26623
26659
  cursor2 = cursor2[p];
26624
26660
  }
26625
26661
  cursor2[parts[parts.length - 1]] = value;
26626
- writeConfigFile(targetFile2, existing);
26662
+ if (!options.dryRun) writeConfigFile(targetFile2, existing);
26663
+ return { file: targetFile2 };
26627
26664
  }
26628
26665
  function formatKxmConfig(config) {
26629
26666
  const display = {
@@ -26722,13 +26759,13 @@ function rolesDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
26722
26759
  }
26723
26760
  function ensureRolesDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
26724
26761
  const dir = rolesDirectory(scope, repoRoot, userConfigDir);
26725
- if (!existsSync15(dir)) {
26762
+ if (!existsSync16(dir)) {
26726
26763
  mkdirSync12(dir, { recursive: true });
26727
26764
  }
26728
26765
  return dir;
26729
26766
  }
26730
26767
  function parseRoleFile(filePath) {
26731
- if (!existsSync15(filePath)) return void 0;
26768
+ if (!existsSync16(filePath)) return void 0;
26732
26769
  try {
26733
26770
  const raw = readFileSync15(filePath, "utf8");
26734
26771
  const parsed = (0, import_yaml7.parse)(raw);
@@ -26757,7 +26794,7 @@ function listRoles(options = {}) {
26757
26794
  const globalDir = rolesDirectory("global", repoRoot, options.userConfigDir);
26758
26795
  const localDir = rolesDirectory("local", repoRoot, options.userConfigDir);
26759
26796
  const localRoles = /* @__PURE__ */ new Map();
26760
- if (scopeFilter !== "global" && existsSync15(localDir)) {
26797
+ if (scopeFilter !== "global" && existsSync16(localDir)) {
26761
26798
  for (const entry of readdirSync4(localDir)) {
26762
26799
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
26763
26800
  const filePath = join17(localDir, entry);
@@ -26769,7 +26806,7 @@ function listRoles(options = {}) {
26769
26806
  }
26770
26807
  }
26771
26808
  const globalRoles = /* @__PURE__ */ new Map();
26772
- if (scopeFilter !== "local" && existsSync15(globalDir)) {
26809
+ if (scopeFilter !== "local" && existsSync16(globalDir)) {
26773
26810
  for (const entry of readdirSync4(globalDir)) {
26774
26811
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
26775
26812
  const filePath = join17(globalDir, entry);
@@ -26835,12 +26872,12 @@ function getRole(roleId, options = {}) {
26835
26872
  function addRole(role, options = {}) {
26836
26873
  const scope = options.scope ?? "local";
26837
26874
  const repoRoot = options.repoRoot ?? process.cwd();
26838
- const dir = ensureRolesDirectory(scope, repoRoot, options.userConfigDir);
26875
+ const dir = options.dryRun ? rolesDirectory(scope, repoRoot, options.userConfigDir) : ensureRolesDirectory(scope, repoRoot, options.userConfigDir);
26839
26876
  const filePath = join17(dir, `${role.id}.yaml`);
26840
- if (existsSync15(filePath) && !options.overwrite) {
26877
+ if (existsSync16(filePath) && !options.overwrite) {
26841
26878
  throw new Error(`role_already_exists: role '${role.id}' already exists at ${filePath}`);
26842
26879
  }
26843
- writeFileSync11(filePath, (0, import_yaml7.stringify)(role), "utf8");
26880
+ if (!options.dryRun) writeFileSync11(filePath, (0, import_yaml7.stringify)(role), "utf8");
26844
26881
  return { id: role.id, filePath, scope };
26845
26882
  }
26846
26883
  function removeRole(roleId, options = {}) {
@@ -26848,9 +26885,10 @@ function removeRole(roleId, options = {}) {
26848
26885
  const repoRoot = options.repoRoot ?? process.cwd();
26849
26886
  const dir = rolesDirectory(scope, repoRoot, options.userConfigDir);
26850
26887
  const filePath = join17(dir, `${roleId}.yaml`);
26851
- if (!existsSync15(filePath)) {
26888
+ if (!existsSync16(filePath)) {
26852
26889
  throw new Error(`role_not_found: role '${roleId}' not found in ${scope} directory (${filePath})`);
26853
26890
  }
26891
+ if (options.dryRun) return { id: roleId, removed: false, filePath, scope };
26854
26892
  rmSync5(filePath);
26855
26893
  return { id: roleId, removed: true, filePath, scope };
26856
26894
  }
@@ -26868,9 +26906,9 @@ function modifyRole(roleId, updates, options = {}) {
26868
26906
  };
26869
26907
  const scope = options.scope ?? target.scope;
26870
26908
  const repoRoot = options.repoRoot ?? process.cwd();
26871
- const dir = ensureRolesDirectory(scope, repoRoot, options.userConfigDir);
26909
+ const dir = options.dryRun ? rolesDirectory(scope, repoRoot, options.userConfigDir) : ensureRolesDirectory(scope, repoRoot, options.userConfigDir);
26872
26910
  const filePath = join17(dir, `${roleId}.yaml`);
26873
- writeFileSync11(filePath, (0, import_yaml7.stringify)(updated), "utf8");
26911
+ if (!options.dryRun) writeFileSync11(filePath, (0, import_yaml7.stringify)(updated), "utf8");
26874
26912
  return { id: roleId, role: updated, filePath, scope };
26875
26913
  }
26876
26914
  var KXM_ROLE_HOSTS_SCHEMA = "kxm.role-hosts.v1";
@@ -26921,13 +26959,13 @@ function findExistingRoleHostsFile(scope, repoRoot = process.cwd(), userConfigDi
26921
26959
  const yamlPath = join17(dir, "role-hosts.yaml");
26922
26960
  const ymlPath = join17(dir, "role-hosts.yml");
26923
26961
  const jsonPath = join17(dir, "role-hosts.json");
26924
- if (existsSync15(yamlPath)) return { filePath: yamlPath, exists: true, format: "yaml" };
26925
- if (existsSync15(ymlPath)) return { filePath: ymlPath, exists: true, format: "yaml" };
26926
- if (existsSync15(jsonPath)) return { filePath: jsonPath, exists: true, format: "json" };
26962
+ if (existsSync16(yamlPath)) return { filePath: yamlPath, exists: true, format: "yaml" };
26963
+ if (existsSync16(ymlPath)) return { filePath: ymlPath, exists: true, format: "yaml" };
26964
+ if (existsSync16(jsonPath)) return { filePath: jsonPath, exists: true, format: "json" };
26927
26965
  return { filePath: yamlPath, exists: false, format: "yaml" };
26928
26966
  }
26929
26967
  function parseRoleHostsFile(filePath) {
26930
- if (!existsSync15(filePath)) return void 0;
26968
+ if (!existsSync16(filePath)) return void 0;
26931
26969
  try {
26932
26970
  const raw = readFileSync15(filePath, "utf8");
26933
26971
  const parsed = (0, import_yaml7.parse)(raw);
@@ -27001,11 +27039,12 @@ function saveRoleHostsConfig(config, options = {}) {
27001
27039
  const scope = options.scope ?? "local";
27002
27040
  const repoRoot = options.repoRoot ?? process.cwd();
27003
27041
  const dir = scope === "global" ? userConfigDirectory(options.userConfigDir) : repoConfigDirectory(repoRoot);
27004
- if (!existsSync15(dir)) {
27005
- mkdirSync12(dir, { recursive: true });
27006
- }
27007
27042
  const format = options.format ?? "yaml";
27008
27043
  const filePath = join17(dir, format === "json" ? "role-hosts.json" : "role-hosts.yaml");
27044
+ if (options.dryRun) return { filePath, scope };
27045
+ if (!existsSync16(dir)) {
27046
+ mkdirSync12(dir, { recursive: true });
27047
+ }
27009
27048
  const payload = {
27010
27049
  schema: KXM_ROLE_HOSTS_SCHEMA,
27011
27050
  seats: config.seats ?? {},
@@ -27040,7 +27079,8 @@ function setRoleSeatHost(seatId, host, options = {}) {
27040
27079
  scope,
27041
27080
  repoRoot: options.repoRoot,
27042
27081
  userConfigDir: options.userConfigDir,
27043
- format: options.format
27082
+ format: options.format,
27083
+ dryRun: options.dryRun
27044
27084
  });
27045
27085
  return { filePath: saved.filePath, seatId, binding, scope };
27046
27086
  }
@@ -27226,8 +27266,13 @@ async function cmdRoleAdd(runtime, roleId, options) {
27226
27266
  scope,
27227
27267
  repoRoot: runtime.cwd,
27228
27268
  userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
27229
- overwrite: options.overwrite
27269
+ overwrite: options.overwrite,
27270
+ dryRun: runtime.dryRun
27230
27271
  });
27272
+ if (runtime.dryRun) {
27273
+ printPlan(runtime, { command: "role add", roleId, ...res }, [{ action: "write", target: res.filePath }], `add role '${roleId}' to ${res.scope}`);
27274
+ return 0;
27275
+ }
27231
27276
  print(
27232
27277
  runtime.io,
27233
27278
  runtime.json,
@@ -27246,7 +27291,7 @@ async function cmdRoleAdd(runtime, roleId, options) {
27246
27291
  }
27247
27292
  let roleDef;
27248
27293
  if (options.file) {
27249
- const filePath = resolve12(runtime.cwd, options.file);
27294
+ const filePath = resolve13(runtime.cwd, options.file);
27250
27295
  const content = readFileSync16(filePath, "utf8");
27251
27296
  roleDef = (0, import_yaml8.parse)(content);
27252
27297
  roleDef.id = roleId;
@@ -27266,8 +27311,13 @@ async function cmdRoleAdd(runtime, roleId, options) {
27266
27311
  scope,
27267
27312
  repoRoot: runtime.cwd,
27268
27313
  userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
27269
- overwrite: options.overwrite
27314
+ overwrite: options.overwrite,
27315
+ dryRun: runtime.dryRun
27270
27316
  });
27317
+ if (runtime.dryRun) {
27318
+ printPlan(runtime, { command: "role add", roleId, ...res }, [{ action: "write", target: res.filePath }], `add role '${roleId}' to ${res.scope}`);
27319
+ return 0;
27320
+ }
27271
27321
  print(
27272
27322
  runtime.io,
27273
27323
  runtime.json,
@@ -27314,8 +27364,13 @@ async function cmdRoleRemove(runtime, roleId, options) {
27314
27364
  const res = removeRole(roleId, {
27315
27365
  scope,
27316
27366
  repoRoot: runtime.cwd,
27317
- userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
27367
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
27368
+ dryRun: runtime.dryRun
27318
27369
  });
27370
+ if (runtime.dryRun) {
27371
+ printPlan(runtime, { command: "role remove", roleId, ...res }, [{ action: "delete", target: res.filePath }], `remove role '${roleId}' from ${res.scope}`);
27372
+ return 0;
27373
+ }
27319
27374
  print(
27320
27375
  runtime.io,
27321
27376
  runtime.json,
@@ -27393,8 +27448,13 @@ async function cmdRoleModify(runtime, roleId, options) {
27393
27448
  const res = modifyRole(roleId, updates, {
27394
27449
  scope: options.scope,
27395
27450
  repoRoot: runtime.cwd,
27396
- userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
27451
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
27452
+ dryRun: runtime.dryRun
27397
27453
  });
27454
+ if (runtime.dryRun) {
27455
+ printPlan(runtime, { command: "role modify", roleId, ...res }, [{ action: "write", target: res.filePath }], `modify role '${roleId}' in ${res.scope}`);
27456
+ return 0;
27457
+ }
27398
27458
  print(
27399
27459
  runtime.io,
27400
27460
  runtime.json,
@@ -27482,8 +27542,18 @@ async function cmdRoleSetHost(runtime, seatId, host, options = {}) {
27482
27542
  effort: options.effort,
27483
27543
  scope: options.scope ?? "local",
27484
27544
  repoRoot: runtime.cwd,
27485
- userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
27545
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
27546
+ dryRun: runtime.dryRun
27486
27547
  });
27548
+ if (runtime.dryRun) {
27549
+ printPlan(
27550
+ runtime,
27551
+ { command: "role set-host", seatId, host, binding: result.binding, filePath: result.filePath, scope: result.scope },
27552
+ [{ action: "write", target: result.filePath }],
27553
+ `bind seat '${seatId}' to host '${host}'`
27554
+ );
27555
+ return 0;
27556
+ }
27487
27557
  print(
27488
27558
  runtime.io,
27489
27559
  runtime.json,
@@ -27515,7 +27585,12 @@ async function cmdRoleResume(runtime, runId, ruling) {
27515
27585
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
27516
27586
  if (projectRoot && /^run_[a-f0-9]{32}$/i.test(runId)) {
27517
27587
  if (runtime.dryRun) {
27518
- print(runtime.io, runtime.json, { ok: true, command: "role resume", runId, ruling: effectiveRuling }, `would resume KXM run ${runId}`);
27588
+ printPlan(
27589
+ runtime,
27590
+ { command: "role resume", runId, ruling: effectiveRuling },
27591
+ [{ action: "request", target: `POST kxm-runtime /v1/runs/${runId}/signal (audit_escalation unblock)` }],
27592
+ `resume KXM run ${runId}`
27593
+ );
27519
27594
  return 0;
27520
27595
  }
27521
27596
  try {
@@ -27547,9 +27622,9 @@ async function cmdRoleResume(runtime, runId, ruling) {
27547
27622
  }
27548
27623
  }
27549
27624
  const dbPath = join18(runtime.cwd, ".kxm", "state", "kxm.db");
27550
- if (existsSync16(dbPath)) {
27625
+ if (existsSync17(dbPath)) {
27551
27626
  try {
27552
- const database = new DatabaseSync(dbPath);
27627
+ const database = runtime.dryRun ? openReadOnlyDatabase(dbPath) : new DatabaseSync(dbPath);
27553
27628
  try {
27554
27629
  const row = database.prepare("SELECT record FROM workflow_runs WHERE id = ?").get(runId);
27555
27630
  if (!row) {
@@ -27560,6 +27635,15 @@ async function cmdRoleResume(runtime, runId, ruling) {
27560
27635
  const run = JSON.parse(row.record);
27561
27636
  const now = (/* @__PURE__ */ new Date()).toISOString();
27562
27637
  const resumeResult = resumeWorkflowFromRuling(run, effectiveRuling, now);
27638
+ if (runtime.dryRun) {
27639
+ printPlan(
27640
+ runtime,
27641
+ { command: "role resume", runId, stageId: resumeResult.stageId, ruling: effectiveRuling, status: resumeResult.run.status },
27642
+ [{ action: "write", target: `${dbPath} (workflow_runs ${runId}, one workflow_journal decision)` }],
27643
+ `resume workflow run ${runId} (stage: ${resumeResult.stageId})`
27644
+ );
27645
+ return 0;
27646
+ }
27563
27647
  database.prepare("UPDATE workflow_runs SET record = ? WHERE id = ?").run(
27564
27648
  JSON.stringify(resumeResult.run),
27565
27649
  runId
@@ -27609,12 +27693,12 @@ async function cmdRoleResume(runtime, runId, ruling) {
27609
27693
 
27610
27694
  // plugins/kxm/src/cli/workflows.ts
27611
27695
  import { createHmac as createHmac3, randomUUID as randomUUID7 } from "node:crypto";
27612
- import { existsSync as existsSync18, readFileSync as readFileSync18 } from "node:fs";
27613
- import { join as join20, resolve as resolve15 } from "node:path";
27696
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
27697
+ import { join as join20, resolve as resolve16 } from "node:path";
27614
27698
 
27615
27699
  // plugins/kxm/src/retrospective.ts
27616
27700
  import { mkdirSync as mkdirSync13, renameSync as renameSync5, writeFileSync as writeFileSync12 } from "node:fs";
27617
- import { resolve as resolve13 } from "node:path";
27701
+ import { resolve as resolve14 } from "node:path";
27618
27702
  var MAX_RETROSPECTIVE_ENTRIES = 500;
27619
27703
  var SAFE_RUN_ID = /^run_[A-Za-z0-9_-]{1,120}$/;
27620
27704
  function durationMs(startedAt, completedAt) {
@@ -27827,9 +27911,9 @@ function renderRetrospectiveMarkdown(doc) {
27827
27911
  function writeRetrospective(outDir, doc) {
27828
27912
  if (!SAFE_RUN_ID.test(doc.runId)) throw new Error("invalid retrospective run id");
27829
27913
  mkdirSync13(outDir, { recursive: true });
27830
- const root = resolve13(outDir);
27831
- const jsonPath = resolve13(root, `${doc.runId}.json`);
27832
- const mdPath = resolve13(root, `${doc.runId}.md`);
27914
+ const root = resolve14(outDir);
27915
+ const jsonPath = resolve14(root, `${doc.runId}.json`);
27916
+ const mdPath = resolve14(root, `${doc.runId}.md`);
27833
27917
  const prefix = `${root}${process.platform === "win32" ? "\\" : "/"}`;
27834
27918
  if (!jsonPath.startsWith(prefix) || !mdPath.startsWith(prefix)) throw new Error("retrospective path escaped output directory");
27835
27919
  const jsonTmp = `${jsonPath}.tmp`;
@@ -27929,7 +28013,7 @@ async function watchGithubChecks(input) {
27929
28013
  }
27930
28014
  const fetchImpl = input.fetchImpl ?? fetch;
27931
28015
  const now = input.now ?? Date.now;
27932
- const sleep = input.sleep ?? ((ms) => new Promise((resolve30) => setTimeout(resolve30, ms)));
28016
+ const sleep = input.sleep ?? ((ms) => new Promise((resolve32) => setTimeout(resolve32, ms)));
27933
28017
  const deadline = now() + input.timeoutMs;
27934
28018
  const headers = {
27935
28019
  authorization: `Bearer ${token}`,
@@ -28028,7 +28112,7 @@ async function watchGithubChecks(input) {
28028
28112
 
28029
28113
  // plugins/kxm/src/workflow-manager.ts
28030
28114
  var import_yaml9 = __toESM(require_dist(), 1);
28031
- import { existsSync as existsSync17, mkdirSync as mkdirSync14, readdirSync as readdirSync5, readFileSync as readFileSync17, rmSync as rmSync6, writeFileSync as writeFileSync13 } from "node:fs";
28115
+ import { existsSync as existsSync18, mkdirSync as mkdirSync14, readdirSync as readdirSync5, readFileSync as readFileSync17, rmSync as rmSync6, writeFileSync as writeFileSync13 } from "node:fs";
28032
28116
  import { join as join19 } from "node:path";
28033
28117
  var WORKFLOW_TEMPLATES = {
28034
28118
  "implement-and-verify": {
@@ -28186,13 +28270,13 @@ function workflowsDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
28186
28270
  }
28187
28271
  function ensureWorkflowsDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
28188
28272
  const dir = workflowsDirectory(scope, repoRoot, userConfigDir);
28189
- if (!existsSync17(dir)) {
28273
+ if (!existsSync18(dir)) {
28190
28274
  mkdirSync14(dir, { recursive: true });
28191
28275
  }
28192
28276
  return dir;
28193
28277
  }
28194
28278
  function parseWorkflowFile(filePath) {
28195
- if (!existsSync17(filePath)) return void 0;
28279
+ if (!existsSync18(filePath)) return void 0;
28196
28280
  try {
28197
28281
  const raw = readFileSync17(filePath, "utf8");
28198
28282
  const parsed = (0, import_yaml9.parse)(raw);
@@ -28208,7 +28292,7 @@ function listWorkflowDefinitions(options = {}) {
28208
28292
  const globalDir = workflowsDirectory("global", repoRoot, options.userConfigDir);
28209
28293
  const localDir = workflowsDirectory("local", repoRoot, options.userConfigDir);
28210
28294
  const localDefs = /* @__PURE__ */ new Map();
28211
- if (scopeFilter !== "global" && existsSync17(localDir)) {
28295
+ if (scopeFilter !== "global" && existsSync18(localDir)) {
28212
28296
  for (const entry of readdirSync5(localDir)) {
28213
28297
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
28214
28298
  const filePath = join19(localDir, entry);
@@ -28221,7 +28305,7 @@ function listWorkflowDefinitions(options = {}) {
28221
28305
  }
28222
28306
  }
28223
28307
  const globalDefs = /* @__PURE__ */ new Map();
28224
- if (scopeFilter !== "local" && existsSync17(globalDir)) {
28308
+ if (scopeFilter !== "local" && existsSync18(globalDir)) {
28225
28309
  for (const entry of readdirSync5(globalDir)) {
28226
28310
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
28227
28311
  const filePath = join19(globalDir, entry);
@@ -28295,13 +28379,13 @@ function getWorkflowDefinition(workflowId, options = {}) {
28295
28379
  function addWorkflowDefinition(workflowId, content = DEFAULT_WORKFLOW_TEMPLATE, options = {}) {
28296
28380
  const scope = options.scope ?? "local";
28297
28381
  const repoRoot = options.repoRoot ?? process.cwd();
28298
- const dir = ensureWorkflowsDirectory(scope, repoRoot, options.userConfigDir);
28382
+ const dir = options.dryRun ? workflowsDirectory(scope, repoRoot, options.userConfigDir) : ensureWorkflowsDirectory(scope, repoRoot, options.userConfigDir);
28299
28383
  const filePath = join19(dir, `${workflowId}.yaml`);
28300
- if (existsSync17(filePath) && !options.overwrite) {
28384
+ if (existsSync18(filePath) && !options.overwrite) {
28301
28385
  throw new Error(`workflow_already_exists: workflow '${workflowId}' already exists at ${filePath}`);
28302
28386
  }
28303
28387
  const payload = typeof content === "string" ? content : (0, import_yaml9.stringify)(content);
28304
- writeFileSync13(filePath, payload, "utf8");
28388
+ if (!options.dryRun) writeFileSync13(filePath, payload, "utf8");
28305
28389
  return { id: workflowId, filePath, scope };
28306
28390
  }
28307
28391
  function removeWorkflowDefinition(workflowId, options = {}) {
@@ -28309,9 +28393,10 @@ function removeWorkflowDefinition(workflowId, options = {}) {
28309
28393
  const repoRoot = options.repoRoot ?? process.cwd();
28310
28394
  const dir = workflowsDirectory(scope, repoRoot, options.userConfigDir);
28311
28395
  const filePath = join19(dir, `${workflowId}.yaml`);
28312
- if (!existsSync17(filePath)) {
28396
+ if (!existsSync18(filePath)) {
28313
28397
  throw new Error(`workflow_not_found: workflow '${workflowId}' not found in ${scope} directory (${filePath})`);
28314
28398
  }
28399
+ if (options.dryRun) return { id: workflowId, removed: false, filePath, scope };
28315
28400
  rmSync6(filePath);
28316
28401
  return { id: workflowId, removed: true, filePath, scope };
28317
28402
  }
@@ -28326,17 +28411,17 @@ function modifyWorkflowDefinition(workflowId, updates, options = {}) {
28326
28411
  };
28327
28412
  const scope = options.scope ?? target.scope;
28328
28413
  const repoRoot = options.repoRoot ?? process.cwd();
28329
- const dir = ensureWorkflowsDirectory(scope, repoRoot, options.userConfigDir);
28414
+ const dir = options.dryRun ? workflowsDirectory(scope, repoRoot, options.userConfigDir) : ensureWorkflowsDirectory(scope, repoRoot, options.userConfigDir);
28330
28415
  const filePath = join19(dir, `${workflowId}.yaml`);
28331
- writeFileSync13(filePath, (0, import_yaml9.stringify)(updated), "utf8");
28416
+ if (!options.dryRun) writeFileSync13(filePath, (0, import_yaml9.stringify)(updated), "utf8");
28332
28417
  return { id: workflowId, workflow: updated, filePath, scope };
28333
28418
  }
28334
28419
 
28335
28420
  // plugins/kxm/src/cli/workflows.ts
28336
28421
  var CLI_NAME = "kxm";
28337
28422
  function localWorkflowSnapshot(dataPath, runId) {
28338
- if (!existsSync18(dataPath)) throw new Error("state_database_not_found");
28339
- const database = new DatabaseSync(dataPath, { readOnly: true });
28423
+ if (!existsSync19(dataPath)) throw new Error("state_database_not_found");
28424
+ const database = openReadOnlyDatabase(dataPath);
28340
28425
  try {
28341
28426
  const rows = runId ? database.prepare("SELECT record FROM workflow_runs WHERE id = ?").all(runId) : database.prepare("SELECT record FROM workflow_runs ORDER BY rowid DESC LIMIT 200").all();
28342
28427
  const runs = rows.map((row) => JSON.parse(row.record));
@@ -28412,7 +28497,7 @@ function activeWorkflowDefinition(runtime, definitionId) {
28412
28497
  let raw;
28413
28498
  if (file) {
28414
28499
  try {
28415
- raw = readFileSync18(resolve15(runtime.cwd, file), "utf8");
28500
+ raw = readFileSync18(resolve16(runtime.cwd, file), "utf8");
28416
28501
  } catch {
28417
28502
  throw new Error("workflow definition file is unavailable");
28418
28503
  }
@@ -28493,7 +28578,7 @@ async function cmdWorkflowAdd(runtime, workflowId, options) {
28493
28578
  }
28494
28579
  }
28495
28580
  if (options.file) {
28496
- const filePath = resolve15(runtime.cwd, options.file);
28581
+ const filePath = resolve16(runtime.cwd, options.file);
28497
28582
  content = readFileSync18(filePath, "utf8");
28498
28583
  } else if (!content) {
28499
28584
  content = {
@@ -28517,8 +28602,13 @@ async function cmdWorkflowAdd(runtime, workflowId, options) {
28517
28602
  scope,
28518
28603
  repoRoot: runtime.cwd,
28519
28604
  userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
28520
- overwrite: options.overwrite
28605
+ overwrite: options.overwrite,
28606
+ dryRun: runtime.dryRun
28521
28607
  });
28608
+ if (runtime.dryRun) {
28609
+ printPlan(runtime, { command: "workflow add", workflowId, ...res }, [{ action: "write", target: res.filePath }], `add workflow '${workflowId}' to ${res.scope}`);
28610
+ return 0;
28611
+ }
28522
28612
  print(
28523
28613
  runtime.io,
28524
28614
  runtime.json,
@@ -28565,8 +28655,13 @@ async function cmdWorkflowRemove(runtime, workflowId, options) {
28565
28655
  const res = removeWorkflowDefinition(workflowId, {
28566
28656
  scope,
28567
28657
  repoRoot: runtime.cwd,
28568
- userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
28658
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
28659
+ dryRun: runtime.dryRun
28569
28660
  });
28661
+ if (runtime.dryRun) {
28662
+ printPlan(runtime, { command: "workflow remove", workflowId, ...res }, [{ action: "delete", target: res.filePath }], `remove workflow '${workflowId}' from ${res.scope}`);
28663
+ return 0;
28664
+ }
28570
28665
  print(
28571
28666
  runtime.io,
28572
28667
  runtime.json,
@@ -28614,9 +28709,14 @@ async function cmdWorkflowModify(runtime, workflowId, options) {
28614
28709
  {
28615
28710
  scope: options.scope,
28616
28711
  repoRoot: runtime.cwd,
28617
- userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
28712
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
28713
+ dryRun: runtime.dryRun
28618
28714
  }
28619
28715
  );
28716
+ if (runtime.dryRun) {
28717
+ printPlan(runtime, { command: "workflow modify", workflowId, ...res }, [{ action: "write", target: res.filePath }], `modify workflow '${workflowId}' in ${res.scope}`);
28718
+ return 0;
28719
+ }
28620
28720
  print(
28621
28721
  runtime.io,
28622
28722
  runtime.json,
@@ -28652,7 +28752,7 @@ async function cmdWorkflowStart(runtime, definitionIdArg, options) {
28652
28752
  }
28653
28753
  let payload;
28654
28754
  try {
28655
- const raw = payloadFlag.startsWith("@") ? readFileSync18(resolve15(runtime.cwd, payloadFlag.slice(1)), "utf8") : payloadFlag;
28755
+ const raw = payloadFlag.startsWith("@") ? readFileSync18(resolve16(runtime.cwd, payloadFlag.slice(1)), "utf8") : payloadFlag;
28656
28756
  const value = JSON.parse(raw);
28657
28757
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("object required");
28658
28758
  payload = value;
@@ -28719,7 +28819,7 @@ async function cmdWorkflowDegrade(runtime, runId, stageId, options) {
28719
28819
  }
28720
28820
  }
28721
28821
  async function cmdWorkflowInspect(runtime, action, runId) {
28722
- const dataPath = resolve15(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join20(runtime.dirs.state, "kxm.db"));
28822
+ const dataPath = resolve16(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join20(runtime.dirs.state, "kxm.db"));
28723
28823
  if (action === "get" && !runId) {
28724
28824
  runtime.io.stderr(`Usage: ${CLI_NAME} workflow get <runId>
28725
28825
  `);
@@ -28761,7 +28861,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
28761
28861
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
28762
28862
  if (projectRoot && /^run_[a-f0-9]{32}$/i.test(runId)) {
28763
28863
  if (runtime.dryRun) {
28764
- printWorker(runtime, worker, { ok: true, command: "signal", runId, signalKey, status, summary, evidence }, "would post signal to KXM run");
28864
+ printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signal to KXM run");
28765
28865
  return 0;
28766
28866
  }
28767
28867
  const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID7()}`);
@@ -28797,7 +28897,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
28797
28897
  return 2;
28798
28898
  }
28799
28899
  if (runtime.dryRun) {
28800
- printWorker(runtime, worker, { ok: true, command: "signal", runId, signalKey, status, summary, evidence }, "would post signed signal");
28900
+ printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signed signal");
28801
28901
  return 0;
28802
28902
  }
28803
28903
  const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID7()}`);
@@ -28888,14 +28988,14 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
28888
28988
  return 2;
28889
28989
  }
28890
28990
  const snapshotFlag = String(options.input || "");
28891
- const snapshotPath = snapshotFlag ? resolve15(runtime.cwd, snapshotFlag) : "";
28991
+ const snapshotPath = snapshotFlag ? resolve16(runtime.cwd, snapshotFlag) : "";
28892
28992
  let snapshot;
28893
28993
  try {
28894
28994
  if (snapshotPath) {
28895
- if (!existsSync18(snapshotPath)) throw new Error("snapshot_missing");
28995
+ if (!existsSync19(snapshotPath)) throw new Error("snapshot_missing");
28896
28996
  snapshot = JSON.parse(readFileSync18(snapshotPath, "utf8"));
28897
28997
  } else {
28898
- const dataPath = resolve15(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join20(runtime.dirs.state, "kxm.db"));
28998
+ const dataPath = resolve16(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join20(runtime.dirs.state, "kxm.db"));
28899
28999
  const local = localWorkflowSnapshot(dataPath, runId);
28900
29000
  if (!local.runs[0]) throw new Error("workflow_not_found");
28901
29001
  snapshot = { run: local.runs[0], journal: local.journal };
@@ -28907,8 +29007,8 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
28907
29007
  return 1;
28908
29008
  }
28909
29009
  const doc = buildRetrospective(snapshot.run, snapshot.journal);
28910
- const outDir = resolve15(runtime.cwd, String(options.outDir || join20(runtime.dirs.assets, "retrospectives")));
28911
- const assetsRoot = resolve15(runtime.dirs.assets);
29010
+ const outDir = resolve16(runtime.cwd, String(options.outDir || join20(runtime.dirs.assets, "retrospectives")));
29011
+ const assetsRoot = resolve16(runtime.dirs.assets);
28912
29012
  const assetsPrefix = `${assetsRoot}${process.platform === "win32" ? "\\" : "/"}`;
28913
29013
  if (outDir !== assetsRoot && !outDir.startsWith(assetsPrefix)) {
28914
29014
  print(runtime.io, runtime.json, { ok: false, command: "retrospective export", error: "output_outside_workspace_assets" }, "retrospectives must stay under the workspace assets directory");
@@ -28925,8 +29025,8 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
28925
29025
 
28926
29026
  // plugins/kxm/src/cli/tasks.ts
28927
29027
  var import_yaml12 = __toESM(require_dist(), 1);
28928
- import { existsSync as existsSync23, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "node:fs";
28929
- import { basename as basename6, join as join27, resolve as resolve20 } from "node:path";
29028
+ import { existsSync as existsSync25, readFileSync as readFileSync23, readdirSync as readdirSync8 } from "node:fs";
29029
+ import { basename as basename6, join as join27, resolve as resolve21 } from "node:path";
28930
29030
 
28931
29031
  // plugins/kxm/src/suggest.ts
28932
29032
  var WORKFLOW_PATTERNS = [
@@ -29037,19 +29137,23 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
29037
29137
  // plugins/kxm/src/task-manager.ts
29038
29138
  var import_yaml10 = __toESM(require_dist(), 1);
29039
29139
  import { randomUUID as randomUUID8 } from "node:crypto";
29040
- import { existsSync as existsSync19, mkdirSync as mkdirSync15, readFileSync as readFileSync19, readdirSync as readdirSync6, writeFileSync as writeFileSync14 } from "node:fs";
29041
- import { join as join21, resolve as resolve16 } from "node:path";
29140
+ import { existsSync as existsSync20, mkdirSync as mkdirSync15, readFileSync as readFileSync19, readdirSync as readdirSync6, writeFileSync as writeFileSync14 } from "node:fs";
29141
+ import { join as join21, resolve as resolve17 } from "node:path";
29042
29142
  var GOAL_SCHEMA = "kxm.goal.v1";
29043
29143
  var TASK_SCHEMA = "kxm.task.v1";
29044
29144
  function goalsDirectory(repoRoot) {
29045
- return resolve16(repoRoot, ".kxm", "goals");
29145
+ return resolve17(repoRoot, ".kxm", "goals");
29046
29146
  }
29047
29147
  function tasksDirectory(repoRoot) {
29048
- return resolve16(repoRoot, ".kxm", "tasks");
29148
+ return resolve17(repoRoot, ".kxm", "tasks");
29049
29149
  }
29050
- function createGoal(repoRoot, input) {
29051
- const dir = goalsDirectory(repoRoot);
29052
- mkdirSync15(dir, { recursive: true });
29150
+ function goalFilePath(repoRoot, goalId) {
29151
+ return join21(goalsDirectory(repoRoot), `${goalId}.yaml`);
29152
+ }
29153
+ function taskFilePath(repoRoot, taskId) {
29154
+ return join21(tasksDirectory(repoRoot), `${taskId}.yaml`);
29155
+ }
29156
+ function createGoal(repoRoot, input, options = {}) {
29053
29157
  const id = `goal_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
29054
29158
  const now = (/* @__PURE__ */ new Date()).toISOString();
29055
29159
  const record = {
@@ -29063,13 +29167,14 @@ function createGoal(repoRoot, input) {
29063
29167
  createdAt: now,
29064
29168
  updatedAt: now
29065
29169
  };
29066
- const filePath = join21(dir, `${id}.yaml`);
29067
- writeFileSync14(filePath, (0, import_yaml10.stringify)(record).trim() + "\n", "utf8");
29170
+ if (options.dryRun) return record;
29171
+ mkdirSync15(goalsDirectory(repoRoot), { recursive: true });
29172
+ writeFileSync14(goalFilePath(repoRoot, id), (0, import_yaml10.stringify)(record).trim() + "\n", "utf8");
29068
29173
  return record;
29069
29174
  }
29070
29175
  function listGoals(repoRoot) {
29071
29176
  const dir = goalsDirectory(repoRoot);
29072
- if (!existsSync19(dir)) return [];
29177
+ if (!existsSync20(dir)) return [];
29073
29178
  const goals = [];
29074
29179
  const entries = readdirSync6(dir, { withFileTypes: true });
29075
29180
  for (const entry of entries) {
@@ -29084,9 +29189,7 @@ function listGoals(repoRoot) {
29084
29189
  }
29085
29190
  return goals.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
29086
29191
  }
29087
- function createTask(repoRoot, input) {
29088
- const dir = tasksDirectory(repoRoot);
29089
- mkdirSync15(dir, { recursive: true });
29192
+ function createTask(repoRoot, input, options = {}) {
29090
29193
  const id = `task_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
29091
29194
  const now = (/* @__PURE__ */ new Date()).toISOString();
29092
29195
  const record = {
@@ -29108,13 +29211,14 @@ function createTask(repoRoot, input) {
29108
29211
  createdAt: now,
29109
29212
  updatedAt: now
29110
29213
  };
29111
- const filePath = join21(dir, `${id}.yaml`);
29112
- writeFileSync14(filePath, (0, import_yaml10.stringify)(record).trim() + "\n", "utf8");
29214
+ if (options.dryRun) return record;
29215
+ mkdirSync15(tasksDirectory(repoRoot), { recursive: true });
29216
+ writeFileSync14(taskFilePath(repoRoot, id), (0, import_yaml10.stringify)(record).trim() + "\n", "utf8");
29113
29217
  return record;
29114
29218
  }
29115
29219
  function listTasks(repoRoot, options = {}) {
29116
29220
  const dir = tasksDirectory(repoRoot);
29117
- if (!existsSync19(dir)) return [];
29221
+ if (!existsSync20(dir)) return [];
29118
29222
  const tasks = [];
29119
29223
  const entries = readdirSync6(dir, { withFileTypes: true });
29120
29224
  for (const entry of entries) {
@@ -29136,7 +29240,7 @@ function listTasks(repoRoot, options = {}) {
29136
29240
  function getTask(repoRoot, taskId) {
29137
29241
  const dir = tasksDirectory(repoRoot);
29138
29242
  const filePath = join21(dir, `${taskId}.yaml`);
29139
- if (!existsSync19(filePath)) return void 0;
29243
+ if (!existsSync20(filePath)) return void 0;
29140
29244
  try {
29141
29245
  const text = readFileSync19(filePath, "utf8");
29142
29246
  const doc = (0, import_yaml10.parse)(text);
@@ -29153,8 +29257,7 @@ function updateTaskStatus(repoRoot, taskId, status, options = {}) {
29153
29257
  task.status = status;
29154
29258
  if (options.workflowRunId) task.workflowRunId = options.workflowRunId;
29155
29259
  task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
29156
- const filePath = join21(tasksDirectory(repoRoot), `${taskId}.yaml`);
29157
- writeFileSync14(filePath, (0, import_yaml10.stringify)(task).trim() + "\n", "utf8");
29260
+ if (!options.dryRun) writeFileSync14(taskFilePath(repoRoot, taskId), (0, import_yaml10.stringify)(task).trim() + "\n", "utf8");
29158
29261
  return task;
29159
29262
  }
29160
29263
  function syncTaskWithTracker(repoRoot, taskId, options = {}) {
@@ -29169,8 +29272,7 @@ function syncTaskWithTracker(repoRoot, taskId, options = {}) {
29169
29272
  task.trackerSync.syncStatus = "synced";
29170
29273
  task.trackerSync.lastSyncedAt = now;
29171
29274
  task.updatedAt = now;
29172
- const filePath = join21(tasksDirectory(repoRoot), `${taskId}.yaml`);
29173
- writeFileSync14(filePath, (0, import_yaml10.stringify)(task).trim() + "\n", "utf8");
29275
+ if (!options.dryRun) writeFileSync14(taskFilePath(repoRoot, taskId), (0, import_yaml10.stringify)(task).trim() + "\n", "utf8");
29174
29276
  return task;
29175
29277
  }
29176
29278
 
@@ -29936,7 +30038,8 @@ function createStudioServer(options = {}) {
29936
30038
  }
29937
30039
 
29938
30040
  // plugins/kxm/src/cli/project.ts
29939
- import { join as join26, resolve as resolve19 } from "node:path";
30041
+ import { existsSync as existsSync24 } from "node:fs";
30042
+ import { join as join26, resolve as resolve20 } from "node:path";
29940
30043
  import { createInterface as createInterface2 } from "node:readline";
29941
30044
 
29942
30045
  // plugins/kxm/src/model-inventory.ts
@@ -30035,7 +30138,7 @@ async function refreshModelInventory(options) {
30035
30138
 
30036
30139
  // plugins/kxm/src/init.ts
30037
30140
  import { randomUUID as randomUUID11 } from "node:crypto";
30038
- import { existsSync as existsSync22 } from "node:fs";
30141
+ import { existsSync as existsSync23 } from "node:fs";
30039
30142
  import { basename as basename5, join as join25 } from "node:path";
30040
30143
 
30041
30144
  // plugins/kxm/src/repair.ts
@@ -30043,7 +30146,7 @@ import { randomUUID as randomUUID10 } from "node:crypto";
30043
30146
  import {
30044
30147
  chmodSync as chmodSync5,
30045
30148
  closeSync as closeSync2,
30046
- existsSync as existsSync21,
30149
+ existsSync as existsSync22,
30047
30150
  fsyncSync as fsyncSync2,
30048
30151
  linkSync,
30049
30152
  lstatSync as lstatSync7,
@@ -30055,14 +30158,14 @@ import {
30055
30158
  rmSync as rmSync8,
30056
30159
  writeFileSync as writeFileSync17
30057
30160
  } from "node:fs";
30058
- import { dirname as dirname15, isAbsolute as isAbsolute7, join as join24, relative as relative4, resolve as resolve18 } from "node:path";
30161
+ import { dirname as dirname15, isAbsolute as isAbsolute7, join as join24, relative as relative4, resolve as resolve19 } from "node:path";
30059
30162
 
30060
30163
  // plugins/kxm/src/permission.ts
30061
30164
  import { spawnSync as spawnSync4 } from "node:child_process";
30062
30165
  import { createHash as createHash13 } from "node:crypto";
30063
- import { existsSync as existsSync20, mkdtempSync, mkdirSync as mkdirSync17, readFileSync as readFileSync20, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "node:fs";
30166
+ import { existsSync as existsSync21, mkdtempSync, mkdirSync as mkdirSync17, readFileSync as readFileSync20, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "node:fs";
30064
30167
  import { tmpdir } from "node:os";
30065
- import { dirname as dirname14, join as join23, resolve as resolve17, sep as sep3 } from "node:path";
30168
+ import { dirname as dirname14, join as join23, resolve as resolve18, sep as sep3 } from "node:path";
30066
30169
  var PROSE_FIELDS = {
30067
30170
  project: /* @__PURE__ */ new Set(["name", "description"]),
30068
30171
  repository: /* @__PURE__ */ new Set(["description"]),
@@ -30584,7 +30687,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
30584
30687
  const listing = gitBuffer(root, ["ls-tree", "-r", "-z", "--name-only", tree]).toString("utf8");
30585
30688
  const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
30586
30689
  const shadow = mkdtempSync(join23(tmpdir(), "kxm-permission-base-"));
30587
- const shadowResolved = resolve17(shadow);
30690
+ const shadowResolved = resolve18(shadow);
30588
30691
  const shadowMembersDir = `.kxm-shadow-members-${createHash13("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
30589
30692
  try {
30590
30693
  for (const path4 of paths) {
@@ -30598,7 +30701,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
30598
30701
  }]);
30599
30702
  }
30600
30703
  const absolute = join23(shadowResolved, ...segments);
30601
- const resolved = resolve17(absolute);
30704
+ const resolved = resolve18(absolute);
30602
30705
  if (!resolved.startsWith(`${shadowResolved}${sep3}`)) {
30603
30706
  throw new KxmConfigError([{
30604
30707
  phase: "path",
@@ -30642,8 +30745,8 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
30642
30745
  }]);
30643
30746
  }
30644
30747
  const boundPath = options.repositoryBindings?.[member.repositoryId];
30645
- const memberWorktree = boundPath ? resolve17(boundPath) : member.pathHint ? resolve17(root, ...member.pathHint.split("/")) : void 0;
30646
- if (!memberWorktree || !existsSync20(memberWorktree)) {
30748
+ const memberWorktree = boundPath ? resolve18(boundPath) : member.pathHint ? resolve18(root, ...member.pathHint.split("/")) : void 0;
30749
+ if (!memberWorktree || !existsSync21(memberWorktree)) {
30647
30750
  if (member.required === false) continue;
30648
30751
  throw new KxmConfigError([{
30649
30752
  phase: "discovery",
@@ -30691,7 +30794,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
30691
30794
  const memberListing = gitBuffer(memberWorktree, ["ls-tree", "-r", "-z", "--name-only", memberTree]).toString("utf8");
30692
30795
  const memberPaths = memberListing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
30693
30796
  const memberShadowRoot = join23(shadowResolved, shadowMembersDir, member.repositoryId);
30694
- if (!resolve17(memberShadowRoot).startsWith(`${resolve17(shadowResolved, shadowMembersDir)}${sep3}`)) {
30797
+ if (!resolve18(memberShadowRoot).startsWith(`${resolve18(shadowResolved, shadowMembersDir)}${sep3}`)) {
30695
30798
  throw new KxmConfigError([{
30696
30799
  phase: "path",
30697
30800
  code: "git_tree_path_invalid",
@@ -30705,8 +30808,8 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
30705
30808
  throw new KxmConfigError([{ phase: "path", code: "git_tree_path_invalid", file: path4, message: "member Git tree entry contains a traversal or non-portable segment" }]);
30706
30809
  }
30707
30810
  const absolute = join23(memberShadowRoot, ...segments);
30708
- const resolved = resolve17(absolute);
30709
- if (!resolved.startsWith(`${resolve17(memberShadowRoot)}${sep3}`)) {
30811
+ const resolved = resolve18(absolute);
30812
+ if (!resolved.startsWith(`${resolve18(memberShadowRoot)}${sep3}`)) {
30710
30813
  throw new KxmConfigError([{ phase: "path", code: "git_tree_path_invalid", file: path4, message: "member Git tree entry escapes the shadow root" }]);
30711
30814
  }
30712
30815
  const content = gitBuffer(memberWorktree, ["cat-file", "blob", `${memberTree}:${path4}`]);
@@ -30732,7 +30835,7 @@ function portableMemberPathHint(pathHint) {
30732
30835
  return pathHint !== "." && kxmPortablePath(pathHint);
30733
30836
  }
30734
30837
  function loadBaseProjectDeclarations(projectFile) {
30735
- if (!existsSync20(projectFile)) return [];
30838
+ if (!existsSync21(projectFile)) return [];
30736
30839
  const value = parseRestrictedYaml2(readFileSync20(projectFile, "utf8"), ".kxm/project.yaml");
30737
30840
  const repositories = Array.isArray(value?.repositories) ? value.repositories : [];
30738
30841
  const members = [];
@@ -30856,12 +30959,12 @@ function writeDurable(file, bytes, mode) {
30856
30959
  syncDirectory2(dirname15(file));
30857
30960
  }
30858
30961
  function sameHostPath3(left, right) {
30859
- const first = resolve18(left);
30860
- const second = resolve18(right);
30962
+ const first = resolve19(left);
30963
+ const second = resolve19(right);
30861
30964
  return process.platform === "win32" ? first.toLocaleLowerCase("en-US") === second.toLocaleLowerCase("en-US") : first === second;
30862
30965
  }
30863
30966
  function transactionRoot(projectRoot) {
30864
- return join24(resolve18(projectRoot), TRANSACTION_NAME);
30967
+ return join24(resolve19(projectRoot), TRANSACTION_NAME);
30865
30968
  }
30866
30969
  function operationFile(projectRoot) {
30867
30970
  return join24(transactionRoot(projectRoot), OPERATION_FILE);
@@ -30886,9 +30989,9 @@ function readRegularBounded(file, label) {
30886
30989
  }
30887
30990
  function readOptionalManaged(projectRoot, path4) {
30888
30991
  const file = join24(projectRoot, ...path4.split("/"));
30889
- if (!existsSync21(file)) return void 0;
30992
+ if (!existsSync22(file)) return void 0;
30890
30993
  let parent = dirname15(file);
30891
- const boundary = resolve18(projectRoot);
30994
+ const boundary = resolve19(projectRoot);
30892
30995
  while (parent !== boundary) {
30893
30996
  const stat = lstatSync7(parent);
30894
30997
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_parent_invalid", path4, "managed file parents must be regular directories", "path");
@@ -30899,7 +31002,7 @@ function readOptionalManaged(projectRoot, path4) {
30899
31002
  function readProjectAndProvenance(projectRoot, schemasDir) {
30900
31003
  const projectFile = join24(projectRoot, ".kxm", "project.yaml");
30901
31004
  const provenanceFile = join24(projectRoot, ...KXM_TEMPLATE_PROVENANCE_PATH.split("/"));
30902
- if (!existsSync21(projectFile) || !existsSync21(provenanceFile)) return void 0;
31005
+ if (!existsSync22(projectFile) || !existsSync22(provenanceFile)) return void 0;
30903
31006
  const projectBytes = readOptionalManaged(projectRoot, ".kxm/project.yaml");
30904
31007
  const provenanceBytes = readOptionalManaged(projectRoot, KXM_TEMPLATE_PROVENANCE_PATH);
30905
31008
  if (!projectBytes || !provenanceBytes) return void 0;
@@ -30935,7 +31038,7 @@ function readProjectAndProvenance(projectRoot, schemasDir) {
30935
31038
  }
30936
31039
  function planKxmTemplateRepair(projectRoot, options = {}) {
30937
31040
  assertNoRegisteredGates(options);
30938
- const root = resolve18(projectRoot);
31041
+ const root = resolve19(projectRoot);
30939
31042
  const source = readProjectAndProvenance(root, options.schemasDir);
30940
31043
  if (!source) return void 0;
30941
31044
  const target = renderKxmTemplate(
@@ -31080,11 +31183,11 @@ function updatePhase(projectRoot, operation, phase) {
31080
31183
  }
31081
31184
  function readOperation(projectRoot, schemasDir) {
31082
31185
  const root = transactionRoot(projectRoot);
31083
- if (!existsSync21(root)) return void 0;
31186
+ if (!existsSync22(root)) return void 0;
31084
31187
  const stat = lstatSync7(root);
31085
31188
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("init_transaction_invalid", TRANSACTION_NAME, "initialization transaction must be a regular directory", "path");
31086
31189
  const file = operationFile(projectRoot);
31087
- if (!existsSync21(file)) {
31190
+ if (!existsSync22(file)) {
31088
31191
  const entries = readdirSync7(root, { withFileTypes: true });
31089
31192
  const recoverable = entries.every((entry) => entry.isFile() && /^\.operation-op_[a-f0-9]{32}-[a-f0-9-]{36}\.tmp$/.test(entry.name));
31090
31193
  if (recoverable) return void 0;
@@ -31112,7 +31215,7 @@ function readOperation(projectRoot, schemasDir) {
31112
31215
  return operation;
31113
31216
  }
31114
31217
  function hasKxmInitTransaction(projectRoot) {
31115
- return existsSync21(transactionRoot(projectRoot));
31218
+ return existsSync22(transactionRoot(projectRoot));
31116
31219
  }
31117
31220
  function readTarget(projectRoot, path4) {
31118
31221
  return readOptionalManaged(join24(transactionRoot(projectRoot), "targets"), path4);
@@ -31189,10 +31292,10 @@ function sourceConfigFiles(projectRoot) {
31189
31292
  ".kxm/repo/repo.yaml",
31190
31293
  ".kxm/repo/env.yaml"
31191
31294
  ];
31192
- const files = fixed.filter((path4) => existsSync21(join24(projectRoot, ...path4.split("/"))));
31295
+ const files = fixed.filter((path4) => existsSync22(join24(projectRoot, ...path4.split("/"))));
31193
31296
  for (const directory of ["agents", "models", "workflows"]) {
31194
31297
  const absolute = join24(projectRoot, ".kxm", directory);
31195
- if (!existsSync21(absolute)) continue;
31298
+ if (!existsSync22(absolute)) continue;
31196
31299
  const stat = lstatSync7(absolute);
31197
31300
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_directory_invalid", `.kxm/${directory}`, "configuration directory must be regular", "path");
31198
31301
  for (const entry of readdirSync7(absolute, { withFileTypes: true })) {
@@ -31213,8 +31316,8 @@ function absoluteMemberBindings(projectRoot, project, explicit = {}) {
31213
31316
  const repository = candidate;
31214
31317
  if (repository.role !== "member" || typeof repository.id !== "string" || Object.hasOwn(bindings, repository.id)) continue;
31215
31318
  if (typeof repository.pathHint === "string") {
31216
- const candidate2 = resolve18(projectRoot, ...repository.pathHint.split("/"));
31217
- if (existsSync21(candidate2)) bindings[repository.id] = candidate2;
31319
+ const candidate2 = resolve19(projectRoot, ...repository.pathHint.split("/"));
31320
+ if (existsSync22(candidate2)) bindings[repository.id] = candidate2;
31218
31321
  }
31219
31322
  }
31220
31323
  return bindings;
@@ -31240,7 +31343,7 @@ function prepareShadow(projectRoot, operation, options) {
31240
31343
  if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
31241
31344
  const destination = join24(shadow, ...entry.path.split("/"));
31242
31345
  mkdirSync18(dirname15(destination), { recursive: true, mode: 448 });
31243
- const mode = existsSync21(destination) ? lstatSync7(destination).mode & 511 : 420;
31346
+ const mode = existsSync22(destination) ? lstatSync7(destination).mode & 511 : 420;
31244
31347
  writeDurable(destination, bytes, mode);
31245
31348
  }
31246
31349
  const project = parseRestrictedYaml2(readFileSync21(join24(shadow, ".kxm", "project.yaml")), ".kxm/project.yaml");
@@ -31252,7 +31355,7 @@ function prepareShadow(projectRoot, operation, options) {
31252
31355
  }
31253
31356
  }
31254
31357
  function createOperation(projectRoot, kind, rendered, files, sourceTemplateRevision) {
31255
- const root = resolve18(projectRoot);
31358
+ const root = resolve19(projectRoot);
31256
31359
  const operationWithoutHash = {
31257
31360
  schema: "kxm.init-operation.v1",
31258
31361
  operationId: `op_${randomUUID10().replaceAll("-", "")}`,
@@ -31281,7 +31384,7 @@ function prepareOperation(projectRoot, operation, rendered, options) {
31281
31384
  const transaction = transactionRoot(projectRoot);
31282
31385
  validateOperationRecord(operation, options.schemasDir);
31283
31386
  validateOperationIntent(projectRoot, operation);
31284
- if (existsSync21(transaction)) fail3("init_transaction_exists", TRANSACTION_NAME, "an initialization transaction already exists and must be resumed");
31387
+ if (existsSync22(transaction)) fail3("init_transaction_exists", TRANSACTION_NAME, "an initialization transaction already exists and must be resumed");
31285
31388
  mkdirSync18(transaction, { mode: 448 });
31286
31389
  syncDirectory2(projectRoot);
31287
31390
  writeOperation(projectRoot, operation);
@@ -31303,19 +31406,19 @@ function prepareOperation(projectRoot, operation, rendered, options) {
31303
31406
  }
31304
31407
  }
31305
31408
  function ensureDestinationParents(projectRoot, path4) {
31306
- const boundary = resolve18(projectRoot);
31409
+ const boundary = resolve19(projectRoot);
31307
31410
  const segments = path4.split("/").slice(0, -1);
31308
31411
  let current = boundary;
31309
31412
  for (const segment of segments) {
31310
31413
  current = join24(current, segment);
31311
- if (!existsSync21(current)) mkdirSync18(current, { mode: 448 });
31414
+ if (!existsSync22(current)) mkdirSync18(current, { mode: 448 });
31312
31415
  const stat = lstatSync7(current);
31313
31416
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_destination_parent_invalid", path4, "destination parent must be a regular directory", "path");
31314
31417
  }
31315
31418
  }
31316
31419
  function destinationSha(projectRoot, path4) {
31317
31420
  const file = join24(projectRoot, ...path4.split("/"));
31318
- if (!existsSync21(file)) return void 0;
31421
+ if (!existsSync22(file)) return void 0;
31319
31422
  return kxmContentSha256(readRegularBounded(file, path4));
31320
31423
  }
31321
31424
  function requireConditionalLinkSupport(directory, operationId, path4) {
@@ -31342,13 +31445,13 @@ function atomicInstallTarget(projectRoot, operation, entry) {
31342
31445
  const temporaryPrefix = `.kxm-repair-${operation.operationId}-`;
31343
31446
  const pathKey = kxmContentSha256(entry.path).slice("sha256:".length, "sha256:".length + 16);
31344
31447
  const displaced = join24(directory, `${temporaryPrefix}${pathKey}.preimage`);
31345
- if (existsSync21(displaced)) {
31448
+ if (existsSync22(displaced)) {
31346
31449
  const stat = lstatSync7(displaced);
31347
31450
  if (stat.isSymbolicLink() || !stat.isFile()) fail3("repair_preimage_temporary_invalid", entry.path, "stale displaced preimage is not a regular file", "path");
31348
31451
  if (entry.observedSha256 === null || kxmContentSha256(readFileSync21(displaced)) !== entry.observedSha256) {
31349
31452
  fail3("repair_preimage_temporary_mismatch", entry.path, "stale displaced preimage does not match the pinned operation");
31350
31453
  }
31351
- if (!existsSync21(destination)) {
31454
+ if (!existsSync22(destination)) {
31352
31455
  try {
31353
31456
  linkSync(displaced, destination);
31354
31457
  } catch {
@@ -31374,14 +31477,14 @@ function atomicInstallTarget(projectRoot, operation, entry) {
31374
31477
  const bytes = readTarget(projectRoot, entry.path);
31375
31478
  if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
31376
31479
  const temporary = join24(directory, `${temporaryPrefix}${randomUUID10()}.tmp`);
31377
- const mode = existsSync21(destination) ? lstatSync7(destination).mode & 511 : 420;
31480
+ const mode = existsSync22(destination) ? lstatSync7(destination).mode & 511 : 420;
31378
31481
  try {
31379
31482
  writeDurableNew(temporary, bytes, mode);
31380
31483
  if (entry.observedSha256 !== null) {
31381
31484
  renameSync6(destination, displaced);
31382
31485
  syncDirectory2(directory);
31383
31486
  if (kxmContentSha256(readFileSync21(displaced)) !== entry.observedSha256) {
31384
- if (!existsSync21(destination)) {
31487
+ if (!existsSync22(destination)) {
31385
31488
  try {
31386
31489
  linkSync(displaced, destination);
31387
31490
  } catch {
@@ -31397,7 +31500,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
31397
31500
  try {
31398
31501
  linkSync(temporary, destination);
31399
31502
  } catch {
31400
- if (!existsSync21(destination) && existsSync21(displaced)) {
31503
+ if (!existsSync22(destination) && existsSync22(displaced)) {
31401
31504
  try {
31402
31505
  linkSync(displaced, destination);
31403
31506
  } catch {
@@ -31420,7 +31523,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
31420
31523
  }
31421
31524
  function installedCreateMatches(projectRoot, operation) {
31422
31525
  const configRoot = join24(projectRoot, ".kxm");
31423
- if (!existsSync21(configRoot)) return false;
31526
+ if (!existsSync22(configRoot)) return false;
31424
31527
  const actual = [];
31425
31528
  const visit2 = (directory) => {
31426
31529
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
@@ -31437,10 +31540,10 @@ function installedCreateMatches(projectRoot, operation) {
31437
31540
  return actual.length === expected.length && actual.every((path4, index) => path4 === expected[index]) && operation.files.every((entry) => destinationSha(projectRoot, entry.path) === entry.targetSha256);
31438
31541
  }
31439
31542
  function relativeConfigPath(projectRoot, file) {
31440
- return relative4(resolve18(projectRoot), file).replaceAll("\\", "/");
31543
+ return relative4(resolve19(projectRoot), file).replaceAll("\\", "/");
31441
31544
  }
31442
31545
  function syncTreeDirectories(root) {
31443
- if (!existsSync21(root)) return;
31546
+ if (!existsSync22(root)) return;
31444
31547
  const directories = [];
31445
31548
  const visit2 = (directory) => {
31446
31549
  directories.push(directory);
@@ -31470,7 +31573,7 @@ function applyOperation(projectRoot, original, options, resumed) {
31470
31573
  operation = updatePhase(projectRoot, operation, "applying");
31471
31574
  if (operation.kind === "create") {
31472
31575
  const destination = join24(projectRoot, ".kxm");
31473
- if (!existsSync21(destination)) {
31576
+ if (!existsSync22(destination)) {
31474
31577
  rebuildCreateShadow(projectRoot, operation, effectiveOptions);
31475
31578
  renameSync6(join24(shadowRoot(projectRoot), ".kxm"), destination);
31476
31579
  syncDirectory2(projectRoot);
@@ -31633,7 +31736,7 @@ function resumeKxmInitTransaction(projectRoot, options = {}) {
31633
31736
  let operation = readOperation(projectRoot, options.schemasDir);
31634
31737
  if (!operation) {
31635
31738
  const root = transactionRoot(projectRoot);
31636
- if (existsSync21(root)) {
31739
+ if (existsSync22(root)) {
31637
31740
  rmSync8(root, { recursive: true, force: true });
31638
31741
  syncDirectory2(projectRoot);
31639
31742
  }
@@ -31758,7 +31861,7 @@ function validateBindingIdentities(bundle, persisted, explicit) {
31758
31861
  }
31759
31862
  function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
31760
31863
  const storeOptions = bindingStoreOptions(options);
31761
- const persisted = existsSync22(join25(gitRoot, ".kxm", "project.yaml")) ? readKxmLocalBindings(gitRoot, storeOptions) : void 0;
31864
+ const persisted = existsSync23(join25(gitRoot, ".kxm", "project.yaml")) ? readKxmLocalBindings(gitRoot, storeOptions) : void 0;
31762
31865
  const repositoryBindings = Object.fromEntries([
31763
31866
  ...Object.entries(persisted?.repositories ?? {}),
31764
31867
  ...Object.entries(options.repositoryBindings ?? {})
@@ -31946,7 +32049,7 @@ function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
31946
32049
  const files = [...rendered.files.keys()];
31947
32050
  if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, files };
31948
32051
  if (!mutationLock) throw new Error("project mutation lock is required to create configuration");
31949
- if (existsSync22(join25(gitRoot, ".kxm"))) {
32052
+ if (existsSync23(join25(gitRoot, ".kxm"))) {
31950
32053
  throw new KxmConfigError([initIssue("workspace_changed", ".kxm", "workspace changed after planning; existing .kxm state was not overwritten")]);
31951
32054
  }
31952
32055
  const created = prepareAndApplyKxmCreate(gitRoot, rendered, transactionOptions);
@@ -32264,10 +32367,24 @@ async function cmdKxmInit(runtime, options, postHooks) {
32264
32367
  }
32265
32368
  async function cmdBackup(runtime, options) {
32266
32369
  try {
32267
- const { manifest, outDir } = createBackup({
32370
+ const backupOptions = {
32268
32371
  projectRoot: runtime.cwd,
32269
- ...options.out ? { outDir: resolve19(runtime.cwd, options.out) } : {}
32270
- });
32372
+ ...options.out ? { outDir: resolve20(runtime.cwd, options.out) } : {}
32373
+ };
32374
+ if (runtime.dryRun) {
32375
+ const plan = planBackup(backupOptions);
32376
+ printPlan(
32377
+ runtime,
32378
+ { command: "backup", outDir: plan.outDir, stores: plan.stores },
32379
+ [
32380
+ ...plan.stores.map((store) => ({ action: "write", target: join26(plan.outDir, store.backupFile) })),
32381
+ { action: "write", target: join26(plan.outDir, "manifest.json") }
32382
+ ],
32383
+ `back up ${plan.stores.length} store(s) to ${plan.outDir} (sources are not opened, so their WAL is not checkpointed)`
32384
+ );
32385
+ return 0;
32386
+ }
32387
+ const { manifest, outDir } = createBackup(backupOptions);
32271
32388
  const payload = {
32272
32389
  ok: true,
32273
32390
  command: "backup",
@@ -32293,7 +32410,25 @@ async function cmdBackup(runtime, options) {
32293
32410
  }
32294
32411
  async function cmdRestore(runtime, manifestArg) {
32295
32412
  try {
32296
- const result = restoreBackup(resolve19(runtime.cwd, manifestArg), {
32413
+ if (runtime.dryRun) {
32414
+ const plan = planRestore(resolve20(runtime.cwd, manifestArg), { projectRoot: runtime.cwd });
32415
+ printPlan(
32416
+ runtime,
32417
+ {
32418
+ command: "restore",
32419
+ backupId: plan.backupId,
32420
+ manifestPath: plan.manifestPath,
32421
+ stores: plan.stores.map(({ storeId, targetPath, schemaVersion }) => ({ storeId, targetPath, schemaVersion }))
32422
+ },
32423
+ plan.stores.flatMap((store) => [
32424
+ { action: "write", target: store.targetPath },
32425
+ ...[`${store.targetPath}-wal`, `${store.targetPath}-shm`].filter((file) => existsSync24(file)).map((file) => ({ action: "delete", target: file }))
32426
+ ]),
32427
+ `restore ${plan.stores.length} store(s) from ${plan.manifestPath}; digests verified against the manifest`
32428
+ );
32429
+ return 0;
32430
+ }
32431
+ const result = restoreBackup(resolve20(runtime.cwd, manifestArg), {
32297
32432
  projectRoot: runtime.cwd
32298
32433
  });
32299
32434
  const payload = {
@@ -32362,9 +32497,14 @@ trust check failed: review every expansion above before merging`);
32362
32497
  return 1;
32363
32498
  }
32364
32499
  }
32500
+ async function readSupervisor(runtime, command) {
32501
+ if (!runtime.dryRun) return await (kxmDriveCliSeams.ensureSupervisor ?? ensureKxmSupervisor)({ env: runtime.env });
32502
+ const attached = await attachKxmSupervisor({ env: runtime.env });
32503
+ return attached ?? refuseDryRun(runtime.io, runtime.json, command, "the Runtime supervisor is not running and --dry-run will not start it");
32504
+ }
32365
32505
  var RUN_ENGINE_PHASE = "pre-3a";
32366
32506
  var RUN_ENGINE_NOTICE = "runs remain created until the run engine lands; no steps execute yet";
32367
- async function cmdKxmRun(runtime, workflow, promptParts) {
32507
+ function resolveKxmRunTarget(runtime, workflow) {
32368
32508
  if (runtime.workspaceFlag !== void 0) {
32369
32509
  print(runtime.io, runtime.json, {
32370
32510
  ok: false,
@@ -32377,33 +32517,37 @@ async function cmdKxmRun(runtime, workflow, promptParts) {
32377
32517
  print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required" }, "usage: kxm run <workflow> [prompt]");
32378
32518
  return 2;
32379
32519
  }
32520
+ const projectRoot = discoverKxmProjectRoot(runtime.cwd);
32521
+ if (!projectRoot) {
32522
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required" }, "kxm run requires a KXM project (run kxm init first)");
32523
+ return 1;
32524
+ }
32525
+ const bundle = loadKxmProject(projectRoot, {});
32526
+ if (!bundle.workflows.has(workflow)) {
32527
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow }, `workflow ${workflow} does not exist in this project`);
32528
+ return 1;
32529
+ }
32530
+ return { projectRoot, workflowId: workflow, configRevision: bundle.configRevision };
32531
+ }
32532
+ async function cmdKxmRun(runtime, workflow, promptParts) {
32380
32533
  try {
32381
- const projectRoot = discoverKxmProjectRoot(runtime.cwd);
32382
- if (!projectRoot) {
32383
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required" }, "kxm run requires a KXM project (run kxm init first)");
32384
- return 1;
32385
- }
32386
- const bundle = loadKxmProject(projectRoot, {});
32387
- if (!bundle.workflows.has(workflow)) {
32388
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow }, `workflow ${workflow} does not exist in this project`);
32389
- return 1;
32390
- }
32534
+ const target = resolveKxmRunTarget(runtime, workflow);
32535
+ if (typeof target === "number") return target;
32536
+ const { projectRoot } = target;
32391
32537
  if (runtime.dryRun) {
32392
32538
  print(runtime.io, runtime.json, {
32393
32539
  ok: true,
32394
32540
  command: "run",
32395
32541
  dryRun: true,
32396
- projectRoot,
32397
- workflowId: workflow,
32398
- configRevision: bundle.configRevision
32399
- }, `run plan: workflow ${workflow} at ${bundle.configRevision.slice(0, 19)}\u2026 (no run created)`);
32542
+ ...target
32543
+ }, `run plan: workflow ${target.workflowId} at ${target.configRevision.slice(0, 19)}\u2026 (no run created)`);
32400
32544
  return 0;
32401
32545
  }
32402
32546
  const supervisor = await ensureKxmSupervisor({ env: runtime.env });
32403
32547
  const prompt = promptParts.join(" ").trim();
32404
32548
  const acceptance = await kxmRuntimeRequest(supervisor, "POST", "/v1/runs", {
32405
32549
  projectRoot,
32406
- workflowId: workflow,
32550
+ workflowId: target.workflowId,
32407
32551
  prompt
32408
32552
  });
32409
32553
  const run = acceptance.run;
@@ -32467,7 +32611,8 @@ async function cmdKxmRunStatus(runtime, runId) {
32467
32611
  print(runtime.io, runtime.json, { ok: false, command: "runs status", error: "project_required" }, "kxm runs status requires a KXM project (run kxm init first)");
32468
32612
  return 1;
32469
32613
  }
32470
- const supervisor = await (kxmDriveCliSeams.ensureSupervisor ?? ensureKxmSupervisor)({ env: runtime.env });
32614
+ const supervisor = await readSupervisor(runtime, "runs status");
32615
+ if (typeof supervisor === "number") return supervisor;
32471
32616
  const result = await (kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest)(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
32472
32617
  const run = result.run;
32473
32618
  const drive = result.drive;
@@ -32498,7 +32643,7 @@ function parseDriveWaitTimeoutMs(raw) {
32498
32643
  return value;
32499
32644
  }
32500
32645
  function sleepMs(ms) {
32501
- return new Promise((resolve30) => setTimeout(resolve30, ms));
32646
+ return new Promise((resolve32) => setTimeout(resolve32, ms));
32502
32647
  }
32503
32648
  function driveWaitCompleted(drive) {
32504
32649
  return drive?.verified === true && drive.receipt?.settlement?.kind === "terminal" && drive.receipt.settlement.status === "completed";
@@ -32613,7 +32758,8 @@ async function cmdKxmRunList(runtime) {
32613
32758
  }
32614
32759
  const bundle = loadKxmProject(projectRoot, {});
32615
32760
  const projectId = String(bundle.project.value.id);
32616
- const supervisor = await ensureKxmSupervisor({ env: runtime.env });
32761
+ const supervisor = await readSupervisor(runtime, "runs list");
32762
+ if (typeof supervisor === "number") return supervisor;
32617
32763
  const result = await kxmRuntimeRequest(supervisor, "GET", `/v1/projects/${encodeURIComponent(projectId)}/runs?projectRoot=${encodeURIComponent(projectRoot)}`);
32618
32764
  const runs = result.runs ?? [];
32619
32765
  print(
@@ -32704,7 +32850,7 @@ async function selectInventoryModel(runtime, requested) {
32704
32850
  if (runtime.json || !process.stdin.isTTY || models.length === 0) return void 0;
32705
32851
  runtime.io.stdout(models.slice(0, 100).map((model, index2) => `${index2 + 1}. ${model}`).join("\n") + "\nSelect model number: ");
32706
32852
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
32707
- const answer = await new Promise((resolve30) => rl.once("line", resolve30));
32853
+ const answer = await new Promise((resolve32) => rl.once("line", resolve32));
32708
32854
  rl.close();
32709
32855
  const index = Number.parseInt(answer.trim(), 10) - 1;
32710
32856
  return Number.isInteger(index) && index >= 0 && index < models.length ? models[index] : void 0;
@@ -32730,7 +32876,7 @@ async function cmdModelsScreen(runtime) {
32730
32876
  return 2;
32731
32877
  }
32732
32878
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
32733
- const ask = (q2) => new Promise((resolve30) => rl.question(q2, resolve30));
32879
+ const ask = (q2) => new Promise((resolve32) => rl.question(q2, resolve32));
32734
32880
  try {
32735
32881
  while (true) {
32736
32882
  const policy = loadRoutePolicy(runtime.dirs.workdir);
@@ -32872,7 +33018,8 @@ async function cmdKxmRunReceipt(runtime, runId, options = {}) {
32872
33018
  print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "project_required" }, "kxm runs receipt requires a KXM project (run kxm init first)");
32873
33019
  return 1;
32874
33020
  }
32875
- const supervisor = await (kxmDriveCliSeams.ensureSupervisor ?? ensureKxmSupervisor)({ env: runtime.env });
33021
+ const supervisor = await readSupervisor(runtime, "runs receipt");
33022
+ if (typeof supervisor === "number") return supervisor;
32876
33023
  const result = await (kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest)(
32877
33024
  supervisor,
32878
33025
  "GET",
@@ -32949,7 +33096,11 @@ async function cmdGoalCreate(runtime, title, options) {
32949
33096
  area: options.area,
32950
33097
  successMetrics: options.metric,
32951
33098
  targetDate: options.targetDate
32952
- });
33099
+ }, { dryRun: runtime.dryRun });
33100
+ if (runtime.dryRun) {
33101
+ printPlan(runtime, { command: "goal create", goal }, [{ action: "write", target: goalFilePath(runtime.cwd, goal.id) }], `create goal ${goal.title} (the id is assigned when it is created)`);
33102
+ return 0;
33103
+ }
32953
33104
  print(
32954
33105
  runtime.io,
32955
33106
  runtime.json,
@@ -32985,7 +33136,11 @@ async function cmdTaskCreate(runtime, title, options) {
32985
33136
  objective: options.objective ?? title,
32986
33137
  assignedWorkflow: options.workflow,
32987
33138
  trackerSync: options.tracker && options.issue ? { tracker: options.tracker, issueKey: options.issue } : void 0
32988
- });
33139
+ }, { dryRun: runtime.dryRun });
33140
+ if (runtime.dryRun) {
33141
+ printPlan(runtime, { command: "task create", task }, [{ action: "write", target: taskFilePath(runtime.cwd, task.id) }], `create task ${task.title} [${task.status}] (the id is assigned when it is created)`);
33142
+ return 0;
33143
+ }
32989
33144
  print(
32990
33145
  runtime.io,
32991
33146
  runtime.json,
@@ -33051,6 +33206,21 @@ async function cmdTaskRun(runtime, taskId) {
33051
33206
  return 1;
33052
33207
  }
33053
33208
  const workflow = task.assignedWorkflow ?? "default";
33209
+ if (runtime.dryRun) {
33210
+ const target = resolveKxmRunTarget(runtime, workflow);
33211
+ if (typeof target === "number") return target;
33212
+ const started = updateTaskStatus(runtime.cwd, taskId, "in_progress", { dryRun: true });
33213
+ printPlan(
33214
+ runtime,
33215
+ { command: "task run", taskId, ...target, status: started.status },
33216
+ [
33217
+ { action: "request", target: "POST kxm-runtime /v1/runs (starts the Runtime supervisor if it is not running)" },
33218
+ { action: "write", target: taskFilePath(runtime.cwd, taskId) }
33219
+ ],
33220
+ `run workflow ${workflow} for task ${taskId}, then mark it ${started.status}`
33221
+ );
33222
+ return 0;
33223
+ }
33054
33224
  const exitCode = await cmdKxmRun(runtime, workflow, [task.objective]);
33055
33225
  if (exitCode === 0) {
33056
33226
  updateTaskStatus(runtime.cwd, taskId, "in_progress");
@@ -33065,7 +33235,11 @@ async function cmdTaskRun(runtime, taskId) {
33065
33235
  }
33066
33236
  async function cmdTaskSync(runtime, taskId) {
33067
33237
  try {
33068
- const synced = syncTaskWithTracker(runtime.cwd, taskId);
33238
+ const synced = syncTaskWithTracker(runtime.cwd, taskId, { dryRun: runtime.dryRun });
33239
+ if (runtime.dryRun) {
33240
+ printPlan(runtime, { command: "task sync", task: synced }, [{ action: "write", target: taskFilePath(runtime.cwd, taskId) }], `mark task ${taskId} synced with ${synced.trackerSync?.tracker} #${synced.trackerSync?.issueKey}`);
33241
+ return 0;
33242
+ }
33069
33243
  print(
33070
33244
  runtime.io,
33071
33245
  runtime.json,
@@ -33085,12 +33259,12 @@ async function cmdStudioLayout(runtime, workflowPath) {
33085
33259
  const projectRoot = discoverKxmProjectRoot(runtime.cwd) ?? runtime.cwd;
33086
33260
  let filePath = workflowPath;
33087
33261
  if (!filePath) {
33088
- filePath = resolve20(projectRoot, ".kxm", "workflows", "default.yaml");
33262
+ filePath = resolve21(projectRoot, ".kxm", "workflows", "default.yaml");
33089
33263
  }
33090
33264
  let yamlContent;
33091
33265
  let workflowId = "default";
33092
- if (existsSync23(filePath)) {
33093
- yamlContent = readFileSync22(filePath, "utf8");
33266
+ if (existsSync25(filePath)) {
33267
+ yamlContent = readFileSync23(filePath, "utf8");
33094
33268
  } else {
33095
33269
  yamlContent = `schema: kxm.workflow.v1
33096
33270
  description: Feature implementation workflow
@@ -33165,13 +33339,13 @@ async function cmdStudioServe(runtime, options) {
33165
33339
  try {
33166
33340
  const wfDir = join27(runtime.cwd, ".kxm", "workflows");
33167
33341
  const defaultPath = join27(wfDir, "default.yaml");
33168
- let targetPath = existsSync23(defaultPath) ? defaultPath : void 0;
33169
- if (!targetPath && existsSync23(wfDir)) {
33342
+ let targetPath = existsSync25(defaultPath) ? defaultPath : void 0;
33343
+ if (!targetPath && existsSync25(wfDir)) {
33170
33344
  const yml = readdirSync8(wfDir).find((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
33171
33345
  if (yml) targetPath = join27(wfDir, yml);
33172
33346
  }
33173
- if (targetPath && existsSync23(targetPath)) {
33174
- const raw = readFileSync22(targetPath, "utf8");
33347
+ if (targetPath && existsSync25(targetPath)) {
33348
+ const raw = readFileSync23(targetPath, "utf8");
33175
33349
  const parsed = (0, import_yaml12.parse)(raw);
33176
33350
  const wfId = basename6(targetPath).replace(/\.(yaml|yml)$/, "");
33177
33351
  return compileKxmWorkflow({ id: wfId, value: parsed });
@@ -33221,14 +33395,14 @@ Press Ctrl+C to stop.
33221
33395
  }
33222
33396
 
33223
33397
  // plugins/kxm/src/cli/context-skills.ts
33224
- import { readFileSync as readFileSync25 } from "node:fs";
33225
- import { join as join31, relative as relative5 } from "node:path";
33398
+ import { readFileSync as readFileSync26 } from "node:fs";
33399
+ import { join as join31, relative as relative5, resolve as resolve23 } from "node:path";
33226
33400
 
33227
33401
  // plugins/kxm/src/memory.ts
33228
33402
  var import_yaml13 = __toESM(require_dist(), 1);
33229
33403
  import { randomUUID as randomUUID12 } from "node:crypto";
33230
- import { existsSync as existsSync24, mkdirSync as mkdirSync19, readdirSync as readdirSync9, readFileSync as readFileSync23, writeFileSync as writeFileSync18 } from "node:fs";
33231
- import { extname as extname2, join as join28, resolve as resolve21 } from "node:path";
33404
+ import { existsSync as existsSync26, mkdirSync as mkdirSync20, readdirSync as readdirSync9, readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "node:fs";
33405
+ import { extname as extname2, join as join28, resolve as resolve22 } from "node:path";
33232
33406
  var MEMORY_SCHEMA = "kxm.memory.v1";
33233
33407
  var MEMORY_BRIEF_SCHEMA = "kxm.memory-brief.v1";
33234
33408
  var MEMORY_MARKER_START = "<!-- kxm:memory:start -->";
@@ -33354,21 +33528,21 @@ ${yamlText}
33354
33528
  `;
33355
33529
  }
33356
33530
  function memoryDirectories(repoRoot) {
33357
- const root = resolve21(repoRoot);
33531
+ const root = resolve22(repoRoot);
33358
33532
  const memoryDir = join28(root, ".kxm", "memory");
33359
33533
  const candidatesDir = join28(memoryDir, "candidates");
33360
33534
  return { memoryDir, candidatesDir };
33361
33535
  }
33362
33536
  function loadAuthoredMemory(repoRoot) {
33363
33537
  const { memoryDir } = memoryDirectories(repoRoot);
33364
- if (!existsSync24(memoryDir)) return [];
33538
+ if (!existsSync26(memoryDir)) return [];
33365
33539
  const records = [];
33366
33540
  const entries = readdirSync9(memoryDir, { withFileTypes: true });
33367
33541
  for (const entry of entries) {
33368
33542
  if (entry.isFile() && extname2(entry.name) === ".md") {
33369
33543
  const fullPath = join28(memoryDir, entry.name);
33370
33544
  try {
33371
- const text = readFileSync23(fullPath, "utf8");
33545
+ const text = readFileSync24(fullPath, "utf8");
33372
33546
  const record = parseMemoryRecord(text, entry.name);
33373
33547
  if (record.lifecycle === "active") {
33374
33548
  records.push(record);
@@ -33382,7 +33556,6 @@ function loadAuthoredMemory(repoRoot) {
33382
33556
  }
33383
33557
  function createMemoryNote(repoRoot, summary, options = {}) {
33384
33558
  const { candidatesDir } = memoryDirectories(repoRoot);
33385
- mkdirSync19(candidatesDir, { recursive: true });
33386
33559
  const scope = options.scope ?? "project";
33387
33560
  if (!VALID_SCOPES.has(scope)) {
33388
33561
  throw new Error(`invalid memory note scope: ${scope}; must be one of agent, project, run, operator`);
@@ -33408,6 +33581,8 @@ function createMemoryNote(repoRoot, summary, options = {}) {
33408
33581
  ...options.body ? { body: options.body.trim() } : {}
33409
33582
  };
33410
33583
  const targetPath = join28(candidatesDir, `${id}.md`);
33584
+ if (options.dryRun) return { record, path: targetPath };
33585
+ mkdirSync20(candidatesDir, { recursive: true });
33411
33586
  writeFileSync18(targetPath, formatMemoryRecord(record), "utf8");
33412
33587
  return { record, path: targetPath };
33413
33588
  }
@@ -33442,10 +33617,10 @@ function formatHarnessMemoryBlock(records) {
33442
33617
  contentLines.push(MEMORY_MARKER_END);
33443
33618
  return contentLines.join("\n");
33444
33619
  }
33445
- function updateHarnessDocument(filePath, block, defaultHeader) {
33620
+ function updateHarnessDocument(filePath, block, defaultHeader, dryRun = false) {
33446
33621
  let original = "";
33447
- if (existsSync24(filePath)) {
33448
- original = readFileSync23(filePath, "utf8");
33622
+ if (existsSync26(filePath)) {
33623
+ original = readFileSync24(filePath, "utf8");
33449
33624
  }
33450
33625
  let updated = "";
33451
33626
  if (original.includes(MEMORY_MARKER_START) && original.includes(MEMORY_MARKER_END)) {
@@ -33473,21 +33648,21 @@ ${block}
33473
33648
  `;
33474
33649
  }
33475
33650
  if (updated !== original) {
33476
- writeFileSync18(filePath, updated, "utf8");
33651
+ if (!dryRun) writeFileSync18(filePath, updated, "utf8");
33477
33652
  return true;
33478
33653
  }
33479
33654
  return false;
33480
33655
  }
33481
- function syncHarnessMemory(repoRoot) {
33482
- const root = resolve21(repoRoot);
33656
+ function syncHarnessMemory(repoRoot, options = {}) {
33657
+ const root = resolve22(repoRoot);
33483
33658
  const records = loadAuthoredMemory(root);
33484
33659
  const block = formatHarnessMemoryBlock(records);
33485
33660
  const updated = [];
33486
33661
  const created = [];
33487
33662
  const agentsPath = join28(root, "AGENTS.md");
33488
33663
  const agentsHeader = "# AGENTS\n\nFollow project instructions.\n";
33489
- const agentsExisted = existsSync24(agentsPath);
33490
- if (updateHarnessDocument(agentsPath, block, agentsHeader)) {
33664
+ const agentsExisted = existsSync26(agentsPath);
33665
+ if (updateHarnessDocument(agentsPath, block, agentsHeader, options.dryRun)) {
33491
33666
  if (agentsExisted) updated.push("AGENTS.md");
33492
33667
  else created.push("AGENTS.md");
33493
33668
  }
@@ -33503,8 +33678,8 @@ rotation, not a sole writer. If \`grok\` is logged out, never bill Grok through
33503
33678
  use a relief route Tracking **admits**, or stop and name the limits hit. Your reviews are
33504
33679
  artifacts, not hub \`peer-reply\` evidence.
33505
33680
  `;
33506
- const claudeExisted = existsSync24(claudePath);
33507
- if (updateHarnessDocument(claudePath, block, claudeHeader)) {
33681
+ const claudeExisted = existsSync26(claudePath);
33682
+ if (updateHarnessDocument(claudePath, block, claudeHeader, options.dryRun)) {
33508
33683
  if (claudeExisted) updated.push("CLAUDE.md");
33509
33684
  else created.push("CLAUDE.md");
33510
33685
  }
@@ -33519,8 +33694,8 @@ Google goes through the \`antigravity\` **Pi provider** (Tracking \u2192 Decided
33519
33694
  Current admissions come from Tracking and \`kxm harness list\`. Starting rotation
33520
33695
  remains Grok.
33521
33696
  `;
33522
- const geminiExisted = existsSync24(geminiPath);
33523
- if (updateHarnessDocument(geminiPath, block, geminiHeader)) {
33697
+ const geminiExisted = existsSync26(geminiPath);
33698
+ if (updateHarnessDocument(geminiPath, block, geminiHeader, options.dryRun)) {
33524
33699
  if (geminiExisted) updated.push("GEMINI.md");
33525
33700
  else created.push("GEMINI.md");
33526
33701
  }
@@ -33530,8 +33705,8 @@ remains Grok.
33530
33705
  // plugins/kxm/src/skills.ts
33531
33706
  var import_yaml14 = __toESM(require_dist(), 1);
33532
33707
  import { createHash as createHash14 } from "node:crypto";
33533
- import { existsSync as existsSync25, mkdirSync as mkdirSync20, readdirSync as readdirSync10, readFileSync as readFileSync24, renameSync as renameSync7, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync19 } from "node:fs";
33534
- import { join as join29 } from "node:path";
33708
+ import { existsSync as existsSync27, mkdirSync as mkdirSync21, readdirSync as readdirSync10, readFileSync as readFileSync25, renameSync as renameSync7, rmSync as rmSync10, statSync as statSync3, writeFileSync as writeFileSync19 } from "node:fs";
33709
+ import { dirname as dirname17, join as join29 } from "node:path";
33535
33710
  var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
33536
33711
  var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
33537
33712
  var SKILL_DECISION_SCHEMA = "kxm.skill-decision.v1";
@@ -33619,10 +33794,22 @@ var SkillLifecycle = class {
33619
33794
  root;
33620
33795
  now;
33621
33796
  allowOptimizationEvals;
33797
+ dryRun;
33798
+ /** What a `dryRun` lifecycle would have written or moved, in order. */
33799
+ planned = [];
33622
33800
  constructor(root, options = {}) {
33623
33801
  this.root = root;
33624
33802
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
33625
33803
  this.allowOptimizationEvals = options.allowOptimizationEvals === true;
33804
+ this.dryRun = options.dryRun === true;
33805
+ }
33806
+ write(file, content) {
33807
+ if (this.dryRun) {
33808
+ this.planned.push({ action: "write", target: file });
33809
+ return;
33810
+ }
33811
+ mkdirSync21(dirname17(file), { recursive: true });
33812
+ writeFileSync19(file, content);
33626
33813
  }
33627
33814
  dir(state) {
33628
33815
  return join29(this.root, state === "candidate" ? "candidates" : `${state}s`.replace("rejecteds", "rejected").replace("promoteds", "promoted"));
@@ -33635,37 +33822,40 @@ var SkillLifecycle = class {
33635
33822
  return { dir, metadata: join29(dir, "metadata.json"), skill: join29(dir, "SKILL.md") };
33636
33823
  }
33637
33824
  appendHistory(id, record) {
33638
- mkdirSync20(join29(this.root, "history"), { recursive: true });
33639
33825
  const line = `${JSON.stringify(record)}
33640
33826
  `;
33641
- if (existsSync25(this.historyFile(id))) {
33642
- const existing = readFileSync24(this.historyFile(id), "utf8");
33827
+ if (existsSync27(this.historyFile(id))) {
33828
+ const existing = readFileSync25(this.historyFile(id), "utf8");
33643
33829
  const lines = existing.split("\n").filter((entry) => entry.trim());
33644
- writeFileSync19(this.historyFile(id), [...lines.slice(-499), line.trim()].join("\n") + "\n");
33830
+ this.write(this.historyFile(id), [...lines.slice(-499), line.trim()].join("\n") + "\n");
33645
33831
  } else {
33646
- writeFileSync19(this.historyFile(id), line);
33832
+ this.write(this.historyFile(id), line);
33647
33833
  }
33648
33834
  }
33649
33835
  history(id) {
33650
33836
  const file = this.historyFile(id);
33651
- if (!existsSync25(file)) return [];
33652
- return readFileSync24(file, "utf8").split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
33837
+ if (!existsSync27(file)) return [];
33838
+ return readFileSync25(file, "utf8").split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
33653
33839
  }
33654
33840
  readMetadata(state, id) {
33655
33841
  const { metadata } = this.paths(state, id);
33656
- if (!existsSync25(metadata)) {
33842
+ if (!existsSync27(metadata)) {
33657
33843
  throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${state}`);
33658
33844
  }
33659
- return JSON.parse(readFileSync24(metadata, "utf8"));
33845
+ return JSON.parse(readFileSync25(metadata, "utf8"));
33660
33846
  }
33661
33847
  move(from, to, id) {
33662
33848
  const fromDir = join29(this.dir(from), id);
33663
33849
  const toDir = join29(this.dir(to), id);
33664
- if (!existsSync25(fromDir)) {
33850
+ if (!existsSync27(fromDir)) {
33665
33851
  throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${from}`);
33666
33852
  }
33667
- mkdirSync20(this.dir(to), { recursive: true });
33668
- if (existsSync25(toDir)) rmSync9(toDir, { recursive: true, force: true });
33853
+ if (this.dryRun) {
33854
+ this.planned.push({ action: "move", target: `${fromDir} -> ${toDir}` });
33855
+ return;
33856
+ }
33857
+ mkdirSync21(this.dir(to), { recursive: true });
33858
+ if (existsSync27(toDir)) rmSync10(toDir, { recursive: true, force: true });
33669
33859
  renameSync7(fromDir, toDir);
33670
33860
  }
33671
33861
  /** Submit a new skill candidate. Content is redacted of secret material at
@@ -33707,8 +33897,8 @@ var SkillLifecycle = class {
33707
33897
  if (!harness) throw new SkillLifecycleError("invalid_skill_compatibility", "compatibility.harness is required");
33708
33898
  const contentSha256 = skillContentSha256(content);
33709
33899
  const id = skillIdFor(name, contentSha256);
33710
- const { dir, metadata, skill } = this.paths("candidate", id);
33711
- if (existsSync25(metadata)) {
33900
+ const { metadata, skill } = this.paths("candidate", id);
33901
+ if (existsSync27(metadata)) {
33712
33902
  throw new SkillLifecycleError(
33713
33903
  "skill_candidate_exists",
33714
33904
  `identical candidate ${id} already exists; changed behavior requires changed content`
@@ -33727,9 +33917,8 @@ var SkillLifecycle = class {
33727
33917
  createdAt: this.now(),
33728
33918
  ...input.supersedes ? { supersedes: input.supersedes } : {}
33729
33919
  };
33730
- mkdirSync20(dir, { recursive: true });
33731
- writeFileSync19(skill, content);
33732
- writeFileSync19(metadata, `${JSON.stringify(record, null, 2)}
33920
+ this.write(skill, content);
33921
+ this.write(metadata, `${JSON.stringify(record, null, 2)}
33733
33922
  `);
33734
33923
  this.appendHistory(id, { schema: "kxm.skill-history-event.v1", event: "candidate_created", by: createdBy, supersedes: input.supersedes, at: record.createdAt });
33735
33924
  return record;
@@ -33818,18 +34007,15 @@ var SkillLifecycle = class {
33818
34007
  };
33819
34008
  const candidatePaths = this.paths("candidate", candidateId);
33820
34009
  const promotedPaths = this.paths("promoted", candidateId);
33821
- mkdirSync20(promotedPaths.dir, { recursive: true });
33822
- const skillContent = readFileSync24(candidatePaths.skill, "utf8");
33823
- const metadataContent = readFileSync24(candidatePaths.metadata, "utf8");
33824
- writeFileSync19(promotedPaths.skill, skillContent);
33825
- writeFileSync19(promotedPaths.metadata, metadataContent);
33826
- const patchesDir = join29(this.root, "patches");
33827
- mkdirSync20(patchesDir, { recursive: true });
33828
- const patchPath = join29(patchesDir, `${candidateId}.patch`);
34010
+ const skillContent = readFileSync25(candidatePaths.skill, "utf8");
34011
+ const metadataContent = readFileSync25(candidatePaths.metadata, "utf8");
34012
+ this.write(promotedPaths.skill, skillContent);
34013
+ this.write(promotedPaths.metadata, metadataContent);
34014
+ const patchPath = join29(this.root, "patches", `${candidateId}.patch`);
33829
34015
  const relSkillPath = `.kxm/skills/promoted/${candidateId}/SKILL.md`;
33830
34016
  const relMetaPath = `.kxm/skills/promoted/${candidateId}/metadata.json`;
33831
34017
  const patch = `${createUnifiedPatch(relSkillPath, skillContent)}${createUnifiedPatch(relMetaPath, metadataContent)}`;
33832
- writeFileSync19(patchPath, patch, "utf8");
34018
+ this.write(patchPath, patch);
33833
34019
  this.appendHistory(candidateId, record);
33834
34020
  return { ...metadata, patch, patchPath };
33835
34021
  }
@@ -33853,7 +34039,7 @@ var SkillLifecycle = class {
33853
34039
  verify(state, id) {
33854
34040
  const metadata = this.readMetadata(state, id);
33855
34041
  const { skill } = this.paths(state, id);
33856
- const content = readFileSync24(skill, "utf8");
34042
+ const content = readFileSync25(skill, "utf8");
33857
34043
  if (skillContentSha256(content) !== metadata.contentSha256) {
33858
34044
  throw new SkillLifecycleError(
33859
34045
  "skill_integrity_violation",
@@ -33871,7 +34057,7 @@ var SkillLifecycle = class {
33871
34057
  }
33872
34058
  list(state) {
33873
34059
  const dir = this.dir(state);
33874
- if (!existsSync25(dir)) return [];
34060
+ if (!existsSync27(dir)) return [];
33875
34061
  const ids = readdirSorted(dir);
33876
34062
  return ids.map((id) => {
33877
34063
  try {
@@ -33884,7 +34070,7 @@ var SkillLifecycle = class {
33884
34070
  read(state, id) {
33885
34071
  const metadata = this.readMetadata(state, id);
33886
34072
  const { skill } = this.paths(state, id);
33887
- return { metadata, content: readFileSync24(skill, "utf8") };
34073
+ return { metadata, content: readFileSync25(skill, "utf8") };
33888
34074
  }
33889
34075
  };
33890
34076
  function boundedList(value, field) {
@@ -33903,13 +34089,13 @@ function readdirSorted(dir) {
33903
34089
  }
33904
34090
 
33905
34091
  // plugins/kxm/src/wiki.ts
33906
- import { mkdirSync as mkdirSync21, writeFileSync as writeFileSync20 } from "node:fs";
34092
+ import { mkdirSync as mkdirSync22, writeFileSync as writeFileSync20 } from "node:fs";
33907
34093
  import { join as join30 } from "node:path";
33908
34094
  function writeCompiledWiki(root, wiki) {
33909
34095
  const written = [];
33910
34096
  for (const [relativePath, content] of [...wiki.pages].sort(([left], [right]) => left.localeCompare(right))) {
33911
34097
  const absolute = join30(root, relativePath);
33912
- mkdirSync21(absolute.slice(0, absolute.lastIndexOf("/")), { recursive: true });
34098
+ mkdirSync22(absolute.slice(0, absolute.lastIndexOf("/")), { recursive: true });
33913
34099
  writeFileSync20(absolute, content);
33914
34100
  written.push(relativePath);
33915
34101
  }
@@ -34012,6 +34198,15 @@ async function cmdContextPromote(runtime, project, proposalId, options) {
34012
34198
  runtime.io.stderr("context promote --evidence must contain at least one durable evidence reference\n");
34013
34199
  return 2;
34014
34200
  }
34201
+ if (runtime.dryRun) {
34202
+ printPlan(
34203
+ runtime,
34204
+ { command: "context promote", project, proposalId, evidence },
34205
+ [{ action: "request", target: `POST ${runtime.serverUrl.replace(/\/$/, "")}/v1/context/state/promote` }],
34206
+ `promote proposal ${proposalId} in ${project}`
34207
+ );
34208
+ return 0;
34209
+ }
34015
34210
  const response = await hubContextPost({
34016
34211
  serverUrl: runtime.serverUrl,
34017
34212
  path: "/v1/context/state/promote",
@@ -34046,6 +34241,16 @@ async function cmdContextWikiCompile(runtime, project, options) {
34046
34241
  return 1;
34047
34242
  }
34048
34243
  const compiled = response.body;
34244
+ if (options.out && runtime.dryRun) {
34245
+ const outDir = options.out;
34246
+ printPlan(
34247
+ runtime,
34248
+ { command: "context wiki-compile", project, pages: compiled.audit.pages, openContradictions: compiled.audit.contradictions, outDir },
34249
+ compiled.pages.map((page) => ({ action: "write", target: resolve23(outDir, page.path) })),
34250
+ `write ${compiled.pages.length} compiled wiki page(s) to ${outDir}`
34251
+ );
34252
+ return 0;
34253
+ }
34049
34254
  let written = [];
34050
34255
  if (options.out) {
34051
34256
  const pages = new Map(compiled.pages.map((page) => [page.path, page.content]));
@@ -34093,7 +34298,7 @@ async function cmdSkillsCreate(runtime, options) {
34093
34298
  const metadata = lifecycle.create({
34094
34299
  name: options.name,
34095
34300
  description: options.description ?? "",
34096
- content: readFileSync25(options.file, "utf8"),
34301
+ content: readFileSync26(options.file, "utf8"),
34097
34302
  createdBy: options.createdBy,
34098
34303
  sources: {
34099
34304
  runIds: csv(options.run) ?? [],
@@ -34113,7 +34318,7 @@ async function cmdSkillsCreate(runtime, options) {
34113
34318
  }
34114
34319
  }
34115
34320
  async function cmdSkillsEvaluate(runtime, skillId, options) {
34116
- const lifecycle = new SkillLifecycle(skillsRoot(runtime));
34321
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime), { dryRun: runtime.dryRun });
34117
34322
  try {
34118
34323
  const outcome = lifecycle.evaluate(skillId, {
34119
34324
  kind: options.kind,
@@ -34122,6 +34327,10 @@ async function cmdSkillsEvaluate(runtime, skillId, options) {
34122
34327
  ...options.score !== void 0 ? { score: Number(options.score) } : {},
34123
34328
  ...options.details ? { details: options.details } : {}
34124
34329
  });
34330
+ if (runtime.dryRun) {
34331
+ printPlan(runtime, { command: "skills evaluate", skillId, quarantined: outcome.quarantined, evaluation: outcome.evaluation }, lifecycle.planned, `record ${options.kind} evaluation${outcome.quarantined ? " (candidate quarantined)" : ""}`);
34332
+ return 0;
34333
+ }
34125
34334
  print(runtime.io, runtime.json, { ok: true, command: "skills evaluate", skillId, quarantined: outcome.quarantined, evaluation: outcome.evaluation }, `recorded ${options.kind} evaluation${outcome.quarantined ? " (candidate quarantined)" : ""}`);
34126
34335
  return 0;
34127
34336
  } catch (error) {
@@ -34132,13 +34341,17 @@ async function cmdSkillsEvaluate(runtime, skillId, options) {
34132
34341
  }
34133
34342
  }
34134
34343
  async function cmdSkillsPromote(runtime, skillId, options) {
34135
- const lifecycle = new SkillLifecycle(skillsRoot(runtime));
34344
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime), { dryRun: runtime.dryRun });
34136
34345
  try {
34137
34346
  const promoted = lifecycle.promote(skillId, {
34138
34347
  decidedBy: options.decidedBy,
34139
34348
  reason: options.reason ?? "passed protected evaluation",
34140
34349
  evidenceRefs: csv(options.evidence) ?? []
34141
34350
  });
34351
+ if (runtime.dryRun) {
34352
+ printPlan(runtime, { command: "skills promote", skillId, metadata: promoted, patchPath: promoted.patchPath }, lifecycle.planned, `promote skill ${skillId}`);
34353
+ return 0;
34354
+ }
34142
34355
  print(runtime.io, runtime.json, { ok: true, command: "skills promote", skillId, metadata: promoted, patchPath: promoted.patchPath }, `promoted skill ${skillId} (patch: ${promoted.patchPath})`);
34143
34356
  return 0;
34144
34357
  } catch (error) {
@@ -34149,9 +34362,13 @@ async function cmdSkillsPromote(runtime, skillId, options) {
34149
34362
  }
34150
34363
  }
34151
34364
  async function cmdSkillsReject(runtime, skillId, options) {
34152
- const lifecycle = new SkillLifecycle(skillsRoot(runtime));
34365
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime), { dryRun: runtime.dryRun });
34153
34366
  try {
34154
34367
  const metadata = lifecycle.reject(skillId, { decidedBy: options.decidedBy, reason: options.reason ?? "rejected" });
34368
+ if (runtime.dryRun) {
34369
+ printPlan(runtime, { command: "skills reject", skillId, metadata }, lifecycle.planned, `reject skill ${skillId} (history retained)`);
34370
+ return 0;
34371
+ }
34155
34372
  print(runtime.io, runtime.json, { ok: true, command: "skills reject", skillId, metadata }, `rejected skill ${skillId} (history retained)`);
34156
34373
  return 0;
34157
34374
  } catch (error) {
@@ -34214,9 +34431,14 @@ async function cmdMemoryNote(runtime, fact, options) {
34214
34431
  const { record, path: path4 } = createMemoryNote(runtime.cwd, fact, {
34215
34432
  scope: options.scope ?? "project",
34216
34433
  ...options.kind !== void 0 ? { kind: options.kind } : {},
34217
- ...options.body !== void 0 ? { body: options.body } : {}
34434
+ ...options.body !== void 0 ? { body: options.body } : {},
34435
+ dryRun: runtime.dryRun
34218
34436
  });
34219
34437
  const relPath = relative5(runtime.cwd, path4);
34438
+ if (runtime.dryRun) {
34439
+ printPlan(runtime, { command: "memory note", candidate: record, path: relPath }, [{ action: "write", target: path4 }], `record memory candidate in ${relPath} (the id is assigned when it is recorded)`);
34440
+ return 0;
34441
+ }
34220
34442
  print(
34221
34443
  runtime.io,
34222
34444
  runtime.json,
@@ -34233,7 +34455,16 @@ async function cmdMemoryNote(runtime, fact, options) {
34233
34455
  }
34234
34456
  async function cmdMemorySync(runtime) {
34235
34457
  try {
34236
- const result = syncHarnessMemory(runtime.cwd);
34458
+ const result = syncHarnessMemory(runtime.cwd, { dryRun: runtime.dryRun });
34459
+ if (runtime.dryRun) {
34460
+ printPlan(
34461
+ runtime,
34462
+ { command: "memory sync", ...result },
34463
+ [...result.created, ...result.updated].map((file) => ({ action: "write", target: join31(runtime.cwd, file) })),
34464
+ "regenerate project memory blocks in AGENTS.md, CLAUDE.md, and GEMINI.md"
34465
+ );
34466
+ return 0;
34467
+ }
34237
34468
  print(
34238
34469
  runtime.io,
34239
34470
  runtime.json,
@@ -34251,8 +34482,8 @@ async function cmdMemorySync(runtime) {
34251
34482
 
34252
34483
  // plugins/kxm/src/cli/hub.ts
34253
34484
  import { randomUUID as randomUUID14 } from "node:crypto";
34254
- import { existsSync as existsSync31, mkdirSync as mkdirSync25, readFileSync as readFileSync31, readdirSync as readdirSync12, rmSync as rmSync10, writeFileSync as writeFileSync23 } from "node:fs";
34255
- import { join as join41, resolve as resolve24 } from "node:path";
34485
+ import { existsSync as existsSync33, mkdirSync as mkdirSync26, readFileSync as readFileSync32, readdirSync as readdirSync12, rmSync as rmSync11, writeFileSync as writeFileSync23 } from "node:fs";
34486
+ import { join as join41, resolve as resolve26 } from "node:path";
34256
34487
 
34257
34488
  // node_modules/marked/lib/marked.esm.js
34258
34489
  function M() {
@@ -38414,10 +38645,10 @@ var TuiBase = class _TuiBase extends Container {
38414
38645
  * @returns Promise containing the parsed RGB color, or undefined if it times out or fails to parse.
38415
38646
  */
38416
38647
  queryTerminalBackgroundColor({ timeoutMs }) {
38417
- return new Promise((resolve30) => {
38648
+ return new Promise((resolve32) => {
38418
38649
  const query = {
38419
38650
  settled: false,
38420
- resolve: resolve30,
38651
+ resolve: resolve32,
38421
38652
  timer: void 0
38422
38653
  };
38423
38654
  query.timer = setTimeout(() => {
@@ -38440,7 +38671,7 @@ var TuiBase = class _TuiBase extends Container {
38440
38671
  * `CSI ? 997 ; 1 n` for dark or `CSI ? 997 ; 2 n` for light.
38441
38672
  */
38442
38673
  queryTerminalColorScheme({ timeoutMs }) {
38443
- return new Promise((resolve30) => {
38674
+ return new Promise((resolve32) => {
38444
38675
  let settled = false;
38445
38676
  let timer;
38446
38677
  let unsubscribe = () => {
@@ -38454,7 +38685,7 @@ var TuiBase = class _TuiBase extends Container {
38454
38685
  timer = void 0;
38455
38686
  }
38456
38687
  unsubscribe();
38457
- resolve30(scheme);
38688
+ resolve32(scheme);
38458
38689
  };
38459
38690
  unsubscribe = this.onTerminalColorSchemeChange(settle);
38460
38691
  timer = setTimeout(() => settle(void 0), timeoutMs);
@@ -40185,19 +40416,19 @@ import * as path2 from "node:path";
40185
40416
 
40186
40417
  // node_modules/@earendil-works/pi-tui/dist/native-module-path.js
40187
40418
  import { createRequire as createRequire2 } from "node:module";
40188
- import { dirname as dirname17, join as join32 } from "node:path";
40419
+ import { dirname as dirname18, join as join32 } from "node:path";
40189
40420
  import { fileURLToPath as fileURLToPath2 } from "node:url";
40190
40421
  var moduleRequire = createRequire2(import.meta.url);
40191
40422
  var TUI_PACKAGE_NAME = "@earendil-works/pi-tui";
40192
40423
  function getNativeModuleCandidates(nativePath, options = {}) {
40193
- const moduleDir = dirname17(fileURLToPath2(options.moduleUrl ?? import.meta.url));
40424
+ const moduleDir = dirname18(fileURLToPath2(options.moduleUrl ?? import.meta.url));
40194
40425
  const candidates = [];
40195
40426
  try {
40196
40427
  const packageEntry = (options.resolvePackage ?? moduleRequire.resolve)(TUI_PACKAGE_NAME);
40197
- candidates.push(join32(dirname17(packageEntry), "..", nativePath));
40428
+ candidates.push(join32(dirname18(packageEntry), "..", nativePath));
40198
40429
  } catch {
40199
40430
  }
40200
- candidates.push(join32(moduleDir, "..", nativePath), join32(moduleDir, nativePath), join32(dirname17(options.execPath ?? process.execPath), nativePath));
40431
+ candidates.push(join32(moduleDir, "..", nativePath), join32(moduleDir, nativePath), join32(dirname18(options.execPath ?? process.execPath), nativePath));
40201
40432
  return Array.from(new Set(candidates));
40202
40433
  }
40203
40434
 
@@ -40538,7 +40769,7 @@ var ProcessTerminal = class {
40538
40769
  break;
40539
40770
  if (now - lastDataTime >= idleMs)
40540
40771
  break;
40541
- await new Promise((resolve30) => setTimeout(resolve30, Math.min(idleMs, timeLeft)));
40772
+ await new Promise((resolve32) => setTimeout(resolve32, Math.min(idleMs, timeLeft)));
40542
40773
  }
40543
40774
  } finally {
40544
40775
  process.stdin.removeListener("data", onData);
@@ -42653,8 +42884,8 @@ var TuiAltScreen = class extends TuiBase {
42653
42884
  var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
42654
42885
 
42655
42886
  // plugins/kxm/src/tui.ts
42656
- import { mkdirSync as mkdirSync22 } from "node:fs";
42657
- import { join as join36, resolve as resolve23, dirname as dirname18 } from "node:path";
42887
+ import { mkdirSync as mkdirSync23 } from "node:fs";
42888
+ import { join as join36, resolve as resolve25, dirname as dirname19 } from "node:path";
42658
42889
  import { spawnSync as spawnSync5 } from "node:child_process";
42659
42890
 
42660
42891
  // packages/core/tui/src/types/surface.ts
@@ -42795,9 +43026,9 @@ var SHARED_EFFECT_KINDS = Object.freeze([
42795
43026
  ]);
42796
43027
 
42797
43028
  // plugins/kxm/src/local-snapshot.ts
42798
- import { existsSync as existsSync26, readdirSync as readdirSync11, readFileSync as readFileSync26 } from "node:fs";
43029
+ import { existsSync as existsSync28, readdirSync as readdirSync11, readFileSync as readFileSync27 } from "node:fs";
42799
43030
  import { homedir as homedir6 } from "node:os";
42800
- import { isAbsolute as isAbsolute8, join as join35, resolve as resolve22 } from "node:path";
43031
+ import { isAbsolute as isAbsolute8, join as join35, resolve as resolve24 } from "node:path";
42801
43032
  function processExists2(pid) {
42802
43033
  try {
42803
43034
  process.kill(pid, 0);
@@ -42919,34 +43150,34 @@ function readOpenMessageMetadata(database) {
42919
43150
  function resolveKxmSnapshotPaths(cwd, env = process.env) {
42920
43151
  const stateDir = env.KXM_STATE_DIR?.trim() || join35(cwd, ".kxm", "state");
42921
43152
  const configured = env.KXM_DATA_PATH?.trim();
42922
- const dataPath = configured ? resolve22(cwd, configured) : join35(stateDir, "kxm.db");
43153
+ const dataPath = configured ? resolve24(cwd, configured) : join35(stateDir, "kxm.db");
42923
43154
  return { dataPath, stateDir };
42924
43155
  }
42925
43156
  function resolveKxmStateRoot(stateDir, options) {
42926
- if (options?.kxmStateRoot && existsSync26(options.kxmStateRoot)) {
42927
- return resolve22(options.kxmStateRoot);
43157
+ if (options?.kxmStateRoot && existsSync28(options.kxmStateRoot)) {
43158
+ return resolve24(options.kxmStateRoot);
42928
43159
  }
42929
- if (existsSync26(join35(stateDir, "runtime", "registry.db")) || existsSync26(join35(stateDir, "runtime", "projects"))) {
43160
+ if (existsSync28(join35(stateDir, "runtime", "registry.db")) || existsSync28(join35(stateDir, "runtime", "projects"))) {
42930
43161
  return stateDir;
42931
43162
  }
42932
43163
  const env = options?.env ?? process.env;
42933
43164
  const explicit = env.KXM_STATE_HOME?.trim() || env.KXM_USER_STATE_DIR?.trim() || env.KXM_STATE_ROOT?.trim();
42934
- if (explicit && isAbsolute8(explicit) && existsSync26(resolve22(explicit))) {
42935
- return resolve22(explicit);
43165
+ if (explicit && isAbsolute8(explicit) && existsSync28(resolve24(explicit))) {
43166
+ return resolve24(explicit);
42936
43167
  }
42937
43168
  let base;
42938
43169
  if (process.platform === "win32") {
42939
43170
  const localAppData = env.LOCALAPPDATA?.trim();
42940
43171
  base = localAppData && isAbsolute8(localAppData) ? localAppData : join35(homedir6(), "AppData", "Local");
42941
- base = resolve22(base, "KXM");
43172
+ base = resolve24(base, "KXM");
42942
43173
  } else if (process.platform === "darwin") {
42943
- base = resolve22(homedir6(), "Library", "Application Support", "KXM");
43174
+ base = resolve24(homedir6(), "Library", "Application Support", "KXM");
42944
43175
  } else {
42945
43176
  const xdgState = env.XDG_STATE_HOME?.trim();
42946
43177
  base = xdgState && isAbsolute8(xdgState) ? xdgState : join35(homedir6(), ".local", "state");
42947
- base = resolve22(base, "kxm");
43178
+ base = resolve24(base, "kxm");
42948
43179
  }
42949
- if (existsSync26(base)) return base;
43180
+ if (existsSync28(base)) return base;
42950
43181
  return void 0;
42951
43182
  }
42952
43183
  function loadLocalMeshSnapshot(dataPath, stateDir, options) {
@@ -42957,9 +43188,9 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
42957
43188
  let legacyRuns = [];
42958
43189
  let legacyRunTotal = 0;
42959
43190
  let plans = [];
42960
- if (existsSync26(dataPath)) {
43191
+ if (existsSync28(dataPath)) {
42961
43192
  hasLegacy = true;
42962
- const database = new DatabaseSync(dataPath, { readOnly: true });
43193
+ const database = openReadOnlyDatabase(dataPath);
42963
43194
  try {
42964
43195
  database.exec("PRAGMA busy_timeout = 5000");
42965
43196
  agents = readJsonRows(database, "SELECT record FROM agents");
@@ -42981,10 +43212,10 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
42981
43212
  const registryDbPath = join35(runtimeDir, "registry.db");
42982
43213
  const projectsDir = join35(runtimeDir, "projects");
42983
43214
  const projectKeys = /* @__PURE__ */ new Set();
42984
- if (existsSync26(registryDbPath)) {
43215
+ if (existsSync28(registryDbPath)) {
42985
43216
  hasKxm = true;
42986
43217
  try {
42987
- const regDb = new DatabaseSync(registryDbPath, { readOnly: true });
43218
+ const regDb = openReadOnlyDatabase(registryDbPath);
42988
43219
  try {
42989
43220
  regDb.exec("PRAGMA busy_timeout = 5000");
42990
43221
  const pRows = regDb.prepare("SELECT project_key FROM projects").all();
@@ -42997,7 +43228,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
42997
43228
  } catch {
42998
43229
  }
42999
43230
  }
43000
- if (existsSync26(projectsDir)) {
43231
+ if (existsSync28(projectsDir)) {
43001
43232
  try {
43002
43233
  for (const entry of readdirSync11(projectsDir, { withFileTypes: true })) {
43003
43234
  if (entry.isDirectory()) {
@@ -43009,10 +43240,10 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
43009
43240
  }
43010
43241
  for (const key of projectKeys) {
43011
43242
  const eventDbPath = join35(projectsDir, key, "run-events.db");
43012
- if (existsSync26(eventDbPath)) {
43243
+ if (existsSync28(eventDbPath)) {
43013
43244
  hasKxm = true;
43014
43245
  try {
43015
- const eventDb = new DatabaseSync(eventDbPath, { readOnly: true });
43246
+ const eventDb = openReadOnlyDatabase(eventDbPath);
43016
43247
  try {
43017
43248
  eventDb.exec("PRAGMA busy_timeout = 5000");
43018
43249
  const runRows = eventDb.prepare(`
@@ -43039,10 +43270,10 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
43039
43270
  }
43040
43271
  }
43041
43272
  const pids = [];
43042
- if (existsSync26(stateDir)) {
43273
+ if (existsSync28(stateDir)) {
43043
43274
  for (const file of readdirSync11(stateDir).filter((name) => name.endsWith(".pid"))) {
43044
43275
  try {
43045
- const record = JSON.parse(readFileSync26(join35(stateDir, file), "utf8"));
43276
+ const record = JSON.parse(readFileSync27(join35(stateDir, file), "utf8"));
43046
43277
  pids.push({
43047
43278
  file,
43048
43279
  ...record.role ? { role: record.role } : {},
@@ -43082,7 +43313,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
43082
43313
  source = "legacy";
43083
43314
  }
43084
43315
  const telemetryFile = join35(stateDir, "telemetry.jsonl");
43085
- const spend = existsSync26(telemetryFile) ? readRoutingRecords(telemetryFile) : [];
43316
+ const spend = existsSync28(telemetryFile) ? readRoutingRecords(telemetryFile) : [];
43086
43317
  return {
43087
43318
  source,
43088
43319
  agents: agents.sort((a, b2) => Number(b2.online) - Number(a.online) || a.name.localeCompare(b2.name)),
@@ -43217,10 +43448,10 @@ function spawnDegradeWorktree(repoRoot, runId, options) {
43217
43448
  const desc = options?.description ? slugifyBranchPart(options.description, 30) : "degrade";
43218
43449
  const branchName = `kxm/run-${cleanRunId}-${desc}`;
43219
43450
  const relativeWtPath = join36(".kxm", "worktrees", `run-${cleanRunId}-${desc}`);
43220
- const absoluteWtPath = resolve23(repoRoot, relativeWtPath);
43451
+ const absoluteWtPath = resolve25(repoRoot, relativeWtPath);
43221
43452
  const jumpCommand = `cd "${absoluteWtPath}"`;
43222
43453
  try {
43223
- mkdirSync22(dirname18(absoluteWtPath), { recursive: true });
43454
+ mkdirSync23(dirname19(absoluteWtPath), { recursive: true });
43224
43455
  const branchCheck = runner("git", ["rev-parse", "--verify", `refs/heads/${branchName}`]);
43225
43456
  let wtRes;
43226
43457
  if (branchCheck.status === 0) {
@@ -43618,11 +43849,11 @@ async function readJson(response) {
43618
43849
  }
43619
43850
  async function waitForReconnect(signal, milliseconds = 1e3) {
43620
43851
  if (signal.aborted) return;
43621
- await new Promise((resolve30) => {
43852
+ await new Promise((resolve32) => {
43622
43853
  const done = () => {
43623
43854
  clearTimeout(timer);
43624
43855
  signal.removeEventListener("abort", done);
43625
- resolve30();
43856
+ resolve32();
43626
43857
  };
43627
43858
  const timer = setTimeout(done, milliseconds);
43628
43859
  timer.unref();
@@ -43985,7 +44216,7 @@ async function runMeshTui(input) {
43985
44216
  // plugins/kxm/src/session-work.ts
43986
44217
  import { spawnSync as spawnSync6 } from "node:child_process";
43987
44218
  import { randomUUID as randomUUID13 } from "node:crypto";
43988
- import { existsSync as existsSync27, mkdirSync as mkdirSync23, readFileSync as readFileSync27, renameSync as renameSync8, writeFileSync as writeFileSync21 } from "node:fs";
44219
+ import { existsSync as existsSync29, mkdirSync as mkdirSync24, readFileSync as readFileSync28, renameSync as renameSync8, writeFileSync as writeFileSync21 } from "node:fs";
43989
44220
  import { join as join37 } from "node:path";
43990
44221
  var MAX_SESSION_BRIEF_TASKS = 5;
43991
44222
  var MAX_SESSION_BRIEF_PLANS = 5;
@@ -44145,8 +44376,8 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
44145
44376
  function readCachedSessionBrief(stateDir) {
44146
44377
  const file = join37(stateDir, "session-brief.json");
44147
44378
  try {
44148
- if (!existsSync27(file)) return void 0;
44149
- const raw = readFileSync27(file, "utf8");
44379
+ if (!existsSync29(file)) return void 0;
44380
+ const raw = readFileSync28(file, "utf8");
44150
44381
  const parsed = JSON.parse(raw);
44151
44382
  if (parsed && parsed.schema === "kxm.session-brief.v1" && typeof parsed.generatedAt === "string") {
44152
44383
  const ageMs = Date.now() - Date.parse(parsed.generatedAt);
@@ -44161,7 +44392,7 @@ function readCachedSessionBrief(stateDir) {
44161
44392
  }
44162
44393
  function writeCachedSessionBrief(stateDir, brief) {
44163
44394
  try {
44164
- mkdirSync23(stateDir, { recursive: true, mode: 448 });
44395
+ mkdirSync24(stateDir, { recursive: true, mode: 448 });
44165
44396
  const file = join37(stateDir, "session-brief.json");
44166
44397
  const tmp = `${file}.tmp.${randomUUID13().slice(0, 8)}`;
44167
44398
  writeFileSync21(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 384 });
@@ -44172,7 +44403,7 @@ function writeCachedSessionBrief(stateDir, brief) {
44172
44403
  function estimateSessionCost(stateDir) {
44173
44404
  try {
44174
44405
  const telemetryFile = join37(stateDir, "telemetry.jsonl");
44175
- if (!existsSync27(telemetryFile)) return void 0;
44406
+ if (!existsSync29(telemetryFile)) return void 0;
44176
44407
  const records = readRoutingRecords(telemetryFile);
44177
44408
  if (records.length === 0) return void 0;
44178
44409
  let totalCost = 0;
@@ -44276,7 +44507,7 @@ function loadSessionBrief(cwd, env = process.env, current, hub, options = {}) {
44276
44507
  options.sessionToken
44277
44508
  );
44278
44509
  }
44279
- writeCachedSessionBrief(paths.stateDir, brief);
44510
+ if (options.writeCache !== false) writeCachedSessionBrief(paths.stateDir, brief);
44280
44511
  return brief;
44281
44512
  }
44282
44513
  async function loadSessionBriefAsync(cwd, env = process.env, current, hub, options = {}) {
@@ -44340,11 +44571,11 @@ function formatSessionBriefText(brief) {
44340
44571
  }
44341
44572
 
44342
44573
  // plugins/kxm/src/kxm-install-kind.ts
44343
- import { existsSync as existsSync28, readFileSync as readFileSync28 } from "node:fs";
44344
- import { dirname as dirname19, join as join38 } from "node:path";
44574
+ import { existsSync as existsSync30, readFileSync as readFileSync29 } from "node:fs";
44575
+ import { dirname as dirname20, join as join38 } from "node:path";
44345
44576
  function packageIsKxm(root) {
44346
44577
  try {
44347
- const pkg = JSON.parse(readFileSync28(join38(root, "package.json"), "utf8"));
44578
+ const pkg = JSON.parse(readFileSync29(join38(root, "package.json"), "utf8"));
44348
44579
  return pkg.name === "@kontextmind/kxm";
44349
44580
  } catch {
44350
44581
  return false;
@@ -44398,7 +44629,7 @@ function classifyInstallRoot(probe) {
44398
44629
  const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() || join38(probe.homeDir, ".claude");
44399
44630
  const pluginsRoot = join38(claudeHome, "plugins");
44400
44631
  if (isUnder(probe.moduleDir, pluginsRoot, probe.platform)) return report("claude-marketplace", probe.moduleDir);
44401
- const hasGit = existsSync28(join38(probe.repoRoot, ".git"));
44632
+ const hasGit = existsSync30(join38(probe.repoRoot, ".git"));
44402
44633
  if (hasGit && packageIsKxm(probe.repoRoot)) return report("source", probe.repoRoot);
44403
44634
  if (isNpmPackageLayout(probe.repoRoot, probe.platform) && packageIsKxm(probe.repoRoot) && !hasGit) {
44404
44635
  return report("npm-package", probe.repoRoot);
@@ -44412,21 +44643,21 @@ function resolveInstallKind(probe, npmGlobalRoot) {
44412
44643
  if (typeof globalRoot !== "string" || globalRoot.trim() === "") {
44413
44644
  return report("unknown", classified.root);
44414
44645
  }
44415
- const expected = dirname19(dirname19(classified.root));
44646
+ const expected = dirname20(dirname20(classified.root));
44416
44647
  if (sameHostPath2(globalRoot.trim(), expected, probe.platform)) return report("npm-global", classified.root);
44417
44648
  return report("npm-local", classified.root);
44418
44649
  }
44419
44650
 
44420
44651
  // plugins/kxm/src/kxm-update-config.ts
44421
44652
  var import_yaml15 = __toESM(require_dist(), 1);
44422
- import { existsSync as existsSync29, readFileSync as readFileSync29 } from "node:fs";
44653
+ import { existsSync as existsSync31, readFileSync as readFileSync30 } from "node:fs";
44423
44654
  import { join as join39 } from "node:path";
44424
44655
  function loadKxmUpdateConfig(env = process.env) {
44425
44656
  const path4 = join39(kxmUserStateRoot({ env }), "update.yaml");
44426
- if (!existsSync29(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "github" };
44657
+ if (!existsSync31(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "github" };
44427
44658
  let parsed;
44428
44659
  try {
44429
- parsed = (0, import_yaml15.parse)(readFileSync29(path4, "utf8"));
44660
+ parsed = (0, import_yaml15.parse)(readFileSync30(path4, "utf8"));
44430
44661
  } catch {
44431
44662
  throw new KxmUpdateConfigError("update.yaml is not valid YAML");
44432
44663
  }
@@ -44452,7 +44683,7 @@ function loadKxmUpdateConfig(env = process.env) {
44452
44683
  }
44453
44684
 
44454
44685
  // plugins/kxm/src/session.ts
44455
- import { existsSync as existsSync30, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync22 } from "node:fs";
44686
+ import { existsSync as existsSync32, mkdirSync as mkdirSync25, readFileSync as readFileSync31, writeFileSync as writeFileSync22 } from "node:fs";
44456
44687
  import { join as join40 } from "node:path";
44457
44688
  var SESSION_SCHEMA = "kxm.session.v1";
44458
44689
  var SessionConfigError = class extends Error {
@@ -44532,10 +44763,10 @@ function loadRosterMap(configDir) {
44532
44763
  return byName;
44533
44764
  }
44534
44765
  function loadRoster(path4, key) {
44535
- if (!existsSync30(path4)) return [];
44766
+ if (!existsSync32(path4)) return [];
44536
44767
  let parsed;
44537
44768
  try {
44538
- parsed = JSON.parse(readFileSync30(path4, "utf8"));
44769
+ parsed = JSON.parse(readFileSync31(path4, "utf8"));
44539
44770
  } catch (error) {
44540
44771
  throw new SessionConfigError(
44541
44772
  `${key} roster at ${path4} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -44576,7 +44807,7 @@ function writeSession(assetsDir, session, dryRun = false) {
44576
44807
  const dir = join40(assetsDir, "sessions", session.id);
44577
44808
  const path4 = join40(dir, "session.json");
44578
44809
  if (!dryRun) {
44579
- mkdirSync24(dir, { recursive: true });
44810
+ mkdirSync25(dir, { recursive: true });
44580
44811
  writeFileSync22(path4, `${JSON.stringify(session, null, 2)}
44581
44812
  `, { encoding: "utf8" });
44582
44813
  }
@@ -44585,7 +44816,7 @@ function writeSession(assetsDir, session, dryRun = false) {
44585
44816
 
44586
44817
  // plugins/kxm/src/cli/hub.ts
44587
44818
  import { fileURLToPath as fileURLToPath3 } from "node:url";
44588
- import { dirname as dirname20 } from "node:path";
44819
+ import { dirname as dirname21 } from "node:path";
44589
44820
  import { homedir as homedir7 } from "node:os";
44590
44821
  async function hubGet(url, fetchImpl) {
44591
44822
  try {
@@ -44603,7 +44834,7 @@ async function hubGet(url, fetchImpl) {
44603
44834
  }
44604
44835
  function installProbeFrom(runtime) {
44605
44836
  const partial = runtime.io.installProbe ?? {};
44606
- const moduleDir = partial.moduleDir ?? dirname20(fileURLToPath3(import.meta.url));
44837
+ const moduleDir = partial.moduleDir ?? dirname21(fileURLToPath3(import.meta.url));
44607
44838
  return {
44608
44839
  moduleDir,
44609
44840
  // Classify the install that is **running**, never the directory the caller
@@ -44624,12 +44855,12 @@ function moduleInstallRoot() {
44624
44855
  try {
44625
44856
  return findKxmRepoRoot(import.meta.url);
44626
44857
  } catch {
44627
- return dirname20(fileURLToPath3(import.meta.url));
44858
+ return dirname21(fileURLToPath3(import.meta.url));
44628
44859
  }
44629
44860
  }
44630
44861
  function warnIgnoredProjectUpdateYaml(runtime) {
44631
44862
  const projectFile = join41(runtime.dirs.workspace, "update.yaml");
44632
- if (!existsSync31(projectFile)) return;
44863
+ if (!existsSync33(projectFile)) return;
44633
44864
  const userFile = join41(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
44634
44865
  runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
44635
44866
  `);
@@ -44648,7 +44879,7 @@ async function refreshKxmUpdateNotice(runtime, config, current) {
44648
44879
  }
44649
44880
  const fetched = await fetchLatestKxmVersion(resolved.source, runtime.env, runtime.fetchImpl);
44650
44881
  const notice = noticeFromVersions(installed, fetched.latest, resolved, fetched.error, fetched.asset);
44651
- writeUpdateCache(runtime.dirs.state, notice);
44882
+ if (!runtime.dryRun) writeUpdateCache(runtime.dirs.state, notice);
44652
44883
  return notice;
44653
44884
  }
44654
44885
  async function cmdStatus(runtime) {
@@ -44678,7 +44909,7 @@ async function cmdDash(runtime, options = {}) {
44678
44909
  return 2;
44679
44910
  }
44680
44911
  const screen = requested;
44681
- const dataPath = resolve24(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join41(runtime.dirs.state, "kxm.db"));
44912
+ const dataPath = resolve26(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join41(runtime.dirs.state, "kxm.db"));
44682
44913
  if (runtime.dryRun) {
44683
44914
  print(runtime.io, runtime.json, {
44684
44915
  ok: true,
@@ -44911,7 +45142,7 @@ async function cmdWorker(runtime, options) {
44911
45142
  return await (runtime.io.spawnWorker ?? ((launchEnv) => spawnScript("kxm-worker.mjs", launchEnv)))(extraEnv);
44912
45143
  }
44913
45144
  async function cmdStop(runtime, waitMsFlag) {
44914
- const pids = existsSync31(runtime.dirs.state) ? readdirSync12(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
45145
+ const pids = existsSync33(runtime.dirs.state) ? readdirSync12(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
44915
45146
  if (runtime.dryRun) {
44916
45147
  print(runtime.io, runtime.json, { ok: true, command: "stop", dryRun: true, pidFiles: pids }, "would signal pid files");
44917
45148
  return 0;
@@ -44926,7 +45157,7 @@ async function cmdStop(runtime, waitMsFlag) {
44926
45157
  const orphans = [];
44927
45158
  for (const file of pids) {
44928
45159
  try {
44929
- const record = JSON.parse(readFileSync31(join41(runtime.dirs.state, file), "utf8"));
45160
+ const record = JSON.parse(readFileSync32(join41(runtime.dirs.state, file), "utf8"));
44930
45161
  const expectedControl = file === "hub.pid" ? "hub.stop" : file.startsWith("worker-") ? `${file.slice(0, -4)}.stop` : void 0;
44931
45162
  const expectedRole = file === "hub.pid" ? "hub" : file.startsWith("worker-") ? "worker" : void 0;
44932
45163
  if (record.version !== 1 || !Number.isInteger(record.pid) || record.pid <= 0 || !record.startedAt || !expectedControl || record.controlFile !== expectedControl || record.role !== expectedRole) {
@@ -44964,7 +45195,7 @@ async function cmdStop(runtime, waitMsFlag) {
44964
45195
  while (Date.now() <= deadline && stopped.size < requested.length) {
44965
45196
  for (const [file, record] of records) {
44966
45197
  try {
44967
- const current = JSON.parse(readFileSync31(join41(runtime.dirs.state, file), "utf8"));
45198
+ const current = JSON.parse(readFileSync32(join41(runtime.dirs.state, file), "utf8"));
44968
45199
  if (current.pid !== record.pid || current.startedAt !== record.startedAt || current.generation !== record.generation || !processExists(record.pid)) stopped.add(file);
44969
45200
  } catch {
44970
45201
  stopped.add(file);
@@ -44977,7 +45208,7 @@ async function cmdStop(runtime, waitMsFlag) {
44977
45208
  for (const file of orphans) {
44978
45209
  while (Date.now() <= deadlineOrphans) {
44979
45210
  try {
44980
- const current = JSON.parse(readFileSync31(join41(runtime.dirs.state, file), "utf8"));
45211
+ const current = JSON.parse(readFileSync32(join41(runtime.dirs.state, file), "utf8"));
44981
45212
  if (!Number.isInteger(current.serverPid) || !processExists(current.serverPid)) break;
44982
45213
  } catch {
44983
45214
  break;
@@ -44985,14 +45216,14 @@ async function cmdStop(runtime, waitMsFlag) {
44985
45216
  await (runtime.io.sleep ?? ((ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms))))(100);
44986
45217
  }
44987
45218
  try {
44988
- const current = JSON.parse(readFileSync31(join41(runtime.dirs.state, file), "utf8"));
45219
+ const current = JSON.parse(readFileSync32(join41(runtime.dirs.state, file), "utf8"));
44989
45220
  if (Number.isInteger(current.serverPid) && processExists(current.serverPid)) {
44990
45221
  try {
44991
45222
  process.kill(current.serverPid, "SIGKILL");
44992
45223
  } catch {
44993
45224
  }
44994
45225
  }
44995
- rmSync10(join41(runtime.dirs.state, file), { force: true });
45226
+ rmSync11(join41(runtime.dirs.state, file), { force: true });
44996
45227
  } catch {
44997
45228
  }
44998
45229
  stopped.add(file);
@@ -45003,11 +45234,11 @@ async function cmdStop(runtime, waitMsFlag) {
45003
45234
  }
45004
45235
  async function cmdSessionStatus(runtime) {
45005
45236
  const stateDir = runtime.dirs.state;
45006
- const names3 = existsSync31(stateDir) ? readdirSync12(stateDir) : [];
45237
+ const names3 = existsSync33(stateDir) ? readdirSync12(stateDir) : [];
45007
45238
  const claims = [];
45008
45239
  for (const file of names3.filter((name) => name.endsWith(".pid"))) {
45009
45240
  try {
45010
- const record = JSON.parse(readFileSync31(join41(stateDir, file), "utf8"));
45241
+ const record = JSON.parse(readFileSync32(join41(stateDir, file), "utf8"));
45011
45242
  claims.push({
45012
45243
  file,
45013
45244
  role: record.role,
@@ -45022,7 +45253,7 @@ async function cmdSessionStatus(runtime) {
45022
45253
  const recoveries = [];
45023
45254
  for (const file of names3.filter((name) => name.startsWith("worker-recovery-") && name.endsWith(".json"))) {
45024
45255
  try {
45025
- const envelope = JSON.parse(readFileSync31(join41(stateDir, file), "utf8"));
45256
+ const envelope = JSON.parse(readFileSync32(join41(stateDir, file), "utf8"));
45026
45257
  recoveries.push({
45027
45258
  file,
45028
45259
  reason: envelope.reason,
@@ -45046,6 +45277,12 @@ async function cmdSessionStatus(runtime) {
45046
45277
  return 0;
45047
45278
  }
45048
45279
  async function cmdAuthToken(runtime, options = {}) {
45280
+ const tokenFile = sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR);
45281
+ if (options.clear && runtime.dryRun) {
45282
+ const present = existsSync33(tokenFile);
45283
+ printPlan(runtime, { command: "auth token", cleared: present }, present ? [{ action: "delete", target: tokenFile }] : [], present ? "clear the session token from disk" : "no session token file to clear");
45284
+ return 0;
45285
+ }
45049
45286
  if (options.clear) {
45050
45287
  const cleared = clearSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45051
45288
  print(
@@ -45101,33 +45338,37 @@ async function cmdAuthToken(runtime, options = {}) {
45101
45338
  );
45102
45339
  return 1;
45103
45340
  }
45341
+ const existingToken = options.issue ? void 0 : readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45342
+ if (runtime.dryRun && !existingToken) {
45343
+ printPlan(runtime, { command: "auth token" }, [{ action: "write", target: tokenFile }], "issue an operator session token and persist it");
45344
+ return 0;
45345
+ }
45104
45346
  let token;
45105
- if (options.issue) {
45347
+ if (existingToken) {
45348
+ token = existingToken.token;
45349
+ } else {
45106
45350
  token = mintSessionToken({ preset: "operator" });
45107
45351
  persistSessionTokenToDisk(token, { userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45108
- } else {
45109
- const existing = readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45110
- if (existing) {
45111
- token = existing.token;
45112
- } else {
45113
- token = mintSessionToken({ preset: "operator" });
45114
- persistSessionTokenToDisk(token, { userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45115
- }
45116
45352
  }
45117
45353
  print(runtime.io, runtime.json, { ok: true, command: "auth token", token }, token);
45118
45354
  return 0;
45119
45355
  }
45120
45356
  async function cmdSessionBrief(runtime, options = {}) {
45121
45357
  const existing = readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45122
- const sessionToken = existing ? existing.token : mintSessionToken({ preset: "operator" });
45123
- if (!existing) {
45358
+ const planned = existing ? [] : [{ action: "write", target: sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR) }];
45359
+ const sessionToken = existing?.token ?? (runtime.dryRun ? void 0 : mintSessionToken({ preset: "operator" }));
45360
+ if (!existing && sessionToken) {
45124
45361
  persistSessionTokenToDisk(sessionToken, { userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
45125
45362
  }
45126
45363
  if (options.token) {
45364
+ if (!sessionToken) {
45365
+ printPlan(runtime, { command: "session brief" }, planned, "issue an operator session token and persist it");
45366
+ return 0;
45367
+ }
45127
45368
  print(runtime.io, runtime.json, { ok: true, command: "session brief", sessionToken }, sessionToken);
45128
45369
  return 0;
45129
45370
  }
45130
- const dataPath = resolve24(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join41(runtime.dirs.state, "kxm.db"));
45371
+ const dataPath = resolve26(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join41(runtime.dirs.state, "kxm.db"));
45131
45372
  const env = {
45132
45373
  ...runtime.env,
45133
45374
  KXM_STATE_DIR: runtime.dirs.state,
@@ -45149,8 +45390,17 @@ async function cmdSessionBrief(runtime, options = {}) {
45149
45390
  }
45150
45391
  const brief = await loadSessionBriefAsync(runtime.dirs.workdir, env, void 0, hub, {
45151
45392
  fetchImpl: runtime.fetchImpl,
45152
- sessionToken
45393
+ ...sessionToken ? { sessionToken } : {},
45394
+ writeCache: !runtime.dryRun
45153
45395
  });
45396
+ if (runtime.dryRun) {
45397
+ if (!readCachedSessionBrief(runtime.dirs.state)) planned.push({ action: "write", target: join41(runtime.dirs.state, "session-brief.json") });
45398
+ print(runtime.io, runtime.json, { ...brief, dryRun: true, planned }, [
45399
+ options.status ? brief.statusLine : formatSessionBriefText(brief),
45400
+ ...planned.map((change) => `dry run: would ${change.action} ${change.target}`)
45401
+ ].join("\n"));
45402
+ return 0;
45403
+ }
45154
45404
  if (options.status) {
45155
45405
  print(runtime.io, runtime.json, brief, brief.statusLine);
45156
45406
  return 0;
@@ -45204,7 +45454,7 @@ async function cmdSessionStart(runtime, options) {
45204
45454
  ...workflowId ? workflowAssetDirs(runtime.dirs.assets, workflowId) : []
45205
45455
  ];
45206
45456
  if (!runtime.dryRun) {
45207
- for (const directory of created) mkdirSync25(directory, { recursive: true });
45457
+ for (const directory of created) mkdirSync26(directory, { recursive: true });
45208
45458
  writeSession(runtime.dirs.assets, session);
45209
45459
  }
45210
45460
  print(runtime.io, runtime.json, {
@@ -45220,15 +45470,15 @@ async function cmdSessionStart(runtime, options) {
45220
45470
  // plugins/kxm/src/cli/system.ts
45221
45471
  import { spawnSync as spawnSync8 } from "node:child_process";
45222
45472
  import { createHash as createHash16 } from "node:crypto";
45223
- import { existsSync as existsSync37, mkdtempSync as mkdtempSync2, readFileSync as readFileSync36, rmSync as rmSync12 } from "node:fs";
45473
+ import { existsSync as existsSync39, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, rmSync as rmSync13 } from "node:fs";
45224
45474
  import { tmpdir as tmpdir2 } from "node:os";
45225
- import { join as join47, resolve as resolve28 } from "node:path";
45475
+ import { join as join47, resolve as resolve30 } from "node:path";
45226
45476
  import { createInterface as createInterface3 } from "node:readline";
45227
45477
 
45228
45478
  // plugins/kxm/src/improve.ts
45229
45479
  import { createHash as createHash15 } from "node:crypto";
45230
- import { existsSync as existsSync32, mkdirSync as mkdirSync26, writeFileSync as writeFileSync24 } from "node:fs";
45231
- import { join as join42, relative as relative6, resolve as resolve25 } from "node:path";
45480
+ import { existsSync as existsSync34, mkdirSync as mkdirSync27, writeFileSync as writeFileSync24 } from "node:fs";
45481
+ import { join as join42, relative as relative6, resolve as resolve27 } from "node:path";
45232
45482
  var CANDIDATE_SCHEMA = "kxm.candidate.v1";
45233
45483
  var IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2";
45234
45484
  function classifyCandidateKind(stepId, agentRole) {
@@ -45399,8 +45649,8 @@ function groupRoutingRecords(records, options = {}) {
45399
45649
  });
45400
45650
  }
45401
45651
  function buildImprovementReport(records, options = {}) {
45402
- const projectRoot = options.projectRoot ? resolve25(options.projectRoot) : process.cwd();
45403
- const candidatesDir = options.candidatesDir ? resolve25(options.candidatesDir) : join42(projectRoot, ".kxm", "candidates");
45652
+ const projectRoot = options.projectRoot ? resolve27(options.projectRoot) : process.cwd();
45653
+ const candidatesDir = options.candidatesDir ? resolve27(options.candidatesDir) : join42(projectRoot, ".kxm", "candidates");
45404
45654
  const groups = groupRoutingRecords(records, options);
45405
45655
  const candidates = [];
45406
45656
  for (const group of groups) {
@@ -45434,8 +45684,8 @@ function buildImprovementReport(records, options = {}) {
45434
45684
  createdAt: nowIso()
45435
45685
  };
45436
45686
  if (!options.dryRun) {
45437
- if (!existsSync32(candidatesDir)) {
45438
- mkdirSync26(candidatesDir, { recursive: true });
45687
+ if (!existsSync34(candidatesDir)) {
45688
+ mkdirSync27(candidatesDir, { recursive: true });
45439
45689
  }
45440
45690
  writeFileSync24(diffFilePath, diff, "utf8");
45441
45691
  const jsonFilePath = join42(candidatesDir, `${group.candidateId}.json`);
@@ -45456,7 +45706,7 @@ function writeImprovementReport(improvementsDir, report2, dryRun = false) {
45456
45706
  const stamp = report2.createdAt.replace(/[:.]/g, "-");
45457
45707
  const path4 = join42(improvementsDir, `${stamp}.json`);
45458
45708
  if (!dryRun) {
45459
- mkdirSync26(improvementsDir, { recursive: true });
45709
+ mkdirSync27(improvementsDir, { recursive: true });
45460
45710
  writeFileSync24(path4, `${JSON.stringify(report2, null, 2)}
45461
45711
  `, { encoding: "utf8" });
45462
45712
  }
@@ -45497,7 +45747,7 @@ function formatImprovementReport(report2) {
45497
45747
 
45498
45748
  // plugins/kxm/src/modes.ts
45499
45749
  var import_yaml16 = __toESM(require_dist(), 1);
45500
- import { existsSync as existsSync33, readFileSync as readFileSync33 } from "node:fs";
45750
+ import { existsSync as existsSync35, readFileSync as readFileSync34 } from "node:fs";
45501
45751
  import { join as join43 } from "node:path";
45502
45752
  var DEFAULT_MODES_CONFIG = Object.freeze({
45503
45753
  schema: "kxm.modes.v1",
@@ -45562,11 +45812,11 @@ function loadModesConfig(projectRoot) {
45562
45812
  return DEFAULT_MODES_CONFIG;
45563
45813
  }
45564
45814
  const modesPath = join43(projectRoot, ".kxm", "modes.yaml");
45565
- if (!existsSync33(modesPath)) {
45815
+ if (!existsSync35(modesPath)) {
45566
45816
  return DEFAULT_MODES_CONFIG;
45567
45817
  }
45568
45818
  try {
45569
- const raw = readFileSync33(modesPath, "utf8");
45819
+ const raw = readFileSync34(modesPath, "utf8");
45570
45820
  const parsed = (0, import_yaml16.parse)(raw);
45571
45821
  if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
45572
45822
  return {
@@ -45654,9 +45904,9 @@ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog
45654
45904
  for (const relPath of resolved.contextFiles) {
45655
45905
  const fullPath = join43(projectRoot, relPath);
45656
45906
  let chars = 0;
45657
- if (existsSync33(fullPath)) {
45907
+ if (existsSync35(fullPath)) {
45658
45908
  try {
45659
- chars = readFileSync33(fullPath, "utf8").length;
45909
+ chars = readFileSync34(fullPath, "utf8").length;
45660
45910
  } catch {
45661
45911
  chars = 0;
45662
45912
  }
@@ -45788,9 +46038,9 @@ ${divider}
45788
46038
 
45789
46039
  // plugins/kxm/src/ssh-remote.ts
45790
46040
  import { spawnSync as spawnSync7 } from "node:child_process";
45791
- import { existsSync as existsSync34, mkdirSync as mkdirSync27, readFileSync as readFileSync34, readdirSync as readdirSync13, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
46041
+ import { existsSync as existsSync36, mkdirSync as mkdirSync28, readFileSync as readFileSync35, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync5 } from "node:fs";
45792
46042
  import { homedir as homedir8 } from "node:os";
45793
- import { join as join44, resolve as resolve26 } from "node:path";
46043
+ import { join as join44, resolve as resolve28 } from "node:path";
45794
46044
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
45795
46045
  var MAX_SSH_OUTPUT_LINES = 2e3;
45796
46046
  var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
@@ -45817,11 +46067,11 @@ function truncateSshOutput(raw) {
45817
46067
  }
45818
46068
  function parseSshConfig(configPath) {
45819
46069
  const targetPath = configPath ?? join44(homedir8(), ".ssh", "config");
45820
- if (!existsSync34(targetPath)) {
46070
+ if (!existsSync36(targetPath)) {
45821
46071
  return [];
45822
46072
  }
45823
46073
  try {
45824
- const content = readFileSync34(targetPath, "utf-8");
46074
+ const content = readFileSync35(targetPath, "utf-8");
45825
46075
  const lines = content.split("\n");
45826
46076
  const hosts = [];
45827
46077
  let currentHosts = [];
@@ -45898,9 +46148,9 @@ function resolveSshHostG(host, execFn = spawnSync7) {
45898
46148
  }
45899
46149
  }
45900
46150
  function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
45901
- const resolved = resolve26(socketDir);
45902
- if (!existsSync34(resolved)) {
45903
- mkdirSync27(resolved, { recursive: true, mode: 448 });
46151
+ const resolved = resolve28(socketDir);
46152
+ if (!existsSync36(resolved)) {
46153
+ mkdirSync28(resolved, { recursive: true, mode: 448 });
45904
46154
  }
45905
46155
  return resolved;
45906
46156
  }
@@ -46388,9 +46638,9 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
46388
46638
  }
46389
46639
 
46390
46640
  // plugins/kxm/src/completion-install.ts
46391
- import { existsSync as existsSync35, mkdirSync as mkdirSync28, readFileSync as readFileSync35, writeFileSync as writeFileSync25 } from "node:fs";
46641
+ import { existsSync as existsSync37, mkdirSync as mkdirSync29, readFileSync as readFileSync36, writeFileSync as writeFileSync25 } from "node:fs";
46392
46642
  import { homedir as homedir9 } from "node:os";
46393
- import { basename as basename9, delimiter, dirname as dirname22, isAbsolute as isAbsolute9, join as join45, resolve as resolve27 } from "node:path";
46643
+ import { basename as basename9, delimiter, dirname as dirname23, isAbsolute as isAbsolute9, join as join45, resolve as resolve29 } from "node:path";
46394
46644
  var COMPLETION_MARKER = "# kxm completion";
46395
46645
  var PATH_MARKER = "# kxm path";
46396
46646
  function detectShell(env = process.env, platform = process.platform) {
@@ -46406,30 +46656,30 @@ function effectiveHome(options) {
46406
46656
  const env = options.env ?? process.env;
46407
46657
  if (options.homeDir) return options.homeDir;
46408
46658
  const envHome = env.HOME?.trim() ?? env.USERPROFILE?.trim();
46409
- return envHome && envHome.length > 0 ? resolve27(envHome) : homedir9();
46659
+ return envHome && envHome.length > 0 ? resolve29(envHome) : homedir9();
46410
46660
  }
46411
46661
  function completionScriptPath(shell, options = {}) {
46412
46662
  const env = options.env ?? process.env;
46413
46663
  const explicit = options.configDir ?? env.KXM_USER_CONFIG_DIR?.trim();
46414
- const base = explicit && explicit.length > 0 ? resolve27(explicit) : resolve27(effectiveHome(options), ".config", "kxm");
46664
+ const base = explicit && explicit.length > 0 ? resolve29(explicit) : resolve29(effectiveHome(options), ".config", "kxm");
46415
46665
  return join45(base, "completions", `kxm.${shell}`);
46416
46666
  }
46417
46667
  function bashRcCandidate(options) {
46418
46668
  const home = effectiveHome(options);
46419
46669
  const candidates = [join45(home, ".bashrc"), join45(home, ".bash_profile")];
46420
- const existing = candidates.find((candidate) => existsSync35(candidate));
46670
+ const existing = candidates.find((candidate) => existsSync37(candidate));
46421
46671
  return existing ?? candidates[0];
46422
46672
  }
46423
46673
  function zshRcCandidate(options) {
46424
46674
  const env = options.env ?? process.env;
46425
46675
  const home = effectiveHome(options);
46426
- if (env.ZDOTDIR?.trim()) return join45(resolve27(env.ZDOTDIR.trim()), ".zshrc");
46676
+ if (env.ZDOTDIR?.trim()) return join45(resolve29(env.ZDOTDIR.trim()), ".zshrc");
46427
46677
  return join45(home, ".zshrc");
46428
46678
  }
46429
46679
  function fishCompletionTarget(shell, options) {
46430
46680
  const env = options.env ?? process.env;
46431
46681
  const home = effectiveHome(options);
46432
- if (env.XDG_CONFIG_HOME?.trim()) return join45(resolve27(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
46682
+ if (env.XDG_CONFIG_HOME?.trim()) return join45(resolve29(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
46433
46683
  return join45(home, ".config", "fish", "completions", "kxm.fish");
46434
46684
  }
46435
46685
  function completionRcTarget(shell, options = {}) {
@@ -46462,16 +46712,16 @@ function installShellCompletion(shellInput, options = {}) {
46462
46712
  const scriptPath = completionScriptPath(shell, options);
46463
46713
  const { rcFile } = completionRcTarget(shell, options);
46464
46714
  const script = generateShellCompletion(shell);
46465
- const existingScript = existsSync35(scriptPath);
46466
- const existingScriptMatches = existingScript && readFileSync35(scriptPath, "utf8") === script;
46715
+ const existingScript = existsSync37(scriptPath);
46716
+ const existingScriptMatches = existingScript && readFileSync36(scriptPath, "utf8") === script;
46467
46717
  let rcModified = false;
46468
46718
  let alreadyInstalled = false;
46469
46719
  if (shell === "fish") {
46470
46720
  const fishTarget = fishCompletionTarget(shell, options);
46471
- const fishInstalled = existsSync35(fishTarget) && readFileSync35(fishTarget, "utf8") === script;
46721
+ const fishInstalled = existsSync37(fishTarget) && readFileSync36(fishTarget, "utf8") === script;
46472
46722
  alreadyInstalled = fishInstalled;
46473
46723
  if (!options.dryRun && (!fishInstalled || options.overwrite)) {
46474
- mkdirSync28(resolve27(fishTarget, ".."), { recursive: true });
46724
+ mkdirSync29(resolve29(fishTarget, ".."), { recursive: true });
46475
46725
  writeFileSync25(fishTarget, script, { encoding: "utf8" });
46476
46726
  }
46477
46727
  return {
@@ -46485,12 +46735,12 @@ function installShellCompletion(shellInput, options = {}) {
46485
46735
  }
46486
46736
  if (rcFile) {
46487
46737
  const line = sourceLine(shell, scriptPath);
46488
- const rcContent = existsSync35(rcFile) ? readFileSync35(rcFile, "utf8") : "";
46738
+ const rcContent = existsSync37(rcFile) ? readFileSync36(rcFile, "utf8") : "";
46489
46739
  alreadyInstalled = rcContent.includes(line) || rcContent.includes(`${COMPLETION_MARKER}`);
46490
46740
  }
46491
46741
  if (!options.dryRun) {
46492
46742
  if (!existingScriptMatches || options.overwrite) {
46493
- mkdirSync28(resolve27(scriptPath, ".."), { recursive: true });
46743
+ mkdirSync29(resolve29(scriptPath, ".."), { recursive: true });
46494
46744
  writeFileSync25(scriptPath, script, { encoding: "utf8" });
46495
46745
  }
46496
46746
  if (rcFile && !alreadyInstalled) {
@@ -46500,8 +46750,8 @@ function installShellCompletion(shellInput, options = {}) {
46500
46750
  ${marker}
46501
46751
  ${line}
46502
46752
  `;
46503
- mkdirSync28(resolve27(rcFile, ".."), { recursive: true });
46504
- writeFileSync25(rcFile, (existsSync35(rcFile) ? readFileSync35(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
46753
+ mkdirSync29(resolve29(rcFile, ".."), { recursive: true });
46754
+ writeFileSync25(rcFile, (existsSync37(rcFile) ? readFileSync36(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
46505
46755
  rcModified = true;
46506
46756
  }
46507
46757
  }
@@ -46517,14 +46767,14 @@ ${line}
46517
46767
  function kxmBinDir(env = process.env) {
46518
46768
  const argv1 = env.KXM_ENTRY ?? process.argv[1];
46519
46769
  if (argv1 && isAbsolute9(argv1)) {
46520
- const dir = dirname22(argv1);
46521
- if (existsSync35(join45(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
46770
+ const dir = dirname23(argv1);
46771
+ if (existsSync37(join45(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
46522
46772
  }
46523
46773
  const pathEnv = env.PATH ?? "";
46524
46774
  for (const part of pathEnv.split(delimiter)) {
46525
46775
  if (!part) continue;
46526
- const candidate = resolve27(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
46527
- if (existsSync35(candidate)) return resolve27(part);
46776
+ const candidate = resolve29(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
46777
+ if (existsSync37(candidate)) return resolve29(part);
46528
46778
  }
46529
46779
  return void 0;
46530
46780
  }
@@ -46542,10 +46792,10 @@ function installPathEntry(shellInput, options = {}) {
46542
46792
  }
46543
46793
  const { rcFile } = completionRcTarget(shell, options);
46544
46794
  const line = pathLine(binDir);
46545
- const rcContent = existsSync35(rcFile ?? "") ? readFileSync35(rcFile, "utf8") : "";
46795
+ const rcContent = existsSync37(rcFile ?? "") ? readFileSync36(rcFile, "utf8") : "";
46546
46796
  const alreadyInstalled = rcContent.includes(binDir) || (options.env?.PATH ?? process.env.PATH ?? "").split(delimiter).includes(binDir);
46547
46797
  if (!options.dryRun && rcFile && !alreadyInstalled) {
46548
- mkdirSync28(dirname22(rcFile), { recursive: true });
46798
+ mkdirSync29(dirname23(rcFile), { recursive: true });
46549
46799
  writeFileSync25(rcFile, rcContent + `
46550
46800
  ${PATH_MARKER}
46551
46801
  ${line}
@@ -46556,8 +46806,8 @@ ${line}
46556
46806
 
46557
46807
  // plugins/kxm/src/init-guide-setup.ts
46558
46808
  var import_yaml17 = __toESM(require_dist(), 1);
46559
- import { existsSync as existsSync36, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
46560
- import { dirname as dirname23, join as join46 } from "node:path";
46809
+ import { existsSync as existsSync38, mkdirSync as mkdirSync30, writeFileSync as writeFileSync26 } from "node:fs";
46810
+ import { dirname as dirname24, join as join46 } from "node:path";
46561
46811
  var NATIVE_VENDOR_HARNESS = Object.freeze({
46562
46812
  anthropic: "claude",
46563
46813
  openai: "codex",
@@ -46972,11 +47222,11 @@ function writeGuideSetupFiles(files) {
46972
47222
  const written = [];
46973
47223
  const existed = [];
46974
47224
  for (const file of files) {
46975
- if (existsSync36(file.path)) {
47225
+ if (existsSync38(file.path)) {
46976
47226
  existed.push(file.path);
46977
47227
  continue;
46978
47228
  }
46979
- mkdirSync29(dirname23(file.path), { recursive: true });
47229
+ mkdirSync30(dirname24(file.path), { recursive: true });
46980
47230
  writeFileSync26(file.path, file.content, "utf8");
46981
47231
  written.push(file.path);
46982
47232
  }
@@ -47040,7 +47290,7 @@ function applyKxmPackageUpdate(runtime, notice) {
47040
47290
  detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
47041
47291
  };
47042
47292
  }
47043
- const releaseDir = mkdtempSync2(join47(tmpdir2(), "kxm-pkg-update-"));
47293
+ const releaseDir = mkdtempSync3(join47(tmpdir2(), "kxm-pkg-update-"));
47044
47294
  try {
47045
47295
  const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset);
47046
47296
  if (runtime.dryRun) {
@@ -47049,7 +47299,7 @@ function applyKxmPackageUpdate(runtime, notice) {
47049
47299
  for (const step of planned) {
47050
47300
  if (step.kind === "verify") {
47051
47301
  if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
47052
- const actual = existsSync37(step.path) ? createHash16("sha256").update(readFileSync36(step.path)).digest("hex") : "missing";
47302
+ const actual = existsSync39(step.path) ? createHash16("sha256").update(readFileSync37(step.path)).digest("hex") : "missing";
47053
47303
  return {
47054
47304
  ok: false,
47055
47305
  error: "release_digest_mismatch",
@@ -47066,7 +47316,7 @@ function applyKxmPackageUpdate(runtime, notice) {
47066
47316
  }
47067
47317
  return { ok: true, detail: `installed ${notice.latest} from ${notice.source}` };
47068
47318
  } finally {
47069
- rmSync12(releaseDir, { recursive: true, force: true });
47319
+ rmSync13(releaseDir, { recursive: true, force: true });
47070
47320
  }
47071
47321
  }
47072
47322
  async function cmdExplain(runtime, options) {
@@ -47104,6 +47354,10 @@ async function cmdSshInfo(runtime, host) {
47104
47354
  }
47105
47355
  async function cmdSshRun(runtime, host, commandParts, options) {
47106
47356
  const command = commandParts.join(" ");
47357
+ if (runtime.dryRun) {
47358
+ printPlan(runtime, { command: "ssh run", host, remoteCommand: command, sudo: options.sudo === true }, [{ action: "ssh", target: `${host}: ${options.sudo ? "sudo " : ""}${command}` }], `run a command on ${host}`);
47359
+ return 0;
47360
+ }
47107
47361
  const receipt = executeSshRun({
47108
47362
  action: "command",
47109
47363
  host,
@@ -47122,6 +47376,10 @@ async function cmdSshRun(runtime, host, commandParts, options) {
47122
47376
  }
47123
47377
  async function cmdSshFile(runtime, host, filePath, options) {
47124
47378
  const op = options.read ? "read" : options.append ? "append" : "write";
47379
+ if (runtime.dryRun) {
47380
+ printPlan(runtime, { command: "ssh file", host, path: filePath, op, sudo: options.sudo === true }, [{ action: "ssh", target: `${host}: ${op} ${filePath}` }], `${op} ${filePath} on ${host}`);
47381
+ return 0;
47382
+ }
47125
47383
  const receipt = executeSshRun({
47126
47384
  action: "file",
47127
47385
  host,
@@ -47145,6 +47403,10 @@ async function cmdSshFile(runtime, host, filePath, options) {
47145
47403
  return receipt.ok ? 0 : receipt.exitCode || 1;
47146
47404
  }
47147
47405
  async function cmdSshClose(runtime, host) {
47406
+ if (runtime.dryRun) {
47407
+ printPlan(runtime, { command: "ssh close", host }, [{ action: "ssh", target: `${host}: close the ControlMaster socket` }], `close the ControlMaster socket for ${host}`);
47408
+ return 0;
47409
+ }
47148
47410
  const closed = closeControlSocket(host);
47149
47411
  if (runtime.json) {
47150
47412
  print(runtime.io, runtime.json, { ok: true, closed, command: "ssh close", host }, "");
@@ -47314,13 +47576,13 @@ async function cmdValidate(runtime, fileFlag) {
47314
47576
  return 2;
47315
47577
  }
47316
47578
  const selectedFile = explicitFile || configuredFile;
47317
- const file = selectedFile ? resolve28(runtime.cwd, selectedFile) : void 0;
47318
- if (file && !existsSync37(file)) {
47579
+ const file = selectedFile ? resolve30(runtime.cwd, selectedFile) : void 0;
47580
+ if (file && !existsSync39(file)) {
47319
47581
  printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
47320
47582
  return 1;
47321
47583
  }
47322
47584
  try {
47323
- const raw = file ? readFileSync36(file, "utf8") : inline;
47585
+ const raw = file ? readFileSync37(file, "utf8") : inline;
47324
47586
  const warnings = [];
47325
47587
  const definitions = parseWorkflowDefinitions(raw, runtime.env, (message) => warnings.push(message));
47326
47588
  const secretEnvs = definitions.map((definition) => ({
@@ -47345,7 +47607,7 @@ async function cmdValidate(runtime, fileFlag) {
47345
47607
  }
47346
47608
  async function cmdArtifactsExist(runtime, pathFlag) {
47347
47609
  const worker = gateOf(runtime, "artifacts-exist");
47348
- const checked = verifyArtifactExists(runtime.dirs.assets, resolve28(runtime.cwd, pathFlag));
47610
+ const checked = verifyArtifactExists(runtime.dirs.assets, resolve30(runtime.cwd, pathFlag));
47349
47611
  if (!checked.ok) {
47350
47612
  printWorker(
47351
47613
  runtime,
@@ -47364,9 +47626,9 @@ async function cmdArtifactsExist(runtime, pathFlag) {
47364
47626
  return 0;
47365
47627
  }
47366
47628
  async function cmdImprove(runtime, options = {}) {
47367
- const file = options.file ? resolve28(runtime.cwd, options.file) : telemetryPath(runtime.dirs.logs);
47368
- const routingRecords = existsSync37(file) ? readRoutingRecords(file).map((entry) => entry.routing) : [];
47369
- const candidatesDir = options.outDir ? resolve28(runtime.cwd, options.outDir) : join47(runtime.cwd, ".kxm", "candidates");
47629
+ const file = options.file ? resolve30(runtime.cwd, options.file) : telemetryPath(runtime.dirs.logs);
47630
+ const routingRecords = existsSync39(file) ? readRoutingRecords(file).map((entry) => entry.routing) : [];
47631
+ const candidatesDir = options.outDir ? resolve30(runtime.cwd, options.outDir) : join47(runtime.cwd, ".kxm", "candidates");
47370
47632
  const report2 = buildImprovementReport(routingRecords, {
47371
47633
  candidatesDir,
47372
47634
  projectRoot: runtime.cwd,
@@ -47415,7 +47677,11 @@ async function cmdConfigSet(runtime, key, value, options) {
47415
47677
  parsedVal = JSON.parse(value);
47416
47678
  } catch {
47417
47679
  }
47418
- setKxmConfigValue(runtime.cwd, key, parsedVal, { scope });
47680
+ const { file } = setKxmConfigValue(runtime.cwd, key, parsedVal, { scope, dryRun: runtime.dryRun });
47681
+ if (runtime.dryRun) {
47682
+ printPlan(runtime, { command: "config set", key, value: parsedVal, scope }, [{ action: "write", target: file }], `set ${key} = ${value} in ${scope} config`);
47683
+ return 0;
47684
+ }
47419
47685
  print(
47420
47686
  runtime.io,
47421
47687
  runtime.json,
@@ -47504,9 +47770,9 @@ async function maybeOfferCompletionInstall(runtime) {
47504
47770
  if (!pathNeeded) {
47505
47771
  const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
47506
47772
  const { rcFile } = completionRcTarget(shell, { env: runtime.env });
47507
- if (rcFile && existsSync37(rcFile)) {
47773
+ if (rcFile && existsSync39(rcFile)) {
47508
47774
  try {
47509
- if (readFileSync36(rcFile, "utf8").includes(scriptPath)) return;
47775
+ if (readFileSync37(rcFile, "utf8").includes(scriptPath)) return;
47510
47776
  } catch {
47511
47777
  }
47512
47778
  }
@@ -47638,7 +47904,7 @@ async function cmdRoutingReport(runtime, options) {
47638
47904
  let catalog;
47639
47905
  if (includeEquivalentListCost) {
47640
47906
  try {
47641
- const pricesPath = options.prices ? resolve28(runtime.cwd, options.prices) : join47(runtime.dirs.workspace, "prices.yaml");
47907
+ const pricesPath = options.prices ? resolve30(runtime.cwd, options.prices) : join47(runtime.dirs.workspace, "prices.yaml");
47642
47908
  catalog = loadPriceCatalog(pricesPath);
47643
47909
  } catch {
47644
47910
  }
@@ -47735,6 +48001,129 @@ var USAGE_ERROR_CODES = /* @__PURE__ */ new Set([
47735
48001
  "commander.invalidOptionArgument",
47736
48002
  "commander.optionMissingArgument"
47737
48003
  ]);
48004
+ var DRY_RUN_COMMANDS = /* @__PURE__ */ new Set([
48005
+ "init",
48006
+ "backup",
48007
+ "restore",
48008
+ "run",
48009
+ "explain",
48010
+ "suggest",
48011
+ "update",
48012
+ "dash",
48013
+ "completion",
48014
+ "completion install",
48015
+ "runs status",
48016
+ "runs drive",
48017
+ "runs receipt",
48018
+ "runs cancel",
48019
+ "runs list",
48020
+ "tenant status",
48021
+ "models inventory-refresh",
48022
+ "harness list",
48023
+ "auth token",
48024
+ "routes list",
48025
+ "routes count",
48026
+ "routes admit",
48027
+ "routes disable",
48028
+ "runtime start",
48029
+ "runtime status",
48030
+ "runtime sync-retry",
48031
+ "runtime stop",
48032
+ "trust diff",
48033
+ "trust check",
48034
+ "agent worker",
48035
+ "session status",
48036
+ "session brief",
48037
+ "session token",
48038
+ "session start",
48039
+ "session stop",
48040
+ "peer list",
48041
+ "peer send",
48042
+ "peer get",
48043
+ "peer await",
48044
+ "peer cancel",
48045
+ "peer fanout",
48046
+ "peer inbox",
48047
+ "peer reply",
48048
+ "workflow list",
48049
+ "workflow get",
48050
+ "workflow checkpoint",
48051
+ "workflow record",
48052
+ "workflow wait",
48053
+ "workflow signal",
48054
+ "workflow start",
48055
+ "workflow export",
48056
+ "workflow definitions",
48057
+ "workflow add",
48058
+ "workflow remove",
48059
+ "workflow modify",
48060
+ "role list",
48061
+ "role get",
48062
+ "role add",
48063
+ "role remove",
48064
+ "role modify",
48065
+ "role hosts",
48066
+ "role set-host",
48067
+ "role resume",
48068
+ "gate validate",
48069
+ "gate artifacts-exist",
48070
+ "gate degrade",
48071
+ "gate signal",
48072
+ "gate github watch",
48073
+ "improve report",
48074
+ "context get",
48075
+ "context recall",
48076
+ "context state",
48077
+ "context episode",
48078
+ "context promote",
48079
+ "context explain",
48080
+ "context wiki-compile",
48081
+ "context wiki-lint",
48082
+ "skills create",
48083
+ "skills evaluate",
48084
+ "skills promote",
48085
+ "skills reject",
48086
+ "skills list",
48087
+ "skills verify",
48088
+ "memory brief",
48089
+ "memory note",
48090
+ "memory sync",
48091
+ "routing report",
48092
+ "routing benchmark",
48093
+ "ssh info",
48094
+ "ssh run",
48095
+ "ssh file",
48096
+ "ssh close",
48097
+ "hub view",
48098
+ "hub start",
48099
+ "hub stop",
48100
+ "hub bind",
48101
+ "hub unbind",
48102
+ "config get",
48103
+ "config set",
48104
+ "config list",
48105
+ "goal create",
48106
+ "goal list",
48107
+ "task create",
48108
+ "task list",
48109
+ "task get",
48110
+ "task run",
48111
+ "task sync",
48112
+ "studio layout",
48113
+ "studio serve"
48114
+ ]);
48115
+ var DryRunRefused = class extends Error {
48116
+ code;
48117
+ constructor(code) {
48118
+ super("dry_run_unsupported");
48119
+ this.code = code;
48120
+ }
48121
+ };
48122
+ function commandPath(command) {
48123
+ const names3 = [];
48124
+ for (let current = command; current?.parent; current = current.parent) names3.unshift(current.name());
48125
+ return names3.join(" ");
48126
+ }
47738
48127
  function addGlobalOptions(command) {
47739
48128
  return command.option("--json", "Print machine-readable JSON").option("--dry-run", "Plan without making changes").option("--workspace <dir>", "Workspace directory");
47740
48129
  }
@@ -47870,6 +48259,13 @@ function createProgram(ctx, result) {
47870
48259
  writeErr: (text) => ctx.io.stderr(text)
47871
48260
  }).helpCommand("help", "Show help");
47872
48261
  addGlobalOptions(program2);
48262
+ program2.hook("preAction", (_program, actionCommand) => {
48263
+ const opts = actionCommand.optsWithGlobals();
48264
+ if (!opts.dryRun) return;
48265
+ const path4 = commandPath(actionCommand);
48266
+ if (DRY_RUN_COMMANDS.has(path4)) return;
48267
+ throw new DryRunRefused(refuseDryRun(ctx.io, Boolean(opts.json), path4, "this command cannot plan without making changes; rerun without --dry-run"));
48268
+ });
47873
48269
  program2.command("init").description("Create, validate, repair, or join a KXM project").option("--json", "Print machine-readable JSON").option("--dry-run", "Plan without making changes").option("--name <name>", "Project display name for a new project").option("--project-id <id>", "Stable project ID for controlled provisioning").option("--repository <id=absolute-path>", "Bind a member repository outside Git configuration", (value, previous) => [...previous, value], []).action(async function initAction(options) {
47874
48270
  result.code = await cmdKxmInit(runtimeFrom(ctx, this), options, {
47875
48271
  maybeOfferCompletionInstall,
@@ -48337,10 +48733,11 @@ async function runCli(argv, env = process.env, io = { stdout: (text) => process.
48337
48733
  return result.code;
48338
48734
  } catch (error) {
48339
48735
  if (error instanceof CommanderError) return mapCommanderError(error);
48736
+ if (error instanceof DryRunRefused) return error.code;
48340
48737
  throw error;
48341
48738
  }
48342
48739
  }
48343
- if (process.argv[1] && resolve29(process.argv[1]) === fileURLToPath4(import.meta.url)) {
48740
+ if (process.argv[1] && resolve31(process.argv[1]) === fileURLToPath4(import.meta.url)) {
48344
48741
  const code = await runCli(process.argv.slice(2));
48345
48742
  process.exitCode = code;
48346
48743
  }