@kaddo/cli 3.44.0 → 3.46.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 +1 -0
  2. package/dist/index.js +457 -122
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -543,6 +543,7 @@ create --from roadmap → owners → guard → explain`.
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
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` |
546
547
 
547
548
  **Optional modules (installed with `kaddo add`):**
548
549
 
package/dist/index.js CHANGED
@@ -8113,7 +8113,124 @@ function buildSharedFileStatuses(statuses) {
8113
8113
  }));
8114
8114
  }
8115
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
+
8116
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
+ }
8117
8234
  function roadmapSignal(dir) {
8118
8235
  const p2 = join(dir, "knowledge/delivery/roadmap.md");
8119
8236
  if (!exists(p2)) return "missing";
@@ -8210,15 +8327,117 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
8210
8327
  if (roadmap !== "has-candidates") {
8211
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." };
8212
8329
  }
8213
- const wi = workItemsSignal(dir);
8214
- if (wi === "none" || wi === "none-ready") {
8215
- 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
+ };
8216
8341
  }
8217
- const adapters = installedAdapters(dir);
8218
- if (adapters.length === 0) {
8219
- 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
+ };
8362
+ }
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
+ });
8220
8422
  }
8221
- 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." };
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;
8222
8441
  }
8223
8442
 
8224
8443
  // src/core/readiness.ts
@@ -8313,92 +8532,6 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
8313
8532
  };
8314
8533
  }
8315
8534
 
8316
- // src/core/decisions.ts
8317
- var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
8318
- var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
8319
- var DECISIONS_DIR = "knowledge/tech/decisions";
8320
- function resolveCandidatesPath(dir) {
8321
- const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
8322
- const legacy = exists(join(dir, CANDIDATES_LEGACY));
8323
- if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
8324
- if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
8325
- return { path: null, legacy: false, bothExist: false };
8326
- }
8327
- function cleanCandidateTitle(title) {
8328
- return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
8329
- }
8330
- function slugify2(s) {
8331
- return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
8332
- }
8333
- function parseDecisionCandidates(md) {
8334
- const out = [];
8335
- for (const line of md.split(/\r?\n/)) {
8336
- const m = line.match(/^##\s+(.+?)\s*$/);
8337
- if (m) {
8338
- const raw = m[1].trim();
8339
- if (!raw || /^_.*_$/.test(raw)) continue;
8340
- const t = cleanCandidateTitle(raw);
8341
- if (t) out.push(t);
8342
- }
8343
- }
8344
- return out;
8345
- }
8346
- function countAdrs(dir) {
8347
- const base = join(dir, DECISIONS_DIR);
8348
- if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
8349
- let total = 0;
8350
- let draft = 0;
8351
- let accepted = 0;
8352
- for (const entry of readDir(base)) {
8353
- if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
8354
- const full = join(base, entry);
8355
- if (!isFile(full)) continue;
8356
- total += 1;
8357
- let content = "";
8358
- try {
8359
- content = readFile(full);
8360
- } catch {
8361
- continue;
8362
- }
8363
- const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
8364
- if (status === "accepted") accepted += 1;
8365
- else draft += 1;
8366
- }
8367
- return { total, draft, accepted };
8368
- }
8369
- function buildTechDecisions(dir) {
8370
- const resolved = resolveCandidatesPath(dir);
8371
- let titles = [];
8372
- if (resolved.path) {
8373
- try {
8374
- titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
8375
- } catch {
8376
- titles = [];
8377
- }
8378
- }
8379
- const { total, draft, accepted } = countAdrs(dir);
8380
- const candidate_list = titles.map((title, i) => {
8381
- const n = String(total + i + 1).padStart(3, "0");
8382
- return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8383
- });
8384
- let status;
8385
- if (accepted > 0) status = "accepted-adrs";
8386
- else if (total > 0) status = "draft-adrs";
8387
- else if (titles.length > 0) status = "candidates";
8388
- else status = "none";
8389
- return {
8390
- status,
8391
- candidates: titles.length,
8392
- adrs: total,
8393
- draft_adrs: draft,
8394
- accepted_adrs: accepted,
8395
- candidate_list,
8396
- candidates_source: resolved.path,
8397
- candidates_legacy_location: resolved.legacy,
8398
- candidates_both_exist: resolved.bothExist
8399
- };
8400
- }
8401
-
8402
8535
  // src/core/assets.ts
8403
8536
  import matter4 from "gray-matter";
8404
8537
  function canonicalAgents() {
@@ -8956,9 +9089,12 @@ function renderExplanationHuman(exp) {
8956
9089
  }
8957
9090
  lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
8958
9091
  lines.push("");
8959
- 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) {
8960
9096
  lines.push("## Suggested Next Steps");
8961
- exp.suggestedNextSteps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
9097
+ steps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
8962
9098
  lines.push("");
8963
9099
  }
8964
9100
  const r = exp.readiness;
@@ -9431,6 +9567,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9431
9567
  nextStep: nextStepRecommendation.label,
9432
9568
  recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
9433
9569
  };
9570
+ const deliveryState = { ...buildDeliveryState(dir), phase: nextStepRecommendation.phase };
9434
9571
  return {
9435
9572
  version: CONTEXT_PACK_VERSION,
9436
9573
  generatedAt: now.toISOString(),
@@ -9463,6 +9600,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9463
9600
  roadmap,
9464
9601
  phase: unifiedPhase,
9465
9602
  nextStepRecommendation,
9603
+ deliveryState,
9466
9604
  techDecisions,
9467
9605
  techKnowledge,
9468
9606
  roadmapQuality: buildRoadmapQuality(dir),
@@ -9527,6 +9665,31 @@ function renderContextPack(pack) {
9527
9665
  parts.push(`Next step: ${pack.phase.nextStep}
9528
9666
  `);
9529
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
+ }
9530
9693
  parts.push("## Knowledge Layers\n");
9531
9694
  parts.push(
9532
9695
  "Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
@@ -9812,7 +9975,8 @@ function buildUnderstandPlan(dir, config) {
9812
9975
  name: config.project.name,
9813
9976
  state,
9814
9977
  teamSize: config.team.size,
9815
- structure: config.project.structure
9978
+ structure: config.project.structure,
9979
+ language: "English"
9816
9980
  },
9817
9981
  scanAvailable: exists(join(dir, ".kaddo", "scan.json")),
9818
9982
  contextPackPath: ".kaddo/context-pack.md",
@@ -9821,6 +9985,18 @@ function buildUnderstandPlan(dir, config) {
9821
9985
  steps
9822
9986
  };
9823
9987
  }
9988
+ function enrichUnderstandPlan(plan, opts) {
9989
+ return {
9990
+ ...plan,
9991
+ project: { ...plan.project, language: opts.language },
9992
+ phase: opts.phase,
9993
+ nextStepRecommendation: opts.nextStepRecommendation,
9994
+ deliveryState: opts.deliveryState,
9995
+ activeWorkItems: opts.activeWorkItems,
9996
+ recommendedPaths: opts.recommendedPaths,
9997
+ recommendedSkillPaths: opts.recommendedSkillPaths
9998
+ };
9999
+ }
9824
10000
 
9825
10001
  // src/templates/understand-template.ts
9826
10002
  function stateLabel2(state) {
@@ -9831,42 +10007,140 @@ function agentName(file) {
9831
10007
  }
9832
10008
  function renderUnderstand(plan) {
9833
10009
  const { project, steps } = plan;
10010
+ const rec = plan.nextStepRecommendation;
10011
+ const ds = plan.deliveryState;
9834
10012
  const parts = [];
9835
10013
  parts.push("# Kaddo Understand Handoff\n");
9836
10014
  parts.push(
9837
10015
  "> Generated by `kaddo understand`. Kaddo does not call an LLM \u2014 you stay in control of the interpretation.\n"
9838
10016
  );
9839
10017
  parts.push("## Project\n");
9840
- parts.push(
9841
- [
9842
- `- Name: ${project.name}`,
9843
- `- State: ${project.state}`,
9844
- `- Team: ${project.teamSize}`,
9845
- `- Structure: ${project.structure}`
9846
- ].join("\n") + "\n"
9847
- );
9848
- parts.push("## Recommended Agent Flow\n");
9849
- parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
10018
+ const projectLines = [
10019
+ `- Name: ${project.name}`,
10020
+ `- State: ${project.state}`,
10021
+ `- Team: ${project.teamSize}`,
10022
+ `- Structure: ${project.structure}`
10023
+ ];
10024
+ if (project.language) projectLines.push(`- Language: ${project.language}`);
10025
+ parts.push(projectLines.join("\n") + "\n");
10026
+ if (plan.phase && rec) {
10027
+ parts.push("## Current Phase\n");
10028
+ const phaseLines = [`- Phase: ${plan.phase}`];
10029
+ if (rec.agent) phaseLines.push(`- Recommended agent: ${agentName(rec.agent)}`);
10030
+ if (rec.skill) phaseLines.push(`- Recommended skill: ${rec.skill}`);
10031
+ phaseLines.push(`- Next step: ${rec.label}`);
10032
+ if (rec.reason) phaseLines.push(`- Reason: ${rec.reason}`);
10033
+ parts.push(phaseLines.join("\n") + "\n");
10034
+ }
10035
+ if (ds && ds.total_work_items > 0) {
10036
+ parts.push("## Delivery State\n");
10037
+ parts.push(
10038
+ [
10039
+ `- Draft Work Items: ${ds.draft_work_items}`,
10040
+ `- Ready Work Items: ${ds.ready_work_items}`,
10041
+ `- In-progress Work Items: ${ds.in_progress_work_items}`,
10042
+ `- Blocked Work Items: ${ds.blocked_work_items}`,
10043
+ `- Ownership coverage: ${ds.ownership_coverage}`,
10044
+ `- Remaining Work Item candidates: ${ds.remaining_work_item_candidates}`,
10045
+ `- Technical decision candidates: ${ds.decision_candidates}`,
10046
+ `- Accepted ADRs: ${ds.accepted_adrs}`,
10047
+ `- Installed adapters: ${ds.adapters_installed}`
10048
+ ].join("\n") + "\n"
10049
+ );
10050
+ }
10051
+ if (steps.length > 0) {
10052
+ parts.push("## Recommended Agent Flow\n");
10053
+ parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
9850
10054
  `);
9851
- parts.push(
9852
- steps.map((s, i) => {
9853
- const flag = s.installed ? "" : " _(not installed \u2014 run `kaddo add agents`)_";
9854
- return `${i + 1}. ${agentName(s.agent)} \u2192 \`${s.output}\`${flag}`;
9855
- }).join("\n") + "\n"
9856
- );
10055
+ parts.push(
10056
+ steps.map((s, i) => {
10057
+ const flag = s.installed ? "" : " _(not installed \u2014 run `kaddo add agents`)_";
10058
+ return `${i + 1}. ${agentName(s.agent)} \u2192 \`${s.output}\`${flag}`;
10059
+ }).join("\n") + "\n"
10060
+ );
10061
+ } else if (rec) {
10062
+ parts.push("## Recommended Agent Flow\n");
10063
+ const flowSteps = [];
10064
+ if (rec.agent) flowSteps.push(agentName(rec.agent));
10065
+ if (rec.secondary) {
10066
+ for (const s of rec.secondary) {
10067
+ if (s.agent) flowSteps.push(agentName(s.agent));
10068
+ else if (s.skill) flowSteps.push(`${s.skill} skill`);
10069
+ else if (s.command) flowSteps.push(`\`${s.command}\``);
10070
+ }
10071
+ }
10072
+ if (flowSteps.length > 0) {
10073
+ parts.push(flowSteps.map((f, i) => `${i + 1}. ${f}`).join("\n") + "\n");
10074
+ }
10075
+ }
10076
+ if (rec) {
10077
+ parts.push("## Primary Recommendation\n");
10078
+ const recLines = [`- id: ${rec.id}`];
10079
+ if (rec.agent) recLines.push(`- agent: ${agentName(rec.agent)}`);
10080
+ if (rec.skill) recLines.push(`- skill: ${rec.skill}`);
10081
+ if (rec.command) recLines.push(`- command: \`${rec.command}\``);
10082
+ recLines.push(`- reason: ${rec.reason}`);
10083
+ parts.push(recLines.join("\n") + "\n");
10084
+ }
10085
+ if (rec?.secondary && rec.secondary.length > 0) {
10086
+ parts.push("## Secondary Recommendations\n");
10087
+ parts.push(rec.secondary.map((s, i) => `${i + 1}. ${s.label}`).join("\n") + "\n");
10088
+ }
10089
+ if (plan.activeWorkItems && plan.activeWorkItems.length > 0) {
10090
+ parts.push("## Active Work Items\n");
10091
+ parts.push(
10092
+ plan.activeWorkItems.map((w) => `- ${w.id} [${w.type}] ${w.lifecycle} \u2014 ${w.title}`).join("\n") + "\n"
10093
+ );
10094
+ }
9857
10095
  parts.push("## Context Pack\n");
9858
10096
  const scanNote = plan.scanAvailable ? "" : " (incomplete \u2014 run `kaddo scan` first for a richer baseline)";
9859
10097
  parts.push(`Use \`${plan.contextPackPath}\` as the primary input${scanNote}.
9860
10098
  `);
10099
+ const agentPaths = [];
10100
+ if (plan.recommendedPaths && plan.recommendedPaths.length > 0) {
10101
+ agentPaths.push(...plan.recommendedPaths);
10102
+ } else if (steps.length > 0) {
10103
+ agentPaths.push(...steps.map((s) => agentInstallPath(s.agent)));
10104
+ }
10105
+ if (plan.recommendedSkillPaths && plan.recommendedSkillPaths.length > 0) {
10106
+ agentPaths.push(...plan.recommendedSkillPaths);
10107
+ }
10108
+ agentPaths.push(plan.contextPackPath);
9861
10109
  parts.push("## Agent Prompts\n");
9862
- parts.push(
9863
- steps.map((s) => `- \`${agentInstallPath(s.agent)}\``).join("\n") + "\n"
9864
- );
9865
- parts.push("## Expected Outputs\n");
9866
- parts.push(steps.map((s) => `- \`${s.output}\``).join("\n") + "\n");
10110
+ if (agentPaths.length > 1) {
10111
+ parts.push("Use:\n");
10112
+ parts.push(agentPaths.map((p2) => `- \`${p2}\``).join("\n") + "\n");
10113
+ } else {
10114
+ parts.push(`- \`${plan.contextPackPath}\`
10115
+ `);
10116
+ }
10117
+ if (steps.length > 0) {
10118
+ parts.push("## Expected Outputs\n");
10119
+ parts.push(steps.map((s) => `- \`${s.output}\``).join("\n") + "\n");
10120
+ } else if (rec) {
10121
+ parts.push("## Expected Outputs\n");
10122
+ const outputs = [];
10123
+ if (rec.target) outputs.push(`- \`${rec.target}\``);
10124
+ if (rec.id === "refine-work-item" || rec.id === "resolve-blocker")
10125
+ outputs.push("- Refined Work Item content.");
10126
+ if (rec.id === "create-work-item")
10127
+ outputs.push("- A new Work Item under `knowledge/delivery/work-items/`.");
10128
+ if (rec.id === "implement")
10129
+ outputs.push("- Implementation plan or code changes guided by the Work Item.");
10130
+ if (rec.id === "guard")
10131
+ outputs.push("- Updated knowledge artifacts reflecting recent code changes.");
10132
+ if (rec.id === "install-adapter")
10133
+ outputs.push("- An adapter configured and injected (`kaddo adapters list`).");
10134
+ if (outputs.length > 0) {
10135
+ parts.push("The LLM should produce:\n");
10136
+ parts.push(outputs.join("\n") + "\n");
10137
+ } else {
10138
+ parts.push("_See the primary recommendation above._\n");
10139
+ }
10140
+ }
9867
10141
  parts.push("## Copy/Paste Instructions\n");
9868
- const first2 = steps[0];
9869
- if (first2) {
10142
+ if (steps.length > 0) {
10143
+ const first2 = steps[0];
9870
10144
  parts.push(
9871
10145
  [
9872
10146
  `Start with **${agentName(first2.agent)}**:`,
@@ -9878,9 +10152,31 @@ function renderUnderstand(plan) {
9878
10152
  `5. Save the result in: \`${first2.output}\``
9879
10153
  ].join("\n") + "\n"
9880
10154
  );
10155
+ } else if (agentPaths.length > 1) {
10156
+ parts.push("Paste the following into your LLM chat:\n");
10157
+ parts.push(agentPaths.map((p2, i) => `${i + 1}. \`${p2}\``).join("\n") + "\n");
10158
+ if (rec?.agent) {
10159
+ parts.push(`Ask the LLM to follow the ${agentName(rec.agent)} instructions.
10160
+ `);
10161
+ }
10162
+ } else {
10163
+ parts.push(`Paste \`${plan.contextPackPath}\` into your LLM chat.
10164
+ `);
9881
10165
  }
9882
10166
  parts.push("## Next Steps\n");
9883
- if (steps.length > 1) {
10167
+ if (rec) {
10168
+ const nextLines = [];
10169
+ nextLines.push(`1. ${rec.label}`);
10170
+ let n = 2;
10171
+ if (rec.secondary) {
10172
+ for (const s of rec.secondary) {
10173
+ nextLines.push(`${n}. ${s.label}`);
10174
+ n++;
10175
+ }
10176
+ }
10177
+ nextLines.push(`${n}. Re-run \`kaddo explain\`.`);
10178
+ parts.push(nextLines.join("\n") + "\n");
10179
+ } else if (steps.length > 1) {
9884
10180
  parts.push(
9885
10181
  `After saving \`${steps[0].output}\`, continue with **${agentName(steps[1].agent)}** (feed it the context pack plus the artifacts you already produced). Re-run \`kaddo understand\` any time to see this plan again.
9886
10182
  `
@@ -10030,8 +10326,13 @@ function runUnderstand() {
10030
10326
  for (const r of assessment.reasons) console.log(` - ${r}`);
10031
10327
  }
10032
10328
  if (rec.agent) console.log(`Recommended: ${rec.agent}`);
10329
+ if (rec.skill) console.log(`Recommended skill: ${rec.skill}`);
10033
10330
  console.log(`Next step: ${rec.label}`);
10034
10331
  if (rec.reason) console.log(`Why: ${rec.reason}`);
10332
+ if (rec.secondary && rec.secondary.length > 0) {
10333
+ console.log("Also:");
10334
+ for (const s of rec.secondary) console.log(` - ${s.label}`);
10335
+ }
10035
10336
  const installedSkills = discoverInstalledSkills(dir);
10036
10337
  if (installedSkills.length > 0 && assessment.recommendedAgents.length > 0) {
10037
10338
  const recSkills = skillsForAgents(installedSkills, assessment.recommendedAgents);
@@ -10053,7 +10354,7 @@ function runUnderstand() {
10053
10354
  console.log(`Roadmap quality: ${rqi.grounded}/${rqi.total} initiatives grounded.`);
10054
10355
  console.log(" \u2192 Use roadmap-agent to ground roadmap initiatives in capability domains, gaps and source signals");
10055
10356
  console.log(" before `kaddo create --from roadmap`.");
10056
- } else if (rqi.total > 0 && rqi.grounded === rqi.total) {
10357
+ } else if (rqi.total > 0 && rqi.grounded === rqi.total && exp.roadmap.materialized_work_items === 0) {
10057
10358
  console.log("");
10058
10359
  console.log("Roadmap initiatives are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
10059
10360
  }
@@ -10100,7 +10401,41 @@ function runUnderstand() {
10100
10401
  console.log(`Other active work items: ${active.slice(1).map((w) => w.id).join(", ")}`);
10101
10402
  }
10102
10403
  }
10103
- writeFile(join(dir, ".kaddo", "understand.md"), renderUnderstand(plan));
10404
+ const ds = buildDeliveryState(dir);
10405
+ ds.phase = assessment.phase;
10406
+ const wis = discoverWorkItems(dir);
10407
+ const activeWis = wis.filter((w) => w.lifecycle && ["draft", "ready", "in-progress", "blocked"].includes(w.lifecycle)).map((w) => ({
10408
+ id: w.id,
10409
+ title: w.title,
10410
+ type: w.type,
10411
+ lifecycle: w.lifecycle ?? "draft",
10412
+ knowledgeLevel: w.knowledgeLevel,
10413
+ hasOwnership: w.codeGlobs.length > 0
10414
+ }));
10415
+ const recommendedPaths = [];
10416
+ if (rec.agent) {
10417
+ const agentFile = rec.agent.endsWith(".md") ? rec.agent : `${rec.agent}.md`;
10418
+ if (agentIsInstalled(dir, agentFile)) {
10419
+ recommendedPaths.push(agentInstallPath(agentFile));
10420
+ }
10421
+ }
10422
+ const recommendedSkillPaths = [];
10423
+ if (rec.skill) {
10424
+ const sp = skillInstallPath(rec.skill);
10425
+ if (exists(join(dir, sp))) {
10426
+ recommendedSkillPaths.push(sp);
10427
+ }
10428
+ }
10429
+ const enrichedPlan = enrichUnderstandPlan(plan, {
10430
+ phase: assessment.phase,
10431
+ nextStepRecommendation: rec,
10432
+ deliveryState: ds,
10433
+ activeWorkItems: activeWis,
10434
+ recommendedPaths,
10435
+ recommendedSkillPaths,
10436
+ language: languageLabel(projectLanguage(config))
10437
+ });
10438
+ writeFile(join(dir, ".kaddo", "understand.md"), renderUnderstand(enrichedPlan));
10104
10439
  log2.success("Wrote .kaddo/understand.md");
10105
10440
  printCommandFooter("understand");
10106
10441
  outro2("Handoff ready. CLI prepares context \u2014 your LLM creates the understanding.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.44.0",
3
+ "version": "3.46.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {