@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.
Files changed (2) hide show
  1. package/dist/index.js +149 -23
  2. 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
- var BROAD_GLOB = /^[^/*]+\/\*\*$/;
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
- for (const g2 of wi.codeGlobs) globCounts.set(g2, (globCounts.get(g2) ?? 0) + 1);
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
- if (hasSection(body, /definition of done|^#{1,6}\s*done\b/i)) withDoD++;
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) => BROAD_GLOB.test(g2)).length;
2129
- const ownershipOverlaps = [...globCounts.values()].filter((n) => n > 1).length;
2130
- let g;
2131
- if (opts.scope) {
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
- } else if (exp.graph) {
2141
- g = { available: true, scope: exp.graph.scope, scopeReason: exp.graph.scopeReason, nodes: exp.graph.nodes, edges: exp.graph.edges, quality: exp.graphHints?.quality ?? "unknown", hints: exp.graphHints?.totalHints ?? 0, generatedAt: exp.graph.generatedAt };
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: "n/a", scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
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 graph export --scope all` to include completed Work Items.");
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
- if (total > 0 && withOwnership < total) actions.push("Add code ownership to Work Items without `code:` globs (`kaddo owners suggest`).");
2229
- if (total > 0 && withInitiative < total) actions.push("Add an initiative to Work Items missing one.");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.23.0",
3
+ "version": "3.23.2",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {