@kaddo/cli 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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +495 -180
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -542,6 +542,8 @@ create --from roadmap → owners → guard → explain`.
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
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` |
546
+ | v3.45 | State-aware next step: `resolveNextStep` decides from the real delivery state (draft/ready/in-progress, ownership, ADRs, adapters) instead of always suggesting `create --from roadmap` — draft → work-item-agent, ready → adapter/implementation-agent, in-progress → guard; parallel **secondary** recommendations (ownership, ADRs, remaining candidates); `deliveryState` + recommendation in `explain`/`context`; MCP `kaddo://next-step` |
545
547
 
546
548
  **Optional modules (installed with `kaddo add`):**
547
549
 
package/dist/index.js CHANGED
@@ -3032,13 +3032,22 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
3032
3032
  without surfacing it. -->
3033
3033
  \`\`\`
3034
3034
 
3035
- ### Preserve roadmap metadata (VS-077)
3035
+ ### Preserve roadmap metadata (VS-077 / VS-078)
3036
3036
 
3037
- When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries
3038
- \`source_roadmap_candidate\`, \`related_domain\`, \`related_capability\` (+ \`related_capabilities\`),
3039
- \`knowledge_level\`, \`expected_value\`, \`risks\` and \`dependencies\`. **Keep and refine** this metadata \u2014
3040
- do not drop the trace back to the capability domain and source signals. Add \`related_decisions\` /
3041
- \`decision_candidates\` when the work depends on a technical decision.
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.
3042
3051
 
3043
3052
  ## Where to Save the Result
3044
3053
 
@@ -4530,7 +4539,12 @@ function splitInitiatives(markdown) {
4530
4539
  return blocks;
4531
4540
  }
4532
4541
  function parseBlock(block) {
4533
- const meta = { relatedCapabilities: [], dependencies: [], openQuestions: [] };
4542
+ const meta = {
4543
+ relatedCapabilities: [],
4544
+ dependencies: [],
4545
+ openQuestions: [],
4546
+ sourceSignals: []
4547
+ };
4534
4548
  const rawCandidates = [];
4535
4549
  let listField = null;
4536
4550
  let inCandidateSection = false;
@@ -4580,7 +4594,7 @@ function parseBlock(block) {
4580
4594
  meta.impact = value || void 0;
4581
4595
  } else if (key === "risk") {
4582
4596
  meta.risk = value || void 0;
4583
- } 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") {
4584
4598
  meta.domain = value || void 0;
4585
4599
  } else if (key === "related capabilities") {
4586
4600
  listField = "relatedCapabilities";
@@ -4591,6 +4605,9 @@ function parseBlock(block) {
4591
4605
  } else if (key === "open questions") {
4592
4606
  listField = "openQuestions";
4593
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);
4594
4611
  }
4595
4612
  continue;
4596
4613
  }
@@ -4608,6 +4625,7 @@ function parseBlock(block) {
4608
4625
  }
4609
4626
  }
4610
4627
  flush();
4628
+ const decisionCandidates = decisionCandidatesFromSignals(meta.sourceSignals);
4611
4629
  return rawCandidates.map((c) => ({
4612
4630
  ...c,
4613
4631
  initiative: block.initiative.id || block.initiative.title ? { ...block.initiative } : void 0,
@@ -4616,9 +4634,23 @@ function parseBlock(block) {
4616
4634
  impact: meta.impact,
4617
4635
  risk: meta.risk,
4618
4636
  dependencies: meta.dependencies.length ? [...meta.dependencies] : void 0,
4619
- 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
4620
4640
  }));
4621
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
+ }
4622
4654
  var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
4623
4655
  var HEADING_RE = /^#{2,4}\s+(.*)$/;
4624
4656
  var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
@@ -4700,10 +4732,51 @@ function parseRoadmapCandidates(markdown) {
4700
4732
  if (strict.length > 0) return strict;
4701
4733
  return parseFlexible(markdown);
4702
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
+ }
4703
4741
  function roadmapStats(markdown, materialized) {
4704
- 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
+ };
4705
4753
  const candidates = parseRoadmapCandidates(markdown).length;
4706
- 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] : [];
4707
4780
  }
4708
4781
 
4709
4782
  // src/commands/create.ts
@@ -4999,32 +5072,44 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4999
5072
  const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5000
5073
  const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
5001
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)}"`)];
5002
5083
  const lines = [
5003
5084
  "---",
5004
5085
  `type: ${type}`,
5005
5086
  `id: ${id}`,
5006
- `title: "${title}"`,
5087
+ `title: "${q(title)}"`,
5007
5088
  `knowledge_level: ${level}`,
5008
5089
  `status: draft`,
5009
5090
  `phase: now`,
5010
- `initiative: "${initiative.replace(/"/g, "'")}"`,
5011
- `domains: []`,
5091
+ `initiative: "${q(initiative)}"`,
5092
+ ...yamlList2("domains", domains),
5093
+ ...domains.length === 0 ? ["domains: []"] : [],
5012
5094
  `code: []`,
5013
5095
  `created_at: ${today2}`,
5014
5096
  `source: roadmap`,
5015
5097
  `source_id: ${candidate.id}`,
5016
5098
  `source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
5017
- // Capability-grounded traceability (VS-077): carry the roadmap metadata into the Work Item.
5018
- `source_roadmap_candidate: ${candidate.initiative?.id ?? candidate.id}`,
5019
- ...candidate.domain ? [`related_domain: "${candidate.domain.replace(/"/g, "'")}"`] : [],
5020
- ...candidate.relatedCapabilities && candidate.relatedCapabilities.length > 0 ? [
5021
- `related_capability: "${candidate.relatedCapabilities[0].replace(/"/g, "'")}"`,
5022
- `related_capabilities: [${candidate.relatedCapabilities.map((c) => `"${c.replace(/"/g, "'")}"`).join(", ")}]`
5023
- ] : [],
5024
- ...candidate.expectedValue ? [`expected_value: "${candidate.expectedValue.replace(/"/g, "'")}"`] : [],
5025
- ...candidate.risk ? [`risks: "${candidate.risk.replace(/"/g, "'")}"`] : [],
5026
- ...candidate.dependencies && candidate.dependencies.length > 0 ? [`dependencies: [${candidate.dependencies.map((d) => `"${d.replace(/"/g, "'")}"`).join(", ")}]`] : [],
5027
- `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)}"`,
5028
5113
  "---"
