@kontextmind/kxm 0.7.91 → 0.7.93
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/workflows/default.yaml +1 -1
- package/CHANGELOG.md +212 -0
- package/README.md +3 -0
- package/docs/README.md +3 -0
- package/docs/agent-skills.md +123 -60
- package/docs/architecture.md +5 -2
- package/docs/cli-reference.md +3527 -0
- package/docs/config-reference.md +1943 -0
- package/docs/configuration.md +30 -4
- package/docs/continuous-improvement.md +122 -10
- package/docs/contracts/routing.md +95 -11
- package/docs/harness-routing.md +616 -0
- package/docs/kxm-handbook.md +106 -19
- package/docs/templates/README.md +1 -1
- package/docs/test-matrix.md +12 -6
- package/docs/troubleshooting.md +2 -2
- package/examples/project/.kxm/workflows/fix.yaml +1 -1
- package/examples/project/.kxm/workflows/improve.yaml +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +9 -10
- package/plugins/kxm/README.md +238 -56
- package/plugins/kxm/dist/claude-hook.js +10083 -0
- package/plugins/kxm/dist/cli.js +2487 -1848
- package/plugins/kxm/dist/client.js +64 -0
- package/plugins/kxm/dist/core.js +102 -9
- package/plugins/kxm/dist/extension.js +210 -68
- package/plugins/kxm/dist/mcp-server.js +217 -40
- package/plugins/kxm/dist/runtime-supervisor.js +1628 -157
- package/plugins/kxm/dist/runtime.js +1874 -298
- package/plugins/kxm/dist/server.js +416 -82
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/hints.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +48 -24
- package/plugins/kxm/skills/kxm/references/protocol.md +3 -3
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +67 -21
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +9 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +82 -16
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +55 -27
- package/plugins/kxm/skills/kxm-insights/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-mind/SKILL.md +2 -2
- package/plugins/kxm/skills/{kxm-setup → kxm-mind-setup}/SKILL.md +4 -4
- package/plugins/kxm/skills/kxm-peer/SKILL.md +68 -93
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +156 -23
- package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-query/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +74 -15
- package/plugins/kxm/skills/kxm-runs/SKILL.md +46 -17
- package/plugins/kxm/skills/kxm-session/SKILL.md +64 -36
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +44 -15
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +16 -4
- package/plugins/kxm/skills/kxm-triage/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-work/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +60 -19
- package/plugins/kxm/src/arbiter.ts +67 -22
- package/plugins/kxm/src/autocomplete.ts +1 -1
- package/plugins/kxm/src/claude-hook.ts +192 -0
- package/plugins/kxm/src/cli/project.ts +11 -5
- package/plugins/kxm/src/cli/system.ts +85 -13
- package/plugins/kxm/src/cli/types.ts +4 -1
- package/plugins/kxm/src/cli/workflows.ts +18 -16
- package/plugins/kxm/src/cli.ts +23 -13
- package/plugins/kxm/src/client.ts +15 -4
- package/plugins/kxm/src/commands.ts +19 -9
- package/plugins/kxm/src/config.ts +42 -7
- package/plugins/kxm/src/context-packet.ts +14 -2
- package/plugins/kxm/src/context.ts +16 -5
- package/plugins/kxm/src/dispatch-context.ts +286 -0
- package/plugins/kxm/src/engine-plan.ts +40 -0
- package/plugins/kxm/src/engine.ts +138 -6
- package/plugins/kxm/src/hub-env.ts +17 -1
- package/plugins/kxm/src/hub.ts +92 -29
- package/plugins/kxm/src/improve-sources.ts +228 -0
- package/plugins/kxm/src/improve.ts +325 -140
- package/plugins/kxm/src/local-snapshot.ts +101 -42
- package/plugins/kxm/src/mcp-server.ts +129 -30
- package/plugins/kxm/src/memory.ts +43 -20
- package/plugins/kxm/src/project-config.ts +25 -0
- package/plugins/kxm/src/protocol.ts +11 -0
- package/plugins/kxm/src/relevance.ts +138 -0
- package/plugins/kxm/src/retrospective.ts +16 -10
- package/plugins/kxm/src/runtime-service.ts +8 -1
- package/plugins/kxm/src/runtime-supervisor.ts +16 -2
- package/plugins/kxm/src/session-token-hint.ts +17 -0
- package/plugins/kxm/src/suggest.ts +7 -7
- package/plugins/kxm/src/workflow-manager.ts +80 -78
- package/plugins/kxm/src/workflow.ts +202 -12
- package/scripts/build-runtime.mjs +7 -1
- package/scripts/check-generated.mjs +1 -0
- package/scripts/emit-codex-artifacts.mjs +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "./sqlite.ts";
|
|
4
|
+
import { discoverKxmProjectRoot } from "./project-config.ts";
|
|
5
|
+
import { kxmRuntimePaths, projectRuntimeKey } from "./runtime-store.ts";
|
|
6
|
+
import { parseRoutingRecordV2, ROUTING_RECORD_V2_SCHEMA, type RoutingRecord, type RoutingRecordV2 } from "./routing.ts";
|
|
7
|
+
import { readRoutingRecords, telemetryPath } from "./telemetry.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Routing-record sources for `kxm improve` and `kxm routing report`.
|
|
11
|
+
*
|
|
12
|
+
* The project's Runtime event store is read read-only: one SELECT over the
|
|
13
|
+
* events table, never the runs table, the run plans, or the prompts sidecar,
|
|
14
|
+
* and never a write or a migration. Each settled attempt's outcome is resolved
|
|
15
|
+
* in memory from the event log; nothing resolved here is stored.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface RoutingSourceSummary {
|
|
19
|
+
kind: "engine" | "telemetry" | "file";
|
|
20
|
+
path: string;
|
|
21
|
+
exists: boolean;
|
|
22
|
+
/** Records this source contributed before cross-source de-duplication. */
|
|
23
|
+
records: number;
|
|
24
|
+
skippedInvalid?: number;
|
|
25
|
+
excludedSimulated?: number;
|
|
26
|
+
undecided?: number;
|
|
27
|
+
duplicatesDropped?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EngineRoutingRead {
|
|
31
|
+
records: RoutingRecordV2[];
|
|
32
|
+
source: RoutingSourceSummary;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LoadedRoutingSources {
|
|
36
|
+
projectRoot?: string;
|
|
37
|
+
records: Array<RoutingRecord | RoutingRecordV2>;
|
|
38
|
+
sources: RoutingSourceSummary[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ENGINE_EVENTS_SQL = "SELECT run_id, sequence, event_type, payload FROM events"
|
|
42
|
+
+ " WHERE event_type IN ('routing.attempt.recorded','step.entered','run.status_changed')"
|
|
43
|
+
+ " ORDER BY run_id, sequence";
|
|
44
|
+
|
|
45
|
+
/** The simulated producer's harness label; its attempts measure nothing. */
|
|
46
|
+
const SIMULATED_HARNESS = "driver-simulated";
|
|
47
|
+
|
|
48
|
+
/** The project's Runtime event store, derived exactly as the Runtime derives it. */
|
|
49
|
+
export function kxmProjectRunEventsPath(projectRoot: string, env: NodeJS.ProcessEnv): string {
|
|
50
|
+
return join(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface RunLog {
|
|
54
|
+
lastStatus?: string;
|
|
55
|
+
/** Latest step.entered sequence per step. */
|
|
56
|
+
lastEntered: Map<string, number>;
|
|
57
|
+
routing: Array<{ sequence: number; payload: string }>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parsePayload(text: string): Record<string, unknown> | undefined {
|
|
61
|
+
try {
|
|
62
|
+
const value = JSON.parse(text) as unknown;
|
|
63
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Read every Runtime-settled routing record from one project's event store.
|
|
71
|
+
*
|
|
72
|
+
* Outcomes resolve per record, first rule that applies: a record-time
|
|
73
|
+
* 'blocked' or 'failed' stays; a later step.entered for the same step is
|
|
74
|
+
* 'reworked'; a completed run is 'accepted'; a failed run is 'failed';
|
|
75
|
+
* anything else (cancelled, still running) is left unset and counted
|
|
76
|
+
* undecided. Simulated attempts are dropped and counted.
|
|
77
|
+
*/
|
|
78
|
+
export function readEngineRoutingRecords(path: string): EngineRoutingRead {
|
|
79
|
+
const source: RoutingSourceSummary = {
|
|
80
|
+
kind: "engine",
|
|
81
|
+
path,
|
|
82
|
+
exists: false,
|
|
83
|
+
records: 0,
|
|
84
|
+
skippedInvalid: 0,
|
|
85
|
+
excludedSimulated: 0,
|
|
86
|
+
undecided: 0,
|
|
87
|
+
duplicatesDropped: 0,
|
|
88
|
+
};
|
|
89
|
+
if (!existsSync(path)) return { records: [], source };
|
|
90
|
+
source.exists = true;
|
|
91
|
+
|
|
92
|
+
let rows: Array<Record<string, unknown>>;
|
|
93
|
+
let database: DatabaseSync | undefined;
|
|
94
|
+
try {
|
|
95
|
+
database = new DatabaseSync(path, { readOnly: true });
|
|
96
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
97
|
+
rows = database.prepare(ENGINE_EVENTS_SQL).all();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
throw new Error(`improve_source_unreadable: ${path}: ${message}`, { cause: error });
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
database?.close();
|
|
104
|
+
} catch {
|
|
105
|
+
// Closing a read-only handle has nothing to flush.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const runs = new Map<string, RunLog>();
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const runId = String(row.run_id);
|
|
112
|
+
const sequence = Number(row.sequence);
|
|
113
|
+
const eventType = String(row.event_type);
|
|
114
|
+
const payload = typeof row.payload === "string" ? row.payload : "";
|
|
115
|
+
let run = runs.get(runId);
|
|
116
|
+
if (!run) {
|
|
117
|
+
run = { lastEntered: new Map(), routing: [] };
|
|
118
|
+
runs.set(runId, run);
|
|
119
|
+
}
|
|
120
|
+
if (eventType === "routing.attempt.recorded") {
|
|
121
|
+
run.routing.push({ sequence, payload });
|
|
122
|
+
} else if (eventType === "step.entered") {
|
|
123
|
+
const stepId = parsePayload(payload)?.stepId;
|
|
124
|
+
if (typeof stepId === "string") run.lastEntered.set(stepId, sequence);
|
|
125
|
+
} else if (eventType === "run.status_changed") {
|
|
126
|
+
const status = parsePayload(payload)?.status;
|
|
127
|
+
if (typeof status === "string") run.lastStatus = status;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let skippedInvalid = 0;
|
|
132
|
+
let excludedSimulated = 0;
|
|
133
|
+
let undecided = 0;
|
|
134
|
+
const records: RoutingRecordV2[] = [];
|
|
135
|
+
for (const run of runs.values()) {
|
|
136
|
+
for (const entry of run.routing) {
|
|
137
|
+
let record: RoutingRecordV2;
|
|
138
|
+
try {
|
|
139
|
+
record = parseRoutingRecordV2(parsePayload(entry.payload)?.routing);
|
|
140
|
+
} catch {
|
|
141
|
+
skippedInvalid += 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (record.harness === SIMULATED_HARNESS) {
|
|
145
|
+
excludedSimulated += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const recorded = record.finalOutcome;
|
|
149
|
+
if (recorded === "blocked" || recorded === "failed") {
|
|
150
|
+
// A gate-negative verdict known at settle time stands.
|
|
151
|
+
} else if ((run.lastEntered.get(record.stepId) ?? -1) > entry.sequence) {
|
|
152
|
+
record.finalOutcome = "reworked";
|
|
153
|
+
} else if (run.lastStatus === "completed") {
|
|
154
|
+
record.finalOutcome = "accepted";
|
|
155
|
+
} else if (run.lastStatus === "failed") {
|
|
156
|
+
record.finalOutcome = "failed";
|
|
157
|
+
} else {
|
|
158
|
+
delete record.finalOutcome;
|
|
159
|
+
undecided += 1;
|
|
160
|
+
}
|
|
161
|
+
records.push(record);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
records,
|
|
166
|
+
source: { ...source, records: records.length, skippedInvalid, excludedSimulated, undecided },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readJsonlSource(kind: "telemetry" | "file", path: string): { records: Array<RoutingRecord | RoutingRecordV2>; source: RoutingSourceSummary } {
|
|
171
|
+
const exists = existsSync(path);
|
|
172
|
+
const records = exists ? readRoutingRecords(path).map((entry) => entry.routing) : [];
|
|
173
|
+
return { records, source: { kind, path, exists, records: records.length, duplicatesDropped: 0 } };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function attemptKey(record: RoutingRecord | RoutingRecordV2): string | undefined {
|
|
177
|
+
if (record.schema !== ROUTING_RECORD_V2_SCHEMA) return undefined;
|
|
178
|
+
const attemptId = typeof record.attemptId === "string" ? record.attemptId.trim() : "";
|
|
179
|
+
return attemptId.length > 0 ? attemptId : undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Load routing records for improvement and routing reports.
|
|
184
|
+
*
|
|
185
|
+
* With `file`, only that file is read. Otherwise the project's Runtime event
|
|
186
|
+
* store (when cwd is inside a KXM project) comes first, then telemetry.jsonl.
|
|
187
|
+
* v2 records are de-duplicated by attemptId: the first occurrence wins and the
|
|
188
|
+
* later source counts the drop.
|
|
189
|
+
*/
|
|
190
|
+
export function loadRoutingSources(options: {
|
|
191
|
+
cwd: string;
|
|
192
|
+
env: NodeJS.ProcessEnv;
|
|
193
|
+
logsDir: string;
|
|
194
|
+
file?: string | undefined;
|
|
195
|
+
}): LoadedRoutingSources {
|
|
196
|
+
const projectRoot = discoverKxmProjectRoot(options.cwd);
|
|
197
|
+
const reads: Array<{ records: Array<RoutingRecord | RoutingRecordV2>; source: RoutingSourceSummary }> = [];
|
|
198
|
+
if (options.file !== undefined) {
|
|
199
|
+
reads.push(readJsonlSource("file", resolve(options.cwd, options.file)));
|
|
200
|
+
} else {
|
|
201
|
+
if (projectRoot !== undefined) reads.push(readEngineRoutingRecords(kxmProjectRunEventsPath(projectRoot, options.env)));
|
|
202
|
+
reads.push(readJsonlSource("telemetry", telemetryPath(options.logsDir)));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const seen = new Set<string>();
|
|
206
|
+
const records: Array<RoutingRecord | RoutingRecordV2> = [];
|
|
207
|
+
for (const read of reads) {
|
|
208
|
+
let dropped = 0;
|
|
209
|
+
for (const record of read.records) {
|
|
210
|
+
const key = attemptKey(record);
|
|
211
|
+
if (key !== undefined) {
|
|
212
|
+
if (seen.has(key)) {
|
|
213
|
+
dropped += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
seen.add(key);
|
|
217
|
+
}
|
|
218
|
+
records.push(record);
|
|
219
|
+
}
|
|
220
|
+
read.source.duplicatesDropped = dropped;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
...(projectRoot !== undefined ? { projectRoot } : {}),
|
|
225
|
+
records,
|
|
226
|
+
sources: reads.map((read) => read.source),
|
|
227
|
+
};
|
|
228
|
+
}
|