@kontextmind/kxm 0.7.165 → 0.7.167

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 (41) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/docs/README.md +1 -0
  4. package/docs/architecture/hosted-direction.md +9 -4
  5. package/docs/architecture/inventory.md +1 -0
  6. package/docs/concepts/trust-model.md +1 -1
  7. package/docs/contributing/decisions.md +153 -0
  8. package/docs/contributing/learnings.md +54 -53
  9. package/docs/contributing/operating-rules.md +19 -6
  10. package/docs/glossary.md +2 -2
  11. package/docs/operations/troubleshooting.md +2 -2
  12. package/docs/operations.md +62 -8
  13. package/docs/reference/cli-reference.md +11 -11
  14. package/docs/reference/config-reference.md +36 -11
  15. package/docs/reference/configuration.md +2 -2
  16. package/docs/reference/tools.md +1 -1
  17. package/docs/start/quickstart-claude-code.md +2 -2
  18. package/package.json +1 -1
  19. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  20. package/plugins/kxm/README.md +3 -3
  21. package/plugins/kxm/dist/claude-hook.js +417 -66
  22. package/plugins/kxm/dist/cli.js +1399 -900
  23. package/plugins/kxm/dist/extension.js +399 -64
  24. package/plugins/kxm/dist/mcp-server.js +622 -135
  25. package/plugins/kxm/dist/runtime-supervisor.js +668 -166
  26. package/plugins/kxm/dist/runtime.js +713 -211
  27. package/plugins/kxm/dist/server.js +466 -58
  28. package/plugins/kxm/package.json +1 -1
  29. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +15 -7
  30. package/plugins/kxm/src/cli/hub.ts +80 -21
  31. package/plugins/kxm/src/cli/types.ts +8 -1
  32. package/plugins/kxm/src/cli.ts +41 -7
  33. package/plugins/kxm/src/config.ts +246 -3
  34. package/plugins/kxm/src/extension.ts +8 -7
  35. package/plugins/kxm/src/hub-autostart.ts +20 -0
  36. package/plugins/kxm/src/hub-env.ts +83 -31
  37. package/plugins/kxm/src/hub-identity.ts +337 -0
  38. package/plugins/kxm/src/mcp-server.ts +17 -15
  39. package/plugins/kxm/src/project-name.ts +6 -47
  40. package/plugins/kxm/src/server.ts +22 -4
  41. package/scripts/kxm-hub.mjs +5 -0
@@ -2635,7 +2635,7 @@ var require_stringify = __commonJS({
2635
2635
  props.push(doc.directives.tagString(tag));
2636
2636
  return props.join(" ");
2637
2637
  }
2638
- function stringify2(item, ctx, onComment, onChompKeep) {
2638
+ function stringify3(item, ctx, onComment, onChompKeep) {
2639
2639
  if (identity.isPair(item))
2640
2640
  return item.toString(ctx, onComment, onChompKeep);
2641
2641
  if (identity.isAlias(item)) {
@@ -2664,7 +2664,7 @@ var require_stringify = __commonJS({
2664
2664
  ${ctx.indent}${str}`;
2665
2665
  }
2666
2666
  exports.createStringifyContext = createStringifyContext;
2667
- exports.stringify = stringify2;
2667
+ exports.stringify = stringify3;
2668
2668
  }
2669
2669
  });
2670
2670
 
@@ -2674,7 +2674,7 @@ var require_stringifyPair = __commonJS({
2674
2674
  "use strict";
2675
2675
  var identity = require_identity();
2676
2676
  var Scalar = require_Scalar();
2677
- var stringify2 = require_stringify();
2677
+ var stringify3 = require_stringify();
2678
2678
  var stringifyComment = require_stringifyComment();
2679
2679
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
2680
2680
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
@@ -2696,7 +2696,7 @@ var require_stringifyPair = __commonJS({
2696
2696
  });
2697
2697
  let keyCommentDone = false;
2698
2698
  let chompKeep = false;
2699
- let str = stringify2.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
2699
+ let str = stringify3.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
2700
2700
  if (!explicitKey && !ctx.inFlow && str.length > 1024) {
2701
2701
  if (simpleKeys)
2702
2702
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -2748,7 +2748,7 @@ ${indent}:`;
2748
2748
  ctx.indent = ctx.indent.substring(2);
2749
2749
  }
2750
2750
  let valueCommentDone = false;
2751
- const valueStr = stringify2.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
2751
+ const valueStr = stringify3.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
2752
2752
  let ws = " ";
2753
2753
  if (keyComment || vsb || vcb) {
2754
2754
  ws = vsb ? "\n" : "";
@@ -2889,7 +2889,7 @@ var require_addPairToJSMap = __commonJS({
2889
2889
  "use strict";
2890
2890
  var log = require_log();
2891
2891
  var merge = require_merge();
2892
- var stringify2 = require_stringify();
2892
+ var stringify3 = require_stringify();
2893
2893
  var identity = require_identity();
2894
2894
  var toJS = require_toJS();
2895
2895
  function addPairToJSMap(ctx, map, { key, value }) {
@@ -2925,7 +2925,7 @@ var require_addPairToJSMap = __commonJS({
2925
2925
  if (typeof jsKey !== "object")
2926
2926
  return String(jsKey);
2927
2927
  if (identity.isNode(key) && ctx?.doc) {
2928
- const strCtx = stringify2.createStringifyContext(ctx.doc, {});
2928
+ const strCtx = stringify3.createStringifyContext(ctx.doc, {});
2929
2929
  strCtx.anchors = /* @__PURE__ */ new Set();
2930
2930
  for (const node of ctx.anchors.keys())
2931
2931
  strCtx.anchors.add(node.anchor);
@@ -2992,12 +2992,12 @@ var require_stringifyCollection = __commonJS({
2992
2992
  "node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
2993
2993
  "use strict";
2994
2994
  var identity = require_identity();
2995
- var stringify2 = require_stringify();
2995
+ var stringify3 = require_stringify();
2996
2996
  var stringifyComment = require_stringifyComment();
2997
2997
  function stringifyCollection(collection, ctx, options) {
2998
2998
  const flow = ctx.inFlow ?? collection.flow;
2999
- const stringify3 = flow ? stringifyFlowCollection : stringifyBlockCollection;
3000
- return stringify3(collection, ctx, options);
2999
+ const stringify4 = flow ? stringifyFlowCollection : stringifyBlockCollection;
3000
+ return stringify4(collection, ctx, options);
3001
3001
  }
3002
3002
  function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
3003
3003
  const { indent, options: { commentString } } = ctx;
@@ -3022,7 +3022,7 @@ var require_stringifyCollection = __commonJS({
3022
3022
  }
3023
3023
  }
3024
3024
  chompKeep = false;
3025
- let str2 = stringify2.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
3025
+ let str2 = stringify3.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
3026
3026
  if (comment2)
3027
3027
  str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
3028
3028
  if (chompKeep && comment2)
@@ -3089,7 +3089,7 @@ ${indent}${line}` : "\n";
3089
3089
  }
3090
3090
  if (comment)
3091
3091
  reqNewline = true;
3092
- let str = stringify2.stringify(item, itemCtx, () => comment = null);
3092
+ let str = stringify3.stringify(item, itemCtx, () => comment = null);
3093
3093
  reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
3094
3094
  if (i < items.length - 1) {
3095
3095
  str += ",";
@@ -4450,7 +4450,7 @@ var require_stringifyDocument = __commonJS({
4450
4450
  "node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
4451
4451
  "use strict";
4452
4452
  var identity = require_identity();
4453
- var stringify2 = require_stringify();
4453
+ var stringify3 = require_stringify();
4454
4454
  var stringifyComment = require_stringifyComment();
4455
4455
  function stringifyDocument(doc, options) {
4456
4456
  const lines = [];
@@ -4465,7 +4465,7 @@ var require_stringifyDocument = __commonJS({
4465
4465
  }
4466
4466
  if (hasDirectives)
4467
4467
  lines.push("---");
4468
- const ctx = stringify2.createStringifyContext(doc, options);
4468
+ const ctx = stringify3.createStringifyContext(doc, options);
4469
4469
  const { commentString } = ctx.options;
4470
4470
  if (doc.commentBefore) {
4471
4471
  if (lines.length !== 1)
@@ -4487,7 +4487,7 @@ var require_stringifyDocument = __commonJS({
4487
4487
  contentComment = doc.contents.comment;
4488
4488
  }
4489
4489
  const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
4490
- let body = stringify2.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
4490
+ let body = stringify3.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
4491
4491
  if (contentComment)
4492
4492
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
4493
4493
  if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
@@ -4495,7 +4495,7 @@ var require_stringifyDocument = __commonJS({
4495
4495
  } else
4496
4496
  lines.push(body);
4497
4497
  } else {
4498
- lines.push(stringify2.stringify(doc.contents, ctx));
4498
+ lines.push(stringify3.stringify(doc.contents, ctx));
4499
4499
  }
4500
4500
  if (doc.directives?.docEnd) {
4501
4501
  if (doc.comment) {
@@ -6631,7 +6631,7 @@ var require_cst_scalar = __commonJS({
6631
6631
  var require_cst_stringify = __commonJS({
6632
6632
  "node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
6633
6633
  "use strict";
6634
- var stringify2 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
6634
+ var stringify3 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
6635
6635
  function stringifyToken(token) {
6636
6636
  switch (token.type) {
6637
6637
  case "block-scalar": {
@@ -6684,7 +6684,7 @@ var require_cst_stringify = __commonJS({
6684
6684
  res += stringifyToken(value);
6685
6685
  return res;
6686
6686
  }
6687
- exports.stringify = stringify2;
6687
+ exports.stringify = stringify3;
6688
6688
  }
6689
6689
  });
6690
6690
 
@@ -8395,7 +8395,7 @@ var require_public_api = __commonJS({
8395
8395
  }
8396
8396
  return doc;
8397
8397
  }
8398
- function parse2(src, reviver, options) {
8398
+ function parse3(src, reviver, options) {
8399
8399
  let _reviver = void 0;
8400
8400
  if (typeof reviver === "function") {
8401
8401
  _reviver = reviver;
@@ -8414,7 +8414,7 @@ var require_public_api = __commonJS({
8414
8414
  }
8415
8415
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
8416
8416
  }
8417
- function stringify2(value, replacer, options) {
8417
+ function stringify3(value, replacer, options) {
8418
8418
  let _replacer = null;
8419
8419
  if (typeof replacer === "function" || Array.isArray(replacer)) {
8420
8420
  _replacer = replacer;
@@ -8436,10 +8436,10 @@ var require_public_api = __commonJS({
8436
8436
  return value.toString(options);
8437
8437
  return new Document.Document(value, _replacer, options).toString(options);
8438
8438
  }
8439
- exports.parse = parse2;
8439
+ exports.parse = parse3;
8440
8440
  exports.parseAllDocuments = parseAllDocuments;
8441
8441
  exports.parseDocument = parseDocument;
8442
- exports.stringify = stringify2;
8442
+ exports.stringify = stringify3;
8443
8443
  }
8444
8444
  });
8445
8445
 
@@ -8844,34 +8844,331 @@ var init_memory = __esm({
8844
8844
  }
8845
8845
  });
8846
8846
 
8847
- // plugins/kxm/src/project-name.ts
8848
- var project_name_exports = {};
8849
- __export(project_name_exports, {
8850
- defaultProjectName: () => defaultProjectName
8851
- });
8852
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
8853
- import { basename as basename2, dirname as dirname3, join as join6, resolve as resolve5 } from "node:path";
8854
- function defaultProjectName(cwd, env = process.env, explicit) {
8855
- const fromExplicit = explicit?.trim();
8856
- if (fromExplicit) return fromExplicit;
8857
- const fromEnv = env.KXM_PROJECT?.trim();
8858
- if (fromEnv) return fromEnv;
8859
- const fromProject = readProjectYamlId(cwd);
8860
- if (fromProject) return fromProject;
8847
+ // plugins/kxm/src/config.ts
8848
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
8849
+ import { tmpdir, homedir as homedir3 } from "node:os";
8850
+ import { dirname as dirname3, join as join6, resolve as resolve5 } from "node:path";
8851
+ function isHubKeyEnvName(value) {
8852
+ return HUB_ENV_NAME.test(value);
8853
+ }
8854
+ function isHubOpReference(value) {
8855
+ return HUB_OP_REF.test(value);
8856
+ }
8857
+ function userConfigDirectory(overrideDir) {
8858
+ if (overrideDir) return resolve5(overrideDir);
8859
+ return resolve5(process.env.KXM_USER_CONFIG_DIR?.trim() || join6(homedir3(), ".config", "kxm"));
8860
+ }
8861
+ function repoConfigDirectory(repoRoot) {
8862
+ return resolve5(repoRoot, ".kxm");
8863
+ }
8864
+ function deepMerge(target, source) {
8865
+ const result = { ...target };
8866
+ for (const [key, val] of Object.entries(source)) {
8867
+ if (val && typeof val === "object" && !Array.isArray(val)) {
8868
+ const existing = result[key] && typeof result[key] === "object" && !Array.isArray(result[key]) ? result[key] : {};
8869
+ result[key] = deepMerge(existing, val);
8870
+ } else if (val !== void 0) {
8871
+ result[key] = val;
8872
+ }
8873
+ }
8874
+ return result;
8875
+ }
8876
+ function literalSecretMessage(path) {
8877
+ return `refusing literal hub token at ${path}: store an op:// reference (key.op) or an environment variable name (key.env), never the token`;
8878
+ }
8879
+ function rejectLiteralHubSecrets(value, path) {
8880
+ if (Array.isArray(value)) {
8881
+ value.forEach((item, index) => rejectLiteralHubSecrets(item, `${path}[${index}]`));
8882
+ return;
8883
+ }
8884
+ if (!value || typeof value !== "object") return;
8885
+ for (const [key, child] of Object.entries(value)) {
8886
+ const childPath = `${path}.${key}`;
8887
+ if (typeof child === "string" && (key === "key" || LITERAL_SECRET_KEY.test(key))) {
8888
+ throw new KxmHubConfigError(literalSecretMessage(childPath));
8889
+ }
8890
+ if (child && typeof child === "object") rejectLiteralHubSecrets(child, childPath);
8891
+ }
8892
+ }
8893
+ function parseKeyRef(raw, path) {
8894
+ if (raw === void 0) return void 0;
8895
+ if (typeof raw === "string") throw new KxmHubConfigError(literalSecretMessage(path));
8896
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8897
+ throw new KxmHubConfigError(`${path} must be a key reference with op and/or env, never a token`);
8898
+ }
8899
+ const row = raw;
8900
+ for (const key of Object.keys(row)) {
8901
+ if (key !== "op" && key !== "env") {
8902
+ throw new KxmHubConfigError(literalSecretMessage(`${path}.${key}`));
8903
+ }
8904
+ }
8905
+ const ref = {};
8906
+ if (row.op !== void 0) {
8907
+ if (typeof row.op !== "string" || !isHubOpReference(row.op)) {
8908
+ throw new KxmHubConfigError(`${path}.op must be an op://vault/item/field reference, not a token`);
8909
+ }
8910
+ ref.op = row.op;
8911
+ }
8912
+ if (row.env !== void 0) {
8913
+ if (typeof row.env !== "string" || !isHubKeyEnvName(row.env)) {
8914
+ throw new KxmHubConfigError(`${path}.env must be an environment variable name, not a token`);
8915
+ }
8916
+ ref.env = row.env;
8917
+ }
8918
+ if (!ref.op && !ref.env) throw new KxmHubConfigError(`${path} needs key.op, key.env, or both`);
8919
+ return ref;
8920
+ }
8921
+ function parseEndpoint(raw, path) {
8922
+ if (raw === void 0) return void 0;
8923
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8924
+ throw new KxmHubConfigError(`${path} must be an object with url, project, and key`);
8925
+ }
8926
+ const row = raw;
8927
+ const endpoint = {};
8928
+ if (row.url !== void 0) {
8929
+ if (typeof row.url !== "string" || !isHubUrl(row.url)) {
8930
+ throw new KxmHubConfigError(`${path}.url must be an http or https URL without credentials, query, or fragment`);
8931
+ }
8932
+ endpoint.url = row.url.replace(/\/$/, "");
8933
+ }
8934
+ if (row.project !== void 0) {
8935
+ if (typeof row.project !== "string" || !isHubProjectId(row.project)) {
8936
+ throw new KxmHubConfigError(`${path}.project must be a single-line project id, not a token`);
8937
+ }
8938
+ endpoint.project = row.project.trim();
8939
+ }
8940
+ const key = parseKeyRef(row.key, `${path}.key`);
8941
+ if (key) endpoint.key = key;
8942
+ for (const name of Object.keys(row)) {
8943
+ if (name !== "url" && name !== "project" && name !== "key") {
8944
+ if (typeof row[name] === "string") throw new KxmHubConfigError(literalSecretMessage(`${path}.${name}`));
8945
+ throw new KxmHubConfigError(`${path}.${name} is not a hub endpoint field`);
8946
+ }
8947
+ }
8948
+ return endpoint;
8949
+ }
8950
+ function isHubUrl(raw) {
8861
8951
  try {
8862
- const pkg = JSON.parse(readFileSync7(join6(cwd, "package.json"), "utf8"));
8863
- if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
8952
+ const parsed = new URL(raw);
8953
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && !raw.includes("?") && !raw.includes("#");
8864
8954
  } catch {
8955
+ return false;
8865
8956
  }
8866
- return basename2(cwd);
8867
8957
  }
8868
- function readProjectYamlId(start) {
8958
+ function isHubProjectId(raw) {
8959
+ const value = raw.trim();
8960
+ return value.length > 0 && value.length <= 200 && !/[\s\r\n]/.test(value);
8961
+ }
8962
+ function parseProjectMap(raw) {
8963
+ if (raw === void 0) return void 0;
8964
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8965
+ throw new KxmHubConfigError("hub.projects must be a map of project id to key reference, never a token");
8966
+ }
8967
+ const projects = {};
8968
+ for (const [project, value] of Object.entries(raw)) {
8969
+ if (!isHubProjectId(project)) {
8970
+ throw new KxmHubConfigError(`hub.projects key ${JSON.stringify(project)} must be a project id, not a token`);
8971
+ }
8972
+ const ref = parseKeyRef(value, `hub.projects.${project}`);
8973
+ if (!ref) throw new KxmHubConfigError(`hub.projects.${project} needs an op:// reference or an environment variable name`);
8974
+ projects[project.trim()] = ref;
8975
+ }
8976
+ return projects;
8977
+ }
8978
+ function normalizeHubConfig(raw) {
8979
+ const autoStart = raw?.autoStart;
8980
+ const hub = {
8981
+ autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart
8982
+ };
8983
+ if (raw === void 0) return hub;
8984
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8985
+ throw new KxmHubConfigError("hub must be an object");
8986
+ }
8987
+ rejectLiteralHubSecrets(raw, "hub");
8988
+ const row = raw;
8989
+ if (row.mode !== void 0) {
8990
+ if (row.mode !== "local" && row.mode !== "cloud") {
8991
+ throw new KxmHubConfigError("hub.mode must be local or cloud");
8992
+ }
8993
+ hub.mode = row.mode;
8994
+ }
8995
+ const local = parseEndpoint(row.local, "hub.local");
8996
+ const cloud = parseEndpoint(row.cloud, "hub.cloud");
8997
+ const projects = parseProjectMap(row.projects);
8998
+ if (local) hub.local = local;
8999
+ if (cloud) hub.cloud = cloud;
9000
+ if (projects && Object.keys(projects).length > 0) hub.projects = projects;
9001
+ return hub;
9002
+ }
9003
+ function findConfigRoot(start) {
8869
9004
  let dir = resolve5(start);
8870
9005
  for (; ; ) {
8871
- const file = join6(dir, ".kxm", "project.yaml");
8872
- if (existsSync7(file)) {
9006
+ if (existsSync7(join6(dir, ".kxm", "project.yaml")) || existsSync7(join6(dir, ".kxm", "config.yaml"))) return dir;
9007
+ const parent = dirname3(dir);
9008
+ if (parent === dir) return resolve5(start);
9009
+ dir = parent;
9010
+ }
9011
+ }
9012
+ function userConfigDirForEnv(env) {
9013
+ const explicit = env.KXM_USER_CONFIG_DIR?.trim();
9014
+ if (explicit) return explicit;
9015
+ if (env === process.env) return void 0;
9016
+ return join6(tmpdir(), "kxm-no-user-config");
9017
+ }
9018
+ function loadHubSettings(start, env = process.env) {
9019
+ const userConfigDir = userConfigDirForEnv(env);
9020
+ return loadKxmConfig(findConfigRoot(start), userConfigDir === void 0 ? {} : { userConfigDir }).hub;
9021
+ }
9022
+ function recordOf(raw) {
9023
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
9024
+ }
9025
+ function finiteNumber(value) {
9026
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9027
+ }
9028
+ function normalizeImprovementConfig(raw) {
9029
+ const value = recordOf(raw);
9030
+ const threshold = recordOf(value.autoThreshold);
9031
+ const policy = value.promotionPolicy;
9032
+ const halfLife = finiteNumber(value.telemetryHalfLifeDays);
9033
+ const minRuns = finiteNumber(threshold.minRuns);
9034
+ const minPassRate = finiteNumber(threshold.minPassRate);
9035
+ const minCostSavings = finiteNumber(threshold.minCostSavings);
9036
+ return {
9037
+ promotionPolicy: IMPROVEMENT_PROMOTION_POLICIES.includes(policy) ? policy : "manual_pr",
9038
+ telemetryHalfLifeDays: halfLife !== void 0 && halfLife > 0 && halfLife <= 3650 ? halfLife : 14,
9039
+ autoThreshold: {
9040
+ minRuns: minRuns !== void 0 && Number.isInteger(minRuns) && minRuns >= 1 && minRuns <= 1e6 ? minRuns : 10,
9041
+ minPassRate: minPassRate !== void 0 && minPassRate >= 0 && minPassRate <= 1 ? minPassRate : 0.95,
9042
+ minCostSavings: minCostSavings !== void 0 && minCostSavings >= 0 ? minCostSavings : 0.5
9043
+ }
9044
+ };
9045
+ }
9046
+ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
9047
+ const userDir = userConfigDirectory(options.userConfigDir);
9048
+ const userConfigFile = join6(userDir, "config.yaml");
9049
+ const repoDir = repoConfigDirectory(repoRoot);
9050
+ const repoConfigFile = join6(repoDir, "config.yaml");
9051
+ let userRaw = {};
9052
+ let userLoadedPath;
9053
+ if (existsSync7(userConfigFile)) {
9054
+ try {
9055
+ const text = readFileSync7(userConfigFile, "utf8");
9056
+ userRaw = (0, import_yaml2.parse)(text) ?? {};
9057
+ userLoadedPath = userConfigFile;
9058
+ } catch (error) {
9059
+ throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
9060
+ }
9061
+ }
9062
+ let repoRaw = {};
9063
+ let repoLoadedPath;
9064
+ if (existsSync7(repoConfigFile)) {
9065
+ try {
9066
+ const text = readFileSync7(repoConfigFile, "utf8");
9067
+ repoRaw = (0, import_yaml2.parse)(text) ?? {};
9068
+ repoLoadedPath = repoConfigFile;
9069
+ } catch (error) {
9070
+ throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
9071
+ }
9072
+ }
9073
+ const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG));
9074
+ const mergedUser = deepMerge(baseCopy, userRaw);
9075
+ const mergedAll = deepMerge(mergedUser, repoRaw);
9076
+ return {
9077
+ schema: KXM_CONFIG_SCHEMA,
9078
+ user: mergedAll.user ?? {},
9079
+ defaults: mergedAll.defaults ?? {},
9080
+ dash: mergedAll.dash ?? {},
9081
+ sync: mergedAll.sync ?? {},
9082
+ hub: normalizeHubConfig(mergedAll.hub),
9083
+ improvement: normalizeImprovementConfig(mergedAll.improvement),
9084
+ routing: mergedAll.routing ?? DEFAULT_KXM_CONFIG.routing,
9085
+ telemetry: mergedAll.telemetry ?? DEFAULT_KXM_CONFIG.telemetry,
9086
+ loadedFrom: {
9087
+ userConfigPath: userLoadedPath,
9088
+ repoConfigPath: repoLoadedPath
9089
+ }
9090
+ };
9091
+ }
9092
+ var import_yaml2, KXM_CONFIG_SCHEMA, KxmHubConfigError, HUB_ENV_NAME, HUB_OP_REF, LITERAL_SECRET_KEY, IMPROVEMENT_PROMOTION_POLICIES, DEFAULT_KXM_CONFIG;
9093
+ var init_config = __esm({
9094
+ "plugins/kxm/src/config.ts"() {
9095
+ "use strict";
9096
+ import_yaml2 = __toESM(require_dist(), 1);
9097
+ KXM_CONFIG_SCHEMA = "kxm.config.v1";
9098
+ KxmHubConfigError = class extends Error {
9099
+ code = "hub_config_invalid";
9100
+ constructor(message) {
9101
+ super(message);
9102
+ this.name = "KxmHubConfigError";
9103
+ }
9104
+ };
9105
+ HUB_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
9106
+ HUB_OP_REF = /^op:\/\/\S+\/\S+\/\S+$/;
9107
+ LITERAL_SECRET_KEY = /^(token|secret|password|authToken|auth_token|apiKey|api_key|api_token|bearer|credential|access_token)$/i;
9108
+ IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
9109
+ DEFAULT_KXM_CONFIG = {
9110
+ schema: KXM_CONFIG_SCHEMA,
9111
+ user: {
9112
+ theme: "dark",
9113
+ preferredCritics: ["reviewer-arch", "reviewer-cli"],
9114
+ tokenBudget: 16e3
9115
+ },
9116
+ defaults: {
9117
+ workflow: "software-engineering/feature-implementation",
9118
+ harness: "pi"
9119
+ },
9120
+ dash: {
9121
+ defaultScreen: "agents",
9122
+ refreshIntervalMs: 1e3,
9123
+ autoOpen: false
9124
+ },
9125
+ sync: {
9126
+ defaultTracker: "none"
9127
+ },
9128
+ hub: {
9129
+ autoStart: "background"
9130
+ },
9131
+ improvement: {
9132
+ promotionPolicy: "manual_pr",
9133
+ telemetryHalfLifeDays: 14,
9134
+ autoThreshold: {
9135
+ minRuns: 10,
9136
+ minPassRate: 0.95,
9137
+ minCostSavings: 0.5
9138
+ }
9139
+ },
9140
+ routing: {
9141
+ shadowExecution: {
9142
+ enabled: false,
9143
+ sampleRate: 0.05,
9144
+ candidateModels: []
9145
+ },
9146
+ circuitBreaker: {
9147
+ mode: "soft_demotion",
9148
+ failureThreshold: 3,
9149
+ windowSeconds: 3600,
9150
+ cooldownSeconds: 1800,
9151
+ penaltyMultiplier: 5
9152
+ }
9153
+ },
9154
+ telemetry: {
9155
+ federated: true,
9156
+ anonymize: true
9157
+ }
9158
+ };
9159
+ }
9160
+ });
9161
+
9162
+ // plugins/kxm/src/hub-identity.ts
9163
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
9164
+ import { basename as basename2, dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
9165
+ function projectYamlId(start) {
9166
+ let dir = resolve6(start);
9167
+ for (; ; ) {
9168
+ const file = join7(dir, ".kxm", "project.yaml");
9169
+ if (existsSync8(file)) {
8873
9170
  try {
8874
- const doc = (0, import_yaml2.parse)(readFileSync7(file, "utf8"));
9171
+ const doc = (0, import_yaml3.parse)(readFileSync8(file, "utf8"));
8875
9172
  if (doc && typeof doc === "object" && !Array.isArray(doc) && typeof doc.id === "string" && doc.id.trim()) {
8876
9173
  return doc.id.trim();
8877
9174
  }
@@ -8879,16 +9176,70 @@ function readProjectYamlId(start) {
8879
9176
  }
8880
9177
  return void 0;
8881
9178
  }
8882
- const parent = dirname3(dir);
9179
+ const parent = dirname4(dir);
8883
9180
  if (parent === dir) return void 0;
8884
9181
  dir = parent;
8885
9182
  }
8886
9183
  }
8887
- var import_yaml2;
9184
+ function packageName(cwd) {
9185
+ try {
9186
+ const pkg = JSON.parse(readFileSync8(join7(cwd, "package.json"), "utf8"));
9187
+ if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
9188
+ } catch {
9189
+ }
9190
+ return void 0;
9191
+ }
9192
+ function activeBinding(env) {
9193
+ try {
9194
+ return readHubBinding(env);
9195
+ } catch (error) {
9196
+ if (error instanceof HubBindingError) return void 0;
9197
+ throw error;
9198
+ }
9199
+ }
9200
+ function endpointFor(mode, settings) {
9201
+ return mode === "cloud" ? settings.cloud : settings.local;
9202
+ }
9203
+ function resolveProjectIdentity(cwd, env = process.env, explicit) {
9204
+ const fromExplicit = explicit?.trim();
9205
+ if (fromExplicit) return { project: fromExplicit, source: "flag", sourceLabel: "--project" };
9206
+ const fromEnv = env.KXM_PROJECT?.trim();
9207
+ if (fromEnv) return { project: fromEnv, source: "env", sourceLabel: "KXM_PROJECT" };
9208
+ const settings = loadHubSettings(cwd, env);
9209
+ const mode = settings.mode ?? (activeBinding(env)?.cloud ? "cloud" : "local");
9210
+ const fromConfig = endpointFor(mode, settings)?.project?.trim();
9211
+ if (fromConfig) {
9212
+ return { project: fromConfig, source: "config", sourceLabel: `.kxm/config.yaml hub.${mode}.project` };
9213
+ }
9214
+ const fromProject = projectYamlId(cwd);
9215
+ if (fromProject) return { project: fromProject, source: "project.yaml", sourceLabel: ".kxm/project.yaml id" };
9216
+ const fromPackage = packageName(cwd);
9217
+ if (fromPackage) return { project: fromPackage, source: "package.json", sourceLabel: "package.json name" };
9218
+ return { project: basename2(cwd), source: "directory", sourceLabel: "directory name" };
9219
+ }
9220
+ function defaultProjectName(cwd, env = process.env, explicit) {
9221
+ return resolveProjectIdentity(cwd, env, explicit).project;
9222
+ }
9223
+ var import_yaml3;
9224
+ var init_hub_identity = __esm({
9225
+ "plugins/kxm/src/hub-identity.ts"() {
9226
+ "use strict";
9227
+ import_yaml3 = __toESM(require_dist(), 1);
9228
+ init_config();
9229
+ init_hub_binding();
9230
+ }
9231
+ });
9232
+
9233
+ // plugins/kxm/src/project-name.ts
9234
+ var project_name_exports = {};
9235
+ __export(project_name_exports, {
9236
+ defaultProjectName: () => defaultProjectName,
9237
+ resolveProjectIdentity: () => resolveProjectIdentity
9238
+ });
8888
9239
  var init_project_name = __esm({
8889
9240
  "plugins/kxm/src/project-name.ts"() {
8890
9241
  "use strict";
8891
- import_yaml2 = __toESM(require_dist(), 1);
9242
+ init_hub_identity();
8892
9243
  }
8893
9244
  });
8894
9245
 
@@ -9034,9 +9385,9 @@ __export(commands_exports, {
9034
9385
  timingSafeStringCompare: () => timingSafeStringCompare
9035
9386
  });
9036
9387
  import { createHash, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
9037
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
9038
- import { homedir as homedir3 } from "node:os";
9039
- import { dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
9388
+ import { chmodSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
9389
+ import { homedir as homedir4 } from "node:os";
9390
+ import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
9040
9391
  function forwardedHops(handling) {
9041
9392
  if (!handling?.length) return void 0;
9042
9393
  return {
@@ -9218,18 +9569,18 @@ function parseSessionToken(token) {
9218
9569
  return void 0;
9219
9570
  }
9220
9571
  function resolveUserConfigDirectory(overrideDir) {
9221
- if (overrideDir) return resolve6(overrideDir);
9222
- return resolve6(process.env.KXM_USER_CONFIG_DIR?.trim() || join7(homedir3(), ".config", "kxm"));
9572
+ if (overrideDir) return resolve7(overrideDir);
9573
+ return resolve7(process.env.KXM_USER_CONFIG_DIR?.trim() || join8(homedir4(), ".config", "kxm"));
9223
9574
  }
9224
9575
  function sessionTokenPath(userConfigDir) {
9225
- return join7(resolveUserConfigDirectory(userConfigDir), "session.token");
9576
+ return join8(resolveUserConfigDirectory(userConfigDir), "session.token");
9226
9577
  }
9227
9578
  function persistSessionTokenToDisk(token, options) {
9228
9579
  const filePath = sessionTokenPath(options?.userConfigDir);
9229
- const dir = dirname4(filePath);
9230
- mkdirSync6(dir, { recursive: true, mode: 448 });
9580
+ const dir = dirname5(filePath);
9581
+ mkdirSync7(dir, { recursive: true, mode: 448 });
9231
9582
  const mode = options?.mode ?? 384;
9232
- writeFileSync5(filePath, `${token.trim()}
9583
+ writeFileSync6(filePath, `${token.trim()}
9233
9584
  `, { encoding: "utf8", mode });
9234
9585
  try {
9235
9586
  chmodSync(filePath, mode);
@@ -9239,9 +9590,9 @@ function persistSessionTokenToDisk(token, options) {
9239
9590
  }
9240
9591
  function readSessionTokenFromDisk(options) {
9241
9592
  const filePath = sessionTokenPath(options?.userConfigDir);
9242
- if (!existsSync8(filePath)) return void 0;
9593
+ if (!existsSync9(filePath)) return void 0;
9243
9594
  try {
9244
- const token = readFileSync8(filePath, "utf8").trim();
9595
+ const token = readFileSync9(filePath, "utf8").trim();
9245
9596
  if (!token) return void 0;
9246
9597
  const payload = parseSessionToken(token);
9247
9598
  if (!payload) return void 0;
@@ -9252,7 +9603,7 @@ function readSessionTokenFromDisk(options) {
9252
9603
  }
9253
9604
  function clearSessionTokenFromDisk(options) {
9254
9605
  const filePath = sessionTokenPath(options?.userConfigDir);
9255
- if (!existsSync8(filePath)) return false;
9606
+ if (!existsSync9(filePath)) return false;
9256
9607
  try {
9257
9608
  unlinkSync(filePath);
9258
9609
  return true;
@@ -9351,10 +9702,10 @@ function enforceToolPolicy(commandName, env = process.env, options) {
9351
9702
  return { allowed: true };
9352
9703
  }
9353
9704
  const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
9354
- if (existsSync8(tokenFile)) {
9705
+ if (existsSync9(tokenFile)) {
9355
9706
  let tokenRaw;
9356
9707
  try {
9357
- tokenRaw = readFileSync8(tokenFile, "utf8").trim();
9708
+ tokenRaw = readFileSync9(tokenFile, "utf8").trim();
9358
9709
  } catch {
9359
9710
  return { allowed: false, error: "session_token_invalid", detail: "Session token file on disk could not be read" };
9360
9711
  }
@@ -10040,8 +10391,8 @@ var init_commands = __esm({
10040
10391
  });
10041
10392
 
10042
10393
  // plugins/kxm/src/claude-hook.ts
10043
- import { readFileSync as readFileSync9, statSync } from "node:fs";
10044
- import { join as join8, resolve as resolve7 } from "node:path";
10394
+ import { readFileSync as readFileSync10, statSync } from "node:fs";
10395
+ import { join as join9, resolve as resolve8 } from "node:path";
10045
10396
 
10046
10397
  // plugins/kxm/src/session-token-hint.ts
10047
10398
  var ENV_TEXT = "KXM_SESSION_TOKEN in the environment Claude Code was launched from is malformed or expired, so every kxm_* tool fails with tool_policy_denied. Ask the user to unset or replace KXM_SESSION_TOKEN in the environment Claude Code was launched from, then restart Claude Code.";
@@ -10114,15 +10465,15 @@ function truncate2(text, max) {
10114
10465
  }
10115
10466
  async function sessionStartContext(input, env) {
10116
10467
  const inputCwd = typeof input.cwd === "string" ? input.cwd.trim() : "";
10117
- const root = resolve7(env.CLAUDE_PROJECT_DIR?.trim() || inputCwd || process.cwd());
10118
- if (!isDirectory(join8(root, ".kxm"))) return void 0;
10468
+ const root = resolve8(env.CLAUDE_PROJECT_DIR?.trim() || inputCwd || process.cwd());
10469
+ if (!isDirectory(join9(root, ".kxm"))) return void 0;
10119
10470
  const [
10120
10471
  { resolveSessionHubStatus: resolveSessionHubStatus2 },
10121
10472
  { loadLocalMeshSnapshot: loadLocalMeshSnapshot2, resolveKxmSnapshotPaths: resolveKxmSnapshotPaths2 },
10122
10473
  { formatMemoryBriefText: formatMemoryBriefText2, generateMemoryBrief: generateMemoryBrief2 },
10123
10474
  { defaultProjectName: defaultProjectName2 },
10124
10475
  { enforceToolPolicy: enforceToolPolicy2 },
10125
- { parse: parse2 }
10476
+ { parse: parse3 }
10126
10477
  ] = await Promise.all([
10127
10478
  Promise.resolve().then(() => (init_session_work(), session_work_exports)),
10128
10479
  Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports)),
@@ -10136,7 +10487,7 @@ async function sessionStartContext(input, env) {
10136
10487
  const url = (env.CLAUDE_PLUGIN_OPTION_SERVER_URL?.trim() || DEFAULT_SERVER_URL).replace(/\/$/, "");
10137
10488
  let runtimeProjectId;
10138
10489
  try {
10139
- const project = parse2(readFileSync9(join8(root, ".kxm", "project.yaml"), "utf8"));
10490
+ const project = parse3(readFileSync10(join9(root, ".kxm", "project.yaml"), "utf8"));
10140
10491
  if (typeof project?.id === "string" && project.id.trim()) runtimeProjectId = project.id.trim();
10141
10492
  } catch {
10142
10493
  }