@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/README.md +200 -360
- package/mcp/README.md +33 -11
- package/mcp/dist/formatters.js +5 -7
- package/mcp/dist/init.js +217 -0
- package/mcp/dist/orchestrator.js +22 -33
- package/mcp/dist/server.js +50 -4
- package/mcp/dist/smoke.js +24 -13
- package/mcp/package.json +6 -2
- package/package.json +3 -2
package/mcp/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# π Kilo-Kit MCP Server
|
|
2
2
|
|
|
3
|
-
> **Version:** 1.
|
|
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` |
|
|
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
|
|
54
|
-
Tests
|
|
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
|
|
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.
|
|
124
|
-
git push origin v1.
|
|
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
|
-
β
|
|
177
|
-
β
|
|
178
|
-
β
|
|
179
|
-
β
|
|
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
|
package/mcp/dist/formatters.js
CHANGED
|
@@ -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
|
|
141
|
-
?
|
|
142
|
-
|
|
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
|
-
"##
|
|
164
|
-
|
|
161
|
+
"## Brainstorming Gate",
|
|
162
|
+
brainstormingGate,
|
|
165
163
|
"",
|
|
166
164
|
"## Workflow",
|
|
167
165
|
workflow,
|
package/mcp/dist/init.js
ADDED
|
@@ -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
|
+
}
|
package/mcp/dist/orchestrator.js
CHANGED
|
@@ -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 =
|
|
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,
|
|
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
|
|
22
|
-
const firstSkillToLoad = finalWorkflow?.[0]?.skill;
|
|
23
|
-
const nextAction = buildNextAction(state, session,
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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,
|
|
136
|
-
if (isSubstantiveWork(session) &&
|
|
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: "
|
|
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,
|
|
182
|
+
function buildNextAction(state, session, pendingSuggestions, firstSkillToLoad) {
|
|
197
183
|
if (state === "brainstorming_required") {
|
|
198
|
-
return
|
|
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.
|
|
198
|
+
return session.workflow.filter((step) => step.skill.id !== "productivity/brainstorming");
|
|
210
199
|
}
|
|
211
200
|
function isSubstantiveWork(session) {
|
|
212
201
|
return !isReadOnlyRequest(session.message);
|
package/mcp/dist/server.js
CHANGED
|
@@ -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.
|
|
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: "
|
|
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,
|
|
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 (
|
|
63
|
-
throw new Error(`Smoke orchestration did not
|
|
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
|
-
|
|
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 !== "
|
|
83
|
-
throw new Error(`Smoke orchestration did not release
|
|
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?.
|
|
86
|
-
throw new Error(`Smoke final workflow
|
|
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",
|