@kontextmind/kxm 0.7.0 → 0.7.5

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 (78) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/roles/writer.yaml +2 -0
  3. package/docs/README.md +3 -0
  4. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  5. package/docs/agent-skills.md +19 -2
  6. package/docs/browser-automation.md +116 -0
  7. package/docs/configuration.md +10 -1
  8. package/docs/getting-started.md +21 -0
  9. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  10. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  11. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  12. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  13. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  14. package/docs/kb/how-to-take-over-session.md +32 -0
  15. package/docs/kb/why-authentication-disappeared.md +32 -0
  16. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  17. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  18. package/docs/operations.md +24 -0
  19. package/docs/prompts/browser-annotate-feedback.md +41 -0
  20. package/docs/prompts/browser-diagnose-recover.md +38 -0
  21. package/docs/prompts/browser-explore.md +42 -0
  22. package/docs/prompts/browser-repro-fix.md +48 -0
  23. package/docs/prompts/browser-start.md +41 -0
  24. package/docs/prompts/browser-takeover.md +50 -0
  25. package/docs/skills/repo-work-delivery.md +5 -0
  26. package/docs/skills.md +2 -0
  27. package/docs/troubleshooting.md +22 -1
  28. package/package.json +1 -1
  29. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  30. package/plugins/kxm/dist/cli.js +2257 -439
  31. package/plugins/kxm/dist/core.js +57 -0
  32. package/plugins/kxm/dist/extension.js +40 -3
  33. package/plugins/kxm/dist/mcp-server.js +1 -1
  34. package/plugins/kxm/dist/runtime.js +572 -17
  35. package/plugins/kxm/dist/server.js +129 -4
  36. package/plugins/kxm/dist/vnext-runtime-supervisor.js +136 -13
  37. package/plugins/kxm/package.json +1 -1
  38. package/plugins/kxm/skills/hints.json +30 -0
  39. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  40. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  41. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  42. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  43. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  44. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  45. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  46. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -0
  47. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +9 -2
  48. package/plugins/kxm/src/autocomplete.ts +1 -1
  49. package/plugins/kxm/src/browser.ts +603 -0
  50. package/plugins/kxm/src/cli.ts +507 -9
  51. package/plugins/kxm/src/completion-install.ts +223 -0
  52. package/plugins/kxm/src/database.ts +1 -1
  53. package/plugins/kxm/src/external-effects.ts +1 -1
  54. package/plugins/kxm/src/hub-env.ts +193 -0
  55. package/plugins/kxm/src/init-guide-setup.ts +547 -0
  56. package/plugins/kxm/src/local-snapshot.ts +1 -1
  57. package/plugins/kxm/src/mcp-server.ts +1 -1
  58. package/plugins/kxm/src/protocol.ts +111 -0
  59. package/plugins/kxm/src/role.ts +335 -0
  60. package/plugins/kxm/src/runtime.ts +1 -0
  61. package/plugins/kxm/src/safety-integrity.ts +76 -0
  62. package/plugins/kxm/src/sqlite.ts +76 -0
  63. package/plugins/kxm/src/store.ts +1 -1
  64. package/plugins/kxm/src/vnext-bindings.ts +1 -1
  65. package/plugins/kxm/src/vnext-config.ts +38 -1
  66. package/plugins/kxm/src/vnext-engine-command.ts +2 -0
  67. package/plugins/kxm/src/vnext-engine.ts +16 -0
  68. package/plugins/kxm/src/vnext-harness.ts +4 -2
  69. package/plugins/kxm/src/vnext-oneshot-process.ts +21 -4
  70. package/plugins/kxm/src/vnext-oneshot-producer.ts +18 -0
  71. package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
  72. package/plugins/kxm/src/workflow-tui.ts +1 -1
  73. package/plugins/kxm/src/workflow.ts +144 -0
  74. package/scripts/kxm-bump-version.mjs +146 -0
  75. package/scripts/kxm-hub.mjs +145 -3
  76. package/scripts/kxm-publish-npm.mjs +3 -1
  77. package/scripts/kxm-release-github.mjs +3 -1
  78. package/scripts/kxm.mjs +0 -0
@@ -1504,7 +1504,7 @@ var require_stringify = __commonJS({
1504
1504
  props.push(doc.directives.tagString(tag));
1505
1505
  return props.join(" ");
1506
1506
  }
1507
- function stringify10(item, ctx, onComment, onChompKeep) {
1507
+ function stringify11(item, ctx, onComment, onChompKeep) {
1508
1508
  if (identity.isPair(item))
1509
1509
  return item.toString(ctx, onComment, onChompKeep);
1510
1510
  if (identity.isAlias(item)) {
@@ -1533,7 +1533,7 @@ var require_stringify = __commonJS({
1533
1533
  ${ctx.indent}${str}`;
1534
1534
  }
1535
1535
  exports.createStringifyContext = createStringifyContext;
1536
- exports.stringify = stringify10;
1536
+ exports.stringify = stringify11;
1537
1537
  }
1538
1538
  });
1539
1539
 
@@ -1543,7 +1543,7 @@ var require_stringifyPair = __commonJS({
1543
1543
  "use strict";
1544
1544
  var identity = require_identity();
1545
1545
  var Scalar = require_Scalar();
1546
- var stringify10 = require_stringify();
1546
+ var stringify11 = require_stringify();
1547
1547
  var stringifyComment = require_stringifyComment();
1548
1548
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
1549
1549
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
@@ -1565,7 +1565,7 @@ var require_stringifyPair = __commonJS({
1565
1565
  });
1566
1566
  let keyCommentDone = false;
1567
1567
  let chompKeep = false;
1568
- let str = stringify10.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1568
+ let str = stringify11.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1569
1569
  if (!explicitKey && !ctx.inFlow && str.length > 1024) {
1570
1570
  if (simpleKeys)
1571
1571
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -1617,7 +1617,7 @@ ${indent}:`;
1617
1617
  ctx.indent = ctx.indent.substring(2);
1618
1618
  }
1619
1619
  let valueCommentDone = false;
1620
- const valueStr = stringify10.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
1620
+ const valueStr = stringify11.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
1621
1621
  let ws = " ";
1622
1622
  if (keyComment || vsb || vcb) {
1623
1623
  ws = vsb ? "\n" : "";
@@ -1758,7 +1758,7 @@ var require_addPairToJSMap = __commonJS({
1758
1758
  "use strict";
1759
1759
  var log = require_log();
1760
1760
  var merge = require_merge();
1761
- var stringify10 = require_stringify();
1761
+ var stringify11 = require_stringify();
1762
1762
  var identity = require_identity();
1763
1763
  var toJS = require_toJS();
1764
1764
  function addPairToJSMap(ctx, map, { key, value }) {
@@ -1794,7 +1794,7 @@ var require_addPairToJSMap = __commonJS({
1794
1794
  if (typeof jsKey !== "object")
1795
1795
  return String(jsKey);
1796
1796
  if (identity.isNode(key) && ctx?.doc) {
1797
- const strCtx = stringify10.createStringifyContext(ctx.doc, {});
1797
+ const strCtx = stringify11.createStringifyContext(ctx.doc, {});
1798
1798
  strCtx.anchors = /* @__PURE__ */ new Set();
1799
1799
  for (const node of ctx.anchors.keys())
1800
1800
  strCtx.anchors.add(node.anchor);
@@ -1861,12 +1861,12 @@ var require_stringifyCollection = __commonJS({
1861
1861
  "node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
1862
1862
  "use strict";
1863
1863
  var identity = require_identity();
1864
- var stringify10 = require_stringify();
1864
+ var stringify11 = require_stringify();
1865
1865
  var stringifyComment = require_stringifyComment();
1866
1866
  function stringifyCollection(collection, ctx, options) {
1867
1867
  const flow = ctx.inFlow ?? collection.flow;
1868
- const stringify11 = flow ? stringifyFlowCollection : stringifyBlockCollection;
1869
- return stringify11(collection, ctx, options);
1868
+ const stringify12 = flow ? stringifyFlowCollection : stringifyBlockCollection;
1869
+ return stringify12(collection, ctx, options);
1870
1870
  }
1871
1871
  function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
1872
1872
  const { indent, options: { commentString } } = ctx;
@@ -1891,7 +1891,7 @@ var require_stringifyCollection = __commonJS({
1891
1891
  }
1892
1892
  }
1893
1893
  chompKeep = false;
1894
- let str2 = stringify10.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
1894
+ let str2 = stringify11.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
1895
1895
  if (comment2)
1896
1896
  str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
1897
1897
  if (chompKeep && comment2)
@@ -1958,7 +1958,7 @@ ${indent}${line}` : "\n";
1958
1958
  }
1959
1959
  if (comment)
1960
1960
  reqNewline = true;
1961
- let str = stringify10.stringify(item, itemCtx, () => comment = null);
1961
+ let str = stringify11.stringify(item, itemCtx, () => comment = null);
1962
1962
  reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
1963
1963
  if (i < items.length - 1) {
1964
1964
  str += ",";
@@ -3319,7 +3319,7 @@ var require_stringifyDocument = __commonJS({
3319
3319
  "node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
3320
3320
  "use strict";
3321
3321
  var identity = require_identity();
3322
- var stringify10 = require_stringify();
3322
+ var stringify11 = require_stringify();
3323
3323
  var stringifyComment = require_stringifyComment();
3324
3324
  function stringifyDocument(doc, options) {
3325
3325
  const lines = [];
@@ -3334,7 +3334,7 @@ var require_stringifyDocument = __commonJS({
3334
3334
  }
3335
3335
  if (hasDirectives)
3336
3336
  lines.push("---");
3337
- const ctx = stringify10.createStringifyContext(doc, options);
3337
+ const ctx = stringify11.createStringifyContext(doc, options);
3338
3338
  const { commentString } = ctx.options;
3339
3339
  if (doc.commentBefore) {
3340
3340
  if (lines.length !== 1)
@@ -3356,7 +3356,7 @@ var require_stringifyDocument = __commonJS({
3356
3356
  contentComment = doc.contents.comment;
3357
3357
  }
3358
3358
  const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
3359
- let body = stringify10.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
3359
+ let body = stringify11.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
3360
3360
  if (contentComment)
3361
3361
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
3362
3362
  if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
@@ -3364,7 +3364,7 @@ var require_stringifyDocument = __commonJS({
3364
3364
  } else
3365
3365
  lines.push(body);
3366
3366
  } else {
3367
- lines.push(stringify10.stringify(doc.contents, ctx));
3367
+ lines.push(stringify11.stringify(doc.contents, ctx));
3368
3368
  }
3369
3369
  if (doc.directives?.docEnd) {
3370
3370
  if (doc.comment) {
@@ -5499,7 +5499,7 @@ var require_cst_scalar = __commonJS({
5499
5499
  var require_cst_stringify = __commonJS({
5500
5500
  "node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
5501
5501
  "use strict";
5502
- var stringify10 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
5502
+ var stringify11 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
5503
5503
  function stringifyToken(token) {
5504
5504
  switch (token.type) {
5505
5505
  case "block-scalar": {
@@ -5552,7 +5552,7 @@ var require_cst_stringify = __commonJS({
5552
5552
  res += stringifyToken(value);
5553
5553
  return res;
5554
5554
  }
5555
- exports.stringify = stringify10;
5555
+ exports.stringify = stringify11;
5556
5556
  }
5557
5557
  });
5558
5558
 
@@ -7282,7 +7282,7 @@ var require_public_api = __commonJS({
7282
7282
  }
7283
7283
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
7284
7284
  }
7285
- function stringify10(value, replacer, options) {
7285
+ function stringify11(value, replacer, options) {
7286
7286
  let _replacer = null;
7287
7287
  if (typeof replacer === "function" || Array.isArray(replacer)) {
7288
7288
  _replacer = replacer;
@@ -7307,7 +7307,7 @@ var require_public_api = __commonJS({
7307
7307
  exports.parse = parse11;
7308
7308
  exports.parseAllDocuments = parseAllDocuments;
7309
7309
  exports.parseDocument = parseDocument2;
7310
- exports.stringify = stringify10;
7310
+ exports.stringify = stringify11;
7311
7311
  }
7312
7312
  });
7313
7313
 
@@ -7491,10 +7491,10 @@ var require_code = __commonJS({
7491
7491
  function interpolate(x2) {
7492
7492
  return typeof x2 == "number" || typeof x2 == "boolean" || x2 === null ? x2 : safeStringify(Array.isArray(x2) ? x2.join(",") : x2);
7493
7493
  }
7494
- function stringify10(x2) {
7494
+ function stringify11(x2) {
7495
7495
  return new _Code(safeStringify(x2));
7496
7496
  }
7497
- exports.stringify = stringify10;
7497
+ exports.stringify = stringify11;
7498
7498
  function safeStringify(x2) {
7499
7499
  return JSON.stringify(x2).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
7500
7500
  }
@@ -10314,7 +10314,7 @@ var require_compile = __commonJS({
10314
10314
  const schOrFunc = root.refs[ref];
10315
10315
  if (schOrFunc)
10316
10316
  return schOrFunc;
10317
- let _sch = resolve22.call(this, root, ref);
10317
+ let _sch = resolve23.call(this, root, ref);
10318
10318
  if (_sch === void 0) {
10319
10319
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
10320
10320
  const { schemaId } = this.opts;
@@ -10341,7 +10341,7 @@ var require_compile = __commonJS({
10341
10341
  function sameSchemaEnv(s1, s2) {
10342
10342
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
10343
10343
  }
10344
- function resolve22(root, ref) {
10344
+ function resolve23(root, ref) {
10345
10345
  let sch;
10346
10346
  while (typeof (sch = this.refs[ref]) == "string")
10347
10347
  ref = sch;
@@ -11166,7 +11166,7 @@ var require_fast_uri = __commonJS({
11166
11166
  }
11167
11167
  return uri;
11168
11168
  }
11169
- function resolve22(baseURI, relativeURI, options) {
11169
+ function resolve23(baseURI, relativeURI, options) {
11170
11170
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
11171
11171
  const {
11172
11172
  parsed: baseParsed,
@@ -11528,7 +11528,7 @@ var require_fast_uri = __commonJS({
11528
11528
  var fastUri = {
11529
11529
  SCHEMES,
11530
11530
  normalize,
11531
- resolve: resolve22,
11531
+ resolve: resolve23,
11532
11532
  resolveComponent,
11533
11533
  equal,
11534
11534
  serialize,
@@ -14777,11 +14777,51 @@ var require__ = __commonJS({
14777
14777
 
14778
14778
  // plugins/kxm/src/cli.ts
14779
14779
  import { spawn as spawn2, spawnSync as spawnSync8 } from "node:child_process";
14780
- import { createHash as createHash15, createHmac as createHmac3, randomUUID as randomUUID11 } from "node:crypto";
14781
- import { existsSync as existsSync28, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync2, readFileSync as readFileSync27, readdirSync as readdirSync11, rmSync as rmSync10, writeFileSync as writeFileSync24 } from "node:fs";
14782
- import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
14783
- import { basename as basename6, dirname as dirname19, join as join34, relative as relative6, resolve as resolve21 } from "node:path";
14784
- import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
14780
+ import { createHash as createHash16, createHmac as createHmac3, randomUUID as randomUUID12 } from "node:crypto";
14781
+ import { existsSync as existsSync30, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync2, readFileSync as readFileSync28, readdirSync as readdirSync11, rmSync as rmSync10, writeFileSync as writeFileSync26 } from "node:fs";
14782
+ import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
14783
+ import { basename as basename7, dirname as dirname21, join as join36, relative as relative6, resolve as resolve22 } from "node:path";
14784
+
14785
+ // plugins/kxm/src/sqlite.ts
14786
+ import { createRequire } from "node:module";
14787
+ var requireFromHere = createRequire(import.meta.url);
14788
+ function loadNative() {
14789
+ try {
14790
+ const mod = requireFromHere("node:sqlite");
14791
+ if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
14792
+ } catch {
14793
+ }
14794
+ try {
14795
+ const mod = requireFromHere("bun:sqlite");
14796
+ const Ctor = mod?.DatabaseSync ?? mod?.Database;
14797
+ if (Ctor) return { Ctor, bun: true };
14798
+ } catch {
14799
+ }
14800
+ throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
14801
+ }
14802
+ var native = loadNative();
14803
+ var DatabaseSync = class {
14804
+ inner;
14805
+ constructor(path4, options) {
14806
+ let normalized = options;
14807
+ if (native.bun && options) {
14808
+ const { readOnly, ...rest } = options;
14809
+ normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
14810
+ }
14811
+ this.inner = normalized === void 0 ? new native.Ctor(path4) : new native.Ctor(path4, normalized);
14812
+ }
14813
+ prepare(sql) {
14814
+ return this.inner.prepare(sql);
14815
+ }
14816
+ exec(sql) {
14817
+ return this.inner.exec(sql);
14818
+ }
14819
+ close() {
14820
+ this.inner.close();
14821
+ }
14822
+ };
14823
+
14824
+ // plugins/kxm/src/cli.ts
14785
14825
  import { createInterface } from "node:readline";
14786
14826
  import { fileURLToPath as fileURLToPath4 } from "node:url";
14787
14827
 
@@ -18491,7 +18531,6 @@ import {
18491
18531
  writeFileSync as writeFileSync2
18492
18532
  } from "node:fs";
18493
18533
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
18494
- import { DatabaseSync } from "node:sqlite";
18495
18534
 
18496
18535
  // plugins/kxm/src/vnext-config.ts
18497
18536
  var import__ = __toESM(require__(), 1);
@@ -19074,7 +19113,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
19074
19113
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
19075
19114
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
19076
19115
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
19077
- grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
19116
+ grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
19117
+ agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
19118
+ kimi: Object.freeze(["--plan"])
19078
19119
  });
19079
19120
  function oneShotReadOnlyArgs(harness) {
19080
19121
  return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
@@ -19128,7 +19169,7 @@ var BUILTIN_HARNESSES = Object.freeze([
19128
19169
  authArgs: ["provider", "list"],
19129
19170
  update: { self: ["upgrade"] },
19130
19171
  oneShot: {
19131
- argv: ["--output-format", "stream-json", "-p"],
19172
+ argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
19132
19173
  promptVia: "arg",
19133
19174
  outputFormat: "stream-json",
19134
19175
  usageParser: parseKimiOneShotUsage
@@ -19191,7 +19232,7 @@ var BUILTIN_HARNESSES = Object.freeze([
19191
19232
  authArgs: ["models"],
19192
19233
  update: { self: ["update"] },
19193
19234
  oneShot: {
19194
- argv: ["--output-format", "json", "-p"],
19235
+ argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
19195
19236
  promptVia: "arg",
19196
19237
  outputFormat: "json",
19197
19238
  usageParser: parseAgyOneShotUsage
@@ -20323,8 +20364,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
20323
20364
  const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
20324
20365
  if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
20325
20366
  }
20326
- const join35 = objectValue(step.join);
20327
- const minimumPassed = join35 && typeof join35.minimumPassed === "number" ? join35.minimumPassed : void 0;
20367
+ const join37 = objectValue(step.join);
20368
+ const minimumPassed = join37 && typeof join37.minimumPassed === "number" ? join37.minimumPassed : void 0;
20328
20369
  if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
20329
20370
  const distinctBy = names(assignment?.distinctBy);
20330
20371
  if (distinctBy.length > 0) {
@@ -20469,7 +20510,7 @@ function validateModelReferences(models, issues) {
20469
20510
  };
20470
20511
  for (const id of models.keys()) walk(id, []);
20471
20512
  }
20472
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
20513
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
20473
20514
  const issues = [];
20474
20515
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
20475
20516
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -20537,6 +20578,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
20537
20578
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
20538
20579
  if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
20539
20580
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
20581
+ if (projectRoot) {
20582
+ const writerRolePath = join2(projectRoot, ".kxm", "roles", "writer.yaml");
20583
+ const implementerAgent = agents.get("implementer") ?? agents.get("writer");
20584
+ if (existsSync3(writerRolePath) && implementerAgent) {
20585
+ try {
20586
+ const rawRole = parseRestrictedYaml2(readFileSync2(writerRolePath, "utf8"));
20587
+ const roleObj = objectValue(rawRole);
20588
+ const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
20589
+ const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m2) => Boolean(m2));
20590
+ const agentModelObj = objectValue(implementerAgent.value.model);
20591
+ const agentModelStr = stringValue(implementerAgent.value.model);
20592
+ const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
20593
+ const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
20594
+ const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
20595
+ if (enabledRosterModels.length > 0 && canonicalAgentModel) {
20596
+ const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
20597
+ if (!matches) {
20598
+ issues.push(issue2("semantic", "role_roster_conflicts_with_agent", ".kxm/roles/writer.yaml", `role roster in .kxm/roles/writer.yaml does not include agent model ${canonicalAgentModel} from ${implementerAgent.logicalPath}`));
20599
+ }
20600
+ }
20601
+ } catch (error) {
20602
+ if (error instanceof VnextConfigError) {
20603
+ issues.push(...error.issues);
20604
+ }
20605
+ }
20606
+ }
20607
+ }
20540
20608
  return sortIssues2(issues);
20541
20609
  }
20542
20610
  function vnextCanonicalJson(value) {
@@ -20721,7 +20789,7 @@ function loadVnextProject(projectRoot, options = {}) {
20721
20789
  }
20722
20790
  }
20723
20791
  if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
20724
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
20792
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
20725
20793
  if (issues.length > 0) throw new VnextConfigError(issues);
20726
20794
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
20727
20795
  return {
@@ -21360,7 +21428,11 @@ function verifyArtifactExists(rootInput, pathInput) {
21360
21428
  }
21361
21429
  }
21362
21430
 
21431
+ // plugins/kxm/src/workflow.ts
21432
+ import { createHash as createHash4 } from "node:crypto";
21433
+
21363
21434
  // plugins/kxm/src/protocol.ts
21435
+ import { randomUUID as randomUUID2 } from "node:crypto";
21364
21436
  var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
21365
21437
  var MIN_MESSAGE_TTL_MS = 1e3;
21366
21438
  var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
@@ -21381,6 +21453,9 @@ var ProtocolError = class extends Error {
21381
21453
  function nowIso() {
21382
21454
  return (/* @__PURE__ */ new Date()).toISOString();
21383
21455
  }
21456
+ function newId(prefix) {
21457
+ return `${prefix}_${randomUUID2().replaceAll("-", "")}`;
21458
+ }
21384
21459
  function requireString(value, field, options = {}) {
21385
21460
  if (typeof value !== "string") {
21386
21461
  throw new ProtocolError(400, `${field} must be a string`);
@@ -21394,6 +21469,65 @@ function requireString(value, field, options = {}) {
21394
21469
  }
21395
21470
  return result;
21396
21471
  }
21472
+ function optionalString(value, field, max) {
21473
+ if (value === void 0 || value === null || value === "") return void 0;
21474
+ return requireString(value, field, { max });
21475
+ }
21476
+ var TERMINAL_RECEIPT_SCHEMA = "kxm.terminal-receipt.v1";
21477
+ var VALID_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["accepted", "audit_escalation", "rejected", "error"]);
21478
+ function validateTerminalReceipt(value) {
21479
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
21480
+ throw new ProtocolError(400, "terminal receipt must be an object", "invalid_terminal_receipt");
21481
+ }
21482
+ const record = value;
21483
+ if (record.schema !== void 0 && record.schema !== TERMINAL_RECEIPT_SCHEMA) {
21484
+ throw new ProtocolError(400, `terminal receipt schema must be ${TERMINAL_RECEIPT_SCHEMA}`, "invalid_terminal_receipt");
21485
+ }
21486
+ const status = record.status;
21487
+ if (!status || !VALID_TERMINAL_STATUSES.has(status)) {
21488
+ throw new ProtocolError(
21489
+ 400,
21490
+ `terminal receipt status must be one of: ${Array.from(VALID_TERMINAL_STATUSES).join(", ")}`,
21491
+ "invalid_terminal_receipt"
21492
+ );
21493
+ }
21494
+ const seat = requireString(record.seat, "seat", { max: 64 });
21495
+ const runId = requireString(record.runId, "runId", { max: 128 });
21496
+ const stageId = requireString(record.stageId, "stageId", { max: 128 });
21497
+ const timestamp = requireString(record.timestamp, "timestamp", { max: 64 });
21498
+ const host = requireString(record.host, "host", { max: 64 });
21499
+ const model = requireString(record.model, "model", { max: 128 });
21500
+ let evidence;
21501
+ if (record.evidence !== void 0) {
21502
+ if (!record.evidence || typeof record.evidence !== "object" || Array.isArray(record.evidence)) {
21503
+ throw new ProtocolError(400, "terminal receipt evidence must be an object", "invalid_terminal_receipt");
21504
+ }
21505
+ evidence = record.evidence;
21506
+ }
21507
+ let metrics;
21508
+ if (record.metrics !== void 0) {
21509
+ if (!record.metrics || typeof record.metrics !== "object" || Array.isArray(record.metrics)) {
21510
+ throw new ProtocolError(400, "terminal receipt metrics must be an object", "invalid_terminal_receipt");
21511
+ }
21512
+ metrics = record.metrics;
21513
+ }
21514
+ const escalationReason = optionalString(record.escalationReason, "escalationReason", 1024);
21515
+ const ruling = optionalString(record.ruling, "ruling", 2048);
21516
+ return {
21517
+ schema: TERMINAL_RECEIPT_SCHEMA,
21518
+ status,
21519
+ seat,
21520
+ runId,
21521
+ stageId,
21522
+ timestamp,
21523
+ host,
21524
+ model,
21525
+ ...evidence ? { evidence } : {},
21526
+ ...metrics ? { metrics } : {},
21527
+ ...escalationReason ? { escalationReason } : {},
21528
+ ...ruling ? { ruling } : {}
21529
+ };
21530
+ }
21397
21531
 
21398
21532
  // plugins/kxm/src/workflow.ts
21399
21533
  var PROMOTABLE_JOURNAL_CATEGORIES = ["skill-candidate", "hypothesis", "experiment"];
@@ -21430,6 +21564,20 @@ function stringArray(value, name) {
21430
21564
  function canonicalWorkflowEvidenceKey(value) {
21431
21565
  return value.trim().replace(/\s+/gu, " ").toLowerCase();
21432
21566
  }
21567
+ function normalizeWorkflowEvidence(value) {
21568
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
21569
+ const normalized = /* @__PURE__ */ new Map();
21570
+ for (const [requirement, candidate] of Object.entries(value)) {
21571
+ const key = canonicalWorkflowEvidenceKey(requirement);
21572
+ if (!key) continue;
21573
+ const values = Array.isArray(candidate) ? candidate : [candidate];
21574
+ const safeValues = values.filter((item) => typeof item === "string" && Boolean(item.trim())).map((item) => item.trim());
21575
+ if (safeValues.length > 0) {
21576
+ normalized.set(key, [.../* @__PURE__ */ new Set([...normalized.get(key) ?? [], ...safeValues])]);
21577
+ }
21578
+ }
21579
+ return Object.fromEntries(normalized);
21580
+ }
21433
21581
  function normalizeVerifiedWorkflowEvidence(value) {
21434
21582
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
21435
21583
  const result = /* @__PURE__ */ new Map();
@@ -21445,6 +21593,105 @@ function normalizeVerifiedWorkflowEvidence(value) {
21445
21593
  }
21446
21594
  return Object.fromEntries(result);
21447
21595
  }
21596
+ function mergeVerifiedWorkflowEvidence(current, incoming = {}) {
21597
+ const merged = new Map(Object.entries(normalizeVerifiedWorkflowEvidence(current)));
21598
+ for (const [rawRequirement, snapshots] of Object.entries(incoming)) {
21599
+ const requirement = canonicalWorkflowEvidenceKey(rawRequirement);
21600
+ if (!requirement) continue;
21601
+ const values = [...merged.get(requirement) ?? []];
21602
+ for (const snapshot of snapshots) {
21603
+ if (!values.some((candidate) => candidate.messageId === snapshot.messageId)) values.push(snapshot);
21604
+ }
21605
+ if (values.length) merged.set(requirement, values);
21606
+ }
21607
+ return Object.fromEntries(merged);
21608
+ }
21609
+ function mergeWorkflowEvidence(current, incoming = {}) {
21610
+ const merged = new Map(Object.entries(normalizeWorkflowEvidence(current)));
21611
+ const seen = /* @__PURE__ */ new Set();
21612
+ for (const [rawRequirement, rawValue] of Object.entries(incoming)) {
21613
+ const requirement = canonicalWorkflowEvidenceKey(rawRequirement);
21614
+ if (!requirement || typeof rawValue !== "string" || !rawValue.trim()) {
21615
+ throw new ProtocolError(400, "evidence must contain non-empty keyed string values", "invalid_workflow_evidence");
21616
+ }
21617
+ if (seen.has(requirement)) {
21618
+ throw new ProtocolError(
21619
+ 400,
21620
+ `evidence contains duplicate normalized requirement identity: ${requirement}`,
21621
+ "invalid_workflow_evidence"
21622
+ );
21623
+ }
21624
+ seen.add(requirement);
21625
+ const value = rawValue.trim();
21626
+ const values = merged.get(requirement) ?? [];
21627
+ if (!values.includes(value)) values.push(value);
21628
+ merged.set(requirement, values);
21629
+ }
21630
+ return Object.fromEntries(merged);
21631
+ }
21632
+ function activeWorkflowAttempt(stage) {
21633
+ return stage.attempts + 1;
21634
+ }
21635
+ function validIsoTimestamp(value) {
21636
+ if (!value) return void 0;
21637
+ const parsed = Date.parse(value);
21638
+ return Number.isFinite(parsed) ? parsed : void 0;
21639
+ }
21640
+ function peerEvidenceRequirementStatus(stage, requirementKey, runId, verifiedEvidence = normalizeVerifiedWorkflowEvidence(stage.verifiedEvidence)) {
21641
+ const canonicalKey = canonicalWorkflowEvidenceKey(requirementKey);
21642
+ const policy = stage.resolvedEvidencePolicies?.[canonicalKey];
21643
+ if (!policy) return void 0;
21644
+ const attempt = activeWorkflowAttempt(stage);
21645
+ const eligibleIds = new Set(policy.eligibleProducers.map((producer) => producer.id));
21646
+ const producers = /* @__PURE__ */ new Set();
21647
+ for (const snapshot of verifiedEvidence[canonicalKey] ?? []) {
21648
+ if (snapshot.schema === "pi-mesh.verified-peer-evidence.v1" && snapshot.status === "replied" && snapshot.context?.schema === "pi-mesh.workflow-message-context.v1" && snapshot.context.runId === runId && snapshot.context.stageId === stage.id && snapshot.context.requirementKey === canonicalKey && snapshot.context.attempt === attempt && eligibleIds.has(snapshot.producerId) && /^[a-f0-9]{64}$/.test(snapshot.requestSha256) && /^[a-f0-9]{64}$/.test(snapshot.replySha256) && validIsoTimestamp(snapshot.createdAt) !== void 0 && validIsoTimestamp(snapshot.replyCreatedAt) !== void 0 && validIsoTimestamp(snapshot.repliedAt) !== void 0 && validIsoTimestamp(snapshot.verifiedAt) !== void 0) producers.add(snapshot.producerId);
21649
+ }
21650
+ const approval = stage.degradationApprovals?.find(
21651
+ (candidate) => candidate.requirementKey === canonicalKey && candidate.attempt === attempt
21652
+ );
21653
+ const effectiveMinProducers = approval?.approvedMinProducers ?? policy.minProducers;
21654
+ return {
21655
+ requirementKey: canonicalKey,
21656
+ policyMinProducers: policy.minProducers,
21657
+ effectiveMinProducers,
21658
+ producers: [...producers],
21659
+ met: producers.size >= effectiveMinProducers,
21660
+ degraded: Boolean(approval && producers.size < policy.minProducers && producers.size >= effectiveMinProducers),
21661
+ ...approval ? { approval } : {}
21662
+ };
21663
+ }
21664
+ function requireCompleteEvidence(stage, evidence, verifiedEvidence, runId) {
21665
+ const missing = [];
21666
+ const peerStatuses = [];
21667
+ for (const rawRequirement of stage.requiredEvidence) {
21668
+ const requirement = canonicalWorkflowEvidenceKey(rawRequirement);
21669
+ const peerStatus = peerEvidenceRequirementStatus(stage, requirement, runId, verifiedEvidence);
21670
+ if (peerStatus) {
21671
+ peerStatuses.push(peerStatus);
21672
+ if (!peerStatus.met) missing.push(requirement);
21673
+ } else if (stage.evidencePolicies?.[requirement]) {
21674
+ throw new ProtocolError(
21675
+ 409,
21676
+ `stage ${stage.id} evidence policy ${requirement} was not resolved when the run started`,
21677
+ "workflow_evidence_policy_unresolved"
21678
+ );
21679
+ } else if (!evidence[requirement]?.length) {
21680
+ missing.push(requirement);
21681
+ }
21682
+ }
21683
+ if (missing.length === 0) return peerStatuses;
21684
+ throw new ProtocolError(
21685
+ 400,
21686
+ `stage ${stage.id} is missing required evidence: ${missing.join(", ")}`,
21687
+ "workflow_evidence_incomplete",
21688
+ {
21689
+ missingRequirements: missing,
21690
+ providedRequirements: Object.keys(evidence),
21691
+ peerRequirements: peerStatuses
21692
+ }
21693
+ );
21694
+ }
21448
21695
  function parseWorkflowEvidencePolicies(value, stageId, requiredEvidence, workflowId, targetName, warn) {
21449
21696
  if (value === void 0) return void 0;
21450
21697
  const rawPolicies = object(value, `stage ${stageId} evidencePolicies`);
@@ -21617,12 +21864,17 @@ function parseWorkflowDefinitions(raw, environment = process.env, onWarning) {
21617
21864
  if (stageMaxTransitions !== void 0 && (!Number.isInteger(stageMaxTransitions) || stageMaxTransitions < 1 || stageMaxTransitions > 100)) {
21618
21865
  throw new Error(`stage ${stageId} maxTransitions must be an integer between 1 and 100`);
21619
21866
  }
21867
+ const autoResumeLimit = stage.autoResumeLimit;
21868
+ if (autoResumeLimit !== void 0 && (!Number.isInteger(autoResumeLimit) || autoResumeLimit < 1 || autoResumeLimit > 20)) {
21869
+ throw new Error(`stage ${stageId} autoResumeLimit must be an integer between 1 and 20`);
21870
+ }
21620
21871
  return {
21621
21872
  id: stageId,
21622
21873
  label: requireString(stage.label ?? stageId, "stage.label", { max: 128 }),
21623
21874
  instructions: requireString(stage.instructions, "stage.instructions", { max: 4e3 }),
21624
21875
  requiredEvidence,
21625
21876
  maxAttempts,
21877
+ ...autoResumeLimit !== void 0 ? { autoResumeLimit } : {},
21626
21878
  ...area ? { area } : {},
21627
21879
  ...evidencePolicies ? { evidencePolicies } : {},
21628
21880
  ...on ? { on } : {},
@@ -21726,9 +21978,298 @@ function parseOutcomeMap(stageId, raw) {
21726
21978
  }
21727
21979
  return map;
21728
21980
  }
21981
+ function resolveOutcomeRule(stage, outcomeKey) {
21982
+ const raw = stage.on?.[outcomeKey];
21983
+ return raw === void 0 ? void 0 : normalizeOutcomeValue(raw, `stage ${stage.id} on.${outcomeKey}`);
21984
+ }
21985
+ function transitionCounts(run, fromStage, outcome, target) {
21986
+ const records = run.transitions ?? [];
21987
+ return {
21988
+ total: records.length,
21989
+ fromStage: records.filter((record) => record.fromStage === fromStage).length,
21990
+ forEdge: records.filter((record) => record.fromStage === fromStage && record.outcome === outcome && record.toStage === target).length
21991
+ };
21992
+ }
21993
+ function recordTransition(run, fromStage, rule, outcome, attempt, evidenceKeys, timestamp) {
21994
+ const record = {
21995
+ id: newId("trans"),
21996
+ fromStage,
21997
+ toStage: rule.target,
21998
+ outcome,
21999
+ attempt,
22000
+ evidenceKeys,
22001
+ at: timestamp
22002
+ };
22003
+ run.transitions = [...run.transitions ?? [], record];
22004
+ return record;
22005
+ }
22006
+ function enterStage(run, stage, timestamp) {
22007
+ stage.status = "in_progress";
22008
+ stage.attempts = 0;
22009
+ stage.evidence = {};
22010
+ stage.verifiedEvidence = {};
22011
+ stage.startedAt = timestamp;
22012
+ stage.updatedAt = timestamp;
22013
+ delete stage.summary;
22014
+ run.currentStage = stage.id;
22015
+ run.updatedAt = timestamp;
22016
+ }
22017
+ function takeDeclaredTransition(run, stage, rule, outcome, summary, attempt, timestamp, evidenceKeys) {
22018
+ const definitionBudget = run.maxTransitions;
22019
+ const counts = transitionCounts(run, stage.id, outcome, rule.target);
22020
+ const edgeExhausted = rule.maxTransitions !== void 0 && counts.forEdge >= rule.maxTransitions;
22021
+ const stageExhausted = stage.maxTransitions !== void 0 && counts.fromStage >= stage.maxTransitions;
22022
+ const globalExhausted = definitionBudget !== void 0 && counts.total >= definitionBudget;
22023
+ if (edgeExhausted || stageExhausted || globalExhausted) {
22024
+ stage.completedAt = timestamp;
22025
+ run.status = "failed";
22026
+ delete run.currentStage;
22027
+ run.updatedAt = timestamp;
22028
+ return {
22029
+ retry: false,
22030
+ completed: false,
22031
+ run,
22032
+ exhausted: true
22033
+ };
22034
+ }
22035
+ const record = recordTransition(run, stage.id, rule, outcome, attempt, evidenceKeys.slice(0, 32), timestamp);
22036
+ if (rule.target === WORKFLOW_TERMINAL_TARGET) {
22037
+ stage.completedAt = timestamp;
22038
+ run.status = "completed";
22039
+ delete run.currentStage;
22040
+ run.completedAt = timestamp;
22041
+ run.updatedAt = timestamp;
22042
+ return { retry: false, completed: true, run, transition: record };
22043
+ }
22044
+ const target = run.stages.find((candidate) => candidate.id === rule.target);
22045
+ if (!target) {
22046
+ run.status = "failed";
22047
+ delete run.currentStage;
22048
+ return { retry: false, completed: false, run, exhausted: true };
22049
+ }
22050
+ stage.summary = summary;
22051
+ enterStage(run, target, timestamp);
22052
+ return { retry: false, completed: false, run, transition: record };
22053
+ }
22054
+ function evidenceValueSha256(evidence, key) {
22055
+ const values = evidence[canonicalWorkflowEvidenceKey(key)];
22056
+ if (!values || values.length === 0) return void 0;
22057
+ return createHash4("sha256").update([...values].sort().join("\n"), "utf8").digest("hex");
22058
+ }
22059
+ function enforceOracles(run, stageId, evidence) {
22060
+ if (run.oracle) {
22061
+ const presented = evidenceValueSha256(evidence, run.oracle.evidenceKey);
22062
+ if (presented !== void 0 && presented !== run.oracle.sha256) {
22063
+ throw new ProtocolError(
22064
+ 400,
22065
+ `evidence ${run.oracle.evidenceKey} does not match the immutable reproduction oracle captured at ${run.oracle.capturedAt}; the confirmed reproduction may not be weakened`,
22066
+ "weakened_reproduction"
22067
+ );
22068
+ }
22069
+ }
22070
+ if (run.requirePlanHash?.includes(stageId) && !run.planHash) {
22071
+ throw new ProtocolError(
22072
+ 400,
22073
+ `stage ${stageId} requires an approved plan hash before it can checkpoint`,
22074
+ "plan_hash_required"
22075
+ );
22076
+ }
22077
+ }
22078
+ function captureOracles(run, stageId, evidence, timestamp) {
22079
+ if (run.reproOracle?.stageId === stageId) {
22080
+ const sha256 = evidenceValueSha256(evidence, run.reproOracle.evidenceKey);
22081
+ if (sha256 !== void 0) {
22082
+ run.oracle = { evidenceKey: run.reproOracle.evidenceKey, sha256, capturedAt: timestamp };
22083
+ }
22084
+ }
22085
+ if (run.planHashConfig?.stageId === stageId) {
22086
+ const sha256 = evidenceValueSha256(evidence, run.planHashConfig.evidenceKey);
22087
+ if (sha256 !== void 0) {
22088
+ run.planHash = { evidenceKey: run.planHashConfig.evidenceKey, sha256, capturedAt: timestamp };
22089
+ }
22090
+ }
22091
+ }
22092
+ function checkpointRun(run, stageId, status, summary, evidence, timestamp, verifiedEvidence = {}, outcome) {
22093
+ if (run.status !== "running") throw new ProtocolError(409, `workflow is ${run.status}`, "workflow_terminal");
22094
+ const stage = run.stages.find((candidate) => candidate.id === stageId);
22095
+ if (!stage) throw new ProtocolError(404, `workflow stage not found: ${stageId}`, "workflow_stage_not_found");
22096
+ if (stage.id !== run.currentStage || stage.status !== "in_progress") {
22097
+ throw new ProtocolError(409, `stage ${stageId} is not currently active`, "workflow_stage_out_of_order");
22098
+ }
22099
+ const accumulatedEvidence = mergeWorkflowEvidence(stage.evidence, evidence);
22100
+ const accumulatedVerifiedEvidence = mergeVerifiedWorkflowEvidence(stage.verifiedEvidence, verifiedEvidence);
22101
+ enforceOracles(run, stageId, accumulatedEvidence);
22102
+ const peerStatuses = status === "passed" ? requireCompleteEvidence(stage, accumulatedEvidence, accumulatedVerifiedEvidence, run.id) : [];
22103
+ const degradedRequirements = peerStatuses.filter((peerStatus) => peerStatus.degraded);
22104
+ stage.attempts += 1;
22105
+ stage.summary = summary;
22106
+ if (status === "passed") {
22107
+ stage.evidence = accumulatedEvidence;
22108
+ if (Object.keys(accumulatedVerifiedEvidence).length) stage.verifiedEvidence = accumulatedVerifiedEvidence;
22109
+ captureOracles(run, stageId, accumulatedEvidence, timestamp);
22110
+ if (degradedRequirements.length) {
22111
+ stage.degraded = true;
22112
+ stage.degradedRequirements = degradedRequirements.map((peerStatus) => peerStatus.requirementKey);
22113
+ }
22114
+ }
22115
+ stage.updatedAt = timestamp;
22116
+ run.updatedAt = timestamp;
22117
+ if (status !== "passed") {
22118
+ stage.status = status;
22119
+ if (stage.attempts >= stage.maxAttempts) {
22120
+ stage.completedAt = timestamp;
22121
+ run.status = "failed";
22122
+ delete run.currentStage;
22123
+ return { retry: false, completed: false, run };
22124
+ }
22125
+ if (stage.autoResumeLimit !== void 0 && stage.attempts >= stage.autoResumeLimit) {
22126
+ stage.status = "in_progress";
22127
+ const reason = summary || `autoResumeLimit of ${stage.autoResumeLimit} reached on stage ${stage.id}`;
22128
+ const receipt = validateTerminalReceipt({
22129
+ schema: TERMINAL_RECEIPT_SCHEMA,
22130
+ status: "audit_escalation",
22131
+ seat: stage.id,
22132
+ runId: run.id,
22133
+ stageId: stage.id,
22134
+ timestamp,
22135
+ host: "pi",
22136
+ model: "default",
22137
+ escalationReason: reason
22138
+ });
22139
+ const expiresAt = new Date(Date.parse(timestamp) + 24 * 60 * 60 * 1e3).toISOString();
22140
+ waitForWorkflowSignal(run, stage.id, "audit_escalation", reason, timestamp, expiresAt);
22141
+ stage.receipt = receipt;
22142
+ stage.auditEscalation = {
22143
+ reason,
22144
+ timestamp,
22145
+ receipt
22146
+ };
22147
+ return { retry: false, completed: false, run };
22148
+ }
22149
+ stage.status = status;
22150
+ const outcomeKey = outcome ?? status;
22151
+ const rule = resolveOutcomeRule(stage, outcomeKey);
22152
+ if (rule && stage.attempts < stage.maxAttempts) {
22153
+ const result = takeDeclaredTransition(run, stage, rule, outcomeKey, summary, stage.attempts, timestamp, Object.keys(accumulatedEvidence));
22154
+ if (result.transition !== void 0 && !result.completed && rule.target !== stage.id) {
22155
+ stage.status = "pending";
22156
+ }
22157
+ return result;
22158
+ }
22159
+ stage.status = "in_progress";
22160
+ return { retry: true, completed: false, run };
22161
+ }
22162
+ stage.status = "passed";
22163
+ stage.completedAt = timestamp;
22164
+ const passedRule = resolveOutcomeRule(stage, outcome ?? "passed");
22165
+ if (passedRule) {
22166
+ return takeDeclaredTransition(run, stage, passedRule, outcome ?? "passed", summary, stage.attempts, timestamp, Object.keys(accumulatedEvidence));
22167
+ }
22168
+ const next = run.stages.find((candidate) => candidate.status === "pending");
22169
+ if (next) {
22170
+ next.status = "in_progress";
22171
+ next.startedAt = timestamp;
22172
+ next.updatedAt = timestamp;
22173
+ run.currentStage = next.id;
22174
+ return {
22175
+ retry: false,
22176
+ completed: false,
22177
+ run,
22178
+ ...degradedRequirements.length ? { degraded: true } : {}
22179
+ };
22180
+ }
22181
+ run.status = "completed";
22182
+ delete run.currentStage;
22183
+ run.completedAt = timestamp;
22184
+ return {
22185
+ retry: false,
22186
+ completed: true,
22187
+ run,
22188
+ ...degradedRequirements.length ? { degraded: true } : {}
22189
+ };
22190
+ }
22191
+ function waitForWorkflowSignal(run, stageId, signalKey, summary, timestamp, expiresAt, evidence = {}, verifiedEvidence = {}) {
22192
+ if (run.status !== "running") throw new ProtocolError(409, `workflow is ${run.status}`, "workflow_not_running");
22193
+ const stage = run.stages.find((candidate) => candidate.id === stageId);
22194
+ if (!stage) throw new ProtocolError(404, `workflow stage not found: ${stageId}`, "workflow_stage_not_found");
22195
+ if (stage.id !== run.currentStage || stage.status !== "in_progress") {
22196
+ throw new ProtocolError(409, `stage ${stageId} is not currently active`, "workflow_stage_out_of_order");
22197
+ }
22198
+ if (Date.parse(expiresAt) <= Date.parse(timestamp)) {
22199
+ throw new ProtocolError(400, "workflow signal expiry must be in the future", "workflow_wait_invalid");
22200
+ }
22201
+ stage.evidence = mergeWorkflowEvidence(stage.evidence, evidence);
22202
+ const accumulatedVerifiedEvidence = mergeVerifiedWorkflowEvidence(stage.verifiedEvidence, verifiedEvidence);
22203
+ if (Object.keys(accumulatedVerifiedEvidence).length) stage.verifiedEvidence = accumulatedVerifiedEvidence;
22204
+ stage.status = "waiting";
22205
+ stage.updatedAt = timestamp;
22206
+ run.status = "waiting";
22207
+ run.waiting = { stageId, signalKey, summary, createdAt: timestamp, expiresAt };
22208
+ run.updatedAt = timestamp;
22209
+ return run;
22210
+ }
22211
+ function resumeWorkflowFromSignal(run, signalKey, status, summary, evidence, timestamp) {
22212
+ if (run.status !== "waiting" || !run.waiting) {
22213
+ throw new ProtocolError(409, `workflow is ${run.status}`, "workflow_not_waiting");
22214
+ }
22215
+ if (run.waiting.signalKey !== signalKey) {
22216
+ throw new ProtocolError(409, `workflow is waiting for ${run.waiting.signalKey}`, "workflow_signal_mismatch");
22217
+ }
22218
+ const stageId = run.waiting.stageId;
22219
+ const stage = run.stages.find((candidate) => candidate.id === stageId);
22220
+ if (!stage || stage.id !== run.currentStage || stage.status !== "waiting") {
22221
+ throw new ProtocolError(409, "workflow wait state is inconsistent", "workflow_wait_inconsistent");
22222
+ }
22223
+ const accumulatedEvidence = mergeWorkflowEvidence(stage.evidence, evidence);
22224
+ if (status === "passed") {
22225
+ requireCompleteEvidence(
22226
+ stage,
22227
+ accumulatedEvidence,
22228
+ normalizeVerifiedWorkflowEvidence(stage.verifiedEvidence),
22229
+ run.id
22230
+ );
22231
+ }
22232
+ run.status = "running";
22233
+ stage.status = "in_progress";
22234
+ delete run.waiting;
22235
+ const result = checkpointRun(run, stageId, status, summary, evidence, timestamp);
22236
+ return { ...result, stageId };
22237
+ }
22238
+ function resumeWorkflowFromRuling(run, ruling, timestamp, options = {}) {
22239
+ if (run.status !== "waiting" || !run.waiting) {
22240
+ throw new ProtocolError(409, `workflow is ${run.status}`, "workflow_not_waiting");
22241
+ }
22242
+ if (run.waiting.signalKey !== "audit_escalation") {
22243
+ throw new ProtocolError(
22244
+ 409,
22245
+ `workflow is waiting for signal '${run.waiting.signalKey}', not 'audit_escalation'`,
22246
+ "workflow_signal_mismatch"
22247
+ );
22248
+ }
22249
+ const stageId = run.waiting.stageId;
22250
+ const stage = run.stages.find((s) => s.id === stageId);
22251
+ if (!stage) throw new ProtocolError(404, `workflow stage not found: ${stageId}`, "workflow_stage_not_found");
22252
+ const status = options.status ?? "passed";
22253
+ const evidence = options.evidence ?? {};
22254
+ if (stage.auditEscalation) {
22255
+ stage.auditEscalation.ruling = ruling;
22256
+ if (stage.auditEscalation.receipt) {
22257
+ stage.auditEscalation.receipt.ruling = ruling;
22258
+ }
22259
+ }
22260
+ stage.summary = `Resumed by operator ruling: ${ruling}`;
22261
+ return resumeWorkflowFromSignal(
22262
+ run,
22263
+ "audit_escalation",
22264
+ status,
22265
+ `Resumed: ${ruling}`,
22266
+ evidence,
22267
+ timestamp
22268
+ );
22269
+ }
21729
22270
 
21730
22271
  // plugins/kxm/src/github-watch.ts
21731
- import { createHmac, randomUUID as randomUUID2 } from "node:crypto";
22272
+ import { createHmac, randomUUID as randomUUID3 } from "node:crypto";
21732
22273
  async function fetchWithTimeout(fetchImpl, input, init, timeoutMs) {
21733
22274
  const controller = new AbortController();
21734
22275
  const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
@@ -21814,7 +22355,7 @@ async function watchGithubChecks(input) {
21814
22355
  }
21815
22356
  const fetchImpl = input.fetchImpl ?? fetch;
21816
22357
  const now = input.now ?? Date.now;
21817
- const sleep = input.sleep ?? ((ms) => new Promise((resolve22) => setTimeout(resolve22, ms)));
22358
+ const sleep = input.sleep ?? ((ms) => new Promise((resolve23) => setTimeout(resolve23, ms)));
21818
22359
  const deadline = now() + input.timeoutMs;
21819
22360
  const headers = {
21820
22361
  authorization: `Bearer ${token}`,
@@ -21827,7 +22368,7 @@ async function watchGithubChecks(input) {
21827
22368
  "workflow.signal": input.signalKey
21828
22369
  };
21829
22370
  const explicitDeliveryId = input.deliveryId?.trim();
21830
- const deliveryGeneration = randomUUID2();
22371
+ const deliveryGeneration = randomUUID3();
21831
22372
  let deliveryId = explicitDeliveryId || `github-watch:${deliveryGeneration}:pr-${input.pr}`;
21832
22373
  const deliver = async (status, summary, evidence) => {
21833
22374
  const boundedEvidence = Object.fromEntries(Object.entries({ ...contextEvidence, ...evidence }).slice(0, 64));
@@ -22143,7 +22684,7 @@ function writeRetrospective(outDir, doc) {
22143
22684
 
22144
22685
  // plugins/kxm/src/skills.ts
22145
22686
  var import_yaml4 = __toESM(require_dist(), 1);
22146
- import { createHash as createHash4 } from "node:crypto";
22687
+ import { createHash as createHash5 } from "node:crypto";
22147
22688
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync4 } from "node:fs";
22148
22689
  import { join as join4 } from "node:path";
22149
22690
  var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
@@ -22168,7 +22709,7 @@ var SkillLifecycleError = class extends Error {
22168
22709
  }
22169
22710
  };
22170
22711
  function skillContentSha256(content) {
22171
- return createHash4("sha256").update(content, "utf8").digest("hex");
22712
+ return createHash5("sha256").update(content, "utf8").digest("hex");
22172
22713
  }
22173
22714
  function skillIdFor(name, contentSha256) {
22174
22715
  const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -23229,7 +23770,7 @@ function readRoutingRecords(path4) {
23229
23770
 
23230
23771
  // plugins/kxm/src/prices.ts
23231
23772
  var import_yaml5 = __toESM(require_dist(), 1);
23232
- import { createHash as createHash5 } from "node:crypto";
23773
+ import { createHash as createHash6 } from "node:crypto";
23233
23774
  import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
23234
23775
  import { join as join7 } from "node:path";
23235
23776
  function hashPriceCatalog(catalog) {
@@ -23251,7 +23792,7 @@ function hashPriceCatalog(catalog) {
23251
23792
  }))
23252
23793
  })).sort((a, b2) => a.id.localeCompare(b2.id))
23253
23794
  };
23254
- return createHash5("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
23795
+ return createHash6("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
23255
23796
  }
23256
23797
  function parsePriceCatalog(text) {
23257
23798
  const parsed = (0, import_yaml5.parse)(text);
@@ -23414,7 +23955,7 @@ async function refreshModelInventory(options) {
23414
23955
  if (item.fast === true) current.capabilities.speed = { fast: true, tiers: ["fast"], source };
23415
23956
  byId.set(key, current);
23416
23957
  };
23417
- const native = (command, args, source) => {
23958
+ const native2 = (command, args, source) => {
23418
23959
  const result = spawnSync3(command, args, { encoding: "utf8", timeout: 2e4, shell: false });
23419
23960
  const ok = result.status === 0;
23420
23961
  sources[source] = { url: `${command} ${args.join(" ")}`, ok, ...ok ? {} : { error: (result.stderr || "command failed").trim().slice(0, 240) } };
@@ -23428,9 +23969,9 @@ async function refreshModelInventory(options) {
23428
23969
  }
23429
23970
  }
23430
23971
  };
23431
- native("pi", ["--list-models"], "pi");
23432
- native("grok", ["models"], "grok");
23433
- native("agy", ["models"], "agy");
23972
+ native2("pi", ["--list-models"], "pi");
23973
+ native2("grok", ["models"], "grok");
23974
+ native2("agy", ["models"], "agy");
23434
23975
  const openrouter = await fetchModels(env.KXM_OPENROUTER_MODELS_URL?.trim() || OR_URL, env.OPENROUTER_API_KEY);
23435
23976
  const openrouterUrl = env.KXM_OPENROUTER_MODELS_URL?.trim() || OR_URL;
23436
23977
  sources.openrouter = { url: openrouterUrl, ok: !openrouter.error, ...openrouter.error ? { error: openrouter.error } : {} };
@@ -23647,7 +24188,7 @@ function writeSession(assetsDir, session, dryRun = false) {
23647
24188
  }
23648
24189
 
23649
24190
  // plugins/kxm/src/improve.ts
23650
- import { createHash as createHash6 } from "node:crypto";
24191
+ import { createHash as createHash7 } from "node:crypto";
23651
24192
  import { existsSync as existsSync9, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
23652
24193
  import { join as join11, relative as relative3, resolve as resolve6 } from "node:path";
23653
24194
  var CANDIDATE_SCHEMA = "kxm.candidate.v1";
@@ -23795,7 +24336,7 @@ function groupRoutingRecords(records, options = {}) {
23795
24336
  const isCandidate = recurrence >= minRecurrence && verifyPassRate >= minPassRate;
23796
24337
  const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : void 0;
23797
24338
  const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
23798
- const keyHash = createHash6("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
24339
+ const keyHash = createHash7("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
23799
24340
  const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : void 0;
23800
24341
  rows.push({
23801
24342
  workflowHash: group.workflowHash,
@@ -28076,10 +28617,10 @@ var TuiBase = class _TuiBase extends Container {
28076
28617
  * @returns Promise containing the parsed RGB color, or undefined if it times out or fails to parse.
28077
28618
  */
28078
28619
  queryTerminalBackgroundColor({ timeoutMs }) {
28079
- return new Promise((resolve22) => {
28620
+ return new Promise((resolve23) => {
28080
28621
  const query = {
28081
28622
  settled: false,
28082
- resolve: resolve22,
28623
+ resolve: resolve23,
28083
28624
  timer: void 0
28084
28625
  };
28085
28626
  query.timer = setTimeout(() => {
@@ -28102,7 +28643,7 @@ var TuiBase = class _TuiBase extends Container {
28102
28643
  * `CSI ? 997 ; 1 n` for dark or `CSI ? 997 ; 2 n` for light.
28103
28644
  */
28104
28645
  queryTerminalColorScheme({ timeoutMs }) {
28105
- return new Promise((resolve22) => {
28646
+ return new Promise((resolve23) => {
28106
28647
  let settled = false;
28107
28648
  let timer;
28108
28649
  let unsubscribe = () => {
@@ -28116,7 +28657,7 @@ var TuiBase = class _TuiBase extends Container {
28116
28657
  timer = void 0;
28117
28658
  }
28118
28659
  unsubscribe();
28119
- resolve22(scheme);
28660
+ resolve23(scheme);
28120
28661
  };
28121
28662
  unsubscribe = this.onTerminalColorSchemeChange(settle);
28122
28663
  timer = setTimeout(() => settle(void 0), timeoutMs);
@@ -29838,18 +30379,18 @@ var StdinBuffer = class extends EventEmitter2 {
29838
30379
 
29839
30380
  // node_modules/@earendil-works/pi-tui/dist/terminal.js
29840
30381
  import * as fs2 from "node:fs";
29841
- import { createRequire as createRequire3 } from "node:module";
30382
+ import { createRequire as createRequire4 } from "node:module";
29842
30383
  import * as path3 from "node:path";
29843
30384
 
29844
30385
  // node_modules/@earendil-works/pi-tui/dist/native-modifiers.js
29845
- import { createRequire as createRequire2 } from "node:module";
30386
+ import { createRequire as createRequire3 } from "node:module";
29846
30387
  import * as path2 from "node:path";
29847
30388
 
29848
30389
  // node_modules/@earendil-works/pi-tui/dist/native-module-path.js
29849
- import { createRequire } from "node:module";
30390
+ import { createRequire as createRequire2 } from "node:module";
29850
30391
  import { dirname as dirname6, join as join12 } from "node:path";
29851
30392
  import { fileURLToPath as fileURLToPath2 } from "node:url";
29852
- var moduleRequire = createRequire(import.meta.url);
30393
+ var moduleRequire = createRequire2(import.meta.url);
29853
30394
  var TUI_PACKAGE_NAME = "@earendil-works/pi-tui";
29854
30395
  function getNativeModuleCandidates(nativePath, options = {}) {
29855
30396
  const moduleDir = dirname6(fileURLToPath2(options.moduleUrl ?? import.meta.url));
@@ -29864,7 +30405,7 @@ function getNativeModuleCandidates(nativePath, options = {}) {
29864
30405
  }
29865
30406
 
29866
30407
  // node_modules/@earendil-works/pi-tui/dist/native-modifiers.js
29867
- var cjsRequire = createRequire2(import.meta.url);
30408
+ var cjsRequire = createRequire3(import.meta.url);
29868
30409
  var nativeModifiersHelper;
29869
30410
  function isNativeModifiersHelper(value) {
29870
30411
  if (typeof value !== "object" || value === null)
@@ -29911,7 +30452,7 @@ function isNativeModifierPressed(key) {
29911
30452
  }
29912
30453
 
29913
30454
  // node_modules/@earendil-works/pi-tui/dist/terminal.js
29914
- var cjsRequire2 = createRequire3(import.meta.url);
30455
+ var cjsRequire2 = createRequire4(import.meta.url);
29915
30456
  var TERMINAL_PROGRESS_KEEPALIVE_MS = 1e3;
29916
30457
  var TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1B]9;4;3\x07";
29917
30458
  var TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1B]9;4;0\x07";
@@ -30200,7 +30741,7 @@ var ProcessTerminal = class {
30200
30741
  break;
30201
30742
  if (now - lastDataTime >= idleMs)
30202
30743
  break;
30203
- await new Promise((resolve22) => setTimeout(resolve22, Math.min(idleMs, timeLeft)));
30744
+ await new Promise((resolve23) => setTimeout(resolve23, Math.min(idleMs, timeLeft)));
30204
30745
  }
30205
30746
  } finally {
30206
30747
  process.stdin.removeListener("data", onData);
@@ -30813,10 +31354,10 @@ function paintBox(box, screen, totalWidth) {
30813
31354
  const firstRow = Math.max(box.rect.y, box.clip.y, 0);
30814
31355
  const lastRow = Math.min(box.rect.y + box.rect.height, box.clip.y + box.clip.height, screen.length);
30815
31356
  for (let row = firstRow; row < lastRow; row++) {
30816
- const sourceLine = box.lines[offset + row - box.rect.y];
30817
- if (sourceLine === void 0)
31357
+ const sourceLine2 = box.lines[offset + row - box.rect.y];
31358
+ if (sourceLine2 === void 0)
30818
31359
  continue;
30819
- let line = sourceLine.replace(OSC133_ZONE_PREFIX, "");
31360
+ let line = sourceLine2.replace(OSC133_ZONE_PREFIX, "");
30820
31361
  const imageMetadata = getKittyImageMetadata(line);
30821
31362
  if (imageMetadata) {
30822
31363
  const clipBottom = Math.min(screen.length, box.clip.y + box.clip.height);
@@ -32320,7 +32861,6 @@ import { join as join16, resolve as resolve8, dirname as dirname7 } from "node:p
32320
32861
  import { spawnSync as spawnSync4 } from "node:child_process";
32321
32862
 
32322
32863
  // plugins/kxm/src/external-effects.ts
32323
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
32324
32864
  function slugifyBranchPart(text, maxLength = 40) {
32325
32865
  const cleaned = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
32326
32866
  if (cleaned.length <= maxLength) return cleaned;
@@ -32331,7 +32871,6 @@ function slugifyBranchPart(text, maxLength = 40) {
32331
32871
  import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync10 } from "node:fs";
32332
32872
  import { homedir } from "node:os";
32333
32873
  import { isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "node:path";
32334
- import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
32335
32874
  function processExists(pid) {
32336
32875
  try {
32337
32876
  process.kill(pid, 0);
@@ -32493,7 +33032,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
32493
33032
  let plans = [];
32494
33033
  if (existsSync10(dataPath)) {
32495
33034
  hasLegacy = true;
32496
- const database = new DatabaseSync3(dataPath, { readOnly: true });
33035
+ const database = new DatabaseSync(dataPath, { readOnly: true });
32497
33036
  try {
32498
33037
  database.exec("PRAGMA busy_timeout = 5000");
32499
33038
  agents = readJsonRows(database, "SELECT record FROM agents");
@@ -32518,7 +33057,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
32518
33057
  if (existsSync10(registryDbPath)) {
32519
33058
  hasVnext = true;
32520
33059
  try {
32521
- const regDb = new DatabaseSync3(registryDbPath, { readOnly: true });
33060
+ const regDb = new DatabaseSync(registryDbPath, { readOnly: true });
32522
33061
  try {
32523
33062
  regDb.exec("PRAGMA busy_timeout = 5000");
32524
33063
  const pRows = regDb.prepare("SELECT project_key FROM projects").all();
@@ -32546,7 +33085,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
32546
33085
  if (existsSync10(eventDbPath)) {
32547
33086
  hasVnext = true;
32548
33087
  try {
32549
- const eventDb = new DatabaseSync3(eventDbPath, { readOnly: true });
33088
+ const eventDb = new DatabaseSync(eventDbPath, { readOnly: true });
32550
33089
  try {
32551
33090
  eventDb.exec("PRAGMA busy_timeout = 5000");
32552
33091
  const runRows = eventDb.prepare(`
@@ -33153,11 +33692,11 @@ async function readJson(response) {
33153
33692
  }
33154
33693
  async function waitForReconnect(signal, milliseconds = 1e3) {
33155
33694
  if (signal.aborted) return;
33156
- await new Promise((resolve22) => {
33695
+ await new Promise((resolve23) => {
33157
33696
  const done = () => {
33158
33697
  clearTimeout(timer);
33159
33698
  signal.removeEventListener("abort", done);
33160
- resolve22();
33699
+ resolve23();
33161
33700
  };
33162
33701
  const timer = setTimeout(done, milliseconds);
33163
33702
  timer.unref();
@@ -33519,12 +34058,12 @@ async function runMeshTui(input) {
33519
34058
 
33520
34059
  // plugins/kxm/src/session-work.ts
33521
34060
  import { spawnSync as spawnSync5 } from "node:child_process";
33522
- import { randomUUID as randomUUID3 } from "node:crypto";
34061
+ import { randomUUID as randomUUID4 } from "node:crypto";
33523
34062
  import { existsSync as existsSync13, mkdirSync as mkdirSync14, readFileSync as readFileSync13, renameSync as renameSync4, writeFileSync as writeFileSync12 } from "node:fs";
33524
34063
  import { join as join19 } from "node:path";
33525
34064
 
33526
34065
  // plugins/kxm/src/kxm-update.ts
33527
- import { createHash as createHash7 } from "node:crypto";
34066
+ import { createHash as createHash8 } from "node:crypto";
33528
34067
  import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync12 } from "node:fs";
33529
34068
  import { join as join17 } from "node:path";
33530
34069
  var KXM_UPDATE_SCHEMA = "kxm.update.v1";
@@ -33677,7 +34216,7 @@ async function fetchLatestKxmVersion(source, env, fetchImpl = fetch) {
33677
34216
  function verifyReleaseAssetDigest(path4, sha256) {
33678
34217
  if (!existsSync11(path4)) return false;
33679
34218
  try {
33680
- const actual = createHash7("sha256").update(readFileSync11(path4)).digest("hex");
34219
+ const actual = createHash8("sha256").update(readFileSync11(path4)).digest("hex");
33681
34220
  return actual === sha256.toLowerCase();
33682
34221
  } catch {
33683
34222
  return false;
@@ -34002,7 +34541,7 @@ function writeCachedSessionBrief(stateDir, brief) {
34002
34541
  try {
34003
34542
  mkdirSync14(stateDir, { recursive: true, mode: 448 });
34004
34543
  const file = join19(stateDir, "session-brief.json");
34005
- const tmp = `${file}.tmp.${randomUUID3().slice(0, 8)}`;
34544
+ const tmp = `${file}.tmp.${randomUUID4().slice(0, 8)}`;
34006
34545
  writeFileSync12(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 384 });
34007
34546
  renameSync4(tmp, file);
34008
34547
  } catch {
@@ -34185,7 +34724,7 @@ import { join as join21 } from "node:path";
34185
34724
 
34186
34725
  // plugins/kxm/src/vnext-bindings.ts
34187
34726
  import { spawnSync as spawnSync6 } from "node:child_process";
34188
- import { createHash as createHash8, randomUUID as randomUUID4 } from "node:crypto";
34727
+ import { createHash as createHash9, randomUUID as randomUUID5 } from "node:crypto";
34189
34728
  import {
34190
34729
  chmodSync as chmodSync2,
34191
34730
  closeSync,
@@ -34201,7 +34740,6 @@ import {
34201
34740
  writeFileSync as writeFileSync13
34202
34741
  } from "node:fs";
34203
34742
  import { homedir as homedir3 } from "node:os";
34204
- import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
34205
34743
  import { dirname as dirname9, isAbsolute as isAbsolute5, join as join20, parse as parse5, relative as relative4, resolve as resolve10, sep as sep2 } from "node:path";
34206
34744
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
34207
34745
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -34247,7 +34785,7 @@ function vnextUserStateRoot(options = {}) {
34247
34785
  function projectBindingKey(projectRoot, platform = process.platform) {
34248
34786
  const canonical2 = canonicalHostPath2(projectRoot);
34249
34787
  const keyInput = platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
34250
- return createHash8("sha256").update(keyInput, "utf8").digest("hex");
34788
+ return createHash9("sha256").update(keyInput, "utf8").digest("hex");
34251
34789
  }
34252
34790
  function vnextLocalBindingFile(projectRoot, options = {}) {
34253
34791
  const stateRoot = vnextUserStateRoot(options);
@@ -34426,7 +34964,7 @@ function withVnextLocalBindingLock(projectRoot, options, callback) {
34426
34964
  bindingError("path", "local_binding_lock_invalid", "project-operation lock must be a regular file, not a link or directory");
34427
34965
  }
34428
34966
  }
34429
- const database = new DatabaseSync4(file);
34967
+ const database = new DatabaseSync(file);
34430
34968
  try {
34431
34969
  chmodSync2(file, 384);
34432
34970
  } catch {
@@ -34474,7 +35012,7 @@ function writeVnextLocalBindings(projectRoot, projectId, repositories, options =
34474
35012
  const { file, record } = planned;
34475
35013
  if (!planned.written) return planned;
34476
35014
  ensureBindingDirectory(file, vnextUserStateRoot(options));
34477
- const temporary = join20(dirname9(file), `.repository-bindings-${process.pid}-${randomUUID4()}.tmp`);
35015
+ const temporary = join20(dirname9(file), `.repository-bindings-${process.pid}-${randomUUID5()}.tmp`);
34478
35016
  let descriptor;
34479
35017
  try {
34480
35018
  descriptor = openSync(temporary, "wx", 384);
@@ -34605,36 +35143,481 @@ function resolveInstallKind(probe, npmGlobalRoot) {
34605
35143
  return report("npm-local", classified.root);
34606
35144
  }
34607
35145
 
35146
+ // plugins/kxm/src/init-guide-setup.ts
35147
+ var import_yaml9 = __toESM(require_dist(), 1);
35148
+ import { existsSync as existsSync17, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "node:fs";
35149
+ import { dirname as dirname11, join as join23 } from "node:path";
35150
+ var NATIVE_VENDOR_HARNESS = Object.freeze({
35151
+ anthropic: "claude",
35152
+ openai: "codex",
35153
+ "x-ai": "grok",
35154
+ xai: "grok",
35155
+ google: "agy",
35156
+ moonshotai: "kimi",
35157
+ moonshot: "kimi",
35158
+ deepseek: "deepseek"
35159
+ });
35160
+ var GUIDE_WORKFLOWS = [
35161
+ {
35162
+ slug: "build-feature",
35163
+ area: "software-engineering",
35164
+ name: "Build Feature",
35165
+ summary: "PRD ingestion \u2192 system planning \u2192 spec/contract \u2192 scaffold \u2192 backend \u2192 frontend \u2192 SDK integration",
35166
+ stages: [
35167
+ {
35168
+ slug: "system-planning",
35169
+ title: "System planning",
35170
+ role: "lead-systems-planner",
35171
+ domain: "Decomposes requirements into an executable, dependency-ordered DAG of modules, tasks, and verification gates.",
35172
+ candidates: [
35173
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35174
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35175
+ { vendor: "openai", model: "gpt-6-astra-pro" },
35176
+ { vendor: "x-ai", model: "grok-4.6" },
35177
+ { vendor: "qwen", model: "qwen3.8-max-0902" }
35178
+ ]
35179
+ },
35180
+ {
35181
+ slug: "spec-contract",
35182
+ title: "Spec and contract",
35183
+ role: "spec-contract-generator",
35184
+ domain: "Authors zero-ambiguity API contracts, type definitions, and schema validation rules before implementation begins.",
35185
+ candidates: [
35186
+ { vendor: "openai", model: "gpt-5.6-sol" },
35187
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35188
+ { vendor: "google", model: "gemini-3.8-flash" },
35189
+ { vendor: "qwen", model: "qwen3-coder-plus" },
35190
+ { vendor: "anthropic", model: "claude-fable-5.1" }
35191
+ ]
35192
+ },
35193
+ {
35194
+ slug: "scaffold",
35195
+ title: "Scaffold",
35196
+ role: "scaffold-build-specialist",
35197
+ domain: "Provisions monorepo workspace topologies, linters, Docker multi-stage builds, and CI pipelines.",
35198
+ candidates: [
35199
+ { vendor: "x-ai", model: "grok-4.6" },
35200
+ { vendor: "mistralai", model: "devstral-2512" },
35201
+ { vendor: "qwen", model: "qwen3-coder-plus" },
35202
+ { vendor: "openai", model: "gpt-5.3-codex" },
35203
+ { vendor: "kwaipilot", model: "kat-coder-pro-v2.5" }
35204
+ ]
35205
+ },
35206
+ {
35207
+ slug: "backend-data",
35208
+ title: "Backend and data",
35209
+ role: "backend-data-implementer",
35210
+ domain: "Implements domain entities, SQL queries/migrations, ORM persistence, concurrency primitives, and transactional boundaries.",
35211
+ candidates: [
35212
+ { vendor: "x-ai", model: "grok-4.6" },
35213
+ { vendor: "qwen", model: "qwen3-coder-plus" },
35214
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35215
+ { vendor: "openai", model: "gpt-5.3-codex" },
35216
+ { vendor: "kwaipilot", model: "kat-coder-pro-v2.5" }
35217
+ ]
35218
+ },
35219
+ {
35220
+ slug: "frontend",
35221
+ title: "Frontend",
35222
+ role: "frontend-fullstack-implementer",
35223
+ domain: "Constructs stateful UI components, routing, client cache management, responsive styles, and accessible interactions.",
35224
+ candidates: [
35225
+ { vendor: "anthropic", model: "claude-sonnet-5" },
35226
+ { vendor: "google", model: "gemini-3.8-flash" },
35227
+ { vendor: "x-ai", model: "grok-4.6" },
35228
+ { vendor: "bytedance-seed", model: "seed-2.0-code" },
35229
+ { vendor: "openai", model: "gpt-5.3-codex" }
35230
+ ]
35231
+ },
35232
+ {
35233
+ slug: "sdk-integration",
35234
+ title: "SDK integration",
35235
+ role: "sdk-integration-specialist",
35236
+ domain: "Wires external APIs, authentication flows (OAuth2/OIDC), webhook validation, cloud SDKs, and async worker queues.",
35237
+ candidates: [
35238
+ { vendor: "openai", model: "gpt-5.6-sol" },
35239
+ { vendor: "x-ai", model: "grok-4.6" },
35240
+ { vendor: "qwen", model: "qwen3-coder-plus" },
35241
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35242
+ { vendor: "google", model: "gemini-3.8-flash" }
35243
+ ]
35244
+ }
35245
+ ]
35246
+ },
35247
+ {
35248
+ slug: "refactor-repair-regressions",
35249
+ area: "software-engineering",
35250
+ name: "Refactor and Repair Regressions",
35251
+ summary: "Codebase smell analysis \u2192 modular decomposition \u2192 surgical multi-file transforms \u2192 equivalence verification",
35252
+ stages: [
35253
+ {
35254
+ slug: "refactor-architecture",
35255
+ title: "Refactor architecture",
35256
+ role: "refactoring-architect",
35257
+ domain: "Analyzes code coupling, designs clean modular boundaries, and authors step-by-step non-breaking migration plans.",
35258
+ candidates: [
35259
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35260
+ { vendor: "z-ai", model: "glm-5.3" },
35261
+ { vendor: "anthropic", model: "claude-opus-5" },
35262
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35263
+ { vendor: "moonshotai", model: "kimi-k3" }
35264
+ ]
35265
+ },
35266
+ {
35267
+ slug: "code-transform",
35268
+ title: "Code transform",
35269
+ role: "code-transform-implementer",
35270
+ domain: "Executes surgical multi-file edits, applying design patterns while preserving comments and formatting.",
35271
+ candidates: [
35272
+ { vendor: "x-ai", model: "grok-4.6" },
35273
+ { vendor: "relace", model: "relace-apply-3" },
35274
+ { vendor: "openai", model: "gpt-5.3-codex" },
35275
+ { vendor: "morph", model: "morph-v3-large" },
35276
+ { vendor: "anthropic", model: "claude-sonnet-5" }
35277
+ ]
35278
+ },
35279
+ {
35280
+ slug: "equivalence-verification",
35281
+ title: "Equivalence verification",
35282
+ role: "semantic-equivalence-verifier",
35283
+ domain: "Critic-with-gate: reviews public contract compliance and behavioral equivalence; deterministic checks remain the fixed witness gate and are never delegated to an LLM.",
35284
+ candidates: [
35285
+ { vendor: "openai", model: "gpt-5.6-sol" },
35286
+ { vendor: "openai", model: "o3-mini-high" },
35287
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35288
+ { vendor: "anthropic", model: "claude-opus-5" },
35289
+ { vendor: "mistralai", model: "devstral-2512" }
35290
+ ]
35291
+ }
35292
+ ]
35293
+ },
35294
+ {
35295
+ slug: "stabilize-flaky-tests",
35296
+ area: "software-engineering",
35297
+ name: "Stabilize Flaky Tests",
35298
+ summary: "Flakiness root-cause extraction \u2192 deterministic mock/async hardening",
35299
+ stages: [
35300
+ {
35301
+ slug: "flakiness-diagnosis",
35302
+ title: "Flakiness diagnosis",
35303
+ role: "concurrency-flakiness-detective",
35304
+ domain: "Untangles timing hazards, async wait conditions, shared state pollution, and unhandled promise rejections.",
35305
+ candidates: [
35306
+ { vendor: "openai", model: "o3" },
35307
+ { vendor: "deepseek", model: "deepseek-r1-0528" },
35308
+ { vendor: "google", model: "gemini-3.8-flash" },
35309
+ { vendor: "anthropic", model: "claude-opus-5" },
35310
+ { vendor: "openai", model: "gpt-6-astra" }
35311
+ ]
35312
+ }
35313
+ ]
35314
+ },
35315
+ {
35316
+ slug: "design-software-system",
35317
+ area: "software-engineering",
35318
+ name: "Design Software System",
35319
+ summary: "Architecture \u2192 database topology \u2192 threat modeling \u2192 IaC \u2192 reliability review \u2192 protocol audit",
35320
+ stages: [
35321
+ {
35322
+ slug: "distributed-architecture",
35323
+ title: "Distributed architecture",
35324
+ role: "principal-distributed-architect",
35325
+ domain: "Evaluates CAP/PACELC trade-offs, consensus protocols (Raft, Sagas), partition boundaries, and authors primary RFCs.",
35326
+ candidates: [
35327
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35328
+ { vendor: "openai", model: "o3" },
35329
+ { vendor: "openai", model: "gpt-6-astra-pro" },
35330
+ { vendor: "anthropic", model: "claude-opus-5" },
35331
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" }
35332
+ ]
35333
+ },
35334
+ {
35335
+ slug: "database-topology",
35336
+ title: "Database topology",
35337
+ role: "scalability-database-topologist",
35338
+ domain: "Designs relational/NoSQL schemas, horizontal sharding keys, caching topologies, and zero-downtime migration plans.",
35339
+ candidates: [
35340
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35341
+ { vendor: "openai", model: "gpt-6-astra-pro" },
35342
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35343
+ { vendor: "qwen", model: "qwen3.8-2.4t-a95b" },
35344
+ { vendor: "x-ai", model: "grok-4.6" }
35345
+ ]
35346
+ },
35347
+ {
35348
+ slug: "threat-modeling",
35349
+ title: "Threat modeling",
35350
+ role: "security-architect-threat-modeler",
35351
+ domain: "Conducts STRIDE threat assessments, reviews IAM least-privilege policies, mTLS boundaries, and zero-trust architectures.",
35352
+ candidates: [
35353
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35354
+ { vendor: "openai", model: "gpt-5.6-sol" },
35355
+ { vendor: "openai", model: "gpt-6-astra-pro" },
35356
+ { vendor: "anthropic", model: "claude-opus-5" },
35357
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" }
35358
+ ]
35359
+ },
35360
+ {
35361
+ slug: "iac-topology",
35362
+ title: "IaC topology",
35363
+ role: "iac-cloud-topology-engineer",
35364
+ domain: "Generates declarative Terraform/OpenTofu, Pulumi, Kubernetes CRDs, and VPC networking definitions.",
35365
+ candidates: [
35366
+ { vendor: "x-ai", model: "grok-4.6" },
35367
+ { vendor: "qwen", model: "qwen3-coder-plus" },
35368
+ { vendor: "openai", model: "gpt-5.3-codex" },
35369
+ { vendor: "google", model: "gemini-3.8-flash" },
35370
+ { vendor: "mistralai", model: "devstral-2512" }
35371
+ ]
35372
+ },
35373
+ {
35374
+ slug: "reliability-review",
35375
+ title: "Reliability review",
35376
+ role: "reliability-chaos-reviewer",
35377
+ domain: "Evaluates single points of failure, RPO/RTO metrics, cascading timeouts, circuit breakers, and active-active failovers.",
35378
+ candidates: [
35379
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35380
+ { vendor: "openai", model: "o3" },
35381
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35382
+ { vendor: "openai", model: "gpt-6-astra-pro" },
35383
+ { vendor: "moonshotai", model: "kimi-k3" }
35384
+ ]
35385
+ },
35386
+ {
35387
+ slug: "protocol-audit",
35388
+ title: "Protocol audit",
35389
+ role: "architecture-critic-protocol-auditor",
35390
+ domain: "Independent peer-review; challenges unvalidated assumptions, prevents architectural drift, and verifies contracts.",
35391
+ candidates: [
35392
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35393
+ { vendor: "openai", model: "gpt-5.6-sol" },
35394
+ { vendor: "z-ai", model: "glm-5.3" },
35395
+ { vendor: "openai", model: "gpt-6-astra-pro" },
35396
+ { vendor: "qwen", model: "qwen3.8-max-0902" }
35397
+ ]
35398
+ }
35399
+ ]
35400
+ },
35401
+ {
35402
+ slug: "maintain-documentation",
35403
+ area: "software-engineering",
35404
+ name: "Maintain Documentation",
35405
+ summary: "Accuracy audit \u2192 documentation write \u2192 review",
35406
+ stages: [
35407
+ {
35408
+ slug: "accuracy-audit",
35409
+ title: "Accuracy audit",
35410
+ role: "documentation-accuracy-auditor",
35411
+ domain: "Read-only audit of developer and operator documentation against CLI --help outputs, source environment variables, tool schemas, and repository configuration.",
35412
+ candidates: [
35413
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35414
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35415
+ { vendor: "openai", model: "gpt-5.6-sol" },
35416
+ { vendor: "qwen", model: "qwen3.8-max-0902" },
35417
+ { vendor: "z-ai", model: "glm-5.3" }
35418
+ ]
35419
+ },
35420
+ {
35421
+ slug: "documentation-write",
35422
+ title: "Documentation write",
35423
+ role: "documentation-writer",
35424
+ domain: "Authors concise, accurate developer and operator documentation with clear terminology, correct command flags, and consistent formatting without marketing drift.",
35425
+ candidates: [
35426
+ { vendor: "x-ai", model: "grok-4.6" },
35427
+ { vendor: "qwen", model: "qwen3-coder-plus" },
35428
+ { vendor: "anthropic", model: "claude-sonnet-5" },
35429
+ { vendor: "openai", model: "gpt-5.3-codex" },
35430
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" }
35431
+ ]
35432
+ },
35433
+ {
35434
+ slug: "review",
35435
+ title: "Review",
35436
+ role: "documentation-reviewer",
35437
+ domain: "Critic-with-gate: the architecture critic verifies authority and permission wording; the CLI critic verifies command/flag accuracy; deterministic docs lint remains the fixed witness gate.",
35438
+ candidates: [
35439
+ { vendor: "anthropic", model: "claude-fable-5.1" },
35440
+ { vendor: "openai", model: "gpt-5.6-sol" },
35441
+ { vendor: "anthropic", model: "claude-opus-5" },
35442
+ { vendor: "deepseek", model: "deepseek-v4-pro-0813" },
35443
+ { vendor: "mistralai", model: "devstral-2512" }
35444
+ ]
35445
+ }
35446
+ ]
35447
+ }
35448
+ ];
35449
+ function authenticatedHarnesses(inventory) {
35450
+ return new Set(
35451
+ inventory.harnesses.filter((entry) => entry.detected && entry.authenticated === true).map((entry) => entry.id)
35452
+ );
35453
+ }
35454
+ function resolveCandidate(candidates, eligible) {
35455
+ for (const candidate of candidates) {
35456
+ const nativeHarness = NATIVE_VENDOR_HARNESS[candidate.vendor];
35457
+ if (nativeHarness) {
35458
+ if (eligible.has(nativeHarness)) {
35459
+ return { harness: nativeHarness, provider: NATIVE_HARNESS_PROVIDERS[nativeHarness], model: candidate.model };
35460
+ }
35461
+ continue;
35462
+ }
35463
+ if (eligible.has("pi")) {
35464
+ return { harness: "pi", provider: "openrouter", model: `${candidate.vendor}/${candidate.model}` };
35465
+ }
35466
+ }
35467
+ return void 0;
35468
+ }
35469
+ function planGuideSetup(options) {
35470
+ const eligible = authenticatedHarnesses(options.inventory);
35471
+ const agents = /* @__PURE__ */ new Map();
35472
+ const workflows = [];
35473
+ const skipped = [];
35474
+ const wanted = new Set(options.selected);
35475
+ for (const workflow of GUIDE_WORKFLOWS) {
35476
+ if (!wanted.has(workflow.slug)) continue;
35477
+ let covered = true;
35478
+ for (const stage of workflow.stages) {
35479
+ if (agents.has(stage.role)) continue;
35480
+ const binding = resolveCandidate(stage.candidates, eligible);
35481
+ if (!binding) {
35482
+ skipped.push({
35483
+ workflow: workflow.slug,
35484
+ stage: stage.slug,
35485
+ role: stage.role,
35486
+ reason: "no guide candidate has an authenticated harness (see `kxm harness list`)"
35487
+ });
35488
+ covered = false;
35489
+ continue;
35490
+ }
35491
+ agents.set(stage.role, binding);
35492
+ }
35493
+ if (covered) workflows.push(workflow);
35494
+ else skipped.push({ workflow: workflow.slug, stage: "*", role: "-", reason: "workflow skipped: at least one stage has no eligible agent" });
35495
+ }
35496
+ return { agents, workflows, skipped };
35497
+ }
35498
+ var WRITER_ROLE_PATTERN = /(writer|implementer|scaffold|backend|frontend|sdk-integration|iac-cloud|code-transform|build-specialist)/;
35499
+ function isWriterRole(roleSlug) {
35500
+ return WRITER_ROLE_PATTERN.test(roleSlug);
35501
+ }
35502
+ function agentDocument(roleSlug, stage, binding) {
35503
+ const writer = isWriterRole(roleSlug);
35504
+ return {
35505
+ schema: "kxm.agent.v1",
35506
+ purpose: `${stage.domain} (workflow-guide ${stage.title}; candidate verification is the operator's responsibility)`,
35507
+ harness: binding.harness,
35508
+ model: { provider: binding.provider, model: binding.model },
35509
+ tools: { preset: writer ? "workspace-writer" : "read-only" },
35510
+ defaultRepositoryAccess: writer ? "none" : "read",
35511
+ repositories: { control: writer ? "write" : "read" },
35512
+ network: "provider-only",
35513
+ resultSchema: "kxm.assignment-result.v1"
35514
+ };
35515
+ }
35516
+ function workflowDocument(workflow) {
35517
+ const steps = workflow.stages.map((stage, index) => {
35518
+ const last = index === workflow.stages.length - 1;
35519
+ return {
35520
+ id: stage.slug,
35521
+ kind: "agent",
35522
+ agent: stage.role,
35523
+ maxAttempts: 2,
35524
+ on: {
35525
+ passed: last ? { target: "$terminal", terminalStatus: "completed" } : workflow.stages[index + 1].slug,
35526
+ failed: { target: "$terminal", terminalStatus: "failed" }
35527
+ }
35528
+ };
35529
+ });
35530
+ return {
35531
+ schema: "kxm.workflow.v1",
35532
+ description: `${workflow.name} (${workflow.area}, workflow-guide): ${workflow.summary}`,
35533
+ coordinator: "coordinator",
35534
+ limits: { maxTransitions: Math.max(4, workflow.stages.length * 4) },
35535
+ steps
35536
+ };
35537
+ }
35538
+ function renderGuideSetupFiles(projectRoot, plan) {
35539
+ const files = [];
35540
+ const roleStages = /* @__PURE__ */ new Map();
35541
+ for (const workflow of plan.workflows) {
35542
+ for (const stage of workflow.stages) roleStages.set(stage.role, stage);
35543
+ }
35544
+ for (const [role, binding] of [...plan.agents.entries()].sort(([a], [b2]) => a.localeCompare(b2))) {
35545
+ const stage = roleStages.get(role);
35546
+ if (!stage) continue;
35547
+ files.push({
35548
+ path: join23(projectRoot, ".kxm", "agents", `${role}.yaml`),
35549
+ content: (0, import_yaml9.stringify)(agentDocument(role, stage, binding))
35550
+ });
35551
+ }
35552
+ for (const workflow of plan.workflows) {
35553
+ files.push({
35554
+ path: join23(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
35555
+ content: (0, import_yaml9.stringify)(workflowDocument(workflow))
35556
+ });
35557
+ }
35558
+ return files;
35559
+ }
35560
+ function writeGuideSetupFiles(files) {
35561
+ const written = [];
35562
+ const existed = [];
35563
+ for (const file of files) {
35564
+ if (existsSync17(file.path)) {
35565
+ existed.push(file.path);
35566
+ continue;
35567
+ }
35568
+ mkdirSync16(dirname11(file.path), { recursive: true });
35569
+ writeFileSync14(file.path, file.content, "utf8");
35570
+ written.push(file.path);
35571
+ }
35572
+ return { written, existed };
35573
+ }
35574
+ function parseGuideSelection(input, catalog = GUIDE_WORKFLOWS) {
35575
+ const trimmed = input.trim().toLowerCase();
35576
+ if (!trimmed || trimmed === "none" || trimmed === "n" || trimmed === "no") return [];
35577
+ if (trimmed === "all") return catalog.map((workflow) => workflow.slug);
35578
+ const selected = /* @__PURE__ */ new Set();
35579
+ for (const token of trimmed.split(/[,\s]+/).filter(Boolean)) {
35580
+ const index = Number.parseInt(token, 10);
35581
+ if (Number.isFinite(index) && index >= 1 && index <= catalog.length) {
35582
+ selected.add(catalog[index - 1].slug);
35583
+ continue;
35584
+ }
35585
+ const match = catalog.find((workflow) => workflow.slug === token);
35586
+ if (match) selected.add(match.slug);
35587
+ }
35588
+ return [...selected];
35589
+ }
35590
+
34608
35591
  // plugins/kxm/src/vnext-init.ts
34609
- import { randomUUID as randomUUID6 } from "node:crypto";
34610
- import { existsSync as existsSync19 } from "node:fs";
34611
- import { basename as basename5, join as join25 } from "node:path";
35592
+ import { randomUUID as randomUUID7 } from "node:crypto";
35593
+ import { existsSync as existsSync20 } from "node:fs";
35594
+ import { basename as basename5, join as join26 } from "node:path";
34612
35595
 
34613
35596
  // plugins/kxm/src/vnext-repair.ts
34614
- import { randomUUID as randomUUID5 } from "node:crypto";
35597
+ import { randomUUID as randomUUID6 } from "node:crypto";
34615
35598
  import {
34616
35599
  chmodSync as chmodSync3,
34617
35600
  closeSync as closeSync2,
34618
- existsSync as existsSync18,
35601
+ existsSync as existsSync19,
34619
35602
  fsyncSync as fsyncSync2,
34620
35603
  linkSync,
34621
35604
  lstatSync as lstatSync5,
34622
- mkdirSync as mkdirSync17,
35605
+ mkdirSync as mkdirSync18,
34623
35606
  openSync as openSync2,
34624
35607
  readFileSync as readFileSync18,
34625
35608
  readdirSync as readdirSync7,
34626
35609
  renameSync as renameSync6,
34627
35610
  rmSync as rmSync5,
34628
- writeFileSync as writeFileSync15
35611
+ writeFileSync as writeFileSync16
34629
35612
  } from "node:fs";
34630
- import { dirname as dirname12, isAbsolute as isAbsolute6, join as join24, relative as relative5, resolve as resolve12 } from "node:path";
35613
+ import { dirname as dirname13, isAbsolute as isAbsolute6, join as join25, relative as relative5, resolve as resolve12 } from "node:path";
34631
35614
 
34632
35615
  // plugins/kxm/src/vnext-permission.ts
34633
35616
  import { spawnSync as spawnSync7 } from "node:child_process";
34634
- import { createHash as createHash9 } from "node:crypto";
34635
- import { existsSync as existsSync17, mkdtempSync, mkdirSync as mkdirSync16, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "node:fs";
35617
+ import { createHash as createHash10 } from "node:crypto";
35618
+ import { existsSync as existsSync18, mkdtempSync, mkdirSync as mkdirSync17, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync15 } from "node:fs";
34636
35619
  import { tmpdir } from "node:os";
34637
- import { dirname as dirname11, join as join23, resolve as resolve11, sep as sep3 } from "node:path";
35620
+ import { dirname as dirname12, join as join24, resolve as resolve11, sep as sep3 } from "node:path";
34638
35621
  var PROSE_FIELDS = {
34639
35622
  project: /* @__PURE__ */ new Set(["name", "description"]),
34640
35623
  repository: /* @__PURE__ */ new Set(["description"]),
@@ -34837,7 +35820,7 @@ function vnextProseEntries(resource) {
34837
35820
  return entries;
34838
35821
  }
34839
35822
  function valueHash(canonicalValue) {
34840
- return `sha256:${createHash9("sha256").update(canonicalValue, "utf8").digest("hex")}`;
35823
+ return `sha256:${createHash10("sha256").update(canonicalValue, "utf8").digest("hex")}`;
34841
35824
  }
34842
35825
  function rankDirection(rank, baseValue, candidateValue) {
34843
35826
  const base = JSON.parse(baseValue);
@@ -35155,9 +36138,9 @@ function loadVnextProjectAtRevision(root, revision, options = {}) {
35155
36138
  const tree = resolveTreeRevision(root, revision);
35156
36139
  const listing = gitBuffer(root, ["ls-tree", "-r", "-z", "--name-only", tree]).toString("utf8");
35157
36140
  const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
35158
- const shadow = mkdtempSync(join23(tmpdir(), "kxm-permission-base-"));
36141
+ const shadow = mkdtempSync(join24(tmpdir(), "kxm-permission-base-"));
35159
36142
  const shadowResolved = resolve11(shadow);
35160
- const shadowMembersDir = `.kxm-shadow-members-${createHash9("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
36143
+ const shadowMembersDir = `.kxm-shadow-members-${createHash10("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
35161
36144
  try {
35162
36145
  for (const path4 of paths) {
35163
36146
  const segments = path4.split("/");
@@ -35169,7 +36152,7 @@ function loadVnextProjectAtRevision(root, revision, options = {}) {
35169
36152
  message: "Git tree entry contains a traversal or non-portable segment"
35170
36153
  }]);
35171
36154
  }
35172
- const absolute = join23(shadowResolved, ...segments);
36155
+ const absolute = join24(shadowResolved, ...segments);
35173
36156
  const resolved = resolve11(absolute);
35174
36157
  if (!resolved.startsWith(`${shadowResolved}${sep3}`)) {
35175
36158
  throw new VnextConfigError([{
@@ -35180,11 +36163,11 @@ function loadVnextProjectAtRevision(root, revision, options = {}) {
35180
36163
  }]);
35181
36164
  }
35182
36165
  const content = gitBuffer(root, ["cat-file", "blob", `${tree}:${path4}`]);
35183
- mkdirSync16(dirname11(absolute), { recursive: true });
35184
- writeFileSync14(absolute, content);
36166
+ mkdirSync17(dirname12(absolute), { recursive: true });
36167
+ writeFileSync15(absolute, content);
35185
36168
  }
35186
36169
  initShadowGitRoot(shadowResolved);
35187
- const baseProjectFile = join23(shadowResolved, ".kxm", "project.yaml");
36170
+ const baseProjectFile = join24(shadowResolved, ".kxm", "project.yaml");
35188
36171
  let baseProject;
35189
36172
  try {
35190
36173
  baseProject = loadBaseProjectDeclarations(baseProjectFile);
@@ -35215,7 +36198,7 @@ function loadVnextProjectAtRevision(root, revision, options = {}) {
35215
36198
  }
35216
36199
  const boundPath = options.repositoryBindings?.[member.repositoryId];
35217
36200
  const memberWorktree = boundPath ? resolve11(boundPath) : member.pathHint ? resolve11(root, ...member.pathHint.split("/")) : void 0;
35218
- if (!memberWorktree || !existsSync17(memberWorktree)) {
36201
+ if (!memberWorktree || !existsSync18(memberWorktree)) {
35219
36202
  if (member.required === false) continue;
35220
36203
  throw new VnextConfigError([{
35221
36204
  phase: "discovery",
@@ -35262,7 +36245,7 @@ function loadVnextProjectAtRevision(root, revision, options = {}) {
35262
36245
  const memberTree = git(memberWorktree, ["rev-parse", "--verify", "--quiet", `${pinnedCommit}^{tree}`]).trim();
35263
36246
  const memberListing = gitBuffer(memberWorktree, ["ls-tree", "-r", "-z", "--name-only", memberTree]).toString("utf8");
35264
36247
  const memberPaths = memberListing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
35265
- const memberShadowRoot = join23(shadowResolved, shadowMembersDir, member.repositoryId);
36248
+ const memberShadowRoot = join24(shadowResolved, shadowMembersDir, member.repositoryId);
35266
36249
  if (!resolve11(memberShadowRoot).startsWith(`${resolve11(shadowResolved, shadowMembersDir)}${sep3}`)) {
35267
36250
  throw new VnextConfigError([{
35268
36251
  phase: "path",
@@ -35276,14 +36259,14 @@ function loadVnextProjectAtRevision(root, revision, options = {}) {
35276
36259
  if (segments.some((segment) => segment === "" || segment === "." || segment === ".." || /[\\:]/.test(segment))) {
35277
36260
  throw new VnextConfigError([{ phase: "path", code: "git_tree_path_invalid", file: path4, message: "member Git tree entry contains a traversal or non-portable segment" }]);
35278
36261
  }
35279
- const absolute = join23(memberShadowRoot, ...segments);
36262
+ const absolute = join24(memberShadowRoot, ...segments);
35280
36263
  const resolved = resolve11(absolute);
35281
36264
  if (!resolved.startsWith(`${resolve11(memberShadowRoot)}${sep3}`)) {
35282
36265
  throw new VnextConfigError([{ phase: "path", code: "git_tree_path_invalid", file: path4, message: "member Git tree entry escapes the shadow root" }]);
35283
36266
  }
35284
36267
  const content = gitBuffer(memberWorktree, ["cat-file", "blob", `${memberTree}:${path4}`]);
35285
- mkdirSync16(dirname11(absolute), { recursive: true });
35286
- writeFileSync14(absolute, content);
36268
+ mkdirSync17(dirname12(absolute), { recursive: true });
36269
+ writeFileSync15(absolute, content);
35287
36270
  }
35288
36271
  initShadowGitRoot(memberShadowRoot);
35289
36272
  shadowBindings[member.repositoryId] = memberShadowRoot;
@@ -35304,7 +36287,7 @@ function portableMemberPathHint(pathHint) {
35304
36287
  return pathHint !== "." && vnextPortablePath(pathHint);
35305
36288
  }
35306
36289
  function loadBaseProjectDeclarations(projectFile) {
35307
- if (!existsSync17(projectFile)) return [];
36290
+ if (!existsSync18(projectFile)) return [];
35308
36291
  const value = parseRestrictedYaml2(readFileSync17(projectFile, "utf8"), ".kxm/project.yaml");
35309
36292
  const repositories = Array.isArray(value?.repositories) ? value.repositories : [];
35310
36293
  const members = [];
@@ -35397,7 +36380,7 @@ function writeDurableNew(file, bytes, mode) {
35397
36380
  let descriptor;
35398
36381
  try {
35399
36382
  descriptor = openSync2(file, "wx", mode);
35400
- writeFileSync15(descriptor, bytes);
36383
+ writeFileSync16(descriptor, bytes);
35401
36384
  fsyncSync2(descriptor);
35402
36385
  closeSync2(descriptor);
35403
36386
  descriptor = void 0;
@@ -35408,13 +36391,13 @@ function writeDurableNew(file, bytes, mode) {
35408
36391
  } finally {
35409
36392
  if (descriptor !== void 0) closeSync2(descriptor);
35410
36393
  }
35411
- syncDirectory2(dirname12(file));
36394
+ syncDirectory2(dirname13(file));
35412
36395
  }
35413
36396
  function writeDurable(file, bytes, mode) {
35414
36397
  let descriptor;
35415
36398
  try {
35416
36399
  descriptor = openSync2(file, "w", mode);
35417
- writeFileSync15(descriptor, bytes);
36400
+ writeFileSync16(descriptor, bytes);
35418
36401
  fsyncSync2(descriptor);
35419
36402
  closeSync2(descriptor);
35420
36403
  descriptor = void 0;
@@ -35425,7 +36408,7 @@ function writeDurable(file, bytes, mode) {
35425
36408
  } finally {
35426
36409
  if (descriptor !== void 0) closeSync2(descriptor);
35427
36410
  }
35428
- syncDirectory2(dirname12(file));
36411
+ syncDirectory2(dirname13(file));
35429
36412
  }
35430
36413
  function sameHostPath3(left, right) {
35431
36414
  const first = resolve12(left);
@@ -35433,19 +36416,19 @@ function sameHostPath3(left, right) {
35433
36416
  return process.platform === "win32" ? first.toLocaleLowerCase("en-US") === second.toLocaleLowerCase("en-US") : first === second;
35434
36417
  }
35435
36418
  function transactionRoot(projectRoot) {
35436
- return join24(resolve12(projectRoot), TRANSACTION_NAME);
36419
+ return join25(resolve12(projectRoot), TRANSACTION_NAME);
35437
36420
  }
35438
36421
  function operationFile(projectRoot) {
35439
- return join24(transactionRoot(projectRoot), OPERATION_FILE);
36422
+ return join25(transactionRoot(projectRoot), OPERATION_FILE);
35440
36423
  }
35441
36424
  function targetFile(projectRoot, portablePath2) {
35442
- return join24(transactionRoot(projectRoot), "targets", ...portablePath2.split("/"));
36425
+ return join25(transactionRoot(projectRoot), "targets", ...portablePath2.split("/"));
35443
36426
  }
35444
36427
  function shadowRoot(projectRoot) {
35445
- return join24(transactionRoot(projectRoot), "shadow");
36428
+ return join25(transactionRoot(projectRoot), "shadow");
35446
36429
  }
35447
36430
  function backupFile(projectRoot, portablePath2) {
35448
- return join24(transactionRoot(projectRoot), "backups", ...portablePath2.split("/"));
36431
+ return join25(transactionRoot(projectRoot), "backups", ...portablePath2.split("/"));
35449
36432
  }
35450
36433
  function portableManagedPath(path4) {
35451
36434
  return path4.startsWith(".kxm/") && path4 !== VNEXT_TEMPLATE_PROVENANCE_PATH && path4.length <= 1024 && !path4.includes("\\") && !isAbsolute6(path4) && PORTABLE_PATH.test(path4) && !/[<>:"|?*]/.test(path4);
@@ -35457,21 +36440,21 @@ function readRegularBounded(file, label) {
35457
36440
  return readFileSync18(file);
35458
36441
  }
35459
36442
  function readOptionalManaged(projectRoot, path4) {
35460
- const file = join24(projectRoot, ...path4.split("/"));
35461
- if (!existsSync18(file)) return void 0;
35462
- let parent = dirname12(file);
36443
+ const file = join25(projectRoot, ...path4.split("/"));
36444
+ if (!existsSync19(file)) return void 0;
36445
+ let parent = dirname13(file);
35463
36446
  const boundary = resolve12(projectRoot);
35464
36447
  while (parent !== boundary) {
35465
36448
  const stat = lstatSync5(parent);
35466
36449
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_parent_invalid", path4, "managed file parents must be regular directories", "path");
35467
- parent = dirname12(parent);
36450
+ parent = dirname13(parent);
35468
36451
  }
35469
36452
  return readRegularBounded(file, path4);
35470
36453
  }
35471
36454
  function readProjectAndProvenance(projectRoot, schemasDir) {
35472
- const projectFile = join24(projectRoot, ".kxm", "project.yaml");
35473
- const provenanceFile = join24(projectRoot, ...VNEXT_TEMPLATE_PROVENANCE_PATH.split("/"));
35474
- if (!existsSync18(projectFile) || !existsSync18(provenanceFile)) return void 0;
36455
+ const projectFile = join25(projectRoot, ".kxm", "project.yaml");
36456
+ const provenanceFile = join25(projectRoot, ...VNEXT_TEMPLATE_PROVENANCE_PATH.split("/"));
36457
+ if (!existsSync19(projectFile) || !existsSync19(provenanceFile)) return void 0;
35475
36458
  const projectBytes = readOptionalManaged(projectRoot, ".kxm/project.yaml");
35476
36459
  const provenanceBytes = readOptionalManaged(projectRoot, VNEXT_TEMPLATE_PROVENANCE_PATH);
35477
36460
  if (!projectBytes || !provenanceBytes) return void 0;
@@ -35635,7 +36618,7 @@ function operationPlanSha(operation) {
35635
36618
  function writeOperation(projectRoot, operation) {
35636
36619
  const root = transactionRoot(projectRoot);
35637
36620
  const file = operationFile(projectRoot);
35638
- const temporary = join24(root, `.operation-${operation.operationId}-${randomUUID5()}.tmp`);
36621
+ const temporary = join25(root, `.operation-${operation.operationId}-${randomUUID6()}.tmp`);
35639
36622
  try {
35640
36623
  writeDurableNew(temporary, Buffer.from(`${JSON.stringify(operation, null, 2)}
35641
36624
  `, "utf8"), 384);
@@ -35652,11 +36635,11 @@ function updatePhase(projectRoot, operation, phase) {
35652
36635
  }
35653
36636
  function readOperation(projectRoot, schemasDir) {
35654
36637
  const root = transactionRoot(projectRoot);
35655
- if (!existsSync18(root)) return void 0;
36638
+ if (!existsSync19(root)) return void 0;
35656
36639
  const stat = lstatSync5(root);
35657
36640
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("init_transaction_invalid", TRANSACTION_NAME, "initialization transaction must be a regular directory", "path");
35658
36641
  const file = operationFile(projectRoot);
35659
- if (!existsSync18(file)) {
36642
+ if (!existsSync19(file)) {
35660
36643
  const entries = readdirSync7(root, { withFileTypes: true });
35661
36644
  const recoverable = entries.every((entry) => entry.isFile() && /^\.operation-op_[a-f0-9]{32}-[a-f0-9-]{36}\.tmp$/.test(entry.name));
35662
36645
  if (recoverable) return void 0;
@@ -35684,15 +36667,15 @@ function readOperation(projectRoot, schemasDir) {
35684
36667
  return operation;
35685
36668
  }
35686
36669
  function hasVnextInitTransaction(projectRoot) {
35687
- return existsSync18(transactionRoot(projectRoot));
36670
+ return existsSync19(transactionRoot(projectRoot));
35688
36671
  }
35689
36672
  function readTarget(projectRoot, path4) {
35690
- return readOptionalManaged(join24(transactionRoot(projectRoot), "targets"), path4);
36673
+ return readOptionalManaged(join25(transactionRoot(projectRoot), "targets"), path4);
35691
36674
  }
35692
36675
  function writeTarget(projectRoot, path4, bytes) {
35693
36676
  if (bytes.byteLength > MAX_MANAGED_FILE_BYTES) fail3("template_target_too_large", path4, `target exceeds ${MAX_MANAGED_FILE_BYTES} bytes`);
35694
36677
  const file = targetFile(projectRoot, path4);
35695
- mkdirSync17(dirname12(file), { recursive: true, mode: 448 });
36678
+ mkdirSync18(dirname13(file), { recursive: true, mode: 448 });
35696
36679
  writeDurableNew(file, bytes, 384);
35697
36680
  }
35698
36681
  function writeAndVerifyBackups(projectRoot, operation) {
@@ -35704,7 +36687,7 @@ function writeAndVerifyBackups(projectRoot, operation) {
35704
36687
  fail3("repair_preimage_changed", entry.path, "destination changed before transaction preparation; local bytes were preserved");
35705
36688
  }
35706
36689
  const file = backupFile(projectRoot, entry.path);
35707
- mkdirSync17(dirname12(file), { recursive: true, mode: 448 });
36690
+ mkdirSync18(dirname13(file), { recursive: true, mode: 448 });
35708
36691
  writeDurableNew(file, current, 384);
35709
36692
  }
35710
36693
  }
@@ -35719,7 +36702,7 @@ function writeKnownBackups(projectRoot, operation) {
35719
36702
  fail3("init_transaction_backup_source_invalid", entry.path, "supported source template cannot reproduce the pinned preimage");
35720
36703
  }
35721
36704
  const file = backupFile(projectRoot, entry.path);
35722
- mkdirSync17(dirname12(file), { recursive: true, mode: 448 });
36705
+ mkdirSync18(dirname13(file), { recursive: true, mode: 448 });
35723
36706
  writeDurableNew(file, bytes, 384);
35724
36707
  }
35725
36708
  }
@@ -35727,7 +36710,7 @@ function verifyBackups(projectRoot, operation) {
35727
36710
  if (operation.kind !== "repair") return;
35728
36711
  for (const entry of operation.files) {
35729
36712
  if (entry.observedSha256 === null) continue;
35730
- const bytes = readOptionalManaged(join24(transactionRoot(projectRoot), "backups"), entry.path);
36713
+ const bytes = readOptionalManaged(join25(transactionRoot(projectRoot), "backups"), entry.path);
35731
36714
  if (!bytes || vnextContentSha256(bytes) !== entry.observedSha256) {
35732
36715
  fail3("init_transaction_backup_invalid", entry.path, "pinned preimage backup is missing or corrupt");
35733
36716
  }
@@ -35761,10 +36744,10 @@ function sourceConfigFiles(projectRoot) {
35761
36744
  ".kxm/repo/repo.yaml",
35762
36745
  ".kxm/repo/env.yaml"
35763
36746
  ];
35764
- const files = fixed.filter((path4) => existsSync18(join24(projectRoot, ...path4.split("/"))));
36747
+ const files = fixed.filter((path4) => existsSync19(join25(projectRoot, ...path4.split("/"))));
35765
36748
  for (const directory of ["agents", "models", "workflows"]) {
35766
- const absolute = join24(projectRoot, ".kxm", directory);
35767
- if (!existsSync18(absolute)) continue;
36749
+ const absolute = join25(projectRoot, ".kxm", directory);
36750
+ if (!existsSync19(absolute)) continue;
35768
36751
  const stat = lstatSync5(absolute);
35769
36752
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_directory_invalid", `.kxm/${directory}`, "configuration directory must be regular", "path");
35770
36753
  for (const entry of readdirSync7(absolute, { withFileTypes: true })) {
@@ -35786,23 +36769,23 @@ function absoluteMemberBindings(projectRoot, project, explicit = {}) {
35786
36769
  if (repository.role !== "member" || typeof repository.id !== "string" || Object.hasOwn(bindings, repository.id)) continue;
35787
36770
  if (typeof repository.pathHint === "string") {
35788
36771
  const candidate2 = resolve12(projectRoot, ...repository.pathHint.split("/"));
35789
- if (existsSync18(candidate2)) bindings[repository.id] = candidate2;
36772
+ if (existsSync19(candidate2)) bindings[repository.id] = candidate2;
35790
36773
  }
35791
36774
  }
35792
36775
  return bindings;
35793
36776
  }
35794
36777
  function prepareShadow(projectRoot, operation, options) {
35795
36778
  const shadow = shadowRoot(projectRoot);
35796
- mkdirSync17(shadow, { recursive: true, mode: 448 });
36779
+ mkdirSync18(shadow, { recursive: true, mode: 448 });
35797
36780
  let total = 0;
35798
36781
  if (operation.kind === "repair") {
35799
36782
  for (const path4 of sourceConfigFiles(projectRoot)) {
35800
36783
  const bytes = readOptionalManaged(projectRoot, path4);
35801
36784
  if (!bytes) fail3("repair_snapshot_file_missing", path4, "configuration changed during shadow preparation");
35802
36785
  total += bytes.byteLength;
35803
- const source = join24(projectRoot, ...path4.split("/"));
35804
- const destination = join24(shadow, ...path4.split("/"));
35805
- mkdirSync17(dirname12(destination), { recursive: true, mode: 448 });
36786
+ const source = join25(projectRoot, ...path4.split("/"));
36787
+ const destination = join25(shadow, ...path4.split("/"));
36788
+ mkdirSync18(dirname13(destination), { recursive: true, mode: 448 });
35806
36789
  writeDurableNew(destination, bytes, lstatSync5(source).mode & 511);
35807
36790
  if (total > MAX_MANAGED_TOTAL_BYTES) fail3("repair_snapshot_too_large", ".kxm", `configuration snapshot exceeds ${MAX_MANAGED_TOTAL_BYTES} bytes`, "parse");
35808
36791
  }
@@ -35810,12 +36793,12 @@ function prepareShadow(projectRoot, operation, options) {
35810
36793
  for (const entry of operation.files) {
35811
36794
  const bytes = readTarget(projectRoot, entry.path);
35812
36795
  if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
35813
- const destination = join24(shadow, ...entry.path.split("/"));
35814
- mkdirSync17(dirname12(destination), { recursive: true, mode: 448 });
35815
- const mode = existsSync18(destination) ? lstatSync5(destination).mode & 511 : 420;
36796
+ const destination = join25(shadow, ...entry.path.split("/"));
36797
+ mkdirSync18(dirname13(destination), { recursive: true, mode: 448 });
36798
+ const mode = existsSync19(destination) ? lstatSync5(destination).mode & 511 : 420;
35816
36799
  writeDurable(destination, bytes, mode);
35817
36800
  }
35818
- const project = parseRestrictedYaml2(readFileSync18(join24(shadow, ".kxm", "project.yaml")), ".kxm/project.yaml");
36801
+ const project = parseRestrictedYaml2(readFileSync18(join25(shadow, ".kxm", "project.yaml")), ".kxm/project.yaml");
35819
36802
  const bound = absoluteMemberBindings(projectRoot, project, options.repositoryBindings);
35820
36803
  loadVnextProject(shadow, { ...loaderOptions(options), repositoryBindings: bound });
35821
36804
  const provenance = readProjectAndProvenance(shadow, options.schemasDir)?.provenance;
@@ -35827,7 +36810,7 @@ function createOperation(projectRoot, kind, rendered, files, sourceTemplateRevis
35827
36810
  const root = resolve12(projectRoot);
35828
36811
  const operationWithoutHash = {
35829
36812
  schema: "kxm.init-operation.v1",
35830
- operationId: `op_${randomUUID5().replaceAll("-", "")}`,
36813
+ operationId: `op_${randomUUID6().replaceAll("-", "")}`,
35831
36814
  kind,
35832
36815
  projectRoot: root,
35833
36816
  projectId: rendered.projectId,
@@ -35853,8 +36836,8 @@ function prepareOperation(projectRoot, operation, rendered, options) {
35853
36836
  const transaction = transactionRoot(projectRoot);
35854
36837
  validateOperationRecord(operation, options.schemasDir);
35855
36838
  validateOperationIntent(projectRoot, operation);
35856
- if (existsSync18(transaction)) fail3("init_transaction_exists", TRANSACTION_NAME, "an initialization transaction already exists and must be resumed");
35857
- mkdirSync17(transaction, { mode: 448 });
36839
+ if (existsSync19(transaction)) fail3("init_transaction_exists", TRANSACTION_NAME, "an initialization transaction already exists and must be resumed");
36840
+ mkdirSync18(transaction, { mode: 448 });
35858
36841
  syncDirectory2(projectRoot);
35859
36842
  writeOperation(projectRoot, operation);
35860
36843
  try {
@@ -35879,21 +36862,21 @@ function ensureDestinationParents(projectRoot, path4) {
35879
36862
  const segments = path4.split("/").slice(0, -1);
35880
36863
  let current = boundary;
35881
36864
  for (const segment of segments) {
35882
- current = join24(current, segment);
35883
- if (!existsSync18(current)) mkdirSync17(current, { mode: 448 });
36865
+ current = join25(current, segment);
36866
+ if (!existsSync19(current)) mkdirSync18(current, { mode: 448 });
35884
36867
  const stat = lstatSync5(current);
35885
36868
  if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_destination_parent_invalid", path4, "destination parent must be a regular directory", "path");
35886
36869
  }
35887
36870
  }
35888
36871
  function destinationSha(projectRoot, path4) {
35889
- const file = join24(projectRoot, ...path4.split("/"));
35890
- if (!existsSync18(file)) return void 0;
36872
+ const file = join25(projectRoot, ...path4.split("/"));
36873
+ if (!existsSync19(file)) return void 0;
35891
36874
  return vnextContentSha256(readRegularBounded(file, path4));
35892
36875
  }
35893
36876
  function requireConditionalLinkSupport(directory, operationId, path4) {
35894
- const nonce = randomUUID5();
35895
- const source = join24(directory, `.kxm-repair-${operationId}-${nonce}.link-source`);
35896
- const target = join24(directory, `.kxm-repair-${operationId}-${nonce}.link-target`);
36877
+ const nonce = randomUUID6();
36878
+ const source = join25(directory, `.kxm-repair-${operationId}-${nonce}.link-source`);
36879
+ const target = join25(directory, `.kxm-repair-${operationId}-${nonce}.link-target`);
35897
36880
  try {
35898
36881
  writeDurableNew(source, Buffer.from("kxm-link-probe\n", "utf8"), 384);
35899
36882
  try {
@@ -35908,19 +36891,19 @@ function requireConditionalLinkSupport(directory, operationId, path4) {
35908
36891
  }
35909
36892
  }
35910
36893
  function atomicInstallTarget(projectRoot, operation, entry) {
35911
- const destination = join24(projectRoot, ...entry.path.split("/"));
36894
+ const destination = join25(projectRoot, ...entry.path.split("/"));
35912
36895
  ensureDestinationParents(projectRoot, entry.path);
35913
- const directory = dirname12(destination);
36896
+ const directory = dirname13(destination);
35914
36897
  const temporaryPrefix = `.kxm-repair-${operation.operationId}-`;
35915
36898
  const pathKey = vnextContentSha256(entry.path).slice("sha256:".length, "sha256:".length + 16);
35916
- const displaced = join24(directory, `${temporaryPrefix}${pathKey}.preimage`);
35917
- if (existsSync18(displaced)) {
36899
+ const displaced = join25(directory, `${temporaryPrefix}${pathKey}.preimage`);
36900
+ if (existsSync19(displaced)) {
35918
36901
  const stat = lstatSync5(displaced);
35919
36902
  if (stat.isSymbolicLink() || !stat.isFile()) fail3("repair_preimage_temporary_invalid", entry.path, "stale displaced preimage is not a regular file", "path");
35920
36903
  if (entry.observedSha256 === null || vnextContentSha256(readFileSync18(displaced)) !== entry.observedSha256) {
35921
36904
  fail3("repair_preimage_temporary_mismatch", entry.path, "stale displaced preimage does not match the pinned operation");
35922
36905
  }
35923
- if (!existsSync18(destination)) {
36906
+ if (!existsSync19(destination)) {
35924
36907
  try {
35925
36908
  linkSync(displaced, destination);
35926
36909
  } catch {
@@ -35935,7 +36918,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
35935
36918
  const owned = candidate.name.startsWith(temporaryPrefix) && (candidate.name.endsWith(".tmp") || candidate.name.endsWith(".link-source") || candidate.name.endsWith(".link-target"));
35936
36919
  if (!owned) continue;
35937
36920
  if (!candidate.isFile()) fail3("repair_temporary_invalid", entry.path, "stale operation temporary is not a regular file", "path");
35938
- rmSync5(join24(directory, candidate.name), { force: true });
36921
+ rmSync5(join25(directory, candidate.name), { force: true });
35939
36922
  }
35940
36923
  const current = destinationSha(projectRoot, entry.path);
35941
36924
  if (current === entry.targetSha256) return;
@@ -35945,15 +36928,15 @@ function atomicInstallTarget(projectRoot, operation, entry) {
35945
36928
  requireConditionalLinkSupport(directory, operation.operationId, entry.path);
35946
36929
  const bytes = readTarget(projectRoot, entry.path);
35947
36930
  if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
35948
- const temporary = join24(directory, `${temporaryPrefix}${randomUUID5()}.tmp`);
35949
- const mode = existsSync18(destination) ? lstatSync5(destination).mode & 511 : 420;
36931
+ const temporary = join25(directory, `${temporaryPrefix}${randomUUID6()}.tmp`);
36932
+ const mode = existsSync19(destination) ? lstatSync5(destination).mode & 511 : 420;
35950
36933
  try {
35951
36934
  writeDurableNew(temporary, bytes, mode);
35952
36935
  if (entry.observedSha256 !== null) {
35953
36936
  renameSync6(destination, displaced);
35954
36937
  syncDirectory2(directory);
35955
36938
  if (vnextContentSha256(readFileSync18(displaced)) !== entry.observedSha256) {
35956
- if (!existsSync18(destination)) {
36939
+ if (!existsSync19(destination)) {
35957
36940
  try {
35958
36941
  linkSync(displaced, destination);
35959
36942
  } catch {
@@ -35969,7 +36952,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
35969
36952
  try {
35970
36953
  linkSync(temporary, destination);
35971
36954
  } catch {
35972
- if (!existsSync18(destination) && existsSync18(displaced)) {
36955
+ if (!existsSync19(destination) && existsSync19(displaced)) {
35973
36956
  try {
35974
36957
  linkSync(displaced, destination);
35975
36958
  } catch {
@@ -35991,12 +36974,12 @@ function atomicInstallTarget(projectRoot, operation, entry) {
35991
36974
  if (destinationSha(projectRoot, entry.path) !== entry.targetSha256) fail3("repair_install_verification_failed", entry.path, "installed target hash does not match the pinned operation");
35992
36975
  }
35993
36976
  function installedCreateMatches(projectRoot, operation) {
35994
- const configRoot = join24(projectRoot, ".kxm");
35995
- if (!existsSync18(configRoot)) return false;
36977
+ const configRoot = join25(projectRoot, ".kxm");
36978
+ if (!existsSync19(configRoot)) return false;
35996
36979
  const actual = [];
35997
36980
  const visit2 = (directory) => {
35998
36981
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
35999
- const absolute = join24(directory, entry.name);
36982
+ const absolute = join25(directory, entry.name);
36000
36983
  if (entry.isSymbolicLink()) fail3("create_resume_link", relativeConfigPath(projectRoot, absolute), "created configuration must not contain links", "path");
36001
36984
  if (entry.isDirectory()) visit2(absolute);
36002
36985
  else if (entry.isFile()) actual.push(relativeConfigPath(projectRoot, absolute));
@@ -36012,12 +36995,12 @@ function relativeConfigPath(projectRoot, file) {
36012
36995
  return relative5(resolve12(projectRoot), file).replaceAll("\\", "/");
36013
36996
  }
36014
36997
  function syncTreeDirectories(root) {
36015
- if (!existsSync18(root)) return;
36998
+ if (!existsSync19(root)) return;
36016
36999
  const directories = [];
36017
37000
  const visit2 = (directory) => {
36018
37001
  directories.push(directory);
36019
37002
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
36020
- if (entry.isDirectory()) visit2(join24(directory, entry.name));
37003
+ if (entry.isDirectory()) visit2(join25(directory, entry.name));
36021
37004
  }
36022
37005
  };
36023
37006
  visit2(root);
@@ -36031,7 +37014,7 @@ function rebuildCreateShadow(projectRoot, operation, options) {
36031
37014
  if (stat.isSymbolicLink() || !stat.isDirectory() || !installedCreateMatches(shadow, operation)) {
36032
37015
  fail3("create_shadow_invalid", TRANSACTION_NAME, "reconstructed create shadow does not exactly match pinned target files", "path");
36033
37016
  }
36034
- syncTreeDirectories(join24(shadow, ".kxm"));
37017
+ syncTreeDirectories(join25(shadow, ".kxm"));
36035
37018
  }
36036
37019
  function applyOperation(projectRoot, original, options, resumed) {
36037
37020
  assertNoRegisteredGates(options);
@@ -36041,10 +37024,10 @@ function applyOperation(projectRoot, original, options, resumed) {
36041
37024
  verifyBackups(projectRoot, operation);
36042
37025
  operation = updatePhase(projectRoot, operation, "applying");
36043
37026
  if (operation.kind === "create") {
36044
- const destination = join24(projectRoot, ".kxm");
36045
- if (!existsSync18(destination)) {
37027
+ const destination = join25(projectRoot, ".kxm");
37028
+ if (!existsSync19(destination)) {
36046
37029
  rebuildCreateShadow(projectRoot, operation, effectiveOptions);
36047
- renameSync6(join24(shadowRoot(projectRoot), ".kxm"), destination);
37030
+ renameSync6(join25(shadowRoot(projectRoot), ".kxm"), destination);
36048
37031
  syncDirectory2(projectRoot);
36049
37032
  } else if (!installedCreateMatches(projectRoot, operation)) {
36050
37033
  fail3("create_resume_conflict", ".kxm", "existing project configuration does not match the pinned create operation");
@@ -36071,7 +37054,7 @@ function applyOperation(projectRoot, original, options, resumed) {
36071
37054
  operation = updatePhase(projectRoot, operation, "verified");
36072
37055
  if (options.testFaultAt === "verified") throw new Error("injected init fault after verification");
36073
37056
  const files = operation.files.map((entry) => entry.path);
36074
- syncTreeDirectories(join24(projectRoot, ".kxm"));
37057
+ syncTreeDirectories(join25(projectRoot, ".kxm"));
36075
37058
  return { kind: operation.kind, resumed, bundle, files };
36076
37059
  }
36077
37060
  function prepareAndApplyVnextCreate(projectRoot, rendered, options = {}) {
@@ -36186,7 +37169,7 @@ function finishPreparingOperation(projectRoot, operation, options) {
36186
37169
  const rendered = rendererForOperation(operation);
36187
37170
  if (!rendered) fail3("init_transaction_renderer_unavailable", TRANSACTION_NAME, "this binary cannot reproduce the pinned pre-application target");
36188
37171
  for (const directory of ["targets", "backups", "shadow"]) {
36189
- rmSync5(join24(transactionRoot(projectRoot), directory), { recursive: true, force: true });
37172
+ rmSync5(join25(transactionRoot(projectRoot), directory), { recursive: true, force: true });
36190
37173
  }
36191
37174
  for (const entry of operation.files) {
36192
37175
  const bytes = rendered.files.get(entry.path);
@@ -36205,7 +37188,7 @@ function resumeVnextInitTransaction(projectRoot, options = {}) {
36205
37188
  let operation = readOperation(projectRoot, options.schemasDir);
36206
37189
  if (!operation) {
36207
37190
  const root = transactionRoot(projectRoot);
36208
- if (existsSync18(root)) {
37191
+ if (existsSync19(root)) {
36209
37192
  rmSync5(root, { recursive: true, force: true });
36210
37193
  syncDirectory2(projectRoot);
36211
37194
  }
@@ -36221,7 +37204,7 @@ function commitVnextInitTransaction(projectRoot, schemasDir) {
36221
37204
  fail3("init_transaction_not_verified", TRANSACTION_NAME, "initialization transaction cannot commit before installed state is verified");
36222
37205
  }
36223
37206
  const transaction = transactionRoot(projectRoot);
36224
- const retired = join24(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID5()}`);
37207
+ const retired = join25(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID6()}`);
36225
37208
  renameSync6(transaction, retired);
36226
37209
  syncDirectory2(projectRoot);
36227
37210
  rmSync5(retired, { recursive: true, force: true });
@@ -36249,7 +37232,7 @@ function normalizedProjectName(root, requested) {
36249
37232
  return name;
36250
37233
  }
36251
37234
  function generatedProjectId(requested) {
36252
- const id = requested?.trim() || `prj_${randomUUID6().replaceAll("-", "")}`;
37235
+ const id = requested?.trim() || `prj_${randomUUID7().replaceAll("-", "")}`;
36253
37236
  if (!PROJECT_ID.test(id) || id.length > 144) {
36254
37237
  throw new VnextConfigError([initIssue("project_id_invalid", ".kxm/project.yaml", "project ID must satisfy the kxm.project.v1 opaque ID grammar and use the prj_ prefix")]);
36255
37238
  }
@@ -36330,7 +37313,7 @@ function validateBindingIdentities(bundle, persisted, explicit) {
36330
37313
  }
36331
37314
  function initializeVnextProjectAtGitRoot(start, gitRoot, options, mutationLock) {
36332
37315
  const storeOptions = bindingStoreOptions(options);
36333
- const persisted = existsSync19(join25(gitRoot, ".kxm", "project.yaml")) ? readVnextLocalBindings(gitRoot, storeOptions) : void 0;
37316
+ const persisted = existsSync20(join26(gitRoot, ".kxm", "project.yaml")) ? readVnextLocalBindings(gitRoot, storeOptions) : void 0;
36334
37317
  const repositoryBindings = Object.fromEntries([
36335
37318
  ...Object.entries(persisted?.repositories ?? {}),
36336
37319
  ...Object.entries(options.repositoryBindings ?? {})
@@ -36519,7 +37502,7 @@ function initializeVnextProjectAtGitRoot(start, gitRoot, options, mutationLock)
36519
37502
  const files = [...rendered.files.keys()];
36520
37503
  if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, files };
36521
37504
  if (!mutationLock) throw new Error("project mutation lock is required to create configuration");
36522
- if (existsSync19(join25(gitRoot, ".kxm"))) {
37505
+ if (existsSync20(join26(gitRoot, ".kxm"))) {
36523
37506
  throw new VnextConfigError([initIssue("workspace_changed", ".kxm", "workspace changed after planning; existing .kxm state was not overwritten")]);
36524
37507
  }
36525
37508
  const created = prepareAndApplyVnextCreate(gitRoot, rendered, transactionOptions);
@@ -36549,10 +37532,10 @@ function initializeVnextProject(start = process.cwd(), options = {}) {
36549
37532
  }
36550
37533
 
36551
37534
  // plugins/kxm/src/vnext-migrate.ts
36552
- var import_yaml9 = __toESM(require_dist(), 1);
36553
- import { createHash as createHash10 } from "node:crypto";
36554
- import { chmodSync as chmodSync4, closeSync as closeSync3, existsSync as existsSync20, fsyncSync as fsyncSync3, lstatSync as lstatSync6, mkdirSync as mkdirSync18, openSync as openSync3, readFileSync as readFileSync19, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
36555
- import { join as join26 } from "node:path";
37535
+ var import_yaml10 = __toESM(require_dist(), 1);
37536
+ import { createHash as createHash11 } from "node:crypto";
37537
+ import { chmodSync as chmodSync4, closeSync as closeSync3, existsSync as existsSync21, fsyncSync as fsyncSync3, lstatSync as lstatSync6, mkdirSync as mkdirSync19, openSync as openSync3, readFileSync as readFileSync19, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync17 } from "node:fs";
37538
+ import { join as join27 } from "node:path";
36556
37539
  var MAX_LEGACY_JSON_BYTES = 1024 * 1024;
36557
37540
  var MAX_LEGACY_JSON_DEPTH = 32;
36558
37541
  var MAX_LEGACY_JSON_NODES = 32768;
@@ -36713,7 +37696,7 @@ function detectDuplicateKeys(text, label) {
36713
37696
  if (index !== length) migrateFail("invalid_json", label, `trailing content at byte ${index}`);
36714
37697
  }
36715
37698
  function sha256Of(input) {
36716
- return `sha256:${createHash10("sha256").update(input).digest("hex")}`;
37699
+ return `sha256:${createHash11("sha256").update(input).digest("hex")}`;
36717
37700
  }
36718
37701
  function readVnextLegacySources(root) {
36719
37702
  const files = legacyConfigFilesAt(root);
@@ -36721,7 +37704,7 @@ function readVnextLegacySources(root) {
36721
37704
  throw new VnextConfigError([migrateIssue("discovery", "legacy_source_limit", ".kxm/config", `legacy configuration exceeds ${MAX_LEGACY_SOURCES} files`)]);
36722
37705
  }
36723
37706
  return files.map((path4) => {
36724
- const absolute = join26(root, ...path4.split("/"));
37707
+ const absolute = join27(root, ...path4.split("/"));
36725
37708
  const stat = lstatSync6(absolute);
36726
37709
  if (stat.isSymbolicLink() || !stat.isFile()) {
36727
37710
  throw new VnextConfigError([migrateIssue("path", "legacy_source_invalid", path4, "legacy configuration must be a regular file, not a link")]);
@@ -36785,7 +37768,7 @@ function normalizeIdentifier(raw) {
36785
37768
  function pointerEscape2(segment) {
36786
37769
  const escaped = segment.replace(/~/g, "~0").replace(/\//g, "~1");
36787
37770
  if (escaped.length <= 200) return escaped;
36788
- const digest = createHash10("sha256").update(segment, "utf8").digest("hex").slice(0, 8);
37771
+ const digest = createHash11("sha256").update(segment, "utf8").digest("hex").slice(0, 8);
36789
37772
  return `${escaped.slice(0, 180)}~digest-${digest}`;
36790
37773
  }
36791
37774
  function elide(raw, max = 120) {
@@ -36794,7 +37777,7 @@ function elide(raw, max = 120) {
36794
37777
  function keyFragment(raw) {
36795
37778
  const full = normalizeIdentifier(raw) || "unnamed";
36796
37779
  const normalized = full.length > 96 ? `${full.slice(0, 96)}-${full.length}` : full;
36797
- const digest = createHash10("sha256").update(raw, "utf8").digest("hex").slice(0, 8);
37780
+ const digest = createHash11("sha256").update(raw, "utf8").digest("hex").slice(0, 8);
36798
37781
  return `${normalized}-${digest}`;
36799
37782
  }
36800
37783
  function recordUnmapped(plan, sourcePath, pointer, value, sensitive) {
@@ -37201,7 +38184,7 @@ function convertWorkflowFile(plan, source, decisions, agentIds, agentRawNames, p
37201
38184
  const rawId = asString(definition.id) ?? `${fileStem}-${definitionIndex}`;
37202
38185
  const workflowIdentity = mapIdentity(plan, source.path, rawId, "workflow", workflowNames, decisions, basePointer || "/", definition);
37203
38186
  if (workflowIdentity.status === "dropped") continue;
37204
- const workflowId = workflowIdentity.status === "adopted" ? workflowIdentity.id : `preview-${(normalizeIdentifier(rawId) || "unnamed").slice(0, 46).replace(/[-_]+$/u, "")}-${createHash10("sha256").update(`${rawId}#${source.path}#${definitionIndex}`, "utf8").digest("hex").slice(0, 8)}`;
38187
+ const workflowId = workflowIdentity.status === "adopted" ? workflowIdentity.id : `preview-${(normalizeIdentifier(rawId) || "unnamed").slice(0, 46).replace(/[-_]+$/u, "")}-${createHash11("sha256").update(`${rawId}#${source.path}#${definitionIndex}`, "utf8").digest("hex").slice(0, 8)}`;
37205
38188
  const workflowKey = keyFragment(rawId);
37206
38189
  for (const field of ["source", "project", "event", "filter", "delivery", "ttlMs", "promptTemplate"]) {
37207
38190
  if (definition[field] !== void 0) recordUnmapped(plan, source.path, `${basePointer}/${field}`, definition[field], false);
@@ -37311,7 +38294,7 @@ function convertWorkflowFile(plan, source, decisions, agentIds, agentRawNames, p
37311
38294
  droppedStageIndexes.add(stageIndex);
37312
38295
  continue;
37313
38296
  }
37314
- const stepId = stepIdentity.status === "adopted" ? stepIdentity.id : `preview-${(normalizeIdentifier(rawStageId) || "unnamed").slice(0, 46).replace(/[-_]+$/u, "")}-${createHash10("sha256").update(`${rawStageId}#${stageIndex}`, "utf8").digest("hex").slice(0, 8)}`;
38297
+ const stepId = stepIdentity.status === "adopted" ? stepIdentity.id : `preview-${(normalizeIdentifier(rawStageId) || "unnamed").slice(0, 46).replace(/[-_]+$/u, "")}-${createHash11("sha256").update(`${rawStageId}#${stageIndex}`, "utf8").digest("hex").slice(0, 8)}`;
37315
38298
  const stepKey = keyFragment(rawStageId);
37316
38299
  stageKeyByRaw.set(rawStageId, stepKey);
37317
38300
  if (!asString(stage.instructions)) {
@@ -37770,7 +38753,7 @@ function planVnextMigration(projectRoot, options = {}) {
37770
38753
  sourceDigest: legacySourceDigest(sources),
37771
38754
  sources: sources.map((source) => ({ path: source.path, sha256: source.sha256, bytes: source.bytes })),
37772
38755
  resources: [...resources.keys()].sort(compareCodeUnits5).map((path4) => {
37773
- const bytes = Buffer.from((0, import_yaml9.stringify)(resources.get(path4), { lineWidth: 0 }), "utf8");
38756
+ const bytes = Buffer.from((0, import_yaml10.stringify)(resources.get(path4), { lineWidth: 0 }), "utf8");
37774
38757
  return { path: path4, sha256: sha256Of(bytes), bytes: bytes.byteLength };
37775
38758
  }),
37776
38759
  ambiguities: [...plan.ambiguities].sort((left, right) => compareCodeUnits5(left.key, right.key)).map((ambiguity) => ({
@@ -37799,7 +38782,7 @@ function planVnextMigration(projectRoot, options = {}) {
37799
38782
  };
37800
38783
  }
37801
38784
  function readVnextMigrationDecisions(path4, schemasDir) {
37802
- if (!existsSync20(path4)) {
38785
+ if (!existsSync21(path4)) {
37803
38786
  throw new VnextConfigError([migrateIssue("discovery", "decisions_missing", path4, "migration decisions file does not exist")]);
37804
38787
  }
37805
38788
  const registry = new VnextSchemaRegistry(schemasDir);
@@ -37826,13 +38809,13 @@ function syncDirectory3(path4) {
37826
38809
  }
37827
38810
  }
37828
38811
  function writeDurable2(path4, content) {
37829
- const temp = `${path4}.kxm-migration-tmp-${process.pid}-${createHash10("sha256").update(`${path4}${Date.now()}${Math.random()}`, "utf8").digest("hex").slice(0, 12)}`;
38812
+ const temp = `${path4}.kxm-migration-tmp-${process.pid}-${createHash11("sha256").update(`${path4}${Date.now()}${Math.random()}`, "utf8").digest("hex").slice(0, 12)}`;
37830
38813
  let created = false;
37831
38814
  try {
37832
38815
  const descriptor = openSync3(temp, "wx", 384);
37833
38816
  created = true;
37834
38817
  try {
37835
- writeFileSync16(descriptor, content);
38818
+ writeFileSync17(descriptor, content);
37836
38819
  fsyncSync3(descriptor);
37837
38820
  } finally {
37838
38821
  closeSync3(descriptor);
@@ -37842,9 +38825,9 @@ function writeDurable2(path4, content) {
37842
38825
  chmodSync4(path4, 384);
37843
38826
  } catch {
37844
38827
  }
37845
- syncDirectory3(join26(path4, ".."));
38828
+ syncDirectory3(join27(path4, ".."));
37846
38829
  } finally {
37847
- if (created && existsSync20(temp)) rmSync6(temp, { force: true });
38830
+ if (created && existsSync21(temp)) rmSync6(temp, { force: true });
37848
38831
  }
37849
38832
  }
37850
38833
  function applyVnextMigration(projectRoot, options = {}) {
@@ -37854,8 +38837,8 @@ function applyVnextMigration(projectRoot, options = {}) {
37854
38837
  ...options.schemasDir === void 0 ? {} : { schemasDir: options.schemasDir }
37855
38838
  };
37856
38839
  const execute = (lock) => {
37857
- const receiptAbsolute = join26(root, ...VNEXT_MIGRATION_RECEIPT_PATH.split("/"));
37858
- if (existsSync20(receiptAbsolute)) {
38840
+ const receiptAbsolute = join27(root, ...VNEXT_MIGRATION_RECEIPT_PATH.split("/"));
38841
+ if (existsSync21(receiptAbsolute)) {
37859
38842
  const stat = lstatSync6(receiptAbsolute);
37860
38843
  if (stat.isSymbolicLink() || !stat.isFile()) {
37861
38844
  throw new VnextConfigError([migrateIssue("path", "migration_receipt_invalid", VNEXT_MIGRATION_RECEIPT_PATH, "migration receipt must be a regular file, not a link")]);
@@ -37907,8 +38890,8 @@ function applyVnextMigration(projectRoot, options = {}) {
37907
38890
  if (first.ambiguities.length > 0) {
37908
38891
  return { action: "planned", projectRoot: root, files: [], plan: first.plan };
37909
38892
  }
37910
- const files = [...first.resources.entries()].sort(([left], [right]) => compareCodeUnits5(left, right)).map(([path4, value]) => ({ path: path4, bytes: Buffer.from((0, import_yaml9.stringify)(value, { lineWidth: 0 }), "utf8") }));
37911
- const collisions = files.filter((file) => existsSync20(join26(root, ...file.path.split("/"))));
38893
+ const files = [...first.resources.entries()].sort(([left], [right]) => compareCodeUnits5(left, right)).map(([path4, value]) => ({ path: path4, bytes: Buffer.from((0, import_yaml10.stringify)(value, { lineWidth: 0 }), "utf8") }));
38894
+ const collisions = files.filter((file) => existsSync21(join27(root, ...file.path.split("/"))));
37912
38895
  if (collisions.length > 0) {
37913
38896
  throw new VnextConfigError(collisions.map((file) => migrateIssue("semantic", "migration_target_exists", file.path, "target resource already exists; refusing to overwrite")));
37914
38897
  }
@@ -37917,8 +38900,8 @@ function applyVnextMigration(projectRoot, options = {}) {
37917
38900
  const segments = file.path.split("/");
37918
38901
  let current = root;
37919
38902
  for (const segment of segments.slice(0, -1)) {
37920
- current = join26(current, segment);
37921
- if (!existsSync20(current)) continue;
38903
+ current = join27(current, segment);
38904
+ if (!existsSync21(current)) continue;
37922
38905
  const stat = lstatSync6(current);
37923
38906
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
37924
38907
  parentIssues.push(migrateIssue("path", "migration_target_parent_invalid", file.path, `migration target parent ${segment} must be a regular directory, not a link`));
@@ -37934,8 +38917,8 @@ function applyVnextMigration(projectRoot, options = {}) {
37934
38917
  const written = [];
37935
38918
  try {
37936
38919
  for (const file of files) {
37937
- const absolute = join26(root, ...file.path.split("/"));
37938
- mkdirSync18(join26(absolute, ".."), { recursive: true });
38920
+ const absolute = join27(root, ...file.path.split("/"));
38921
+ mkdirSync19(join27(absolute, ".."), { recursive: true });
37939
38922
  written.push(absolute);
37940
38923
  writeDurable2(absolute, file.bytes);
37941
38924
  }
@@ -37956,7 +38939,7 @@ ${bundle.configRevision}`).slice(7, 39)}`,
37956
38939
  const receiptIssues = registry.validateMigrationReceipt(receipt, VNEXT_MIGRATION_RECEIPT_PATH);
37957
38940
  if (receiptIssues.length > 0) throw new VnextConfigError(receiptIssues);
37958
38941
  written.push(receiptAbsolute);
37959
- writeDurable2(receiptAbsolute, Buffer.from((0, import_yaml9.stringify)(receipt, { lineWidth: 0 }), "utf8"));
38942
+ writeDurable2(receiptAbsolute, Buffer.from((0, import_yaml10.stringify)(receipt, { lineWidth: 0 }), "utf8"));
37960
38943
  const verified = loadVnextProject(root, options);
37961
38944
  return {
37962
38945
  action: "applied",
@@ -37981,8 +38964,8 @@ ${bundle.configRevision}`).slice(7, 39)}`,
37981
38964
  }
37982
38965
  function verifyVnextMigration(projectRoot, options = {}) {
37983
38966
  const root = migrationGitRoot(projectRoot);
37984
- const receiptAbsolute = join26(root, ...VNEXT_MIGRATION_RECEIPT_PATH.split("/"));
37985
- if (!existsSync20(receiptAbsolute)) {
38967
+ const receiptAbsolute = join27(root, ...VNEXT_MIGRATION_RECEIPT_PATH.split("/"));
38968
+ if (!existsSync21(receiptAbsolute)) {
37986
38969
  return {
37987
38970
  ok: false,
37988
38971
  projectRoot: root,
@@ -38030,23 +39013,23 @@ function verifyVnextMigration(projectRoot, options = {}) {
38030
39013
 
38031
39014
  // plugins/kxm/src/vnext-runtime-supervisor.ts
38032
39015
  import { spawn } from "node:child_process";
38033
- import { createHash as createHash12, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
38034
- import { chmodSync as chmodSync5, existsSync as existsSync22, lstatSync as lstatSync8, mkdirSync as mkdirSync20, readFileSync as readFileSync21, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "node:fs";
38035
- import { dirname as dirname14, isAbsolute as isAbsolute7, join as join28, resolve as resolve15 } from "node:path";
39016
+ import { createHash as createHash13, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
39017
+ import { chmodSync as chmodSync5, existsSync as existsSync23, lstatSync as lstatSync8, mkdirSync as mkdirSync21, readFileSync as readFileSync21, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync19 } from "node:fs";
39018
+ import { dirname as dirname15, isAbsolute as isAbsolute7, join as join29, resolve as resolve15 } from "node:path";
38036
39019
  import { fileURLToPath as fileURLToPath3 } from "node:url";
38037
39020
 
38038
39021
  // plugins/kxm/src/vnext-runtime-store.ts
38039
- import { createHash as createHash11, randomUUID as randomUUID7 } from "node:crypto";
38040
- import { existsSync as existsSync21, lstatSync as lstatSync7, mkdirSync as mkdirSync19, readFileSync as readFileSync20, realpathSync as realpathSync4, writeFileSync as writeFileSync17 } from "node:fs";
38041
- import { dirname as dirname13, join as join27, resolve as resolve14 } from "node:path";
39022
+ import { createHash as createHash12, randomUUID as randomUUID8 } from "node:crypto";
39023
+ import { existsSync as existsSync22, lstatSync as lstatSync7, mkdirSync as mkdirSync20, readFileSync as readFileSync20, realpathSync as realpathSync4, writeFileSync as writeFileSync18 } from "node:fs";
39024
+ import { dirname as dirname14, join as join28, resolve as resolve14 } from "node:path";
38042
39025
  function vnextRuntimePaths(options = {}) {
38043
39026
  const stateRoot = options.stateRoot ? resolve14(options.stateRoot) : vnextUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
38044
- const runtimeDir = join27(stateRoot, "runtime");
39027
+ const runtimeDir = join28(stateRoot, "runtime");
38045
39028
  return {
38046
39029
  stateRoot,
38047
39030
  runtimeDir,
38048
- registryDb: join27(runtimeDir, "registry.db"),
38049
- projectsDir: join27(runtimeDir, "projects")
39031
+ registryDb: join28(runtimeDir, "registry.db"),
39032
+ projectsDir: join28(runtimeDir, "projects")
38050
39033
  };
38051
39034
  }
38052
39035
  function runtimeIssue(phase, code, file, message) {
@@ -38063,7 +39046,7 @@ function projectRuntimeKey(projectRoot) {
38063
39046
  canonical2 = resolve14(projectRoot);
38064
39047
  }
38065
39048
  const folded = process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
38066
- return createHash11("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
39049
+ return createHash12("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
38067
39050
  }
38068
39051
  var VNEXT_REGISTRY_SCHEMA_VERSION = 1;
38069
39052
  var REGISTRY_TABLES = {
@@ -38405,7 +39388,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
38405
39388
  const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
38406
39389
  const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
38407
39390
  const assignments = compileAssignments(step, id, agent, sink);
38408
- const join35 = compileJoin(step, id, sink);
39391
+ const join37 = compileJoin(step, id, sink);
38409
39392
  const requiredEvidence = compileEvidence(step, id, sink);
38410
39393
  const transitions = compileTransitions(step, id, index, stepIndex, sink);
38411
39394
  const outcomes = Object.keys(transitions).sort(compareCodeUnits6);
@@ -38433,7 +39416,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
38433
39416
  transitions: orderedTransitions,
38434
39417
  requiresPlanHash: requirePlanHash.includes(id),
38435
39418
  assignments,
38436
- join: join35
39419
+ join: join37
38437
39420
  };
38438
39421
  if (kind === "agent" || kind === "moa") {
38439
39422
  if (!agent) return void 0;
@@ -38483,15 +39466,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
38483
39466
  };
38484
39467
  }
38485
39468
  function compileJoin(step, stepId, sink) {
38486
- const join35 = objectValue2(step.join);
38487
- if (!join35) return { strategy: "all" };
38488
- const declared = stringValue3(join35.strategy);
39469
+ const join37 = objectValue2(step.join);
39470
+ if (!join37) return { strategy: "all" };
39471
+ const declared = stringValue3(join37.strategy);
38489
39472
  const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
38490
- const minimumPassed = compileOptionalCount(join35.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
39473
+ const minimumPassed = compileOptionalCount(join37.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
38491
39474
  const compiled = {
38492
39475
  strategy,
38493
39476
  ...minimumPassed !== void 0 ? { minimumPassed } : {},
38494
- ...typeof join35.cancelRemaining === "boolean" ? { cancelRemaining: join35.cancelRemaining } : {}
39477
+ ...typeof join37.cancelRemaining === "boolean" ? { cancelRemaining: join37.cancelRemaining } : {}
38495
39478
  };
38496
39479
  return compiled;
38497
39480
  }
@@ -38768,12 +39751,27 @@ var TEXT_LIMIT = 4 * 1024 * 1024;
38768
39751
  var RECORD_LIMIT = 16 * 1024 * 1024;
38769
39752
 
38770
39753
  // plugins/kxm/src/vnext-engine.ts
38771
- var import_yaml10 = __toESM(require_dist(), 1);
39754
+ var import_yaml11 = __toESM(require_dist(), 1);
39755
+
39756
+ // plugins/kxm/src/safety-integrity.ts
39757
+ var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
39758
+ /^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
39759
+ /^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
39760
+ /^\s*rm\s+.*(-[a-zA-Z]*r[a-zA-Z]*\s+.*-[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*\s+.*-[a-zA-Z]*r[a-zA-Z]*)/i,
39761
+ /^\s*git\s+reset\s+--hard/i,
39762
+ /^\s*git\s+clean\s+-[a-zA-Z]*f/i,
39763
+ /^\s*git\s+checkout\s+--\s+/i,
39764
+ /^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
39765
+ ]);
39766
+ var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
39767
+ /StrictHostKeyChecking=(accept-new|no|off)/i,
39768
+ /UserKnownHostsFile=\/dev\/null/i
39769
+ ]);
38772
39770
 
38773
39771
  // plugins/kxm/src/vnext-runtime-supervisor.ts
38774
39772
  var repoRoot = resolve15(fileURLToPath3(new URL("../../../", import.meta.url)));
38775
39773
  function vnextSupervisorTokenFile(paths) {
38776
- return join28(paths.runtimeDir, "supervisor.token");
39774
+ return join29(paths.runtimeDir, "supervisor.token");
38777
39775
  }
38778
39776
  function readVnextSupervisorToken(paths) {
38779
39777
  const file = vnextSupervisorTokenFile(paths);
@@ -38796,11 +39794,11 @@ function processAlive(pid) {
38796
39794
  var HEARTBEAT_STALE_MS = 15e3;
38797
39795
  var SUPERVISOR_ERROR_MAX_AGE_MS = 3e4;
38798
39796
  function supervisorErrorFile(paths) {
38799
- return join28(paths.runtimeDir, "supervisor.error");
39797
+ return join29(paths.runtimeDir, "supervisor.error");
38800
39798
  }
38801
39799
  function clearSupervisorError(paths) {
38802
39800
  const file = supervisorErrorFile(paths);
38803
- if (existsSync22(file)) rmSync7(file, { force: true });
39801
+ if (existsSync23(file)) rmSync7(file, { force: true });
38804
39802
  }
38805
39803
  function readRecentSupervisorError(paths) {
38806
39804
  const file = supervisorErrorFile(paths);
@@ -38815,7 +39813,7 @@ function readRecentSupervisorError(paths) {
38815
39813
  }
38816
39814
  }
38817
39815
  function vnextSupervisorStatus(paths) {
38818
- if (!existsSync22(paths.registryDb)) return { running: false };
39816
+ if (!existsSync23(paths.registryDb)) return { running: false };
38819
39817
  const registry = new VnextRuntimeRegistry(paths.registryDb);
38820
39818
  try {
38821
39819
  const record = registry.supervisor();
@@ -38876,7 +39874,7 @@ async function ensureVnextSupervisor(options = {}) {
38876
39874
  throw runtimeError("runtime_supervisor_unreachable", paths.registryDb, `runtime supervisor pid ${status.pid} is registered as running but cannot be probed`);
38877
39875
  }
38878
39876
  clearSupervisorError(paths);
38879
- const scriptPath = join28(repoRoot, "scripts", "kxm-runtime-supervisor.mjs");
39877
+ const scriptPath = join29(repoRoot, "scripts", "kxm-runtime-supervisor.mjs");
38880
39878
  const spawnImpl = options.spawnImpl ?? ((script, env) => {
38881
39879
  const child = spawn(process.execPath, [script], {
38882
39880
  detached: true,
@@ -38928,7 +39926,7 @@ async function vnextRuntimeRequest(handle, method, path4, body) {
38928
39926
  }
38929
39927
 
38930
39928
  // plugins/kxm/src/client.ts
38931
- import { createHash as createHash13 } from "node:crypto";
39929
+ import { createHash as createHash14 } from "node:crypto";
38932
39930
  var MeshWaitError = class extends Error {
38933
39931
  waitStatus;
38934
39932
  constructor(waitStatus, messageId) {
@@ -38965,7 +39963,7 @@ function fanoutIdempotencyKey(prefix, target, correlationId, workflowContext) {
38965
39963
  correlationId: correlationId ?? null,
38966
39964
  target: target.toLowerCase()
38967
39965
  });
38968
- return `fanout:${createHash13("sha256").update(scope).digest("hex")}`;
39966
+ return `fanout:${createHash14("sha256").update(scope).digest("hex")}`;
38969
39967
  }
38970
39968
  var HubHttpError = class extends Error {
38971
39969
  statusCode;
@@ -39183,7 +40181,7 @@ var HubClient = class {
39183
40181
  if (signal?.aborted) throw new MeshWaitError("aborted", messageId);
39184
40182
  const message = await this.getMessage(messageId);
39185
40183
  if (["replied", "cancelled", "expired", "error"].includes(message.status)) return message;
39186
- await new Promise((resolve22, reject) => {
40184
+ await new Promise((resolve23, reject) => {
39187
40185
  const onAbort = () => {
39188
40186
  signal?.removeEventListener("abort", onAbort);
39189
40187
  clearTimeout(timer);
@@ -39191,7 +40189,7 @@ var HubClient = class {
39191
40189
  };
39192
40190
  const timer = setTimeout(() => {
39193
40191
  signal?.removeEventListener("abort", onAbort);
39194
- resolve22();
40192
+ resolve23();
39195
40193
  }, Math.min(500, Math.max(1, deadline - Date.now())));
39196
40194
  signal?.addEventListener("abort", onAbort, { once: true });
39197
40195
  if (signal?.aborted) onAbort();
@@ -39246,7 +40244,7 @@ var HubClient = class {
39246
40244
  } catch (error) {
39247
40245
  if (this.stopped || error instanceof Error && error.name === "AbortError") return;
39248
40246
  }
39249
- if (!this.stopped) await new Promise((resolve22) => setTimeout(resolve22, this.options.reconnectMs));
40247
+ if (!this.stopped) await new Promise((resolve23) => setTimeout(resolve23, this.options.reconnectMs));
39250
40248
  }
39251
40249
  }
39252
40250
  headers(includeIdentity = true) {
@@ -39328,15 +40326,15 @@ var HubClient = class {
39328
40326
  };
39329
40327
 
39330
40328
  // plugins/kxm/src/commands.ts
39331
- import { createHash as createHash14, randomUUID as randomUUID8, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
39332
- import { chmodSync as chmodSync6, existsSync as existsSync23, mkdirSync as mkdirSync21, readFileSync as readFileSync22, unlinkSync as unlinkSync2, writeFileSync as writeFileSync19 } from "node:fs";
40329
+ import { createHash as createHash15, randomUUID as randomUUID9, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
40330
+ import { chmodSync as chmodSync6, existsSync as existsSync24, mkdirSync as mkdirSync22, readFileSync as readFileSync22, unlinkSync as unlinkSync2, writeFileSync as writeFileSync20 } from "node:fs";
39333
40331
  import { homedir as homedir4 } from "node:os";
39334
- import { dirname as dirname15, join as join29, resolve as resolve16 } from "node:path";
40332
+ import { dirname as dirname16, join as join30, resolve as resolve16 } from "node:path";
39335
40333
  function requiredString(value, name) {
39336
40334
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
39337
40335
  return value.trim();
39338
40336
  }
39339
- function optionalString(value) {
40337
+ function optionalString2(value) {
39340
40338
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
39341
40339
  }
39342
40340
  function asRecord2(value) {
@@ -39387,7 +40385,7 @@ async function reconcileInbox(client, inbox, notifiedInbox) {
39387
40385
  );
39388
40386
  }
39389
40387
  function resolveProject(client, projectArg) {
39390
- const proj = optionalString(projectArg) ?? client.agent?.project;
40388
+ const proj = optionalString2(projectArg) ?? client.agent?.project;
39391
40389
  if (!proj) {
39392
40390
  throw new Error('missing required parameter "project"');
39393
40391
  }
@@ -39452,9 +40450,9 @@ var AGENT_COMMANDS = [
39452
40450
  additionalProperties: false
39453
40451
  },
39454
40452
  async execute(client, args) {
39455
- const delivery = optionalString(args.delivery);
39456
- const correlationId = optionalString(args.correlationId);
39457
- const idempotencyKey = optionalString(args.idempotencyKey);
40453
+ const delivery = optionalString2(args.delivery);
40454
+ const correlationId = optionalString2(args.correlationId);
40455
+ const idempotencyKey = optionalString2(args.idempotencyKey);
39458
40456
  const workflowContext = optionalWorkflowContext(args.workflowContext);
39459
40457
  const message = await client.send({
39460
40458
  target: requiredString(args.target, "target"),
@@ -39535,8 +40533,8 @@ var AGENT_COMMANDS = [
39535
40533
  responses: await client.fanout({
39536
40534
  targets,
39537
40535
  content: requiredString(args.content, "content"),
39538
- ...optionalString(args.correlationId) ? { correlationId: optionalString(args.correlationId) } : {},
39539
- ...optionalString(args.idempotencyKeyPrefix) ? { idempotencyKeyPrefix: optionalString(args.idempotencyKeyPrefix) } : {},
40536
+ ...optionalString2(args.correlationId) ? { correlationId: optionalString2(args.correlationId) } : {},
40537
+ ...optionalString2(args.idempotencyKeyPrefix) ? { idempotencyKeyPrefix: optionalString2(args.idempotencyKeyPrefix) } : {},
39540
40538
  ...optionalWorkflowContext(args.workflowContext) ? { workflowContext: optionalWorkflowContext(args.workflowContext) } : {},
39541
40539
  ...typeof args.ttlMs === "number" ? { ttlMs: args.ttlMs } : {},
39542
40540
  ...typeof args.timeoutMs === "number" ? { timeoutMs: args.timeoutMs } : {},
@@ -39785,9 +40783,9 @@ var AGENT_COMMANDS = [
39785
40783
  return await client.recordWorkflowEntry(requiredString(args.runId, "runId"), {
39786
40784
  category: requiredString(args.category, "category"),
39787
40785
  area: requiredString(args.area, "area"),
39788
- ...optionalString(args.severity) ? { severity: optionalString(args.severity) } : {},
40786
+ ...optionalString2(args.severity) ? { severity: optionalString2(args.severity) } : {},
39789
40787
  summary: requiredString(args.summary, "summary"),
39790
- ...optionalString(args.details) ? { details: optionalString(args.details) } : {},
40788
+ ...optionalString2(args.details) ? { details: optionalString2(args.details) } : {},
39791
40789
  ...Array.isArray(args.evidence) ? { evidence: args.evidence } : {},
39792
40790
  ...Array.isArray(args.relatedEntryIds) ? { relatedEntryIds: args.relatedEntryIds } : {}
39793
40791
  });
@@ -39903,8 +40901,8 @@ var AGENT_COMMANDS = [
39903
40901
  project: resolveProject(client, args.project),
39904
40902
  role: requiredString(args.role, "role"),
39905
40903
  task: requiredString(args.task, "task"),
39906
- ...optionalString(args.workflowRunId) ? { workflowRunId: optionalString(args.workflowRunId) } : {},
39907
- ...optionalString(args.stageId) ? { stageId: optionalString(args.stageId) } : {},
40904
+ ...optionalString2(args.workflowRunId) ? { workflowRunId: optionalString2(args.workflowRunId) } : {},
40905
+ ...optionalString2(args.stageId) ? { stageId: optionalString2(args.stageId) } : {},
39908
40906
  ...typeof args.budgetTokens === "number" ? { budgetTokens: args.budgetTokens } : {},
39909
40907
  ...Array.isArray(args.includeKinds) ? { includeKinds: args.includeKinds } : {}
39910
40908
  });
@@ -39929,7 +40927,7 @@ var AGENT_COMMANDS = [
39929
40927
  async execute(client, args) {
39930
40928
  return await client.contextRecall({
39931
40929
  project: resolveProject(client, args.project),
39932
- ...optionalString(args.query) ? { query: optionalString(args.query) } : {},
40930
+ ...optionalString2(args.query) ? { query: optionalString2(args.query) } : {},
39933
40931
  ...Array.isArray(args.kinds) ? { kinds: args.kinds } : {},
39934
40932
  ...typeof args.limit === "number" ? { limit: args.limit } : {}
39935
40933
  });
@@ -39955,7 +40953,7 @@ var AGENT_COMMANDS = [
39955
40953
  return await client.contextState({
39956
40954
  project: resolveProject(client, args.project),
39957
40955
  key: requiredString(args.key, "key"),
39958
- ...optionalString(args.asOf) ? { asOf: optionalString(args.asOf) } : {}
40956
+ ...optionalString2(args.asOf) ? { asOf: optionalString2(args.asOf) } : {}
39959
40957
  });
39960
40958
  }
39961
40959
  },
@@ -39976,7 +40974,7 @@ var AGENT_COMMANDS = [
39976
40974
  async execute(client, args) {
39977
40975
  return await client.contextEpisode({
39978
40976
  project: resolveProject(client, args.project),
39979
- ...optionalString(args.workflowRunId) ? { workflowRunId: optionalString(args.workflowRunId) } : {}
40977
+ ...optionalString2(args.workflowRunId) ? { workflowRunId: optionalString2(args.workflowRunId) } : {}
39980
40978
  });
39981
40979
  }
39982
40980
  },
@@ -40052,7 +41050,7 @@ function mintSessionToken(input) {
40052
41050
  const expiresAt = input?.expiresAt ?? new Date(Date.now() + ttlMs).toISOString();
40053
41051
  const payload = {
40054
41052
  schema: "kxm.session-token.v1",
40055
- sessionId: input?.sessionId ?? `session-${randomUUID8()}`,
41053
+ sessionId: input?.sessionId ?? `session-${randomUUID9()}`,
40056
41054
  issuedAt,
40057
41055
  expiresAt,
40058
41056
  ...input?.agentName ? { agentName: input.agentName } : {},
@@ -40080,17 +41078,17 @@ function parseSessionToken(token) {
40080
41078
  }
40081
41079
  function resolveUserConfigDirectory(overrideDir) {
40082
41080
  if (overrideDir) return resolve16(overrideDir);
40083
- return resolve16(process.env.KXM_USER_CONFIG_DIR?.trim() || join29(homedir4(), ".config", "kxm"));
41081
+ return resolve16(process.env.KXM_USER_CONFIG_DIR?.trim() || join30(homedir4(), ".config", "kxm"));
40084
41082
  }
40085
41083
  function sessionTokenPath(userConfigDir) {
40086
- return join29(resolveUserConfigDirectory(userConfigDir), "session.token");
41084
+ return join30(resolveUserConfigDirectory(userConfigDir), "session.token");
40087
41085
  }
40088
41086
  function persistSessionTokenToDisk(token, options) {
40089
41087
  const filePath = sessionTokenPath(options?.userConfigDir);
40090
- const dir = dirname15(filePath);
40091
- mkdirSync21(dir, { recursive: true, mode: 448 });
41088
+ const dir = dirname16(filePath);
41089
+ mkdirSync22(dir, { recursive: true, mode: 448 });
40092
41090
  const mode = options?.mode ?? 384;
40093
- writeFileSync19(filePath, `${token.trim()}
41091
+ writeFileSync20(filePath, `${token.trim()}
40094
41092
  `, { encoding: "utf8", mode });
40095
41093
  try {
40096
41094
  chmodSync6(filePath, mode);
@@ -40100,7 +41098,7 @@ function persistSessionTokenToDisk(token, options) {
40100
41098
  }
40101
41099
  function readSessionTokenFromDisk(options) {
40102
41100
  const filePath = sessionTokenPath(options?.userConfigDir);
40103
- if (!existsSync23(filePath)) return void 0;
41101
+ if (!existsSync24(filePath)) return void 0;
40104
41102
  try {
40105
41103
  const token = readFileSync22(filePath, "utf8").trim();
40106
41104
  if (!token) return void 0;
@@ -40113,7 +41111,7 @@ function readSessionTokenFromDisk(options) {
40113
41111
  }
40114
41112
  function clearSessionTokenFromDisk(options) {
40115
41113
  const filePath = sessionTokenPath(options?.userConfigDir);
40116
- if (!existsSync23(filePath)) return false;
41114
+ if (!existsSync24(filePath)) return false;
40117
41115
  try {
40118
41116
  unlinkSync2(filePath);
40119
41117
  return true;
@@ -40212,7 +41210,7 @@ function enforceToolPolicy(commandName, env = process.env, options) {
40212
41210
  return { allowed: true };
40213
41211
  }
40214
41212
  const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
40215
- if (existsSync23(tokenFile)) {
41213
+ if (existsSync24(tokenFile)) {
40216
41214
  let tokenRaw;
40217
41215
  try {
40218
41216
  tokenRaw = readFileSync22(tokenFile, "utf8").trim();
@@ -40236,10 +41234,10 @@ function enforceToolPolicy(commandName, env = process.env, options) {
40236
41234
  }
40237
41235
 
40238
41236
  // plugins/kxm/src/config.ts
40239
- var import_yaml11 = __toESM(require_dist(), 1);
40240
- import { existsSync as existsSync24, mkdirSync as mkdirSync22, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "node:fs";
41237
+ var import_yaml12 = __toESM(require_dist(), 1);
41238
+ import { existsSync as existsSync25, mkdirSync as mkdirSync23, readFileSync as readFileSync23, writeFileSync as writeFileSync21 } from "node:fs";
40241
41239
  import { homedir as homedir5 } from "node:os";
40242
- import { dirname as dirname16, join as join30, resolve as resolve17 } from "node:path";
41240
+ import { dirname as dirname17, join as join31, resolve as resolve17 } from "node:path";
40243
41241
  var KXM_CONFIG_SCHEMA = "kxm.config.v1";
40244
41242
  var DEFAULT_KXM_CONFIG = {
40245
41243
  schema: KXM_CONFIG_SCHEMA,
@@ -40290,7 +41288,7 @@ var DEFAULT_KXM_CONFIG = {
40290
41288
  };
40291
41289
  function userConfigDirectory(overrideDir) {
40292
41290
  if (overrideDir) return resolve17(overrideDir);
40293
- return resolve17(process.env.KXM_USER_CONFIG_DIR?.trim() || join30(homedir5(), ".config", "kxm"));
41291
+ return resolve17(process.env.KXM_USER_CONFIG_DIR?.trim() || join31(homedir5(), ".config", "kxm"));
40294
41292
  }
40295
41293
  function repoConfigDirectory(repoRoot3) {
40296
41294
  return resolve17(repoRoot3, ".kxm");
@@ -40309,15 +41307,15 @@ function deepMerge(target, source) {
40309
41307
  }
40310
41308
  function loadKxmConfig(repoRoot3 = process.cwd(), options = {}) {
40311
41309
  const userDir = userConfigDirectory(options.userConfigDir);
40312
- const userConfigFile = join30(userDir, "config.yaml");
41310
+ const userConfigFile = join31(userDir, "config.yaml");
40313
41311
  const repoDir = repoConfigDirectory(repoRoot3);
40314
- const repoConfigFile = join30(repoDir, "config.yaml");
41312
+ const repoConfigFile = join31(repoDir, "config.yaml");
40315
41313
  let userRaw = {};
40316
41314
  let userLoadedPath;
40317
- if (existsSync24(userConfigFile)) {
41315
+ if (existsSync25(userConfigFile)) {
40318
41316
  try {
40319
41317
  const text = readFileSync23(userConfigFile, "utf8");
40320
- userRaw = (0, import_yaml11.parse)(text) ?? {};
41318
+ userRaw = (0, import_yaml12.parse)(text) ?? {};
40321
41319
  userLoadedPath = userConfigFile;
40322
41320
  } catch (error) {
40323
41321
  throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
@@ -40325,10 +41323,10 @@ function loadKxmConfig(repoRoot3 = process.cwd(), options = {}) {
40325
41323
  }
40326
41324
  let repoRaw = {};
40327
41325
  let repoLoadedPath;
40328
- if (existsSync24(repoConfigFile)) {
41326
+ if (existsSync25(repoConfigFile)) {
40329
41327
  try {
40330
41328
  const text = readFileSync23(repoConfigFile, "utf8");
40331
- repoRaw = (0, import_yaml11.parse)(text) ?? {};
41329
+ repoRaw = (0, import_yaml12.parse)(text) ?? {};
40332
41330
  repoLoadedPath = repoConfigFile;
40333
41331
  } catch (error) {
40334
41332
  throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
@@ -40363,12 +41361,12 @@ function getKxmConfigValue(config, keyPath) {
40363
41361
  }
40364
41362
  function setKxmConfigValue(repoRoot3, keyPath, value, options = {}) {
40365
41363
  const scope = options.scope ?? "project";
40366
- const targetFile2 = scope === "user" ? join30(userConfigDirectory(options.userConfigDir), "config.yaml") : join30(repoConfigDirectory(repoRoot3), "config.yaml");
40367
- mkdirSync22(dirname16(targetFile2), { recursive: true });
41364
+ const targetFile2 = scope === "user" ? join31(userConfigDirectory(options.userConfigDir), "config.yaml") : join31(repoConfigDirectory(repoRoot3), "config.yaml");
41365
+ mkdirSync23(dirname17(targetFile2), { recursive: true });
40368
41366
  let existing = {};
40369
- if (existsSync24(targetFile2)) {
41367
+ if (existsSync25(targetFile2)) {
40370
41368
  try {
40371
- existing = (0, import_yaml11.parse)(readFileSync23(targetFile2, "utf8")) ?? {};
41369
+ existing = (0, import_yaml12.parse)(readFileSync23(targetFile2, "utf8")) ?? {};
40372
41370
  } catch {
40373
41371
  existing = {};
40374
41372
  }
@@ -40386,7 +41384,7 @@ function setKxmConfigValue(repoRoot3, keyPath, value, options = {}) {
40386
41384
  cursor2 = cursor2[p];
40387
41385
  }
40388
41386
  cursor2[parts[parts.length - 1]] = value;
40389
- writeFileSync20(targetFile2, (0, import_yaml11.stringify)(existing).trim() + "\n", "utf8");
41387
+ writeFileSync21(targetFile2, (0, import_yaml12.stringify)(existing).trim() + "\n", "utf8");
40390
41388
  }
40391
41389
  function formatKxmConfig(config) {
40392
41390
  const display = {
@@ -40397,7 +41395,7 @@ function formatKxmConfig(config) {
40397
41395
  sync: config.sync,
40398
41396
  loadedFrom: config.loadedFrom
40399
41397
  };
40400
- return (0, import_yaml11.stringify)(display).trim();
41398
+ return (0, import_yaml12.stringify)(display).trim();
40401
41399
  }
40402
41400
 
40403
41401
  // plugins/kxm/src/autocomplete.ts
@@ -40455,7 +41453,7 @@ var SUBCOMMANDS = {
40455
41453
  goal: ["create", "list", "get"],
40456
41454
  task: ["create", "list", "get", "run", "sync"],
40457
41455
  studio: ["layout", "serve"],
40458
- role: ["list", "get", "add", "remove", "modify"]
41456
+ role: ["list", "get", "add", "remove", "modify", "hosts", "set-host", "resume"]
40459
41457
  };
40460
41458
  function generateShellCompletion(shell) {
40461
41459
  switch (shell) {
@@ -40636,6 +41634,173 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
40636
41634
  `;
40637
41635
  }
40638
41636
 
41637
+ // plugins/kxm/src/completion-install.ts
41638
+ import { existsSync as existsSync26, mkdirSync as mkdirSync24, readFileSync as readFileSync24, writeFileSync as writeFileSync22 } from "node:fs";
41639
+ import { homedir as homedir6 } from "node:os";
41640
+ import { basename as basename6, delimiter, dirname as dirname18, isAbsolute as isAbsolute8, join as join32, resolve as resolve18 } from "node:path";
41641
+ var COMPLETION_MARKER = "# kxm completion";
41642
+ var PATH_MARKER = "# kxm path";
41643
+ function detectShell(env = process.env, platform = process.platform) {
41644
+ const shellPath = env.SHELL?.trim() || (process.platform === "win32" ? void 0 : env.SHELL?.trim());
41645
+ const name = shellPath ? basename6(shellPath).toLowerCase() : "";
41646
+ if (name === "bash" || name === "zsh" || name === "fish") return name;
41647
+ if (name.endsWith("bash") || name.includes("bash")) return "bash";
41648
+ if (name.endsWith("zsh")) return "zsh";
41649
+ if (name.endsWith("fish")) return "fish";
41650
+ return "unknown";
41651
+ }
41652
+ function effectiveHome(options) {
41653
+ const env = options.env ?? process.env;
41654
+ if (options.homeDir) return options.homeDir;
41655
+ const envHome = env.HOME?.trim() ?? env.USERPROFILE?.trim();
41656
+ return envHome && envHome.length > 0 ? resolve18(envHome) : homedir6();
41657
+ }
41658
+ function completionScriptPath(shell, options = {}) {
41659
+ const env = options.env ?? process.env;
41660
+ const explicit = options.configDir ?? env.KXM_USER_CONFIG_DIR?.trim();
41661
+ const base = explicit && explicit.length > 0 ? resolve18(explicit) : resolve18(effectiveHome(options), ".config", "kxm");
41662
+ return join32(base, "completions", `kxm.${shell}`);
41663
+ }
41664
+ function bashRcCandidate(options) {
41665
+ const home = effectiveHome(options);
41666
+ const candidates = [join32(home, ".bashrc"), join32(home, ".bash_profile")];
41667
+ const existing = candidates.find((candidate) => existsSync26(candidate));
41668
+ return existing ?? candidates[0];
41669
+ }
41670
+ function zshRcCandidate(options) {
41671
+ const env = options.env ?? process.env;
41672
+ const home = effectiveHome(options);
41673
+ if (env.ZDOTDIR?.trim()) return join32(resolve18(env.ZDOTDIR.trim()), ".zshrc");
41674
+ return join32(home, ".zshrc");
41675
+ }
41676
+ function fishCompletionTarget(shell, options) {
41677
+ const env = options.env ?? process.env;
41678
+ const home = effectiveHome(options);
41679
+ if (env.XDG_CONFIG_HOME?.trim()) return join32(resolve18(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
41680
+ return join32(home, ".config", "fish", "completions", "kxm.fish");
41681
+ }
41682
+ function completionRcTarget(shell, options = {}) {
41683
+ if (shell === "fish") {
41684
+ return { rcFile: void 0 };
41685
+ }
41686
+ if (shell === "zsh") {
41687
+ return { rcFile: zshRcCandidate(options) };
41688
+ }
41689
+ return { rcFile: bashRcCandidate(options) };
41690
+ }
41691
+ function sourceLine(shell, scriptPath) {
41692
+ if (shell === "fish") return `source ${scriptPath}`;
41693
+ if (shell === "zsh") return `[[ -f ${scriptPath} ]] && source ${scriptPath}`;
41694
+ return `[[ -f ${scriptPath} ]] && source ${scriptPath}`;
41695
+ }
41696
+ function installShellCompletion(shellInput, options = {}) {
41697
+ const shell = shellInput?.trim() && shellInput !== "auto" ? shellInput.trim() : detectShell(options.env ?? process.env, options.platform ?? process.platform);
41698
+ if (shell !== "bash" && shell !== "zsh" && shell !== "fish") {
41699
+ return {
41700
+ ok: false,
41701
+ shell: "unknown",
41702
+ scriptPath: "",
41703
+ rcFile: void 0,
41704
+ rcModified: false,
41705
+ alreadyInstalled: false,
41706
+ reason: "shell_not_detected"
41707
+ };
41708
+ }
41709
+ const scriptPath = completionScriptPath(shell, options);
41710
+ const { rcFile } = completionRcTarget(shell, options);
41711
+ const script = generateShellCompletion(shell);
41712
+ const existingScript = existsSync26(scriptPath);
41713
+ const existingScriptMatches = existingScript && readFileSync24(scriptPath, "utf8") === script;
41714
+ let rcModified = false;
41715
+ let alreadyInstalled = false;
41716
+ if (shell === "fish") {
41717
+ const fishTarget = fishCompletionTarget(shell, options);
41718
+ const fishInstalled = existsSync26(fishTarget) && readFileSync24(fishTarget, "utf8") === script;
41719
+ alreadyInstalled = fishInstalled;
41720
+ if (!options.dryRun && (!fishInstalled || options.overwrite)) {
41721
+ mkdirSync24(resolve18(fishTarget, ".."), { recursive: true });
41722
+ writeFileSync22(fishTarget, script, { encoding: "utf8" });
41723
+ }
41724
+ return {
41725
+ ok: true,
41726
+ shell,
41727
+ scriptPath: fishTarget,
41728
+ rcFile: void 0,
41729
+ rcModified: !fishInstalled,
41730
+ alreadyInstalled
41731
+ };
41732
+ }
41733
+ if (rcFile) {
41734
+ const line = sourceLine(shell, scriptPath);
41735
+ const rcContent = existsSync26(rcFile) ? readFileSync24(rcFile, "utf8") : "";
41736
+ alreadyInstalled = rcContent.includes(line) || rcContent.includes(`${COMPLETION_MARKER}`);
41737
+ }
41738
+ if (!options.dryRun) {
41739
+ if (!existingScriptMatches || options.overwrite) {
41740
+ mkdirSync24(resolve18(scriptPath, ".."), { recursive: true });
41741
+ writeFileSync22(scriptPath, script, { encoding: "utf8" });
41742
+ }
41743
+ if (rcFile && !alreadyInstalled) {
41744
+ const line = sourceLine(shell, scriptPath);
41745
+ const marker = `${COMPLETION_MARKER} (${(/* @__PURE__ */ new Date()).toISOString()})`;
41746
+ const stanza = `
41747
+ ${marker}
41748
+ ${line}
41749
+ `;
41750
+ mkdirSync24(resolve18(rcFile, ".."), { recursive: true });
41751
+ writeFileSync22(rcFile, (existsSync26(rcFile) ? readFileSync24(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
41752
+ rcModified = true;
41753
+ }
41754
+ }
41755
+ return {
41756
+ ok: true,
41757
+ shell,
41758
+ scriptPath,
41759
+ rcFile,
41760
+ rcModified,
41761
+ alreadyInstalled
41762
+ };
41763
+ }
41764
+ function kxmBinDir(env = process.env) {
41765
+ const argv1 = env.KXM_ENTRY ?? process.argv[1];
41766
+ if (argv1 && isAbsolute8(argv1)) {
41767
+ const dir = dirname18(argv1);
41768
+ if (existsSync26(join32(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
41769
+ }
41770
+ const pathEnv = env.PATH ?? "";
41771
+ for (const part of pathEnv.split(delimiter)) {
41772
+ if (!part) continue;
41773
+ const candidate = resolve18(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
41774
+ if (existsSync26(candidate)) return resolve18(part);
41775
+ }
41776
+ return void 0;
41777
+ }
41778
+ function pathLine(binDir) {
41779
+ return `export PATH="${binDir}:$PATH" # kxm`;
41780
+ }
41781
+ function installPathEntry(shellInput, options = {}) {
41782
+ const shell = shellInput?.trim() && shellInput !== "auto" ? shellInput.trim() : detectShell(options.env ?? process.env, options.platform ?? process.platform);
41783
+ if (shell !== "bash" && shell !== "zsh") {
41784
+ return { ok: false, binDir: void 0, rcFile: void 0, rcModified: false, alreadyInstalled: false, reason: "shell_not_supported" };
41785
+ }
41786
+ const binDir = options.binDir ?? kxmBinDir(options.env ?? process.env);
41787
+ if (!binDir) {
41788
+ return { ok: false, binDir: void 0, rcFile: void 0, rcModified: false, alreadyInstalled: false, reason: "kxm_not_found" };
41789
+ }
41790
+ const { rcFile } = completionRcTarget(shell, options);
41791
+ const line = pathLine(binDir);
41792
+ const rcContent = existsSync26(rcFile ?? "") ? readFileSync24(rcFile, "utf8") : "";
41793
+ const alreadyInstalled = rcContent.includes(binDir) || (options.env?.PATH ?? process.env.PATH ?? "").split(delimiter).includes(binDir);
41794
+ if (!options.dryRun && rcFile && !alreadyInstalled) {
41795
+ mkdirSync24(dirname18(rcFile), { recursive: true });
41796
+ writeFileSync22(rcFile, rcContent + `
41797
+ ${PATH_MARKER}
41798
+ ${line}
41799
+ `, { encoding: "utf8" });
41800
+ }
41801
+ return { ok: true, binDir, rcFile, rcModified: !alreadyInstalled && !options.dryRun, alreadyInstalled };
41802
+ }
41803
+
40639
41804
  // plugins/kxm/src/suggest.ts
40640
41805
  var WORKFLOW_PATTERNS = [
40641
41806
  {
@@ -40743,22 +41908,22 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
40743
41908
  }
40744
41909
 
40745
41910
  // plugins/kxm/src/task-manager.ts
40746
- var import_yaml12 = __toESM(require_dist(), 1);
40747
- import { randomUUID as randomUUID9 } from "node:crypto";
40748
- import { existsSync as existsSync25, mkdirSync as mkdirSync23, readFileSync as readFileSync24, readdirSync as readdirSync8, writeFileSync as writeFileSync21 } from "node:fs";
40749
- import { join as join31, resolve as resolve18 } from "node:path";
41911
+ var import_yaml13 = __toESM(require_dist(), 1);
41912
+ import { randomUUID as randomUUID10 } from "node:crypto";
41913
+ import { existsSync as existsSync27, mkdirSync as mkdirSync25, readFileSync as readFileSync25, readdirSync as readdirSync8, writeFileSync as writeFileSync23 } from "node:fs";
41914
+ import { join as join33, resolve as resolve19 } from "node:path";
40750
41915
  var GOAL_SCHEMA = "kxm.goal.v1";
40751
41916
  var TASK_SCHEMA = "kxm.task.v1";
40752
41917
  function goalsDirectory(repoRoot3) {
40753
- return resolve18(repoRoot3, ".kxm", "goals");
41918
+ return resolve19(repoRoot3, ".kxm", "goals");
40754
41919
  }
40755
41920
  function tasksDirectory(repoRoot3) {
40756
- return resolve18(repoRoot3, ".kxm", "tasks");
41921
+ return resolve19(repoRoot3, ".kxm", "tasks");
40757
41922
  }
40758
41923
  function createGoal(repoRoot3, input) {
40759
41924
  const dir = goalsDirectory(repoRoot3);
40760
- mkdirSync23(dir, { recursive: true });
40761
- const id = `goal_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
41925
+ mkdirSync25(dir, { recursive: true });
41926
+ const id = `goal_${randomUUID10().replaceAll("-", "").slice(0, 12)}`;
40762
41927
  const now = (/* @__PURE__ */ new Date()).toISOString();
40763
41928
  const record = {
40764
41929
  schema: GOAL_SCHEMA,
@@ -40771,20 +41936,20 @@ function createGoal(repoRoot3, input) {
40771
41936
  createdAt: now,
40772
41937
  updatedAt: now
40773
41938
  };
40774
- const filePath = join31(dir, `${id}.yaml`);
40775
- writeFileSync21(filePath, (0, import_yaml12.stringify)(record).trim() + "\n", "utf8");
41939
+ const filePath = join33(dir, `${id}.yaml`);
41940
+ writeFileSync23(filePath, (0, import_yaml13.stringify)(record).trim() + "\n", "utf8");
40776
41941
  return record;
40777
41942
  }
40778
41943
  function listGoals(repoRoot3) {
40779
41944
  const dir = goalsDirectory(repoRoot3);
40780
- if (!existsSync25(dir)) return [];
41945
+ if (!existsSync27(dir)) return [];
40781
41946
  const goals = [];
40782
41947
  const entries = readdirSync8(dir, { withFileTypes: true });
40783
41948
  for (const entry of entries) {
40784
41949
  if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
40785
41950
  try {
40786
- const text = readFileSync24(join31(dir, entry.name), "utf8");
40787
- const doc = (0, import_yaml12.parse)(text);
41951
+ const text = readFileSync25(join33(dir, entry.name), "utf8");
41952
+ const doc = (0, import_yaml13.parse)(text);
40788
41953
  if (doc && doc.schema === GOAL_SCHEMA) goals.push(doc);
40789
41954
  } catch {
40790
41955
  }
@@ -40794,8 +41959,8 @@ function listGoals(repoRoot3) {
40794
41959
  }
40795
41960
  function createTask(repoRoot3, input) {
40796
41961
  const dir = tasksDirectory(repoRoot3);
40797
- mkdirSync23(dir, { recursive: true });
40798
- const id = `task_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
41962
+ mkdirSync25(dir, { recursive: true });
41963
+ const id = `task_${randomUUID10().replaceAll("-", "").slice(0, 12)}`;
40799
41964
  const now = (/* @__PURE__ */ new Date()).toISOString();
40800
41965
  const record = {
40801
41966
  schema: TASK_SCHEMA,
@@ -40816,20 +41981,20 @@ function createTask(repoRoot3, input) {
40816
41981
  createdAt: now,
40817
41982
  updatedAt: now
40818
41983
  };
40819
- const filePath = join31(dir, `${id}.yaml`);
40820
- writeFileSync21(filePath, (0, import_yaml12.stringify)(record).trim() + "\n", "utf8");
41984
+ const filePath = join33(dir, `${id}.yaml`);
41985
+ writeFileSync23(filePath, (0, import_yaml13.stringify)(record).trim() + "\n", "utf8");
40821
41986
  return record;
40822
41987
  }
40823
41988
  function listTasks(repoRoot3, options = {}) {
40824
41989
  const dir = tasksDirectory(repoRoot3);
40825
- if (!existsSync25(dir)) return [];
41990
+ if (!existsSync27(dir)) return [];
40826
41991
  const tasks = [];
40827
41992
  const entries = readdirSync8(dir, { withFileTypes: true });
40828
41993
  for (const entry of entries) {
40829
41994
  if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
40830
41995
  try {
40831
- const text = readFileSync24(join31(dir, entry.name), "utf8");
40832
- const doc = (0, import_yaml12.parse)(text);
41996
+ const text = readFileSync25(join33(dir, entry.name), "utf8");
41997
+ const doc = (0, import_yaml13.parse)(text);
40833
41998
  if (doc && doc.schema === TASK_SCHEMA) {
40834
41999
  if (options.goalId && doc.goalId !== options.goalId) continue;
40835
42000
  if (options.status && doc.status !== options.status) continue;
@@ -40843,11 +42008,11 @@ function listTasks(repoRoot3, options = {}) {
40843
42008
  }
40844
42009
  function getTask(repoRoot3, taskId) {
40845
42010
  const dir = tasksDirectory(repoRoot3);
40846
- const filePath = join31(dir, `${taskId}.yaml`);
40847
- if (!existsSync25(filePath)) return void 0;
42011
+ const filePath = join33(dir, `${taskId}.yaml`);
42012
+ if (!existsSync27(filePath)) return void 0;
40848
42013
  try {
40849
- const text = readFileSync24(filePath, "utf8");
40850
- const doc = (0, import_yaml12.parse)(text);
42014
+ const text = readFileSync25(filePath, "utf8");
42015
+ const doc = (0, import_yaml13.parse)(text);
40851
42016
  return doc && doc.schema === TASK_SCHEMA ? doc : void 0;
40852
42017
  } catch {
40853
42018
  return void 0;
@@ -40861,8 +42026,8 @@ function updateTaskStatus(repoRoot3, taskId, status, options = {}) {
40861
42026
  task.status = status;
40862
42027
  if (options.workflowRunId) task.workflowRunId = options.workflowRunId;
40863
42028
  task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
40864
- const filePath = join31(tasksDirectory(repoRoot3), `${taskId}.yaml`);
40865
- writeFileSync21(filePath, (0, import_yaml12.stringify)(task).trim() + "\n", "utf8");
42029
+ const filePath = join33(tasksDirectory(repoRoot3), `${taskId}.yaml`);
42030
+ writeFileSync23(filePath, (0, import_yaml13.stringify)(task).trim() + "\n", "utf8");
40866
42031
  return task;
40867
42032
  }
40868
42033
  function syncTaskWithTracker(repoRoot3, taskId, options = {}) {
@@ -40877,17 +42042,17 @@ function syncTaskWithTracker(repoRoot3, taskId, options = {}) {
40877
42042
  task.trackerSync.syncStatus = "synced";
40878
42043
  task.trackerSync.lastSyncedAt = now;
40879
42044
  task.updatedAt = now;
40880
- const filePath = join31(tasksDirectory(repoRoot3), `${taskId}.yaml`);
40881
- writeFileSync21(filePath, (0, import_yaml12.stringify)(task).trim() + "\n", "utf8");
42045
+ const filePath = join33(tasksDirectory(repoRoot3), `${taskId}.yaml`);
42046
+ writeFileSync23(filePath, (0, import_yaml13.stringify)(task).trim() + "\n", "utf8");
40882
42047
  return task;
40883
42048
  }
40884
42049
 
40885
42050
  // plugins/kxm/src/cli.ts
40886
- var import_yaml15 = __toESM(require_dist(), 1);
42051
+ var import_yaml16 = __toESM(require_dist(), 1);
40887
42052
 
40888
42053
  // plugins/kxm/src/studio-layout.ts
40889
42054
  import { createServer } from "node:http";
40890
- import { randomUUID as randomUUID10 } from "node:crypto";
42055
+ import { randomUUID as randomUUID11 } from "node:crypto";
40891
42056
  var STUDIO_LAYOUT_SCHEMA = "kxm.studio-layout.v1";
40892
42057
  function generateStudioLayout(plan, state) {
40893
42058
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -41150,7 +42315,7 @@ function createStudioServer(options = {}) {
41150
42315
  }));
41151
42316
  return;
41152
42317
  }
41153
- const mutationId = `mut_${randomUUID10().replaceAll("-", "").slice(0, 12)}`;
42318
+ const mutationId = `mut_${randomUUID11().replaceAll("-", "").slice(0, 12)}`;
41154
42319
  if (options.onMutation) {
41155
42320
  try {
41156
42321
  const outcome = await options.onMutation(command, args);
@@ -41647,9 +42812,9 @@ function createStudioServer(options = {}) {
41647
42812
  }
41648
42813
 
41649
42814
  // plugins/kxm/src/role.ts
41650
- var import_yaml13 = __toESM(require_dist(), 1);
41651
- import { existsSync as existsSync26, mkdirSync as mkdirSync24, readdirSync as readdirSync9, readFileSync as readFileSync25, rmSync as rmSync8, writeFileSync as writeFileSync22 } from "node:fs";
41652
- import { join as join32 } from "node:path";
42815
+ var import_yaml14 = __toESM(require_dist(), 1);
42816
+ import { existsSync as existsSync28, mkdirSync as mkdirSync26, readdirSync as readdirSync9, readFileSync as readFileSync26, rmSync as rmSync8, writeFileSync as writeFileSync24 } from "node:fs";
42817
+ import { join as join34 } from "node:path";
41653
42818
  var KXM_ROLE_SCHEMA = "kxm.role.v1";
41654
42819
  var DEFAULT_ROLES = {
41655
42820
  writer: {
@@ -41728,22 +42893,22 @@ var DEFAULT_ROLES = {
41728
42893
  };
41729
42894
  function rolesDirectory(scope, repoRoot3 = process.cwd(), userConfigDir) {
41730
42895
  if (scope === "global") {
41731
- return join32(userConfigDirectory(userConfigDir), "roles");
42896
+ return join34(userConfigDirectory(userConfigDir), "roles");
41732
42897
  }
41733
- return join32(repoConfigDirectory(repoRoot3), "roles");
42898
+ return join34(repoConfigDirectory(repoRoot3), "roles");
41734
42899
  }
41735
42900
  function ensureRolesDirectory(scope, repoRoot3 = process.cwd(), userConfigDir) {
41736
42901
  const dir = rolesDirectory(scope, repoRoot3, userConfigDir);
41737
- if (!existsSync26(dir)) {
41738
- mkdirSync24(dir, { recursive: true });
42902
+ if (!existsSync28(dir)) {
42903
+ mkdirSync26(dir, { recursive: true });
41739
42904
  }
41740
42905
  return dir;
41741
42906
  }
41742
42907
  function parseRoleFile(filePath) {
41743
- if (!existsSync26(filePath)) return void 0;
42908
+ if (!existsSync28(filePath)) return void 0;
41744
42909
  try {
41745
- const raw = readFileSync25(filePath, "utf8");
41746
- const parsed = (0, import_yaml13.parse)(raw);
42910
+ const raw = readFileSync26(filePath, "utf8");
42911
+ const parsed = (0, import_yaml14.parse)(raw);
41747
42912
  if (!parsed || typeof parsed !== "object" || parsed.schema !== KXM_ROLE_SCHEMA) {
41748
42913
  return void 0;
41749
42914
  }
@@ -41769,10 +42934,10 @@ function listRoles(options = {}) {
41769
42934
  const globalDir = rolesDirectory("global", repoRoot3, options.userConfigDir);
41770
42935
  const localDir = rolesDirectory("local", repoRoot3, options.userConfigDir);
41771
42936
  const localRoles = /* @__PURE__ */ new Map();
41772
- if (scopeFilter !== "global" && existsSync26(localDir)) {
42937
+ if (scopeFilter !== "global" && existsSync28(localDir)) {
41773
42938
  for (const entry of readdirSync9(localDir)) {
41774
42939
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
41775
- const filePath = join32(localDir, entry);
42940
+ const filePath = join34(localDir, entry);
41776
42941
  const parsed = parseRoleFile(filePath);
41777
42942
  if (parsed) {
41778
42943
  localRoles.set(parsed.id, { role: parsed, filePath });
@@ -41781,10 +42946,10 @@ function listRoles(options = {}) {
41781
42946
  }
41782
42947
  }
41783
42948
  const globalRoles = /* @__PURE__ */ new Map();
41784
- if (scopeFilter !== "local" && existsSync26(globalDir)) {
42949
+ if (scopeFilter !== "local" && existsSync28(globalDir)) {
41785
42950
  for (const entry of readdirSync9(globalDir)) {
41786
42951
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
41787
- const filePath = join32(globalDir, entry);
42952
+ const filePath = join34(globalDir, entry);
41788
42953
  const parsed = parseRoleFile(filePath);
41789
42954
  if (parsed) {
41790
42955
  globalRoles.set(parsed.id, { role: parsed, filePath });
@@ -41832,13 +42997,13 @@ function getRole(roleId, options = {}) {
41832
42997
  const repoRoot3 = options.repoRoot ?? process.cwd();
41833
42998
  if (scope !== "global") {
41834
42999
  const localDir = rolesDirectory("local", repoRoot3, options.userConfigDir);
41835
- const localFile = join32(localDir, `${roleId}.yaml`);
43000
+ const localFile = join34(localDir, `${roleId}.yaml`);
41836
43001
  const parsed = parseRoleFile(localFile);
41837
43002
  if (parsed) return { role: parsed, scope: "local", filePath: localFile };
41838
43003
  }
41839
43004
  if (scope !== "local") {
41840
43005
  const globalDir = rolesDirectory("global", repoRoot3, options.userConfigDir);
41841
- const globalFile = join32(globalDir, `${roleId}.yaml`);
43006
+ const globalFile = join34(globalDir, `${roleId}.yaml`);
41842
43007
  const parsed = parseRoleFile(globalFile);
41843
43008
  if (parsed) return { role: parsed, scope: "global", filePath: globalFile };
41844
43009
  }
@@ -41848,19 +43013,19 @@ function addRole(role, options = {}) {
41848
43013
  const scope = options.scope ?? "local";
41849
43014
  const repoRoot3 = options.repoRoot ?? process.cwd();
41850
43015
  const dir = ensureRolesDirectory(scope, repoRoot3, options.userConfigDir);
41851
- const filePath = join32(dir, `${role.id}.yaml`);
41852
- if (existsSync26(filePath) && !options.overwrite) {
43016
+ const filePath = join34(dir, `${role.id}.yaml`);
43017
+ if (existsSync28(filePath) && !options.overwrite) {
41853
43018
  throw new Error(`role_already_exists: role '${role.id}' already exists at ${filePath}`);
41854
43019
  }
41855
- writeFileSync22(filePath, (0, import_yaml13.stringify)(role), "utf8");
43020
+ writeFileSync24(filePath, (0, import_yaml14.stringify)(role), "utf8");
41856
43021
  return { id: role.id, filePath, scope };
41857
43022
  }
41858
43023
  function removeRole(roleId, options = {}) {
41859
43024
  const scope = options.scope ?? "local";
41860
43025
  const repoRoot3 = options.repoRoot ?? process.cwd();
41861
43026
  const dir = rolesDirectory(scope, repoRoot3, options.userConfigDir);
41862
- const filePath = join32(dir, `${roleId}.yaml`);
41863
- if (!existsSync26(filePath)) {
43027
+ const filePath = join34(dir, `${roleId}.yaml`);
43028
+ if (!existsSync28(filePath)) {
41864
43029
  throw new Error(`role_not_found: role '${roleId}' not found in ${scope} directory (${filePath})`);
41865
43030
  }
41866
43031
  rmSync8(filePath);
@@ -41881,15 +43046,236 @@ function modifyRole(roleId, updates, options = {}) {
41881
43046
  const scope = options.scope ?? target.scope;
41882
43047
  const repoRoot3 = options.repoRoot ?? process.cwd();
41883
43048
  const dir = ensureRolesDirectory(scope, repoRoot3, options.userConfigDir);
41884
- const filePath = join32(dir, `${roleId}.yaml`);
41885
- writeFileSync22(filePath, (0, import_yaml13.stringify)(updated), "utf8");
43049
+ const filePath = join34(dir, `${roleId}.yaml`);
43050
+ writeFileSync24(filePath, (0, import_yaml14.stringify)(updated), "utf8");
41886
43051
  return { id: roleId, role: updated, filePath, scope };
41887
43052
  }
43053
+ var KXM_ROLE_HOSTS_SCHEMA = "kxm.role-hosts.v1";
43054
+ var DEFAULT_ROLE_SEATS = {
43055
+ planner: {
43056
+ seatId: "planner",
43057
+ description: "Architecture breakdown, requirement decomposition, and safety boundary definition.",
43058
+ defaultModel: "anthropic/claude-fable-5.1",
43059
+ defaultHost: "pi",
43060
+ allowedTools: ["view_file", "grep_search", "find_by_name", "list_dir"],
43061
+ requiredEvidenceKind: "architecture_review"
43062
+ },
43063
+ writer: {
43064
+ seatId: "writer",
43065
+ description: "Primary implementation agent. Writes code, refactors components, and authors unit tests.",
43066
+ defaultModel: "x-ai/grok-4.6",
43067
+ defaultHost: "grok",
43068
+ allowedTools: ["view_file", "replace_file_content", "write_to_file", "run_command"],
43069
+ requiredEvidenceKind: "git_diff"
43070
+ },
43071
+ "critic-arch": {
43072
+ seatId: "critic-arch",
43073
+ description: "Independent critic reviewing implementation diffs for architectural integrity.",
43074
+ defaultModel: "anthropic/claude-fable-5.1",
43075
+ defaultHost: "pi",
43076
+ allowedTools: ["view_file", "grep_search"],
43077
+ requiredEvidenceKind: "architecture_review"
43078
+ },
43079
+ "critic-cli": {
43080
+ seatId: "critic-cli",
43081
+ description: "CLI, documentation, and developer ergonomics critic.",
43082
+ defaultModel: "openai/gpt-5.6-sol",
43083
+ defaultHost: "pi",
43084
+ allowedTools: ["view_file", "grep_search"],
43085
+ requiredEvidenceKind: "cli_review"
43086
+ },
43087
+ verifier: {
43088
+ seatId: "verifier",
43089
+ description: "Deterministic gate evaluation runner.",
43090
+ defaultModel: "evaluator",
43091
+ defaultHost: "pi",
43092
+ allowedTools: ["run_command"],
43093
+ requiredEvidenceKind: "test_run"
43094
+ }
43095
+ };
43096
+ function findExistingRoleHostsFile(scope, repoRoot3 = process.cwd(), userConfigDir) {
43097
+ const dir = scope === "global" ? userConfigDirectory(userConfigDir) : repoConfigDirectory(repoRoot3);
43098
+ const yamlPath = join34(dir, "role-hosts.yaml");
43099
+ const ymlPath = join34(dir, "role-hosts.yml");
43100
+ const jsonPath = join34(dir, "role-hosts.json");
43101
+ if (existsSync28(yamlPath)) return { filePath: yamlPath, exists: true, format: "yaml" };
43102
+ if (existsSync28(ymlPath)) return { filePath: ymlPath, exists: true, format: "yaml" };
43103
+ if (existsSync28(jsonPath)) return { filePath: jsonPath, exists: true, format: "json" };
43104
+ return { filePath: yamlPath, exists: false, format: "yaml" };
43105
+ }
43106
+ function parseRoleHostsFile(filePath) {
43107
+ if (!existsSync28(filePath)) return void 0;
43108
+ try {
43109
+ const raw = readFileSync26(filePath, "utf8");
43110
+ const parsed = (0, import_yaml14.parse)(raw);
43111
+ if (!parsed || typeof parsed !== "object") return void 0;
43112
+ if (parsed.schema && parsed.schema !== KXM_ROLE_HOSTS_SCHEMA) return void 0;
43113
+ return {
43114
+ schema: KXM_ROLE_HOSTS_SCHEMA,
43115
+ seats: parsed.seats && typeof parsed.seats === "object" ? parsed.seats : {},
43116
+ hostProviders: parsed.hostProviders && typeof parsed.hostProviders === "object" ? parsed.hostProviders : {}
43117
+ };
43118
+ } catch {
43119
+ return void 0;
43120
+ }
43121
+ }
43122
+ function loadRoleHostsConfig(options = {}) {
43123
+ const scopeFilter = options.scope ?? "all";
43124
+ const repoRoot3 = options.repoRoot ?? process.cwd();
43125
+ let localConfig;
43126
+ let localPath;
43127
+ if (scopeFilter !== "global") {
43128
+ const localFound = findExistingRoleHostsFile("local", repoRoot3, options.userConfigDir);
43129
+ if (localFound.exists) {
43130
+ localConfig = parseRoleHostsFile(localFound.filePath);
43131
+ localPath = localFound.filePath;
43132
+ }
43133
+ }
43134
+ let globalConfig;
43135
+ let globalPath;
43136
+ if (scopeFilter !== "local") {
43137
+ const globalFound = findExistingRoleHostsFile("global", repoRoot3, options.userConfigDir);
43138
+ if (globalFound.exists) {
43139
+ globalConfig = parseRoleHostsFile(globalFound.filePath);
43140
+ globalPath = globalFound.filePath;
43141
+ }
43142
+ }
43143
+ if (scopeFilter === "local") {
43144
+ return {
43145
+ config: localConfig ?? { schema: KXM_ROLE_HOSTS_SCHEMA, seats: {}, hostProviders: {} },
43146
+ filePath: localPath,
43147
+ scope: localPath ? "local" : "default"
43148
+ };
43149
+ }
43150
+ if (scopeFilter === "global") {
43151
+ return {
43152
+ config: globalConfig ?? { schema: KXM_ROLE_HOSTS_SCHEMA, seats: {}, hostProviders: {} },
43153
+ filePath: globalPath,
43154
+ scope: globalPath ? "global" : "default"
43155
+ };
43156
+ }
43157
+ const mergedSeats = {
43158
+ ...globalConfig?.seats ?? {},
43159
+ ...localConfig?.seats ?? {}
43160
+ };
43161
+ const mergedHostProviders = {
43162
+ ...globalConfig?.hostProviders ?? {},
43163
+ ...localConfig?.hostProviders ?? {}
43164
+ };
43165
+ const primaryPath = localPath ?? globalPath;
43166
+ const primaryScope = localPath ? "local" : globalPath ? "global" : "default";
43167
+ return {
43168
+ config: {
43169
+ schema: KXM_ROLE_HOSTS_SCHEMA,
43170
+ seats: mergedSeats,
43171
+ hostProviders: mergedHostProviders
43172
+ },
43173
+ filePath: primaryPath,
43174
+ scope: primaryScope
43175
+ };
43176
+ }
43177
+ function saveRoleHostsConfig(config, options = {}) {
43178
+ const scope = options.scope ?? "local";
43179
+ const repoRoot3 = options.repoRoot ?? process.cwd();
43180
+ const dir = scope === "global" ? userConfigDirectory(options.userConfigDir) : repoConfigDirectory(repoRoot3);
43181
+ if (!existsSync28(dir)) {
43182
+ mkdirSync26(dir, { recursive: true });
43183
+ }
43184
+ const format = options.format ?? "yaml";
43185
+ const filePath = join34(dir, format === "json" ? "role-hosts.json" : "role-hosts.yaml");
43186
+ const payload = {
43187
+ schema: KXM_ROLE_HOSTS_SCHEMA,
43188
+ seats: config.seats ?? {},
43189
+ ...config.hostProviders && Object.keys(config.hostProviders).length > 0 ? { hostProviders: config.hostProviders } : {}
43190
+ };
43191
+ const content = format === "json" ? JSON.stringify(payload, null, 2) + "\n" : (0, import_yaml14.stringify)(payload);
43192
+ writeFileSync24(filePath, content, "utf8");
43193
+ return { filePath, scope };
43194
+ }
43195
+ function setRoleSeatHost(seatId, host, options = {}) {
43196
+ const scope = options.scope ?? "local";
43197
+ const current = loadRoleHostsConfig({
43198
+ scope,
43199
+ repoRoot: options.repoRoot,
43200
+ userConfigDir: options.userConfigDir
43201
+ });
43202
+ const seats = { ...current.config.seats ?? {} };
43203
+ const existing = seats[seatId] ?? {};
43204
+ const binding = {
43205
+ ...existing,
43206
+ host,
43207
+ ...options.model !== void 0 ? { model: options.model } : {},
43208
+ ...options.effort !== void 0 ? { effort: options.effort } : {}
43209
+ };
43210
+ seats[seatId] = binding;
43211
+ const updatedConfig = {
43212
+ ...current.config,
43213
+ schema: KXM_ROLE_HOSTS_SCHEMA,
43214
+ seats
43215
+ };
43216
+ const saved = saveRoleHostsConfig(updatedConfig, {
43217
+ scope,
43218
+ repoRoot: options.repoRoot,
43219
+ userConfigDir: options.userConfigDir,
43220
+ format: options.format
43221
+ });
43222
+ return { filePath: saved.filePath, seatId, binding, scope };
43223
+ }
43224
+ function resolveRoleSeat(seatId, options = {}) {
43225
+ const hostsConfig = loadRoleHostsConfig({
43226
+ scope: "all",
43227
+ repoRoot: options.repoRoot,
43228
+ userConfigDir: options.userConfigDir
43229
+ }).config;
43230
+ const configuredSeat = hostsConfig.seats?.[seatId];
43231
+ const defaultSeat = DEFAULT_ROLE_SEATS[seatId];
43232
+ const roleDef = getRole(seatId, {
43233
+ scope: "all",
43234
+ repoRoot: options.repoRoot,
43235
+ userConfigDir: options.userConfigDir
43236
+ })?.role ?? DEFAULT_ROLES[seatId];
43237
+ const primaryRoster = roleDef?.roster?.[0];
43238
+ let host = "pi";
43239
+ let source = "fallback";
43240
+ if (options.hostOverride) {
43241
+ host = options.hostOverride;
43242
+ source = "override";
43243
+ } else if (configuredSeat?.host) {
43244
+ host = configuredSeat.host;
43245
+ source = "role-hosts";
43246
+ } else if (defaultSeat?.defaultHost) {
43247
+ host = defaultSeat.defaultHost;
43248
+ source = "seat-default";
43249
+ } else if (primaryRoster?.harness) {
43250
+ host = primaryRoster.harness;
43251
+ source = "role-roster";
43252
+ }
43253
+ let model;
43254
+ if (options.modelOverride) {
43255
+ model = options.modelOverride;
43256
+ } else if (configuredSeat?.model) {
43257
+ model = configuredSeat.model;
43258
+ } else if (defaultSeat?.defaultModel) {
43259
+ model = defaultSeat.defaultModel;
43260
+ } else if (primaryRoster?.model) {
43261
+ model = primaryRoster.model;
43262
+ }
43263
+ const provider = hostsConfig.hostProviders?.[host] ?? roleDef?.roster?.find((r) => r.harness === host)?.provider;
43264
+ const effort = configuredSeat?.effort ?? roleDef?.roster?.find((r) => r.harness === host)?.effort;
43265
+ return {
43266
+ seatId,
43267
+ host,
43268
+ model,
43269
+ provider,
43270
+ effort,
43271
+ source
43272
+ };
43273
+ }
41888
43274
 
41889
43275
  // plugins/kxm/src/workflow-manager.ts
41890
- var import_yaml14 = __toESM(require_dist(), 1);
41891
- import { existsSync as existsSync27, mkdirSync as mkdirSync25, readdirSync as readdirSync10, readFileSync as readFileSync26, rmSync as rmSync9, writeFileSync as writeFileSync23 } from "node:fs";
41892
- import { join as join33 } from "node:path";
43276
+ var import_yaml15 = __toESM(require_dist(), 1);
43277
+ import { existsSync as existsSync29, mkdirSync as mkdirSync27, readdirSync as readdirSync10, readFileSync as readFileSync27, rmSync as rmSync9, writeFileSync as writeFileSync25 } from "node:fs";
43278
+ import { join as join35 } from "node:path";
41893
43279
  var WORKFLOW_TEMPLATES = {
41894
43280
  "implement-and-verify": {
41895
43281
  schema: "kxm.workflow.v1",
@@ -42040,22 +43426,22 @@ var WORKFLOW_TEMPLATES = {
42040
43426
  var DEFAULT_WORKFLOW_TEMPLATE = WORKFLOW_TEMPLATES["implement-and-verify"];
42041
43427
  function workflowsDirectory(scope, repoRoot3 = process.cwd(), userConfigDir) {
42042
43428
  if (scope === "global") {
42043
- return join33(userConfigDirectory(userConfigDir), "workflows");
43429
+ return join35(userConfigDirectory(userConfigDir), "workflows");
42044
43430
  }
42045
- return join33(repoConfigDirectory(repoRoot3), "workflows");
43431
+ return join35(repoConfigDirectory(repoRoot3), "workflows");
42046
43432
  }
42047
43433
  function ensureWorkflowsDirectory(scope, repoRoot3 = process.cwd(), userConfigDir) {
42048
43434
  const dir = workflowsDirectory(scope, repoRoot3, userConfigDir);
42049
- if (!existsSync27(dir)) {
42050
- mkdirSync25(dir, { recursive: true });
43435
+ if (!existsSync29(dir)) {
43436
+ mkdirSync27(dir, { recursive: true });
42051
43437
  }
42052
43438
  return dir;
42053
43439
  }
42054
43440
  function parseWorkflowFile(filePath) {
42055
- if (!existsSync27(filePath)) return void 0;
43441
+ if (!existsSync29(filePath)) return void 0;
42056
43442
  try {
42057
- const raw = readFileSync26(filePath, "utf8");
42058
- const parsed = (0, import_yaml14.parse)(raw);
43443
+ const raw = readFileSync27(filePath, "utf8");
43444
+ const parsed = (0, import_yaml15.parse)(raw);
42059
43445
  if (!parsed || typeof parsed !== "object") return void 0;
42060
43446
  return parsed;
42061
43447
  } catch {
@@ -42068,10 +43454,10 @@ function listWorkflowDefinitions(options = {}) {
42068
43454
  const globalDir = workflowsDirectory("global", repoRoot3, options.userConfigDir);
42069
43455
  const localDir = workflowsDirectory("local", repoRoot3, options.userConfigDir);
42070
43456
  const localDefs = /* @__PURE__ */ new Map();
42071
- if (scopeFilter !== "global" && existsSync27(localDir)) {
43457
+ if (scopeFilter !== "global" && existsSync29(localDir)) {
42072
43458
  for (const entry of readdirSync10(localDir)) {
42073
43459
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
42074
- const filePath = join33(localDir, entry);
43460
+ const filePath = join35(localDir, entry);
42075
43461
  const def = parseWorkflowFile(filePath);
42076
43462
  if (def) {
42077
43463
  const id = def.id || entry.replace(/\.ya?ml$/i, "");
@@ -42081,10 +43467,10 @@ function listWorkflowDefinitions(options = {}) {
42081
43467
  }
42082
43468
  }
42083
43469
  const globalDefs = /* @__PURE__ */ new Map();
42084
- if (scopeFilter !== "local" && existsSync27(globalDir)) {
43470
+ if (scopeFilter !== "local" && existsSync29(globalDir)) {
42085
43471
  for (const entry of readdirSync10(globalDir)) {
42086
43472
  if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
42087
- const filePath = join33(globalDir, entry);
43473
+ const filePath = join35(globalDir, entry);
42088
43474
  const def = parseWorkflowFile(filePath);
42089
43475
  if (def) {
42090
43476
  const id = def.id || entry.replace(/\.ya?ml$/i, "");
@@ -42140,13 +43526,13 @@ function getWorkflowDefinition(workflowId, options = {}) {
42140
43526
  const repoRoot3 = options.repoRoot ?? process.cwd();
42141
43527
  if (scope !== "global") {
42142
43528
  const localDir = workflowsDirectory("local", repoRoot3, options.userConfigDir);
42143
- const localFile = join33(localDir, `${workflowId}.yaml`);
43529
+ const localFile = join35(localDir, `${workflowId}.yaml`);
42144
43530
  const parsed = parseWorkflowFile(localFile);
42145
43531
  if (parsed) return { workflow: parsed, scope: "local", filePath: localFile };
42146
43532
  }
42147
43533
  if (scope !== "local") {
42148
43534
  const globalDir = workflowsDirectory("global", repoRoot3, options.userConfigDir);
42149
- const globalFile = join33(globalDir, `${workflowId}.yaml`);
43535
+ const globalFile = join35(globalDir, `${workflowId}.yaml`);
42150
43536
  const parsed = parseWorkflowFile(globalFile);
42151
43537
  if (parsed) return { workflow: parsed, scope: "global", filePath: globalFile };
42152
43538
  }
@@ -42156,20 +43542,20 @@ function addWorkflowDefinition(workflowId, content = DEFAULT_WORKFLOW_TEMPLATE,
42156
43542
  const scope = options.scope ?? "local";
42157
43543
  const repoRoot3 = options.repoRoot ?? process.cwd();
42158
43544
  const dir = ensureWorkflowsDirectory(scope, repoRoot3, options.userConfigDir);
42159
- const filePath = join33(dir, `${workflowId}.yaml`);
42160
- if (existsSync27(filePath) && !options.overwrite) {
43545
+ const filePath = join35(dir, `${workflowId}.yaml`);
43546
+ if (existsSync29(filePath) && !options.overwrite) {
42161
43547
  throw new Error(`workflow_already_exists: workflow '${workflowId}' already exists at ${filePath}`);
42162
43548
  }
42163
- const payload = typeof content === "string" ? content : (0, import_yaml14.stringify)(content);
42164
- writeFileSync23(filePath, payload, "utf8");
43549
+ const payload = typeof content === "string" ? content : (0, import_yaml15.stringify)(content);
43550
+ writeFileSync25(filePath, payload, "utf8");
42165
43551
  return { id: workflowId, filePath, scope };
42166
43552
  }
42167
43553
  function removeWorkflowDefinition(workflowId, options = {}) {
42168
43554
  const scope = options.scope ?? "local";
42169
43555
  const repoRoot3 = options.repoRoot ?? process.cwd();
42170
43556
  const dir = workflowsDirectory(scope, repoRoot3, options.userConfigDir);
42171
- const filePath = join33(dir, `${workflowId}.yaml`);
42172
- if (!existsSync27(filePath)) {
43557
+ const filePath = join35(dir, `${workflowId}.yaml`);
43558
+ if (!existsSync29(filePath)) {
42173
43559
  throw new Error(`workflow_not_found: workflow '${workflowId}' not found in ${scope} directory (${filePath})`);
42174
43560
  }
42175
43561
  rmSync9(filePath);
@@ -42187,14 +43573,14 @@ function modifyWorkflowDefinition(workflowId, updates, options = {}) {
42187
43573
  const scope = options.scope ?? target.scope;
42188
43574
  const repoRoot3 = options.repoRoot ?? process.cwd();
42189
43575
  const dir = ensureWorkflowsDirectory(scope, repoRoot3, options.userConfigDir);
42190
- const filePath = join33(dir, `${workflowId}.yaml`);
42191
- writeFileSync23(filePath, (0, import_yaml14.stringify)(updated), "utf8");
43576
+ const filePath = join35(dir, `${workflowId}.yaml`);
43577
+ writeFileSync25(filePath, (0, import_yaml15.stringify)(updated), "utf8");
42192
43578
  return { id: workflowId, workflow: updated, filePath, scope };
42193
43579
  }
42194
43580
 
42195
43581
  // plugins/kxm/src/cli.ts
42196
43582
  var CLI_NAME = "kxm";
42197
- var repoRoot2 = resolve21(fileURLToPath4(new URL("../../../", import.meta.url)));
43583
+ var repoRoot2 = resolve22(fileURLToPath4(new URL("../../../", import.meta.url)));
42198
43584
  var USAGE_ERROR_CODES = /* @__PURE__ */ new Set([
42199
43585
  "commander.help",
42200
43586
  "commander.helpDisplayed",
@@ -42210,7 +43596,7 @@ var USAGE_ERROR_CODES = /* @__PURE__ */ new Set([
42210
43596
  ]);
42211
43597
  function spawnScript(scriptName, extraEnv = {}) {
42212
43598
  return new Promise((resolveExit) => {
42213
- const child = spawn2(process.execPath, [join34(repoRoot2, "scripts", scriptName)], {
43599
+ const child = spawn2(process.execPath, [join36(repoRoot2, "scripts", scriptName)], {
42214
43600
  stdio: "inherit",
42215
43601
  env: { ...process.env, ...extraEnv }
42216
43602
  });
@@ -42294,16 +43680,16 @@ function redactCliValue(value, field = "") {
42294
43680
  return value;
42295
43681
  }
42296
43682
  function workspaceDirs(cwd, workspaceFlag, env) {
42297
- const workdir = resolve21(env.KXM_WORKDIR?.trim() || cwd);
42298
- const workspace = resolve21(workdir, workspaceFlag || env.KXM_WORKSPACE_DIR?.trim() || ".kxm");
43683
+ const workdir = resolve22(env.KXM_WORKDIR?.trim() || cwd);
43684
+ const workspace = resolve22(workdir, workspaceFlag || env.KXM_WORKSPACE_DIR?.trim() || ".kxm");
42299
43685
  const derive = workspaceFlag !== void 0;
42300
43686
  return {
42301
43687
  workdir,
42302
43688
  workspace,
42303
- config: derive ? join34(workspace, "config") : resolve21(workdir, env.KXM_CONFIG_DIR?.trim() || join34(workspace, "config")),
42304
- logs: derive ? join34(workspace, "logs") : resolve21(workdir, env.KXM_LOGS_DIR?.trim() || join34(workspace, "logs")),
42305
- assets: derive ? join34(workspace, "assets") : resolve21(workdir, env.KXM_ASSETS_DIR?.trim() || join34(workspace, "assets")),
42306
- state: derive ? join34(workspace, "state") : resolve21(workdir, env.KXM_STATE_DIR?.trim() || join34(workspace, "state"))
43689
+ config: derive ? join36(workspace, "config") : resolve22(workdir, env.KXM_CONFIG_DIR?.trim() || join36(workspace, "config")),
43690
+ logs: derive ? join36(workspace, "logs") : resolve22(workdir, env.KXM_LOGS_DIR?.trim() || join36(workspace, "logs")),
43691
+ assets: derive ? join36(workspace, "assets") : resolve22(workdir, env.KXM_ASSETS_DIR?.trim() || join36(workspace, "assets")),
43692
+ state: derive ? join36(workspace, "state") : resolve22(workdir, env.KXM_STATE_DIR?.trim() || join36(workspace, "state"))
42307
43693
  };
42308
43694
  }
42309
43695
  function maskEnvName(name) {
@@ -42363,8 +43749,8 @@ async function hubGet(url, fetchImpl) {
42363
43749
  }
42364
43750
  }
42365
43751
  function localWorkflowSnapshot(dataPath, runId) {
42366
- if (!existsSync28(dataPath)) throw new Error("state_database_not_found");
42367
- const database = new DatabaseSync5(dataPath, { readOnly: true });
43752
+ if (!existsSync30(dataPath)) throw new Error("state_database_not_found");
43753
+ const database = new DatabaseSync(dataPath, { readOnly: true });
42368
43754
  try {
42369
43755
  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();
42370
43756
  const runs = rows.map((row) => JSON.parse(row.record));
@@ -42469,7 +43855,7 @@ function activeWorkflowDefinition(runtime, definitionId) {
42469
43855
  let raw;
42470
43856
  if (file) {
42471
43857
  try {
42472
- raw = readFileSync27(resolve21(runtime.cwd, file), "utf8");
43858
+ raw = readFileSync28(resolve22(runtime.cwd, file), "utf8");
42473
43859
  } catch {
42474
43860
  throw new Error("workflow definition file is unavailable");
42475
43861
  }
@@ -42565,9 +43951,13 @@ async function cmdVnextInit(runtime, options) {
42565
43951
  return code;
42566
43952
  };
42567
43953
  if (initialized.action === "created") {
43954
+ await maybeOfferCompletionInstall(runtime);
43955
+ await maybeOfferGuideSetup(runtime);
42568
43956
  return finishInit(0, `initialized vNext project at ${initialized.projectRoot ?? runtime.cwd}`);
42569
43957
  }
42570
43958
  if (initialized.action === "joined") {
43959
+ await maybeOfferCompletionInstall(runtime);
43960
+ await maybeOfferGuideSetup(runtime);
42571
43961
  return finishInit(0, `joined vNext project at ${initialized.projectRoot ?? runtime.cwd}`);
42572
43962
  }
42573
43963
  if (initialized.action === "repaired") {
@@ -42736,7 +44126,7 @@ async function cmdBackup(runtime, options) {
42736
44126
  try {
42737
44127
  const { manifest, outDir } = createBackup({
42738
44128
  projectRoot: runtime.cwd,
42739
- ...options.out ? { outDir: resolve21(runtime.cwd, options.out) } : {}
44129
+ ...options.out ? { outDir: resolve22(runtime.cwd, options.out) } : {}
42740
44130
  });
42741
44131
  const payload = {
42742
44132
  ok: true,
@@ -42748,7 +44138,7 @@ async function cmdBackup(runtime, options) {
42748
44138
  const summary = [
42749
44139
  `Created SQLite backup with ${manifest.stores.length} store(s):`,
42750
44140
  ...manifest.stores.map((s) => ` - ${s.storeId}: ${s.sourcePath} -> ${s.backupFile} (schema v${s.schemaVersion}, ${s.bytes} bytes, sha256 ${s.sha256.slice(0, 12)}...)`),
42751
- `Manifest: ${join34(outDir, "manifest.json")}`
44141
+ `Manifest: ${join36(outDir, "manifest.json")}`
42752
44142
  ].join("\n");
42753
44143
  print(runtime.io, runtime.json, payload, summary);
42754
44144
  return 0;
@@ -42763,7 +44153,7 @@ async function cmdBackup(runtime, options) {
42763
44153
  }
42764
44154
  async function cmdRestore(runtime, manifestArg) {
42765
44155
  try {
42766
- const result = restoreBackup(resolve21(runtime.cwd, manifestArg), {
44156
+ const result = restoreBackup(resolve22(runtime.cwd, manifestArg), {
42767
44157
  projectRoot: runtime.cwd
42768
44158
  });
42769
44159
  const payload = {
@@ -43008,9 +44398,9 @@ async function cmdHarnessList(runtime) {
43008
44398
  function installProbeFrom(runtime) {
43009
44399
  const partial = runtime.io.installProbe ?? {};
43010
44400
  return {
43011
- moduleDir: partial.moduleDir ?? dirname19(fileURLToPath4(import.meta.url)),
44401
+ moduleDir: partial.moduleDir ?? dirname21(fileURLToPath4(import.meta.url)),
43012
44402
  repoRoot: partial.repoRoot ?? repoRoot2,
43013
- homeDir: partial.homeDir ?? homedir6(),
44403
+ homeDir: partial.homeDir ?? homedir7(),
43014
44404
  platform: partial.platform ?? process.platform,
43015
44405
  env: partial.env ?? runtime.env
43016
44406
  };
@@ -43039,9 +44429,9 @@ function npmGlobalRootFn(runtime) {
43039
44429
  };
43040
44430
  }
43041
44431
  function warnIgnoredProjectUpdateYaml(runtime) {
43042
- const projectFile = join34(runtime.dirs.workspace, "update.yaml");
43043
- if (!existsSync28(projectFile)) return;
43044
- const userFile = join34(vnextUserStateRoot({ env: runtime.env }), "update.yaml");
44432
+ const projectFile = join36(runtime.dirs.workspace, "update.yaml");
44433
+ if (!existsSync30(projectFile)) return;
44434
+ const userFile = join36(vnextUserStateRoot({ env: runtime.env }), "update.yaml");
43045
44435
  runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
43046
44436
  `);
43047
44437
  }
@@ -43070,7 +44460,7 @@ function applyKxmPackageUpdate(runtime, notice) {
43070
44460
  detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
43071
44461
  };
43072
44462
  }
43073
- const releaseDir = mkdtempSync2(join34(tmpdir2(), "kxm-pkg-update-"));
44463
+ const releaseDir = mkdtempSync2(join36(tmpdir2(), "kxm-pkg-update-"));
43074
44464
  try {
43075
44465
  const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset);
43076
44466
  if (runtime.dryRun) {
@@ -43079,7 +44469,7 @@ function applyKxmPackageUpdate(runtime, notice) {
43079
44469
  for (const step of planned) {
43080
44470
  if (step.kind === "verify") {
43081
44471
  if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
43082
- const actual = existsSync28(step.path) ? createHash15("sha256").update(readFileSync27(step.path)).digest("hex") : "missing";
44472
+ const actual = existsSync30(step.path) ? createHash16("sha256").update(readFileSync28(step.path)).digest("hex") : "missing";
43083
44473
  return {
43084
44474
  ok: false,
43085
44475
  error: "release_digest_mismatch",
@@ -43105,7 +44495,7 @@ async function selectProducerModel(runtime, requested) {
43105
44495
  if (runtime.json || !process.stdin.isTTY || models.length === 0) return void 0;
43106
44496
  runtime.io.stdout(models.slice(0, 100).map((model, index2) => `${index2 + 1}. ${model}`).join("\n") + "\nSelect model number: ");
43107
44497
  const rl = createInterface({ input: process.stdin, output: process.stderr });
43108
- const answer = await new Promise((resolve22) => rl.once("line", resolve22));
44498
+ const answer = await new Promise((resolve23) => rl.once("line", resolve23));
43109
44499
  rl.close();
43110
44500
  const index = Number.parseInt(answer.trim(), 10) - 1;
43111
44501
  return Number.isInteger(index) && index >= 0 && index < models.length ? models[index] : void 0;
@@ -43131,7 +44521,7 @@ async function cmdModelsScreen(runtime) {
43131
44521
  return 2;
43132
44522
  }
43133
44523
  const rl = createInterface({ input: process.stdin, output: process.stderr });
43134
- const ask = (q2) => new Promise((resolve22) => rl.question(q2, resolve22));
44524
+ const ask = (q2) => new Promise((resolve23) => rl.question(q2, resolve23));
43135
44525
  try {
43136
44526
  while (true) {
43137
44527
  const policy = loadProducerPolicy(runtime.dirs.workdir);
@@ -43373,13 +44763,13 @@ async function cmdValidate(runtime, fileFlag) {
43373
44763
  return 2;
43374
44764
  }
43375
44765
  const selectedFile = explicitFile || configuredFile;
43376
- const file = selectedFile ? resolve21(runtime.cwd, selectedFile) : void 0;
43377
- if (file && !existsSync28(file)) {
44766
+ const file = selectedFile ? resolve22(runtime.cwd, selectedFile) : void 0;
44767
+ if (file && !existsSync30(file)) {
43378
44768
  printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
43379
44769
  return 1;
43380
44770
  }
43381
44771
  try {
43382
- const raw = file ? readFileSync27(file, "utf8") : inline;
44772
+ const raw = file ? readFileSync28(file, "utf8") : inline;
43383
44773
  const warnings = [];
43384
44774
  const definitions = parseWorkflowDefinitions(raw, runtime.env, (message) => warnings.push(message));
43385
44775
  const secretEnvs = definitions.map((definition) => ({
@@ -43404,7 +44794,7 @@ async function cmdValidate(runtime, fileFlag) {
43404
44794
  }
43405
44795
  async function cmdArtifactsExist(runtime, pathFlag) {
43406
44796
  const worker = gateOf(runtime, "artifacts-exist");
43407
- const checked = verifyArtifactExists(runtime.dirs.assets, resolve21(runtime.cwd, pathFlag));
44797
+ const checked = verifyArtifactExists(runtime.dirs.assets, resolve22(runtime.cwd, pathFlag));
43408
44798
  if (!checked.ok) {
43409
44799
  printWorker(
43410
44800
  runtime,
@@ -43436,7 +44826,7 @@ async function cmdDash(runtime, options = {}) {
43436
44826
  return 2;
43437
44827
  }
43438
44828
  const screen = requested;
43439
- const dataPath = resolve21(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join34(runtime.dirs.state, "kxm.db"));
44829
+ const dataPath = resolve22(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join36(runtime.dirs.state, "kxm.db"));
43440
44830
  if (runtime.dryRun) {
43441
44831
  print(runtime.io, runtime.json, {
43442
44832
  ok: true,
@@ -43452,7 +44842,7 @@ async function cmdDash(runtime, options = {}) {
43452
44842
  runtime.io.stderr("kxm dash does not support --json; use kxm hub view\n");
43453
44843
  return 2;
43454
44844
  }
43455
- const project = runtime.env.KXM_PROJECT?.trim() || basename6(runtime.dirs.workdir) || "project";
44845
+ const project = runtime.env.KXM_PROJECT?.trim() || basename7(runtime.dirs.workdir) || "project";
43456
44846
  const authToken = runtime.env.KXM_AUTH_TOKEN?.trim();
43457
44847
  return await runMeshTui({
43458
44848
  serverUrl: runtime.serverUrl,
@@ -43482,7 +44872,7 @@ async function cmdHub(runtime) {
43482
44872
  config = loadKxmUpdateConfig(runtime.env);
43483
44873
  } catch (error) {
43484
44874
  if (error instanceof KxmUpdateConfigError) {
43485
- const yamlPath = join34(vnextUserStateRoot({ env: runtime.env }), "update.yaml");
44875
+ const yamlPath = join36(vnextUserStateRoot({ env: runtime.env }), "update.yaml");
43486
44876
  runtime.io.stderr(`kxm: ${error.message}; update check skipped; fix or remove ${yamlPath}
43487
44877
  `);
43488
44878
  } else {
@@ -43618,7 +45008,7 @@ async function cmdWorker(runtime, options) {
43618
45008
  return await (runtime.io.spawnWorker ?? ((launchEnv) => spawnScript("kxm-worker.mjs", launchEnv)))(extraEnv);
43619
45009
  }
43620
45010
  async function cmdStop(runtime, waitMsFlag) {
43621
- const pids = existsSync28(runtime.dirs.state) ? readdirSync11(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
45011
+ const pids = existsSync30(runtime.dirs.state) ? readdirSync11(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
43622
45012
  if (runtime.dryRun) {
43623
45013
  print(runtime.io, runtime.json, { ok: true, command: "stop", dryRun: true, pidFiles: pids }, "would signal pid files");
43624
45014
  return 0;
@@ -43630,16 +45020,30 @@ async function cmdStop(runtime, waitMsFlag) {
43630
45020
  const requested = [];
43631
45021
  const ignored = [];
43632
45022
  const records = /* @__PURE__ */ new Map();
45023
+ const orphans = [];
43633
45024
  for (const file of pids) {
43634
45025
  try {
43635
- const record = JSON.parse(readFileSync27(join34(runtime.dirs.state, file), "utf8"));
45026
+ const record = JSON.parse(readFileSync28(join36(runtime.dirs.state, file), "utf8"));
43636
45027
  const expectedControl = file === "hub.pid" ? "hub.stop" : file.startsWith("worker-") ? `${file.slice(0, -4)}.stop` : void 0;
43637
45028
  const expectedRole = file === "hub.pid" ? "hub" : file.startsWith("worker-") ? "worker" : void 0;
43638
- if (record.version !== 1 || !Number.isInteger(record.pid) || record.pid <= 0 || !record.startedAt || !expectedControl || record.controlFile !== expectedControl || record.role !== expectedRole || !processExists2(record.pid)) {
45029
+ if (record.version !== 1 || !Number.isInteger(record.pid) || record.pid <= 0 || !record.startedAt || !expectedControl || record.controlFile !== expectedControl || record.role !== expectedRole) {
45030
+ ignored.push(file);
45031
+ continue;
45032
+ }
45033
+ if (!processExists2(record.pid)) {
45034
+ if (file === "hub.pid" && Number.isInteger(record.serverPid) && record.serverPid > 0 && record.serverPid !== record.pid && processExists2(record.serverPid)) {
45035
+ try {
45036
+ process.kill(record.serverPid, "SIGTERM");
45037
+ } catch {
45038
+ }
45039
+ orphans.push(file);
45040
+ records.set(file, { pid: record.pid, startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {} });
45041
+ continue;
45042
+ }
43639
45043
  ignored.push(file);
43640
45044
  continue;
43641
45045
  }
43642
- writeFileSync24(join34(runtime.dirs.state, record.controlFile), `${JSON.stringify({ startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {}, requestedAt: (/* @__PURE__ */ new Date()).toISOString() })}
45046
+ writeFileSync26(join36(runtime.dirs.state, record.controlFile), `${JSON.stringify({ startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {}, requestedAt: (/* @__PURE__ */ new Date()).toISOString() })}
43643
45047
  `, { encoding: "utf8", mode: 384 });
43644
45048
  requested.push(file);
43645
45049
  records.set(file, { pid: record.pid, startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {} });
@@ -43647,7 +45051,7 @@ async function cmdStop(runtime, waitMsFlag) {
43647
45051
  ignored.push(file);
43648
45052
  }
43649
45053
  }
43650
- if (requested.length === 0) {
45054
+ if (requested.length === 0 && orphans.length === 0) {
43651
45055
  print(runtime.io, runtime.json, { ok: false, command: "stop", requested, ignored }, "no current managed processes found");
43652
45056
  return 1;
43653
45057
  }
@@ -43657,7 +45061,7 @@ async function cmdStop(runtime, waitMsFlag) {
43657
45061
  while (Date.now() <= deadline && stopped.size < requested.length) {
43658
45062
  for (const [file, record] of records) {
43659
45063
  try {
43660
- const current = JSON.parse(readFileSync27(join34(runtime.dirs.state, file), "utf8"));
45064
+ const current = JSON.parse(readFileSync28(join36(runtime.dirs.state, file), "utf8"));
43661
45065
  if (current.pid !== record.pid || current.startedAt !== record.startedAt || current.generation !== record.generation || !processExists2(record.pid)) stopped.add(file);
43662
45066
  } catch {
43663
45067
  stopped.add(file);
@@ -43666,17 +45070,41 @@ async function cmdStop(runtime, waitMsFlag) {
43666
45070
  if (stopped.size < requested.length) await (runtime.io.sleep ?? ((ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms))))(100);
43667
45071
  }
43668
45072
  const timedOut = requested.filter((file) => !stopped.has(file));
45073
+ const deadlineOrphans = Date.now() + Math.min(1e4, waitMs);
45074
+ for (const file of orphans) {
45075
+ while (Date.now() <= deadlineOrphans) {
45076
+ try {
45077
+ const current = JSON.parse(readFileSync28(join36(runtime.dirs.state, file), "utf8"));
45078
+ if (!Number.isInteger(current.serverPid) || !processExists2(current.serverPid)) break;
45079
+ } catch {
45080
+ break;
45081
+ }
45082
+ await (runtime.io.sleep ?? ((ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms))))(100);
45083
+ }
45084
+ try {
45085
+ const current = JSON.parse(readFileSync28(join36(runtime.dirs.state, file), "utf8"));
45086
+ if (Number.isInteger(current.serverPid) && processExists2(current.serverPid)) {
45087
+ try {
45088
+ process.kill(current.serverPid, "SIGKILL");
45089
+ } catch {
45090
+ }
45091
+ }
45092
+ rmSync10(join36(runtime.dirs.state, file), { force: true });
45093
+ } catch {
45094
+ }
45095
+ stopped.add(file);
45096
+ }
43669
45097
  const ok = timedOut.length === 0;
43670
- print(runtime.io, runtime.json, { ok, command: "stop", requested, stopped: [...stopped], timedOut, ignored }, ok ? "managed processes stopped" : "stop request timed out");
45098
+ print(runtime.io, runtime.json, { ok, command: "stop", requested, stopped: [...stopped], timedOut, ...orphans.length ? { orphans } : {}, ignored }, ok ? "managed processes stopped" : "stop request timed out");
43671
45099
  return ok ? 0 : 1;
43672
45100
  }
43673
45101
  async function cmdSessionStatus(runtime) {
43674
45102
  const stateDir = runtime.dirs.state;
43675
- const names3 = existsSync28(stateDir) ? readdirSync11(stateDir) : [];
45103
+ const names3 = existsSync30(stateDir) ? readdirSync11(stateDir) : [];
43676
45104
  const claims = [];
43677
45105
  for (const file of names3.filter((name) => name.endsWith(".pid"))) {
43678
45106
  try {
43679
- const record = JSON.parse(readFileSync27(join34(stateDir, file), "utf8"));
45107
+ const record = JSON.parse(readFileSync28(join36(stateDir, file), "utf8"));
43680
45108
  claims.push({
43681
45109
  file,
43682
45110
  role: record.role,
@@ -43691,7 +45119,7 @@ async function cmdSessionStatus(runtime) {
43691
45119
  const recoveries = [];
43692
45120
  for (const file of names3.filter((name) => name.startsWith("worker-recovery-") && name.endsWith(".json"))) {
43693
45121
  try {
43694
- const envelope = JSON.parse(readFileSync27(join34(stateDir, file), "utf8"));
45122
+ const envelope = JSON.parse(readFileSync28(join36(stateDir, file), "utf8"));
43695
45123
  recoveries.push({
43696
45124
  file,
43697
45125
  reason: envelope.reason,
@@ -43796,7 +45224,7 @@ async function cmdSessionBrief(runtime, options = {}) {
43796
45224
  print(runtime.io, runtime.json, { ok: true, command: "session brief", sessionToken }, sessionToken);
43797
45225
  return 0;
43798
45226
  }
43799
- const dataPath = resolve21(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join34(runtime.dirs.state, "kxm.db"));
45227
+ const dataPath = resolve22(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join36(runtime.dirs.state, "kxm.db"));
43800
45228
  const env = {
43801
45229
  ...runtime.env,
43802
45230
  KXM_STATE_DIR: runtime.dirs.state,
@@ -43835,7 +45263,7 @@ Session token: ${sessionToken}
43835
45263
  return 0;
43836
45264
  }
43837
45265
  async function cmdSessionStart(runtime, options) {
43838
- const id = options.id?.trim() || `session_${randomUUID11().replaceAll("-", "").slice(0, 12)}`;
45266
+ const id = options.id?.trim() || `session_${randomUUID12().replaceAll("-", "").slice(0, 12)}`;
43839
45267
  const workflowId = options.workflow?.trim();
43840
45268
  const mix = options.mix?.trim();
43841
45269
  if (workflowId && mix) {
@@ -43872,7 +45300,7 @@ async function cmdSessionStart(runtime, options) {
43872
45300
  ...workflowId ? workflowAssetDirs(runtime.dirs.assets, workflowId) : []
43873
45301
  ];
43874
45302
  if (!runtime.dryRun) {
43875
- for (const directory of created) mkdirSync26(directory, { recursive: true });
45303
+ for (const directory of created) mkdirSync28(directory, { recursive: true });
43876
45304
  writeSession(runtime.dirs.assets, session);
43877
45305
  }
43878
45306
  print(runtime.io, runtime.json, {
@@ -43885,15 +45313,15 @@ async function cmdSessionStart(runtime, options) {
43885
45313
  return 0;
43886
45314
  }
43887
45315
  async function cmdImprove(runtime, options = {}) {
43888
- const file = options.file ? resolve21(runtime.cwd, options.file) : telemetryPath(runtime.dirs.logs);
43889
- const routingRecords = existsSync28(file) ? readRoutingRecords(file).map((entry) => entry.routing) : [];
43890
- const candidatesDir = options.outDir ? resolve21(runtime.cwd, options.outDir) : join34(runtime.cwd, ".kxm", "candidates");
45316
+ const file = options.file ? resolve22(runtime.cwd, options.file) : telemetryPath(runtime.dirs.logs);
45317
+ const routingRecords = existsSync30(file) ? readRoutingRecords(file).map((entry) => entry.routing) : [];
45318
+ const candidatesDir = options.outDir ? resolve22(runtime.cwd, options.outDir) : join36(runtime.cwd, ".kxm", "candidates");
43891
45319
  const report2 = buildImprovementReport(routingRecords, {
43892
45320
  candidatesDir,
43893
45321
  projectRoot: runtime.cwd,
43894
45322
  dryRun: runtime.dryRun
43895
45323
  });
43896
- const reportDir = join34(runtime.dirs.assets, "improvements");
45324
+ const reportDir = join36(runtime.dirs.assets, "improvements");
43897
45325
  const reportPath = writeImprovementReport(reportDir, report2, runtime.dryRun);
43898
45326
  const text = formatImprovementReport(report2);
43899
45327
  print(runtime.io, runtime.json, {
@@ -44052,7 +45480,7 @@ function skillStateFromFlag(value) {
44052
45480
  throw new Error(`invalid skill state ${value}`);
44053
45481
  }
44054
45482
  function skillsRoot(runtime) {
44055
- return join34(runtime.dirs.workdir, ".kxm", "skills");
45483
+ return join36(runtime.dirs.workdir, ".kxm", "skills");
44056
45484
  }
44057
45485
  function csv(value) {
44058
45486
  if (!value) return void 0;
@@ -44069,7 +45497,7 @@ async function cmdSkillsCreate(runtime, options) {
44069
45497
  const metadata = lifecycle.create({
44070
45498
  name: options.name,
44071
45499
  description: options.description ?? "",
44072
- content: readFileSync27(options.file, "utf8"),
45500
+ content: readFileSync28(options.file, "utf8"),
44073
45501
  createdBy: options.createdBy,
44074
45502
  sources: {
44075
45503
  runIds: csv(options.run) ?? [],
@@ -44295,6 +45723,177 @@ async function cmdCompletion(runtime, shell) {
44295
45723
  return 1;
44296
45724
  }
44297
45725
  }
45726
+ async function cmdCompletionInstall(runtime, options) {
45727
+ const installOptions = {
45728
+ env: runtime.env,
45729
+ configDir: runtime.env.KXM_USER_CONFIG_DIR,
45730
+ platform: process.platform,
45731
+ dryRun: runtime.dryRun
45732
+ };
45733
+ const report2 = installShellCompletion(options.shell, installOptions);
45734
+ const pathReport = options.path === false ? void 0 : installPathEntry(options.shell, installOptions);
45735
+ if (!report2.ok) {
45736
+ print(runtime.io, runtime.json, {
45737
+ ok: false,
45738
+ command: "completion install",
45739
+ error: report2.reason ?? "shell_not_detected"
45740
+ }, "could not detect your shell; pass --shell bash, zsh, or fish");
45741
+ return 1;
45742
+ }
45743
+ const shellNote = report2.shell;
45744
+ const applyNote = runtime.dryRun ? "planned; rerun without --dry-run to apply" : report2.alreadyInstalled ? "already installed" : "installed; restart your shell or open a new terminal to activate";
45745
+ const pathNote = pathReport ? pathReport.ok ? runtime.dryRun ? `; PATH entry for ${pathReport.binDir} planned` : pathReport.alreadyInstalled ? "; kxm already on PATH" : `; PATH entry for ${pathReport.binDir} added to ${pathReport.rcFile}` : void 0 : void 0;
45746
+ print(runtime.io, runtime.json, {
45747
+ ok: true,
45748
+ command: "completion install",
45749
+ shell: shellNote,
45750
+ scriptPath: report2.scriptPath,
45751
+ ...report2.rcFile ? { rcFile: report2.rcFile } : {},
45752
+ rcModified: report2.rcModified,
45753
+ alreadyInstalled: report2.alreadyInstalled,
45754
+ ...pathReport ? { path: pathReport } : {},
45755
+ dryRun: runtime.dryRun === true
45756
+ }, `kxm ${shellNote} completion: ${applyNote}${report2.rcFile ? ` (rc: ${report2.rcFile})` : ""}${pathNote ?? ""}`);
45757
+ return 0;
45758
+ }
45759
+ async function maybeOfferCompletionInstall(runtime) {
45760
+ if (runtime.json || runtime.dryRun) return;
45761
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
45762
+ if (runtime.env.KXM_SKIP_COMPLETION_PROMPT?.trim()) return;
45763
+ const shell = detectShell(runtime.env, process.platform);
45764
+ if (shell !== "bash" && shell !== "zsh" && shell !== "fish") return;
45765
+ const binDir = kxmBinDir(runtime.env);
45766
+ const pathNeeded = shell !== "fish" && binDir && !(runtime.env.PATH ?? "").split(":").includes(binDir);
45767
+ if (!pathNeeded) {
45768
+ const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
45769
+ const { rcFile } = completionRcTarget(shell, { env: runtime.env });
45770
+ if (rcFile && existsSync30(rcFile)) {
45771
+ try {
45772
+ if (readFileSync28(rcFile, "utf8").includes(scriptPath)) return;
45773
+ } catch {
45774
+ }
45775
+ }
45776
+ }
45777
+ const question = pathNeeded ? `
45778
+ Install ${shell} tab completion for kxm and add ${binDir} to PATH? [Y/n] ` : `
45779
+ Install ${shell} tab completion for kxm? [y/N] `;
45780
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
45781
+ await new Promise((resolvePrompt) => {
45782
+ rl.question(question, (answer) => {
45783
+ rl.close();
45784
+ const trimmed = answer.trim().toLowerCase();
45785
+ const accept = pathNeeded ? trimmed !== "n" && trimmed !== "no" : trimmed === "y" || trimmed === "yes";
45786
+ if (accept) {
45787
+ try {
45788
+ const report2 = installShellCompletion(shell, {
45789
+ env: runtime.env,
45790
+ configDir: runtime.env.KXM_USER_CONFIG_DIR,
45791
+ platform: process.platform
45792
+ });
45793
+ if (report2.ok) {
45794
+ runtime.io.stdout(`completion installed for ${report2.shell}`);
45795
+ } else {
45796
+ runtime.io.stdout(`completion install skipped: ${report2.reason ?? "unknown"}
45797
+ `);
45798
+ resolvePrompt();
45799
+ return;
45800
+ }
45801
+ } catch {
45802
+ runtime.io.stdout("completion install skipped: local filesystem operation did not complete\n");
45803
+ resolvePrompt();
45804
+ return;
45805
+ }
45806
+ if (pathNeeded && binDir) {
45807
+ try {
45808
+ const pathReport = installPathEntry(shell, {
45809
+ env: runtime.env,
45810
+ configDir: runtime.env.KXM_USER_CONFIG_DIR,
45811
+ platform: process.platform,
45812
+ binDir
45813
+ });
45814
+ runtime.io.stdout(pathReport.ok && pathReport.rcModified ? `; ${binDir} added to PATH in ${pathReport.rcFile}` : "; kxm already on PATH");
45815
+ } catch {
45816
+ runtime.io.stdout("; PATH entry skipped: local filesystem operation did not complete");
45817
+ }
45818
+ }
45819
+ runtime.io.stdout("; restart your shell or open a new terminal to activate\n");
45820
+ } else {
45821
+ runtime.io.stdout("skipped; run `kxm completion install` anytime\n");
45822
+ }
45823
+ resolvePrompt();
45824
+ });
45825
+ });
45826
+ }
45827
+ var GUIDE_SETUP_OPT_OUT_ENV = "KXM_SKIP_GUIDE_SETUP_PROMPT";
45828
+ async function askYesNo(question) {
45829
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
45830
+ return new Promise((resolvePrompt) => {
45831
+ rl.question(question, (answer) => {
45832
+ rl.close();
45833
+ const trimmed = answer.trim().toLowerCase();
45834
+ resolvePrompt(trimmed === "y" || trimmed === "yes");
45835
+ });
45836
+ });
45837
+ }
45838
+ async function askLine(question) {
45839
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
45840
+ return new Promise((resolvePrompt) => {
45841
+ rl.question(question, (answer) => {
45842
+ rl.close();
45843
+ resolvePrompt(answer);
45844
+ });
45845
+ });
45846
+ }
45847
+ async function maybeOfferGuideSetup(runtime) {
45848
+ if (runtime.json || runtime.dryRun) return;
45849
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
45850
+ if (runtime.env[GUIDE_SETUP_OPT_OUT_ENV]?.trim()) return;
45851
+ let inventory;
45852
+ try {
45853
+ inventory = probeHarnesses({ env: runtime.env });
45854
+ } catch {
45855
+ return;
45856
+ }
45857
+ const authenticated = inventory.harnesses.filter((entry) => entry.detected && entry.authenticated === true);
45858
+ if (authenticated.length === 0) {
45859
+ runtime.io.stdout("no authenticated harnesses detected; skipping workflow-guide setup (see `kxm harness list`)\n");
45860
+ return;
45861
+ }
45862
+ const harnessList = authenticated.map((entry) => entry.id).join(", ");
45863
+ const accept = await askYesNo(`
45864
+ Set up workflow-guide agents and workflows for authenticated harnesses (${harnessList})? [y/N] `);
45865
+ if (!accept) {
45866
+ runtime.io.stdout(`skipped; set ${GUIDE_SETUP_OPT_OUT_ENV}=1 to suppress this offer, or re-run on a fresh project
45867
+ `);
45868
+ return;
45869
+ }
45870
+ const lines = ["", "Workflow-guide software-engineering workflows (docs/workflow-guide.md):"];
45871
+ GUIDE_WORKFLOWS.forEach((workflow, index) => {
45872
+ lines.push(` ${index + 1}) ${workflow.slug.padEnd(32)} ${workflow.summary}`);
45873
+ });
45874
+ runtime.io.stdout(`${lines.join("\n")}
45875
+ `);
45876
+ const answer = await askLine("Install which workflows? (numbers or slugs, comma-separated, 'all', or 'none'): ");
45877
+ const selected = parseGuideSelection(answer);
45878
+ if (selected.length === 0) {
45879
+ runtime.io.stdout("no workflows selected; nothing written\n");
45880
+ return;
45881
+ }
45882
+ const plan = planGuideSetup({ inventory, selected });
45883
+ const files = renderGuideSetupFiles(runtime.cwd, plan);
45884
+ const report2 = writeGuideSetupFiles(files);
45885
+ for (const file of report2.written) runtime.io.stdout(`wrote ${file}
45886
+ `);
45887
+ for (const file of report2.existed) runtime.io.stdout(`kept existing ${file} (not overwritten)
45888
+ `);
45889
+ for (const skip of plan.skipped) {
45890
+ runtime.io.stdout(`skipped ${skip.workflow}/${skip.role}: ${skip.reason}
45891
+ `);
45892
+ }
45893
+ if (report2.written.length > 0) {
45894
+ runtime.io.stdout("inspect with `kxm workflow definitions`; guide candidates are dated research \u2014 verify before dispatch\n");
45895
+ }
45896
+ }
44298
45897
  async function cmdSuggest(runtime, promptParts) {
44299
45898
  try {
44300
45899
  const prompt = promptParts.join(" ").trim();
@@ -44474,12 +46073,12 @@ async function cmdStudioLayout(runtime, workflowPath) {
44474
46073
  const projectRoot = discoverVnextProjectRoot(runtime.cwd) ?? runtime.cwd;
44475
46074
  let filePath = workflowPath;
44476
46075
  if (!filePath) {
44477
- filePath = resolve21(projectRoot, ".kxm", "workflows", "default.yaml");
46076
+ filePath = resolve22(projectRoot, ".kxm", "workflows", "default.yaml");
44478
46077
  }
44479
46078
  let yamlContent;
44480
46079
  let workflowId = "default";
44481
- if (existsSync28(filePath)) {
44482
- yamlContent = readFileSync27(filePath, "utf8");
46080
+ if (existsSync30(filePath)) {
46081
+ yamlContent = readFileSync28(filePath, "utf8");
44483
46082
  } else {
44484
46083
  yamlContent = `schema: kxm.workflow.v1
44485
46084
  description: Feature implementation workflow
@@ -44519,7 +46118,7 @@ steps:
44519
46118
  maxTransitions: 2
44520
46119
  `;
44521
46120
  }
44522
- const parsedYaml = (0, import_yaml15.parse)(yamlContent);
46121
+ const parsedYaml = (0, import_yaml16.parse)(yamlContent);
44523
46122
  const plan = compileVnextWorkflow({ id: workflowId, value: parsedYaml });
44524
46123
  const layout = generateStudioLayout(plan);
44525
46124
  print(runtime.io, runtime.json, { ok: true, command: "studio layout", layout }, JSON.stringify(layout, null, 2));
@@ -44552,17 +46151,17 @@ async function cmdStudioServe(runtime, options) {
44552
46151
  sessionToken,
44553
46152
  planProvider: () => {
44554
46153
  try {
44555
- const wfDir = join34(runtime.cwd, ".kxm", "workflows");
44556
- const defaultPath = join34(wfDir, "default.yaml");
44557
- let targetPath = existsSync28(defaultPath) ? defaultPath : void 0;
44558
- if (!targetPath && existsSync28(wfDir)) {
46154
+ const wfDir = join36(runtime.cwd, ".kxm", "workflows");
46155
+ const defaultPath = join36(wfDir, "default.yaml");
46156
+ let targetPath = existsSync30(defaultPath) ? defaultPath : void 0;
46157
+ if (!targetPath && existsSync30(wfDir)) {
44559
46158
  const yml = readdirSync11(wfDir).find((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
44560
- if (yml) targetPath = join34(wfDir, yml);
46159
+ if (yml) targetPath = join36(wfDir, yml);
44561
46160
  }
44562
- if (targetPath && existsSync28(targetPath)) {
44563
- const raw = readFileSync27(targetPath, "utf8");
44564
- const parsed = (0, import_yaml15.parse)(raw);
44565
- const wfId = basename6(targetPath).replace(/\.(yaml|yml)$/, "");
46161
+ if (targetPath && existsSync30(targetPath)) {
46162
+ const raw = readFileSync28(targetPath, "utf8");
46163
+ const parsed = (0, import_yaml16.parse)(raw);
46164
+ const wfId = basename7(targetPath).replace(/\.(yaml|yml)$/, "");
44566
46165
  return compileVnextWorkflow({ id: wfId, value: parsed });
44567
46166
  }
44568
46167
  } catch {
@@ -44647,7 +46246,7 @@ async function cmdRoleGet(runtime, roleId, options) {
44647
46246
  runtime.io,
44648
46247
  runtime.json,
44649
46248
  { ok: true, command: "role get", roleId, scope: result.scope, filePath: result.filePath, role: result.role },
44650
- (0, import_yaml15.stringify)(result.role)
46249
+ (0, import_yaml16.stringify)(result.role)
44651
46250
  );
44652
46251
  return 0;
44653
46252
  }
@@ -44758,9 +46357,9 @@ async function cmdRoleAdd(runtime, roleId, options) {
44758
46357
  }
44759
46358
  let roleDef;
44760
46359
  if (options.file) {
44761
- const filePath = resolve21(runtime.cwd, options.file);
44762
- const content = readFileSync27(filePath, "utf8");
44763
- roleDef = (0, import_yaml15.parse)(content);
46360
+ const filePath = resolve22(runtime.cwd, options.file);
46361
+ const content = readFileSync28(filePath, "utf8");
46362
+ roleDef = (0, import_yaml16.parse)(content);
44764
46363
  roleDef.id = roleId;
44765
46364
  } else {
44766
46365
  const skills = options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : [];
@@ -44921,6 +46520,203 @@ async function cmdRoleModify(runtime, roleId, options) {
44921
46520
  return 1;
44922
46521
  }
44923
46522
  }
46523
+ async function cmdRoleHosts(runtime, options = {}) {
46524
+ const hostsConfig = loadRoleHostsConfig({
46525
+ scope: options.scope,
46526
+ repoRoot: runtime.cwd,
46527
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
46528
+ });
46529
+ const seatIds = Array.from(
46530
+ /* @__PURE__ */ new Set([
46531
+ ...Object.keys(DEFAULT_ROLE_SEATS),
46532
+ ...Object.keys(hostsConfig.config.seats ?? {})
46533
+ ])
46534
+ ).sort();
46535
+ const seats = seatIds.map((seatId) => {
46536
+ const resolved = resolveRoleSeat(seatId, {
46537
+ repoRoot: runtime.cwd,
46538
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
46539
+ });
46540
+ return {
46541
+ seatId,
46542
+ host: resolved.host,
46543
+ model: resolved.model,
46544
+ provider: resolved.provider,
46545
+ effort: resolved.effort,
46546
+ source: resolved.source,
46547
+ configuredHost: hostsConfig.config.seats?.[seatId]?.host,
46548
+ configuredModel: hostsConfig.config.seats?.[seatId]?.model
46549
+ };
46550
+ });
46551
+ if (runtime.json) {
46552
+ print(
46553
+ runtime.io,
46554
+ true,
46555
+ {
46556
+ ok: true,
46557
+ command: "role hosts",
46558
+ scope: hostsConfig.scope,
46559
+ filePath: hostsConfig.filePath,
46560
+ seats,
46561
+ hostProviders: hostsConfig.config.hostProviders ?? {}
46562
+ },
46563
+ ""
46564
+ );
46565
+ return 0;
46566
+ }
46567
+ const lines = [
46568
+ `ROLE SEATS (${hostsConfig.scope}${hostsConfig.filePath ? ` at ${hostsConfig.filePath}` : ""}):`
46569
+ ];
46570
+ for (const s of seats) {
46571
+ const modelStr = s.model ? ` [${s.model}]` : "";
46572
+ const sourceTag = `(via ${s.source})`;
46573
+ lines.push(` ${s.seatId.padEnd(16)} -> host: ${s.host.padEnd(12)} ${modelStr.padEnd(30)} ${sourceTag}`);
46574
+ }
46575
+ if (hostsConfig.config.hostProviders && Object.keys(hostsConfig.config.hostProviders).length > 0) {
46576
+ lines.push("\nHOST PROVIDERS:");
46577
+ for (const [h, p] of Object.entries(hostsConfig.config.hostProviders)) {
46578
+ lines.push(` ${h.padEnd(16)} -> provider: ${p}`);
46579
+ }
46580
+ }
46581
+ print(runtime.io, false, {}, `${lines.join("\n")}
46582
+ `);
46583
+ return 0;
46584
+ }
46585
+ async function cmdRoleSetHost(runtime, seatId, host, options = {}) {
46586
+ if (!seatId || !host) {
46587
+ runtime.io.stderr("kxm role set-host requires <seatId> and <host>\n");
46588
+ return 1;
46589
+ }
46590
+ try {
46591
+ const result = setRoleSeatHost(seatId, host, {
46592
+ model: options.model,
46593
+ effort: options.effort,
46594
+ scope: options.scope ?? "local",
46595
+ repoRoot: runtime.cwd,
46596
+ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR
46597
+ });
46598
+ print(
46599
+ runtime.io,
46600
+ runtime.json,
46601
+ {
46602
+ ok: true,
46603
+ command: "role set-host",
46604
+ seatId,
46605
+ host,
46606
+ binding: result.binding,
46607
+ filePath: result.filePath,
46608
+ scope: result.scope
46609
+ },
46610
+ `Bound seat '${seatId}' to host '${host}' in ${result.filePath}
46611
+ `
46612
+ );
46613
+ return 0;
46614
+ } catch (err) {
46615
+ runtime.io.stderr(`role set-host failed: ${err.message}
46616
+ `);
46617
+ return 1;
46618
+ }
46619
+ }
46620
+ async function cmdRoleResume(runtime, runId, ruling) {
46621
+ if (!runId) {
46622
+ runtime.io.stderr("kxm role resume requires <runId>\n");
46623
+ return 1;
46624
+ }
46625
+ const effectiveRuling = ruling?.trim() || "operator_ruling: waived and resumed";
46626
+ const projectRoot = discoverVnextProjectRoot(runtime.cwd);
46627
+ if (projectRoot && /^run_[a-f0-9]{32}$/i.test(runId)) {
46628
+ if (runtime.dryRun) {
46629
+ print(runtime.io, runtime.json, { ok: true, command: "role resume", runId, ruling: effectiveRuling }, `would resume vNext run ${runId}`);
46630
+ return 0;
46631
+ }
46632
+ try {
46633
+ const supervisor = await ensureVnextSupervisor({ env: runtime.env });
46634
+ const posted = await vnextRuntimeRequest(
46635
+ supervisor,
46636
+ "POST",
46637
+ `/v1/runs/${encodeURIComponent(runId)}/signal?projectRoot=${encodeURIComponent(projectRoot)}`,
46638
+ {
46639
+ signalKey: "audit_escalation",
46640
+ status: "passed",
46641
+ summary: effectiveRuling,
46642
+ action: "unblock"
46643
+ }
46644
+ );
46645
+ print(
46646
+ runtime.io,
46647
+ runtime.json,
46648
+ { ok: true, command: "role resume", runId, ruling: effectiveRuling, unblocked: posted.unblocked === true },
46649
+ `Resumed vNext run ${runId} with ruling: ${effectiveRuling}
46650
+ `
46651
+ );
46652
+ return 0;
46653
+ } catch (error) {
46654
+ const msg = error instanceof Error ? error.message : "resume_failed";
46655
+ print(runtime.io, runtime.json, { ok: false, command: "role resume", error: "resume_failed", detail: msg }, `resume vNext run failed: ${msg}
46656
+ `);
46657
+ return 1;
46658
+ }
46659
+ }
46660
+ const dbPath = join36(runtime.cwd, ".kxm", "state", "kxm.db");
46661
+ if (existsSync30(dbPath)) {
46662
+ try {
46663
+ const database = new DatabaseSync(dbPath);
46664
+ try {
46665
+ const row = database.prepare("SELECT record FROM workflow_runs WHERE id = ?").get(runId);
46666
+ if (!row) {
46667
+ runtime.io.stderr(`kxm: workflow run '${runId}' not found in ${dbPath}
46668
+ `);
46669
+ return 1;
46670
+ }
46671
+ const run = JSON.parse(row.record);
46672
+ const now = (/* @__PURE__ */ new Date()).toISOString();
46673
+ const resumeResult = resumeWorkflowFromRuling(run, effectiveRuling, now);
46674
+ database.prepare("UPDATE workflow_runs SET record = ? WHERE id = ?").run(
46675
+ JSON.stringify(resumeResult.run),
46676
+ runId
46677
+ );
46678
+ const journalId = randomUUID12();
46679
+ const journalPayload = {
46680
+ id: journalId,
46681
+ runId,
46682
+ stageId: resumeResult.stageId,
46683
+ category: "decision",
46684
+ area: "workflow",
46685
+ summary: `Role resume ruling: ${effectiveRuling}`,
46686
+ details: { ruling: effectiveRuling },
46687
+ evidence: {},
46688
+ createdAt: now
46689
+ };
46690
+ database.prepare(
46691
+ "INSERT INTO workflow_journal (id, run_id, category, area, record) VALUES (?, ?, ?, ?, ?)"
46692
+ ).run(
46693
+ journalId,
46694
+ runId,
46695
+ "decision",
46696
+ "workflow",
46697
+ JSON.stringify(journalPayload)
46698
+ );
46699
+ print(
46700
+ runtime.io,
46701
+ runtime.json,
46702
+ { ok: true, command: "role resume", runId, stageId: resumeResult.stageId, ruling: effectiveRuling, status: resumeResult.run.status },
46703
+ `Resumed workflow run ${runId} (stage: ${resumeResult.stageId}) with ruling: ${effectiveRuling}
46704
+ `
46705
+ );
46706
+ return 0;
46707
+ } finally {
46708
+ database.close();
46709
+ }
46710
+ } catch (err) {
46711
+ runtime.io.stderr(`role resume failed: ${err.message}
46712
+ `);
46713
+ return 1;
46714
+ }
46715
+ }
46716
+ runtime.io.stderr(`kxm: no active run store or database found for run '${runId}'
46717
+ `);
46718
+ return 1;
46719
+ }
44924
46720
  async function cmdWorkflowDefinitions(runtime, options) {
44925
46721
  const workflows = listWorkflowDefinitions({
44926
46722
  scope: options.scope,
@@ -44980,8 +46776,8 @@ async function cmdWorkflowAdd(runtime, workflowId, options) {
44980
46776
  }
44981
46777
  }
44982
46778
  if (options.file) {
44983
- const filePath = resolve21(runtime.cwd, options.file);
44984
- content = readFileSync27(filePath, "utf8");
46779
+ const filePath = resolve22(runtime.cwd, options.file);
46780
+ content = readFileSync28(filePath, "utf8");
44985
46781
  } else if (!content) {
44986
46782
  content = {
44987
46783
  schema: "kxm.workflow.v1",
@@ -45125,7 +46921,7 @@ async function cmdRoutingReport(runtime, options) {
45125
46921
  let catalog;
45126
46922
  if (includeEquivalentListCost) {
45127
46923
  try {
45128
- const pricesPath = options.prices ? resolve21(runtime.cwd, options.prices) : join34(runtime.dirs.workspace, "prices.yaml");
46924
+ const pricesPath = options.prices ? resolve22(runtime.cwd, options.prices) : join36(runtime.dirs.workspace, "prices.yaml");
45129
46925
  catalog = loadPriceCatalog(pricesPath);
45130
46926
  } catch {
45131
46927
  }
@@ -45208,7 +47004,7 @@ async function cmdRoutingBenchmark(runtime, options) {
45208
47004
  }
45209
47005
  async function cmdWorkflowStart(runtime, definitionIdArg, options) {
45210
47006
  const definitionId = definitionIdArg || runtime.env.KXM_WORKFLOW_ID?.trim();
45211
- const deliveryId = String(options.deliveryId || `cli-${randomUUID11()}`);
47007
+ const deliveryId = String(options.deliveryId || `cli-${randomUUID12()}`);
45212
47008
  const event = options.event;
45213
47009
  const payloadFlag = options.payload ?? "{}";
45214
47010
  if (!definitionId) {
@@ -45227,7 +47023,7 @@ async function cmdWorkflowStart(runtime, definitionIdArg, options) {
45227
47023
  }
45228
47024
  let payload;
45229
47025
  try {
45230
- const raw = payloadFlag.startsWith("@") ? readFileSync27(resolve21(runtime.cwd, payloadFlag.slice(1)), "utf8") : payloadFlag;
47026
+ const raw = payloadFlag.startsWith("@") ? readFileSync28(resolve22(runtime.cwd, payloadFlag.slice(1)), "utf8") : payloadFlag;
45231
47027
  const value = JSON.parse(raw);
45232
47028
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("object required");
45233
47029
  payload = value;
@@ -45294,7 +47090,7 @@ async function cmdWorkflowDegrade(runtime, runId, stageId, options) {
45294
47090
  }
45295
47091
  }
45296
47092
  async function cmdWorkflowInspect(runtime, action, runId) {
45297
- const dataPath = resolve21(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join34(runtime.dirs.state, "kxm.db"));
47093
+ const dataPath = resolve22(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join36(runtime.dirs.state, "kxm.db"));
45298
47094
  if (action === "get" && !runId) {
45299
47095
  runtime.io.stderr(`Usage: ${CLI_NAME} workflow get <runId>
45300
47096
  `);
@@ -45339,7 +47135,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
45339
47135
  printWorker(runtime, worker, { ok: true, command: "signal", runId, signalKey, status, summary, evidence }, "would post signal to vNext run");
45340
47136
  return 0;
45341
47137
  }
45342
- const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID11()}`);
47138
+ const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID12()}`);
45343
47139
  try {
45344
47140
  const supervisor = await ensureVnextSupervisor({ env: runtime.env });
45345
47141
  const posted = await vnextRuntimeRequest(
@@ -45375,7 +47171,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
45375
47171
  printWorker(runtime, worker, { ok: true, command: "signal", runId, signalKey, status, summary, evidence }, "would post signed signal");
45376
47172
  return 0;
45377
47173
  }
45378
- const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID11()}`);
47174
+ const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID12()}`);
45379
47175
  try {
45380
47176
  const posted = await postWorkflowSignal({
45381
47177
  serverUrl: runtime.serverUrl,
@@ -45463,14 +47259,14 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
45463
47259
  return 2;
45464
47260
  }
45465
47261
  const snapshotFlag = String(options.input || "");
45466
- const snapshotPath = snapshotFlag ? resolve21(runtime.cwd, snapshotFlag) : "";
47262
+ const snapshotPath = snapshotFlag ? resolve22(runtime.cwd, snapshotFlag) : "";
45467
47263
  let snapshot;
45468
47264
  try {
45469
47265
  if (snapshotPath) {
45470
- if (!existsSync28(snapshotPath)) throw new Error("snapshot_missing");
45471
- snapshot = JSON.parse(readFileSync27(snapshotPath, "utf8"));
47266
+ if (!existsSync30(snapshotPath)) throw new Error("snapshot_missing");
47267
+ snapshot = JSON.parse(readFileSync28(snapshotPath, "utf8"));
45472
47268
  } else {
45473
- const dataPath = resolve21(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join34(runtime.dirs.state, "kxm.db"));
47269
+ const dataPath = resolve22(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join36(runtime.dirs.state, "kxm.db"));
45474
47270
  const local = localWorkflowSnapshot(dataPath, runId);
45475
47271
  if (!local.runs[0]) throw new Error("workflow_not_found");
45476
47272
  snapshot = { run: local.runs[0], journal: local.journal };
@@ -45482,8 +47278,8 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
45482
47278
  return 1;
45483
47279
  }
45484
47280
  const doc = buildRetrospective(snapshot.run, snapshot.journal);
45485
- const outDir = resolve21(runtime.cwd, String(options.outDir || join34(runtime.dirs.assets, "retrospectives")));
45486
- const assetsRoot = resolve21(runtime.dirs.assets);
47281
+ const outDir = resolve22(runtime.cwd, String(options.outDir || join36(runtime.dirs.assets, "retrospectives")));
47282
+ const assetsRoot = resolve22(runtime.dirs.assets);
45487
47283
  const assetsPrefix = `${assetsRoot}${process.platform === "win32" ? "\\" : "/"}`;
45488
47284
  if (outDir !== assetsRoot && !outDir.startsWith(assetsPrefix)) {
45489
47285
  print(runtime.io, runtime.json, { ok: false, command: "retrospective export", error: "output_outside_workspace_assets" }, "retrospectives must stay under the workspace assets directory");
@@ -45499,7 +47295,7 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
45499
47295
  }
45500
47296
  async function ensureCliClient(runtime) {
45501
47297
  const serverUrl = runtime.serverUrl;
45502
- const project = runtime.env.KXM_PROJECT?.trim() || basename6(runtime.dirs.workspace || runtime.cwd);
47298
+ const project = runtime.env.KXM_PROJECT?.trim() || basename7(runtime.dirs.workspace || runtime.cwd);
45503
47299
  const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
45504
47300
  const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
45505
47301
  const authToken = runtime.env.KXM_AUTH_TOKEN?.trim();
@@ -45840,6 +47636,15 @@ function createProgram(ctx, result) {
45840
47636
  addGlobalOptions(role.command("modify [roleId]").description("Modify an existing role definition")).option("--description <text>", "Updated description").option("--add-skill <skill>", "Skill to add").option("--remove-skill <skill>", "Skill to remove").option("--add-model <harness:model>", "Model to add to roster").option("--remove-model <model>", "Model to remove from roster").option("--scope <scope>", "Configuration scope: global or local").option("--pick [selection]", "Pick a role to modify (index or id)").action(async function roleModifyAction(roleId, options) {
45841
47637
  result.code = await cmdRoleModify(runtimeFrom(ctx, this), roleId, options ?? {});
45842
47638
  });
47639
+ addGlobalOptions(role.command("hosts").description("List role seats and resolved execution hosts from .kxm/role-hosts.yaml")).option("--scope <scope>", "Filter by scope: all, global, or local", "all").action(async function roleHostsAction(options) {
47640
+ result.code = await cmdRoleHosts(runtimeFrom(ctx, this), options);
47641
+ });
47642
+ addGlobalOptions(role.command("set-host <seatId> <host>").description("Bind a role seat to a host in .kxm/role-hosts.yaml")).option("--model <model>", "Model identifier for this seat").option("--effort <effort>", "Effort level: low, medium, high, xhigh").option("--scope <scope>", "Configuration scope: global or local (default: local)", "local").action(async function roleSetHostAction(seatId, host, options) {
47643
+ result.code = await cmdRoleSetHost(runtimeFrom(ctx, this), seatId, host, options);
47644
+ });
47645
+ addGlobalOptions(role.command("resume <runId> [ruling]").description("Resume an audit-escalated role run with an operator directive")).action(async function roleResumeAction(runId, ruling) {
47646
+ result.code = await cmdRoleResume(runtimeFrom(ctx, this), runId, ruling);
47647
+ });
45843
47648
  const gate = addGlobalOptions(program2.command("gate").description("Validate definitions and operate evidence gates"));
45844
47649
  gate.helpCommand("help", "Show gate help");
45845
47650
  addGlobalOptions(gate.command("validate").description("Parse workflow definitions without printing secrets")).option("--file <path>", "Workflow definition file").action(async function validateAction(options) {
@@ -45954,8 +47759,21 @@ function createProgram(ctx, result) {
45954
47759
  addGlobalOptions(configCmd.command("list", { isDefault: true }).description("List resolved configuration values")).action(async function configListAction() {
45955
47760
  result.code = await cmdConfigList(runtimeFrom(ctx, this));
45956
47761
  });
45957
- addGlobalOptions(program2.command("completion <shell>").description("Generate shell completion script (bash, zsh, fish)")).action(async function completionAction(shell) {
45958
- result.code = await cmdCompletion(runtimeFrom(ctx, this), shell);
47762
+ const completionCmd = addGlobalOptions(program2.command("completion [shell]").description("Generate shell completion script, or install it into the current shell")).action(async function completionAction(shell) {
47763
+ if (shell && shell !== "install") {
47764
+ result.code = await cmdCompletion(runtimeFrom(ctx, this), shell);
47765
+ return;
47766
+ }
47767
+ if (shell === "install") {
47768
+ result.code = await cmdCompletionInstall(runtimeFrom(ctx, this), this.opts());
47769
+ return;
47770
+ }
47771
+ ctx.io.stderr("usage: kxm completion <bash|zsh|fish> | kxm completion install [--shell <shell>] [--no-path]\n");
47772
+ result.code = 2;
47773
+ });
47774
+ completionCmd.helpCommand("help", "Show completion help");
47775
+ addGlobalOptions(completionCmd.command("install").description("Install tab completion for the detected or given shell and ensure kxm is on PATH")).option("--shell <shell>", "Shell to install for (bash, zsh, fish; default: detect from $SHELL)").option("--no-path", "Only install completion; do not add a PATH entry").action(async function completionInstallAction(options) {
47776
+ result.code = await cmdCompletionInstall(runtimeFrom(ctx, this), options);
45959
47777
  });
45960
47778
  addGlobalOptions(program2.command("suggest <prompt...>").description("Recommend workflow, area, roles, and skills from a prompt or issue description")).action(async function suggestAction(promptParts) {
45961
47779
  result.code = await cmdSuggest(runtimeFrom(ctx, this), promptParts);
@@ -46046,7 +47864,7 @@ async function runCli(argv, env = process.env, io = { stdout: (text) => process.
46046
47864
  throw error;
46047
47865
  }
46048
47866
  }
46049
- if (process.argv[1] && resolve21(process.argv[1]) === fileURLToPath4(import.meta.url)) {
47867
+ if (process.argv[1] && resolve22(process.argv[1]) === fileURLToPath4(import.meta.url)) {
46050
47868
  const code = await runCli(process.argv.slice(2));
46051
47869
  process.exitCode = code;
46052
47870
  }