@kaddo/cli 3.23.1 → 3.24.0
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 +2 -0
- package/dist/index.js +286 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -512,6 +512,8 @@ create --from roadmap → owners → guard → explain`.
|
|
|
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
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 |
|
|
515
|
+
| v3.23.2 | Impact report defaults to `all` scope (accumulated impact), graph built in memory; `--scope active`; `scope_source`/`default_scope` in JSON |
|
|
516
|
+
| v3.24 | Estimated Savings Model: `kaddo savings` (+ `savings init`, `.kaddo/savings.yml`); evidence-based time/value estimates, confidence; MCP resource + tool |
|
|
515
517
|
|
|
516
518
|
**Optional modules (installed with `kaddo add`):**
|
|
517
519
|
|
package/dist/index.js
CHANGED
|
@@ -688,6 +688,10 @@ var COMMAND_HELP = {
|
|
|
688
688
|
"report impact": {
|
|
689
689
|
question: "What impact is Kaddo having on this project?",
|
|
690
690
|
next: "Act on the Suggested Actions section"
|
|
691
|
+
},
|
|
692
|
+
savings: {
|
|
693
|
+
question: "How much time/value might Kaddo be saving (estimate)?",
|
|
694
|
+
next: "Run `kaddo savings init` to calibrate assumptions"
|
|
691
695
|
}
|
|
692
696
|
};
|
|
693
697
|
function commandFooterLines(name) {
|
|
@@ -10933,6 +10937,8 @@ function hasSection(body, re) {
|
|
|
10933
10937
|
return body.split(/\r?\n/).some((l) => /^#{1,6}\s+/.test(l) && re.test(l));
|
|
10934
10938
|
}
|
|
10935
10939
|
function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
10940
|
+
const resolvedScope = opts.scope ?? "all";
|
|
10941
|
+
const scopeSource = opts.scopeSource ?? (opts.scope ? "explicit" : "default");
|
|
10936
10942
|
const exp = buildProjectExplanation(dir);
|
|
10937
10943
|
const wis = discoverWorkItems(dir);
|
|
10938
10944
|
const total = wis.length;
|
|
@@ -11003,19 +11009,13 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
11003
11009
|
}
|
|
11004
11010
|
const ownershipOverlaps = gaps.ownership_overlaps.length;
|
|
11005
11011
|
let g;
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
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 };
|
|
11012
|
+
const config = loadConfig(dir);
|
|
11013
|
+
if (config) {
|
|
11014
|
+
const graph = buildGraph(dir, config, { scope: resolvedScope }, now);
|
|
11015
|
+
const hints = buildGraphHints(dir, graph, now);
|
|
11016
|
+
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 };
|
|
11017
11017
|
} else {
|
|
11018
|
-
g = { available: false, scope:
|
|
11018
|
+
g = { available: false, scope: resolvedScope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
11019
11019
|
}
|
|
11020
11020
|
const layerStatus = (name) => exp.layers.find((l) => l.layer === name)?.status ?? "Missing";
|
|
11021
11021
|
const byState = exp.workItems.byState;
|
|
@@ -11113,7 +11113,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
11113
11113
|
};
|
|
11114
11114
|
const actions = [];
|
|
11115
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
|
|
11116
|
+
else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo impact --scope all` to measure accumulated knowledge impact.");
|
|
11117
11117
|
if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
|
|
11118
11118
|
for (const a of [
|
|
11119
11119
|
groupedAction(gaps.missing_code_ownership, "Add `code:` ownership to"),
|
|
@@ -11138,6 +11138,8 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
11138
11138
|
generated_at: now.toISOString(),
|
|
11139
11139
|
project: exp.project.name,
|
|
11140
11140
|
scope: g.scope,
|
|
11141
|
+
default_scope: "all",
|
|
11142
|
+
scope_source: scopeSource,
|
|
11141
11143
|
executive_summary: summary,
|
|
11142
11144
|
knowledge_health,
|
|
11143
11145
|
knowledge_coverage,
|
|
@@ -11192,6 +11194,11 @@ function renderImpactMarkdown(r) {
|
|
|
11192
11194
|
L.push(`Scope: ${r.scope}`);
|
|
11193
11195
|
if (r.score !== null) L.push(`Knowledge Impact Score: ${r.score}/100`);
|
|
11194
11196
|
else L.push("Knowledge Impact Score: not available");
|
|
11197
|
+
if (r.scope_source === "default") {
|
|
11198
|
+
L.push("Scope note: Impact reports use `all` by default to measure accumulated knowledge impact.");
|
|
11199
|
+
} else if (r.scope === "active" && r.graph_quality.available === false) {
|
|
11200
|
+
L.push("Tip: Run `kaddo impact --scope all` to inspect accumulated knowledge impact.");
|
|
11201
|
+
}
|
|
11195
11202
|
L.push("");
|
|
11196
11203
|
L.push("## Executive Summary", "");
|
|
11197
11204
|
for (const s2 of r.executive_summary) L.push(`- ${s2}`);
|
|
@@ -11248,6 +11255,9 @@ function renderImpactMarkdown(r) {
|
|
|
11248
11255
|
L.push(`- Hints: ${gq.hints}`);
|
|
11249
11256
|
if (gq.reason) L.push(`- Reason: ${gq.reason}`);
|
|
11250
11257
|
if (gq.last_exported) L.push(`- Last exported: ${gq.last_exported}`);
|
|
11258
|
+
if (gq.scope === "active" && gq.quality === "empty") {
|
|
11259
|
+
L.push("- Tip: Run `kaddo impact --scope all` to inspect accumulated knowledge impact.");
|
|
11260
|
+
}
|
|
11251
11261
|
} else {
|
|
11252
11262
|
L.push("- Graph data not available.");
|
|
11253
11263
|
L.push(`- Tip: ${r.graph_quality.suggestion}`);
|
|
@@ -11331,7 +11341,8 @@ function serializeImpactJson(r) {
|
|
|
11331
11341
|
function runReportImpact(opts = {}) {
|
|
11332
11342
|
const dir = cwd();
|
|
11333
11343
|
requireConfig(dir);
|
|
11334
|
-
const
|
|
11344
|
+
const scope = opts.scope === "active" ? "active" : opts.scope === "all" ? "all" : void 0;
|
|
11345
|
+
const report = buildImpactReport(dir, { scope });
|
|
11335
11346
|
const content = opts.json ? serializeImpactJson(report) : renderImpactMarkdown(report);
|
|
11336
11347
|
if (opts.output) {
|
|
11337
11348
|
intro2("kaddo report impact");
|
|
@@ -11345,6 +11356,256 @@ function runReportImpact(opts = {}) {
|
|
|
11345
11356
|
console.log(content);
|
|
11346
11357
|
}
|
|
11347
11358
|
|
|
11359
|
+
// src/core/savings.ts
|
|
11360
|
+
import { parse as parseYaml15 } from "yaml";
|
|
11361
|
+
var DEFAULT_ASSUMPTIONS = {
|
|
11362
|
+
currency: "USD",
|
|
11363
|
+
hourly_cost: 40,
|
|
11364
|
+
context_preparation_minutes_saved_per_work_item: 30,
|
|
11365
|
+
rework_hours_avoided_per_resolved_drift: 2,
|
|
11366
|
+
onboarding_hours_saved_per_new_contributor: 4,
|
|
11367
|
+
review_minutes_saved_per_work_item_with_ownership: 20,
|
|
11368
|
+
clarification_minutes_saved_per_ready_work_item: 25,
|
|
11369
|
+
architecture_discovery_hours_saved_when_graph_good: 3,
|
|
11370
|
+
expected_new_contributors_per_month: 1,
|
|
11371
|
+
expected_work_items_per_month: 8
|
|
11372
|
+
};
|
|
11373
|
+
var SAVINGS_PATH = ".kaddo/savings.yml";
|
|
11374
|
+
function loadAssumptions(dir) {
|
|
11375
|
+
const p2 = join(dir, SAVINGS_PATH);
|
|
11376
|
+
if (!exists(p2)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
|
|
11377
|
+
try {
|
|
11378
|
+
const raw = parseYaml15(readFile(p2));
|
|
11379
|
+
const a = raw?.assumptions ?? {};
|
|
11380
|
+
const t = raw?.team ?? {};
|
|
11381
|
+
const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
11382
|
+
return {
|
|
11383
|
+
source: "file",
|
|
11384
|
+
assumptions: {
|
|
11385
|
+
currency: raw?.currency ?? DEFAULT_ASSUMPTIONS.currency,
|
|
11386
|
+
hourly_cost: num(raw?.hourly_cost, DEFAULT_ASSUMPTIONS.hourly_cost),
|
|
11387
|
+
context_preparation_minutes_saved_per_work_item: num(a.context_preparation_minutes_saved_per_work_item, DEFAULT_ASSUMPTIONS.context_preparation_minutes_saved_per_work_item),
|
|
11388
|
+
rework_hours_avoided_per_resolved_drift: num(a.rework_hours_avoided_per_resolved_drift, DEFAULT_ASSUMPTIONS.rework_hours_avoided_per_resolved_drift),
|
|
11389
|
+
onboarding_hours_saved_per_new_contributor: num(a.onboarding_hours_saved_per_new_contributor, DEFAULT_ASSUMPTIONS.onboarding_hours_saved_per_new_contributor),
|
|
11390
|
+
review_minutes_saved_per_work_item_with_ownership: num(a.review_minutes_saved_per_work_item_with_ownership, DEFAULT_ASSUMPTIONS.review_minutes_saved_per_work_item_with_ownership),
|
|
11391
|
+
clarification_minutes_saved_per_ready_work_item: num(a.clarification_minutes_saved_per_ready_work_item, DEFAULT_ASSUMPTIONS.clarification_minutes_saved_per_ready_work_item),
|
|
11392
|
+
architecture_discovery_hours_saved_when_graph_good: num(a.architecture_discovery_hours_saved_when_graph_good, DEFAULT_ASSUMPTIONS.architecture_discovery_hours_saved_when_graph_good),
|
|
11393
|
+
expected_new_contributors_per_month: num(t.expected_new_contributors_per_month, DEFAULT_ASSUMPTIONS.expected_new_contributors_per_month),
|
|
11394
|
+
expected_work_items_per_month: num(t.expected_work_items_per_month, DEFAULT_ASSUMPTIONS.expected_work_items_per_month)
|
|
11395
|
+
}
|
|
11396
|
+
};
|
|
11397
|
+
} catch {
|
|
11398
|
+
return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
|
|
11399
|
+
}
|
|
11400
|
+
}
|
|
11401
|
+
function savingsTemplate() {
|
|
11402
|
+
return [
|
|
11403
|
+
"# Kaddo savings assumptions (VS-062). Estimates are evidence-based, not exact ROI.",
|
|
11404
|
+
"# Edit these to match your team, then run `kaddo savings`.",
|
|
11405
|
+
"currency: USD",
|
|
11406
|
+
"",
|
|
11407
|
+
"hourly_cost: 40",
|
|
11408
|
+
"",
|
|
11409
|
+
"assumptions:",
|
|
11410
|
+
" context_preparation_minutes_saved_per_work_item: 30",
|
|
11411
|
+
" rework_hours_avoided_per_resolved_drift: 2",
|
|
11412
|
+
" onboarding_hours_saved_per_new_contributor: 4",
|
|
11413
|
+
" review_minutes_saved_per_work_item_with_ownership: 20",
|
|
11414
|
+
" clarification_minutes_saved_per_ready_work_item: 25",
|
|
11415
|
+
" architecture_discovery_hours_saved_when_graph_good: 3",
|
|
11416
|
+
"",
|
|
11417
|
+
"team:",
|
|
11418
|
+
" expected_new_contributors_per_month: 1",
|
|
11419
|
+
" expected_work_items_per_month: 8",
|
|
11420
|
+
""
|
|
11421
|
+
].join("\n");
|
|
11422
|
+
}
|
|
11423
|
+
var READINESS_MULT = { Low: 0.25, Medium: 0.5, High: 0.75, "Very High": 1 };
|
|
11424
|
+
var GRAPH_MULT = { empty: 0, sparse: 0.25, partial: 0.6, good: 1, unknown: 0 };
|
|
11425
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
11426
|
+
function parseRatioNumerator(s) {
|
|
11427
|
+
const m = s.match(/^(\d+)/);
|
|
11428
|
+
return m ? Number(m[1]) : 0;
|
|
11429
|
+
}
|
|
11430
|
+
var SAVINGS_DISCLAIMER = "These are evidence-based estimates, not exact ROI. Adjust assumptions with real team data.";
|
|
11431
|
+
function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
11432
|
+
const { assumptions: a, source } = loadAssumptions(dir);
|
|
11433
|
+
const impact = buildImpactReport(dir, { scope: opts.scope, scopeSource: opts.scopeSource }, now);
|
|
11434
|
+
const completed = impact.traceability.completed_work_items;
|
|
11435
|
+
const wiWithOwnership = parseRatioNumerator(impact.traceability.work_items_connected_to_code);
|
|
11436
|
+
const acceptanceCov = impact.knowledge_coverage.find((c) => c.label.toLowerCase().includes("acceptance"));
|
|
11437
|
+
const wiWithAcceptance = acceptanceCov?.have ?? 0;
|
|
11438
|
+
const graphQuality = impact.graph_quality.available ? impact.graph_quality.quality : "unknown";
|
|
11439
|
+
const readiness = impact.context_readiness.level;
|
|
11440
|
+
const ctxPrepH = round2(completed * a.context_preparation_minutes_saved_per_work_item / 60);
|
|
11441
|
+
const reviewH = round2(wiWithOwnership * a.review_minutes_saved_per_work_item_with_ownership / 60);
|
|
11442
|
+
const clarH = round2(wiWithAcceptance * a.clarification_minutes_saved_per_ready_work_item / 60);
|
|
11443
|
+
const readinessMult = READINESS_MULT[readiness] ?? 0.5;
|
|
11444
|
+
const onboardH = round2(a.expected_new_contributors_per_month * a.onboarding_hours_saved_per_new_contributor * readinessMult);
|
|
11445
|
+
const graphMult = GRAPH_MULT[graphQuality] ?? 0;
|
|
11446
|
+
const archH = round2(a.architecture_discovery_hours_saved_when_graph_good * graphMult);
|
|
11447
|
+
const totalHours = round2(ctxPrepH + reviewH + clarH + onboardH + archH);
|
|
11448
|
+
const totalValue = Math.round(totalHours * a.hourly_cost);
|
|
11449
|
+
const reasons = [];
|
|
11450
|
+
let level;
|
|
11451
|
+
if (impact.score === null || impact.score < 60 || graphQuality === "empty") {
|
|
11452
|
+
level = "Low";
|
|
11453
|
+
reasons.push("Impact score is low or the knowledge graph is empty.");
|
|
11454
|
+
} else {
|
|
11455
|
+
level = "Medium";
|
|
11456
|
+
reasons.push("Strong knowledge and graph evidence.");
|
|
11457
|
+
}
|
|
11458
|
+
reasons.push("Guard history is not persisted yet \u2014 avoided rework is not estimated.");
|
|
11459
|
+
reasons.push(source === "file" ? "Assumptions come from `.kaddo/savings.yml`." : "Savings assumptions are defaults and should be calibrated.");
|
|
11460
|
+
const actions = [];
|
|
11461
|
+
if (source === "default") actions.push("Run `kaddo savings init` to customize assumptions.");
|
|
11462
|
+
actions.push("Calibrate hourly cost and team assumptions with real team data.");
|
|
11463
|
+
actions.push("Run `kaddo impact` to verify the underlying evidence quality.");
|
|
11464
|
+
actions.push("Use this report as directional evidence, not accounting data.");
|
|
11465
|
+
actions.push("Future: persist Guard history to estimate avoided rework.");
|
|
11466
|
+
return {
|
|
11467
|
+
generated_at: now.toISOString(),
|
|
11468
|
+
project: impact.project,
|
|
11469
|
+
scope: impact.scope,
|
|
11470
|
+
currency: a.currency,
|
|
11471
|
+
disclaimer: SAVINGS_DISCLAIMER,
|
|
11472
|
+
assumptions_source: source,
|
|
11473
|
+
assumptions: {
|
|
11474
|
+
hourly_cost: a.hourly_cost,
|
|
11475
|
+
context_preparation_minutes_saved_per_work_item: a.context_preparation_minutes_saved_per_work_item,
|
|
11476
|
+
review_minutes_saved_per_work_item_with_ownership: a.review_minutes_saved_per_work_item_with_ownership,
|
|
11477
|
+
clarification_minutes_saved_per_ready_work_item: a.clarification_minutes_saved_per_ready_work_item,
|
|
11478
|
+
onboarding_hours_saved_per_new_contributor: a.onboarding_hours_saved_per_new_contributor,
|
|
11479
|
+
expected_new_contributors_per_month: a.expected_new_contributors_per_month,
|
|
11480
|
+
architecture_discovery_hours_saved_when_graph_good: a.architecture_discovery_hours_saved_when_graph_good
|
|
11481
|
+
},
|
|
11482
|
+
evidence: {
|
|
11483
|
+
knowledge_impact_score: impact.score,
|
|
11484
|
+
completed_work_items: completed,
|
|
11485
|
+
ownership_coverage_percent: impact.ownership_coverage.coverage_percent,
|
|
11486
|
+
work_items_with_acceptance_criteria: acceptanceCov ? `${acceptanceCov.have}/${acceptanceCov.total}` : "0/0",
|
|
11487
|
+
graph_quality: graphQuality,
|
|
11488
|
+
graph_nodes: impact.graph_quality.available ? impact.graph_quality.nodes : null,
|
|
11489
|
+
graph_edges: impact.graph_quality.available ? impact.graph_quality.edges : null,
|
|
11490
|
+
context_readiness: readiness,
|
|
11491
|
+
guard_history_available: false
|
|
11492
|
+
},
|
|
11493
|
+
estimated_savings: {
|
|
11494
|
+
context_preparation: { hours: ctxPrepH, formula: `${completed} Work Items \xD7 ${a.context_preparation_minutes_saved_per_work_item} min` },
|
|
11495
|
+
review_effort: { hours: reviewH, formula: `${wiWithOwnership} Work Items with ownership \xD7 ${a.review_minutes_saved_per_work_item_with_ownership} min` },
|
|
11496
|
+
clarification_reduction: { hours: clarH, formula: `${wiWithAcceptance} Work Items with Acceptance Criteria \xD7 ${a.clarification_minutes_saved_per_ready_work_item} min` },
|
|
11497
|
+
onboarding: { hours: onboardH, formula: `${a.expected_new_contributors_per_month} contributor \xD7 ${a.onboarding_hours_saved_per_new_contributor} h \xD7 ${readinessMult.toFixed(2)} readiness multiplier` },
|
|
11498
|
+
architecture_discovery: { hours: archH, formula: `${a.architecture_discovery_hours_saved_when_graph_good} h \xD7 ${graphMult.toFixed(2)} graph multiplier` },
|
|
11499
|
+
drift_prevention: { available: false, hours: 0, reason: "Guard history is not persisted yet." }
|
|
11500
|
+
},
|
|
11501
|
+
total: { estimated_hours_saved: totalHours, estimated_value: totalValue, currency: a.currency },
|
|
11502
|
+
confidence: { level, reasons },
|
|
11503
|
+
suggested_actions: actions
|
|
11504
|
+
};
|
|
11505
|
+
}
|
|
11506
|
+
function renderSavingsMarkdown(r) {
|
|
11507
|
+
const L = [];
|
|
11508
|
+
L.push("# Kaddo Estimated Savings Report", "");
|
|
11509
|
+
L.push(`Generated at: ${r.generated_at}`);
|
|
11510
|
+
L.push(`Project: ${r.project}`);
|
|
11511
|
+
L.push(`Scope: ${r.scope}`);
|
|
11512
|
+
L.push(`Currency: ${r.currency}`);
|
|
11513
|
+
L.push("");
|
|
11514
|
+
L.push("## Disclaimer", "");
|
|
11515
|
+
L.push(r.disclaimer, "");
|
|
11516
|
+
L.push("## Executive Summary", "");
|
|
11517
|
+
L.push(`- Estimated time saved: ${r.total.estimated_hours_saved} hours`);
|
|
11518
|
+
L.push(`- Estimated value: ${r.total.estimated_value} ${r.currency}`);
|
|
11519
|
+
L.push(`- Confidence: ${r.confidence.level}`);
|
|
11520
|
+
if (r.assumptions_source === "default") L.push("- Using default assumptions. Run `kaddo savings init` to customize them.");
|
|
11521
|
+
L.push("");
|
|
11522
|
+
L.push("## Assumptions", "");
|
|
11523
|
+
L.push(`- Hourly cost: ${r.assumptions.hourly_cost} ${r.currency}`);
|
|
11524
|
+
L.push(`- Context preparation saved per Work Item: ${r.assumptions.context_preparation_minutes_saved_per_work_item} min`);
|
|
11525
|
+
L.push(`- Review saved per Work Item with ownership: ${r.assumptions.review_minutes_saved_per_work_item_with_ownership} min`);
|
|
11526
|
+
L.push(`- Clarification saved per Work Item with acceptance criteria: ${r.assumptions.clarification_minutes_saved_per_ready_work_item} min`);
|
|
11527
|
+
L.push(`- Onboarding saved per new contributor: ${r.assumptions.onboarding_hours_saved_per_new_contributor} h`);
|
|
11528
|
+
L.push(`- Expected new contributors per month: ${r.assumptions.expected_new_contributors_per_month}`);
|
|
11529
|
+
L.push(`- Architecture discovery saved when graph is good: ${r.assumptions.architecture_discovery_hours_saved_when_graph_good} h`);
|
|
11530
|
+
L.push("");
|
|
11531
|
+
L.push("## Evidence Used", "");
|
|
11532
|
+
const e = r.evidence;
|
|
11533
|
+
L.push(`- Knowledge Impact Score: ${e.knowledge_impact_score ?? "not available"}${e.knowledge_impact_score !== null ? "/100" : ""}`);
|
|
11534
|
+
L.push(`- Completed Work Items: ${e.completed_work_items}`);
|
|
11535
|
+
L.push(`- Ownership coverage: ${e.ownership_coverage_percent}%`);
|
|
11536
|
+
L.push(`- Work Items with Acceptance Criteria: ${e.work_items_with_acceptance_criteria}`);
|
|
11537
|
+
L.push(`- Graph quality: ${e.graph_quality}`);
|
|
11538
|
+
if (e.graph_nodes !== null) L.push(`- Graph nodes: ${e.graph_nodes}`);
|
|
11539
|
+
if (e.graph_edges !== null) L.push(`- Graph edges: ${e.graph_edges}`);
|
|
11540
|
+
L.push(`- Context readiness: ${e.context_readiness}`);
|
|
11541
|
+
L.push("- Guard history: not available");
|
|
11542
|
+
L.push("");
|
|
11543
|
+
L.push("## Estimated Savings", "");
|
|
11544
|
+
const s = r.estimated_savings;
|
|
11545
|
+
const line = (title, l) => {
|
|
11546
|
+
L.push(`### ${title}`, "");
|
|
11547
|
+
L.push(`- Formula: ${l.formula}`);
|
|
11548
|
+
L.push(`- Estimated: ${l.hours} h`, "");
|
|
11549
|
+
};
|
|
11550
|
+
line("Context Preparation", s.context_preparation);
|
|
11551
|
+
line("Review Effort", s.review_effort);
|
|
11552
|
+
line("Clarification Reduction", s.clarification_reduction);
|
|
11553
|
+
line("Onboarding", s.onboarding);
|
|
11554
|
+
line("Architecture Discovery", s.architecture_discovery);
|
|
11555
|
+
L.push("### Drift Prevention", "");
|
|
11556
|
+
L.push("- Not available yet.");
|
|
11557
|
+
L.push(`- Reason: ${s.drift_prevention.reason}`, "");
|
|
11558
|
+
L.push("## Total", "");
|
|
11559
|
+
L.push(`- Estimated time saved: ${r.total.estimated_hours_saved} h`);
|
|
11560
|
+
L.push(`- Estimated value: ${r.total.estimated_value} ${r.currency}`);
|
|
11561
|
+
L.push("");
|
|
11562
|
+
L.push("## Confidence", "");
|
|
11563
|
+
L.push(`Confidence: ${r.confidence.level}`, "", "Reason:");
|
|
11564
|
+
for (const reason of r.confidence.reasons) L.push(`- ${reason}`);
|
|
11565
|
+
L.push("");
|
|
11566
|
+
L.push("## Suggested Actions", "");
|
|
11567
|
+
r.suggested_actions.forEach((act, i) => L.push(`${i + 1}. ${act}`));
|
|
11568
|
+
L.push("");
|
|
11569
|
+
return L.join("\n");
|
|
11570
|
+
}
|
|
11571
|
+
function serializeSavingsJson(r) {
|
|
11572
|
+
return JSON.stringify(r, null, 2) + "\n";
|
|
11573
|
+
}
|
|
11574
|
+
|
|
11575
|
+
// src/commands/savings.ts
|
|
11576
|
+
function runSavings(opts = {}) {
|
|
11577
|
+
const dir = cwd();
|
|
11578
|
+
requireConfig(dir);
|
|
11579
|
+
const scope = opts.scope === "active" ? "active" : opts.scope === "all" ? "all" : void 0;
|
|
11580
|
+
const report = buildSavingsReport(dir, { scope });
|
|
11581
|
+
const content = opts.json ? serializeSavingsJson(report) : renderSavingsMarkdown(report);
|
|
11582
|
+
if (opts.output) {
|
|
11583
|
+
intro2("kaddo savings");
|
|
11584
|
+
writeFile(join(dir, opts.output), content);
|
|
11585
|
+
log2.success(`Wrote ${opts.output.replace(/\\/g, "/")}`);
|
|
11586
|
+
printCommandFooter("savings");
|
|
11587
|
+
outro2("Savings report written.");
|
|
11588
|
+
return;
|
|
11589
|
+
}
|
|
11590
|
+
console.log(content);
|
|
11591
|
+
}
|
|
11592
|
+
function runSavingsInit(opts = {}) {
|
|
11593
|
+
const dir = cwd();
|
|
11594
|
+
requireConfig(dir);
|
|
11595
|
+
intro2("kaddo savings init");
|
|
11596
|
+
const rel = ".kaddo/savings.yml";
|
|
11597
|
+
const full = join(dir, rel);
|
|
11598
|
+
if (exists(full) && !opts.force) {
|
|
11599
|
+
log2.warn(`${rel} already exists. Use --force to overwrite.`);
|
|
11600
|
+
outro2("Nothing changed.");
|
|
11601
|
+
return;
|
|
11602
|
+
}
|
|
11603
|
+
writeFile(full, savingsTemplate());
|
|
11604
|
+
log2.success(`Wrote ${rel}`);
|
|
11605
|
+
log2.info("Edit the assumptions, then run `kaddo savings`.");
|
|
11606
|
+
outro2("Savings assumptions ready.");
|
|
11607
|
+
}
|
|
11608
|
+
|
|
11348
11609
|
// src/index.ts
|
|
11349
11610
|
var require2 = createRequire(import.meta.url);
|
|
11350
11611
|
var { version } = require2("../package.json");
|
|
@@ -11374,12 +11635,21 @@ graphCmd.command("export").description("Write the knowledge graph to .kaddo/grap
|
|
|
11374
11635
|
runGraphExport(opts);
|
|
11375
11636
|
});
|
|
11376
11637
|
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) => {
|
|
11638
|
+
reportCmd.command("impact").description("Knowledge Impact Report: knowledge health, coverage, traceability, readiness (deterministic, no LLM)").option("--json", "Output JSON instead of Markdown").option("--scope <scope>", "Scope: all (default \u2014 accumulated impact) or active").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/impact-report.md)").action((opts) => {
|
|
11378
11639
|
runReportImpact(opts);
|
|
11379
11640
|
});
|
|
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) => {
|
|
11641
|
+
program.command("impact").description("Alias for `kaddo report impact`").option("--json", "Output JSON instead of Markdown").option("--scope <scope>", "Scope: all (default \u2014 accumulated impact) or active").option("--output <path>", "Write the report to a file").action((opts) => {
|
|
11381
11642
|
runReportImpact(opts);
|
|
11382
11643
|
});
|
|
11644
|
+
reportCmd.command("savings").description("Estimated Savings Report: evidence-based time/effort/value estimates (deterministic, no LLM)").option("--json", "Output JSON instead of Markdown").option("--scope <scope>", "Scope: all (default) or active").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/savings-report.md)").action((opts) => {
|
|
11645
|
+
runSavings(opts);
|
|
11646
|
+
});
|
|
11647
|
+
var savingsCmd = program.command("savings").description("Estimated Savings Report from impact evidence + configurable assumptions").option("--json", "Output JSON instead of Markdown").option("--scope <scope>", "Scope: all (default) or active").option("--output <path>", "Write the report to a file").action((opts) => {
|
|
11648
|
+
runSavings(opts);
|
|
11649
|
+
});
|
|
11650
|
+
savingsCmd.command("init").description("Create an editable `.kaddo/savings.yml` with savings assumptions").option("--force", "Overwrite an existing .kaddo/savings.yml").action((opts) => {
|
|
11651
|
+
runSavingsInit(opts);
|
|
11652
|
+
});
|
|
11383
11653
|
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) => {
|
|
11384
11654
|
await runGuard(opts);
|
|
11385
11655
|
});
|