@kaddo/mcp 3.20.0 → 3.22.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 +229 -30
- 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
|
|
|
@@ -465,8 +504,12 @@ function projectStatus(root) {
|
|
|
465
504
|
withOwnership: explain.ownership?.workItemsWithOwnership ?? 0,
|
|
466
505
|
total: explain.ownership?.workItemsTotal ?? 0
|
|
467
506
|
},
|
|
468
|
-
|
|
469
|
-
|
|
507
|
+
graph: {
|
|
508
|
+
scope: hints?.scope ?? "unknown (run `kaddo graph export`)",
|
|
509
|
+
scope_reason: hints?.scope_reason ?? "",
|
|
510
|
+
quality: hints?.quality ?? "unknown (run `kaddo graph export`)",
|
|
511
|
+
hints: hints?.summary?.hints ?? 0
|
|
512
|
+
},
|
|
470
513
|
capsules: capsules.length
|
|
471
514
|
});
|
|
472
515
|
}
|
|
@@ -499,6 +542,14 @@ function getAgentPromptTool(root, name) {
|
|
|
499
542
|
if (!agent) return fail(`Agent "${name}" is not installed. Run \`kaddo add agents\` first.`);
|
|
500
543
|
return ok(agent);
|
|
501
544
|
}
|
|
545
|
+
function listSkillsTool(root) {
|
|
546
|
+
return ok(listSkills(root));
|
|
547
|
+
}
|
|
548
|
+
function getSkillTool(root, id) {
|
|
549
|
+
const skill = getSkill(root, id);
|
|
550
|
+
if (!skill) return fail(`Skill "${id}" is not installed. Run \`kaddo add skills\` first.`);
|
|
551
|
+
return ok(skill);
|
|
552
|
+
}
|
|
502
553
|
function listGraphHints(root, filter = {}) {
|
|
503
554
|
const report = readJson(root, ".kaddo/graph-hints.json");
|
|
504
555
|
if (!report) return fail("Graph hints not found. Run `kaddo graph export` first.");
|
|
@@ -506,7 +557,13 @@ function listGraphHints(root, filter = {}) {
|
|
|
506
557
|
if (filter.artifact_type) hints = hints.filter((h) => h.artifact_type === filter.artifact_type);
|
|
507
558
|
if (filter.severity) hints = hints.filter((h) => h.severity === filter.severity);
|
|
508
559
|
if (filter.active_only) hints = hints.filter((h) => h.artifact_type === "work-item");
|
|
509
|
-
return ok({
|
|
560
|
+
return ok({
|
|
561
|
+
scope: report.scope ?? "active",
|
|
562
|
+
scope_reason: report.scope_reason ?? "",
|
|
563
|
+
quality: report.quality ?? "unknown",
|
|
564
|
+
count: hints.length,
|
|
565
|
+
hints
|
|
566
|
+
});
|
|
510
567
|
}
|
|
511
568
|
|
|
512
569
|
// ../cli/src/core/config.ts
|
|
@@ -528,6 +585,9 @@ function readDir(dirPath) {
|
|
|
528
585
|
function isFile(p) {
|
|
529
586
|
return exists(p) && fs2.statSync(p).isFile();
|
|
530
587
|
}
|
|
588
|
+
function isDir(p) {
|
|
589
|
+
return exists(p) && fs2.statSync(p).isDirectory();
|
|
590
|
+
}
|
|
531
591
|
function join(...parts) {
|
|
532
592
|
return path2.join(...parts);
|
|
533
593
|
}
|
|
@@ -619,13 +679,13 @@ ${issues}`);
|
|
|
619
679
|
}
|
|
620
680
|
|
|
621
681
|
// ../cli/src/core/context-pack.ts
|
|
622
|
-
import
|
|
682
|
+
import matter7 from "gray-matter";
|
|
623
683
|
|
|
624
684
|
// ../cli/src/services/artifact-reader.ts
|
|
625
|
-
import
|
|
685
|
+
import matter4 from "gray-matter";
|
|
626
686
|
function parseArtifact(filePath, raw) {
|
|
627
687
|
try {
|
|
628
|
-
const { data } =
|
|
688
|
+
const { data } = matter4(raw);
|
|
629
689
|
const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
|
|
630
690
|
return {
|
|
631
691
|
filePath,
|
|
@@ -1283,7 +1343,7 @@ function assessPhase(input) {
|
|
|
1283
1343
|
}
|
|
1284
1344
|
|
|
1285
1345
|
// ../cli/src/core/capsule.ts
|
|
1286
|
-
import
|
|
1346
|
+
import matter5 from "gray-matter";
|
|
1287
1347
|
import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
|
|
1288
1348
|
|
|
1289
1349
|
// ../cli/src/services/owners.ts
|
|
@@ -1436,7 +1496,7 @@ function sectionParagraph2(md, title) {
|
|
|
1436
1496
|
return "";
|
|
1437
1497
|
}
|
|
1438
1498
|
function parseCapsule(id, path3, md) {
|
|
1439
|
-
const { data } =
|
|
1499
|
+
const { data } = matter5(md);
|
|
1440
1500
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
1441
1501
|
let ageDays = null;
|
|
1442
1502
|
if (updatedAt) {
|
|
@@ -1471,6 +1531,11 @@ function loadExternalCapsules(dir) {
|
|
|
1471
1531
|
|
|
1472
1532
|
// ../cli/src/core/graph.ts
|
|
1473
1533
|
var KNOWLEDGE3 = "knowledge";
|
|
1534
|
+
var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
|
|
1535
|
+
var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
1536
|
+
function scopeStatuses(scope) {
|
|
1537
|
+
return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
|
|
1538
|
+
}
|
|
1474
1539
|
function toPosix3(p) {
|
|
1475
1540
|
return p.replace(/\\/g, "/");
|
|
1476
1541
|
}
|
|
@@ -1513,7 +1578,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
1513
1578
|
}
|
|
1514
1579
|
const all = discoverKnowledge(dir);
|
|
1515
1580
|
const workItems = all.filter((a) => a.isWorkItem);
|
|
1516
|
-
const
|
|
1581
|
+
const { included, excluded } = scopeStatuses(scope);
|
|
1582
|
+
const includedSet = new Set(included);
|
|
1583
|
+
const activeWICount = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)).length;
|
|
1584
|
+
const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
|
|
1517
1585
|
for (const wi of selectedWIs) {
|
|
1518
1586
|
const id = wi.id || wi.title;
|
|
1519
1587
|
const wiNodeId = `wi:${id}`;
|
|
@@ -1583,6 +1651,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
1583
1651
|
}
|
|
1584
1652
|
}
|
|
1585
1653
|
}
|
|
1654
|
+
const scopeReason = scope === "all" ? "All supported Work Item statuses are included." : activeWICount === 0 ? "No active Work Items found. Completed Work Items are excluded from active scope." : "Active Work Items only; completed and archived are excluded.";
|
|
1586
1655
|
return {
|
|
1587
1656
|
generated_at: now.toISOString(),
|
|
1588
1657
|
project: {
|
|
@@ -1590,6 +1659,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
1590
1659
|
state: config.project.state,
|
|
1591
1660
|
structure: config.project.structure
|
|
1592
1661
|
},
|
|
1662
|
+
scope,
|
|
1663
|
+
scope_reason: scopeReason,
|
|
1664
|
+
included_statuses: included,
|
|
1665
|
+
excluded_statuses: excluded,
|
|
1593
1666
|
nodes: [...nodes.values()],
|
|
1594
1667
|
edges
|
|
1595
1668
|
};
|
|
@@ -1637,8 +1710,13 @@ function loadGraphSummary(dir) {
|
|
|
1637
1710
|
const connected = new Set(
|
|
1638
1711
|
edges.filter((e) => e.type === "owns" && activeWiIds.has(e.from)).map((e) => e.from)
|
|
1639
1712
|
);
|
|
1713
|
+
const scope = graph.scope === "all" ? "all" : "active";
|
|
1640
1714
|
return {
|
|
1641
1715
|
generatedAt: String(graph.generated_at ?? ""),
|
|
1716
|
+
scope,
|
|
1717
|
+
scopeReason: String(graph.scope_reason ?? ""),
|
|
1718
|
+
includedStatuses: Array.isArray(graph.included_statuses) ? graph.included_statuses : [],
|
|
1719
|
+
excludedStatuses: Array.isArray(graph.excluded_statuses) ? graph.excluded_statuses : [],
|
|
1642
1720
|
nodes: nodes.length,
|
|
1643
1721
|
edges: edges.length,
|
|
1644
1722
|
activeWorkItemsConnectedToCode: connected.size
|
|
@@ -1790,6 +1868,8 @@ function buildGraphHints(dir, graph, now = /* @__PURE__ */ new Date()) {
|
|
|
1790
1868
|
const quality = assessQuality(nodes, relationshipEdges, hints.length);
|
|
1791
1869
|
return {
|
|
1792
1870
|
generated_at: now.toISOString(),
|
|
1871
|
+
scope: graph.scope,
|
|
1872
|
+
scope_reason: graph.scope_reason,
|
|
1793
1873
|
quality,
|
|
1794
1874
|
summary: { nodes, edges: graph.edges.length, hints: hints.length },
|
|
1795
1875
|
metrics,
|
|
@@ -1825,13 +1905,23 @@ function renderGraphHintsMarkdown(report) {
|
|
|
1825
1905
|
lines.push("");
|
|
1826
1906
|
lines.push("## Summary");
|
|
1827
1907
|
lines.push("");
|
|
1908
|
+
lines.push(`- Scope: ${report.scope} \u2014 ${report.scope_reason}`);
|
|
1828
1909
|
lines.push(`- Relationship quality: ${report.quality} \u2014 ${QUALITY_NOTE[report.quality]}`);
|
|
1829
1910
|
lines.push(`- Nodes: ${report.summary.nodes}`);
|
|
1830
1911
|
lines.push(`- Edges: ${report.summary.edges}`);
|
|
1831
1912
|
lines.push(`- Hints: ${report.summary.hints}`);
|
|
1832
1913
|
lines.push("");
|
|
1833
1914
|
if (report.hints.length === 0) {
|
|
1834
|
-
|
|
1915
|
+
if (report.quality === "empty") {
|
|
1916
|
+
lines.push("No active relationship hints were generated.");
|
|
1917
|
+
lines.push("");
|
|
1918
|
+
lines.push(`The graph is ${report.quality} for the **${report.scope}** scope \u2014 ${report.scope_reason}`);
|
|
1919
|
+
if (report.scope !== "all") {
|
|
1920
|
+
lines.push("Run `kaddo graph export --scope all` to inspect completed Work Items and historical relationships.");
|
|
1921
|
+
}
|
|
1922
|
+
} else {
|
|
1923
|
+
lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
|
|
1924
|
+
}
|
|
1835
1925
|
lines.push("");
|
|
1836
1926
|
return lines.join("\n");
|
|
1837
1927
|
}
|
|
@@ -1867,6 +1957,8 @@ function loadGraphHints(dir) {
|
|
|
1867
1957
|
const report = JSON.parse(readFile(p));
|
|
1868
1958
|
const hints = Array.isArray(report.hints) ? report.hints : [];
|
|
1869
1959
|
return {
|
|
1960
|
+
scope: String(report.scope ?? "active"),
|
|
1961
|
+
scopeReason: String(report.scope_reason ?? ""),
|
|
1870
1962
|
quality: report.quality ?? "empty",
|
|
1871
1963
|
totalHints: hints.length,
|
|
1872
1964
|
activeWorkItemHints: hints.filter((h) => h.artifact_type === "work-item").length,
|
|
@@ -1877,6 +1969,38 @@ function loadGraphHints(dir) {
|
|
|
1877
1969
|
}
|
|
1878
1970
|
}
|
|
1879
1971
|
|
|
1972
|
+
// ../cli/src/services/installed-skills.ts
|
|
1973
|
+
import matter6 from "gray-matter";
|
|
1974
|
+
var SKILLS_DIR = join("knowledge", "skills");
|
|
1975
|
+
function discoverInstalledSkills(dir) {
|
|
1976
|
+
const base = join(dir, SKILLS_DIR);
|
|
1977
|
+
if (!exists(base) || !isDir(base)) return [];
|
|
1978
|
+
const out = [];
|
|
1979
|
+
for (const entry of readDir(base)) {
|
|
1980
|
+
const skillFile = join(base, entry, "skill.md");
|
|
1981
|
+
if (!isFile(skillFile)) continue;
|
|
1982
|
+
try {
|
|
1983
|
+
const { data } = matter6(readFile(skillFile));
|
|
1984
|
+
if (data.type && String(data.type) !== "skill") continue;
|
|
1985
|
+
const id = String(data.id ?? entry);
|
|
1986
|
+
out.push({
|
|
1987
|
+
id,
|
|
1988
|
+
title: String(data.title ?? id),
|
|
1989
|
+
group: String(data.group ?? "unknown"),
|
|
1990
|
+
appliesTo: Array.isArray(data.applies_to) ? data.applies_to.map(String).filter(Boolean) : [],
|
|
1991
|
+
relPath: `${SKILLS_DIR.replace(/\\/g, "/")}/${entry}/skill.md`
|
|
1992
|
+
});
|
|
1993
|
+
} catch {
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
1997
|
+
}
|
|
1998
|
+
function skillGroupCounts(skills) {
|
|
1999
|
+
const counts = {};
|
|
2000
|
+
for (const s of skills) counts[s.group] = (counts[s.group] ?? 0) + 1;
|
|
2001
|
+
return counts;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1880
2004
|
// ../cli/src/core/context-pack.ts
|
|
1881
2005
|
var CONTEXT_PACK_VERSION = "1";
|
|
1882
2006
|
var ARCH_DIR2 = "knowledge";
|
|
@@ -1890,7 +2014,7 @@ function readScanJson(dir) {
|
|
|
1890
2014
|
}
|
|
1891
2015
|
}
|
|
1892
2016
|
function firstParagraph3(markdown) {
|
|
1893
|
-
const body =
|
|
2017
|
+
const body = matter7(markdown).content.trim();
|
|
1894
2018
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
1895
2019
|
return para ?? "";
|
|
1896
2020
|
}
|
|
@@ -2063,6 +2187,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
2063
2187
|
external: loadExternalCapsules(dir),
|
|
2064
2188
|
graph: loadGraphSummary(dir),
|
|
2065
2189
|
graphHints: loadGraphHints(dir),
|
|
2190
|
+
skills: discoverInstalledSkills(dir).map((s) => s.id),
|
|
2066
2191
|
mappedModules,
|
|
2067
2192
|
missing,
|
|
2068
2193
|
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
@@ -2233,9 +2358,12 @@ function renderContextPack(pack) {
|
|
|
2233
2358
|
parts.push(
|
|
2234
2359
|
[
|
|
2235
2360
|
"- Available: yes",
|
|
2361
|
+
`- Scope: ${pack.graph.scope}`,
|
|
2236
2362
|
`- Nodes: ${pack.graph.nodes}`,
|
|
2237
2363
|
`- Edges: ${pack.graph.edges}`,
|
|
2238
|
-
|
|
2364
|
+
...pack.graphHints ? [`- Quality: ${pack.graphHints.quality}`] : [],
|
|
2365
|
+
`- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`,
|
|
2366
|
+
...pack.graph.scopeReason ? [`- Reason: ${pack.graph.scopeReason}`] : []
|
|
2239
2367
|
].join("\n") + "\n"
|
|
2240
2368
|
);
|
|
2241
2369
|
parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
|
|
@@ -2256,6 +2384,12 @@ function renderContextPack(pack) {
|
|
|
2256
2384
|
`);
|
|
2257
2385
|
}
|
|
2258
2386
|
}
|
|
2387
|
+
if (pack.skills.length > 0) {
|
|
2388
|
+
parts.push("## Skills\n");
|
|
2389
|
+
parts.push("Available reusable skills (agents apply these; content is not inlined):\n");
|
|
2390
|
+
parts.push(pack.skills.map((s) => `- ${s}`).join("\n") + "\n");
|
|
2391
|
+
parts.push("Read full skill definitions in `knowledge/skills/` or via the Kaddo MCP server (`kaddo://skills`).\n");
|
|
2392
|
+
}
|
|
2259
2393
|
parts.push("## Missing Context\n");
|
|
2260
2394
|
if (missing.length > 0) {
|
|
2261
2395
|
parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
|
|
@@ -2475,6 +2609,10 @@ function buildProjectExplanation(dir) {
|
|
|
2475
2609
|
externalCapsules: loadExternalCapsules(dir),
|
|
2476
2610
|
graph: loadGraphSummary(dir),
|
|
2477
2611
|
graphHints: loadGraphHints(dir),
|
|
2612
|
+
skills: (() => {
|
|
2613
|
+
const installed = discoverInstalledSkills(dir);
|
|
2614
|
+
return { total: installed.length, byGroup: skillGroupCounts(installed) };
|
|
2615
|
+
})(),
|
|
2478
2616
|
layers,
|
|
2479
2617
|
roadmap,
|
|
2480
2618
|
mappedModules,
|
|
@@ -2616,13 +2754,27 @@ function renderExplanationHuman(exp) {
|
|
|
2616
2754
|
}
|
|
2617
2755
|
if (exp.graph) {
|
|
2618
2756
|
lines.push("## Knowledge Graph");
|
|
2757
|
+
lines.push(`- Scope: ${exp.graph.scope}`);
|
|
2619
2758
|
lines.push(`- Nodes: ${exp.graph.nodes}`);
|
|
2620
2759
|
lines.push(`- Edges: ${exp.graph.edges}`);
|
|
2621
2760
|
if (exp.graphHints) {
|
|
2622
2761
|
lines.push(`- Quality: ${exp.graphHints.quality}`);
|
|
2623
2762
|
lines.push(`- Hints: ${exp.graphHints.totalHints}`);
|
|
2624
2763
|
}
|
|
2764
|
+
if (exp.graph.scopeReason) lines.push(`- Reason: ${exp.graph.scopeReason}`);
|
|
2625
2765
|
if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
|
|
2766
|
+
if (exp.graph.scope === "active" && exp.graphHints?.quality === "empty") {
|
|
2767
|
+
lines.push("- Tip: Run `kaddo graph export --scope all` to include completed Work Items");
|
|
2768
|
+
}
|
|
2769
|
+
lines.push("");
|
|
2770
|
+
}
|
|
2771
|
+
if (exp.skills.total > 0) {
|
|
2772
|
+
lines.push(`## Skills installed: ${exp.skills.total}`);
|
|
2773
|
+
const groups = Object.entries(exp.skills.byGroup).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2774
|
+
if (groups.length > 0) {
|
|
2775
|
+
lines.push("Groups:");
|
|
2776
|
+
for (const [g, n] of groups) lines.push(`- ${g}: ${n}`);
|
|
2777
|
+
}
|
|
2626
2778
|
lines.push("");
|
|
2627
2779
|
}
|
|
2628
2780
|
if (exp.missingKnowledge.length > 0) {
|
|
@@ -2871,10 +3023,10 @@ function generateUnderstand(root) {
|
|
|
2871
3023
|
next_suggested_resources: ["kaddo://understand"]
|
|
2872
3024
|
};
|
|
2873
3025
|
}
|
|
2874
|
-
function generateGraph(root) {
|
|
3026
|
+
function generateGraph(root, scope = "active") {
|
|
2875
3027
|
const config = requireConfig(root);
|
|
2876
3028
|
requireKnowledge(root);
|
|
2877
|
-
const graph = buildGraph(root, config, { scope
|
|
3029
|
+
const graph = buildGraph(root, config, { scope });
|
|
2878
3030
|
const hints = buildGraphHints(root, graph);
|
|
2879
3031
|
writeDerived(root, ".kaddo/graph.json", serializeGraphJson(graph));
|
|
2880
3032
|
writeDerived(root, ".kaddo/graph.mmd", renderGraphMermaid(graph));
|
|
@@ -2889,7 +3041,7 @@ function generateGraph(root) {
|
|
|
2889
3041
|
".kaddo/graph-hints.md",
|
|
2890
3042
|
".kaddo/graph-hints.json"
|
|
2891
3043
|
],
|
|
2892
|
-
summary:
|
|
3044
|
+
summary: `Knowledge graph and hints generated successfully (${scope} scope).`,
|
|
2893
3045
|
warnings,
|
|
2894
3046
|
next_suggested_resources: ["kaddo://graph", "kaddo://graph-hints"]
|
|
2895
3047
|
};
|
|
@@ -3021,6 +3173,38 @@ function createServer(root) {
|
|
|
3021
3173
|
},
|
|
3022
3174
|
async (args) => toolText(guarded(root, () => listGraphHints(root, args)))
|
|
3023
3175
|
);
|
|
3176
|
+
server.registerTool(
|
|
3177
|
+
"kaddo_list_skills",
|
|
3178
|
+
{ title: "List skills", description: "List installed reusable skills.", inputSchema: {} },
|
|
3179
|
+
async () => toolText(guarded(root, () => listSkillsTool(root)))
|
|
3180
|
+
);
|
|
3181
|
+
server.registerTool(
|
|
3182
|
+
"kaddo_get_skill",
|
|
3183
|
+
{ title: "Get skill", description: "Get an installed reusable skill by id.", inputSchema: { id: z2.string() } },
|
|
3184
|
+
async (args) => toolText(guarded(root, () => getSkillTool(root, args.id)))
|
|
3185
|
+
);
|
|
3186
|
+
let installedSkills = [];
|
|
3187
|
+
try {
|
|
3188
|
+
assertKaddoProject(root);
|
|
3189
|
+
installedSkills = listSkills(root);
|
|
3190
|
+
} catch {
|
|
3191
|
+
installedSkills = [];
|
|
3192
|
+
}
|
|
3193
|
+
for (const s of installedSkills) {
|
|
3194
|
+
server.registerResource(
|
|
3195
|
+
`skill: ${s.id}`,
|
|
3196
|
+
`kaddo://skills/${s.id}`,
|
|
3197
|
+
{ title: s.title, description: `Reusable skill "${s.id}" (${s.group}).`, mimeType: "text/markdown" },
|
|
3198
|
+
async (uri) => {
|
|
3199
|
+
const full = getSkill(root, s.id);
|
|
3200
|
+
return {
|
|
3201
|
+
contents: [
|
|
3202
|
+
{ uri: uri.href, text: full?.content ?? `Skill "${s.id}" not found.`, mimeType: "text/markdown" }
|
|
3203
|
+
]
|
|
3204
|
+
};
|
|
3205
|
+
}
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3024
3208
|
server.registerTool(
|
|
3025
3209
|
"kaddo_generate_context",
|
|
3026
3210
|
{ title: "Generate context pack", description: `Regenerate .kaddo/context-pack.md + .json. ${DERIVED_NOTE}`, inputSchema: {} },
|
|
@@ -3038,8 +3222,12 @@ function createServer(root) {
|
|
|
3038
3222
|
);
|
|
3039
3223
|
server.registerTool(
|
|
3040
3224
|
"kaddo_generate_graph",
|
|
3041
|
-
{
|
|
3042
|
-
|
|
3225
|
+
{
|
|
3226
|
+
title: "Generate knowledge graph",
|
|
3227
|
+
description: `Regenerate .kaddo/graph.json/.mmd + graph-hints.md/.json. scope: active (default) or all. ${DERIVED_NOTE}`,
|
|
3228
|
+
inputSchema: { scope: z2.enum(["active", "all"]).optional() }
|
|
3229
|
+
},
|
|
3230
|
+
async (args) => generated(root, "generate knowledge graph", () => generateGraph(root, args.scope ?? "active"))
|
|
3043
3231
|
);
|
|
3044
3232
|
server.registerTool(
|
|
3045
3233
|
"kaddo_generate_capsule_draft",
|
|
@@ -3064,6 +3252,17 @@ function createServer(root) {
|
|
|
3064
3252
|
}
|
|
3065
3253
|
);
|
|
3066
3254
|
}
|
|
3255
|
+
for (const s of installedSkills) {
|
|
3256
|
+
server.registerPrompt(
|
|
3257
|
+
`skill-${s.id}`,
|
|
3258
|
+
{ title: s.title, description: `Reusable skill "${s.id}" (${s.group}).` },
|
|
3259
|
+
async () => {
|
|
3260
|
+
const full = getSkill(root, s.id);
|
|
3261
|
+
const content = full?.content ?? `Skill "${s.id}" is not installed.`;
|
|
3262
|
+
return { messages: [{ role: "user", content: { type: "text", text: content } }] };
|
|
3263
|
+
}
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3067
3266
|
return server;
|
|
3068
3267
|
}
|
|
3069
3268
|
|
package/package.json
CHANGED