@kaddo/cli 3.42.0 → 3.44.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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +361 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -541,6 +541,8 @@ create --from roadmap → owners → guard → explain`.
541
541
  | v3.40.1 | ADR slug cleanup + MCP: suggested ADR filenames strip list/heading prefixes (no `ADR-001-1-…`) and normalize acronyms; new read-only MCP resource `kaddo://tech-decisions` sharing `buildTechDecisions` with `kaddo adr` |
542
542
  | v3.41 | Tech knowledge structure: `knowledge/tech/discovery/` for architecture-notes/decision-candidates (core vs decisions vs discovery); `kaddo adr` reads discovery-first with legacy fallback; `kaddo tech organize` migrates safely; `explain` shows `## Tech Knowledge` |
543
543
  | v3.42 | Agent & skill version metadata: installed agents/skills carry a `version:`; `kaddo agents status` / `kaddo skills status` classify up-to-date/outdated/unknown-version/modified/missing; `agents update` / `skills update` refresh outdated safely (never overwrite edits without `--force`); MCP `kaddo://installed-assets` |
544
+ | v3.43 | Capability-grounded roadmap: each `RM-xxx` candidate is graded on related domain / capability / source signals; `roadmap_quality` surfaced in `explain`/`context`/`understand`; `create --from roadmap` preserves `source_roadmap_candidate` + related metadata into the Work Item; roadmap-agent emits grounded fields (never materializes Work Items); MCP `kaddo://roadmap-quality` |
545
+ | v3.44 | Roadmap counting alignment + materialization quality: `explain`/`context` separate **Roadmap initiatives** from **Work Item candidates** (`## Roadmap Status`); two-level `roadmapQuality` (initiatives + work_item_candidates); `create --from roadmap` normalizes metadata — fills `domains` from `related_domain`, splits comma-joined `related_capabilities` into a real list, carries `source_roadmap_initiative`/`source_work_item_candidate`/`source_signals`/`decision_candidates`, improved Source + Context-From-Roadmap body + ADR warning; MCP `kaddo://work-item-candidates` |
544
546
 
545
547
  **Optional modules (installed with `kaddo add`):**
546
548
 
package/dist/index.js CHANGED
@@ -2645,6 +2645,24 @@ Adapt priorities to the project state from the context pack:
2645
2645
  - **legacy** \u2014 prioritize risk reduction, unknowns and safe modernization before feature
2646
2646
  delivery.
2647
2647
 
2648
+ ## Grounding rules (VS-077)
2649
+
2650
+ Every candidate initiative must be **grounded** in the knowledge base \u2014 never a loose idea. For each
2651
+ \`### RM-xxx\` you must provide:
2652
+
2653
+ - **Related domain** \u2014 a domain from \`## Capability Domains\` in \`capabilities.md\` (or, if genuinely
2654
+ new, prefix it \`[new candidate domain] <name>\` \u2014 do not invent domains silently).
2655
+ - **Related capabilities** \u2014 one or more existing/partial capabilities.
2656
+ - **Source signals** \u2014 at least one traceable reason: Capability Gap, Roadmap Candidate Signal, Risk,
2657
+ Open Question, Assumption, Deferred Decision, Tech Decision Candidate, ADR, Business Goal,
2658
+ Operational Need or Legacy Modernization Signal.
2659
+ - **Expected value**, **Risks**, **Dependencies**, and **Suggested Work Items** (candidates only).
2660
+
2661
+ Do **not** emit a candidate with no source signal. Keep initiatives at initiative granularity (small
2662
+ tasks go under **Suggested Work Items**, not as their own RM). Every roadmap ends with a global
2663
+ **## Not Now** section. For pre-ai/legacy, prioritize stabilization, security, data, operations,
2664
+ architectural decisions and business-blocking gaps before expansive features.
2665
+
2648
2666
  ## Constraints
2649
2667
 
2650
2668
  - Do not invent business priorities or business facts \u2014 mark them as assumptions when inferred.
@@ -2653,6 +2671,8 @@ Adapt priorities to the project state from the context pack:
2653
2671
  implementation-agent, and only after Work Items are materialized. Your handoff is
2654
2672
  \`kaddo create --from roadmap\` \u2192 work-item-agent.
2655
2673
  - Do not create the work items themselves; only propose candidates.
2674
+ - **Never create files under \`knowledge/delivery/work-items/\`** \u2014 materialization is
2675
+ \`kaddo create --from roadmap\`, not the roadmap-agent.
2656
2676
  - Make clear that initiatives and work items are **candidates**, not final decisions.
2657
2677
  - Mark any uncertain information as an assumption or open question.
2658
2678
  - Keep sequencing justified by dependencies and risk.
@@ -2686,31 +2706,38 @@ human review \u2014 not final commitments.
2686
2706
 
2687
2707
  ### RM-001: <Initiative Name>
2688
2708
 
2689
- **Goal:**
2709
+ **Status:** candidate <!-- candidate | selected | deferred | rejected -->
2690
2710
 
2691
- **Related capabilities:**
2711
+ **Priority:** high / medium / low
2692
2712
 
2693
- **Project area / domain:**
2713
+ **Suggested Knowledge Level:** K1 / K2 / K3 / K4
2694
2714
 
2695
- **Impact:** Low / Medium / High
2715
+ **Related domain:** <one of the ## Capability Domains from capabilities.md>
2696
2716
 
2697
- **Risk:** Low / Medium / High
2717
+ **Related capabilities:**
2718
+ - <existing or partial capability>
2698
2719
 
2699
- **Suggested Knowledge Level:** K1 / K2 / K3 / K4
2720
+ **Source signals:** <!-- REQUIRED: why this candidate exists (at least one) -->
2721
+ - Capability Gap: <...>
2722
+ - Roadmap Candidate Signal: <...>
2723
+ - Risk / Open Question / Assumption / Deferred Decision / Tech Decision Candidate / ADR / Business Goal: <...>
2700
2724
 
2701
- **Dependencies:**
2725
+ **Problem / opportunity:**
2726
+
2727
+ **Expected value:**
2702
2728
 
2703
- **Why this comes now:**
2729
+ **Risks:**
2704
2730
 
2705
- **Candidate Work Items:**
2731
+ **Dependencies:**
2706
2732
 
2733
+ **Suggested Work Items:**
2707
2734
  - WI-CANDIDATE-001: <candidate work item>
2708
2735
  - type:
2709
2736
  - suggested knowledge level:
2710
2737
  - expected value:
2711
2738
  - notes:
2712
2739
 
2713
- **Open questions:**
2740
+ **Not now:**
2714
2741
 
2715
2742
  ---
2716
2743
 
@@ -3005,6 +3032,23 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
3005
3032
  without surfacing it. -->
3006
3033
  \`\`\`
3007
3034
 
3035
+ ### Preserve roadmap metadata (VS-077 / VS-078)
3036
+
3037
+ When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries the trace
3038
+ back to the roadmap. **Keep and refine \u2014 never delete** these fields:
3039
+
3040
+ - \`source_roadmap_initiative\` and \`source_work_item_candidate\` (the RM-xxx initiative and the
3041
+ WI-CANDIDATE-xxx it was materialized from)
3042
+ - \`related_domain\` and \`domains\` (keep them consistent \u2014 \`domains\` must not be empty when
3043
+ \`related_domain\` exists)
3044
+ - \`related_capabilities\` (a real list, one capability per item \u2014 never a single comma-joined string)
3045
+ - \`expected_value\`, \`risks\`, \`dependencies\`
3046
+ - \`source_signals\` (do **not** invent them \u2014 if absent, leave them absent)
3047
+ - \`decision_candidates\` and \`related_decisions\` (when the work depends on a technical decision)
3048
+
3049
+ Do not drop the trace back to the capability domain and source signals. If a Work Item depends on a
3050
+ tech decision candidate with no ADR yet, keep the warning surfaced in the body.
3051
+
3008
3052
  ## Where to Save the Result
3009
3053
 
3010
3054
  Save new output as a draft under \`knowledge/delivery/work-items/draft/\` unless a human
@@ -4495,7 +4539,12 @@ function splitInitiatives(markdown) {
4495
4539
  return blocks;
4496
4540
  }
4497
4541
  function parseBlock(block) {
4498
- const meta = { relatedCapabilities: [], dependencies: [], openQuestions: [] };
4542
+ const meta = {
4543
+ relatedCapabilities: [],
4544
+ dependencies: [],
4545
+ openQuestions: [],
4546
+ sourceSignals: []
4547
+ };
4499
4548
  const rawCandidates = [];
4500
4549
  let listField = null;
4501
4550
  let inCandidateSection = false;
@@ -4545,7 +4594,7 @@ function parseBlock(block) {
4545
4594
  meta.impact = value || void 0;
4546
4595
  } else if (key === "risk") {
4547
4596
  meta.risk = value || void 0;
4548
- } else if (key === "project area / domain" || key === "domain" || key === "project area") {
4597
+ } else if (key === "project area / domain" || key === "domain" || key === "project area" || key === "related domain") {
4549
4598
  meta.domain = value || void 0;
4550
4599
  } else if (key === "related capabilities") {
4551
4600
  listField = "relatedCapabilities";
@@ -4556,6 +4605,9 @@ function parseBlock(block) {
4556
4605
  } else if (key === "open questions") {
4557
4606
  listField = "openQuestions";
4558
4607
  if (value) meta.openQuestions.push(value);
4608
+ } else if (key === "source signals" || key === "source signal") {
4609
+ listField = "sourceSignals";
4610
+ if (value) meta.sourceSignals.push(value);
4559
4611
  }
4560
4612
  continue;
4561
4613
  }
@@ -4573,6 +4625,7 @@ function parseBlock(block) {
4573
4625
  }
4574
4626
  }
4575
4627
  flush();
4628
+ const decisionCandidates = decisionCandidatesFromSignals(meta.sourceSignals);
4576
4629
  return rawCandidates.map((c) => ({
4577
4630
  ...c,
4578
4631
  initiative: block.initiative.id || block.initiative.title ? { ...block.initiative } : void 0,
@@ -4581,9 +4634,23 @@ function parseBlock(block) {
4581
4634
  impact: meta.impact,
4582
4635
  risk: meta.risk,
4583
4636
  dependencies: meta.dependencies.length ? [...meta.dependencies] : void 0,
4584
- openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0
4637
+ openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0,
4638
+ sourceSignals: meta.sourceSignals.length ? [...meta.sourceSignals] : void 0,
4639
+ decisionCandidates: decisionCandidates.length ? decisionCandidates : void 0
4585
4640
  }));
4586
4641
  }
4642
+ function decisionCandidatesFromSignals(signals) {
4643
+ const out = [];
4644
+ for (const sig of signals) {
4645
+ const m = sig.match(/decision candidate\s*:?\s*(.+)$/i);
4646
+ if (!m) continue;
4647
+ const rest = m[1].trim();
4648
+ const token = rest.match(/\b[A-Z][A-Z0-9_]{2,}\b/);
4649
+ const value = token ? token[0] : rest.replace(/[.。]+$/, "").trim();
4650
+ if (value && !out.includes(value)) out.push(value);
4651
+ }
4652
+ return out;
4653
+ }
4587
4654
  var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
4588
4655
  var HEADING_RE = /^#{2,4}\s+(.*)$/;
4589
4656
  var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
@@ -4665,10 +4732,51 @@ function parseRoadmapCandidates(markdown) {
4665
4732
  if (strict.length > 0) return strict;
4666
4733
  return parseFlexible(markdown);
4667
4734
  }
4735
+ function countRoadmapInitiatives(markdown) {
4736
+ if (markdown == null) return 0;
4737
+ let n = 0;
4738
+ for (const line of markdown.split(/\r?\n/)) if (INITIATIVE_RE.test(line)) n++;
4739
+ return n;
4740
+ }
4668
4741
  function roadmapStats(markdown, materialized) {
4669
- if (markdown == null) return { present: false, candidates: 0, materialized, remaining: 0 };
4742
+ if (markdown == null)
4743
+ return {
4744
+ present: false,
4745
+ initiatives: 0,
4746
+ work_item_candidates: 0,
4747
+ materialized_work_items: materialized,
4748
+ remaining_work_item_candidates: 0,
4749
+ candidates: 0,
4750
+ materialized,
4751
+ remaining: 0
4752
+ };
4670
4753
  const candidates = parseRoadmapCandidates(markdown).length;
4671
- return { present: true, candidates, materialized, remaining: Math.max(0, candidates - materialized) };
4754
+ const remaining = Math.max(0, candidates - materialized);
4755
+ return {
4756
+ present: true,
4757
+ initiatives: countRoadmapInitiatives(markdown),
4758
+ work_item_candidates: candidates,
4759
+ materialized_work_items: materialized,
4760
+ remaining_work_item_candidates: remaining,
4761
+ candidates,
4762
+ materialized,
4763
+ remaining
4764
+ };
4765
+ }
4766
+ function normalizeCapabilityList(raw) {
4767
+ if (!raw || raw.length === 0) return [];
4768
+ const out = [];
4769
+ for (const entry of raw) {
4770
+ for (const part of entry.split(/[,;]/)) {
4771
+ const cap = part.replace(/[.。]+\s*$/, "").trim();
4772
+ if (cap && !out.includes(cap)) out.push(cap);
4773
+ }
4774
+ }
4775
+ return out;
4776
+ }
4777
+ function domainsFromRelated(domain) {
4778
+ const d = domain?.trim();
4779
+ return d ? [d] : [];
4672
4780
  }
4673
4781
 
4674
4782
  // src/commands/create.ts
@@ -4964,22 +5072,44 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4964
5072
  const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4965
5073
  const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
4966
5074
  const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
5075
+ const q = (s) => s.replace(/"/g, "'");
5076
+ const domains = domainsFromRelated(candidate.domain);
5077
+ const relatedCapabilities = normalizeCapabilityList(candidate.relatedCapabilities);
5078
+ const risks = candidate.risk ? [candidate.risk] : [];
5079
+ const dependencies = candidate.dependencies ?? [];
5080
+ const sourceSignals = candidate.sourceSignals ?? [];
5081
+ const decisionCandidates = candidate.decisionCandidates ?? [];
5082
+ const yamlList2 = (key, items) => items.length === 0 ? [] : [`${key}:`, ...items.map((i) => ` - "${q(i)}"`)];
4967
5083
  const lines = [
4968
5084
  "---",
4969
5085
  `type: ${type}`,
4970
5086
  `id: ${id}`,
4971
- `title: "${title}"`,
5087
+ `title: "${q(title)}"`,
4972
5088
  `knowledge_level: ${level}`,
4973
5089
  `status: draft`,
4974
5090
  `phase: now`,
4975
- `initiative: "${initiative.replace(/"/g, "'")}"`,
4976
- `domains: []`,
5091
+ `initiative: "${q(initiative)}"`,
5092
+ ...yamlList2("domains", domains),
5093
+ ...domains.length === 0 ? ["domains: []"] : [],
4977
5094
  `code: []`,
4978
5095
  `created_at: ${today2}`,
4979
5096
  `source: roadmap`,
4980
5097
  `source_id: ${candidate.id}`,
4981
5098
  `source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
4982
- `summary: "${summary.replace(/"/g, "'")}"`,
5099
+ // Capability-grounded traceability (VS-077 / VS-078): distinguish the parent roadmap initiative
5100
+ // from the specific Work Item candidate it was materialized from.
5101
+ `source_roadmap_initiative: ${candidate.initiative?.id ?? "unknown"}`,
5102
+ `source_work_item_candidate: ${candidate.id}`,
5103
+ ...candidate.initiative?.title ? [`source_initiative_title: "${q(candidate.initiative.title)}"`] : [],
5104
+ ...candidate.domain ? [`related_domain: "${q(candidate.domain)}"`] : [],
5105
+ ...yamlList2("related_capabilities", relatedCapabilities),
5106
+ ...candidate.expectedValue ? [`expected_value: "${q(candidate.expectedValue)}"`] : [],
5107
+ ...yamlList2("risks", risks),
5108
+ ...yamlList2("dependencies", dependencies),
5109
+ ...yamlList2("source_signals", sourceSignals),
5110
+ ...yamlList2("decision_candidates", decisionCandidates),
5111
+ ...decisionCandidates.length > 0 ? ["related_decisions: []"] : [],
5112
+ `summary: "${q(summary)}"`,
4983
5113
  "---"
4984
5114
  ];
4985
5115
  return lines.join("\n");
@@ -4991,14 +5121,24 @@ function buildRoadmapBody(type, level, title, candidate, answers, qualityGate) {
4991
5121
  sections.push(`> Type: ${type} \xB7 Level: ${level}
4992
5122
  `);
4993
5123
  const initiativeLabel = candidate.initiative ? `${candidate.initiative.id ?? ""}${candidate.initiative.title ? ` \u2014 ${candidate.initiative.title}` : ""}`.trim() : "unknown";
4994
- sections.push(
4995
- [
4996
- "## Source\n",
4997
- `- Source: roadmap`,
4998
- `- Candidate: ${candidate.id}`,
4999
- `- Initiative: ${initiativeLabel || "unknown"}`
5000
- ].join("\n") + "\n"
5001
- );
5124
+ const relatedCapabilities = normalizeCapabilityList(candidate.relatedCapabilities);
5125
+ const sourceLines = [
5126
+ "## Source\n",
5127
+ `- Source: roadmap`,
5128
+ `- Roadmap Initiative: ${initiativeLabel || "unknown"}`,
5129
+ `- Work Item Candidate: ${candidate.id}`
5130
+ ];
5131
+ if (candidate.domain) sourceLines.push(`- Related domain: ${candidate.domain}`);
5132
+ if (relatedCapabilities.length) {
5133
+ sourceLines.push("- Related capabilities:");
5134
+ relatedCapabilities.forEach((c) => sourceLines.push(` - ${c}`));
5135
+ }
5136
+ sections.push(sourceLines.join("\n") + "\n");
5137
+ if (candidate.decisionCandidates?.length) {
5138
+ sections.push(
5139
+ "> Warning: This Work Item is related to technical decision candidates that have not been materialized as ADRs.\n"
5140
+ );
5141
+ }
5002
5142
  if (answers.problem) sections.push(`## Problem
5003
5143
 
5004
5144
  ${answers.problem}
@@ -5008,22 +5148,22 @@ ${answers.problem}
5008
5148
 
5009
5149
  ${expectedValue}
5010
5150
  `);
5011
- const ctx = [];
5012
- if (candidate.relatedCapabilities?.length)
5013
- ctx.push(`**Related capabilities:** ${candidate.relatedCapabilities.join(", ")}`);
5014
- if (candidate.domain) ctx.push(`**Domain:** ${candidate.domain}`);
5015
- if (candidate.impact) ctx.push(`**Impact:** ${candidate.impact}`);
5016
- if (candidate.risk) ctx.push(`**Risk:** ${candidate.risk}`);
5151
+ const initiativeRef = candidate.initiative?.id ? `roadmap initiative ${candidate.initiative.id}` : "a roadmap candidate";
5152
+ const ctx = [`This Work Item was materialized from ${initiativeRef}.`, ""];
5153
+ if (candidate.expectedValue) ctx.push(`**Expected value:** ${candidate.expectedValue}`);
5154
+ if (candidate.risk) ctx.push(`**Risks:** ${candidate.risk}`);
5017
5155
  if (candidate.dependencies?.length)
5018
- ctx.push(`**Dependencies:**
5019
- ${formatList(candidate.dependencies)}`);
5020
- const initiativeNote = candidate.initiative?.id ? `This candidate was created from the roadmap initiative ${candidate.initiative.id}.` : "This work item was created from a roadmap candidate.";
5021
- sections.push(
5022
- `## Context From Roadmap
5156
+ ctx.push(`**Dependencies:** ${candidate.dependencies.join("; ")}`);
5157
+ if (candidate.sourceSignals?.length) {
5158
+ ctx.push(`**Source signals:**
5159
+ ${formatList(candidate.sourceSignals)}`);
5160
+ } else {
5161
+ ctx.push("**Source signals:** _Not provided in roadmap._");
5162
+ }
5163
+ sections.push(`## Context From Roadmap
5023
5164
 
5024
- ${initiativeNote}${ctx.length ? "\n\n" + ctx.join("\n\n") : ""}
5025
- `
5026
- );
5165
+ ${ctx.join("\n\n")}
5166
+ `);
5027
5167
  if (answers.acceptance_criteria) {
5028
5168
  const items = answers.acceptance_criteria.split("\n").map((l) => l.trim()).filter(Boolean);
5029
5169
  sections.push(`## Acceptance Criteria
@@ -8325,6 +8465,88 @@ function installedAssetsSummary(dir) {
8325
8465
  return { version: KADDO_VERSION, agents: assetStatus(dir, "agent"), skills: assetStatus(dir, "skill") };
8326
8466
  }
8327
8467
 
8468
+ // src/core/roadmap-quality.ts
8469
+ var ROADMAP_PATH2 = "knowledge/delivery/roadmap.md";
8470
+ var INITIATIVE_RE2 = /^#{2,4}\s+(RM-[\w.-]+)\s*[:\-–]?\s*(.*)$/;
8471
+ function fieldPresent(lines, label) {
8472
+ for (let i = 0; i < lines.length; i++) {
8473
+ const m = lines[i].match(new RegExp(`^\\s*[-*]?\\s*(?:\\*\\*)?\\s*(${label.source})\\s*(?:\\*\\*)?\\s*:\\s*(.*)$`, "i"));
8474
+ if (!m) continue;
8475
+ const inline = m[2].trim();
8476
+ if (inline) return true;
8477
+ for (let j = i + 1; j < lines.length; j++) {
8478
+ if (/^\s{2,}[-*]\s+\S/.test(lines[j])) return true;
8479
+ if (/^\S/.test(lines[j]) || /^#{2,4}\s/.test(lines[j])) break;
8480
+ if (/^\s*[-*]\s+\S/.test(lines[j]) && !/^\s{2,}/.test(lines[j])) break;
8481
+ }
8482
+ }
8483
+ return false;
8484
+ }
8485
+ function parseRoadmapCandidateQuality(md) {
8486
+ const lines = md.split(/\r?\n/);
8487
+ const items = [];
8488
+ let cur = null;
8489
+ const flush = () => {
8490
+ if (!cur) return;
8491
+ const hasRelatedDomain = fieldPresent(cur.lines, /related domain/);
8492
+ const hasRelatedCapability = fieldPresent(cur.lines, /related capabilit(?:y|ies)/);
8493
+ const hasSourceSignals = fieldPresent(cur.lines, /source signals?/);
8494
+ items.push({
8495
+ id: cur.id,
8496
+ title: cur.title,
8497
+ hasRelatedDomain,
8498
+ hasRelatedCapability,
8499
+ hasSourceSignals,
8500
+ grounded: hasRelatedDomain && hasRelatedCapability && hasSourceSignals
8501
+ });
8502
+ cur = null;
8503
+ };
8504
+ for (const line of lines) {
8505
+ const m = line.match(INITIATIVE_RE2);
8506
+ if (m) {
8507
+ flush();
8508
+ cur = { id: m[1], title: m[2].trim(), lines: [] };
8509
+ continue;
8510
+ }
8511
+ if (cur) cur.lines.push(line);
8512
+ }
8513
+ flush();
8514
+ return items;
8515
+ }
8516
+ function buildRoadmapQuality(dir) {
8517
+ let md = "";
8518
+ const p2 = join(dir, ROADMAP_PATH2);
8519
+ if (exists(p2)) {
8520
+ try {
8521
+ md = readFile(p2);
8522
+ } catch {
8523
+ md = "";
8524
+ }
8525
+ }
8526
+ const items = parseRoadmapCandidateQuality(md);
8527
+ const count = (pred) => items.filter(pred).length;
8528
+ const grounded = count((i) => i.grounded);
8529
+ const initiatives = {
8530
+ total: items.length,
8531
+ grounded,
8532
+ with_related_domain: count((i) => i.hasRelatedDomain),
8533
+ with_related_capability: count((i) => i.hasRelatedCapability),
8534
+ with_source_signals: count((i) => i.hasSourceSignals),
8535
+ // Only "needs refinement" when there are initiatives and at least one isn't grounded.
8536
+ needs_refinement: items.length > 0 && grounded < items.length,
8537
+ items
8538
+ };
8539
+ const wiCandidates = md ? parseRoadmapCandidates(md) : [];
8540
+ const wcount = (pred) => wiCandidates.filter(pred).length;
8541
+ const work_item_candidates = {
8542
+ total: wiCandidates.length,
8543
+ with_source_initiative: wcount((c) => Boolean(c.initiative?.id || c.initiative?.title)),
8544
+ with_related_domain: wcount((c) => Boolean(c.domain)),
8545
+ with_related_capability: wcount((c) => Boolean(c.relatedCapabilities?.length))
8546
+ };
8547
+ return { initiatives, work_item_candidates };
8548
+ }
8549
+
8328
8550
  // src/core/project-explain.ts
8329
8551
  var ARCH_DIR4 = "knowledge";
8330
8552
  function normalizeTitle(t) {
@@ -8549,7 +8771,8 @@ function buildProjectExplanation(dir) {
8549
8771
  legacyLocation: exists(join(dir, "knowledge/tech/architecture-notes.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md"))
8550
8772
  }
8551
8773
  },
8552
- installedAssets: installedAssetsSummary(dir)
8774
+ installedAssets: installedAssetsSummary(dir),
8775
+ roadmapQuality: buildRoadmapQuality(dir)
8553
8776
  };
8554
8777
  }
8555
8778
  function stateLabel(state) {
@@ -8604,10 +8827,11 @@ function renderExplanationHuman(exp) {
8604
8827
  lines.push(`- Delivery: ${ls("Delivery")}`);
8605
8828
  lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
8606
8829
  if (exp.roadmap.present) {
8607
- lines.push(`- Roadmap candidates: ${exp.roadmap.candidates}`);
8608
- lines.push(`- Materialized work items: ${exp.roadmap.materialized}`);
8609
- if (exp.roadmap.remaining > 0)
8610
- lines.push(`- Remaining candidates: ${exp.roadmap.remaining}`);
8830
+ lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
8831
+ lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
8832
+ lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
8833
+ if (exp.roadmap.remaining_work_item_candidates > 0)
8834
+ lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
8611
8835
  } else {
8612
8836
  lines.push(`- Work items: ${exp.workItems.total}`);
8613
8837
  }
@@ -8784,6 +9008,45 @@ function renderExplanationHuman(exp) {
8784
9008
  lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
8785
9009
  lines.push("");
8786
9010
  }
9011
+ if (exp.roadmap.present) {
9012
+ lines.push("## Roadmap Status");
9013
+ lines.push(`- Initiatives: ${exp.roadmap.initiatives}`);
9014
+ lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
9015
+ lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
9016
+ lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
9017
+ lines.push("");
9018
+ }
9019
+ const rq = exp.roadmapQuality;
9020
+ const rqi = rq.initiatives;
9021
+ const rqw = rq.work_item_candidates;
9022
+ if (rqi.total > 0 || rqw.total > 0) {
9023
+ lines.push("## Roadmap Quality");
9024
+ if (rqi.total > 0) {
9025
+ lines.push("Initiatives:");
9026
+ lines.push(`- Candidates evaluated: ${rqi.total}`);
9027
+ lines.push(`- Grounded: ${rqi.grounded}/${rqi.total}`);
9028
+ lines.push(`- With related domain: ${rqi.with_related_domain}/${rqi.total}`);
9029
+ lines.push(`- With related capability: ${rqi.with_related_capability}/${rqi.total}`);
9030
+ lines.push(`- With source signals: ${rqi.with_source_signals}/${rqi.total}`);
9031
+ }
9032
+ if (rqw.total > 0) {
9033
+ lines.push("");
9034
+ lines.push("Work Item Candidates:");
9035
+ lines.push(`- Candidates: ${rqw.total}`);
9036
+ lines.push(`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`);
9037
+ lines.push(`- With related domain: ${rqw.with_related_domain}/${rqw.total}`);
9038
+ lines.push(`- With related capability: ${rqw.with_related_capability}/${rqw.total}`);
9039
+ }
9040
+ if (rqi.needs_refinement) {
9041
+ lines.push("");
9042
+ lines.push("Roadmap quality: needs refinement.");
9043
+ lines.push("Suggested: use roadmap-agent to add domain / capability / source signals.");
9044
+ } else if (rqw.total > 0) {
9045
+ lines.push("");
9046
+ lines.push("Work Item candidate quality: good.");
9047
+ }
9048
+ lines.push("");
9049
+ }
8787
9050
  const ia = exp.installedAssets;
8788
9051
  const agentsInstalled = ia.agents.total - ia.agents.missing;
8789
9052
  const skillsInstalled = ia.skills.total - ia.skills.missing;
@@ -9202,6 +9465,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9202
9465
  nextStepRecommendation,
9203
9466
  techDecisions,
9204
9467
  techKnowledge,
9468
+ roadmapQuality: buildRoadmapQuality(dir),
9205
9469
  installedAssets: (() => {
9206
9470
  const s = installedAssetsSummary(dir);
9207
9471
  const compact = (a) => ({ total: a.total, installed: a.total - a.missing, outdated: a.outdated, unknown_version: a.unknown_version, modified: a.modified });
@@ -9300,22 +9564,57 @@ function renderContextPack(pack) {
9300
9564
  }
9301
9565
  parts.push("## Current Knowledge\n");
9302
9566
  parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
9303
- parts.push("## Roadmap\n");
9567
+ parts.push("## Roadmap Status\n");
9304
9568
  if (pack.roadmap.present) {
9305
9569
  parts.push(
9306
9570
  [
9307
- `- Roadmap candidates: ${pack.roadmap.candidates}`,
9308
- `- Materialized work items: ${pack.roadmap.materialized}`,
9309
- `- Remaining candidates: ${pack.roadmap.remaining}`
9571
+ `- Initiatives: ${pack.roadmap.initiatives}`,
9572
+ `- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
9573
+ `- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
9574
+ `- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
9310
9575
  ].join("\n") + "\n"
9311
9576
  );
9312
- if (pack.roadmap.remaining > 0) {
9577
+ if (pack.roadmap.remaining_work_item_candidates > 0) {
9313
9578
  parts.push(
9314
- "Candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
9579
+ "Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
9315
9580
  );
9316
9581
  }
9317
9582
  }
9318
9583
  parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
9584
+ const rq = pack.roadmapQuality;
9585
+ const rqi = rq.initiatives;
9586
+ const rqw = rq.work_item_candidates;
9587
+ if (rqi.total > 0 || rqw.total > 0) {
9588
+ parts.push("## Roadmap Quality\n");
9589
+ if (rqi.total > 0) {
9590
+ parts.push(
9591
+ [
9592
+ "Initiatives:",
9593
+ `- Candidates evaluated: ${rqi.total}`,
9594
+ `- Grounded: ${rqi.grounded}/${rqi.total}`,
9595
+ `- With related domain: ${rqi.with_related_domain}/${rqi.total}`,
9596
+ `- With related capability: ${rqi.with_related_capability}/${rqi.total}`,
9597
+ `- With source signals: ${rqi.with_source_signals}/${rqi.total}`
9598
+ ].join("\n") + "\n"
9599
+ );
9600
+ }
9601
+ if (rqw.total > 0) {
9602
+ parts.push(
9603
+ [
9604
+ "Work Item Candidates:",
9605
+ `- Candidates: ${rqw.total}`,
9606
+ `- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`,
9607
+ `- With related domain: ${rqw.with_related_domain}/${rqw.total}`,
9608
+ `- With related capability: ${rqw.with_related_capability}/${rqw.total}`
9609
+ ].join("\n") + "\n"
9610
+ );
9611
+ }
9612
+ if (rqi.needs_refinement) {
9613
+ parts.push(
9614
+ "Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
9615
+ );
9616
+ }
9617
+ }
9319
9618
  parts.push("## Active Work Items\n");
9320
9619
  if (knowledge.workItems.length > 0) {
9321
9620
  const lines = knowledge.workItems.map((wi) => {
@@ -9748,6 +10047,16 @@ function runUnderstand() {
9748
10047
  console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
9749
10048
  console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
9750
10049
  }
10050
+ const rqi = exp.roadmapQuality.initiatives;
10051
+ if (rqi.needs_refinement) {
10052
+ console.log("");
10053
+ console.log(`Roadmap quality: ${rqi.grounded}/${rqi.total} initiatives grounded.`);
10054
+ console.log(" \u2192 Use roadmap-agent to ground roadmap initiatives in capability domains, gaps and source signals");
10055
+ console.log(" before `kaddo create --from roadmap`.");
10056
+ } else if (rqi.total > 0 && rqi.grounded === rqi.total) {
10057
+ console.log("");
10058
+ console.log("Roadmap initiatives are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
10059
+ }
9751
10060
  const ia = exp.installedAssets;
9752
10061
  const outdatedRecommended = ia.agents.items.filter(
9753
10062
  (a) => (a.state === "outdated" || a.state === "unknown-version") && assessment.recommendedAgents.includes(a.name)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.42.0",
3
+ "version": "3.44.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {