@vodailoc/kilo-kit-mcp 1.2.0 β†’ 1.3.1

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/mcp/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # πŸ”Œ Kilo-Kit MCP Server
2
2
 
3
- > **Version:** 1.2.0
3
+ > **Version:** 1.3.1
4
4
  > **Mode:** Read-only Skill Registry + Validator
5
5
  > **Transport:** stdio
6
6
 
@@ -12,7 +12,7 @@ The Kilo-Kit MCP server exposes the `skills/` workflow surface to MCP-capable ag
12
12
 
13
13
  | Capability | MCP Tool | Purpose |
14
14
  |------------|----------|---------|
15
- | C4 orchestration | `kilo_orchestrate_task` | Enforce brainstorming-first workflow, required questions, memory suggestions, and final workflow release |
15
+ | C4 orchestration | `kilo_orchestrate_task` | Require the real `/brainstorming` skill before substantive work, then log/route the approved task and release the post-brainstorming workflow |
16
16
  | Skill discovery | `kilo_search_skills` | Search the skill library by task/query |
17
17
  | Intent routing | `kilo_route_intent` | Recommend skills, workflow order, rule hierarchy, and decision trail for the current chat context |
18
18
  | Skill loading | `kilo_get_skill` | Load one exact `SKILL.md` with output limits |
@@ -26,12 +26,14 @@ Resources:
26
26
  |----------|---------|
27
27
  | `kilo://skills/index` | Lightweight skill index |
28
28
  | `kilo://core/master` | Kilo-Kit master skill |
29
+ | `kilo://rules/c4` | Minimal host-agent operating rules for the C4 workflow |
29
30
  | `kilo://skills/{category}/{skill}` | Dynamic skill resource |
30
31
 
31
32
  Prompts:
32
33
 
33
34
  | Prompt | Purpose |
34
35
  |--------|---------|
36
+ | `kilo-c4-workflow` | Run a request through the C4 gate before substantive implementation |
35
37
  | `kilo-select-skill` | Route a request before implementation |
36
38
  | `kilo-validate-library` | Run the validation quality gate |
37
39
 
@@ -50,8 +52,8 @@ npm run smoke
50
52
  Expected verification:
51
53
 
52
54
  ```text
53
- Test Files 9 passed
54
- Tests 22 passed
55
+ Test Files 10 passed
56
+ Tests 28 passed
55
57
  MCP smoke check passed.
56
58
  ```
57
59
 
@@ -74,6 +76,16 @@ Use the published npm package in any MCP-capable client:
74
76
 
75
77
  The npm package includes the Kilo-Kit skill library, core master file, validator, and built MCP server.
76
78
 
79
+ Install the host-agent bootstrap rule in each project where you want C4 to run automatically:
80
+
81
+ ```bash
82
+ npx -y --package=@vodailoc/kilo-kit-mcp kilo-kit-init init --client gemini
83
+ npx -y --package=@vodailoc/kilo-kit-mcp kilo-kit-init init --client codex
84
+ npx -y --package=@vodailoc/kilo-kit-mcp kilo-kit-init init --client claude
85
+ ```
86
+
87
+ Use `--client all` to write `GEMINI.md`, `AGENTS.md`, and `CLAUDE.md` in the target project. Use `--dir <path>` to initialize another project directory.
88
+
77
89
  Route telemetry is in-memory by default. To persist route decisions between server runs, set:
78
90
 
79
91
  ```bash
@@ -91,7 +103,15 @@ KILO_KIT_MEMORY_PATH=/absolute/path/orchestrator.sqlite
91
103
  KILO_KIT_ORCHESTRATION_AUDIT_PATH=/absolute/path/orchestration-audit.jsonl
92
104
  ```
93
105
 
94
- `kilo_orchestrate_task` uses the C4 Brainstorming-First Gate: substantive work starts with `productivity/brainstorming`, then required questions, then memory suggestions that require explicit accept/reject before a final workflow is released.
106
+ `kilo_orchestrate_task` uses the C4 Brainstorming-First Gate as a skill gate, not a separate C4 questionnaire. Substantive work starts by loading and following `productivity/brainstorming`. After the user approves the brainstorming direction, call `kilo_orchestrate_task` again with `brainstormingApproved=true`; C4 then checks memory suggestions and releases the post-brainstorming workflow.
107
+
108
+ The released workflow is the primary C4 route, not an exclusive context source. After loading the first C4-selected skill, the agent must still inspect its own available skill list and load any other relevant skills before coding.
109
+
110
+ Installing the MCP server exposes tools, prompts, resources, and server instructions. It does not force every MCP host to call those tools automatically. If a client does not reliably follow MCP server instructions, use `kilo-kit-init` or add one bootstrap rule to the local agent configuration:
111
+
112
+ ```text
113
+ For substantive project work, call kilo_orchestrate_task before implementation and follow the returned C4 state, workflow, and verificationGate.
114
+ ```
95
115
 
96
116
  ### Codex CLI on Windows
97
117
 
@@ -120,8 +140,8 @@ Configure the npm package once:
120
140
  Then publish by running the GitHub Actions workflow `Publish npm package`, or by pushing a version tag:
121
141
 
122
142
  ```bash
123
- git tag v1.2.0
124
- git push origin v1.2.0
143
+ git tag v1.3.1
144
+ git push origin v1.3.1
125
145
  ```
126
146
 
127
147
  The workflow runs build, typecheck, tests, smoke, skill validation, package dry-run, and then `npm publish --access public --ignore-scripts` through OIDC.
@@ -173,10 +193,12 @@ Use `.mcp/kilo-kit.example.json` as the portable template.
173
193
 
174
194
  ```text
175
195
  User request
176
- β†’ kilo_orchestrate_task(message, context)
177
- β†’ answer required C4 questions
178
- β†’ accept/reject memory suggestions
179
- β†’ kilo_get_skill(category, skill) for the first workflow skill
196
+ β†’ kilo_get_skill(productivity, brainstorming)
197
+ β†’ follow the real /brainstorming hard gate and get user approval
198
+ β†’ kilo_orchestrate_task(message, context, brainstormingApproved=true)
199
+ β†’ accept/reject memory suggestions when relevant
200
+ β†’ kilo_get_skill(category, skill) for the first post-brainstorming workflow skill
201
+ β†’ inspect the agent's internal skill list and load any other relevant skills
180
202
  β†’ follow final workflow skills in order
181
203
  β†’ kilo_route_report when you need routing analytics
182
204
  β†’ kilo_memory_report when you need memory analytics
@@ -137,11 +137,9 @@ export function formatOrchestration(result, format) {
137
137
  if (format === "json") {
138
138
  return JSON.stringify(result, null, 2);
139
139
  }
140
- const questions = result.questions.length > 0
141
- ? result.questions
142
- .map((question, index) => `${index + 1}. **${question.id}**${question.required ? " (required)" : ""}\n ${question.prompt}`)
143
- .join("\n")
144
- : "No questions required.";
140
+ const brainstormingGate = result.state === "brainstorming_required"
141
+ ? "Load `productivity/brainstorming` and follow the real skill hard-gate. C4 questions are not used as a separate questionnaire."
142
+ : "Brainstorming approval has been recorded or this is a read-only route.";
145
143
  const workflow = result.workflow.length > 0
146
144
  ? result.workflow.map((step, index) => `${index + 1}. **${step.skill.id}** (${step.role})\n ${step.reason}`).join("\n")
147
145
  : "No workflow selected.";
@@ -160,8 +158,8 @@ export function formatOrchestration(result, format) {
160
158
  `State: \`${result.state}\``,
161
159
  `Task mode: \`${result.taskMode}\``,
162
160
  "",
163
- "## Questions",
164
- questions,
161
+ "## Brainstorming Gate",
162
+ brainstormingGate,
165
163
  "",
166
164
  "## Workflow",
167
165
  workflow,
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ import { lstatSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const START_MARKER = "<!-- KILO-KIT:C4:START -->";
6
+ const END_MARKER = "<!-- KILO-KIT:C4:END -->";
7
+ const MAX_BOOTSTRAP_FILE_BYTES = 256 * 1024;
8
+ const CLIENT_FILES = {
9
+ gemini: {
10
+ fileName: "GEMINI.md",
11
+ displayName: "Gemini CLI",
12
+ },
13
+ codex: {
14
+ fileName: "AGENTS.md",
15
+ displayName: "OpenAI Codex",
16
+ },
17
+ claude: {
18
+ fileName: "CLAUDE.md",
19
+ displayName: "Claude Code",
20
+ },
21
+ };
22
+ export function bootstrap(options) {
23
+ validateTargetDirectory(options.cwd);
24
+ const targets = resolveTargets(options);
25
+ return targets.map((target) => writeBootstrapBlock(target));
26
+ }
27
+ function resolveTargets(options) {
28
+ const clients = options.client === "all" ? Object.keys(CLIENT_FILES) : [options.client];
29
+ return clients.map((client) => {
30
+ const config = CLIENT_FILES[client];
31
+ return {
32
+ client,
33
+ filePath: path.resolve(options.cwd, config.fileName),
34
+ displayName: config.displayName,
35
+ };
36
+ });
37
+ }
38
+ function writeBootstrapBlock(target) {
39
+ assertSafeTarget(target.filePath);
40
+ const block = buildBootstrapBlock(target);
41
+ let action = "created";
42
+ let nextContent = block;
43
+ try {
44
+ const current = readFileSync(target.filePath, "utf8");
45
+ action = "updated";
46
+ nextContent = replaceOrAppendBlock(current, block);
47
+ }
48
+ catch (error) {
49
+ if (!isNotFoundError(error)) {
50
+ throw error;
51
+ }
52
+ }
53
+ writeFileAtomic(target.filePath, nextContent);
54
+ return {
55
+ client: target.client,
56
+ filePath: target.filePath,
57
+ action,
58
+ };
59
+ }
60
+ function replaceOrAppendBlock(current, block) {
61
+ const start = current.indexOf(START_MARKER);
62
+ const end = current.indexOf(END_MARKER);
63
+ const hasStart = start >= 0;
64
+ const hasEnd = end >= 0;
65
+ if (hasStart !== hasEnd || (hasStart && end < start)) {
66
+ throw new Error(`Refusing to update bootstrap file because the Kilo-Kit marker block is malformed. Fix or remove ${START_MARKER} / ${END_MARKER} manually.`);
67
+ }
68
+ if (start >= 0 && end > start) {
69
+ const afterEnd = end + END_MARKER.length;
70
+ return `${current.slice(0, start)}${block}${current.slice(afterEnd)}`;
71
+ }
72
+ const separator = current.endsWith("\n") ? "\n" : "\n\n";
73
+ return `${current}${separator}${block}`;
74
+ }
75
+ function buildBootstrapBlock(target) {
76
+ return `${START_MARKER}
77
+ ## Kilo-Kit C4 Workflow
78
+
79
+ Applies to: ${target.displayName}
80
+
81
+ For substantive coding, debugging, refactoring, review, publishing, or project-work requests:
82
+
83
+ 1. Call \`kilo_orchestrate_task\` before implementation.
84
+ 2. If state is \`brainstorming_required\`, load \`productivity/brainstorming\` with \`kilo_get_skill\`, follow it, and get user approval.
85
+ 3. Call \`kilo_orchestrate_task\` again with the same \`sessionId\` and \`brainstormingApproved=true\`.
86
+ 4. If state is \`awaiting_memory_confirmation\`, accept or reject memory suggestions before execution.
87
+ 5. When state is \`ready\`, load \`firstSkillToLoad\` with \`kilo_get_skill\`.
88
+ 6. Also inspect the host agent's own available skill list and load any other relevant skills before coding.
89
+ 7. Follow \`finalWorkflow\`.
90
+ 8. Satisfy \`verificationGate\` before claiming completion.
91
+
92
+ For read-only requests, \`kilo_route_intent\` is enough.
93
+ ${END_MARKER}
94
+ `;
95
+ }
96
+ function isNotFoundError(error) {
97
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
98
+ }
99
+ function validateTargetDirectory(cwd) {
100
+ let stats;
101
+ try {
102
+ stats = statSync(cwd);
103
+ }
104
+ catch (error) {
105
+ if (isNotFoundError(error)) {
106
+ throw new Error(`Target directory does not exist: ${cwd}`);
107
+ }
108
+ throw error;
109
+ }
110
+ if (!stats.isDirectory()) {
111
+ throw new Error(`Target path is not a directory: ${cwd}`);
112
+ }
113
+ if (lstatSync(cwd).isSymbolicLink()) {
114
+ throw new Error(`Refusing to write bootstrap files through a symlinked directory: ${cwd}`);
115
+ }
116
+ }
117
+ function assertSafeTarget(filePath) {
118
+ try {
119
+ const stats = lstatSync(filePath);
120
+ if (stats.isSymbolicLink()) {
121
+ throw new Error(`Refusing to update symlinked bootstrap file: ${filePath}`);
122
+ }
123
+ if (!stats.isFile()) {
124
+ throw new Error(`Refusing to update non-file bootstrap target: ${filePath}`);
125
+ }
126
+ if (stats.size > MAX_BOOTSTRAP_FILE_BYTES) {
127
+ throw new Error(`Refusing to update bootstrap file larger than ${MAX_BOOTSTRAP_FILE_BYTES} bytes: ${filePath}`);
128
+ }
129
+ }
130
+ catch (error) {
131
+ if (!isNotFoundError(error)) {
132
+ throw error;
133
+ }
134
+ }
135
+ }
136
+ function writeFileAtomic(filePath, content) {
137
+ const dir = path.dirname(filePath);
138
+ const base = path.basename(filePath);
139
+ const tempPath = path.join(dir, `.${base}.${process.pid}.${Date.now()}.tmp`);
140
+ try {
141
+ writeFileSync(tempPath, content, { encoding: "utf8", flag: "wx", mode: 0o644 });
142
+ renameSync(tempPath, filePath);
143
+ }
144
+ catch (error) {
145
+ try {
146
+ unlinkSync(tempPath);
147
+ }
148
+ catch {
149
+ // Best-effort cleanup only.
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+ function parseArgs(argv) {
155
+ const command = argv[0];
156
+ if (command !== "init") {
157
+ throw new Error(usage());
158
+ }
159
+ let client;
160
+ let cwd = process.cwd();
161
+ for (let index = 1; index < argv.length; index += 1) {
162
+ const arg = argv[index];
163
+ if (arg === "--client") {
164
+ const value = argv[index + 1];
165
+ if (!value || !isClient(value)) {
166
+ throw new Error("Expected --client to be one of: gemini, codex, claude, all.");
167
+ }
168
+ client = value;
169
+ index += 1;
170
+ continue;
171
+ }
172
+ if (arg === "--dir") {
173
+ const value = argv[index + 1];
174
+ if (!value) {
175
+ throw new Error("Expected --dir to receive a path.");
176
+ }
177
+ cwd = path.resolve(value);
178
+ index += 1;
179
+ continue;
180
+ }
181
+ if (arg === "--help" || arg === "-h") {
182
+ throw new Error(usage());
183
+ }
184
+ throw new Error(`Unknown argument: ${arg}\n\n${usage()}`);
185
+ }
186
+ return {
187
+ client: client ?? "all",
188
+ cwd,
189
+ };
190
+ }
191
+ function isClient(value) {
192
+ return value === "gemini" || value === "codex" || value === "claude" || value === "all";
193
+ }
194
+ function usage() {
195
+ return [
196
+ "Usage:",
197
+ " kilo-kit-init init [--client gemini|codex|claude|all] [--dir <path>]",
198
+ "",
199
+ "Examples:",
200
+ " kilo-kit-init init --client gemini",
201
+ " kilo-kit-init init --client codex --dir /path/to/project",
202
+ " kilo-kit-init init --client all",
203
+ ].join("\n");
204
+ }
205
+ async function main() {
206
+ const options = parseArgs(process.argv.slice(2));
207
+ const results = bootstrap(options);
208
+ for (const result of results) {
209
+ console.log(`${result.action}: ${result.filePath}`);
210
+ }
211
+ }
212
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
213
+ main().catch((error) => {
214
+ console.error(error instanceof Error ? error.message : String(error));
215
+ process.exit(1);
216
+ });
217
+ }
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { createNoopOrchestrationAudit } from "./orchestration-audit.js";
3
- import { selectQuestionTemplate } from "./question-templates.js";
4
3
  import { routeIntent } from "./router.js";
5
4
  export function createOrchestrator(options) {
6
5
  const sessions = new Map();
@@ -9,18 +8,18 @@ export function createOrchestrator(options) {
9
8
  orchestrate(input) {
10
9
  const session = getOrCreateSession(sessions, options.registry, input);
11
10
  mergeInput(session, input);
12
- const missingInfo = missingRequiredQuestionIds(session.questions, session.answers);
11
+ const missingInfo = [];
13
12
  const suggestions = ensureMemorySuggestions(options.memory, session);
14
13
  const pendingSuggestions = suggestions.filter((suggestion) => session.memoryConfirmations[suggestion.key] === undefined);
15
14
  const acceptedSuggestions = suggestions.filter((suggestion) => session.memoryConfirmations[suggestion.key] === "accepted");
16
- const state = selectState(session, missingInfo, pendingSuggestions);
15
+ const state = selectState(session, pendingSuggestions);
17
16
  for (const [suggestionKey, decision] of Object.entries(input.memoryConfirmations ?? {})) {
18
17
  options.memory.recordDecision({ suggestionKey, decision });
19
18
  }
20
19
  const verificationGate = buildVerificationGate(acceptedSuggestions);
21
- const finalWorkflow = state === "ready" ? session.workflow : undefined;
22
- const firstSkillToLoad = finalWorkflow?.[0]?.skill;
23
- const nextAction = buildNextAction(state, session, missingInfo, pendingSuggestions, firstSkillToLoad);
20
+ const finalWorkflow = state === "ready" ? executableWorkflow(session) : undefined;
21
+ const firstSkillToLoad = state === "brainstorming_required" ? findSkillById(options.registry, "productivity/brainstorming") : finalWorkflow?.[0]?.skill;
22
+ const nextAction = buildNextAction(state, session, pendingSuggestions, firstSkillToLoad);
24
23
  persistSession(options.memory, session, state, verificationGate, finalWorkflow);
25
24
  const auditRef = audit.record({
26
25
  sessionId: session.sessionId,
@@ -34,7 +33,7 @@ export function createOrchestrator(options) {
34
33
  state,
35
34
  message: session.message,
36
35
  taskMode: session.route.taskMode,
37
- questions: session.questions,
36
+ questions: [],
38
37
  missingInfo,
39
38
  route: session.route,
40
39
  workflow: session.workflow,
@@ -66,18 +65,13 @@ function getOrCreateSession(sessions, registry, input) {
66
65
  limit: 5,
67
66
  });
68
67
  const workflow = ensureBrainstormingFirst(registry, route.workflow, input);
69
- const template = selectQuestionTemplate({
70
- taskMode: route.taskMode,
71
- workflowSkillIds: workflow.map((step) => step.skill.id),
72
- recommendedSkillIds: route.recommended.map((item) => item.skill.id),
73
- });
74
68
  const session = {
75
69
  sessionId: input.sessionId ?? randomUUID(),
76
70
  message: input.message,
77
71
  createdAt: new Date().toISOString(),
78
72
  route,
79
73
  workflow,
80
- questions: template.questions,
74
+ brainstormingApproved: input.brainstormingApproved === true,
81
75
  answers: {},
82
76
  memorySuggestions: [],
83
77
  memoryConfirmations: {},
@@ -94,7 +88,7 @@ function persistSession(memory, session, state, verificationGate, finalWorkflow)
94
88
  message: session.message,
95
89
  taskMode: session.route.taskMode,
96
90
  route: toJsonObject(session.route),
97
- questions: toJsonArray(session.questions),
91
+ questions: [],
98
92
  answers: { ...session.answers },
99
93
  memorySuggestions: session.memorySuggestions.map((suggestion) => ({ ...suggestion })),
100
94
  finalWorkflow: toJsonArray(finalWorkflow ?? []),
@@ -116,6 +110,7 @@ function persistSession(memory, session, state, verificationGate, finalWorkflow)
116
110
  function mergeInput(session, input) {
117
111
  session.message = input.message || session.message;
118
112
  session.answers = { ...session.answers, ...(input.answers ?? {}) };
113
+ session.brainstormingApproved = session.brainstormingApproved || input.brainstormingApproved === true;
119
114
  session.memoryConfirmations = { ...session.memoryConfirmations, ...(input.memoryConfirmations ?? {}) };
120
115
  if (input.context) {
121
116
  session.context = input.context;
@@ -132,13 +127,10 @@ function ensureMemorySuggestions(memory, session) {
132
127
  });
133
128
  return session.memorySuggestions;
134
129
  }
135
- function selectState(session, missingInfo, pendingSuggestions) {
136
- if (isSubstantiveWork(session) && Object.keys(session.answers).length === 0) {
130
+ function selectState(session, pendingSuggestions) {
131
+ if (isSubstantiveWork(session) && !session.brainstormingApproved) {
137
132
  return "brainstorming_required";
138
133
  }
139
- if (missingInfo.length > 0) {
140
- return "questioning";
141
- }
142
134
  if (pendingSuggestions.length > 0) {
143
135
  return "awaiting_memory_confirmation";
144
136
  }
@@ -153,7 +145,7 @@ function ensureBrainstormingFirst(registry, workflow, input) {
153
145
  const step = {
154
146
  skill: brainstorming,
155
147
  role: "prepare",
156
- reason: "C4 Brainstorming-First Gate requires design clarification before substantive work.",
148
+ reason: "Load and follow the real /brainstorming skill before substantive work.",
157
149
  };
158
150
  if (input.context?.mode === "brainstorming" || /\bbrainstorm(?:ing)?\b/i.test(input.message)) {
159
151
  return [step, ...existing];
@@ -175,12 +167,6 @@ function findSkillById(registry, id) {
175
167
  return undefined;
176
168
  }
177
169
  }
178
- function missingRequiredQuestionIds(questions, answers) {
179
- return questions
180
- .filter((question) => question.required)
181
- .filter((question) => !answers[question.id]?.trim())
182
- .map((question) => question.id);
183
- }
184
170
  function buildVerificationGate(acceptedSuggestions) {
185
171
  const commands = acceptedSuggestions.flatMap((suggestion) => {
186
172
  const commands = suggestion.value.commands;
@@ -193,20 +179,23 @@ function buildVerificationGate(acceptedSuggestions) {
193
179
  : "Default MCP verification gate for C4 orchestration.",
194
180
  };
195
181
  }
196
- function buildNextAction(state, session, missingInfo, pendingSuggestions, firstSkillToLoad) {
182
+ function buildNextAction(state, session, pendingSuggestions, firstSkillToLoad) {
197
183
  if (state === "brainstorming_required") {
198
- return `Start with productivity/brainstorming and answer required C4 questions: ${missingInfo.join(", ")}.`;
199
- }
200
- if (state === "questioning") {
201
- return `Answer missing C4 questions before workflow execution: ${missingInfo.join(", ")}.`;
184
+ return "Load productivity/brainstorming with kilo_get_skill, follow its hard-gate, get user approval, then call kilo_orchestrate_task again with brainstormingApproved=true.";
202
185
  }
203
186
  if (state === "awaiting_memory_confirmation") {
204
187
  return `Accept or reject memory suggestions before execution: ${pendingSuggestions.map((item) => item.key).join(", ")}.`;
205
188
  }
206
189
  if (firstSkillToLoad) {
207
- return `Load ${firstSkillToLoad.id} with kilo_get_skill, then follow the final workflow.`;
190
+ return `Load ${firstSkillToLoad.id} with kilo_get_skill, then follow the final workflow. IMPORTANT: Do NOT rely solely on this workflow. You MUST also scan your full internal skill list (in your system prompt) and use view_file to load any other relevant skills before coding.`;
191
+ }
192
+ return session.route.nextAction + " IMPORTANT: You MUST also scan your full internal skill list and load any other relevant skills before coding.";
193
+ }
194
+ function executableWorkflow(session) {
195
+ if (!session.brainstormingApproved) {
196
+ return session.workflow;
208
197
  }
209
- return session.route.nextAction;
198
+ return session.workflow.filter((step) => step.skill.id !== "productivity/brainstorming");
210
199
  }
211
200
  function isSubstantiveWork(session) {
212
201
  return !isReadOnlyRequest(session.message);
@@ -15,7 +15,7 @@ import { createInMemoryRouteAnalytics, createJsonlRouteAnalytics } from "./route
15
15
  import { createSkillRegistry } from "./registry.js";
16
16
  import { routeIntent } from "./router.js";
17
17
  import { validateSkills } from "./validator.js";
18
- const SERVER_VERSION = "1.2.0";
18
+ const SERVER_VERSION = "1.3.1";
19
19
  const DEFAULT_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
20
20
  const formatSchema = z.enum(["markdown", "json"]).default("markdown");
21
21
  export async function createKiloKitServer(options = {}) {
@@ -43,11 +43,11 @@ export async function createKiloKitServer(options = {}) {
43
43
  name: "kilo-kit",
44
44
  version: SERVER_VERSION,
45
45
  }, {
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.",
46
+ instructions: "For substantive coding, debugging, refactoring, review, publishing, or project-work requests, call kilo_orchestrate_task before implementation. If it returns brainstorming_required, load productivity/brainstorming with kilo_get_skill and get user approval before coding. After approval, call kilo_orchestrate_task again with the same sessionId and brainstormingApproved=true. If it returns awaiting_memory_confirmation, accept or reject memory suggestions before execution. When it returns ready, load firstSkillToLoad with kilo_get_skill, inspect any additional relevant skills available in the host agent, follow the finalWorkflow, and satisfy verificationGate before claiming completion. For read-only requests, kilo_route_intent is acceptable. Route telemetry is in-memory by default and only persists when KILO_KIT_WRITE_DECISIONS=true.",
47
47
  });
48
48
  server.registerTool("kilo_orchestrate_task", {
49
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.",
50
+ description: "Central C4 orchestration gate. Routes internally, requires the real /brainstorming skill before substantive work, checks memory suggestions, and releases the post-brainstorming workflow after approval.",
51
51
  inputSchema: {
52
52
  message: z.string().min(1).max(4000).describe("Current user request or task summary."),
53
53
  context: z
@@ -59,6 +59,7 @@ export async function createKiloKitServer(options = {}) {
59
59
  })
60
60
  .optional(),
61
61
  sessionId: z.string().min(1).max(120).optional(),
62
+ brainstormingApproved: z.boolean().optional(),
62
63
  answers: z.record(z.string().max(2000)).optional(),
63
64
  memoryConfirmations: z.record(z.enum(["accepted", "rejected"])).optional(),
64
65
  format: formatSchema.optional(),
@@ -68,7 +69,7 @@ export async function createKiloKitServer(options = {}) {
68
69
  destructiveHint: false,
69
70
  idempotentHint: false,
70
71
  },
71
- }, async ({ message, context, sessionId, answers, memoryConfirmations, format }) => {
72
+ }, async ({ message, context, sessionId, brainstormingApproved, answers, memoryConfirmations, format }) => {
72
73
  const result = orchestrator.orchestrate({
73
74
  message,
74
75
  ...(context
@@ -82,6 +83,7 @@ export async function createKiloKitServer(options = {}) {
82
83
  }
83
84
  : {}),
84
85
  ...(sessionId ? { sessionId } : {}),
86
+ ...(brainstormingApproved !== undefined ? { brainstormingApproved } : {}),
85
87
  ...(answers ? { answers } : {}),
86
88
  ...(memoryConfirmations ? { memoryConfirmations } : {}),
87
89
  });
@@ -235,6 +237,18 @@ function registerResources(server, repoRoot, registry) {
235
237
  },
236
238
  ],
237
239
  }));
240
+ server.registerResource("kilo-c4-operating-rules", "kilo://rules/c4", {
241
+ title: "Kilo-Kit C4 Operating Rules",
242
+ description: "Minimal rules a host agent should follow after installing the Kilo-Kit MCP server.",
243
+ mimeType: "text/markdown",
244
+ }, async (uri) => ({
245
+ contents: [
246
+ {
247
+ uri: uri.href,
248
+ text: C4_OPERATING_RULES,
249
+ },
250
+ ],
251
+ }));
238
252
  server.registerResource("kilo-skill", new ResourceTemplate("kilo://skills/{category}/{skill}", {
239
253
  list: async () => ({
240
254
  resources: registry.listSkills().map((skill) => ({
@@ -263,6 +277,23 @@ function registerResources(server, repoRoot, registry) {
263
277
  });
264
278
  }
265
279
  function registerPrompts(server) {
280
+ server.registerPrompt("kilo-c4-workflow", {
281
+ title: "Run Request Through C4",
282
+ description: "Prompt the agent to use the C4 gate before substantive implementation work.",
283
+ argsSchema: {
284
+ request: z.string().min(1).max(4000),
285
+ },
286
+ }, ({ request }) => ({
287
+ messages: [
288
+ {
289
+ role: "user",
290
+ content: {
291
+ type: "text",
292
+ text: `Use the Kilo-Kit C4 workflow for this request. Call kilo_orchestrate_task first. If the state is brainstorming_required, load productivity/brainstorming with kilo_get_skill and get approval before coding. If the state is awaiting_memory_confirmation, resolve the suggestions. When the state is ready, load firstSkillToLoad, inspect any other relevant host-agent skills, follow finalWorkflow, and satisfy verificationGate before completion.\n\nRequest:\n${request}`,
293
+ },
294
+ },
295
+ ],
296
+ }));
266
297
  server.registerPrompt("kilo-select-skill", {
267
298
  title: "Select Kilo-Kit Skill",
268
299
  description: "Prompt the agent to route the current request through Kilo-Kit before implementation.",
@@ -299,6 +330,21 @@ function registerPrompts(server) {
299
330
  function normalizeFormat(format) {
300
331
  return format ?? "markdown";
301
332
  }
333
+ const C4_OPERATING_RULES = `# Kilo-Kit C4 Operating Rules
334
+
335
+ For substantive coding, debugging, refactoring, review, publishing, or project-work requests:
336
+
337
+ 1. Call \`kilo_orchestrate_task\` before implementation.
338
+ 2. If state is \`brainstorming_required\`, load \`productivity/brainstorming\` with \`kilo_get_skill\`, follow it, and get user approval.
339
+ 3. Call \`kilo_orchestrate_task\` again with the same \`sessionId\` and \`brainstormingApproved=true\`.
340
+ 4. If state is \`awaiting_memory_confirmation\`, accept or reject memory suggestions before execution.
341
+ 5. When state is \`ready\`, load \`firstSkillToLoad\` with \`kilo_get_skill\`.
342
+ 6. Also inspect the host agent's own available skill list and load any other relevant skills before coding.
343
+ 7. Follow \`finalWorkflow\`.
344
+ 8. Satisfy \`verificationGate\` before claiming completion.
345
+
346
+ For read-only requests, \`kilo_route_intent\` is enough.
347
+ `;
302
348
  async function main() {
303
349
  const server = await createKiloKitServer();
304
350
  const transport = new StdioServerTransport();
package/mcp/dist/smoke.js CHANGED
@@ -23,6 +23,20 @@ try {
23
23
  throw new Error(`Missing expected tool: ${required}`);
24
24
  }
25
25
  }
26
+ const prompts = await client.listPrompts();
27
+ const promptNames = prompts.prompts.map((prompt) => prompt.name);
28
+ for (const required of ["kilo-c4-workflow", "kilo-select-skill", "kilo-validate-library"]) {
29
+ if (!promptNames.includes(required)) {
30
+ throw new Error(`Missing expected prompt: ${required}`);
31
+ }
32
+ }
33
+ const resources = await client.listResources();
34
+ const resourceUris = resources.resources.map((resource) => resource.uri);
35
+ for (const required of ["kilo://skills/index", "kilo://core/master", "kilo://rules/c4"]) {
36
+ if (!resourceUris.includes(required)) {
37
+ throw new Error(`Missing expected resource: ${required}`);
38
+ }
39
+ }
26
40
  const route = await client.callTool({
27
41
  name: "kilo_route_intent",
28
42
  arguments: {
@@ -59,31 +73,28 @@ try {
59
73
  if (orchestrationResult.workflow?.[0]?.skill?.id !== "productivity/brainstorming") {
60
74
  throw new Error(`Smoke orchestration did not start with brainstorming: ${orchestrationText}`);
61
75
  }
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}`);
76
+ if (orchestrationResult.firstSkillToLoad?.id !== "productivity/brainstorming") {
77
+ throw new Error(`Smoke orchestration did not instruct loading brainstorming: ${orchestrationText}`);
78
+ }
79
+ if ((orchestrationResult.questions?.length ?? 0) !== 0 || (orchestrationResult.missingInfo?.length ?? 0) !== 0) {
80
+ throw new Error(`Smoke orchestration should not use C4 questions as a gate: ${orchestrationText}`);
64
81
  }
65
82
  const readyOrchestration = await client.callTool({
66
83
  name: "kilo_orchestrate_task",
67
84
  arguments: {
68
85
  message: "Fix bug login, viαΊΏt test trΖ°α»›c",
69
86
  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
- },
87
+ brainstormingApproved: true,
77
88
  format: "json",
78
89
  },
79
90
  });
80
91
  const readyText = extractFirstText(readyOrchestration);
81
92
  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}`);
93
+ if (readyResult.state !== "ready" || readyResult.firstSkillToLoad?.id !== "engineering/diagnose") {
94
+ throw new Error(`Smoke orchestration did not release post-brainstorming workflow after approval: ${readyText}`);
84
95
  }
85
- if (readyResult.finalWorkflow?.[0]?.skill?.id !== "productivity/brainstorming") {
86
- throw new Error(`Smoke final workflow did not start with brainstorming: ${readyText}`);
96
+ if (readyResult.finalWorkflow?.some((step) => step.skill?.id === "productivity/brainstorming")) {
97
+ throw new Error(`Smoke final workflow should not repeat brainstorming after approval: ${readyText}`);
87
98
  }
88
99
  const memoryReport = await client.callTool({
89
100
  name: "kilo_memory_report",