@kontextmind/kxm 0.7.6 → 0.7.8

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -0,0 +1,373 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { redactSecrets } from "../redact.ts";
4
+ import {
5
+ createMemoryNote,
6
+ formatMemoryBriefText,
7
+ generateMemoryBrief,
8
+ syncHarnessMemory,
9
+ type MemoryScope,
10
+ } from "../memory.ts";
11
+ import { SkillLifecycle, type SkillEvaluationKind, type SkillState } from "../skills.ts";
12
+ import { writeCompiledWiki } from "../wiki.ts";
13
+ import { print, type Runtime } from "./types.ts";
14
+
15
+ /** Authenticated hub POST for context operations. The CLI operates as the
16
+ * control plane: the administrative token scopes one project per request. */
17
+ export async function hubContextPost(input: {
18
+ serverUrl: string;
19
+ path: string;
20
+ body: Record<string, unknown>;
21
+ authToken?: string | undefined;
22
+ fetchImpl: typeof fetch;
23
+ }): Promise<{ ok: boolean; status: number; body: unknown }> {
24
+ const response = await input.fetchImpl(`${input.serverUrl.replace(/\/$/, "")}${input.path}`, {
25
+ method: "POST",
26
+ headers: {
27
+ "content-type": "application/json",
28
+ ...(input.authToken ? { authorization: `Bearer ${input.authToken}` } : {}),
29
+ },
30
+ body: JSON.stringify(input.body),
31
+ });
32
+ const text = redactSecrets((await response.text()).slice(0, 64_000));
33
+ let body: unknown = text;
34
+ try {
35
+ body = JSON.parse(text);
36
+ } catch {
37
+ // Keep text for diagnostics without treating it as a secret.
38
+ }
39
+ return { ok: response.ok, status: response.status, body };
40
+ }
41
+
42
+ export function parseContextKinds(value: string | undefined): string[] | undefined {
43
+ if (!value) return undefined;
44
+ return value.split(",").map((kind) => kind.trim()).filter((kind) => kind.length > 0);
45
+ }
46
+
47
+ export async function cmdContextGet(runtime: Runtime, project: string, options: { role: string; task: string; run?: string | undefined; stage?: string | undefined; budget?: string | undefined; kinds?: string | undefined }): Promise<number> {
48
+ const budget = options.budget === undefined ? undefined : Number(options.budget);
49
+ if (options.budget !== undefined && (!Number.isInteger(budget) || (budget as number) < 512 || (budget as number) > 200_000)) {
50
+ runtime.io.stderr("context get --budget must be an integer between 512 and 200000\n");
51
+ return 2;
52
+ }
53
+ const body: Record<string, unknown> = {
54
+ project,
55
+ role: options.role,
56
+ task: options.task,
57
+ };
58
+ if (options.run) body.workflowRunId = options.run;
59
+ if (options.stage) body.stageId = options.stage;
60
+ if (budget !== undefined) body.budgetTokens = budget;
61
+ const kinds = parseContextKinds(options.kinds);
62
+ if (kinds) body.includeKinds = kinds;
63
+ const response = await hubContextPost({
64
+ serverUrl: runtime.serverUrl,
65
+ path: "/v1/context/get",
66
+ body,
67
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
68
+ fetchImpl: runtime.fetchImpl,
69
+ });
70
+ print(runtime.io, runtime.json, { ok: response.ok, command: "context get", status: response.status, ...(response.body as object) }, `context get ${response.ok ? "assembled" : `failed (${response.status})`}`);
71
+ return response.ok ? 0 : 1;
72
+ }
73
+
74
+ export async function cmdContextRecall(runtime: Runtime, project: string, options: { query?: string | undefined; kinds?: string | undefined; limit?: string | undefined }): Promise<number> {
75
+ const body: Record<string, unknown> = { project };
76
+ if (options.query) body.query = options.query;
77
+ const kinds = parseContextKinds(options.kinds);
78
+ if (kinds) body.kinds = kinds;
79
+ if (options.limit) body.limit = Number(options.limit);
80
+ const response = await hubContextPost({
81
+ serverUrl: runtime.serverUrl,
82
+ path: "/v1/context/recall",
83
+ body,
84
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
85
+ fetchImpl: runtime.fetchImpl,
86
+ });
87
+ print(runtime.io, runtime.json, { ok: response.ok, command: "context recall", status: response.status, ...(response.body as object) }, `context recall ${response.ok ? "complete" : `failed (${response.status})`}`);
88
+ return response.ok ? 0 : 1;
89
+ }
90
+
91
+ export async function cmdContextState(runtime: Runtime, project: string, key: string, options: { asOf?: string | undefined }): Promise<number> {
92
+ const body: Record<string, unknown> = { project, key };
93
+ if (options.asOf) body.asOf = options.asOf;
94
+ const response = await hubContextPost({
95
+ serverUrl: runtime.serverUrl,
96
+ path: "/v1/context/state",
97
+ body,
98
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
99
+ fetchImpl: runtime.fetchImpl,
100
+ });
101
+ print(runtime.io, runtime.json, { ok: response.ok, command: "context state", status: response.status, ...(response.body as object) }, `context state ${response.ok ? "resolved" : `failed (${response.status})`}`);
102
+ return response.ok ? 0 : 1;
103
+ }
104
+
105
+ export async function cmdContextEpisode(runtime: Runtime, project: string, options: { run?: string | undefined }): Promise<number> {
106
+ const body: Record<string, unknown> = { project };
107
+ if (options.run) body.workflowRunId = options.run;
108
+ const response = await hubContextPost({
109
+ serverUrl: runtime.serverUrl,
110
+ path: "/v1/context/episode",
111
+ body,
112
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
113
+ fetchImpl: runtime.fetchImpl,
114
+ });
115
+ print(runtime.io, runtime.json, { ok: response.ok, command: "context episode", status: response.status, ...(response.body as object) }, `context episode ${response.ok ? "complete" : `failed (${response.status})`}`);
116
+ return response.ok ? 0 : 1;
117
+ }
118
+
119
+ export async function cmdContextPromote(runtime: Runtime, project: string, proposalId: string, options: { evidence: string }): Promise<number> {
120
+ const evidence = options.evidence.split(",").map((ref) => ref.trim()).filter((ref) => ref.length > 0);
121
+ if (evidence.length === 0) {
122
+ runtime.io.stderr("context promote --evidence must contain at least one durable evidence reference\n");
123
+ return 2;
124
+ }
125
+ const response = await hubContextPost({
126
+ serverUrl: runtime.serverUrl,
127
+ path: "/v1/context/state/promote",
128
+ body: { project, proposalId, evidence },
129
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
130
+ fetchImpl: runtime.fetchImpl,
131
+ });
132
+ print(runtime.io, runtime.json, { ok: response.ok, command: "context promote", status: response.status, ...(response.body as object) }, `context promote ${response.ok ? "recorded" : `failed (${response.status})`}`);
133
+ return response.ok ? 0 : 1;
134
+ }
135
+
136
+ export async function cmdContextExplain(runtime: Runtime, project: string, itemId: string): Promise<number> {
137
+ const response = await hubContextPost({
138
+ serverUrl: runtime.serverUrl,
139
+ path: "/v1/context/explain",
140
+ body: { project, id: itemId },
141
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
142
+ fetchImpl: runtime.fetchImpl,
143
+ });
144
+ print(runtime.io, runtime.json, { ok: response.ok, command: "context explain", status: response.status, ...(response.body as object) }, `context explain ${response.ok ? "complete" : `failed (${response.status})`}`);
145
+ return response.ok ? 0 : 1;
146
+ }
147
+
148
+ export async function cmdContextWikiCompile(runtime: Runtime, project: string, options: { out?: string | undefined }): Promise<number> {
149
+ const response = await hubContextPost({
150
+ serverUrl: runtime.serverUrl,
151
+ path: "/v1/context/wiki/compile",
152
+ body: { project },
153
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
154
+ fetchImpl: runtime.fetchImpl,
155
+ });
156
+ if (!response.ok) {
157
+ print(runtime.io, runtime.json, { ok: false, command: "context wiki-compile", status: response.status, body: response.body }, `wiki compile failed (${response.status})`);
158
+ return 1;
159
+ }
160
+ const compiled = response.body as { audit: { pages: string[]; contradictions: number }; pages: { path: string; content: string }[] };
161
+ let written: string[] = [];
162
+ if (options.out) {
163
+ const pages = new Map(compiled.pages.map((page) => [page.path, page.content]));
164
+ written = writeCompiledWiki(options.out, { pages, index: pages.get(".kxm/knowledge/wiki/index.md") ?? "", audit: { project, pages: compiled.audit.pages, stateItems: 0, contextItems: 0, contradictions: compiled.audit.contradictions, compiledAt: "" } });
165
+ }
166
+ print(runtime.io, runtime.json, { ok: true, command: "context wiki-compile", project, pages: compiled.audit.pages, openContradictions: compiled.audit.contradictions, ...(written.length > 0 ? { written: written.length, outDir: options.out } : { dryRun: true }) }, `compiled ${compiled.audit.pages.length} wiki page(s)${written.length > 0 ? ` to ${options.out}` : " (dry-run)"}`);
167
+ return 0;
168
+ }
169
+
170
+ export async function cmdContextWikiLint(runtime: Runtime, project: string): Promise<number> {
171
+ const response = await hubContextPost({
172
+ serverUrl: runtime.serverUrl,
173
+ path: "/v1/context/wiki/compile",
174
+ body: { project },
175
+ ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
176
+ fetchImpl: runtime.fetchImpl,
177
+ });
178
+ if (!response.ok) {
179
+ print(runtime.io, runtime.json, { ok: false, command: "context wiki-lint", status: response.status, body: response.body }, `wiki lint failed (${response.status})`);
180
+ return 1;
181
+ }
182
+ const compiled = response.body as {
183
+ audit: { stateItems: number; contextItems: number; contradictions: number; compiledAt: string };
184
+ lint: { severity: string; rule: string; path: string; message: string }[];
185
+ };
186
+ const issues = compiled.lint;
187
+ print(runtime.io, runtime.json, { ok: issues.length === 0, command: "context wiki-lint", project, issues, audit: compiled.audit }, issues.length === 0 ? "wiki lint clean" : `wiki lint found ${issues.length} issue(s)`);
188
+ return issues.every((issue) => issue.severity !== "error") ? 0 : 1;
189
+ }
190
+
191
+ export function skillStateFromFlag(value: string): SkillState {
192
+ if (value === "candidate" || value === "promoted" || value === "quarantined" || value === "rejected") return value;
193
+ throw new Error(`invalid skill state ${value}`);
194
+ }
195
+
196
+ export function skillsRoot(runtime: Runtime): string {
197
+ return join(runtime.dirs.workdir, ".kxm", "skills");
198
+ }
199
+
200
+ export function csv(value: string | undefined): string[] | undefined {
201
+ if (!value) return undefined;
202
+ const items = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
203
+ return items.length > 0 ? items : undefined;
204
+ }
205
+
206
+ export async function cmdSkillsCreate(runtime: Runtime, options: { file: string; name: string; description?: string | undefined; createdBy: string; run?: string | undefined; journal?: string | undefined; receipt?: string | undefined; harness: string; models: string; supersedes?: string | undefined }): Promise<number> {
207
+ if (runtime.dryRun) {
208
+ print(runtime.io, runtime.json, { ok: true, command: "skills create", dryRun: true, name: options.name }, "would create skill candidate");
209
+ return 0;
210
+ }
211
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime));
212
+ try {
213
+ const metadata = lifecycle.create({
214
+ name: options.name,
215
+ description: options.description ?? "",
216
+ content: readFileSync(options.file, "utf8"),
217
+ createdBy: options.createdBy,
218
+ sources: {
219
+ runIds: csv(options.run) ?? [],
220
+ journalEntryIds: csv(options.journal) ?? [],
221
+ evidenceReceipts: csv(options.receipt) ?? [],
222
+ },
223
+ compatibility: { harness: options.harness, models: csv(options.models) ?? [] },
224
+ ...(options.supersedes ? { supersedes: options.supersedes } : {}),
225
+ });
226
+ print(runtime.io, runtime.json, { ok: true, command: "skills create", metadata }, `created skill candidate ${metadata.id}`);
227
+ return 0;
228
+ } catch (error) {
229
+ const message = error instanceof Error ? error.message : String(error);
230
+ runtime.io.stderr(`skills create failed: ${message}\n`);
231
+ return 1;
232
+ }
233
+ }
234
+
235
+ export async function cmdSkillsEvaluate(runtime: Runtime, skillId: string, options: { kind: string; evaluator: string; fail?: boolean | undefined; score?: string | undefined; details?: string | undefined }): Promise<number> {
236
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime));
237
+ try {
238
+ const outcome = lifecycle.evaluate(skillId, {
239
+ kind: options.kind as SkillEvaluationKind,
240
+ evaluatorVersion: options.evaluator,
241
+ passed: options.fail !== true,
242
+ ...(options.score !== undefined ? { score: Number(options.score) } : {}),
243
+ ...(options.details ? { details: options.details } : {}),
244
+ });
245
+ print(runtime.io, runtime.json, { ok: true, command: "skills evaluate", skillId, quarantined: outcome.quarantined, evaluation: outcome.evaluation }, `recorded ${options.kind} evaluation${outcome.quarantined ? " (candidate quarantined)" : ""}`);
246
+ return 0;
247
+ } catch (error) {
248
+ const message = error instanceof Error ? error.message : String(error);
249
+ runtime.io.stderr(`skills evaluate failed: ${message}\n`);
250
+ return 1;
251
+ }
252
+ }
253
+
254
+ export async function cmdSkillsPromote(runtime: Runtime, skillId: string, options: { decidedBy: string; evidence: string; reason?: string | undefined }): Promise<number> {
255
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime));
256
+ try {
257
+ const promoted = lifecycle.promote(skillId, {
258
+ decidedBy: options.decidedBy,
259
+ reason: options.reason ?? "passed protected evaluation",
260
+ evidenceRefs: csv(options.evidence) ?? [],
261
+ });
262
+ print(runtime.io, runtime.json, { ok: true, command: "skills promote", skillId, metadata: promoted, patchPath: promoted.patchPath }, `promoted skill ${skillId} (patch: ${promoted.patchPath})`);
263
+ return 0;
264
+ } catch (error) {
265
+ const message = error instanceof Error ? error.message : String(error);
266
+ runtime.io.stderr(`skills promote failed: ${message}\n`);
267
+ return 1;
268
+ }
269
+ }
270
+
271
+ export async function cmdSkillsReject(runtime: Runtime, skillId: string, options: { decidedBy: string; reason?: string | undefined }): Promise<number> {
272
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime));
273
+ try {
274
+ const metadata = lifecycle.reject(skillId, { decidedBy: options.decidedBy, reason: options.reason ?? "rejected" });
275
+ print(runtime.io, runtime.json, { ok: true, command: "skills reject", skillId, metadata }, `rejected skill ${skillId} (history retained)`);
276
+ return 0;
277
+ } catch (error) {
278
+ const message = error instanceof Error ? error.message : String(error);
279
+ runtime.io.stderr(`skills reject failed: ${message}\n`);
280
+ return 1;
281
+ }
282
+ }
283
+
284
+ export async function cmdSkillsList(runtime: Runtime, options: { state: string }): Promise<number> {
285
+ try {
286
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime));
287
+ const state = skillStateFromFlag(options.state);
288
+ const items = lifecycle.list(state).map((metadata) => ({
289
+ id: metadata.id,
290
+ name: metadata.name,
291
+ version: metadata.version,
292
+ createdBy: metadata.createdBy,
293
+ createdAt: metadata.createdAt,
294
+ models: metadata.compatibility.models,
295
+ }));
296
+ print(runtime.io, runtime.json, { ok: true, command: "skills list", state: options.state, skills: items }, `${items.length} ${state} skill(s)`);
297
+ return 0;
298
+ } catch (error) {
299
+ const message = error instanceof Error ? error.message : String(error);
300
+ runtime.io.stderr(`skills list failed: ${message}\n`);
301
+ return 1;
302
+ }
303
+ }
304
+
305
+ export async function cmdSkillsVerify(runtime: Runtime, skillId: string, options: { state: string }): Promise<number> {
306
+ try {
307
+ const lifecycle = new SkillLifecycle(skillsRoot(runtime));
308
+ const state = skillStateFromFlag(options.state);
309
+ const metadata = lifecycle.verify(state, skillId);
310
+ print(runtime.io, runtime.json, { ok: true, command: "skills verify", skillId, state: options.state, contentSha256: metadata.contentSha256 }, `skill ${skillId} integrity verified`);
311
+ return 0;
312
+ } catch (error) {
313
+ const message = error instanceof Error ? error.message : String(error);
314
+ runtime.io.stderr(`skills verify failed: ${message}\n`);
315
+ return 1;
316
+ }
317
+ }
318
+
319
+ export async function cmdMemoryBrief(runtime: Runtime): Promise<number> {
320
+ try {
321
+ const brief = generateMemoryBrief(runtime.cwd);
322
+ const text = formatMemoryBriefText(brief);
323
+ print(runtime.io, runtime.json, { ok: true, command: "memory brief", brief }, text);
324
+ return 0;
325
+ } catch (error) {
326
+ const message = error instanceof Error ? error.message : String(error);
327
+ runtime.io.stderr(`memory brief failed: ${message}\n`);
328
+ return 1;
329
+ }
330
+ }
331
+
332
+ export async function cmdMemoryNote(
333
+ runtime: Runtime,
334
+ fact: string,
335
+ options: { scope?: string | undefined; kind?: string | undefined; body?: string | undefined },
336
+ ): Promise<number> {
337
+ try {
338
+ const { record, path } = createMemoryNote(runtime.cwd, fact, {
339
+ scope: (options.scope ?? "project") as MemoryScope,
340
+ ...(options.kind !== undefined ? { kind: options.kind } : {}),
341
+ ...(options.body !== undefined ? { body: options.body } : {}),
342
+ });
343
+ const relPath = relative(runtime.cwd, path);
344
+ print(
345
+ runtime.io,
346
+ runtime.json,
347
+ { ok: true, command: "memory note", candidate: record, path: relPath },
348
+ `Recorded memory candidate ${record.id} in ${relPath} (promoted via PR)`,
349
+ );
350
+ return 0;
351
+ } catch (error) {
352
+ const message = error instanceof Error ? error.message : String(error);
353
+ runtime.io.stderr(`memory note failed: ${message}\n`);
354
+ return 1;
355
+ }
356
+ }
357
+
358
+ export async function cmdMemorySync(runtime: Runtime): Promise<number> {
359
+ try {
360
+ const result = syncHarnessMemory(runtime.cwd);
361
+ print(
362
+ runtime.io,
363
+ runtime.json,
364
+ { ok: true, command: "memory sync", ...result },
365
+ `Synced project memory across AGENTS.md, CLAUDE.md, and GEMINI.md`,
366
+ );
367
+ return 0;
368
+ } catch (error) {
369
+ const message = error instanceof Error ? error.message : String(error);
370
+ runtime.io.stderr(`memory sync failed: ${message}\n`);
371
+ return 1;
372
+ }
373
+ }