5029
5114
  ];
5030
5115
  return lines.join("\n");
@@ -5036,14 +5121,24 @@ function buildRoadmapBody(type, level, title, candidate, answers, qualityGate) {
5036
5121
  sections.push(`> Type: ${type} \xB7 Level: ${level}
5037
5122
  `);
5038
5123
  const initiativeLabel = candidate.initiative ? `${candidate.initiative.id ?? ""}${candidate.initiative.title ? ` \u2014 ${candidate.initiative.title}` : ""}`.trim() : "unknown";
5039
- sections.push(
5040
- [
5041
- "## Source\n",
5042
- `- Source: roadmap`,
5043
- `- Candidate: ${candidate.id}`,
5044
- `- Initiative: ${initiativeLabel || "unknown"}`
5045
- ].join("\n") + "\n"
5046
- );
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
+ }
5047
5142
  if (answers.problem) sections.push(`## Problem
5048
5143
 
5049
5144
  ${answers.problem}
@@ -5053,22 +5148,22 @@ ${answers.problem}
5053
5148
 
5054
5149
  ${expectedValue}
5055
5150
  `);
5056
- const ctx = [];
5057
- if (candidate.relatedCapabilities?.length)
5058
- ctx.push(`**Related capabilities:** ${candidate.relatedCapabilities.join(", ")}`);
5059
- if (candidate.domain) ctx.push(`**Domain:** ${candidate.domain}`);
5060
- if (candidate.impact) ctx.push(`**Impact:** ${candidate.impact}`);
5061
- 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}`);
5062
5155
  if (candidate.dependencies?.length)
5063
- ctx.push(`**Dependencies:**
5064
- ${formatList(candidate.dependencies)}`);
5065
- 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.";
5066
- sections.push(
5067
- `## 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
5068
5164
 
5069
- ${initiativeNote}${ctx.length ? "\n\n" + ctx.join("\n\n") : ""}
5070
- `
5071
- );
5165
+ ${ctx.join("\n\n")}
5166
+ `);
5072
5167
  if (answers.acceptance_criteria) {
5073
5168
  const items = answers.acceptance_criteria.split("\n").map((l) => l.trim()).filter(Boolean);
5074
5169
  sections.push(`## Acceptance Criteria
@@ -8018,7 +8113,124 @@ function buildSharedFileStatuses(statuses) {
8018
8113
  }));
8019
8114
  }
8020
8115
 
8116
+ // src/core/decisions.ts
8117
+ var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
8118
+ var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
8119
+ var DECISIONS_DIR = "knowledge/tech/decisions";
8120
+ function resolveCandidatesPath(dir) {
8121
+ const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
8122
+ const legacy = exists(join(dir, CANDIDATES_LEGACY));
8123
+ if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
8124
+ if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
8125
+ return { path: null, legacy: false, bothExist: false };
8126
+ }
8127
+ function cleanCandidateTitle(title) {
8128
+ return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
8129
+ }
8130
+ function slugify2(s) {
8131
+ return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
8132
+ }
8133
+ function parseDecisionCandidates(md) {
8134
+ const out = [];
8135
+ for (const line of md.split(/\r?\n/)) {
8136
+ const m = line.match(/^##\s+(.+?)\s*$/);
8137
+ if (m) {
8138
+ const raw = m[1].trim();
8139
+ if (!raw || /^_.*_$/.test(raw)) continue;
8140
+ const t = cleanCandidateTitle(raw);
8141
+ if (t) out.push(t);
8142
+ }
8143
+ }
8144
+ return out;
8145
+ }
8146
+ function countAdrs(dir) {
8147
+ const base = join(dir, DECISIONS_DIR);
8148
+ if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
8149
+ let total = 0;
8150
+ let draft = 0;
8151
+ let accepted = 0;
8152
+ for (const entry of readDir(base)) {
8153
+ if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
8154
+ const full = join(base, entry);
8155
+ if (!isFile(full)) continue;
8156
+ total += 1;
8157
+ let content = "";
8158
+ try {
8159
+ content = readFile(full);
8160
+ } catch {
8161
+ continue;
8162
+ }
8163
+ const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
8164
+ if (status === "accepted") accepted += 1;
8165
+ else draft += 1;
8166
+ }
8167
+ return { total, draft, accepted };
8168
+ }
8169
+ function buildTechDecisions(dir) {
8170
+ const resolved = resolveCandidatesPath(dir);
8171
+ let titles = [];
8172
+ if (resolved.path) {
8173
+ try {
8174
+ titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
8175
+ } catch {
8176
+ titles = [];
8177
+ }
8178
+ }
8179
+ const { total, draft, accepted } = countAdrs(dir);
8180
+ const candidate_list = titles.map((title, i) => {
8181
+ const n = String(total + i + 1).padStart(3, "0");
8182
+ return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8183
+ });
8184
+ let status;
8185
+ if (accepted > 0) status = "accepted-adrs";
8186
+ else if (total > 0) status = "draft-adrs";
8187
+ else if (titles.length > 0) status = "candidates";
8188
+ else status = "none";
8189
+ return {
8190
+ status,
8191
+ candidates: titles.length,
8192
+ adrs: total,
8193
+ draft_adrs: draft,
8194
+ accepted_adrs: accepted,
8195
+ candidate_list,
8196
+ candidates_source: resolved.path,
8197
+ candidates_legacy_location: resolved.legacy,
8198
+ candidates_both_exist: resolved.bothExist
8199
+ };
8200
+ }
8201
+
8021
8202
  // src/core/next-step.ts
8203
+ function buildDeliveryState(dir) {
8204
+ const wis = discoverWorkItems(dir);
8205
+ const byState = (s) => wis.filter((w) => w.lifecycle === s).length;
8206
+ const total = wis.length;
8207
+ const withOwnership = wis.filter((w) => w.codeGlobs.length > 0).length;
8208
+ const td = buildTechDecisions(dir);
8209
+ const roadmapPath = join(dir, "knowledge/delivery/roadmap.md");
8210
+ const roadmapMd = exists(roadmapPath) ? safeRead(roadmapPath) : null;
8211
+ const stats = roadmapStats(roadmapMd, total);
8212
+ const adapters = installedAdapters(dir);
8213
+ return {
8214
+ phase: "",
8215
+ draft_work_items: byState("draft"),
8216
+ ready_work_items: byState("ready"),
8217
+ in_progress_work_items: byState("in-progress"),
8218
+ blocked_work_items: byState("blocked"),
8219
+ total_work_items: total,
8220
+ ownership_coverage: `${withOwnership}/${total}`,
8221
+ remaining_work_item_candidates: stats.remaining_work_item_candidates,
8222
+ decision_candidates: td.candidates,
8223
+ accepted_adrs: td.accepted_adrs,
8224
+ adapters_installed: adapters.length
8225
+ };
8226
+ }
8227
+ function safeRead(p2) {
8228
+ try {
8229
+ return readFile(p2);
8230
+ } catch {
8231
+ return null;
8232
+ }
8233
+ }
8022
8234
  function roadmapSignal(dir) {
8023
8235
  const p2 = join(dir, "knowledge/delivery/roadmap.md");
8024
8236
  if (!exists(p2)) return "missing";
@@ -8115,15 +8327,117 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
8115
8327
  if (roadmap !== "has-candidates") {
8116
8328
  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." };
8117
8329
  }
8118
- const wi = workItemsSignal(dir);
8119
- if (wi === "none" || wi === "none-ready") {
8120
- 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." };
8330
+ const st = buildDeliveryState(dir);
8331
+ const secondary = buildSecondaryRecommendations(st);
8332
+ if (st.total_work_items === 0) {
8333
+ return {
8334
+ id: "create-work-item",
8335
+ phase: "Delivery Preparation",
8336
+ label: "Run `kaddo create --from roadmap` to materialize the first Work Item.",
8337
+ command: "kaddo create --from roadmap",
8338
+ reason: "The roadmap has candidates but no Work Item exists yet.",
8339
+ ...secondary.length ? { secondary } : {}
8340
+ };
8121
8341
  }
8122
- const adapters = installedAdapters(dir);
8123
- if (adapters.length === 0) {
8124
- 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." };
8342
+ const adapters = st.adapters_installed;
8343
+ if (st.ready_work_items > 0) {
8344
+ if (adapters === 0) {
8345
+ return {
8346
+ id: "install-adapter",
8347
+ phase: "Active Delivery",
8348
+ label: "Install or configure an adapter before implementation (`kaddo adapters list`).",
8349
+ command: "kaddo adapters list",
8350
+ reason: `${st.ready_work_items} Work Item(s) are ready but no adapter is installed.`,
8351
+ ...secondary.length ? { secondary } : {}
8352
+ };
8353
+ }
8354
+ return {
8355
+ id: "implement",
8356
+ phase: "Active Delivery",
8357
+ label: "Use the implementation-agent or your installed adapter to plan implementation.",
8358
+ agent: "implementation-agent",
8359
+ reason: `${st.ready_work_items} Work Item(s) are ready and an adapter is installed.`,
8360
+ ...secondary.length ? { secondary } : {}
8361
+ };
8125
8362
  }
8126
- 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." };
8363
+ if (st.in_progress_work_items > 0) {
8364
+ return {
8365
+ id: "guard",
8366
+ phase: "Active Delivery",
8367
+ label: "Run `kaddo guard` and update affected knowledge after significant changes.",
8368
+ command: "kaddo guard",
8369
+ reason: `${st.in_progress_work_items} Work Item(s) are in progress.`,
8370
+ ...secondary.length ? { secondary } : {}
8371
+ };
8372
+ }
8373
+ if (st.draft_work_items > 0) {
8374
+ return {
8375
+ id: "refine-work-item",
8376
+ phase: "Active Delivery",
8377
+ label: "Refine the existing draft Work Item with the work-item-agent.",
8378
+ agent: "work-item-agent",
8379
+ skill: "work-item-refinement",
8380
+ 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.`,
8381
+ ...secondary.length ? { secondary } : {}
8382
+ };
8383
+ }
8384
+ if (st.blocked_work_items > 0) {
8385
+ return {
8386
+ id: "resolve-blocker",
8387
+ phase: "Active Delivery",
8388
+ label: "Resolve the blocker on the blocked Work Item with the work-item-agent.",
8389
+ agent: "work-item-agent",
8390
+ reason: `${st.blocked_work_items} Work Item(s) are blocked.`,
8391
+ ...secondary.length ? { secondary } : {}
8392
+ };
8393
+ }
8394
+ if (st.remaining_work_item_candidates > 0) {
8395
+ return {
8396
+ id: "materialize-more-work-items",
8397
+ phase: "Maintenance",
8398
+ label: `Materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
8399
+ command: "kaddo create --from roadmap",
8400
+ reason: "No active Work Items remain; roadmap candidates are still pending.",
8401
+ ...secondary.length ? { secondary } : {}
8402
+ };
8403
+ }
8404
+ return {
8405
+ id: "plan-next",
8406
+ phase: "Maintenance",
8407
+ label: "Use the roadmap-agent to plan the next initiative.",
8408
+ agent: "roadmap-agent",
8409
+ reason: "No active Work Items and no remaining roadmap candidates."
8410
+ };
8411
+ }
8412
+ function buildSecondaryRecommendations(st) {
8413
+ const out = [];
8414
+ const [withOwnership] = st.ownership_coverage.split("/").map(Number);
8415
+ if (st.total_work_items > 0 && withOwnership < st.total_work_items) {
8416
+ out.push({
8417
+ id: "suggest-ownership",
8418
+ label: "Run `kaddo owners suggest` for Work Items without code ownership.",
8419
+ command: "kaddo owners suggest",
8420
+ reason: `Ownership coverage is ${st.ownership_coverage}.`
8421
+ });
8422
+ }
8423
+ if (st.total_work_items > 0 && st.decision_candidates > 0 && st.accepted_adrs === 0) {
8424
+ out.push({
8425
+ id: "materialize-adrs",
8426
+ label: "Use the adr-writing skill (`kaddo adr`) to materialize decision candidates into ADRs before implementing related technical Work Items.",
8427
+ command: "kaddo adr",
8428
+ skill: "adr-writing",
8429
+ reason: `There are ${st.decision_candidates} technical decision candidate(s) and ${st.accepted_adrs} accepted ADR(s).`
8430
+ });
8431
+ }
8432
+ if (st.total_work_items > 0 && st.remaining_work_item_candidates > 0) {
8433
+ out.push({
8434
+ id: "materialize-more-work-items",
8435
+ label: `Later, materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
8436
+ command: "kaddo create --from roadmap",
8437
+ reason: `There are ${st.remaining_work_item_candidates} remaining Work Item candidate(s).`
8438
+ });
8439
+ }
8440
+ return out;
8127
8441
  }
8128
8442
 
8129
8443
  // src/core/readiness.ts
@@ -8218,92 +8532,6 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
8218
8532
  };
8219
8533
  }
8220
8534
 
8221
- // src/core/decisions.ts
8222
- var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
8223
- var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
8224
- var DECISIONS_DIR = "knowledge/tech/decisions";
8225
- function resolveCandidatesPath(dir) {
8226
- const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
8227
- const legacy = exists(join(dir, CANDIDATES_LEGACY));
8228
- if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
8229
- if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
8230
- return { path: null, legacy: false, bothExist: false };
8231
- }
8232
- function cleanCandidateTitle(title) {
8233
- return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
8234
- }
8235
- function slugify2(s) {
8236
- return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
8237
- }
8238
- function parseDecisionCandidates(md) {
8239
- const out = [];
8240
- for (const line of md.split(/\r?\n/)) {
8241
- const m = line.match(/^##\s+(.+?)\s*$/);
8242
- if (m) {
8243
- const raw = m[1].trim();
8244
- if (!raw || /^_.*_$/.test(raw)) continue;
8245
- const t = cleanCandidateTitle(raw);
8246
- if (t) out.push(t);
8247
- }
8248
- }
8249
- return out;
8250
- }
8251
- function countAdrs(dir) {
8252
- const base = join(dir, DECISIONS_DIR);
8253
- if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
8254
- let total = 0;
8255
- let draft = 0;
8256
- let accepted = 0;
8257
- for (const entry of readDir(base)) {
8258
- if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
8259
- const full = join(base, entry);
8260
- if (!isFile(full)) continue;
8261
- total += 1;
8262
- let content = "";
8263
- try {
8264
- content = readFile(full);
8265
- } catch {
8266
- continue;
8267
- }
8268
- const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
8269
- if (status === "accepted") accepted += 1;
8270
- else draft += 1;
8271
- }
8272
- return { total, draft, accepted };
8273
- }
8274
- function buildTechDecisions(dir) {
8275
- const resolved = resolveCandidatesPath(dir);
8276
- let titles = [];
8277
- if (resolved.path) {
8278
- try {
8279
- titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
8280
- } catch {
8281
- titles = [];
8282
- }
8283
- }
8284
- const { total, draft, accepted } = countAdrs(dir);
8285
- const candidate_list = titles.map((title, i) => {
8286
- const n = String(total + i + 1).padStart(3, "0");
8287
- return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8288
- });
8289
- let status;
8290
- if (accepted > 0) status = "accepted-adrs";
8291
- else if (total > 0) status = "draft-adrs";
8292
- else if (titles.length > 0) status = "candidates";
8293
- else status = "none";
8294
- return {
8295
- status,
8296
- candidates: titles.length,
8297
- adrs: total,
8298
- draft_adrs: draft,
8299
- accepted_adrs: accepted,
8300
- candidate_list,
8301
- candidates_source: resolved.path,
8302
- candidates_legacy_location: resolved.legacy,
8303
- candidates_both_exist: resolved.bothExist
8304
- };
8305
- }
8306
-
8307
8535
  // src/core/assets.ts
8308
8536
  import matter4 from "gray-matter";
8309
8537
  function canonicalAgents() {
@@ -8431,16 +8659,25 @@ function buildRoadmapQuality(dir) {
8431
8659
  const items = parseRoadmapCandidateQuality(md);
8432
8660
  const count = (pred) => items.filter(pred).length;
8433
8661
  const grounded = count((i) => i.grounded);
8434
- return {
8435
- candidates: items.length,
8662
+ const initiatives = {
8663
+ total: items.length,
8436
8664
  grounded,
8437
8665
  with_related_domain: count((i) => i.hasRelatedDomain),
8438
8666
  with_related_capability: count((i) => i.hasRelatedCapability),
8439
8667
  with_source_signals: count((i) => i.hasSourceSignals),
8440
- // Only "needs refinement" when there are candidates and at least one isn't grounded.
8668
+ // Only "needs refinement" when there are initiatives and at least one isn't grounded.
8441
8669
  needs_refinement: items.length > 0 && grounded < items.length,
8442
8670
  items
8443
8671
  };
8672
+ const wiCandidates = md ? parseRoadmapCandidates(md) : [];
8673
+ const wcount = (pred) => wiCandidates.filter(pred).length;
8674
+ const work_item_candidates = {
8675
+ total: wiCandidates.length,
8676
+ with_source_initiative: wcount((c) => Boolean(c.initiative?.id || c.initiative?.title)),
8677
+ with_related_domain: wcount((c) => Boolean(c.domain)),
8678
+ with_related_capability: wcount((c) => Boolean(c.relatedCapabilities?.length))
8679
+ };
8680
+ return { initiatives, work_item_candidates };
8444
8681
  }
8445
8682
 
8446
8683
  // src/core/project-explain.ts
@@ -8723,10 +8960,11 @@ function renderExplanationHuman(exp) {
8723
8960
  lines.push(`- Delivery: ${ls("Delivery")}`);
8724
8961
  lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
8725
8962
  if (exp.roadmap.present) {
8726
- lines.push(`- Roadmap candidates: ${exp.roadmap.candidates}`);
8727
- lines.push(`- Materialized work items: ${exp.roadmap.materialized}`);
8728
- if (exp.roadmap.remaining > 0)
8729
- lines.push(`- Remaining candidates: ${exp.roadmap.remaining}`);
8963
+ lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
8964
+ lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
8965
+ lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
8966
+ if (exp.roadmap.remaining_work_item_candidates > 0)
8967
+ lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
8730
8968
  } else {
8731
8969
  lines.push(`- Work items: ${exp.workItems.total}`);
8732
8970
  }
@@ -8851,9 +9089,12 @@ function renderExplanationHuman(exp) {
8851
9089
  }
8852
9090
  lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
8853
9091
  lines.push("");
8854
- if (exp.suggestedNextSteps.length > 0) {
9092
+ const rec = exp.nextStepRecommendation;
9093
+ const secondary = rec.secondary ?? [];
9094
+ const steps = secondary.length > 0 || /Delivery|Active|Maintenance/.test(rec.phase) ? [rec.label, ...secondary.map((s2) => s2.label)] : exp.suggestedNextSteps;
9095
+ if (steps.length > 0) {
8855
9096
  lines.push("## Suggested Next Steps");
8856
- exp.suggestedNextSteps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
9097
+ steps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
8857
9098
  lines.push("");
8858
9099
  }
8859
9100
  const r = exp.readiness;
@@ -8903,17 +9144,42 @@ function renderExplanationHuman(exp) {
8903
9144
  lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
8904
9145
  lines.push("");
8905
9146
  }
9147
+ if (exp.roadmap.present) {
9148
+ lines.push("## Roadmap Status");
9149
+ lines.push(`- Initiatives: ${exp.roadmap.initiatives}`);
9150
+ lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
9151
+ lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
9152
+ lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
9153
+ lines.push("");
9154
+ }
8906
9155
  const rq = exp.roadmapQuality;
8907
- if (rq.candidates > 0) {
9156
+ const rqi = rq.initiatives;
9157
+ const rqw = rq.work_item_candidates;
9158
+ if (rqi.total > 0 || rqw.total > 0) {
8908
9159
  lines.push("## Roadmap Quality");
8909
- lines.push(`- Candidates: ${rq.candidates}`);
8910
- lines.push(`- Grounded: ${rq.grounded}/${rq.candidates}`);
8911
- lines.push(`- With related domain: ${rq.with_related_domain}/${rq.candidates}`);
8912
- lines.push(`- With related capability: ${rq.with_related_capability}/${rq.candidates}`);
8913
- lines.push(`- With source signals: ${rq.with_source_signals}/${rq.candidates}`);
8914
- if (rq.needs_refinement) {
9160
+ if (rqi.total > 0) {
9161
+ lines.push("Initiatives:");
9162
+ lines.push(`- Candidates evaluated: ${rqi.total}`);
9163
+ lines.push(`- Grounded: ${rqi.grounded}/${rqi.total}`);
9164
+ lines.push(`- With related domain: ${rqi.with_related_domain}/${rqi.total}`);
9165
+ lines.push(`- With related capability: ${rqi.with_related_capability}/${rqi.total}`);
9166
+ lines.push(`- With source signals: ${rqi.with_source_signals}/${rqi.total}`);
9167
+ }
9168
+ if (rqw.total > 0) {
8915
9169
  lines.push("");
8916
- lines.push("Roadmap quality: needs refinement. Suggested: use roadmap-agent to add domain / capability / source signals.");
9170
+ lines.push("Work Item Candidates:");
9171
+ lines.push(`- Candidates: ${rqw.total}`);
9172
+ lines.push(`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`);
9173
+ lines.push(`- With related domain: ${rqw.with_related_domain}/${rqw.total}`);
9174
+ lines.push(`- With related capability: ${rqw.with_related_capability}/${rqw.total}`);
9175
+ }
9176
+ if (rqi.needs_refinement) {
9177
+ lines.push("");
9178
+ lines.push("Roadmap quality: needs refinement.");
9179
+ lines.push("Suggested: use roadmap-agent to add domain / capability / source signals.");
9180
+ } else if (rqw.total > 0) {
9181
+ lines.push("");
9182
+ lines.push("Work Item candidate quality: good.");
8917
9183
  }
8918
9184
  lines.push("");
8919
9185
  }
@@ -9301,6 +9567,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9301
9567
  nextStep: nextStepRecommendation.label,
9302
9568
  recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
9303
9569
  };
9570
+ const deliveryState = { ...buildDeliveryState(dir), phase: nextStepRecommendation.phase };
9304
9571
  return {
9305
9572
  version: CONTEXT_PACK_VERSION,
9306
9573
  generatedAt: now.toISOString(),
@@ -9333,6 +9600,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9333
9600
  roadmap,
9334
9601
  phase: unifiedPhase,
9335
9602
  nextStepRecommendation,
9603
+ deliveryState,
9336
9604
  techDecisions,
9337
9605
  techKnowledge,
9338
9606
  roadmapQuality: buildRoadmapQuality(dir),
@@ -9397,6 +9665,31 @@ function renderContextPack(pack) {
9397
9665
  parts.push(`Next step: ${pack.phase.nextStep}
9398
9666
  `);
9399
9667
  }
9668
+ const ds = pack.deliveryState;
9669
+ const rec = pack.nextStepRecommendation;
9670
+ if (ds.total_work_items > 0 || rec.phase !== "Setup") {
9671
+ parts.push("## Delivery State\n");
9672
+ parts.push(
9673
+ [
9674
+ `- Phase: ${ds.phase}`,
9675
+ `- Draft Work Items: ${ds.draft_work_items}`,
9676
+ `- Ready Work Items: ${ds.ready_work_items}`,
9677
+ `- In-progress Work Items: ${ds.in_progress_work_items}`,
9678
+ `- Ownership coverage: ${ds.ownership_coverage}`,
9679
+ `- Remaining Work Item candidates: ${ds.remaining_work_item_candidates}`
9680
+ ].join("\n") + "\n"
9681
+ );
9682
+ parts.push("## Next Step Recommendation\n");
9683
+ const recLines = [`- ${rec.label}`, ` - id: ${rec.id}`, ` - reason: ${rec.reason}`];
9684
+ if (rec.agent) recLines.push(` - agent: ${rec.agent}`);
9685
+ if (rec.skill) recLines.push(` - skill: ${rec.skill}`);
9686
+ if (rec.command) recLines.push(` - command: \`${rec.command}\``);
9687
+ parts.push(recLines.join("\n") + "\n");
9688
+ if (rec.secondary && rec.secondary.length > 0) {
9689
+ parts.push("Also (secondary):\n");
9690
+ parts.push(rec.secondary.map((s) => `- ${s.label}`).join("\n") + "\n");
9691
+ }
9692
+ }
9400
9693
  parts.push("## Knowledge Layers\n");
9401
9694
  parts.push(
9402
9695
  "Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
@@ -9434,35 +9727,52 @@ function renderContextPack(pack) {
9434
9727
  }
9435
9728
  parts.push("## Current Knowledge\n");
9436
9729
  parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
9437
- parts.push("## Roadmap\n");
9730
+ parts.push("## Roadmap Status\n");
9438
9731
  if (pack.roadmap.present) {
9439
9732
  parts.push(
9440
9733
  [
9441
- `- Roadmap candidates: ${pack.roadmap.candidates}`,
9442
- `- Materialized work items: ${pack.roadmap.materialized}`,
9443
- `- Remaining candidates: ${pack.roadmap.remaining}`
9734
+ `- Initiatives: ${pack.roadmap.initiatives}`,
9735
+ `- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
9736
+ `- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
9737
+ `- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
9444
9738
  ].join("\n") + "\n"
9445
9739
  );
9446
- if (pack.roadmap.remaining > 0) {
9740
+ if (pack.roadmap.remaining_work_item_candidates > 0) {
9447
9741
  parts.push(
9448
- "Candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
9742
+ "Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
9449
9743
  );
9450
9744
  }
9451
9745
  }
9452
9746
  parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
9453
9747
  const rq = pack.roadmapQuality;
9454
- if (rq.candidates > 0) {
9748
+ const rqi = rq.initiatives;
9749
+ const rqw = rq.work_item_candidates;
9750
+ if (rqi.total > 0 || rqw.total > 0) {
9455
9751
  parts.push("## Roadmap Quality\n");
9456
- parts.push(
9457
- [
9458
- `- Candidates: ${rq.candidates}`,
9459
- `- Grounded: ${rq.grounded}/${rq.candidates}`,
9460
- `- With related domain: ${rq.with_related_domain}/${rq.candidates}`,
9461
- `- With related capability: ${rq.with_related_capability}/${rq.candidates}`,
9462
- `- With source signals: ${rq.with_source_signals}/${rq.candidates}`
9463
- ].join("\n") + "\n"
9464
- );
9465
- if (rq.needs_refinement) {
9752
+ if (rqi.total > 0) {
9753
+ parts.push(
9754
+ [
9755
+ "Initiatives:",
9756
+ `- Candidates evaluated: ${rqi.total}`,
9757
+ `- Grounded: ${rqi.grounded}/${rqi.total}`,
9758
+ `- With related domain: ${rqi.with_related_domain}/${rqi.total}`,
9759
+ `- With related capability: ${rqi.with_related_capability}/${rqi.total}`,
9760
+ `- With source signals: ${rqi.with_source_signals}/${rqi.total}`
9761
+ ].join("\n") + "\n"
9762
+ );
9763
+ }
9764
+ if (rqw.total > 0) {
9765
+ parts.push(
9766
+ [
9767
+ "Work Item Candidates:",
9768
+ `- Candidates: ${rqw.total}`,
9769
+ `- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`,
9770
+ `- With related domain: ${rqw.with_related_domain}/${rqw.total}`,
9771
+ `- With related capability: ${rqw.with_related_capability}/${rqw.total}`
9772
+ ].join("\n") + "\n"
9773
+ );
9774
+ }
9775
+ if (rqi.needs_refinement) {
9466
9776
  parts.push(
9467
9777
  "Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
9468
9778
  );
@@ -9883,8 +10193,13 @@ function runUnderstand() {
9883
10193
  for (const r of assessment.reasons) console.log(` - ${r}`);
9884
10194
  }
9885
10195
  if (rec.agent) console.log(`Recommended: ${rec.agent}`);
10196
+ if (rec.skill) console.log(`Recommended skill: ${rec.skill}`);
9886
10197
  console.log(`Next step: ${rec.label}`);
9887
10198
  if (rec.reason) console.log(`Why: ${rec.reason}`);
10199
+ if (rec.secondary && rec.secondary.length > 0) {
10200
+ console.log("Also:");
10201
+ for (const s of rec.secondary) console.log(` - ${s.label}`);
10202
+ }
9888
10203
  const installedSkills = discoverInstalledSkills(dir);
9889
10204
  if (installedSkills.length > 0 && assessment.recommendedAgents.length > 0) {
9890
10205
  const recSkills = skillsForAgents(installedSkills, assessment.recommendedAgents);
@@ -9900,15 +10215,15 @@ function runUnderstand() {
9900
10215
  console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
9901
10216
  console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
9902
10217
  }
9903
- const rq = exp.roadmapQuality;
9904
- if (rq.needs_refinement) {
10218
+ const rqi = exp.roadmapQuality.initiatives;
10219
+ if (rqi.needs_refinement) {
9905
10220
  console.log("");
9906
- console.log(`Roadmap quality: ${rq.grounded}/${rq.candidates} candidates grounded.`);
9907
- console.log(" \u2192 Use roadmap-agent to ground roadmap candidates in capability domains, gaps and source signals");
10221
+ console.log(`Roadmap quality: ${rqi.grounded}/${rqi.total} initiatives grounded.`);
10222
+ console.log(" \u2192 Use roadmap-agent to ground roadmap initiatives in capability domains, gaps and source signals");
9908
10223
  console.log(" before `kaddo create --from roadmap`.");
9909
- } else if (rq.candidates > 0 && rq.grounded === rq.candidates) {
10224
+ } else if (rqi.total > 0 && rqi.grounded === rqi.total && exp.roadmap.materialized_work_items === 0) {
9910
10225
  console.log("");
9911
- console.log("Roadmap candidates are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
10226
+ console.log("Roadmap initiatives are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
9912
10227
  }
9913
10228
  const ia = exp.installedAssets;
9914
10229
  const outdatedRecommended = ia.agents.items.filter(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.43.0",
3
+ "version": "3.45.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {