@vodailoc/kilo-kit-mcp 1.1.1 → 1.2.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.
@@ -1,27 +1,104 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from "node:fs/promises";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
6
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
8
  import { z } from "zod";
8
- import { formatLoadedSkill, formatRoute, formatSkills, formatValidation, textResponse, } from "./formatters.js";
9
+ import { formatLoadedSkill, formatMemoryReport, formatOrchestration, formatRoute, formatRouteReport, formatSkills, formatValidation, textResponse, } from "./formatters.js";
10
+ import { createJsonlOrchestrationAudit, createNoopOrchestrationAudit } from "./orchestration-audit.js";
11
+ import { createSqliteOrchestrationMemory } from "./orchestration-memory.js";
12
+ import { createOrchestrator } from "./orchestrator.js";
9
13
  import { resolveInsideRepo } from "./paths.js";
14
+ import { createInMemoryRouteAnalytics, createJsonlRouteAnalytics } from "./route-analytics.js";
10
15
  import { createSkillRegistry } from "./registry.js";
11
16
  import { routeIntent } from "./router.js";
12
17
  import { validateSkills } from "./validator.js";
13
- const SERVER_VERSION = "1.1.1";
18
+ const SERVER_VERSION = "1.2.0";
14
19
  const DEFAULT_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
15
20
  const formatSchema = z.enum(["markdown", "json"]).default("markdown");
16
21
  export async function createKiloKitServer(options = {}) {
17
22
  const repoRoot = path.resolve(options.repoRoot ?? process.env.KILO_KIT_REPO_ROOT ?? DEFAULT_REPO_ROOT);
18
23
  const registry = await createSkillRegistry({ repoRoot });
24
+ const routeAnalytics = process.env.KILO_KIT_WRITE_DECISIONS === "true"
25
+ ? createJsonlRouteAnalytics({
26
+ filePath: process.env.KILO_KIT_DECISION_TRAIL_PATH
27
+ ? path.resolve(process.env.KILO_KIT_DECISION_TRAIL_PATH)
28
+ : resolveInsideRepo(repoRoot, ".kilo/decision-trail.jsonl"),
29
+ })
30
+ : createInMemoryRouteAnalytics();
31
+ const orchestrationMemory = await createSqliteOrchestrationMemory({
32
+ filePath: path.resolve(process.env.KILO_KIT_MEMORY_PATH ?? path.join(os.homedir(), ".kilo-kit/orchestrator.sqlite")),
33
+ });
34
+ const orchestrationAudit = process.env.KILO_KIT_ORCHESTRATION_AUDIT_PATH
35
+ ? createJsonlOrchestrationAudit(path.resolve(process.env.KILO_KIT_ORCHESTRATION_AUDIT_PATH))
36
+ : createNoopOrchestrationAudit();
37
+ const orchestrator = createOrchestrator({
38
+ registry,
39
+ memory: orchestrationMemory,
40
+ audit: orchestrationAudit,
41
+ });
19
42
  const server = new McpServer({
20
43
  name: "kilo-kit",
21
44
  version: SERVER_VERSION,
22
45
  }, {
23
- instructions: "Use kilo_route_intent before selecting a Kilo-Kit workflow skill. Load one selected skill with kilo_get_skill, then follow its instructions. All tools are read-only.",
46
+ instructions: "Use kilo_route_intent before selecting a Kilo-Kit workflow skill. Load one selected skill with kilo_get_skill, then follow its instructions. Route telemetry is in-memory by default and only persists when KILO_KIT_WRITE_DECISIONS=true.",
24
47
  });
48
+ server.registerTool("kilo_orchestrate_task", {
49
+ title: "Kilo-Kit C4 Orchestrate Task",
50
+ description: "Central C4 orchestration gate. Routes internally, enforces brainstorming-first, asks required questions, checks memory suggestions, and releases a final workflow only after confirmation.",
51
+ inputSchema: {
52
+ message: z.string().min(1).max(4000).describe("Current user request or task summary."),
53
+ context: z
54
+ .object({
55
+ files: z.array(z.string().max(300)).max(30).optional(),
56
+ mode: z.string().max(80).optional(),
57
+ previousErrors: z.string().max(2000).optional(),
58
+ projectFingerprint: z.string().max(200).optional(),
59
+ })
60
+ .optional(),
61
+ sessionId: z.string().min(1).max(120).optional(),
62
+ answers: z.record(z.string().max(2000)).optional(),
63
+ memoryConfirmations: z.record(z.enum(["accepted", "rejected"])).optional(),
64
+ format: formatSchema.optional(),
65
+ },
66
+ annotations: {
67
+ readOnlyHint: true,
68
+ destructiveHint: false,
69
+ idempotentHint: false,
70
+ },
71
+ }, async ({ message, context, sessionId, answers, memoryConfirmations, format }) => {
72
+ const result = orchestrator.orchestrate({
73
+ message,
74
+ ...(context
75
+ ? {
76
+ context: {
77
+ ...(context.files ? { files: context.files } : {}),
78
+ ...(context.mode ? { mode: context.mode } : {}),
79
+ ...(context.previousErrors ? { previousErrors: context.previousErrors } : {}),
80
+ ...(context.projectFingerprint ? { projectFingerprint: context.projectFingerprint } : {}),
81
+ },
82
+ }
83
+ : {}),
84
+ ...(sessionId ? { sessionId } : {}),
85
+ ...(answers ? { answers } : {}),
86
+ ...(memoryConfirmations ? { memoryConfirmations } : {}),
87
+ });
88
+ return textResponse(formatOrchestration(result, normalizeFormat(format)));
89
+ });
90
+ server.registerTool("kilo_memory_report", {
91
+ title: "Kilo-Kit C4 Memory Report",
92
+ description: "Read global C4 memory facts, decisions, and recent suggestions.",
93
+ inputSchema: {
94
+ format: formatSchema.optional(),
95
+ },
96
+ annotations: {
97
+ readOnlyHint: true,
98
+ destructiveHint: false,
99
+ idempotentHint: true,
100
+ },
101
+ }, async ({ format }) => textResponse(formatMemoryReport(orchestrationMemory.report(), normalizeFormat(format))));
25
102
  server.registerTool("kilo_search_skills", {
26
103
  title: "Search Kilo-Kit Skills",
27
104
  description: "Search the Kilo-Kit skill library by natural-language query. Use this for broad discovery before loading a specific skill.",
@@ -99,9 +176,21 @@ export async function createKiloKitServer(options = {}) {
99
176
  message,
100
177
  ...(routeContext ? { context: routeContext } : {}),
101
178
  ...(limit ? { limit } : {}),
102
- });
179
+ }, { analytics: routeAnalytics });
103
180
  return textResponse(formatRoute(result, normalizeFormat(format)));
104
181
  });
182
+ server.registerTool("kilo_route_report", {
183
+ title: "Kilo-Kit Route Report",
184
+ description: "Summarize route telemetry: top skills, task modes, workflow chains, score averages, and conflict penalties.",
185
+ inputSchema: {
186
+ format: formatSchema.optional(),
187
+ },
188
+ annotations: {
189
+ readOnlyHint: true,
190
+ destructiveHint: false,
191
+ idempotentHint: true,
192
+ },
193
+ }, async ({ format }) => textResponse(formatRouteReport(routeAnalytics.report(), normalizeFormat(format))));
105
194
  server.registerTool("kilo_validate_skills", {
106
195
  title: "Validate Kilo-Kit Skills",
107
196
  description: "Run the Kilo-Kit skill validator and return a concise quality-gate summary. This is read-only and does not modify files.",
@@ -0,0 +1,18 @@
1
+ import { getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
2
+ const FORWARDED_ENV_VARS = [
3
+ "KILO_KIT_WRITE_DECISIONS",
4
+ "KILO_KIT_DECISION_TRAIL_PATH",
5
+ "KILO_KIT_REPO_ROOT",
6
+ "KILO_KIT_MEMORY_PATH",
7
+ "KILO_KIT_ORCHESTRATION_AUDIT_PATH",
8
+ ];
9
+ export function buildSmokeEnvironment(sourceEnv = process.env, baseEnv = getDefaultEnvironment()) {
10
+ const env = { ...baseEnv };
11
+ for (const name of FORWARDED_ENV_VARS) {
12
+ const value = sourceEnv[name];
13
+ if (value !== undefined) {
14
+ env[name] = value;
15
+ }
16
+ }
17
+ return env;
18
+ }
package/mcp/dist/smoke.js CHANGED
@@ -1,15 +1,24 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { buildSmokeEnvironment } from "./smoke-env.js";
3
4
  const transport = new StdioClientTransport({
4
5
  command: process.env.KILO_KIT_SMOKE_COMMAND ?? process.execPath,
5
6
  args: parseSmokeArgs(),
7
+ env: buildSmokeEnvironment(),
6
8
  });
7
9
  const client = new Client({ name: "kilo-kit-smoke", version: "1.0.0" });
8
10
  try {
9
11
  await client.connect(transport);
10
12
  const tools = await client.listTools();
11
13
  const toolNames = tools.tools.map((tool) => tool.name);
12
- for (const required of ["kilo_route_intent", "kilo_get_skill", "kilo_validate_skills"]) {
14
+ for (const required of [
15
+ "kilo_route_intent",
16
+ "kilo_get_skill",
17
+ "kilo_validate_skills",
18
+ "kilo_route_report",
19
+ "kilo_orchestrate_task",
20
+ "kilo_memory_report",
21
+ ]) {
13
22
  if (!toolNames.includes(required)) {
14
23
  throw new Error(`Missing expected tool: ${required}`);
15
24
  }
@@ -26,6 +35,64 @@ try {
26
35
  if (!routeText.includes("engineering/tdd")) {
27
36
  throw new Error(`Smoke route did not recommend engineering/tdd: ${routeText}`);
28
37
  }
38
+ const report = await client.callTool({
39
+ name: "kilo_route_report",
40
+ arguments: { format: "json" },
41
+ });
42
+ const reportText = extractFirstText(report);
43
+ if (!reportText.includes('"totalEvents": 1') || !reportText.includes("engineering/tdd")) {
44
+ throw new Error(`Smoke route report did not include the routed event: ${reportText}`);
45
+ }
46
+ const orchestration = await client.callTool({
47
+ name: "kilo_orchestrate_task",
48
+ arguments: {
49
+ message: "Fix bug login, viết test trước",
50
+ context: { files: ["src/auth/login.ts"], mode: "coding", projectFingerprint: "smoke:typescript" },
51
+ format: "json",
52
+ },
53
+ });
54
+ const orchestrationText = extractFirstText(orchestration);
55
+ const orchestrationResult = JSON.parse(orchestrationText);
56
+ if (orchestrationResult.state !== "brainstorming_required") {
57
+ throw new Error(`Smoke orchestration did not require brainstorming: ${orchestrationText}`);
58
+ }
59
+ if (orchestrationResult.workflow?.[0]?.skill?.id !== "productivity/brainstorming") {
60
+ throw new Error(`Smoke orchestration did not start with brainstorming: ${orchestrationText}`);
61
+ }
62
+ if (!orchestrationResult.questions?.some((question) => question.id === "test_command" || question.skillId === "engineering/tdd")) {
63
+ throw new Error(`Smoke orchestration did not include TDD questions: ${orchestrationText}`);
64
+ }
65
+ const readyOrchestration = await client.callTool({
66
+ name: "kilo_orchestrate_task",
67
+ arguments: {
68
+ message: "Fix bug login, viết test trước",
69
+ sessionId: orchestrationResult.sessionId,
70
+ answers: {
71
+ goal: "Fix login failure",
72
+ scope: "src/auth/login.ts",
73
+ success_criteria: "login test passes",
74
+ failing_behavior: "valid credentials are rejected",
75
+ test_command: "npm test -- login",
76
+ },
77
+ format: "json",
78
+ },
79
+ });
80
+ const readyText = extractFirstText(readyOrchestration);
81
+ const readyResult = JSON.parse(readyText);
82
+ if (readyResult.state !== "ready" || readyResult.firstSkillToLoad?.id !== "productivity/brainstorming") {
83
+ throw new Error(`Smoke orchestration did not release final workflow after answers: ${readyText}`);
84
+ }
85
+ if (readyResult.finalWorkflow?.[0]?.skill?.id !== "productivity/brainstorming") {
86
+ throw new Error(`Smoke final workflow did not start with brainstorming: ${readyText}`);
87
+ }
88
+ const memoryReport = await client.callTool({
89
+ name: "kilo_memory_report",
90
+ arguments: { format: "json" },
91
+ });
92
+ const memoryReportText = extractFirstText(memoryReport);
93
+ if (!memoryReportText.includes('"facts"')) {
94
+ throw new Error(`Smoke memory report did not return memory facts: ${memoryReportText}`);
95
+ }
29
96
  const skill = await client.callTool({
30
97
  name: "kilo_get_skill",
31
98
  arguments: { category: "engineering", skill: "tdd", maxChars: 800 },
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kilo-kit/mcp",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Kilo-Kit MCP server for skill routing, skill loading, and validation.",
5
5
  "type": "module",
6
6
  "private": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vodailoc/kilo-kit-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Kilo-Kit MCP server for adaptive skill routing, skill loading, and validation.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Kilo-Kit Team",