@kaddo/mcp 3.124.1 → 3.124.3

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 (2) hide show
  1. package/dist/index.js +196 -29
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10404,6 +10404,20 @@ function buildGuardHistory(dir) {
10404
10404
  }
10405
10405
 
10406
10406
  // ../cli/src/core/reporting-evidence.ts
10407
+ function graphHintFinding(hint) {
10408
+ const affected = [hint.artifact_id, ...hint.path ? [hint.path] : []];
10409
+ return {
10410
+ id: `graph-hint:${hint.artifact_id}:${hint.missing.join(",")}`,
10411
+ category: "topology",
10412
+ source: "graph-hints",
10413
+ affected_artifacts: affected,
10414
+ // Graph Hints are intentionally informational; review is useful but not a blocking error.
10415
+ priority: "medium",
10416
+ evidence: hint.reason,
10417
+ explanation: hint.message,
10418
+ suggested_action: `Review the declared relationships for ${hint.artifact_id}${hint.path ? ` at ${hint.path}` : ""}.`
10419
+ };
10420
+ }
10407
10421
  function reportingProjectContext(dir) {
10408
10422
  const config = loadConfig(dir);
10409
10423
  if (!config) return { state: "unknown", mode: "unknown", repository_structure: "unknown" };
@@ -10458,7 +10472,11 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10458
10472
  const projectContext = reportingProjectContext(dir);
10459
10473
  const wis = discoverWorkItems(dir);
10460
10474
  const total = wis.length;
10461
- const verifiedWorkItems = wis.filter((wi) => wi.validationStatus === "passed").length;
10475
+ const completedWis = wis.filter((wi) => wi.lifecycle === "completed");
10476
+ const verifiedWorkItems = completedWis.filter((wi) => wi.validationStatus === "passed").length;
10477
+ const failedValidationWorkItems = completedWis.filter((wi) => wi.validationStatus === "failed").length;
10478
+ const pendingValidationWorkItems = completedWis.filter((wi) => Boolean(wi.validationStatus) && !["passed", "failed"].includes(wi.validationStatus)).length;
10479
+ const notRecordedValidationWorkItems = completedWis.length - verifiedWorkItems - failedValidationWorkItems - pendingValidationWorkItems;
10462
10480
  let withOwnership = 0;
10463
10481
  let withSource = 0;
10464
10482
  let withInitiative = 0;
@@ -10466,7 +10484,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10466
10484
  let withAcceptance = 0;
10467
10485
  let withDoD = 0;
10468
10486
  let withValidation = 0;
10469
- let connectedRoadmap = 0;
10487
+ const connectedRoadmapCandidates = /* @__PURE__ */ new Set();
10470
10488
  const globCounts = /* @__PURE__ */ new Map();
10471
10489
  const globOwners = /* @__PURE__ */ new Map();
10472
10490
  const gaps = {
@@ -10490,13 +10508,17 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10490
10508
  for (const wi of wis) {
10491
10509
  const id = wi.id || wi.title;
10492
10510
  if (wi.codeGlobs.length > 0) withOwnership++;
10493
- else gaps.missing_code_ownership.push(gapItem(wi, "Add `code:` globs to connect this Work Item to source paths (`kaddo owners suggest`)."));
10494
- const hasSource = Boolean(wi.sourceId) || wi.source === "roadmap";
10511
+ else if (wi.type === "bugfix" || wi.type === "hotfix") {
10512
+ gaps.missing_code_ownership.push(gapItem(wi, "Add `code:` globs to connect this corrective Work Item to source paths (`kaddo owners suggest`)."));
10513
+ }
10514
+ const roadmapOrigin = wi.source === "roadmap";
10515
+ const hasSource = Boolean(wi.source) || Boolean(wi.sourceId);
10495
10516
  if (hasSource) withSource++;
10496
- else gaps.missing_source.push(gapItem(wi, "Add `source` or `source_id` if this Work Item came from the roadmap."));
10497
- if (hasSource || wi.initiative) connectedRoadmap++;
10517
+ if (roadmapOrigin && !wi.sourceId) {
10518
+ gaps.missing_source.push(gapItem(wi, "Add `source_id` for this roadmap-originated Work Item."));
10519
+ }
10520
+ if (roadmapOrigin && wi.sourceId) connectedRoadmapCandidates.add(wi.sourceId);
10498
10521
  if (wi.initiative) withInitiative++;
10499
- else gaps.missing_initiative.push(gapItem(wi, "Add `initiative` to connect this Work Item to a delivery initiative."));
10500
10522
  if (wi.knowledgeLevel) withLevel++;
10501
10523
  else gaps.missing_knowledge_level.push(gapItem(wi, "Add `knowledge_level` (K0\u2013K4) to the front matter."));
10502
10524
  for (const g2 of wi.codeGlobs) {
@@ -10518,18 +10540,21 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10518
10540
  }
10519
10541
  }
10520
10542
  const ownedCodePaths = [...globCounts.values()].reduce((a2, b2) => a2 + b2, 0);
10543
+ const connectedRoadmap = connectedRoadmapCandidates.size;
10521
10544
  const broadGlobs = [...globCounts.keys()].filter((g2) => isBroadGlob(g2)).length;
10522
10545
  for (const [glob, owners] of globOwners) {
10523
10546
  if (owners.length > 1) {
10524
- gaps.ownership_overlaps.push({ code_path: glob, work_items: [...new Set(owners)], suggested_action: "Review whether the overlap is expected or should be narrowed." });
10547
+ gaps.ownership_overlaps.push({ code_path: glob, work_items: [...new Set(owners)], suggested_action: "Shared historical modification path; it is not a conflict unless incompatible ownership is documented." });
10525
10548
  }
10526
10549
  }
10527
10550
  const ownershipOverlaps = gaps.ownership_overlaps.length;
10528
10551
  let g;
10552
+ let graphHintEntries = [];
10529
10553
  const config = loadConfig(dir);
10530
10554
  if (config) {
10531
10555
  const graph = buildGraph(dir, config, { scope: resolvedScope }, now);
10532
10556
  const hints = buildGraphHints(dir, graph, now);
10557
+ graphHintEntries = hints.hints;
10533
10558
  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 };
10534
10559
  } else {
10535
10560
  g = { available: false, scope: resolvedScope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
@@ -10593,28 +10618,62 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10593
10618
  const deliveryTrace = levelFromRatio(
10594
10619
  coverageRatio(withOwnership) * 0.5 + (g.available && g.edges > 0 ? 1 : 0) * 0.25 + (exp.roadmap.present ? 1 : 0) * 0.25
10595
10620
  );
10621
+ const guardHistory = buildGuardHistory(dir);
10596
10622
  const impact_signals = {
10597
10623
  ambiguity_reduction: levelFromRatio(ambiguityRatio),
10598
- drift_prevention: withOwnership > 0 ? "Active" : "Limited",
10624
+ drift_prevention: guardHistory.available && guardHistory.resolved > 0 ? "Observed" : "Unknown",
10599
10625
  onboarding_support: onboarding,
10600
10626
  delivery_traceability: deliveryTrace,
10601
10627
  ai_context_readiness: aiReady,
10602
10628
  maintenance_readiness: levelFromRatio((coverageRatio(withOwnership) + (deliveryTraceable ? 1 : 0)) / 2)
10603
10629
  };
10630
+ const baselineLayers = projectContext.mode === "poc" ? ["Tech", "Delivery"] : ["Business", "Product", "Tech", "Delivery"];
10631
+ const findings = [];
10632
+ for (const layer2 of baselineLayers) {
10633
+ const status = layerStatus(layer2);
10634
+ if (status !== "Weak" && status !== "Missing") continue;
10635
+ findings.push({
10636
+ id: `knowledge-health:${layer2.toLowerCase()}`,
10637
+ category: "knowledge",
10638
+ source: "knowledge-health",
10639
+ affected_artifacts: [layer2],
10640
+ priority: status === "Missing" ? "high" : "medium",
10641
+ evidence: `${layer2} knowledge maturity is ${status}.`,
10642
+ explanation: `This assesses documented ${layer2} knowledge, not the quality of the product or implementation.`,
10643
+ suggested_action: `Review ${layer2} knowledge coverage and the artifacts or relationships available for it.`
10644
+ });
10645
+ }
10646
+ for (const gap of gaps.missing_code_ownership) {
10647
+ findings.push({
10648
+ id: `ownership:${gap.id}`,
10649
+ category: "delivery",
10650
+ source: "work-item-ownership",
10651
+ affected_artifacts: [gap.id, gap.path],
10652
+ priority: "medium",
10653
+ evidence: "A corrective Work Item has no declared code ownership.",
10654
+ explanation: gap.title,
10655
+ suggested_action: gap.suggested_action
10656
+ });
10657
+ }
10658
+ findings.push(...graphHintEntries.map(graphHintFinding));
10659
+ const priorityOrder = { high: 0, medium: 1, low: 2, unknown: 3 };
10660
+ findings.sort((a2, b2) => priorityOrder[a2.priority] - priorityOrder[b2.priority] || a2.id.localeCompare(b2.id));
10604
10661
  let score = null;
10605
10662
  let score_breakdown = null;
10606
10663
  if (total > 0) {
10607
- const healthPts = [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * 20;
10664
+ const layerMaturity = { Consolidated: 1, Structured: 0.8, Traceable: 0.75, Weak: 0.4, Missing: 0 };
10665
+ const healthPts = [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].reduce((sum, status) => sum + (layerMaturity[status] ?? 0), 0) / 4 * 20;
10608
10666
  const applicableCoverage = knowledge_coverage.filter((c) => c.label !== "Work Items using optional initiatives");
10609
10667
  const covAvg = applicableCoverage.reduce((a2, c) => a2 + coverageRatio(c.have), 0) / applicableCoverage.length;
10610
10668
  const coveragePts = covAvg * 20;
10611
10669
  const ownPts = coverageRatio(withOwnership) * 15;
10612
- const traceBlend = (exp.roadmap.candidates > 0 ? exp.roadmap.materialized / exp.roadmap.candidates : 1) * 0.5 + coverageRatio(withOwnership) * 0.5;
10670
+ const roadmapTraceRatio = exp.roadmap.candidates > 0 ? Math.min(connectedRoadmap, exp.roadmap.candidates) / exp.roadmap.candidates : null;
10671
+ const traceSignals = [coverageRatio(withOwnership), ...roadmapTraceRatio === null ? [] : [roadmapTraceRatio]];
10672
+ const traceBlend = traceSignals.reduce((sum, value) => sum + value, 0) / traceSignals.length;
10613
10673
  const tracePts = traceBlend * 20;
10614
10674
  const qualityMap = { good: 1, partial: 0.6, sparse: 0.3, empty: 0, unknown: 0 };
10615
10675
  const graphPts = (g.available ? qualityMap[g.quality] ?? 0 : 0) * 15;
10616
10676
  const ctxPts = { Low: 0.25, Medium: 0.5, High: 0.8, "Very High": 1 }[ctxLevel] * 10;
10617
- score = Math.round(healthPts + coveragePts + ownPts + tracePts + graphPts + ctxPts);
10618
10677
  score_breakdown = {
10619
10678
  knowledge_health: { points: Math.round(healthPts), max: 20 },
10620
10679
  knowledge_coverage: { points: Math.round(coveragePts), max: 20 },
@@ -10623,6 +10682,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10623
10682
  graph_quality: { points: Math.round(graphPts), max: 15 },
10624
10683
  context_readiness: { points: Math.round(ctxPts), max: 10 }
10625
10684
  };
10685
+ score = Object.values(score_breakdown).reduce((sum, component) => sum + component.points, 0);
10626
10686
  }
10627
10687
  const idsOf = (items) => items.map((i) => i.id);
10628
10688
  const groupedAction = (items, verb) => {
@@ -10636,16 +10696,18 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10636
10696
  if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
10637
10697
  for (const a2 of [
10638
10698
  groupedAction(gaps.missing_code_ownership, "Add `code:` ownership to"),
10639
- groupedAction(gaps.missing_source, "Add `source`/`source_id` to"),
10640
- groupedAction(gaps.missing_acceptance_criteria, "Add Acceptance Criteria to"),
10641
- groupedAction(gaps.missing_definition_of_done, "Add a Definition of Done to"),
10642
- groupedAction(gaps.missing_validation, "Add a validation (How to test it) section to")
10699
+ groupedAction(gaps.missing_source, "Add `source`/`source_id` to")
10643
10700
  ]) if (a2) actions.push(a2);
10644
10701
  if (gaps.broad_ownership_globs.length > 0) actions.push(`Narrow ${gaps.broad_ownership_globs.length} broad ownership glob(s) into specific paths.`);
10645
- if (g.available && g.hints > 0) actions.push("Review graph hints (`kaddo://graph-hints` or `.kaddo/graph-hints.md`).");
10702
+ for (const finding of findings) {
10703
+ if (!actions.includes(finding.suggested_action)) actions.push(finding.suggested_action);
10704
+ }
10646
10705
  if (skills.length === 0) actions.push("Install reusable skills with `kaddo add skills`.");
10647
10706
  actions.push("Run `kaddo guard` before committing to catch knowledge drift.");
10648
10707
  const summary = [];
10708
+ if (completedWis.length > 0) {
10709
+ summary.push(`Delivery evidence: ${completedWis.length} completed; ${verifiedWorkItems} validation passed; ${notRecordedValidationWorkItems} not confirmed by persisted validation.`);
10710
+ }
10649
10711
  if (total > 0) summary.push(`${withOwnership}/${total} Work Items include code ownership.`);
10650
10712
  if (g.available) summary.push(`The knowledge graph is ${g.quality} with ${g.nodes} nodes and ${g.edges} edges (${g.scope} scope).`);
10651
10713
  else summary.push("The knowledge graph has not been exported yet.");
@@ -10661,12 +10723,21 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10661
10723
  { id: "work-items.lifecycle", label: "Work Item lifecycle", classification: total > 0 ? "observed" : "unknown", reason: total > 0 ? `${total} Work Item artifact(s) were discovered.` : "No Work Item artifacts were discovered." },
10662
10724
  { id: "delivery.verification", label: "Verified delivery outcomes", classification: verifiedWorkItems > 0 ? "observed" : "unknown", reason: verifiedWorkItems > 0 ? `${verifiedWorkItems} Work Item(s) have persisted validation_status: passed.` : "Acceptance Criteria and Definition of Done sections show declared intent, not completed verification evidence." },
10663
10725
  ...deliveryCapabilityEvidence(dir, projectContext),
10664
- { id: "guard-history", label: "Guard consistency history", classification: buildGuardHistory(dir).available ? "observed" : "unknown", reason: buildGuardHistory(dir).available ? "Recorded Guard runs provide current consistency evidence." : "No recorded Guard history is available." }
10726
+ { id: "guard-history", label: "Guard consistency history", classification: guardHistory.available ? "observed" : "unknown", reason: guardHistory.available ? "Recorded Guard runs provide current consistency evidence." : "No recorded Guard history is available." }
10665
10727
  ],
10666
10728
  scope: g.scope,
10667
10729
  default_scope: "all",
10668
10730
  scope_source: scopeSource,
10669
10731
  executive_summary: summary,
10732
+ delivery_evidence: {
10733
+ completed: completedWis.length,
10734
+ validation_passed: verifiedWorkItems,
10735
+ validation_failed: failedValidationWorkItems,
10736
+ validation_pending: pendingValidationWorkItems,
10737
+ validation_not_recorded: notRecordedValidationWorkItems,
10738
+ completion_with_passed_validation_percent: completedWis.length > 0 ? Math.round(verifiedWorkItems / completedWis.length * 1e3) / 10 : null
10739
+ },
10740
+ findings,
10670
10741
  knowledge_health,
10671
10742
  knowledge_coverage,
10672
10743
  ownership_coverage: {
@@ -10699,7 +10770,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10699
10770
  },
10700
10771
  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`." },
10701
10772
  guard_activity: (() => {
10702
- const gh = buildGuardHistory(dir);
10773
+ const gh = guardHistory;
10703
10774
  return gh.available ? { available: true, runs_recorded: gh.total_runs, detected: gh.detected, open: gh.open, resolved: gh.resolved, resolution_rate: gh.resolution_rate } : { available: false, note: "Guard history is not persisted. Run `kaddo guard --record` to record runs for trend analysis." };
10704
10775
  })(),
10705
10776
  impact_signals,
@@ -10730,6 +10801,27 @@ function renderImpactMarkdown(r2) {
10730
10801
  L.push("## Executive Summary", "");
10731
10802
  for (const s2 of r2.executive_summary) L.push(`- ${s2}`);
10732
10803
  L.push("");
10804
+ L.push("## Key Findings", "");
10805
+ if (r2.findings.length === 0) L.push("No current findings detected by the executed deterministic checks.");
10806
+ if (r2.findings.length > 0) {
10807
+ const cell = (value) => value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
10808
+ L.push("| Priority | Source | Affected | Finding | Suggested action |");
10809
+ L.push("|---|---|---|---|---|");
10810
+ for (const finding of r2.findings) {
10811
+ L.push(`| ${finding.priority} | ${cell(finding.source)} | ${cell(finding.affected_artifacts.join(", ") || "not available")} | ${cell(finding.explanation)} Evidence: ${cell(finding.evidence)} | ${cell(finding.suggested_action)} |`);
10812
+ }
10813
+ }
10814
+ L.push("");
10815
+ L.push("## Delivery Evidence", "");
10816
+ const de = r2.delivery_evidence;
10817
+ L.push(`- Completed Work Items: ${de.completed}`);
10818
+ L.push(`- Validation passed: ${de.validation_passed}`);
10819
+ L.push(`- Validation failed: ${de.validation_failed}`);
10820
+ L.push(`- Validation pending: ${de.validation_pending}`);
10821
+ L.push(`- Validation not recorded: ${de.validation_not_recorded}`);
10822
+ L.push(`- Completion with passed validation: ${de.completion_with_passed_validation_percent === null ? "not available" : `${de.completion_with_passed_validation_percent}%`}`);
10823
+ L.push("- \u201CNot recorded\u201D means persisted evidence is absent; it does not prove validation never happened.");
10824
+ L.push("");
10733
10825
  L.push("## Evidence and Applicability", "");
10734
10826
  for (const item of r2.evidence) L.push(`- ${item.label}: ${item.classification} \u2014 ${item.reason}`);
10735
10827
  L.push("");
@@ -10810,7 +10902,7 @@ function renderImpactMarkdown(r2) {
10810
10902
  L.push("## Impact Signals", "");
10811
10903
  const s = r2.impact_signals;
10812
10904
  L.push(`- Ambiguity reduction: ${s.ambiguity_reduction}`);
10813
- L.push(`- Drift prevention: ${s.drift_prevention}`);
10905
+ L.push(`- Drift prevention evidence: ${s.drift_prevention}`);
10814
10906
  L.push(`- Onboarding support: ${s.onboarding_support}`);
10815
10907
  L.push(`- Delivery traceability: ${s.delivery_traceability}`);
10816
10908
  L.push(`- AI context readiness: ${s.ai_context_readiness}`);
@@ -10829,12 +10921,7 @@ function renderImpactMarkdown(r2) {
10829
10921
  L.push("");
10830
10922
  };
10831
10923
  gapSection("Work Items missing source", gp.missing_source);
10832
- gapSection("Work Items missing initiative", gp.missing_initiative);
10833
10924
  gapSection("Work Items missing code ownership", gp.missing_code_ownership);
10834
- gapSection("Work Items missing knowledge level", gp.missing_knowledge_level);
10835
- gapSection("Work Items missing acceptance criteria", gp.missing_acceptance_criteria);
10836
- gapSection("Work Items missing Definition of Done", gp.missing_definition_of_done);
10837
- gapSection("Work Items missing validation", gp.missing_validation);
10838
10925
  if (gp.broad_ownership_globs.length > 0) {
10839
10926
  L.push("### Broad ownership globs", "");
10840
10927
  for (const b2 of gp.broad_ownership_globs) {
@@ -10845,7 +10932,7 @@ function renderImpactMarkdown(r2) {
10845
10932
  L.push("");
10846
10933
  }
10847
10934
  if (gp.ownership_overlaps.length > 0) {
10848
- L.push("### Ownership overlaps", "");
10935
+ L.push("### Shared code-path history (review only)", "");
10849
10936
  for (const o2 of gp.ownership_overlaps) {
10850
10937
  L.push(`- \`${o2.code_path}\``);
10851
10938
  L.push(` - Owned by: ${o2.work_items.join(", ")}`);
@@ -10853,10 +10940,26 @@ function renderImpactMarkdown(r2) {
10853
10940
  }
10854
10941
  L.push("");
10855
10942
  }
10856
- 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;
10943
+ const anyGaps = gp.missing_source.length + gp.missing_code_ownership.length + gp.broad_ownership_globs.length;
10857
10944
  if (anyGaps === 0) {
10858
10945
  L.push("No actionable knowledge gaps detected. \u{1F389}", "");
10859
10946
  }
10947
+ const historical = [
10948
+ ["knowledge level", gp.missing_knowledge_level],
10949
+ ["Acceptance Criteria", gp.missing_acceptance_criteria],
10950
+ ["Definition of Done", gp.missing_definition_of_done],
10951
+ ["validation section", gp.missing_validation]
10952
+ ];
10953
+ L.push("## Historical Documentation Completeness", "");
10954
+ for (const [label, items] of historical) {
10955
+ const completed = items.filter((item) => item.status === "completed");
10956
+ if (completed.length > 0) L.push(`- Completed Work Items without detected ${label}: ${completed.length}`);
10957
+ }
10958
+ if (!historical.some(([, items]) => items.some((item) => item.status === "completed"))) {
10959
+ L.push("No historical documentation completeness gaps were detected.");
10960
+ }
10961
+ L.push("- This is an inventory for review, not a recommendation to rewrite historical Work Items merely to increase a metric.");
10962
+ L.push("");
10860
10963
  if (r2.score_breakdown) {
10861
10964
  L.push("## Score Breakdown", "");
10862
10965
  const b2 = r2.score_breakdown;
@@ -11006,6 +11109,16 @@ function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
11006
11109
  } else {
11007
11110
  actions.push("Use drift history to calibrate `rework_hours_avoided_per_resolved_drift`.");
11008
11111
  }
11112
+ const calibrationOpportunities = !a2.calibrated_at ? {
11113
+ available: true,
11114
+ reason: source3 === "default" ? "Default assumptions are uncalibrated estimates." : "Project assumptions are not marked as calibrated.",
11115
+ opportunities: [
11116
+ { area: "Context preparation", suggested_evidence: "Compare preparation effort on comparable tasks before and after using the knowledge artifacts." },
11117
+ { area: "Review effort", suggested_evidence: "Record a baseline for comparable reviews with similar ownership coverage." },
11118
+ { area: "Clarification reduction", suggested_evidence: "Estimate effort spent resolving repeated scope or context questions." },
11119
+ { area: "Architecture discovery", suggested_evidence: "Compare discovery time for similar dependency or relationship analyses." }
11120
+ ]
11121
+ } : { available: false, reason: `Assumptions are marked as calibrated on ${a2.calibrated_at}.`, opportunities: [] };
11009
11122
  return {
11010
11123
  generated_at: now.toISOString(),
11011
11124
  project: impact.project,
@@ -11057,6 +11170,7 @@ function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
11057
11170
  },
11058
11171
  total: { estimated_hours_saved: observedHours, estimated_value: observedValue, currency: a2.currency },
11059
11172
  confidence: { level, reasons },
11173
+ calibration_opportunities: calibrationOpportunities,
11060
11174
  suggested_actions: actions
11061
11175
  };
11062
11176
  }
@@ -11138,6 +11252,17 @@ function renderSavingsMarkdown(r2) {
11138
11252
  L.push(`Confidence: ${r2.confidence.level}`, "", "Reason:");
11139
11253
  for (const reason of r2.confidence.reasons) L.push(`- ${reason}`);
11140
11254
  L.push("");
11255
+ L.push("## Calibration Opportunities", "");
11256
+ if (!r2.calibration_opportunities.available) {
11257
+ L.push(`- ${r2.calibration_opportunities.reason}`);
11258
+ } else {
11259
+ L.push(`- ${r2.calibration_opportunities.reason}`);
11260
+ L.push("- Use external, aggregate project observations; do not collect or store personal data.");
11261
+ for (const opportunity of r2.calibration_opportunities.opportunities) {
11262
+ L.push(`- ${opportunity.area}: ${opportunity.suggested_evidence}`);
11263
+ }
11264
+ }
11265
+ L.push("");
11141
11266
  L.push("## Suggested Actions", "");
11142
11267
  r2.suggested_actions.forEach((act, i) => L.push(`${i + 1}. ${act}`));
11143
11268
  L.push("");
@@ -11158,10 +11283,24 @@ function buildDriftReport(dir, now = /* @__PURE__ */ new Date()) {
11158
11283
  const deprecationFindings = plans.flatMap((plan) => getDeprecationPlan(dir, plan.id)?.targets.flatMap(validateTarget) ?? []);
11159
11284
  const config = loadConfig(dir);
11160
11285
  const topology = config ? buildGraph(dir, config, { scope: "all" }, now) : null;
11161
- const topologyHints = topology ? buildGraphHints(dir, topology, now).summary.hints : null;
11286
+ const graphHints = topology ? buildGraphHints(dir, topology, now) : null;
11287
+ const topologyHints = graphHints?.summary.hints ?? null;
11288
+ const currentConsistencyFindings = [
11289
+ ...graphHints?.hints.map(graphHintFinding) ?? [],
11290
+ ...deprecationFindings.map((finding, index) => ({
11291
+ id: `deprecation-target:${index + 1}`,
11292
+ category: "consistency",
11293
+ source: "deprecation-target-validation",
11294
+ affected_artifacts: [],
11295
+ priority: "medium",
11296
+ evidence: String(finding),
11297
+ explanation: "A deterministic deprecation target consistency check reported a finding.",
11298
+ suggested_action: "Review the affected deprecation target and its declared lifecycle evidence."
11299
+ }))
11300
+ ];
11162
11301
  const actions = [];
11163
11302
  if (!h2.available) {
11164
- actions.push("Run `kaddo guard --record` to start recording guard history.");
11303
+ actions.push("Start recording Guard runs at relevant delivery checkpoints with `kaddo guard --record`; one run creates an observation, while multiple comparable runs are needed for a historical trend.");
11165
11304
  } else {
11166
11305
  if (h2.open > 0) actions.push("Review open drift warnings and update related Work Items or ownership.");
11167
11306
  actions.push("Run `kaddo guard --record` before important commits.");
@@ -11177,6 +11316,11 @@ function buildDriftReport(dir, now = /* @__PURE__ */ new Date()) {
11177
11316
  { id: "deprecation-consistency", label: "Deprecation consistency", classification: plans.length === 0 ? "not_applicable" : deprecationFindings.length === 0 ? "derived" : "observed", reason: plans.length === 0 ? "No deprecation plans exist in this project." : deprecationFindings.length === 0 ? `${plans.length} plan(s) passed deterministic target consistency checks.` : `${deprecationFindings.length} deterministic deprecation consistency finding(s) were detected.` },
11178
11317
  { id: "topology-consistency", label: "Knowledge topology consistency", classification: topology === null ? "unknown" : topologyHints === 0 ? "derived" : "observed", reason: topology === null ? "No project configuration is available to construct the topology." : topologyHints === 0 ? `The in-memory topology has ${topology.nodes.length} node(s) and no open deterministic hints.` : `The in-memory topology has ${topologyHints} open deterministic hint(s).` }
11179
11318
  ],
11319
+ current_consistency_findings: currentConsistencyFindings,
11320
+ structural_consistency: {
11321
+ deprecation_target_checks: plans.length === 0 ? "not_applicable" : deprecationFindings.length === 0 ? "passed" : "findings_detected",
11322
+ note: plans.length === 0 ? "No deprecation plans exist in this project." : "Target consistency checks validate declared structural state only; they do not verify operational removal from infrastructure, production or external consumers."
11323
+ },
11180
11324
  guard_history: {
11181
11325
  available: h2.available,
11182
11326
  total_runs: h2.total_runs,
@@ -11202,10 +11346,31 @@ function renderDriftMarkdown(r2) {
11202
11346
  L.push("## Consistency Evidence", "");
11203
11347
  for (const item of r2.consistency_evidence) L.push(`- ${item.label}: ${item.classification} \u2014 ${item.reason}`);
11204
11348
  L.push("");
11349
+ L.push("## Current Consistency Findings", "");
11350
+ if (r2.current_consistency_findings.length === 0) {
11351
+ L.push("No findings detected by the executed consistency checks.");
11352
+ L.push("");
11353
+ }
11354
+ if (r2.current_consistency_findings.length > 0) {
11355
+ const cell = (value) => value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
11356
+ L.push("| Priority | Source | Affected | Finding | Suggested action |");
11357
+ L.push("|---|---|---|---|---|");
11358
+ for (const finding of r2.current_consistency_findings) {
11359
+ L.push(`| ${finding.priority} | ${cell(finding.source)} | ${cell(finding.affected_artifacts.join(", ") || "not available")} | ${cell(finding.explanation)} Evidence: ${cell(finding.evidence)} | ${cell(finding.suggested_action)} |`);
11360
+ }
11361
+ L.push("");
11362
+ }
11363
+ L.push("## Structural Consistency Boundaries", "");
11364
+ L.push(`- Deprecation target checks: ${r2.structural_consistency.deprecation_target_checks}`);
11365
+ L.push(`- Note: ${r2.structural_consistency.note}`);
11366
+ L.push("");
11205
11367
  if (!r2.guard_history.available) {
11206
11368
  L.push("## Guard History", "");
11207
11369
  L.push("No guard history recorded yet.");
11208
11370
  L.push("");
11371
+ L.push("## Historical Drift Trend", "");
11372
+ L.push("Not available \u2014 no recorded Guard runs.");
11373
+ L.push("");
11209
11374
  L.push("Run `kaddo guard --record` to start recording drift evidence.");
11210
11375
  L.push("");
11211
11376
  L.push("## Suggested Actions", "");
@@ -11270,6 +11435,8 @@ function serializeDriftJson(r2) {
11270
11435
  project: r2.project,
11271
11436
  project_context: r2.project_context,
11272
11437
  consistency_evidence: r2.consistency_evidence,
11438
+ current_consistency_findings: r2.current_consistency_findings,
11439
+ structural_consistency: r2.structural_consistency,
11273
11440
  guard_history: r2.guard_history,
11274
11441
  drift_warnings: r2.drift_warnings,
11275
11442
  hotspots: r2.hotspots,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.124.1",
3
+ "version": "3.124.3",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {