@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
@@ -1650,284 +1650,6 @@ var init_client = __esm({
1650
1650
  }
1651
1651
  });
1652
1652
 
1653
- // plugins/kxm/src/hub-binding.ts
1654
- import { spawnSync } from "node:child_process";
1655
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
1656
- import { homedir } from "node:os";
1657
- import { dirname as dirname2, isAbsolute, join as join3, resolve } from "node:path";
1658
- function resolveUserStateRoot(env) {
1659
- const explicit = env.KXM_STATE_HOME?.trim();
1660
- if (explicit) {
1661
- if (!isAbsolute(explicit)) throw new HubBindingError("local_state_root_not_absolute");
1662
- return resolve(explicit);
1663
- }
1664
- if (process.platform === "win32") {
1665
- const localAppData = env.LOCALAPPDATA?.trim();
1666
- const base2 = localAppData && isAbsolute(localAppData) ? localAppData : join3(homedir(), "AppData", "Local");
1667
- return resolve(base2, "KXM");
1668
- }
1669
- if (process.platform === "darwin") return resolve(homedir(), "Library", "Application Support", "KXM");
1670
- const xdgState = env.XDG_STATE_HOME?.trim();
1671
- const base = xdgState && isAbsolute(xdgState) ? xdgState : join3(homedir(), ".local", "state");
1672
- return resolve(base, "kxm");
1673
- }
1674
- function hubBindingFile(env = process.env) {
1675
- return join3(resolveUserStateRoot(env), "hub-binding.json");
1676
- }
1677
- function validateHubUrl(raw) {
1678
- let parsed;
1679
- try {
1680
- parsed = new URL(raw);
1681
- } catch {
1682
- throw new HubBindingError("hub_url_invalid");
1683
- }
1684
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || raw.includes("?") || raw.includes("#")) {
1685
- throw new HubBindingError("hub_url_invalid");
1686
- }
1687
- return parsed.href.replace(/\/$/, "");
1688
- }
1689
- function splitTokenCommand(raw) {
1690
- if (/[|&;<>$`\n\r]/.test(raw)) {
1691
- throw new CloudTokenError(
1692
- "cloud_token_command_invalid",
1693
- "token command must be a program and arguments, not a shell pipeline"
1694
- );
1695
- }
1696
- const args = [];
1697
- let current = "";
1698
- let quote;
1699
- for (const ch of raw) {
1700
- if (quote) {
1701
- if (ch === quote) quote = void 0;
1702
- else current += ch;
1703
- continue;
1704
- }
1705
- if (ch === "'" || ch === '"') {
1706
- quote = ch;
1707
- continue;
1708
- }
1709
- if (ch === " " || ch === " ") {
1710
- if (current.length > 0) {
1711
- args.push(current);
1712
- current = "";
1713
- }
1714
- continue;
1715
- }
1716
- current += ch;
1717
- }
1718
- if (quote) {
1719
- throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
1720
- }
1721
- if (current.length > 0) args.push(current);
1722
- if (args.length === 0) {
1723
- throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
1724
- }
1725
- return args;
1726
- }
1727
- function isCloudTokenEnvName(value) {
1728
- return TOKEN_ENV_NAME.test(value);
1729
- }
1730
- function effectiveHubBindingScope(url, env = process.env) {
1731
- try {
1732
- const binding = readHubBinding(env);
1733
- if (binding?.cloud) {
1734
- try {
1735
- if (validateHubUrl(url) === binding.url) return "remote";
1736
- } catch {
1737
- }
1738
- }
1739
- } catch (error) {
1740
- if (!(error instanceof HubBindingError)) throw error;
1741
- }
1742
- return hubBindingScope(url);
1743
- }
1744
- function resolveCloudHubToken(binding, env = process.env) {
1745
- if (!binding.cloud) {
1746
- throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
1747
- }
1748
- if (binding.tokenEnv) {
1749
- const value = env[binding.tokenEnv]?.trim();
1750
- if (value) return value;
1751
- }
1752
- if (binding.tokenCommand) return runTokenCommand(binding.tokenCommand, env);
1753
- const source = binding.tokenEnv ? `${binding.tokenEnv} is unset or empty` : "no token source is configured";
1754
- throw new CloudTokenError(
1755
- "cloud_token_missing",
1756
- `cloud hub token is missing: ${source}; the local hub-env token was not used`
1757
- );
1758
- }
1759
- function runTokenCommand(command, env) {
1760
- const argv = splitTokenCommand(command);
1761
- const [program2, ...args] = argv;
1762
- const result = spawnSync(program2, args, {
1763
- encoding: "utf8",
1764
- shell: false,
1765
- timeout: TOKEN_COMMAND_TIMEOUT_MS,
1766
- maxBuffer: TOKEN_COMMAND_MAX_BUFFER,
1767
- env,
1768
- windowsHide: true
1769
- });
1770
- if (result.error || result.status !== 0) {
1771
- throw new CloudTokenError(
1772
- "cloud_token_command_failed",
1773
- "token command failed; the local hub-env token was not used"
1774
- );
1775
- }
1776
- const stdout = result.stdout ?? "";
1777
- if (stdout.trim().length === 0 || stdout.trim() !== stdout.trim().split(/\r?\n/)[0]) {
1778
- throw new CloudTokenError(
1779
- "cloud_token_command_failed",
1780
- "token command did not print a single token; the local hub-env token was not used"
1781
- );
1782
- }
1783
- return stdout.trim();
1784
- }
1785
- function hubBindingScope(url) {
1786
- let host;
1787
- try {
1788
- host = new URL(url).hostname.toLowerCase();
1789
- } catch {
1790
- return "remote";
1791
- }
1792
- if (host === "localhost" || host === "::1" || host === "[::1]" || host.endsWith(".localhost")) return "loopback";
1793
- const v4 = /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.exec(host);
1794
- return v4 && [v4[1], v4[2], v4[3]].every((part) => Number(part) <= 255) ? "loopback" : "remote";
1795
- }
1796
- function isIsoTimestamp(value) {
1797
- if (Number.isNaN(Date.parse(value))) return false;
1798
- return value === new Date(value).toISOString();
1799
- }
1800
- function isAbortError(error) {
1801
- return Boolean(
1802
- error && typeof error === "object" && ("name" in error && error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR")
1803
- );
1804
- }
1805
- function readHubBinding(env = process.env) {
1806
- const file = hubBindingFile(env);
1807
- if (!existsSync3(file)) return void 0;
1808
- let parsed;
1809
- try {
1810
- parsed = JSON.parse(readFileSync2(file, "utf8"));
1811
- } catch {
1812
- throw new HubBindingError(`malformed hub binding at ${file}`);
1813
- }
1814
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1815
- throw new HubBindingError(`malformed hub binding at ${file}`);
1816
- }
1817
- const row = parsed;
1818
- const keys = Object.keys(row);
1819
- const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
1820
- const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
1821
- if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
1822
- throw new HubBindingError(`malformed hub binding at ${file}`);
1823
- }
1824
- let url;
1825
- try {
1826
- url = validateHubUrl(row.url);
1827
- } catch {
1828
- throw new HubBindingError(`malformed hub binding at ${file}`);
1829
- }
1830
- if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
1831
- if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
1832
- let tokenEnv;
1833
- let tokenCommand;
1834
- if (row.tokenEnv !== void 0) {
1835
- if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
1836
- throw new HubBindingError(`malformed hub binding at ${file}`);
1837
- }
1838
- tokenEnv = row.tokenEnv;
1839
- }
1840
- if (row.tokenCommand !== void 0) {
1841
- if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
1842
- try {
1843
- splitTokenCommand(row.tokenCommand);
1844
- } catch {
1845
- throw new HubBindingError(`malformed hub binding at ${file}`);
1846
- }
1847
- tokenCommand = row.tokenCommand;
1848
- }
1849
- if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
1850
- return {
1851
- schema: HUB_BINDING_SCHEMA,
1852
- url,
1853
- boundAt: row.boundAt,
1854
- cloud: true,
1855
- ...tokenEnv ? { tokenEnv } : {},
1856
- ...tokenCommand ? { tokenCommand } : {}
1857
- };
1858
- }
1859
- function writeHubBinding(record, env = process.env) {
1860
- const file = hubBindingFile(env);
1861
- mkdirSync2(dirname2(file), { recursive: true, mode: 448 });
1862
- const temporary = join3(dirname2(file), `.hub-binding-${process.pid}.tmp`);
1863
- try {
1864
- writeFileSync2(temporary, `${JSON.stringify(record, null, 2)}
1865
- `, { encoding: "utf8", mode: 384 });
1866
- renameSync(temporary, file);
1867
- } finally {
1868
- rmSync(temporary, { force: true });
1869
- }
1870
- return file;
1871
- }
1872
- function removeHubBinding(env = process.env) {
1873
- const file = hubBindingFile(env);
1874
- try {
1875
- rmSync(file);
1876
- return true;
1877
- } catch (error) {
1878
- if (error.code === "ENOENT") return false;
1879
- throw error;
1880
- }
1881
- }
1882
- async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
1883
- const started = Date.now();
1884
- const controller = new AbortController();
1885
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1886
- try {
1887
- const response = await fetchImpl(`${url}/health`, { signal: controller.signal });
1888
- if (!response.ok) return { health: "unknown", probeMs: Date.now() - started };
1889
- let body;
1890
- try {
1891
- body = await response.json();
1892
- } catch {
1893
- return { health: "unknown", probeMs: Date.now() - started };
1894
- }
1895
- if (body && typeof body === "object" && body.ok === true) {
1896
- return { health: "on", probeMs: Date.now() - started };
1897
- }
1898
- return { health: "unknown", probeMs: Date.now() - started };
1899
- } catch (error) {
1900
- return { health: isAbortError(error) ? "unknown" : "off", probeMs: Date.now() - started };
1901
- } finally {
1902
- clearTimeout(timer);
1903
- }
1904
- }
1905
- var HUB_BINDING_SCHEMA, HUB_HEALTH_PROBE_MS, CloudTokenError, TOKEN_ENV_NAME, TOKEN_COMMAND_TIMEOUT_MS, TOKEN_COMMAND_MAX_BUFFER, HubBindingError;
1906
- var init_hub_binding = __esm({
1907
- "plugins/kxm/src/hub-binding.ts"() {
1908
- "use strict";
1909
- HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
1910
- HUB_HEALTH_PROBE_MS = 300;
1911
- CloudTokenError = class extends Error {
1912
- code;
1913
- constructor(code, message) {
1914
- super(message);
1915
- this.name = "CloudTokenError";
1916
- this.code = code;
1917
- }
1918
- };
1919
- TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
1920
- TOKEN_COMMAND_TIMEOUT_MS = 15e3;
1921
- TOKEN_COMMAND_MAX_BUFFER = 4096;
1922
- HubBindingError = class extends Error {
1923
- constructor(message) {
1924
- super(message);
1925
- this.name = "HubBindingError";
1926
- }
1927
- };
1928
- }
1929
- });
1930
-
1931
1653
  // node_modules/yaml/dist/nodes/identity.js
1932
1654
  var require_identity = __commonJS({
1933
1655
  "node_modules/yaml/dist/nodes/identity.js"(exports) {
@@ -9258,30 +8980,730 @@ var require_dist = __commonJS({
9258
8980
  }
9259
8981
  });
9260
8982
 
9261
- // plugins/kxm/src/project-name.ts
9262
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
9263
- import { basename, dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
9264
- function defaultProjectName(cwd, env = process.env, explicit) {
9265
- const fromExplicit = explicit?.trim();
9266
- if (fromExplicit) return fromExplicit;
9267
- const fromEnv = env.KXM_PROJECT?.trim();
9268
- if (fromEnv) return fromEnv;
9269
- const fromProject = readProjectYamlId(cwd);
9270
- if (fromProject) return fromProject;
8983
+ // plugins/kxm/src/config.ts
8984
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
8985
+ import { tmpdir, homedir } from "node:os";
8986
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
8987
+ function isHubKeyEnvName(value) {
8988
+ return HUB_ENV_NAME.test(value);
8989
+ }
8990
+ function isHubOpReference(value) {
8991
+ return HUB_OP_REF.test(value);
8992
+ }
8993
+ function userConfigDirectory(overrideDir) {
8994
+ if (overrideDir) return resolve(overrideDir);
8995
+ return resolve(process.env.KXM_USER_CONFIG_DIR?.trim() || join3(homedir(), ".config", "kxm"));
8996
+ }
8997
+ function repoConfigDirectory(repoRoot) {
8998
+ return resolve(repoRoot, ".kxm");
8999
+ }
9000
+ function deepMerge(target, source) {
9001
+ const result = { ...target };
9002
+ for (const [key, val] of Object.entries(source)) {
9003
+ if (val && typeof val === "object" && !Array.isArray(val)) {
9004
+ const existing = result[key] && typeof result[key] === "object" && !Array.isArray(result[key]) ? result[key] : {};
9005
+ result[key] = deepMerge(existing, val);
9006
+ } else if (val !== void 0) {
9007
+ result[key] = val;
9008
+ }
9009
+ }
9010
+ return result;
9011
+ }
9012
+ function literalSecretMessage(path4) {
9013
+ return `refusing literal hub token at ${path4}: store an op:// reference (key.op) or an environment variable name (key.env), never the token`;
9014
+ }
9015
+ function rejectLiteralHubSecrets(value, path4) {
9016
+ if (Array.isArray(value)) {
9017
+ value.forEach((item, index) => rejectLiteralHubSecrets(item, `${path4}[${index}]`));
9018
+ return;
9019
+ }
9020
+ if (!value || typeof value !== "object") return;
9021
+ for (const [key, child] of Object.entries(value)) {
9022
+ const childPath = `${path4}.${key}`;
9023
+ if (typeof child === "string" && (key === "key" || LITERAL_SECRET_KEY.test(key))) {
9024
+ throw new KxmHubConfigError(literalSecretMessage(childPath));
9025
+ }
9026
+ if (child && typeof child === "object") rejectLiteralHubSecrets(child, childPath);
9027
+ }
9028
+ }
9029
+ function parseKeyRef(raw, path4) {
9030
+ if (raw === void 0) return void 0;
9031
+ if (typeof raw === "string") throw new KxmHubConfigError(literalSecretMessage(path4));
9032
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
9033
+ throw new KxmHubConfigError(`${path4} must be a key reference with op and/or env, never a token`);
9034
+ }
9035
+ const row = raw;
9036
+ for (const key of Object.keys(row)) {
9037
+ if (key !== "op" && key !== "env") {
9038
+ throw new KxmHubConfigError(literalSecretMessage(`${path4}.${key}`));
9039
+ }
9040
+ }
9041
+ const ref = {};
9042
+ if (row.op !== void 0) {
9043
+ if (typeof row.op !== "string" || !isHubOpReference(row.op)) {
9044
+ throw new KxmHubConfigError(`${path4}.op must be an op://vault/item/field reference, not a token`);
9045
+ }
9046
+ ref.op = row.op;
9047
+ }
9048
+ if (row.env !== void 0) {
9049
+ if (typeof row.env !== "string" || !isHubKeyEnvName(row.env)) {
9050
+ throw new KxmHubConfigError(`${path4}.env must be an environment variable name, not a token`);
9051
+ }
9052
+ ref.env = row.env;
9053
+ }
9054
+ if (!ref.op && !ref.env) throw new KxmHubConfigError(`${path4} needs key.op, key.env, or both`);
9055
+ return ref;
9056
+ }
9057
+ function parseEndpoint(raw, path4) {
9058
+ if (raw === void 0) return void 0;
9059
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
9060
+ throw new KxmHubConfigError(`${path4} must be an object with url, project, and key`);
9061
+ }
9062
+ const row = raw;
9063
+ const endpoint = {};
9064
+ if (row.url !== void 0) {
9065
+ if (typeof row.url !== "string" || !isHubUrl(row.url)) {
9066
+ throw new KxmHubConfigError(`${path4}.url must be an http or https URL without credentials, query, or fragment`);
9067
+ }
9068
+ endpoint.url = row.url.replace(/\/$/, "");
9069
+ }
9070
+ if (row.project !== void 0) {
9071
+ if (typeof row.project !== "string" || !isHubProjectId(row.project)) {
9072
+ throw new KxmHubConfigError(`${path4}.project must be a single-line project id, not a token`);
9073
+ }
9074
+ endpoint.project = row.project.trim();
9075
+ }
9076
+ const key = parseKeyRef(row.key, `${path4}.key`);
9077
+ if (key) endpoint.key = key;
9078
+ for (const name of Object.keys(row)) {
9079
+ if (name !== "url" && name !== "project" && name !== "key") {
9080
+ if (typeof row[name] === "string") throw new KxmHubConfigError(literalSecretMessage(`${path4}.${name}`));
9081
+ throw new KxmHubConfigError(`${path4}.${name} is not a hub endpoint field`);
9082
+ }
9083
+ }
9084
+ return endpoint;
9085
+ }
9086
+ function isHubUrl(raw) {
9271
9087
  try {
9272
- const pkg = JSON.parse(readFileSync3(join4(cwd, "package.json"), "utf8"));
9273
- if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
9088
+ const parsed = new URL(raw);
9089
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && !raw.includes("?") && !raw.includes("#");
9274
9090
  } catch {
9091
+ return false;
9092
+ }
9093
+ }
9094
+ function isHubProjectId(raw) {
9095
+ const value = raw.trim();
9096
+ return value.length > 0 && value.length <= 200 && !/[\s\r\n]/.test(value);
9097
+ }
9098
+ function parseProjectMap(raw) {
9099
+ if (raw === void 0) return void 0;
9100
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
9101
+ throw new KxmHubConfigError("hub.projects must be a map of project id to key reference, never a token");
9102
+ }
9103
+ const projects = {};
9104
+ for (const [project, value] of Object.entries(raw)) {
9105
+ if (!isHubProjectId(project)) {
9106
+ throw new KxmHubConfigError(`hub.projects key ${JSON.stringify(project)} must be a project id, not a token`);
9107
+ }
9108
+ const ref = parseKeyRef(value, `hub.projects.${project}`);
9109
+ if (!ref) throw new KxmHubConfigError(`hub.projects.${project} needs an op:// reference or an environment variable name`);
9110
+ projects[project.trim()] = ref;
9275
9111
  }
9276
- return basename(cwd);
9112
+ return projects;
9277
9113
  }
9278
- function readProjectYamlId(start) {
9279
- let dir = resolve2(start);
9114
+ function normalizeHubConfig(raw) {
9115
+ const autoStart = raw?.autoStart;
9116
+ const hub = {
9117
+ autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart
9118
+ };
9119
+ if (raw === void 0) return hub;
9120
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
9121
+ throw new KxmHubConfigError("hub must be an object");
9122
+ }
9123
+ rejectLiteralHubSecrets(raw, "hub");
9124
+ const row = raw;
9125
+ if (row.mode !== void 0) {
9126
+ if (row.mode !== "local" && row.mode !== "cloud") {
9127
+ throw new KxmHubConfigError("hub.mode must be local or cloud");
9128
+ }
9129
+ hub.mode = row.mode;
9130
+ }
9131
+ const local = parseEndpoint(row.local, "hub.local");
9132
+ const cloud = parseEndpoint(row.cloud, "hub.cloud");
9133
+ const projects = parseProjectMap(row.projects);
9134
+ if (local) hub.local = local;
9135
+ if (cloud) hub.cloud = cloud;
9136
+ if (projects && Object.keys(projects).length > 0) hub.projects = projects;
9137
+ return hub;
9138
+ }
9139
+ function findConfigRoot(start) {
9140
+ let dir = resolve(start);
9280
9141
  for (; ; ) {
9281
- const file = join4(dir, ".kxm", "project.yaml");
9282
- if (existsSync4(file)) {
9142
+ if (existsSync3(join3(dir, ".kxm", "project.yaml")) || existsSync3(join3(dir, ".kxm", "config.yaml"))) return dir;
9143
+ const parent = dirname2(dir);
9144
+ if (parent === dir) return resolve(start);
9145
+ dir = parent;
9146
+ }
9147
+ }
9148
+ function userConfigDirForEnv(env) {
9149
+ const explicit = env.KXM_USER_CONFIG_DIR?.trim();
9150
+ if (explicit) return explicit;
9151
+ if (env === process.env) return void 0;
9152
+ return join3(tmpdir(), "kxm-no-user-config");
9153
+ }
9154
+ function loadHubSettings(start, env = process.env) {
9155
+ const userConfigDir = userConfigDirForEnv(env);
9156
+ return loadKxmConfig(findConfigRoot(start), userConfigDir === void 0 ? {} : { userConfigDir }).hub;
9157
+ }
9158
+ function recordOf(raw) {
9159
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
9160
+ }
9161
+ function finiteNumber(value) {
9162
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9163
+ }
9164
+ function normalizeImprovementConfig(raw) {
9165
+ const value = recordOf(raw);
9166
+ const threshold = recordOf(value.autoThreshold);
9167
+ const policy = value.promotionPolicy;
9168
+ const halfLife = finiteNumber(value.telemetryHalfLifeDays);
9169
+ const minRuns = finiteNumber(threshold.minRuns);
9170
+ const minPassRate = finiteNumber(threshold.minPassRate);
9171
+ const minCostSavings = finiteNumber(threshold.minCostSavings);
9172
+ return {
9173
+ promotionPolicy: IMPROVEMENT_PROMOTION_POLICIES.includes(policy) ? policy : "manual_pr",
9174
+ telemetryHalfLifeDays: halfLife !== void 0 && halfLife > 0 && halfLife <= 3650 ? halfLife : 14,
9175
+ autoThreshold: {
9176
+ minRuns: minRuns !== void 0 && Number.isInteger(minRuns) && minRuns >= 1 && minRuns <= 1e6 ? minRuns : 10,
9177
+ minPassRate: minPassRate !== void 0 && minPassRate >= 0 && minPassRate <= 1 ? minPassRate : 0.95,
9178
+ minCostSavings: minCostSavings !== void 0 && minCostSavings >= 0 ? minCostSavings : 0.5
9179
+ }
9180
+ };
9181
+ }
9182
+ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
9183
+ const userDir = userConfigDirectory(options.userConfigDir);
9184
+ const userConfigFile = join3(userDir, "config.yaml");
9185
+ const repoDir = repoConfigDirectory(repoRoot);
9186
+ const repoConfigFile = join3(repoDir, "config.yaml");
9187
+ let userRaw = {};
9188
+ let userLoadedPath;
9189
+ if (existsSync3(userConfigFile)) {
9190
+ try {
9191
+ const text = readFileSync2(userConfigFile, "utf8");
9192
+ userRaw = (0, import_yaml.parse)(text) ?? {};
9193
+ userLoadedPath = userConfigFile;
9194
+ } catch (error) {
9195
+ throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
9196
+ }
9197
+ }
9198
+ let repoRaw = {};
9199
+ let repoLoadedPath;
9200
+ if (existsSync3(repoConfigFile)) {
9201
+ try {
9202
+ const text = readFileSync2(repoConfigFile, "utf8");
9203
+ repoRaw = (0, import_yaml.parse)(text) ?? {};
9204
+ repoLoadedPath = repoConfigFile;
9205
+ } catch (error) {
9206
+ throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
9207
+ }
9208
+ }
9209
+ const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG));
9210
+ const mergedUser = deepMerge(baseCopy, userRaw);
9211
+ const mergedAll = deepMerge(mergedUser, repoRaw);
9212
+ return {
9213
+ schema: KXM_CONFIG_SCHEMA,
9214
+ user: mergedAll.user ?? {},
9215
+ defaults: mergedAll.defaults ?? {},
9216
+ dash: mergedAll.dash ?? {},
9217
+ sync: mergedAll.sync ?? {},
9218
+ hub: normalizeHubConfig(mergedAll.hub),
9219
+ improvement: normalizeImprovementConfig(mergedAll.improvement),
9220
+ routing: mergedAll.routing ?? DEFAULT_KXM_CONFIG.routing,
9221
+ telemetry: mergedAll.telemetry ?? DEFAULT_KXM_CONFIG.telemetry,
9222
+ loadedFrom: {
9223
+ userConfigPath: userLoadedPath,
9224
+ repoConfigPath: repoLoadedPath
9225
+ }
9226
+ };
9227
+ }
9228
+ function getKxmConfigValue(config, keyPath) {
9229
+ const parts = keyPath.split(".");
9230
+ let current = config;
9231
+ for (const part of parts) {
9232
+ if (!current || typeof current !== "object") return void 0;
9233
+ current = current[part];
9234
+ }
9235
+ return current;
9236
+ }
9237
+ function kxmConfigFileForScope(repoRoot, scope, userConfigDir) {
9238
+ return scope === "user" ? join3(userConfigDirectory(userConfigDir), "config.yaml") : join3(repoConfigDirectory(repoRoot), "config.yaml");
9239
+ }
9240
+ function configKeyParts(keyPath) {
9241
+ const parts = keyPath.split(".");
9242
+ if (parts.some((part) => !part || part === "__proto__" || part === "prototype" || part === "constructor")) {
9243
+ throw new Error("invalid config key path");
9244
+ }
9245
+ return parts;
9246
+ }
9247
+ function readConfigFile(targetFile2) {
9248
+ let existing = {};
9249
+ if (existsSync3(targetFile2)) {
9250
+ try {
9251
+ existing = (0, import_yaml.parse)(readFileSync2(targetFile2, "utf8")) ?? {};
9252
+ } catch {
9253
+ existing = {};
9254
+ }
9255
+ }
9256
+ return existing;
9257
+ }
9258
+ function writeConfigFile(targetFile2, value) {
9259
+ mkdirSync2(dirname2(targetFile2), { recursive: true });
9260
+ writeFileSync2(targetFile2, (0, import_yaml.stringify)(value).trim() + "\n", "utf8");
9261
+ }
9262
+ function setKxmConfigValue(repoRoot, keyPath, value, options = {}) {
9263
+ const targetFile2 = kxmConfigFileForScope(repoRoot, options.scope ?? "project", options.userConfigDir);
9264
+ const existing = readConfigFile(targetFile2);
9265
+ const parts = configKeyParts(keyPath);
9266
+ let cursor2 = existing;
9267
+ for (let i = 0; i < parts.length - 1; i++) {
9268
+ const p = parts[i];
9269
+ if (!cursor2[p] || typeof cursor2[p] !== "object") {
9270
+ cursor2[p] = {};
9271
+ }
9272
+ cursor2 = cursor2[p];
9273
+ }
9274
+ cursor2[parts[parts.length - 1]] = value;
9275
+ if (parts[0] === "hub") normalizeHubConfig(existing.hub);
9276
+ if (!options.dryRun) writeConfigFile(targetFile2, existing);
9277
+ return { file: targetFile2 };
9278
+ }
9279
+ function writeHubEndpoint(start, endpoint) {
9280
+ if (!isHubUrl(endpoint.url)) {
9281
+ throw new KxmHubConfigError("hub url must be an http or https URL without credentials, query, or fragment");
9282
+ }
9283
+ if (!isHubProjectId(endpoint.project)) {
9284
+ throw new KxmHubConfigError("hub project id must be a single-line project id, not a token");
9285
+ }
9286
+ if (endpoint.key) parseKeyRef(endpoint.key, `hub.${endpoint.mode}.key`);
9287
+ const root = findConfigRoot(start);
9288
+ const file = join3(root, ".kxm", "config.yaml");
9289
+ const existing = readConfigFile(file);
9290
+ const hub = recordOf(existing.hub);
9291
+ const current = recordOf(hub[endpoint.mode]);
9292
+ current.url = endpoint.url.replace(/\/$/, "");
9293
+ current.project = endpoint.project.trim();
9294
+ if (endpoint.key && (endpoint.key.op || endpoint.key.env)) {
9295
+ const key = recordOf(current.key);
9296
+ if (endpoint.key.op) key.op = endpoint.key.op;
9297
+ if (endpoint.key.env) key.env = endpoint.key.env;
9298
+ current.key = key;
9299
+ }
9300
+ hub[endpoint.mode] = current;
9301
+ hub.mode = endpoint.mode;
9302
+ existing.hub = hub;
9303
+ normalizeHubConfig(hub);
9304
+ writeConfigFile(file, existing);
9305
+ return { file };
9306
+ }
9307
+ function deleteKxmConfigValue(repoRoot, keyPath, options = {}) {
9308
+ const targetFile2 = kxmConfigFileForScope(repoRoot, options.scope ?? "project", options.userConfigDir);
9309
+ if (!existsSync3(targetFile2)) return false;
9310
+ const existing = readConfigFile(targetFile2);
9311
+ const parts = configKeyParts(keyPath);
9312
+ const parents = [existing];
9313
+ let cursor2 = existing;
9314
+ for (const part of parts.slice(0, -1)) {
9315
+ const next = cursor2[part];
9316
+ if (!next || typeof next !== "object" || Array.isArray(next)) return false;
9317
+ cursor2 = next;
9318
+ parents.push(cursor2);
9319
+ }
9320
+ const leaf = parts[parts.length - 1];
9321
+ if (!Object.prototype.hasOwnProperty.call(cursor2, leaf)) return false;
9322
+ delete cursor2[leaf];
9323
+ for (let i = parents.length - 1; i > 0; i -= 1) {
9324
+ if (Object.keys(parents[i]).length > 0) break;
9325
+ delete parents[i - 1][parts[i - 1]];
9326
+ }
9327
+ writeConfigFile(targetFile2, existing);
9328
+ return true;
9329
+ }
9330
+ function formatKxmConfig(config) {
9331
+ const display = {
9332
+ schema: config.schema,
9333
+ user: config.user,
9334
+ defaults: config.defaults,
9335
+ dash: config.dash,
9336
+ sync: config.sync,
9337
+ loadedFrom: config.loadedFrom
9338
+ };
9339
+ return (0, import_yaml.stringify)(display).trim();
9340
+ }
9341
+ var import_yaml, KXM_CONFIG_SCHEMA, KxmHubConfigError, HUB_ENV_NAME, HUB_OP_REF, LITERAL_SECRET_KEY, IMPROVEMENT_PROMOTION_POLICIES, DEFAULT_KXM_CONFIG;
9342
+ var init_config = __esm({
9343
+ "plugins/kxm/src/config.ts"() {
9344
+ "use strict";
9345
+ import_yaml = __toESM(require_dist(), 1);
9346
+ KXM_CONFIG_SCHEMA = "kxm.config.v1";
9347
+ KxmHubConfigError = class extends Error {
9348
+ code = "hub_config_invalid";
9349
+ constructor(message) {
9350
+ super(message);
9351
+ this.name = "KxmHubConfigError";
9352
+ }
9353
+ };
9354
+ HUB_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
9355
+ HUB_OP_REF = /^op:\/\/\S+\/\S+\/\S+$/;
9356
+ LITERAL_SECRET_KEY = /^(token|secret|password|authToken|auth_token|apiKey|api_key|api_token|bearer|credential|access_token)$/i;
9357
+ IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
9358
+ DEFAULT_KXM_CONFIG = {
9359
+ schema: KXM_CONFIG_SCHEMA,
9360
+ user: {
9361
+ theme: "dark",
9362
+ preferredCritics: ["reviewer-arch", "reviewer-cli"],
9363
+ tokenBudget: 16e3
9364
+ },
9365
+ defaults: {
9366
+ workflow: "software-engineering/feature-implementation",
9367
+ harness: "pi"
9368
+ },
9369
+ dash: {
9370
+ defaultScreen: "agents",
9371
+ refreshIntervalMs: 1e3,
9372
+ autoOpen: false
9373
+ },
9374
+ sync: {
9375
+ defaultTracker: "none"
9376
+ },
9377
+ hub: {
9378
+ autoStart: "background"
9379
+ },
9380
+ improvement: {
9381
+ promotionPolicy: "manual_pr",
9382
+ telemetryHalfLifeDays: 14,
9383
+ autoThreshold: {
9384
+ minRuns: 10,
9385
+ minPassRate: 0.95,
9386
+ minCostSavings: 0.5
9387
+ }
9388
+ },
9389
+ routing: {
9390
+ shadowExecution: {
9391
+ enabled: false,
9392
+ sampleRate: 0.05,
9393
+ candidateModels: []
9394
+ },
9395
+ circuitBreaker: {
9396
+ mode: "soft_demotion",
9397
+ failureThreshold: 3,
9398
+ windowSeconds: 3600,
9399
+ cooldownSeconds: 1800,
9400
+ penaltyMultiplier: 5
9401
+ }
9402
+ },
9403
+ telemetry: {
9404
+ federated: true,
9405
+ anonymize: true
9406
+ }
9407
+ };
9408
+ }
9409
+ });
9410
+
9411
+ // plugins/kxm/src/hub-binding.ts
9412
+ import { spawnSync } from "node:child_process";
9413
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
9414
+ import { homedir as homedir2 } from "node:os";
9415
+ import { dirname as dirname3, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
9416
+ function resolveUserStateRoot(env) {
9417
+ const explicit = env.KXM_STATE_HOME?.trim();
9418
+ if (explicit) {
9419
+ if (!isAbsolute(explicit)) throw new HubBindingError("local_state_root_not_absolute");
9420
+ return resolve2(explicit);
9421
+ }
9422
+ if (process.platform === "win32") {
9423
+ const localAppData = env.LOCALAPPDATA?.trim();
9424
+ const base2 = localAppData && isAbsolute(localAppData) ? localAppData : join4(homedir2(), "AppData", "Local");
9425
+ return resolve2(base2, "KXM");
9426
+ }
9427
+ if (process.platform === "darwin") return resolve2(homedir2(), "Library", "Application Support", "KXM");
9428
+ const xdgState = env.XDG_STATE_HOME?.trim();
9429
+ const base = xdgState && isAbsolute(xdgState) ? xdgState : join4(homedir2(), ".local", "state");
9430
+ return resolve2(base, "kxm");
9431
+ }
9432
+ function hubBindingFile(env = process.env) {
9433
+ return join4(resolveUserStateRoot(env), "hub-binding.json");
9434
+ }
9435
+ function validateHubUrl(raw) {
9436
+ let parsed;
9437
+ try {
9438
+ parsed = new URL(raw);
9439
+ } catch {
9440
+ throw new HubBindingError("hub_url_invalid");
9441
+ }
9442
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || raw.includes("?") || raw.includes("#")) {
9443
+ throw new HubBindingError("hub_url_invalid");
9444
+ }
9445
+ return parsed.href.replace(/\/$/, "");
9446
+ }
9447
+ function splitTokenCommand(raw) {
9448
+ if (/[|&;<>$`\n\r]/.test(raw)) {
9449
+ throw new CloudTokenError(
9450
+ "cloud_token_command_invalid",
9451
+ "token command must be a program and arguments, not a shell pipeline"
9452
+ );
9453
+ }
9454
+ const args = [];
9455
+ let current = "";
9456
+ let quote;
9457
+ for (const ch of raw) {
9458
+ if (quote) {
9459
+ if (ch === quote) quote = void 0;
9460
+ else current += ch;
9461
+ continue;
9462
+ }
9463
+ if (ch === "'" || ch === '"') {
9464
+ quote = ch;
9465
+ continue;
9466
+ }
9467
+ if (ch === " " || ch === " ") {
9468
+ if (current.length > 0) {
9469
+ args.push(current);
9470
+ current = "";
9471
+ }
9472
+ continue;
9473
+ }
9474
+ current += ch;
9475
+ }
9476
+ if (quote) {
9477
+ throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
9478
+ }
9479
+ if (current.length > 0) args.push(current);
9480
+ if (args.length === 0) {
9481
+ throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
9482
+ }
9483
+ return args;
9484
+ }
9485
+ function isCloudTokenEnvName(value) {
9486
+ return TOKEN_ENV_NAME.test(value);
9487
+ }
9488
+ function effectiveHubBindingScope(url, env = process.env) {
9489
+ try {
9490
+ const binding = readHubBinding(env);
9491
+ if (binding?.cloud) {
9283
9492
  try {
9284
- const doc = (0, import_yaml.parse)(readFileSync3(file, "utf8"));
9493
+ if (validateHubUrl(url) === binding.url) return "remote";
9494
+ } catch {
9495
+ }
9496
+ }
9497
+ } catch (error) {
9498
+ if (!(error instanceof HubBindingError)) throw error;
9499
+ }
9500
+ return hubBindingScope(url);
9501
+ }
9502
+ function resolveCloudHubToken(binding, env = process.env) {
9503
+ if (!binding.cloud) {
9504
+ throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
9505
+ }
9506
+ if (binding.tokenEnv) {
9507
+ const value = env[binding.tokenEnv]?.trim();
9508
+ if (value) return value;
9509
+ }
9510
+ if (binding.tokenCommand) return runTokenCommand(binding.tokenCommand, env);
9511
+ const source = binding.tokenEnv ? `${binding.tokenEnv} is unset or empty` : "no token source is configured";
9512
+ throw new CloudTokenError(
9513
+ "cloud_token_missing",
9514
+ `cloud hub token is missing: ${source}; the local hub-env token was not used`
9515
+ );
9516
+ }
9517
+ function runTokenCommand(command, env) {
9518
+ const argv = splitTokenCommand(command);
9519
+ const [program2, ...args] = argv;
9520
+ const result = spawnSync(program2, args, {
9521
+ encoding: "utf8",
9522
+ shell: false,
9523
+ timeout: TOKEN_COMMAND_TIMEOUT_MS,
9524
+ maxBuffer: TOKEN_COMMAND_MAX_BUFFER,
9525
+ env,
9526
+ windowsHide: true
9527
+ });
9528
+ if (result.error || result.status !== 0) {
9529
+ throw new CloudTokenError(
9530
+ "cloud_token_command_failed",
9531
+ "token command failed; the local hub-env token was not used"
9532
+ );
9533
+ }
9534
+ const stdout = result.stdout ?? "";
9535
+ if (stdout.trim().length === 0 || stdout.trim() !== stdout.trim().split(/\r?\n/)[0]) {
9536
+ throw new CloudTokenError(
9537
+ "cloud_token_command_failed",
9538
+ "token command did not print a single token; the local hub-env token was not used"
9539
+ );
9540
+ }
9541
+ return stdout.trim();
9542
+ }
9543
+ function hubBindingScope(url) {
9544
+ let host;
9545
+ try {
9546
+ host = new URL(url).hostname.toLowerCase();
9547
+ } catch {
9548
+ return "remote";
9549
+ }
9550
+ if (host === "localhost" || host === "::1" || host === "[::1]" || host.endsWith(".localhost")) return "loopback";
9551
+ const v4 = /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.exec(host);
9552
+ return v4 && [v4[1], v4[2], v4[3]].every((part) => Number(part) <= 255) ? "loopback" : "remote";
9553
+ }
9554
+ function isIsoTimestamp(value) {
9555
+ if (Number.isNaN(Date.parse(value))) return false;
9556
+ return value === new Date(value).toISOString();
9557
+ }
9558
+ function isAbortError(error) {
9559
+ return Boolean(
9560
+ error && typeof error === "object" && ("name" in error && error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR")
9561
+ );
9562
+ }
9563
+ function readHubBinding(env = process.env) {
9564
+ const file = hubBindingFile(env);
9565
+ if (!existsSync4(file)) return void 0;
9566
+ let parsed;
9567
+ try {
9568
+ parsed = JSON.parse(readFileSync3(file, "utf8"));
9569
+ } catch {
9570
+ throw new HubBindingError(`malformed hub binding at ${file}`);
9571
+ }
9572
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9573
+ throw new HubBindingError(`malformed hub binding at ${file}`);
9574
+ }
9575
+ const row = parsed;
9576
+ const keys = Object.keys(row);
9577
+ const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
9578
+ const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
9579
+ if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
9580
+ throw new HubBindingError(`malformed hub binding at ${file}`);
9581
+ }
9582
+ let url;
9583
+ try {
9584
+ url = validateHubUrl(row.url);
9585
+ } catch {
9586
+ throw new HubBindingError(`malformed hub binding at ${file}`);
9587
+ }
9588
+ if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
9589
+ if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
9590
+ let tokenEnv;
9591
+ let tokenCommand;
9592
+ if (row.tokenEnv !== void 0) {
9593
+ if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
9594
+ throw new HubBindingError(`malformed hub binding at ${file}`);
9595
+ }
9596
+ tokenEnv = row.tokenEnv;
9597
+ }
9598
+ if (row.tokenCommand !== void 0) {
9599
+ if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
9600
+ try {
9601
+ splitTokenCommand(row.tokenCommand);
9602
+ } catch {
9603
+ throw new HubBindingError(`malformed hub binding at ${file}`);
9604
+ }
9605
+ tokenCommand = row.tokenCommand;
9606
+ }
9607
+ if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
9608
+ return {
9609
+ schema: HUB_BINDING_SCHEMA,
9610
+ url,
9611
+ boundAt: row.boundAt,
9612
+ cloud: true,
9613
+ ...tokenEnv ? { tokenEnv } : {},
9614
+ ...tokenCommand ? { tokenCommand } : {}
9615
+ };
9616
+ }
9617
+ function writeHubBinding(record, env = process.env) {
9618
+ const file = hubBindingFile(env);
9619
+ mkdirSync3(dirname3(file), { recursive: true, mode: 448 });
9620
+ const temporary = join4(dirname3(file), `.hub-binding-${process.pid}.tmp`);
9621
+ try {
9622
+ writeFileSync3(temporary, `${JSON.stringify(record, null, 2)}
9623
+ `, { encoding: "utf8", mode: 384 });
9624
+ renameSync(temporary, file);
9625
+ } finally {
9626
+ rmSync(temporary, { force: true });
9627
+ }
9628
+ return file;
9629
+ }
9630
+ function removeHubBinding(env = process.env) {
9631
+ const file = hubBindingFile(env);
9632
+ try {
9633
+ rmSync(file);
9634
+ return true;
9635
+ } catch (error) {
9636
+ if (error.code === "ENOENT") return false;
9637
+ throw error;
9638
+ }
9639
+ }
9640
+ async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
9641
+ const started = Date.now();
9642
+ const controller = new AbortController();
9643
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
9644
+ try {
9645
+ const response = await fetchImpl(`${url}/health`, { signal: controller.signal });
9646
+ if (!response.ok) return { health: "unknown", probeMs: Date.now() - started };
9647
+ let body;
9648
+ try {
9649
+ body = await response.json();
9650
+ } catch {
9651
+ return { health: "unknown", probeMs: Date.now() - started };
9652
+ }
9653
+ if (body && typeof body === "object" && body.ok === true) {
9654
+ return { health: "on", probeMs: Date.now() - started };
9655
+ }
9656
+ return { health: "unknown", probeMs: Date.now() - started };
9657
+ } catch (error) {
9658
+ return { health: isAbortError(error) ? "unknown" : "off", probeMs: Date.now() - started };
9659
+ } finally {
9660
+ clearTimeout(timer);
9661
+ }
9662
+ }
9663
+ var HUB_BINDING_SCHEMA, HUB_HEALTH_PROBE_MS, CloudTokenError, TOKEN_ENV_NAME, TOKEN_COMMAND_TIMEOUT_MS, TOKEN_COMMAND_MAX_BUFFER, HubBindingError;
9664
+ var init_hub_binding = __esm({
9665
+ "plugins/kxm/src/hub-binding.ts"() {
9666
+ "use strict";
9667
+ HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
9668
+ HUB_HEALTH_PROBE_MS = 300;
9669
+ CloudTokenError = class extends Error {
9670
+ code;
9671
+ constructor(code, message) {
9672
+ super(message);
9673
+ this.name = "CloudTokenError";
9674
+ this.code = code;
9675
+ }
9676
+ };
9677
+ TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
9678
+ TOKEN_COMMAND_TIMEOUT_MS = 15e3;
9679
+ TOKEN_COMMAND_MAX_BUFFER = 4096;
9680
+ HubBindingError = class extends Error {
9681
+ constructor(message) {
9682
+ super(message);
9683
+ this.name = "HubBindingError";
9684
+ }
9685
+ };
9686
+ }
9687
+ });
9688
+
9689
+ // plugins/kxm/src/hub-identity.ts
9690
+ import { spawnSync as spawnSync2 } from "node:child_process";
9691
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
9692
+ import { basename, dirname as dirname4, join as join5, resolve as resolve3 } from "node:path";
9693
+ function normalizeUrl(raw) {
9694
+ try {
9695
+ return validateHubUrl(raw);
9696
+ } catch {
9697
+ return void 0;
9698
+ }
9699
+ }
9700
+ function projectYamlId(start) {
9701
+ let dir = resolve3(start);
9702
+ for (; ; ) {
9703
+ const file = join5(dir, ".kxm", "project.yaml");
9704
+ if (existsSync5(file)) {
9705
+ try {
9706
+ const doc = (0, import_yaml2.parse)(readFileSync4(file, "utf8"));
9285
9707
  if (doc && typeof doc === "object" && !Array.isArray(doc) && typeof doc.id === "string" && doc.id.trim()) {
9286
9708
  return doc.id.trim();
9287
9709
  }
@@ -9289,48 +9711,217 @@ function readProjectYamlId(start) {
9289
9711
  }
9290
9712
  return void 0;
9291
9713
  }
9292
- const parent = dirname3(dir);
9714
+ const parent = dirname4(dir);
9293
9715
  if (parent === dir) return void 0;
9294
9716
  dir = parent;
9295
9717
  }
9296
9718
  }
9297
- var import_yaml;
9298
- var init_project_name = __esm({
9299
- "plugins/kxm/src/project-name.ts"() {
9719
+ function packageName(cwd) {
9720
+ try {
9721
+ const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf8"));
9722
+ if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
9723
+ } catch {
9724
+ }
9725
+ return void 0;
9726
+ }
9727
+ function activeBinding(env) {
9728
+ try {
9729
+ return readHubBinding(env);
9730
+ } catch (error) {
9731
+ if (error instanceof HubBindingError) return void 0;
9732
+ throw error;
9733
+ }
9734
+ }
9735
+ function endpointFor(mode, settings) {
9736
+ return mode === "cloud" ? settings.cloud : settings.local;
9737
+ }
9738
+ function resolveProjectIdentity(cwd, env = process.env, explicit) {
9739
+ const fromExplicit = explicit?.trim();
9740
+ if (fromExplicit) return { project: fromExplicit, source: "flag", sourceLabel: "--project" };
9741
+ const fromEnv = env.KXM_PROJECT?.trim();
9742
+ if (fromEnv) return { project: fromEnv, source: "env", sourceLabel: "KXM_PROJECT" };
9743
+ const settings = loadHubSettings(cwd, env);
9744
+ const mode = settings.mode ?? (activeBinding(env)?.cloud ? "cloud" : "local");
9745
+ const fromConfig = endpointFor(mode, settings)?.project?.trim();
9746
+ if (fromConfig) {
9747
+ return { project: fromConfig, source: "config", sourceLabel: `.kxm/config.yaml hub.${mode}.project` };
9748
+ }
9749
+ const fromProject = projectYamlId(cwd);
9750
+ if (fromProject) return { project: fromProject, source: "project.yaml", sourceLabel: ".kxm/project.yaml id" };
9751
+ const fromPackage = packageName(cwd);
9752
+ if (fromPackage) return { project: fromPackage, source: "package.json", sourceLabel: "package.json name" };
9753
+ return { project: basename(cwd), source: "directory", sourceLabel: "directory name" };
9754
+ }
9755
+ function defaultProjectName(cwd, env = process.env, explicit) {
9756
+ return resolveProjectIdentity(cwd, env, explicit).project;
9757
+ }
9758
+ function opReferenceFromTokenCommand(command) {
9759
+ try {
9760
+ const args = splitTokenCommand(command);
9761
+ if (args[0] === "op" && args[1] === "read" && args[2] && isHubOpReference(args[2])) return args[2];
9762
+ } catch {
9763
+ return void 0;
9764
+ }
9765
+ return void 0;
9766
+ }
9767
+ function defaultOpRead(reference, env) {
9768
+ if (!isHubOpReference(reference)) {
9769
+ throw new CloudTokenError("cloud_token_command_invalid", `refusing to resolve ${reference}; an op:// reference is required`);
9770
+ }
9771
+ const result = spawnSync2("op", ["read", reference], {
9772
+ encoding: "utf8",
9773
+ shell: false,
9774
+ timeout: OP_TIMEOUT_MS,
9775
+ maxBuffer: OP_MAX_BUFFER,
9776
+ env,
9777
+ windowsHide: true
9778
+ });
9779
+ if (result.error || result.status !== 0) {
9780
+ throw new CloudTokenError(
9781
+ "cloud_token_command_failed",
9782
+ `op read failed for ${reference}; the token was not saved`
9783
+ );
9784
+ }
9785
+ const line = (result.stdout ?? "").trim();
9786
+ if (!line || line.split(/\r?\n/).length !== 1) {
9787
+ throw new CloudTokenError(
9788
+ "cloud_token_command_failed",
9789
+ `op read did not print a single token for ${reference}; the token was not saved`
9790
+ );
9791
+ }
9792
+ return line;
9793
+ }
9794
+ function resolveKeyReference(ref, env, opRead = defaultOpRead) {
9795
+ if (!ref) return void 0;
9796
+ if (ref.env) {
9797
+ const value = env[ref.env]?.trim();
9798
+ if (value) return { value, source: `env:${ref.env}` };
9799
+ }
9800
+ if (ref.op) {
9801
+ const value = opRead(ref.op, env).trim();
9802
+ if (!value || value.split(/\r?\n/).length !== 1) {
9803
+ throw new CloudTokenError("cloud_token_command_failed", `op read did not print a single token for ${ref.op}`);
9804
+ }
9805
+ return { value, source: `op:${ref.op}` };
9806
+ }
9807
+ return void 0;
9808
+ }
9809
+ function keySourceFor(input) {
9810
+ const ref = input.endpoint?.key;
9811
+ if (input.mode === "local" && input.env.KXM_AUTH_TOKEN?.trim()) return "env:KXM_AUTH_TOKEN";
9812
+ if (ref?.env && input.env[ref.env]?.trim()) return `env:${ref.env}`;
9813
+ if (ref?.op) return `op:${ref.op}`;
9814
+ if (ref?.env) return `env:${ref.env}`;
9815
+ if (input.mode === "cloud") {
9816
+ if (input.binding?.tokenEnv) return `env:${input.binding.tokenEnv}`;
9817
+ if (input.binding?.tokenCommand) {
9818
+ const op = opReferenceFromTokenCommand(input.binding.tokenCommand);
9819
+ return op ? `op:${op}` : "command";
9820
+ }
9821
+ return "missing";
9822
+ }
9823
+ if (input.hasSavedProjectToken?.(input.project)) return "hub-env";
9824
+ return "missing";
9825
+ }
9826
+ function describeHubConnection(cwd, env = process.env, hooks = {}) {
9827
+ const settings = loadHubSettings(cwd, env);
9828
+ const binding = activeBinding(env);
9829
+ const mode = settings.mode ?? (binding?.cloud ? "cloud" : "local");
9830
+ const modeSource = settings.mode ? "config" : binding?.cloud ? "binding" : "default";
9831
+ const endpoint = endpointFor(mode, settings);
9832
+ const fromEnv = env.KXM_SERVER_URL?.trim();
9833
+ const fromConfig = endpoint?.url?.trim();
9834
+ const fromBinding = binding?.url;
9835
+ let url = "http://127.0.0.1:7331";
9836
+ let urlSource = "default";
9837
+ if (fromEnv) {
9838
+ url = fromEnv.replace(/\/$/, "");
9839
+ urlSource = "env";
9840
+ } else if (fromConfig) {
9841
+ url = fromConfig.replace(/\/$/, "");
9842
+ urlSource = "config";
9843
+ } else if (fromBinding) {
9844
+ url = fromBinding;
9845
+ urlSource = "binding";
9846
+ }
9847
+ const identity = resolveProjectIdentity(cwd, env, hooks.explicitProject);
9848
+ const configuredCloudUrl = settings.cloud?.url ?? (binding?.cloud ? binding.url : void 0);
9849
+ let scope;
9850
+ if (mode === "cloud") {
9851
+ const override = fromEnv ? normalizeUrl(fromEnv) : void 0;
9852
+ const named = configuredCloudUrl ? normalizeUrl(configuredCloudUrl) : void 0;
9853
+ scope = override && named && override !== named ? hubBindingScope(url) : "remote";
9854
+ } else {
9855
+ scope = hubBindingScope(url);
9856
+ }
9857
+ return {
9858
+ ...identity,
9859
+ mode,
9860
+ modeSource,
9861
+ url,
9862
+ urlSource,
9863
+ scope,
9864
+ ...endpoint ? { endpoint } : {},
9865
+ ...binding ? { binding } : {},
9866
+ keySource: keySourceFor({
9867
+ mode,
9868
+ env,
9869
+ ...endpoint ? { endpoint } : {},
9870
+ ...binding ? { binding } : {},
9871
+ project: identity.project,
9872
+ ...hooks.hasSavedProjectToken ? { hasSavedProjectToken: hooks.hasSavedProjectToken } : {}
9873
+ })
9874
+ };
9875
+ }
9876
+ function projectTokenMissingMessage(identity) {
9877
+ return [
9878
+ `kxm has no project token for project ${identity.project} (from ${identity.sourceLabel}).`,
9879
+ `Ask the user to set this project's token. Set KXM_AUTH_TOKEN, or set hub.local.project or hub.cloud.project in .kxm/config.yaml to the project key the hub already knows, or add a key reference under hub.projects.${identity.project} (key.op is an op:// reference, key.env is an environment variable name, never the token) and restart the hub.`,
9880
+ "KXM_PROJECT or --project overrides the id for one command.",
9881
+ "An agent never uses the hub admin token."
9882
+ ].join(" ");
9883
+ }
9884
+ var import_yaml2, OP_TIMEOUT_MS, OP_MAX_BUFFER;
9885
+ var init_hub_identity = __esm({
9886
+ "plugins/kxm/src/hub-identity.ts"() {
9300
9887
  "use strict";
9301
- import_yaml = __toESM(require_dist(), 1);
9888
+ import_yaml2 = __toESM(require_dist(), 1);
9889
+ init_config();
9890
+ init_hub_binding();
9891
+ OP_TIMEOUT_MS = 15e3;
9892
+ OP_MAX_BUFFER = 4096;
9302
9893
  }
9303
9894
  });
9304
9895
 
9305
9896
  // plugins/kxm/src/hub-env.ts
9306
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
9307
- import { homedir as homedir2 } from "node:os";
9308
- import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve3 } from "node:path";
9897
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
9898
+ import { homedir as homedir3 } from "node:os";
9899
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "node:path";
9309
9900
  function resolveUserStateRoot2(env) {
9310
9901
  const explicit = env.KXM_STATE_HOME?.trim();
9311
9902
  if (explicit) {
9312
9903
  if (!isAbsolute2(explicit)) throw new HubEnvError("local_state_root_not_absolute");
9313
- return resolve3(explicit);
9904
+ return resolve4(explicit);
9314
9905
  }
9315
9906
  if (process.platform === "win32") {
9316
9907
  const localAppData = env.LOCALAPPDATA?.trim();
9317
- const base2 = localAppData && isAbsolute2(localAppData) ? localAppData : join5(homedir2(), "AppData", "Local");
9318
- return resolve3(base2, "KXM");
9908
+ const base2 = localAppData && isAbsolute2(localAppData) ? localAppData : join6(homedir3(), "AppData", "Local");
9909
+ return resolve4(base2, "KXM");
9319
9910
  }
9320
- if (process.platform === "darwin") return resolve3(homedir2(), "Library", "Application Support", "KXM");
9911
+ if (process.platform === "darwin") return resolve4(homedir3(), "Library", "Application Support", "KXM");
9321
9912
  const xdgState = env.XDG_STATE_HOME?.trim();
9322
- const base = xdgState && isAbsolute2(xdgState) ? xdgState : join5(homedir2(), ".local", "state");
9323
- return resolve3(base, "kxm");
9913
+ const base = xdgState && isAbsolute2(xdgState) ? xdgState : join6(homedir3(), ".local", "state");
9914
+ return resolve4(base, "kxm");
9324
9915
  }
9325
9916
  function hubEnvFile(env = process.env) {
9326
- return join5(resolveUserStateRoot2(env), "hub-env.json");
9917
+ return join6(resolveUserStateRoot2(env), "hub-env.json");
9327
9918
  }
9328
9919
  function readHubEnvRecord(env = process.env) {
9329
9920
  const file = hubEnvFile(env);
9330
- if (!existsSync5(file)) return void 0;
9921
+ if (!existsSync6(file)) return void 0;
9331
9922
  let parsed;
9332
9923
  try {
9333
- parsed = JSON.parse(readFileSync4(file, "utf8"));
9924
+ parsed = JSON.parse(readFileSync5(file, "utf8"));
9334
9925
  } catch (error) {
9335
9926
  throw new HubEnvError(`hub env file is malformed at ${file}: ${error instanceof Error ? error.message : String(error)}`);
9336
9927
  }
@@ -9375,29 +9966,39 @@ function activeCloudBinding(env) {
9375
9966
  throw error;
9376
9967
  }
9377
9968
  }
9378
- function resolveControlPlaneAuthToken(env = process.env) {
9379
- const cloud = activeCloudBinding(env);
9380
- if (cloud) return resolveCloudHubToken(cloud, env);
9969
+ function resolveControlPlaneAuthToken(env = process.env, cwd = process.cwd(), opRead) {
9970
+ const cloud = cloudModeToken(cwd, env, void 0, opRead);
9971
+ if (cloud) return cloud;
9381
9972
  return env.KXM_AUTH_TOKEN?.trim() || void 0;
9382
9973
  }
9383
- function agentSessionEnv(cwd, env = process.env, explicitProject) {
9384
- const project = defaultProjectName(cwd, env, explicitProject);
9385
- const cloud = activeCloudBinding(env);
9386
- let serverUrl = env.KXM_SERVER_URL?.trim();
9387
- if (!serverUrl) {
9388
- try {
9389
- serverUrl = readHubBinding(env)?.url;
9390
- } catch (error) {
9391
- if (!(error instanceof HubBindingError)) throw error;
9392
- }
9974
+ function cloudModeToken(cwd, env, explicitProject, opRead) {
9975
+ const connection = describeHubConnection(cwd, env, explicitProject === void 0 ? {} : { explicitProject });
9976
+ if (connection.mode !== "cloud") return void 0;
9977
+ const fromConfig = resolveKeyReference(connection.endpoint?.key, env, opRead);
9978
+ if (fromConfig) return fromConfig.value;
9979
+ if (connection.binding?.cloud) return resolveCloudHubToken(connection.binding, env);
9980
+ throw new CloudTokenError(
9981
+ "cloud_token_missing",
9982
+ "cloud hub token is missing; the local hub-env token was not used"
9983
+ );
9984
+ }
9985
+ function agentSessionEnv(cwd, env = process.env, explicitProject, opRead) {
9986
+ const connection = describeHubConnection(cwd, env, explicitProject === void 0 ? {} : { explicitProject });
9987
+ const session = { KXM_PROJECT: connection.project };
9988
+ if (connection.url) session.KXM_SERVER_URL = connection.url.replace(/\/$/, "");
9989
+ if (connection.mode === "cloud") {
9990
+ const token = cloudModeToken(cwd, env, explicitProject, opRead);
9991
+ if (token) session.KXM_AUTH_TOKEN = token;
9393
9992
  }
9394
- const session = { KXM_PROJECT: project };
9395
- if (serverUrl) session.KXM_SERVER_URL = serverUrl.replace(/\/$/, "");
9396
- if (cloud) session.KXM_AUTH_TOKEN = resolveCloudHubToken(cloud, env);
9397
9993
  return session;
9398
9994
  }
9399
- function hasClientHubCredential(env = process.env, project) {
9995
+ function hasClientHubCredential(env = process.env, project, cwd = process.cwd()) {
9400
9996
  if (env.KXM_AUTH_TOKEN?.trim()) return true;
9997
+ const settings = loadHubSettings(cwd, env);
9998
+ const mode = settings.mode ?? (activeCloudBinding(env) ? "cloud" : "local");
9999
+ const key = (mode === "cloud" ? settings.cloud : settings.local)?.key;
10000
+ if (key?.op) return true;
10001
+ if (key?.env && env[key.env]?.trim()) return true;
9401
10002
  let record;
9402
10003
  try {
9403
10004
  record = readHubEnvRecord(env);
@@ -9411,26 +10012,30 @@ function hasClientHubCredential(env = process.env, project) {
9411
10012
  if (project !== void 0) return typeof tokens[project] === "string" && tokens[project].trim().length > 0;
9412
10013
  return Object.values(tokens).some((token) => typeof token === "string" && token.trim().length > 0);
9413
10014
  }
9414
- function resolveClientHubAuthToken(env, project) {
9415
- const cloud = activeCloudBinding(env);
9416
- if (cloud) return resolveCloudHubToken(cloud, env);
10015
+ function resolveClientHubAuthToken(env, project, cwd = process.cwd(), opRead) {
10016
+ const cloud = cloudModeToken(cwd, env, void 0, opRead);
10017
+ if (cloud) return cloud;
9417
10018
  const envToken = env.KXM_AUTH_TOKEN?.trim();
9418
10019
  if (envToken) return envToken;
10020
+ const fromConfig = resolveKeyReference(describeHubConnection(cwd, env).endpoint?.key, env, opRead);
10021
+ if (fromConfig) return fromConfig.value;
9419
10022
  const record = readHubEnvRecord(env);
9420
10023
  return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || void 0;
9421
10024
  }
9422
- function resolveAgentHubAuthToken(env, project) {
9423
- const cloud = activeCloudBinding(env);
9424
- if (cloud) return resolveCloudHubToken(cloud, env);
10025
+ function resolveAgentHubAuthToken(env, project, cwd = process.cwd(), opRead) {
10026
+ const cloud = cloudModeToken(cwd, env, void 0, opRead);
10027
+ if (cloud) return cloud;
9425
10028
  const envToken = env.KXM_AUTH_TOKEN?.trim();
9426
10029
  if (envToken) return envToken;
10030
+ const fromConfig = resolveKeyReference(describeHubConnection(cwd, env).endpoint?.key, env, opRead);
10031
+ if (fromConfig) return fromConfig.value;
9427
10032
  const tokens = readHubEnvRecord(env)?.projectTokens;
9428
10033
  if (!tokens || !Object.hasOwn(tokens, project)) return void 0;
9429
10034
  return tokens[project]?.trim() || void 0;
9430
10035
  }
9431
- function resolveClientAdminAuthToken(env = process.env) {
9432
- const cloud = activeCloudBinding(env);
9433
- if (cloud) return resolveCloudHubToken(cloud, env);
10036
+ function resolveClientAdminAuthToken(env = process.env, cwd = process.cwd(), opRead) {
10037
+ const cloud = cloudModeToken(cwd, env, void 0, opRead);
10038
+ if (cloud) return cloud;
9434
10039
  const envToken = env.KXM_AUTH_TOKEN?.trim();
9435
10040
  if (envToken) return envToken;
9436
10041
  return readHubEnvRecord(env)?.authToken?.trim() || void 0;
@@ -9439,8 +10044,9 @@ var HUB_ENV_SCHEMA, HubEnvError, AgentProjectTokenMissingError;
9439
10044
  var init_hub_env = __esm({
9440
10045
  "plugins/kxm/src/hub-env.ts"() {
9441
10046
  "use strict";
10047
+ init_config();
10048
+ init_hub_identity();
9442
10049
  init_hub_binding();
9443
- init_project_name();
9444
10050
  HUB_ENV_SCHEMA = "kxm.hub-env.v1";
9445
10051
  HubEnvError = class extends Error {
9446
10052
  constructor(message) {
@@ -9451,12 +10057,16 @@ var init_hub_env = __esm({
9451
10057
  AgentProjectTokenMissingError = class extends Error {
9452
10058
  code = "project_token_missing";
9453
10059
  project;
9454
- constructor(project) {
9455
- super(
9456
- `kxm has no project token for project ${project} on this machine. Set KXM_AUTH_TOKEN to that project's token, or add ${project} to the hub KXM_PROJECT_TOKENS (list every existing project too, because that variable replaces the saved map). An agent never uses the hub admin token.`
9457
- );
10060
+ projectSource;
10061
+ hint;
10062
+ constructor(projectOrIdentity) {
10063
+ const identity = typeof projectOrIdentity === "string" ? { project: projectOrIdentity, source: "env", sourceLabel: "the project id sent to the hub" } : projectOrIdentity;
10064
+ const message = projectTokenMissingMessage(identity);
10065
+ super(message);
9458
10066
  this.name = "AgentProjectTokenMissingError";
9459
- this.project = project;
10067
+ this.project = identity.project;
10068
+ this.projectSource = typeof projectOrIdentity === "string" ? "unknown" : identity.source;
10069
+ this.hint = message;
9460
10070
  }
9461
10071
  };
9462
10072
  }
@@ -16924,7 +17534,7 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
16924
17534
  fail("parse", "document_too_large", label, `document exceeds ${limits.maxDocumentBytes} bytes`);
16925
17535
  }
16926
17536
  const text = decodeUtf8(input, label);
16927
- const document = (0, import_yaml2.parseDocument)(text, {
17537
+ const document = (0, import_yaml3.parseDocument)(text, {
16928
17538
  customTags: [],
16929
17539
  strict: true,
16930
17540
  uniqueKeys: true
@@ -16937,27 +17547,27 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
16937
17547
  }
16938
17548
  let nodes = 0;
16939
17549
  let keys = 0;
16940
- (0, import_yaml2.visit)(document, (_key, node, path4) => {
17550
+ (0, import_yaml3.visit)(document, (_key, node, path4) => {
16941
17551
  nodes += 1;
16942
17552
  if (nodes > limits.maxTotalNodes) fail("parse", "node_limit", label, `document exceeds ${limits.maxTotalNodes} nodes`);
16943
17553
  if (path4.length > limits.maxDepth) fail("parse", "depth_limit", label, `document exceeds nesting depth ${limits.maxDepth}`);
16944
- if ((0, import_yaml2.isAlias)(node)) fail("parse", "alias_forbidden", label, "aliases are forbidden");
17554
+ if ((0, import_yaml3.isAlias)(node)) fail("parse", "alias_forbidden", label, "aliases are forbidden");
16945
17555
  if (node && typeof node === "object" && "anchor" in node && typeof node.anchor === "string") {
16946
17556
  fail("parse", "anchor_forbidden", label, "anchors are forbidden");
16947
17557
  }
16948
- if ((0, import_yaml2.isCollection)(node) && node.items.length > limits.maxCollectionItems) {
17558
+ if ((0, import_yaml3.isCollection)(node) && node.items.length > limits.maxCollectionItems) {
16949
17559
  fail("parse", "collection_limit", label, `collection exceeds ${limits.maxCollectionItems} items`);
16950
17560
  }
16951
- if ((0, import_yaml2.isMap)(node)) {
17561
+ if ((0, import_yaml3.isMap)(node)) {
16952
17562
  keys += node.items.length;
16953
17563
  if (keys > limits.maxKeys) fail("parse", "key_limit", label, `document exceeds ${limits.maxKeys} mapping keys`);
16954
17564
  for (const pair of node.items) {
16955
- if (!(0, import_yaml2.isScalar)(pair.key) || typeof pair.key.value !== "string") {
17565
+ if (!(0, import_yaml3.isScalar)(pair.key) || typeof pair.key.value !== "string") {
16956
17566
  fail("parse", "non_string_key", label, "mapping keys must be strings");
16957
17567
  }
16958
17568
  }
16959
17569
  }
16960
- if ((0, import_yaml2.isScalar)(node) && typeof node.value === "string" && Buffer.byteLength(node.value, "utf8") > limits.maxScalarBytes) {
17570
+ if ((0, import_yaml3.isScalar)(node) && typeof node.value === "string" && Buffer.byteLength(node.value, "utf8") > limits.maxScalarBytes) {
16961
17571
  fail("parse", "scalar_limit", label, `scalar exceeds ${limits.maxScalarBytes} bytes`);
16962
17572
  }
16963
17573
  if (node && typeof node === "object" && "tag" in node && typeof node.tag === "string" && !ALLOWED_YAML_TAGS.has(node.tag)) {
@@ -16969,11 +17579,11 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
16969
17579
  if (!isJsonObject(value)) fail("parse", "root_not_object", label, "resource root must be a mapping");
16970
17580
  return value;
16971
17581
  }
16972
- var import_yaml2, KXM_YAML_LIMITS, ALLOWED_YAML_TAGS, RestrictedYamlError;
17582
+ var import_yaml3, KXM_YAML_LIMITS, ALLOWED_YAML_TAGS, RestrictedYamlError;
16973
17583
  var init_restricted_yaml = __esm({
16974
17584
  "plugins/kxm/src/restricted-yaml.mjs"() {
16975
17585
  "use strict";
16976
- import_yaml2 = __toESM(require_dist(), 1);
17586
+ import_yaml3 = __toESM(require_dist(), 1);
16977
17587
  KXM_YAML_LIMITS = Object.freeze({
16978
17588
  maxDocumentBytes: 256 * 1024,
16979
17589
  maxDepth: 32,
@@ -17598,8 +18208,8 @@ var init_policy_draft = __esm({
17598
18208
  });
17599
18209
 
17600
18210
  // plugins/kxm/src/workforce-names.mjs
17601
- import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync6 } from "node:fs";
17602
- import { join as join7 } from "node:path";
18211
+ import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7 } from "node:fs";
18212
+ import { join as join8 } from "node:path";
17603
18213
  function noteDeprecatedId(kind, from, to) {
17604
18214
  const key = `${kind}\0${from}\0${to}`;
17605
18215
  if (warned.has(key)) return;
@@ -17650,15 +18260,15 @@ function lookupById(records, requested, kind) {
17650
18260
  }
17651
18261
  function findYamlBasename(directory, requested, kind) {
17652
18262
  if (typeof requested !== "string" || !requested) return void 0;
17653
- if (existsSync7(join7(directory, `${requested}.yaml`))) return requested;
17654
- if (!existsSync7(directory)) return void 0;
18263
+ if (existsSync8(join8(directory, `${requested}.yaml`))) return requested;
18264
+ if (!existsSync8(directory)) return void 0;
17655
18265
  const found = [];
17656
18266
  for (const name of readdirSync(directory)) {
17657
18267
  if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
17658
18268
  const id = name.slice(0, -5);
17659
18269
  let doc;
17660
18270
  try {
17661
- doc = import_yaml3.default.parse(readFileSync6(join7(directory, name), "utf8"));
18271
+ doc = import_yaml4.default.parse(readFileSync7(join8(directory, name), "utf8"));
17662
18272
  } catch {
17663
18273
  continue;
17664
18274
  }
@@ -17669,7 +18279,7 @@ function findYamlBasename(directory, requested, kind) {
17669
18279
  return hit?.record.id;
17670
18280
  }
17671
18281
  function readYamlDir(directory, skipInventory) {
17672
- if (!existsSync7(directory)) return [];
18282
+ if (!existsSync8(directory)) return [];
17673
18283
  const docs = [];
17674
18284
  for (const name of readdirSync(directory)) {
17675
18285
  if (!name.endsWith(".yaml")) continue;
@@ -17677,7 +18287,7 @@ function readYamlDir(directory, skipInventory) {
17677
18287
  if (skipInventory && id === "inventory") continue;
17678
18288
  let doc;
17679
18289
  try {
17680
- doc = import_yaml3.default.parse(readFileSync6(join7(directory, name), "utf8"));
18290
+ doc = import_yaml4.default.parse(readFileSync7(join8(directory, name), "utf8"));
17681
18291
  } catch (error) {
17682
18292
  docs.push({ id, file: `${directory}/${name}`, error: error instanceof Error ? error.message : "invalid YAML" });
17683
18293
  continue;
@@ -17702,18 +18312,18 @@ function selectorsFor(doc) {
17702
18312
  function lintWorkforce(root) {
17703
18313
  const errors = [];
17704
18314
  const warnings = [];
17705
- const models = readYamlDir(join7(root, ".kxm", "models"), true).filter((entry) => !entry.error);
17706
- for (const entry of readYamlDir(join7(root, ".kxm", "models"), true)) {
18315
+ const models = readYamlDir(join8(root, ".kxm", "models"), true).filter((entry) => !entry.error);
18316
+ for (const entry of readYamlDir(join8(root, ".kxm", "models"), true)) {
17707
18317
  if (entry.error) push(errors, "error", "invalid_yaml", `.kxm/models/${entry.id}.yaml`, entry.error);
17708
18318
  }
17709
- const roles = readYamlDir(join7(root, ".kxm", "roles"), false).filter((entry) => !entry.error);
17710
- const agents = readYamlDir(join7(root, ".kxm", "agents"), false).filter((entry) => !entry.error);
17711
- const workflows = readYamlDir(join7(root, ".kxm", "workflows"), false).filter((entry) => !entry.error);
18319
+ const roles = readYamlDir(join8(root, ".kxm", "roles"), false).filter((entry) => !entry.error);
18320
+ const agents = readYamlDir(join8(root, ".kxm", "agents"), false).filter((entry) => !entry.error);
18321
+ const workflows = readYamlDir(join8(root, ".kxm", "workflows"), false).filter((entry) => !entry.error);
17712
18322
  let admitted = [];
17713
- const routesFile = join7(root, ".kxm", "routes.yaml");
17714
- if (existsSync7(routesFile)) {
18323
+ const routesFile = join8(root, ".kxm", "routes.yaml");
18324
+ if (existsSync8(routesFile)) {
17715
18325
  try {
17716
- const policy = import_yaml3.default.parse(readFileSync6(routesFile, "utf8"));
18326
+ const policy = import_yaml4.default.parse(readFileSync7(routesFile, "utf8"));
17717
18327
  admitted = Array.isArray(policy?.admitted) ? policy.admitted.filter((item) => typeof item === "string") : [];
17718
18328
  } catch (error) {
17719
18329
  push(errors, "error", "invalid_yaml", ".kxm/routes.yaml", error instanceof Error ? error.message : "invalid YAML");
@@ -17816,11 +18426,11 @@ function lintWorkforce(root) {
17816
18426
  }
17817
18427
  return { errors, warnings };
17818
18428
  }
17819
- var import_yaml3, ROSTER_EFFORTS, ROLE_IDS, ROUTE_RENAMES, AGENT_RENAMES, WORKFLOW_RENAMES, STEP_RENAMES, RENAMES, RESERVED_WORKFLOW_IDS, warned;
18429
+ var import_yaml4, ROSTER_EFFORTS, ROLE_IDS, ROUTE_RENAMES, AGENT_RENAMES, WORKFLOW_RENAMES, STEP_RENAMES, RENAMES, RESERVED_WORKFLOW_IDS, warned;
17820
18430
  var init_workforce_names = __esm({
17821
18431
  "plugins/kxm/src/workforce-names.mjs"() {
17822
18432
  "use strict";
17823
- import_yaml3 = __toESM(require_dist(), 1);
18433
+ import_yaml4 = __toESM(require_dist(), 1);
17824
18434
  ROSTER_EFFORTS = /* @__PURE__ */ new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
17825
18435
  ROLE_IDS = Object.freeze([
17826
18436
  "writer",
@@ -18095,7 +18705,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
18095
18705
  const values = coreTemplate(projectId, projectName, variant);
18096
18706
  const coreFiles = /* @__PURE__ */ new Map();
18097
18707
  const records = [...values.entries()].map(([path4, value]) => {
18098
- const bytes = Buffer.from((0, import_yaml4.stringify)(value, { lineWidth: 0 }), "utf8");
18708
+ const bytes = Buffer.from((0, import_yaml5.stringify)(value, { lineWidth: 0 }), "utf8");
18099
18709
  coreFiles.set(path4, bytes);
18100
18710
  return {
18101
18711
  path: path4,
@@ -18115,7 +18725,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
18115
18725
  const fileEntries = [...coreFiles.entries()];
18116
18726
  fileEntries.push([
18117
18727
  KXM_TEMPLATE_PROVENANCE_PATH,
18118
- Buffer.from((0, import_yaml4.stringify)(provenance, { lineWidth: 0 }), "utf8")
18728
+ Buffer.from((0, import_yaml5.stringify)(provenance, { lineWidth: 0 }), "utf8")
18119
18729
  ]);
18120
18730
  fileEntries.sort(([left], [right]) => compareCodeUnits3(left, right));
18121
18731
  const files = new Map(fileEntries);
@@ -18137,11 +18747,11 @@ function resolveKxmTemplateBaseline(value) {
18137
18747
  }
18138
18748
  return void 0;
18139
18749
  }
18140
- var import_yaml4, KXM_TEMPLATE_ID, KXM_TEMPLATE_PROVENANCE_PATH, CURRENT_KXM_TEMPLATE_VARIANT, SUPPORTED_KXM_TEMPLATE_VARIANTS;
18750
+ var import_yaml5, KXM_TEMPLATE_ID, KXM_TEMPLATE_PROVENANCE_PATH, CURRENT_KXM_TEMPLATE_VARIANT, SUPPORTED_KXM_TEMPLATE_VARIANTS;
18141
18751
  var init_template = __esm({
18142
18752
  "plugins/kxm/src/template.ts"() {
18143
18753
  "use strict";
18144
- import_yaml4 = __toESM(require_dist(), 1);
18754
+ import_yaml5 = __toESM(require_dist(), 1);
18145
18755
  KXM_TEMPLATE_ID = "builtin-minimal";
18146
18756
  KXM_TEMPLATE_PROVENANCE_PATH = ".kxm/template-provenance.yaml";
18147
18757
  CURRENT_KXM_TEMPLATE_VARIANT = "v4-registry";
@@ -18309,8 +18919,8 @@ var init_oneshot_process = __esm({
18309
18919
  });
18310
18920
 
18311
18921
  // plugins/kxm/src/harness.ts
18312
- import { spawnSync as spawnSync2 } from "node:child_process";
18313
- import { existsSync as existsSync8 } from "node:fs";
18922
+ import { spawnSync as spawnSync3 } from "node:child_process";
18923
+ import { existsSync as existsSync9 } from "node:fs";
18314
18924
  import { win32 as win32Path } from "node:path";
18315
18925
  function reportedModelId(value) {
18316
18926
  return typeof value === "string" && /^[a-z0-9][a-z0-9._:/-]{0,199}$/i.test(value) ? value : void 0;
@@ -18586,7 +19196,7 @@ function findWinNpmInnerExe(cliId, options = {}) {
18586
19196
  if ((options.platform ?? process.platform) !== "win32") return void 0;
18587
19197
  const segments = WIN_NPM_INNER_EXE[cliId];
18588
19198
  if (!segments) return void 0;
18589
- const exists = options.existsSync ?? existsSync8;
19199
+ const exists = options.existsSync ?? existsSync9;
18590
19200
  const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
18591
19201
  for (const dir of pathEnv.split(";").filter(Boolean)) {
18592
19202
  if (!exists(win32Path.join(dir, `${cliId}.cmd`))) continue;
@@ -18598,7 +19208,7 @@ function findWinNpmInnerExe(cliId, options = {}) {
18598
19208
  function defaultRunner(env) {
18599
19209
  return (command, args, timeoutMs) => {
18600
19210
  try {
18601
- const result = spawnSync2(command, [...args], {
19211
+ const result = spawnSync3(command, [...args], {
18602
19212
  encoding: "utf8",
18603
19213
  timeout: timeoutMs,
18604
19214
  windowsHide: true,
@@ -19332,13 +19942,13 @@ var init_harness = __esm({
19332
19942
  });
19333
19943
 
19334
19944
  // plugins/kxm/src/project-config.ts
19335
- import { spawnSync as spawnSync3 } from "node:child_process";
19945
+ import { spawnSync as spawnSync4 } from "node:child_process";
19336
19946
  import { createHash as createHash7 } from "node:crypto";
19337
- import { existsSync as existsSync9, lstatSync, readFileSync as readFileSync7, readdirSync as readdirSync2, realpathSync } from "node:fs";
19338
- import { basename as basename2, dirname as dirname6, extname, isAbsolute as isAbsolute3, join as join8, relative, resolve as resolve5, sep } from "node:path";
19947
+ import { existsSync as existsSync10, lstatSync, readFileSync as readFileSync8, readdirSync as readdirSync2, realpathSync } from "node:fs";
19948
+ import { basename as basename2, dirname as dirname7, extname, isAbsolute as isAbsolute3, join as join9, relative, resolve as resolve6, sep } from "node:path";
19339
19949
  import { pathToFileURL } from "node:url";
19340
19950
  function defaultKxmSchemaDir() {
19341
- return join8(findKxmRepoRoot(import.meta.url), "schemas");
19951
+ return join9(findKxmRepoRoot(import.meta.url), "schemas");
19342
19952
  }
19343
19953
  function compareCodeUnits4(left, right) {
19344
19954
  return left < right ? -1 : left > right ? 1 : 0;
@@ -19364,7 +19974,7 @@ function parseRestrictedYaml2(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
19364
19974
  }
19365
19975
  }
19366
19976
  function readJsonObject(file) {
19367
- const parsed = JSON.parse(readFileSync7(file, "utf8"));
19977
+ const parsed = JSON.parse(readFileSync8(file, "utf8"));
19368
19978
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${file} is not a JSON object`);
19369
19979
  return parsed;
19370
19980
  }
@@ -19437,7 +20047,7 @@ function schemaIssue(file, error) {
19437
20047
  return issue3("schema", `schema_${error.keyword}`, file, `${location} ${error.message ?? "is invalid"}${suffix}`);
19438
20048
  }
19439
20049
  function canonicalHostPath(path4) {
19440
- const resolved = resolve5(path4);
20050
+ const resolved = resolve6(path4);
19441
20051
  try {
19442
20052
  return realpathSync.native(resolved);
19443
20053
  } catch {
@@ -19456,15 +20066,15 @@ function containedHostPath(root, candidate) {
19456
20066
  function portableBindingIssue(root, pathHint, repositoryId) {
19457
20067
  let current = root;
19458
20068
  for (const segment of pathHint === "." ? [] : pathHint.split("/")) {
19459
- current = join8(current, segment);
19460
- if (!existsSync9(current)) break;
20069
+ current = join9(current, segment);
20070
+ if (!existsSync10(current)) break;
19461
20071
  const stat = lstatSync(current);
19462
20072
  if (stat.isSymbolicLink()) {
19463
20073
  return issue3("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
19464
20074
  }
19465
20075
  }
19466
- const binding = resolve5(root, ...pathHint.split("/"));
19467
- if (existsSync9(binding)) {
20076
+ const binding = resolve6(root, ...pathHint.split("/"));
20077
+ if (existsSync10(binding)) {
19468
20078
  const realRoot = canonicalHostPath(root);
19469
20079
  const realBinding = canonicalHostPath(binding);
19470
20080
  if (!containedHostPath(realRoot, realBinding)) {
@@ -19501,10 +20111,10 @@ function readResource(registry, root, file, logicalPath, kind, id, containmentRo
19501
20111
  fail2("discovery", "resource_missing", label, "resource does not exist");
19502
20112
  }
19503
20113
  if (stat.isSymbolicLink()) fail2("path", "resource_symlink", label, "configuration resources must not be symbolic links");
19504
- const boundary = resolve5(containmentRoot);
19505
- let parent = dirname6(file);
20114
+ const boundary = resolve6(containmentRoot);
20115
+ let parent = dirname7(file);
19506
20116
  while (parent !== boundary) {
19507
- if (parent === dirname6(parent)) fail2("path", "resource_outside_project", label, "resource escapes the project root");
20117
+ if (parent === dirname7(parent)) fail2("path", "resource_outside_project", label, "resource escapes the project root");
19508
20118
  let parentStat;
19509
20119
  try {
19510
20120
  parentStat = lstatSync(parent);
@@ -19512,10 +20122,10 @@ function readResource(registry, root, file, logicalPath, kind, id, containmentRo
19512
20122
  fail2("discovery", "resource_parent_missing", label, "resource parent does not exist");
19513
20123
  }
19514
20124
  if (parentStat.isSymbolicLink()) fail2("path", "resource_parent_symlink", label, "configuration resource parents must not be symbolic links");
19515
- parent = dirname6(parent);
20125
+ parent = dirname7(parent);
19516
20126
  }
19517
20127
  if (!stat.isFile()) fail2("path", "resource_not_file", label, "configuration resource must be a regular file");
19518
- return { kind, ...id === void 0 ? {} : { id }, file, logicalPath, value: resourceValue(registry, readFileSync7(file), label, kind) };
20128
+ return { kind, ...id === void 0 ? {} : { id }, file, logicalPath, value: resourceValue(registry, readFileSync8(file), label, kind) };
19519
20129
  }
19520
20130
  function resourceValue(registry, input, label, kind) {
19521
20131
  const value = parseRestrictedYaml2(input, label);
@@ -19524,24 +20134,24 @@ function resourceValue(registry, input, label, kind) {
19524
20134
  return value;
19525
20135
  }
19526
20136
  function readTemplateProvenance(registry, root) {
19527
- const file = join8(root, ".kxm", "template-provenance.yaml");
19528
- if (!existsSync9(file)) return void 0;
20137
+ const file = join9(root, ".kxm", "template-provenance.yaml");
20138
+ if (!existsSync10(file)) return void 0;
19529
20139
  const label = ".kxm/template-provenance.yaml";
19530
20140
  const stat = lstatSync(file);
19531
20141
  if (stat.isSymbolicLink() || !stat.isFile()) {
19532
20142
  fail2("path", "resource_not_file", label, "template provenance must be a regular file, not a link or directory");
19533
20143
  }
19534
- const configRoot = join8(root, ".kxm");
19535
- let parent = dirname6(file);
20144
+ const configRoot = join9(root, ".kxm");
20145
+ let parent = dirname7(file);
19536
20146
  while (parent !== root) {
19537
20147
  const parentStat = lstatSync(parent);
19538
20148
  if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) {
19539
20149
  fail2("path", "resource_parent_symlink", label, "template provenance parents must be regular directories");
19540
20150
  }
19541
20151
  if (parent === configRoot) break;
19542
- parent = dirname6(parent);
20152
+ parent = dirname7(parent);
19543
20153
  }
19544
- const value = parseRestrictedYaml2(readFileSync7(file), label);
20154
+ const value = parseRestrictedYaml2(readFileSync8(file), label);
19545
20155
  const issues = registry.validateTemplateProvenance(value, label);
19546
20156
  if (issues.length > 0) throw new KxmConfigError(issues);
19547
20157
  if (!resolveKxmTemplateBaseline(value)) {
@@ -19572,7 +20182,7 @@ function readTemplateProvenance(registry, root) {
19572
20182
  }
19573
20183
  function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
19574
20184
  const resources = /* @__PURE__ */ new Map();
19575
- if (!existsSync9(directory)) return resources;
20185
+ if (!existsSync10(directory)) return resources;
19576
20186
  const stat = lstatSync(directory);
19577
20187
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
19578
20188
  fail2("path", "resource_directory_invalid", displayPath(root, directory), "resource directory must be a regular directory");
@@ -19581,28 +20191,28 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
19581
20191
  const entries = readdirSync2(directory, { withFileTypes: true }).sort((left, right) => compareCodeUnits4(left.name, right.name));
19582
20192
  for (const entry of entries) {
19583
20193
  if (entry.isDirectory()) {
19584
- issues.push(issue3("path", "nested_resource_directory", displayPath(root, join8(directory, entry.name)), "nested resource directories are not allowed"));
20194
+ issues.push(issue3("path", "nested_resource_directory", displayPath(root, join9(directory, entry.name)), "nested resource directories are not allowed"));
19585
20195
  continue;
19586
20196
  }
19587
20197
  if (!entry.name.toLowerCase().endsWith(".yaml") && !entry.name.toLowerCase().endsWith(".yml")) continue;
19588
20198
  if (!entry.isFile() || extname(entry.name) !== ".yaml") {
19589
- issues.push(issue3("path", "resource_filename_invalid", displayPath(root, join8(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
20199
+ issues.push(issue3("path", "resource_filename_invalid", displayPath(root, join9(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
19590
20200
  continue;
19591
20201
  }
19592
20202
  const id = basename2(entry.name, ".yaml");
19593
20203
  if (kind === "model" && id === "inventory") continue;
19594
20204
  if (id === replacedId) continue;
19595
20205
  if (!resourceIdentifier(id)) {
19596
- issues.push(issue3("path", "resource_id_invalid", displayPath(root, join8(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
20206
+ issues.push(issue3("path", "resource_id_invalid", displayPath(root, join9(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
19597
20207
  continue;
19598
20208
  }
19599
20209
  const collision = [...resources.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
19600
20210
  if (collision) {
19601
- issues.push(issue3("path", "resource_id_collision", displayPath(root, join8(directory, entry.name)), `${id} case-folds to existing ${collision}`));
20211
+ issues.push(issue3("path", "resource_id_collision", displayPath(root, join9(directory, entry.name)), `${id} case-folds to existing ${collision}`));
19602
20212
  continue;
19603
20213
  }
19604
20214
  try {
19605
- const resource = readResource(registry, root, join8(directory, entry.name), `${logicalDirectory}/${id}.yaml`, kind, id);
20215
+ const resource = readResource(registry, root, join9(directory, entry.name), `${logicalDirectory}/${id}.yaml`, kind, id);
19606
20216
  resources.set(id, resource);
19607
20217
  } catch (error) {
19608
20218
  if (error instanceof KxmConfigError) issues.push(...error.issues);
@@ -20143,10 +20753,10 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
20143
20753
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
20144
20754
  if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
20145
20755
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
20146
- if (projectRoot && existsSync9(join8(projectRoot, ".kxm", "roster.yaml"))) {
20756
+ if (projectRoot && existsSync10(join9(projectRoot, ".kxm", "roster.yaml"))) {
20147
20757
  issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
20148
20758
  }
20149
- if (projectRoot && existsSync9(join8(projectRoot, ".kxm", "roles"))) {
20759
+ if (projectRoot && existsSync10(join9(projectRoot, ".kxm", "roles"))) {
20150
20760
  issues.push(...developerRolePolicyIssues(projectRoot));
20151
20761
  }
20152
20762
  if (projectRoot) {
@@ -20161,12 +20771,12 @@ function rosterModelIssues(projectRoot, roles) {
20161
20771
  for (const entry of valuesOf(role.value, "roster")) {
20162
20772
  const route = stringValue(objectValue(entry)?.route);
20163
20773
  if (!route) continue;
20164
- const resolved = findYamlBasename(join8(projectRoot, ".kxm", "models"), route, "route") ?? route;
20774
+ const resolved = findYamlBasename(join9(projectRoot, ".kxm", "models"), route, "route") ?? route;
20165
20775
  const logical = `.kxm/models/${resolved}.yaml`;
20166
- const file = join8(projectRoot, ".kxm", "models", `${resolved}.yaml`);
20776
+ const file = join9(projectRoot, ".kxm", "models", `${resolved}.yaml`);
20167
20777
  let harness;
20168
20778
  try {
20169
- if (existsSync9(file)) harness = stringValue(parseRestrictedYaml2(readFileSync7(file, "utf8"), logical).harness);
20779
+ if (existsSync10(file)) harness = stringValue(parseRestrictedYaml2(readFileSync8(file, "utf8"), logical).harness);
20170
20780
  } catch {
20171
20781
  harness = void 0;
20172
20782
  }
@@ -20184,7 +20794,7 @@ function originHashIssues(projectRoot, models) {
20184
20794
  const source = origin ? stringValue(origin.source) : void 0;
20185
20795
  const digest = origin ? stringValue(origin.sha256) : void 0;
20186
20796
  if (!source || !digest) continue;
20187
- const file = resolve5(projectRoot, source);
20797
+ const file = resolve6(projectRoot, source);
20188
20798
  const escaped = relative(projectRoot, file);
20189
20799
  if (isAbsolute3(source) || escaped.startsWith("..") || isAbsolute3(escaped)) {
20190
20800
  issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
@@ -20192,7 +20802,7 @@ function originHashIssues(projectRoot, models) {
20192
20802
  }
20193
20803
  let actual;
20194
20804
  try {
20195
- actual = createHash7("sha256").update(readFileSync7(file, "utf8"), "utf8").digest("hex");
20805
+ actual = createHash7("sha256").update(readFileSync8(file, "utf8"), "utf8").digest("hex");
20196
20806
  } catch (error) {
20197
20807
  const code = error.code;
20198
20808
  if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
@@ -20208,10 +20818,10 @@ function originHashIssues(projectRoot, models) {
20208
20818
  return issues;
20209
20819
  }
20210
20820
  function developerCeilings() {
20211
- const script = join8(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
20821
+ const script = join9(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
20212
20822
  let loaded;
20213
20823
  try {
20214
- loaded = spawnSync3(process.execPath, ["--input-type=module", "-e", `const m = await import(${JSON.stringify(pathToFileURL(script).href)}); process.stdout.write(JSON.stringify({ROUTES:m.ROUTES,NATIVE_PI_BRAKE_PROVIDERS:m.NATIVE_PI_BRAKE_PROVIDERS,PI_ALLOWED_PROVIDERS:m.PI_ALLOWED_PROVIDERS,PI_NATIVE_VENDOR_PROVIDERS:m.PI_NATIVE_VENDOR_PROVIDERS}))`], {
20824
+ loaded = spawnSync4(process.execPath, ["--input-type=module", "-e", `const m = await import(${JSON.stringify(pathToFileURL(script).href)}); process.stdout.write(JSON.stringify({ROUTES:m.ROUTES,NATIVE_PI_BRAKE_PROVIDERS:m.NATIVE_PI_BRAKE_PROVIDERS,PI_ALLOWED_PROVIDERS:m.PI_ALLOWED_PROVIDERS,PI_NATIVE_VENDOR_PROVIDERS:m.PI_NATIVE_VENDOR_PROVIDERS}))`], {
20215
20825
  encoding: "utf8",
20216
20826
  timeout: 15e3
20217
20827
  });
@@ -20234,22 +20844,22 @@ function developerCeilings() {
20234
20844
  }
20235
20845
  }
20236
20846
  function developerRolePolicyIssues(projectRoot) {
20237
- if (resolve5(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
20847
+ if (resolve6(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
20238
20848
  const lintIssues = lintWorkforce(projectRoot).errors.map((entry) => issue3("semantic", entry.code, entry.file, entry.message));
20239
- const rolesDir = join8(projectRoot, ".kxm", "roles");
20240
- const modelsDir = join8(projectRoot, ".kxm", "models");
20849
+ const rolesDir = join9(projectRoot, ".kxm", "roles");
20850
+ const modelsDir = join9(projectRoot, ".kxm", "models");
20241
20851
  const roles = {};
20242
20852
  const models = {};
20243
20853
  const evidence = {};
20244
20854
  const parseIssues = [];
20245
20855
  const readMap = (dir, into, skipInventory) => {
20246
- if (!existsSync9(dir)) return;
20856
+ if (!existsSync10(dir)) return;
20247
20857
  for (const name of readdirSync2(dir)) {
20248
20858
  if (!name.endsWith(".yaml")) continue;
20249
20859
  const id = basename2(name, ".yaml");
20250
20860
  if (skipInventory && id === "inventory") continue;
20251
20861
  try {
20252
- const value = parseRestrictedYaml2(readFileSync7(join8(dir, name), "utf8"), `${dir}/${name}`);
20862
+ const value = parseRestrictedYaml2(readFileSync8(join9(dir, name), "utf8"), `${dir}/${name}`);
20253
20863
  into[id] = value;
20254
20864
  } catch (error) {
20255
20865
  if (error instanceof KxmConfigError) {
@@ -20266,8 +20876,8 @@ function developerRolePolicyIssues(projectRoot) {
20266
20876
  const origin = objectValue(model.origin);
20267
20877
  const source = origin ? stringValue(origin.source) : void 0;
20268
20878
  if (!source || evidence[source] !== void 0) continue;
20269
- const evidenceFile = isAbsolute3(source) ? source : join8(projectRoot, source);
20270
- if (existsSync9(evidenceFile)) evidence[source] = readFileSync7(evidenceFile, "utf8");
20879
+ const evidenceFile = isAbsolute3(source) ? source : join9(projectRoot, source);
20880
+ if (existsSync10(evidenceFile)) evidence[source] = readFileSync8(evidenceFile, "utf8");
20271
20881
  }
20272
20882
  const ceilings = developerCeilings();
20273
20883
  if (!ceilings.ok) {
@@ -20305,21 +20915,21 @@ function gitEnvironment() {
20305
20915
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
20306
20916
  }
20307
20917
  function discoverGitRoot(start = process.cwd()) {
20308
- let current = resolve5(start);
20309
- if (existsSync9(current) && !lstatSync(current).isDirectory()) current = dirname6(current);
20310
- const result = spawnSync3("git", ["-C", current, "rev-parse", "--show-toplevel"], {
20918
+ let current = resolve6(start);
20919
+ if (existsSync10(current) && !lstatSync(current).isDirectory()) current = dirname7(current);
20920
+ const result = spawnSync4("git", ["-C", current, "rev-parse", "--show-toplevel"], {
20311
20921
  encoding: "utf8",
20312
20922
  env: gitEnvironment(),
20313
20923
  timeout: 5e3,
20314
20924
  windowsHide: true
20315
20925
  });
20316
20926
  if (result.status !== 0 || result.error || !result.stdout.trim()) return void 0;
20317
- const root = resolve5(result.stdout.trim());
20318
- return existsSync9(root) && lstatSync(root).isDirectory() ? root : void 0;
20927
+ const root = resolve6(result.stdout.trim());
20928
+ return existsSync10(root) && lstatSync(root).isDirectory() ? root : void 0;
20319
20929
  }
20320
20930
  function discoverKxmProjectRoot(start = process.cwd()) {
20321
20931
  const gitRoot = discoverGitRoot(start);
20322
- return gitRoot && existsSync9(join8(gitRoot, ".kxm", "project.yaml")) ? gitRoot : void 0;
20932
+ return gitRoot && existsSync10(join9(gitRoot, ".kxm", "project.yaml")) ? gitRoot : void 0;
20323
20933
  }
20324
20934
  function assertNoRegisteredGates(options) {
20325
20935
  if ("registeredGates" in options) {
@@ -20348,7 +20958,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
20348
20958
  const workflowCandidate = candidate?.kind === "workflow" ? candidate : void 0;
20349
20959
  const writerRole = candidate?.kind === "role" && candidate.id.toLocaleLowerCase("en-US") === "writer" ? candidate.document : void 0;
20350
20960
  assertNoRegisteredGates(options);
20351
- const root = resolve5(projectRoot);
20961
+ const root = resolve6(projectRoot);
20352
20962
  const legacyPresent = legacyConfigFilesAt(root);
20353
20963
  if (legacyPresent.length > 0) {
20354
20964
  throw new KxmConfigError(legacyPresent.map((file) => issue3(
@@ -20359,7 +20969,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
20359
20969
  )));
20360
20970
  }
20361
20971
  const registry = new KxmSchemaRegistry(options.schemasDir);
20362
- const project = readResource(registry, root, join8(root, ".kxm", "project.yaml"), ".kxm/project.yaml", "project");
20972
+ const project = readResource(registry, root, join9(root, ".kxm", "project.yaml"), ".kxm/project.yaml", "project");
20363
20973
  const earlyIssues = [];
20364
20974
  validatePortablePaths(project, earlyIssues);
20365
20975
  const templateProvenance = readTemplateProvenance(registry, root);
@@ -20378,30 +20988,30 @@ function loadProjectBundle(projectRoot, options, candidate) {
20378
20988
  }
20379
20989
  }
20380
20990
  if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
20381
- const agents = listNamedResources(registry, root, join8(root, ".kxm", "agents"), ".kxm/agents", "agent");
20991
+ const agents = listNamedResources(registry, root, join9(root, ".kxm", "agents"), ".kxm/agents", "agent");
20382
20992
  const modelIssues = [];
20383
- const models = listNamedResources(registry, root, join8(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
20384
- const roles = listNamedResources(registry, root, join8(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
20993
+ const models = listNamedResources(registry, root, join9(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
20994
+ const roles = listNamedResources(registry, root, join9(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
20385
20995
  if (writerRole !== void 0) {
20386
20996
  const logicalPath = ".kxm/roles/writer.yaml";
20387
- roles.set("writer", { kind: "role", id: "writer", file: join8(root, logicalPath), logicalPath, value: resourceValue(registry, writerRole, logicalPath, "role") });
20997
+ roles.set("writer", { kind: "role", id: "writer", file: join9(root, logicalPath), logicalPath, value: resourceValue(registry, writerRole, logicalPath, "role") });
20388
20998
  }
20389
- const workflows = listNamedResources(registry, root, join8(root, ".kxm", "workflows"), ".kxm/workflows", "workflow", workflowCandidate?.id);
20999
+ const workflows = listNamedResources(registry, root, join9(root, ".kxm", "workflows"), ".kxm/workflows", "workflow", workflowCandidate?.id);
20390
21000
  if (workflowCandidate) {
20391
21001
  const { id, document } = workflowCandidate;
20392
21002
  const logicalPath = `.kxm/workflows/${id}.yaml`;
20393
21003
  if (!resourceIdentifier(id)) fail2("path", "resource_id_invalid", logicalPath, `filename-derived identity ${id} is invalid or platform-reserved`);
20394
21004
  const collision = [...workflows.keys()].find((candidate2) => candidate2.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
20395
21005
  if (collision) fail2("path", "resource_id_collision", logicalPath, `${id} case-folds to existing ${collision}`);
20396
- workflows.set(id, { kind: "workflow", id, file: join8(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
21006
+ workflows.set(id, { kind: "workflow", id, file: join9(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
20397
21007
  }
20398
- const gatePath = join8(root, ".kxm", "gates.yaml");
20399
- const gateRegistry = existsSync9(gatePath) ? readResource(registry, root, gatePath, ".kxm/gates.yaml", "gate-registry") : void 0;
21008
+ const gatePath = join9(root, ".kxm", "gates.yaml");
21009
+ const gateRegistry = existsSync10(gatePath) ? readResource(registry, root, gatePath, ".kxm/gates.yaml", "gate-registry") : void 0;
20400
21010
  const environments = [];
20401
21011
  const repositories = /* @__PURE__ */ new Map();
20402
21012
  const loadIssues = [];
20403
- const projectEnvironment = join8(root, ".kxm", "project", "env.yaml");
20404
- if (existsSync9(projectEnvironment)) {
21013
+ const projectEnvironment = join9(root, ".kxm", "project", "env.yaml");
21014
+ if (existsSync10(projectEnvironment)) {
20405
21015
  try {
20406
21016
  environments.push(readResource(registry, root, projectEnvironment, ".kxm/project/env.yaml", "environment"));
20407
21017
  } catch (error) {
@@ -20438,12 +21048,12 @@ function loadProjectBundle(projectRoot, options, candidate) {
20438
21048
  continue;
20439
21049
  }
20440
21050
  }
20441
- const binding = hasLocalBinding && localBinding ? resolve5(localBinding) : role === "control" ? root : pathHint && portablePath(pathHint) ? resolve5(root, ...pathHint.split("/")) : void 0;
21051
+ const binding = hasLocalBinding && localBinding ? resolve6(localBinding) : role === "control" ? root : pathHint && portablePath(pathHint) ? resolve6(root, ...pathHint.split("/")) : void 0;
20442
21052
  if (!binding) {
20443
21053
  if (required2) loadIssues.push(issue3("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
20444
21054
  continue;
20445
21055
  }
20446
- if (!existsSync9(binding)) {
21056
+ if (!existsSync10(binding)) {
20447
21057
  if (required2 || hasLocalBinding) loadIssues.push(issue3("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
20448
21058
  continue;
20449
21059
  }
@@ -20475,9 +21085,9 @@ function loadProjectBundle(projectRoot, options, candidate) {
20475
21085
  continue;
20476
21086
  }
20477
21087
  seenBindings.set(foldedBinding, repositoryId);
20478
- const repositoryFile = join8(binding, ".kxm", "repo", "repo.yaml");
21088
+ const repositoryFile = join9(binding, ".kxm", "repo", "repo.yaml");
20479
21089
  let definitionValidated = false;
20480
- if (existsSync9(repositoryFile)) {
21090
+ if (existsSync10(repositoryFile)) {
20481
21091
  try {
20482
21092
  const resource = readResource(registry, root, repositoryFile, `.kxm/repositories/${repositoryId}/repo.yaml`, "repository", repositoryId, binding);
20483
21093
  if (resource.value.repositoryId !== repositoryId) {
@@ -20493,8 +21103,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
20493
21103
  } else if (required2 || hasLocalBinding) {
20494
21104
  loadIssues.push(issue3("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
20495
21105
  }
20496
- const environmentFile = join8(binding, ".kxm", "repo", "env.yaml");
20497
- if (existsSync9(environmentFile)) {
21106
+ const environmentFile = join9(binding, ".kxm", "repo", "env.yaml");
21107
+ if (existsSync10(environmentFile)) {
20498
21108
  if (!definitionValidated) {
20499
21109
  loadIssues.push(issue3("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
20500
21110
  } else {
@@ -20533,19 +21143,19 @@ function legacyInputsAt(root) {
20533
21143
  ".kxm/config/workflows",
20534
21144
  ".kxm/state/kxm.db"
20535
21145
  ];
20536
- return candidates.filter((candidate) => existsSync9(join8(root, ...candidate.split("/"))));
21146
+ return candidates.filter((candidate) => existsSync10(join9(root, ...candidate.split("/"))));
20537
21147
  }
20538
21148
  function legacyConfigFilesAt(root) {
20539
21149
  const files = [];
20540
21150
  for (const component of [".kxm", ".kxm/config"]) {
20541
- const absolute = join8(root, ...component.split("/"));
20542
- if (existsSync9(absolute) && lstatSync(absolute).isSymbolicLink()) return [component];
21151
+ const absolute = join9(root, ...component.split("/"));
21152
+ if (existsSync10(absolute) && lstatSync(absolute).isSymbolicLink()) return [component];
20543
21153
  }
20544
21154
  for (const candidate of LEGACY_CONFIG_FILES) {
20545
- if (existsSync9(join8(root, ...candidate.split("/")))) files.push(candidate);
21155
+ if (existsSync10(join9(root, ...candidate.split("/")))) files.push(candidate);
20546
21156
  }
20547
- const workflowsDir = join8(root, ".kxm", "config", "workflows");
20548
- if (existsSync9(workflowsDir)) {
21157
+ const workflowsDir = join9(root, ".kxm", "config", "workflows");
21158
+ if (existsSync10(workflowsDir)) {
20549
21159
  const stat = lstatSync(workflowsDir);
20550
21160
  if (stat.isSymbolicLink()) {
20551
21161
  files.push(".kxm/config/workflows");
@@ -20560,14 +21170,14 @@ function legacyConfigFilesAt(root) {
20560
21170
  return files;
20561
21171
  }
20562
21172
  function discoverLegacyRoot(start) {
20563
- let initial = resolve5(start);
20564
- if (existsSync9(initial) && !lstatSync(initial).isDirectory()) initial = dirname6(initial);
21173
+ let initial = resolve6(start);
21174
+ if (existsSync10(initial) && !lstatSync(initial).isDirectory()) initial = dirname7(initial);
20565
21175
  const candidate = discoverGitRoot(initial) ?? initial;
20566
21176
  const inputs = legacyInputsAt(candidate);
20567
21177
  return inputs.length > 0 ? { root: candidate, inputs } : void 0;
20568
21178
  }
20569
21179
  function planKxmInitialization(start = process.cwd(), options = {}) {
20570
- const inspectedFrom = resolve5(start);
21180
+ const inspectedFrom = resolve6(start);
20571
21181
  const projectRoot = discoverKxmProjectRoot(inspectedFrom);
20572
21182
  if (projectRoot) {
20573
21183
  try {
@@ -20604,7 +21214,7 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
20604
21214
  }
20605
21215
  const gitRoot = discoverGitRoot(inspectedFrom);
20606
21216
  const candidateRoot = gitRoot ?? inspectedFrom;
20607
- if (existsSync9(join8(candidateRoot, ".kxm"))) {
21217
+ if (existsSync10(join9(candidateRoot, ".kxm"))) {
20608
21218
  return {
20609
21219
  mode: "repair",
20610
21220
  inspectedFrom,
@@ -20673,12 +21283,12 @@ var init_project_config = __esm({
20673
21283
  intakeMessageValidator;
20674
21284
  syncEventValidator;
20675
21285
  constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
20676
- this.schemasDir = resolve5(schemasDir);
21286
+ this.schemasDir = resolve6(schemasDir);
20677
21287
  this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
20678
- const common = readJsonObject(join8(this.schemasDir, "common.schema.json"));
21288
+ const common = readJsonObject(join9(this.schemasDir, "common.schema.json"));
20679
21289
  this.ajv.addSchema(common);
20680
21290
  for (const definition of Object.values(RESOURCE_SCHEMA)) {
20681
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, definition.file)));
21291
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, definition.file)));
20682
21292
  }
20683
21293
  const localBindingsFile = "local-repository-bindings.schema.json";
20684
21294
  const templateProvenanceFile = "template-provenance.schema.json";
@@ -20689,15 +21299,15 @@ var init_project_config = __esm({
20689
21299
  const coordinatorFile = "coordinator.schema.json";
20690
21300
  const intakeMessageFile = "intake-message.schema.json";
20691
21301
  const syncEventFile = "sync-event.schema.json";
20692
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, localBindingsFile)));
20693
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, templateProvenanceFile)));
20694
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, initOperationFile)));
20695
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, permissionDiffFile)));
20696
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, runEventFile)));
20697
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, driveReceiptFile)));
20698
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, coordinatorFile)));
20699
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, intakeMessageFile)));
20700
- this.ajv.addSchema(readJsonObject(join8(this.schemasDir, syncEventFile)));
21302
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, localBindingsFile)));
21303
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, templateProvenanceFile)));
21304
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, initOperationFile)));
21305
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, permissionDiffFile)));
21306
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, runEventFile)));
21307
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, driveReceiptFile)));
21308
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, coordinatorFile)));
21309
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, intakeMessageFile)));
21310
+ this.ajv.addSchema(readJsonObject(join9(this.schemasDir, syncEventFile)));
20701
21311
  for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
20702
21312
  const validator = this.ajv.getSchema(`https://schemas.kxm.dev/${definition.file}`);
20703
21313
  if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
@@ -20773,9 +21383,9 @@ var init_project_config = __esm({
20773
21383
  });
20774
21384
 
20775
21385
  // plugins/kxm/src/sqlite.ts
20776
- import { existsSync as existsSync10 } from "node:fs";
21386
+ import { existsSync as existsSync11 } from "node:fs";
20777
21387
  import { createRequire } from "node:module";
20778
- import { resolve as resolve6 } from "node:path";
21388
+ import { resolve as resolve7 } from "node:path";
20779
21389
  import { pathToFileURL as pathToFileURL2 } from "node:url";
20780
21390
  function loadNative() {
20781
21391
  try {
@@ -20792,8 +21402,8 @@ function loadNative() {
20792
21402
  throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
20793
21403
  }
20794
21404
  function openReadOnlyDatabase(path4) {
20795
- if (existsSync10(`${path4}-wal`)) return new DatabaseSync(path4, { readOnly: true });
20796
- const uri = pathToFileURL2(resolve6(path4));
21405
+ if (existsSync11(`${path4}-wal`)) return new DatabaseSync(path4, { readOnly: true });
21406
+ const uri = pathToFileURL2(resolve7(path4));
20797
21407
  uri.searchParams.set("immutable", "1");
20798
21408
  return new DatabaseSync(uri.href, { readOnly: true });
20799
21409
  }
@@ -20827,24 +21437,24 @@ var init_sqlite = __esm({
20827
21437
  });
20828
21438
 
20829
21439
  // plugins/kxm/src/bindings.ts
20830
- import { spawnSync as spawnSync4 } from "node:child_process";
21440
+ import { spawnSync as spawnSync5 } from "node:child_process";
20831
21441
  import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
20832
21442
  import {
20833
21443
  chmodSync as chmodSync2,
20834
21444
  closeSync,
20835
- existsSync as existsSync11,
21445
+ existsSync as existsSync12,
20836
21446
  fsyncSync,
20837
21447
  lstatSync as lstatSync2,
20838
- mkdirSync as mkdirSync5,
21448
+ mkdirSync as mkdirSync6,
20839
21449
  openSync,
20840
- readFileSync as readFileSync8,
21450
+ readFileSync as readFileSync9,
20841
21451
  realpathSync as realpathSync2,
20842
21452
  renameSync as renameSync3,
20843
21453
  rmSync as rmSync3,
20844
- writeFileSync as writeFileSync5
21454
+ writeFileSync as writeFileSync6
20845
21455
  } from "node:fs";
20846
- import { homedir as homedir4 } from "node:os";
20847
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join9, parse, relative as relative2, resolve as resolve7, sep as sep2 } from "node:path";
21456
+ import { homedir as homedir5 } from "node:os";
21457
+ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join10, parse as parse2, relative as relative2, resolve as resolve8, sep as sep2 } from "node:path";
20848
21458
  function bindingIssue(phase, code, message) {
20849
21459
  return { phase, code, file: BINDING_LABEL, message };
20850
21460
  }
@@ -20852,7 +21462,7 @@ function bindingError(phase, code, message) {
20852
21462
  throw new KxmConfigError([bindingIssue(phase, code, message)]);
20853
21463
  }
20854
21464
  function canonicalHostPath2(path4) {
20855
- const absolute = resolve7(path4);
21465
+ const absolute = resolve8(path4);
20856
21466
  try {
20857
21467
  return realpathSync2.native(absolute);
20858
21468
  } catch {
@@ -20867,21 +21477,21 @@ function sameHostPath2(left, right, platform = process.platform) {
20867
21477
  function kxmUserStateRoot(options = {}) {
20868
21478
  const env = options.env ?? process.env;
20869
21479
  const platform = options.platform ?? process.platform;
20870
- const home = options.homeDir ?? homedir4();
21480
+ const home = options.homeDir ?? homedir5();
20871
21481
  const explicit = options.stateRoot ?? env.KXM_STATE_HOME?.trim();
20872
21482
  if (explicit) {
20873
21483
  if (!isAbsolute4(explicit)) bindingError("path", "local_state_root_not_absolute", "KXM_STATE_HOME must be an absolute host-local path");
20874
- return resolve7(explicit);
21484
+ return resolve8(explicit);
20875
21485
  }
20876
21486
  if (platform === "win32") {
20877
21487
  const localAppData = env.LOCALAPPDATA?.trim();
20878
- const base2 = localAppData && isAbsolute4(localAppData) ? localAppData : join9(home, "AppData", "Local");
20879
- return resolve7(base2, "KXM");
21488
+ const base2 = localAppData && isAbsolute4(localAppData) ? localAppData : join10(home, "AppData", "Local");
21489
+ return resolve8(base2, "KXM");
20880
21490
  }
20881
- if (platform === "darwin") return resolve7(home, "Library", "Application Support", "KXM");
21491
+ if (platform === "darwin") return resolve8(home, "Library", "Application Support", "KXM");
20882
21492
  const xdgState = env.XDG_STATE_HOME?.trim();
20883
- const base = xdgState && isAbsolute4(xdgState) ? xdgState : join9(home, ".local", "state");
20884
- return resolve7(base, "kxm");
21493
+ const base = xdgState && isAbsolute4(xdgState) ? xdgState : join10(home, ".local", "state");
21494
+ return resolve8(base, "kxm");
20885
21495
  }
20886
21496
  function projectBindingKey(projectRoot, platform = process.platform) {
20887
21497
  const canonical2 = canonicalHostPath2(projectRoot);
@@ -20890,10 +21500,10 @@ function projectBindingKey(projectRoot, platform = process.platform) {
20890
21500
  }
20891
21501
  function kxmLocalBindingFile(projectRoot, options = {}) {
20892
21502
  const stateRoot = kxmUserStateRoot(options);
20893
- return join9(stateRoot, "projects", projectBindingKey(projectRoot, options.platform), "repository-bindings.json");
21503
+ return join10(stateRoot, "projects", projectBindingKey(projectRoot, options.platform), "repository-bindings.json");
20894
21504
  }
20895
21505
  function checkedDirectory(path4, description) {
20896
- if (!existsSync11(path4)) return false;
21506
+ if (!existsSync12(path4)) return false;
20897
21507
  const stat = lstatSync2(path4);
20898
21508
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
20899
21509
  bindingError("path", "local_binding_directory_invalid", `${description} must be a regular directory, not a link or file`);
@@ -20901,8 +21511,8 @@ function checkedDirectory(path4, description) {
20901
21511
  return true;
20902
21512
  }
20903
21513
  function existingBindingDirectory(file, stateRoot) {
20904
- const projects = join9(stateRoot, "projects");
20905
- const project = dirname7(file);
21514
+ const projects = join10(stateRoot, "projects");
21515
+ const project = dirname8(file);
20906
21516
  if (!checkedDirectory(stateRoot, "local state root")) return false;
20907
21517
  if (!checkedDirectory(projects, "local projects directory")) return false;
20908
21518
  return checkedDirectory(project, "local project binding directory");
@@ -20925,10 +21535,10 @@ function asRecord2(value, file, projectRoot, schemasDir) {
20925
21535
  };
20926
21536
  }
20927
21537
  function readKxmLocalBindings(projectRoot, options = {}) {
20928
- const root = resolve7(projectRoot);
21538
+ const root = resolve8(projectRoot);
20929
21539
  const stateRoot = kxmUserStateRoot(options);
20930
21540
  const file = kxmLocalBindingFile(root, options);
20931
- if (!existingBindingDirectory(file, stateRoot) || !existsSync11(file)) return void 0;
21541
+ if (!existingBindingDirectory(file, stateRoot) || !existsSync12(file)) return void 0;
20932
21542
  const stat = lstatSync2(file);
20933
21543
  if (stat.isSymbolicLink() || !stat.isFile()) {
20934
21544
  bindingError("path", "local_binding_file_invalid", "binding record must be a regular file, not a link or directory");
@@ -20936,7 +21546,7 @@ function readKxmLocalBindings(projectRoot, options = {}) {
20936
21546
  if (stat.size > MAX_BINDING_RECORD_BYTES) {
20937
21547
  bindingError("parse", "local_binding_file_too_large", `binding record exceeds ${MAX_BINDING_RECORD_BYTES} bytes`);
20938
21548
  }
20939
- const parsed = parseRestrictedYaml2(readFileSync8(file), BINDING_LABEL);
21549
+ const parsed = parseRestrictedYaml2(readFileSync9(file), BINDING_LABEL);
20940
21550
  return asRecord2(parsed, file, root, options.schemasDir);
20941
21551
  }
20942
21552
  function normalizedRecord(projectRoot, projectId, repositories, schemasDir) {
@@ -20976,48 +21586,48 @@ function syncDirectory(path4) {
20976
21586
  }
20977
21587
  }
20978
21588
  function createDirectoryIfMissing(path4) {
20979
- if (existsSync11(path4)) return;
21589
+ if (existsSync12(path4)) return;
20980
21590
  try {
20981
- mkdirSync5(path4, { mode: 448 });
20982
- syncDirectory(dirname7(path4));
21591
+ mkdirSync6(path4, { mode: 448 });
21592
+ syncDirectory(dirname8(path4));
20983
21593
  } catch (error) {
20984
21594
  if (error.code !== "EEXIST") throw error;
20985
21595
  }
20986
21596
  }
20987
21597
  function ensureDurableDirectory(path4, description) {
20988
21598
  const missing = [];
20989
- let current = resolve7(path4);
20990
- while (!existsSync11(current)) {
21599
+ let current = resolve8(path4);
21600
+ while (!existsSync12(current)) {
20991
21601
  missing.push(current);
20992
- const parent = dirname7(current);
21602
+ const parent = dirname8(current);
20993
21603
  if (parent === current) bindingError("path", "local_binding_directory_invalid", `${description} has no existing filesystem ancestor`);
20994
21604
  current = parent;
20995
21605
  }
20996
21606
  checkedDirectory(current, `${description} ancestor`);
20997
21607
  for (const directory of missing.reverse()) {
20998
- mkdirSync5(directory, { mode: 448 });
21608
+ mkdirSync6(directory, { mode: 448 });
20999
21609
  checkedDirectory(directory, description);
21000
- syncDirectory(dirname7(directory));
21610
+ syncDirectory(dirname8(directory));
21001
21611
  }
21002
21612
  }
21003
21613
  function ensureBindingDirectory(file, stateRoot) {
21004
21614
  ensureDurableDirectory(stateRoot, "local state root");
21005
21615
  checkedDirectory(stateRoot, "local state root");
21006
21616
  syncDirectory(stateRoot);
21007
- const projects = join9(stateRoot, "projects");
21617
+ const projects = join10(stateRoot, "projects");
21008
21618
  createDirectoryIfMissing(projects);
21009
21619
  checkedDirectory(projects, "local projects directory");
21010
- const project = dirname7(file);
21620
+ const project = dirname8(file);
21011
21621
  createDirectoryIfMissing(project);
21012
21622
  checkedDirectory(project, "local project binding directory");
21013
21623
  }
21014
21624
  function assertNoLinkedDirectoryComponents(path4, description) {
21015
- const absolute = resolve7(path4);
21016
- const filesystemRoot = parse(absolute).root;
21625
+ const absolute = resolve8(path4);
21626
+ const filesystemRoot = parse2(absolute).root;
21017
21627
  let current = filesystemRoot;
21018
21628
  const remainder = relative2(filesystemRoot, absolute);
21019
21629
  for (const segment of remainder.split(sep2).filter(Boolean)) {
21020
- current = join9(current, segment);
21630
+ current = join10(current, segment);
21021
21631
  const stat = lstatSync2(current);
21022
21632
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
21023
21633
  bindingError("path", "project_operation_lock_parent_invalid", `${description} contains a linked or non-directory component`);
@@ -21029,7 +21639,7 @@ function projectOperationLockFile(projectRoot) {
21029
21639
  for (const key of Object.keys(env)) {
21030
21640
  if (key.toLocaleUpperCase("en-US").startsWith("GIT_")) delete env[key];
21031
21641
  }
21032
- const result = spawnSync4("git", ["-C", projectRoot, "rev-parse", "--absolute-git-dir"], {
21642
+ const result = spawnSync5("git", ["-C", projectRoot, "rev-parse", "--absolute-git-dir"], {
21033
21643
  encoding: "utf8",
21034
21644
  env,
21035
21645
  timeout: 5e3,
@@ -21038,28 +21648,28 @@ function projectOperationLockFile(projectRoot) {
21038
21648
  if (result.status !== 0 || result.error || !result.stdout.trim()) {
21039
21649
  bindingError("discovery", "project_operation_lock_unavailable", "cannot resolve the authoritative Git metadata path for project locking");
21040
21650
  }
21041
- const gitDirectory = resolve7(result.stdout.trim());
21651
+ const gitDirectory = resolve8(result.stdout.trim());
21042
21652
  assertNoLinkedDirectoryComponents(gitDirectory, "authoritative Git metadata path");
21043
21653
  const gitStat = lstatSync2(gitDirectory);
21044
21654
  if (gitStat.isSymbolicLink() || !gitStat.isDirectory()) {
21045
21655
  bindingError("path", "project_operation_lock_parent_invalid", "authoritative Git metadata must resolve to a regular directory");
21046
21656
  }
21047
- const lockDirectory = join9(gitDirectory, "kxm");
21048
- if (existsSync11(lockDirectory)) {
21657
+ const lockDirectory = join10(gitDirectory, "kxm");
21658
+ if (existsSync12(lockDirectory)) {
21049
21659
  const stat = lstatSync2(lockDirectory);
21050
21660
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
21051
21661
  bindingError("path", "project_operation_lock_parent_invalid", "Git-local KXM lock directory must be regular, not a link or file");
21052
21662
  }
21053
21663
  } else {
21054
- mkdirSync5(lockDirectory, { mode: 448 });
21664
+ mkdirSync6(lockDirectory, { mode: 448 });
21055
21665
  syncDirectory(gitDirectory);
21056
21666
  }
21057
- return join9(lockDirectory, "project-operation-lock.sqlite");
21667
+ return join10(lockDirectory, "project-operation-lock.sqlite");
21058
21668
  }
21059
21669
  function withKxmLocalBindingLock(projectRoot, options, callback) {
21060
- const root = resolve7(projectRoot);
21670
+ const root = resolve8(projectRoot);
21061
21671
  const file = projectOperationLockFile(root);
21062
- if (existsSync11(file)) {
21672
+ if (existsSync12(file)) {
21063
21673
  const stat = lstatSync2(file);
21064
21674
  if (stat.isSymbolicLink() || !stat.isFile()) {
21065
21675
  bindingError("path", "local_binding_lock_invalid", "project-operation lock must be a regular file, not a link or directory");
@@ -21092,7 +21702,7 @@ function withKxmLocalBindingLock(projectRoot, options, callback) {
21092
21702
  }
21093
21703
  }
21094
21704
  function planKxmLocalBindings(projectRoot, projectId, repositories, options = {}) {
21095
- const root = resolve7(projectRoot);
21705
+ const root = resolve8(projectRoot);
21096
21706
  const file = kxmLocalBindingFile(root, options);
21097
21707
  const record = normalizedRecord(root, projectId, repositories, options.schemasDir);
21098
21708
  const existing = readKxmLocalBindings(root, options);
@@ -21102,7 +21712,7 @@ function planKxmLocalBindings(projectRoot, projectId, repositories, options = {}
21102
21712
  return { file, written: !existing || !recordsEqual(existing, record), record };
21103
21713
  }
21104
21714
  function writeKxmLocalBindings(projectRoot, projectId, repositories, options = {}, lock) {
21105
- const root = resolve7(projectRoot);
21715
+ const root = resolve8(projectRoot);
21106
21716
  if (!lock) {
21107
21717
  return withKxmLocalBindingLock(root, options, (acquired) => writeKxmLocalBindings(root, projectId, repositories, options, acquired));
21108
21718
  }
@@ -21113,17 +21723,17 @@ function writeKxmLocalBindings(projectRoot, projectId, repositories, options = {
21113
21723
  const { file, record } = planned;
21114
21724
  if (!planned.written) return planned;
21115
21725
  ensureBindingDirectory(file, kxmUserStateRoot(options));
21116
- const temporary = join9(dirname7(file), `.repository-bindings-${process.pid}-${randomUUID3()}.tmp`);
21726
+ const temporary = join10(dirname8(file), `.repository-bindings-${process.pid}-${randomUUID3()}.tmp`);
21117
21727
  let descriptor;
21118
21728
  try {
21119
21729
  descriptor = openSync(temporary, "wx", 384);
21120
- writeFileSync5(descriptor, `${JSON.stringify(record, null, 2)}
21730
+ writeFileSync6(descriptor, `${JSON.stringify(record, null, 2)}
21121
21731
  `, "utf8");
21122
21732
  fsyncSync(descriptor);
21123
21733
  closeSync(descriptor);
21124
21734
  descriptor = void 0;
21125
21735
  renameSync3(temporary, file);
21126
- syncDirectory(dirname7(file));
21736
+ syncDirectory(dirname8(file));
21127
21737
  } finally {
21128
21738
  if (descriptor !== void 0) closeSync(descriptor);
21129
21739
  rmSync3(temporary, { force: true });
@@ -21149,29 +21759,29 @@ var init_bindings = __esm({
21149
21759
  // plugins/kxm/src/runtime-paths.ts
21150
21760
  import { createHash as createHash9 } from "node:crypto";
21151
21761
  import { realpathSync as realpathSync3 } from "node:fs";
21152
- import { join as join10, resolve as resolve8 } from "node:path";
21762
+ import { join as join11, resolve as resolve9 } from "node:path";
21153
21763
  function kxmRuntimePaths(options = {}) {
21154
- const stateRoot = options.stateRoot ? resolve8(options.stateRoot) : kxmUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
21155
- const runtimeDir = join10(stateRoot, "runtime");
21764
+ const stateRoot = options.stateRoot ? resolve9(options.stateRoot) : kxmUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
21765
+ const runtimeDir = join11(stateRoot, "runtime");
21156
21766
  return {
21157
21767
  stateRoot,
21158
21768
  runtimeDir,
21159
- registryDb: join10(runtimeDir, "registry.db"),
21160
- projectsDir: join10(runtimeDir, "projects")
21769
+ registryDb: join11(runtimeDir, "registry.db"),
21770
+ projectsDir: join11(runtimeDir, "projects")
21161
21771
  };
21162
21772
  }
21163
21773
  function projectRuntimeKey(projectRoot) {
21164
21774
  let canonical2;
21165
21775
  try {
21166
- canonical2 = realpathSync3.native(resolve8(projectRoot));
21776
+ canonical2 = realpathSync3.native(resolve9(projectRoot));
21167
21777
  } catch {
21168
- canonical2 = resolve8(projectRoot);
21778
+ canonical2 = resolve9(projectRoot);
21169
21779
  }
21170
21780
  const folded = process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
21171
21781
  return createHash9("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
21172
21782
  }
21173
21783
  function kxmProjectRunEventsPath(projectRoot, env) {
21174
- return join10(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
21784
+ return join11(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
21175
21785
  }
21176
21786
  var init_runtime_paths = __esm({
21177
21787
  "plugins/kxm/src/runtime-paths.ts"() {
@@ -21498,22 +22108,22 @@ import { createHash as createHash11, randomBytes } from "node:crypto";
21498
22108
  import {
21499
22109
  chmodSync as chmodSync3,
21500
22110
  copyFileSync,
21501
- existsSync as existsSync12,
22111
+ existsSync as existsSync13,
21502
22112
  lstatSync as lstatSync3,
21503
- mkdirSync as mkdirSync6,
22113
+ mkdirSync as mkdirSync7,
21504
22114
  readdirSync as readdirSync3,
21505
- readFileSync as readFileSync9,
22115
+ readFileSync as readFileSync10,
21506
22116
  unlinkSync as unlinkSync2,
21507
- writeFileSync as writeFileSync6
22117
+ writeFileSync as writeFileSync7
21508
22118
  } from "node:fs";
21509
- import { basename as basename3, dirname as dirname8, isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve9, sep as sep3 } from "node:path";
22119
+ import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative3, resolve as resolve10, sep as sep3 } from "node:path";
21510
22120
  function databaseError(code, file, message) {
21511
22121
  const issue4 = { phase: "semantic", code, file, message };
21512
22122
  return new KxmConfigError([issue4]);
21513
22123
  }
21514
22124
  function checkedParent(path4, description) {
21515
- const parent = dirname8(path4);
21516
- if (!existsSync12(parent)) mkdirSync6(parent, { recursive: true, mode: 448 });
22125
+ const parent = dirname9(path4);
22126
+ if (!existsSync13(parent)) mkdirSync7(parent, { recursive: true, mode: 448 });
21517
22127
  const stat = lstatSync3(parent, { throwIfNoEntry: false });
21518
22128
  if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
21519
22129
  throw databaseError("runtime_path_invalid", description, `${description} parent must be a regular directory, not a link`);
@@ -21727,18 +22337,18 @@ function checkIntegrity(database) {
21727
22337
  return rows.length === 1 && rows[0]?.integrity_check === "ok";
21728
22338
  }
21729
22339
  function fileSha256(filePath) {
21730
- const bytes = readFileSync9(filePath);
22340
+ const bytes = readFileSync10(filePath);
21731
22341
  return `sha256:${createHash11("sha256").update(bytes).digest("hex")}`;
21732
22342
  }
21733
22343
  function backupDatabaseFile(sourcePath2, targetPath, storeId) {
21734
- const resolvedSource = resolve9(sourcePath2);
21735
- const resolvedTarget = resolve9(targetPath);
22344
+ const resolvedSource = resolve10(sourcePath2);
22345
+ const resolvedTarget = resolve10(targetPath);
21736
22346
  const sourceStat = lstatSync3(resolvedSource, { throwIfNoEntry: false });
21737
22347
  if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
21738
22348
  throw databaseError("runtime_path_invalid", resolvedSource, `source database ${resolvedSource} must be a regular file, not a link or directory`);
21739
22349
  }
21740
22350
  checkedParent(resolvedTarget, "backup target");
21741
- if (existsSync12(resolvedTarget)) {
22351
+ if (existsSync13(resolvedTarget)) {
21742
22352
  unlinkSync2(resolvedTarget);
21743
22353
  }
21744
22354
  const sourceDb = new DatabaseSync(resolvedSource);
@@ -21782,8 +22392,8 @@ function backupDatabaseFile(sourcePath2, targetPath, storeId) {
21782
22392
  };
21783
22393
  }
21784
22394
  function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVersion, maxSupportedVersion) {
21785
- const resolvedBackup = resolve9(backupPath);
21786
- const resolvedTarget = resolve9(targetPath);
22395
+ const resolvedBackup = resolve10(backupPath);
22396
+ const resolvedTarget = resolve10(targetPath);
21787
22397
  const backupStat = lstatSync3(resolvedBackup, { throwIfNoEntry: false });
21788
22398
  if (!backupStat || !backupStat.isFile() || backupStat.isSymbolicLink()) {
21789
22399
  throw databaseError("runtime_path_invalid", resolvedBackup, `backup database ${resolvedBackup} must be a regular file, not a link or directory`);
@@ -21816,7 +22426,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
21816
22426
  }
21817
22427
  checkedParent(resolvedTarget, "restore target");
21818
22428
  for (const file of [resolvedTarget, `${resolvedTarget}-wal`, `${resolvedTarget}-shm`]) {
21819
- if (existsSync12(file)) {
22429
+ if (existsSync13(file)) {
21820
22430
  try {
21821
22431
  unlinkSync2(file);
21822
22432
  } catch {
@@ -21855,14 +22465,14 @@ function pushStore(stores, storeId, sourcePath2) {
21855
22465
  stores.push({ storeId, sourcePath: sourcePath2, maxSupportedVersion: kxmBackupCeiling(storeId) });
21856
22466
  }
21857
22467
  function pushEventStore(stores, files, paths, key) {
21858
- const dbPath = join11(paths.projectsDir, key, "run-events.db");
21859
- if (!existsSync12(dbPath)) return;
22468
+ const dbPath = join12(paths.projectsDir, key, "run-events.db");
22469
+ if (!existsSync13(dbPath)) return;
21860
22470
  const safe = key.replace(/[^a-zA-Z0-9_.-]/g, "_");
21861
22471
  let storeId = `events:${safe}`;
21862
22472
  if (stores.some((store) => store.storeId === storeId)) storeId = `events:runtime:${safe}`;
21863
22473
  pushStore(stores, storeId, dbPath);
21864
22474
  const sidecar = `${dbPath}.run-prompts.json`;
21865
- if (existsSync12(sidecar)) {
22475
+ if (existsSync13(sidecar)) {
21866
22476
  const id = `${storeId}:run-prompts`;
21867
22477
  if (!files.some((file) => file.id === id || file.sourcePath === sidecar)) {
21868
22478
  files.push({ id, sourcePath: sidecar });
@@ -21880,11 +22490,11 @@ function discoverUserRuntime(runtimeProjectKey, options, stores, files) {
21880
22490
  pushEventStore(stores, files, paths, runtimeProjectKey);
21881
22491
  return paths.stateRoot;
21882
22492
  }
21883
- if (existsSync12(paths.registryDb)) {
22493
+ if (existsSync13(paths.registryDb)) {
21884
22494
  const storeId = stores.some((store) => store.storeId === "registry") ? "runtime-registry" : "registry";
21885
22495
  pushStore(stores, storeId, paths.registryDb);
21886
22496
  }
21887
- if (!existsSync12(paths.projectsDir)) return paths.stateRoot;
22497
+ if (!existsSync13(paths.projectsDir)) return paths.stateRoot;
21888
22498
  let entries;
21889
22499
  try {
21890
22500
  entries = readdirSync3(paths.projectsDir, { withFileTypes: true });
@@ -21897,22 +22507,22 @@ function discoverUserRuntime(runtimeProjectKey, options, stores, files) {
21897
22507
  return paths.stateRoot;
21898
22508
  }
21899
22509
  function discoverBackupSources(projectRoot, options = {}) {
21900
- const root = resolve9(projectRoot);
22510
+ const root = resolve10(projectRoot);
21901
22511
  const stores = [];
21902
22512
  const files = [];
21903
- const hubPath = options.hubDataPath ? resolve9(options.hubDataPath) : join11(root, ".kxm", "state", "kxm.db");
21904
- if (existsSync12(hubPath)) pushStore(stores, "hub-store", hubPath);
21905
- const registryPath = join11(root, ".kxm", "runtime", "registry.db");
21906
- if (existsSync12(registryPath)) pushStore(stores, "registry", registryPath);
21907
- const bindingsPath = join11(root, ".kxm", "runtime", "bindings.db");
21908
- if (existsSync12(bindingsPath)) pushStore(stores, "binding-store", bindingsPath);
21909
- const eventsDir = join11(root, ".kxm", "runtime", "events");
21910
- if (existsSync12(eventsDir)) {
22513
+ const hubPath = options.hubDataPath ? resolve10(options.hubDataPath) : join12(root, ".kxm", "state", "kxm.db");
22514
+ if (existsSync13(hubPath)) pushStore(stores, "hub-store", hubPath);
22515
+ const registryPath = join12(root, ".kxm", "runtime", "registry.db");
22516
+ if (existsSync13(registryPath)) pushStore(stores, "registry", registryPath);
22517
+ const bindingsPath = join12(root, ".kxm", "runtime", "bindings.db");
22518
+ if (existsSync13(bindingsPath)) pushStore(stores, "binding-store", bindingsPath);
22519
+ const eventsDir = join12(root, ".kxm", "runtime", "events");
22520
+ if (existsSync13(eventsDir)) {
21911
22521
  const entries = readdirSync3(eventsDir, { withFileTypes: true });
21912
22522
  for (const entry of entries) {
21913
22523
  if (entry.isFile() && entry.name.endsWith(".db")) {
21914
22524
  const key = entry.name.replace(/\.db$/, "").replace(/[^a-zA-Z0-9_.-]/g, "_");
21915
- pushStore(stores, `events:${key}`, join11(eventsDir, entry.name));
22525
+ pushStore(stores, `events:${key}`, join12(eventsDir, entry.name));
21916
22526
  }
21917
22527
  }
21918
22528
  }
@@ -21921,13 +22531,13 @@ function discoverBackupSources(projectRoot, options = {}) {
21921
22531
  return { stores, files, runtimeProjectKey, ...stateRoot !== void 0 ? { stateRoot } : {} };
21922
22532
  }
21923
22533
  function backupPlainFile(sourcePath2, targetPath, id) {
21924
- const resolvedSource = resolve9(sourcePath2);
22534
+ const resolvedSource = resolve10(sourcePath2);
21925
22535
  const sourceStat = lstatSync3(resolvedSource, { throwIfNoEntry: false });
21926
22536
  if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
21927
22537
  throw databaseError("runtime_path_invalid", resolvedSource, `backup file ${resolvedSource} must be a regular file, not a link or directory`);
21928
22538
  }
21929
22539
  checkedParent(targetPath, "backup target");
21930
- if (existsSync12(targetPath)) unlinkSync2(targetPath);
22540
+ if (existsSync13(targetPath)) unlinkSync2(targetPath);
21931
22541
  copyFileSync(resolvedSource, targetPath);
21932
22542
  try {
21933
22543
  chmodSync3(targetPath, 384);
@@ -21951,7 +22561,7 @@ function backupFilename(sourcePath2, id, used) {
21951
22561
  }
21952
22562
  function restorePlainFile(backupFilePath, targetPath) {
21953
22563
  checkedParent(targetPath, "restore target");
21954
- if (existsSync12(targetPath)) unlinkSync2(targetPath);
22564
+ if (existsSync13(targetPath)) unlinkSync2(targetPath);
21955
22565
  copyFileSync(backupFilePath, targetPath);
21956
22566
  try {
21957
22567
  chmodSync3(targetPath, 384);
@@ -21966,14 +22576,14 @@ function backupDiscoverOptions(options) {
21966
22576
  };
21967
22577
  }
21968
22578
  function planBackup(options = {}) {
21969
- const projectRoot = options.projectRoot ? resolve9(options.projectRoot) : process.cwd();
22579
+ const projectRoot = options.projectRoot ? resolve10(options.projectRoot) : process.cwd();
21970
22580
  const discovered = discoverBackupSources(projectRoot, backupDiscoverOptions(options));
21971
22581
  if (discovered.stores.length === 0) {
21972
22582
  throw databaseError("backup_no_stores", projectRoot, "no existing SQLite stores found to backup");
21973
22583
  }
21974
22584
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
21975
22585
  const timestamp = createdAt.replace(/[:.]/g, "-");
21976
- const outDir = options.outDir ? resolve9(options.outDir) : join11(projectRoot, ".kxm", "backups", `backup-${timestamp}`);
22586
+ const outDir = options.outDir ? resolve10(options.outDir) : join12(projectRoot, ".kxm", "backups", `backup-${timestamp}`);
21977
22587
  const usedFilenames = /* @__PURE__ */ new Set();
21978
22588
  const stores = discovered.stores.map((store) => ({
21979
22589
  storeId: store.storeId,
@@ -21999,22 +22609,22 @@ function planBackup(options = {}) {
21999
22609
  function createBackup(options = {}) {
22000
22610
  const { projectRoot, outDir, createdAt, scope, stateRoot, runtimeProjectKey, stores, files } = planBackup(options);
22001
22611
  const backupId = `bk_${randomBytes(8).toString("hex")}`;
22002
- if (!existsSync12(outDir)) {
22003
- mkdirSync6(outDir, { recursive: true, mode: 448 });
22612
+ if (!existsSync13(outDir)) {
22613
+ mkdirSync7(outDir, { recursive: true, mode: 448 });
22004
22614
  }
22005
22615
  const backedUpStores = [];
22006
22616
  const backedUpFiles = [];
22007
22617
  const omitted = [];
22008
22618
  for (const store of stores) {
22009
22619
  try {
22010
- backedUpStores.push(backupDatabaseFile(store.sourcePath, join11(outDir, store.backupFile), store.storeId));
22620
+ backedUpStores.push(backupDatabaseFile(store.sourcePath, join12(outDir, store.backupFile), store.storeId));
22011
22621
  } catch {
22012
22622
  omitted.push(store.storeId);
22013
22623
  }
22014
22624
  }
22015
22625
  for (const file of files) {
22016
22626
  try {
22017
- backedUpFiles.push(backupPlainFile(file.sourcePath, join11(outDir, file.backupFile), file.id));
22627
+ backedUpFiles.push(backupPlainFile(file.sourcePath, join12(outDir, file.backupFile), file.id));
22018
22628
  } catch {
22019
22629
  omitted.push(file.id);
22020
22630
  }
@@ -22050,8 +22660,8 @@ function createBackup(options = {}) {
22050
22660
  const manifestSha256 = `sha256:${createHash11("sha256").update(manifestJson, "utf8").digest("hex")}`;
22051
22661
  manifest.manifestSha256 = manifestSha256;
22052
22662
  const finalJson = JSON.stringify(manifest, null, 2) + "\n";
22053
- const manifestPath = join11(outDir, "manifest.json");
22054
- writeFileSync6(manifestPath, finalJson, "utf8");
22663
+ const manifestPath = join12(outDir, "manifest.json");
22664
+ writeFileSync7(manifestPath, finalJson, "utf8");
22055
22665
  return { manifest, outDir };
22056
22666
  }
22057
22667
  function pathUnder(root, path4) {
@@ -22064,33 +22674,33 @@ function restoreTarget(manifest, sourcePath2, id, options, currentStateRoot) {
22064
22674
  const stateRel = pathUnder(manifest.stateRoot, sourcePath2);
22065
22675
  const projectRel = pathUnder(manifest.projectRoot, sourcePath2);
22066
22676
  if (stateRel !== void 0 && (projectRel === void 0 || stateRel.length <= projectRel.length)) {
22067
- const ownEvents = manifest.runtimeProjectKey !== void 0 ? join11("runtime", "projects", manifest.runtimeProjectKey, "run-events.db") : void 0;
22677
+ const ownEvents = manifest.runtimeProjectKey !== void 0 ? join12("runtime", "projects", manifest.runtimeProjectKey, "run-events.db") : void 0;
22068
22678
  const own2 = ownEvents !== void 0 && (stateRel === ownEvents || stateRel === `${ownEvents}.run-prompts.json`);
22069
22679
  if (own2 && !options.allProjects && options.projectRoot !== void 0) {
22070
- const eventsPath = kxmProjectRunEventsPath(resolve9(options.projectRoot), options.env ?? process.env);
22680
+ const eventsPath = kxmProjectRunEventsPath(resolve10(options.projectRoot), options.env ?? process.env);
22071
22681
  return { targetPath: stateRel === ownEvents ? eventsPath : `${eventsPath}.run-prompts.json`, machineWide: false };
22072
22682
  }
22073
- return { targetPath: join11(currentStateRoot(), stateRel), machineWide: !own2 };
22683
+ return { targetPath: join12(currentStateRoot(), stateRel), machineWide: !own2 };
22074
22684
  }
22075
22685
  if (projectRel !== void 0) {
22076
- return { targetPath: options.projectRoot ? join11(resolve9(options.projectRoot), projectRel) : sourcePath2, machineWide: false };
22686
+ return { targetPath: options.projectRoot ? join12(resolve10(options.projectRoot), projectRel) : sourcePath2, machineWide: false };
22077
22687
  }
22078
22688
  return { targetPath: sourcePath2, machineWide: id !== "hub-store" };
22079
22689
  }
22080
22690
  function planRestore(manifestPathOrDir, options = {}) {
22081
- let manifestPath = resolve9(manifestPathOrDir);
22691
+ let manifestPath = resolve10(manifestPathOrDir);
22082
22692
  const stat = lstatSync3(manifestPath, { throwIfNoEntry: false });
22083
22693
  if (!stat) {
22084
22694
  throw databaseError("runtime_path_invalid", manifestPath, `manifest path ${manifestPath} does not exist`);
22085
22695
  }
22086
22696
  if (stat.isDirectory()) {
22087
- manifestPath = join11(manifestPath, "manifest.json");
22697
+ manifestPath = join12(manifestPath, "manifest.json");
22088
22698
  }
22089
- if (!existsSync12(manifestPath)) {
22699
+ if (!existsSync13(manifestPath)) {
22090
22700
  throw databaseError("runtime_path_invalid", manifestPath, `backup manifest ${manifestPath} not found`);
22091
22701
  }
22092
- const manifestDir = dirname8(manifestPath);
22093
- const rawText = readFileSync9(manifestPath, "utf8");
22702
+ const manifestDir = dirname9(manifestPath);
22703
+ const rawText = readFileSync10(manifestPath, "utf8");
22094
22704
  let manifest;
22095
22705
  try {
22096
22706
  manifest = JSON.parse(rawText);
@@ -22108,8 +22718,8 @@ function planRestore(manifestPathOrDir, options = {}) {
22108
22718
  const machineWide = [];
22109
22719
  const stores = [];
22110
22720
  for (const store of manifest.stores) {
22111
- const backupFilePath = join11(manifestDir, store.backupFile);
22112
- if (!existsSync12(backupFilePath)) {
22721
+ const backupFilePath = join12(manifestDir, store.backupFile);
22722
+ if (!existsSync13(backupFilePath)) {
22113
22723
  throw databaseError("restore_file_missing", backupFilePath, `backup file ${store.backupFile} missing from ${manifestDir}`);
22114
22724
  }
22115
22725
  const actualSha256 = fileSha256(backupFilePath);
@@ -22134,8 +22744,8 @@ function planRestore(manifestPathOrDir, options = {}) {
22134
22744
  }
22135
22745
  const files = [];
22136
22746
  for (const file of manifest.files ?? []) {
22137
- const backupFilePath = join11(manifestDir, file.backupFile);
22138
- if (!existsSync12(backupFilePath)) {
22747
+ const backupFilePath = join12(manifestDir, file.backupFile);
22748
+ if (!existsSync13(backupFilePath)) {
22139
22749
  throw databaseError("restore_file_missing", backupFilePath, `backup file ${file.backupFile} missing from ${manifestDir}`);
22140
22750
  }
22141
22751
  const actualSha256 = fileSha256(backupFilePath);
@@ -22206,10 +22816,10 @@ var init_database = __esm({
22206
22816
  });
22207
22817
 
22208
22818
  // plugins/kxm/src/runtime-store.ts
22209
- import { spawnSync as spawnSync5 } from "node:child_process";
22819
+ import { spawnSync as spawnSync6 } from "node:child_process";
22210
22820
  import { createHash as createHash12, randomUUID as randomUUID4 } from "node:crypto";
22211
- import { existsSync as existsSync13, lstatSync as lstatSync4, mkdirSync as mkdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync4, statSync, writeFileSync as writeFileSync7 } from "node:fs";
22212
- import { dirname as dirname9, isAbsolute as isAbsolute6, resolve as resolve10 } from "node:path";
22821
+ import { existsSync as existsSync14, lstatSync as lstatSync4, mkdirSync as mkdirSync8, readFileSync as readFileSync11, realpathSync as realpathSync4, statSync, writeFileSync as writeFileSync8 } from "node:fs";
22822
+ import { dirname as dirname10, isAbsolute as isAbsolute6, resolve as resolve11 } from "node:path";
22213
22823
  function runtimeIssue(phase, code, file, message) {
22214
22824
  return { phase, code, file, message };
22215
22825
  }
@@ -22218,7 +22828,7 @@ function runtimeError(code, file, message) {
22218
22828
  }
22219
22829
  function projectRuntimeOwnsRun(projectRoot, runId, env) {
22220
22830
  const path4 = kxmProjectRunEventsPath(projectRoot, env);
22221
- if (!existsSync13(path4)) return false;
22831
+ if (!existsSync14(path4)) return false;
22222
22832
  const database = openReadOnlyDatabase(path4);
22223
22833
  try {
22224
22834
  database.exec("PRAGMA busy_timeout = 5000");
@@ -22246,7 +22856,7 @@ function gitEnv() {
22246
22856
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
22247
22857
  }
22248
22858
  function gitCommonDirectory(projectRoot) {
22249
- const result = spawnSync5("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
22859
+ const result = spawnSync6("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
22250
22860
  encoding: "utf8",
22251
22861
  env: gitEnv(),
22252
22862
  timeout: 3e4,
@@ -22255,23 +22865,23 @@ function gitCommonDirectory(projectRoot) {
22255
22865
  if (result.status !== 0) return void 0;
22256
22866
  const text = (result.stdout ?? "").trim();
22257
22867
  if (!text) return void 0;
22258
- const absolute = isAbsolute6(text) ? text : resolve10(projectRoot, text);
22868
+ const absolute = isAbsolute6(text) ? text : resolve11(projectRoot, text);
22259
22869
  let canonical2;
22260
22870
  try {
22261
22871
  canonical2 = realpathSync4.native(absolute);
22262
22872
  } catch {
22263
- canonical2 = resolve10(absolute);
22873
+ canonical2 = resolve11(absolute);
22264
22874
  }
22265
22875
  return process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
22266
22876
  }
22267
22877
  function isPrimaryWorktree(projectRoot, commonDir) {
22268
22878
  if (commonDir === void 0) return false;
22269
- const dotGit = resolve10(projectRoot, ".git");
22879
+ const dotGit = resolve11(projectRoot, ".git");
22270
22880
  let canonical2;
22271
22881
  try {
22272
22882
  canonical2 = realpathSync4.native(dotGit);
22273
22883
  } catch {
22274
- canonical2 = resolve10(dotGit);
22884
+ canonical2 = resolve11(dotGit);
22275
22885
  }
22276
22886
  if (process.platform === "win32") canonical2 = canonical2.toLocaleLowerCase("en-US");
22277
22887
  return canonical2 === commonDir;
@@ -22700,7 +23310,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
22700
23310
  path;
22701
23311
  database;
22702
23312
  constructor(path4) {
22703
- this.path = resolve10(path4);
23313
+ this.path = resolve11(path4);
22704
23314
  migrateRegistryLanes(this.path);
22705
23315
  this.database = openDatabase(this.path, "runtime registry", {
22706
23316
  schema: REGISTRY_SCHEMA,
@@ -22800,7 +23410,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
22800
23410
  * `project_home_conflict`.
22801
23411
  */
22802
23412
  registerProject(registration) {
22803
- const projectRoot = resolve10(registration.projectRoot);
23413
+ const projectRoot = resolve11(registration.projectRoot);
22804
23414
  const projectKey = projectRuntimeKey(projectRoot);
22805
23415
  const candidateCommon = gitCommonDirectory(projectRoot);
22806
23416
  const commonByRoot = /* @__PURE__ */ new Map();
@@ -22972,7 +23582,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
22972
23582
  }
22973
23583
  /** How many other rows share this home root's project. Zero when this root is a lane or missing. */
22974
23584
  homeLaneCount(projectRoot) {
22975
- const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve10(projectRoot)));
23585
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve11(projectRoot)));
22976
23586
  if (!row || row.lane_of !== null) return 0;
22977
23587
  const count = this.database.prepare(
22978
23588
  "SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
@@ -22981,7 +23591,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
22981
23591
  }
22982
23592
  /** Caller holds the write transaction. Does not commit. */
22983
23593
  deleteRegisteredRoot(projectRoot) {
22984
- const projectKey = projectRuntimeKey(resolve10(projectRoot));
23594
+ const projectKey = projectRuntimeKey(resolve11(projectRoot));
22985
23595
  const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
22986
23596
  if (!row) return false;
22987
23597
  this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
@@ -23356,7 +23966,7 @@ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
23356
23966
  syncRedactor = new KxmSyncRedactor();
23357
23967
  database;
23358
23968
  constructor(path4) {
23359
- this.path = resolve10(path4);
23969
+ this.path = resolve11(path4);
23360
23970
  this.database = openDatabase(this.path, "run event store", {
23361
23971
  schema: EVENT_STORE_SCHEMA,
23362
23972
  version: KXM_EVENT_STORE_SCHEMA_VERSION,
@@ -23381,9 +23991,9 @@ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
23381
23991
  }
23382
23992
  readRunPrompts() {
23383
23993
  const sidecar = this.runPromptSidecarPath();
23384
- if (!existsSync13(sidecar)) return {};
23994
+ if (!existsSync14(sidecar)) return {};
23385
23995
  try {
23386
- const parsed = JSON.parse(readFileSync10(sidecar, "utf8"));
23996
+ const parsed = JSON.parse(readFileSync11(sidecar, "utf8"));
23387
23997
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
23388
23998
  return Object.fromEntries(
23389
23999
  Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
@@ -23393,7 +24003,7 @@ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
23393
24003
  }
23394
24004
  }
23395
24005
  writeRunPrompts(all) {
23396
- writeFileSync7(this.runPromptSidecarPath(), `${JSON.stringify(all)}
24006
+ writeFileSync8(this.runPromptSidecarPath(), `${JSON.stringify(all)}
23397
24007
  `, { encoding: "utf8", mode: 384 });
23398
24008
  }
23399
24009
  /** Run one immutable transaction, rolling back on any failure. */
@@ -24848,8 +25458,8 @@ var init_price_calc = __esm({
24848
25458
 
24849
25459
  // plugins/kxm/src/prices.ts
24850
25460
  import { createHash as createHash14 } from "node:crypto";
24851
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync2, writeFileSync as writeFileSync8 } from "node:fs";
24852
- import { join as join13 } from "node:path";
25461
+ import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "node:fs";
25462
+ import { join as join14 } from "node:path";
24853
25463
  function hashPriceCatalog(catalog) {
24854
25464
  const canonical2 = {
24855
25465
  schema: catalog.schema,
@@ -24872,7 +25482,7 @@ function hashPriceCatalog(catalog) {
24872
25482
  return createHash14("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
24873
25483
  }
24874
25484
  function parsePriceCatalog(text) {
24875
- const parsed = (0, import_yaml5.parse)(text);
25485
+ const parsed = (0, import_yaml6.parse)(text);
24876
25486
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
24877
25487
  throw new Error("price catalog must be an object");
24878
25488
  }
@@ -24960,11 +25570,11 @@ function parsePriceCatalog(text) {
24960
25570
  return catalog;
24961
25571
  }
24962
25572
  function loadPriceCatalog(rootOrPath) {
24963
- const candidatePath = existsSync14(join13(rootOrPath, ".kxm", "prices.yaml")) ? join13(rootOrPath, ".kxm", "prices.yaml") : existsSync14(join13(rootOrPath, "prices.yaml")) ? join13(rootOrPath, "prices.yaml") : existsSync14(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
24964
- if (!candidatePath || !existsSync14(candidatePath)) {
25573
+ const candidatePath = existsSync15(join14(rootOrPath, ".kxm", "prices.yaml")) ? join14(rootOrPath, ".kxm", "prices.yaml") : existsSync15(join14(rootOrPath, "prices.yaml")) ? join14(rootOrPath, "prices.yaml") : existsSync15(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
25574
+ if (!candidatePath || !existsSync15(candidatePath)) {
24965
25575
  return void 0;
24966
25576
  }
24967
- const content = readFileSync11(candidatePath, "utf8");
25577
+ const content = readFileSync12(candidatePath, "utf8");
24968
25578
  return parsePriceCatalog(content);
24969
25579
  }
24970
25580
  function loadPriceCatalogForEstimate(options) {
@@ -24980,7 +25590,7 @@ function loadPriceCatalogForEstimate(options) {
24980
25590
  return { catalog, unavailable: false, stale: false };
24981
25591
  }
24982
25592
  function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date()) {
24983
- const path4 = join13(projectRoot, ".kxm", "prices.yaml");
25593
+ const path4 = join14(projectRoot, ".kxm", "prices.yaml");
24984
25594
  const loaded = loadPriceCatalog(projectRoot);
24985
25595
  if (!loaded) throw new Error("price catalog missing");
24986
25596
  const date = now.toISOString().slice(0, 10);
@@ -24991,7 +25601,7 @@ function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date())
24991
25601
  models: loaded.models
24992
25602
  };
24993
25603
  const stamped = { ...body, sha256: hashPriceCatalog(body) };
24994
- writeFileSync8(path4, (0, import_yaml5.stringify)({
25604
+ writeFileSync9(path4, (0, import_yaml6.stringify)({
24995
25605
  schema: stamped.schema,
24996
25606
  date: stamped.date,
24997
25607
  sha256: stamped.sha256,
@@ -25004,234 +25614,16 @@ function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date())
25004
25614
  }
25005
25615
  return again;
25006
25616
  }
25007
- var import_yaml5;
25617
+ var import_yaml6;
25008
25618
  var init_prices = __esm({
25009
25619
  "plugins/kxm/src/prices.ts"() {
25010
25620
  "use strict";
25011
- import_yaml5 = __toESM(require_dist(), 1);
25621
+ import_yaml6 = __toESM(require_dist(), 1);
25012
25622
  init_price_calc();
25013
25623
  init_price_calc();
25014
25624
  }
25015
25625
  });
25016
25626
 
25017
- // plugins/kxm/src/config.ts
25018
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
25019
- import { homedir as homedir5 } from "node:os";
25020
- import { dirname as dirname10, join as join14, resolve as resolve11 } from "node:path";
25021
- function userConfigDirectory(overrideDir) {
25022
- if (overrideDir) return resolve11(overrideDir);
25023
- return resolve11(process.env.KXM_USER_CONFIG_DIR?.trim() || join14(homedir5(), ".config", "kxm"));
25024
- }
25025
- function repoConfigDirectory(repoRoot) {
25026
- return resolve11(repoRoot, ".kxm");
25027
- }
25028
- function deepMerge(target, source) {
25029
- const result = { ...target };
25030
- for (const [key, val] of Object.entries(source)) {
25031
- if (val && typeof val === "object" && !Array.isArray(val)) {
25032
- const existing = result[key] && typeof result[key] === "object" && !Array.isArray(result[key]) ? result[key] : {};
25033
- result[key] = deepMerge(existing, val);
25034
- } else if (val !== void 0) {
25035
- result[key] = val;
25036
- }
25037
- }
25038
- return result;
25039
- }
25040
- function normalizeHubConfig(raw) {
25041
- const autoStart = raw?.autoStart;
25042
- return { autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart };
25043
- }
25044
- function recordOf(raw) {
25045
- return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
25046
- }
25047
- function finiteNumber(value) {
25048
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
25049
- }
25050
- function normalizeImprovementConfig(raw) {
25051
- const value = recordOf(raw);
25052
- const threshold = recordOf(value.autoThreshold);
25053
- const policy = value.promotionPolicy;
25054
- const halfLife = finiteNumber(value.telemetryHalfLifeDays);
25055
- const minRuns = finiteNumber(threshold.minRuns);
25056
- const minPassRate = finiteNumber(threshold.minPassRate);
25057
- const minCostSavings = finiteNumber(threshold.minCostSavings);
25058
- return {
25059
- promotionPolicy: IMPROVEMENT_PROMOTION_POLICIES.includes(policy) ? policy : "manual_pr",
25060
- telemetryHalfLifeDays: halfLife !== void 0 && halfLife > 0 && halfLife <= 3650 ? halfLife : 14,
25061
- autoThreshold: {
25062
- minRuns: minRuns !== void 0 && Number.isInteger(minRuns) && minRuns >= 1 && minRuns <= 1e6 ? minRuns : 10,
25063
- minPassRate: minPassRate !== void 0 && minPassRate >= 0 && minPassRate <= 1 ? minPassRate : 0.95,
25064
- minCostSavings: minCostSavings !== void 0 && minCostSavings >= 0 ? minCostSavings : 0.5
25065
- }
25066
- };
25067
- }
25068
- function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
25069
- const userDir = userConfigDirectory(options.userConfigDir);
25070
- const userConfigFile = join14(userDir, "config.yaml");
25071
- const repoDir = repoConfigDirectory(repoRoot);
25072
- const repoConfigFile = join14(repoDir, "config.yaml");
25073
- let userRaw = {};
25074
- let userLoadedPath;
25075
- if (existsSync15(userConfigFile)) {
25076
- try {
25077
- const text = readFileSync12(userConfigFile, "utf8");
25078
- userRaw = (0, import_yaml6.parse)(text) ?? {};
25079
- userLoadedPath = userConfigFile;
25080
- } catch (error) {
25081
- throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
25082
- }
25083
- }
25084
- let repoRaw = {};
25085
- let repoLoadedPath;
25086
- if (existsSync15(repoConfigFile)) {
25087
- try {
25088
- const text = readFileSync12(repoConfigFile, "utf8");
25089
- repoRaw = (0, import_yaml6.parse)(text) ?? {};
25090
- repoLoadedPath = repoConfigFile;
25091
- } catch (error) {
25092
- throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
25093
- }
25094
- }
25095
- const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG));
25096
- const mergedUser = deepMerge(baseCopy, userRaw);
25097
- const mergedAll = deepMerge(mergedUser, repoRaw);
25098
- return {
25099
- schema: KXM_CONFIG_SCHEMA,
25100
- user: mergedAll.user ?? {},
25101
- defaults: mergedAll.defaults ?? {},
25102
- dash: mergedAll.dash ?? {},
25103
- sync: mergedAll.sync ?? {},
25104
- hub: normalizeHubConfig(mergedAll.hub),
25105
- improvement: normalizeImprovementConfig(mergedAll.improvement),
25106
- routing: mergedAll.routing ?? DEFAULT_KXM_CONFIG.routing,
25107
- telemetry: mergedAll.telemetry ?? DEFAULT_KXM_CONFIG.telemetry,
25108
- loadedFrom: {
25109
- userConfigPath: userLoadedPath,
25110
- repoConfigPath: repoLoadedPath
25111
- }
25112
- };
25113
- }
25114
- function getKxmConfigValue(config, keyPath) {
25115
- const parts = keyPath.split(".");
25116
- let current = config;
25117
- for (const part of parts) {
25118
- if (!current || typeof current !== "object") return void 0;
25119
- current = current[part];
25120
- }
25121
- return current;
25122
- }
25123
- function kxmConfigFileForScope(repoRoot, scope, userConfigDir) {
25124
- return scope === "user" ? join14(userConfigDirectory(userConfigDir), "config.yaml") : join14(repoConfigDirectory(repoRoot), "config.yaml");
25125
- }
25126
- function configKeyParts(keyPath) {
25127
- const parts = keyPath.split(".");
25128
- if (parts.some((part) => !part || part === "__proto__" || part === "prototype" || part === "constructor")) {
25129
- throw new Error("invalid config key path");
25130
- }
25131
- return parts;
25132
- }
25133
- function readConfigFile(targetFile2) {
25134
- let existing = {};
25135
- if (existsSync15(targetFile2)) {
25136
- try {
25137
- existing = (0, import_yaml6.parse)(readFileSync12(targetFile2, "utf8")) ?? {};
25138
- } catch {
25139
- existing = {};
25140
- }
25141
- }
25142
- return existing;
25143
- }
25144
- function writeConfigFile(targetFile2, value) {
25145
- mkdirSync8(dirname10(targetFile2), { recursive: true });
25146
- writeFileSync9(targetFile2, (0, import_yaml6.stringify)(value).trim() + "\n", "utf8");
25147
- }
25148
- function setKxmConfigValue(repoRoot, keyPath, value, options = {}) {
25149
- const targetFile2 = kxmConfigFileForScope(repoRoot, options.scope ?? "project", options.userConfigDir);
25150
- const existing = readConfigFile(targetFile2);
25151
- const parts = configKeyParts(keyPath);
25152
- let cursor2 = existing;
25153
- for (let i = 0; i < parts.length - 1; i++) {
25154
- const p = parts[i];
25155
- if (!cursor2[p] || typeof cursor2[p] !== "object") {
25156
- cursor2[p] = {};
25157
- }
25158
- cursor2 = cursor2[p];
25159
- }
25160
- cursor2[parts[parts.length - 1]] = value;
25161
- if (!options.dryRun) writeConfigFile(targetFile2, existing);
25162
- return { file: targetFile2 };
25163
- }
25164
- function formatKxmConfig(config) {
25165
- const display = {
25166
- schema: config.schema,
25167
- user: config.user,
25168
- defaults: config.defaults,
25169
- dash: config.dash,
25170
- sync: config.sync,
25171
- loadedFrom: config.loadedFrom
25172
- };
25173
- return (0, import_yaml6.stringify)(display).trim();
25174
- }
25175
- var import_yaml6, KXM_CONFIG_SCHEMA, IMPROVEMENT_PROMOTION_POLICIES, DEFAULT_KXM_CONFIG;
25176
- var init_config = __esm({
25177
- "plugins/kxm/src/config.ts"() {
25178
- "use strict";
25179
- import_yaml6 = __toESM(require_dist(), 1);
25180
- KXM_CONFIG_SCHEMA = "kxm.config.v1";
25181
- IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
25182
- DEFAULT_KXM_CONFIG = {
25183
- schema: KXM_CONFIG_SCHEMA,
25184
- user: {
25185
- theme: "dark",
25186
- preferredCritics: ["reviewer-arch", "reviewer-cli"],
25187
- tokenBudget: 16e3
25188
- },
25189
- defaults: {
25190
- workflow: "software-engineering/feature-implementation",
25191
- harness: "pi"
25192
- },
25193
- dash: {
25194
- defaultScreen: "agents",
25195
- refreshIntervalMs: 1e3,
25196
- autoOpen: false
25197
- },
25198
- sync: {
25199
- defaultTracker: "none"
25200
- },
25201
- hub: {
25202
- autoStart: "background"
25203
- },
25204
- improvement: {
25205
- promotionPolicy: "manual_pr",
25206
- telemetryHalfLifeDays: 14,
25207
- autoThreshold: {
25208
- minRuns: 10,
25209
- minPassRate: 0.95,
25210
- minCostSavings: 0.5
25211
- }
25212
- },
25213
- routing: {
25214
- shadowExecution: {
25215
- enabled: false,
25216
- sampleRate: 0.05,
25217
- candidateModels: []
25218
- },
25219
- circuitBreaker: {
25220
- mode: "soft_demotion",
25221
- failureThreshold: 3,
25222
- windowSeconds: 3600,
25223
- cooldownSeconds: 1800,
25224
- penaltyMultiplier: 5
25225
- }
25226
- },
25227
- telemetry: {
25228
- federated: true,
25229
- anonymize: true
25230
- }
25231
- };
25232
- }
25233
- });
25234
-
25235
25627
  // plugins/kxm/src/role.ts
25236
25628
  import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync4, readFileSync as readFileSync13, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "node:fs";
25237
25629
  import { join as join15 } from "node:path";
@@ -27902,6 +28294,10 @@ function printWorker(runtime, worker, payload, text, outcome) {
27902
28294
  print(runtime.io, runtime.json, envelope2, text);
27903
28295
  }
27904
28296
  function hostMode(runtime) {
28297
+ try {
28298
+ if (describeHubConnection(runtime.cwd, runtime.env).scope === "remote") return "hub";
28299
+ } catch {
28300
+ }
27905
28301
  try {
27906
28302
  if (effectiveHubBindingScope(runtime.serverUrl, runtime.env) === "remote") return "hub";
27907
28303
  } catch {
@@ -27982,13 +28378,14 @@ function runtimeFrom(ctx, command) {
27982
28378
  throw error;
27983
28379
  }
27984
28380
  }
28381
+ const connection = describeHubConnection(ctx.cwd, ctx.env);
27985
28382
  return {
27986
28383
  ...ctx,
27987
28384
  json: Boolean(opts.json),
27988
28385
  dryRun: Boolean(opts.dryRun),
27989
28386
  ...opts.workspace === void 0 ? {} : { workspaceFlag: opts.workspace },
27990
28387
  dirs: workspaceDirs(ctx.cwd, opts.workspace, ctx.env),
27991
- serverUrl: envServerUrl || boundHubUrl || "http://127.0.0.1:7331",
28388
+ serverUrl: connection.url || envServerUrl || boundHubUrl || "http://127.0.0.1:7331",
27992
28389
  ...boundHubUrl ? { boundHubUrl } : {},
27993
28390
  fetchImpl: ctx.io.fetchImpl ?? fetch
27994
28391
  };
@@ -28014,6 +28411,7 @@ var init_types = __esm({
28014
28411
  init_envelope();
28015
28412
  init_workflow();
28016
28413
  init_hub_binding();
28414
+ init_hub_identity();
28017
28415
  CLI_RESULT_SCHEMA = "kxm.cli-result.v1";
28018
28416
  DRY_RUN_UNSUPPORTED = "dry_run_unsupported";
28019
28417
  }
@@ -28051,6 +28449,7 @@ var init_hub_autostart = __esm({
28051
28449
  "plugins/kxm/src/hub-autostart.ts"() {
28052
28450
  "use strict";
28053
28451
  init_hub_binding();
28452
+ init_hub_identity();
28054
28453
  init_hub_env();
28055
28454
  init_repo_root();
28056
28455
  init_redact();
@@ -28222,10 +28621,10 @@ var init_vision_gate = __esm({
28222
28621
  });
28223
28622
 
28224
28623
  // plugins/kxm/src/permission.ts
28225
- import { spawnSync as spawnSync6 } from "node:child_process";
28624
+ import { spawnSync as spawnSync7 } from "node:child_process";
28226
28625
  import { createHash as createHash17 } from "node:crypto";
28227
28626
  import { existsSync as existsSync27, mkdtempSync, mkdirSync as mkdirSync20, readFileSync as readFileSync25, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "node:fs";
28228
- import { tmpdir } from "node:os";
28627
+ import { tmpdir as tmpdir2 } from "node:os";
28229
28628
  import { dirname as dirname15, join as join29, resolve as resolve20, sep as sep4 } from "node:path";
28230
28629
  function canonical(value) {
28231
28630
  return kxmCanonicalJson(value === void 0 ? null : value);
@@ -28696,7 +29095,7 @@ function gitEnvironment2() {
28696
29095
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
28697
29096
  }
28698
29097
  function git(root, args) {
28699
- const result = spawnSync6("git", ["-C", root, ...args], {
29098
+ const result = spawnSync7("git", ["-C", root, ...args], {
28700
29099
  encoding: "utf8",
28701
29100
  env: gitEnvironment2(),
28702
29101
  timeout: 15e3,
@@ -28714,7 +29113,7 @@ function git(root, args) {
28714
29113
  return result.stdout;
28715
29114
  }
28716
29115
  function gitBuffer(root, args) {
28717
- const result = spawnSync6("git", ["-C", root, ...args], {
29116
+ const result = spawnSync7("git", ["-C", root, ...args], {
28718
29117
  env: gitEnvironment2(),
28719
29118
  timeout: 15e3,
28720
29119
  windowsHide: true,
@@ -28745,7 +29144,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
28745
29144
  const tree = resolveTreeRevision(root, revision);
28746
29145
  const listing = gitBuffer(root, ["ls-tree", "-r", "-z", "--name-only", tree]).toString("utf8");
28747
29146
  const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
28748
- const shadow = mkdtempSync(join29(tmpdir(), "kxm-permission-base-"));
29147
+ const shadow = mkdtempSync(join29(tmpdir2(), "kxm-permission-base-"));
28749
29148
  const shadowResolved = resolve20(shadow);
28750
29149
  const shadowMembersDir = `.kxm-shadow-members-${createHash17("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
28751
29150
  try {
@@ -28912,7 +29311,7 @@ function loadBaseProjectDeclarations(projectFile) {
28912
29311
  return members;
28913
29312
  }
28914
29313
  function initShadowGitRoot(directory) {
28915
- const result = spawnSync6("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
29314
+ const result = spawnSync7("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
28916
29315
  encoding: "utf8",
28917
29316
  env: gitEnvironment2(),
28918
29317
  timeout: 1e4,
@@ -30377,6 +30776,14 @@ var init_tenant_status = __esm({
30377
30776
  }
30378
30777
  });
30379
30778
 
30779
+ // plugins/kxm/src/project-name.ts
30780
+ var init_project_name = __esm({
30781
+ "plugins/kxm/src/project-name.ts"() {
30782
+ "use strict";
30783
+ init_hub_identity();
30784
+ }
30785
+ });
30786
+
30380
30787
  // plugins/kxm/src/cli/project.ts
30381
30788
  var project_exports = {};
30382
30789
  __export(project_exports, {
@@ -34957,16 +35364,17 @@ init_repo_root();
34957
35364
  init_client();
34958
35365
  init_hub_env();
34959
35366
  init_hub_binding();
34960
- init_project_name();
35367
+ init_config();
35368
+ init_hub_identity();
34961
35369
 
34962
35370
  // plugins/kxm/src/commands.ts
34963
35371
  init_client();
34964
35372
  init_protocol();
34965
35373
  init_workflow();
34966
35374
  import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
34967
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
34968
- import { homedir as homedir3 } from "node:os";
34969
- import { dirname as dirname5, join as join6, resolve as resolve4 } from "node:path";
35375
+ import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
35376
+ import { homedir as homedir4 } from "node:os";
35377
+ import { dirname as dirname6, join as join7, resolve as resolve5 } from "node:path";
34970
35378
  function forwardedHops(handling) {
34971
35379
  if (!handling?.length) return void 0;
34972
35380
  return {
@@ -35742,18 +36150,18 @@ function parseSessionToken(token) {
35742
36150
  return void 0;
35743
36151
  }
35744
36152
  function resolveUserConfigDirectory(overrideDir) {
35745
- if (overrideDir) return resolve4(overrideDir);
35746
- return resolve4(process.env.KXM_USER_CONFIG_DIR?.trim() || join6(homedir3(), ".config", "kxm"));
36153
+ if (overrideDir) return resolve5(overrideDir);
36154
+ return resolve5(process.env.KXM_USER_CONFIG_DIR?.trim() || join7(homedir4(), ".config", "kxm"));
35747
36155
  }
35748
36156
  function sessionTokenPath(userConfigDir) {
35749
- return join6(resolveUserConfigDirectory(userConfigDir), "session.token");
36157
+ return join7(resolveUserConfigDirectory(userConfigDir), "session.token");
35750
36158
  }
35751
36159
  function persistSessionTokenToDisk(token, options) {
35752
36160
  const filePath = sessionTokenPath(options?.userConfigDir);
35753
- const dir = dirname5(filePath);
35754
- mkdirSync4(dir, { recursive: true, mode: 448 });
36161
+ const dir = dirname6(filePath);
36162
+ mkdirSync5(dir, { recursive: true, mode: 448 });
35755
36163
  const mode = options?.mode ?? 384;
35756
- writeFileSync4(filePath, `${token.trim()}
36164
+ writeFileSync5(filePath, `${token.trim()}
35757
36165
  `, { encoding: "utf8", mode });
35758
36166
  try {
35759
36167
  chmodSync(filePath, mode);
@@ -35763,9 +36171,9 @@ function persistSessionTokenToDisk(token, options) {
35763
36171
  }
35764
36172
  function readSessionTokenFromDisk(options) {
35765
36173
  const filePath = sessionTokenPath(options?.userConfigDir);
35766
- if (!existsSync6(filePath)) return void 0;
36174
+ if (!existsSync7(filePath)) return void 0;
35767
36175
  try {
35768
- const token = readFileSync5(filePath, "utf8").trim();
36176
+ const token = readFileSync6(filePath, "utf8").trim();
35769
36177
  if (!token) return void 0;
35770
36178
  const payload = parseSessionToken(token);
35771
36179
  if (!payload) return void 0;
@@ -35776,7 +36184,7 @@ function readSessionTokenFromDisk(options) {
35776
36184
  }
35777
36185
  function clearSessionTokenFromDisk(options) {
35778
36186
  const filePath = sessionTokenPath(options?.userConfigDir);
35779
- if (!existsSync6(filePath)) return false;
36187
+ if (!existsSync7(filePath)) return false;
35780
36188
  try {
35781
36189
  unlinkSync(filePath);
35782
36190
  return true;
@@ -35875,10 +36283,10 @@ function enforceToolPolicy(commandName, env = process.env, options) {
35875
36283
  return { allowed: true };
35876
36284
  }
35877
36285
  const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
35878
- if (existsSync6(tokenFile)) {
36286
+ if (existsSync7(tokenFile)) {
35879
36287
  let tokenRaw;
35880
36288
  try {
35881
- tokenRaw = readFileSync5(tokenFile, "utf8").trim();
36289
+ tokenRaw = readFileSync6(tokenFile, "utf8").trim();
35882
36290
  } catch {
35883
36291
  return { allowed: false, error: "session_token_invalid", detail: "Session token file on disk could not be read" };
35884
36292
  }
@@ -39668,7 +40076,7 @@ init_project();
39668
40076
  init_repo_root();
39669
40077
  init_harness();
39670
40078
  init_types();
39671
- import { spawnSync as spawnSync7 } from "node:child_process";
40079
+ import { spawnSync as spawnSync8 } from "node:child_process";
39672
40080
  var PLUGIN_SUPPORTED_HARNESSES = Object.freeze(["pi", "omp", "claude"]);
39673
40081
  async function cmdPluginInstall(runtime, options) {
39674
40082
  const repoRoot = findKxmRepoRoot(import.meta.url);
@@ -39707,7 +40115,7 @@ async function cmdPluginInstall(runtime, options) {
39707
40115
  detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`
39708
40116
  });
39709
40117
  } else {
39710
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync7(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
40118
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync8(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
39711
40119
  if (run.error || run.status !== 0 && run.status !== null) {
39712
40120
  results.push({
39713
40121
  harness: "pi",
@@ -39738,7 +40146,7 @@ async function cmdPluginInstall(runtime, options) {
39738
40146
  detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`
39739
40147
  });
39740
40148
  } else {
39741
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync7(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
40149
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync8(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
39742
40150
  if (run.error || run.status !== 0 && run.status !== null) {
39743
40151
  results.push({
39744
40152
  harness: "omp",
@@ -39773,9 +40181,9 @@ async function cmdPluginInstall(runtime, options) {
39773
40181
  if (runtime.io.spawnSync) {
39774
40182
  runtime.io.spawnSync(command, marketplaceArgs);
39775
40183
  } else {
39776
- spawnSync7(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
40184
+ spawnSync8(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
39777
40185
  }
39778
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync7(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
40186
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync8(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
39779
40187
  if (run.error || run.status !== 0 && run.status !== null) {
39780
40188
  results.push({
39781
40189
  harness: "claude",
@@ -39899,7 +40307,7 @@ init_project_config();
39899
40307
  init_runtime_store();
39900
40308
  init_runtime_supervisor();
39901
40309
  init_types();
39902
- import { spawnSync as spawnSync8 } from "node:child_process";
40310
+ import { spawnSync as spawnSync9 } from "node:child_process";
39903
40311
  import { chmodSync as chmodSync6, existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync22 } from "node:fs";
39904
40312
  import { basename as basename8, dirname as dirname18, join as join37, resolve as resolve25 } from "node:path";
39905
40313
  var LANE_SCHEMA = "kxm.lanes.v1";
@@ -39914,7 +40322,7 @@ function gitEnv2() {
39914
40322
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
39915
40323
  }
39916
40324
  function git2(cwd, args) {
39917
- const result = spawnSync8("git", ["-C", cwd, ...args], {
40325
+ const result = spawnSync9("git", ["-C", cwd, ...args], {
39918
40326
  encoding: "utf8",
39919
40327
  env: gitEnv2(),
39920
40328
  timeout: 3e4,
@@ -40489,7 +40897,7 @@ function cmdLand(runtime, options = {}) {
40489
40897
  // plugins/kxm/src/cli/assign.ts
40490
40898
  init_project_config();
40491
40899
  init_types();
40492
- import { spawnSync as spawnSync9 } from "node:child_process";
40900
+ import { spawnSync as spawnSync10 } from "node:child_process";
40493
40901
  import { existsSync as existsSync34 } from "node:fs";
40494
40902
  import { join as join39 } from "node:path";
40495
40903
  var kxmAssignCliSeams = {};
@@ -40575,7 +40983,7 @@ async function cmdAssign(runtime, subcommand, args) {
40575
40983
  return 0;
40576
40984
  }
40577
40985
  const spawn6 = kxmAssignCliSeams.spawn ?? ((commandName, commandArgs, options) => {
40578
- const result2 = spawnSync9(commandName, commandArgs, {
40986
+ const result2 = spawnSync10(commandName, commandArgs, {
40579
40987
  cwd: options.cwd,
40580
40988
  stdio: options.stdio,
40581
40989
  env: options.env,
@@ -40595,6 +41003,8 @@ async function cmdAssign(runtime, subcommand, args) {
40595
41003
  init_redact();
40596
41004
  init_project_name();
40597
41005
  init_hub_env();
41006
+ init_config();
41007
+ init_hub_identity();
40598
41008
  init_envelope();
40599
41009
  import { randomUUID as randomUUID14 } from "node:crypto";
40600
41010
  import { existsSync as existsSync40, mkdirSync as mkdirSync28, readFileSync as readFileSync36, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync25 } from "node:fs";
@@ -49038,7 +49448,7 @@ var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
49038
49448
  // plugins/kxm/src/tui.ts
49039
49449
  import { mkdirSync as mkdirSync25 } from "node:fs";
49040
49450
  import { join as join44, resolve as resolve27, dirname as dirname20 } from "node:path";
49041
- import { spawnSync as spawnSync10 } from "node:child_process";
49451
+ import { spawnSync as spawnSync11 } from "node:child_process";
49042
49452
 
49043
49453
  // packages/core/tui/src/types/surface.ts
49044
49454
  var KXM_TUI_LIMITS = Object.freeze({
@@ -49595,16 +50005,16 @@ function applyMeshTuiKey(view, key, itemCount = 0) {
49595
50005
  function copyToClipboard(text) {
49596
50006
  try {
49597
50007
  if (process.platform === "darwin") {
49598
- const proc = spawnSync10("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
50008
+ const proc = spawnSync11("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
49599
50009
  return proc.status === 0;
49600
50010
  }
49601
50011
  if (process.platform === "win32") {
49602
- const proc = spawnSync10("clip", { input: text, encoding: "utf8", windowsHide: true });
50012
+ const proc = spawnSync11("clip", { input: text, encoding: "utf8", windowsHide: true });
49603
50013
  return proc.status === 0;
49604
50014
  }
49605
- const wl = spawnSync10("wl-copy", [text], { encoding: "utf8", windowsHide: true });
50015
+ const wl = spawnSync11("wl-copy", [text], { encoding: "utf8", windowsHide: true });
49606
50016
  if (wl.status === 0) return true;
49607
- const xclip = spawnSync10("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
50017
+ const xclip = spawnSync11("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
49608
50018
  return xclip.status === 0;
49609
50019
  } catch {
49610
50020
  return false;
@@ -49612,7 +50022,7 @@ function copyToClipboard(text) {
49612
50022
  }
49613
50023
  function spawnDegradeWorktree(repoRoot, runId, options) {
49614
50024
  const runner = options?.execFn ?? ((cmd, args) => {
49615
- const res = spawnSync10(cmd, args, {
50025
+ const res = spawnSync11(cmd, args, {
49616
50026
  cwd: repoRoot,
49617
50027
  encoding: "utf8",
49618
50028
  windowsHide: true,
@@ -50396,7 +50806,7 @@ async function runMeshTui(input) {
50396
50806
  }
50397
50807
 
50398
50808
  // plugins/kxm/src/session-work.ts
50399
- import { spawnSync as spawnSync11 } from "node:child_process";
50809
+ import { spawnSync as spawnSync12 } from "node:child_process";
50400
50810
  import { randomUUID as randomUUID13 } from "node:crypto";
50401
50811
  import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync32, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
50402
50812
  import { join as join45 } from "node:path";
@@ -50457,10 +50867,10 @@ function formatShipLine(ship) {
50457
50867
  }
50458
50868
  function readGitShip(cwd) {
50459
50869
  try {
50460
- const dirty = spawnSync11("git", ["--no-optional-locks", "-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
50870
+ const dirty = spawnSync12("git", ["--no-optional-locks", "-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
50461
50871
  if (dirty.status !== 0) return void 0;
50462
50872
  const isDirty = dirty.stdout.trim().length > 0;
50463
- const upstream = spawnSync11("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
50873
+ const upstream = spawnSync12("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
50464
50874
  if (upstream.status === 0) {
50465
50875
  return {
50466
50876
  dirty: isDirty,
@@ -50468,9 +50878,9 @@ function readGitShip(cwd) {
50468
50878
  };
50469
50879
  }
50470
50880
  for (const baseRef of ["origin/HEAD", "main", "origin/main", "master", "origin/master"]) {
50471
- const mb = spawnSync11("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
50881
+ const mb = spawnSync12("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
50472
50882
  if (mb.status === 0 && mb.stdout.trim()) {
50473
- const count = spawnSync11("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
50883
+ const count = spawnSync12("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
50474
50884
  if (count.status === 0) {
50475
50885
  return {
50476
50886
  dirty: isDirty,
@@ -51079,11 +51489,21 @@ async function refreshKxmUpdateNotice(runtime, config, current) {
51079
51489
  return notice;
51080
51490
  }
51081
51491
  async function cmdStatus(runtime) {
51492
+ const connection = describeHubConnection(runtime.dirs.workdir, runtime.env, {
51493
+ hasSavedProjectToken: (project) => {
51494
+ try {
51495
+ const token = readHubEnvRecord(runtime.env)?.projectTokens?.[project];
51496
+ return typeof token === "string" && token.trim().length > 0;
51497
+ } catch {
51498
+ return false;
51499
+ }
51500
+ }
51501
+ });
51082
51502
  let headers;
51083
51503
  try {
51084
- const binding = readHubBinding(runtime.env);
51085
- if (binding?.cloud) {
51086
- headers = { authorization: `Bearer ${resolveCloudHubToken(binding, runtime.env)}` };
51504
+ if (connection.mode === "cloud") {
51505
+ const token = resolveClientHubAuthToken(runtime.env, connection.project, runtime.dirs.workdir);
51506
+ if (token) headers = { authorization: `Bearer ${token}` };
51087
51507
  }
51088
51508
  } catch (error) {
51089
51509
  if (error instanceof CloudTokenError) {
@@ -51094,12 +51514,22 @@ async function cmdStatus(runtime) {
51094
51514
  }
51095
51515
  const health = await hubGet(`${runtime.serverUrl}/health`, runtime.fetchImpl, headers);
51096
51516
  const ready = await hubGet(`${runtime.serverUrl}/ready`, runtime.fetchImpl, headers);
51097
- const effectiveScope = effectiveHubBindingScope(runtime.serverUrl, runtime.env);
51098
- const overridden = Boolean(runtime.boundHubUrl && runtime.boundHubUrl !== runtime.serverUrl);
51517
+ const effectiveScope = connection.scope;
51518
+ const overridden = connection.urlSource === "env";
51519
+ const identity = {
51520
+ mode: connection.mode,
51521
+ modeSource: connection.modeSource,
51522
+ url: connection.url,
51523
+ urlSource: connection.urlSource,
51524
+ project: connection.project,
51525
+ projectSource: connection.source,
51526
+ keySource: connection.keySource
51527
+ };
51099
51528
  const payload = {
51100
51529
  ok: health.ok && ready.ok,
51101
51530
  command: "hub view",
51102
51531
  target: { url: runtime.serverUrl, scope: effectiveScope, ...overridden ? { source: "env" } : {} },
51532
+ identity,
51103
51533
  health: health.body,
51104
51534
  ready: ready.body
51105
51535
  };
@@ -51107,7 +51537,7 @@ async function cmdStatus(runtime) {
51107
51537
  runtime.io,
51108
51538
  runtime.json,
51109
51539
  payload,
51110
- `hub health=${health.ok} ready=${ready.ok} \xB7 ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""}`
51540
+ `hub health=${health.ok} ready=${ready.ok} \xB7 ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""} \xB7 mode=${identity.mode} project=${identity.project} (${connection.sourceLabel}) key=${identity.keySource}`
51111
51541
  );
51112
51542
  return payload.ok ? 0 : 1;
51113
51543
  }
@@ -51134,10 +51564,10 @@ async function cmdDash(runtime, options = {}) {
51134
51564
  runtime.io.stderr("kxm dash does not support --json; use kxm hub view\n");
51135
51565
  return 2;
51136
51566
  }
51137
- const project = defaultProjectName(runtime.dirs.workdir, runtime.env) || "project";
51567
+ const project = resolveProjectIdentity(runtime.dirs.workdir, runtime.env).project || "project";
51138
51568
  let authToken;
51139
51569
  try {
51140
- authToken = resolveClientHubAuthToken(runtime.env, project);
51570
+ authToken = resolveClientHubAuthToken(runtime.env, project, runtime.dirs.workdir);
51141
51571
  } catch (error) {
51142
51572
  if (error instanceof CloudTokenError) {
51143
51573
  print(runtime.io, runtime.json, { ok: false, command: "dash", error: error.code, detail: error.message }, error.message);
@@ -51222,25 +51652,35 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
51222
51652
  const cloud = options.cloud === true;
51223
51653
  const tokenEnv = options.tokenEnv?.trim() || void 0;
51224
51654
  const tokenCommand = options.tokenCommand?.trim() || void 0;
51225
- if ((tokenEnv || tokenCommand) && !cloud) {
51655
+ const keyOp = options.keyOp?.trim() || opReferenceFromTokenCommand(tokenCommand ?? "") || void 0;
51656
+ if (options.keyOp?.trim() && !isHubOpReference(options.keyOp.trim())) {
51657
+ print(
51658
+ runtime.io,
51659
+ runtime.json,
51660
+ { ok: false, command: "hub bind", error: "hub_config_invalid", url },
51661
+ "--key-op must be an op://vault/item/field reference, not a token; no binding was written"
51662
+ );
51663
+ return 2;
51664
+ }
51665
+ if (tokenCommand && !cloud) {
51226
51666
  print(
51227
51667
  runtime.io,
51228
51668
  runtime.json,
51229
51669
  { ok: false, command: "hub bind", error: "cloud_flag_required", url },
51230
- "--token-env and --token-command require --cloud; no binding was written"
51670
+ "--token-command requires --cloud; no binding was written"
51231
51671
  );
51232
51672
  return 2;
51233
51673
  }
51234
- if (cloud && !tokenEnv && !tokenCommand) {
51674
+ if (cloud && !tokenEnv && !tokenCommand && !keyOp) {
51235
51675
  print(
51236
51676
  runtime.io,
51237
51677
  runtime.json,
51238
51678
  { ok: false, command: "hub bind", error: "cloud_token_source_required", url },
51239
- "--cloud needs --token-env, --token-command, or both; the token is not stored in the binding"
51679
+ "--cloud needs --token-env, --token-command, --key-op, or a combination; the token is not stored"
51240
51680
  );
51241
51681
  return 2;
51242
51682
  }
51243
- if (cloud && tokenEnv && !isCloudTokenEnvName(tokenEnv)) {
51683
+ if (tokenEnv && !isCloudTokenEnvName(tokenEnv)) {
51244
51684
  print(
51245
51685
  runtime.io,
51246
51686
  runtime.json,
@@ -51265,10 +51705,10 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
51265
51705
  }
51266
51706
  const scope = cloud ? "remote" : hubBindingScope(url);
51267
51707
  if (!cloud && scope === "remote") {
51268
- const bindProject = defaultProjectName(runtime.dirs.workdir, runtime.env) || "project";
51708
+ const bindProject = resolveProjectIdentity(runtime.dirs.workdir, runtime.env).project || "project";
51269
51709
  let credentialReady = false;
51270
51710
  try {
51271
- credentialReady = hasClientHubCredential(runtime.env, bindProject);
51711
+ credentialReady = hasClientHubCredential(runtime.env, bindProject, runtime.dirs.workdir);
51272
51712
  } catch (error) {
51273
51713
  print(
51274
51714
  runtime.io,
@@ -51309,29 +51749,55 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
51309
51749
  }
51310
51750
  }
51311
51751
  const file = hubBindingFile(runtime.env);
51312
- const cloudFields = cloud ? { cloud: true, ...tokenEnv ? { tokenEnv } : {}, ...tokenCommand ? { tokenCommand } : {} } : {};
51752
+ const bindingCommand = tokenCommand ?? (cloud && keyOp ? `op read ${keyOp}` : void 0);
51753
+ const cloudFields = cloud ? { cloud: true, ...tokenEnv ? { tokenEnv } : {}, ...bindingCommand ? { tokenCommand: bindingCommand } : {} } : {};
51313
51754
  const record = {
51314
51755
  schema: HUB_BINDING_SCHEMA,
51315
51756
  url,
51316
51757
  boundAt: (/* @__PURE__ */ new Date()).toISOString(),
51317
51758
  ...cloudFields
51318
51759
  };
51760
+ const identity = resolveProjectIdentity(runtime.dirs.workdir, runtime.env);
51761
+ const configFile = join49(findConfigRoot(runtime.dirs.workdir), ".kxm", "config.yaml");
51762
+ const key = {
51763
+ ...keyOp ? { op: keyOp } : {},
51764
+ ...tokenEnv ? { env: tokenEnv } : {}
51765
+ };
51319
51766
  const cloudNote = cloud ? " \xB7 token is not stored" : "";
51320
51767
  if (runtime.dryRun) {
51321
51768
  print(
51322
51769
  runtime.io,
51323
51770
  runtime.json,
51324
- { ok: true, command: "hub bind", dryRun: true, url, scope, file, ...cloudFields },
51771
+ { ok: true, command: "hub bind", dryRun: true, url, scope, file, configFile, project: identity.project, ...cloudFields },
51325
51772
  `would bind hub ${url} (${scope})${cloudNote}`
51326
51773
  );
51327
51774
  return 0;
51328
51775
  }
51776
+ try {
51777
+ writeHubEndpoint(runtime.dirs.workdir, {
51778
+ mode: cloud ? "cloud" : "local",
51779
+ url,
51780
+ project: identity.project,
51781
+ ...key.op || key.env ? { key } : {}
51782
+ });
51783
+ } catch (error) {
51784
+ if (error instanceof KxmHubConfigError) {
51785
+ print(
51786
+ runtime.io,
51787
+ runtime.json,
51788
+ { ok: false, command: "hub bind", error: error.code, detail: error.message },
51789
+ `${error.message}; no binding was written`
51790
+ );
51791
+ return 2;
51792
+ }
51793
+ throw error;
51794
+ }
51329
51795
  writeHubBinding({ ...record, boundAt: (/* @__PURE__ */ new Date()).toISOString() }, runtime.env);
51330
51796
  const { health, probeMs } = await probeHubHealth(url, runtime.fetchImpl);
51331
51797
  print(
51332
51798
  runtime.io,
51333
51799
  runtime.json,
51334
- { ok: true, command: "hub bind", url, scope, file, health, probeMs, ...cloudFields },
51800
+ { ok: true, command: "hub bind", url, scope, file, configFile, project: identity.project, health, probeMs, ...cloudFields },
51335
51801
  `bound hub ${url} \xB7 ${scope} \xB7 ${formatHubBindHealth(health)}${cloud ? cloudNote : scope === "remote" ? " \xB7 token leaves this machine" : ""}`
51336
51802
  );
51337
51803
  return 0;
@@ -51360,6 +51826,7 @@ async function cmdHubUnbind(runtime) {
51360
51826
  return 1;
51361
51827
  }
51362
51828
  removeHubBinding(runtime.env);
51829
+ deleteKxmConfigValue(findConfigRoot(runtime.dirs.workdir), "hub.mode");
51363
51830
  if (malformed) {
51364
51831
  print(runtime.io, runtime.json, { ok: true, command: "hub unbind", file }, "unbound hub (record was malformed)");
51365
51832
  return 0;
@@ -51764,10 +52231,10 @@ init_redact();
51764
52231
  init_telemetry();
51765
52232
  init_routing();
51766
52233
  init_prices();
51767
- import { spawnSync as spawnSync13 } from "node:child_process";
52234
+ import { spawnSync as spawnSync14 } from "node:child_process";
51768
52235
  import { createHash as createHash19 } from "node:crypto";
51769
52236
  import { existsSync as existsSync47, mkdtempSync as mkdtempSync3, readFileSync as readFileSync41, rmSync as rmSync13 } from "node:fs";
51770
- import { tmpdir as tmpdir2 } from "node:os";
52237
+ import { tmpdir as tmpdir3 } from "node:os";
51771
52238
  import { basename as basename11, extname as extname3, join as join55, resolve as resolve33 } from "node:path";
51772
52239
  import { createInterface as createInterface3 } from "node:readline";
51773
52240
 
@@ -52643,7 +53110,7 @@ ${divider}
52643
53110
 
52644
53111
  // plugins/kxm/src/ssh-remote.ts
52645
53112
  init_safety_integrity();
52646
- import { spawnSync as spawnSync12 } from "node:child_process";
53113
+ import { spawnSync as spawnSync13 } from "node:child_process";
52647
53114
  import { existsSync as existsSync44, mkdirSync as mkdirSync30, readFileSync as readFileSync39, readdirSync as readdirSync14, rmSync as rmSync12, statSync as statSync7 } from "node:fs";
52648
53115
  import { homedir as homedir8 } from "node:os";
52649
53116
  import { join as join52, resolve as resolve31 } from "node:path";
@@ -52719,7 +53186,7 @@ function parseSshConfig(configPath) {
52719
53186
  return [];
52720
53187
  }
52721
53188
  }
52722
- function resolveSshHostG(host, execFn = spawnSync12) {
53189
+ function resolveSshHostG(host, execFn = spawnSync13) {
52723
53190
  try {
52724
53191
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
52725
53192
  if (result.status !== 0 || !result.stdout) {
@@ -52785,7 +53252,7 @@ function buildSshArgs(options) {
52785
53252
  args.push(options.host);
52786
53253
  return args;
52787
53254
  }
52788
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync12) {
53255
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync13) {
52789
53256
  const resolvedDir = ensureSocketDir(socketDir);
52790
53257
  const controlPath = join52(resolvedDir, "%C");
52791
53258
  try {
@@ -52797,7 +53264,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
52797
53264
  return false;
52798
53265
  }
52799
53266
  }
52800
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync12) {
53267
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync13) {
52801
53268
  const resolvedDir = ensureSocketDir(socketDir);
52802
53269
  const controlPath = join52(resolvedDir, "%C");
52803
53270
  try {
@@ -52811,7 +53278,7 @@ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
52811
53278
  }
52812
53279
  function executeSshRun(params) {
52813
53280
  const startTime = Date.now();
52814
- const execSyncFn = params.execFn ?? spawnSync12;
53281
+ const execSyncFn = params.execFn ?? spawnSync13;
52815
53282
  if (params.action === "info") {
52816
53283
  if (params.host) {
52817
53284
  const hostInfo = resolveSshHostG(params.host, execSyncFn);
@@ -53956,7 +54423,7 @@ init_types();
53956
54423
  init_repo_root();
53957
54424
  function cliSpawn(runtime, command, args, extra) {
53958
54425
  if (runtime.io.spawnSync) return runtime.io.spawnSync(command, args);
53959
- const result = spawnSync13(command, [...args], {
54426
+ const result = spawnSync14(command, [...args], {
53960
54427
  encoding: "utf8",
53961
54428
  windowsHide: true,
53962
54429
  shell: process.platform === "win32",
@@ -53994,7 +54461,7 @@ function applyKxmPackageUpdate(runtime, notice) {
53994
54461
  detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
53995
54462
  };
53996
54463
  }
53997
- const releaseDir = mkdtempSync3(join55(tmpdir2(), "kxm-pkg-update-"));
54464
+ const releaseDir = mkdtempSync3(join55(tmpdir3(), "kxm-pkg-update-"));
53998
54465
  try {
53999
54466
  const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset);
54000
54467
  if (runtime.dryRun) {
@@ -54942,11 +55409,12 @@ function addGlobalOptions(command) {
54942
55409
  }
54943
55410
  async function ensureCliClient(runtime) {
54944
55411
  const serverUrl = runtime.serverUrl;
54945
- const project = defaultProjectName(runtime.cwd, runtime.env);
55412
+ const identity = resolveProjectIdentity(runtime.cwd, runtime.env);
55413
+ const project = identity.project;
54946
55414
  const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
54947
55415
  const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
54948
- const authToken = resolveAgentHubAuthToken(runtime.env, project);
54949
- if (!authToken) throw new AgentProjectTokenMissingError(project);
55416
+ const authToken = resolveAgentHubAuthToken(runtime.env, project, runtime.cwd);
55417
+ if (!authToken) throw new AgentProjectTokenMissingError(identity);
54950
55418
  const client = new HubClient({
54951
55419
  serverUrl,
54952
55420
  name,
@@ -55055,11 +55523,38 @@ async function dispatchAgentCliCommand(runtime, toolName, rawArgs) {
55055
55523
  print(
55056
55524
  runtime.io,
55057
55525
  runtime.json,
55058
- { ok: false, error: error.code, project: error.project, nextAction: "export_kxm_auth_token", detail: message },
55526
+ {
55527
+ ok: false,
55528
+ error: error.code,
55529
+ project: error.project,
55530
+ projectSource: error.projectSource,
55531
+ nextAction: "configure_hub_project",
55532
+ hint: error.hint,
55533
+ detail: message
55534
+ },
55059
55535
  message
55060
55536
  );
55061
55537
  return 2;
55062
55538
  }
55539
+ if (error instanceof HubHttpError && error.code === "project_token_missing") {
55540
+ const identity = resolveProjectIdentity(runtime.cwd, runtime.env);
55541
+ const missing = new AgentProjectTokenMissingError(identity);
55542
+ print(
55543
+ runtime.io,
55544
+ runtime.json,
55545
+ {
55546
+ ok: false,
55547
+ error: missing.code,
55548
+ project: identity.project,
55549
+ projectSource: identity.source,
55550
+ nextAction: "configure_hub_project",
55551
+ hint: missing.hint,
55552
+ detail: missing.message
55553
+ },
55554
+ missing.message
55555
+ );
55556
+ return 2;
55557
+ }
55063
55558
  if (error instanceof CloudTokenError) {
55064
55559
  print(
55065
55560
  runtime.io,
@@ -55547,7 +56042,7 @@ function createProgram(ctx, result, argv) {
55547
56042
  addGlobalOptions(hub.command("stop").description("Request managed hub and worker shutdown")).option("--wait-ms <ms>", "How long to wait for PID files to clear").action(async function hubStopAction(options) {
55548
56043
  result.code = await cmdStop(runtimeFrom(ctx, this), options.waitMs);
55549
56044
  });
55550
- addGlobalOptions(hub.command("bind").description("Bind this machine to a running hub").argument("<url>", "Hub base URL (http or https)")).option("--cloud", "Mark the hub remote even when the URL is a loopback forward").option("--token-env <name>", "Environment variable that holds the hub token; the token is not stored").option("--token-command <command>", "Program that prints the hub token; the token is not stored").action(async function hubBindAction(url, options) {
56045
+ addGlobalOptions(hub.command("bind").description("Bind this machine to a running hub").argument("<url>", "Hub base URL (http or https)")).option("--cloud", "Mark the hub remote even when the URL is a loopback forward").option("--token-env <name>", "Environment variable that holds the hub token; the token is not stored").option("--token-command <command>", "Program that prints the hub token; the token is not stored").option("--key-op <ref>", "op:// reference for the hub token; resolved with op read, never stored").action(async function hubBindAction(url, options) {
55551
56046
  result.code = await cmdHubBind(runtimeFrom(ctx, this), url, options);
55552
56047
  });
55553
56048
  addGlobalOptions(hub.command("unbind").description("Remove this machine's hub binding")).action(bind(cmdHubUnbind));
@@ -55715,6 +56210,10 @@ async function runCli(argv, env = process.env, io = { stdout: (text) => process.
55715
56210
  } catch (error) {
55716
56211
  if (error instanceof CommanderError) return mapCommanderError(error, argv, program2, io);
55717
56212
  if (error instanceof DryRunRefused) return error.code;
56213
+ if (error instanceof KxmHubConfigError) {
56214
+ print(io, hasJsonFlag(argv), { ok: false, error: error.code, detail: error.message }, error.message);
56215
+ return 2;
56216
+ }
55718
56217
  throw error;
55719
56218
  }
55720
56219
  }