@kaddo/mcp 3.40.0 → 3.41.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.
Files changed (2) hide show
  1. package/dist/index.js +83 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2434,18 +2434,31 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
2434
2434
  }
2435
2435
 
2436
2436
  // ../cli/src/core/decisions.ts
2437
- var CANDIDATES_PATH = "knowledge/tech/decision-candidates.md";
2437
+ var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
2438
+ var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
2438
2439
  var DECISIONS_DIR = "knowledge/tech/decisions";
2440
+ function resolveCandidatesPath(dir) {
2441
+ const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
2442
+ const legacy = exists(join(dir, CANDIDATES_LEGACY));
2443
+ if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
2444
+ if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
2445
+ return { path: null, legacy: false, bothExist: false };
2446
+ }
2447
+ function cleanCandidateTitle(title) {
2448
+ return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
2449
+ }
2439
2450
  function slugify(s) {
2440
- return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
2451
+ return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
2441
2452
  }
2442
2453
  function parseDecisionCandidates(md) {
2443
2454
  const out = [];
2444
2455
  for (const line of md.split(/\r?\n/)) {
2445
2456
  const m = line.match(/^##\s+(.+?)\s*$/);
2446
2457
  if (m) {
2447
- const t = m[1].trim();
2448
- if (t && !/^_.*_$/.test(t)) out.push(t);
2458
+ const raw = m[1].trim();
2459
+ if (!raw || /^_.*_$/.test(raw)) continue;
2460
+ const t = cleanCandidateTitle(raw);
2461
+ if (t) out.push(t);
2449
2462
  }
2450
2463
  }
2451
2464
  return out;
@@ -2474,11 +2487,11 @@ function countAdrs(dir) {
2474
2487
  return { total, draft, accepted };
2475
2488
  }
2476
2489
  function buildTechDecisions(dir) {
2477
- const candFile = join(dir, CANDIDATES_PATH);
2490
+ const resolved = resolveCandidatesPath(dir);
2478
2491
  let titles = [];
2479
- if (exists(candFile)) {
2492
+ if (resolved.path) {
2480
2493
  try {
2481
- titles = parseDecisionCandidates(readFile(candFile));
2494
+ titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
2482
2495
  } catch {
2483
2496
  titles = [];
2484
2497
  }
@@ -2486,7 +2499,7 @@ function buildTechDecisions(dir) {
2486
2499
  const { total, draft, accepted } = countAdrs(dir);
2487
2500
  const candidate_list = titles.map((title, i) => {
2488
2501
  const n = String(total + i + 1).padStart(3, "0");
2489
- return { title, source: CANDIDATES_PATH, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
2502
+ return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
2490
2503
  });
2491
2504
  let status;
2492
2505
  if (accepted > 0) status = "accepted-adrs";
@@ -2499,7 +2512,10 @@ function buildTechDecisions(dir) {
2499
2512
  adrs: total,
2500
2513
  draft_adrs: draft,
2501
2514
  accepted_adrs: accepted,
2502
- candidate_list
2515
+ candidate_list,
2516
+ candidates_source: resolved.path,
2517
+ candidates_legacy_location: resolved.legacy,
2518
+ candidates_both_exist: resolved.bothExist
2503
2519
  };
2504
2520
  }
2505
2521
 
@@ -2715,7 +2731,18 @@ function buildProjectExplanation(dir) {
2715
2731
  suggestedNextSteps,
2716
2732
  readiness,
2717
2733
  nextStepRecommendation: readiness.nextStepRecommendation,
2718
- techDecisions: buildTechDecisions(dir)
2734
+ techDecisions: buildTechDecisions(dir),
2735
+ techKnowledge: {
2736
+ core: {
2737
+ currentState: exists(join(dir, "knowledge/tech/current-state.md")),
2738
+ codebase: exists(join(dir, "knowledge/tech/codebase.md"))
2739
+ },
2740
+ discovery: {
2741
+ architectureNotes: exists(join(dir, "knowledge/tech/discovery/architecture-notes.md")) || exists(join(dir, "knowledge/tech/architecture-notes.md")),
2742
+ decisionCandidates: exists(join(dir, "knowledge/tech/discovery/decision-candidates.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md")),
2743
+ legacyLocation: exists(join(dir, "knowledge/tech/architecture-notes.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md"))
2744
+ }
2745
+ }
2719
2746
  };
2720
2747
  }
2721
2748
  function stateLabel(state) {
@@ -2930,15 +2957,26 @@ function renderExplanationHuman(exp) {
2930
2957
  lines.push(r.recommended_next_step.label);
2931
2958
  lines.push("");
2932
2959
  const td = exp.techDecisions;
2933
- lines.push("## Tech Decisions");
2934
- lines.push(`- Decision candidates: ${td.candidates}`);
2935
- lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
2936
- lines.push(`- Status: ${td.status}`);
2960
+ const tk = exp.techKnowledge;
2961
+ const mark = (b) => b ? "\u2713" : "\u2717";
2962
+ lines.push("## Tech Knowledge");
2963
+ lines.push("Core:");
2964
+ lines.push(`- ${mark(tk.core.currentState)} current-state.md`);
2965
+ lines.push(`- ${mark(tk.core.codebase)} codebase.md`);
2966
+ lines.push("Decisions:");
2967
+ lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs}) \xB7 status: ${td.status}`);
2968
+ lines.push("Discovery:");
2969
+ lines.push(`- ${mark(tk.discovery.architectureNotes)} architecture-notes.md`);
2970
+ lines.push(`- ${mark(tk.discovery.decisionCandidates)} decision-candidates.md`);
2971
+ lines.push("");
2937
2972
  if (td.candidates > 0 && td.adrs === 0) {
2938
- lines.push("");
2939
2973
  lines.push("Use the adr-writing skill to materialize decision candidates into ADRs (`kaddo adr`) before implementing related technical Work Items.");
2974
+ lines.push("");
2975
+ }
2976
+ if (tk.discovery.legacyLocation) {
2977
+ lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
2978
+ lines.push("");
2940
2979
  }
2941
- lines.push("");
2942
2980
  return lines.join("\n").trimEnd() + "\n";
2943
2981
  }
2944
2982
  function renderExplanationAgent(exp) {
@@ -4125,6 +4163,18 @@ var RESOURCES = [
4125
4163
  }
4126
4164
  return [{ uri: "kaddo://roadmap-readiness", text: JSON.stringify(roadmapReadinessSummary(root), null, 2), mimeType: "application/json" }];
4127
4165
  }
4166
+ },
4167
+ {
4168
+ uri: "kaddo://tech-decisions",
4169
+ name: "Kaddo tech decisions",
4170
+ description: "Technical decision candidates vs materialized ADRs (status, counts, candidate list with suggested ADR filenames). Read-only; shares buildTechDecisions with `kaddo adr`.",
4171
+ mimeType: "application/json",
4172
+ read: (root) => {
4173
+ if (!hasKnowledge(root)) {
4174
+ return text("kaddo://tech-decisions", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
4175
+ }
4176
+ return [{ uri: "kaddo://tech-decisions", text: JSON.stringify(buildTechDecisions(root), null, 2), mimeType: "application/json" }];
4177
+ }
4128
4178
  }
4129
4179
  ];
4130
4180
 
@@ -4398,6 +4448,22 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
4398
4448
  if (techDecisions.candidates > 0 && techDecisions.adrs === 0) {
4399
4449
  missing.push(`${techDecisions.candidates} technical decision candidate(s) not yet materialized as ADRs (run \`kaddo adr\`).`);
4400
4450
  }
4451
+ const techFile = (rel) => exists(join(dir, ARCH_DIR3, rel));
4452
+ const techKnowledge = {
4453
+ core: {
4454
+ "current-state.md": techFile("tech/current-state.md"),
4455
+ "codebase.md": techFile("tech/codebase.md")
4456
+ },
4457
+ decisions: { adrs: techDecisions.adrs, dir: exists(join(dir, ARCH_DIR3, "tech/decisions")) },
4458
+ discovery: {
4459
+ "architecture-notes.md": techFile("tech/discovery/architecture-notes.md") || techFile("tech/architecture-notes.md"),
4460
+ "decision-candidates.md": techFile("tech/discovery/decision-candidates.md") || techFile("tech/decision-candidates.md"),
4461
+ legacyLocation: techFile("tech/architecture-notes.md") || techFile("tech/decision-candidates.md")
4462
+ }
4463
+ };
4464
+ if (techKnowledge.discovery.legacyLocation) {
4465
+ missing.push("Tech discovery files are in the legacy `knowledge/tech/` root. Run `kaddo tech organize` to move them to `knowledge/tech/discovery/`.");
4466
+ }
4401
4467
  const unifiedPhase = {
4402
4468
  ...phase,
4403
4469
  nextStep: nextStepRecommendation.label,
@@ -4436,6 +4502,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
4436
4502
  phase: unifiedPhase,
4437
4503
  nextStepRecommendation,
4438
4504
  techDecisions,
4505
+ techKnowledge,
4439
4506
  deliveryMix,
4440
4507
  external: loadExternalCapsules(dir),
4441
4508
  graph: loadGraphSummary(dir),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.40.0",
3
+ "version": "3.41.0",
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": {