@kontextmind/kxm 0.6.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 (227) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.kxm/README.md +14 -0
  3. package/.kxm/assets/README.md +5 -0
  4. package/.kxm/assets/retrospectives/README.md +5 -0
  5. package/.kxm/config/README.md +5 -0
  6. package/.kxm/config/agents.json +43 -0
  7. package/.kxm/config/env.example +56 -0
  8. package/.kxm/config/update.example.yaml +9 -0
  9. package/.kxm/config/workflows/fix.json +160 -0
  10. package/.kxm/config/workflows/jira-development.json +116 -0
  11. package/.kxm/config/workflows/provenance-quorum.json +150 -0
  12. package/.kxm/config/workflows/v04-dogfood.json +72 -0
  13. package/CHANGELOG.md +465 -0
  14. package/LICENSE +21 -0
  15. package/README.md +306 -0
  16. package/SECURITY.md +72 -0
  17. package/docs/README.md +48 -0
  18. package/docs/agent-communication-envelopes-and-gates.md +553 -0
  19. package/docs/architecture.md +242 -0
  20. package/docs/assignment-runner.md +241 -0
  21. package/docs/configuration.md +361 -0
  22. package/docs/continuous-improvement.md +114 -0
  23. package/docs/getting-started.md +253 -0
  24. package/docs/kxm-handbook.md +1090 -0
  25. package/docs/operations.md +205 -0
  26. package/docs/provenance-gates.md +291 -0
  27. package/docs/skills.md +45 -0
  28. package/docs/templates/README.md +95 -0
  29. package/docs/templates/adr.md +88 -0
  30. package/docs/templates/architecture.md +120 -0
  31. package/docs/templates/bug-fix.md +109 -0
  32. package/docs/templates/feature.md +108 -0
  33. package/docs/templates/handoff.md +72 -0
  34. package/docs/templates/postmortem.md +77 -0
  35. package/docs/templates/research.md +100 -0
  36. package/docs/templates/review.md +85 -0
  37. package/docs/templates/runbook.md +73 -0
  38. package/docs/templates/test-plan.md +87 -0
  39. package/docs/templates/test-report.md +72 -0
  40. package/docs/test-matrix.md +121 -0
  41. package/docs/troubleshooting.md +249 -0
  42. package/docs/vnext/README.md +62 -0
  43. package/docs/vnext/architecture.md +185 -0
  44. package/docs/vnext/effects-and-recovery.md +172 -0
  45. package/docs/vnext/lifecycles.md +235 -0
  46. package/docs/vnext/migration.md +220 -0
  47. package/docs/vnext/routing.md +184 -0
  48. package/docs/vnext/synchronization.md +172 -0
  49. package/docs/vnext/terminology.md +240 -0
  50. package/docs/vnext/validation.md +335 -0
  51. package/docs/webhook-workflows.md +240 -0
  52. package/docs/workflow-guide.md +1150 -0
  53. package/examples/README.md +102 -0
  54. package/examples/provenance-workflow.json +40 -0
  55. package/examples/requester.ts +30 -0
  56. package/examples/reviewer-agent.ts +29 -0
  57. package/examples/roundtrip.ts +46 -0
  58. package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
  59. package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
  60. package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
  61. package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
  62. package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
  63. package/examples/vnext/.kxm/agents/planner.yaml +13 -0
  64. package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
  65. package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
  66. package/examples/vnext/.kxm/gates.yaml +8 -0
  67. package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
  68. package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
  69. package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
  70. package/examples/vnext/.kxm/models/implementation.yaml +14 -0
  71. package/examples/vnext/.kxm/models/primary.yaml +17 -0
  72. package/examples/vnext/.kxm/prices.yaml +111 -0
  73. package/examples/vnext/.kxm/project/env.yaml +7 -0
  74. package/examples/vnext/.kxm/project.yaml +32 -0
  75. package/examples/vnext/.kxm/repo/repo.yaml +8 -0
  76. package/examples/vnext/.kxm/workflows/default.yaml +92 -0
  77. package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
  78. package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
  79. package/examples/vnext/README.md +53 -0
  80. package/examples/vnext/records/assignment-result-recorded.json +63 -0
  81. package/examples/vnext/records/assignment-result.json +46 -0
  82. package/examples/vnext/records/context-candidate.json +42 -0
  83. package/examples/vnext/records/delivery-manifest.json +66 -0
  84. package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
  85. package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
  86. package/examples/vnext/records/run-created.json +54 -0
  87. package/examples/vnext/records/sync-event.json +65 -0
  88. package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
  89. package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
  90. package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
  91. package/examples/workflow-signal.ts +63 -0
  92. package/package.json +129 -0
  93. package/plugins/kxm/.claude-plugin/plugin.json +73 -0
  94. package/plugins/kxm/.mcp.json +19 -0
  95. package/plugins/kxm/README.md +93 -0
  96. package/plugins/kxm/dist/cli.js +42853 -0
  97. package/plugins/kxm/dist/client.js +416 -0
  98. package/plugins/kxm/dist/core.js +1823 -0
  99. package/plugins/kxm/dist/extension.js +3797 -0
  100. package/plugins/kxm/dist/mcp-server.js +17104 -0
  101. package/plugins/kxm/dist/runtime.js +23361 -0
  102. package/plugins/kxm/dist/server.js +13640 -0
  103. package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
  104. package/plugins/kxm/package.json +12 -0
  105. package/plugins/kxm/skills/kxm/SKILL.md +97 -0
  106. package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
  107. package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
  108. package/plugins/kxm/src/arbiter.ts +355 -0
  109. package/plugins/kxm/src/artifacts-exist.ts +62 -0
  110. package/plugins/kxm/src/autocomplete.ts +236 -0
  111. package/plugins/kxm/src/cli.ts +3707 -0
  112. package/plugins/kxm/src/client.ts +614 -0
  113. package/plugins/kxm/src/commands.ts +1063 -0
  114. package/plugins/kxm/src/config.ts +290 -0
  115. package/plugins/kxm/src/context/providers.ts +101 -0
  116. package/plugins/kxm/src/context-packet.ts +332 -0
  117. package/plugins/kxm/src/context.ts +499 -0
  118. package/plugins/kxm/src/core.ts +6 -0
  119. package/plugins/kxm/src/database.ts +563 -0
  120. package/plugins/kxm/src/diagnostics.ts +184 -0
  121. package/plugins/kxm/src/envelope.ts +118 -0
  122. package/plugins/kxm/src/extension.ts +895 -0
  123. package/plugins/kxm/src/external-effects.ts +299 -0
  124. package/plugins/kxm/src/github-watch.ts +255 -0
  125. package/plugins/kxm/src/hub-binding.ts +160 -0
  126. package/plugins/kxm/src/hub.ts +2502 -0
  127. package/plugins/kxm/src/improve.ts +383 -0
  128. package/plugins/kxm/src/inbox.ts +10 -0
  129. package/plugins/kxm/src/kxm-install-kind.ts +113 -0
  130. package/plugins/kxm/src/kxm-update-config.ts +39 -0
  131. package/plugins/kxm/src/kxm-update.ts +238 -0
  132. package/plugins/kxm/src/local-snapshot.ts +406 -0
  133. package/plugins/kxm/src/logger.ts +198 -0
  134. package/plugins/kxm/src/mcp-server.ts +143 -0
  135. package/plugins/kxm/src/memory.ts +385 -0
  136. package/plugins/kxm/src/nous-pi.ts +287 -0
  137. package/plugins/kxm/src/nous-provider.ts +729 -0
  138. package/plugins/kxm/src/price-calc.ts +87 -0
  139. package/plugins/kxm/src/prices.ts +121 -0
  140. package/plugins/kxm/src/protocol.ts +172 -0
  141. package/plugins/kxm/src/recovery.ts +211 -0
  142. package/plugins/kxm/src/redact.ts +26 -0
  143. package/plugins/kxm/src/retrospective.ts +400 -0
  144. package/plugins/kxm/src/routing.ts +830 -0
  145. package/plugins/kxm/src/runtime.ts +9 -0
  146. package/plugins/kxm/src/server.ts +117 -0
  147. package/plugins/kxm/src/session-work.ts +571 -0
  148. package/plugins/kxm/src/session.ts +184 -0
  149. package/plugins/kxm/src/skills.ts +535 -0
  150. package/plugins/kxm/src/state.ts +326 -0
  151. package/plugins/kxm/src/store.ts +637 -0
  152. package/plugins/kxm/src/studio-layout.ts +268 -0
  153. package/plugins/kxm/src/suggest.ts +162 -0
  154. package/plugins/kxm/src/task-manager.ts +244 -0
  155. package/plugins/kxm/src/telemetry.ts +116 -0
  156. package/plugins/kxm/src/tui.ts +1046 -0
  157. package/plugins/kxm/src/vnext-bindings.ts +403 -0
  158. package/plugins/kxm/src/vnext-config.ts +1646 -0
  159. package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
  160. package/plugins/kxm/src/vnext-engine-command.ts +533 -0
  161. package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
  162. package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
  163. package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
  164. package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
  165. package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
  166. package/plugins/kxm/src/vnext-engine.ts +2458 -0
  167. package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
  168. package/plugins/kxm/src/vnext-harness.ts +1142 -0
  169. package/plugins/kxm/src/vnext-init.ts +430 -0
  170. package/plugins/kxm/src/vnext-migrate.ts +1848 -0
  171. package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
  172. package/plugins/kxm/src/vnext-permission.ts +936 -0
  173. package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
  174. package/plugins/kxm/src/vnext-repair.ts +1094 -0
  175. package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
  176. package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
  177. package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
  178. package/plugins/kxm/src/vnext-runtime.ts +663 -0
  179. package/plugins/kxm/src/vnext-template.ts +247 -0
  180. package/plugins/kxm/src/wiki.ts +313 -0
  181. package/plugins/kxm/src/workflow.ts +1548 -0
  182. package/schemas/vnext/README.md +46 -0
  183. package/schemas/vnext/agent.schema.json +40 -0
  184. package/schemas/vnext/assignment-result.schema.json +66 -0
  185. package/schemas/vnext/backup-manifest.schema.json +89 -0
  186. package/schemas/vnext/candidate.schema.json +109 -0
  187. package/schemas/vnext/common.schema.json +422 -0
  188. package/schemas/vnext/context-candidate.schema.json +76 -0
  189. package/schemas/vnext/context-packet.schema.json +192 -0
  190. package/schemas/vnext/delivery-manifest.schema.json +159 -0
  191. package/schemas/vnext/environment.schema.json +66 -0
  192. package/schemas/vnext/gate-registry.schema.json +109 -0
  193. package/schemas/vnext/handoff-manifest.schema.json +146 -0
  194. package/schemas/vnext/init-operation.schema.json +61 -0
  195. package/schemas/vnext/local-repository-bindings.schema.json +30 -0
  196. package/schemas/vnext/memory-record.schema.json +45 -0
  197. package/schemas/vnext/migration-decision.schema.json +26 -0
  198. package/schemas/vnext/migration-plan.schema.json +123 -0
  199. package/schemas/vnext/migration-receipt.schema.json +52 -0
  200. package/schemas/vnext/model.schema.json +42 -0
  201. package/schemas/vnext/permission-diff.schema.json +57 -0
  202. package/schemas/vnext/prices.schema.json +115 -0
  203. package/schemas/vnext/project.schema.json +85 -0
  204. package/schemas/vnext/repository.schema.json +24 -0
  205. package/schemas/vnext/run-event.schema.json +460 -0
  206. package/schemas/vnext/session-brief.schema.json +153 -0
  207. package/schemas/vnext/sync-event.schema.json +234 -0
  208. package/schemas/vnext/template-provenance.schema.json +38 -0
  209. package/schemas/vnext/workflow.schema.json +248 -0
  210. package/scripts/assignment-run.d.mts +354 -0
  211. package/scripts/assignment-run.mjs +4451 -0
  212. package/scripts/build-runtime.mjs +56 -0
  213. package/scripts/check-generated.mjs +77 -0
  214. package/scripts/check-versions.mjs +34 -0
  215. package/scripts/emit-codex-artifacts.d.mts +9 -0
  216. package/scripts/emit-codex-artifacts.mjs +91 -0
  217. package/scripts/harness-run.d.mts +83 -0
  218. package/scripts/harness-run.mjs +2095 -0
  219. package/scripts/kxm-hub.mjs +105 -0
  220. package/scripts/kxm-publish-npm.mjs +327 -0
  221. package/scripts/kxm-release-github.mjs +472 -0
  222. package/scripts/kxm-runtime-supervisor.mjs +7 -0
  223. package/scripts/kxm-worker.mjs +1127 -0
  224. package/scripts/kxm.mjs +27 -0
  225. package/scripts/roster-policy.d.mts +20 -0
  226. package/scripts/roster-policy.mjs +161 -0
  227. package/scripts/smoke-multi-pi.mjs +479 -0
@@ -0,0 +1,383 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
4
+ import type { RoutingRecord, RoutingRecordV2 } from "./routing.ts";
5
+ import { nowIso } from "./protocol.ts";
6
+
7
+ export const CANDIDATE_SCHEMA = "kxm.candidate.v1" as const;
8
+ export const IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2" as const;
9
+ export const IMPROVEMENT_REPORT_V1_SCHEMA = "kxm.improvement-report.v1" as const;
10
+
11
+ export type CandidateKind = "gate" | "skill" | "workflow-step";
12
+
13
+ export interface CandidateBaselineMetrics {
14
+ recurrence: number;
15
+ meanCost: number;
16
+ meanLatency: number;
17
+ verifyPassRate: number;
18
+ rework: number;
19
+ }
20
+
21
+ export interface ImprovementCandidate {
22
+ schema: typeof CANDIDATE_SCHEMA;
23
+ id: string;
24
+ kind: CandidateKind;
25
+ summary: string;
26
+ evidenceRefs: string[];
27
+ baselineMetrics: CandidateBaselineMetrics;
28
+ declaredOutcome: string;
29
+ measure: string;
30
+ proposedDiffPath: string;
31
+ status: "proposed" | "evaluating" | "review-required" | "promoted" | "rejected";
32
+ createdAt: string;
33
+ }
34
+
35
+ export interface ImprovementGroupRow {
36
+ workflowHash: string;
37
+ stepId: string;
38
+ agentRole: string;
39
+ promptHash: string;
40
+ recurrence: number;
41
+ meanCost: number;
42
+ meanLatency: number;
43
+ verifyPassRate: number;
44
+ rework: number;
45
+ evidenceRefs: string[];
46
+ isCandidate: boolean;
47
+ candidateKind?: CandidateKind;
48
+ candidateId?: string;
49
+ }
50
+
51
+ export interface ImprovementReport {
52
+ schema: typeof IMPROVEMENT_REPORT_SCHEMA;
53
+ createdAt: string;
54
+ reviewDecision: "proposed";
55
+ recordsCount: number;
56
+ groups: ImprovementGroupRow[];
57
+ candidates: ImprovementCandidate[];
58
+ }
59
+
60
+ export function classifyCandidateKind(stepId: string, agentRole: string): CandidateKind {
61
+ const s = stepId.toLowerCase();
62
+ const r = agentRole.toLowerCase();
63
+ if (s.includes("verify") || s.includes("gate") || s.includes("test") || s.includes("check") || s.includes("lint") || r === "verifier") {
64
+ return "gate";
65
+ }
66
+ if (s.includes("plan") || s.includes("review") || s.includes("repro") || r === "planner" || r === "reviewer" || r === "critic") {
67
+ return "skill";
68
+ }
69
+ return "workflow-step";
70
+ }
71
+
72
+ function generateCandidateDiff(kind: CandidateKind, stepId: string, agentRole: string, workflowHash: string): { diff: string; declaredOutcome: string; measure: string; summary: string } {
73
+ const safeSlug = stepId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "step";
74
+ if (kind === "gate") {
75
+ const diff = [
76
+ "diff --git a/.kxm/gates.yaml b/.kxm/gates.yaml",
77
+ "--- a/.kxm/gates.yaml",
78
+ "+++ b/.kxm/gates.yaml",
79
+ "@@ -1,3 +1,7 @@",
80
+ " schema: kxm.gate-registry.v1",
81
+ " gates:",
82
+ `+ ${safeSlug}:`,
83
+ `+ kind: command`,
84
+ `+ argv: [npm, run, ${safeSlug}]`,
85
+ `+ timeoutMs: 3600000`,
86
+ "",
87
+ ].join("\n");
88
+ return {
89
+ diff,
90
+ declaredOutcome: `Deterministic exit-code verification replacing LLM turn for ${stepId}`,
91
+ measure: `100% reduction in LLM inference cost and latency for ${stepId}`,
92
+ summary: `Promote deterministic check '${stepId}' (${agentRole}) to coded gate`,
93
+ };
94
+ }
95
+ if (kind === "skill") {
96
+ const rel = `.kxm/skills/candidates/${safeSlug}/SKILL.md`;
97
+ const skillContent = [
98
+ "---",
99
+ `name: ${safeSlug}`,
100
+ `description: Governed repeat skill for ${stepId}`,
101
+ "---",
102
+ "",
103
+ `# ${stepId}`,
104
+ "",
105
+ `Reusable skill instructions for ${stepId} (${agentRole}).`,
106
+ "",
107
+ ].join("\n");
108
+ const diff = [
109
+ `diff --git a/${rel} b/${rel}`,
110
+ "new file mode 100644",
111
+ "--- /dev/null",
112
+ `+++ b/${rel}`,
113
+ `@@ -0,0 +1,${skillContent.split("\n").length} @@`,
114
+ ...skillContent.split("\n").map((l) => `+${l}`),
115
+ "",
116
+ ].join("\n");
117
+ return {
118
+ diff,
119
+ declaredOutcome: `Governed reusable skill for ${stepId}`,
120
+ measure: `Reduced prompt drift and consistent model guidance for ${stepId}`,
121
+ summary: `Promote repeated instructions for '${stepId}' (${agentRole}) to governed skill`,
122
+ };
123
+ }
124
+ const rel = `.kxm/workflows/${safeSlug}.yaml`;
125
+ const diff = [
126
+ `diff --git a/${rel} b/${rel}`,
127
+ `--- a/${rel}`,
128
+ `+++ b/${rel}`,
129
+ "@@ -1,3 +1,6 @@",
130
+ " steps:",
131
+ `+ - id: ${stepId}`,
132
+ `+ kind: agent`,
133
+ `+ agent: ${agentRole}`,
134
+ "",
135
+ ].join("\n");
136
+ return {
137
+ diff,
138
+ declaredOutcome: `Dedicated workflow step with typed inputs and transitions for ${stepId}`,
139
+ measure: `Reduced manual coordination and faster stage transitions for ${stepId}`,
140
+ summary: `Automate repetitive step '${stepId}' (${agentRole}) in workflow`,
141
+ };
142
+ }
143
+
144
+ export function groupRoutingRecords(
145
+ records: Array<RoutingRecord | RoutingRecordV2>,
146
+ options: { minRecurrence?: number; minPassRate?: number } = {},
147
+ ): ImprovementGroupRow[] {
148
+ const minRecurrence = options.minRecurrence ?? 2;
149
+ const minPassRate = options.minPassRate ?? 0.75;
150
+
151
+ const map = new Map<string, {
152
+ workflowHash: string;
153
+ stepId: string;
154
+ agentRole: string;
155
+ promptHash: string;
156
+ costs: number[];
157
+ latencies: number[];
158
+ passedCount: number;
159
+ reworkCount: number;
160
+ evidenceRefs: Set<string>;
161
+ total: number;
162
+ }>();
163
+
164
+ for (const r of records) {
165
+ const raw = r as unknown as Record<string, unknown>;
166
+ const providerMeta = (raw.providerMetadata && typeof raw.providerMetadata === "object") ? raw.providerMetadata as Record<string, unknown> : {};
167
+ const workflowHash = (raw.workflowDefinitionSha256 as string)
168
+ || (providerMeta.workflowDefinitionSha256 as string)
169
+ || (raw.workflowRunId as string)
170
+ || (raw.runId as string)
171
+ || "standalone";
172
+ const stepId = (raw.stepId as string) || (raw.stageId as string) || "unknown";
173
+ const agentRole = (raw.agentRole as string)?.trim() || "agent";
174
+ const promptHash = (raw.rolePromptSha256 as string)?.trim()
175
+ || (providerMeta.rolePromptSha256 as string)?.trim()
176
+ || "none";
177
+
178
+ const key = `${workflowHash}:${stepId}:${agentRole}:${promptHash}`;
179
+ let group = map.get(key);
180
+ if (!group) {
181
+ group = {
182
+ workflowHash,
183
+ stepId,
184
+ agentRole,
185
+ promptHash,
186
+ costs: [],
187
+ latencies: [],
188
+ passedCount: 0,
189
+ reworkCount: 0,
190
+ evidenceRefs: new Set<string>(),
191
+ total: 0,
192
+ };
193
+ map.set(key, group);
194
+ }
195
+
196
+ group.total += 1;
197
+ const cost = raw.costUsd;
198
+ if (typeof cost === "number" && !Number.isNaN(cost) && cost >= 0) {
199
+ group.costs.push(cost);
200
+ }
201
+ const latency = raw.latencyMs;
202
+ if (typeof latency === "number" && !Number.isNaN(latency) && latency >= 0) {
203
+ group.latencies.push(latency);
204
+ }
205
+
206
+ const verifierOutcome = raw.verifierOutcome;
207
+ const finalOutcome = raw.finalOutcome;
208
+ if (verifierOutcome === "passed" || finalOutcome === "accepted" || finalOutcome === "completed") {
209
+ group.passedCount += 1;
210
+ }
211
+
212
+ const retries = raw.retries;
213
+ if (typeof retries === "number") {
214
+ group.reworkCount += retries;
215
+ }
216
+
217
+ const ref = (raw.attemptId as string) || (raw.runId as string) || (raw.workflowRunId as string) || (raw.behavioralSha256 as string);
218
+ if (ref && group.evidenceRefs.size < 16) {
219
+ group.evidenceRefs.add(ref);
220
+ }
221
+ }
222
+
223
+ const rows: ImprovementGroupRow[] = [];
224
+ for (const group of map.values()) {
225
+ const recurrence = group.total;
226
+ const meanCost = group.costs.length > 0
227
+ ? Number((group.costs.reduce((a, b) => a + b, 0) / group.costs.length).toFixed(4))
228
+ : 0;
229
+ const meanLatency = group.latencies.length > 0
230
+ ? Math.round(group.latencies.reduce((a, b) => a + b, 0) / group.latencies.length)
231
+ : 0;
232
+ const verifyPassRate = recurrence > 0
233
+ ? Number((group.passedCount / recurrence).toFixed(3))
234
+ : 0;
235
+ const rework = group.reworkCount;
236
+
237
+ const isCandidate = recurrence >= minRecurrence && verifyPassRate >= minPassRate;
238
+ const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : undefined;
239
+ const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
240
+ const keyHash = createHash("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
241
+ const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : undefined;
242
+
243
+ rows.push({
244
+ workflowHash: group.workflowHash,
245
+ stepId: group.stepId,
246
+ agentRole: group.agentRole,
247
+ promptHash: group.promptHash,
248
+ recurrence,
249
+ meanCost,
250
+ meanLatency,
251
+ verifyPassRate,
252
+ rework,
253
+ evidenceRefs: [...group.evidenceRefs],
254
+ isCandidate,
255
+ ...(candidateKind !== undefined ? { candidateKind } : {}),
256
+ ...(candidateId !== undefined ? { candidateId } : {}),
257
+ });
258
+ }
259
+
260
+ return rows.sort((a, b) => {
261
+ if (a.isCandidate !== b.isCandidate) return a.isCandidate ? -1 : 1;
262
+ if (b.recurrence !== a.recurrence) return b.recurrence - a.recurrence;
263
+ return a.stepId.localeCompare(b.stepId);
264
+ });
265
+ }
266
+
267
+ export function buildImprovementReport(
268
+ records: Array<RoutingRecord | RoutingRecordV2>,
269
+ options: {
270
+ minRecurrence?: number;
271
+ minPassRate?: number;
272
+ candidatesDir?: string;
273
+ projectRoot?: string;
274
+ dryRun?: boolean;
275
+ } = {},
276
+ ): ImprovementReport {
277
+ const projectRoot = options.projectRoot ? resolve(options.projectRoot) : process.cwd();
278
+ const candidatesDir = options.candidatesDir ? resolve(options.candidatesDir) : join(projectRoot, ".kxm", "candidates");
279
+
280
+ const groups = groupRoutingRecords(records, options);
281
+ const candidates: ImprovementCandidate[] = [];
282
+
283
+ for (const group of groups) {
284
+ if (!group.isCandidate || !group.candidateKind || !group.candidateId) continue;
285
+
286
+ const { diff, declaredOutcome, measure, summary } = generateCandidateDiff(
287
+ group.candidateKind,
288
+ group.stepId,
289
+ group.agentRole,
290
+ group.workflowHash,
291
+ );
292
+
293
+ const diffFileName = `${group.candidateId}.diff`;
294
+ const diffFilePath = join(candidatesDir, diffFileName);
295
+ const relDiffPath = relative(projectRoot, diffFilePath).replace(/\\/g, "/");
296
+
297
+ const candidate: ImprovementCandidate = {
298
+ schema: CANDIDATE_SCHEMA,
299
+ id: group.candidateId,
300
+ kind: group.candidateKind,
301
+ summary,
302
+ evidenceRefs: group.evidenceRefs.length > 0 ? group.evidenceRefs : ["evidence:telemetry"],
303
+ baselineMetrics: {
304
+ recurrence: group.recurrence,
305
+ meanCost: group.meanCost,
306
+ meanLatency: group.meanLatency,
307
+ verifyPassRate: group.verifyPassRate,
308
+ rework: group.rework,
309
+ },
310
+ declaredOutcome,
311
+ measure,
312
+ proposedDiffPath: relDiffPath,
313
+ status: "proposed",
314
+ createdAt: nowIso(),
315
+ };
316
+
317
+ if (!options.dryRun) {
318
+ if (!existsSync(candidatesDir)) {
319
+ mkdirSync(candidatesDir, { recursive: true });
320
+ }
321
+ writeFileSync(diffFilePath, diff, "utf8");
322
+ const jsonFilePath = join(candidatesDir, `${group.candidateId}.json`);
323
+ writeFileSync(jsonFilePath, JSON.stringify(candidate, null, 2) + "\n", "utf8");
324
+ }
325
+
326
+ candidates.push(candidate);
327
+ }
328
+
329
+ return {
330
+ schema: IMPROVEMENT_REPORT_SCHEMA,
331
+ createdAt: nowIso(),
332
+ reviewDecision: "proposed",
333
+ recordsCount: records.length,
334
+ groups,
335
+ candidates,
336
+ };
337
+ }
338
+
339
+ export function writeImprovementReport(improvementsDir: string, report: ImprovementReport, dryRun = false): string {
340
+ const stamp = report.createdAt.replace(/[:.]/g, "-");
341
+ const path = join(improvementsDir, `${stamp}.json`);
342
+ if (!dryRun) {
343
+ mkdirSync(improvementsDir, { recursive: true });
344
+ writeFileSync(path, `${JSON.stringify(report, null, 2)}\n`, { encoding: "utf8" });
345
+ }
346
+ return path;
347
+ }
348
+
349
+ export function formatImprovementReport(report: ImprovementReport): string {
350
+ const lines: string[] = [
351
+ `Improvement Report (${report.recordsCount} record(s), ${report.groups.length} group(s), ${report.candidates.length} candidate(s))`,
352
+ "",
353
+ "Workflow Step Role Prompt Recurrence Cost ($) Latency (ms) Pass Rate Rework Candidate",
354
+ "-------------------------------------------------------------------------------------------------------------------------",
355
+ ];
356
+
357
+ for (const g of report.groups) {
358
+ const wf = g.workflowHash.slice(0, 12).padEnd(14);
359
+ const step = g.stepId.slice(0, 11).padEnd(12);
360
+ const role = g.agentRole.slice(0, 11).padEnd(12);
361
+ const prompt = g.promptHash.slice(0, 10).padEnd(12);
362
+ const rec = String(g.recurrence).padStart(10);
363
+ const cost = g.meanCost.toFixed(3).padStart(9);
364
+ const lat = String(g.meanLatency).padStart(13);
365
+ const pass = `${(g.verifyPassRate * 100).toFixed(0)}%`.padStart(10);
366
+ const rework = String(g.rework).padStart(7);
367
+ const cand = g.isCandidate ? `yes (${g.candidateKind})` : "no";
368
+ lines.push(`${wf} ${step} ${role} ${prompt} ${rec} ${cost} ${lat} ${pass} ${rework} ${cand}`);
369
+ }
370
+
371
+ if (report.candidates.length > 0) {
372
+ lines.push("");
373
+ lines.push("Emitted Coded-Repeat Candidates:");
374
+ for (const c of report.candidates) {
375
+ lines.push(` - ${c.id} [${c.kind}]: ${c.summary}`);
376
+ lines.push(` Outcome: ${c.declaredOutcome}`);
377
+ lines.push(` Measure: ${c.measure}`);
378
+ lines.push(` Proposed diff: ${c.proposedDiffPath}`);
379
+ }
380
+ }
381
+
382
+ return lines.join("\n");
383
+ }
@@ -0,0 +1,10 @@
1
+ export async function deliverInboxNotification(
2
+ messageId: string,
3
+ delivered: Set<string>,
4
+ notify: () => Promise<void>,
5
+ ): Promise<boolean> {
6
+ if (delivered.has(messageId)) return false;
7
+ await notify();
8
+ delivered.add(messageId);
9
+ return true;
10
+ }
@@ -0,0 +1,113 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { canonicalHostPath, sameHostPath } from "./vnext-bindings.ts";
4
+
5
+ export interface InstallProbe {
6
+ moduleDir: string;
7
+ repoRoot: string;
8
+ homeDir: string;
9
+ platform: NodeJS.Platform;
10
+ env: NodeJS.ProcessEnv;
11
+ }
12
+
13
+ export type InstallKind = "npm-global" | "npm-local" | "pi-git" | "claude-marketplace" | "source" | "unknown";
14
+ export type ClassifiedInstallKind = InstallKind | "npm-package";
15
+
16
+ export interface InstallKindReport {
17
+ kind: ClassifiedInstallKind;
18
+ root: string;
19
+ instruction: string;
20
+ }
21
+
22
+ function packageIsKxm(root: string): boolean {
23
+ try {
24
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { name?: unknown };
25
+ return pkg.name === "@kontextmind/kxm";
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ function pathSegments(path: string): string[] {
32
+ return canonicalHostPath(path).split(/[/\\]+/).filter((part) => part.length > 0);
33
+ }
34
+
35
+ function isUnder(child: string, parent: string, platform: NodeJS.Platform): boolean {
36
+ if (sameHostPath(child, parent, platform)) return true;
37
+ const childParts = pathSegments(child);
38
+ const parentParts = pathSegments(parent);
39
+ if (parentParts.length === 0 || childParts.length < parentParts.length) return false;
40
+ return parentParts.every((part, i) => segmentEq(part, childParts[i]!, platform));
41
+ }
42
+
43
+ function lastSegments(path: string): string[] {
44
+ return pathSegments(path).slice(-3);
45
+ }
46
+
47
+ function segmentEq(left: string, right: string, platform: NodeJS.Platform): boolean {
48
+ return platform === "win32"
49
+ ? left.toLocaleLowerCase("en-US") === right.toLocaleLowerCase("en-US")
50
+ : left === right;
51
+ }
52
+
53
+ function isNpmPackageLayout(repoRoot: string, platform: NodeJS.Platform): boolean {
54
+ const last = lastSegments(repoRoot);
55
+ const expected = ["node_modules", "@kontextmind", "kxm"];
56
+ return last.length === 3 && last.every((part, i) => segmentEq(part, expected[i]!, platform));
57
+ }
58
+
59
+ export function installKindInstruction(kind: ClassifiedInstallKind, root: string): string {
60
+ switch (kind) {
61
+ case "pi-git":
62
+ return `kxm is installed by Pi at ${root}; update it with: pi update`;
63
+ case "claude-marketplace":
64
+ return `kxm is a Claude Code marketplace plugin at ${root}; update it with: claude plugin update kxm@kxm`;
65
+ case "source":
66
+ return `kxm is running from source at ${root}; update it with git pull there, not kxm update --kxm`;
67
+ case "npm-local":
68
+ return `kxm is installed as a project dependency at ${root}; update it with: npm install @kontextmind/kxm@latest in that project`;
69
+ case "npm-global":
70
+ return `kxm is an npm global install at ${root}`;
71
+ case "npm-package":
72
+ return `kxm is an npm package at ${root}`;
73
+ default:
74
+ return `kxm cannot tell how it was installed (${root}); update it the way it was installed, or reinstall with: npm install --global @kontextmind/kxm`;
75
+ }
76
+ }
77
+
78
+ function report(kind: ClassifiedInstallKind, root: string): InstallKindReport {
79
+ return { kind, root, instruction: installKindInstruction(kind, root) };
80
+ }
81
+
82
+ export function classifyInstallRoot(probe: InstallProbe): InstallKindReport {
83
+ const piGitRoot = join(probe.homeDir, ".pi", "agent", "git");
84
+ if (isUnder(probe.repoRoot, piGitRoot, probe.platform)) return report("pi-git", probe.repoRoot);
85
+
86
+ const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() || join(probe.homeDir, ".claude");
87
+ const pluginsRoot = join(claudeHome, "plugins");
88
+ if (isUnder(probe.moduleDir, pluginsRoot, probe.platform)) return report("claude-marketplace", probe.moduleDir);
89
+
90
+ const hasGit = existsSync(join(probe.repoRoot, ".git"));
91
+ if (hasGit && packageIsKxm(probe.repoRoot)) return report("source", probe.repoRoot);
92
+
93
+ if (isNpmPackageLayout(probe.repoRoot, probe.platform) && packageIsKxm(probe.repoRoot) && !hasGit) {
94
+ return report("npm-package", probe.repoRoot);
95
+ }
96
+
97
+ return report("unknown", probe.repoRoot);
98
+ }
99
+
100
+ export function resolveInstallKind(
101
+ probe: InstallProbe,
102
+ npmGlobalRoot: () => string | undefined,
103
+ ): InstallKindReport {
104
+ const classified = classifyInstallRoot(probe);
105
+ if (classified.kind !== "npm-package") return classified;
106
+ const globalRoot = npmGlobalRoot();
107
+ if (typeof globalRoot !== "string" || globalRoot.trim() === "") {
108
+ return report("unknown", classified.root);
109
+ }
110
+ const expected = dirname(dirname(classified.root));
111
+ if (sameHostPath(globalRoot.trim(), expected, probe.platform)) return report("npm-global", classified.root);
112
+ return report("npm-local", classified.root);
113
+ }
@@ -0,0 +1,39 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import {
5
+ KXM_UPDATE_SCHEMA,
6
+ KxmUpdateConfigError,
7
+ type KxmUpdateConfig,
8
+ } from "./kxm-update.ts";
9
+ import { vnextUserStateRoot } from "./vnext-bindings.ts";
10
+
11
+ export function loadKxmUpdateConfig(env: NodeJS.ProcessEnv = process.env): KxmUpdateConfig {
12
+ const path = join(vnextUserStateRoot({ env }), "update.yaml");
13
+ if (!existsSync(path)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "github" };
14
+ let parsed: unknown;
15
+ try {
16
+ parsed = parseYaml(readFileSync(path, "utf8"));
17
+ } catch {
18
+ throw new KxmUpdateConfigError("update.yaml is not valid YAML");
19
+ }
20
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
21
+ throw new KxmUpdateConfigError("update.yaml must be a mapping");
22
+ }
23
+ const row = parsed as Record<string, unknown>;
24
+ const allowed = new Set(["schema", "auto", "source"]);
25
+ for (const key of Object.keys(row)) {
26
+ if (!allowed.has(key)) throw new KxmUpdateConfigError(`update.yaml unknown field ${key}`);
27
+ }
28
+ if (row.schema !== KXM_UPDATE_SCHEMA) {
29
+ throw new KxmUpdateConfigError("update.yaml schema must be kxm.update.v1");
30
+ }
31
+ if (typeof row.auto !== "boolean") {
32
+ throw new KxmUpdateConfigError("update.yaml auto must be a boolean");
33
+ }
34
+ const source = row.source === undefined ? "github" : row.source;
35
+ if (source !== "npm" && source !== "github") {
36
+ throw new KxmUpdateConfigError("update.yaml source must be npm or github");
37
+ }
38
+ return { schema: KXM_UPDATE_SCHEMA, auto: row.auto, source };
39
+ }