@kaddo/mcp 3.20.0 → 3.21.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.
- package/README.md +3 -1
- package/dist/index.js +164 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,8 @@ directory set via the `KADDO_PROJECT_DIR` environment variable).
|
|
|
49
49
|
| `kaddo://roadmap` | `knowledge/delivery/roadmap.md` | delivery roadmap |
|
|
50
50
|
| `kaddo://capsules` | `.kaddo/external.yml` + `external/` | external Knowledge Capsules |
|
|
51
51
|
| `kaddo://agents` | `knowledge/agents/` | installed agent prompts |
|
|
52
|
-
| `kaddo://skills` | `knowledge/skills/` | installed skills (empty if none) |
|
|
52
|
+
| `kaddo://skills` | `knowledge/skills/` | installed reusable skills (empty if none) |
|
|
53
|
+
| `kaddo://skills/<id>` | `knowledge/skills/<id>/skill.md` | one reusable skill |
|
|
53
54
|
|
|
54
55
|
## Tools (read-only)
|
|
55
56
|
|
|
@@ -58,6 +59,7 @@ directory set via the `KADDO_PROJECT_DIR` environment variable).
|
|
|
58
59
|
- `kaddo_get_work_item` — a Work Item by `id` (summary + full markdown).
|
|
59
60
|
- `kaddo_list_capsules` / `kaddo_get_capsule` — external Knowledge Capsules.
|
|
60
61
|
- `kaddo_list_agents` / `kaddo_get_agent_prompt` — installed agent prompts.
|
|
62
|
+
- `kaddo_list_skills` / `kaddo_get_skill` — installed reusable skills.
|
|
61
63
|
- `kaddo_list_graph_hints` — graph hints, filter by `artifact_type` / `severity` / `active_only`.
|
|
62
64
|
|
|
63
65
|
## Derived tools (write only under `.kaddo/`)
|
package/dist/index.js
CHANGED
|
@@ -207,8 +207,50 @@ function getAgentPrompt(root, name) {
|
|
|
207
207
|
return { ...info, content };
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// src/
|
|
210
|
+
// src/skills.ts
|
|
211
211
|
import matter2 from "gray-matter";
|
|
212
|
+
function firstHeadingOrLine2(md) {
|
|
213
|
+
for (const line of md.split(/\r?\n/)) {
|
|
214
|
+
const t = line.trim();
|
|
215
|
+
if (!t || t.startsWith("#") || t.startsWith("---")) continue;
|
|
216
|
+
return t.replace(/\s+/g, " ").slice(0, 200);
|
|
217
|
+
}
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
function listSkills(root) {
|
|
221
|
+
const files = listFiles(root, "knowledge/skills", "skill.md");
|
|
222
|
+
const out = [];
|
|
223
|
+
for (const rel of files) {
|
|
224
|
+
const raw = readText(root, rel);
|
|
225
|
+
if (raw === null) continue;
|
|
226
|
+
let data = {};
|
|
227
|
+
try {
|
|
228
|
+
data = matter2(raw).data;
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
if (data.type && String(data.type) !== "skill") continue;
|
|
232
|
+
const folder = rel.split("/").slice(-2)[0];
|
|
233
|
+
out.push({
|
|
234
|
+
id: String(data.id ?? folder),
|
|
235
|
+
title: String(data.title ?? folder),
|
|
236
|
+
group: String(data.group ?? "unknown"),
|
|
237
|
+
applies_to: Array.isArray(data.applies_to) ? data.applies_to.map(String).filter(Boolean) : [],
|
|
238
|
+
path: rel
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
242
|
+
}
|
|
243
|
+
function getSkill(root, id) {
|
|
244
|
+
const want = id.replace(/\.md$/, "");
|
|
245
|
+
const info = listSkills(root).find((s) => s.id === want);
|
|
246
|
+
if (!info) return null;
|
|
247
|
+
const content = readText(root, info.path);
|
|
248
|
+
if (content === null) return null;
|
|
249
|
+
return { ...info, content, description: firstHeadingOrLine2(content) || info.title };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/workitems.ts
|
|
253
|
+
import matter3 from "gray-matter";
|
|
212
254
|
var ACTIVE_STATES = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
213
255
|
function strArray(v) {
|
|
214
256
|
return Array.isArray(v) ? v.map((x) => String(x)).filter(Boolean) : [];
|
|
@@ -235,7 +277,7 @@ function listWorkItems(root) {
|
|
|
235
277
|
if (raw === null) continue;
|
|
236
278
|
let parsed;
|
|
237
279
|
try {
|
|
238
|
-
parsed =
|
|
280
|
+
parsed = matter3(raw);
|
|
239
281
|
} catch {
|
|
240
282
|
continue;
|
|
241
283
|
}
|
|
@@ -396,18 +438,15 @@ var RESOURCES = [
|
|
|
396
438
|
{
|
|
397
439
|
uri: "kaddo://skills",
|
|
398
440
|
name: "Kaddo skills",
|
|
399
|
-
description: "Installed skills from knowledge/skills/ (empty if none).",
|
|
441
|
+
description: "Installed reusable skills from knowledge/skills/ (empty if none).",
|
|
400
442
|
mimeType: "application/json",
|
|
401
|
-
read: (root) =>
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
{
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
];
|
|
410
|
-
}
|
|
443
|
+
read: (root) => [
|
|
444
|
+
{
|
|
445
|
+
uri: "kaddo://skills",
|
|
446
|
+
text: JSON.stringify({ skills: listSkills(root) }, null, 2),
|
|
447
|
+
mimeType: "application/json"
|
|
448
|
+
}
|
|
449
|
+
]
|
|
411
450
|
}
|
|
412
451
|
];
|
|
413
452
|
|
|
@@ -499,6 +538,14 @@ function getAgentPromptTool(root, name) {
|
|
|
499
538
|
if (!agent) return fail(`Agent "${name}" is not installed. Run \`kaddo add agents\` first.`);
|
|
500
539
|
return ok(agent);
|
|
501
540
|
}
|
|
541
|
+
function listSkillsTool(root) {
|
|
542
|
+
return ok(listSkills(root));
|
|
543
|
+
}
|
|
544
|
+
function getSkillTool(root, id) {
|
|
545
|
+
const skill = getSkill(root, id);
|
|
546
|
+
if (!skill) return fail(`Skill "${id}" is not installed. Run \`kaddo add skills\` first.`);
|
|
547
|
+
return ok(skill);
|
|
548
|
+
}
|
|
502
549
|
function listGraphHints(root, filter = {}) {
|
|
503
550
|
const report = readJson(root, ".kaddo/graph-hints.json");
|
|
504
551
|
if (!report) return fail("Graph hints not found. Run `kaddo graph export` first.");
|
|
@@ -528,6 +575,9 @@ function readDir(dirPath) {
|
|
|
528
575
|
function isFile(p) {
|
|
529
576
|
return exists(p) && fs2.statSync(p).isFile();
|
|
530
577
|
}
|
|
578
|
+
function isDir(p) {
|
|
579
|
+
return exists(p) && fs2.statSync(p).isDirectory();
|
|
580
|
+
}
|
|
531
581
|
function join(...parts) {
|
|
532
582
|
return path2.join(...parts);
|
|
533
583
|
}
|
|
@@ -619,13 +669,13 @@ ${issues}`);
|
|
|
619
669
|
}
|
|
620
670
|
|
|
621
671
|
// ../cli/src/core/context-pack.ts
|
|
622
|
-
import
|
|
672
|
+
import matter7 from "gray-matter";
|
|
623
673
|
|
|
624
674
|
// ../cli/src/services/artifact-reader.ts
|
|
625
|
-
import
|
|
675
|
+
import matter4 from "gray-matter";
|
|
626
676
|
function parseArtifact(filePath, raw) {
|
|
627
677
|
try {
|
|
628
|
-
const { data } =
|
|
678
|
+
const { data } = matter4(raw);
|
|
629
679
|
const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
|
|
630
680
|
return {
|
|
631
681
|
filePath,
|
|
@@ -1283,7 +1333,7 @@ function assessPhase(input) {
|
|
|
1283
1333
|
}
|
|
1284
1334
|
|
|
1285
1335
|
// ../cli/src/core/capsule.ts
|
|
1286
|
-
import
|
|
1336
|
+
import matter5 from "gray-matter";
|
|
1287
1337
|
import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
|
|
1288
1338
|
|
|
1289
1339
|
// ../cli/src/services/owners.ts
|
|
@@ -1436,7 +1486,7 @@ function sectionParagraph2(md, title) {
|
|
|
1436
1486
|
return "";
|
|
1437
1487
|
}
|
|
1438
1488
|
function parseCapsule(id, path3, md) {
|
|
1439
|
-
const { data } =
|
|
1489
|
+
const { data } = matter5(md);
|
|
1440
1490
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
1441
1491
|
let ageDays = null;
|
|
1442
1492
|
if (updatedAt) {
|
|
@@ -1877,6 +1927,38 @@ function loadGraphHints(dir) {
|
|
|
1877
1927
|
}
|
|
1878
1928
|
}
|
|
1879
1929
|
|
|
1930
|
+
// ../cli/src/services/installed-skills.ts
|
|
1931
|
+
import matter6 from "gray-matter";
|
|
1932
|
+
var SKILLS_DIR = join("knowledge", "skills");
|
|
1933
|
+
function discoverInstalledSkills(dir) {
|
|
1934
|
+
const base = join(dir, SKILLS_DIR);
|
|
1935
|
+
if (!exists(base) || !isDir(base)) return [];
|
|
1936
|
+
const out = [];
|
|
1937
|
+
for (const entry of readDir(base)) {
|
|
1938
|
+
const skillFile = join(base, entry, "skill.md");
|
|
1939
|
+
if (!isFile(skillFile)) continue;
|
|
1940
|
+
try {
|
|
1941
|
+
const { data } = matter6(readFile(skillFile));
|
|
1942
|
+
if (data.type && String(data.type) !== "skill") continue;
|
|
1943
|
+
const id = String(data.id ?? entry);
|
|
1944
|
+
out.push({
|
|
1945
|
+
id,
|
|
1946
|
+
title: String(data.title ?? id),
|
|
1947
|
+
group: String(data.group ?? "unknown"),
|
|
1948
|
+
appliesTo: Array.isArray(data.applies_to) ? data.applies_to.map(String).filter(Boolean) : [],
|
|
1949
|
+
relPath: `${SKILLS_DIR.replace(/\\/g, "/")}/${entry}/skill.md`
|
|
1950
|
+
});
|
|
1951
|
+
} catch {
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
1955
|
+
}
|
|
1956
|
+
function skillGroupCounts(skills) {
|
|
1957
|
+
const counts = {};
|
|
1958
|
+
for (const s of skills) counts[s.group] = (counts[s.group] ?? 0) + 1;
|
|
1959
|
+
return counts;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1880
1962
|
// ../cli/src/core/context-pack.ts
|
|
1881
1963
|
var CONTEXT_PACK_VERSION = "1";
|
|
1882
1964
|
var ARCH_DIR2 = "knowledge";
|
|
@@ -1890,7 +1972,7 @@ function readScanJson(dir) {
|
|
|
1890
1972
|
}
|
|
1891
1973
|
}
|
|
1892
1974
|
function firstParagraph3(markdown) {
|
|
1893
|
-
const body =
|
|
1975
|
+
const body = matter7(markdown).content.trim();
|
|
1894
1976
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
1895
1977
|
return para ?? "";
|
|
1896
1978
|
}
|
|
@@ -2063,6 +2145,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
2063
2145
|
external: loadExternalCapsules(dir),
|
|
2064
2146
|
graph: loadGraphSummary(dir),
|
|
2065
2147
|
graphHints: loadGraphHints(dir),
|
|
2148
|
+
skills: discoverInstalledSkills(dir).map((s) => s.id),
|
|
2066
2149
|
mappedModules,
|
|
2067
2150
|
missing,
|
|
2068
2151
|
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
@@ -2256,6 +2339,12 @@ function renderContextPack(pack) {
|
|
|
2256
2339
|
`);
|
|
2257
2340
|
}
|
|
2258
2341
|
}
|
|
2342
|
+
if (pack.skills.length > 0) {
|
|
2343
|
+
parts.push("## Skills\n");
|
|
2344
|
+
parts.push("Available reusable skills (agents apply these; content is not inlined):\n");
|
|
2345
|
+
parts.push(pack.skills.map((s) => `- ${s}`).join("\n") + "\n");
|
|
2346
|
+
parts.push("Read full skill definitions in `knowledge/skills/` or via the Kaddo MCP server (`kaddo://skills`).\n");
|
|
2347
|
+
}
|
|
2259
2348
|
parts.push("## Missing Context\n");
|
|
2260
2349
|
if (missing.length > 0) {
|
|
2261
2350
|
parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
|
|
@@ -2475,6 +2564,10 @@ function buildProjectExplanation(dir) {
|
|
|
2475
2564
|
externalCapsules: loadExternalCapsules(dir),
|
|
2476
2565
|
graph: loadGraphSummary(dir),
|
|
2477
2566
|
graphHints: loadGraphHints(dir),
|
|
2567
|
+
skills: (() => {
|
|
2568
|
+
const installed = discoverInstalledSkills(dir);
|
|
2569
|
+
return { total: installed.length, byGroup: skillGroupCounts(installed) };
|
|
2570
|
+
})(),
|
|
2478
2571
|
layers,
|
|
2479
2572
|
roadmap,
|
|
2480
2573
|
mappedModules,
|
|
@@ -2625,6 +2718,15 @@ function renderExplanationHuman(exp) {
|
|
|
2625
2718
|
if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
|
|
2626
2719
|
lines.push("");
|
|
2627
2720
|
}
|
|
2721
|
+
if (exp.skills.total > 0) {
|
|
2722
|
+
lines.push(`## Skills installed: ${exp.skills.total}`);
|
|
2723
|
+
const groups = Object.entries(exp.skills.byGroup).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2724
|
+
if (groups.length > 0) {
|
|
2725
|
+
lines.push("Groups:");
|
|
2726
|
+
for (const [g, n] of groups) lines.push(`- ${g}: ${n}`);
|
|
2727
|
+
}
|
|
2728
|
+
lines.push("");
|
|
2729
|
+
}
|
|
2628
2730
|
if (exp.missingKnowledge.length > 0) {
|
|
2629
2731
|
lines.push("## Missing Knowledge");
|
|
2630
2732
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
@@ -3021,6 +3123,38 @@ function createServer(root) {
|
|
|
3021
3123
|
},
|
|
3022
3124
|
async (args) => toolText(guarded(root, () => listGraphHints(root, args)))
|
|
3023
3125
|
);
|
|
3126
|
+
server.registerTool(
|
|
3127
|
+
"kaddo_list_skills",
|
|
3128
|
+
{ title: "List skills", description: "List installed reusable skills.", inputSchema: {} },
|
|
3129
|
+
async () => toolText(guarded(root, () => listSkillsTool(root)))
|
|
3130
|
+
);
|
|
3131
|
+
server.registerTool(
|
|
3132
|
+
"kaddo_get_skill",
|
|
3133
|
+
{ title: "Get skill", description: "Get an installed reusable skill by id.", inputSchema: { id: z2.string() } },
|
|
3134
|
+
async (args) => toolText(guarded(root, () => getSkillTool(root, args.id)))
|
|
3135
|
+
);
|
|
3136
|
+
let installedSkills = [];
|
|
3137
|
+
try {
|
|
3138
|
+
assertKaddoProject(root);
|
|
3139
|
+
installedSkills = listSkills(root);
|
|
3140
|
+
} catch {
|
|
3141
|
+
installedSkills = [];
|
|
3142
|
+
}
|
|
3143
|
+
for (const s of installedSkills) {
|
|
3144
|
+
server.registerResource(
|
|
3145
|
+
`skill: ${s.id}`,
|
|
3146
|
+
`kaddo://skills/${s.id}`,
|
|
3147
|
+
{ title: s.title, description: `Reusable skill "${s.id}" (${s.group}).`, mimeType: "text/markdown" },
|
|
3148
|
+
async (uri) => {
|
|
3149
|
+
const full = getSkill(root, s.id);
|
|
3150
|
+
return {
|
|
3151
|
+
contents: [
|
|
3152
|
+
{ uri: uri.href, text: full?.content ?? `Skill "${s.id}" not found.`, mimeType: "text/markdown" }
|
|
3153
|
+
]
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3024
3158
|
server.registerTool(
|
|
3025
3159
|
"kaddo_generate_context",
|
|
3026
3160
|
{ title: "Generate context pack", description: `Regenerate .kaddo/context-pack.md + .json. ${DERIVED_NOTE}`, inputSchema: {} },
|
|
@@ -3064,6 +3198,17 @@ function createServer(root) {
|
|
|
3064
3198
|
}
|
|
3065
3199
|
);
|
|
3066
3200
|
}
|
|
3201
|
+
for (const s of installedSkills) {
|
|
3202
|
+
server.registerPrompt(
|
|
3203
|
+
`skill-${s.id}`,
|
|
3204
|
+
{ title: s.title, description: `Reusable skill "${s.id}" (${s.group}).` },
|
|
3205
|
+
async () => {
|
|
3206
|
+
const full = getSkill(root, s.id);
|
|
3207
|
+
const content = full?.content ?? `Skill "${s.id}" is not installed.`;
|
|
3208
|
+
return { messages: [{ role: "user", content: { type: "text", text: content } }] };
|
|
3209
|
+
}
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3067
3212
|
return server;
|
|
3068
3213
|
}
|
|
3069
3214
|
|
package/package.json
CHANGED