@kaddo/cli 3.40.1 → 3.42.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 +356 -39
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -539,6 +539,8 @@ create --from roadmap → owners → guard → explain`.
539
539
  | v3.39.1 | Domain-oriented capability inventory: pre-ai/legacy `capabilities.md` groups capabilities under `## Capability Domains` (functional domains, not technical folders); gaps/candidates name their `Domain`; roadmap-agent reads it as a domain map; work-item-agent recommends `related_domain` + `related_capability` |
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
+ | 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` |
542
544
 
543
545
  **Optional modules (installed with `kaddo add`):**
544
546
 
package/dist/index.js CHANGED
@@ -708,6 +708,18 @@ var COMMAND_HELP = {
708
708
  adr: {
709
709
  question: "Which technical decisions still need to become ADRs?",
710
710
  next: "Use the adr-writing skill to draft ADRs, then mark them accepted when confirmed"
711
+ },
712
+ "tech organize": {
713
+ question: "Is knowledge/tech/ cleanly separated (core vs discovery vs decisions)?",
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"
711
723
  }
712
724
  };
713
725
  function commandFooterLines(name) {
@@ -1511,14 +1523,38 @@ var guardAdvancedModule = {
1511
1523
  ]
1512
1524
  };
1513
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
+
1514
1549
  // src/skills/skills.ts
1515
1550
  function skill(id, title, group, appliesTo, body) {
1516
1551
  const frontMatter2 = [
1517
1552
  "---",
1518
1553
  "type: skill",
1519
1554
  `id: ${id}`,
1555
+ `name: ${id}`,
1520
1556
  `title: ${title}`,
1521
- "version: 1",
1557
+ `version: ${KADDO_VERSION}`,
1522
1558
  `group: ${group}`,
1523
1559
  "applies_to:",
1524
1560
  ...appliesTo.map((a) => ` - ${a}`),
@@ -2439,9 +2475,9 @@ Optionally provide: existing diagrams, infra config, README, dependency manifest
2439
2475
 
2440
2476
  Markdown artifacts intended to be saved as:
2441
2477
 
2442
- - \`knowledge/tech/current-state.md\`
2443
- - \`knowledge/tech/architecture-notes.md\`
2444
- - \`knowledge/tech/decision-candidates.md\`
2478
+ - \`knowledge/tech/current-state.md\` (core artifact)
2479
+ - \`knowledge/tech/discovery/architecture-notes.md\` (discovery note)
2480
+ - \`knowledge/tech/discovery/decision-candidates.md\` (discovery input for ADRs)
2445
2481
 
2446
2482
  ## Instructions
2447
2483
 
@@ -2487,10 +2523,12 @@ Generated from Kaddo Context Pack.
2487
2523
 
2488
2524
  ## Where to Save the Result
2489
2525
 
2490
- Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
2491
- \`knowledge/tech/architecture-notes.md\`, and decision candidates as
2492
- \`knowledge/tech/decision-candidates.md\`. Final ADRs always live under
2493
- \`knowledge/tech/decisions/\` \u2014 never directly in \`knowledge/tech/\`.
2526
+ Save the architecture overview as \`knowledge/tech/current-state.md\` (a **core** artifact). Save
2527
+ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decision candidates as
2528
+ \`knowledge/tech/discovery/decision-candidates.md\` \u2014 **discovery** inputs live under
2529
+ \`knowledge/tech/discovery/\`, not directly in \`knowledge/tech/\` (VS-075.2). Final ADRs always live
2530
+ under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
2531
+ compatibility, but new output should use \`discovery/\`.
2494
2532
 
2495
2533
  ## Quality Checklist
2496
2534
 
@@ -4044,6 +4082,19 @@ function selectAgentFiles(opts) {
4044
4082
  }
4045
4083
 
4046
4084
  // src/modules/agents.ts
4085
+ function withAgentFrontMatter(fileName, content) {
4086
+ const name = fileName.replace(/\.md$/, "");
4087
+ const fm2 = [
4088
+ "---",
4089
+ "type: agent",
4090
+ `name: ${name}`,
4091
+ `version: ${KADDO_VERSION}`,
4092
+ `group: ${agentGroupOf(fileName)}`,
4093
+ "---",
4094
+ ""
4095
+ ].join("\n");
4096
+ return fm2 + content.replace(/^\s+/, "");
4097
+ }
4047
4098
  var agentReadme = {
4048
4099
  path: "knowledge/agents/README.md",
4049
4100
  content: [
@@ -4115,7 +4166,7 @@ var agentReadme = {
4115
4166
  };
4116
4167
  var agentFiles = AGENT_PROMPTS.map((a) => ({
4117
4168
  path: agentInstallPath(a.fileName),
4118
- content: a.content
4169
+ content: withAgentFrontMatter(a.fileName, a.content)
4119
4170
  }));
4120
4171
  var agentsModule = {
4121
4172
  name: "agents",
@@ -6196,7 +6247,7 @@ function runIgnoreRemove(artifactId) {
6196
6247
  }
6197
6248
 
6198
6249
  // src/commands/explain.ts
6199
- import matter4 from "gray-matter";
6250
+ import matter5 from "gray-matter";
6200
6251
  import { parse as parseYaml9 } from "yaml";
6201
6252
 
6202
6253
  // src/core/delivery-phase.ts
@@ -8123,8 +8174,16 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
8123
8174
  }
8124
8175
 
8125
8176
  // src/core/decisions.ts
8126
- var CANDIDATES_PATH = "knowledge/tech/decision-candidates.md";
8177
+ var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
8178
+ var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
8127
8179
  var DECISIONS_DIR = "knowledge/tech/decisions";
8180
+ function resolveCandidatesPath(dir) {
8181
+ const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
8182
+ const legacy = exists(join(dir, CANDIDATES_LEGACY));
8183
+ if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
8184
+ if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
8185
+ return { path: null, legacy: false, bothExist: false };
8186
+ }
8128
8187
  function cleanCandidateTitle(title) {
8129
8188
  return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
8130
8189
  }
@@ -8168,11 +8227,11 @@ function countAdrs(dir) {
8168
8227
  return { total, draft, accepted };
8169
8228
  }
8170
8229
  function buildTechDecisions(dir) {
8171
- const candFile = join(dir, CANDIDATES_PATH);
8230
+ const resolved = resolveCandidatesPath(dir);
8172
8231
  let titles = [];
8173
- if (exists(candFile)) {
8232
+ if (resolved.path) {
8174
8233
  try {
8175
- titles = parseDecisionCandidates(readFile(candFile));
8234
+ titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
8176
8235
  } catch {
8177
8236
  titles = [];
8178
8237
  }
@@ -8180,7 +8239,7 @@ function buildTechDecisions(dir) {
8180
8239
  const { total, draft, accepted } = countAdrs(dir);
8181
8240
  const candidate_list = titles.map((title, i) => {
8182
8241
  const n = String(total + i + 1).padStart(3, "0");
8183
- return { title, source: CANDIDATES_PATH, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8242
+ return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8184
8243
  });
8185
8244
  let status;
8186
8245
  if (accepted > 0) status = "accepted-adrs";
@@ -8193,10 +8252,79 @@ function buildTechDecisions(dir) {
8193
8252
  adrs: total,
8194
8253
  draft_adrs: draft,
8195
8254
  accepted_adrs: accepted,
8196
- candidate_list
8255
+ candidate_list,
8256
+ candidates_source: resolved.path,
8257
+ candidates_legacy_location: resolved.legacy,
8258
+ candidates_both_exist: resolved.bothExist
8197
8259
  };
8198
8260
  }
8199
8261
 
8262
+ // src/core/assets.ts
8263
+ import matter4 from "gray-matter";
8264
+ function canonicalAgents() {
8265
+ return AGENT_PROMPTS.map((a) => ({
8266
+ name: a.fileName.replace(/\.md$/, ""),
8267
+ path: agentInstallPath(a.fileName),
8268
+ content: withAgentFrontMatter(a.fileName, a.content)
8269
+ }));
8270
+ }
8271
+ function canonicalSkills() {
8272
+ return SKILLS.map((s) => ({ name: s.id, path: skillInstallPath(s.id), content: s.content }));
8273
+ }
8274
+ function versionOf(content) {
8275
+ try {
8276
+ const v = matter4(content).data.version;
8277
+ return v === void 0 || v === null ? null : String(v);
8278
+ } catch {
8279
+ return null;
8280
+ }
8281
+ }
8282
+ function cmpVersion(a, b) {
8283
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
8284
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
8285
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
8286
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
8287
+ if (d !== 0) return d < 0 ? -1 : 1;
8288
+ }
8289
+ return 0;
8290
+ }
8291
+ function classify(dir, asset) {
8292
+ const full = join(dir, asset.path);
8293
+ const base = { name: asset.name, path: asset.path, available: KADDO_VERSION };
8294
+ if (!exists(full)) return { ...base, installed: null, state: "missing" };
8295
+ let content = "";
8296
+ try {
8297
+ content = readFile(full);
8298
+ } catch {
8299
+ return { ...base, installed: null, state: "missing" };
8300
+ }
8301
+ const installed = versionOf(content);
8302
+ if (installed === null) return { ...base, installed: null, state: "unknown-version" };
8303
+ const cmp = cmpVersion(installed, KADDO_VERSION);
8304
+ if (cmp < 0) return { ...base, installed, state: "outdated" };
8305
+ if (content.trim() !== asset.content.trim()) return { ...base, installed, state: "modified" };
8306
+ return { ...base, installed, state: "up-to-date" };
8307
+ }
8308
+ function summarize(items) {
8309
+ const count = (s) => items.filter((i) => i.state === s).length;
8310
+ return {
8311
+ total: items.length,
8312
+ up_to_date: count("up-to-date"),
8313
+ outdated: count("outdated"),
8314
+ missing: count("missing"),
8315
+ unknown_version: count("unknown-version"),
8316
+ modified: count("modified"),
8317
+ items
8318
+ };
8319
+ }
8320
+ function assetStatus(dir, kind) {
8321
+ const catalog = kind === "agent" ? canonicalAgents() : canonicalSkills();
8322
+ return summarize(catalog.map((a) => classify(dir, a)));
8323
+ }
8324
+ function installedAssetsSummary(dir) {
8325
+ return { version: KADDO_VERSION, agents: assetStatus(dir, "agent"), skills: assetStatus(dir, "skill") };
8326
+ }
8327
+
8200
8328
  // src/core/project-explain.ts
8201
8329
  var ARCH_DIR4 = "knowledge";
8202
8330
  function normalizeTitle(t) {
@@ -8409,7 +8537,19 @@ function buildProjectExplanation(dir) {
8409
8537
  suggestedNextSteps,
8410
8538
  readiness,
8411
8539
  nextStepRecommendation: readiness.nextStepRecommendation,
8412
- techDecisions: buildTechDecisions(dir)
8540
+ techDecisions: buildTechDecisions(dir),
8541
+ techKnowledge: {
8542
+ core: {
8543
+ currentState: exists(join(dir, "knowledge/tech/current-state.md")),
8544
+ codebase: exists(join(dir, "knowledge/tech/codebase.md"))
8545
+ },
8546
+ discovery: {
8547
+ architectureNotes: exists(join(dir, "knowledge/tech/discovery/architecture-notes.md")) || exists(join(dir, "knowledge/tech/architecture-notes.md")),
8548
+ decisionCandidates: exists(join(dir, "knowledge/tech/discovery/decision-candidates.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md")),
8549
+ legacyLocation: exists(join(dir, "knowledge/tech/architecture-notes.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md"))
8550
+ }
8551
+ },
8552
+ installedAssets: installedAssetsSummary(dir)
8413
8553
  };
8414
8554
  }
8415
8555
  function stateLabel(state) {
@@ -8624,15 +8764,41 @@ function renderExplanationHuman(exp) {
8624
8764
  lines.push(r.recommended_next_step.label);
8625
8765
  lines.push("");
8626
8766
  const td = exp.techDecisions;
8627
- lines.push("## Tech Decisions");
8628
- lines.push(`- Decision candidates: ${td.candidates}`);
8629
- lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
8630
- lines.push(`- Status: ${td.status}`);
8767
+ const tk = exp.techKnowledge;
8768
+ const mark = (b) => b ? "\u2713" : "\u2717";
8769
+ lines.push("## Tech Knowledge");
8770
+ lines.push("Core:");
8771
+ lines.push(`- ${mark(tk.core.currentState)} current-state.md`);
8772
+ lines.push(`- ${mark(tk.core.codebase)} codebase.md`);
8773
+ lines.push("Decisions:");
8774
+ lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs}) \xB7 status: ${td.status}`);
8775
+ lines.push("Discovery:");
8776
+ lines.push(`- ${mark(tk.discovery.architectureNotes)} architecture-notes.md`);
8777
+ lines.push(`- ${mark(tk.discovery.decisionCandidates)} decision-candidates.md`);
8778
+ lines.push("");
8631
8779
  if (td.candidates > 0 && td.adrs === 0) {
8632
- lines.push("");
8633
8780
  lines.push("Use the adr-writing skill to materialize decision candidates into ADRs (`kaddo adr`) before implementing related technical Work Items.");
8781
+ lines.push("");
8782
+ }
8783
+ if (tk.discovery.legacyLocation) {
8784
+ lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
8785
+ lines.push("");
8786
+ }
8787
+ const ia = exp.installedAssets;
8788
+ const agentsInstalled = ia.agents.total - ia.agents.missing;
8789
+ const skillsInstalled = ia.skills.total - ia.skills.missing;
8790
+ if (agentsInstalled > 0 || skillsInstalled > 0) {
8791
+ const issues = (s2) => [s2.outdated ? `${s2.outdated} outdated` : "", s2.unknown_version ? `${s2.unknown_version} unknown-version` : "", s2.modified ? `${s2.modified} modified` : ""].filter(Boolean).join(", ");
8792
+ lines.push("## Installed Assets");
8793
+ lines.push(`- CLI version: ${ia.version}`);
8794
+ lines.push(`- Agents: ${agentsInstalled} installed${issues(ia.agents) ? ` (${issues(ia.agents)})` : ""}`);
8795
+ lines.push(`- Skills: ${skillsInstalled} installed${issues(ia.skills) ? ` (${issues(ia.skills)})` : ""}`);
8796
+ if (ia.agents.outdated + ia.agents.unknown_version + ia.skills.outdated + ia.skills.unknown_version > 0) {
8797
+ lines.push("");
8798
+ lines.push("Suggested: run `kaddo agents status` and `kaddo skills status`.");
8799
+ }
8800
+ lines.push("");
8634
8801
  }
8635
- lines.push("");
8636
8802
  return lines.join("\n").trimEnd() + "\n";
8637
8803
  }
8638
8804
  function renderExplanationAgent(exp) {
@@ -8648,7 +8814,7 @@ function readKnowledge(dir) {
8648
8814
  if (!exists(knowledgePath)) return null;
8649
8815
  try {
8650
8816
  const raw = readFile(knowledgePath);
8651
- const { data, content } = matter4(raw);
8817
+ const { data, content } = matter5(raw);
8652
8818
  return { content, data };
8653
8819
  } catch {
8654
8820
  return null;
@@ -8659,7 +8825,7 @@ function readRoadmap(dir) {
8659
8825
  if (!exists(roadmapPath)) return null;
8660
8826
  try {
8661
8827
  const raw = readFile(roadmapPath);
8662
- const { content } = matter4(raw);
8828
+ const { content } = matter5(raw);
8663
8829
  return content.trim();
8664
8830
  } catch {
8665
8831
  return null;
@@ -8828,7 +8994,7 @@ function runExplain(opts) {
8828
8994
  }
8829
8995
 
8830
8996
  // src/core/context-pack.ts
8831
- import matter5 from "gray-matter";
8997
+ import matter6 from "gray-matter";
8832
8998
  var CONTEXT_PACK_VERSION = "1";
8833
8999
  var ARCH_DIR6 = "knowledge";
8834
9000
  function readScanJson(dir) {
@@ -8841,7 +9007,7 @@ function readScanJson(dir) {
8841
9007
  }
8842
9008
  }
8843
9009
  function firstParagraph2(markdown) {
8844
- const body = matter5(markdown).content.trim();
9010
+ const body = matter6(markdown).content.trim();
8845
9011
  const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
8846
9012
  return para ?? "";
8847
9013
  }
@@ -8981,6 +9147,22 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8981
9147
  if (techDecisions.candidates > 0 && techDecisions.adrs === 0) {
8982
9148
  missing.push(`${techDecisions.candidates} technical decision candidate(s) not yet materialized as ADRs (run \`kaddo adr\`).`);
8983
9149
  }
9150
+ const techFile = (rel) => exists(join(dir, ARCH_DIR6, rel));
9151
+ const techKnowledge = {
9152
+ core: {
9153
+ "current-state.md": techFile("tech/current-state.md"),
9154
+ "codebase.md": techFile("tech/codebase.md")
9155
+ },
9156
+ decisions: { adrs: techDecisions.adrs, dir: exists(join(dir, ARCH_DIR6, "tech/decisions")) },
9157
+ discovery: {
9158
+ "architecture-notes.md": techFile("tech/discovery/architecture-notes.md") || techFile("tech/architecture-notes.md"),
9159
+ "decision-candidates.md": techFile("tech/discovery/decision-candidates.md") || techFile("tech/decision-candidates.md"),
9160
+ legacyLocation: techFile("tech/architecture-notes.md") || techFile("tech/decision-candidates.md")
9161
+ }
9162
+ };
9163
+ if (techKnowledge.discovery.legacyLocation) {
9164
+ missing.push("Tech discovery files are in the legacy `knowledge/tech/` root. Run `kaddo tech organize` to move them to `knowledge/tech/discovery/`.");
9165
+ }
8984
9166
  const unifiedPhase = {
8985
9167
  ...phase,
8986
9168
  nextStep: nextStepRecommendation.label,
@@ -9019,6 +9201,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9019
9201
  phase: unifiedPhase,
9020
9202
  nextStepRecommendation,
9021
9203
  techDecisions,
9204
+ techKnowledge,
9205
+ installedAssets: (() => {
9206
+ const s = installedAssetsSummary(dir);
9207
+ const compact = (a) => ({ total: a.total, installed: a.total - a.missing, outdated: a.outdated, unknown_version: a.unknown_version, modified: a.modified });
9208
+ return { version: s.version, agents: compact(s.agents), skills: compact(s.skills) };
9209
+ })(),
9022
9210
  deliveryMix,
9023
9211
  external: loadExternalCapsules(dir),
9024
9212
  graph: loadGraphSummary(dir),
@@ -9560,6 +9748,17 @@ function runUnderstand() {
9560
9748
  console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
9561
9749
  console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
9562
9750
  }
9751
+ const ia = exp.installedAssets;
9752
+ const outdatedRecommended = ia.agents.items.filter(
9753
+ (a) => (a.state === "outdated" || a.state === "unknown-version") && assessment.recommendedAgents.includes(a.name)
9754
+ );
9755
+ if (outdatedRecommended.length > 0) {
9756
+ console.log("");
9757
+ for (const a of outdatedRecommended) {
9758
+ console.log(`Recommended agent ${a.name} is ${a.state} (installed ${a.installed ?? "unknown"}, available ${a.available}).`);
9759
+ }
9760
+ console.log(" \u2192 Run `kaddo agents update` or review `kaddo agents status`.");
9761
+ }
9563
9762
  if (exp.externalCapsules.length > 0) {
9564
9763
  console.log("");
9565
9764
  console.log("External knowledge:");
@@ -9720,7 +9919,7 @@ var DECLARED_LEVEL = {
9720
9919
  incident: "K2",
9721
9920
  rfc: "K3"
9722
9921
  };
9723
- function classify(declaredType, declaredLevel, touchedFiles) {
9922
+ function classify2(declaredType, declaredLevel, touchedFiles) {
9724
9923
  const signals = detectSignals(touchedFiles);
9725
9924
  const observedLevel = highestLevel(signals);
9726
9925
  const expectedLevel = DECLARED_LEVEL[declaredType] ?? declaredLevel ?? "K2";
@@ -9800,7 +9999,7 @@ async function runClassify(opts = {}) {
9800
9999
  return;
9801
10000
  }
9802
10001
  if (opts.type) {
9803
- const result2 = classify(opts.type, opts.level ?? "", touchedFiles);
10002
+ const result2 = classify2(opts.type, opts.level ?? "", touchedFiles);
9804
10003
  printResult2(result2);
9805
10004
  return;
9806
10005
  }
@@ -9811,7 +10010,7 @@ async function runClassify(opts = {}) {
9811
10010
  console.log("Or create one with: kaddo create <type>");
9812
10011
  return;
9813
10012
  }
9814
- const result = classify(activeItem.type, activeItem.knowledgeLevel, touchedFiles);
10013
+ const result = classify2(activeItem.type, activeItem.knowledgeLevel, touchedFiles);
9815
10014
  printResult2(result, activeItem.id || activeItem.title);
9816
10015
  }
9817
10016
 
@@ -9880,7 +10079,7 @@ function runStatus() {
9880
10079
  }
9881
10080
 
9882
10081
  // src/commands/learn.ts
9883
- import matter6 from "gray-matter";
10082
+ import matter7 from "gray-matter";
9884
10083
  var ARCH_DIR9 = "knowledge";
9885
10084
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
9886
10085
  function findWorkItemFile(dir, id) {
@@ -9892,7 +10091,7 @@ function findWorkItemFile(dir, id) {
9892
10091
  }
9893
10092
  function updateWorkItemFile(filePath, learning) {
9894
10093
  const raw = readFile(filePath);
9895
- const { data, content } = matter6(raw);
10094
+ const { data, content } = matter7(raw);
9896
10095
  data.status = "done";
9897
10096
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9898
10097
  let updatedContent = content;
@@ -9917,7 +10116,7 @@ ${learning.trim()}
9917
10116
  ${learning.trim()}
9918
10117
  `;
9919
10118
  }
9920
- const newRaw = matter6.stringify(updatedContent, data);
10119
+ const newRaw = matter7.stringify(updatedContent, data);
9921
10120
  writeFile(filePath, newRaw);
9922
10121
  }
9923
10122
  async function runLearn(artifactId) {
@@ -10167,7 +10366,7 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
10167
10366
  }
10168
10367
 
10169
10368
  // src/core/ownership-suggest.ts
10170
- import matter7 from "gray-matter";
10369
+ import matter8 from "gray-matter";
10171
10370
  var SCAN_PATH = ".kaddo/scan.json";
10172
10371
  function normalizeGlob(input) {
10173
10372
  let g = input.trim().replace(/\\/g, "/");
@@ -10289,11 +10488,11 @@ function suggestGlobs(artifact, signals) {
10289
10488
  return [...new Set(out)];
10290
10489
  }
10291
10490
  function applyOwnership(raw, globs, mode = "replace") {
10292
- const parsed = matter7(raw);
10491
+ const parsed = matter8(raw);
10293
10492
  const existing = toStringArray3(parsed.data.code);
10294
10493
  const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
10295
10494
  const data = { ...parsed.data, code: next };
10296
- return matter7.stringify(parsed.content, data);
10495
+ return matter8.stringify(parsed.content, data);
10297
10496
  }
10298
10497
 
10299
10498
  // src/commands/owners.ts
@@ -12725,7 +12924,7 @@ var FILE_TARGETS = [
12725
12924
  { path: "knowledge/tech/current-state.md", kind: "current-state" },
12726
12925
  { path: "knowledge/delivery/roadmap.md", kind: "roadmap" }
12727
12926
  ];
12728
- var DIR_TARGETS = ["knowledge/tech/decisions", "knowledge/delivery/work-items"];
12927
+ var DIR_TARGETS = ["knowledge/tech/decisions", "knowledge/tech/discovery", "knowledge/delivery/work-items"];
12729
12928
  function withLanguageDirective(content, language) {
12730
12929
  if (language !== "es") return content;
12731
12930
  const note = "> Idioma del proyecto: **espa\xF1ol**. Escribe este conocimiento en espa\xF1ol. Mant\xE9n en ingl\xE9s el c\xF3digo, los nombres de archivo, los comandos y las claves de configuraci\xF3n.\n";
@@ -12905,7 +13104,7 @@ function runGraphExport(opts = {}) {
12905
13104
  }
12906
13105
 
12907
13106
  // src/core/impact-report.ts
12908
- import matter8 from "gray-matter";
13107
+ import matter9 from "gray-matter";
12909
13108
  function isBroadGlob(glob) {
12910
13109
  if (!glob.endsWith("/**")) return false;
12911
13110
  const prefix = glob.slice(0, -3);
@@ -12974,7 +13173,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
12974
13173
  }
12975
13174
  }
12976
13175
  try {
12977
- const body = matter8(readFile(wi.filePath)).content;
13176
+ const body = matter9(readFile(wi.filePath)).content;
12978
13177
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
12979
13178
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
12980
13179
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
@@ -14017,6 +14216,15 @@ function runAdr(opts = {}) {
14017
14216
  console.log(` Decision candidates: ${td.candidates}`);
14018
14217
  console.log(` ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
14019
14218
  console.log(` Status: ${td.status}`);
14219
+ if (td.candidates_source) console.log(` Source: ${td.candidates_source}`);
14220
+ if (td.candidates_both_exist) {
14221
+ console.log("");
14222
+ console.log(" Note: both decision-candidate files exist.");
14223
+ console.log(" Using: knowledge/tech/discovery/decision-candidates.md");
14224
+ console.log(" Legacy file also found: knowledge/tech/decision-candidates.md");
14225
+ } else if (td.candidates_legacy_location) {
14226
+ console.log(" (legacy location \u2014 consider `kaddo tech organize` to move it to knowledge/tech/discovery/)");
14227
+ }
14020
14228
  if (td.candidate_list.length > 0 && td.adrs === 0) {
14021
14229
  console.log("");
14022
14230
  console.log("ADR candidates found:");
@@ -14041,6 +14249,105 @@ function runAdr(opts = {}) {
14041
14249
  printCommandFooter("adr");
14042
14250
  }
14043
14251
 
14252
+ // src/commands/tech.ts
14253
+ import fs3 from "fs";
14254
+ var DISCOVERY_FILES = ["architecture-notes.md", "decision-candidates.md"];
14255
+ var DISCOVERY_DIR = "knowledge/tech/discovery";
14256
+ function runTechOrganize(dir = cwd()) {
14257
+ requireConfig(dir);
14258
+ intro2("kaddo tech organize");
14259
+ const moved = [];
14260
+ const skipped = [];
14261
+ for (const file of DISCOVERY_FILES) {
14262
+ const from = join(dir, "knowledge/tech", file);
14263
+ const to = join(dir, DISCOVERY_DIR, file);
14264
+ if (!exists(from)) continue;
14265
+ if (exists(to)) {
14266
+ skipped.push(file);
14267
+ continue;
14268
+ }
14269
+ ensureDir(join(dir, DISCOVERY_DIR));
14270
+ fs3.renameSync(from, to);
14271
+ moved.push(`knowledge/tech/${file} \u2192 ${DISCOVERY_DIR}/${file}`);
14272
+ }
14273
+ if (moved.length > 0) {
14274
+ log2.success("Moved:");
14275
+ for (const m of moved) console.log(` - ${m}`);
14276
+ }
14277
+ for (const file of skipped) {
14278
+ log2.warn(`Cannot move ${file} because knowledge/tech/discovery/${file} already exists. Review both files manually.`);
14279
+ }
14280
+ if (moved.length === 0 && skipped.length === 0) {
14281
+ log2.info("Nothing to organize \u2014 no discovery files in the legacy `knowledge/tech/` root.");
14282
+ }
14283
+ printCommandFooter("tech organize");
14284
+ outro2("Tech knowledge organized.");
14285
+ }
14286
+
14287
+ // src/commands/assets.ts
14288
+ var LABEL = { agent: "Agents", skill: "Skills" };
14289
+ function runAssetsStatus(kind, opts = {}) {
14290
+ const dir = cwd();
14291
+ requireConfig(dir);
14292
+ const summary = assetStatus(dir, kind);
14293
+ if (opts.json) {
14294
+ console.log(JSON.stringify(summary, null, 2));
14295
+ return;
14296
+ }
14297
+ console.log("");
14298
+ console.log(LABEL[kind]);
14299
+ console.log("");
14300
+ const installed = summary.items.filter((i) => i.state !== "missing");
14301
+ for (const a of installed) {
14302
+ console.log(`${a.path}`);
14303
+ console.log(` Name: ${a.name}`);
14304
+ console.log(` Installed: ${a.installed ?? "unknown"}`);
14305
+ console.log(` Available: ${a.available}`);
14306
+ console.log(` Status: ${a.state}`);
14307
+ console.log("");
14308
+ }
14309
+ if (installed.length === 0) console.log(`No ${kind}s installed. Run \`kaddo add ${kind}s\`.`);
14310
+ console.log(
14311
+ `Summary: ${summary.up_to_date} up-to-date, ${summary.outdated} outdated, ${summary.unknown_version} unknown-version, ${summary.modified} modified, ${summary.missing} not installed.`
14312
+ );
14313
+ if (summary.outdated > 0 || summary.unknown_version > 0) {
14314
+ console.log(`Run \`kaddo ${kind}s update\` to refresh outdated ${kind}s (\`--force\` for unknown/modified).`);
14315
+ }
14316
+ printCommandFooter(`${kind}s status`);
14317
+ }
14318
+ function runAssetsUpdate(kind, opts = {}) {
14319
+ const dir = cwd();
14320
+ requireConfig(dir);
14321
+ intro2(`kaddo ${kind}s update`);
14322
+ const summary = assetStatus(dir, kind);
14323
+ const catalog = kind === "agent" ? canonicalAgents() : canonicalSkills();
14324
+ const byPath = new Map(catalog.map((a) => [a.path, a.content]));
14325
+ const updated = [];
14326
+ const skipped = [];
14327
+ for (const item of summary.items) {
14328
+ const canonical = byPath.get(item.path);
14329
+ if (!canonical) continue;
14330
+ const safe = item.state === "outdated";
14331
+ const needsForce = item.state === "unknown-version" || item.state === "modified";
14332
+ if (safe || needsForce && opts.force) {
14333
+ writeFile(join(dir, item.path), canonical);
14334
+ updated.push(`${item.path} (${item.installed ?? "unknown"} \u2192 ${item.available})`);
14335
+ } else if (needsForce) {
14336
+ skipped.push(`${item.path} \u2014 ${item.state} (use --force to overwrite local changes)`);
14337
+ }
14338
+ }
14339
+ if (updated.length > 0) {
14340
+ log2.success("Updated:");
14341
+ for (const u of updated) console.log(` - ${u}`);
14342
+ } else {
14343
+ log2.info(`No ${kind}s to update.`);
14344
+ }
14345
+ for (const s of skipped) log2.warn(`Skipped ${s}`);
14346
+ if (summary.missing > 0) log2.info(`${summary.missing} ${kind}(s) not installed \u2014 run \`kaddo add ${kind}s\` to add them.`);
14347
+ printCommandFooter(`${kind}s status`);
14348
+ outro2(`${LABEL[kind]} update complete.`);
14349
+ }
14350
+
14044
14351
  // src/index.ts
14045
14352
  var require2 = createRequire(import.meta.url);
14046
14353
  var { version } = require2("../package.json");
@@ -14094,6 +14401,16 @@ program.command("drift").description("Drift Trend Report from recorded `kaddo gu
14094
14401
  var questionsAction = (opts) => runQuestions(opts);
14095
14402
  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);
14096
14403
  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);
14404
+ var agentsCmd = program.command("agents").description("Inspect and update installed Kaddo agents (version status)");
14405
+ 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));
14406
+ 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));
14407
+ var skillsCmd = program.command("skills").description("Inspect and update installed Kaddo skills (version status)");
14408
+ 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));
14409
+ 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));
14410
+ var techCmd = program.command("tech").description("Organize the knowledge/tech/ structure (core vs discovery vs decisions)");
14411
+ techCmd.command("organize").description("Move discovery artifacts into knowledge/tech/discovery/ (never overwrites, no content change)").action(() => {
14412
+ runTechOrganize();
14413
+ });
14097
14414
  program.command("adr").alias("decisions").description("List technical decision candidates and the ADR files to create from them (read-only)").option("--json", "Output JSON").action((opts) => {
14098
14415
  runAdr(opts);
14099
14416
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.40.1",
3
+ "version": "3.42.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {