@kaddo/cli 3.22.1 → 3.23.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +440 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -510,6 +510,8 @@ create --from roadmap → owners → guard → explain`.
510
510
  | v3.21 | Reusable Skills layer (`kaddo add skills`); skills in context/explain/understand, agent prompts and MCP |
511
511
  | v3.22 | Guard & graph scope semantics: graph scope metadata, contextual-empty messaging, Guard ownership scope (active + completed; `--include-archived`) |
512
512
  | v3.22.1 | Guard project-root path normalization: matches `code:` globs when the project is a subfolder of the Git root |
513
+ | v3.23 | Knowledge Impact Report: `kaddo report impact` (Markdown/JSON, `--output`); evidence-first health/coverage/traceability/readiness/signals; MCP resource + tool |
514
+ | v3.23.1 | Impact report Actionable Gaps: per-Work-Item missing source/initiative/ownership/level/acceptance/DoD/validation, broad globs, overlaps; Work-Item-specific Suggested Actions + Score Breakdown |
513
515
 
514
516
  **Optional modules (installed with `kaddo add`):**
515
517
 
package/dist/index.js CHANGED
@@ -684,6 +684,10 @@ var COMMAND_HELP = {
684
684
  "graph export": {
685
685
  question: "How is my project knowledge connected?",
686
686
  next: "Open .kaddo/graph.mmd, or run kaddo explain for a summary"
687
+ },
688
+ "report impact": {
689
+ question: "What impact is Kaddo having on this project?",
690
+ next: "Act on the Suggested Actions section"
687
691
  }
688
692
  };
689
693
  function commandFooterLines(name) {
@@ -10912,6 +10916,435 @@ function runGraphExport(opts = {}) {
10912
10916
  outro2("Knowledge graph ready.");
10913
10917
  }
10914
10918
 
10919
+ // src/core/impact-report.ts
10920
+ import matter8 from "gray-matter";
10921
+ function isBroadGlob(glob) {
10922
+ if (!glob.endsWith("/**")) return false;
10923
+ const prefix = glob.slice(0, -3);
10924
+ return prefix.length > 0 && prefix.split("/").length <= 2;
10925
+ }
10926
+ function levelFromRatio(r) {
10927
+ if (r >= 0.9) return "Very High";
10928
+ if (r >= 0.7) return "High";
10929
+ if (r >= 0.4) return "Medium";
10930
+ return "Low";
10931
+ }
10932
+ function hasSection(body, re) {
10933
+ return body.split(/\r?\n/).some((l) => /^#{1,6}\s+/.test(l) && re.test(l));
10934
+ }
10935
+ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10936
+ const exp = buildProjectExplanation(dir);
10937
+ const wis = discoverWorkItems(dir);
10938
+ const total = wis.length;
10939
+ let withOwnership = 0;
10940
+ let withSource = 0;
10941
+ let withInitiative = 0;
10942
+ let withLevel = 0;
10943
+ let withAcceptance = 0;
10944
+ let withDoD = 0;
10945
+ let withValidation = 0;
10946
+ let connectedRoadmap = 0;
10947
+ const globCounts = /* @__PURE__ */ new Map();
10948
+ const globOwners = /* @__PURE__ */ new Map();
10949
+ const gaps = {
10950
+ missing_source: [],
10951
+ missing_initiative: [],
10952
+ missing_code_ownership: [],
10953
+ missing_knowledge_level: [],
10954
+ missing_acceptance_criteria: [],
10955
+ missing_definition_of_done: [],
10956
+ missing_validation: [],
10957
+ broad_ownership_globs: [],
10958
+ ownership_overlaps: []
10959
+ };
10960
+ const gapItem = (wi, action) => ({
10961
+ id: wi.id || wi.title,
10962
+ title: wi.title,
10963
+ status: wi.lifecycle ?? wi.status,
10964
+ path: wi.relPath,
10965
+ suggested_action: action
10966
+ });
10967
+ for (const wi of wis) {
10968
+ const id = wi.id || wi.title;
10969
+ if (wi.codeGlobs.length > 0) withOwnership++;
10970
+ else gaps.missing_code_ownership.push(gapItem(wi, "Add `code:` globs to connect this Work Item to source paths (`kaddo owners suggest`)."));
10971
+ const hasSource = Boolean(wi.sourceId) || wi.source === "roadmap";
10972
+ if (hasSource) withSource++;
10973
+ else gaps.missing_source.push(gapItem(wi, "Add `source` or `source_id` if this Work Item came from the roadmap."));
10974
+ if (hasSource || wi.initiative) connectedRoadmap++;
10975
+ if (wi.initiative) withInitiative++;
10976
+ else gaps.missing_initiative.push(gapItem(wi, "Add `initiative` to connect this Work Item to a delivery initiative."));
10977
+ if (wi.knowledgeLevel) withLevel++;
10978
+ else gaps.missing_knowledge_level.push(gapItem(wi, "Add `knowledge_level` (K0\u2013K4) to the front matter."));
10979
+ for (const g2 of wi.codeGlobs) {
10980
+ globCounts.set(g2, (globCounts.get(g2) ?? 0) + 1);
10981
+ globOwners.set(g2, [...globOwners.get(g2) ?? [], id]);
10982
+ if (isBroadGlob(g2)) {
10983
+ gaps.broad_ownership_globs.push({ id, title: wi.title, glob: g2, suggested_action: "Replace with specific files or narrower module paths." });
10984
+ }
10985
+ }
10986
+ try {
10987
+ const body = matter8(readFile(wi.filePath)).content;
10988
+ if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
10989
+ else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
10990
+ if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
10991
+ else gaps.missing_definition_of_done.push(gapItem(wi, "Add a `## Definition of Done` section."));
10992
+ if (hasSection(body, /how to test|validation|validaci|c[oó]mo probarlo/i)) withValidation++;
10993
+ else gaps.missing_validation.push(gapItem(wi, "Add a `## How to test it` (validation) section."));
10994
+ } catch {
10995
+ }
10996
+ }
10997
+ const ownedCodePaths = [...globCounts.values()].reduce((a, b) => a + b, 0);
10998
+ const broadGlobs = [...globCounts.keys()].filter((g2) => isBroadGlob(g2)).length;
10999
+ for (const [glob, owners] of globOwners) {
11000
+ if (owners.length > 1) {
11001
+ gaps.ownership_overlaps.push({ code_path: glob, work_items: [...new Set(owners)], suggested_action: "Review whether the overlap is expected or should be narrowed." });
11002
+ }
11003
+ }
11004
+ const ownershipOverlaps = gaps.ownership_overlaps.length;
11005
+ let g;
11006
+ if (opts.scope) {
11007
+ const config = loadConfig(dir);
11008
+ if (config) {
11009
+ const graph = buildGraph(dir, config, { scope: opts.scope }, now);
11010
+ const hints = buildGraphHints(dir, graph, now);
11011
+ 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 };
11012
+ } else {
11013
+ g = { available: false, scope: opts.scope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
11014
+ }
11015
+ } else if (exp.graph) {
11016
+ 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 };
11017
+ } else {
11018
+ g = { available: false, scope: "n/a", scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
11019
+ }
11020
+ const layerStatus = (name) => exp.layers.find((l) => l.layer === name)?.status ?? "Missing";
11021
+ const byState = exp.workItems.byState;
11022
+ const skills = discoverInstalledSkills(dir);
11023
+ const knowledge_health = {
11024
+ Business: layerStatus("Business"),
11025
+ Product: layerStatus("Product"),
11026
+ Tech: layerStatus("Tech"),
11027
+ Delivery: layerStatus("Delivery"),
11028
+ Inventory: exp.knowledge.hasInventory ? "available" : "missing",
11029
+ "Context pack": exp.knowledge.hasContextPack ? "available" : "missing",
11030
+ Agents: exp.knowledge.hasAgents ? "available" : "missing",
11031
+ Skills: skills.length > 0 ? "available" : "missing"
11032
+ };
11033
+ const knowledge_coverage = [
11034
+ { label: "Work Items with ownership", have: withOwnership, total },
11035
+ { label: "Work Items with source", have: withSource, total },
11036
+ { label: "Work Items with initiative", have: withInitiative, total },
11037
+ { label: "Work Items with acceptance criteria", have: withAcceptance, total },
11038
+ { label: "Work Items with Definition of Done", have: withDoD, total },
11039
+ { label: "Work Items with knowledge level", have: withLevel, total }
11040
+ ];
11041
+ const coverageRatio = (have) => total > 0 ? have / total : 0;
11042
+ const deliveryTraceable = ["Traceable", "Structured", "Consolidated"].includes(layerStatus("Delivery"));
11043
+ const ctxReasons = [];
11044
+ let ctxScore = 0;
11045
+ if (["Consolidated", "Structured"].includes(layerStatus("Business"))) {
11046
+ ctxScore++;
11047
+ ctxReasons.push("Business knowledge is present.");
11048
+ }
11049
+ if (["Consolidated", "Structured"].includes(layerStatus("Product"))) {
11050
+ ctxScore++;
11051
+ ctxReasons.push("Product knowledge is present.");
11052
+ }
11053
+ if (layerStatus("Tech") !== "Missing") {
11054
+ ctxScore++;
11055
+ ctxReasons.push("Tech knowledge is structured.");
11056
+ }
11057
+ if (deliveryTraceable) {
11058
+ ctxScore++;
11059
+ ctxReasons.push("Delivery is traceable.");
11060
+ }
11061
+ if (exp.knowledge.hasContextPack) {
11062
+ ctxScore++;
11063
+ ctxReasons.push("Context pack is available.");
11064
+ }
11065
+ if (g.available && g.quality !== "empty") {
11066
+ ctxScore++;
11067
+ ctxReasons.push(`Graph quality is ${g.quality}.`);
11068
+ }
11069
+ const ctxLevel = ctxScore >= 6 ? "Very High" : ctxScore >= 4 ? "High" : ctxScore >= 2 ? "Medium" : "Low";
11070
+ const ambiguityRatio = total > 0 ? (withAcceptance + withDoD + withSource) / (3 * total) : 0;
11071
+ const aiReady = exp.knowledge.hasContextPack && g.available && g.quality !== "empty" && skills.length > 0 && deliveryTraceable ? "High" : exp.knowledge.hasContextPack && g.available && g.quality !== "empty" ? "Medium" : "Low";
11072
+ const onboarding = levelFromRatio(
11073
+ [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * (exp.knowledge.hasContextPack ? 1 : 0.6)
11074
+ );
11075
+ const deliveryTrace = levelFromRatio(
11076
+ coverageRatio(withOwnership) * 0.5 + (g.available && g.edges > 0 ? 1 : 0) * 0.25 + (exp.roadmap.present ? 1 : 0) * 0.25
11077
+ );
11078
+ const impact_signals = {
11079
+ ambiguity_reduction: levelFromRatio(ambiguityRatio),
11080
+ drift_prevention: withOwnership > 0 ? "Active" : "Limited",
11081
+ onboarding_support: onboarding,
11082
+ delivery_traceability: deliveryTrace,
11083
+ ai_context_readiness: aiReady,
11084
+ maintenance_readiness: levelFromRatio((coverageRatio(withOwnership) + (deliveryTraceable ? 1 : 0)) / 2)
11085
+ };
11086
+ let score = null;
11087
+ let score_breakdown = null;
11088
+ if (total > 0) {
11089
+ const healthPts = [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * 20;
11090
+ const covAvg = knowledge_coverage.reduce((a, c) => a + coverageRatio(c.have), 0) / knowledge_coverage.length;
11091
+ const coveragePts = covAvg * 20;
11092
+ const ownPts = coverageRatio(withOwnership) * 15;
11093
+ const traceBlend = (exp.roadmap.candidates > 0 ? exp.roadmap.materialized / exp.roadmap.candidates : 1) * 0.5 + coverageRatio(withOwnership) * 0.5;
11094
+ const tracePts = traceBlend * 20;
11095
+ const qualityMap = { good: 1, partial: 0.6, sparse: 0.3, empty: 0, unknown: 0 };
11096
+ const graphPts = (g.available ? qualityMap[g.quality] ?? 0 : 0) * 15;
11097
+ const ctxPts = { Low: 0.25, Medium: 0.5, High: 0.8, "Very High": 1 }[ctxLevel] * 10;
11098
+ score = Math.round(healthPts + coveragePts + ownPts + tracePts + graphPts + ctxPts);
11099
+ score_breakdown = {
11100
+ knowledge_health: { points: Math.round(healthPts), max: 20 },
11101
+ knowledge_coverage: { points: Math.round(coveragePts), max: 20 },
11102
+ ownership_coverage: { points: Math.round(ownPts), max: 15 },
11103
+ traceability: { points: Math.round(tracePts), max: 20 },
11104
+ graph_quality: { points: Math.round(graphPts), max: 15 },
11105
+ context_readiness: { points: Math.round(ctxPts), max: 10 }
11106
+ };
11107
+ }
11108
+ const idsOf = (items) => items.map((i) => i.id);
11109
+ const groupedAction = (items, verb) => {
11110
+ if (items.length === 0) return null;
11111
+ const ids = idsOf(items);
11112
+ return ids.length <= 3 ? `${verb} ${ids.join(", ")}.` : `${verb} ${ids.length} Work Items: ${ids.slice(0, 3).join(", ")}, \u2026`;
11113
+ };
11114
+ const actions = [];
11115
+ if (!g.available) actions.push("Run `kaddo graph export --scope all` to inspect full traceability.");
11116
+ else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo graph export --scope all` to include completed Work Items.");
11117
+ if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
11118
+ for (const a of [
11119
+ groupedAction(gaps.missing_code_ownership, "Add `code:` ownership to"),
11120
+ groupedAction(gaps.missing_source, "Add `source`/`source_id` to"),
11121
+ groupedAction(gaps.missing_initiative, "Add an `initiative` to"),
11122
+ groupedAction(gaps.missing_acceptance_criteria, "Add Acceptance Criteria to"),
11123
+ groupedAction(gaps.missing_definition_of_done, "Add a Definition of Done to"),
11124
+ groupedAction(gaps.missing_validation, "Add a validation (How to test it) section to")
11125
+ ]) if (a) actions.push(a);
11126
+ if (gaps.broad_ownership_globs.length > 0) actions.push(`Narrow ${gaps.broad_ownership_globs.length} broad ownership glob(s) into specific paths.`);
11127
+ if (g.available && g.hints > 0) actions.push("Review graph hints (`kaddo://graph-hints` or `.kaddo/graph-hints.md`).");
11128
+ if (skills.length === 0) actions.push("Install reusable skills with `kaddo add skills`.");
11129
+ actions.push("Run `kaddo guard` before committing to catch knowledge drift.");
11130
+ const summary = [];
11131
+ if (total > 0) summary.push(`${withOwnership}/${total} Work Items include code ownership.`);
11132
+ if (g.available) summary.push(`The knowledge graph is ${g.quality} with ${g.nodes} nodes and ${g.edges} edges (${g.scope} scope).`);
11133
+ else summary.push("The knowledge graph has not been exported yet.");
11134
+ if (g.available) summary.push(g.hints === 0 ? "No graph hints are currently open." : `${g.hints} graph hint(s) are open.`);
11135
+ if (exp.roadmap.present) summary.push(`Delivery: ${exp.roadmap.materialized} materialized, ${exp.roadmap.remaining} roadmap candidate(s) remaining.`);
11136
+ summary.push(`Context readiness: ${ctxLevel}.`);
11137
+ return {
11138
+ generated_at: now.toISOString(),
11139
+ project: exp.project.name,
11140
+ scope: g.scope,
11141
+ executive_summary: summary,
11142
+ knowledge_health,
11143
+ knowledge_coverage,
11144
+ ownership_coverage: {
11145
+ coverage_percent: total > 0 ? Math.round(withOwnership / total * 100) : 0,
11146
+ work_items_with_ownership: `${withOwnership}/${total}`,
11147
+ owned_code_paths: ownedCodePaths,
11148
+ broad_globs: broadGlobs,
11149
+ ownership_overlaps: ownershipOverlaps
11150
+ },
11151
+ traceability: {
11152
+ roadmap_candidates: exp.roadmap.candidates,
11153
+ materialized_work_items: exp.roadmap.materialized,
11154
+ remaining_candidates: exp.roadmap.remaining,
11155
+ completed_work_items: byState.completed,
11156
+ work_items_connected_to_roadmap: `${connectedRoadmap}/${total}`,
11157
+ work_items_connected_to_code: `${withOwnership}/${total}`,
11158
+ graph_quality: g.available ? g.quality : "not available",
11159
+ graph_nodes: g.available ? g.nodes : null,
11160
+ graph_edges: g.available ? g.edges : null,
11161
+ graph_hints: g.available ? g.hints : null
11162
+ },
11163
+ context_readiness: { level: ctxLevel, reasons: ctxReasons },
11164
+ work_item_readiness: {
11165
+ draft: byState.draft,
11166
+ ready: byState.ready,
11167
+ in_progress: byState["in-progress"],
11168
+ blocked: byState.blocked,
11169
+ completed: byState.completed,
11170
+ ready_quality: byState.ready > 0 ? `${byState.ready} ready` : "not applicable"
11171
+ },
11172
+ graph_quality: g.available ? { available: true, scope: g.scope, quality: g.quality, nodes: g.nodes, edges: g.edges, hints: g.hints, reason: g.scopeReason, last_exported: g.generatedAt } : { available: false, suggestion: "Run `kaddo graph export --scope all`." },
11173
+ guard_activity: {
11174
+ available: false,
11175
+ note: "Guard history is not persisted. Run `kaddo guard` to check drift; future versions may store runs for trend analysis."
11176
+ },
11177
+ impact_signals,
11178
+ actionable_gaps: gaps,
11179
+ score,
11180
+ score_breakdown,
11181
+ suggested_actions: actions
11182
+ };
11183
+ }
11184
+ function ratio(have, total) {
11185
+ return `${have}/${total}`;
11186
+ }
11187
+ function renderImpactMarkdown(r) {
11188
+ const L = [];
11189
+ L.push("# Kaddo Knowledge Impact Report", "");
11190
+ L.push(`Generated at: ${r.generated_at}`);
11191
+ L.push(`Project: ${r.project}`);
11192
+ L.push(`Scope: ${r.scope}`);
11193
+ if (r.score !== null) L.push(`Knowledge Impact Score: ${r.score}/100`);
11194
+ else L.push("Knowledge Impact Score: not available");
11195
+ L.push("");
11196
+ L.push("## Executive Summary", "");
11197
+ for (const s2 of r.executive_summary) L.push(`- ${s2}`);
11198
+ L.push("");
11199
+ L.push("## Knowledge Health", "");
11200
+ for (const [k, v] of Object.entries(r.knowledge_health)) L.push(`- ${k}: ${v}`);
11201
+ L.push("");
11202
+ L.push("## Knowledge Coverage", "");
11203
+ for (const c of r.knowledge_coverage) L.push(`- ${c.label}: ${ratio(c.have, c.total)}`);
11204
+ L.push("");
11205
+ L.push("## Ownership Coverage", "");
11206
+ L.push(`- Coverage: ${r.ownership_coverage.coverage_percent}%`);
11207
+ L.push(`- Work Items with code ownership: ${r.ownership_coverage.work_items_with_ownership}`);
11208
+ L.push(`- Owned code paths: ${r.ownership_coverage.owned_code_paths}`);
11209
+ L.push(`- Broad globs detected: ${r.ownership_coverage.broad_globs}`);
11210
+ L.push(`- Ownership overlaps: ${r.ownership_coverage.ownership_overlaps}`);
11211
+ L.push("");
11212
+ L.push("## Traceability", "");
11213
+ const t = r.traceability;
11214
+ L.push(`- Roadmap candidates: ${t.roadmap_candidates}`);
11215
+ L.push(`- Materialized Work Items: ${t.materialized_work_items}`);
11216
+ L.push(`- Remaining candidates: ${t.remaining_candidates}`);
11217
+ L.push(`- Completed Work Items: ${t.completed_work_items}`);
11218
+ L.push(`- Work Items connected to roadmap: ${t.work_items_connected_to_roadmap}`);
11219
+ L.push(`- Work Items connected to code: ${t.work_items_connected_to_code}`);
11220
+ L.push(`- Graph quality: ${t.graph_quality}`);
11221
+ if (t.graph_nodes !== null) L.push(`- Graph nodes: ${t.graph_nodes}`);
11222
+ if (t.graph_edges !== null) L.push(`- Graph edges: ${t.graph_edges}`);
11223
+ if (t.graph_hints !== null) L.push(`- Graph hints: ${t.graph_hints}`);
11224
+ L.push("");
11225
+ L.push("## Context Readiness", "");
11226
+ L.push(`Context Readiness: ${r.context_readiness.level}`);
11227
+ if (r.context_readiness.reasons.length > 0) {
11228
+ L.push("", "Reason:");
11229
+ for (const reason of r.context_readiness.reasons) L.push(`- ${reason}`);
11230
+ }
11231
+ L.push("");
11232
+ L.push("## Work Item Readiness", "");
11233
+ const w = r.work_item_readiness;
11234
+ L.push(`- Draft: ${w.draft}`);
11235
+ L.push(`- Ready: ${w.ready}`);
11236
+ L.push(`- In Progress: ${w.in_progress}`);
11237
+ L.push(`- Blocked: ${w.blocked}`);
11238
+ L.push(`- Completed: ${w.completed}`);
11239
+ L.push(`- Ready quality: ${w.ready_quality}`);
11240
+ L.push("");
11241
+ L.push("## Graph Quality", "");
11242
+ if (r.graph_quality.available) {
11243
+ const gq = r.graph_quality;
11244
+ L.push(`- Scope: ${gq.scope}`);
11245
+ L.push(`- Quality: ${gq.quality}`);
11246
+ L.push(`- Nodes: ${gq.nodes}`);
11247
+ L.push(`- Edges: ${gq.edges}`);
11248
+ L.push(`- Hints: ${gq.hints}`);
11249
+ if (gq.reason) L.push(`- Reason: ${gq.reason}`);
11250
+ if (gq.last_exported) L.push(`- Last exported: ${gq.last_exported}`);
11251
+ } else {
11252
+ L.push("- Graph data not available.");
11253
+ L.push(`- Tip: ${r.graph_quality.suggestion}`);
11254
+ }
11255
+ L.push("");
11256
+ L.push("## Guard Activity", "");
11257
+ L.push("- Guard history: not available");
11258
+ L.push(`- Note: ${r.guard_activity.note}`);
11259
+ L.push("");
11260
+ L.push("## Impact Signals", "");
11261
+ const s = r.impact_signals;
11262
+ L.push(`- Ambiguity reduction: ${s.ambiguity_reduction}`);
11263
+ L.push(`- Drift prevention: ${s.drift_prevention}`);
11264
+ L.push(`- Onboarding support: ${s.onboarding_support}`);
11265
+ L.push(`- Delivery traceability: ${s.delivery_traceability}`);
11266
+ L.push(`- AI context readiness: ${s.ai_context_readiness}`);
11267
+ L.push(`- Maintenance readiness: ${s.maintenance_readiness}`);
11268
+ L.push("");
11269
+ L.push("## Actionable Gaps", "");
11270
+ const gp = r.actionable_gaps;
11271
+ const gapSection = (title, items) => {
11272
+ if (items.length === 0) return;
11273
+ L.push(`### ${title}`, "");
11274
+ for (const it of items) {
11275
+ L.push(`- ${it.id} \u2014 ${it.title}`);
11276
+ L.push(` - Path: ${it.path}`);
11277
+ L.push(` - Suggested action: ${it.suggested_action}`);
11278
+ }
11279
+ L.push("");
11280
+ };
11281
+ gapSection("Work Items missing source", gp.missing_source);
11282
+ gapSection("Work Items missing initiative", gp.missing_initiative);
11283
+ gapSection("Work Items missing code ownership", gp.missing_code_ownership);
11284
+ gapSection("Work Items missing knowledge level", gp.missing_knowledge_level);
11285
+ gapSection("Work Items missing acceptance criteria", gp.missing_acceptance_criteria);
11286
+ gapSection("Work Items missing Definition of Done", gp.missing_definition_of_done);
11287
+ gapSection("Work Items missing validation", gp.missing_validation);
11288
+ if (gp.broad_ownership_globs.length > 0) {
11289
+ L.push("### Broad ownership globs", "");
11290
+ for (const b of gp.broad_ownership_globs) {
11291
+ L.push(`- ${b.id} \u2014 ${b.title}`);
11292
+ L.push(` - Glob: \`${b.glob}\``);
11293
+ L.push(` - Suggested action: ${b.suggested_action}`);
11294
+ }
11295
+ L.push("");
11296
+ }
11297
+ if (gp.ownership_overlaps.length > 0) {
11298
+ L.push("### Ownership overlaps", "");
11299
+ for (const o of gp.ownership_overlaps) {
11300
+ L.push(`- \`${o.code_path}\``);
11301
+ L.push(` - Owned by: ${o.work_items.join(", ")}`);
11302
+ L.push(` - Suggested action: ${o.suggested_action}`);
11303
+ }
11304
+ L.push("");
11305
+ }
11306
+ 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;
11307
+ if (anyGaps === 0) {
11308
+ L.push("No actionable knowledge gaps detected. \u{1F389}", "");
11309
+ }
11310
+ if (r.score_breakdown) {
11311
+ L.push("## Score Breakdown", "");
11312
+ const b = r.score_breakdown;
11313
+ L.push(`- Knowledge Health: ${b.knowledge_health.points}/${b.knowledge_health.max}`);
11314
+ L.push(`- Knowledge Coverage: ${b.knowledge_coverage.points}/${b.knowledge_coverage.max}`);
11315
+ L.push(`- Ownership Coverage: ${b.ownership_coverage.points}/${b.ownership_coverage.max}`);
11316
+ L.push(`- Traceability: ${b.traceability.points}/${b.traceability.max}`);
11317
+ L.push(`- Graph Quality: ${b.graph_quality.points}/${b.graph_quality.max}`);
11318
+ L.push(`- Context Readiness: ${b.context_readiness.points}/${b.context_readiness.max}`);
11319
+ L.push("");
11320
+ }
11321
+ L.push("## Suggested Actions", "");
11322
+ r.suggested_actions.forEach((a, i) => L.push(`${i + 1}. ${a}`));
11323
+ L.push("");
11324
+ return L.join("\n");
11325
+ }
11326
+ function serializeImpactJson(r) {
11327
+ return JSON.stringify(r, null, 2) + "\n";
11328
+ }
11329
+
11330
+ // src/commands/report.ts
11331
+ function runReportImpact(opts = {}) {
11332
+ const dir = cwd();
11333
+ requireConfig(dir);
11334
+ const report = buildImpactReport(dir);
11335
+ const content = opts.json ? serializeImpactJson(report) : renderImpactMarkdown(report);
11336
+ if (opts.output) {
11337
+ intro2("kaddo report impact");
11338
+ const rel = opts.output;
11339
+ writeFile(join(dir, rel), content);
11340
+ log2.success(`Wrote ${rel.replace(/\\/g, "/")}`);
11341
+ printCommandFooter("report impact");
11342
+ outro2("Impact report written.");
11343
+ return;
11344
+ }
11345
+ console.log(content);
11346
+ }
11347
+
10915
11348
  // src/index.ts
10916
11349
  var require2 = createRequire(import.meta.url);
10917
11350
  var { version } = require2("../package.json");
@@ -10940,6 +11373,13 @@ var graphCmd = program.command("graph").description("Export the lightweight, fil
10940
11373
  graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
10941
11374
  runGraphExport(opts);
10942
11375
  });
11376
+ var reportCmd = program.command("report").description("Generate Kaddo reports");
11377
+ reportCmd.command("impact").description("Knowledge Impact Report: knowledge health, coverage, traceability, readiness (deterministic, no LLM)").option("--json", "Output JSON instead of Markdown").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/impact-report.md)").action((opts) => {
11378
+ runReportImpact(opts);
11379
+ });
11380
+ program.command("impact").description("Alias for `kaddo report impact`").option("--json", "Output JSON instead of Markdown").option("--output <path>", "Write the report to a file").action((opts) => {
11381
+ runReportImpact(opts);
11382
+ });
10943
11383
  program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").option("--include-archived", "Include archived Work Items in ownership matching (excluded by default)").action(async (opts) => {
10944
11384
  await runGuard(opts);
10945
11385
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.22.1",
3
+ "version": "3.23.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {