@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.
- package/README.md +1 -0
- package/dist/index.js +128 -8
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
10960
|
-
const
|
|
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
|
-
|
|
11060
|
-
|
|
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("");
|