@kaddo/mcp 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/dist/index.js +262 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2102,6 +2102,8 @@ function hasSection(body, re) {
|
|
|
2102
2102
|
return body.split(/\r?\n/).some((l) => /^#{1,6}\s+/.test(l) && re.test(l));
|
|
2103
2103
|
}
|
|
2104
2104
|
function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
2105
|
+
const resolvedScope = opts.scope ?? "all";
|
|
2106
|
+
const scopeSource = opts.scopeSource ?? (opts.scope ? "explicit" : "default");
|
|
2105
2107
|
const exp = buildProjectExplanation(dir);
|
|
2106
2108
|
const wis = discoverWorkItems(dir);
|
|
2107
2109
|
const total = wis.length;
|
|
@@ -2172,19 +2174,13 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2172
2174
|
}
|
|
2173
2175
|
const ownershipOverlaps = gaps.ownership_overlaps.length;
|
|
2174
2176
|
let g;
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
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 };
|
|
2181
|
-
} else {
|
|
2182
|
-
g = { available: false, scope: opts.scope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
2183
|
-
}
|
|
2184
|
-
} else if (exp.graph) {
|
|
2185
|
-
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 };
|
|
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 };
|
|
2186
2182
|
} else {
|
|
2187
|
-
g = { available: false, scope:
|
|
2183
|
+
g = { available: false, scope: resolvedScope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
2188
2184
|
}
|
|
2189
2185
|
const layerStatus = (name) => exp.layers.find((l) => l.layer === name)?.status ?? "Missing";
|
|
2190
2186
|
const byState = exp.workItems.byState;
|
|
@@ -2282,7 +2278,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2282
2278
|
};
|
|
2283
2279
|
const actions = [];
|
|
2284
2280
|
if (!g.available) actions.push("Run `kaddo graph export --scope all` to inspect full traceability.");
|
|
2285
|
-
else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo
|
|
2281
|
+
else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo impact --scope all` to measure accumulated knowledge impact.");
|
|
2286
2282
|
if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
|
|
2287
2283
|
for (const a of [
|
|
2288
2284
|
groupedAction(gaps.missing_code_ownership, "Add `code:` ownership to"),
|
|
@@ -2307,6 +2303,8 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2307
2303
|
generated_at: now.toISOString(),
|
|
2308
2304
|
project: exp.project.name,
|
|
2309
2305
|
scope: g.scope,
|
|
2306
|
+
default_scope: "all",
|
|
2307
|
+
scope_source: scopeSource,
|
|
2310
2308
|
executive_summary: summary,
|
|
2311
2309
|
knowledge_health,
|
|
2312
2310
|
knowledge_coverage,
|
|
@@ -2361,6 +2359,11 @@ function renderImpactMarkdown(r) {
|
|
|
2361
2359
|
L.push(`Scope: ${r.scope}`);
|
|
2362
2360
|
if (r.score !== null) L.push(`Knowledge Impact Score: ${r.score}/100`);
|
|
2363
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
|
+
}
|
|
2364
2367
|
L.push("");
|
|
2365
2368
|
L.push("## Executive Summary", "");
|
|
2366
2369
|
for (const s2 of r.executive_summary) L.push(`- ${s2}`);
|
|
@@ -2417,6 +2420,9 @@ function renderImpactMarkdown(r) {
|
|
|
2417
2420
|
L.push(`- Hints: ${gq.hints}`);
|
|
2418
2421
|
if (gq.reason) L.push(`- Reason: ${gq.reason}`);
|
|
2419
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
|
+
}
|
|
2420
2426
|
} else {
|
|
2421
2427
|
L.push("- Graph data not available.");
|
|
2422
2428
|
L.push(`- Tip: ${r.graph_quality.suggestion}`);
|
|
@@ -2496,6 +2502,200 @@ function serializeImpactJson(r) {
|
|
|
2496
2502
|
return JSON.stringify(r, null, 2) + "\n";
|
|
2497
2503
|
}
|
|
2498
2504
|
|
|
2505
|
+
// ../cli/src/core/savings.ts
|
|
2506
|
+
import { parse as parseYaml6 } from "yaml";
|
|
2507
|
+
var DEFAULT_ASSUMPTIONS = {
|
|
2508
|
+
currency: "USD",
|
|
2509
|
+
hourly_cost: 40,
|
|
2510
|
+
context_preparation_minutes_saved_per_work_item: 30,
|
|
2511
|
+
rework_hours_avoided_per_resolved_drift: 2,
|
|
2512
|
+
onboarding_hours_saved_per_new_contributor: 4,
|
|
2513
|
+
review_minutes_saved_per_work_item_with_ownership: 20,
|
|
2514
|
+
clarification_minutes_saved_per_ready_work_item: 25,
|
|
2515
|
+
architecture_discovery_hours_saved_when_graph_good: 3,
|
|
2516
|
+
expected_new_contributors_per_month: 1,
|
|
2517
|
+
expected_work_items_per_month: 8
|
|
2518
|
+
};
|
|
2519
|
+
var SAVINGS_PATH = ".kaddo/savings.yml";
|
|
2520
|
+
function loadAssumptions(dir) {
|
|
2521
|
+
const p = join(dir, SAVINGS_PATH);
|
|
2522
|
+
if (!exists(p)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
|
|
2523
|
+
try {
|
|
2524
|
+
const raw = parseYaml6(readFile(p));
|
|
2525
|
+
const a = raw?.assumptions ?? {};
|
|
2526
|
+
const t = raw?.team ?? {};
|
|
2527
|
+
const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
2528
|
+
return {
|
|
2529
|
+
source: "file",
|
|
2530
|
+
assumptions: {
|
|
2531
|
+
currency: raw?.currency ?? DEFAULT_ASSUMPTIONS.currency,
|
|
2532
|
+
hourly_cost: num(raw?.hourly_cost, DEFAULT_ASSUMPTIONS.hourly_cost),
|
|
2533
|
+
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),
|
|
2534
|
+
rework_hours_avoided_per_resolved_drift: num(a.rework_hours_avoided_per_resolved_drift, DEFAULT_ASSUMPTIONS.rework_hours_avoided_per_resolved_drift),
|
|
2535
|
+
onboarding_hours_saved_per_new_contributor: num(a.onboarding_hours_saved_per_new_contributor, DEFAULT_ASSUMPTIONS.onboarding_hours_saved_per_new_contributor),
|
|
2536
|
+
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),
|
|
2537
|
+
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),
|
|
2538
|
+
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),
|
|
2539
|
+
expected_new_contributors_per_month: num(t.expected_new_contributors_per_month, DEFAULT_ASSUMPTIONS.expected_new_contributors_per_month),
|
|
2540
|
+
expected_work_items_per_month: num(t.expected_work_items_per_month, DEFAULT_ASSUMPTIONS.expected_work_items_per_month)
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
} catch {
|
|
2544
|
+
return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
var READINESS_MULT = { Low: 0.25, Medium: 0.5, High: 0.75, "Very High": 1 };
|
|
2548
|
+
var GRAPH_MULT = { empty: 0, sparse: 0.25, partial: 0.6, good: 1, unknown: 0 };
|
|
2549
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
2550
|
+
function parseRatioNumerator(s) {
|
|
2551
|
+
const m = s.match(/^(\d+)/);
|
|
2552
|
+
return m ? Number(m[1]) : 0;
|
|
2553
|
+
}
|
|
2554
|
+
var SAVINGS_DISCLAIMER = "These are evidence-based estimates, not exact ROI. Adjust assumptions with real team data.";
|
|
2555
|
+
function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
2556
|
+
const { assumptions: a, source } = loadAssumptions(dir);
|
|
2557
|
+
const impact = buildImpactReport(dir, { scope: opts.scope, scopeSource: opts.scopeSource }, now);
|
|
2558
|
+
const completed = impact.traceability.completed_work_items;
|
|
2559
|
+
const wiWithOwnership = parseRatioNumerator(impact.traceability.work_items_connected_to_code);
|
|
2560
|
+
const acceptanceCov = impact.knowledge_coverage.find((c) => c.label.toLowerCase().includes("acceptance"));
|
|
2561
|
+
const wiWithAcceptance = acceptanceCov?.have ?? 0;
|
|
2562
|
+
const graphQuality = impact.graph_quality.available ? impact.graph_quality.quality : "unknown";
|
|
2563
|
+
const readiness = impact.context_readiness.level;
|
|
2564
|
+
const ctxPrepH = round2(completed * a.context_preparation_minutes_saved_per_work_item / 60);
|
|
2565
|
+
const reviewH = round2(wiWithOwnership * a.review_minutes_saved_per_work_item_with_ownership / 60);
|
|
2566
|
+
const clarH = round2(wiWithAcceptance * a.clarification_minutes_saved_per_ready_work_item / 60);
|
|
2567
|
+
const readinessMult = READINESS_MULT[readiness] ?? 0.5;
|
|
2568
|
+
const onboardH = round2(a.expected_new_contributors_per_month * a.onboarding_hours_saved_per_new_contributor * readinessMult);
|
|
2569
|
+
const graphMult = GRAPH_MULT[graphQuality] ?? 0;
|
|
2570
|
+
const archH = round2(a.architecture_discovery_hours_saved_when_graph_good * graphMult);
|
|
2571
|
+
const totalHours = round2(ctxPrepH + reviewH + clarH + onboardH + archH);
|
|
2572
|
+
const totalValue = Math.round(totalHours * a.hourly_cost);
|
|
2573
|
+
const reasons = [];
|
|
2574
|
+
let level;
|
|
2575
|
+
if (impact.score === null || impact.score < 60 || graphQuality === "empty") {
|
|
2576
|
+
level = "Low";
|
|
2577
|
+
reasons.push("Impact score is low or the knowledge graph is empty.");
|
|
2578
|
+
} else {
|
|
2579
|
+
level = "Medium";
|
|
2580
|
+
reasons.push("Strong knowledge and graph evidence.");
|
|
2581
|
+
}
|
|
2582
|
+
reasons.push("Guard history is not persisted yet \u2014 avoided rework is not estimated.");
|
|
2583
|
+
reasons.push(source === "file" ? "Assumptions come from `.kaddo/savings.yml`." : "Savings assumptions are defaults and should be calibrated.");
|
|
2584
|
+
const actions = [];
|
|
2585
|
+
if (source === "default") actions.push("Run `kaddo savings init` to customize assumptions.");
|
|
2586
|
+
actions.push("Calibrate hourly cost and team assumptions with real team data.");
|
|
2587
|
+
actions.push("Run `kaddo impact` to verify the underlying evidence quality.");
|
|
2588
|
+
actions.push("Use this report as directional evidence, not accounting data.");
|
|
2589
|
+
actions.push("Future: persist Guard history to estimate avoided rework.");
|
|
2590
|
+
return {
|
|
2591
|
+
generated_at: now.toISOString(),
|
|
2592
|
+
project: impact.project,
|
|
2593
|
+
scope: impact.scope,
|
|
2594
|
+
currency: a.currency,
|
|
2595
|
+
disclaimer: SAVINGS_DISCLAIMER,
|
|
2596
|
+
assumptions_source: source,
|
|
2597
|
+
assumptions: {
|
|
2598
|
+
hourly_cost: a.hourly_cost,
|
|
2599
|
+
context_preparation_minutes_saved_per_work_item: a.context_preparation_minutes_saved_per_work_item,
|
|
2600
|
+
review_minutes_saved_per_work_item_with_ownership: a.review_minutes_saved_per_work_item_with_ownership,
|
|
2601
|
+
clarification_minutes_saved_per_ready_work_item: a.clarification_minutes_saved_per_ready_work_item,
|
|
2602
|
+
onboarding_hours_saved_per_new_contributor: a.onboarding_hours_saved_per_new_contributor,
|
|
2603
|
+
expected_new_contributors_per_month: a.expected_new_contributors_per_month,
|
|
2604
|
+
architecture_discovery_hours_saved_when_graph_good: a.architecture_discovery_hours_saved_when_graph_good
|
|
2605
|
+
},
|
|
2606
|
+
evidence: {
|
|
2607
|
+
knowledge_impact_score: impact.score,
|
|
2608
|
+
completed_work_items: completed,
|
|
2609
|
+
ownership_coverage_percent: impact.ownership_coverage.coverage_percent,
|
|
2610
|
+
work_items_with_acceptance_criteria: acceptanceCov ? `${acceptanceCov.have}/${acceptanceCov.total}` : "0/0",
|
|
2611
|
+
graph_quality: graphQuality,
|
|
2612
|
+
graph_nodes: impact.graph_quality.available ? impact.graph_quality.nodes : null,
|
|
2613
|
+
graph_edges: impact.graph_quality.available ? impact.graph_quality.edges : null,
|
|
2614
|
+
context_readiness: readiness,
|
|
2615
|
+
guard_history_available: false
|
|
2616
|
+
},
|
|
2617
|
+
estimated_savings: {
|
|
2618
|
+
context_preparation: { hours: ctxPrepH, formula: `${completed} Work Items \xD7 ${a.context_preparation_minutes_saved_per_work_item} min` },
|
|
2619
|
+
review_effort: { hours: reviewH, formula: `${wiWithOwnership} Work Items with ownership \xD7 ${a.review_minutes_saved_per_work_item_with_ownership} min` },
|
|
2620
|
+
clarification_reduction: { hours: clarH, formula: `${wiWithAcceptance} Work Items with Acceptance Criteria \xD7 ${a.clarification_minutes_saved_per_ready_work_item} min` },
|
|
2621
|
+
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` },
|
|
2622
|
+
architecture_discovery: { hours: archH, formula: `${a.architecture_discovery_hours_saved_when_graph_good} h \xD7 ${graphMult.toFixed(2)} graph multiplier` },
|
|
2623
|
+
drift_prevention: { available: false, hours: 0, reason: "Guard history is not persisted yet." }
|
|
2624
|
+
},
|
|
2625
|
+
total: { estimated_hours_saved: totalHours, estimated_value: totalValue, currency: a.currency },
|
|
2626
|
+
confidence: { level, reasons },
|
|
2627
|
+
suggested_actions: actions
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
function renderSavingsMarkdown(r) {
|
|
2631
|
+
const L = [];
|
|
2632
|
+
L.push("# Kaddo Estimated Savings Report", "");
|
|
2633
|
+
L.push(`Generated at: ${r.generated_at}`);
|
|
2634
|
+
L.push(`Project: ${r.project}`);
|
|
2635
|
+
L.push(`Scope: ${r.scope}`);
|
|
2636
|
+
L.push(`Currency: ${r.currency}`);
|
|
2637
|
+
L.push("");
|
|
2638
|
+
L.push("## Disclaimer", "");
|
|
2639
|
+
L.push(r.disclaimer, "");
|
|
2640
|
+
L.push("## Executive Summary", "");
|
|
2641
|
+
L.push(`- Estimated time saved: ${r.total.estimated_hours_saved} hours`);
|
|
2642
|
+
L.push(`- Estimated value: ${r.total.estimated_value} ${r.currency}`);
|
|
2643
|
+
L.push(`- Confidence: ${r.confidence.level}`);
|
|
2644
|
+
if (r.assumptions_source === "default") L.push("- Using default assumptions. Run `kaddo savings init` to customize them.");
|
|
2645
|
+
L.push("");
|
|
2646
|
+
L.push("## Assumptions", "");
|
|
2647
|
+
L.push(`- Hourly cost: ${r.assumptions.hourly_cost} ${r.currency}`);
|
|
2648
|
+
L.push(`- Context preparation saved per Work Item: ${r.assumptions.context_preparation_minutes_saved_per_work_item} min`);
|
|
2649
|
+
L.push(`- Review saved per Work Item with ownership: ${r.assumptions.review_minutes_saved_per_work_item_with_ownership} min`);
|
|
2650
|
+
L.push(`- Clarification saved per Work Item with acceptance criteria: ${r.assumptions.clarification_minutes_saved_per_ready_work_item} min`);
|
|
2651
|
+
L.push(`- Onboarding saved per new contributor: ${r.assumptions.onboarding_hours_saved_per_new_contributor} h`);
|
|
2652
|
+
L.push(`- Expected new contributors per month: ${r.assumptions.expected_new_contributors_per_month}`);
|
|
2653
|
+
L.push(`- Architecture discovery saved when graph is good: ${r.assumptions.architecture_discovery_hours_saved_when_graph_good} h`);
|
|
2654
|
+
L.push("");
|
|
2655
|
+
L.push("## Evidence Used", "");
|
|
2656
|
+
const e = r.evidence;
|
|
2657
|
+
L.push(`- Knowledge Impact Score: ${e.knowledge_impact_score ?? "not available"}${e.knowledge_impact_score !== null ? "/100" : ""}`);
|
|
2658
|
+
L.push(`- Completed Work Items: ${e.completed_work_items}`);
|
|
2659
|
+
L.push(`- Ownership coverage: ${e.ownership_coverage_percent}%`);
|
|
2660
|
+
L.push(`- Work Items with Acceptance Criteria: ${e.work_items_with_acceptance_criteria}`);
|
|
2661
|
+
L.push(`- Graph quality: ${e.graph_quality}`);
|
|
2662
|
+
if (e.graph_nodes !== null) L.push(`- Graph nodes: ${e.graph_nodes}`);
|
|
2663
|
+
if (e.graph_edges !== null) L.push(`- Graph edges: ${e.graph_edges}`);
|
|
2664
|
+
L.push(`- Context readiness: ${e.context_readiness}`);
|
|
2665
|
+
L.push("- Guard history: not available");
|
|
2666
|
+
L.push("");
|
|
2667
|
+
L.push("## Estimated Savings", "");
|
|
2668
|
+
const s = r.estimated_savings;
|
|
2669
|
+
const line = (title, l) => {
|
|
2670
|
+
L.push(`### ${title}`, "");
|
|
2671
|
+
L.push(`- Formula: ${l.formula}`);
|
|
2672
|
+
L.push(`- Estimated: ${l.hours} h`, "");
|
|
2673
|
+
};
|
|
2674
|
+
line("Context Preparation", s.context_preparation);
|
|
2675
|
+
line("Review Effort", s.review_effort);
|
|
2676
|
+
line("Clarification Reduction", s.clarification_reduction);
|
|
2677
|
+
line("Onboarding", s.onboarding);
|
|
2678
|
+
line("Architecture Discovery", s.architecture_discovery);
|
|
2679
|
+
L.push("### Drift Prevention", "");
|
|
2680
|
+
L.push("- Not available yet.");
|
|
2681
|
+
L.push(`- Reason: ${s.drift_prevention.reason}`, "");
|
|
2682
|
+
L.push("## Total", "");
|
|
2683
|
+
L.push(`- Estimated time saved: ${r.total.estimated_hours_saved} h`);
|
|
2684
|
+
L.push(`- Estimated value: ${r.total.estimated_value} ${r.currency}`);
|
|
2685
|
+
L.push("");
|
|
2686
|
+
L.push("## Confidence", "");
|
|
2687
|
+
L.push(`Confidence: ${r.confidence.level}`, "", "Reason:");
|
|
2688
|
+
for (const reason of r.confidence.reasons) L.push(`- ${reason}`);
|
|
2689
|
+
L.push("");
|
|
2690
|
+
L.push("## Suggested Actions", "");
|
|
2691
|
+
r.suggested_actions.forEach((act, i) => L.push(`${i + 1}. ${act}`));
|
|
2692
|
+
L.push("");
|
|
2693
|
+
return L.join("\n");
|
|
2694
|
+
}
|
|
2695
|
+
function serializeSavingsJson(r) {
|
|
2696
|
+
return JSON.stringify(r, null, 2) + "\n";
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2499
2699
|
// src/workitems.ts
|
|
2500
2700
|
import matter7 from "gray-matter";
|
|
2501
2701
|
var ACTIVE_STATES2 = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
@@ -2706,7 +2906,21 @@ var RESOURCES = [
|
|
|
2706
2906
|
if (!hasKnowledge(root)) {
|
|
2707
2907
|
return text("kaddo://impact-report", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
|
|
2708
2908
|
}
|
|
2709
|
-
return [{ uri: "kaddo://impact-report", text: renderImpactMarkdown(buildImpactReport(root, { scope: "all" })), mimeType: "text/markdown" }];
|
|
2909
|
+
return [{ uri: "kaddo://impact-report", text: renderImpactMarkdown(buildImpactReport(root, { scope: "all", scopeSource: "default" })), mimeType: "text/markdown" }];
|
|
2910
|
+
}
|
|
2911
|
+
},
|
|
2912
|
+
{
|
|
2913
|
+
uri: "kaddo://savings-report",
|
|
2914
|
+
name: "Kaddo savings report",
|
|
2915
|
+
description: "Estimated Savings Report \u2014 the last written report, or generated in memory (read-only).",
|
|
2916
|
+
mimeType: "text/markdown",
|
|
2917
|
+
read: (root) => {
|
|
2918
|
+
const saved = readText(root, ".kaddo/reports/savings-report.md");
|
|
2919
|
+
if (saved) return [{ uri: "kaddo://savings-report", text: saved, mimeType: "text/markdown" }];
|
|
2920
|
+
if (!hasKnowledge(root)) {
|
|
2921
|
+
return text("kaddo://savings-report", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
|
|
2922
|
+
}
|
|
2923
|
+
return [{ uri: "kaddo://savings-report", text: renderSavingsMarkdown(buildSavingsReport(root, { scope: "all", scopeSource: "default" })), mimeType: "text/markdown" }];
|
|
2710
2924
|
}
|
|
2711
2925
|
}
|
|
2712
2926
|
];
|
|
@@ -3474,7 +3688,7 @@ function generateImpactReport(root, opts = {}) {
|
|
|
3474
3688
|
requireConfig(root);
|
|
3475
3689
|
requireKnowledge(root);
|
|
3476
3690
|
const format = opts.format ?? "markdown";
|
|
3477
|
-
const report = buildImpactReport(root, { scope: opts.scope ?? "all" });
|
|
3691
|
+
const report = buildImpactReport(root, { scope: opts.scope ?? "all", scopeSource: opts.scope ? "explicit" : "default" });
|
|
3478
3692
|
const content = format === "json" ? serializeImpactJson(report) : renderImpactMarkdown(report);
|
|
3479
3693
|
const out = opts.output ?? `.kaddo/reports/impact-report.${format === "json" ? "json" : "md"}`;
|
|
3480
3694
|
writeDerived(root, out, content);
|
|
@@ -3486,6 +3700,22 @@ function generateImpactReport(root, opts = {}) {
|
|
|
3486
3700
|
next_suggested_resources: ["kaddo://impact-report"]
|
|
3487
3701
|
};
|
|
3488
3702
|
}
|
|
3703
|
+
function generateSavingsReport(root, opts = {}) {
|
|
3704
|
+
requireConfig(root);
|
|
3705
|
+
requireKnowledge(root);
|
|
3706
|
+
const format = opts.format ?? "markdown";
|
|
3707
|
+
const report = buildSavingsReport(root, { scope: opts.scope ?? "all", scopeSource: opts.scope ? "explicit" : "default" });
|
|
3708
|
+
const content = format === "json" ? serializeSavingsJson(report) : renderSavingsMarkdown(report);
|
|
3709
|
+
const out = opts.output ?? `.kaddo/reports/savings-report.${format === "json" ? "json" : "md"}`;
|
|
3710
|
+
writeDerived(root, out, content);
|
|
3711
|
+
return {
|
|
3712
|
+
status: "ok",
|
|
3713
|
+
files_written: [out.replace(/\\/g, "/")],
|
|
3714
|
+
summary: `Savings report generated (${format}, ${report.scope} scope, ~${report.total.estimated_hours_saved}h).`,
|
|
3715
|
+
warnings: report.assumptions_source === "default" ? ["Using default assumptions \u2014 run `kaddo savings init` to calibrate."] : [],
|
|
3716
|
+
next_suggested_resources: ["kaddo://savings-report"]
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3489
3719
|
function generateCapsuleDraft(root) {
|
|
3490
3720
|
const config = requireConfig(root);
|
|
3491
3721
|
requireKnowledge(root);
|
|
@@ -3686,6 +3916,23 @@ function createServer(root) {
|
|
|
3686
3916
|
() => generateImpactReport(root, { format: args.format, scope: args.scope, output: args.output })
|
|
3687
3917
|
)
|
|
3688
3918
|
);
|
|
3919
|
+
server.registerTool(
|
|
3920
|
+
"kaddo_generate_savings_report",
|
|
3921
|
+
{
|
|
3922
|
+
title: "Generate savings report",
|
|
3923
|
+
description: `Write the Estimated Savings Report under .kaddo/reports/. format: markdown (default) or json; scope: active or all (default all). Evidence-based estimates, not exact ROI. ${DERIVED_NOTE}`,
|
|
3924
|
+
inputSchema: {
|
|
3925
|
+
format: z2.enum(["markdown", "json"]).optional(),
|
|
3926
|
+
scope: z2.enum(["active", "all"]).optional(),
|
|
3927
|
+
output: z2.string().optional()
|
|
3928
|
+
}
|
|
3929
|
+
},
|
|
3930
|
+
async (args) => generated(
|
|
3931
|
+
root,
|
|
3932
|
+
"generate savings report",
|
|
3933
|
+
() => generateSavingsReport(root, { format: args.format, scope: args.scope, output: args.output })
|
|
3934
|
+
)
|
|
3935
|
+
);
|
|
3689
3936
|
server.registerTool(
|
|
3690
3937
|
"kaddo_generate_capsule_draft",
|
|
3691
3938
|
{ title: "Generate capsule draft", description: `Write a capsule DRAFT under .kaddo/exports/ (never registers it). ${DERIVED_NOTE}`, inputSchema: {} },
|
package/package.json
CHANGED