@kaddo/mcp 3.44.0 → 3.45.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 +272 -95
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2282,7 +2282,124 @@ function buildAdapterStatuses(dir) {
2282
2282
  });
2283
2283
  }
2284
2284
 
2285
+ // ../cli/src/core/decisions.ts
2286
+ var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
2287
+ var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
2288
+ var DECISIONS_DIR = "knowledge/tech/decisions";
2289
+ function resolveCandidatesPath(dir) {
2290
+ const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
2291
+ const legacy = exists(join(dir, CANDIDATES_LEGACY));
2292
+ if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
2293
+ if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
2294
+ return { path: null, legacy: false, bothExist: false };
2295
+ }
2296
+ function cleanCandidateTitle(title) {
2297
+ return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
2298
+ }
2299
+ function slugify(s) {
2300
+ return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
2301
+ }
2302
+ function parseDecisionCandidates(md) {
2303
+ const out = [];
2304
+ for (const line of md.split(/\r?\n/)) {
2305
+ const m = line.match(/^##\s+(.+?)\s*$/);
2306
+ if (m) {
2307
+ const raw = m[1].trim();
2308
+ if (!raw || /^_.*_$/.test(raw)) continue;
2309
+ const t = cleanCandidateTitle(raw);
2310
+ if (t) out.push(t);
2311
+ }
2312
+ }
2313
+ return out;
2314
+ }
2315
+ function countAdrs(dir) {
2316
+ const base = join(dir, DECISIONS_DIR);
2317
+ if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
2318
+ let total = 0;
2319
+ let draft = 0;
2320
+ let accepted = 0;
2321
+ for (const entry of readDir(base)) {
2322
+ if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
2323
+ const full = join(base, entry);
2324
+ if (!isFile(full)) continue;
2325
+ total += 1;
2326
+ let content = "";
2327
+ try {
2328
+ content = readFile(full);
2329
+ } catch {
2330
+ continue;
2331
+ }
2332
+ const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
2333
+ if (status === "accepted") accepted += 1;
2334
+ else draft += 1;
2335
+ }
2336
+ return { total, draft, accepted };
2337
+ }
2338
+ function buildTechDecisions(dir) {
2339
+ const resolved = resolveCandidatesPath(dir);
2340
+ let titles = [];
2341
+ if (resolved.path) {
2342
+ try {
2343
+ titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
2344
+ } catch {
2345
+ titles = [];
2346
+ }
2347
+ }
2348
+ const { total, draft, accepted } = countAdrs(dir);
2349
+ const candidate_list = titles.map((title, i) => {
2350
+ const n = String(total + i + 1).padStart(3, "0");
2351
+ return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
2352
+ });
2353
+ let status;
2354
+ if (accepted > 0) status = "accepted-adrs";
2355
+ else if (total > 0) status = "draft-adrs";
2356
+ else if (titles.length > 0) status = "candidates";
2357
+ else status = "none";
2358
+ return {
2359
+ status,
2360
+ candidates: titles.length,
2361
+ adrs: total,
2362
+ draft_adrs: draft,
2363
+ accepted_adrs: accepted,
2364
+ candidate_list,
2365
+ candidates_source: resolved.path,
2366
+ candidates_legacy_location: resolved.legacy,
2367
+ candidates_both_exist: resolved.bothExist
2368
+ };
2369
+ }
2370
+
2285
2371
  // ../cli/src/core/next-step.ts
2372
+ function buildDeliveryState(dir) {
2373
+ const wis = discoverWorkItems(dir);
2374
+ const byState = (s) => wis.filter((w) => w.lifecycle === s).length;
2375
+ const total = wis.length;
2376
+ const withOwnership = wis.filter((w) => w.codeGlobs.length > 0).length;
2377
+ const td = buildTechDecisions(dir);
2378
+ const roadmapPath = join(dir, "knowledge/delivery/roadmap.md");
2379
+ const roadmapMd = exists(roadmapPath) ? safeRead(roadmapPath) : null;
2380
+ const stats = roadmapStats(roadmapMd, total);
2381
+ const adapters = installedAdapters(dir);
2382
+ return {
2383
+ phase: "",
2384
+ draft_work_items: byState("draft"),
2385
+ ready_work_items: byState("ready"),
2386
+ in_progress_work_items: byState("in-progress"),
2387
+ blocked_work_items: byState("blocked"),
2388
+ total_work_items: total,
2389
+ ownership_coverage: `${withOwnership}/${total}`,
2390
+ remaining_work_item_candidates: stats.remaining_work_item_candidates,
2391
+ decision_candidates: td.candidates,
2392
+ accepted_adrs: td.accepted_adrs,
2393
+ adapters_installed: adapters.length
2394
+ };
2395
+ }
2396
+ function safeRead(p) {
2397
+ try {
2398
+ return readFile(p);
2399
+ } catch {
2400
+ return null;
2401
+ }
2402
+ }
2286
2403
  function roadmapSignal(dir) {
2287
2404
  const p = join(dir, "knowledge/delivery/roadmap.md");
2288
2405
  if (!exists(p)) return "missing";
@@ -2379,15 +2496,117 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
2379
2496
  if (roadmap !== "has-candidates") {
2380
2497
  return { id: "roadmap", phase: "Planning", label: "Use roadmap-agent to define roadmap candidates (`kaddo roadmap`).", command: "kaddo roadmap", agent: "roadmap-agent", target: "knowledge/delivery/roadmap.md", reason: "The roadmap has no candidates yet." };
2381
2498
  }
2382
- const wi = workItemsSignal(dir);
2383
- if (wi === "none" || wi === "none-ready") {
2384
- return { id: "create-work-item", phase: "Delivery Preparation", label: "Run `kaddo create --from roadmap` to materialize the first Work Item.", command: "kaddo create --from roadmap", reason: "The roadmap has candidates but no Work Item is ready." };
2499
+ const st = buildDeliveryState(dir);
2500
+ const secondary = buildSecondaryRecommendations(st);
2501
+ if (st.total_work_items === 0) {
2502
+ return {
2503
+ id: "create-work-item",
2504
+ phase: "Delivery Preparation",
2505
+ label: "Run `kaddo create --from roadmap` to materialize the first Work Item.",
2506
+ command: "kaddo create --from roadmap",
2507
+ reason: "The roadmap has candidates but no Work Item exists yet.",
2508
+ ...secondary.length ? { secondary } : {}
2509
+ };
2385
2510
  }
2386
- const adapters = installedAdapters(dir);
2387
- if (adapters.length === 0) {
2388
- return { id: "install-adapter", phase: "Active Delivery", label: "Run `kaddo adapters list` and install the adapter for your preferred agent.", command: "kaddo adapters list", reason: "A Work Item is ready but no adapter is installed." };
2511
+ const adapters = st.adapters_installed;
2512
+ if (st.ready_work_items > 0) {
2513
+ if (adapters === 0) {
2514
+ return {
2515
+ id: "install-adapter",
2516
+ phase: "Active Delivery",
2517
+ label: "Install or configure an adapter before implementation (`kaddo adapters list`).",
2518
+ command: "kaddo adapters list",
2519
+ reason: `${st.ready_work_items} Work Item(s) are ready but no adapter is installed.`,
2520
+ ...secondary.length ? { secondary } : {}
2521
+ };
2522
+ }
2523
+ return {
2524
+ id: "implement",
2525
+ phase: "Active Delivery",
2526
+ label: "Use the implementation-agent or your installed adapter to plan implementation.",
2527
+ agent: "implementation-agent",
2528
+ reason: `${st.ready_work_items} Work Item(s) are ready and an adapter is installed.`,
2529
+ ...secondary.length ? { secondary } : {}
2530
+ };
2531
+ }
2532
+ if (st.in_progress_work_items > 0) {
2533
+ return {
2534
+ id: "guard",
2535
+ phase: "Active Delivery",
2536
+ label: "Run `kaddo guard` and update affected knowledge after significant changes.",
2537
+ command: "kaddo guard",
2538
+ reason: `${st.in_progress_work_items} Work Item(s) are in progress.`,
2539
+ ...secondary.length ? { secondary } : {}
2540
+ };
2541
+ }
2542
+ if (st.draft_work_items > 0) {
2543
+ return {
2544
+ id: "refine-work-item",
2545
+ phase: "Active Delivery",
2546
+ label: "Refine the existing draft Work Item with the work-item-agent.",
2547
+ agent: "work-item-agent",
2548
+ skill: "work-item-refinement",
2549
+ reason: `There ${st.draft_work_items === 1 ? "is" : "are"} ${st.draft_work_items} draft Work Item${st.draft_work_items === 1 ? "" : "s"} and no Work Item is ready.`,
2550
+ ...secondary.length ? { secondary } : {}
2551
+ };
2552
+ }
2553
+ if (st.blocked_work_items > 0) {
2554
+ return {
2555
+ id: "resolve-blocker",
2556
+ phase: "Active Delivery",
2557
+ label: "Resolve the blocker on the blocked Work Item with the work-item-agent.",
2558
+ agent: "work-item-agent",
2559
+ reason: `${st.blocked_work_items} Work Item(s) are blocked.`,
2560
+ ...secondary.length ? { secondary } : {}
2561
+ };
2562
+ }
2563
+ if (st.remaining_work_item_candidates > 0) {
2564
+ return {
2565
+ id: "materialize-more-work-items",
2566
+ phase: "Maintenance",
2567
+ label: `Materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
2568
+ command: "kaddo create --from roadmap",
2569
+ reason: "No active Work Items remain; roadmap candidates are still pending.",
2570
+ ...secondary.length ? { secondary } : {}
2571
+ };
2389
2572
  }
2390
- return { id: "implement", phase: "Active Delivery", label: "Implement the ready Work Item and run `kaddo guard`.", reason: "A Work Item is ready and an adapter is installed." };
2573
+ return {
2574
+ id: "plan-next",
2575
+ phase: "Maintenance",
2576
+ label: "Use the roadmap-agent to plan the next initiative.",
2577
+ agent: "roadmap-agent",
2578
+ reason: "No active Work Items and no remaining roadmap candidates."
2579
+ };
2580
+ }
2581
+ function buildSecondaryRecommendations(st) {
2582
+ const out = [];
2583
+ const [withOwnership] = st.ownership_coverage.split("/").map(Number);
2584
+ if (st.total_work_items > 0 && withOwnership < st.total_work_items) {
2585
+ out.push({
2586
+ id: "suggest-ownership",
2587
+ label: "Run `kaddo owners suggest` for Work Items without code ownership.",
2588
+ command: "kaddo owners suggest",
2589
+ reason: `Ownership coverage is ${st.ownership_coverage}.`
2590
+ });
2591
+ }
2592
+ if (st.total_work_items > 0 && st.decision_candidates > 0 && st.accepted_adrs === 0) {
2593
+ out.push({
2594
+ id: "materialize-adrs",
2595
+ label: "Use the adr-writing skill (`kaddo adr`) to materialize decision candidates into ADRs before implementing related technical Work Items.",
2596
+ command: "kaddo adr",
2597
+ skill: "adr-writing",
2598
+ reason: `There are ${st.decision_candidates} technical decision candidate(s) and ${st.accepted_adrs} accepted ADR(s).`
2599
+ });
2600
+ }
2601
+ if (st.total_work_items > 0 && st.remaining_work_item_candidates > 0) {
2602
+ out.push({
2603
+ id: "materialize-more-work-items",
2604
+ label: `Later, materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
2605
+ command: "kaddo create --from roadmap",
2606
+ reason: `There are ${st.remaining_work_item_candidates} remaining Work Item candidate(s).`
2607
+ });
2608
+ }
2609
+ return out;
2391
2610
  }
2392
2611
 
2393
2612
  // ../cli/src/core/readiness.ts
@@ -2482,92 +2701,6 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
2482
2701
  };
2483
2702
  }
2484
2703
 
2485
- // ../cli/src/core/decisions.ts
2486
- var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
2487
- var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
2488
- var DECISIONS_DIR = "knowledge/tech/decisions";
2489
- function resolveCandidatesPath(dir) {
2490
- const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
2491
- const legacy = exists(join(dir, CANDIDATES_LEGACY));
2492
- if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
2493
- if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
2494
- return { path: null, legacy: false, bothExist: false };
2495
- }
2496
- function cleanCandidateTitle(title) {
2497
- return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
2498
- }
2499
- function slugify(s) {
2500
- return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
2501
- }
2502
- function parseDecisionCandidates(md) {
2503
- const out = [];
2504
- for (const line of md.split(/\r?\n/)) {
2505
- const m = line.match(/^##\s+(.+?)\s*$/);
2506
- if (m) {
2507
- const raw = m[1].trim();
2508
- if (!raw || /^_.*_$/.test(raw)) continue;
2509
- const t = cleanCandidateTitle(raw);
2510
- if (t) out.push(t);
2511
- }
2512
- }
2513
- return out;
2514
- }
2515
- function countAdrs(dir) {
2516
- const base = join(dir, DECISIONS_DIR);
2517
- if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
2518
- let total = 0;
2519
- let draft = 0;
2520
- let accepted = 0;
2521
- for (const entry of readDir(base)) {
2522
- if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
2523
- const full = join(base, entry);
2524
- if (!isFile(full)) continue;
2525
- total += 1;
2526
- let content = "";
2527
- try {
2528
- content = readFile(full);
2529
- } catch {
2530
- continue;
2531
- }
2532
- const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
2533
- if (status === "accepted") accepted += 1;
2534
- else draft += 1;
2535
- }
2536
- return { total, draft, accepted };
2537
- }
2538
- function buildTechDecisions(dir) {
2539
- const resolved = resolveCandidatesPath(dir);
2540
- let titles = [];
2541
- if (resolved.path) {
2542
- try {
2543
- titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
2544
- } catch {
2545
- titles = [];
2546
- }
2547
- }
2548
- const { total, draft, accepted } = countAdrs(dir);
2549
- const candidate_list = titles.map((title, i) => {
2550
- const n = String(total + i + 1).padStart(3, "0");
2551
- return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
2552
- });
2553
- let status;
2554
- if (accepted > 0) status = "accepted-adrs";
2555
- else if (total > 0) status = "draft-adrs";
2556
- else if (titles.length > 0) status = "candidates";
2557
- else status = "none";
2558
- return {
2559
- status,
2560
- candidates: titles.length,
2561
- adrs: total,
2562
- draft_adrs: draft,
2563
- accepted_adrs: accepted,
2564
- candidate_list,
2565
- candidates_source: resolved.path,
2566
- candidates_legacy_location: resolved.legacy,
2567
- candidates_both_exist: resolved.bothExist
2568
- };
2569
- }
2570
-
2571
2704
  // ../cli/src/core/assets.ts
2572
2705
  import matter6 from "gray-matter";
2573
2706
 
@@ -5792,9 +5925,12 @@ function renderExplanationHuman(exp) {
5792
5925
  }
5793
5926
  lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
5794
5927
  lines.push("");
5795
- if (exp.suggestedNextSteps.length > 0) {
5928
+ const rec = exp.nextStepRecommendation;
5929
+ const secondary = rec.secondary ?? [];
5930
+ const steps = secondary.length > 0 || /Delivery|Active|Maintenance/.test(rec.phase) ? [rec.label, ...secondary.map((s2) => s2.label)] : exp.suggestedNextSteps;
5931
+ if (steps.length > 0) {
5796
5932
  lines.push("## Suggested Next Steps");
5797
- exp.suggestedNextSteps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
5933
+ steps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
5798
5934
  lines.push("");
5799
5935
  }
5800
5936
  const r = exp.readiness;
@@ -7134,6 +7270,20 @@ var RESOURCES = [
7134
7270
  const candidates = md ? parseRoadmapCandidates(md) : [];
7135
7271
  return [{ uri: "kaddo://work-item-candidates", text: JSON.stringify({ candidates }, null, 2), mimeType: "application/json" }];
7136
7272
  }
7273
+ },
7274
+ {
7275
+ uri: "kaddo://next-step",
7276
+ name: "Kaddo next step",
7277
+ description: "State-aware next-step recommendation for the current delivery state, with parallel (secondary) recommendations and the delivery-state counts behind it (VS-079). Read-only.",
7278
+ mimeType: "application/json",
7279
+ read: (root) => {
7280
+ if (!hasKnowledge(root)) {
7281
+ return text("kaddo://next-step", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
7282
+ }
7283
+ const nextStepRecommendation = resolveNextStep(root);
7284
+ const deliveryState = { ...buildDeliveryState(root), phase: nextStepRecommendation.phase };
7285
+ return [{ uri: "kaddo://next-step", text: JSON.stringify({ nextStepRecommendation, deliveryState }, null, 2), mimeType: "application/json" }];
7286
+ }
7137
7287
  }
7138
7288
  ];
7139
7289
 
@@ -7428,6 +7578,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
7428
7578
  nextStep: nextStepRecommendation.label,
7429
7579
  recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
7430
7580
  };
7581
+ const deliveryState = { ...buildDeliveryState(dir), phase: nextStepRecommendation.phase };
7431
7582
  return {
7432
7583
  version: CONTEXT_PACK_VERSION,
7433
7584
  generatedAt: now.toISOString(),
@@ -7460,6 +7611,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
7460
7611
  roadmap,
7461
7612
  phase: unifiedPhase,
7462
7613
  nextStepRecommendation,
7614
+ deliveryState,
7463
7615
  techDecisions,
7464
7616
  techKnowledge,
7465
7617
  roadmapQuality: buildRoadmapQuality(dir),
@@ -7524,6 +7676,31 @@ function renderContextPack(pack) {
7524
7676
  parts.push(`Next step: ${pack.phase.nextStep}
7525
7677
  `);
7526
7678
  }
7679
+ const ds = pack.deliveryState;
7680
+ const rec = pack.nextStepRecommendation;
7681
+ if (ds.total_work_items > 0 || rec.phase !== "Setup") {
7682
+ parts.push("## Delivery State\n");
7683
+ parts.push(
7684
+ [
7685
+ `- Phase: ${ds.phase}`,
7686
+ `- Draft Work Items: ${ds.draft_work_items}`,
7687
+ `- Ready Work Items: ${ds.ready_work_items}`,
7688
+ `- In-progress Work Items: ${ds.in_progress_work_items}`,
7689
+ `- Ownership coverage: ${ds.ownership_coverage}`,
7690
+ `- Remaining Work Item candidates: ${ds.remaining_work_item_candidates}`
7691
+ ].join("\n") + "\n"
7692
+ );
7693
+ parts.push("## Next Step Recommendation\n");
7694
+ const recLines = [`- ${rec.label}`, ` - id: ${rec.id}`, ` - reason: ${rec.reason}`];
7695
+ if (rec.agent) recLines.push(` - agent: ${rec.agent}`);
7696
+ if (rec.skill) recLines.push(` - skill: ${rec.skill}`);
7697
+ if (rec.command) recLines.push(` - command: \`${rec.command}\``);
7698
+ parts.push(recLines.join("\n") + "\n");
7699
+ if (rec.secondary && rec.secondary.length > 0) {
7700
+ parts.push("Also (secondary):\n");
7701
+ parts.push(rec.secondary.map((s) => `- ${s.label}`).join("\n") + "\n");
7702
+ }
7703
+ }
7527
7704
  parts.push("## Knowledge Layers\n");
7528
7705
  parts.push(
7529
7706
  "Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.44.0",
3
+ "version": "3.45.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": {