@kaddo/mcp 3.43.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 +440 -139
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1629,7 +1629,12 @@ function splitInitiatives(markdown) {
1629
1629
  return blocks;
1630
1630
  }
1631
1631
  function parseBlock(block) {
1632
- const meta = { relatedCapabilities: [], dependencies: [], openQuestions: [] };
1632
+ const meta = {
1633
+ relatedCapabilities: [],
1634
+ dependencies: [],
1635
+ openQuestions: [],
1636
+ sourceSignals: []
1637
+ };
1633
1638
  const rawCandidates = [];
1634
1639
  let listField = null;
1635
1640
  let inCandidateSection = false;
@@ -1679,7 +1684,7 @@ function parseBlock(block) {
1679
1684
  meta.impact = value || void 0;
1680
1685
  } else if (key === "risk") {
1681
1686
  meta.risk = value || void 0;
1682
- } else if (key === "project area / domain" || key === "domain" || key === "project area") {
1687
+ } else if (key === "project area / domain" || key === "domain" || key === "project area" || key === "related domain") {
1683
1688
  meta.domain = value || void 0;
1684
1689
  } else if (key === "related capabilities") {
1685
1690
  listField = "relatedCapabilities";
@@ -1690,6 +1695,9 @@ function parseBlock(block) {
1690
1695
  } else if (key === "open questions") {
1691
1696
  listField = "openQuestions";
1692
1697
  if (value) meta.openQuestions.push(value);
1698
+ } else if (key === "source signals" || key === "source signal") {
1699
+ listField = "sourceSignals";
1700
+ if (value) meta.sourceSignals.push(value);
1693
1701
  }
1694
1702
  continue;
1695
1703
  }
@@ -1707,6 +1715,7 @@ function parseBlock(block) {
1707
1715
  }
1708
1716
  }
1709
1717
  flush();
1718
+ const decisionCandidates = decisionCandidatesFromSignals(meta.sourceSignals);
1710
1719
  return rawCandidates.map((c) => ({
1711
1720
  ...c,
1712
1721
  initiative: block.initiative.id || block.initiative.title ? { ...block.initiative } : void 0,
@@ -1715,9 +1724,23 @@ function parseBlock(block) {
1715
1724
  impact: meta.impact,
1716
1725
  risk: meta.risk,
1717
1726
  dependencies: meta.dependencies.length ? [...meta.dependencies] : void 0,
1718
- openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0
1727
+ openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0,
1728
+ sourceSignals: meta.sourceSignals.length ? [...meta.sourceSignals] : void 0,
1729
+ decisionCandidates: decisionCandidates.length ? decisionCandidates : void 0
1719
1730
  }));
1720
1731
  }
1732
+ function decisionCandidatesFromSignals(signals) {
1733
+ const out = [];
1734
+ for (const sig of signals) {
1735
+ const m = sig.match(/decision candidate\s*:?\s*(.+)$/i);
1736
+ if (!m) continue;
1737
+ const rest = m[1].trim();
1738
+ const token = rest.match(/\b[A-Z][A-Z0-9_]{2,}\b/);
1739
+ const value = token ? token[0] : rest.replace(/[.。]+$/, "").trim();
1740
+ if (value && !out.includes(value)) out.push(value);
1741
+ }
1742
+ return out;
1743
+ }
1721
1744
  var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
1722
1745
  var HEADING_RE = /^#{2,4}\s+(.*)$/;
1723
1746
  var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
@@ -1799,10 +1822,36 @@ function parseRoadmapCandidates(markdown) {
1799
1822
  if (strict.length > 0) return strict;
1800
1823
  return parseFlexible(markdown);
1801
1824
  }
1825
+ function countRoadmapInitiatives(markdown) {
1826
+ if (markdown == null) return 0;
1827
+ let n = 0;
1828
+ for (const line of markdown.split(/\r?\n/)) if (INITIATIVE_RE.test(line)) n++;
1829
+ return n;
1830
+ }
1802
1831
  function roadmapStats(markdown, materialized) {
1803
- if (markdown == null) return { present: false, candidates: 0, materialized, remaining: 0 };
1832
+ if (markdown == null)
1833
+ return {
1834
+ present: false,
1835
+ initiatives: 0,
1836
+ work_item_candidates: 0,
1837
+ materialized_work_items: materialized,
1838
+ remaining_work_item_candidates: 0,
1839
+ candidates: 0,
1840
+ materialized,
1841
+ remaining: 0
1842
+ };
1804
1843
  const candidates = parseRoadmapCandidates(markdown).length;
1805
- return { present: true, candidates, materialized, remaining: Math.max(0, candidates - materialized) };
1844
+ const remaining = Math.max(0, candidates - materialized);
1845
+ return {
1846
+ present: true,
1847
+ initiatives: countRoadmapInitiatives(markdown),
1848
+ work_item_candidates: candidates,
1849
+ materialized_work_items: materialized,
1850
+ remaining_work_item_candidates: remaining,
1851
+ candidates,
1852
+ materialized,
1853
+ remaining
1854
+ };
1806
1855
  }
1807
1856
 
1808
1857
  // ../cli/src/core/open-questions.ts
@@ -2233,7 +2282,124 @@ function buildAdapterStatuses(dir) {
2233
2282
  });
2234
2283
  }
2235
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
+
2236
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
+ }
2237
2403
  function roadmapSignal(dir) {
2238
2404
  const p = join(dir, "knowledge/delivery/roadmap.md");
2239
2405
  if (!exists(p)) return "missing";
@@ -2330,15 +2496,117 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
2330
2496
  if (roadmap !== "has-candidates") {
2331
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." };
2332
2498
  }
2333
- const wi = workItemsSignal(dir);
2334
- if (wi === "none" || wi === "none-ready") {
2335
- 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
+ };
2336
2510
  }
2337
- const adapters = installedAdapters(dir);
2338
- if (adapters.length === 0) {
2339
- 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
+ };
2572
+ }
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
+ });
2340
2591
  }
2341
- 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." };
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;
2342
2610
  }
2343
2611
 
2344
2612
  // ../cli/src/core/readiness.ts
@@ -2433,92 +2701,6 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
2433
2701
  };
2434
2702
  }
2435
2703
 
2436
- // ../cli/src/core/decisions.ts
2437
- var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
2438
- var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
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
- }
2450
- function slugify(s) {
2451
- return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
2452
- }
2453
- function parseDecisionCandidates(md) {
2454
- const out = [];
2455
- for (const line of md.split(/\r?\n/)) {
2456
- const m = line.match(/^##\s+(.+?)\s*$/);
2457
- if (m) {
2458
- const raw = m[1].trim();
2459
- if (!raw || /^_.*_$/.test(raw)) continue;
2460
- const t = cleanCandidateTitle(raw);
2461
- if (t) out.push(t);
2462
- }
2463
- }
2464
- return out;
2465
- }
2466
- function countAdrs(dir) {
2467
- const base = join(dir, DECISIONS_DIR);
2468
- if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
2469
- let total = 0;
2470
- let draft = 0;
2471
- let accepted = 0;
2472
- for (const entry of readDir(base)) {
2473
- if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
2474
- const full = join(base, entry);
2475
- if (!isFile(full)) continue;
2476
- total += 1;
2477
- let content = "";
2478
- try {
2479
- content = readFile(full);
2480
- } catch {
2481
- continue;
2482
- }
2483
- const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
2484
- if (status === "accepted") accepted += 1;
2485
- else draft += 1;
2486
- }
2487
- return { total, draft, accepted };
2488
- }
2489
- function buildTechDecisions(dir) {
2490
- const resolved = resolveCandidatesPath(dir);
2491
- let titles = [];
2492
- if (resolved.path) {
2493
- try {
2494
- titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
2495
- } catch {
2496
- titles = [];
2497
- }
2498
- }
2499
- const { total, draft, accepted } = countAdrs(dir);
2500
- const candidate_list = titles.map((title, i) => {
2501
- const n = String(total + i + 1).padStart(3, "0");
2502
- return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
2503
- });
2504
- let status;
2505
- if (accepted > 0) status = "accepted-adrs";
2506
- else if (total > 0) status = "draft-adrs";
2507
- else if (titles.length > 0) status = "candidates";
2508
- else status = "none";
2509
- return {
2510
- status,
2511
- candidates: titles.length,
2512
- adrs: total,
2513
- draft_adrs: draft,
2514
- accepted_adrs: accepted,
2515
- candidate_list,
2516
- candidates_source: resolved.path,
2517
- candidates_legacy_location: resolved.legacy,
2518
- candidates_both_exist: resolved.bothExist
2519
- };
2520
- }
2521
-
2522
2704
  // ../cli/src/core/assets.ts
2523
2705
  import matter6 from "gray-matter";
2524
2706
 
@@ -4015,13 +4197,22 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
4015
4197
  without surfacing it. -->
4016
4198
  \`\`\`
4017
4199
 
4018
- ### Preserve roadmap metadata (VS-077)
4200
+ ### Preserve roadmap metadata (VS-077 / VS-078)
4201
+
4202
+ When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries the trace
4203
+ back to the roadmap. **Keep and refine \u2014 never delete** these fields:
4019
4204
 
4020
- When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries
4021
- \`source_roadmap_candidate\`, \`related_domain\`, \`related_capability\` (+ \`related_capabilities\`),
4022
- \`knowledge_level\`, \`expected_value\`, \`risks\` and \`dependencies\`. **Keep and refine** this metadata \u2014
4023
- do not drop the trace back to the capability domain and source signals. Add \`related_decisions\` /
4024
- \`decision_candidates\` when the work depends on a technical decision.
4205
+ - \`source_roadmap_initiative\` and \`source_work_item_candidate\` (the RM-xxx initiative and the
4206
+ WI-CANDIDATE-xxx it was materialized from)
4207
+ - \`related_domain\` and \`domains\` (keep them consistent \u2014 \`domains\` must not be empty when
4208
+ \`related_domain\` exists)
4209
+ - \`related_capabilities\` (a real list, one capability per item \u2014 never a single comma-joined string)
4210
+ - \`expected_value\`, \`risks\`, \`dependencies\`
4211
+ - \`source_signals\` (do **not** invent them \u2014 if absent, leave them absent)
4212
+ - \`decision_candidates\` and \`related_decisions\` (when the work depends on a technical decision)
4213
+
4214
+ Do not drop the trace back to the capability domain and source signals. If a Work Item depends on a
4215
+ tech decision candidate with no ADR yet, keep the warning surfaced in the body.
4025
4216
 
4026
4217
  ## Where to Save the Result
4027
4218
 
@@ -5304,16 +5495,25 @@ function buildRoadmapQuality(dir) {
5304
5495
  const items = parseRoadmapCandidateQuality(md);
5305
5496
  const count = (pred) => items.filter(pred).length;
5306
5497
  const grounded = count((i) => i.grounded);
5307
- return {
5308
- candidates: items.length,
5498
+ const initiatives = {
5499
+ total: items.length,
5309
5500
  grounded,
5310
5501
  with_related_domain: count((i) => i.hasRelatedDomain),
5311
5502
  with_related_capability: count((i) => i.hasRelatedCapability),
5312
5503
  with_source_signals: count((i) => i.hasSourceSignals),
5313
- // Only "needs refinement" when there are candidates and at least one isn't grounded.
5504
+ // Only "needs refinement" when there are initiatives and at least one isn't grounded.
5314
5505
  needs_refinement: items.length > 0 && grounded < items.length,
5315
5506
  items
5316
5507
  };
5508
+ const wiCandidates = md ? parseRoadmapCandidates(md) : [];
5509
+ const wcount = (pred) => wiCandidates.filter(pred).length;
5510
+ const work_item_candidates = {
5511
+ total: wiCandidates.length,
5512
+ with_source_initiative: wcount((c) => Boolean(c.initiative?.id || c.initiative?.title)),
5513
+ with_related_domain: wcount((c) => Boolean(c.domain)),
5514
+ with_related_capability: wcount((c) => Boolean(c.relatedCapabilities?.length))
5515
+ };
5516
+ return { initiatives, work_item_candidates };
5317
5517
  }
5318
5518
 
5319
5519
  // ../cli/src/core/project-explain.ts
@@ -5596,10 +5796,11 @@ function renderExplanationHuman(exp) {
5596
5796
  lines.push(`- Delivery: ${ls("Delivery")}`);
5597
5797
  lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
5598
5798
  if (exp.roadmap.present) {
5599
- lines.push(`- Roadmap candidates: ${exp.roadmap.candidates}`);
5600
- lines.push(`- Materialized work items: ${exp.roadmap.materialized}`);
5601
- if (exp.roadmap.remaining > 0)
5602
- lines.push(`- Remaining candidates: ${exp.roadmap.remaining}`);
5799
+ lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
5800
+ lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
5801
+ lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
5802
+ if (exp.roadmap.remaining_work_item_candidates > 0)
5803
+ lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
5603
5804
  } else {
5604
5805
  lines.push(`- Work items: ${exp.workItems.total}`);
5605
5806
  }
@@ -5724,9 +5925,12 @@ function renderExplanationHuman(exp) {
5724
5925
  }
5725
5926
  lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
5726
5927
  lines.push("");
5727
- 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) {
5728
5932
  lines.push("## Suggested Next Steps");
5729
- exp.suggestedNextSteps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
5933
+ steps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
5730
5934
  lines.push("");
5731
5935
  }
5732
5936
  const r = exp.readiness;
@@ -5776,17 +5980,42 @@ function renderExplanationHuman(exp) {
5776
5980
  lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
5777
5981
  lines.push("");
5778
5982
  }
5983
+ if (exp.roadmap.present) {
5984
+ lines.push("## Roadmap Status");
5985
+ lines.push(`- Initiatives: ${exp.roadmap.initiatives}`);
5986
+ lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
5987
+ lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
5988
+ lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
5989
+ lines.push("");
5990
+ }
5779
5991
  const rq = exp.roadmapQuality;
5780
- if (rq.candidates > 0) {
5992
+ const rqi = rq.initiatives;
5993
+ const rqw = rq.work_item_candidates;
5994
+ if (rqi.total > 0 || rqw.total > 0) {
5781
5995
  lines.push("## Roadmap Quality");
5782
- lines.push(`- Candidates: ${rq.candidates}`);
5783
- lines.push(`- Grounded: ${rq.grounded}/${rq.candidates}`);
5784
- lines.push(`- With related domain: ${rq.with_related_domain}/${rq.candidates}`);
5785
- lines.push(`- With related capability: ${rq.with_related_capability}/${rq.candidates}`);
5786
- lines.push(`- With source signals: ${rq.with_source_signals}/${rq.candidates}`);
5787
- if (rq.needs_refinement) {
5996
+ if (rqi.total > 0) {
5997
+ lines.push("Initiatives:");
5998
+ lines.push(`- Candidates evaluated: ${rqi.total}`);
5999
+ lines.push(`- Grounded: ${rqi.grounded}/${rqi.total}`);
6000
+ lines.push(`- With related domain: ${rqi.with_related_domain}/${rqi.total}`);
6001
+ lines.push(`- With related capability: ${rqi.with_related_capability}/${rqi.total}`);
6002
+ lines.push(`- With source signals: ${rqi.with_source_signals}/${rqi.total}`);
6003
+ }
6004
+ if (rqw.total > 0) {
5788
6005
  lines.push("");
5789
- lines.push("Roadmap quality: needs refinement. Suggested: use roadmap-agent to add domain / capability / source signals.");
6006
+ lines.push("Work Item Candidates:");
6007
+ lines.push(`- Candidates: ${rqw.total}`);
6008
+ lines.push(`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`);
6009
+ lines.push(`- With related domain: ${rqw.with_related_domain}/${rqw.total}`);
6010
+ lines.push(`- With related capability: ${rqw.with_related_capability}/${rqw.total}`);
6011
+ }
6012
+ if (rqi.needs_refinement) {
6013
+ lines.push("");
6014
+ lines.push("Roadmap quality: needs refinement.");
6015
+ lines.push("Suggested: use roadmap-agent to add domain / capability / source signals.");
6016
+ } else if (rqw.total > 0) {
6017
+ lines.push("");
6018
+ lines.push("Work Item candidate quality: good.");
5790
6019
  }
5791
6020
  lines.push("");
5792
6021
  }
@@ -7019,7 +7248,7 @@ var RESOURCES = [
7019
7248
  {
7020
7249
  uri: "kaddo://roadmap-quality",
7021
7250
  name: "Kaddo roadmap quality",
7022
- description: "How well roadmap candidates are grounded in capability domains, related capabilities and source signals (VS-077). Read-only.",
7251
+ description: "How well roadmap initiatives and Work Item candidates are grounded in capability domains, related capabilities and source signals (VS-077 / VS-077.1). Two levels: initiatives + work_item_candidates. Read-only.",
7023
7252
  mimeType: "application/json",
7024
7253
  read: (root) => {
7025
7254
  if (!hasKnowledge(root)) {
@@ -7027,6 +7256,34 @@ var RESOURCES = [
7027
7256
  }
7028
7257
  return [{ uri: "kaddo://roadmap-quality", text: JSON.stringify(buildRoadmapQuality(root), null, 2), mimeType: "application/json" }];
7029
7258
  }
7259
+ },
7260
+ {
7261
+ uri: "kaddo://work-item-candidates",
7262
+ name: "Kaddo work item candidates",
7263
+ description: "Materializable Work Item candidates (WI-CANDIDATE-xxx) parsed from the roadmap, with their source initiative and inherited metadata (VS-078). Read-only.",
7264
+ mimeType: "application/json",
7265
+ read: (root) => {
7266
+ if (!hasKnowledge(root)) {
7267
+ return text("kaddo://work-item-candidates", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
7268
+ }
7269
+ const md = readText(root, "knowledge/delivery/roadmap.md");
7270
+ const candidates = md ? parseRoadmapCandidates(md) : [];
7271
+ return [{ uri: "kaddo://work-item-candidates", text: JSON.stringify({ candidates }, null, 2), mimeType: "application/json" }];
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
+ }
7030
7287
  }
7031
7288
  ];
7032
7289
 
@@ -7321,6 +7578,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
7321
7578
  nextStep: nextStepRecommendation.label,
7322
7579
  recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
7323
7580
  };
7581
+ const deliveryState = { ...buildDeliveryState(dir), phase: nextStepRecommendation.phase };
7324
7582
  return {
7325
7583
  version: CONTEXT_PACK_VERSION,
7326
7584
  generatedAt: now.toISOString(),
@@ -7353,6 +7611,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
7353
7611
  roadmap,
7354
7612
  phase: unifiedPhase,
7355
7613
  nextStepRecommendation,
7614
+ deliveryState,
7356
7615
  techDecisions,
7357
7616
  techKnowledge,
7358
7617
  roadmapQuality: buildRoadmapQuality(dir),
@@ -7417,6 +7676,31 @@ function renderContextPack(pack) {
7417
7676
  parts.push(`Next step: ${pack.phase.nextStep}
7418
7677
  `);
7419
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
+ }
7420
7704
  parts.push("## Knowledge Layers\n");
7421
7705
  parts.push(
7422
7706
  "Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
@@ -7454,35 +7738,52 @@ function renderContextPack(pack) {
7454
7738
  }
7455
7739
  parts.push("## Current Knowledge\n");
7456
7740
  parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
7457
- parts.push("## Roadmap\n");
7741
+ parts.push("## Roadmap Status\n");
7458
7742
  if (pack.roadmap.present) {
7459
7743
  parts.push(
7460
7744
  [
7461
- `- Roadmap candidates: ${pack.roadmap.candidates}`,
7462
- `- Materialized work items: ${pack.roadmap.materialized}`,
7463
- `- Remaining candidates: ${pack.roadmap.remaining}`
7745
+ `- Initiatives: ${pack.roadmap.initiatives}`,
7746
+ `- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
7747
+ `- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
7748
+ `- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
7464
7749
  ].join("\n") + "\n"
7465
7750
  );
7466
- if (pack.roadmap.remaining > 0) {
7751
+ if (pack.roadmap.remaining_work_item_candidates > 0) {
7467
7752
  parts.push(
7468
- "Candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
7753
+ "Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
7469
7754
  );
7470
7755
  }
7471
7756
  }
7472
7757
  parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
7473
7758
  const rq = pack.roadmapQuality;
7474
- if (rq.candidates > 0) {
7759
+ const rqi = rq.initiatives;
7760
+ const rqw = rq.work_item_candidates;
7761
+ if (rqi.total > 0 || rqw.total > 0) {
7475
7762
  parts.push("## Roadmap Quality\n");
7476
- parts.push(
7477
- [
7478
- `- Candidates: ${rq.candidates}`,
7479
- `- Grounded: ${rq.grounded}/${rq.candidates}`,
7480
- `- With related domain: ${rq.with_related_domain}/${rq.candidates}`,
7481
- `- With related capability: ${rq.with_related_capability}/${rq.candidates}`,
7482
- `- With source signals: ${rq.with_source_signals}/${rq.candidates}`
7483
- ].join("\n") + "\n"
7484
- );
7485
- if (rq.needs_refinement) {
7763
+ if (rqi.total > 0) {
7764
+ parts.push(
7765
+ [
7766
+ "Initiatives:",
7767
+ `- Candidates evaluated: ${rqi.total}`,
7768
+ `- Grounded: ${rqi.grounded}/${rqi.total}`,
7769
+ `- With related domain: ${rqi.with_related_domain}/${rqi.total}`,
7770
+ `- With related capability: ${rqi.with_related_capability}/${rqi.total}`,
7771
+ `- With source signals: ${rqi.with_source_signals}/${rqi.total}`
7772
+ ].join("\n") + "\n"
7773
+ );
7774
+ }
7775
+ if (rqw.total > 0) {
7776
+ parts.push(
7777
+ [
7778
+ "Work Item Candidates:",
7779
+ `- Candidates: ${rqw.total}`,
7780
+ `- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`,
7781
+ `- With related domain: ${rqw.with_related_domain}/${rqw.total}`,
7782
+ `- With related capability: ${rqw.with_related_capability}/${rqw.total}`
7783
+ ].join("\n") + "\n"
7784
+ );
7785
+ }
7786
+ if (rqi.needs_refinement) {
7486
7787
  parts.push(
7487
7788
  "Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
7488
7789
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.43.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": {