@kontextmind/kxm 0.6.0 → 0.7.0

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 (117) 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 +5 -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 +2 -0
  14. package/docs/agent-skills.md +118 -0
  15. package/docs/architecture.md +1 -1
  16. package/docs/assignment-runner.md +21 -8
  17. package/docs/configuration.md +2 -2
  18. package/docs/kxm-handbook.md +3 -3
  19. package/docs/operator-pi-packages.md +63 -0
  20. package/docs/skills/repo-work-delivery.md +102 -0
  21. package/docs/test-matrix.md +4 -3
  22. package/docs/troubleshooting.md +19 -0
  23. package/docs/vnext/validation.md +9 -0
  24. package/docs/webhook-workflows.md +2 -2
  25. package/examples/README.md +1 -1
  26. package/package.json +16 -17
  27. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  28. package/plugins/kxm/README.md +1 -1
  29. package/plugins/kxm/dist/cli.js +6875 -3672
  30. package/plugins/kxm/dist/core.js +214 -34
  31. package/plugins/kxm/dist/extension.js +7721 -85
  32. package/plugins/kxm/dist/mcp-server.js +75 -21
  33. package/plugins/kxm/dist/runtime.js +5403 -1014
  34. package/plugins/kxm/dist/server.js +3008 -2268
  35. package/plugins/kxm/dist/vnext-runtime-supervisor.js +5751 -631
  36. package/plugins/kxm/package.json +1 -1
  37. package/plugins/kxm/skills/SUITE.md +5 -0
  38. package/plugins/kxm/skills/hints.json +73 -0
  39. package/plugins/kxm/skills/kxm/SKILL.md +30 -83
  40. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
  41. package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
  42. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
  43. package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
  44. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +34 -0
  45. package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
  46. package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
  47. package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
  48. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +35 -0
  49. package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
  50. package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
  51. package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
  52. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
  53. package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
  54. package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
  55. package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
  56. package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
  57. package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
  58. package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
  59. package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
  60. package/plugins/kxm/src/autocomplete.ts +9 -3
  61. package/plugins/kxm/src/cli.ts +1077 -11
  62. package/plugins/kxm/src/commands.ts +150 -8
  63. package/plugins/kxm/src/config.ts +7 -4
  64. package/plugins/kxm/src/context-packet.ts +172 -0
  65. package/plugins/kxm/src/extension.ts +36 -1
  66. package/plugins/kxm/src/external-effects.ts +356 -7
  67. package/plugins/kxm/src/hub.ts +2 -4
  68. package/plugins/kxm/src/improve.ts +72 -0
  69. package/plugins/kxm/src/mcp-server.ts +1 -1
  70. package/plugins/kxm/src/model-inventory.ts +127 -0
  71. package/plugins/kxm/src/policy-draft.d.mts +55 -0
  72. package/plugins/kxm/src/policy-draft.mjs +565 -0
  73. package/plugins/kxm/src/price-calc.ts +17 -18
  74. package/plugins/kxm/src/prices.ts +32 -16
  75. package/plugins/kxm/src/producers.ts +71 -0
  76. package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
  77. package/plugins/kxm/src/restricted-yaml.mjs +145 -0
  78. package/plugins/kxm/src/role.ts +375 -0
  79. package/plugins/kxm/src/routing.ts +99 -1
  80. package/plugins/kxm/src/session-work.ts +9 -2
  81. package/plugins/kxm/src/studio-layout.ts +660 -17
  82. package/plugins/kxm/src/suggest.ts +7 -13
  83. package/plugins/kxm/src/telemetry.ts +82 -0
  84. package/plugins/kxm/src/tui.ts +140 -0
  85. package/plugins/kxm/src/vnext-config.ts +15 -110
  86. package/plugins/kxm/src/vnext-engine.ts +198 -62
  87. package/plugins/kxm/src/vnext-harness.ts +263 -81
  88. package/plugins/kxm/src/vnext-oneshot-evidence.ts +85 -0
  89. package/plugins/kxm/src/vnext-oneshot-process.ts +149 -0
  90. package/plugins/kxm/src/vnext-oneshot-producer.ts +170 -233
  91. package/plugins/kxm/src/vnext-runtime-store.ts +35 -1
  92. package/plugins/kxm/src/vnext-runtime-supervisor.ts +120 -5
  93. package/plugins/kxm/src/vnext-runtime.ts +14 -0
  94. package/plugins/kxm/src/workflow-manager.ts +392 -0
  95. package/plugins/kxm/src/workflow-tui.ts +255 -0
  96. package/schemas/policy-draft/README.md +17 -0
  97. package/schemas/policy-draft/model.v2.schema.json +140 -0
  98. package/schemas/policy-draft/role.v2.schema.json +91 -0
  99. package/schemas/vnext/role.schema.json +76 -0
  100. package/schemas/vnext/run-event.schema.json +1 -0
  101. package/scripts/assignment-run.d.mts +1 -1
  102. package/scripts/assignment-run.mjs +44 -35
  103. package/scripts/check-generated.mjs +33 -9
  104. package/scripts/emit-codex-artifacts.mjs +255 -11
  105. package/scripts/harness-run.d.mts +12 -4
  106. package/scripts/harness-run.mjs +65 -17
  107. package/scripts/kxm-hub.mjs +6 -0
  108. package/scripts/native-critic.d.mts +5 -0
  109. package/scripts/native-critic.mjs +60 -0
  110. package/.kxm/config/README.md +0 -5
  111. package/.kxm/config/agents.json +0 -43
  112. package/.kxm/config/env.example +0 -56
  113. package/.kxm/config/update.example.yaml +0 -9
  114. package/.kxm/config/workflows/fix.json +0 -160
  115. package/.kxm/config/workflows/jira-development.json +0 -116
  116. package/.kxm/config/workflows/provenance-quorum.json +0 -150
  117. package/.kxm/config/workflows/v04-dogfood.json +0 -72
@@ -0,0 +1,85 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { lstat, mkdir, open, rename } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import type { VnextOneShotProcessResult } from "./vnext-oneshot-process.ts";
5
+
6
+ const TEXT_LIMIT = 4 * 1024 * 1024;
7
+ const RECORD_LIMIT = 16 * 1024 * 1024;
8
+ const digest = (value: string): string => `sha256:${createHash("sha256").update(value).digest("hex")}`;
9
+
10
+ /** Private diagnostic evidence, never a peer reply or acceptance authority.
11
+ * Retained until operator archival; no automatic deletion of failed attempts.
12
+ * Each record is bounded; inability to reserve/write evidence fails closed.
13
+ */
14
+ export async function beginOneShotEvidence(root: string, intent: {
15
+ runId: string; stepId: string; attemptId: string; assignmentId: string;
16
+ harness: string; provider: string; model: string; cwd: string;
17
+ command: string; args: readonly string[]; input?: string | undefined;
18
+ }, sensitive: readonly string[]): Promise<{
19
+ id: string;
20
+ finish(result: VnextOneShotProcessResult, observation: unknown): Promise<string>;
21
+ }> {
22
+ await mkdir(root, { recursive: true, mode: 0o700 });
23
+ const stat = await lstat(root);
24
+ if (!stat.isDirectory() || stat.isSymbolicLink()
25
+ || (process.platform !== "win32" && ((stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.()))) {
26
+ throw new Error("oneshot_evidence_root_not_private");
27
+ }
28
+ const id = `evd_${randomUUID().replaceAll("-", "")}`;
29
+ const dir = join(root, id);
30
+ await mkdir(dir, { mode: 0o700 });
31
+ const secrets = [...new Set(sensitive.filter((value) => value.length >= 8))].sort((a, b) => b.length - a.length);
32
+ const redact = (value: string): string => {
33
+ let text = value;
34
+ for (const secret of secrets) text = text.replaceAll(secret, "[REDACTED]");
35
+ return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]");
36
+ };
37
+ const bounded = (value: string): { text: string; truncated: boolean; sha256: string } => {
38
+ const bytes = Buffer.from(redact(value));
39
+ return { text: bytes.subarray(0, TEXT_LIMIT).toString("utf8"), truncated: bytes.length > TEXT_LIMIT, sha256: digest(value) };
40
+ };
41
+ const write = async (name: string, value: unknown): Promise<string> => {
42
+ const current = await lstat(dir);
43
+ if (!current.isDirectory() || current.isSymbolicLink()
44
+ || (process.platform !== "win32" && ((current.mode & 0o077) !== 0 || current.uid !== process.getuid?.()))) {
45
+ throw new Error("oneshot_evidence_directory_changed");
46
+ }
47
+ const bytes = JSON.stringify(value);
48
+ if (Buffer.byteLength(bytes) > RECORD_LIMIT) throw new Error("oneshot_evidence_record_limit");
49
+ const target = join(dir, name);
50
+ const partial = `${target}.partial`;
51
+ const file = await open(partial, "wx", 0o600);
52
+ try { await file.writeFile(bytes); await file.sync(); }
53
+ finally { await file.close(); }
54
+ await rename(partial, target);
55
+ if (process.platform !== "win32") {
56
+ const directory = await open(dir, "r");
57
+ try { await directory.sync(); } finally { await directory.close(); }
58
+ }
59
+ return digest(bytes);
60
+ };
61
+ await write("intent.json", {
62
+ schema: "kxm.oneshot-evidence.v1", id, recordedAt: new Date().toISOString(),
63
+ runId: intent.runId, stepId: intent.stepId, attemptId: intent.attemptId, assignmentId: intent.assignmentId,
64
+ harness: intent.harness, provider: intent.provider, requestedModel: intent.model,
65
+ cwd: intent.cwd, command: intent.command,
66
+ argv: bounded(JSON.stringify(intent.args)), stdin: intent.input === undefined ? null : bounded(intent.input),
67
+ acceptance: false,
68
+ });
69
+ let finished = false;
70
+ return {
71
+ id,
72
+ async finish(result, observation) {
73
+ if (finished) throw new Error("oneshot_evidence_already_finished");
74
+ finished = true;
75
+ return write("result.json", {
76
+ schema: "kxm.oneshot-evidence.v1", id, recordedAt: new Date().toISOString(),
77
+ stdout: bounded(result.stdout), stderr: bounded(result.stderr),
78
+ code: result.code, signal: result.signal ?? null, started: result.started ?? null,
79
+ observedChildExit: result.observedChildExit ?? null, terminationRequested: result.terminationRequested ?? null,
80
+ error: result.error ? bounded(result.error.message) : null,
81
+ observation: bounded(JSON.stringify(observation)), acceptance: false,
82
+ });
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,149 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ export interface VnextOneShotProcessResult {
4
+ stdout: string;
5
+ stderr: string;
6
+ code: number | null;
7
+ signal?: NodeJS.Signals | null | undefined;
8
+ observedChildExit?: boolean | undefined;
9
+ started?: boolean | undefined;
10
+ /** Stop was requested; direct-child reaping cannot attest escaped descendants. */
11
+ terminationRequested?: boolean | undefined;
12
+ error?: Error | undefined;
13
+ }
14
+
15
+ export interface VnextOneShotSpawnOptions {
16
+ cwd?: string | undefined;
17
+ env?: NodeJS.ProcessEnv | undefined;
18
+ input?: string | undefined;
19
+ timeoutMs?: number | undefined;
20
+ signal?: AbortSignal | undefined;
21
+ }
22
+ export type VnextOneShotSpawn = (command: string, args: readonly string[], options: VnextOneShotSpawnOptions) => Promise<VnextOneShotProcessResult>;
23
+
24
+ const OUTPUT_LIMIT = 8 * 1024 * 1024;
25
+ const KILL_GRACE_MS = 250;
26
+ const DRAIN_GRACE_MS = 500;
27
+ const REAP_GRACE_MS = 1000;
28
+
29
+ /** Bound both execution and pipe drain. Observing exit is not proof of descendant death. */
30
+ export function defaultSpawn(command: string, args: readonly string[], options: VnextOneShotSpawnOptions): Promise<VnextOneShotProcessResult> {
31
+ if (options.signal?.aborted) {
32
+ return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
33
+ }
34
+ return new Promise((resolve) => {
35
+ const stdout: Buffer[] = [];
36
+ const stderr: Buffer[] = [];
37
+ let outputBytes = 0;
38
+ let outputChunks = 0;
39
+ let code: number | null = null;
40
+ let signal: NodeJS.Signals | null = null;
41
+ let observedChildExit = false;
42
+ let finished = false;
43
+ let stopping = false;
44
+ let closed = false;
45
+ let killSent = false;
46
+ let error: Error | undefined;
47
+ let wallTimer: ReturnType<typeof setTimeout> | undefined;
48
+ let killTimer: ReturnType<typeof setTimeout> | undefined;
49
+ let drainTimer: ReturnType<typeof setTimeout> | undefined;
50
+ let reapTimer: ReturnType<typeof setTimeout> | undefined;
51
+ let child: ReturnType<typeof spawn>;
52
+ const finish = (): void => {
53
+ if (finished) return;
54
+ finished = true;
55
+ for (const timer of [wallTimer, killTimer, drainTimer, reapTimer]) clearTimeout(timer);
56
+ options.signal?.removeEventListener("abort", onAbort);
57
+ child?.stdin?.destroy();
58
+ child?.stdout?.destroy();
59
+ child?.stderr?.destroy();
60
+ child?.unref();
61
+ const started = Boolean(child?.pid);
62
+ if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
63
+ resolve({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, signal, started, observedChildExit, terminationRequested: stopping, ...(error ? { error } : {}) });
64
+ };
65
+ const kill = (requested: NodeJS.Signals): void => {
66
+ try {
67
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
68
+ else child.kill(requested);
69
+ } catch { /* Reaping, not kill() success, establishes direct-child exit. */ }
70
+ };
71
+ const stop = (reason: string): void => {
72
+ if (finished) return;
73
+ error ??= new Error(reason);
74
+ if (stopping) return;
75
+ stopping = true;
76
+ clearTimeout(wallTimer);
77
+ kill("SIGTERM");
78
+ killTimer = setTimeout(() => {
79
+ kill("SIGKILL");
80
+ killSent = true;
81
+ if (closed) { finish(); return; }
82
+ reapTimer = setTimeout(() => {
83
+ if (!observedChildExit) error ??= new Error("process_exit_unobserved");
84
+ finish();
85
+ }, REAP_GRACE_MS);
86
+ }, KILL_GRACE_MS);
87
+ };
88
+ const onAbort = (): void => stop("process_aborted");
89
+ try {
90
+ child = spawn(command, [...args], {
91
+ cwd: options.cwd,
92
+ env: options.env ?? process.env,
93
+ stdio: ["pipe", "pipe", "pipe"],
94
+ shell: false,
95
+ windowsHide: true,
96
+ detached: process.platform !== "win32",
97
+ });
98
+ } catch (cause) {
99
+ error = cause instanceof Error ? cause : new Error("process_spawn_failed");
100
+ finish();
101
+ return;
102
+ }
103
+ const collect = (target: Buffer[], chunk: Buffer | string): void => {
104
+ if (finished) return;
105
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
106
+ const remaining = Math.max(0, OUTPUT_LIMIT - outputBytes);
107
+ if (remaining && outputChunks < 16384) {
108
+ const kept = bytes.subarray(0, remaining);
109
+ target.push(kept);
110
+ outputBytes += kept.length;
111
+ outputChunks++;
112
+ }
113
+ if (bytes.length > remaining || outputChunks >= 16384) stop("process_output_limit");
114
+ };
115
+ child.stdout!.on("data", (chunk: Buffer) => collect(stdout, chunk));
116
+ child.stderr!.on("data", (chunk: Buffer) => collect(stderr, chunk));
117
+ child.stdin!.on("error", () => stop("process_stdin_error"));
118
+ child.stdout!.on("error", () => stop("process_stdio_error"));
119
+ child.stderr!.on("error", () => stop("process_stdio_error"));
120
+ child.on("error", (cause) => {
121
+ error ??= cause;
122
+ if (!child.pid) finish();
123
+ else stop("process_error");
124
+ });
125
+ child.on("exit", (exitCode, exitSignal) => {
126
+ if (finished) return;
127
+ observedChildExit = true;
128
+ code = exitCode;
129
+ signal = exitSignal;
130
+ clearTimeout(wallTimer);
131
+ drainTimer = setTimeout(() => stop("process_stdio_unclosed"), DRAIN_GRACE_MS);
132
+ });
133
+ child.on("close", (exitCode, exitSignal) => {
134
+ if (finished) return;
135
+ closed = true;
136
+ code = exitCode;
137
+ signal = exitSignal;
138
+ // The leader can exit and close its pipes while a descendant ignores TERM.
139
+ // Never cancel the pending group escalation just because close arrived.
140
+ if (!stopping || killSent) finish();
141
+ });
142
+ options.signal?.addEventListener("abort", onAbort, { once: true });
143
+ if (options.signal?.aborted) onAbort();
144
+ const timeoutMs = options.timeoutMs ?? 120_000;
145
+ if (!stopping) wallTimer = setTimeout(() => stop("process_timeout"), timeoutMs > 0 && Number.isFinite(timeoutMs) ? timeoutMs : 120_000);
146
+ try { child.stdin!.end(options.input); }
147
+ catch { stop("process_stdin_error"); }
148
+ });
149
+ }