@kaddo/cli 3.53.0 → 3.55.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 (2) hide show
  1. package/dist/index.js +199 -45
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2791,16 +2791,49 @@ function renderSkillsSection(agent) {
2791
2791
  ""
2792
2792
  ].join("\n");
2793
2793
  }
2794
+ var KNOWLEDGE_REFINING_AGENTS = {
2795
+ "business-agent": "business-agent",
2796
+ "capability-agent": "capability-agent",
2797
+ "bootstrap-agent": "bootstrap-agent",
2798
+ "codebase-agent": "codebase-agent",
2799
+ "architecture-agent": "architecture-agent",
2800
+ "roadmap-agent": "roadmap-agent",
2801
+ "legacy-agent": "legacy-agent",
2802
+ "work-item-agent": "work-item-agent",
2803
+ "backlog-agent": "backlog-agent",
2804
+ "adr-agent": "adr-agent",
2805
+ "implementation-agent": "implementation-agent"
2806
+ };
2807
+ function renderFrontmatterRules(agent) {
2808
+ if (!KNOWLEDGE_REFINING_AGENTS[agent]) return "";
2809
+ return [
2810
+ "## Frontmatter Rules",
2811
+ "",
2812
+ "When rewriting an existing Kaddo knowledge file:",
2813
+ "",
2814
+ "- Preserve the existing YAML frontmatter.",
2815
+ "- Do not remove `type`, `generated_by`, or `template_version`.",
2816
+ "- If the document is no longer a placeholder, set `project_state: ai-assisted`.",
2817
+ `- Add or update \`refined_by: ${agent}\`.`,
2818
+ "- Preserve unknown frontmatter keys \u2014 do not strip fields you do not recognize.",
2819
+ "- Only rewrite the markdown body unless metadata changes are explicitly required by these rules.",
2820
+ "- Preserve structural sections like `## Open Questions` \u2014 leave them empty rather than removing them.",
2821
+ ""
2822
+ ].join("\n");
2823
+ }
2794
2824
  function withResponsibilityTrace(fileName, content) {
2795
2825
  const agent = fileName.replace(/\.md$/, "");
2796
2826
  if (!RESPONSIBILITY_MATRIX[agent]) return content;
2797
2827
  const skills = renderSkillsSection(agent);
2798
2828
  const skillsBlock = skills ? `${skills}
2829
+ ` : "";
2830
+ const frontmatter = renderFrontmatterRules(agent);
2831
+ const frontmatterBlock = frontmatter ? `${frontmatter}
2799
2832
  ` : "";
2800
2833
  return `${content.trimEnd()}
2801
2834
 
2802
2835
  ${renderLanguageRule()}
2803
- ${renderAgentBoundaries(agent)}
2836
+ ${frontmatterBlock}${renderAgentBoundaries(agent)}
2804
2837
  ${skillsBlock}${renderAgentTrace(agent)}`;
2805
2838
  }
2806
2839
 
@@ -5345,6 +5378,42 @@ function domainsFromRelated(domain) {
5345
5378
  var WORK_ITEMS_DIR = "knowledge/delivery/work-items";
5346
5379
  var DRAFT_DIR = `${WORK_ITEMS_DIR}/draft`;
5347
5380
  var ROADMAP_PATH = "knowledge/delivery/roadmap.md";
5381
+ function ensureWorkItemsDir(dir) {
5382
+ if (exists(join(dir, WORK_ITEMS_DIR))) return;
5383
+ if (!exists(join(dir, ".kaddo", "config.yml"))) {
5384
+ log2.warn("Kaddo is not initialized. Run `kaddo init` first.");
5385
+ process.exit(1);
5386
+ }
5387
+ ensureDir(join(dir, WORK_ITEMS_DIR));
5388
+ log2.info(`Created ${WORK_ITEMS_DIR}/`);
5389
+ }
5390
+ async function collectAcceptanceCriteria() {
5391
+ const criteria = [];
5392
+ while (true) {
5393
+ const criterion = await text2({
5394
+ message: criteria.length === 0 ? "Acceptance criterion" : "Acceptance criterion",
5395
+ placeholder: "e.g. Users see confirmation after checkout",
5396
+ validate: (v) => criteria.length === 0 && v.trim().length === 0 ? "At least one criterion is required." : void 0
5397
+ });
5398
+ const trimmed = criterion.trim();
5399
+ if (!trimmed && criteria.length > 0) break;
5400
+ if (trimmed.includes(";")) {
5401
+ criteria.push(...trimmed.split(";").map((s) => s.trim()).filter(Boolean));
5402
+ } else {
5403
+ criteria.push(trimmed);
5404
+ }
5405
+ const addMore = await confirm2({ message: "Add another acceptance criterion?" });
5406
+ if (!addMore) break;
5407
+ }
5408
+ return criteria.join("\n");
5409
+ }
5410
+ function renderAcceptanceCriteria(raw) {
5411
+ const items = raw.split(/\n|;/).map((l) => l.trim()).filter(Boolean);
5412
+ return items.map((item) => {
5413
+ const normalized = item.endsWith(".") ? item : `${item}.`;
5414
+ return `- [ ] ${normalized.charAt(0).toUpperCase() + normalized.slice(1)}`;
5415
+ }).join("\n");
5416
+ }
5348
5417
  function printStateGuidance(dir) {
5349
5418
  let config;
5350
5419
  try {
@@ -5393,11 +5462,16 @@ function buildFrontMatter(id, type, level, title, answers) {
5393
5462
  `knowledge_level: ${level}`,
5394
5463
  `status: draft`,
5395
5464
  `phase: now`,
5465
+ `work_type: ${type}`,
5396
5466
  `initiative:`,
5397
5467
  `domains: []`,
5398
5468
  `code: []`,
5399
5469
  `created_at: ${today2}`,
5400
- `source: manual`,
5470
+ `source:`,
5471
+ ` type: manual`,
5472
+ ` inferred: false`,
5473
+ `generated_by: kaddo-create`,
5474
+ `template_version: 1`,
5401
5475
  `summary: "${answers.problem?.split(".")[0] ?? title}"`,
5402
5476
  "---"
5403
5477
  ];
@@ -5428,10 +5502,9 @@ ${answers.impact}
5428
5502
  `);
5429
5503
  }
5430
5504
  if (answers.acceptance_criteria) {
5431
- const items = answers.acceptance_criteria.split("\n").map((l) => l.trim()).filter(Boolean);
5432
5505
  sections.push(`## Acceptance criteria
5433
5506
 
5434
- ${formatList(items)}
5507
+ ${renderAcceptanceCriteria(answers.acceptance_criteria)}
5435
5508
  `);
5436
5509
  }
5437
5510
  if (answers.design) {
@@ -5554,6 +5627,7 @@ async function runCreate(type, opts = {}) {
5554
5627
  intro2(`kaddo create ${workItemType}`);
5555
5628
  log2.info(`Knowledge level: ${level} \u2014 ${levelDef.description}`);
5556
5629
  printStateGuidance(dir);
5630
+ ensureWorkItemsDir(dir);
5557
5631
  const title = await text2({
5558
5632
  message: "Title for this work item",
5559
5633
  placeholder: `e.g. ${workItemType === "feature" ? "Add email verification to checkout" : workItemType === "hotfix" ? "Fix null pointer in payment handler" : workItemType === "bugfix" ? "Fix broken pagination on orders list" : workItemType === "chore" ? "Configure Vitest and CI pipeline" : "Explore caching strategies for API responses"}`,
@@ -5561,6 +5635,10 @@ async function runCreate(type, opts = {}) {
5561
5635
  });
5562
5636
  const answers = {};
5563
5637
  for (const question of levelDef.questions) {
5638
+ if (question.frontMatterField === "acceptance_criteria") {
5639
+ answers.acceptance_criteria = await collectAcceptanceCriteria();
5640
+ continue;
5641
+ }
5564
5642
  const answer = await text2({
5565
5643
  message: question.prompt,
5566
5644
  placeholder: question.placeholder,
@@ -5577,10 +5655,6 @@ async function runCreate(type, opts = {}) {
5577
5655
  const content = `${frontMatter2}
5578
5656
 
5579
5657
  ${body}`;
5580
- if (!exists(join(dir, WORK_ITEMS_DIR))) {
5581
- log2.warn(`${WORK_ITEMS_DIR}/ not found. Run \`kaddo init\` first.`);
5582
- process.exit(1);
5583
- }
5584
5658
  writeFile(filePath, content);
5585
5659
  log2.success(`Created ${DRAFT_DIR}/${fileName}`);
5586
5660
  log2.info(`New Work Items start in \`draft\`. Move to \`ready\` when scope and acceptance are set.`);
@@ -5591,10 +5665,7 @@ async function runCreateModule(dir, modType) {
5591
5665
  intro2(`kaddo create ${modType.name}`);
5592
5666
  log2.info(`Knowledge level: ${modType.knowledgeLevel} \u2014 ${modType.description}`);
5593
5667
  printStateGuidance(dir);
5594
- if (!exists(join(dir, WORK_ITEMS_DIR))) {
5595
- log2.warn(`${WORK_ITEMS_DIR}/ not found. Run \`kaddo init\` first.`);
5596
- process.exit(1);
5597
- }
5668
+ ensureWorkItemsDir(dir);
5598
5669
  const title = await text2({
5599
5670
  message: "Title for this work item",
5600
5671
  placeholder: `e.g. ${modType.questions[0]?.placeholder ?? modType.description}`,
@@ -5731,10 +5802,9 @@ ${formatList(candidate.sourceSignals)}`);
5731
5802
  ${ctx.join("\n\n")}
5732
5803
  `);
5733
5804
  if (answers.acceptance_criteria) {
5734
- const items = answers.acceptance_criteria.split("\n").map((l) => l.trim()).filter(Boolean);
5735
5805
  sections.push(`## Acceptance Criteria
5736
5806
 
5737
- ${formatList(items)}
5807
+ ${renderAcceptanceCriteria(answers.acceptance_criteria)}
5738
5808
  `);
5739
5809
  }
5740
5810
  if (answers.design) sections.push(`## Design
@@ -5788,10 +5858,7 @@ ${body}` };
5788
5858
  }
5789
5859
  async function runCreateFromRoadmap(dir, cliType) {
5790
5860
  intro2("kaddo create --from roadmap");
5791
- if (!exists(join(dir, WORK_ITEMS_DIR))) {
5792
- log2.warn(`${WORK_ITEMS_DIR}/ not found. Run \`kaddo init\` first.`);
5793
- process.exit(1);
5794
- }
5861
+ ensureWorkItemsDir(dir);
5795
5862
  const roadmapFull = join(dir, ROADMAP_PATH);
5796
5863
  if (!exists(roadmapFull)) {
5797
5864
  console.error(`No roadmap found at ${ROADMAP_PATH}.`);
@@ -6271,6 +6338,54 @@ async function collectWorkspaceChanges(dir, mode = "head", deps = defaultDeps) {
6271
6338
  };
6272
6339
  }
6273
6340
 
6341
+ // src/core/metadata-health.ts
6342
+ import matter2 from "gray-matter";
6343
+ var KNOWLEDGE_FILES = [
6344
+ "knowledge/business/business.md",
6345
+ "knowledge/product/product.md",
6346
+ "knowledge/product/capabilities.md",
6347
+ "knowledge/tech/codebase.md",
6348
+ "knowledge/tech/current-state.md",
6349
+ "knowledge/delivery/roadmap.md"
6350
+ ];
6351
+ var REQUIRED_FIELDS = ["type", "generated_by", "template_version"];
6352
+ function analyzeMetadataHealth(dir) {
6353
+ const findings = [];
6354
+ let healthy = 0;
6355
+ let drifted = 0;
6356
+ for (const rel of KNOWLEDGE_FILES) {
6357
+ const p2 = join(dir, rel);
6358
+ if (!exists(p2)) continue;
6359
+ let data;
6360
+ try {
6361
+ const raw = readFile(p2);
6362
+ data = matter2(raw).data;
6363
+ } catch {
6364
+ continue;
6365
+ }
6366
+ if (Object.keys(data).length === 0) continue;
6367
+ let fileDrifted = false;
6368
+ for (const field of REQUIRED_FIELDS) {
6369
+ if (data[field] === void 0 || data[field] === null || data[field] === "") {
6370
+ findings.push({ file: rel, field, issue: "missing", detail: `Missing \`${field}\` in frontmatter.` });
6371
+ fileDrifted = true;
6372
+ }
6373
+ }
6374
+ if (data.refined_by && data.project_state && data.project_state !== "ai-assisted") {
6375
+ findings.push({
6376
+ file: rel,
6377
+ field: "project_state",
6378
+ issue: "inconsistent",
6379
+ detail: `File has \`refined_by: ${data.refined_by}\` but \`project_state\` is \`${data.project_state}\`, expected \`ai-assisted\`.`
6380
+ });
6381
+ fileDrifted = true;
6382
+ }
6383
+ if (fileDrifted) drifted++;
6384
+ else healthy++;
6385
+ }
6386
+ return { findings, healthy, drifted };
6387
+ }
6388
+
6274
6389
  // src/commands/guard.ts
6275
6390
  import path4 from "path";
6276
6391
  import { parse as parseYaml7 } from "yaml";
@@ -6893,6 +7008,14 @@ async function runGuard(opts = {}) {
6893
7008
  console.log("");
6894
7009
  }
6895
7010
  printPluginSignals(pluginSignals);
7011
+ const mh = analyzeMetadataHealth(dir);
7012
+ if (mh.findings.length > 0) {
7013
+ console.log("Metadata health:");
7014
+ for (const f of mh.findings) {
7015
+ console.log(` \u26A0 ${f.file}: ${f.detail}`);
7016
+ }
7017
+ console.log("");
7018
+ }
6896
7019
  const ownerMap = loadOwners(dir);
6897
7020
  const matchedDomains = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
6898
7021
  const affectedOwners = resolveAffectedOwners(matchedDomains, ownerMap);
@@ -6954,7 +7077,7 @@ function runIgnoreRemove(artifactId) {
6954
7077
  }
6955
7078
 
6956
7079
  // src/commands/explain.ts
6957
- import matter5 from "gray-matter";
7080
+ import matter6 from "gray-matter";
6958
7081
  import { parse as parseYaml9 } from "yaml";
6959
7082
 
6960
7083
  // src/core/delivery-phase.ts
@@ -7120,7 +7243,7 @@ function assessPhase(input) {
7120
7243
  }
7121
7244
 
7122
7245
  // src/core/capsule.ts
7123
- import matter2 from "gray-matter";
7246
+ import matter3 from "gray-matter";
7124
7247
  import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
7125
7248
  var KNOWLEDGE = "knowledge";
7126
7249
  function today() {
@@ -7255,7 +7378,7 @@ function sectionParagraph(md, title) {
7255
7378
  return "";
7256
7379
  }
7257
7380
  function parseCapsule(id, path6, md) {
7258
- const { data } = matter2(md);
7381
+ const { data } = matter3(md);
7259
7382
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
7260
7383
  let ageDays = null;
7261
7384
  if (updatedAt) {
@@ -7281,7 +7404,7 @@ function slugId(s) {
7281
7404
  function addExternalCapsule(dir, sourceFile) {
7282
7405
  if (!exists(sourceFile)) throw new Error(`Capsule not found: ${sourceFile}`);
7283
7406
  const raw = readFile(sourceFile);
7284
- const { data } = matter2(raw);
7407
+ const { data } = matter3(raw);
7285
7408
  const isCapsuleType = data.type === "knowledge-capsule";
7286
7409
  const fallbackName = sourceFile.split(/[\\/]/).pop()?.replace(/\.capsule\.md$/, "") ?? "external";
7287
7410
  const id = slugId(String(data.system ?? fallbackName));
@@ -7753,7 +7876,7 @@ function loadGraphHints(dir) {
7753
7876
  }
7754
7877
 
7755
7878
  // src/services/installed-skills.ts
7756
- import matter3 from "gray-matter";
7879
+ import matter4 from "gray-matter";
7757
7880
  var SKILLS_DIR = join("knowledge", "skills");
7758
7881
  function discoverInstalledSkills(dir) {
7759
7882
  const base = join(dir, SKILLS_DIR);
@@ -7763,7 +7886,7 @@ function discoverInstalledSkills(dir) {
7763
7886
  const skillFile = join(base, entry, "skill.md");
7764
7887
  if (!isFile(skillFile)) continue;
7765
7888
  try {
7766
- const { data } = matter3(readFile(skillFile));
7889
+ const { data } = matter4(readFile(skillFile));
7767
7890
  if (data.type && String(data.type) !== "skill") continue;
7768
7891
  const id = String(data.id ?? entry);
7769
7892
  out.push({
@@ -9021,7 +9144,7 @@ function buildSecondaryRecommendations(st) {
9021
9144
  }
9022
9145
 
9023
9146
  // src/core/readiness.ts
9024
- var KNOWLEDGE_FILES = [
9147
+ var KNOWLEDGE_FILES2 = [
9025
9148
  { key: "current_state", path: "knowledge/tech/current-state.md", label: "knowledge/tech/current-state.md", agent: "architecture-agent" },
9026
9149
  { key: "codebase", path: "knowledge/tech/codebase.md", label: "knowledge/tech/codebase.md", agent: "codebase-agent" },
9027
9150
  { key: "capabilities", path: "knowledge/product/capabilities.md", label: "knowledge/product/capabilities.md", agent: "capability-agent" },
@@ -9062,7 +9185,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
9062
9185
  if (config.project.state === "legacy") return stub("legacy-project", "Use the legacy project flow.");
9063
9186
  const scan2 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
9064
9187
  const understand = exists(join(dir, ".kaddo", "understand.md")) ? "available" : "missing";
9065
- const presence = Object.fromEntries(KNOWLEDGE_FILES.map((f) => [f.key, analyzeKnowledgeArtifact(dir, f.path)]));
9188
+ const presence = Object.fromEntries(KNOWLEDGE_FILES2.map((f) => [f.key, analyzeKnowledgeArtifact(dir, f.path)]));
9066
9189
  const ctx = buildCodexAdapterContext(dir);
9067
9190
  const agents = ctx.hasAgents ? "present" : "missing";
9068
9191
  const skills = ctx.hasSkills ? "present" : "missing";
@@ -9090,7 +9213,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
9090
9213
  resolved_questions: oq.summary.resolution.resolved,
9091
9214
  deferred_questions: oq.summary.resolution.deferred
9092
9215
  };
9093
- const firstWeak = KNOWLEDGE_FILES.find((f) => presence[f.key] !== "useful");
9216
+ const firstWeak = KNOWLEDGE_FILES2.find((f) => presence[f.key] !== "useful");
9094
9217
  let overall;
9095
9218
  if (scan2 === "missing") overall = "initialized";
9096
9219
  else if (bootstrapBaseline2 === "incomplete") overall = "bootstrap-incomplete";
@@ -9113,7 +9236,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
9113
9236
  }
9114
9237
 
9115
9238
  // src/core/assets.ts
9116
- import matter4 from "gray-matter";
9239
+ import matter5 from "gray-matter";
9117
9240
  function canonicalAgents() {
9118
9241
  return AGENT_PROMPTS.map((a) => ({
9119
9242
  name: a.fileName.replace(/\.md$/, ""),
@@ -9126,7 +9249,7 @@ function canonicalSkills() {
9126
9249
  }
9127
9250
  function versionOf(content) {
9128
9251
  try {
9129
- const v = matter4(content).data.version;
9252
+ const v = matter5(content).data.version;
9130
9253
  return v === void 0 || v === null ? null : String(v);
9131
9254
  } catch {
9132
9255
  return null;
@@ -10028,7 +10151,8 @@ function buildProjectExplanation(dir) {
10028
10151
  installedAssets: installedAssetsSummary(dir),
10029
10152
  roadmapQuality: buildRoadmapQuality(dir),
10030
10153
  projectRoute: buildProjectRoute(dir),
10031
- scanSignals: loadScanSignals(dir)
10154
+ scanSignals: loadScanSignals(dir),
10155
+ metadataHealth: analyzeMetadataHealth(dir)
10032
10156
  };
10033
10157
  }
10034
10158
  function stateLabel(state) {
@@ -10337,6 +10461,13 @@ function renderExplanationHuman(exp) {
10337
10461
  }
10338
10462
  lines.push("");
10339
10463
  }
10464
+ if (exp.metadataHealth.findings.length > 0) {
10465
+ lines.push("## Metadata Health");
10466
+ for (const f of exp.metadataHealth.findings) {
10467
+ lines.push(`- \`${f.file}\`: ${f.detail}`);
10468
+ }
10469
+ lines.push("");
10470
+ }
10340
10471
  return lines.join("\n").trimEnd() + "\n";
10341
10472
  }
10342
10473
  function renderExplanationAgent(exp) {
@@ -10352,7 +10483,7 @@ function readKnowledge(dir) {
10352
10483
  if (!exists(knowledgePath)) return null;
10353
10484
  try {
10354
10485
  const raw = readFile(knowledgePath);
10355
- const { data, content } = matter5(raw);
10486
+ const { data, content } = matter6(raw);
10356
10487
  return { content, data };
10357
10488
  } catch {
10358
10489
  return null;
@@ -10363,7 +10494,7 @@ function readRoadmap(dir) {
10363
10494
  if (!exists(roadmapPath)) return null;
10364
10495
  try {
10365
10496
  const raw = readFile(roadmapPath);
10366
- const { content } = matter5(raw);
10497
+ const { content } = matter6(raw);
10367
10498
  return content.trim();
10368
10499
  } catch {
10369
10500
  return null;
@@ -10532,7 +10663,7 @@ function runExplain(opts) {
10532
10663
  }
10533
10664
 
10534
10665
  // src/core/context-pack.ts
10535
- import matter6 from "gray-matter";
10666
+ import matter7 from "gray-matter";
10536
10667
  var CONTEXT_PACK_VERSION = "1";
10537
10668
  var ARCH_DIR6 = "knowledge";
10538
10669
  function readScanJson(dir) {
@@ -10545,7 +10676,7 @@ function readScanJson(dir) {
10545
10676
  }
10546
10677
  }
10547
10678
  function firstParagraph2(markdown) {
10548
- const body = matter6(markdown).content.trim();
10679
+ const body = matter7(markdown).content.trim();
10549
10680
  const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
10550
10681
  return para ?? "";
10551
10682
  }
@@ -10760,6 +10891,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
10760
10891
  skills: discoverInstalledSkills(dir).map((s) => s.id),
10761
10892
  scanSignals: scanJson?.signals ?? null,
10762
10893
  projectRoute: buildProjectRoute(dir),
10894
+ metadataHealth: analyzeMetadataHealth(dir),
10763
10895
  mappedModules,
10764
10896
  missing,
10765
10897
  // VS-052/VS-073.2: the handoff is driven by the unified next step, so the pack never contradicts
@@ -11048,6 +11180,13 @@ function renderContextPack(pack) {
11048
11180
  } else {
11049
11181
  parts.push("_None \u2014 all expected context is present._\n");
11050
11182
  }
11183
+ if (pack.metadataHealth.findings.length > 0) {
11184
+ parts.push("\n### Metadata Health\n");
11185
+ for (const f of pack.metadataHealth.findings) {
11186
+ parts.push(`- \`${f.file}\`: ${f.detail}
11187
+ `);
11188
+ }
11189
+ }
11051
11190
  parts.push("## Recommended Agent Handoff\n");
11052
11191
  if (isBootstrapIncomplete) {
11053
11192
  parts.push("No agent handoff yet.\n");
@@ -11179,7 +11318,8 @@ function enrichUnderstandPlan(plan, opts) {
11179
11318
  activeWorkItems: opts.activeWorkItems,
11180
11319
  recommendedPaths: opts.recommendedPaths,
11181
11320
  recommendedSkillPaths: opts.recommendedSkillPaths,
11182
- projectRoute: opts.projectRoute
11321
+ projectRoute: opts.projectRoute,
11322
+ metadataHealth: opts.metadataHealth
11183
11323
  };
11184
11324
  }
11185
11325
 
@@ -11407,6 +11547,13 @@ function renderUnderstand(plan) {
11407
11547
  } else {
11408
11548
  parts.push("Re-run `kaddo understand` any time to see this plan again.\n");
11409
11549
  }
11550
+ if (plan.metadataHealth && plan.metadataHealth.findings.length > 0) {
11551
+ parts.push("## Metadata Health\n");
11552
+ for (const f of plan.metadataHealth.findings) {
11553
+ parts.push(`- \`${f.file}\`: ${f.detail}
11554
+ `);
11555
+ }
11556
+ }
11410
11557
  return parts.join("\n");
11411
11558
  }
11412
11559
  function renderUnderstandTerminal(plan) {
@@ -11467,7 +11614,13 @@ function renderUnderstandTerminal(plan) {
11467
11614
  }
11468
11615
  }
11469
11616
  }
11470
- lines.push("");
11617
+ if (plan.metadataHealth && plan.metadataHealth.findings.length > 0) {
11618
+ lines.push("Metadata health:");
11619
+ for (const f of plan.metadataHealth.findings) {
11620
+ lines.push(` \u26A0 ${f.file}: ${f.detail}`);
11621
+ }
11622
+ lines.push("");
11623
+ }
11471
11624
  lines.push("Kaddo does not call an LLM. You stay in control of the interpretation.");
11472
11625
  lines.push("");
11473
11626
  return lines.join("\n");
@@ -11697,7 +11850,8 @@ function runUnderstand() {
11697
11850
  recommendedPaths,
11698
11851
  recommendedSkillPaths,
11699
11852
  language: languageLabel(projectLanguage(config)),
11700
- projectRoute: buildProjectRoute(dir)
11853
+ projectRoute: buildProjectRoute(dir),
11854
+ metadataHealth: analyzeMetadataHealth(dir)
11701
11855
  });
11702
11856
  writeFile(join(dir, ".kaddo", "understand.md"), renderUnderstand(enrichedPlan));
11703
11857
  log2.success("Wrote .kaddo/understand.md");
@@ -11987,7 +12141,7 @@ function runStatus() {
11987
12141
  }
11988
12142
 
11989
12143
  // src/commands/learn.ts
11990
- import matter7 from "gray-matter";
12144
+ import matter8 from "gray-matter";
11991
12145
  var ARCH_DIR9 = "knowledge";
11992
12146
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
11993
12147
  function findWorkItemFile(dir, id) {
@@ -11999,7 +12153,7 @@ function findWorkItemFile(dir, id) {
11999
12153
  }
12000
12154
  function updateWorkItemFile(filePath, learning) {
12001
12155
  const raw = readFile(filePath);
12002
- const { data, content } = matter7(raw);
12156
+ const { data, content } = matter8(raw);
12003
12157
  data.status = "done";
12004
12158
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
12005
12159
  let updatedContent = content;
@@ -12024,7 +12178,7 @@ ${learning.trim()}
12024
12178
  ${learning.trim()}
12025
12179
  `;
12026
12180
  }
12027
- const newRaw = matter7.stringify(updatedContent, data);
12181
+ const newRaw = matter8.stringify(updatedContent, data);
12028
12182
  writeFile(filePath, newRaw);
12029
12183
  }
12030
12184
  async function runLearn(artifactId) {
@@ -12274,7 +12428,7 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
12274
12428
  }
12275
12429
 
12276
12430
  // src/core/ownership-suggest.ts
12277
- import matter8 from "gray-matter";
12431
+ import matter9 from "gray-matter";
12278
12432
  var SCAN_PATH = ".kaddo/scan.json";
12279
12433
  function normalizeGlob(input) {
12280
12434
  let g = input.trim().replace(/\\/g, "/");
@@ -12396,11 +12550,11 @@ function suggestGlobs(artifact, signals) {
12396
12550
  return [...new Set(out)];
12397
12551
  }
12398
12552
  function applyOwnership(raw, globs, mode = "replace") {
12399
- const parsed = matter8(raw);
12553
+ const parsed = matter9(raw);
12400
12554
  const existing = toStringArray3(parsed.data.code);
12401
12555
  const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
12402
12556
  const data = { ...parsed.data, code: next };
12403
- return matter8.stringify(parsed.content, data);
12557
+ return matter9.stringify(parsed.content, data);
12404
12558
  }
12405
12559
 
12406
12560
  // src/commands/owners.ts
@@ -15012,7 +15166,7 @@ function runGraphExport(opts = {}) {
15012
15166
  }
15013
15167
 
15014
15168
  // src/core/impact-report.ts
15015
- import matter9 from "gray-matter";
15169
+ import matter10 from "gray-matter";
15016
15170
  function isBroadGlob(glob) {
15017
15171
  if (!glob.endsWith("/**")) return false;
15018
15172
  const prefix = glob.slice(0, -3);
@@ -15081,7 +15235,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
15081
15235
  }
15082
15236
  }
15083
15237
  try {
15084
- const body = matter9(readFile(wi.filePath)).content;
15238
+ const body = matter10(readFile(wi.filePath)).content;
15085
15239
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
15086
15240
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
15087
15241
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.53.0",
3
+ "version": "3.55.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {