@kaddo/cli 3.41.0 → 3.43.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 +404 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -540,6 +540,8 @@ create --from roadmap → owners → guard → explain`.
540
540
  | v3.40 | ADR materialization: `kaddo adr` (alias `decisions`) detects decision candidates + ADRs and hands off the ADR files to create; `tech_decisions` status (none/candidates/draft-adrs/accepted-adrs) surfaced in `explain`/`context`/`understand`; adr-writing skill formalized |
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
+ | 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` |
543
545
 
544
546
  **Optional modules (installed with `kaddo add`):**
545
547
 
package/dist/index.js CHANGED
@@ -712,6 +712,14 @@ var COMMAND_HELP = {
712
712
  "tech organize": {
713
713
  question: "Is knowledge/tech/ cleanly separated (core vs discovery vs decisions)?",
714
714
  next: "Discovery notes now live under knowledge/tech/discovery/"
715
+ },
716
+ "agents status": {
717
+ question: "Are the installed agents aligned with the current Kaddo version?",
718
+ next: "Run `kaddo agents update` to refresh outdated agents"
719
+ },
720
+ "skills status": {
721
+ question: "Are the installed skills aligned with the current Kaddo version?",
722
+ next: "Run `kaddo skills update` to refresh outdated skills"
715
723
  }
716
724
  };
717
725
  function commandFooterLines(name) {
@@ -1515,14 +1523,38 @@ var guardAdvancedModule = {
1515
1523
  ]
1516
1524
  };
1517
1525
 
1526
+ // src/core/version.ts
1527
+ import { fileURLToPath } from "url";
1528
+ import { dirname, join as join2, parse } from "path";
1529
+ import { existsSync, readFileSync } from "fs";
1530
+ function resolveVersion() {
1531
+ try {
1532
+ let dir = dirname(fileURLToPath(import.meta.url));
1533
+ const root = parse(dir).root;
1534
+ for (; ; ) {
1535
+ const pkg = join2(dir, "package.json");
1536
+ if (existsSync(pkg)) {
1537
+ const v = JSON.parse(readFileSync(pkg, "utf-8")).version;
1538
+ if (v) return v;
1539
+ }
1540
+ if (dir === root) break;
1541
+ dir = dirname(dir);
1542
+ }
1543
+ } catch {
1544
+ }
1545
+ return "0.0.0";
1546
+ }
1547
+ var KADDO_VERSION = resolveVersion();
1548
+
1518
1549
  // src/skills/skills.ts
1519
1550
  function skill(id, title, group, appliesTo, body) {
1520
1551
  const frontMatter2 = [
1521
1552
  "---",
1522
1553
  "type: skill",
1523
1554
  `id: ${id}`,
1555
+ `name: ${id}`,
1524
1556
  `title: ${title}`,
1525
- "version: 1",
1557
+ `version: ${KADDO_VERSION}`,
1526
1558
  `group: ${group}`,
1527
1559
  "applies_to:",
1528
1560
  ...appliesTo.map((a) => ` - ${a}`),
@@ -2613,6 +2645,24 @@ Adapt priorities to the project state from the context pack:
2613
2645
  - **legacy** \u2014 prioritize risk reduction, unknowns and safe modernization before feature
2614
2646
  delivery.
2615
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
+
2616
2666
  ## Constraints
2617
2667
 
2618
2668
  - Do not invent business priorities or business facts \u2014 mark them as assumptions when inferred.
@@ -2621,6 +2671,8 @@ Adapt priorities to the project state from the context pack:
2621
2671
  implementation-agent, and only after Work Items are materialized. Your handoff is
2622
2672
  \`kaddo create --from roadmap\` \u2192 work-item-agent.
2623
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.
2624
2676
  - Make clear that initiatives and work items are **candidates**, not final decisions.
2625
2677
  - Mark any uncertain information as an assumption or open question.
2626
2678
  - Keep sequencing justified by dependencies and risk.
@@ -2654,31 +2706,38 @@ human review \u2014 not final commitments.
2654
2706
 
2655
2707
  ### RM-001: <Initiative Name>
2656
2708
 
2657
- **Goal:**
2709
+ **Status:** candidate <!-- candidate | selected | deferred | rejected -->
2658
2710
 
2659
- **Related capabilities:**
2711
+ **Priority:** high / medium / low
2660
2712
 
2661
- **Project area / domain:**
2713
+ **Suggested Knowledge Level:** K1 / K2 / K3 / K4
2662
2714
 
2663
- **Impact:** Low / Medium / High
2715
+ **Related domain:** <one of the ## Capability Domains from capabilities.md>
2664
2716
 
2665
- **Risk:** Low / Medium / High
2717
+ **Related capabilities:**
2718
+ - <existing or partial capability>
2666
2719
 
2667
- **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: <...>
2668
2724
 
2669
- **Dependencies:**
2725
+ **Problem / opportunity:**
2726
+
2727
+ **Expected value:**
2670
2728
 
2671
- **Why this comes now:**
2729
+ **Risks:**
2672
2730
 
2673
- **Candidate Work Items:**
2731
+ **Dependencies:**
2674
2732
 
2733
+ **Suggested Work Items:**
2675
2734
  - WI-CANDIDATE-001: <candidate work item>
2676
2735
  - type:
2677
2736
  - suggested knowledge level:
2678
2737
  - expected value:
2679
2738
  - notes:
2680
2739
 
2681
- **Open questions:**
2740
+ **Not now:**
2682
2741
 
2683
2742
  ---
2684
2743
 
@@ -2973,6 +3032,14 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
2973
3032
  without surfacing it. -->
2974
3033
  \`\`\`
2975
3034
 
3035
+ ### Preserve roadmap metadata (VS-077)
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.
3042
+
2976
3043
  ## Where to Save the Result
2977
3044
 
2978
3045
  Save new output as a draft under \`knowledge/delivery/work-items/draft/\` unless a human
@@ -4050,6 +4117,19 @@ function selectAgentFiles(opts) {
4050
4117
  }
4051
4118
 
4052
4119
  // src/modules/agents.ts
4120
+ function withAgentFrontMatter(fileName, content) {
4121
+ const name = fileName.replace(/\.md$/, "");
4122
+ const fm2 = [
4123
+ "---",
4124
+ "type: agent",
4125
+ `name: ${name}`,
4126
+ `version: ${KADDO_VERSION}`,
4127
+ `group: ${agentGroupOf(fileName)}`,
4128
+ "---",
4129
+ ""
4130
+ ].join("\n");
4131
+ return fm2 + content.replace(/^\s+/, "");
4132
+ }
4053
4133
  var agentReadme = {
4054
4134
  path: "knowledge/agents/README.md",
4055
4135
  content: [
@@ -4121,7 +4201,7 @@ var agentReadme = {
4121
4201
  };
4122
4202
  var agentFiles = AGENT_PROMPTS.map((a) => ({
4123
4203
  path: agentInstallPath(a.fileName),
4124
- content: a.content
4204
+ content: withAgentFrontMatter(a.fileName, a.content)
4125
4205
  }));
4126
4206
  var agentsModule = {
4127
4207
  name: "agents",
@@ -4934,6 +5014,16 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4934
5014
  `source: roadmap`,
4935
5015
  `source_id: ${candidate.id}`,
4936
5016
  `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(", ")}]`] : [],
4937
5027
  `summary: "${summary.replace(/"/g, "'")}"`,
4938
5028
  "---"
4939
5029
  ];
@@ -6202,7 +6292,7 @@ function runIgnoreRemove(artifactId) {
6202
6292
  }
6203
6293
 
6204
6294
  // src/commands/explain.ts
6205
- import matter4 from "gray-matter";
6295
+ import matter5 from "gray-matter";
6206
6296
  import { parse as parseYaml9 } from "yaml";
6207
6297
 
6208
6298
  // src/core/delivery-phase.ts
@@ -8214,6 +8304,145 @@ function buildTechDecisions(dir) {
8214
8304
  };
8215
8305
  }
8216
8306
 
8307
+ // src/core/assets.ts
8308
+ import matter4 from "gray-matter";
8309
+ function canonicalAgents() {
8310
+ return AGENT_PROMPTS.map((a) => ({
8311
+ name: a.fileName.replace(/\.md$/, ""),
8312
+ path: agentInstallPath(a.fileName),
8313
+ content: withAgentFrontMatter(a.fileName, a.content)
8314
+ }));
8315
+ }
8316
+ function canonicalSkills() {
8317
+ return SKILLS.map((s) => ({ name: s.id, path: skillInstallPath(s.id), content: s.content }));
8318
+ }
8319
+ function versionOf(content) {
8320
+ try {
8321
+ const v = matter4(content).data.version;
8322
+ return v === void 0 || v === null ? null : String(v);
8323
+ } catch {
8324
+ return null;
8325
+ }
8326
+ }
8327
+ function cmpVersion(a, b) {
8328
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
8329
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
8330
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
8331
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
8332
+ if (d !== 0) return d < 0 ? -1 : 1;
8333
+ }
8334
+ return 0;
8335
+ }
8336
+ function classify(dir, asset) {
8337
+ const full = join(dir, asset.path);
8338
+ const base = { name: asset.name, path: asset.path, available: KADDO_VERSION };
8339
+ if (!exists(full)) return { ...base, installed: null, state: "missing" };
8340
+ let content = "";
8341
+ try {
8342
+ content = readFile(full);
8343
+ } catch {
8344
+ return { ...base, installed: null, state: "missing" };
8345
+ }
8346
+ const installed = versionOf(content);
8347
+ if (installed === null) return { ...base, installed: null, state: "unknown-version" };
8348
+ const cmp = cmpVersion(installed, KADDO_VERSION);
8349
+ if (cmp < 0) return { ...base, installed, state: "outdated" };
8350
+ if (content.trim() !== asset.content.trim()) return { ...base, installed, state: "modified" };
8351
+ return { ...base, installed, state: "up-to-date" };
8352
+ }
8353
+ function summarize(items) {
8354
+ const count = (s) => items.filter((i) => i.state === s).length;
8355
+ return {
8356
+ total: items.length,
8357
+ up_to_date: count("up-to-date"),
8358
+ outdated: count("outdated"),
8359
+ missing: count("missing"),
8360
+ unknown_version: count("unknown-version"),
8361
+ modified: count("modified"),
8362
+ items
8363
+ };
8364
+ }
8365
+ function assetStatus(dir, kind) {
8366
+ const catalog = kind === "agent" ? canonicalAgents() : canonicalSkills();
8367
+ return summarize(catalog.map((a) => classify(dir, a)));
8368
+ }
8369
+ function installedAssetsSummary(dir) {
8370
+ return { version: KADDO_VERSION, agents: assetStatus(dir, "agent"), skills: assetStatus(dir, "skill") };
8371
+ }
8372
+
8373
+ // src/core/roadmap-quality.ts
8374
+ var ROADMAP_PATH2 = "knowledge/delivery/roadmap.md";
8375
+ var INITIATIVE_RE2 = /^#{2,4}\s+(RM-[\w.-]+)\s*[:\-–]?\s*(.*)$/;
8376
+ function fieldPresent(lines, label) {
8377
+ for (let i = 0; i < lines.length; i++) {
8378
+ const m = lines[i].match(new RegExp(`^\\s*[-*]?\\s*(?:\\*\\*)?\\s*(${label.source})\\s*(?:\\*\\*)?\\s*:\\s*(.*)$`, "i"));
8379
+ if (!m) continue;
8380
+ const inline = m[2].trim();
8381
+ if (inline) return true;
8382
+ for (let j = i + 1; j < lines.length; j++) {
8383
+ if (/^\s{2,}[-*]\s+\S/.test(lines[j])) return true;
8384
+ if (/^\S/.test(lines[j]) || /^#{2,4}\s/.test(lines[j])) break;
8385
+ if (/^\s*[-*]\s+\S/.test(lines[j]) && !/^\s{2,}/.test(lines[j])) break;
8386
+ }
8387
+ }
8388
+ return false;
8389
+ }
8390
+ function parseRoadmapCandidateQuality(md) {
8391
+ const lines = md.split(/\r?\n/);
8392
+ const items = [];
8393
+ let cur = null;
8394
+ const flush = () => {
8395
+ if (!cur) return;
8396
+ const hasRelatedDomain = fieldPresent(cur.lines, /related domain/);
8397
+ const hasRelatedCapability = fieldPresent(cur.lines, /related capabilit(?:y|ies)/);
8398
+ const hasSourceSignals = fieldPresent(cur.lines, /source signals?/);
8399
+ items.push({
8400
+ id: cur.id,
8401
+ title: cur.title,
8402
+ hasRelatedDomain,
8403
+ hasRelatedCapability,
8404
+ hasSourceSignals,
8405
+ grounded: hasRelatedDomain && hasRelatedCapability && hasSourceSignals
8406
+ });
8407
+ cur = null;
8408
+ };
8409
+ for (const line of lines) {
8410
+ const m = line.match(INITIATIVE_RE2);
8411
+ if (m) {
8412
+ flush();
8413
+ cur = { id: m[1], title: m[2].trim(), lines: [] };
8414
+ continue;
8415
+ }
8416
+ if (cur) cur.lines.push(line);
8417
+ }
8418
+ flush();
8419
+ return items;
8420
+ }
8421
+ function buildRoadmapQuality(dir) {
8422
+ let md = "";
8423
+ const p2 = join(dir, ROADMAP_PATH2);
8424
+ if (exists(p2)) {
8425
+ try {
8426
+ md = readFile(p2);
8427
+ } catch {
8428
+ md = "";
8429
+ }
8430
+ }
8431
+ const items = parseRoadmapCandidateQuality(md);
8432
+ const count = (pred) => items.filter(pred).length;
8433
+ const grounded = count((i) => i.grounded);
8434
+ return {
8435
+ candidates: items.length,
8436
+ grounded,
8437
+ with_related_domain: count((i) => i.hasRelatedDomain),
8438
+ with_related_capability: count((i) => i.hasRelatedCapability),
8439
+ with_source_signals: count((i) => i.hasSourceSignals),
8440
+ // Only "needs refinement" when there are candidates and at least one isn't grounded.
8441
+ needs_refinement: items.length > 0 && grounded < items.length,
8442
+ items
8443
+ };
8444
+ }
8445
+
8217
8446
  // src/core/project-explain.ts
8218
8447
  var ARCH_DIR4 = "knowledge";
8219
8448
  function normalizeTitle(t) {
@@ -8437,7 +8666,9 @@ function buildProjectExplanation(dir) {
8437
8666
  decisionCandidates: exists(join(dir, "knowledge/tech/discovery/decision-candidates.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md")),
8438
8667
  legacyLocation: exists(join(dir, "knowledge/tech/architecture-notes.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md"))
8439
8668
  }
8440
- }
8669
+ },
8670
+ installedAssets: installedAssetsSummary(dir),
8671
+ roadmapQuality: buildRoadmapQuality(dir)
8441
8672
  };
8442
8673
  }
8443
8674
  function stateLabel(state) {
@@ -8672,6 +8903,35 @@ function renderExplanationHuman(exp) {
8672
8903
  lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
8673
8904
  lines.push("");
8674
8905
  }
8906
+ const rq = exp.roadmapQuality;
8907
+ if (rq.candidates > 0) {
8908
+ 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) {
8915
+ lines.push("");
8916
+ lines.push("Roadmap quality: needs refinement. Suggested: use roadmap-agent to add domain / capability / source signals.");
8917
+ }
8918
+ lines.push("");
8919
+ }
8920
+ const ia = exp.installedAssets;
8921
+ const agentsInstalled = ia.agents.total - ia.agents.missing;
8922
+ const skillsInstalled = ia.skills.total - ia.skills.missing;
8923
+ if (agentsInstalled > 0 || skillsInstalled > 0) {
8924
+ const issues = (s2) => [s2.outdated ? `${s2.outdated} outdated` : "", s2.unknown_version ? `${s2.unknown_version} unknown-version` : "", s2.modified ? `${s2.modified} modified` : ""].filter(Boolean).join(", ");
8925
+ lines.push("## Installed Assets");
8926
+ lines.push(`- CLI version: ${ia.version}`);
8927
+ lines.push(`- Agents: ${agentsInstalled} installed${issues(ia.agents) ? ` (${issues(ia.agents)})` : ""}`);
8928
+ lines.push(`- Skills: ${skillsInstalled} installed${issues(ia.skills) ? ` (${issues(ia.skills)})` : ""}`);
8929
+ if (ia.agents.outdated + ia.agents.unknown_version + ia.skills.outdated + ia.skills.unknown_version > 0) {
8930
+ lines.push("");
8931
+ lines.push("Suggested: run `kaddo agents status` and `kaddo skills status`.");
8932
+ }
8933
+ lines.push("");
8934
+ }
8675
8935
  return lines.join("\n").trimEnd() + "\n";
8676
8936
  }
8677
8937
  function renderExplanationAgent(exp) {
@@ -8687,7 +8947,7 @@ function readKnowledge(dir) {
8687
8947
  if (!exists(knowledgePath)) return null;
8688
8948
  try {
8689
8949
  const raw = readFile(knowledgePath);
8690
- const { data, content } = matter4(raw);
8950
+ const { data, content } = matter5(raw);
8691
8951
  return { content, data };
8692
8952
  } catch {
8693
8953
  return null;
@@ -8698,7 +8958,7 @@ function readRoadmap(dir) {
8698
8958
  if (!exists(roadmapPath)) return null;
8699
8959
  try {
8700
8960
  const raw = readFile(roadmapPath);
8701
- const { content } = matter4(raw);
8961
+ const { content } = matter5(raw);
8702
8962
  return content.trim();
8703
8963
  } catch {
8704
8964
  return null;
@@ -8867,7 +9127,7 @@ function runExplain(opts) {
8867
9127
  }
8868
9128
 
8869
9129
  // src/core/context-pack.ts
8870
- import matter5 from "gray-matter";
9130
+ import matter6 from "gray-matter";
8871
9131
  var CONTEXT_PACK_VERSION = "1";
8872
9132
  var ARCH_DIR6 = "knowledge";
8873
9133
  function readScanJson(dir) {
@@ -8880,7 +9140,7 @@ function readScanJson(dir) {
8880
9140
  }
8881
9141
  }
8882
9142
  function firstParagraph2(markdown) {
8883
- const body = matter5(markdown).content.trim();
9143
+ const body = matter6(markdown).content.trim();
8884
9144
  const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
8885
9145
  return para ?? "";
8886
9146
  }
@@ -9075,6 +9335,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9075
9335
  nextStepRecommendation,
9076
9336
  techDecisions,
9077
9337
  techKnowledge,
9338
+ roadmapQuality: buildRoadmapQuality(dir),
9339
+ installedAssets: (() => {
9340
+ const s = installedAssetsSummary(dir);
9341
+ const compact = (a) => ({ total: a.total, installed: a.total - a.missing, outdated: a.outdated, unknown_version: a.unknown_version, modified: a.modified });
9342
+ return { version: s.version, agents: compact(s.agents), skills: compact(s.skills) };
9343
+ })(),
9078
9344
  deliveryMix,
9079
9345
  external: loadExternalCapsules(dir),
9080
9346
  graph: loadGraphSummary(dir),
@@ -9184,6 +9450,24 @@ function renderContextPack(pack) {
9184
9450
  }
9185
9451
  }
9186
9452
  parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
9453
+ const rq = pack.roadmapQuality;
9454
+ if (rq.candidates > 0) {
9455
+ 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) {
9466
+ parts.push(
9467
+ "Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
9468
+ );
9469
+ }
9470
+ }
9187
9471
  parts.push("## Active Work Items\n");
9188
9472
  if (knowledge.workItems.length > 0) {
9189
9473
  const lines = knowledge.workItems.map((wi) => {
@@ -9616,6 +9900,27 @@ function runUnderstand() {
9616
9900
  console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
9617
9901
  console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
9618
9902
  }
9903
+ const rq = exp.roadmapQuality;
9904
+ if (rq.needs_refinement) {
9905
+ 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");
9908
+ console.log(" before `kaddo create --from roadmap`.");
9909
+ } else if (rq.candidates > 0 && rq.grounded === rq.candidates) {
9910
+ console.log("");
9911
+ console.log("Roadmap candidates are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
9912
+ }
9913
+ const ia = exp.installedAssets;
9914
+ const outdatedRecommended = ia.agents.items.filter(
9915
+ (a) => (a.state === "outdated" || a.state === "unknown-version") && assessment.recommendedAgents.includes(a.name)
9916
+ );
9917
+ if (outdatedRecommended.length > 0) {
9918
+ console.log("");
9919
+ for (const a of outdatedRecommended) {
9920
+ console.log(`Recommended agent ${a.name} is ${a.state} (installed ${a.installed ?? "unknown"}, available ${a.available}).`);
9921
+ }
9922
+ console.log(" \u2192 Run `kaddo agents update` or review `kaddo agents status`.");
9923
+ }
9619
9924
  if (exp.externalCapsules.length > 0) {
9620
9925
  console.log("");
9621
9926
  console.log("External knowledge:");
@@ -9776,7 +10081,7 @@ var DECLARED_LEVEL = {
9776
10081
  incident: "K2",
9777
10082
  rfc: "K3"
9778
10083
  };
9779
- function classify(declaredType, declaredLevel, touchedFiles) {
10084
+ function classify2(declaredType, declaredLevel, touchedFiles) {
9780
10085
  const signals = detectSignals(touchedFiles);
9781
10086
  const observedLevel = highestLevel(signals);
9782
10087
  const expectedLevel = DECLARED_LEVEL[declaredType] ?? declaredLevel ?? "K2";
@@ -9856,7 +10161,7 @@ async function runClassify(opts = {}) {
9856
10161
  return;
9857
10162
  }
9858
10163
  if (opts.type) {
9859
- const result2 = classify(opts.type, opts.level ?? "", touchedFiles);
10164
+ const result2 = classify2(opts.type, opts.level ?? "", touchedFiles);
9860
10165
  printResult2(result2);
9861
10166
  return;
9862
10167
  }
@@ -9867,7 +10172,7 @@ async function runClassify(opts = {}) {
9867
10172
  console.log("Or create one with: kaddo create <type>");
9868
10173
  return;
9869
10174
  }
9870
- const result = classify(activeItem.type, activeItem.knowledgeLevel, touchedFiles);
10175
+ const result = classify2(activeItem.type, activeItem.knowledgeLevel, touchedFiles);
9871
10176
  printResult2(result, activeItem.id || activeItem.title);
9872
10177
  }
9873
10178
 
@@ -9936,7 +10241,7 @@ function runStatus() {
9936
10241
  }
9937
10242
 
9938
10243
  // src/commands/learn.ts
9939
- import matter6 from "gray-matter";
10244
+ import matter7 from "gray-matter";
9940
10245
  var ARCH_DIR9 = "knowledge";
9941
10246
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
9942
10247
  function findWorkItemFile(dir, id) {
@@ -9948,7 +10253,7 @@ function findWorkItemFile(dir, id) {
9948
10253
  }
9949
10254
  function updateWorkItemFile(filePath, learning) {
9950
10255
  const raw = readFile(filePath);
9951
- const { data, content } = matter6(raw);
10256
+ const { data, content } = matter7(raw);
9952
10257
  data.status = "done";
9953
10258
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9954
10259
  let updatedContent = content;
@@ -9973,7 +10278,7 @@ ${learning.trim()}
9973
10278
  ${learning.trim()}
9974
10279
  `;
9975
10280
  }
9976
- const newRaw = matter6.stringify(updatedContent, data);
10281
+ const newRaw = matter7.stringify(updatedContent, data);
9977
10282
  writeFile(filePath, newRaw);
9978
10283
  }
9979
10284
  async function runLearn(artifactId) {
@@ -10223,7 +10528,7 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
10223
10528
  }
10224
10529
 
10225
10530
  // src/core/ownership-suggest.ts
10226
- import matter7 from "gray-matter";
10531
+ import matter8 from "gray-matter";
10227
10532
  var SCAN_PATH = ".kaddo/scan.json";
10228
10533
  function normalizeGlob(input) {
10229
10534
  let g = input.trim().replace(/\\/g, "/");
@@ -10345,11 +10650,11 @@ function suggestGlobs(artifact, signals) {
10345
10650
  return [...new Set(out)];
10346
10651
  }
10347
10652
  function applyOwnership(raw, globs, mode = "replace") {
10348
- const parsed = matter7(raw);
10653
+ const parsed = matter8(raw);
10349
10654
  const existing = toStringArray3(parsed.data.code);
10350
10655
  const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
10351
10656
  const data = { ...parsed.data, code: next };
10352
- return matter7.stringify(parsed.content, data);
10657
+ return matter8.stringify(parsed.content, data);
10353
10658
  }
10354
10659
 
10355
10660
  // src/commands/owners.ts
@@ -12961,7 +13266,7 @@ function runGraphExport(opts = {}) {
12961
13266
  }
12962
13267
 
12963
13268
  // src/core/impact-report.ts
12964
- import matter8 from "gray-matter";
13269
+ import matter9 from "gray-matter";
12965
13270
  function isBroadGlob(glob) {
12966
13271
  if (!glob.endsWith("/**")) return false;
12967
13272
  const prefix = glob.slice(0, -3);
@@ -13030,7 +13335,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
13030
13335
  }
13031
13336
  }
13032
13337
  try {
13033
- const body = matter8(readFile(wi.filePath)).content;
13338
+ const body = matter9(readFile(wi.filePath)).content;
13034
13339
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
13035
13340
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
13036
13341
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
@@ -14141,6 +14446,70 @@ function runTechOrganize(dir = cwd()) {
14141
14446
  outro2("Tech knowledge organized.");
14142
14447
  }
14143
14448
 
14449
+ // src/commands/assets.ts
14450
+ var LABEL = { agent: "Agents", skill: "Skills" };
14451
+ function runAssetsStatus(kind, opts = {}) {
14452
+ const dir = cwd();
14453
+ requireConfig(dir);
14454
+ const summary = assetStatus(dir, kind);
14455
+ if (opts.json) {
14456
+ console.log(JSON.stringify(summary, null, 2));
14457
+ return;
14458
+ }
14459
+ console.log("");
14460
+ console.log(LABEL[kind]);
14461
+ console.log("");
14462
+ const installed = summary.items.filter((i) => i.state !== "missing");
14463
+ for (const a of installed) {
14464
+ console.log(`${a.path}`);
14465
+ console.log(` Name: ${a.name}`);
14466
+ console.log(` Installed: ${a.installed ?? "unknown"}`);
14467
+ console.log(` Available: ${a.available}`);
14468
+ console.log(` Status: ${a.state}`);
14469
+ console.log("");
14470
+ }
14471
+ if (installed.length === 0) console.log(`No ${kind}s installed. Run \`kaddo add ${kind}s\`.`);
14472
+ console.log(
14473
+ `Summary: ${summary.up_to_date} up-to-date, ${summary.outdated} outdated, ${summary.unknown_version} unknown-version, ${summary.modified} modified, ${summary.missing} not installed.`
14474
+ );
14475
+ if (summary.outdated > 0 || summary.unknown_version > 0) {
14476
+ console.log(`Run \`kaddo ${kind}s update\` to refresh outdated ${kind}s (\`--force\` for unknown/modified).`);
14477
+ }
14478
+ printCommandFooter(`${kind}s status`);
14479
+ }
14480
+ function runAssetsUpdate(kind, opts = {}) {
14481
+ const dir = cwd();
14482
+ requireConfig(dir);
14483
+ intro2(`kaddo ${kind}s update`);
14484
+ const summary = assetStatus(dir, kind);
14485
+ const catalog = kind === "agent" ? canonicalAgents() : canonicalSkills();
14486
+ const byPath = new Map(catalog.map((a) => [a.path, a.content]));
14487
+ const updated = [];
14488
+ const skipped = [];
14489
+ for (const item of summary.items) {
14490
+ const canonical = byPath.get(item.path);
14491
+ if (!canonical) continue;
14492
+ const safe = item.state === "outdated";
14493
+ const needsForce = item.state === "unknown-version" || item.state === "modified";
14494
+ if (safe || needsForce && opts.force) {
14495
+ writeFile(join(dir, item.path), canonical);
14496
+ updated.push(`${item.path} (${item.installed ?? "unknown"} \u2192 ${item.available})`);
14497
+ } else if (needsForce) {
14498
+ skipped.push(`${item.path} \u2014 ${item.state} (use --force to overwrite local changes)`);
14499
+ }
14500
+ }
14501
+ if (updated.length > 0) {
14502
+ log2.success("Updated:");
14503
+ for (const u of updated) console.log(` - ${u}`);
14504
+ } else {
14505
+ log2.info(`No ${kind}s to update.`);
14506
+ }
14507
+ for (const s of skipped) log2.warn(`Skipped ${s}`);
14508
+ if (summary.missing > 0) log2.info(`${summary.missing} ${kind}(s) not installed \u2014 run \`kaddo add ${kind}s\` to add them.`);
14509
+ printCommandFooter(`${kind}s status`);
14510
+ outro2(`${LABEL[kind]} update complete.`);
14511
+ }
14512
+
14144
14513
  // src/index.ts
14145
14514
  var require2 = createRequire(import.meta.url);
14146
14515
  var { version } = require2("../package.json");
@@ -14194,6 +14563,12 @@ program.command("drift").description("Drift Trend Report from recorded `kaddo gu
14194
14563
  var questionsAction = (opts) => runQuestions(opts);
14195
14564
  program.command("questions").description("Open-questions readiness gate: blocking/important/deferred decisions before the roadmap").option("--json", "Output JSON instead of a summary").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/questions-report.md)").action(questionsAction);
14196
14565
  program.command("readiness").description("Alias for `kaddo questions`").option("--json", "Output JSON instead of a summary").option("--output <path>", "Write the report to a file").action(questionsAction);
14566
+ var agentsCmd = program.command("agents").description("Inspect and update installed Kaddo agents (version status)");
14567
+ agentsCmd.command("status").description("Show installed agents, their version and whether they are up to date").option("--json", "Output JSON").action((opts) => runAssetsStatus("agent", opts));
14568
+ agentsCmd.command("update").description("Refresh outdated agents (never overwrites modified files without --force)").option("--force", "Overwrite unknown-version / locally-modified agents").action((opts) => runAssetsUpdate("agent", opts));
14569
+ var skillsCmd = program.command("skills").description("Inspect and update installed Kaddo skills (version status)");
14570
+ skillsCmd.command("status").description("Show installed skills, their version and whether they are up to date").option("--json", "Output JSON").action((opts) => runAssetsStatus("skill", opts));
14571
+ skillsCmd.command("update").description("Refresh outdated skills (never overwrites modified files without --force)").option("--force", "Overwrite unknown-version / locally-modified skills").action((opts) => runAssetsUpdate("skill", opts));
14197
14572
  var techCmd = program.command("tech").description("Organize the knowledge/tech/ structure (core vs discovery vs decisions)");
14198
14573
  techCmd.command("organize").description("Move discovery artifacts into knowledge/tech/discovery/ (never overwrites, no content change)").action(() => {
14199
14574
  runTechOrganize();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.41.0",
3
+ "version": "3.43.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {