@kaddo/cli 3.23.0 → 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 +1 -0
  2. package/dist/index.js +128 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -511,6 +511,7 @@ create --from roadmap → owners → guard → explain`.
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
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 |
514
515
 
515
516
  **Optional modules (installed with `kaddo add`):**
516
517
 
package/dist/index.js CHANGED
@@ -10918,7 +10918,11 @@ function runGraphExport(opts = {}) {
10918
10918
 
10919
10919
  // src/core/impact-report.ts
10920
10920
  import matter8 from "gray-matter";
10921
- var BROAD_GLOB = /^[^/*]+\/\*\*$/;
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
+ }
10922
10926
  function levelFromRatio(r) {
10923
10927
  if (r >= 0.9) return "Very High";
10924
10928
  if (r >= 0.7) return "High";
@@ -10938,26 +10942,66 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10938
10942
  let withLevel = 0;
10939
10943
  let withAcceptance = 0;
10940
10944
  let withDoD = 0;
10945
+ let withValidation = 0;
10941
10946
  let connectedRoadmap = 0;
10942
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
+ });
10943
10967
  for (const wi of wis) {
10968
+ const id = wi.id || wi.title;
10944
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`)."));
10945
10971
  const hasSource = Boolean(wi.sourceId) || wi.source === "roadmap";
10946
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."));
10947
10974
  if (hasSource || wi.initiative) connectedRoadmap++;
10948
10975
  if (wi.initiative) withInitiative++;
10976
+ else gaps.missing_initiative.push(gapItem(wi, "Add `initiative` to connect this Work Item to a delivery initiative."));
10949
10977
  if (wi.knowledgeLevel) withLevel++;
10950
- for (const g2 of wi.codeGlobs) globCounts.set(g2, (globCounts.get(g2) ?? 0) + 1);
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
+ }
10951
10986
  try {
10952
10987
  const body = matter8(readFile(wi.filePath)).content;
10953
- if (hasSection(body, /acceptance/i)) withAcceptance++;
10954
- if (hasSection(body, /definition of done|^#{1,6}\s*done\b/i)) withDoD++;
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."));
10955
10994
  } catch {
10956
10995
  }
10957
10996
  }
10958
10997
  const ownedCodePaths = [...globCounts.values()].reduce((a, b) => a + b, 0);
10959
- const broadGlobs = [...globCounts.keys()].filter((g2) => BROAD_GLOB.test(g2)).length;
10960
- const ownershipOverlaps = [...globCounts.values()].filter((n) => n > 1).length;
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;
10961
11005
  let g;
10962
11006
  if (opts.scope) {
10963
11007
  const config = loadConfig(dir);
@@ -11040,6 +11084,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
11040
11084
  maintenance_readiness: levelFromRatio((coverageRatio(withOwnership) + (deliveryTraceable ? 1 : 0)) / 2)
11041
11085
  };
11042
11086
  let score = null;
11087
+ let score_breakdown = null;
11043
11088
  if (total > 0) {
11044
11089
  const healthPts = [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * 20;
11045
11090
  const covAvg = knowledge_coverage.reduce((a, c) => a + coverageRatio(c.have), 0) / knowledge_coverage.length;
@@ -11051,13 +11096,34 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
11051
11096
  const graphPts = (g.available ? qualityMap[g.quality] ?? 0 : 0) * 15;
11052
11097
  const ctxPts = { Low: 0.25, Medium: 0.5, High: 0.8, "Very High": 1 }[ctxLevel] * 10;
11053
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
+ };
11054
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
+ };
11055
11114
  const actions = [];
11056
11115
  if (!g.available) actions.push("Run `kaddo graph export --scope all` to inspect full traceability.");
11057
11116
  else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo graph export --scope all` to include completed Work Items.");
11058
11117
  if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
11059
- if (total > 0 && withOwnership < total) actions.push("Add code ownership to Work Items without `code:` globs (`kaddo owners suggest`).");
11060
- if (total > 0 && withInitiative < total) actions.push("Add an initiative to Work Items missing one.");
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.`);
11061
11127
  if (g.available && g.hints > 0) actions.push("Review graph hints (`kaddo://graph-hints` or `.kaddo/graph-hints.md`).");
11062
11128
  if (skills.length === 0) actions.push("Install reusable skills with `kaddo add skills`.");
11063
11129
  actions.push("Run `kaddo guard` before committing to catch knowledge drift.");
@@ -11109,7 +11175,9 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
11109
11175
  note: "Guard history is not persisted. Run `kaddo guard` to check drift; future versions may store runs for trend analysis."
11110
11176
  },
11111
11177
  impact_signals,
11178
+ actionable_gaps: gaps,
11112
11179
  score,
11180
+ score_breakdown,
11113
11181
  suggested_actions: actions
11114
11182
  };
11115
11183
  }
@@ -11198,6 +11266,58 @@ function renderImpactMarkdown(r) {
11198
11266
  L.push(`- AI context readiness: ${s.ai_context_readiness}`);
11199
11267
  L.push(`- Maintenance readiness: ${s.maintenance_readiness}`);
11200
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
+ }
11201
11321
  L.push("## Suggested Actions", "");
11202
11322
  r.suggested_actions.forEach((a, i) => L.push(`${i + 1}. ${a}`));
11203
11323
  L.push("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.23.0",
3
+ "version": "3.23.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {