@kontextmind/kxm 0.7.5 → 0.7.6
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/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +421 -155
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +267 -16
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli.ts +39 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/modes.ts +348 -0
- package/plugins/kxm/src/runtime.ts +1 -0
- package/schemas/vnext/modes.schema.json +56 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KXM Declarative Workflow Modes, Domain Isolation, and Pre-Flight Explain
|
|
3
|
+
*
|
|
4
|
+
* Implements major modes (base role + tools + prompt) and stackable domain
|
|
5
|
+
* toolkits to eliminate prompt bloat and calculate pre-flight token costs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { parse } from "yaml";
|
|
11
|
+
import { calculateModelCost, type PriceCatalog, loadPriceCatalog } from "./prices.ts";
|
|
12
|
+
|
|
13
|
+
export interface MajorMode {
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
baseTools: string[];
|
|
16
|
+
contextFiles?: string[] | undefined;
|
|
17
|
+
thinkingLevel?: "low" | "medium" | "high" | "xhigh" | undefined;
|
|
18
|
+
model?: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Domain {
|
|
22
|
+
description?: string | undefined;
|
|
23
|
+
tools: string[];
|
|
24
|
+
contextFiles?: string[] | undefined;
|
|
25
|
+
promptSnippet?: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ModesConfig {
|
|
29
|
+
schema: "kxm.modes.v1";
|
|
30
|
+
majorModes: Record<string, MajorMode>;
|
|
31
|
+
domains?: Record<string, Domain> | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ResolvedMode {
|
|
35
|
+
majorMode: string;
|
|
36
|
+
enabledDomains: string[];
|
|
37
|
+
tools: string[];
|
|
38
|
+
contextFiles: string[];
|
|
39
|
+
promptSnippets: string[];
|
|
40
|
+
thinkingLevel?: string | undefined;
|
|
41
|
+
model?: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ContextBreakdownItem {
|
|
45
|
+
name: string;
|
|
46
|
+
chars: number;
|
|
47
|
+
estimatedTokens: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PromptFootprint {
|
|
51
|
+
majorMode: string;
|
|
52
|
+
enabledDomains: string[];
|
|
53
|
+
model: string;
|
|
54
|
+
breakdown: ContextBreakdownItem[];
|
|
55
|
+
totalChars: number;
|
|
56
|
+
totalTokens: number;
|
|
57
|
+
contextWindowRatio: number;
|
|
58
|
+
projectedCost: {
|
|
59
|
+
inputCostUsd: number | null;
|
|
60
|
+
cacheReadCostUsd: number | null;
|
|
61
|
+
outputCostEstimateUsd: number | null;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const DEFAULT_MODES_CONFIG: ModesConfig = Object.freeze({
|
|
66
|
+
schema: "kxm.modes.v1",
|
|
67
|
+
majorModes: {
|
|
68
|
+
coder: {
|
|
69
|
+
description: "First-pass implementation, bug fixing, and test authoring",
|
|
70
|
+
baseTools: ["read", "edit", "write", "bash"],
|
|
71
|
+
contextFiles: ["AGENTS.md"],
|
|
72
|
+
thinkingLevel: "medium" as const,
|
|
73
|
+
model: "grok/grok-4.6",
|
|
74
|
+
},
|
|
75
|
+
planner: {
|
|
76
|
+
description: "High-level architectural planning, scoping, and decomposition",
|
|
77
|
+
baseTools: ["read", "grep", "find"],
|
|
78
|
+
contextFiles: ["plans/implementation-plan.md"],
|
|
79
|
+
thinkingLevel: "high" as const,
|
|
80
|
+
model: "claude/fable",
|
|
81
|
+
},
|
|
82
|
+
auditor: {
|
|
83
|
+
description: "Security, compliance, and code quality verification",
|
|
84
|
+
baseTools: ["read", "grep"],
|
|
85
|
+
contextFiles: ["SECURITY.md"],
|
|
86
|
+
thinkingLevel: "high" as const,
|
|
87
|
+
model: "codex/gpt-5.6-sol",
|
|
88
|
+
},
|
|
89
|
+
browser: {
|
|
90
|
+
description: "Web application exploration, screenshotting, and UI testing",
|
|
91
|
+
baseTools: ["read", "bash"],
|
|
92
|
+
contextFiles: ["docs/browser-automation.md"],
|
|
93
|
+
thinkingLevel: "medium" as const,
|
|
94
|
+
model: "grok/grok-4.6",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
domains: {
|
|
98
|
+
git: {
|
|
99
|
+
description: "Git version control operations",
|
|
100
|
+
tools: ["git_status", "git_diff", "git_commit"],
|
|
101
|
+
promptSnippet: "Follow git branch conventions; never commit directly to main.",
|
|
102
|
+
},
|
|
103
|
+
k8s: {
|
|
104
|
+
description: "Kubernetes cluster inspection and deployment",
|
|
105
|
+
tools: ["kubectl_get", "kubectl_describe"],
|
|
106
|
+
promptSnippet: "Target local dev cluster; verify namespaces before mutating.",
|
|
107
|
+
},
|
|
108
|
+
database: {
|
|
109
|
+
description: "Database queries and schema verification",
|
|
110
|
+
tools: ["sqlite_query", "sqlite_schema"],
|
|
111
|
+
promptSnippet: "Database is SQLite at .kxm/state/kxm.db; use read-only queries.",
|
|
112
|
+
},
|
|
113
|
+
browser: {
|
|
114
|
+
description: "Remote Steel browser sessions and visual testing",
|
|
115
|
+
tools: ["steel_session", "steel_scrape", "steel_screenshot"],
|
|
116
|
+
promptSnippet: "Use Steel on DOKS for browser automation; invoke takeover on MFA.",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Estimate tokens from character count using standard ~4 chars/token heuristic.
|
|
123
|
+
*/
|
|
124
|
+
export function estimateTokens(charCount: number): number {
|
|
125
|
+
return Math.ceil(charCount / 3.8);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Load and validate `.kxm/modes.yaml` from project root or return defaults.
|
|
130
|
+
*/
|
|
131
|
+
export function loadModesConfig(projectRoot?: string): ModesConfig {
|
|
132
|
+
if (!projectRoot) {
|
|
133
|
+
return DEFAULT_MODES_CONFIG;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const modesPath = join(projectRoot, ".kxm", "modes.yaml");
|
|
137
|
+
if (!existsSync(modesPath)) {
|
|
138
|
+
return DEFAULT_MODES_CONFIG;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const raw = readFileSync(modesPath, "utf8");
|
|
143
|
+
const parsed = parse(raw) as Partial<ModesConfig>;
|
|
144
|
+
if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
|
|
145
|
+
return {
|
|
146
|
+
schema: "kxm.modes.v1",
|
|
147
|
+
majorModes: { ...DEFAULT_MODES_CONFIG.majorModes, ...parsed.majorModes },
|
|
148
|
+
domains: { ...DEFAULT_MODES_CONFIG.domains, ...parsed.domains },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Malformed config falls back to default safely
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return DEFAULT_MODES_CONFIG;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolve active tools, context files, and prompt snippets for a major mode and domain set.
|
|
160
|
+
*/
|
|
161
|
+
export function resolveActiveMode(
|
|
162
|
+
config: ModesConfig,
|
|
163
|
+
majorModeName = "coder",
|
|
164
|
+
domainNames: string[] = []
|
|
165
|
+
): ResolvedMode {
|
|
166
|
+
const major = config.majorModes[majorModeName] || config.majorModes["coder"] || DEFAULT_MODES_CONFIG.majorModes["coder"]!;
|
|
167
|
+
const tools = new Set<string>(major.baseTools);
|
|
168
|
+
const contextFiles = new Set<string>(major.contextFiles || []);
|
|
169
|
+
const promptSnippets: string[] = [];
|
|
170
|
+
|
|
171
|
+
const enabledDomains: string[] = [];
|
|
172
|
+
|
|
173
|
+
if (config.domains) {
|
|
174
|
+
for (const dName of domainNames) {
|
|
175
|
+
const d = config.domains[dName.toLowerCase().trim()];
|
|
176
|
+
if (d) {
|
|
177
|
+
enabledDomains.push(dName.toLowerCase().trim());
|
|
178
|
+
d.tools.forEach((t) => tools.add(t));
|
|
179
|
+
(d.contextFiles || []).forEach((f) => contextFiles.add(f));
|
|
180
|
+
if (d.promptSnippet) {
|
|
181
|
+
promptSnippets.push(d.promptSnippet);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
majorMode: majorModeName,
|
|
189
|
+
enabledDomains,
|
|
190
|
+
tools: Array.from(tools),
|
|
191
|
+
contextFiles: Array.from(contextFiles),
|
|
192
|
+
promptSnippets,
|
|
193
|
+
thinkingLevel: major.thinkingLevel,
|
|
194
|
+
model: major.model || "grok/grok-4.6",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Calculate the exact prompt token footprint and projected costs for a mode configuration.
|
|
200
|
+
*/
|
|
201
|
+
export function calculatePromptFootprint(
|
|
202
|
+
resolved: ResolvedMode,
|
|
203
|
+
projectRoot: string = process.cwd(),
|
|
204
|
+
catalog?: PriceCatalog
|
|
205
|
+
): PromptFootprint {
|
|
206
|
+
const breakdown: ContextBreakdownItem[] = [];
|
|
207
|
+
|
|
208
|
+
// 1. Base System Prompt
|
|
209
|
+
const baseSystemPromptChars = 3200; // ~850 tokens base system prompt
|
|
210
|
+
breakdown.push({
|
|
211
|
+
name: `Base System Prompt (${resolved.majorMode})`,
|
|
212
|
+
chars: baseSystemPromptChars,
|
|
213
|
+
estimatedTokens: estimateTokens(baseSystemPromptChars),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// 2. Context Files
|
|
217
|
+
for (const relPath of resolved.contextFiles) {
|
|
218
|
+
const fullPath = join(projectRoot, relPath);
|
|
219
|
+
let chars = 0;
|
|
220
|
+
if (existsSync(fullPath)) {
|
|
221
|
+
try {
|
|
222
|
+
chars = readFileSync(fullPath, "utf8").length;
|
|
223
|
+
} catch {
|
|
224
|
+
chars = 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (chars === 0) {
|
|
228
|
+
chars = 1500; // Estimated fallback if missing
|
|
229
|
+
}
|
|
230
|
+
breakdown.push({
|
|
231
|
+
name: `Context File: ${relPath}`,
|
|
232
|
+
chars,
|
|
233
|
+
estimatedTokens: estimateTokens(chars),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 3. Domain Prompts
|
|
238
|
+
if (resolved.promptSnippets.length > 0) {
|
|
239
|
+
const snippetChars = resolved.promptSnippets.join("\n").length;
|
|
240
|
+
breakdown.push({
|
|
241
|
+
name: `Domain Prompts (${resolved.enabledDomains.join(", ")})`,
|
|
242
|
+
chars: snippetChars,
|
|
243
|
+
estimatedTokens: estimateTokens(snippetChars),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 4. Tool Schemas
|
|
248
|
+
// Each MCP / standard tool schema consumes ~150-200 tokens
|
|
249
|
+
const toolSchemaChars = resolved.tools.length * 650;
|
|
250
|
+
breakdown.push({
|
|
251
|
+
name: `Tool Schemas (${resolved.tools.length} active tools)`,
|
|
252
|
+
chars: toolSchemaChars,
|
|
253
|
+
estimatedTokens: estimateTokens(toolSchemaChars),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const totalChars = breakdown.reduce((sum, item) => sum + item.chars, 0);
|
|
257
|
+
const totalTokens = breakdown.reduce((sum, item) => sum + item.estimatedTokens, 0);
|
|
258
|
+
const maxContextWindow = 200000;
|
|
259
|
+
const contextWindowRatio = Math.round((totalTokens / maxContextWindow) * 1000) / 10;
|
|
260
|
+
|
|
261
|
+
// 5. Projected Costs
|
|
262
|
+
const cat = catalog || loadPriceCatalog(projectRoot);
|
|
263
|
+
const rawModel = resolved.model || "grok/grok-4.6";
|
|
264
|
+
const [prov, mod] = rawModel.includes("/") ? rawModel.split("/", 2) : [undefined, rawModel];
|
|
265
|
+
|
|
266
|
+
const inputCost = cat
|
|
267
|
+
? calculateModelCost(cat, {
|
|
268
|
+
model: mod || rawModel,
|
|
269
|
+
...(prov !== undefined ? { provider: prov } : {}),
|
|
270
|
+
tokensIn: totalTokens,
|
|
271
|
+
tokensOut: 0,
|
|
272
|
+
cacheReadTokens: 0,
|
|
273
|
+
cacheWriteTokens: 0,
|
|
274
|
+
})
|
|
275
|
+
: undefined;
|
|
276
|
+
|
|
277
|
+
const cacheReadCost = cat
|
|
278
|
+
? calculateModelCost(cat, {
|
|
279
|
+
model: mod || rawModel,
|
|
280
|
+
...(prov !== undefined ? { provider: prov } : {}),
|
|
281
|
+
tokensIn: 0,
|
|
282
|
+
tokensOut: 0,
|
|
283
|
+
cacheReadTokens: totalTokens,
|
|
284
|
+
cacheWriteTokens: 0,
|
|
285
|
+
})
|
|
286
|
+
: undefined;
|
|
287
|
+
|
|
288
|
+
const outputEstimateCost = cat
|
|
289
|
+
? calculateModelCost(cat, {
|
|
290
|
+
model: mod || rawModel,
|
|
291
|
+
...(prov !== undefined ? { provider: prov } : {}),
|
|
292
|
+
tokensIn: 0,
|
|
293
|
+
tokensOut: 1000,
|
|
294
|
+
cacheReadTokens: 0,
|
|
295
|
+
cacheWriteTokens: 0,
|
|
296
|
+
})
|
|
297
|
+
: undefined;
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
majorMode: resolved.majorMode,
|
|
301
|
+
enabledDomains: resolved.enabledDomains,
|
|
302
|
+
model: resolved.model || "grok/grok-4.6",
|
|
303
|
+
breakdown,
|
|
304
|
+
totalChars,
|
|
305
|
+
totalTokens,
|
|
306
|
+
contextWindowRatio,
|
|
307
|
+
projectedCost: {
|
|
308
|
+
inputCostUsd: inputCost?.costUsd ?? null,
|
|
309
|
+
cacheReadCostUsd: cacheReadCost?.costUsd ?? null,
|
|
310
|
+
outputCostEstimateUsd: outputEstimateCost?.costUsd ?? null,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Format a human-readable ASCII report for `kxm explain`.
|
|
317
|
+
*/
|
|
318
|
+
export function formatModesExplainReport(footprint: PromptFootprint): string {
|
|
319
|
+
const divider = "═".repeat(60);
|
|
320
|
+
const subDivider = "─".repeat(60);
|
|
321
|
+
|
|
322
|
+
let out = `\n${divider}\n`;
|
|
323
|
+
out += `KXM PRE-FLIGHT CONTEXT EXPLAIN\n`;
|
|
324
|
+
out += `${divider}\n`;
|
|
325
|
+
out += `Major Mode: ${footprint.majorMode}\n`;
|
|
326
|
+
out += `Enabled Domains: ${footprint.enabledDomains.length > 0 ? footprint.enabledDomains.join(", ") : "(none)"}\n`;
|
|
327
|
+
out += `Target Model: ${footprint.model}\n\n`;
|
|
328
|
+
|
|
329
|
+
out += `CONTEXT BREAKDOWN:\n`;
|
|
330
|
+
for (const item of footprint.breakdown) {
|
|
331
|
+
const padName = item.name.padEnd(38, " ");
|
|
332
|
+
const tokenStr = `${item.estimatedTokens.toLocaleString()} tokens`.padStart(16, " ");
|
|
333
|
+
out += ` • ${padName} ${tokenStr}\n`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
out += `${subDivider}\n`;
|
|
337
|
+
const totalPad = `TOTAL PROMPT FOOTPRINT:`.padEnd(38, " ");
|
|
338
|
+
const totalStr = `${footprint.totalTokens.toLocaleString()} tokens`.padStart(16, " ");
|
|
339
|
+
out += ` ${totalPad} ${totalStr} (${footprint.contextWindowRatio}% of 200k window)\n\n`;
|
|
340
|
+
|
|
341
|
+
out += `PROJECTED COSTS (per turn):\n`;
|
|
342
|
+
out += ` • Initial Turn Input Cost: ${footprint.projectedCost.inputCostUsd !== null ? `$${footprint.projectedCost.inputCostUsd.toFixed(4)}` : "unmetered/unknown"}\n`;
|
|
343
|
+
out += ` • Subsequent Cache-Read Cost: ${footprint.projectedCost.cacheReadCostUsd !== null ? `$${footprint.projectedCost.cacheReadCostUsd.toFixed(4)} (approx 90% savings)` : "unmetered/unknown"}\n`;
|
|
344
|
+
out += ` • Output Estimate (1k tokens): ${footprint.projectedCost.outputCostEstimateUsd !== null ? `$${footprint.projectedCost.outputCostEstimateUsd.toFixed(4)}` : "unmetered/unknown"}\n`;
|
|
345
|
+
out += `${divider}\n`;
|
|
346
|
+
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.kxm.dev/vnext/modes.schema.json",
|
|
4
|
+
"title": "KXM Modes Configuration",
|
|
5
|
+
"description": "Declarative major modes and domain modules for selective tool loading and context scoping",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schema", "majorModes"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": { "const": "kxm.modes.v1" },
|
|
10
|
+
"majorModes": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"additionalProperties": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"required": ["baseTools"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"description": { "type": "string" },
|
|
17
|
+
"baseTools": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": { "type": "string" }
|
|
20
|
+
},
|
|
21
|
+
"contextFiles": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": { "type": "string" }
|
|
24
|
+
},
|
|
25
|
+
"thinkingLevel": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": ["low", "medium", "high", "xhigh"]
|
|
28
|
+
},
|
|
29
|
+
"model": { "type": "string" }
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"domains": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"required": ["tools"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"description": { "type": "string" },
|
|
41
|
+
"tools": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": { "type": "string" }
|
|
44
|
+
},
|
|
45
|
+
"contextFiles": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": { "type": "string" }
|
|
48
|
+
},
|
|
49
|
+
"promptSnippet": { "type": "string" }
|
|
50
|
+
},
|
|
51
|
+
"additionalProperties": false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": false
|
|
56
|
+
}
|