@kontextmind/kxm 0.6.0 → 0.7.10

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 (175) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/agents/coordinator.yaml +9 -0
  3. package/.kxm/agents/critic-arch.yaml +13 -0
  4. package/.kxm/agents/critic-cli.yaml +13 -0
  5. package/.kxm/agents/implementer.yaml +13 -0
  6. package/.kxm/gates.yaml +8 -0
  7. package/.kxm/producers.yaml +22 -0
  8. package/.kxm/project.yaml +15 -0
  9. package/.kxm/roles/writer.yaml +7 -0
  10. package/.kxm/workflows/default.yaml +47 -0
  11. package/CHANGELOG.md +39 -7
  12. package/README.md +1 -0
  13. package/docs/README.md +5 -0
  14. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  15. package/docs/agent-skills.md +135 -0
  16. package/docs/architecture.md +1 -1
  17. package/docs/assignment-runner.md +21 -8
  18. package/docs/browser-automation.md +116 -0
  19. package/docs/configuration.md +11 -2
  20. package/docs/getting-started.md +21 -0
  21. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  22. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  23. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  24. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  25. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  26. package/docs/kb/how-to-take-over-session.md +32 -0
  27. package/docs/kb/why-authentication-disappeared.md +32 -0
  28. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  29. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  30. package/docs/kxm-handbook.md +3 -3
  31. package/docs/operations.md +24 -0
  32. package/docs/operator-pi-packages.md +63 -0
  33. package/docs/prompts/browser-annotate-feedback.md +41 -0
  34. package/docs/prompts/browser-diagnose-recover.md +38 -0
  35. package/docs/prompts/browser-explore.md +42 -0
  36. package/docs/prompts/browser-repro-fix.md +48 -0
  37. package/docs/prompts/browser-start.md +41 -0
  38. package/docs/prompts/browser-takeover.md +50 -0
  39. package/docs/skills/repo-work-delivery.md +107 -0
  40. package/docs/skills.md +2 -0
  41. package/docs/test-matrix.md +4 -3
  42. package/docs/troubleshooting.md +41 -1
  43. package/docs/vnext/validation.md +9 -0
  44. package/docs/webhook-workflows.md +2 -2
  45. package/examples/README.md +1 -1
  46. package/package.json +16 -17
  47. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  48. package/plugins/kxm/README.md +1 -1
  49. package/plugins/kxm/dist/cli.js +41620 -35578
  50. package/plugins/kxm/dist/core.js +271 -34
  51. package/plugins/kxm/dist/extension.js +7759 -86
  52. package/plugins/kxm/dist/mcp-server.js +75 -21
  53. package/plugins/kxm/dist/runtime.js +8218 -2328
  54. package/plugins/kxm/dist/server.js +3125 -2260
  55. package/plugins/kxm/dist/vnext-runtime-supervisor.js +5961 -661
  56. package/plugins/kxm/package.json +1 -1
  57. package/plugins/kxm/skills/SUITE.md +5 -0
  58. package/plugins/kxm/skills/hints.json +103 -0
  59. package/plugins/kxm/skills/kxm/SKILL.md +30 -83
  60. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  61. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  62. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  63. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  64. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  65. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  66. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  67. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
  68. package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
  69. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
  70. package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
  71. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +43 -0
  72. package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
  73. package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
  74. package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
  75. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +42 -0
  76. package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
  77. package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
  78. package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
  79. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
  80. package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
  81. package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
  82. package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
  83. package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
  84. package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
  85. package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
  86. package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
  87. package/plugins/kxm/src/autocomplete.ts +9 -3
  88. package/plugins/kxm/src/browser.ts +603 -0
  89. package/plugins/kxm/src/cli/context-skills.ts +373 -0
  90. package/plugins/kxm/src/cli/hub.ts +614 -0
  91. package/plugins/kxm/src/cli/roles.ts +615 -0
  92. package/plugins/kxm/src/cli/system.ts +906 -0
  93. package/plugins/kxm/src/cli/tasks.ts +364 -0
  94. package/plugins/kxm/src/cli/types.ts +270 -0
  95. package/plugins/kxm/src/cli/vnext.ts +698 -0
  96. package/plugins/kxm/src/cli/workflows.ts +699 -0
  97. package/plugins/kxm/src/cli.ts +362 -2849
  98. package/plugins/kxm/src/commands.ts +150 -8
  99. package/plugins/kxm/src/completion-install.ts +223 -0
  100. package/plugins/kxm/src/config.ts +7 -4
  101. package/plugins/kxm/src/context-packet.ts +172 -0
  102. package/plugins/kxm/src/database.ts +1 -1
  103. package/plugins/kxm/src/extension.ts +36 -1
  104. package/plugins/kxm/src/external-effects.ts +357 -8
  105. package/plugins/kxm/src/hub-env.ts +193 -0
  106. package/plugins/kxm/src/hub.ts +2 -4
  107. package/plugins/kxm/src/improve.ts +72 -0
  108. package/plugins/kxm/src/init-guide-setup.ts +547 -0
  109. package/plugins/kxm/src/local-snapshot.ts +1 -1
  110. package/plugins/kxm/src/mcp-server.ts +1 -1
  111. package/plugins/kxm/src/model-inventory.ts +127 -0
  112. package/plugins/kxm/src/modes.ts +348 -0
  113. package/plugins/kxm/src/policy-draft.d.mts +55 -0
  114. package/plugins/kxm/src/policy-draft.mjs +565 -0
  115. package/plugins/kxm/src/price-calc.ts +17 -18
  116. package/plugins/kxm/src/prices.ts +32 -16
  117. package/plugins/kxm/src/producers.ts +71 -0
  118. package/plugins/kxm/src/protocol.ts +111 -0
  119. package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
  120. package/plugins/kxm/src/restricted-yaml.mjs +145 -0
  121. package/plugins/kxm/src/role.ts +710 -0
  122. package/plugins/kxm/src/routing.ts +99 -1
  123. package/plugins/kxm/src/runtime.ts +4 -0
  124. package/plugins/kxm/src/safety-integrity.ts +76 -0
  125. package/plugins/kxm/src/session-work.ts +9 -2
  126. package/plugins/kxm/src/sqlite.ts +76 -0
  127. package/plugins/kxm/src/ssh-remote.ts +560 -0
  128. package/plugins/kxm/src/store.ts +1 -1
  129. package/plugins/kxm/src/studio-layout.ts +660 -17
  130. package/plugins/kxm/src/subagent-control.ts +312 -0
  131. package/plugins/kxm/src/suggest.ts +7 -13
  132. package/plugins/kxm/src/telemetry.ts +82 -0
  133. package/plugins/kxm/src/tui.ts +140 -0
  134. package/plugins/kxm/src/vnext-bindings.ts +1 -1
  135. package/plugins/kxm/src/vnext-config.ts +53 -111
  136. package/plugins/kxm/src/vnext-engine-command.ts +2 -0
  137. package/plugins/kxm/src/vnext-engine.ts +214 -62
  138. package/plugins/kxm/src/vnext-harness.ts +336 -84
  139. package/plugins/kxm/src/vnext-oneshot-evidence.ts +117 -0
  140. package/plugins/kxm/src/vnext-oneshot-process.ts +187 -0
  141. package/plugins/kxm/src/vnext-oneshot-producer.ts +182 -224
  142. package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
  143. package/plugins/kxm/src/vnext-runtime-store.ts +36 -2
  144. package/plugins/kxm/src/vnext-runtime-supervisor.ts +122 -5
  145. package/plugins/kxm/src/vnext-runtime.ts +14 -0
  146. package/plugins/kxm/src/workflow-manager.ts +392 -0
  147. package/plugins/kxm/src/workflow-tui.ts +255 -0
  148. package/plugins/kxm/src/workflow.ts +144 -0
  149. package/schemas/policy-draft/README.md +17 -0
  150. package/schemas/policy-draft/model.v2.schema.json +140 -0
  151. package/schemas/policy-draft/role.v2.schema.json +91 -0
  152. package/schemas/vnext/modes.schema.json +56 -0
  153. package/schemas/vnext/role.schema.json +76 -0
  154. package/schemas/vnext/run-event.schema.json +1 -0
  155. package/scripts/assignment-run.d.mts +1 -1
  156. package/scripts/assignment-run.mjs +44 -35
  157. package/scripts/check-generated.mjs +33 -9
  158. package/scripts/emit-codex-artifacts.mjs +255 -11
  159. package/scripts/harness-run.d.mts +12 -4
  160. package/scripts/harness-run.mjs +65 -17
  161. package/scripts/kxm-bump-version.mjs +146 -0
  162. package/scripts/kxm-hub.mjs +150 -2
  163. package/scripts/kxm-publish-npm.mjs +3 -1
  164. package/scripts/kxm-release-github.mjs +3 -1
  165. package/scripts/kxm.mjs +0 -0
  166. package/scripts/native-critic.d.mts +5 -0
  167. package/scripts/native-critic.mjs +60 -0
  168. package/.kxm/config/README.md +0 -5
  169. package/.kxm/config/agents.json +0 -43
  170. package/.kxm/config/env.example +0 -56
  171. package/.kxm/config/update.example.yaml +0 -9
  172. package/.kxm/config/workflows/fix.json +0 -160
  173. package/.kxm/config/workflows/jira-development.json +0 -116
  174. package/.kxm/config/workflows/provenance-quorum.json +0 -150
  175. package/.kxm/config/workflows/v04-dogfood.json +0 -72
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn, spawnSync } from "node:child_process";
4
- import { closeSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { randomBytes } from "node:crypto";
5
+ import { closeSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
5
7
  import { join, resolve } from "node:path";
6
8
  import { fileURLToPath } from "node:url";
7
9
 
@@ -15,6 +17,19 @@ const startedAt = new Date().toISOString();
15
17
  const controlFile = "hub.stop";
16
18
  const controlPath = join(stateDir, controlFile);
17
19
 
20
+ // --- Persistent hub credentials (kxm.hub-env.v1) -------------------------
21
+ // Precedence: KXM_AUTH_TOKEN/KXM_PROJECT_TOKENS env, then the persisted
22
+ // user-state file, then a generated admin token that is persisted so later
23
+ // restarts, workers, and dashboards reuse the same credential.
24
+ function resolveUserStateRoot() {
25
+ const explicit = process.env.KXM_STATE_HOME?.trim();
26
+ if (explicit) return resolve(explicit);
27
+ const home = homedir();
28
+ if (process.platform === "win32") return resolve(process.env.LOCALAPPDATA?.trim() || join(home, "AppData", "Local"), "KXM");
29
+ if (process.platform === "darwin") return resolve(home, "Library", "Application Support", "KXM");
30
+ return resolve(process.env.XDG_STATE_HOME?.trim() || join(home, ".local", "state"), "kxm");
31
+ }
32
+
18
33
  function processExists(pid) {
19
34
  try {
20
35
  process.kill(pid, 0);
@@ -24,6 +39,119 @@ function processExists(pid) {
24
39
  }
25
40
  }
26
41
 
42
+ function parseHubEnvFile() {
43
+ const file = join(resolveUserStateRoot(), "hub-env.json");
44
+ let raw;
45
+ try {
46
+ raw = readFileSync(file, "utf8");
47
+ } catch (error) {
48
+ if (error?.code === "ENOENT") return { file, record: undefined };
49
+ throw new Error(`KXM hub could not read the persisted hub env at ${file}: ${error.message}`);
50
+ }
51
+ let parsed;
52
+ try {
53
+ parsed = JSON.parse(raw);
54
+ } catch (error) {
55
+ throw new Error(`KXM hub env file is malformed at ${file}; fix or remove it (it holds only KXM_AUTH_TOKEN/KXM_PROJECT_TOKENS values): ${error.message}`);
56
+ }
57
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schema !== "kxm.hub-env.v1") {
58
+ throw new Error(`KXM hub env file at ${file} does not use schema kxm.hub-env.v1`);
59
+ }
60
+ return { file, record: parsed };
61
+ }
62
+
63
+ function generateAdminToken() {
64
+ return `kxm_admin_${randomBytes(24).toString("base64url")}`;
65
+ }
66
+
67
+ function persistHubEnv(file, record) {
68
+ mkdirSync(resolve(file, ".."), { recursive: true });
69
+ const temporary = join(resolve(file, ".."), `.hub-env-${process.pid}.tmp`);
70
+ writeFileSync(temporary, `${JSON.stringify(record, undefined, 2)}\n`, { encoding: "utf8", mode: 0o600 });
71
+ try {
72
+ renameSync(temporary, file);
73
+ } catch (error) {
74
+ rmSync(temporary, { force: true });
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ function parseProjectTokensEnv(raw) {
80
+ const trimmed = raw?.trim();
81
+ if (!trimmed) return undefined;
82
+ let parsed;
83
+ try {
84
+ parsed = JSON.parse(trimmed);
85
+ } catch {
86
+ throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
87
+ }
88
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
89
+ throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
90
+ }
91
+ for (const [project, token] of Object.entries(parsed)) {
92
+ if (!project.trim() || typeof token !== "string" || !token.trim()) {
93
+ throw new Error("KXM_PROJECT_TOKENS must contain non-empty project names and token strings");
94
+ }
95
+ }
96
+ return parsed;
97
+ }
98
+
99
+ /** Resolve credentials, persisting generated/env values into the user-state file. */
100
+ function resolveCredentials() {
101
+ const { file, record } = parseHubEnvFile();
102
+ const envToken = process.env.KXM_AUTH_TOKEN?.trim();
103
+ const envProjectTokens = parseProjectTokensEnv(process.env.KXM_PROJECT_TOKENS);
104
+ let authToken = envToken || record?.authToken;
105
+ let authTokenSource = envToken ? "env" : record?.authToken ? "file" : "none";
106
+ if (!authToken) {
107
+ authToken = generateAdminToken();
108
+ authTokenSource = "generated";
109
+ }
110
+ const projectTokens = envProjectTokens || record?.projectTokens;
111
+ const projectTokensSource = envProjectTokens ? "env" : record?.projectTokens ? "file" : "none";
112
+ const envChanged = (authTokenSource === "env" && record?.authToken !== authToken)
113
+ || (projectTokensSource === "env" && JSON.stringify(record?.projectTokens ?? undefined) !== JSON.stringify(projectTokens ?? undefined));
114
+ if (authTokenSource === "generated" || envChanged) {
115
+ persistHubEnv(file, {
116
+ schema: "kxm.hub-env.v1",
117
+ createdAt: record?.createdAt || new Date().toISOString(),
118
+ ...(authToken ? { authToken } : {}),
119
+ ...(projectTokens ? { projectTokens } : {}),
120
+ });
121
+ }
122
+ return { authToken, authTokenSource, projectTokens, projectTokensSource, file };
123
+ }
124
+
125
+ let credentials;
126
+ try {
127
+ credentials = resolveCredentials();
128
+ } catch (error) {
129
+ process.stderr.write(`kxm hub: ${error.message}\n`);
130
+ process.exit(1);
131
+ }
132
+ const launchEnv = { ...process.env };
133
+ if (credentials.authToken) launchEnv.KXM_AUTH_TOKEN = credentials.authToken;
134
+ if (credentials.projectTokens) launchEnv.KXM_PROJECT_TOKENS = JSON.stringify(credentials.projectTokens);
135
+ if (credentials.authTokenSource !== "env") {
136
+ process.stdout.write(`kxm hub: using ${credentials.authTokenSource === "generated" ? "newly generated" : "persisted"} KXM_AUTH_TOKEN from ${credentials.file}\n`);
137
+ }
138
+
139
+ function terminateTree(pid) {
140
+ // Best-effort cleanup of an orphaned hub server whose wrapper died
141
+ // without reclaiming (e.g. SIGKILL). The wrapper could not be stopped
142
+ // gracefully in that state, so the server is asked to shut down first and
143
+ // forced after a short grace period.
144
+ if (!Number.isInteger(pid) || pid <= 0) return;
145
+ try { process.kill(pid, "SIGTERM"); } catch { /* already gone */ }
146
+ const deadline = Date.now() + 3000;
147
+ while (Date.now() < deadline) {
148
+ if (!processExists(pid)) return;
149
+ const wait = Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
150
+ if (wait !== "timed-out") return;
151
+ }
152
+ try { process.kill(pid, "SIGKILL"); } catch { /* already gone */ }
153
+ }
154
+
27
155
  function claimPidFile() {
28
156
  const record = { version: 1, pid: process.pid, role: "hub", startedAt, controlFile };
29
157
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -50,6 +178,19 @@ function claimPidFile() {
50
178
  if (existing?.version === 1 && existing.role === "hub" && Number.isInteger(existing.pid) && processExists(existing.pid)) {
51
179
  throw new Error(`KXM hub is already managed by PID ${existing.pid}`);
52
180
  }
181
+ // A well-formed claim whose wrapper is dead is safe to reclaim. Keep
182
+ // malformed claims fail-closed because they may belong to another tool.
183
+ if (existing?.version === 1 && existing.role === "hub" && Number.isInteger(existing.pid) && !processExists(existing.pid)) {
184
+ // The wrapper is gone. An orphaned server child recorded by the dead
185
+ // wrapper is cleaned up before reclaiming so a SIGKILLed wrapper does
186
+ // not leave a port-holding server behind.
187
+ if (Number.isInteger(existing.serverPid) && existing.serverPid !== existing.pid && processExists(existing.serverPid)) {
188
+ process.stderr.write(`kxm hub: cleaning up orphaned hub server PID ${existing.serverPid} from a dead wrapper\n`);
189
+ terminateTree(existing.serverPid);
190
+ }
191
+ rmSync(pidPath, { force: true });
192
+ continue;
193
+ }
53
194
  throw new Error(
54
195
  `KXM hub PID claim is stale at ${pidPath}; remove it only after verifying no hub process is running`,
55
196
  );
@@ -73,8 +214,15 @@ const child = spawn(process.execPath, [
73
214
  ...process.argv.slice(2),
74
215
  ], {
75
216
  stdio: ["inherit", "inherit", "inherit", "ipc"],
76
- env: process.env,
217
+ env: launchEnv,
77
218
  });
219
+ // Record the server child PID inside the claim so a later wrapper can clean
220
+ // up an orphaned server if this wrapper is killed without running cleanupPid.
221
+ if (child.pid) {
222
+ try {
223
+ writeFileSync(pidPath, `${JSON.stringify({ version: 1, pid: process.pid, serverPid: child.pid, role: "hub", startedAt, controlFile })}\n`, "utf8");
224
+ } catch { /* claim already written; serverPid tracking is best-effort */ }
225
+ }
78
226
  let stopping = false;
79
227
 
80
228
  child.once("error", (error) => {
@@ -275,7 +275,9 @@ export async function main(env = process.env, stdout = process.stdout, stderr =
275
275
  const token = env.GITHUB_TOKEN || env.GH_TOKEN;
276
276
  const npmToken = env.NODE_AUTH_TOKEN || env.NPM_TOKEN;
277
277
  const repo = env.GITHUB_REPOSITORY;
278
- const tag = env.GITHUB_REF_NAME || env.TAG;
278
+ // KXM_RELEASE_TAG is explicit for workflow_dispatch runs; GITHUB_REF_NAME
279
+ // is the branch name for that event, not the requested release tag.
280
+ const tag = env.KXM_RELEASE_TAG || env.TAG || env.GITHUB_REF_NAME;
279
281
  const assetName = env.KXM_ASSET;
280
282
  const assetPath = env.KXM_ASSET_PATH;
281
283
  const declaredSha256 = env.KXM_ASSET_SHA256;
@@ -427,7 +427,9 @@ export function fileSha256(path) {
427
427
  export async function main(env = process.env, stdout = process.stdout, stderr = process.stderr) {
428
428
  const token = env.GITHUB_TOKEN || env.GH_TOKEN;
429
429
  const repo = env.GITHUB_REPOSITORY;
430
- const tag = env.GITHUB_REF_NAME;
430
+ // KXM_RELEASE_TAG is explicit for workflow_dispatch runs; GITHUB_REF_NAME
431
+ // is the branch name for that event, not the requested release tag.
432
+ const tag = env.KXM_RELEASE_TAG || env.TAG || env.GITHUB_REF_NAME;
431
433
  const assetName = env.KXM_ASSET;
432
434
  const assetPath = env.KXM_ASSET_PATH;
433
435
  const declared = env.KXM_ASSET_SHA256;
package/scripts/kxm.mjs CHANGED
File without changes
@@ -0,0 +1,5 @@
1
+ export function nativeCriticLaunch(harness: string): {
2
+ model: string;
3
+ command: string;
4
+ args: string[];
5
+ };
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { HubClient } from "../plugins/kxm/dist/client.js";
7
+ import { buildArgv } from "./harness-run.mjs";
8
+
9
+ // Launch intent only: these labels are not observed model identity or admission.
10
+ export function nativeCriticLaunch(harness) {
11
+ if (!["fable", "astra"].includes(harness)) {
12
+ throw new Error("usage: native-critic.mjs <fable|astra> <prompt>");
13
+ }
14
+ const model = harness === "fable" ? "fable" : "gpt-6-astra";
15
+ const command = harness === "fable" ? "claude" : "codex";
16
+ const args = harness === "fable"
17
+ ? ["-p", "--model", model, "--output-format", "json", "--no-session-persistence", "--tools", "Read,Glob,Grep", "--setting-sources", "user"]
18
+ : buildArgv({ harness: "codex", model, effort: "low", permission: "read-only", prompt_file: "-" });
19
+ return { model, command, args };
20
+ }
21
+
22
+ async function main() {
23
+ const [harness, ...promptParts] = process.argv.slice(2);
24
+ let launch;
25
+ try {
26
+ launch = nativeCriticLaunch(harness);
27
+ } catch (error) {
28
+ console.error(error.message);
29
+ process.exitCode = 2;
30
+ return;
31
+ }
32
+ const { model, command, args } = launch;
33
+ const env = process.env;
34
+ const serverUrl = env.KXM_SERVER_URL || "http://127.0.0.1:7331";
35
+ const outDir = env.KXM_CRITIC_DIR || join(process.cwd(), ".kxm", "assets", "critic-reviews");
36
+ mkdirSync(outDir, { recursive: true });
37
+ const client = new HubClient({ serverUrl, authToken: env.KXM_AUTH_TOKEN, name: `${harness}-critic`, purpose: "independent CLI critic", project: env.KXM_PROJECT || ".kxm", model });
38
+ const prompt = promptParts.join(" ");
39
+ const agent = await client.start(() => {});
40
+ const result = await new Promise((resolve) => {
41
+ const child = spawn(command, args, { env, stdio: ["pipe", "pipe", "pipe"], shell: false });
42
+ let stdout = "", stderr = "";
43
+ child.stdout.on("data", (chunk) => { stdout += chunk; });
44
+ child.stderr.on("data", (chunk) => { stderr += chunk; });
45
+ child.on("error", (error) => resolve({ code: null, stdout, stderr: String(error) }));
46
+ child.on("close", (code, signal) => resolve({ code, signal, stdout, stderr }));
47
+ child.stdin.end(prompt);
48
+ });
49
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
50
+ const path = join(outDir, `${harness}-${stamp}.json`);
51
+ writeFileSync(path, JSON.stringify({ schema: "kxm.native-critic-review.v1", harness, model, agentId: agent.id, completedAt: new Date().toISOString(), ...result }, null, 2) + "\n", { mode: 0o600 });
52
+ try { await client.send({ target: env.KXM_REVIEW_TARGET || agent.id, content: `${harness} review completed; artifact=${path}; exit=${String(result.code)}`, delivery: "followUp", idempotencyKey: `critic:${harness}:${stamp}` }); } catch { /* Transport artifact remains, not approval or hub peer-reply evidence. */ }
53
+ await client.stop();
54
+ if (result.code !== 0) process.exit(result.code ?? 1);
55
+ console.log(path);
56
+ }
57
+
58
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
59
+ await main();
60
+ }
@@ -1,5 +0,0 @@
1
- # Workspace configuration
2
-
3
- Store reviewable, repository-specific harness and workflow configuration here. `env.example` documents supported environment settings. `workflows/` contains the complete Jira development lifecycle, the v0.4 dogfood workflow, and the provenance/quorum workflow used by parser and evidence-policy tests.
4
-
5
- Configuration files may reference secret environment-variable names such as `JIRA_WEBHOOK_SECRET`; they must never contain the secret values. Set `KXM_WEBHOOK_WORKFLOWS_FILE` to the chosen workflow file.
@@ -1,43 +0,0 @@
1
- {
2
- "schema": "kxm.agents.v1",
3
- "project": "kxm",
4
- "host": "local",
5
- "agents": [
6
- {
7
- "name": "grok",
8
- "kind": "agent",
9
- "driver": "ai",
10
- "model": "xai/grok-4.6",
11
- "thinking": "xhigh",
12
- "purpose": "Sole writer; independent critic of kxm CLI and envelopes"
13
- },
14
- {
15
- "name": "fable",
16
- "kind": "agent",
17
- "driver": "ai",
18
- "model": "anthropic/claude-fable-5-1",
19
- "thinking": "xhigh",
20
- "purpose": "Independent critic: architecture and permissions"
21
- },
22
- {
23
- "name": "gpt-sol",
24
- "kind": "agent",
25
- "driver": "ai",
26
- "model": "openai-codex/gpt-5.6-sol",
27
- "thinking": "xhigh",
28
- "purpose": "Independent critic: CLI contracts and documentation"
29
- },
30
- {
31
- "name": "kimi",
32
- "kind": "agent",
33
- "driver": "ai",
34
- "model": "kimi-coding/kimi-for-coding",
35
- "thinking": "max",
36
- "purpose": "Independent critic: mapping gaps and portability"
37
- }
38
- ],
39
- "ownership": {
40
- "writeAgent": "grok",
41
- "peerAgents": ["fable", "gpt-sol", "kimi"]
42
- }
43
- }
@@ -1,56 +0,0 @@
1
- KXM_HOST=127.0.0.1
2
- KXM_PORT=7331
3
- KXM_AUTH_TOKEN=replace-with-a-long-random-token
4
- KXM_WORKSPACE_DIR=.kxm
5
- # KXM_CONFIG_DIR=.kxm/config
6
- # KXM_LOGS_DIR=.kxm/logs
7
- # KXM_ASSETS_DIR=.kxm/assets
8
- # KXM_STATE_DIR=.kxm/state
9
- # KXM_DATA_PATH=.kxm/state/kxm.db
10
- # KXM_LOG_PATH=.kxm/logs/kxm-hub.jsonl
11
- KXM_PROJECT_TOKENS={"my-project":"replace-with-a-project-token"}
12
- KXM_MESSAGE_TTL_MS=86400000
13
- KXM_MESSAGE_RETENTION_MS=604800000
14
- KXM_RATE_LIMIT_MAX=600
15
- KXM_RATE_LIMIT_WINDOW_MS=60000
16
- KXM_WEBHOOK_WORKFLOWS_FILE=.kxm/config/workflows/jira-development.json
17
- JIRA_WEBHOOK_SECRET=replace-with-a-high-entropy-webhook-secret
18
- WORKFLOW_SIGNAL_SECRET=replace-with-a-separate-callback-secret
19
-
20
- # Client / Pi extension settings
21
- KXM_SERVER_URL=http://127.0.0.1:7331
22
- KXM_PROJECT=my-project
23
- KXM_AGENT_NAME=planner
24
- KXM_AGENT_PURPOSE=Plans work and reviews architecture
25
-
26
- # Long-lived headless Pi worker
27
- KXM_WORKDIR=C:\path\to\project
28
- KXM_WORKER_CONTINUE=true
29
- # Opt in after reviewing the fresh scoped-history upgrade behavior:
30
- # workflow = stable default Pi session plus one session per durable workflow run
31
- # KXM_WORKER_SESSION_ISOLATION=workflow
32
- # KXM_WORKER_MAX_RUN_SESSIONS=128
33
- # KXM_WORKER_INITIAL_CONTINUE=false
34
- # KXM_WORKER_LOG_PATH=.kxm/logs/kxm-worker-planner.jsonl
35
- # KXM_AGENT_LOG_PATH=.kxm/logs/pi-agent-planner.log
36
- # KXM_WORKER_MAX_RESTARTS=20
37
- # KXM_WORKER_MODEL=provider/model
38
- # KXM_WORKER_FALLBACK_MODELS=provider/fallback-one,provider/fallback-two
39
- # KXM_WORKER_PROVIDER_RETRY_MS=60000
40
- # KXM_WORKER_TOOL_TIMEOUT_MS=1860000
41
- # KXM_WORKER_ACTIVATION_TIMEOUT_MS=60000
42
- # KXM_WORKER_TOOLS=read,grep,find,ls,kxm_fanout,kxm_workflow_get
43
- # KXM_WORKER_EXTENSION_PATHS=plugins/kxm/src/extension.ts;C:\path\to\provider\extension.ts
44
- # KXM_WORKER_SKILL_PATHS=plugins/kxm/skills/kxm
45
-
46
- # Optional proposed-improvement telemetry classification override: cli or project
47
- # KXM_IMPROVE_TARGET=project
48
-
49
- # Opt-in Nous Pi providers (unset = no fetch, no registration)
50
- # KXM_NOUS_PROVIDERS=direct
51
- # KXM_NOUS_PROVIDERS=proxy
52
- # KXM_NOUS_PROVIDERS=direct,proxy
53
- # NOUS_API_KEY=
54
- # KXM_NOUS_PROXY_URL=http://127.0.0.1:8645/v1
55
- # KXM_NOUS_DISCOVERY_TIMEOUT_MS=5000
56
- # KXM_NOUS_CATALOG_FILE=
@@ -1,9 +0,0 @@
1
- # Copy this file to the per-user state root. kxm never reads a project
2
- # .kxm/update.yaml (it prints a warning and continues). Locations:
3
- # Windows: %LOCALAPPDATA%\KXM\update.yaml
4
- # macOS: ~/Library/Application Support/KXM/update.yaml
5
- # Linux: ${XDG_STATE_HOME:-~/.local/state}/kxm/update.yaml
6
- # Override the state root with an absolute KXM_STATE_HOME for tests.
7
- schema: kxm.update.v1
8
- auto: false
9
- source: github
@@ -1,160 +0,0 @@
1
- [
2
- {
3
- "id": "fix",
4
- "source": "generic",
5
- "project": "kxm",
6
- "target": "coordinator",
7
- "secretEnv": "FIX_WEBHOOK_SECRET",
8
- "signalSecretEnv": "WORKFLOW_SIGNAL_SECRET",
9
- "delivery": "followUp",
10
- "maxTransitions": 28,
11
- "reproOracle": { "stageId": "repro-review", "evidenceKey": "repro" },
12
- "planHash": { "stageId": "final-plan", "evidenceKey": "plan" },
13
- "requirePlanHash": ["implement", "local-verify", "delivery"],
14
- "promptTemplate": "Bug report: {{task}}\n\nRun the /fix workflow: two-phase reproduction before any production edit, approved plan before implementation, bounded rework on failure. The confirmed reproduction is an immutable oracle.\n\nHard rules: (1) The oracle must invoke the diagnosis-named production callables on the reporter path — a sibling API or newer stack is invalid even if it fails. (2) After repro_invalidated, rewrite the draft against that named seam; never checkpoint blocked because the previous seam was wrong or because a method news its own DbContext. (3) Search existing test hosts (controller fixtures, HttpContext helpers, TransactionScope, read-only call against existing data) before declaring a seam untestable.",
15
- "stages": [
16
- {
17
- "id": "intake",
18
- "label": "Intake",
19
- "area": "workflow",
20
- "instructions": "Classify the bug report: expected vs actual behavior, affected surface, severity, and reproduction hints. Record a journal 'plan' entry with the classification. If the report is not actionable, checkpoint with outcome 'blocked'.",
21
- "requiredEvidence": ["classification"],
22
- "maxAttempts": 2,
23
- "on": { "passed": "repro-explore", "blocked": "$terminal" }
24
- },
25
- {
26
- "id": "repro-explore",
27
- "label": "Repro explore (read-only)",
28
- "area": "implementation",
29
- "instructions": "Read and search only: locate the bug, note prior reproductions, test conventions, and similar incidents via kxm context. No source or test edits. Produce a diagnosis that names the exact production callables on the reporter path (handler, data method, or rendered component), root-cause hypothesis, expected/actual behavior, reproduction strategy, reference tests in existing area folders, and target assertions. Secondary surfaces (other API versions, other tabs) are notes only — they are not substitute oracles. Record the diagnosis with journal category 'hypothesis'.",
30
- "requiredEvidence": ["diagnosis"],
31
- "maxAttempts": 3,
32
- "on": { "passed": "repro-write", "blocked": "$terminal" }
33
- },
34
- {
35
- "id": "repro-write",
36
- "label": "Repro write (tests only)",
37
- "area": "implementation",
38
- "instructions": "Write and execute the failing reproduction. Tests and test helpers only — production and configuration stay byte-for-byte unchanged. Every target assertion must call the diagnosis-named production handler, production seam, or rendered production component and fail for the reported reason rather than compilation/setup. A sibling API, newer stack, or 'also used by some admins' handler is invalid even if it fails. A method that news its own DbContext is not grounds for blocked: use existing test hosts (controller fixtures, HttpContext helpers, TransactionScope so a new context still rolls back) or a read-only call against existing data with no seed. After repro_invalidated, write a new draft against the named seam; do not checkpoint outcome blocked because the previous draft was the wrong seam. Checkpoint failed (outcome failed) to consume an attempt and rewrite. blocked is only when the ticket is not a code defect. Cite evidence key 'repro' with every test path, sha256, exact command, and failed/passed counts. This is a reviewable draft; it is not immutable until independent repro-review passes.",
39
- "requiredEvidence": ["repro"],
40
- "maxAttempts": 3,
41
- "on": { "passed": "repro-review", "failed": "repro-write", "blocked": "$terminal" }
42
- },
43
- {
44
- "id": "repro-review",
45
- "label": "Independent reproduction review",
46
- "area": "gates",
47
- "instructions": "Before the oracle is captured, send the diagnosis, test files/hashes, exact failing results, and production/config git status to both independent critics. They must verify production coupling to the diagnosis-named reporter-path seam (a sibling API is invalid), full expected-behavior coverage, failure causality, immutability feasibility, and absence of production/config edits. Caller-authored evidence cannot satisfy peer review. If either critic finds an omission or invalid test, checkpoint status 'failed' with outcome 'repro_invalidated' and return to repro-write. Only unanimous validation may pass; cite the unchanged draft under evidence key 'repro'. Passing this stage captures the immutable oracle.",
48
- "requiredEvidence": ["repro", "review"],
49
- "maxAttempts": 3,
50
- "evidencePolicies": {
51
- "review": {
52
- "kind": "peer-reply",
53
- "minProducers": 2,
54
- "eligibleAgents": ["critic-1", "critic-2"],
55
- "acceptedStatuses": ["replied"]
56
- }
57
- },
58
- "on": { "passed": "plan", "repro_invalidated": "repro-write", "blocked": "$terminal" }
59
- },
60
- {
61
- "id": "plan",
62
- "label": "Plan",
63
- "area": "workflow",
64
- "instructions": "Using the diagnosis and the confirmed reproduction, write the implementation plan: changed files, approach, risks, and how the reproduction proves the fix. Cite evidence key 'plan' with the plan artifact path and content hash.",
65
- "requiredEvidence": ["plan"],
66
- "maxAttempts": 3,
67
- "on": { "passed": "critics", "blocked": "$terminal" }
68
- },
69
- {
70
- "id": "critics",
71
- "label": "Independent critics",
72
- "area": "gates",
73
- "instructions": "Two independent critics review the plan for correctness, scope creep, and risk. Both must reply through the mesh; caller-authored evidence cannot satisfy this policy. If the plan is invalidated, checkpoint with outcome 'plan_invalidated'.",
74
- "requiredEvidence": ["critique"],
75
- "maxAttempts": 3,
76
- "evidencePolicies": {
77
- "critique": {
78
- "kind": "peer-reply",
79
- "minProducers": 2,
80
- "eligibleAgents": ["critic-1", "critic-2"],
81
- "acceptedStatuses": ["replied"]
82
- }
83
- },
84
- "on": { "passed": "final-plan", "plan_invalidated": "plan", "blocked": "$terminal" }
85
- },
86
- {
87
- "id": "final-plan",
88
- "label": "Final plan",
89
- "area": "workflow",
90
- "instructions": "Fold critic feedback into the final plan. Re-cite evidence key 'plan' with the final artifact path and content hash. The hash of this stage's plan evidence becomes the approved plan hash required by implementation.",
91
- "requiredEvidence": ["plan"],
92
- "maxAttempts": 2,
93
- "on": { "passed": "approval", "plan_invalidated": "plan", "blocked": "$terminal" }
94
- },
95
- {
96
- "id": "approval",
97
- "label": "Human approval",
98
- "area": "security",
99
- "instructions": "Human operator approves the plan. Cite evidence key 'signoff' with the approver identity and approval reference. A 'rejected' outcome routes back to planning; this stage cannot be bypassed by any transition.",
100
- "requiredEvidence": ["signoff"],
101
- "maxAttempts": 8,
102
- "on": { "passed": "implement", "rejected": "plan", "blocked": "$terminal" }
103
- },
104
- {
105
- "id": "implement",
106
- "label": "Implement",
107
- "area": "implementation",
108
- "instructions": "Implement the approved plan. The reproduction oracle is immutable: local verification must fail before the fix and pass after it. Cite evidence keys 'diff' and 'repro' (same value as the oracle).",
109
- "requiredEvidence": ["diff"],
110
- "maxAttempts": 4,
111
- "on": { "passed": "local-verify", "blocked": "$terminal" }
112
- },
113
- {
114
- "id": "local-verify",
115
- "label": "Local verify",
116
- "area": "gates",
117
- "instructions": "Run the repo-defined gates: unit, lint, typecheck, build, and the confirmed reproduction test. Cite evidence key 'gates' with per-gate results. 'implementation_failure' reworks implementation; 'plan_invalidated' routes back to planning rather than expanding scope.",
118
- "requiredEvidence": ["gates"],
119
- "maxAttempts": 4,
120
- "on": {
121
- "passed": "delivery",
122
- "implementation_failure": "implement",
123
- "plan_invalidated": "plan",
124
- "blocked": "$terminal"
125
- }
126
- },
127
- {
128
- "id": "delivery",
129
- "label": "Delivery",
130
- "area": "workflow",
131
- "instructions": "Create and push the branch and merge request with the reproduction, plan, and diff references. Delivery never auto-merges: CI and review outcomes are watched in the next stage.",
132
- "requiredEvidence": ["mr"],
133
- "maxAttempts": 2,
134
- "on": { "passed": "ci-watch", "blocked": "$terminal" }
135
- },
136
- {
137
- "id": "ci-watch",
138
- "label": "CI and review watch",
139
- "area": "gates",
140
- "instructions": "Watch required CI checks and review status. On failure, checkpoint with outcome 'failure' to rework through bounded transitions. On success, continue to human acceptance.",
141
- "requiredEvidence": ["ci"],
142
- "maxAttempts": 6,
143
- "on": {
144
- "passed": "ready-for-human-acceptance",
145
- "failure": "implement",
146
- "blocked": "$terminal"
147
- }
148
- },
149
- {
150
- "id": "ready-for-human-acceptance",
151
- "label": "Ready for human acceptance",
152
- "area": "workflow",
153
- "instructions": "Summarize the fix: reproduction (immutable oracle), approved plan hash, diff, gate results, CI outcome, and the full claim-to-evidence trail from the run journal. Checkpoint 'passed' to complete the run; a human performs final acceptance outside the workflow.",
154
- "requiredEvidence": ["summary"],
155
- "maxAttempts": 2,
156
- "on": { "passed": "$terminal", "blocked": "$terminal" }
157
- }
158
- ]
159
- }
160
- ]