@kaddo/mcp 3.23.0 → 3.23.2
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/dist/index.js +149 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2087,7 +2087,11 @@ function renderExplanationAgent(exp) {
|
|
|
2087
2087
|
}
|
|
2088
2088
|
|
|
2089
2089
|
// ../cli/src/core/impact-report.ts
|
|
2090
|
-
|
|
2090
|
+
function isBroadGlob(glob) {
|
|
2091
|
+
if (!glob.endsWith("/**")) return false;
|
|
2092
|
+
const prefix = glob.slice(0, -3);
|
|
2093
|
+
return prefix.length > 0 && prefix.split("/").length <= 2;
|
|
2094
|
+
}
|
|
2091
2095
|
function levelFromRatio(r) {
|
|
2092
2096
|
if (r >= 0.9) return "Very High";
|
|
2093
2097
|
if (r >= 0.7) return "High";
|
|
@@ -2098,6 +2102,8 @@ function hasSection(body, re) {
|
|
|
2098
2102
|
return body.split(/\r?\n/).some((l) => /^#{1,6}\s+/.test(l) && re.test(l));
|
|
2099
2103
|
}
|
|
2100
2104
|
function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
2105
|
+
const resolvedScope = opts.scope ?? "all";
|
|
2106
|
+
const scopeSource = opts.scopeSource ?? (opts.scope ? "explicit" : "default");
|
|
2101
2107
|
const exp = buildProjectExplanation(dir);
|
|
2102
2108
|
const wis = discoverWorkItems(dir);
|
|
2103
2109
|
const total = wis.length;
|
|
@@ -2107,40 +2113,74 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2107
2113
|
let withLevel = 0;
|
|
2108
2114
|
let withAcceptance = 0;
|
|
2109
2115
|
let withDoD = 0;
|
|
2116
|
+
let withValidation = 0;
|
|
2110
2117
|
let connectedRoadmap = 0;
|
|
2111
2118
|
const globCounts = /* @__PURE__ */ new Map();
|
|
2119
|
+
const globOwners = /* @__PURE__ */ new Map();
|
|
2120
|
+
const gaps = {
|
|
2121
|
+
missing_source: [],
|
|
2122
|
+
missing_initiative: [],
|
|
2123
|
+
missing_code_ownership: [],
|
|
2124
|
+
missing_knowledge_level: [],
|
|
2125
|
+
missing_acceptance_criteria: [],
|
|
2126
|
+
missing_definition_of_done: [],
|
|
2127
|
+
missing_validation: [],
|
|
2128
|
+
broad_ownership_globs: [],
|
|
2129
|
+
ownership_overlaps: []
|
|
2130
|
+
};
|
|
2131
|
+
const gapItem = (wi, action) => ({
|
|
2132
|
+
id: wi.id || wi.title,
|
|
2133
|
+
title: wi.title,
|
|
2134
|
+
status: wi.lifecycle ?? wi.status,
|
|
2135
|
+
path: wi.relPath,
|
|
2136
|
+
suggested_action: action
|
|
2137
|
+
});
|
|
2112
2138
|
for (const wi of wis) {
|
|
2139
|
+
const id = wi.id || wi.title;
|
|
2113
2140
|
if (wi.codeGlobs.length > 0) withOwnership++;
|
|
2141
|
+
else gaps.missing_code_ownership.push(gapItem(wi, "Add `code:` globs to connect this Work Item to source paths (`kaddo owners suggest`)."));
|
|
2114
2142
|
const hasSource = Boolean(wi.sourceId) || wi.source === "roadmap";
|
|
2115
2143
|
if (hasSource) withSource++;
|
|
2144
|
+
else gaps.missing_source.push(gapItem(wi, "Add `source` or `source_id` if this Work Item came from the roadmap."));
|
|
2116
2145
|
if (hasSource || wi.initiative) connectedRoadmap++;
|
|
2117
2146
|
if (wi.initiative) withInitiative++;
|
|
2147
|
+
else gaps.missing_initiative.push(gapItem(wi, "Add `initiative` to connect this Work Item to a delivery initiative."));
|
|
2118
2148
|
if (wi.knowledgeLevel) withLevel++;
|
|
2119
|
-
|
|
2149
|
+
else gaps.missing_knowledge_level.push(gapItem(wi, "Add `knowledge_level` (K0\u2013K4) to the front matter."));
|
|
2150
|
+
for (const g2 of wi.codeGlobs) {
|
|
2151
|
+
globCounts.set(g2, (globCounts.get(g2) ?? 0) + 1);
|
|
2152
|
+
globOwners.set(g2, [...globOwners.get(g2) ?? [], id]);
|
|
2153
|
+
if (isBroadGlob(g2)) {
|
|
2154
|
+
gaps.broad_ownership_globs.push({ id, title: wi.title, glob: g2, suggested_action: "Replace with specific files or narrower module paths." });
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2120
2157
|
try {
|
|
2121
2158
|
const body = matter6(readFile(wi.filePath)).content;
|
|
2122
|
-
if (hasSection(body, /acceptance/i)) withAcceptance++;
|
|
2123
|
-
|
|
2159
|
+
if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
|
|
2160
|
+
else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
|
|
2161
|
+
if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
|
|
2162
|
+
else gaps.missing_definition_of_done.push(gapItem(wi, "Add a `## Definition of Done` section."));
|
|
2163
|
+
if (hasSection(body, /how to test|validation|validaci|c[oó]mo probarlo/i)) withValidation++;
|
|
2164
|
+
else gaps.missing_validation.push(gapItem(wi, "Add a `## How to test it` (validation) section."));
|
|
2124
2165
|
} catch {
|
|
2125
2166
|
}
|
|
2126
2167
|
}
|
|
2127
2168
|
const ownedCodePaths = [...globCounts.values()].reduce((a, b) => a + b, 0);
|
|
2128
|
-
const broadGlobs = [...globCounts.keys()].filter((g2) =>
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
const config = loadConfig(dir);
|
|
2133
|
-
if (config) {
|
|
2134
|
-
const graph = buildGraph(dir, config, { scope: opts.scope }, now);
|
|
2135
|
-
const hints = buildGraphHints(dir, graph, now);
|
|
2136
|
-
g = { available: true, scope: graph.scope, scopeReason: graph.scope_reason, nodes: graph.nodes.length, edges: graph.edges.length, quality: hints.quality, hints: hints.summary.hints, generatedAt: graph.generated_at };
|
|
2137
|
-
} else {
|
|
2138
|
-
g = { available: false, scope: opts.scope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
2169
|
+
const broadGlobs = [...globCounts.keys()].filter((g2) => isBroadGlob(g2)).length;
|
|
2170
|
+
for (const [glob, owners] of globOwners) {
|
|
2171
|
+
if (owners.length > 1) {
|
|
2172
|
+
gaps.ownership_overlaps.push({ code_path: glob, work_items: [...new Set(owners)], suggested_action: "Review whether the overlap is expected or should be narrowed." });
|
|
2139
2173
|
}
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2174
|
+
}
|
|
2175
|
+
const ownershipOverlaps = gaps.ownership_overlaps.length;
|
|
2176
|
+
let g;
|
|
2177
|
+
const config = loadConfig(dir);
|
|
2178
|
+
if (config) {
|
|
2179
|
+
const graph = buildGraph(dir, config, { scope: resolvedScope }, now);
|
|
2180
|
+
const hints = buildGraphHints(dir, graph, now);
|
|
2181
|
+
g = { available: true, scope: graph.scope, scopeReason: graph.scope_reason, nodes: graph.nodes.length, edges: graph.edges.length, quality: hints.quality, hints: hints.summary.hints, generatedAt: graph.generated_at };
|
|
2142
2182
|
} else {
|
|
2143
|
-
g = { available: false, scope:
|
|
2183
|
+
g = { available: false, scope: resolvedScope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
2144
2184
|
}
|
|
2145
2185
|
const layerStatus = (name) => exp.layers.find((l) => l.layer === name)?.status ?? "Missing";
|
|
2146
2186
|
const byState = exp.workItems.byState;
|
|
@@ -2209,6 +2249,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2209
2249
|
maintenance_readiness: levelFromRatio((coverageRatio(withOwnership) + (deliveryTraceable ? 1 : 0)) / 2)
|
|
2210
2250
|
};
|
|
2211
2251
|
let score = null;
|
|
2252
|
+
let score_breakdown = null;
|
|
2212
2253
|
if (total > 0) {
|
|
2213
2254
|
const healthPts = [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * 20;
|
|
2214
2255
|
const covAvg = knowledge_coverage.reduce((a, c) => a + coverageRatio(c.have), 0) / knowledge_coverage.length;
|
|
@@ -2220,13 +2261,34 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2220
2261
|
const graphPts = (g.available ? qualityMap[g.quality] ?? 0 : 0) * 15;
|
|
2221
2262
|
const ctxPts = { Low: 0.25, Medium: 0.5, High: 0.8, "Very High": 1 }[ctxLevel] * 10;
|
|
2222
2263
|
score = Math.round(healthPts + coveragePts + ownPts + tracePts + graphPts + ctxPts);
|
|
2264
|
+
score_breakdown = {
|
|
2265
|
+
knowledge_health: { points: Math.round(healthPts), max: 20 },
|
|
2266
|
+
knowledge_coverage: { points: Math.round(coveragePts), max: 20 },
|
|
2267
|
+
ownership_coverage: { points: Math.round(ownPts), max: 15 },
|
|
2268
|
+
traceability: { points: Math.round(tracePts), max: 20 },
|
|
2269
|
+
graph_quality: { points: Math.round(graphPts), max: 15 },
|
|
2270
|
+
context_readiness: { points: Math.round(ctxPts), max: 10 }
|
|
2271
|
+
};
|
|
2223
2272
|
}
|
|
2273
|
+
const idsOf = (items) => items.map((i) => i.id);
|
|
2274
|
+
const groupedAction = (items, verb) => {
|
|
2275
|
+
if (items.length === 0) return null;
|
|
2276
|
+
const ids = idsOf(items);
|
|
2277
|
+
return ids.length <= 3 ? `${verb} ${ids.join(", ")}.` : `${verb} ${ids.length} Work Items: ${ids.slice(0, 3).join(", ")}, \u2026`;
|
|
2278
|
+
};
|
|
2224
2279
|
const actions = [];
|
|
2225
2280
|
if (!g.available) actions.push("Run `kaddo graph export --scope all` to inspect full traceability.");
|
|
2226
|
-
else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo
|
|
2281
|
+
else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo impact --scope all` to measure accumulated knowledge impact.");
|
|
2227
2282
|
if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
|
|
2228
|
-
|
|
2229
|
-
|
|
2283
|
+
for (const a of [
|
|
2284
|
+
groupedAction(gaps.missing_code_ownership, "Add `code:` ownership to"),
|
|
2285
|
+
groupedAction(gaps.missing_source, "Add `source`/`source_id` to"),
|
|
2286
|
+
groupedAction(gaps.missing_initiative, "Add an `initiative` to"),
|
|
2287
|
+
groupedAction(gaps.missing_acceptance_criteria, "Add Acceptance Criteria to"),
|
|
2288
|
+
groupedAction(gaps.missing_definition_of_done, "Add a Definition of Done to"),
|
|
2289
|
+
groupedAction(gaps.missing_validation, "Add a validation (How to test it) section to")
|
|
2290
|
+
]) if (a) actions.push(a);
|
|
2291
|
+
if (gaps.broad_ownership_globs.length > 0) actions.push(`Narrow ${gaps.broad_ownership_globs.length} broad ownership glob(s) into specific paths.`);
|
|
2230
2292
|
if (g.available && g.hints > 0) actions.push("Review graph hints (`kaddo://graph-hints` or `.kaddo/graph-hints.md`).");
|
|
2231
2293
|
if (skills.length === 0) actions.push("Install reusable skills with `kaddo add skills`.");
|
|
2232
2294
|
actions.push("Run `kaddo guard` before committing to catch knowledge drift.");
|
|
@@ -2241,6 +2303,8 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2241
2303
|
generated_at: now.toISOString(),
|
|
2242
2304
|
project: exp.project.name,
|
|
2243
2305
|
scope: g.scope,
|
|
2306
|
+
default_scope: "all",
|
|
2307
|
+
scope_source: scopeSource,
|
|
2244
2308
|
executive_summary: summary,
|
|
2245
2309
|
knowledge_health,
|
|
2246
2310
|
knowledge_coverage,
|
|
@@ -2278,7 +2342,9 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2278
2342
|
note: "Guard history is not persisted. Run `kaddo guard` to check drift; future versions may store runs for trend analysis."
|
|
2279
2343
|
},
|
|
2280
2344
|
impact_signals,
|
|
2345
|
+
actionable_gaps: gaps,
|
|
2281
2346
|
score,
|
|
2347
|
+
score_breakdown,
|
|
2282
2348
|
suggested_actions: actions
|
|
2283
2349
|
};
|
|
2284
2350
|
}
|
|
@@ -2293,6 +2359,11 @@ function renderImpactMarkdown(r) {
|
|
|
2293
2359
|
L.push(`Scope: ${r.scope}`);
|
|
2294
2360
|
if (r.score !== null) L.push(`Knowledge Impact Score: ${r.score}/100`);
|
|
2295
2361
|
else L.push("Knowledge Impact Score: not available");
|
|
2362
|
+
if (r.scope_source === "default") {
|
|
2363
|
+
L.push("Scope note: Impact reports use `all` by default to measure accumulated knowledge impact.");
|
|
2364
|
+
} else if (r.scope === "active" && r.graph_quality.available === false) {
|
|
2365
|
+
L.push("Tip: Run `kaddo impact --scope all` to inspect accumulated knowledge impact.");
|
|
2366
|
+
}
|
|
2296
2367
|
L.push("");
|
|
2297
2368
|
L.push("## Executive Summary", "");
|
|
2298
2369
|
for (const s2 of r.executive_summary) L.push(`- ${s2}`);
|
|
@@ -2349,6 +2420,9 @@ function renderImpactMarkdown(r) {
|
|
|
2349
2420
|
L.push(`- Hints: ${gq.hints}`);
|
|
2350
2421
|
if (gq.reason) L.push(`- Reason: ${gq.reason}`);
|
|
2351
2422
|
if (gq.last_exported) L.push(`- Last exported: ${gq.last_exported}`);
|
|
2423
|
+
if (gq.scope === "active" && gq.quality === "empty") {
|
|
2424
|
+
L.push("- Tip: Run `kaddo impact --scope all` to inspect accumulated knowledge impact.");
|
|
2425
|
+
}
|
|
2352
2426
|
} else {
|
|
2353
2427
|
L.push("- Graph data not available.");
|
|
2354
2428
|
L.push(`- Tip: ${r.graph_quality.suggestion}`);
|
|
@@ -2367,6 +2441,58 @@ function renderImpactMarkdown(r) {
|
|
|
2367
2441
|
L.push(`- AI context readiness: ${s.ai_context_readiness}`);
|
|
2368
2442
|
L.push(`- Maintenance readiness: ${s.maintenance_readiness}`);
|
|
2369
2443
|
L.push("");
|
|
2444
|
+
L.push("## Actionable Gaps", "");
|
|
2445
|
+
const gp = r.actionable_gaps;
|
|
2446
|
+
const gapSection = (title, items) => {
|
|
2447
|
+
if (items.length === 0) return;
|
|
2448
|
+
L.push(`### ${title}`, "");
|
|
2449
|
+
for (const it of items) {
|
|
2450
|
+
L.push(`- ${it.id} \u2014 ${it.title}`);
|
|
2451
|
+
L.push(` - Path: ${it.path}`);
|
|
2452
|
+
L.push(` - Suggested action: ${it.suggested_action}`);
|
|
2453
|
+
}
|
|
2454
|
+
L.push("");
|
|
2455
|
+
};
|
|
2456
|
+
gapSection("Work Items missing source", gp.missing_source);
|
|
2457
|
+
gapSection("Work Items missing initiative", gp.missing_initiative);
|
|
2458
|
+
gapSection("Work Items missing code ownership", gp.missing_code_ownership);
|
|
2459
|
+
gapSection("Work Items missing knowledge level", gp.missing_knowledge_level);
|
|
2460
|
+
gapSection("Work Items missing acceptance criteria", gp.missing_acceptance_criteria);
|
|
2461
|
+
gapSection("Work Items missing Definition of Done", gp.missing_definition_of_done);
|
|
2462
|
+
gapSection("Work Items missing validation", gp.missing_validation);
|
|
2463
|
+
if (gp.broad_ownership_globs.length > 0) {
|
|
2464
|
+
L.push("### Broad ownership globs", "");
|
|
2465
|
+
for (const b of gp.broad_ownership_globs) {
|
|
2466
|
+
L.push(`- ${b.id} \u2014 ${b.title}`);
|
|
2467
|
+
L.push(` - Glob: \`${b.glob}\``);
|
|
2468
|
+
L.push(` - Suggested action: ${b.suggested_action}`);
|
|
2469
|
+
}
|
|
2470
|
+
L.push("");
|
|
2471
|
+
}
|
|
2472
|
+
if (gp.ownership_overlaps.length > 0) {
|
|
2473
|
+
L.push("### Ownership overlaps", "");
|
|
2474
|
+
for (const o of gp.ownership_overlaps) {
|
|
2475
|
+
L.push(`- \`${o.code_path}\``);
|
|
2476
|
+
L.push(` - Owned by: ${o.work_items.join(", ")}`);
|
|
2477
|
+
L.push(` - Suggested action: ${o.suggested_action}`);
|
|
2478
|
+
}
|
|
2479
|
+
L.push("");
|
|
2480
|
+
}
|
|
2481
|
+
const anyGaps = gp.missing_source.length + gp.missing_initiative.length + gp.missing_code_ownership.length + gp.missing_knowledge_level.length + gp.missing_acceptance_criteria.length + gp.missing_definition_of_done.length + gp.missing_validation.length + gp.broad_ownership_globs.length + gp.ownership_overlaps.length;
|
|
2482
|
+
if (anyGaps === 0) {
|
|
2483
|
+
L.push("No actionable knowledge gaps detected. \u{1F389}", "");
|
|
2484
|
+
}
|
|
2485
|
+
if (r.score_breakdown) {
|
|
2486
|
+
L.push("## Score Breakdown", "");
|
|
2487
|
+
const b = r.score_breakdown;
|
|
2488
|
+
L.push(`- Knowledge Health: ${b.knowledge_health.points}/${b.knowledge_health.max}`);
|
|
2489
|
+
L.push(`- Knowledge Coverage: ${b.knowledge_coverage.points}/${b.knowledge_coverage.max}`);
|
|
2490
|
+
L.push(`- Ownership Coverage: ${b.ownership_coverage.points}/${b.ownership_coverage.max}`);
|
|
2491
|
+
L.push(`- Traceability: ${b.traceability.points}/${b.traceability.max}`);
|
|
2492
|
+
L.push(`- Graph Quality: ${b.graph_quality.points}/${b.graph_quality.max}`);
|
|
2493
|
+
L.push(`- Context Readiness: ${b.context_readiness.points}/${b.context_readiness.max}`);
|
|
2494
|
+
L.push("");
|
|
2495
|
+
}
|
|
2370
2496
|
L.push("## Suggested Actions", "");
|
|
2371
2497
|
r.suggested_actions.forEach((a, i) => L.push(`${i + 1}. ${a}`));
|
|
2372
2498
|
L.push("");
|
|
@@ -2586,7 +2712,7 @@ var RESOURCES = [
|
|
|
2586
2712
|
if (!hasKnowledge(root)) {
|
|
2587
2713
|
return text("kaddo://impact-report", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
|
|
2588
2714
|
}
|
|
2589
|
-
return [{ uri: "kaddo://impact-report", text: renderImpactMarkdown(buildImpactReport(root, { scope: "all" })), mimeType: "text/markdown" }];
|
|
2715
|
+
return [{ uri: "kaddo://impact-report", text: renderImpactMarkdown(buildImpactReport(root, { scope: "all", scopeSource: "default" })), mimeType: "text/markdown" }];
|
|
2590
2716
|
}
|
|
2591
2717
|
}
|
|
2592
2718
|
];
|
|
@@ -3354,7 +3480,7 @@ function generateImpactReport(root, opts = {}) {
|
|
|
3354
3480
|
requireConfig(root);
|
|
3355
3481
|
requireKnowledge(root);
|
|
3356
3482
|
const format = opts.format ?? "markdown";
|
|
3357
|
-
const report = buildImpactReport(root, { scope: opts.scope ?? "all" });
|
|
3483
|
+
const report = buildImpactReport(root, { scope: opts.scope ?? "all", scopeSource: opts.scope ? "explicit" : "default" });
|
|
3358
3484
|
const content = format === "json" ? serializeImpactJson(report) : renderImpactMarkdown(report);
|
|
3359
3485
|
const out = opts.output ?? `.kaddo/reports/impact-report.${format === "json" ? "json" : "md"}`;
|
|
3360
3486
|
writeDerived(root, out, content);
|
package/package.json
CHANGED