@kontextmind/kxm 0.7.92 → 0.7.93

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 (91) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/workflows/default.yaml +1 -1
  3. package/CHANGELOG.md +204 -0
  4. package/README.md +3 -0
  5. package/docs/README.md +3 -0
  6. package/docs/agent-skills.md +123 -60
  7. package/docs/architecture.md +5 -2
  8. package/docs/cli-reference.md +3527 -0
  9. package/docs/config-reference.md +1943 -0
  10. package/docs/configuration.md +29 -3
  11. package/docs/continuous-improvement.md +122 -10
  12. package/docs/contracts/routing.md +95 -11
  13. package/docs/harness-routing.md +616 -0
  14. package/docs/kxm-handbook.md +106 -19
  15. package/docs/templates/README.md +1 -1
  16. package/docs/test-matrix.md +12 -6
  17. package/docs/troubleshooting.md +2 -2
  18. package/examples/project/.kxm/workflows/fix.yaml +1 -1
  19. package/examples/project/.kxm/workflows/improve.yaml +1 -1
  20. package/package.json +1 -1
  21. package/plugins/kxm/.claude-plugin/plugin.json +9 -10
  22. package/plugins/kxm/README.md +238 -56
  23. package/plugins/kxm/dist/claude-hook.js +10083 -0
  24. package/plugins/kxm/dist/cli.js +3068 -2446
  25. package/plugins/kxm/dist/client.js +64 -0
  26. package/plugins/kxm/dist/core.js +102 -9
  27. package/plugins/kxm/dist/extension.js +210 -68
  28. package/plugins/kxm/dist/mcp-server.js +217 -40
  29. package/plugins/kxm/dist/runtime-supervisor.js +1628 -157
  30. package/plugins/kxm/dist/runtime.js +1874 -298
  31. package/plugins/kxm/dist/server.js +416 -82
  32. package/plugins/kxm/package.json +1 -1
  33. package/plugins/kxm/skills/hints.json +1 -1
  34. package/plugins/kxm/skills/kxm/SKILL.md +48 -24
  35. package/plugins/kxm/skills/kxm/references/protocol.md +3 -3
  36. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +61 -21
  37. package/plugins/kxm/skills/kxm-definitions/SKILL.md +9 -0
  38. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +82 -16
  39. package/plugins/kxm/skills/kxm-harvest/SKILL.md +1 -1
  40. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +55 -27
  41. package/plugins/kxm/skills/kxm-insights/SKILL.md +1 -1
  42. package/plugins/kxm/skills/kxm-mind/SKILL.md +2 -2
  43. package/plugins/kxm/skills/{kxm-setup → kxm-mind-setup}/SKILL.md +4 -4
  44. package/plugins/kxm/skills/kxm-peer/SKILL.md +68 -93
  45. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +156 -23
  46. package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
  47. package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
  48. package/plugins/kxm/skills/kxm-query/SKILL.md +1 -1
  49. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +74 -15
  50. package/plugins/kxm/skills/kxm-runs/SKILL.md +46 -17
  51. package/plugins/kxm/skills/kxm-session/SKILL.md +64 -36
  52. package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +44 -15
  53. package/plugins/kxm/skills/kxm-tasks/SKILL.md +16 -4
  54. package/plugins/kxm/skills/kxm-triage/SKILL.md +1 -1
  55. package/plugins/kxm/skills/kxm-work/SKILL.md +1 -1
  56. package/plugins/kxm/skills/kxm-workflow/SKILL.md +60 -19
  57. package/plugins/kxm/src/arbiter.ts +67 -22
  58. package/plugins/kxm/src/autocomplete.ts +1 -1
  59. package/plugins/kxm/src/claude-hook.ts +192 -0
  60. package/plugins/kxm/src/cli/project.ts +11 -5
  61. package/plugins/kxm/src/cli/system.ts +85 -13
  62. package/plugins/kxm/src/cli/types.ts +4 -1
  63. package/plugins/kxm/src/cli/workflows.ts +18 -16
  64. package/plugins/kxm/src/cli.ts +23 -13
  65. package/plugins/kxm/src/client.ts +15 -4
  66. package/plugins/kxm/src/commands.ts +19 -9
  67. package/plugins/kxm/src/config.ts +42 -7
  68. package/plugins/kxm/src/context-packet.ts +14 -2
  69. package/plugins/kxm/src/context.ts +16 -5
  70. package/plugins/kxm/src/dispatch-context.ts +286 -0
  71. package/plugins/kxm/src/engine-plan.ts +40 -0
  72. package/plugins/kxm/src/engine.ts +138 -6
  73. package/plugins/kxm/src/hub-env.ts +17 -1
  74. package/plugins/kxm/src/hub.ts +92 -29
  75. package/plugins/kxm/src/improve-sources.ts +228 -0
  76. package/plugins/kxm/src/improve.ts +325 -140
  77. package/plugins/kxm/src/local-snapshot.ts +101 -42
  78. package/plugins/kxm/src/mcp-server.ts +129 -30
  79. package/plugins/kxm/src/project-config.ts +25 -0
  80. package/plugins/kxm/src/protocol.ts +11 -0
  81. package/plugins/kxm/src/relevance.ts +138 -0
  82. package/plugins/kxm/src/retrospective.ts +16 -10
  83. package/plugins/kxm/src/runtime-service.ts +8 -1
  84. package/plugins/kxm/src/runtime-supervisor.ts +16 -2
  85. package/plugins/kxm/src/session-token-hint.ts +17 -0
  86. package/plugins/kxm/src/suggest.ts +7 -7
  87. package/plugins/kxm/src/workflow-manager.ts +80 -78
  88. package/plugins/kxm/src/workflow.ts +202 -12
  89. package/scripts/build-runtime.mjs +7 -1
  90. package/scripts/check-generated.mjs +1 -0
  91. package/scripts/emit-codex-artifacts.mjs +1 -1
@@ -163,10 +163,10 @@ var require_code = __commonJS({
163
163
  function interpolate(x) {
164
164
  return typeof x == "number" || typeof x == "boolean" || x === null ? x : safeStringify(Array.isArray(x) ? x.join(",") : x);
165
165
  }
166
- function stringify3(x) {
166
+ function stringify4(x) {
167
167
  return new _Code(safeStringify(x));
168
168
  }
169
- exports.stringify = stringify3;
169
+ exports.stringify = stringify4;
170
170
  function safeStringify(x) {
171
171
  return JSON.stringify(x).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
172
172
  }
@@ -2986,7 +2986,7 @@ var require_compile = __commonJS({
2986
2986
  const schOrFunc = root.refs[ref];
2987
2987
  if (schOrFunc)
2988
2988
  return schOrFunc;
2989
- let _sch = resolve10.call(this, root, ref);
2989
+ let _sch = resolve11.call(this, root, ref);
2990
2990
  if (_sch === void 0) {
2991
2991
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2992
2992
  const { schemaId } = this.opts;
@@ -3013,7 +3013,7 @@ var require_compile = __commonJS({
3013
3013
  function sameSchemaEnv(s1, s2) {
3014
3014
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3015
3015
  }
3016
- function resolve10(root, ref) {
3016
+ function resolve11(root, ref) {
3017
3017
  let sch;
3018
3018
  while (typeof (sch = this.refs[ref]) == "string")
3019
3019
  ref = sch;
@@ -3834,11 +3834,11 @@ var require_fast_uri = __commonJS({
3834
3834
  normalizeString(uri, options);
3835
3835
  } else if (typeof uri === "object") {
3836
3836
  uri = /** @type {T} */
3837
- parse6(serialize(uri, options), options);
3837
+ parse8(serialize(uri, options), options);
3838
3838
  }
3839
3839
  return uri;
3840
3840
  }
3841
- function resolve10(baseURI, relativeURI, options) {
3841
+ function resolve11(baseURI, relativeURI, options) {
3842
3842
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3843
3843
  const {
3844
3844
  parsed: baseParsed,
@@ -3874,8 +3874,8 @@ var require_fast_uri = __commonJS({
3874
3874
  function resolveComponent(base, relative5, options, skipNormalization) {
3875
3875
  const target = {};
3876
3876
  if (!skipNormalization) {
3877
- base = parse6(serialize(base, options), options);
3878
- relative5 = parse6(serialize(relative5, options), options);
3877
+ base = parse8(serialize(base, options), options);
3878
+ relative5 = parse8(serialize(relative5, options), options);
3879
3879
  }
3880
3880
  options = options || {};
3881
3881
  if (!options.tolerant && relative5.scheme) {
@@ -4167,7 +4167,7 @@ var require_fast_uri = __commonJS({
4167
4167
  }
4168
4168
  return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
4169
4169
  }
4170
- function parse6(uri, opts) {
4170
+ function parse8(uri, opts) {
4171
4171
  return parseWithStatus(uri, opts).parsed;
4172
4172
  }
4173
4173
  function normalizeString(uri, opts) {
@@ -4200,11 +4200,11 @@ var require_fast_uri = __commonJS({
4200
4200
  var fastUri = {
4201
4201
  SCHEMES,
4202
4202
  normalize,
4203
- resolve: resolve10,
4203
+ resolve: resolve11,
4204
4204
  resolveComponent,
4205
4205
  equal,
4206
4206
  serialize,
4207
- parse: parse6
4207
+ parse: parse8
4208
4208
  };
4209
4209
  module.exports = fastUri;
4210
4210
  module.exports.default = fastUri;
@@ -6886,7 +6886,7 @@ var require_discriminator = __commonJS({
6886
6886
  error,
6887
6887
  code(cxt) {
6888
6888
  const { gen, data, schema, parentSchema, it } = cxt;
6889
- const { oneOf } = parentSchema;
6889
+ const { oneOf: oneOf2 } = parentSchema;
6890
6890
  if (!it.opts.discriminator) {
6891
6891
  throw new Error("discriminator: requires discriminator option");
6892
6892
  }
@@ -6895,7 +6895,7 @@ var require_discriminator = __commonJS({
6895
6895
  throw new Error("discriminator: requires propertyName");
6896
6896
  if (schema.mapping)
6897
6897
  throw new Error("discriminator: mapping is not supported");
6898
- if (!oneOf)
6898
+ if (!oneOf2)
6899
6899
  throw new Error("discriminator: requires oneOf keyword");
6900
6900
  const valid = gen.let("valid", false);
6901
6901
  const tag = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
@@ -6923,8 +6923,8 @@ var require_discriminator = __commonJS({
6923
6923
  const oneOfMapping = {};
6924
6924
  const topRequired = hasRequired(parentSchema);
6925
6925
  let tagRequired = true;
6926
- for (let i = 0; i < oneOf.length; i++) {
6927
- let sch = oneOf[i];
6926
+ for (let i = 0; i < oneOf2.length; i++) {
6927
+ let sch = oneOf2[i];
6928
6928
  if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
6929
6929
  const ref = sch.$ref;
6930
6930
  sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
@@ -8917,7 +8917,7 @@ var require_stringify = __commonJS({
8917
8917
  props.push(doc.directives.tagString(tag));
8918
8918
  return props.join(" ");
8919
8919
  }
8920
- function stringify3(item, ctx, onComment, onChompKeep) {
8920
+ function stringify4(item, ctx, onComment, onChompKeep) {
8921
8921
  if (identity.isPair(item))
8922
8922
  return item.toString(ctx, onComment, onChompKeep);
8923
8923
  if (identity.isAlias(item)) {
@@ -8946,7 +8946,7 @@ var require_stringify = __commonJS({
8946
8946
  ${ctx.indent}${str}`;
8947
8947
  }
8948
8948
  exports.createStringifyContext = createStringifyContext;
8949
- exports.stringify = stringify3;
8949
+ exports.stringify = stringify4;
8950
8950
  }
8951
8951
  });
8952
8952
 
@@ -8956,7 +8956,7 @@ var require_stringifyPair = __commonJS({
8956
8956
  "use strict";
8957
8957
  var identity = require_identity();
8958
8958
  var Scalar = require_Scalar();
8959
- var stringify3 = require_stringify();
8959
+ var stringify4 = require_stringify();
8960
8960
  var stringifyComment = require_stringifyComment();
8961
8961
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
8962
8962
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
@@ -8978,7 +8978,7 @@ var require_stringifyPair = __commonJS({
8978
8978
  });
8979
8979
  let keyCommentDone = false;
8980
8980
  let chompKeep = false;
8981
- let str = stringify3.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
8981
+ let str = stringify4.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
8982
8982
  if (!explicitKey && !ctx.inFlow && str.length > 1024) {
8983
8983
  if (simpleKeys)
8984
8984
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -9030,7 +9030,7 @@ ${indent}:`;
9030
9030
  ctx.indent = ctx.indent.substring(2);
9031
9031
  }
9032
9032
  let valueCommentDone = false;
9033
- const valueStr = stringify3.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
9033
+ const valueStr = stringify4.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
9034
9034
  let ws = " ";
9035
9035
  if (keyComment || vsb || vcb) {
9036
9036
  ws = vsb ? "\n" : "";
@@ -9171,7 +9171,7 @@ var require_addPairToJSMap = __commonJS({
9171
9171
  "use strict";
9172
9172
  var log = require_log();
9173
9173
  var merge = require_merge();
9174
- var stringify3 = require_stringify();
9174
+ var stringify4 = require_stringify();
9175
9175
  var identity = require_identity();
9176
9176
  var toJS = require_toJS();
9177
9177
  function addPairToJSMap(ctx, map, { key, value }) {
@@ -9207,7 +9207,7 @@ var require_addPairToJSMap = __commonJS({
9207
9207
  if (typeof jsKey !== "object")
9208
9208
  return String(jsKey);
9209
9209
  if (identity.isNode(key) && ctx?.doc) {
9210
- const strCtx = stringify3.createStringifyContext(ctx.doc, {});
9210
+ const strCtx = stringify4.createStringifyContext(ctx.doc, {});
9211
9211
  strCtx.anchors = /* @__PURE__ */ new Set();
9212
9212
  for (const node of ctx.anchors.keys())
9213
9213
  strCtx.anchors.add(node.anchor);
@@ -9274,12 +9274,12 @@ var require_stringifyCollection = __commonJS({
9274
9274
  "node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
9275
9275
  "use strict";
9276
9276
  var identity = require_identity();
9277
- var stringify3 = require_stringify();
9277
+ var stringify4 = require_stringify();
9278
9278
  var stringifyComment = require_stringifyComment();
9279
9279
  function stringifyCollection(collection, ctx, options) {
9280
9280
  const flow = ctx.inFlow ?? collection.flow;
9281
- const stringify4 = flow ? stringifyFlowCollection : stringifyBlockCollection;
9282
- return stringify4(collection, ctx, options);
9281
+ const stringify5 = flow ? stringifyFlowCollection : stringifyBlockCollection;
9282
+ return stringify5(collection, ctx, options);
9283
9283
  }
9284
9284
  function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
9285
9285
  const { indent, options: { commentString } } = ctx;
@@ -9304,7 +9304,7 @@ var require_stringifyCollection = __commonJS({
9304
9304
  }
9305
9305
  }
9306
9306
  chompKeep = false;
9307
- let str2 = stringify3.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
9307
+ let str2 = stringify4.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
9308
9308
  if (comment2)
9309
9309
  str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
9310
9310
  if (chompKeep && comment2)
@@ -9371,7 +9371,7 @@ ${indent}${line}` : "\n";
9371
9371
  }
9372
9372
  if (comment)
9373
9373
  reqNewline = true;
9374
- let str = stringify3.stringify(item, itemCtx, () => comment = null);
9374
+ let str = stringify4.stringify(item, itemCtx, () => comment = null);
9375
9375
  reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
9376
9376
  if (i < items.length - 1) {
9377
9377
  str += ",";
@@ -10732,7 +10732,7 @@ var require_stringifyDocument = __commonJS({
10732
10732
  "node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
10733
10733
  "use strict";
10734
10734
  var identity = require_identity();
10735
- var stringify3 = require_stringify();
10735
+ var stringify4 = require_stringify();
10736
10736
  var stringifyComment = require_stringifyComment();
10737
10737
  function stringifyDocument(doc, options) {
10738
10738
  const lines = [];
@@ -10747,7 +10747,7 @@ var require_stringifyDocument = __commonJS({
10747
10747
  }
10748
10748
  if (hasDirectives)
10749
10749
  lines.push("---");
10750
- const ctx = stringify3.createStringifyContext(doc, options);
10750
+ const ctx = stringify4.createStringifyContext(doc, options);
10751
10751
  const { commentString } = ctx.options;
10752
10752
  if (doc.commentBefore) {
10753
10753
  if (lines.length !== 1)
@@ -10769,7 +10769,7 @@ var require_stringifyDocument = __commonJS({
10769
10769
  contentComment = doc.contents.comment;
10770
10770
  }
10771
10771
  const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
10772
- let body = stringify3.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
10772
+ let body = stringify4.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
10773
10773
  if (contentComment)
10774
10774
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
10775
10775
  if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
@@ -10777,7 +10777,7 @@ var require_stringifyDocument = __commonJS({
10777
10777
  } else
10778
10778
  lines.push(body);
10779
10779
  } else {
10780
- lines.push(stringify3.stringify(doc.contents, ctx));
10780
+ lines.push(stringify4.stringify(doc.contents, ctx));
10781
10781
  }
10782
10782
  if (doc.directives?.docEnd) {
10783
10783
  if (doc.comment) {
@@ -12913,7 +12913,7 @@ var require_cst_scalar = __commonJS({
12913
12913
  var require_cst_stringify = __commonJS({
12914
12914
  "node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
12915
12915
  "use strict";
12916
- var stringify3 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
12916
+ var stringify4 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
12917
12917
  function stringifyToken(token) {
12918
12918
  switch (token.type) {
12919
12919
  case "block-scalar": {
@@ -12966,7 +12966,7 @@ var require_cst_stringify = __commonJS({
12966
12966
  res += stringifyToken(value);
12967
12967
  return res;
12968
12968
  }
12969
- exports.stringify = stringify3;
12969
+ exports.stringify = stringify4;
12970
12970
  }
12971
12971
  });
12972
12972
 
@@ -14677,7 +14677,7 @@ var require_public_api = __commonJS({
14677
14677
  }
14678
14678
  return doc;
14679
14679
  }
14680
- function parse6(src, reviver, options) {
14680
+ function parse8(src, reviver, options) {
14681
14681
  let _reviver = void 0;
14682
14682
  if (typeof reviver === "function") {
14683
14683
  _reviver = reviver;
@@ -14696,7 +14696,7 @@ var require_public_api = __commonJS({
14696
14696
  }
14697
14697
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
14698
14698
  }
14699
- function stringify3(value, replacer, options) {
14699
+ function stringify4(value, replacer, options) {
14700
14700
  let _replacer = null;
14701
14701
  if (typeof replacer === "function" || Array.isArray(replacer)) {
14702
14702
  _replacer = replacer;
@@ -14718,10 +14718,10 @@ var require_public_api = __commonJS({
14718
14718
  return value.toString(options);
14719
14719
  return new Document.Document(value, _replacer, options).toString(options);
14720
14720
  }
14721
- exports.parse = parse6;
14721
+ exports.parse = parse8;
14722
14722
  exports.parseAllDocuments = parseAllDocuments;
14723
14723
  exports.parseDocument = parseDocument2;
14724
- exports.stringify = stringify3;
14724
+ exports.stringify = stringify4;
14725
14725
  }
14726
14726
  });
14727
14727
 
@@ -15165,7 +15165,7 @@ function defaultSpawn(command, args, options) {
15165
15165
  if (options.signal?.aborted) {
15166
15166
  return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
15167
15167
  }
15168
- return new Promise((resolve10) => {
15168
+ return new Promise((resolve11) => {
15169
15169
  const stdout = [];
15170
15170
  const stderr = [];
15171
15171
  let outputBytes = 0;
@@ -15196,7 +15196,7 @@ function defaultSpawn(command, args, options) {
15196
15196
  const started = Boolean(child?.pid);
15197
15197
  if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
15198
15198
  const unverifiedDescendants = stopping || started && !observedChildExit;
15199
- resolve10({
15199
+ resolve11({
15200
15200
  stdout: Buffer.concat(stdout).toString("utf8"),
15201
15201
  stderr: Buffer.concat(stderr).toString("utf8"),
15202
15202
  code,
@@ -16991,6 +16991,19 @@ function validateAgentScope(agent, step, repositories, file, stepId, issues) {
16991
16991
  }
16992
16992
  }
16993
16993
  }
16994
+ var GATE_STEP_OUTCOMES = {
16995
+ pass: ["passed", "implementation-failure"],
16996
+ fail: ["passed", "repro-missing"]
16997
+ };
16998
+ function gateOutcomeImpossible(step, stepId, file) {
16999
+ const expect = step.expect === "fail" ? "fail" : "pass";
17000
+ const produced = GATE_STEP_OUTCOMES[expect];
17001
+ const declared = Object.keys(objectValue(step.on) ?? {});
17002
+ const impossible = declared.filter((outcome) => !produced.includes(outcome) && outcome !== "implementation_failure" && outcome !== "repro_missing");
17003
+ const missing = produced.filter((outcome) => !declared.includes(outcome));
17004
+ if (impossible.length === 0 || missing.length === 0) return void 0;
17005
+ return issue2("semantic", "gate_outcome_impossible", file, `${stepId} declares ${impossible.join(", ")}, which a gate step with expect ${expect} never produces; it settles on ${produced.join(" or ")}, so declare ${missing.join(" and ")}`);
17006
+ }
16994
17007
  function transition(value) {
16995
17008
  if (typeof value === "string") return { target: value };
16996
17009
  const object = objectValue(value);
@@ -17055,6 +17068,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17055
17068
  if (kind === "gate" && Object.keys(objectValue(step.on) ?? {}).some((outcome) => outcome === "implementation_failure" || outcome === "repro_missing")) {
17056
17069
  issues.push(issue2("semantic", "gate_outcome_renamed", file, `${stepId} must use implementation-failure and repro-missing`));
17057
17070
  }
17071
+ const impossibleOutcome = kind === "gate" ? gateOutcomeImpossible(step, stepId, file) : void 0;
17072
+ if (impossibleOutcome) issues.push(impossibleOutcome);
17058
17073
  for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
17059
17074
  if (!repositories.has(repositoryId)) issues.push(issue2("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
17060
17075
  }
@@ -17094,8 +17109,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17094
17109
  const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
17095
17110
  if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
17096
17111
  }
17097
- const join20 = objectValue(step.join);
17098
- const minimumPassed = join20 && typeof join20.minimumPassed === "number" ? join20.minimumPassed : void 0;
17112
+ const join23 = objectValue(step.join);
17113
+ const minimumPassed = join23 && typeof join23.minimumPassed === "number" ? join23.minimumPassed : void 0;
17099
17114
  if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
17100
17115
  const distinctBy = names(assignment?.distinctBy);
17101
17116
  if (distinctBy.length > 0) {
@@ -21536,7 +21551,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
21536
21551
  const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
21537
21552
  const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
21538
21553
  const assignments = compileAssignments(step, id, agent, sink);
21539
- const join20 = compileJoin(step, id, sink);
21554
+ const join23 = compileJoin(step, id, sink);
21540
21555
  const requiredEvidence = compileEvidence(step, id, sink);
21541
21556
  const transitions = compileTransitions(step, id, index, stepIndex, sink);
21542
21557
  const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
@@ -21564,7 +21579,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
21564
21579
  transitions: orderedTransitions,
21565
21580
  requiresPlanHash: requirePlanHash.includes(id),
21566
21581
  assignments,
21567
- join: join20
21582
+ join: join23
21568
21583
  };
21569
21584
  if (kind === "agent" || kind === "moa") {
21570
21585
  if (!agent) return void 0;
@@ -21614,15 +21629,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
21614
21629
  };
21615
21630
  }
21616
21631
  function compileJoin(step, stepId, sink) {
21617
- const join20 = objectValue2(step.join);
21618
- if (!join20) return { strategy: "all" };
21619
- const declared = stringValue2(join20.strategy);
21632
+ const join23 = objectValue2(step.join);
21633
+ if (!join23) return { strategy: "all" };
21634
+ const declared = stringValue2(join23.strategy);
21620
21635
  const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
21621
- const minimumPassed = compileOptionalCount(join20.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
21636
+ const minimumPassed = compileOptionalCount(join23.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
21622
21637
  const compiled = {
21623
21638
  strategy,
21624
21639
  ...minimumPassed !== void 0 ? { minimumPassed } : {},
21625
- ...typeof join20.cancelRemaining === "boolean" ? { cancelRemaining: join20.cancelRemaining } : {}
21640
+ ...typeof join23.cancelRemaining === "boolean" ? { cancelRemaining: join23.cancelRemaining } : {}
21626
21641
  };
21627
21642
  return compiled;
21628
21643
  }
@@ -21935,6 +21950,28 @@ var EVIDENCE_OPTIONAL = ["producerPolicy"];
21935
21950
  function kxmSha256(input) {
21936
21951
  return `sha256:${createHash6("sha256").update(input, "utf8").digest("hex")}`;
21937
21952
  }
21953
+ function kxmStepAskSha256(plan, stepId, agentId) {
21954
+ const step = Object.hasOwn(plan.steps, stepId) ? plan.steps[stepId] : void 0;
21955
+ if (!step) throw runtimeError("run_plan_corrupt", plan.workflowId, `compiled plan is missing step ${stepId}`);
21956
+ return kxmSha256(kxmCanonicalJson({
21957
+ v: 1,
21958
+ workflowId: plan.workflowId,
21959
+ stepId,
21960
+ kind: step.kind,
21961
+ agentId,
21962
+ instructions: step.instructions ?? null,
21963
+ outcomes: [...step.outcomes],
21964
+ requiredEvidence: step.requiredEvidence.map((entry) => entry.key)
21965
+ }));
21966
+ }
21967
+ function kxmAttemptFinalOutcome(step, settled) {
21968
+ if (settled.resultClass !== "outcome") return "failed";
21969
+ const outcome = settled.outcome;
21970
+ const transition2 = outcome !== void 0 && Object.hasOwn(step.transitions, outcome) ? step.transitions[outcome] : void 0;
21971
+ if (!transition2) return "failed";
21972
+ if (transition2.to === "terminal") return transition2.terminalStatus === "completed" ? void 0 : "failed";
21973
+ return transition2.edge === "back" ? "blocked" : void 0;
21974
+ }
21938
21975
  function hashKxmRunPlanEnvelope(envelope2) {
21939
21976
  return kxmSha256(kxmCanonicalJson(envelope2));
21940
21977
  }
@@ -22946,12 +22983,12 @@ function enqueueKxmScheduledRun(storePath, runId, configRevision, bound, start)
22946
22983
  if (owner.admitted.has(runId) || owner.queue.some((item) => item.runId === runId)) {
22947
22984
  return Promise.reject(runtimeError("run_busy", runId, `run ${runId} is already admitted or queued`));
22948
22985
  }
22949
- return new Promise((resolve10, reject) => {
22986
+ return new Promise((resolve11, reject) => {
22950
22987
  owner.queue.push({
22951
22988
  runId,
22952
22989
  configRevision,
22953
22990
  bound,
22954
- start: (token) => start(token).then(resolve10, reject),
22991
+ start: (token) => start(token).then(resolve11, reject),
22955
22992
  fail: (error) => reject(error)
22956
22993
  });
22957
22994
  pump(storePath, owner);
@@ -23130,7 +23167,8 @@ function openKxmRuntimeContext(projectRoot, options) {
23130
23167
  homeRuntimeId: registration.homeRuntimeId,
23131
23168
  eventStore,
23132
23169
  configRevision: bundle.configRevision,
23133
- ...options.budgetClock ? { budgetClock: options.budgetClock } : {}
23170
+ ...options.budgetClock ? { budgetClock: options.budgetClock } : {},
23171
+ ...options.logger ? { logger: options.logger } : {}
23134
23172
  };
23135
23173
  } catch (error) {
23136
23174
  registry.close();
@@ -23469,13 +23507,13 @@ function assertKxmConfigError(error) {
23469
23507
 
23470
23508
  // plugins/kxm/src/runtime-supervisor.ts
23471
23509
  import { spawn as spawn3 } from "node:child_process";
23472
- import { createHash as createHash13, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
23473
- import { chmodSync as chmodSync2, existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
23510
+ import { createHash as createHash14, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
23511
+ import { chmodSync as chmodSync2, existsSync as existsSync15, lstatSync as lstatSync5, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
23474
23512
  import { createServer } from "node:http";
23475
- import { dirname as dirname9, isAbsolute as isAbsolute6, join as join15 } from "node:path";
23513
+ import { dirname as dirname11, isAbsolute as isAbsolute6, join as join18 } from "node:path";
23476
23514
 
23477
23515
  // plugins/kxm/src/oneshot-producer.ts
23478
- import { join as join12 } from "node:path";
23516
+ import { join as join15 } from "node:path";
23479
23517
 
23480
23518
  // plugins/kxm/src/oneshot-evidence.ts
23481
23519
  import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
@@ -23771,10 +23809,10 @@ function loadPriceCatalogForEstimate(options) {
23771
23809
  }
23772
23810
 
23773
23811
  // plugins/kxm/src/engine.ts
23774
- var import_yaml5 = __toESM(require_dist(), 1);
23775
- import { createHash as createHash12, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
23776
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
23777
- import { join as join11 } from "node:path";
23812
+ var import_yaml7 = __toESM(require_dist(), 1);
23813
+ import { createHash as createHash13, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
23814
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
23815
+ import { join as join14 } from "node:path";
23778
23816
 
23779
23817
  // plugins/kxm/src/routes.ts
23780
23818
  var import_yaml4 = __toESM(require_dist(), 1);
@@ -23864,7 +23902,7 @@ function buildFormalContextPacket(input) {
23864
23902
  budget: {
23865
23903
  allocatedTokens,
23866
23904
  estimatedTokens,
23867
- unresolvedGaps: [],
23905
+ unresolvedGaps: [...input.unresolvedGaps ?? []],
23868
23906
  provenanceSummary
23869
23907
  }
23870
23908
  };
@@ -23913,7 +23951,7 @@ function formatContextPacketForPrompt(packet) {
23913
23951
  }
23914
23952
  sections.push("");
23915
23953
  }
23916
- if (packet.environment.projectKnowledge.length > 0 || packet.environment.sharedDefaults.length > 0) {
23954
+ if (packet.environment.projectKnowledge.length > 0 || packet.environment.sharedDefaults.length > 0 || packet.environment.activeSkills.length > 0 || packet.environment.contradictions.length > 0) {
23917
23955
  sections.push(`## 5. Environment & Memory`);
23918
23956
  if (packet.environment.sharedDefaults.length > 0) {
23919
23957
  sections.push(`### Shared Defaults`);
@@ -23927,6 +23965,12 @@ function formatContextPacketForPrompt(packet) {
23927
23965
  sections.push(`- [${k.authority}] ${k.summary}`);
23928
23966
  }
23929
23967
  }
23968
+ if (packet.environment.activeSkills.length > 0) {
23969
+ sections.push(`### Active Skills`);
23970
+ for (const skill of packet.environment.activeSkills) {
23971
+ sections.push(`- ${skill.summary} (${skill.provenance.sourceRef ?? skill.id})`);
23972
+ }
23973
+ }
23930
23974
  if (packet.environment.contradictions.length > 0) {
23931
23975
  sections.push(`### Active Contradictions`);
23932
23976
  for (const c of packet.environment.contradictions) {
@@ -24066,19 +24110,1362 @@ function pruneContextPacket(packet, tokenBudget) {
24066
24110
  };
24067
24111
  }
24068
24112
 
24113
+ // plugins/kxm/src/dispatch-context.ts
24114
+ import { spawnSync as spawnSync3 } from "node:child_process";
24115
+ import { readdirSync as readdirSync7 } from "node:fs";
24116
+ import { extname as extname3, join as join12 } from "node:path";
24117
+
24118
+ // plugins/kxm/src/protocol.ts
24119
+ var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
24120
+ var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
24121
+ var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
24122
+ var MAX_BODY_BYTES = 256 * 1024;
24123
+ var MAX_AGENT_HOST_CHARS = 64;
24124
+ var MAX_LEASE_TTL_MS = 10 * 6e4;
24125
+ var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
24126
+ var ProtocolError = class extends Error {
24127
+ statusCode;
24128
+ code;
24129
+ extras;
24130
+ constructor(statusCode, message, code = "protocol_error", extras) {
24131
+ super(message);
24132
+ this.name = "ProtocolError";
24133
+ this.statusCode = statusCode;
24134
+ this.code = code;
24135
+ if (extras) this.extras = extras;
24136
+ }
24137
+ };
24138
+ function nowIso() {
24139
+ return (/* @__PURE__ */ new Date()).toISOString();
24140
+ }
24141
+ function requireString(value, field, options = {}) {
24142
+ if (typeof value !== "string") {
24143
+ throw new ProtocolError(400, `${field} must be a string`);
24144
+ }
24145
+ const result = value.trim();
24146
+ if (!options.allowEmpty && result.length === 0) {
24147
+ throw new ProtocolError(400, `${field} cannot be empty`);
24148
+ }
24149
+ if (options.max !== void 0 && result.length > options.max) {
24150
+ throw new ProtocolError(400, `${field} exceeds ${options.max} characters`);
24151
+ }
24152
+ return result;
24153
+ }
24154
+
24155
+ // plugins/kxm/src/context.ts
24156
+ var MAX_CONTEXT_SUMMARY_CHARS = 4e3;
24157
+ var MAX_CONTEXT_ID_REFS = 64;
24158
+ var MAX_CONTEXT_ITEMS = 256;
24159
+ var MIN_CONTEXT_BUDGET_TOKENS = 512;
24160
+ var MAX_CONTEXT_BUDGET_TOKENS = 2e5;
24161
+ var DEFAULT_CONTEXT_BUDGET_TOKENS = 32e3;
24162
+ var MAX_CONTEXT_ROLE_CHARS = 64;
24163
+ var MAX_CONTEXT_TASK_CHARS = 2e3;
24164
+ var AUTHORITY_GRANT_FLOOR = {
24165
+ human: "policy",
24166
+ workflow: "policy",
24167
+ git: "instruction",
24168
+ peer: "evidence",
24169
+ tool: "evidence",
24170
+ external: "evidence",
24171
+ derived: "evidence"
24172
+ };
24173
+ function authorityGrantFloor(sourceType) {
24174
+ return AUTHORITY_GRANT_FLOOR[sourceType];
24175
+ }
24176
+ var RESERVED_CONTROL_PLANE_FIELDS = /* @__PURE__ */ new Set([
24177
+ "permissions",
24178
+ "tools",
24179
+ "allow",
24180
+ "deny",
24181
+ "grants",
24182
+ "approval",
24183
+ "policy",
24184
+ "scopes",
24185
+ "credentials",
24186
+ "secrets",
24187
+ "token",
24188
+ "apiKey",
24189
+ "password"
24190
+ ]);
24191
+ var CONTEXT_ITEM_KINDS = ["evidence", "state", "episode", "knowledge", "skill"];
24192
+ var CONTEXT_SOURCE_TYPES = [
24193
+ "human",
24194
+ "git",
24195
+ "workflow",
24196
+ "tool",
24197
+ "peer",
24198
+ "external",
24199
+ "derived"
24200
+ ];
24201
+ var CONTEXT_AUTHORITIES = ["policy", "instruction", "evidence", "hypothesis"];
24202
+ var CONTEXT_CONFIDENCES = ["verified", "probable", "uncertain"];
24203
+ var CONTEXT_SCOPES = ["agent", "project", "run", "operator"];
24204
+ var CONTEXT_ROLES = ["repro", "planner", "critic", "implementer", "verifier"];
24205
+ function oneOf(value, field, allowed) {
24206
+ if (typeof value !== "string" || !allowed.includes(value)) {
24207
+ throw new ProtocolError(400, `${field} must be one of ${allowed.join(", ")}`, "invalid_context_field");
24208
+ }
24209
+ return value;
24210
+ }
24211
+ function idRefs(value, field, required = false) {
24212
+ if (value === void 0 || value === null) return required ? [] : void 0;
24213
+ if (!Array.isArray(value)) {
24214
+ throw new ProtocolError(400, `${field} must be an array of identifiers`, "invalid_context_field");
24215
+ }
24216
+ if (value.length > MAX_CONTEXT_ID_REFS) {
24217
+ throw new ProtocolError(
24218
+ 400,
24219
+ `${field} exceeds ${MAX_CONTEXT_ID_REFS} references`,
24220
+ "context_limits_exceeded"
24221
+ );
24222
+ }
24223
+ const seen = /* @__PURE__ */ new Set();
24224
+ const refs = [];
24225
+ for (const candidate of value) {
24226
+ if (typeof candidate !== "string" || !candidate.trim()) {
24227
+ throw new ProtocolError(400, `${field} must contain non-empty identifiers`, "invalid_context_field");
24228
+ }
24229
+ const ref = candidate.trim();
24230
+ if (seen.has(ref)) {
24231
+ throw new ProtocolError(400, `${field} contains duplicate reference ${ref}`, "invalid_context_field");
24232
+ }
24233
+ seen.add(ref);
24234
+ refs.push(ref);
24235
+ }
24236
+ return refs;
24237
+ }
24238
+ function optionalIsoTimestamp(value, field) {
24239
+ if (value === void 0 || value === null) return void 0;
24240
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
24241
+ throw new ProtocolError(400, `${field} must be an ISO-8601 timestamp`, "invalid_context_field");
24242
+ }
24243
+ return value;
24244
+ }
24245
+ function parseContextItem(value) {
24246
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24247
+ throw new ProtocolError(400, "context item must be an object", "invalid_context_item");
24248
+ }
24249
+ const input = value;
24250
+ for (const field of Object.keys(input)) {
24251
+ if (RESERVED_CONTROL_PLANE_FIELDS.has(field)) {
24252
+ throw new ProtocolError(
24253
+ 403,
24254
+ `context items may not carry control-plane field "${field}"`,
24255
+ "context_authority_violation"
24256
+ );
24257
+ }
24258
+ }
24259
+ const item = {
24260
+ id: requireString(input.id, "context item id", { max: 128 }),
24261
+ kind: oneOf(input.kind, "context item kind", CONTEXT_ITEM_KINDS),
24262
+ project: requireString(input.project, "context item project", { max: 200 }),
24263
+ summary: redactSecrets(requireString(input.summary, "context item summary", { max: MAX_CONTEXT_SUMMARY_CHARS })),
24264
+ provenance: parseContextProvenance(input.provenance),
24265
+ authority: oneOf(input.authority, "context item authority", CONTEXT_AUTHORITIES),
24266
+ confidence: oneOf(input.confidence, "context item confidence", CONTEXT_CONFIDENCES)
24267
+ };
24268
+ if (input.scope !== void 0 && input.scope !== null) {
24269
+ item.scope = oneOf(input.scope, "context item scope", CONTEXT_SCOPES);
24270
+ }
24271
+ const observedAt = optionalIsoTimestamp(input.observedAt, "context item observedAt");
24272
+ const validFrom = optionalIsoTimestamp(input.validFrom, "context item validFrom");
24273
+ const validUntil = optionalIsoTimestamp(input.validUntil, "context item validUntil");
24274
+ if (observedAt !== void 0) item.observedAt = observedAt;
24275
+ if (validFrom !== void 0) item.validFrom = validFrom;
24276
+ if (validUntil !== void 0) item.validUntil = validUntil;
24277
+ if (input.status !== void 0 && input.status !== null) {
24278
+ item.status = oneOf(input.status, "context item status", ["current", "superseded", "proposed", "rejected"]);
24279
+ }
24280
+ const supersedes = idRefs(input.supersedes, "context item supersedes");
24281
+ if (supersedes !== void 0) {
24282
+ if (supersedes.includes(item.id)) {
24283
+ throw new ProtocolError(400, "context item cannot supersede itself", "invalid_context_item");
24284
+ }
24285
+ item.supersedes = supersedes;
24286
+ }
24287
+ const evidenceRefs = idRefs(input.evidenceRefs, "context item evidenceRefs");
24288
+ if (evidenceRefs !== void 0) item.evidenceRefs = evidenceRefs;
24289
+ const stateKey = input.stateKey === void 0 || input.stateKey === null ? void 0 : requireString(input.stateKey, "context item stateKey", { max: 200 });
24290
+ if (stateKey !== void 0) item.stateKey = stateKey;
24291
+ if (item.kind === "state" && item.stateKey === void 0) {
24292
+ throw new ProtocolError(400, "state items require a stateKey", "invalid_context_item");
24293
+ }
24294
+ if (item.kind === "state" && item.status === void 0) {
24295
+ throw new ProtocolError(400, "state items require an explicit lifecycle status", "invalid_context_item");
24296
+ }
24297
+ const grantFloor = authorityRank(authorityGrantFloor(item.provenance.sourceType));
24298
+ if (authorityRank(item.authority) > grantFloor) {
24299
+ throw new ProtocolError(
24300
+ 403,
24301
+ `content of origin ${item.provenance.sourceType} cannot claim ${item.authority} authority`,
24302
+ "context_authority_violation"
24303
+ );
24304
+ }
24305
+ return item;
24306
+ }
24307
+ function parseContextProvenance(value) {
24308
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24309
+ throw new ProtocolError(400, "context provenance must be an object", "invalid_context_item");
24310
+ }
24311
+ const input = value;
24312
+ const provenance = {
24313
+ sourceType: oneOf(input.sourceType, "context provenance sourceType", CONTEXT_SOURCE_TYPES)
24314
+ };
24315
+ const sourceRef = input.sourceRef === void 0 || input.sourceRef === null ? void 0 : requireString(input.sourceRef, "context provenance sourceRef", { max: 512 });
24316
+ if (sourceRef !== void 0) provenance.sourceRef = redactSecrets(sourceRef);
24317
+ const derivedFrom = idRefs(input.derivedFrom, "context provenance derivedFrom");
24318
+ if (derivedFrom !== void 0) provenance.derivedFrom = derivedFrom;
24319
+ return provenance;
24320
+ }
24321
+ function parseContextRequest(value) {
24322
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24323
+ throw new ProtocolError(400, "context request must be an object", "invalid_context_request");
24324
+ }
24325
+ const input = value;
24326
+ const request = {
24327
+ project: requireString(input.project, "context request project", { max: 200 }),
24328
+ role: requireString(input.role, "context request role", { max: MAX_CONTEXT_ROLE_CHARS }),
24329
+ task: requireString(input.task, "context request task", { max: MAX_CONTEXT_TASK_CHARS })
24330
+ };
24331
+ const workflowRunId = input.workflowRunId === void 0 || input.workflowRunId === null ? void 0 : requireString(input.workflowRunId, "context request workflowRunId", { max: 128 });
24332
+ if (workflowRunId !== void 0) request.workflowRunId = workflowRunId;
24333
+ const stageId = input.stageId === void 0 || input.stageId === null ? void 0 : requireString(input.stageId, "context request stageId", { max: 128 });
24334
+ if (stageId !== void 0) request.stageId = stageId;
24335
+ if (input.budgetTokens !== void 0 && input.budgetTokens !== null) {
24336
+ const budget = input.budgetTokens;
24337
+ if (!Number.isInteger(budget) || budget < MIN_CONTEXT_BUDGET_TOKENS || budget > MAX_CONTEXT_BUDGET_TOKENS) {
24338
+ throw new ProtocolError(
24339
+ 400,
24340
+ `context request budgetTokens must be an integer between ${MIN_CONTEXT_BUDGET_TOKENS} and ${MAX_CONTEXT_BUDGET_TOKENS}`,
24341
+ "invalid_context_request"
24342
+ );
24343
+ }
24344
+ request.budgetTokens = budget;
24345
+ }
24346
+ if (input.includeKinds !== void 0 && input.includeKinds !== null) {
24347
+ if (!Array.isArray(input.includeKinds) || input.includeKinds.length < 1 || input.includeKinds.length > CONTEXT_ITEM_KINDS.length) {
24348
+ throw new ProtocolError(
24349
+ 400,
24350
+ "context request includeKinds must be a non-empty array of item kinds",
24351
+ "invalid_context_request"
24352
+ );
24353
+ }
24354
+ request.includeKinds = input.includeKinds.map((kind) => oneOf(kind, "context request includeKinds", CONTEXT_ITEM_KINDS));
24355
+ }
24356
+ return request;
24357
+ }
24358
+ function validateContextPacketContents(request, packet) {
24359
+ const items = [
24360
+ ...packet.currentState,
24361
+ ...packet.knowledge,
24362
+ ...packet.evidence,
24363
+ ...packet.episodes,
24364
+ ...packet.skills,
24365
+ ...packet.contradictions
24366
+ ];
24367
+ if (items.length > MAX_CONTEXT_ITEMS) {
24368
+ throw new ProtocolError(400, `context packet exceeds ${MAX_CONTEXT_ITEMS} items`, "context_limits_exceeded");
24369
+ }
24370
+ const allowed = request.includeKinds ? new Set(request.includeKinds) : void 0;
24371
+ for (const item of items) {
24372
+ if (item.project !== request.project && item.project !== "_shared") {
24373
+ throw new ProtocolError(
24374
+ 400,
24375
+ `context packet contains cross-project item ${item.id}`,
24376
+ "context_isolation_violation"
24377
+ );
24378
+ }
24379
+ if (allowed && !allowed.has(item.kind)) {
24380
+ throw new ProtocolError(
24381
+ 400,
24382
+ `context packet contains item ${item.id} of unrequested kind ${item.kind}`,
24383
+ "context_isolation_violation"
24384
+ );
24385
+ }
24386
+ }
24387
+ }
24388
+ function contextItemCharacters(item) {
24389
+ return item.summary.length + item.id.length + item.kind.length + (item.provenance.sourceRef?.length ?? 0);
24390
+ }
24391
+ function estimateContextTokens(items) {
24392
+ let characters = 0;
24393
+ for (const item of items) characters += contextItemCharacters(item);
24394
+ return Math.ceil(characters / 4);
24395
+ }
24396
+ function authorityRank(authority) {
24397
+ switch (authority) {
24398
+ case "policy":
24399
+ return 3;
24400
+ case "instruction":
24401
+ return 2;
24402
+ case "evidence":
24403
+ return 1;
24404
+ case "hypothesis":
24405
+ return 0;
24406
+ }
24407
+ }
24408
+ function provenanceSummaryOf(items) {
24409
+ const summary = {};
24410
+ for (const item of items) {
24411
+ summary[item.provenance.sourceType] = (summary[item.provenance.sourceType] ?? 0) + 1;
24412
+ }
24413
+ for (const key of Object.keys(summary).sort()) {
24414
+ if (summary[key] === 0) delete summary[key];
24415
+ }
24416
+ return summary;
24417
+ }
24418
+
24419
+ // plugins/kxm/src/relevance.ts
24420
+ var RELEVANCE_STOPWORDS = Object.freeze(/* @__PURE__ */ new Set([
24421
+ "a",
24422
+ "an",
24423
+ "and",
24424
+ "are",
24425
+ "as",
24426
+ "at",
24427
+ "be",
24428
+ "been",
24429
+ "but",
24430
+ "by",
24431
+ "can",
24432
+ "could",
24433
+ "did",
24434
+ "do",
24435
+ "does",
24436
+ "for",
24437
+ "from",
24438
+ "had",
24439
+ "has",
24440
+ "have",
24441
+ "how",
24442
+ "if",
24443
+ "in",
24444
+ "into",
24445
+ "is",
24446
+ "it",
24447
+ "its",
24448
+ "of",
24449
+ "on",
24450
+ "or",
24451
+ "our",
24452
+ "should",
24453
+ "so",
24454
+ "than",
24455
+ "that",
24456
+ "the",
24457
+ "their",
24458
+ "them",
24459
+ "then",
24460
+ "there",
24461
+ "these",
24462
+ "they",
24463
+ "this",
24464
+ "those",
24465
+ "to",
24466
+ "was",
24467
+ "we",
24468
+ "were",
24469
+ "what",
24470
+ "when",
24471
+ "where",
24472
+ "which",
24473
+ "while",
24474
+ "who",
24475
+ "why",
24476
+ "will",
24477
+ "with",
24478
+ "would",
24479
+ "you",
24480
+ "your"
24481
+ ]));
24482
+ var RELEVANCE_K1 = 1.2;
24483
+ var RELEVANCE_B = 0.75;
24484
+ var MIN_TOKEN_CHARS = 2;
24485
+ var MAX_TOKEN_CHARS = 64;
24486
+ function foldPlural(token) {
24487
+ if (new RegExp("^\\p{N}+$", "u").test(token)) return token;
24488
+ if (token.length > 4 && token.endsWith("ies")) return `${token.slice(0, -3)}y`;
24489
+ if (token.length > 4 && token.endsWith("sses")) return token.slice(0, -2);
24490
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is")) {
24491
+ return token.slice(0, -1);
24492
+ }
24493
+ return token;
24494
+ }
24495
+ function relevanceTokens(text) {
24496
+ const tokens = [];
24497
+ for (const raw of text.normalize("NFKC").toLowerCase().split(/[^\p{L}\p{N}]+/u)) {
24498
+ if (raw.length < MIN_TOKEN_CHARS || raw.length > MAX_TOKEN_CHARS) continue;
24499
+ if (RELEVANCE_STOPWORDS.has(raw)) continue;
24500
+ tokens.push(foldPlural(raw));
24501
+ }
24502
+ return tokens;
24503
+ }
24504
+ function scoreRelevance(query, documents) {
24505
+ const scores = documents.map(() => 0);
24506
+ const terms = [...new Set(relevanceTokens(query))];
24507
+ if (terms.length === 0 || documents.length === 0) return scores;
24508
+ const indexed = documents.map((document) => {
24509
+ const tokens = relevanceTokens(document);
24510
+ const frequencies = /* @__PURE__ */ new Map();
24511
+ for (const token of tokens) frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
24512
+ return { length: tokens.length, frequencies };
24513
+ });
24514
+ const count = indexed.length;
24515
+ let totalLength = 0;
24516
+ for (const document of indexed) totalLength += document.length;
24517
+ const averageLength = totalLength > 0 ? totalLength / count : 1;
24518
+ const inverseFrequency = /* @__PURE__ */ new Map();
24519
+ for (const term of terms) {
24520
+ let documentFrequency = 0;
24521
+ for (const document of indexed) {
24522
+ if (document.frequencies.has(term)) documentFrequency += 1;
24523
+ }
24524
+ inverseFrequency.set(term, Math.log(1 + (count - documentFrequency + 0.5) / (documentFrequency + 0.5)));
24525
+ }
24526
+ indexed.forEach((document, index) => {
24527
+ let score = 0;
24528
+ for (const term of terms) {
24529
+ const frequency = document.frequencies.get(term) ?? 0;
24530
+ if (frequency === 0) continue;
24531
+ const lengthNorm = 1 - RELEVANCE_B + RELEVANCE_B * (document.length / averageLength);
24532
+ score += inverseFrequency.get(term) * (frequency * (RELEVANCE_K1 + 1) / (frequency + RELEVANCE_K1 * lengthNorm));
24533
+ }
24534
+ scores[index] = score;
24535
+ });
24536
+ return scores;
24537
+ }
24538
+ function roundRelevance(score) {
24539
+ return Math.round(score * 1e3) / 1e3;
24540
+ }
24541
+ function contextItemRelevanceText(item) {
24542
+ return item.stateKey !== void 0 ? `${item.summary} ${item.stateKey}` : item.summary;
24543
+ }
24544
+ function compareCodeUnitIds(left, right) {
24545
+ return left < right ? -1 : left > right ? 1 : 0;
24546
+ }
24547
+
24548
+ // plugins/kxm/src/arbiter.ts
24549
+ var ROLE_POLICIES = [
24550
+ {
24551
+ role: "repro",
24552
+ label: "Reproduction specialist",
24553
+ kinds: ["episode", "knowledge", "evidence"],
24554
+ journalCategories: ["error", "lesson", "observation", "contradiction"],
24555
+ budgetTokens: 8e3
24556
+ },
24557
+ {
24558
+ role: "planner",
24559
+ label: "Planner",
24560
+ kinds: ["state", "knowledge", "evidence"],
24561
+ journalCategories: ["plan", "decision", "contradiction", "observation", "hypothesis", "experiment"],
24562
+ budgetTokens: 16e3
24563
+ },
24564
+ {
24565
+ role: "critic",
24566
+ label: "Independent critic",
24567
+ kinds: ["knowledge", "evidence", "episode"],
24568
+ journalCategories: ["contradiction", "error", "lesson", "experiment"],
24569
+ budgetTokens: 12e3
24570
+ },
24571
+ {
24572
+ role: "implementer",
24573
+ label: "Implementer",
24574
+ kinds: ["knowledge", "state", "skill", "episode", "evidence"],
24575
+ journalCategories: ["plan", "decision", "lesson", "state-change"],
24576
+ budgetTokens: 16e3
24577
+ },
24578
+ {
24579
+ role: "verifier",
24580
+ label: "Verifier",
24581
+ kinds: ["evidence", "knowledge", "episode"],
24582
+ journalCategories: ["plan", "error", "lesson", "contradiction"],
24583
+ budgetTokens: 8e3
24584
+ }
24585
+ ];
24586
+ function rolePolicy(role) {
24587
+ return ROLE_POLICIES.find((policy) => policy.role === role) ?? {
24588
+ role,
24589
+ label: role,
24590
+ kinds: ["knowledge", "evidence"],
24591
+ journalCategories: ["lesson", "observation"],
24592
+ budgetTokens: DEFAULT_CONTEXT_BUDGET_TOKENS
24593
+ };
24594
+ }
24595
+ var CONFIDENCE_RANK = { verified: 3, probable: 2, uncertain: 1 };
24596
+ var AUTHORITY_WEIGHT = { policy: 3, instruction: 2, evidence: 1, hypothesis: 0 };
24597
+ function arbitrate(requestInput, pool, options = {}) {
24598
+ const request = parseContextRequest(requestInput);
24599
+ const policy = rolePolicy(request.role);
24600
+ const budget = request.budgetTokens ?? policy.budgetTokens;
24601
+ const contradictions = new Set(options.contradictionIds ?? []);
24602
+ const candidates = [];
24603
+ let excludedSuperseded = 0;
24604
+ for (const candidate of pool) {
24605
+ if (candidate.project !== request.project && candidate.project !== "_shared") {
24606
+ throw new ProtocolError(
24607
+ 403,
24608
+ `context pool contains cross-project item ${candidate.id}`,
24609
+ "context_isolation_violation"
24610
+ );
24611
+ }
24612
+ if (candidate.status === "superseded" || candidate.status === "rejected") {
24613
+ excludedSuperseded += 1;
24614
+ continue;
24615
+ }
24616
+ candidates.push(candidate);
24617
+ }
24618
+ if (options.skillLifecycle) {
24619
+ for (const metadata of options.skillLifecycle.list("promoted")) {
24620
+ try {
24621
+ options.skillLifecycle.verify("promoted", metadata.id);
24622
+ candidates.push(parseContextItem({
24623
+ id: `skill_${metadata.id}`,
24624
+ kind: "skill",
24625
+ project: request.project,
24626
+ summary: metadata.description ? `${metadata.name}: ${metadata.description}` : metadata.name,
24627
+ provenance: {
24628
+ sourceType: "git",
24629
+ sourceRef: `skill:${metadata.id}@${metadata.contentSha256}`
24630
+ },
24631
+ authority: "instruction",
24632
+ confidence: "verified",
24633
+ status: "current"
24634
+ }));
24635
+ } catch {
24636
+ }
24637
+ }
24638
+ }
24639
+ const kindRank = /* @__PURE__ */ new Map();
24640
+ const requestedKinds = request.includeKinds ?? policy.kinds;
24641
+ requestedKinds.forEach((kind, index) => kindRank.set(kind, index));
24642
+ const kindPreference = (item) => {
24643
+ const rank = kindRank.get(item.kind);
24644
+ return rank === void 0 ? requestedKinds.length : rank;
24645
+ };
24646
+ const allowedKinds = new Set(requestedKinds);
24647
+ const inertProposal = (item) => item.kind === "state" && item.status !== void 0 && item.status !== "current" || item.kind === "skill" && item.status === "proposed";
24648
+ const eligible = candidates.filter((item) => contradictions.has(item.id) || allowedKinds.has(item.kind) && !inertProposal(item));
24649
+ const scoreList = scoreRelevance(request.task, eligible.map(contextItemRelevanceText));
24650
+ const scores = /* @__PURE__ */ new Map();
24651
+ eligible.forEach((item, index) => scores.set(item, scoreList[index] ?? 0));
24652
+ const scoreOf = (item) => scores.get(item) ?? 0;
24653
+ const when = (item) => {
24654
+ const parsed = Date.parse(item.observedAt ?? item.validFrom ?? "");
24655
+ return Number.isFinite(parsed) ? parsed : 0;
24656
+ };
24657
+ const ordered = [...eligible].sort(
24658
+ (left, right) => (contradictions.has(right.id) ? 1 : 0) - (contradictions.has(left.id) ? 1 : 0) || (left.project === request.project ? 0 : 1) - (right.project === request.project ? 0 : 1) || (scoreOf(right) > 0 ? 1 : 0) - (scoreOf(left) > 0 ? 1 : 0) || kindPreference(left) - kindPreference(right) || scoreOf(right) - scoreOf(left) || CONFIDENCE_RANK[right.confidence] - CONFIDENCE_RANK[left.confidence] || AUTHORITY_WEIGHT[right.authority] - AUTHORITY_WEIGHT[left.authority] || when(right) - when(left) || compareCodeUnitIds(left.id, right.id)
24659
+ );
24660
+ const selected = [];
24661
+ const unresolvedGaps = [];
24662
+ let characters = 0;
24663
+ let deferredForBudget = 0;
24664
+ for (const item of ordered) {
24665
+ if (selected.length >= MAX_CONTEXT_ITEMS) {
24666
+ unresolvedGaps.push("context item limit reached; refine the task or kinds");
24667
+ break;
24668
+ }
24669
+ const itemCharacters = contextItemCharacters(item);
24670
+ if (Math.ceil((characters + itemCharacters) / 4) > budget) {
24671
+ deferredForBudget += 1;
24672
+ continue;
24673
+ }
24674
+ characters += itemCharacters;
24675
+ selected.push(item);
24676
+ }
24677
+ if (deferredForBudget > 0) {
24678
+ unresolvedGaps.push(selected.length === 0 ? `budget of ${budget} tokens cannot fit any selected context` : `budget of ${budget} tokens reached; ${deferredForBudget} candidates deferred`);
24679
+ }
24680
+ if (candidates.length === 0) {
24681
+ unresolvedGaps.push("no context records exist for this project yet");
24682
+ }
24683
+ const bySection = (kind) => selected.filter((item) => item.kind === kind && !contradictions.has(item.id));
24684
+ const packet = {
24685
+ workingState: options.workingState ?? {},
24686
+ currentState: bySection("state").filter((item) => item.status === "current" || item.status === void 0),
24687
+ knowledge: bySection("knowledge"),
24688
+ evidence: bySection("evidence"),
24689
+ episodes: bySection("episode"),
24690
+ skills: bySection("skill").filter((item) => item.status !== "proposed"),
24691
+ contradictions: selected.filter((item) => contradictions.has(item.id)),
24692
+ unresolvedGaps,
24693
+ provenanceSummary: provenanceSummaryOf(selected),
24694
+ estimatedTokens: estimateContextTokens(selected)
24695
+ };
24696
+ validateContextPacketContents(request, packet);
24697
+ return {
24698
+ packet,
24699
+ audit: {
24700
+ request: {
24701
+ project: request.project,
24702
+ role: request.role,
24703
+ task: request.task,
24704
+ ...request.workflowRunId !== void 0 ? { workflowRunId: request.workflowRunId } : {},
24705
+ ...request.stageId !== void 0 ? { stageId: request.stageId } : {}
24706
+ },
24707
+ selectedIds: selected.map((item) => item.id),
24708
+ provenanceSummary: packet.provenanceSummary,
24709
+ estimatedTokens: packet.estimatedTokens,
24710
+ budgetTokens: budget,
24711
+ candidateCount: candidates.length,
24712
+ excludedSuperseded,
24713
+ unresolvedGaps,
24714
+ relevance: {
24715
+ taskTokens: new Set(relevanceTokens(request.task)).size,
24716
+ matchedCandidates: scoreList.filter((score) => score > 0).length,
24717
+ selected: selected.map((item) => roundRelevance(scoreOf(item)))
24718
+ }
24719
+ }
24720
+ };
24721
+ }
24722
+ function memoryRecordToContextItem(record2, project) {
24723
+ let authority = "instruction";
24724
+ if (record2.authority === "evidence") {
24725
+ authority = "evidence";
24726
+ }
24727
+ const sourceType = CONTEXT_SOURCE_TYPES.includes(record2.provenance?.sourceType) ? record2.provenance.sourceType : "git";
24728
+ const targetProject = record2.scope === "operator" ? "_shared" : project;
24729
+ return parseContextItem({
24730
+ id: `mem_${record2.id}`,
24731
+ kind: "knowledge",
24732
+ scope: record2.scope,
24733
+ project: targetProject,
24734
+ summary: record2.summary,
24735
+ provenance: {
24736
+ sourceType,
24737
+ sourceRef: record2.provenance?.sourceRef ?? `memory:${record2.id}.md`
24738
+ },
24739
+ authority,
24740
+ confidence: record2.confidence,
24741
+ status: record2.lifecycle === "active" ? "current" : "superseded",
24742
+ evidenceRefs: record2.evidenceRefs && record2.evidenceRefs.length > 0 ? record2.evidenceRefs : void 0
24743
+ });
24744
+ }
24745
+
24746
+ // plugins/kxm/src/memory.ts
24747
+ var import_yaml5 = __toESM(require_dist(), 1);
24748
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
24749
+ import { extname as extname2, join as join10, resolve as resolve5 } from "node:path";
24750
+ var MEMORY_SCHEMA = "kxm.memory.v1";
24751
+ var VALID_SCOPES = /* @__PURE__ */ new Set(["agent", "project", "run", "operator"]);
24752
+ var VALID_AUTHORITIES = /* @__PURE__ */ new Set(["instruction", "evidence", "promoted"]);
24753
+ var VALID_CONFIDENCES = /* @__PURE__ */ new Set(["verified", "probable", "uncertain"]);
24754
+ var VALID_LIFECYCLES = /* @__PURE__ */ new Set(["active", "deprecated", "superseded"]);
24755
+ var BANNED_CONTROL_PLANE_FIELDS = /* @__PURE__ */ new Set([
24756
+ "permissions",
24757
+ "tools",
24758
+ "allow",
24759
+ "deny",
24760
+ "grants",
24761
+ "approval",
24762
+ "policy",
24763
+ "scopes",
24764
+ "credentials",
24765
+ "secrets",
24766
+ "token",
24767
+ "apiKey",
24768
+ "password"
24769
+ ]);
24770
+ function parseFrontmatter(content) {
24771
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
24772
+ if (!match) {
24773
+ throw new Error("memory file must contain YAML frontmatter enclosed in '---'");
24774
+ }
24775
+ return { frontmatter: match[1] ?? "", body: match[2]?.trim() ?? "" };
24776
+ }
24777
+ function parseMemoryRecord(raw, filename = "memory.md") {
24778
+ const { frontmatter, body } = parseFrontmatter(raw);
24779
+ const data = (0, import_yaml5.parse)(frontmatter);
24780
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
24781
+ throw new Error(`invalid YAML frontmatter in ${filename}`);
24782
+ }
24783
+ for (const field of BANNED_CONTROL_PLANE_FIELDS) {
24784
+ if (field in data) {
24785
+ throw new Error(`memory record ${filename} may not carry control-plane field '${field}'`);
24786
+ }
24787
+ }
24788
+ if (data.schema !== MEMORY_SCHEMA) {
24789
+ throw new Error(`memory record ${filename} has unsupported schema: expected ${MEMORY_SCHEMA}, got ${String(data.schema)}`);
24790
+ }
24791
+ const id = typeof data.id === "string" ? data.id.trim() : "";
24792
+ if (!id || !/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i.test(id)) {
24793
+ throw new Error(`memory record ${filename} has invalid id: ${String(data.id)}`);
24794
+ }
24795
+ const scope = data.scope;
24796
+ if (!VALID_SCOPES.has(scope)) {
24797
+ throw new Error(`memory record ${filename} has invalid scope: must be one of agent, project, run, operator`);
24798
+ }
24799
+ const kind = typeof data.kind === "string" ? data.kind.trim() : "";
24800
+ if (!kind) {
24801
+ throw new Error(`memory record ${filename} is missing kind`);
24802
+ }
24803
+ const rawSummary = typeof data.summary === "string" ? data.summary.trim() : "";
24804
+ if (!rawSummary) {
24805
+ throw new Error(`memory record ${filename} is missing summary`);
24806
+ }
24807
+ const summary = redactSecrets(rawSummary);
24808
+ const rawProv = data.provenance;
24809
+ if (!rawProv || typeof rawProv !== "object" || typeof rawProv.sourceType !== "string" || !rawProv.sourceType.trim()) {
24810
+ throw new Error(`memory record ${filename} is missing provenance.sourceType`);
24811
+ }
24812
+ const provenance = {
24813
+ sourceType: rawProv.sourceType.trim(),
24814
+ ...typeof rawProv.sourceRef === "string" ? { sourceRef: redactSecrets(rawProv.sourceRef.trim()) } : {},
24815
+ ...typeof rawProv.runId === "string" ? { runId: rawProv.runId.trim() } : {},
24816
+ ...typeof rawProv.timestamp === "string" ? { timestamp: rawProv.timestamp.trim() } : {}
24817
+ };
24818
+ const authority = data.authority;
24819
+ if (!VALID_AUTHORITIES.has(authority)) {
24820
+ throw new Error(`memory record ${filename} has invalid authority: ${String(data.authority)}`);
24821
+ }
24822
+ const confidence = data.confidence;
24823
+ if (!VALID_CONFIDENCES.has(confidence)) {
24824
+ throw new Error(`memory record ${filename} has invalid confidence: ${String(data.confidence)}`);
24825
+ }
24826
+ const lifecycle = data.lifecycle;
24827
+ if (!VALID_LIFECYCLES.has(lifecycle)) {
24828
+ throw new Error(`memory record ${filename} has invalid lifecycle: ${String(data.lifecycle)}`);
24829
+ }
24830
+ const rawRefs = Array.isArray(data.evidenceRefs) ? data.evidenceRefs : [];
24831
+ const evidenceRefs = rawRefs.map(String);
24832
+ return {
24833
+ schema: MEMORY_SCHEMA,
24834
+ id,
24835
+ scope,
24836
+ kind,
24837
+ summary,
24838
+ provenance,
24839
+ authority,
24840
+ confidence,
24841
+ lifecycle,
24842
+ evidenceRefs,
24843
+ ...body ? { body } : {}
24844
+ };
24845
+ }
24846
+ function memoryDirectories(repoRoot) {
24847
+ const root = resolve5(repoRoot);
24848
+ const memoryDir = join10(root, ".kxm", "memory");
24849
+ const candidatesDir = join10(memoryDir, "candidates");
24850
+ return { memoryDir, candidatesDir };
24851
+ }
24852
+ function loadAuthoredMemory(repoRoot) {
24853
+ const { memoryDir } = memoryDirectories(repoRoot);
24854
+ if (!existsSync9(memoryDir)) return [];
24855
+ const records = [];
24856
+ const entries = readdirSync5(memoryDir, { withFileTypes: true });
24857
+ for (const entry of entries) {
24858
+ if (entry.isFile() && extname2(entry.name) === ".md") {
24859
+ const fullPath = join10(memoryDir, entry.name);
24860
+ try {
24861
+ const text = readFileSync7(fullPath, "utf8");
24862
+ const record2 = parseMemoryRecord(text, entry.name);
24863
+ if (record2.lifecycle === "active") {
24864
+ records.push(record2);
24865
+ }
24866
+ } catch (error) {
24867
+ throw new Error(`failed to parse authored memory file ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
24868
+ }
24869
+ }
24870
+ }
24871
+ return records.sort((left, right) => left.id.localeCompare(right.id));
24872
+ }
24873
+
24874
+ // plugins/kxm/src/skills.ts
24875
+ var import_yaml6 = __toESM(require_dist(), 1);
24876
+ import { createHash as createHash10 } from "node:crypto";
24877
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, readFileSync as readFileSync8, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "node:fs";
24878
+ import { dirname as dirname7, join as join11 } from "node:path";
24879
+ var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
24880
+ var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
24881
+ var SKILL_DECISION_SCHEMA = "kxm.skill-decision.v1";
24882
+ var MAX_SKILL_NAME_CHARS = 64;
24883
+ var MAX_SKILL_CONTENT_CHARS = 32e3;
24884
+ var MAX_SKILL_EVIDENCE_REFS = 32;
24885
+ var MAX_SKILL_MODELS = 16;
24886
+ var PROMOTION_REQUIRED_EVALUATIONS = [
24887
+ "static-review",
24888
+ "sandbox",
24889
+ "functional",
24890
+ "safety"
24891
+ ];
24892
+ var SkillLifecycleError = class extends Error {
24893
+ code;
24894
+ constructor(code, message) {
24895
+ super(message);
24896
+ this.name = "SkillLifecycleError";
24897
+ this.code = code;
24898
+ }
24899
+ };
24900
+ function skillContentSha256(content) {
24901
+ return createHash10("sha256").update(content, "utf8").digest("hex");
24902
+ }
24903
+ function skillIdFor(name, contentSha256) {
24904
+ const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
24905
+ if (!slug) throw new SkillLifecycleError("invalid_skill_name", "skill name must contain alphanumeric characters");
24906
+ return `${slug}.${contentSha256.slice(0, 12)}`;
24907
+ }
24908
+ function parseSkillFrontmatter(content) {
24909
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
24910
+ if (!match) {
24911
+ return { frontmatter: null, body: content };
24912
+ }
24913
+ const rawFm = match[1];
24914
+ const rawBody = match[2];
24915
+ if (rawFm === void 0 || rawBody === void 0) {
24916
+ return { frontmatter: null, body: content };
24917
+ }
24918
+ try {
24919
+ const parsed = (0, import_yaml6.parse)(rawFm);
24920
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
24921
+ return { frontmatter: parsed, body: rawBody };
24922
+ }
24923
+ } catch {
24924
+ }
24925
+ return { frontmatter: null, body: content };
24926
+ }
24927
+ function ensureSkillFrontmatter(content, name, description) {
24928
+ const parsed = parseSkillFrontmatter(content);
24929
+ if (parsed.frontmatter) {
24930
+ const fmName = typeof parsed.frontmatter.name === "string" && parsed.frontmatter.name.trim() ? parsed.frontmatter.name.trim() : name;
24931
+ const fmDesc = typeof parsed.frontmatter.description === "string" && parsed.frontmatter.description.trim() ? parsed.frontmatter.description.trim() : description || `Governed skill for ${fmName}`;
24932
+ const rest2 = parsed.body.replace(/^(\r?\n)+/, "");
24933
+ return `---
24934
+ name: ${fmName}
24935
+ description: ${fmDesc}
24936
+ ---
24937
+
24938
+ ${rest2}`;
24939
+ }
24940
+ const desc = description || `Governed skill for ${name}`;
24941
+ const rest = content.replace(/^(\r?\n)+/, "");
24942
+ return `---
24943
+ name: ${name}
24944
+ description: ${desc}
24945
+ ---
24946
+
24947
+ ${rest}`;
24948
+ }
24949
+ function createUnifiedPatch(relativePath, content) {
24950
+ const lines = content.split("\n");
24951
+ const count = lines.length;
24952
+ const header = [
24953
+ `diff --git a/${relativePath} b/${relativePath}`,
24954
+ "new file mode 100644",
24955
+ "--- /dev/null",
24956
+ `+++ b/${relativePath}`,
24957
+ `@@ -0,0 +1,${count} @@`
24958
+ ];
24959
+ const body = lines.map((l) => `+${l}`);
24960
+ return [...header, ...body, ""].join("\n");
24961
+ }
24962
+ var SkillLifecycle = class {
24963
+ root;
24964
+ now;
24965
+ allowOptimizationEvals;
24966
+ dryRun;
24967
+ /** What a `dryRun` lifecycle would have written or moved, in order. */
24968
+ planned = [];
24969
+ constructor(root, options = {}) {
24970
+ this.root = root;
24971
+ this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
24972
+ this.allowOptimizationEvals = options.allowOptimizationEvals === true;
24973
+ this.dryRun = options.dryRun === true;
24974
+ }
24975
+ write(file, content) {
24976
+ if (this.dryRun) {
24977
+ this.planned.push({ action: "write", target: file });
24978
+ return;
24979
+ }
24980
+ mkdirSync5(dirname7(file), { recursive: true });
24981
+ writeFileSync5(file, content);
24982
+ }
24983
+ dir(state) {
24984
+ return join11(this.root, state === "candidate" ? "candidates" : `${state}s`.replace("rejecteds", "rejected").replace("promoteds", "promoted"));
24985
+ }
24986
+ historyFile(id) {
24987
+ return join11(this.root, "history", `${id}.jsonl`);
24988
+ }
24989
+ paths(state, id) {
24990
+ const dir = join11(this.dir(state), id);
24991
+ return { dir, metadata: join11(dir, "metadata.json"), skill: join11(dir, "SKILL.md") };
24992
+ }
24993
+ appendHistory(id, record2) {
24994
+ const line = `${JSON.stringify(record2)}
24995
+ `;
24996
+ if (existsSync10(this.historyFile(id))) {
24997
+ const existing = readFileSync8(this.historyFile(id), "utf8");
24998
+ const lines = existing.split("\n").filter((entry) => entry.trim());
24999
+ this.write(this.historyFile(id), [...lines.slice(-499), line.trim()].join("\n") + "\n");
25000
+ } else {
25001
+ this.write(this.historyFile(id), line);
25002
+ }
25003
+ }
25004
+ history(id) {
25005
+ const file = this.historyFile(id);
25006
+ if (!existsSync10(file)) return [];
25007
+ return readFileSync8(file, "utf8").split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
25008
+ }
25009
+ readMetadata(state, id) {
25010
+ const { metadata } = this.paths(state, id);
25011
+ if (!existsSync10(metadata)) {
25012
+ throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${state}`);
25013
+ }
25014
+ return JSON.parse(readFileSync8(metadata, "utf8"));
25015
+ }
25016
+ move(from, to, id) {
25017
+ const fromDir = join11(this.dir(from), id);
25018
+ const toDir = join11(this.dir(to), id);
25019
+ if (!existsSync10(fromDir)) {
25020
+ throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${from}`);
25021
+ }
25022
+ if (this.dryRun) {
25023
+ this.planned.push({ action: "move", target: `${fromDir} -> ${toDir}` });
25024
+ return;
25025
+ }
25026
+ mkdirSync5(this.dir(to), { recursive: true });
25027
+ if (existsSync10(toDir)) rmSync(toDir, { recursive: true, force: true });
25028
+ renameSync(fromDir, toDir);
25029
+ }
25030
+ /** Submit a new skill candidate. Content is redacted of secret material at
25031
+ * creation; the ID is derived from name + content hash. */
25032
+ create(input) {
25033
+ const name = input.name?.trim();
25034
+ if (!name || name.length > MAX_SKILL_NAME_CHARS) {
25035
+ throw new SkillLifecycleError("invalid_skill_name", `skill name must be 1-${MAX_SKILL_NAME_CHARS} characters`);
25036
+ }
25037
+ const rawContent = redactSecrets(input.content ?? "");
25038
+ if (!rawContent.trim() || rawContent.length > MAX_SKILL_CONTENT_CHARS) {
25039
+ throw new SkillLifecycleError("invalid_skill_content", `skill content must be 1-${MAX_SKILL_CONTENT_CHARS} characters`);
25040
+ }
25041
+ const rawDesc = redactSecrets(input.description?.trim() ?? "");
25042
+ const content = ensureSkillFrontmatter(rawContent, name, rawDesc);
25043
+ if (content.length > MAX_SKILL_CONTENT_CHARS) {
25044
+ throw new SkillLifecycleError("invalid_skill_content", `skill content must be 1-${MAX_SKILL_CONTENT_CHARS} characters`);
25045
+ }
25046
+ const { frontmatter } = parseSkillFrontmatter(content);
25047
+ const description = typeof frontmatter?.description === "string" && frontmatter.description.trim() ? frontmatter.description.trim() : rawDesc || `Governed skill for ${name}`;
25048
+ const sources = {
25049
+ runIds: boundedList(input.sources?.runIds, "runIds"),
25050
+ journalEntryIds: boundedList(input.sources?.journalEntryIds, "journalEntryIds"),
25051
+ evidenceReceipts: boundedList(input.sources?.evidenceReceipts, "evidenceReceipts")
25052
+ };
25053
+ if (sources.runIds.length === 0 && sources.journalEntryIds.length === 0 && sources.evidenceReceipts.length === 0) {
25054
+ throw new SkillLifecycleError(
25055
+ "skill_sources_required",
25056
+ "a skill candidate must reference at least one source run, journal entry, or evidence receipt"
25057
+ );
25058
+ }
25059
+ const createdBy = input.createdBy?.trim();
25060
+ if (!createdBy) throw new SkillLifecycleError("invalid_skill_author", "createdBy is required");
25061
+ const models = boundedList(input.compatibility?.models, "compatibility.models");
25062
+ if (models.length === 0 || models.length > MAX_SKILL_MODELS) {
25063
+ throw new SkillLifecycleError("invalid_skill_compatibility", `compatibility.models must list 1-${MAX_SKILL_MODELS} models`);
25064
+ }
25065
+ const harness = input.compatibility?.harness?.trim();
25066
+ if (!harness) throw new SkillLifecycleError("invalid_skill_compatibility", "compatibility.harness is required");
25067
+ const contentSha256 = skillContentSha256(content);
25068
+ const id = skillIdFor(name, contentSha256);
25069
+ const { metadata, skill } = this.paths("candidate", id);
25070
+ if (existsSync10(metadata)) {
25071
+ throw new SkillLifecycleError(
25072
+ "skill_candidate_exists",
25073
+ `identical candidate ${id} already exists; changed behavior requires changed content`
25074
+ );
25075
+ }
25076
+ const record2 = {
25077
+ schema: SKILL_CANDIDATE_SCHEMA,
25078
+ id,
25079
+ name,
25080
+ description,
25081
+ contentSha256,
25082
+ version: input.version ?? 1,
25083
+ sources,
25084
+ compatibility: { harness, models },
25085
+ createdBy,
25086
+ createdAt: this.now(),
25087
+ ...input.supersedes ? { supersedes: input.supersedes } : {}
25088
+ };
25089
+ this.write(skill, content);
25090
+ this.write(metadata, `${JSON.stringify(record2, null, 2)}
25091
+ `);
25092
+ this.appendHistory(id, { schema: "kxm.skill-history-event.v1", event: "candidate_created", by: createdBy, supersedes: input.supersedes, at: record2.createdAt });
25093
+ return record2;
25094
+ }
25095
+ /** Record a protected evaluation. A failed functional or safety evaluation
25096
+ * deterministically quarantines the candidate. */
25097
+ evaluate(candidateId, input) {
25098
+ if (input.kind === "optimization" && !this.allowOptimizationEvals) {
25099
+ throw new SkillLifecycleError(
25100
+ "skill_optimization_disabled",
25101
+ "optimization evaluations are disabled; enable them explicitly behind protected evals"
25102
+ );
25103
+ }
25104
+ const metadata = this.readMetadata("candidate", candidateId);
25105
+ const evaluatorVersion = input.evaluatorVersion?.trim();
25106
+ if (!evaluatorVersion) throw new SkillLifecycleError("invalid_skill_evaluation", "evaluatorVersion is required");
25107
+ const evaluation = {
25108
+ schema: SKILL_EVALUATION_SCHEMA,
25109
+ candidateId,
25110
+ kind: input.kind,
25111
+ evaluatorVersion,
25112
+ passed: input.passed === true,
25113
+ ...input.score !== void 0 ? { score: input.score } : {},
25114
+ ...input.details ? { details: redactSecrets(input.details.slice(0, 2e3)) } : {},
25115
+ evaluatedAt: this.now()
25116
+ };
25117
+ this.appendHistory(candidateId, evaluation);
25118
+ let quarantined = false;
25119
+ if (!evaluation.passed && (input.kind === "functional" || input.kind === "safety")) {
25120
+ const decision = {
25121
+ schema: SKILL_DECISION_SCHEMA,
25122
+ candidateId,
25123
+ decision: "quarantined",
25124
+ decidedBy: input.evaluatedBy?.trim() || `evaluator:${evaluatorVersion}`,
25125
+ reason: `automatic quarantine: ${input.kind} evaluation failed (${evaluatorVersion})`,
25126
+ evidenceRefs: [`evaluation:${input.kind}:${evaluatorVersion}`],
25127
+ decidedAt: this.now()
25128
+ };
25129
+ this.move("candidate", "quarantined", candidateId);
25130
+ this.appendHistory(candidateId, decision);
25131
+ quarantined = true;
25132
+ }
25133
+ return { evaluation, quarantined };
25134
+ }
25135
+ evaluationsFor(candidateId) {
25136
+ return this.history(candidateId).filter(
25137
+ (record2) => record2.schema === SKILL_EVALUATION_SCHEMA
25138
+ );
25139
+ }
25140
+ /** Promote a candidate that passed every protected evaluation. The
25141
+ * promoter must differ from the author, cite durable evidence, and the
25142
+ * promoted content is hash-pinned and immutable. Emits a unified diff patch
25143
+ * instead of moving the candidate directory. */
25144
+ promote(candidateId, decision) {
25145
+ const metadata = this.readMetadata("candidate", candidateId);
25146
+ const decidedBy = decision.decidedBy?.trim();
25147
+ if (!decidedBy) throw new SkillLifecycleError("invalid_skill_decision", "decidedBy is required");
25148
+ if (decidedBy === metadata.createdBy) {
25149
+ throw new SkillLifecycleError("skill_promotion_invalid", "the author of a skill candidate cannot promote it");
25150
+ }
25151
+ const evidenceRefs = boundedList(decision.evidenceRefs, "evidenceRefs");
25152
+ if (evidenceRefs.length === 0) {
25153
+ throw new SkillLifecycleError("skill_promotion_invalid", "promotion requires durable evidence references");
25154
+ }
25155
+ const evaluations = this.evaluationsFor(candidateId);
25156
+ const missing = [];
25157
+ for (const kind of PROMOTION_REQUIRED_EVALUATIONS) {
25158
+ const latest = [...evaluations].reverse().find((record3) => record3.kind === kind);
25159
+ if (!latest || !latest.passed) missing.push(kind);
25160
+ }
25161
+ if (missing.length > 0) {
25162
+ throw new SkillLifecycleError(
25163
+ "skill_evaluations_incomplete",
25164
+ `promotion requires passing ${missing.join(", ")} evaluations`
25165
+ );
25166
+ }
25167
+ this.verify("candidate", candidateId);
25168
+ const record2 = {
25169
+ schema: SKILL_DECISION_SCHEMA,
25170
+ candidateId,
25171
+ decision: "promoted",
25172
+ decidedBy,
25173
+ reason: decision.reason?.trim() || "passed protected evaluation",
25174
+ evidenceRefs,
25175
+ decidedAt: this.now()
25176
+ };
25177
+ const candidatePaths = this.paths("candidate", candidateId);
25178
+ const promotedPaths = this.paths("promoted", candidateId);
25179
+ const skillContent = readFileSync8(candidatePaths.skill, "utf8");
25180
+ const metadataContent = readFileSync8(candidatePaths.metadata, "utf8");
25181
+ this.write(promotedPaths.skill, skillContent);
25182
+ this.write(promotedPaths.metadata, metadataContent);
25183
+ const patchPath = join11(this.root, "patches", `${candidateId}.patch`);
25184
+ const relSkillPath = `.kxm/skills/promoted/${candidateId}/SKILL.md`;
25185
+ const relMetaPath = `.kxm/skills/promoted/${candidateId}/metadata.json`;
25186
+ const patch = `${createUnifiedPatch(relSkillPath, skillContent)}${createUnifiedPatch(relMetaPath, metadataContent)}`;
25187
+ this.write(patchPath, patch);
25188
+ this.appendHistory(candidateId, record2);
25189
+ return { ...metadata, patch, patchPath };
25190
+ }
25191
+ reject(candidateId, decision) {
25192
+ const metadata = this.readMetadata("candidate", candidateId);
25193
+ const record2 = {
25194
+ schema: SKILL_DECISION_SCHEMA,
25195
+ candidateId,
25196
+ decision: "rejected",
25197
+ decidedBy: decision.decidedBy?.trim() || "kxm-admin",
25198
+ reason: decision.reason?.trim() || "rejected",
25199
+ evidenceRefs: [],
25200
+ decidedAt: this.now()
25201
+ };
25202
+ this.move("candidate", "rejected", candidateId);
25203
+ this.appendHistory(candidateId, record2);
25204
+ return metadata;
25205
+ }
25206
+ /** Verify content integrity of a stored skill (any state). Detects
25207
+ * out-of-band edits to promoted skills and verifies standard YAML frontmatter. */
25208
+ verify(state, id) {
25209
+ const metadata = this.readMetadata(state, id);
25210
+ const { skill } = this.paths(state, id);
25211
+ const content = readFileSync8(skill, "utf8");
25212
+ if (skillContentSha256(content) !== metadata.contentSha256) {
25213
+ throw new SkillLifecycleError(
25214
+ "skill_integrity_violation",
25215
+ `skill ${id} content does not match its pinned hash; promoted skills are immutable and require a new candidate/eval cycle`
25216
+ );
25217
+ }
25218
+ const { frontmatter } = parseSkillFrontmatter(content);
25219
+ if (!frontmatter || typeof frontmatter.name !== "string" || !frontmatter.name.trim() || typeof frontmatter.description !== "string" || !frontmatter.description.trim()) {
25220
+ throw new SkillLifecycleError(
25221
+ "invalid_skill_frontmatter",
25222
+ `skill ${id} must contain valid YAML frontmatter with 'name' and 'description'`
25223
+ );
25224
+ }
25225
+ return metadata;
25226
+ }
25227
+ list(state) {
25228
+ const dir = this.dir(state);
25229
+ if (!existsSync10(dir)) return [];
25230
+ const ids = readdirSorted(dir);
25231
+ return ids.map((id) => {
25232
+ try {
25233
+ return this.readMetadata(state, id);
25234
+ } catch {
25235
+ return void 0;
25236
+ }
25237
+ }).filter((metadata) => metadata !== void 0);
25238
+ }
25239
+ read(state, id) {
25240
+ const metadata = this.readMetadata(state, id);
25241
+ const { skill } = this.paths(state, id);
25242
+ return { metadata, content: readFileSync8(skill, "utf8") };
25243
+ }
25244
+ };
25245
+ function boundedList(value, field) {
25246
+ if (value === void 0) return [];
25247
+ if (!Array.isArray(value)) {
25248
+ throw new SkillLifecycleError("invalid_skill_input", `${field} must be an array of strings`);
25249
+ }
25250
+ const refs = value.map((ref) => String(ref).trim()).filter((ref) => ref.length > 0);
25251
+ if (refs.length > MAX_SKILL_EVIDENCE_REFS) {
25252
+ throw new SkillLifecycleError("invalid_skill_input", `${field} exceeds ${MAX_SKILL_EVIDENCE_REFS} references`);
25253
+ }
25254
+ return [...new Set(refs)];
25255
+ }
25256
+ function readdirSorted(dir) {
25257
+ return readdirSync6(dir).filter((entry) => statSync2(join11(dir, entry)).isDirectory()).sort();
25258
+ }
25259
+
25260
+ // plugins/kxm/src/dispatch-context.ts
25261
+ var DISPATCH_CONTEXT_BUDGET_TOKENS = 4e3;
25262
+ var DISPATCH_CONTEXT_MAX_PROJECT_ITEMS = 5;
25263
+ var GAP_NOT_LOADED = "dispatch_context_not_loaded";
25264
+ var GAP_FAILED = "dispatch_context_failed";
25265
+ var GAP_GIT_UNAVAILABLE = "dispatch_context_withheld:git_unavailable";
25266
+ var GAP_UNCOMMITTED = "dispatch_context_withheld:uncommitted";
25267
+ var GAP_DRIFT = "dispatch_context_withheld:memory_revision_drift";
25268
+ var GAP_MEMORY_UNREADABLE = "dispatch_context_memory_unreadable";
25269
+ var GAP_SKILLS_UNREADABLE = "dispatch_context_skills_unreadable";
25270
+ var DISPATCH_CONTEXT_PATHSPEC = [
25271
+ ":(glob).kxm/memory/*.md",
25272
+ ":(glob).kxm/skills/promoted/*/SKILL.md",
25273
+ ":(glob).kxm/skills/promoted/*/metadata.json"
25274
+ ];
25275
+ var GIT_STATUS_TIMEOUT_MS = 5e3;
25276
+ var EMPTY_DISPATCH_SOURCES = Object.freeze({
25277
+ present: false,
25278
+ pool: Object.freeze([]),
25279
+ promoted: Object.freeze([]),
25280
+ gaps: Object.freeze([]),
25281
+ skippedUnboundScopes: 0
25282
+ });
25283
+ var NOT_LOADED_DISPATCH_SOURCES = Object.freeze({
25284
+ present: true,
25285
+ pool: Object.freeze([]),
25286
+ promoted: Object.freeze([]),
25287
+ gaps: Object.freeze([GAP_NOT_LOADED]),
25288
+ skippedUnboundScopes: 0
25289
+ });
25290
+ function contextRoleForAgent(agentId) {
25291
+ if (CONTEXT_ROLES.includes(agentId)) return agentId;
25292
+ for (const role of CONTEXT_ROLES) {
25293
+ if (agentId.startsWith(`${role}-`)) return role;
25294
+ }
25295
+ return agentId;
25296
+ }
25297
+ function dispatchContextPresent(projectRoot) {
25298
+ return hasTopLevelMarkdown(join12(projectRoot, ".kxm", "memory")) || hasAnyEntry(join12(projectRoot, ".kxm", "skills", "promoted"));
25299
+ }
25300
+ function hasTopLevelMarkdown(dir) {
25301
+ try {
25302
+ return readdirSync7(dir, { withFileTypes: true }).some((entry) => entry.isFile() && extname3(entry.name) === ".md");
25303
+ } catch {
25304
+ return false;
25305
+ }
25306
+ }
25307
+ function hasAnyEntry(dir) {
25308
+ try {
25309
+ return readdirSync7(dir).length > 0;
25310
+ } catch {
25311
+ return false;
25312
+ }
25313
+ }
25314
+ function gitEnvironment2() {
25315
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
25316
+ }
25317
+ function committedGateGap(projectRoot) {
25318
+ const result = spawnSync3("git", [
25319
+ "--no-optional-locks",
25320
+ "-C",
25321
+ projectRoot,
25322
+ "status",
25323
+ "--porcelain=v1",
25324
+ "--untracked-files=all",
25325
+ "--ignored=matching",
25326
+ "--",
25327
+ ...DISPATCH_CONTEXT_PATHSPEC
25328
+ ], {
25329
+ encoding: "utf8",
25330
+ env: gitEnvironment2(),
25331
+ timeout: GIT_STATUS_TIMEOUT_MS,
25332
+ windowsHide: true
25333
+ });
25334
+ if (result.error || result.status !== 0) return GAP_GIT_UNAVAILABLE;
25335
+ if (typeof result.stdout !== "string" || result.stdout.length > 0) return GAP_UNCOMMITTED;
25336
+ return void 0;
25337
+ }
25338
+ function gapId(value) {
25339
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value) ? value : "invalid";
25340
+ }
25341
+ function withheld(gaps) {
25342
+ return { present: true, pool: [], promoted: [], gaps: [...gaps], skippedUnboundScopes: 0 };
25343
+ }
25344
+ function loadDispatchContextSources(input) {
25345
+ try {
25346
+ const { projectRoot, projectId, pinnedMemoryRevision } = input;
25347
+ if (!dispatchContextPresent(projectRoot)) return EMPTY_DISPATCH_SOURCES;
25348
+ const gateGap = committedGateGap(projectRoot);
25349
+ if (gateGap !== void 0) return withheld([gateGap]);
25350
+ if (computeKxmMemoryRevision({ projectRoot }) !== pinnedMemoryRevision) return withheld([GAP_DRIFT]);
25351
+ const gaps = [];
25352
+ let records;
25353
+ try {
25354
+ records = loadAuthoredMemory(projectRoot);
25355
+ } catch {
25356
+ gaps.push(GAP_MEMORY_UNREADABLE);
25357
+ records = [];
25358
+ }
25359
+ const pool = [];
25360
+ let skippedUnboundScopes = 0;
25361
+ for (const record2 of records) {
25362
+ if (record2.scope !== "project" && record2.scope !== "operator") {
25363
+ skippedUnboundScopes += 1;
25364
+ continue;
25365
+ }
25366
+ try {
25367
+ pool.push(memoryRecordToContextItem(record2, projectId));
25368
+ } catch {
25369
+ gaps.push(`dispatch_context_memory_rejected:${gapId(record2.id)}`);
25370
+ }
25371
+ }
25372
+ const promoted = [];
25373
+ try {
25374
+ const lifecycle = new SkillLifecycle(join12(projectRoot, ".kxm", "skills"));
25375
+ for (const metadata of lifecycle.list("promoted")) {
25376
+ try {
25377
+ promoted.push(lifecycle.verify("promoted", metadata.id));
25378
+ } catch {
25379
+ gaps.push(`dispatch_context_skill_unverified:${gapId(metadata.id)}`);
25380
+ }
25381
+ }
25382
+ } catch {
25383
+ gaps.push(GAP_SKILLS_UNREADABLE);
25384
+ }
25385
+ if (computeKxmMemoryRevision({ projectRoot }) !== pinnedMemoryRevision) return withheld([...gaps, GAP_DRIFT]);
25386
+ return { present: true, pool, promoted, gaps, skippedUnboundScopes };
25387
+ } catch {
25388
+ return withheld([GAP_FAILED]);
25389
+ }
25390
+ }
25391
+ function assembleDispatchContext(sources, input) {
25392
+ const role = contextRoleForAgent(input.agentId);
25393
+ try {
25394
+ if (sources.pool.length === 0 && sources.promoted.length === 0) {
25395
+ return { items: [], deliveredIds: [], renderDeferred: 0, role, unresolvedGaps: [...sources.gaps] };
25396
+ }
25397
+ const task = (input.task.trim() || input.stepId).slice(0, MAX_CONTEXT_TASK_CHARS);
25398
+ const promoted = sources.promoted;
25399
+ const snapshot = {
25400
+ list: (state) => state === "promoted" ? [...promoted] : [],
25401
+ verify: (state, id) => {
25402
+ const found = state === "promoted" ? promoted.find((metadata) => metadata.id === id) : void 0;
25403
+ if (!found) throw new Error("skill is not in the verified dispatch snapshot");
25404
+ return found;
25405
+ }
25406
+ };
25407
+ const { packet, audit } = arbitrate(
25408
+ {
25409
+ project: input.projectId,
25410
+ role,
25411
+ task,
25412
+ workflowRunId: input.runId,
25413
+ stageId: input.stepId,
25414
+ budgetTokens: Math.min(rolePolicy(role).budgetTokens, DISPATCH_CONTEXT_BUDGET_TOKENS)
25415
+ },
25416
+ [...sources.pool],
25417
+ promoted.length > 0 ? { skillLifecycle: snapshot } : {}
25418
+ );
25419
+ const byId = /* @__PURE__ */ new Map();
25420
+ for (const section of [packet.currentState, packet.knowledge, packet.evidence, packet.episodes, packet.skills, packet.contradictions]) {
25421
+ for (const item of section) byId.set(item.id, item);
25422
+ }
25423
+ const items = [];
25424
+ let projectItems = 0;
25425
+ let renderDeferred = 0;
25426
+ for (const id of audit.selectedIds) {
25427
+ const item = byId.get(id);
25428
+ if (!item) continue;
25429
+ const projectRouted = item.project !== "_shared" && item.kind !== "state" && item.kind !== "episode" && item.kind !== "skill";
25430
+ if (projectRouted) {
25431
+ if (projectItems >= DISPATCH_CONTEXT_MAX_PROJECT_ITEMS) {
25432
+ renderDeferred += 1;
25433
+ continue;
25434
+ }
25435
+ projectItems += 1;
25436
+ }
25437
+ items.push(item);
25438
+ }
25439
+ return {
25440
+ items,
25441
+ deliveredIds: items.map((item) => item.id),
25442
+ renderDeferred,
25443
+ role,
25444
+ unresolvedGaps: [
25445
+ ...sources.gaps,
25446
+ ...packet.unresolvedGaps,
25447
+ ...renderDeferred > 0 ? [`dispatch_context_render_deferred:${renderDeferred}`] : []
25448
+ ],
25449
+ audit
25450
+ };
25451
+ } catch {
25452
+ return { items: [], deliveredIds: [], renderDeferred: 0, role, unresolvedGaps: [...sources.gaps, GAP_FAILED] };
25453
+ }
25454
+ }
25455
+
24069
25456
  // plugins/kxm/src/engine-artifacts.ts
24070
- import { join as join10 } from "node:path";
25457
+ import { join as join13 } from "node:path";
24071
25458
 
24072
25459
  // plugins/kxm/src/artifacts-exist.ts
24073
- import { lstatSync as lstatSync4, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
24074
- import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve5 } from "node:path";
25460
+ import { lstatSync as lstatSync4, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
25461
+ import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve6 } from "node:path";
24075
25462
  function staysUnder(root, candidate) {
24076
25463
  const child = relative3(root, candidate);
24077
25464
  return child === "" || !isAbsolute3(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
24078
25465
  }
24079
25466
  function verifyArtifactExists(rootInput, pathInput) {
24080
- const lexicalRoot = resolve5(rootInput);
24081
- const lexicalPath = resolve5(pathInput);
25467
+ const lexicalRoot = resolve6(rootInput);
25468
+ const lexicalPath = resolve6(pathInput);
24082
25469
  if (!staysUnder(lexicalRoot, lexicalPath)) {
24083
25470
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
24084
25471
  }
@@ -24102,7 +25489,7 @@ function verifyArtifactExists(rootInput, pathInput) {
24102
25489
  if (link.isSymbolicLink()) {
24103
25490
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
24104
25491
  }
24105
- const artifact = statSync2(realPath);
25492
+ const artifact = statSync3(realPath);
24106
25493
  if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
24107
25494
  if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
24108
25495
  return { ok: true, path: realPath, bytes: artifact.size };
@@ -24114,14 +25501,14 @@ function verifyArtifactExists(rootInput, pathInput) {
24114
25501
  // plugins/kxm/src/engine-artifacts.ts
24115
25502
  var kxmArtifactsGateSeams = {};
24116
25503
  function evaluateArtifactsGate(context, definition) {
24117
- const root = join10(context.projectRoot, ".kxm", "assets");
25504
+ const root = join13(context.projectRoot, ".kxm", "assets");
24118
25505
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
24119
25506
  const startTime = process.hrtime.bigint();
24120
25507
  let checkedCount = 0;
24121
25508
  let failedCount = 0;
24122
25509
  for (const [index, path] of definition.paths.entries()) {
24123
25510
  checkedCount += 1;
24124
- const result = verifyArtifactExists(root, join10(root, path));
25511
+ const result = verifyArtifactExists(root, join13(root, path));
24125
25512
  if (!result.ok) failedCount += 1;
24126
25513
  kxmArtifactsGateSeams.afterPathChecked?.({ path, index, checkedCount, failedCount });
24127
25514
  }
@@ -24154,7 +25541,7 @@ function evaluateArtifactsGate(context, definition) {
24154
25541
 
24155
25542
  // plugins/kxm/src/engine-command.ts
24156
25543
  import { spawn as spawn2 } from "node:child_process";
24157
- import { createHash as createHash10 } from "node:crypto";
25544
+ import { createHash as createHash11 } from "node:crypto";
24158
25545
 
24159
25546
  // plugins/kxm/src/safety-integrity.ts
24160
25547
  var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
@@ -24202,7 +25589,7 @@ function createCommandObserver(input) {
24202
25589
  return new CommandObserver(input);
24203
25590
  }
24204
25591
  var StreamDigest = class {
24205
- hash = createHash10("sha256");
25592
+ hash = createHash11("sha256");
24206
25593
  bytes = 0;
24207
25594
  complete = 0;
24208
25595
  digest() {
@@ -24277,8 +25664,8 @@ var CommandObserver = class {
24277
25664
  this.requestStop("error", "lost-close");
24278
25665
  }
24279
25666
  run() {
24280
- return new Promise((resolve10) => {
24281
- this.resolveOutcome = resolve10;
25667
+ return new Promise((resolve11) => {
25668
+ this.resolveOutcome = resolve11;
24282
25669
  try {
24283
25670
  this.start();
24284
25671
  } catch (error) {
@@ -25049,7 +26436,7 @@ function hashFromStore(context, runId) {
25049
26436
  }
25050
26437
 
25051
26438
  // plugins/kxm/src/routing.ts
25052
- import { createHash as createHash11 } from "node:crypto";
26439
+ import { createHash as createHash12 } from "node:crypto";
25053
26440
  var ROUTING_RECORD_V2_SCHEMA = "kxm.routing-record.v2";
25054
26441
  var BEHAVIORAL_HASH_VERSION = 1;
25055
26442
  var MAX_PROVIDER_METADATA_FIELDS = 32;
@@ -25072,7 +26459,7 @@ function behavioralConfigHash(input) {
25072
26459
  workflowDefinitionSha256: input.workflowDefinitionSha256?.trim() ?? null,
25073
26460
  verifierConfigSha256: input.verifierConfigSha256?.trim() ?? null
25074
26461
  };
25075
- return createHash11("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
26462
+ return createHash12("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
25076
26463
  }
25077
26464
  function boundedInt(value, field) {
25078
26465
  if (value === void 0 || value === null) return void 0;
@@ -25203,6 +26590,13 @@ function parseRoutingRecordV2(value) {
25203
26590
  }
25204
26591
  return record2;
25205
26592
  }
26593
+ function computeDecayedWeight(recordedAt, halfLifeDays = 14, now = Date.now()) {
26594
+ const ts = typeof recordedAt === "number" ? recordedAt : Date.parse(recordedAt);
26595
+ if (!Number.isFinite(ts)) return 1;
26596
+ const deltaMs = Math.max(0, now - ts);
26597
+ const halfLifeMs = halfLifeDays * 24 * 60 * 60 * 1e3;
26598
+ return Math.pow(2, -deltaMs / halfLifeMs);
26599
+ }
25206
26600
 
25207
26601
  // plugins/kxm/src/engine.ts
25208
26602
  var trustedProducers = /* @__PURE__ */ new WeakSet();
@@ -25241,7 +26635,7 @@ function requireTrustedProducer(producer) {
25241
26635
  }
25242
26636
  }
25243
26637
  function hashCapabilitySecret(secret) {
25244
- return `sha256:${createHash12("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
26638
+ return `sha256:${createHash13("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
25245
26639
  }
25246
26640
  function mintKxmCapabilitySecret() {
25247
26641
  const secret = `kxmcap_${randomBytes2(32).toString("base64url")}`;
@@ -25370,7 +26764,7 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
25370
26764
  return latest;
25371
26765
  }
25372
26766
  function newDriveId(runId, token, runtimeId, monotonicNs) {
25373
- return `drv_${createHash12("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
26767
+ return `drv_${createHash13("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
25374
26768
  }
25375
26769
  function driveErrorSummary(error) {
25376
26770
  const raw = error instanceof KxmConfigError ? error.issues[0]?.code : void 0;
@@ -25597,8 +26991,8 @@ var KxmRunScheduler = class _KxmRunScheduler {
25597
26991
  resolve: (_value) => void 0,
25598
26992
  reject: (_error) => void 0
25599
26993
  };
25600
- const settled = new Promise((resolve10, reject) => {
25601
- pending.resolve = resolve10;
26994
+ const settled = new Promise((resolve11, reject) => {
26995
+ pending.resolve = resolve11;
25602
26996
  pending.reject = reject;
25603
26997
  });
25604
26998
  void settled.then(void 0, () => void 0);
@@ -25712,8 +27106,28 @@ function invokeProducer(producer, request) {
25712
27106
  );
25713
27107
  }
25714
27108
  var kxmGateDispatchSeams = {};
27109
+ function peekDispatchContextSources(context, runId) {
27110
+ if (!dispatchContextPresent(context.projectRoot)) return EMPTY_DISPATCH_SOURCES;
27111
+ let run;
27112
+ try {
27113
+ run = requireRun(context, runId);
27114
+ const state = foldStoredKxmRun(context, run);
27115
+ const plan = rehydrateKxmCompiledPlanFromStore(context.eventStore, run);
27116
+ if (state.status !== "running" || state.currentStep) return NOT_LOADED_DISPATCH_SOURCES;
27117
+ const next = plan.steps[state.pendingStepId ?? plan.entryStepId];
27118
+ if (next?.kind !== "agent" && next?.kind !== "moa") return NOT_LOADED_DISPATCH_SOURCES;
27119
+ } catch {
27120
+ return NOT_LOADED_DISPATCH_SOURCES;
27121
+ }
27122
+ return loadDispatchContextSources({
27123
+ projectRoot: context.projectRoot,
27124
+ projectId: run.projectId,
27125
+ pinnedMemoryRevision: run.memoryRevision
27126
+ });
27127
+ }
25715
27128
  async function stepLocked(context, runId, producer, token) {
25716
- const prepared = context.eventStore.transaction(() => prepareDispatch(context, runId, producer.id));
27129
+ const dispatchSources = peekDispatchContextSources(context, runId);
27130
+ const prepared = context.eventStore.transaction(() => prepareDispatch(context, runId, producer.id, dispatchSources));
25717
27131
  if (prepared.kind === "return") {
25718
27132
  return prepared.handoff ? { state: prepared.state, handoff: prepared.handoff } : { state: prepared.state };
25719
27133
  }
@@ -25976,10 +27390,10 @@ function unreconciledPanelAttemptId(state) {
25976
27390
  }
25977
27391
  function resolveProducerRoute(projectRoot, step, agentId) {
25978
27392
  let agentModel;
25979
- const agentFile = join11(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
25980
- if (existsSync9(agentFile)) {
27393
+ const agentFile = join14(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
27394
+ if (existsSync11(agentFile)) {
25981
27395
  try {
25982
- const parsed = (0, import_yaml5.parse)(readFileSync7(agentFile, "utf8"));
27396
+ const parsed = (0, import_yaml7.parse)(readFileSync9(agentFile, "utf8"));
25983
27397
  if (typeof parsed?.model === "string") {
25984
27398
  agentModel = parsed.model;
25985
27399
  } else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
@@ -26051,7 +27465,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
26051
27465
  const model = selector.slice(slash + 1);
26052
27466
  return { provider, model, selector };
26053
27467
  }
26054
- function prepareDispatch(context, runId, producerId) {
27468
+ function prepareDispatch(context, runId, producerId, dispatchSources) {
26055
27469
  const run = requireRun(context, runId);
26056
27470
  const plan = rehydrateKxmCompiledPlanFromStore(context.eventStore, run);
26057
27471
  const state = foldStoredKxmRun(context, run);
@@ -26221,7 +27635,8 @@ function prepareDispatch(context, runId, producerId) {
26221
27635
  stepAttempt,
26222
27636
  enterRunning: true,
26223
27637
  producerId,
26224
- resolvedRoute
27638
+ resolvedRoute,
27639
+ dispatchSources
26225
27640
  });
26226
27641
  return {
26227
27642
  kind: "panel",
@@ -26234,7 +27649,8 @@ function prepareDispatch(context, runId, producerId) {
26234
27649
  target: step.assignments.target,
26235
27650
  maxParallel: step.assignments.maxParallel,
26236
27651
  first,
26237
- state: first.state
27652
+ state: first.state,
27653
+ dispatchSources
26238
27654
  }
26239
27655
  };
26240
27656
  }
@@ -26273,7 +27689,7 @@ function birthMember(context, input) {
26273
27689
  if (promptText === void 0) {
26274
27690
  throw runtimeError("run_prompt_mismatch", run.runId, "prompt text missing from accepted run prompt store");
26275
27691
  }
26276
- const promptHash = createHash12("sha256").update(promptText, "utf8").digest("hex");
27692
+ const promptHash = createHash13("sha256").update(promptText, "utf8").digest("hex");
26277
27693
  const expectedHash = run.promptSha256.replace(/^sha256:/, "");
26278
27694
  if (promptHash !== expectedHash) {
26279
27695
  throw runtimeError("run_prompt_mismatch", run.runId, "prompt text does not match accepted promptSha256");
@@ -26288,6 +27704,13 @@ function birthMember(context, input) {
26288
27704
  resolvedRoute = routeResult;
26289
27705
  }
26290
27706
  }
27707
+ const dispatchContext = assembleDispatchContext(input.dispatchSources, {
27708
+ projectId: run.projectId,
27709
+ runId: run.runId,
27710
+ stepId: input.stepId,
27711
+ agentId,
27712
+ task: [input.step.instructions, promptText].filter((part) => typeof part === "string" && part.trim().length > 0).join("\n\n")
27713
+ });
26291
27714
  const assignmentId = newKxmAssignmentId();
26292
27715
  const attemptId = newKxmAttemptId();
26293
27716
  const minted = mintCapabilitySecret();
@@ -26354,7 +27777,9 @@ function birthMember(context, input) {
26354
27777
  activeStepIndex: input.step.index,
26355
27778
  totalSteps: input.plan.order.length,
26356
27779
  settledDecisions: []
26357
- }
27780
+ },
27781
+ ...dispatchContext.items.length > 0 ? { arbitratedItems: dispatchContext.items } : {},
27782
+ ...dispatchContext.unresolvedGaps.length > 0 ? { unresolvedGaps: dispatchContext.unresolvedGaps } : {}
26358
27783
  });
26359
27784
  const { packet: contextPacket } = pruneContextPacket(rawContextPacket);
26360
27785
  const generatedPrompt = formatContextPacketForPrompt(contextPacket);
@@ -26391,6 +27816,26 @@ ${generatedPrompt}` : generatedPrompt,
26391
27816
  controller,
26392
27817
  state: next
26393
27818
  };
27819
+ if (input.dispatchSources.present && context.logger) {
27820
+ try {
27821
+ context.logger({
27822
+ event: "dispatch_context_assembled",
27823
+ runId: run.runId,
27824
+ stepId: input.stepId,
27825
+ stepAttempt: input.stepAttempt,
27826
+ agentId,
27827
+ role: dispatchContext.role,
27828
+ deliveredIds: dispatchContext.deliveredIds,
27829
+ renderDeferred: dispatchContext.renderDeferred,
27830
+ provenanceSummary: dispatchContext.audit?.provenanceSummary ?? {},
27831
+ estimatedTokens: dispatchContext.audit?.estimatedTokens ?? 0,
27832
+ budgetTokens: dispatchContext.audit?.budgetTokens ?? null,
27833
+ skippedUnboundScopes: input.dispatchSources.skippedUnboundScopes,
27834
+ unresolvedGaps: dispatchContext.unresolvedGaps
27835
+ });
27836
+ } catch {
27837
+ }
27838
+ }
26394
27839
  kxmPanelDispatchSeams.afterBirth?.(member);
26395
27840
  return member;
26396
27841
  }
@@ -26512,7 +27957,8 @@ async function drivePanel(context, panel, producer) {
26512
27957
  step: panel.step,
26513
27958
  stepId: panel.stepId,
26514
27959
  stepAttempt: panel.stepAttempt,
26515
- producerId: producer.id
27960
+ producerId: producer.id,
27961
+ dispatchSources: panel.dispatchSources
26516
27962
  });
26517
27963
  });
26518
27964
  };
@@ -26640,7 +28086,29 @@ async function drivePanel(context, panel, producer) {
26640
28086
  }
26641
28087
  }
26642
28088
  }
26643
- function producerRoutingRecord(context, dispatch, result, now) {
28089
+ var ENGINE_ROUTING_METADATA_KEYS = /* @__PURE__ */ new Set(["workflowId", "askSha256", "objectiveSha256", "stepWrites"]);
28090
+ var MAX_PRODUCER_ROUTING_METADATA_FIELDS = MAX_PROVIDER_METADATA_FIELDS - ENGINE_ROUTING_METADATA_KEYS.size;
28091
+ function engineRoutingMetadata(dispatch, producer) {
28092
+ const metadata = {};
28093
+ if (producer !== void 0 && producer !== null) {
28094
+ if (typeof producer !== "object" || Array.isArray(producer)) {
28095
+ throw new Error("routing providerMetadata must be an object");
28096
+ }
28097
+ let kept = 0;
28098
+ for (const [key, value] of Object.entries(producer)) {
28099
+ if (kept >= MAX_PRODUCER_ROUTING_METADATA_FIELDS) break;
28100
+ if (ENGINE_ROUTING_METADATA_KEYS.has(key)) continue;
28101
+ metadata[key] = value;
28102
+ kept += 1;
28103
+ }
28104
+ }
28105
+ metadata.workflowId = dispatch.plan.workflowId;
28106
+ metadata.askSha256 = kxmStepAskSha256(dispatch.plan, dispatch.stepId, dispatch.agentId);
28107
+ metadata.objectiveSha256 = dispatch.run.promptSha256;
28108
+ metadata.stepWrites = Object.values(dispatch.step.repositories).some((access) => access === "write");
28109
+ return metadata;
28110
+ }
28111
+ function producerRoutingRecord(context, dispatch, result, now, finalOutcome) {
26644
28112
  const run = requireRun(context, dispatch.run.runId);
26645
28113
  if (result.costBasis === void 0 || result.costBasis === null) {
26646
28114
  throw runtimeError("settle_missing_cost_basis", run.runId, `attempt settlement rejected: missing required costBasis for attempt ${dispatch.attemptId}`);
@@ -26677,9 +28145,12 @@ function producerRoutingRecord(context, dispatch, result, now) {
26677
28145
  retries: Math.max(0, dispatch.stepAttempt - 1),
26678
28146
  thinking: result.thinking ?? dispatch.request.thinking
26679
28147
  };
26680
- for (const field of ["agentRole", "contextTokens", "tokensIn", "tokensOut", "cacheReadTokens", "cacheWriteTokens", "priceRef", "providerMetadata"]) {
28148
+ for (const field of ["contextTokens", "tokensIn", "tokensOut", "cacheReadTokens", "cacheWriteTokens", "priceRef"]) {
26681
28149
  if (result[field] !== void 0) record2[field] = result[field];
26682
28150
  }
28151
+ record2.agentRole = result.agentRole ?? dispatch.agentId;
28152
+ record2.providerMetadata = engineRoutingMetadata(dispatch, result.providerMetadata);
28153
+ if (finalOutcome !== void 0) record2.finalOutcome = finalOutcome;
26683
28154
  return parseRoutingRecordV2(record2);
26684
28155
  }
26685
28156
  function settleMember(context, dispatch, result, produceError, produceErrorCode) {
@@ -26744,7 +28215,10 @@ function settleMember(context, dispatch, result, produceError, produceErrorCode)
26744
28215
  latencyMs: typeof result?.latencyMs === "number" ? result.latencyMs : 0,
26745
28216
  costBasis: "unknown",
26746
28217
  costUsd: null,
26747
- retries: Math.max(0, dispatch.stepAttempt - 1)
28218
+ retries: Math.max(0, dispatch.stepAttempt - 1),
28219
+ agentRole: dispatch.agentId,
28220
+ finalOutcome: "failed",
28221
+ providerMetadata: engineRoutingMetadata(dispatch)
26748
28222
  };
26749
28223
  push("routing.attempt.recorded", { routing: parseRoutingRecordV2(routingRecord) });
26750
28224
  push("assignment.result_recorded", {
@@ -26759,9 +28233,17 @@ function settleMember(context, dispatch, result, produceError, produceErrorCode)
26759
28233
  if (!result) {
26760
28234
  throw runtimeError("settle_missing_cost_basis", run.runId, `attempt settlement rejected: missing required costBasis for attempt ${dispatch.attemptId}`);
26761
28235
  }
26762
- push("routing.attempt.recorded", { routing: producerRoutingRecord(context, dispatch, result, now) });
26763
28236
  const outcome = typeof result.outcome === "string" ? result.outcome : void 0;
26764
28237
  const known = outcome !== void 0 && dispatch.step.outcomes.includes(outcome);
28238
+ push("routing.attempt.recorded", {
28239
+ routing: producerRoutingRecord(
28240
+ context,
28241
+ dispatch,
28242
+ result,
28243
+ now,
28244
+ known ? kxmAttemptFinalOutcome(dispatch.step, { resultClass: "outcome", outcome }) : "failed"
28245
+ )
28246
+ });
26765
28247
  if (!known) {
26766
28248
  push("assignment.result_recorded", {
26767
28249
  assignmentId: dispatch.assignmentId,
@@ -27708,7 +29190,7 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
27708
29190
  }
27709
29191
  const spawnFn = options.spawnProcess ?? defaultSpawn;
27710
29192
  const cwd = options.projectRoot ?? process.cwd();
27711
- const evidence = await beginOneShotEvidence(options.evidenceRoot ?? join12(kxmUserStateRoot(), "runtime", "oneshot-evidence"), {
29193
+ const evidence = await beginOneShotEvidence(options.evidenceRoot ?? join15(kxmUserStateRoot(), "runtime", "oneshot-evidence"), {
27712
29194
  runId: request.runId,
27713
29195
  stepId: request.stepId,
27714
29196
  attemptId: request.attemptId,
@@ -27853,20 +29335,6 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
27853
29335
 
27854
29336
  // plugins/kxm/src/client.ts
27855
29337
  import { hostname } from "node:os";
27856
-
27857
- // plugins/kxm/src/protocol.ts
27858
- var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
27859
- var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
27860
- var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
27861
- var MAX_BODY_BYTES = 256 * 1024;
27862
- var MAX_AGENT_HOST_CHARS = 64;
27863
- var MAX_LEASE_TTL_MS = 10 * 6e4;
27864
- var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
27865
- function nowIso() {
27866
- return (/* @__PURE__ */ new Date()).toISOString();
27867
- }
27868
-
27869
- // plugins/kxm/src/client.ts
27870
29338
  function defaultHostLabel() {
27871
29339
  try {
27872
29340
  return hostname().trim().slice(0, MAX_AGENT_HOST_CHARS) || void 0;
@@ -27957,9 +29425,9 @@ var RuntimeHubClient = class {
27957
29425
  };
27958
29426
 
27959
29427
  // plugins/kxm/src/hub-binding.ts
27960
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
29428
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
27961
29429
  import { homedir as homedir2 } from "node:os";
27962
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join13, resolve as resolve6 } from "node:path";
29430
+ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join16, resolve as resolve7 } from "node:path";
27963
29431
  var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
27964
29432
  var HubBindingError = class extends Error {
27965
29433
  constructor(message) {
@@ -27971,20 +29439,20 @@ function resolveUserStateRoot(env) {
27971
29439
  const explicit = env.KXM_STATE_HOME?.trim();
27972
29440
  if (explicit) {
27973
29441
  if (!isAbsolute4(explicit)) throw new HubBindingError("local_state_root_not_absolute");
27974
- return resolve6(explicit);
29442
+ return resolve7(explicit);
27975
29443
  }
27976
29444
  if (process.platform === "win32") {
27977
29445
  const localAppData = env.LOCALAPPDATA?.trim();
27978
- const base2 = localAppData && isAbsolute4(localAppData) ? localAppData : join13(homedir2(), "AppData", "Local");
27979
- return resolve6(base2, "KXM");
29446
+ const base2 = localAppData && isAbsolute4(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
29447
+ return resolve7(base2, "KXM");
27980
29448
  }
27981
- if (process.platform === "darwin") return resolve6(homedir2(), "Library", "Application Support", "KXM");
29449
+ if (process.platform === "darwin") return resolve7(homedir2(), "Library", "Application Support", "KXM");
27982
29450
  const xdgState = env.XDG_STATE_HOME?.trim();
27983
- const base = xdgState && isAbsolute4(xdgState) ? xdgState : join13(homedir2(), ".local", "state");
27984
- return resolve6(base, "kxm");
29451
+ const base = xdgState && isAbsolute4(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
29452
+ return resolve7(base, "kxm");
27985
29453
  }
27986
29454
  function hubBindingFile(env = process.env) {
27987
- return join13(resolveUserStateRoot(env), "hub-binding.json");
29455
+ return join16(resolveUserStateRoot(env), "hub-binding.json");
27988
29456
  }
27989
29457
  function validateHubUrl(raw) {
27990
29458
  let parsed;
@@ -28004,10 +29472,10 @@ function isIsoTimestamp(value) {
28004
29472
  }
28005
29473
  function readHubBinding(env = process.env) {
28006
29474
  const file = hubBindingFile(env);
28007
- if (!existsSync10(file)) return void 0;
29475
+ if (!existsSync12(file)) return void 0;
28008
29476
  let parsed;
28009
29477
  try {
28010
- parsed = JSON.parse(readFileSync8(file, "utf8"));
29478
+ parsed = JSON.parse(readFileSync10(file, "utf8"));
28011
29479
  } catch {
28012
29480
  throw new HubBindingError(`malformed hub binding at ${file}`);
28013
29481
  }
@@ -28029,9 +29497,9 @@ function readHubBinding(env = process.env) {
28029
29497
  }
28030
29498
 
28031
29499
  // plugins/kxm/src/hub-env.ts
28032
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
29500
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
28033
29501
  import { homedir as homedir3 } from "node:os";
28034
- import { dirname as dirname7, isAbsolute as isAbsolute5, join as join14, resolve as resolve7 } from "node:path";
29502
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join17, resolve as resolve8 } from "node:path";
28035
29503
  var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
28036
29504
  var HubEnvError = class extends Error {
28037
29505
  constructor(message) {
@@ -28043,27 +29511,27 @@ function resolveUserStateRoot2(env) {
28043
29511
  const explicit = env.KXM_STATE_HOME?.trim();
28044
29512
  if (explicit) {
28045
29513
  if (!isAbsolute5(explicit)) throw new HubEnvError("local_state_root_not_absolute");
28046
- return resolve7(explicit);
29514
+ return resolve8(explicit);
28047
29515
  }
28048
29516
  if (process.platform === "win32") {
28049
29517
  const localAppData = env.LOCALAPPDATA?.trim();
28050
- const base2 = localAppData && isAbsolute5(localAppData) ? localAppData : join14(homedir3(), "AppData", "Local");
28051
- return resolve7(base2, "KXM");
29518
+ const base2 = localAppData && isAbsolute5(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
29519
+ return resolve8(base2, "KXM");
28052
29520
  }
28053
- if (process.platform === "darwin") return resolve7(homedir3(), "Library", "Application Support", "KXM");
29521
+ if (process.platform === "darwin") return resolve8(homedir3(), "Library", "Application Support", "KXM");
28054
29522
  const xdgState = env.XDG_STATE_HOME?.trim();
28055
- const base = xdgState && isAbsolute5(xdgState) ? xdgState : join14(homedir3(), ".local", "state");
28056
- return resolve7(base, "kxm");
29523
+ const base = xdgState && isAbsolute5(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
29524
+ return resolve8(base, "kxm");
28057
29525
  }
28058
29526
  function hubEnvFile(env = process.env) {
28059
- return join14(resolveUserStateRoot2(env), "hub-env.json");
29527
+ return join17(resolveUserStateRoot2(env), "hub-env.json");
28060
29528
  }
28061
29529
  function readHubEnvRecord(env = process.env) {
28062
29530
  const file = hubEnvFile(env);
28063
- if (!existsSync11(file)) return void 0;
29531
+ if (!existsSync13(file)) return void 0;
28064
29532
  let parsed;
28065
29533
  try {
28066
- parsed = JSON.parse(readFileSync9(file, "utf8"));
29534
+ parsed = JSON.parse(readFileSync11(file, "utf8"));
28067
29535
  } catch (error) {
28068
29536
  throw new HubEnvError(`hub env file is malformed at ${file}: ${error instanceof Error ? error.message : String(error)}`);
28069
29537
  }
@@ -28103,8 +29571,8 @@ function resolveClientHubAuthToken(env, project) {
28103
29571
  }
28104
29572
 
28105
29573
  // plugins/kxm/src/logger.ts
28106
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync6, renameSync as renameSync3, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
28107
- import { dirname as dirname8 } from "node:path";
29574
+ import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync4, unlinkSync as unlinkSync2 } from "node:fs";
29575
+ import { dirname as dirname10 } from "node:path";
28108
29576
  var LOG_LEVEL_PRIORITY = {
28109
29577
  debug: 10,
28110
29578
  info: 20,
@@ -28141,7 +29609,7 @@ function redactLogValue(val, key) {
28141
29609
  function rotateLogFiles(filePath, maxFiles) {
28142
29610
  for (let i = maxFiles; i >= 1; i--) {
28143
29611
  const current = `${filePath}.${i}`;
28144
- if (existsSync12(current)) {
29612
+ if (existsSync14(current)) {
28145
29613
  if (i >= maxFiles) {
28146
29614
  try {
28147
29615
  unlinkSync2(current);
@@ -28149,15 +29617,15 @@ function rotateLogFiles(filePath, maxFiles) {
28149
29617
  }
28150
29618
  } else {
28151
29619
  try {
28152
- renameSync3(current, `${filePath}.${i + 1}`);
29620
+ renameSync4(current, `${filePath}.${i + 1}`);
28153
29621
  } catch {
28154
29622
  }
28155
29623
  }
28156
29624
  }
28157
29625
  }
28158
- if (existsSync12(filePath)) {
29626
+ if (existsSync14(filePath)) {
28159
29627
  try {
28160
- renameSync3(filePath, `${filePath}.1`);
29628
+ renameSync4(filePath, `${filePath}.1`);
28161
29629
  } catch {
28162
29630
  }
28163
29631
  }
@@ -28172,9 +29640,9 @@ function createLogger(options) {
28172
29640
  const shouldStdout = options.stdout ?? !isDaemon;
28173
29641
  const correlationDefaults = options.correlation ?? {};
28174
29642
  let currentSize = 0;
28175
- if (filePath && existsSync12(filePath)) {
29643
+ if (filePath && existsSync14(filePath)) {
28176
29644
  try {
28177
- currentSize = statSync3(filePath).size;
29645
+ currentSize = statSync4(filePath).size;
28178
29646
  } catch {
28179
29647
  currentSize = 0;
28180
29648
  }
@@ -28210,7 +29678,7 @@ function createLogger(options) {
28210
29678
  currentSize = 0;
28211
29679
  }
28212
29680
  try {
28213
- mkdirSync6(dirname8(filePath), { recursive: true });
29681
+ mkdirSync8(dirname10(filePath), { recursive: true });
28214
29682
  appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
28215
29683
  currentSize += lineBytes;
28216
29684
  } catch {
@@ -28253,26 +29721,26 @@ function createLogger(options) {
28253
29721
 
28254
29722
  // plugins/kxm/src/runtime-supervisor.ts
28255
29723
  function kxmSupervisorTokenFile(paths) {
28256
- return join15(paths.runtimeDir, "supervisor.token");
29724
+ return join18(paths.runtimeDir, "supervisor.token");
28257
29725
  }
28258
29726
  function publishKxmSupervisorToken(paths, token) {
28259
29727
  const file = kxmSupervisorTokenFile(paths);
28260
- mkdirSync7(dirname9(file), { recursive: true, mode: 448 });
29728
+ mkdirSync9(dirname11(file), { recursive: true, mode: 448 });
28261
29729
  const temp = `${file}.${process.pid}.tmp`;
28262
- writeFileSync6(temp, `${token}
29730
+ writeFileSync8(temp, `${token}
28263
29731
  `, { encoding: "utf8", mode: 384 });
28264
29732
  try {
28265
29733
  chmodSync2(temp, 384);
28266
29734
  } catch {
28267
29735
  }
28268
- renameSync4(temp, file);
29736
+ renameSync5(temp, file);
28269
29737
  try {
28270
29738
  chmodSync2(file, 384);
28271
29739
  } catch {
28272
29740
  }
28273
29741
  }
28274
29742
  function hashKxmSupervisorToken(token) {
28275
- return `sha256:${createHash13("sha256").update(`kxm-runtime-supervisor\0${token}`, "utf8").digest("hex")}`;
29743
+ return `sha256:${createHash14("sha256").update(`kxm-runtime-supervisor\0${token}`, "utf8").digest("hex")}`;
28276
29744
  }
28277
29745
  function readKxmSupervisorToken(paths) {
28278
29746
  const file = kxmSupervisorTokenFile(paths);
@@ -28281,7 +29749,7 @@ function readKxmSupervisorToken(paths) {
28281
29749
  if (stat.isSymbolicLink() || !stat.isFile()) {
28282
29750
  throw runtimeError("runtime_path_invalid", file, "supervisor token file must be a regular file, not a link");
28283
29751
  }
28284
- const token = readFileSync10(file, "utf8").trim();
29752
+ const token = readFileSync12(file, "utf8").trim();
28285
29753
  return token.length >= 32 ? token : void 0;
28286
29754
  }
28287
29755
  function processAlive(pid) {
@@ -28295,16 +29763,16 @@ function processAlive(pid) {
28295
29763
  var HEARTBEAT_STALE_MS = 15e3;
28296
29764
  var SUPERVISOR_ERROR_MAX_AGE_MS = 3e4;
28297
29765
  function supervisorErrorFile(paths) {
28298
- return join15(paths.runtimeDir, "supervisor.error");
29766
+ return join18(paths.runtimeDir, "supervisor.error");
28299
29767
  }
28300
29768
  function clearSupervisorError(paths) {
28301
29769
  const file = supervisorErrorFile(paths);
28302
- if (existsSync13(file)) rmSync3(file, { force: true });
29770
+ if (existsSync15(file)) rmSync4(file, { force: true });
28303
29771
  }
28304
29772
  function recordSupervisorError(paths, message) {
28305
29773
  try {
28306
- mkdirSync7(paths.runtimeDir, { recursive: true, mode: 448 });
28307
- writeFileSync6(supervisorErrorFile(paths), `${message}
29774
+ mkdirSync9(paths.runtimeDir, { recursive: true, mode: 448 });
29775
+ writeFileSync8(supervisorErrorFile(paths), `${message}
28308
29776
  `, { encoding: "utf8", mode: 384 });
28309
29777
  } catch {
28310
29778
  }
@@ -28316,13 +29784,13 @@ function readRecentSupervisorError(paths) {
28316
29784
  const ageMs = Date.now() - stat.mtimeMs;
28317
29785
  if (ageMs > SUPERVISOR_ERROR_MAX_AGE_MS) return void 0;
28318
29786
  try {
28319
- return readFileSync10(file, "utf8").trim();
29787
+ return readFileSync12(file, "utf8").trim();
28320
29788
  } catch {
28321
29789
  return void 0;
28322
29790
  }
28323
29791
  }
28324
29792
  function kxmSupervisorStatus(paths) {
28325
- if (!existsSync13(paths.registryDb)) return { running: false };
29793
+ if (!existsSync15(paths.registryDb)) return { running: false };
28326
29794
  const registry = new KxmRuntimeRegistry(paths.registryDb);
28327
29795
  try {
28328
29796
  const record2 = registry.supervisor();
@@ -28392,7 +29860,7 @@ async function ensureKxmSupervisor(options = {}) {
28392
29860
  throw runtimeError("runtime_supervisor_unreachable", paths.registryDb, `runtime supervisor pid ${status.pid} is registered as running but cannot be probed`);
28393
29861
  }
28394
29862
  clearSupervisorError(paths);
28395
- const scriptPath = join15(findKxmRepoRoot(import.meta.url), "scripts", "kxm-runtime-supervisor.mjs");
29863
+ const scriptPath = join18(findKxmRepoRoot(import.meta.url), "scripts", "kxm-runtime-supervisor.mjs");
28396
29864
  const spawnImpl = options.spawnImpl ?? ((script, env) => {
28397
29865
  const child = spawn3(process.execPath, [script], {
28398
29866
  detached: true,
@@ -28477,8 +29945,8 @@ async function waitForDriveSessions(settled, graceMs) {
28477
29945
  try {
28478
29946
  await Promise.race([
28479
29947
  Promise.allSettled(settled),
28480
- new Promise((resolve10) => {
28481
- timeout = setTimeout(resolve10, graceMs);
29948
+ new Promise((resolve11) => {
29949
+ timeout = setTimeout(resolve11, graceMs);
28482
29950
  timeout.unref?.();
28483
29951
  })
28484
29952
  ]);
@@ -28615,7 +30083,7 @@ function syncFailureText(error) {
28615
30083
  return `${code ? `${code}: ` : ""}${message}`.replace(/(kxm_[A-Za-z0-9_]+|[A-Za-z0-9._-]{40,})/g, "[redacted]").slice(0, 300);
28616
30084
  }
28617
30085
  function runtimeSupervisorLogFile(paths) {
28618
- return join15(paths.runtimeDir, "logs", "kxm-runtime.jsonl");
30086
+ return join18(paths.runtimeDir, "logs", "kxm-runtime.jsonl");
28619
30087
  }
28620
30088
  async function startKxmRuntimeSupervisor(options = {}) {
28621
30089
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
@@ -28631,8 +30099,8 @@ async function startKxmRuntimeSupervisor(options = {}) {
28631
30099
  }
28632
30100
  }
28633
30101
  async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, env) {
28634
- mkdirSync7(paths.runtimeDir, { recursive: true, mode: 448 });
28635
- mkdirSync7(paths.projectsDir, { recursive: true, mode: 448 });
30102
+ mkdirSync9(paths.runtimeDir, { recursive: true, mode: 448 });
30103
+ mkdirSync9(paths.projectsDir, { recursive: true, mode: 448 });
28636
30104
  const logger = createLogger({
28637
30105
  component: "runtime",
28638
30106
  path: runtimeSupervisorLogFile(paths),
@@ -28642,7 +30110,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
28642
30110
  const token = randomBytes3(32).toString("hex");
28643
30111
  const tokenHash = hashKxmSupervisorToken(token);
28644
30112
  const registry = new KxmRuntimeRegistry(paths.registryDb);
28645
- const runtimeId = `rtm_${createHash13("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes3(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
30113
+ const runtimeId = `rtm_${createHash14("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes3(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
28646
30114
  const requestedPort = requestedPortOption ?? 0;
28647
30115
  let activeRuntimeId = runtimeId;
28648
30116
  const contexts = /* @__PURE__ */ new Map();
@@ -28664,7 +30132,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
28664
30132
  const key = projectRuntimeKey(projectRoot);
28665
30133
  const existing = contexts.get(key);
28666
30134
  if (existing) return existing;
28667
- const context = openKxmRuntimeContext(projectRoot, { homeRuntimeId: activeRuntimeId, stateRoot: paths.stateRoot });
30135
+ const context = openKxmRuntimeContext(projectRoot, { homeRuntimeId: activeRuntimeId, stateRoot: paths.stateRoot, logger });
28668
30136
  registerSyncCredentials(context);
28669
30137
  contexts.set(key, context);
28670
30138
  return context;
@@ -29197,14 +30665,24 @@ async function kxmRuntimeRequest(handle, method, path, body) {
29197
30665
  if (!response.ok) {
29198
30666
  const code = typeof payload.error === "string" ? payload.error : "runtime_request_failed";
29199
30667
  const message = typeof payload.message === "string" ? payload.message : `runtime request failed with HTTP ${response.status}`;
29200
- throw runtimeError(code, path, message);
30668
+ throw runtimeError(code, path, `${message}${handoffSuffix(payload.handoff)}`);
29201
30669
  }
29202
30670
  return payload;
29203
30671
  }
30672
+ var HANDOFF_TEXT_MAX = 200;
30673
+ function handoffSuffix(handoff) {
30674
+ if (!handoff || typeof handoff !== "object" || Array.isArray(handoff)) return "";
30675
+ const parts = [];
30676
+ for (const key of ["reason", "field", "detail"]) {
30677
+ const value = handoff[key];
30678
+ if (typeof value === "string" && value.length > 0) parts.push(`${key} ${value.slice(0, HANDOFF_TEXT_MAX)}`);
30679
+ }
30680
+ return parts.length > 0 ? ` (handoff ${parts.join("; ")})` : "";
30681
+ }
29204
30682
 
29205
30683
  // plugins/kxm/src/pi-producer.ts
29206
30684
  import { spawn as spawn4 } from "node:child_process";
29207
- import { join as join16 } from "node:path";
30685
+ import { join as join19 } from "node:path";
29208
30686
  import { StringDecoder } from "node:string_decoder";
29209
30687
  function formatPiSessionKey(params) {
29210
30688
  if (params.agentId === "coordinator") {
@@ -29420,8 +30898,8 @@ var PiSession = class {
29420
30898
  }
29421
30899
  const id = `cmd_${++this.commandCounter}`;
29422
30900
  const payload = { ...command, id };
29423
- return new Promise((resolve10, reject) => {
29424
- this.pendingCommands.set(id, { resolve: resolve10, reject });
30901
+ return new Promise((resolve11, reject) => {
30902
+ this.pendingCommands.set(id, { resolve: resolve11, reject });
29425
30903
  try {
29426
30904
  this.process.stdin.write(JSON.stringify(payload) + "\n");
29427
30905
  } catch (err) {
@@ -29441,7 +30919,7 @@ var PiSession = class {
29441
30919
  return { outcome: "cancelled", text: "aborted", usage: {} };
29442
30920
  }
29443
30921
  this.status = "busy";
29444
- return new Promise((resolve10, reject) => {
30922
+ return new Promise((resolve11, reject) => {
29445
30923
  let signalCleanup;
29446
30924
  if (signal) {
29447
30925
  const onAbort = () => {
@@ -29455,7 +30933,7 @@ var PiSession = class {
29455
30933
  signalCleanup = () => signal.removeEventListener("abort", onAbort);
29456
30934
  }
29457
30935
  this.activePrompt = {
29458
- resolve: resolve10,
30936
+ resolve: resolve11,
29459
30937
  reject,
29460
30938
  allowedOutcomes,
29461
30939
  text: "",
@@ -29578,7 +31056,7 @@ function createKxmPiProducer(options = {}) {
29578
31056
  const args = ["--mode", "rpc", "--name", displayName];
29579
31057
  if (options.sessionDir) {
29580
31058
  const safeDirName = key.replace(/[^a-zA-Z0-9_-]/g, "_");
29581
- args.push("--session-dir", join16(options.sessionDir, request.runId, safeDirName));
31059
+ args.push("--session-dir", join19(options.sessionDir, request.runId, safeDirName));
29582
31060
  }
29583
31061
  if (model) {
29584
31062
  args.push("--model", model);
@@ -29706,9 +31184,9 @@ function createKxmPiProducer(options = {}) {
29706
31184
  }
29707
31185
 
29708
31186
  // plugins/kxm/src/improve.ts
29709
- import { createHash as createHash14 } from "node:crypto";
29710
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
29711
- import { join as join17, relative as relative4, resolve as resolve8 } from "node:path";
31187
+ import { createHash as createHash15 } from "node:crypto";
31188
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
31189
+ import { join as join20, relative as relative4, resolve as resolve9 } from "node:path";
29712
31190
  var CANDIDATE_SCHEMA = "kxm.candidate.v1";
29713
31191
  var IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2";
29714
31192
  var IMPROVEMENT_REPORT_V1_SCHEMA = "kxm.improvement-report.v1";
@@ -29723,8 +31201,11 @@ function classifyCandidateKind(stepId, agentRole) {
29723
31201
  }
29724
31202
  return "workflow-step";
29725
31203
  }
29726
- function generateCandidateDiff(kind, stepId, agentRole, workflowHash) {
29727
- const safeSlug = stepId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "step";
31204
+ function candidateSlug(value) {
31205
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "step";
31206
+ }
31207
+ function generateCandidateDiff(kind, stepId, agentRole, workflowId) {
31208
+ const safeSlug = candidateSlug(stepId);
29728
31209
  if (kind === "gate") {
29729
31210
  const diff2 = [
29730
31211
  "diff --git a/.kxm/gates.yaml b/.kxm/gates.yaml",
@@ -29772,56 +31253,96 @@ function generateCandidateDiff(kind, stepId, agentRole, workflowHash) {
29772
31253
  diff: diff2,
29773
31254
  declaredOutcome: `Governed reusable skill for ${stepId}`,
29774
31255
  measure: `Reduced prompt drift and consistent model guidance for ${stepId}`,
29775
- summary: `Promote repeated instructions for '${stepId}' (${agentRole}) to governed skill`
31256
+ summary: `Consolidate repeated instructions for '${stepId}' (${agentRole}) into a governed skill (consolidation, not a coded step)`
29776
31257
  };
29777
31258
  }
29778
- const rel = `.kxm/workflows/${safeSlug}.yaml`;
31259
+ const rel = `.kxm/workflows/${workflowId !== void 0 ? candidateSlug(workflowId) : safeSlug}.yaml`;
29779
31260
  const diff = [
29780
31261
  `diff --git a/${rel} b/${rel}`,
29781
31262
  `--- a/${rel}`,
29782
31263
  `+++ b/${rel}`,
29783
- "@@ -1,3 +1,6 @@",
29784
- " steps:",
29785
- `+ - id: ${stepId}`,
29786
- `+ kind: agent`,
29787
- `+ agent: ${agentRole}`,
31264
+ "@@ -1,3 +1,3 @@",
31265
+ ` - id: ${stepId}`,
31266
+ "- kind: agent",
31267
+ `- agent: ${agentRole}`,
31268
+ "+ kind: gate",
31269
+ `+ gate: ${safeSlug}`,
31270
+ "diff --git a/.kxm/gates.yaml b/.kxm/gates.yaml",
31271
+ "--- a/.kxm/gates.yaml",
31272
+ "+++ b/.kxm/gates.yaml",
31273
+ "@@ -1,2 +1,6 @@",
31274
+ " schema: kxm.gate-registry.v1",
31275
+ " gates:",
31276
+ `+ ${safeSlug}:`,
31277
+ "+ kind: command",
31278
+ `+ argv: [node, scripts/${safeSlug}.mjs]`,
31279
+ "+ timeoutMs: 600000",
29788
31280
  ""
29789
31281
  ].join("\n");
29790
31282
  return {
29791
31283
  diff,
29792
- declaredOutcome: `Dedicated workflow step with typed inputs and transitions for ${stepId}`,
29793
- measure: `Reduced manual coordination and faster stage transitions for ${stepId}`,
29794
- summary: `Automate repetitive step '${stepId}' (${agentRole}) in workflow`
31284
+ declaredOutcome: `Coded gate replaces the model turn for ${stepId}; the operator writes scripts/${safeSlug}.mjs`,
31285
+ measure: `Model cost and latency for ${stepId} drop to zero while its accepted rate holds`,
31286
+ summary: `Replace the model turn for '${stepId}' (${agentRole}) with a coded gate step`
29795
31287
  };
29796
31288
  }
31289
+ function nonEmptyText(value) {
31290
+ if (typeof value !== "string") return void 0;
31291
+ const trimmed = value.trim();
31292
+ return trimmed.length > 0 ? trimmed : void 0;
31293
+ }
31294
+ function roundThree(value) {
31295
+ return Number(value.toFixed(3));
31296
+ }
29797
31297
  function groupRoutingRecords(records, options = {}) {
29798
31298
  const minRecurrence = options.minRecurrence ?? 2;
29799
31299
  const minPassRate = options.minPassRate ?? 0.75;
31300
+ const halfLifeDays = options.halfLifeDays ?? 14;
31301
+ const now = options.now ?? Date.now();
31302
+ const raws = records.map((record2) => record2);
31303
+ const runKeys = raws.map((raw, index) => nonEmptyText(raw.runId) ?? nonEmptyText(raw.workflowRunId) ?? `record:${index}`);
31304
+ const stepIds = raws.map((raw) => nonEmptyText(raw.stepId) ?? nonEmptyText(raw.stageId) ?? "unknown");
31305
+ const retriesOf = (raw) => typeof raw.retries === "number" && Number.isFinite(raw.retries) ? raw.retries : 0;
31306
+ const maxRetries = /* @__PURE__ */ new Map();
31307
+ raws.forEach((raw, index) => {
31308
+ const attemptKey = `${runKeys[index]}\0${stepIds[index]}`;
31309
+ maxRetries.set(attemptKey, Math.max(maxRetries.get(attemptKey) ?? 0, retriesOf(raw)));
31310
+ });
29800
31311
  const map = /* @__PURE__ */ new Map();
29801
- for (const r of records) {
29802
- const raw = r;
29803
- const providerMeta = raw.providerMetadata && typeof raw.providerMetadata === "object" ? raw.providerMetadata : {};
29804
- const workflowHash = raw.workflowDefinitionSha256 || providerMeta.workflowDefinitionSha256 || raw.workflowRunId || raw.runId || "standalone";
29805
- const stepId = raw.stepId || raw.stageId || "unknown";
29806
- const agentRole = raw.agentRole?.trim() || "agent";
29807
- const promptHash = raw.rolePromptSha256?.trim() || providerMeta.rolePromptSha256?.trim() || "none";
31312
+ raws.forEach((raw, index) => {
31313
+ const providerMeta = raw.providerMetadata && typeof raw.providerMetadata === "object" && !Array.isArray(raw.providerMetadata) ? raw.providerMetadata : {};
31314
+ const workflowId = nonEmptyText(providerMeta.workflowId);
31315
+ const workflowHash = workflowId ?? nonEmptyText(raw.workflowDefinitionSha256) ?? nonEmptyText(providerMeta.workflowDefinitionSha256) ?? nonEmptyText(raw.workflowRunId) ?? nonEmptyText(raw.runId) ?? "standalone";
31316
+ const stepId = stepIds[index];
31317
+ const agentRole = nonEmptyText(raw.agentRole) ?? "agent";
31318
+ const promptHash = nonEmptyText(providerMeta.askSha256) ?? nonEmptyText(raw.rolePromptSha256) ?? "none";
31319
+ const runKey = runKeys[index];
29808
31320
  const key = `${workflowHash}:${stepId}:${agentRole}:${promptHash}`;
29809
31321
  let group = map.get(key);
29810
31322
  if (!group) {
29811
31323
  group = {
31324
+ key,
29812
31325
  workflowHash,
29813
31326
  stepId,
29814
31327
  agentRole,
29815
31328
  promptHash,
29816
31329
  costs: [],
29817
31330
  latencies: [],
29818
- passedCount: 0,
31331
+ total: 0,
31332
+ decided: 0,
31333
+ passed: 0,
31334
+ undecided: 0,
29819
31335
  reworkCount: 0,
29820
- evidenceRefs: /* @__PURE__ */ new Set(),
29821
- total: 0
31336
+ weight: 0,
31337
+ undated: 0,
31338
+ writes: false,
31339
+ decidedRuns: /* @__PURE__ */ new Set(),
31340
+ askRuns: /* @__PURE__ */ new Map(),
31341
+ evidenceRefs: /* @__PURE__ */ new Set()
29822
31342
  };
29823
31343
  map.set(key, group);
29824
31344
  }
31345
+ if (group.workflowId === void 0 && workflowId !== void 0) group.workflowId = workflowId;
29825
31346
  group.total += 1;
29826
31347
  const cost = raw.costUsd;
29827
31348
  if (typeof cost === "number" && !Number.isNaN(cost) && cost >= 0) {
@@ -29833,67 +31354,110 @@ function groupRoutingRecords(records, options = {}) {
29833
31354
  }
29834
31355
  const verifierOutcome = raw.verifierOutcome;
29835
31356
  const finalOutcome = raw.finalOutcome;
29836
- if (verifierOutcome === "passed" || finalOutcome === "accepted" || finalOutcome === "completed") {
29837
- group.passedCount += 1;
31357
+ const decided = verifierOutcome !== void 0 && verifierOutcome !== null || finalOutcome !== void 0 && finalOutcome !== null && finalOutcome !== "pending";
31358
+ if (decided) {
31359
+ group.decided += 1;
31360
+ group.decidedRuns.add(runKey);
31361
+ const ask = nonEmptyText(providerMeta.objectiveSha256) ?? "(unkeyed)";
31362
+ let askRuns = group.askRuns.get(ask);
31363
+ if (!askRuns) {
31364
+ askRuns = /* @__PURE__ */ new Set();
31365
+ group.askRuns.set(ask, askRuns);
31366
+ }
31367
+ askRuns.add(runKey);
31368
+ const superseded = (maxRetries.get(`${runKey}\0${stepId}`) ?? 0) > retriesOf(raw);
31369
+ if (!superseded && (verifierOutcome === "passed" || finalOutcome === "accepted" || finalOutcome === "completed")) {
31370
+ group.passed += 1;
31371
+ }
31372
+ } else {
31373
+ group.undecided += 1;
29838
31374
  }
29839
31375
  const retries = raw.retries;
29840
31376
  if (typeof retries === "number") {
29841
31377
  group.reworkCount += retries;
29842
31378
  }
31379
+ const recordedAt = nonEmptyText(raw.recordedAt);
31380
+ if (recordedAt === void 0 || !Number.isFinite(Date.parse(recordedAt))) group.undated += 1;
31381
+ group.weight += computeDecayedWeight(recordedAt ?? "", halfLifeDays, now);
31382
+ if (providerMeta.stepWrites === true) group.writes = true;
29843
31383
  const ref = raw.attemptId || raw.runId || raw.workflowRunId || raw.behavioralSha256;
29844
31384
  if (ref && group.evidenceRefs.size < 16) {
29845
31385
  group.evidenceRefs.add(ref);
29846
31386
  }
29847
- }
29848
- const rows = [];
31387
+ });
31388
+ const keyed = [];
29849
31389
  for (const group of map.values()) {
29850
31390
  const recurrence = group.total;
29851
31391
  const meanCost = group.costs.length > 0 ? Number((group.costs.reduce((a, b) => a + b, 0) / group.costs.length).toFixed(4)) : 0;
29852
31392
  const meanLatency = group.latencies.length > 0 ? Math.round(group.latencies.reduce((a, b) => a + b, 0) / group.latencies.length) : 0;
29853
- const verifyPassRate = recurrence > 0 ? Number((group.passedCount / recurrence).toFixed(3)) : 0;
29854
- const rework = group.reworkCount;
29855
- const isCandidate = recurrence >= minRecurrence && verifyPassRate >= minPassRate;
31393
+ const verifyPassRate = group.decided > 0 ? roundThree(group.passed / group.decided) : 0;
31394
+ const distinctRuns = group.decidedRuns.size;
31395
+ const askRecurrence = Math.max(0, ...[...group.askRuns.values()].map((runs) => runs.size));
31396
+ const passes = verifyPassRate >= minPassRate;
31397
+ const isCandidate = askRecurrence >= minRecurrence && passes && !group.writes;
31398
+ let excludedReason;
31399
+ if (!isCandidate && passes) {
31400
+ if (askRecurrence >= minRecurrence && group.writes) excludedReason = "writes-repository";
31401
+ else if (distinctRuns >= minRecurrence && askRecurrence < minRecurrence) excludedReason = "ask-not-repeated";
31402
+ }
29856
31403
  const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : void 0;
29857
31404
  const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
29858
- const keyHash = createHash14("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
31405
+ const keyHash = createHash15("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
29859
31406
  const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : void 0;
29860
- rows.push({
29861
- workflowHash: group.workflowHash,
29862
- stepId: group.stepId,
29863
- agentRole: group.agentRole,
29864
- promptHash: group.promptHash,
29865
- recurrence,
29866
- meanCost,
29867
- meanLatency,
29868
- verifyPassRate,
29869
- rework,
29870
- evidenceRefs: [...group.evidenceRefs],
29871
- isCandidate,
29872
- ...candidateKind !== void 0 ? { candidateKind } : {},
29873
- ...candidateId !== void 0 ? { candidateId } : {}
31407
+ keyed.push({
31408
+ key: group.key,
31409
+ row: {
31410
+ workflowHash: group.workflowHash,
31411
+ ...group.workflowId !== void 0 ? { workflowId: group.workflowId } : {},
31412
+ stepId: group.stepId,
31413
+ agentRole: group.agentRole,
31414
+ promptHash: group.promptHash,
31415
+ recurrence,
31416
+ distinctRuns,
31417
+ askRecurrence,
31418
+ undecidedRecords: group.undecided,
31419
+ meanCost,
31420
+ meanLatency,
31421
+ verifyPassRate,
31422
+ rework: group.reworkCount,
31423
+ weightedRecurrence: roundThree(group.weight),
31424
+ undatedRecords: group.undated,
31425
+ costSamples: group.costs.length,
31426
+ writesRepository: group.writes,
31427
+ evidenceRefs: [...group.evidenceRefs],
31428
+ isCandidate,
31429
+ ...excludedReason !== void 0 ? { excludedReason } : {},
31430
+ ...candidateKind !== void 0 ? { candidateKind } : {},
31431
+ ...candidateId !== void 0 ? { candidateId } : {}
31432
+ }
29874
31433
  });
29875
31434
  }
29876
- return rows.sort((a, b) => {
31435
+ return keyed.sort((left, right) => {
31436
+ const a = left.row;
31437
+ const b = right.row;
29877
31438
  if (a.isCandidate !== b.isCandidate) return a.isCandidate ? -1 : 1;
31439
+ if (b.weightedRecurrence !== a.weightedRecurrence) return b.weightedRecurrence - a.weightedRecurrence;
29878
31440
  if (b.recurrence !== a.recurrence) return b.recurrence - a.recurrence;
29879
- return a.stepId.localeCompare(b.stepId);
29880
- });
31441
+ return compareCodeUnitIds(a.stepId, b.stepId) || compareCodeUnitIds(left.key, right.key);
31442
+ }).map((entry) => entry.row);
29881
31443
  }
29882
31444
  function buildImprovementReport(records, options = {}) {
29883
- const projectRoot = options.projectRoot ? resolve8(options.projectRoot) : process.cwd();
29884
- const candidatesDir = options.candidatesDir ? resolve8(options.candidatesDir) : join17(projectRoot, ".kxm", "candidates");
31445
+ const projectRoot = options.projectRoot ? resolve9(options.projectRoot) : process.cwd();
31446
+ const candidatesDir = options.candidatesDir ? resolve9(options.candidatesDir) : join20(projectRoot, ".kxm", "candidates");
31447
+ const promotionPolicy = options.promotionPolicy ?? "manual_pr";
29885
31448
  const groups = groupRoutingRecords(records, options);
29886
31449
  const candidates = [];
31450
+ const promotion = [];
29887
31451
  for (const group of groups) {
29888
31452
  if (!group.isCandidate || !group.candidateKind || !group.candidateId) continue;
29889
31453
  const { diff, declaredOutcome, measure, summary } = generateCandidateDiff(
29890
31454
  group.candidateKind,
29891
31455
  group.stepId,
29892
31456
  group.agentRole,
29893
- group.workflowHash
31457
+ group.workflowId
29894
31458
  );
29895
31459
  const diffFileName = `${group.candidateId}.diff`;
29896
- const diffFilePath = join17(candidatesDir, diffFileName);
31460
+ const diffFilePath = join20(candidatesDir, diffFileName);
29897
31461
  const relDiffPath = relative4(projectRoot, diffFilePath).replace(/\\/g, "/");
29898
31462
  const candidate = {
29899
31463
  schema: CANDIDATE_SCHEMA,
@@ -29915,55 +31479,72 @@ function buildImprovementReport(records, options = {}) {
29915
31479
  createdAt: nowIso()
29916
31480
  };
29917
31481
  if (!options.dryRun) {
29918
- if (!existsSync14(candidatesDir)) {
29919
- mkdirSync8(candidatesDir, { recursive: true });
31482
+ if (!existsSync16(candidatesDir)) {
31483
+ mkdirSync10(candidatesDir, { recursive: true });
29920
31484
  }
29921
- writeFileSync7(diffFilePath, diff, "utf8");
29922
- const jsonFilePath = join17(candidatesDir, `${group.candidateId}.json`);
29923
- writeFileSync7(jsonFilePath, JSON.stringify(candidate, null, 2) + "\n", "utf8");
31485
+ writeFileSync9(diffFilePath, diff, "utf8");
31486
+ const jsonFilePath = join20(candidatesDir, `${group.candidateId}.json`);
31487
+ writeFileSync9(jsonFilePath, JSON.stringify(candidate, null, 2) + "\n", "utf8");
29924
31488
  }
29925
31489
  candidates.push(candidate);
29926
31490
  }
31491
+ const rowsByCandidate = new Map(groups.flatMap((row) => row.candidateId !== void 0 ? [[row.candidateId, row]] : []));
31492
+ for (const candidate of candidates) {
31493
+ const row = rowsByCandidate.get(candidate.id);
31494
+ promotion.push({
31495
+ candidateId: candidate.id,
31496
+ ...evaluatePromotionPolicy(candidate, promotionPolicy, {
31497
+ autoThreshold: options.autoThreshold,
31498
+ ...row ? { costSamples: row.costSamples, distinctRuns: row.distinctRuns } : {}
31499
+ })
31500
+ });
31501
+ }
29927
31502
  return {
29928
31503
  schema: IMPROVEMENT_REPORT_SCHEMA,
29929
31504
  createdAt: nowIso(),
29930
31505
  reviewDecision: "proposed",
29931
31506
  recordsCount: records.length,
29932
31507
  groups,
29933
- candidates
31508
+ candidates,
31509
+ promotionPolicy,
31510
+ promotion
29934
31511
  };
29935
31512
  }
29936
31513
  function writeImprovementReport(improvementsDir, report, dryRun = false) {
29937
31514
  const stamp = report.createdAt.replace(/[:.]/g, "-");
29938
- const path = join17(improvementsDir, `${stamp}.json`);
31515
+ const path = join20(improvementsDir, `${stamp}.json`);
29939
31516
  if (!dryRun) {
29940
- mkdirSync8(improvementsDir, { recursive: true });
29941
- writeFileSync7(path, `${JSON.stringify(report, null, 2)}
31517
+ mkdirSync10(improvementsDir, { recursive: true });
31518
+ writeFileSync9(path, `${JSON.stringify(report, null, 2)}
29942
31519
  `, { encoding: "utf8" });
29943
31520
  }
29944
31521
  return path;
29945
31522
  }
29946
31523
  function formatImprovementReport(report) {
29947
31524
  const lines = [
29948
- `Improvement Report (${report.recordsCount} record(s), ${report.groups.length} group(s), ${report.candidates.length} candidate(s))`,
31525
+ `Improvement Report (${report.recordsCount} record(s), ${report.groups.length} group(s), ${report.candidates.length} candidate(s); promotion policy ${report.promotionPolicy})`,
29949
31526
  "",
29950
- "Workflow Step Role Prompt Recurrence Cost ($) Latency (ms) Pass Rate Rework Candidate",
29951
- "-------------------------------------------------------------------------------------------------------------------------"
31527
+ "Workflow Step Role Prompt Records Runs Asks Weighted Cost ($) Latency (ms) Accepted Rework Candidate",
31528
+ "------------------------------------------------------------------------------------------------------------------------------------------"
29952
31529
  ];
29953
31530
  for (const g of report.groups) {
29954
31531
  const wf = g.workflowHash.slice(0, 12).padEnd(14);
29955
31532
  const step = g.stepId.slice(0, 11).padEnd(12);
29956
31533
  const role = g.agentRole.slice(0, 11).padEnd(12);
29957
- const prompt = g.promptHash.slice(0, 10).padEnd(12);
29958
- const rec = String(g.recurrence).padStart(10);
31534
+ const prompt = g.promptHash.replace(/^sha256:/, "").slice(0, 10).padEnd(12);
31535
+ const rec = String(g.recurrence).padStart(7);
31536
+ const runs = String(g.distinctRuns).padStart(5);
31537
+ const asks = String(g.askRecurrence).padStart(5);
31538
+ const weighted = g.weightedRecurrence.toFixed(3).padStart(9);
29959
31539
  const cost = g.meanCost.toFixed(3).padStart(9);
29960
31540
  const lat = String(g.meanLatency).padStart(13);
29961
- const pass = `${(g.verifyPassRate * 100).toFixed(0)}%`.padStart(10);
31541
+ const accepted = `${(g.verifyPassRate * 100).toFixed(0)}%`.padStart(9);
29962
31542
  const rework = String(g.rework).padStart(7);
29963
- const cand = g.isCandidate ? `yes (${g.candidateKind})` : "no";
29964
- lines.push(`${wf} ${step} ${role} ${prompt} ${rec} ${cost} ${lat} ${pass} ${rework} ${cand}`);
31543
+ const cand = g.isCandidate ? `yes (${g.candidateKind})` : g.excludedReason !== void 0 ? `no (${g.excludedReason})` : "no";
31544
+ lines.push(`${wf} ${step} ${role} ${prompt} ${rec} ${runs} ${asks} ${weighted} ${cost} ${lat} ${accepted} ${rework} ${cand}`);
29965
31545
  }
29966
31546
  if (report.candidates.length > 0) {
31547
+ const readiness = new Map(report.promotion.map((entry) => [entry.candidateId, entry]));
29967
31548
  lines.push("");
29968
31549
  lines.push("Emitted Coded-Repeat Candidates:");
29969
31550
  for (const c of report.candidates) {
@@ -29971,47 +31552,42 @@ function formatImprovementReport(report) {
29971
31552
  lines.push(` Outcome: ${c.declaredOutcome}`);
29972
31553
  lines.push(` Measure: ${c.measure}`);
29973
31554
  lines.push(` Proposed diff: ${c.proposedDiffPath}`);
31555
+ const entry = readiness.get(c.id);
31556
+ if (entry) {
31557
+ lines.push(` Promotion (${entry.policy}): ${entry.readyForReview ? "ready for review" : "not ready for review"}; ${entry.reason}`);
31558
+ }
29974
31559
  }
31560
+ lines.push("");
31561
+ lines.push("Candidates are proposals: readiness never authorizes, and activation is a reviewed Git change.");
29975
31562
  }
29976
31563
  return lines.join("\n");
29977
31564
  }
29978
- function evaluatePromotionPolicy(candidate, policy = "manual_pr", options) {
31565
+ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options = {}) {
29979
31566
  if (policy === "manual_pr") {
29980
- return {
29981
- eligible: true,
29982
- policy,
29983
- authorized: false,
29984
- reason: "Manual PR review and signoff required by policy (fail-closed anti-privilege-escalation)"
29985
- };
31567
+ return { policy, readyForReview: true, reason: `operator PR applying ${candidate.proposedDiffPath} required` };
29986
31568
  }
29987
31569
  if (policy === "critic_quorum") {
29988
- const approvals = options?.criticApprovals ?? [];
29989
- const hasQuorum = approvals.length >= 2;
29990
- return {
29991
- eligible: true,
29992
- policy,
29993
- authorized: hasQuorum,
29994
- reason: hasQuorum ? `Authorized by critic quorum (${approvals.join(", ")})` : `Requires dual critic quorum; current approvals: ${approvals.length}/2`
29995
- };
31570
+ const receipts = new Set((options.criticReceipts ?? []).map((receipt) => receipt.trim()).filter((receipt) => receipt.length > 0));
31571
+ return receipts.size >= 2 ? { policy, readyForReview: true, reason: "two critic receipts cited; operator PR still required" } : { policy, readyForReview: false, reason: `awaiting 2 distinct critic receipts (have ${receipts.size})` };
29996
31572
  }
29997
31573
  if (policy === "auto_threshold") {
29998
- const threshold = options?.autoThreshold ?? { minRuns: 10, minPassRate: 0.95 };
29999
- const recurrence = candidate.baselineMetrics.recurrence;
31574
+ const minRuns = options.autoThreshold?.minRuns ?? 10;
31575
+ const minPassRate = options.autoThreshold?.minPassRate ?? 0.95;
31576
+ const minCostSavings = options.autoThreshold?.minCostSavings;
31577
+ const runs = options.distinctRuns ?? candidate.baselineMetrics.recurrence;
30000
31578
  const passRate = candidate.baselineMetrics.verifyPassRate;
30001
- const meetsThreshold = recurrence >= threshold.minRuns && passRate >= threshold.minPassRate;
30002
- return {
30003
- eligible: true,
30004
- policy,
30005
- authorized: meetsThreshold,
30006
- reason: meetsThreshold ? `Authorized by auto-threshold (runs=${recurrence}>=${threshold.minRuns}, passRate=${passRate}>=${threshold.minPassRate})` : `Auto-threshold not met: runs=${recurrence}/${threshold.minRuns}, passRate=${passRate}/${threshold.minPassRate}`
30007
- };
30008
- }
30009
- return {
30010
- eligible: false,
30011
- policy,
30012
- authorized: false,
30013
- reason: `Unknown promotion policy: ${String(policy)}`
30014
- };
31579
+ const costSamples = options.costSamples ?? 0;
31580
+ const meanCost = candidate.baselineMetrics.meanCost;
31581
+ const costMet = minCostSavings === void 0 || costSamples > 0 && meanCost >= minCostSavings;
31582
+ const ready = runs >= minRuns && passRate >= minPassRate && costMet;
31583
+ const detail = [
31584
+ `runs=${runs}/${minRuns}`,
31585
+ `passRate=${passRate}/${minPassRate}`,
31586
+ ...minCostSavings === void 0 ? [] : [`meanCost=${meanCost}/${minCostSavings} over ${costSamples} cost sample(s)`]
31587
+ ].join(", ");
31588
+ return ready ? { policy, readyForReview: true, reason: `auto-threshold met (${detail}); operator PR still required` } : { policy, readyForReview: false, reason: `auto-threshold not met (${detail})` };
31589
+ }
31590
+ return { policy, readyForReview: false, reason: "unknown promotion policy" };
30015
31591
  }
30016
31592
 
30017
31593
  // plugins/kxm/src/browser.ts
@@ -30438,9 +32014,9 @@ Instructions for Operator:
30438
32014
  };
30439
32015
 
30440
32016
  // plugins/kxm/src/modes.ts
30441
- var import_yaml6 = __toESM(require_dist(), 1);
30442
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
30443
- import { join as join18 } from "node:path";
32017
+ var import_yaml8 = __toESM(require_dist(), 1);
32018
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
32019
+ import { join as join21 } from "node:path";
30444
32020
  var DEFAULT_MODES_CONFIG = Object.freeze({
30445
32021
  schema: "kxm.modes.v1",
30446
32022
  majorModes: {
@@ -30503,13 +32079,13 @@ function loadModesConfig(projectRoot) {
30503
32079
  if (!projectRoot) {
30504
32080
  return DEFAULT_MODES_CONFIG;
30505
32081
  }
30506
- const modesPath = join18(projectRoot, ".kxm", "modes.yaml");
30507
- if (!existsSync15(modesPath)) {
32082
+ const modesPath = join21(projectRoot, ".kxm", "modes.yaml");
32083
+ if (!existsSync17(modesPath)) {
30508
32084
  return DEFAULT_MODES_CONFIG;
30509
32085
  }
30510
32086
  try {
30511
- const raw = readFileSync12(modesPath, "utf8");
30512
- const parsed = (0, import_yaml6.parse)(raw);
32087
+ const raw = readFileSync14(modesPath, "utf8");
32088
+ const parsed = (0, import_yaml8.parse)(raw);
30513
32089
  if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
30514
32090
  return {
30515
32091
  schema: "kxm.modes.v1",
@@ -30594,11 +32170,11 @@ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog
30594
32170
  estimatedTokens: estimateTokens(baseSystemPromptChars)
30595
32171
  });
30596
32172
  for (const relPath of resolved.contextFiles) {
30597
- const fullPath = join18(projectRoot, relPath);
32173
+ const fullPath = join21(projectRoot, relPath);
30598
32174
  let chars = 0;
30599
- if (existsSync15(fullPath)) {
32175
+ if (existsSync17(fullPath)) {
30600
32176
  try {
30601
- chars = readFileSync12(fullPath, "utf8").length;
32177
+ chars = readFileSync14(fullPath, "utf8").length;
30602
32178
  } catch {
30603
32179
  chars = 0;
30604
32180
  }
@@ -30729,10 +32305,10 @@ ${divider}
30729
32305
  }
30730
32306
 
30731
32307
  // plugins/kxm/src/ssh-remote.ts
30732
- import { spawnSync as spawnSync3 } from "node:child_process";
30733
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync13, readdirSync as readdirSync5, rmSync as rmSync4, statSync as statSync4 } from "node:fs";
32308
+ import { spawnSync as spawnSync4 } from "node:child_process";
32309
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync15, readdirSync as readdirSync8, rmSync as rmSync5, statSync as statSync5 } from "node:fs";
30734
32310
  import { homedir as homedir4 } from "node:os";
30735
- import { join as join19, resolve as resolve9 } from "node:path";
32311
+ import { join as join22, resolve as resolve10 } from "node:path";
30736
32312
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
30737
32313
  var MAX_SSH_OUTPUT_LINES = 2e3;
30738
32314
  var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
@@ -30758,12 +32334,12 @@ function truncateSshOutput(raw) {
30758
32334
  return { text, truncated };
30759
32335
  }
30760
32336
  function parseSshConfig(configPath) {
30761
- const targetPath = configPath ?? join19(homedir4(), ".ssh", "config");
30762
- if (!existsSync16(targetPath)) {
32337
+ const targetPath = configPath ?? join22(homedir4(), ".ssh", "config");
32338
+ if (!existsSync18(targetPath)) {
30763
32339
  return [];
30764
32340
  }
30765
32341
  try {
30766
- const content = readFileSync13(targetPath, "utf-8");
32342
+ const content = readFileSync15(targetPath, "utf-8");
30767
32343
  const lines = content.split("\n");
30768
32344
  const hosts = [];
30769
32345
  let currentHosts = [];
@@ -30805,7 +32381,7 @@ function parseSshConfig(configPath) {
30805
32381
  return [];
30806
32382
  }
30807
32383
  }
30808
- function resolveSshHostG(host, execFn = spawnSync3) {
32384
+ function resolveSshHostG(host, execFn = spawnSync4) {
30809
32385
  try {
30810
32386
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
30811
32387
  if (result.status !== 0 || !result.stdout) {
@@ -30840,15 +32416,15 @@ function resolveSshHostG(host, execFn = spawnSync3) {
30840
32416
  }
30841
32417
  }
30842
32418
  function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
30843
- const resolved = resolve9(socketDir);
30844
- if (!existsSync16(resolved)) {
30845
- mkdirSync9(resolved, { recursive: true, mode: 448 });
32419
+ const resolved = resolve10(socketDir);
32420
+ if (!existsSync18(resolved)) {
32421
+ mkdirSync11(resolved, { recursive: true, mode: 448 });
30846
32422
  }
30847
32423
  return resolved;
30848
32424
  }
30849
32425
  function buildSshArgs(options) {
30850
32426
  const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
30851
- const controlPath = join19(socketDir, "%C");
32427
+ const controlPath = join22(socketDir, "%C");
30852
32428
  const persist2 = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
30853
32429
  const args = [
30854
32430
  "-o",
@@ -30871,9 +32447,9 @@ function buildSshArgs(options) {
30871
32447
  args.push(options.host);
30872
32448
  return args;
30873
32449
  }
30874
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync3) {
32450
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync4) {
30875
32451
  const resolvedDir = ensureSocketDir(socketDir);
30876
- const controlPath = join19(resolvedDir, "%C");
32452
+ const controlPath = join22(resolvedDir, "%C");
30877
32453
  try {
30878
32454
  const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
30879
32455
  encoding: "utf-8"
@@ -30883,9 +32459,9 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
30883
32459
  return false;
30884
32460
  }
30885
32461
  }
30886
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync3) {
32462
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync4) {
30887
32463
  const resolvedDir = ensureSocketDir(socketDir);
30888
- const controlPath = join19(resolvedDir, "%C");
32464
+ const controlPath = join22(resolvedDir, "%C");
30889
32465
  try {
30890
32466
  const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
30891
32467
  encoding: "utf-8"
@@ -30899,13 +32475,13 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
30899
32475
  const resolvedDir = ensureSocketDir(socketDir);
30900
32476
  let removed = 0;
30901
32477
  try {
30902
- const entries = readdirSync5(resolvedDir);
32478
+ const entries = readdirSync8(resolvedDir);
30903
32479
  for (const entry of entries) {
30904
- const fullPath = join19(resolvedDir, entry);
32480
+ const fullPath = join22(resolvedDir, entry);
30905
32481
  try {
30906
- const stat = statSync4(fullPath);
32482
+ const stat = statSync5(fullPath);
30907
32483
  if (stat.isSocket()) {
30908
- rmSync4(fullPath, { force: true });
32484
+ rmSync5(fullPath, { force: true });
30909
32485
  removed++;
30910
32486
  }
30911
32487
  } catch {
@@ -30917,7 +32493,7 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
30917
32493
  }
30918
32494
  function executeSshRun(params) {
30919
32495
  const startTime = Date.now();
30920
- const execSyncFn = params.execFn ?? spawnSync3;
32496
+ const execSyncFn = params.execFn ?? spawnSync4;
30921
32497
  if (params.action === "info") {
30922
32498
  if (params.host) {
30923
32499
  const hostInfo = resolveSshHostG(params.host, execSyncFn);