@kaddo/cli 3.64.0 → 3.66.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 +405 -73
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6401,7 +6401,14 @@ function parseArtifact(filePath, raw) {
6401
6401
  sourceId: String(data.source_id ?? ""),
6402
6402
  rawFrontmatter: data,
6403
6403
  decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : [],
6404
- capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : []
6404
+ capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : [],
6405
+ implementationStatus: String(data.implementation_status ?? ""),
6406
+ validationStatus: String(data.validation_status ?? ""),
6407
+ releaseStatus: String(data.release_status ?? ""),
6408
+ affectedModules: Array.isArray(data.affected_modules) ? data.affected_modules.map(String).filter(Boolean) : [],
6409
+ refinedBy: String(data.refined_by ?? ""),
6410
+ implementedBy: String(data.implemented_by ?? ""),
6411
+ closedBy: String(data.closed_by ?? "")
6405
6412
  };
6406
6413
  } catch {
6407
6414
  return null;
@@ -6833,9 +6840,125 @@ function analyzeMetadataHealth(dir) {
6833
6840
  if (fileDrifted) drifted++;
6834
6841
  else healthy++;
6835
6842
  }
6843
+ const wiDir = join(dir, "knowledge/delivery/work-items");
6844
+ if (exists(wiDir)) {
6845
+ const walkWIs = (d) => {
6846
+ for (const entry of readDir(d)) {
6847
+ const p2 = join(d, entry);
6848
+ if (isFile(p2) && entry.endsWith(".md")) {
6849
+ try {
6850
+ const raw = readFile(p2);
6851
+ const fm2 = matter2(raw).data;
6852
+ if (fm2.status === "done") {
6853
+ const rel = p2.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
6854
+ findings.push({
6855
+ file: rel,
6856
+ field: "status",
6857
+ issue: "inconsistent",
6858
+ detail: "Legacy status `done` detected. Canonical status is `completed`."
6859
+ });
6860
+ }
6861
+ } catch {
6862
+ }
6863
+ } else if (!isFile(p2) && !entry.startsWith(".")) {
6864
+ walkWIs(p2);
6865
+ }
6866
+ }
6867
+ };
6868
+ walkWIs(wiDir);
6869
+ }
6836
6870
  return { findings, healthy, drifted };
6837
6871
  }
6838
6872
 
6873
+ // src/core/cross-repo-evidence.ts
6874
+ function analyzeCrossRepoEvidence(input) {
6875
+ const findings = [];
6876
+ const { id, lifecycle, implementationStatus, validationStatus, releaseStatus, affectedModules } = input;
6877
+ const raw = input.rawFrontmatter;
6878
+ const evidence = raw.implementation_evidence;
6879
+ const repos = evidence?.repositories ?? {};
6880
+ const releaseGates = Array.isArray(raw.release_gates) ? raw.release_gates : [];
6881
+ const exceptions = Array.isArray(raw.completion_exceptions) ? raw.completion_exceptions : [];
6882
+ const repoEvidence = {};
6883
+ for (const [repoId, data] of Object.entries(repos)) {
6884
+ const d = data;
6885
+ repoEvidence[repoId] = {
6886
+ role: String(d.role ?? (repoId === "core" ? "core" : "module")),
6887
+ status: String(d.status ?? "unknown")
6888
+ };
6889
+ }
6890
+ for (const mod of affectedModules) {
6891
+ if (mod === "core") continue;
6892
+ if (!input.registeredModuleIds.includes(mod)) {
6893
+ findings.push({ id, severity: "blocking", message: `Affected module "${mod}" is not registered in .kaddo/modules.yml.` });
6894
+ }
6895
+ }
6896
+ for (const repoId of input.modifiedRepoIds) {
6897
+ if (!affectedModules.includes(repoId) && repoId !== "core") {
6898
+ findings.push({ id, severity: "blocking", message: `Repository "${repoId}" was modified but not declared in affected_modules.` });
6899
+ }
6900
+ }
6901
+ for (const mod of affectedModules) {
6902
+ if (!repoEvidence[mod] && Object.keys(repos).length > 0) {
6903
+ findings.push({ id, severity: "warning", message: `Declared affected module "${mod}" has no implementation evidence.` });
6904
+ }
6905
+ }
6906
+ for (const [repoId, data] of Object.entries(repos)) {
6907
+ const d = data;
6908
+ const validations = Array.isArray(d.validations) ? d.validations : [];
6909
+ for (const v of validations) {
6910
+ if (v.status === "not-run") {
6911
+ findings.push({ id, severity: "warning", message: `Validation "${v.command}" in ${repoId} was not executed.` });
6912
+ }
6913
+ }
6914
+ const migrations = Array.isArray(d.migrations) ? d.migrations : [];
6915
+ for (const m of migrations) {
6916
+ if (m.status === "blocked") {
6917
+ findings.push({ id, severity: "warning", message: `Migration "${m.id}" (${m.environment}) in ${repoId} is blocked.` });
6918
+ }
6919
+ }
6920
+ }
6921
+ if (lifecycle === "completed") {
6922
+ const proposedExceptions = exceptions.filter((e) => e.status === "proposed");
6923
+ if (proposedExceptions.length > 0) {
6924
+ findings.push({ id, severity: "blocking", message: "Work Item is completed but has proposed (not accepted) exceptions." });
6925
+ }
6926
+ if (releaseStatus === "ready") {
6927
+ const blockedGates = releaseGates.filter((g) => g.status === "blocked" || g.status === "failed");
6928
+ if (blockedGates.length > 0) {
6929
+ findings.push({ id, severity: "blocking", message: `Release status is "ready" but ${blockedGates.length} gate(s) are blocked/failed.` });
6930
+ }
6931
+ }
6932
+ }
6933
+ for (const [repoId, data] of Object.entries(repos)) {
6934
+ const d = data;
6935
+ const validations = Array.isArray(d.validations) ? d.validations : [];
6936
+ for (const v of validations) {
6937
+ if (v.status === "passed" && (!v.command || !v.command.trim())) {
6938
+ findings.push({ id, severity: "blocking", message: `Evidence in ${repoId} marked "passed" without a command record.` });
6939
+ }
6940
+ }
6941
+ }
6942
+ if (raw.status === "done") {
6943
+ findings.push({ id, severity: "warning", message: "Legacy status `done` detected. Canonical status is `completed`." });
6944
+ }
6945
+ if (raw.refined_by && raw.implemented_by && raw.refined_by === raw.implemented_by) {
6946
+ findings.push({ id, severity: "fyi", message: "refined_by and implemented_by point to the same agent." });
6947
+ }
6948
+ return {
6949
+ id,
6950
+ lifecycle,
6951
+ implementationStatus: implementationStatus || "not-started",
6952
+ validationStatus: validationStatus || "not-started",
6953
+ releaseStatus: releaseStatus || "not-assessed",
6954
+ affectedModules,
6955
+ findings,
6956
+ releaseGates,
6957
+ completionExceptions: exceptions,
6958
+ repoEvidence
6959
+ };
6960
+ }
6961
+
6839
6962
  // src/commands/guard.ts
6840
6963
  import path4 from "path";
6841
6964
  import { parse as parseYaml7 } from "yaml";
@@ -7466,6 +7589,37 @@ async function runGuard(opts = {}) {
7466
7589
  }
7467
7590
  console.log("");
7468
7591
  }
7592
+ const registeredModuleIds = loadMappedModules(dir).map((m) => m.id);
7593
+ const modifiedRepoIds = workspaceScan ? [...new Set(workspaceScan.changedFiles.map((c) => c.repoId))] : [];
7594
+ const wiArtifacts = artifacts.filter((a) => a.isWorkItem && a.affectedModules.length > 0);
7595
+ if (wiArtifacts.length > 0) {
7596
+ const evidenceSummaries = [];
7597
+ for (const wi of wiArtifacts) {
7598
+ const summary = analyzeCrossRepoEvidence({
7599
+ id: wi.id || wi.title,
7600
+ lifecycle: wi.lifecycle ?? "ready",
7601
+ implementationStatus: wi.implementationStatus,
7602
+ validationStatus: wi.validationStatus,
7603
+ releaseStatus: wi.releaseStatus,
7604
+ affectedModules: wi.affectedModules,
7605
+ rawFrontmatter: wi.rawFrontmatter,
7606
+ registeredModuleIds,
7607
+ modifiedRepoIds
7608
+ });
7609
+ if (summary.findings.length > 0) evidenceSummaries.push(summary);
7610
+ }
7611
+ if (evidenceSummaries.length > 0) {
7612
+ console.log("Cross-repo implementation evidence:");
7613
+ for (const s of evidenceSummaries) {
7614
+ console.log(` ${s.id}:`);
7615
+ for (const f of s.findings) {
7616
+ const icon = f.severity === "blocking" ? "\u2717" : f.severity === "warning" ? "!" : "\xB7";
7617
+ console.log(` ${icon} ${f.message}`);
7618
+ }
7619
+ }
7620
+ console.log("");
7621
+ }
7622
+ }
7469
7623
  const ownerMap = loadOwners(dir);
7470
7624
  const matchedDomains = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
7471
7625
  const affectedOwners = resolveAffectedOwners(matchedDomains, ownerMap);
@@ -7594,7 +7748,7 @@ function assessPhase(input) {
7594
7748
  const techReady = !NOT_READY_LAYER.includes(techStatus);
7595
7749
  if (techReady) {
7596
7750
  return {
7597
- phase: "Active Delivery",
7751
+ phase: "Ready for Core Orchestration",
7598
7752
  reasons: ["Module repo \u2014 Tech layer ready", "Business/Product managed by core"],
7599
7753
  recommendedAgents: [],
7600
7754
  nextStep: "Module knowledge is ready for core orchestration.",
@@ -10089,6 +10243,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
10089
10243
  const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
10090
10244
  for (const wi of selectedWIs) {
10091
10245
  const id = wi.id || wi.title;
10246
+ if (!id || !id.trim()) continue;
10092
10247
  const wiNodeId = `wi:${id}`;
10093
10248
  addNode({
10094
10249
  id: wiNodeId,
@@ -10099,16 +10254,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
10099
10254
  knowledge_level: wi.knowledgeLevel || void 0
10100
10255
  });
10101
10256
  for (const glob of wi.codeGlobs) {
10257
+ if (!glob || !glob.trim()) continue;
10102
10258
  const codeId = `code:${glob}`;
10103
10259
  addNode({ id: codeId, type: "code-glob", label: glob });
10104
10260
  addEdge(wiNodeId, codeId, "owns");
10105
10261
  }
10106
10262
  for (const cap of wi.capabilities) {
10263
+ if (!cap || !cap.trim()) continue;
10107
10264
  const capId = `capability:${slug(cap) || cap}`;
10108
10265
  addNode({ id: capId, type: "capability", label: cap });
10109
10266
  addEdge(wiNodeId, capId, "implements");
10110
10267
  }
10111
10268
  for (const dec of wi.decisions) {
10269
+ if (!dec || !dec.trim()) continue;
10112
10270
  const adrId = `adr:${dec}`;
10113
10271
  addNode({ id: adrId, type: "decision", label: dec });
10114
10272
  addEdge(wiNodeId, adrId, "depends_on");
@@ -10125,16 +10283,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
10125
10283
  }
10126
10284
  }
10127
10285
  for (const adr of all.filter(isAdr)) {
10128
- const adrId = `adr:${adr.id || adr.title}`;
10286
+ const adrLabel = adr.id || adr.title;
10287
+ if (!adrLabel || !adrLabel.trim()) continue;
10288
+ const adrId = `adr:${adrLabel}`;
10129
10289
  const referenced = nodes.has(adrId);
10130
10290
  if (scope === "all" || referenced) {
10131
10291
  nodes.set(adrId, {
10132
10292
  id: adrId,
10133
10293
  type: "decision",
10134
- label: `${adr.id} ${adr.title}`.trim() || adr.id || adr.title,
10294
+ label: `${adr.id} ${adr.title}`.trim() || adrLabel,
10135
10295
  path: adr.relPath
10136
10296
  });
10137
10297
  for (const glob of adr.codeGlobs) {
10298
+ if (!glob || !glob.trim()) continue;
10138
10299
  const codeId = `code:${glob}`;
10139
10300
  addNode({ id: codeId, type: "code-glob", label: glob });
10140
10301
  addEdge(adrId, codeId, "governs");
@@ -10190,13 +10351,20 @@ function renderGraphMermaid(graph) {
10190
10351
  safeIds.set(id, candidate);
10191
10352
  return candidate;
10192
10353
  };
10193
- const escapeLabel = (s) => s.replace(/"/g, "'");
10354
+ const escapeLabel = (s) => s.replace(/"/g, "'").replace(/\[/g, "(").replace(/\]/g, ")").replace(/\n/g, " ");
10355
+ const validNodes = graph.nodes.filter(
10356
+ (n) => n.id && n.id.trim() !== "" && n.label && n.label.trim() !== ""
10357
+ );
10358
+ const validNodeIds = new Set(validNodes.map((n) => n.id));
10194
10359
  const lines = ["flowchart LR"];
10195
- for (const node of graph.nodes) {
10360
+ for (const node of validNodes) {
10196
10361
  lines.push(` ${safe(node.id)}["${escapeLabel(node.label)}"]`);
10197
10362
  }
10198
- if (graph.edges.length > 0) lines.push("");
10199
- for (const edge of graph.edges) {
10363
+ const validEdges = graph.edges.filter(
10364
+ (e) => validNodeIds.has(e.from) && validNodeIds.has(e.to)
10365
+ );
10366
+ if (validEdges.length > 0) lines.push("");
10367
+ for (const edge of validEdges) {
10200
10368
  lines.push(` ${safe(edge.from)} -->|${edge.type}| ${safe(edge.to)}`);
10201
10369
  }
10202
10370
  return lines.join("\n") + "\n";
@@ -11921,6 +12089,47 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
11921
12089
  if (!config) return stub("not-initialized", rec.label, rec.command);
11922
12090
  if (config.project.state === "new") return stub("not-applicable", "This project uses the standard new-project Kaddo flow.");
11923
12091
  if (config.project.state === "legacy") return stub("legacy-project", "Use the legacy project flow.");
12092
+ if (isModule(config)) {
12093
+ const scan3 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
12094
+ const understand2 = exists(join(dir, ".kaddo", "understand.md")) ? "available" : "missing";
12095
+ const mcNew = "knowledge/tech/module/module-context.md";
12096
+ const mcLegacy = "knowledge/module/module-context.md";
12097
+ const mcPath = exists(join(dir, mcNew)) ? mcNew : mcLegacy;
12098
+ const qMC = analyzeKnowledgeArtifact(dir, mcPath);
12099
+ const qCS = analyzeKnowledgeArtifact(dir, "knowledge/tech/current-state.md");
12100
+ const qCB = analyzeKnowledgeArtifact(dir, "knowledge/tech/codebase.md");
12101
+ const moduleBaseline = qMC !== "missing" && qCS !== "missing" && qCB !== "missing" ? "complete" : "incomplete";
12102
+ const moduleReady = qMC === "useful" && qCS === "useful" && qCB === "useful";
12103
+ const overall2 = moduleReady ? "ready-for-core-orchestration" : moduleBaseline === "incomplete" ? "bootstrap-incomplete" : "knowledge-incomplete";
12104
+ return {
12105
+ project_name: config.project.name,
12106
+ project_type: config.project.state,
12107
+ project_role: "module",
12108
+ overall: overall2,
12109
+ signals: {
12110
+ scan: scan3,
12111
+ understand: understand2,
12112
+ bootstrap_baseline: moduleBaseline,
12113
+ agents: "managed-by-core",
12114
+ skills: "managed-by-core",
12115
+ current_state: qCS,
12116
+ codebase: qCB,
12117
+ module_context: qMC,
12118
+ capabilities: "managed-by-core",
12119
+ product: "not-applicable",
12120
+ business: "not-applicable",
12121
+ roadmap: "managed-by-core",
12122
+ work_items: "managed-by-core",
12123
+ adapters: [],
12124
+ blocking_open_questions: 0,
12125
+ assumed_questions: 0,
12126
+ resolved_questions: 0,
12127
+ deferred_questions: 0
12128
+ },
12129
+ recommended_next_step: { label: rec.label, ...rec.command ? { command: rec.command } : {} },
12130
+ nextStepRecommendation: rec
12131
+ };
12132
+ }
11924
12133
  const scan2 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
11925
12134
  const understand = exists(join(dir, ".kaddo", "understand.md")) ? "available" : "missing";
11926
12135
  const presence = Object.fromEntries(KNOWLEDGE_FILES2.map((f) => [f.key, analyzeKnowledgeArtifact(dir, f.path)]));
@@ -12891,51 +13100,73 @@ function buildProjectExplanation(dir) {
12891
13100
  const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
12892
13101
  const roadmap = roadmapStats(roadmapMd, items.length);
12893
13102
  const mappedModules = loadMappedModules(dir);
13103
+ const moduleRepo = config != null && isModule(config);
12894
13104
  const missingKnowledge = [];
12895
- if (!knowledge.hasScan) missingKnowledge.push("Scan baseline (.kaddo/scan.json)");
12896
- if (!knowledge.hasContextPack) missingKnowledge.push("Context pack (.kaddo/context-pack.md)");
12897
- if (!knowledge.hasInventory) missingKnowledge.push("Inventory (knowledge/inventory.md)");
12898
- if (!knowledge.hasCapabilities) missingKnowledge.push("Product knowledge (knowledge/product/)");
12899
- if (!knowledge.hasArchitecture) missingKnowledge.push("Tech knowledge (knowledge/tech/)");
12900
- if (!knowledge.hasRoadmap) missingKnowledge.push("Roadmap (knowledge/delivery/roadmap.md)");
12901
- if (!knowledge.hasAgents) missingKnowledge.push("Agents (knowledge/agents/)");
12902
- if (items.length === 0) missingKnowledge.push("Work items (knowledge/delivery/work-items/)");
12903
- const suggestedNextSteps = [];
12904
- const baselineIncomplete = !knowledge.hasBusiness || !knowledge.hasProduct;
12905
- if (baselineIncomplete) {
12906
- suggestedNextSteps.push("Run `kaddo bootstrap` to create the project knowledge baseline.");
12907
- if (!knowledge.hasAgents) suggestedNextSteps.push("Then run `kaddo add agents`.");
12908
- if (!knowledge.hasSkills) suggestedNextSteps.push("Then run `kaddo add skills`.");
12909
- suggestedNextSteps.push("Then run `kaddo context`.");
12910
- suggestedNextSteps.push("Then run `kaddo understand`.");
13105
+ if (moduleRepo) {
13106
+ if (!knowledge.hasScan) missingKnowledge.push("Scan baseline (.kaddo/scan.json)");
13107
+ if (!knowledge.hasContextPack) missingKnowledge.push("Context pack (.kaddo/context-pack.md)");
13108
+ if (!knowledge.hasArchitecture) missingKnowledge.push("Tech knowledge (knowledge/tech/)");
12911
13109
  } else {
13110
+ if (!knowledge.hasScan) missingKnowledge.push("Scan baseline (.kaddo/scan.json)");
13111
+ if (!knowledge.hasContextPack) missingKnowledge.push("Context pack (.kaddo/context-pack.md)");
13112
+ if (!knowledge.hasInventory) missingKnowledge.push("Inventory (knowledge/inventory.md)");
13113
+ if (!knowledge.hasCapabilities) missingKnowledge.push("Product knowledge (knowledge/product/)");
13114
+ if (!knowledge.hasArchitecture) missingKnowledge.push("Tech knowledge (knowledge/tech/)");
13115
+ if (!knowledge.hasRoadmap) missingKnowledge.push("Roadmap (knowledge/delivery/roadmap.md)");
13116
+ if (!knowledge.hasAgents) missingKnowledge.push("Agents (knowledge/agents/)");
13117
+ if (items.length === 0) missingKnowledge.push("Work items (knowledge/delivery/work-items/)");
13118
+ }
13119
+ const suggestedNextSteps = [];
13120
+ if (moduleRepo) {
12912
13121
  if (!knowledge.hasScan) {
12913
13122
  suggestedNextSteps.push("Run `kaddo scan` to detect the technical stack.");
12914
13123
  } else if (!knowledge.hasContextPack) {
12915
13124
  suggestedNextSteps.push("Run `kaddo context` to prepare an LLM context pack.");
13125
+ } else if (!knowledge.hasArchitecture) {
13126
+ suggestedNextSteps.push("Use module-context-agent to refine knowledge/tech/module/module-context.md.");
13127
+ } else {
13128
+ suggestedNextSteps.push("Return to the core repository.");
13129
+ suggestedNextSteps.push("Create or continue the Work Item from the core.");
13130
+ suggestedNextSteps.push("Add this module to `affected_modules` when the change touches it.");
13131
+ suggestedNextSteps.push("Re-run `kaddo context` and `kaddo understand` from the core.");
12916
13132
  }
12917
- if (!knowledge.hasAgents) {
12918
- suggestedNextSteps.push("Run `kaddo add agents` to install knowledge agents.");
12919
- }
12920
- if (!knowledge.hasCapabilities) {
12921
- suggestedNextSteps.push("Use capability-agent to generate knowledge/product/capabilities.md.");
12922
- }
12923
- if (!knowledge.hasArchitecture) {
12924
- suggestedNextSteps.push("Use architecture-agent to generate knowledge/tech/current-state.md.");
12925
- }
12926
- if (!knowledge.hasRoadmap) {
12927
- suggestedNextSteps.push("Use roadmap-agent to generate knowledge/delivery/roadmap.md.");
12928
- } else if (roadmap.remaining > 0) {
12929
- suggestedNextSteps.push(
12930
- `Materialize ${roadmap.remaining} roadmap candidate(s) with \`kaddo create --from roadmap\`.`
12931
- );
12932
- }
12933
- if (items.length === 0 && !roadmap.present) {
12934
- suggestedNextSteps.push("Create your first Work Item with `kaddo create`.");
12935
- } else if (ownership.workItemsMissingOwnership > 0) {
12936
- suggestedNextSteps.push(
12937
- "Run `kaddo owners suggest` for Work Items without code ownership."
12938
- );
13133
+ } else {
13134
+ const baselineIncomplete = !knowledge.hasBusiness || !knowledge.hasProduct;
13135
+ if (baselineIncomplete) {
13136
+ suggestedNextSteps.push("Run `kaddo bootstrap` to create the project knowledge baseline.");
13137
+ if (!knowledge.hasAgents) suggestedNextSteps.push("Then run `kaddo add agents`.");
13138
+ if (!knowledge.hasSkills) suggestedNextSteps.push("Then run `kaddo add skills`.");
13139
+ suggestedNextSteps.push("Then run `kaddo context`.");
13140
+ suggestedNextSteps.push("Then run `kaddo understand`.");
13141
+ } else {
13142
+ if (!knowledge.hasScan) {
13143
+ suggestedNextSteps.push("Run `kaddo scan` to detect the technical stack.");
13144
+ } else if (!knowledge.hasContextPack) {
13145
+ suggestedNextSteps.push("Run `kaddo context` to prepare an LLM context pack.");
13146
+ }
13147
+ if (!knowledge.hasAgents) {
13148
+ suggestedNextSteps.push("Run `kaddo add agents` to install knowledge agents.");
13149
+ }
13150
+ if (!knowledge.hasCapabilities) {
13151
+ suggestedNextSteps.push("Use capability-agent to generate knowledge/product/capabilities.md.");
13152
+ }
13153
+ if (!knowledge.hasArchitecture) {
13154
+ suggestedNextSteps.push("Use architecture-agent to generate knowledge/tech/current-state.md.");
13155
+ }
13156
+ if (!knowledge.hasRoadmap) {
13157
+ suggestedNextSteps.push("Use roadmap-agent to generate knowledge/delivery/roadmap.md.");
13158
+ } else if (roadmap.remaining > 0) {
13159
+ suggestedNextSteps.push(
13160
+ `Materialize ${roadmap.remaining} roadmap candidate(s) with \`kaddo create --from roadmap\`.`
13161
+ );
13162
+ }
13163
+ if (items.length === 0 && !roadmap.present) {
13164
+ suggestedNextSteps.push("Create your first Work Item with `kaddo create`.");
13165
+ } else if (ownership.workItemsMissingOwnership > 0) {
13166
+ suggestedNextSteps.push(
13167
+ "Run `kaddo owners suggest` for Work Items without code ownership."
13168
+ );
13169
+ }
12939
13170
  }
12940
13171
  }
12941
13172
  const readiness = buildReadinessReport(dir);
@@ -12978,7 +13209,30 @@ function buildProjectExplanation(dir) {
12978
13209
  projectRoute: buildProjectRoute(dir),
12979
13210
  scanSignals: loadScanSignals(dir),
12980
13211
  metadataHealth: analyzeMetadataHealth(dir),
12981
- isModuleRepo: config != null && isModule(config)
13212
+ isModuleRepo: config != null && isModule(config),
13213
+ implementationEvidence: workItemArtifacts.filter((a) => a.affectedModules.length > 0).map((a) => {
13214
+ const fm2 = a.rawFrontmatter;
13215
+ const evidence = fm2.implementation_evidence;
13216
+ const repos = evidence?.repositories ?? {};
13217
+ const repoEvidence = {};
13218
+ for (const [repoId, data] of Object.entries(repos)) {
13219
+ repoEvidence[repoId] = {
13220
+ role: String(data.role ?? (repoId === "core" ? "core" : "module")),
13221
+ status: String(data.status ?? "unknown")
13222
+ };
13223
+ }
13224
+ return {
13225
+ id: a.id || a.title,
13226
+ lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath }),
13227
+ implementationStatus: a.implementationStatus || "not-started",
13228
+ validationStatus: a.validationStatus || "not-started",
13229
+ releaseStatus: a.releaseStatus || "not-assessed",
13230
+ affectedModules: a.affectedModules,
13231
+ releaseGates: Array.isArray(fm2.release_gates) ? fm2.release_gates : [],
13232
+ completionExceptions: Array.isArray(fm2.completion_exceptions) ? fm2.completion_exceptions : [],
13233
+ repoEvidence
13234
+ };
13235
+ })
12982
13236
  };
12983
13237
  }
12984
13238
  function stateLabel(state) {
@@ -13031,11 +13285,19 @@ function renderExplanationHuman(exp) {
13031
13285
  lines.push("## Knowledge Status");
13032
13286
  lines.push(`- Inventory: ${exp.knowledge.hasInventory ? "available" : "missing"}`);
13033
13287
  lines.push(`- Context pack: ${exp.knowledge.hasContextPack ? "available" : "missing"}`);
13034
- lines.push(`- Business: ${ls("Business")}`);
13035
- lines.push(`- Product: ${ls("Product")}`);
13036
- lines.push(`- Tech: ${ls("Tech")}`);
13037
- lines.push(`- Delivery: ${ls("Delivery")}`);
13038
- lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
13288
+ if (exp.isModuleRepo) {
13289
+ lines.push("- Business: Not applicable");
13290
+ lines.push("- Product: Not applicable");
13291
+ lines.push(`- Tech: ${ls("Tech")}`);
13292
+ lines.push("- Delivery: Managed by core");
13293
+ lines.push("- Agents: Managed by core");
13294
+ } else {
13295
+ lines.push(`- Business: ${ls("Business")}`);
13296
+ lines.push(`- Product: ${ls("Product")}`);
13297
+ lines.push(`- Tech: ${ls("Tech")}`);
13298
+ lines.push(`- Delivery: ${ls("Delivery")}`);
13299
+ lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
13300
+ }
13039
13301
  if (exp.roadmap.present) {
13040
13302
  lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
13041
13303
  lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
@@ -13096,6 +13358,36 @@ function renderExplanationHuman(exp) {
13096
13358
  }
13097
13359
  lines.push("");
13098
13360
  }
13361
+ if (exp.implementationEvidence.length > 0) {
13362
+ lines.push("## Implementation Evidence");
13363
+ for (const ev of exp.implementationEvidence) {
13364
+ lines.push("");
13365
+ lines.push(`### ${ev.id}`);
13366
+ lines.push(`- Lifecycle: ${ev.lifecycle.charAt(0).toUpperCase() + ev.lifecycle.slice(1)}`);
13367
+ lines.push(`- Implementation: ${ev.implementationStatus}`);
13368
+ lines.push(`- Validation: ${ev.validationStatus}`);
13369
+ lines.push(`- Release: ${ev.releaseStatus}`);
13370
+ if (Object.keys(ev.repoEvidence).length > 0) {
13371
+ lines.push("Repositories:");
13372
+ for (const [repoId, info] of Object.entries(ev.repoEvidence)) {
13373
+ lines.push(`- ${repoId} \u2014 ${info.status}`);
13374
+ }
13375
+ } else if (ev.affectedModules.length > 0) {
13376
+ lines.push(`Affected modules: ${ev.affectedModules.join(", ")}`);
13377
+ }
13378
+ const pendingGates = ev.releaseGates.filter((g) => g.status === "blocked" || g.status === "pending");
13379
+ if (pendingGates.length > 0) {
13380
+ lines.push("Pending release gates:");
13381
+ for (const g of pendingGates) lines.push(`- ${g.id}${g.reason ? ` \u2014 ${g.reason}` : ""}`);
13382
+ }
13383
+ const acceptedExceptions = ev.completionExceptions.filter((e) => e.status === "accepted" || e.status === "deferred");
13384
+ if (acceptedExceptions.length > 0) {
13385
+ lines.push("Completion exceptions:");
13386
+ for (const e of acceptedExceptions) lines.push(`- ${e.id}: ${e.status}${e.reason ? ` \u2014 ${e.reason}` : ""}`);
13387
+ }
13388
+ }
13389
+ lines.push("");
13390
+ }
13099
13391
  if (exp.domains.length > 0) {
13100
13392
  lines.push("## Domains");
13101
13393
  lines.push(`- ${exp.domains.join(", ")}`);
@@ -13196,14 +13488,27 @@ function renderExplanationHuman(exp) {
13196
13488
  lines.push(`- understand: ${s.understand}`);
13197
13489
  lines.push(`- agents: ${s.agents}`);
13198
13490
  lines.push(`- skills: ${s.skills}`);
13199
- lines.push(`- business: ${s.business}`);
13200
- lines.push(`- product: ${s.product}`);
13491
+ if (exp.isModuleRepo) {
13492
+ lines.push("- business: not-applicable");
13493
+ lines.push("- product: not-applicable");
13494
+ } else {
13495
+ lines.push(`- business: ${s.business}`);
13496
+ lines.push(`- product: ${s.product}`);
13497
+ }
13201
13498
  lines.push(`- capabilities: ${s.capabilities}`);
13202
13499
  lines.push(`- current-state: ${s.current_state}`);
13203
13500
  lines.push(`- codebase: ${s.codebase}`);
13204
- lines.push(`- roadmap: ${s.roadmap}`);
13205
- lines.push(`- work-items: ${s.work_items}`);
13206
- lines.push(`- adapters: ${s.adapters.length > 0 ? s.adapters.join(", ") + " installed" : "none installed"}`);
13501
+ if (s.module_context != null) {
13502
+ lines.push(`- module-context: ${s.module_context}`);
13503
+ }
13504
+ if (!exp.isModuleRepo) {
13505
+ lines.push(`- roadmap: ${s.roadmap}`);
13506
+ lines.push(`- work-items: ${s.work_items}`);
13507
+ lines.push(`- adapters: ${s.adapters.length > 0 ? s.adapters.join(", ") + " installed" : "none installed"}`);
13508
+ } else {
13509
+ lines.push("- roadmap: managed-by-core");
13510
+ lines.push("- work-items: managed-by-core");
13511
+ }
13207
13512
  lines.push(`- blocking open questions: ${s.blocking_open_questions}`);
13208
13513
  lines.push(`- assumptions: ${s.assumed_questions}`);
13209
13514
  lines.push(`- deferred: ${s.deferred_questions}`);
@@ -13540,7 +13845,7 @@ var OPERATING_RULES = [
13540
13845
  "Kaddo itself never calls an LLM and never runs git \u2014 every git action is the human\u2019s."
13541
13846
  ];
13542
13847
  function toContextWorkItem(a) {
13543
- return {
13848
+ const wi = {
13544
13849
  id: a.id,
13545
13850
  type: a.type,
13546
13851
  title: a.title,
@@ -13550,6 +13855,11 @@ function toContextWorkItem(a) {
13550
13855
  domains: a.domains,
13551
13856
  source: parseWorkItemSource(a.rawFrontmatter)
13552
13857
  };
13858
+ if (a.implementationStatus) wi.implementationStatus = a.implementationStatus;
13859
+ if (a.validationStatus) wi.validationStatus = a.validationStatus;
13860
+ if (a.releaseStatus) wi.releaseStatus = a.releaseStatus;
13861
+ if (a.affectedModules.length > 0) wi.affectedModules = a.affectedModules;
13862
+ return wi;
13553
13863
  }
13554
13864
  function toContextArtifact(a) {
13555
13865
  return { id: a.id, type: a.type, title: a.title, summary: a.summary, codeGlobs: a.codeGlobs };
@@ -13568,11 +13878,11 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
13568
13878
  if (!inventoryAvailable) {
13569
13879
  missing.push("No technical inventory found. Run `kaddo scan` to generate it.");
13570
13880
  }
13881
+ const moduleRepo = config != null && isModule(config);
13571
13882
  const knowledgeSummary = readMarkdownSummary(dir, "knowledge.md") ?? "";
13572
- if (!knowledgeSummary) {
13883
+ if (!knowledgeSummary && !moduleRepo) {
13573
13884
  missing.push("No project knowledge summary found yet.");
13574
13885
  }
13575
- const moduleRepo = config != null && isModule(config);
13576
13886
  const roadmapSummary = readMarkdownSummary(dir, "delivery/roadmap.md") ?? "";
13577
13887
  if (!roadmapSummary && !moduleRepo) {
13578
13888
  missing.push("No roadmap baseline found.");
@@ -13746,11 +14056,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
13746
14056
  }
13747
14057
  return contexts;
13748
14058
  })(),
14059
+ isModuleRepo: moduleRepo,
13749
14060
  missing,
13750
14061
  // VS-052/VS-073.2: the handoff is driven by the unified next step, so the pack never contradicts
13751
14062
  // the Current Phase block above it.
13752
14063
  handoff: {
13753
- recommendedAgents: isBootstrap ? [] : unifiedPhase.recommendedAgents.length > 0 ? unifiedPhase.recommendedAgents : recommendedAgentsForState(state),
14064
+ recommendedAgents: isBootstrap ? [] : unifiedPhase.recommendedAgents.length > 0 ? unifiedPhase.recommendedAgents : moduleRepo ? [] : recommendedAgentsForState(state),
13754
14065
  nextSteps: [nextStepRecommendation.label],
13755
14066
  instructions: isBootstrap ? ["No agent handoff yet.", "Run `kaddo bootstrap` first to create the baseline files."] : unifiedPhase.llmInstructions.length > 0 ? unifiedPhase.llmInstructions : LLM_INSTRUCTIONS,
13756
14067
  operatingRules: OPERATING_RULES
@@ -14061,7 +14372,11 @@ function renderContextPack(pack) {
14061
14372
  }
14062
14373
  }
14063
14374
  parts.push("## Recommended Agent Handoff\n");
14064
- if (isBootstrapIncomplete) {
14375
+ if (pack.isModuleRepo && handoff.recommendedAgents.length === 0) {
14376
+ parts.push("No local agent action is required.\n");
14377
+ parts.push("This module is ready for core orchestration.\n");
14378
+ parts.push("Business, Product, Delivery, Agents and Skills are managed by the core repository.\n");
14379
+ } else if (isBootstrapIncomplete) {
14065
14380
  parts.push("No agent handoff yet.\n");
14066
14381
  parts.push("Run `kaddo bootstrap` first to create the baseline files.\n");
14067
14382
  } else if (rec.agent && rec.agentInstalled === false) {
@@ -15053,7 +15368,7 @@ function findWorkItemFile(dir, id) {
15053
15368
  function updateWorkItemFile(filePath, learning) {
15054
15369
  const raw = readFile(filePath);
15055
15370
  const { data, content } = matter8(raw);
15056
- data.status = "done";
15371
+ data.status = "completed";
15057
15372
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
15058
15373
  let updatedContent = content;
15059
15374
  if (content.includes("_What did we learn from this change? Update after completion._")) {
@@ -15088,24 +15403,24 @@ async function runLearn(artifactId) {
15088
15403
  }
15089
15404
  intro2("kaddo learn");
15090
15405
  const artifacts = readArtifacts(join(dir, ARCH_DIR9));
15091
- const inProgress = artifacts.filter(
15092
- (a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap"
15406
+ const closable = artifacts.filter(
15407
+ (a) => (a.status === "in-progress" || a.status === "completed" || a.status === "done") && a.type !== "current-state" && a.type !== "roadmap"
15093
15408
  );
15094
- if (inProgress.length === 0) {
15095
- log2.warn("No in-progress work items found.");
15409
+ if (closable.length === 0) {
15410
+ log2.warn("No in-progress or completed work items found.");
15096
15411
  outro2("Nothing to close.");
15097
15412
  return;
15098
15413
  }
15099
15414
  let targetId;
15100
15415
  if (artifactId) {
15101
15416
  targetId = artifactId;
15102
- } else if (inProgress.length === 1) {
15103
- targetId = inProgress[0].id || inProgress[0].title;
15104
- log2.info(`Closing: ${targetId} \u2014 ${inProgress[0].summary || inProgress[0].title}`);
15417
+ } else if (closable.length === 1) {
15418
+ targetId = closable[0].id || closable[0].title;
15419
+ log2.info(`Closing: ${targetId} \u2014 ${closable[0].summary || closable[0].title}`);
15105
15420
  } else {
15106
15421
  const chosen = await select2({
15107
15422
  message: "Which work item are you closing?",
15108
- options: inProgress.map((a) => ({
15423
+ options: closable.map((a) => ({
15109
15424
  value: a.id || a.title,
15110
15425
  label: `${a.id || a.title} \u2014 ${a.summary || a.title}`
15111
15426
  }))
@@ -15122,9 +15437,26 @@ async function runLearn(artifactId) {
15122
15437
  placeholder: "e.g. The retry logic needed a separate queue to avoid blocking the main flow",
15123
15438
  validate: (v) => v.trim().length === 0 ? "Learning is required." : void 0
15124
15439
  });
15125
- updateWorkItemFile(filePath, learning.trim());
15126
- log2.success(`${targetId} marked as done`);
15440
+ const wiRaw = readFile(filePath);
15441
+ const wiData = matter8(wiRaw).data;
15442
+ const hasExceptions = wiData.validation_status === "accepted-with-exceptions" || Array.isArray(wiData.completion_exceptions) && wiData.completion_exceptions.length > 0;
15443
+ const releaseBlocked = wiData.release_status === "blocked";
15444
+ let enrichedLearning = learning.trim();
15445
+ if (hasExceptions || releaseBlocked) {
15446
+ const notes = [];
15447
+ if (hasExceptions) notes.push("Validation exceptions were accepted for this Work Item.");
15448
+ if (releaseBlocked) {
15449
+ const gates = Array.isArray(wiData.release_gates) ? wiData.release_gates.filter((g) => g.status === "blocked" || g.status === "pending").map((g) => g.id) : [];
15450
+ notes.push(gates.length > 0 ? `Release gates remain: ${gates.join(", ")}.` : "Production release remains blocked.");
15451
+ }
15452
+ enrichedLearning += "\n\n> " + notes.join(" ");
15453
+ }
15454
+ updateWorkItemFile(filePath, enrichedLearning);
15455
+ log2.success(`${targetId} marked as completed`);
15127
15456
  log2.success(`Learning recorded in ${filePath.replace(dir + "/", "")}`);
15457
+ if (hasExceptions) {
15458
+ log2.warn("Learning captured from a Work Item completed with validation exceptions.");
15459
+ }
15128
15460
  log2.info("Consider updating knowledge/knowledge.md if this changes the current state.");
15129
15461
  outro2("Work item closed.");
15130
15462
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.64.0",
3
+ "version": "3.66.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {