@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,290 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { parse, stringify } from "yaml";
5
+ import { redactSecrets } from "./redact.ts";
6
+
7
+ export const KXM_CONFIG_SCHEMA = "kxm.config.v1" as const;
8
+
9
+ export interface KxmUserConfig {
10
+ name?: string;
11
+ email?: string;
12
+ preferredHarness?: string;
13
+ preferredModel?: string;
14
+ preferredCritics?: string[];
15
+ theme?: "dark" | "light" | "minimal";
16
+ tokenBudget?: number;
17
+ }
18
+
19
+ export interface KxmProjectDefaults {
20
+ project?: string;
21
+ workflow?: string;
22
+ harness?: string;
23
+ model?: string;
24
+ }
25
+
26
+ export interface KxmDashConfig {
27
+ defaultScreen?: "agents" | "tasks" | "workflows" | "plans" | "inbox" | "procs" | "spend";
28
+ refreshIntervalMs?: number;
29
+ autoOpen?: boolean;
30
+ }
31
+
32
+ export interface KxmSyncTrackerConfig {
33
+ defaultTracker?: "github" | "jira" | "none";
34
+ github?: {
35
+ owner?: string;
36
+ repo?: string;
37
+ syncLabels?: boolean;
38
+ autoComment?: boolean;
39
+ };
40
+ jira?: {
41
+ host?: string;
42
+ projectKey?: string;
43
+ issueType?: string;
44
+ autoTransition?: boolean;
45
+ };
46
+ }
47
+
48
+ export type ImprovementPromotionPolicy = "manual_pr" | "critic_quorum" | "auto_threshold";
49
+
50
+ export interface KxmImprovementConfig {
51
+ promotionPolicy: ImprovementPromotionPolicy;
52
+ telemetryHalfLifeDays?: number | undefined;
53
+ autoThreshold?: {
54
+ minRuns?: number | undefined;
55
+ minPassRate?: number | undefined;
56
+ minCostSavings?: number | undefined;
57
+ } | undefined;
58
+ }
59
+
60
+ export interface KxmShadowExecutionConfig {
61
+ enabled: boolean;
62
+ sampleRate: number; // e.g. 0.05 for 5%
63
+ candidateModels?: string[] | undefined;
64
+ }
65
+
66
+ export type CircuitBreakerMode = "soft_demotion" | "quarantine";
67
+
68
+ export interface KxmCircuitBreakerConfig {
69
+ mode?: CircuitBreakerMode | undefined;
70
+ failureThreshold?: number | undefined;
71
+ windowSeconds?: number | undefined;
72
+ cooldownSeconds?: number | undefined;
73
+ penaltyMultiplier?: number | undefined;
74
+ }
75
+
76
+ export interface KxmRoutingConfig {
77
+ shadowExecution: KxmShadowExecutionConfig;
78
+ circuitBreaker?: KxmCircuitBreakerConfig | undefined;
79
+ }
80
+
81
+ export interface KxmTelemetryConfig {
82
+ federated: boolean;
83
+ anonymize: boolean;
84
+ userTelemetryDir?: string | undefined;
85
+ }
86
+
87
+ export interface KxmResolvedConfig {
88
+ schema: typeof KXM_CONFIG_SCHEMA;
89
+ user: KxmUserConfig;
90
+ defaults: KxmProjectDefaults;
91
+ dash: KxmDashConfig;
92
+ sync: KxmSyncTrackerConfig;
93
+ improvement: KxmImprovementConfig;
94
+ routing: KxmRoutingConfig;
95
+ telemetry: KxmTelemetryConfig;
96
+ loadedFrom: {
97
+ userConfigPath?: string | undefined;
98
+ repoConfigPath?: string | undefined;
99
+ };
100
+ }
101
+
102
+ export const DEFAULT_KXM_CONFIG: Omit<KxmResolvedConfig, "loadedFrom"> = {
103
+ schema: KXM_CONFIG_SCHEMA,
104
+ user: {
105
+ theme: "dark",
106
+ preferredCritics: ["reviewer-arch", "reviewer-cli"],
107
+ tokenBudget: 16000,
108
+ },
109
+ defaults: {
110
+ workflow: "software-engineering/feature-implementation",
111
+ harness: "pi",
112
+ },
113
+ dash: {
114
+ defaultScreen: "agents",
115
+ refreshIntervalMs: 1000,
116
+ autoOpen: false,
117
+ },
118
+ sync: {
119
+ defaultTracker: "none",
120
+ },
121
+ improvement: {
122
+ promotionPolicy: "manual_pr",
123
+ telemetryHalfLifeDays: 14,
124
+ autoThreshold: {
125
+ minRuns: 10,
126
+ minPassRate: 0.95,
127
+ minCostSavings: 0.50,
128
+ },
129
+ },
130
+ routing: {
131
+ shadowExecution: {
132
+ enabled: false,
133
+ sampleRate: 0.05,
134
+ candidateModels: [],
135
+ },
136
+ circuitBreaker: {
137
+ mode: "soft_demotion",
138
+ failureThreshold: 3,
139
+ windowSeconds: 3600,
140
+ cooldownSeconds: 1800,
141
+ penaltyMultiplier: 5.0,
142
+ },
143
+ },
144
+ telemetry: {
145
+ federated: true,
146
+ anonymize: true,
147
+ },
148
+ };
149
+
150
+ export function userConfigDirectory(overrideDir?: string): string {
151
+ if (overrideDir) return resolve(overrideDir);
152
+ return resolve(process.env.KXM_USER_CONFIG_DIR?.trim() || join(homedir(), ".config", "kxm"));
153
+ }
154
+
155
+ export function userTelemetryDirectory(overrideDir?: string): string {
156
+ if (overrideDir) return resolve(overrideDir);
157
+ return resolve(process.env.KXM_USER_TELEMETRY_DIR?.trim() || join(homedir(), ".config", "kxm", "telemetry"));
158
+ }
159
+
160
+ export function repoConfigDirectory(repoRoot: string): string {
161
+ return resolve(repoRoot, ".kxm");
162
+ }
163
+
164
+ function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T {
165
+ const result = { ...target } as Record<string, unknown>;
166
+ for (const [key, val] of Object.entries(source)) {
167
+ if (val && typeof val === "object" && !Array.isArray(val)) {
168
+ const existing = (result[key] && typeof result[key] === "object" && !Array.isArray(result[key]))
169
+ ? (result[key] as Record<string, unknown>)
170
+ : {};
171
+ result[key] = deepMerge(existing, val as Record<string, unknown>);
172
+ } else if (val !== undefined) {
173
+ result[key] = val;
174
+ }
175
+ }
176
+ return result as T;
177
+ }
178
+
179
+ export function loadKxmConfig(
180
+ repoRoot = process.cwd(),
181
+ options: { userConfigDir?: string } = {},
182
+ ): KxmResolvedConfig {
183
+ const userDir = userConfigDirectory(options.userConfigDir);
184
+ const userConfigFile = join(userDir, "config.yaml");
185
+
186
+ const repoDir = repoConfigDirectory(repoRoot);
187
+ const repoConfigFile = join(repoDir, "config.yaml");
188
+
189
+ let userRaw: Record<string, unknown> = {};
190
+ let userLoadedPath: string | undefined;
191
+ if (existsSync(userConfigFile)) {
192
+ try {
193
+ const text = readFileSync(userConfigFile, "utf8");
194
+ userRaw = (parse(text) as Record<string, unknown>) ?? {};
195
+ userLoadedPath = userConfigFile;
196
+ } catch {
197
+ // ignore parse error, fallback
198
+ }
199
+ }
200
+
201
+ let repoRaw: Record<string, unknown> = {};
202
+ let repoLoadedPath: string | undefined;
203
+ if (existsSync(repoConfigFile)) {
204
+ try {
205
+ const text = readFileSync(repoConfigFile, "utf8");
206
+ repoRaw = (parse(text) as Record<string, unknown>) ?? {};
207
+ repoLoadedPath = repoConfigFile;
208
+ } catch {
209
+ // ignore parse error, fallback
210
+ }
211
+ }
212
+
213
+ // Base -> User -> Repo
214
+ const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG)) as typeof DEFAULT_KXM_CONFIG;
215
+ const mergedUser = deepMerge(baseCopy as unknown as Record<string, unknown>, userRaw);
216
+ const mergedAll = deepMerge(mergedUser, repoRaw);
217
+
218
+ return {
219
+ schema: KXM_CONFIG_SCHEMA,
220
+ user: (mergedAll.user as KxmUserConfig) ?? {},
221
+ defaults: (mergedAll.defaults as KxmProjectDefaults) ?? {},
222
+ dash: (mergedAll.dash as KxmDashConfig) ?? {},
223
+ sync: (mergedAll.sync as KxmSyncTrackerConfig) ?? {},
224
+ improvement: (mergedAll.improvement as KxmImprovementConfig) ?? DEFAULT_KXM_CONFIG.improvement,
225
+ routing: (mergedAll.routing as KxmRoutingConfig) ?? DEFAULT_KXM_CONFIG.routing,
226
+ telemetry: (mergedAll.telemetry as KxmTelemetryConfig) ?? DEFAULT_KXM_CONFIG.telemetry,
227
+ loadedFrom: {
228
+ userConfigPath: userLoadedPath,
229
+ repoConfigPath: repoLoadedPath,
230
+ },
231
+ };
232
+ }
233
+
234
+ export function getKxmConfigValue(config: KxmResolvedConfig, keyPath: string): unknown {
235
+ const parts = keyPath.split(".");
236
+ let current: unknown = config;
237
+ for (const part of parts) {
238
+ if (!current || typeof current !== "object") return undefined;
239
+ current = (current as Record<string, unknown>)[part];
240
+ }
241
+ return current;
242
+ }
243
+
244
+ export function setKxmConfigValue(
245
+ repoRoot: string,
246
+ keyPath: string,
247
+ value: unknown,
248
+ options: { scope?: "user" | "project"; userConfigDir?: string } = {},
249
+ ): void {
250
+ const scope = options.scope ?? "project";
251
+ const targetFile = scope === "user"
252
+ ? join(userConfigDirectory(options.userConfigDir), "config.yaml")
253
+ : join(repoConfigDirectory(repoRoot), "config.yaml");
254
+
255
+ mkdirSync(dirname(targetFile), { recursive: true });
256
+
257
+ let existing: Record<string, unknown> = {};
258
+ if (existsSync(targetFile)) {
259
+ try {
260
+ existing = (parse(readFileSync(targetFile, "utf8")) as Record<string, unknown>) ?? {};
261
+ } catch {
262
+ existing = {};
263
+ }
264
+ }
265
+
266
+ const parts = keyPath.split(".");
267
+ let cursor = existing;
268
+ for (let i = 0; i < parts.length - 1; i++) {
269
+ const p = parts[i]!;
270
+ if (!cursor[p] || typeof cursor[p] !== "object") {
271
+ cursor[p] = {};
272
+ }
273
+ cursor = cursor[p] as Record<string, unknown>;
274
+ }
275
+ cursor[parts[parts.length - 1]!] = value;
276
+
277
+ writeFileSync(targetFile, stringify(existing).trim() + "\n", "utf8");
278
+ }
279
+
280
+ export function formatKxmConfig(config: KxmResolvedConfig): string {
281
+ const display = {
282
+ schema: config.schema,
283
+ user: config.user,
284
+ defaults: config.defaults,
285
+ dash: config.dash,
286
+ sync: config.sync,
287
+ loadedFrom: config.loadedFrom,
288
+ };
289
+ return stringify(display).trim();
290
+ }
@@ -0,0 +1,101 @@
1
+ import type { ContextItem, ContextItemKind } from "../context.ts";
2
+
3
+ /**
4
+ * Internal context provider seams. These interfaces exist so optional backends
5
+ * (a native SQLite provider, a Graphiti temporal-graph adapter, an
6
+ * experimental Hindsight adapter, or a filesystem/Git wiki provider) can be
7
+ * evaluated later without changing KXM's public API.
8
+ *
9
+ * Agents NEVER bind to these interfaces. Agent-facing surfaces are the
10
+ * `kxm context` CLI and the `kxm_context`/`kxm_recall`/`kxm_state`/
11
+ * `kxm_episode`/`kxm_promote` Pi/MCP tools, which route exclusively through
12
+ * the KXM context engine. Provider details, credentials, and failure modes
13
+ * must remain invisible to agent prompts.
14
+ */
15
+
16
+ /** Bounded recall request scoped to exactly one project. Providers must fail
17
+ * closed (return nothing) rather than approximate across project bounds. */
18
+ export interface ProviderRecallRequest {
19
+ project: string;
20
+ kinds: ContextItemKind[];
21
+ /** Maximum items a provider may return. Hard ceiling. */
22
+ limit: number;
23
+ }
24
+
25
+ /** Recall providers surface candidate context items. They cannot grant
26
+ * authority: every returned item still passes arbiter provenance/authority
27
+ * rules before reaching an agent. */
28
+ export interface ContextProvider {
29
+ readonly name: string;
30
+ recall(request: ProviderRecallRequest): Promise<ContextItem[]>;
31
+ /** Health signal for telemetry. Unhealthy providers shrink context; they
32
+ * never widen authority or scope. */
33
+ healthy(): Promise<boolean>;
34
+ }
35
+
36
+ /** A proposed change to one state key. Proposal alone changes nothing: state
37
+ * only moves on an evidence-backed, authorized promotion. */
38
+ export interface StateChangeProposal {
39
+ schema: "kxm.state-change-proposal.v1";
40
+ project: string;
41
+ key: string;
42
+ summary: string;
43
+ authority: ContextItem["authority"];
44
+ confidence: ContextItem["confidence"];
45
+ evidenceRefs: string[];
46
+ proposedBy: string;
47
+ supersedes?: string[];
48
+ }
49
+
50
+ /** Temporal state providers implement current/superseded/proposed/rejected
51
+ * lifecycle with historical queries. One current value per key unless the
52
+ * state schema explicitly declares a set-valued key. */
53
+ export interface StateProvider {
54
+ readonly name: string;
55
+ /** Current value for a key at a point in time, or null when absent. */
56
+ get(project: string, key: string, asOf?: string): Promise<ContextItem | null>;
57
+ /** Record a proposal; returns the durable proposal ID. */
58
+ propose(change: StateChangeProposal): Promise<string>;
59
+ /** Promote a proposal with durable evidence. Callers must be authorized by
60
+ * the control plane; agents may propose but never silently promote. */
61
+ promote(proposalId: string, evidence: string[], promotedBy: string): Promise<ContextItem>;
62
+ /** Supersession graph query: the item superseded by `id`, if any. */
63
+ supersededBy(project: string, id: string): Promise<ContextItem | null>;
64
+ }
65
+
66
+ /** Registry of internal providers. Bounded, named, and project-scoped; a
67
+ * provider failure fails closed to a smaller context, never broader
68
+ * authority. */
69
+ export class ContextProviderRegistry {
70
+ private readonly contextProviders = new Map<string, ContextProvider>();
71
+ private readonly stateProviders = new Map<string, StateProvider>();
72
+
73
+ registerContextProvider(provider: ContextProvider): void {
74
+ this.contextProviders.set(provider.name, provider);
75
+ }
76
+
77
+ registerStateProvider(provider: StateProvider): void {
78
+ this.stateProviders.set(provider.name, provider);
79
+ }
80
+
81
+ get contextProviderNames(): string[] {
82
+ return [...this.contextProviders.keys()].sort();
83
+ }
84
+
85
+ get stateProviderNames(): string[] {
86
+ return [...this.stateProviders.keys()].sort();
87
+ }
88
+
89
+ async recallFromAll(request: ProviderRecallRequest): Promise<ContextItem[]> {
90
+ const collected: ContextItem[] = [];
91
+ for (const provider of this.contextProviders.values()) {
92
+ try {
93
+ const items = await provider.recall(request);
94
+ collected.push(...items.slice(0, request.limit));
95
+ } catch {
96
+ // Fail closed: an unhealthy provider contributes nothing.
97
+ }
98
+ }
99
+ return collected.slice(0, request.limit);
100
+ }
101
+ }