@kaddo/cli 3.9.0 → 3.10.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 +319 -218
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4044,9 +4044,11 @@ function parseArtifact(filePath, raw) {
4044
4044
  knowledgeLevel: String(data.knowledge_level ?? ""),
4045
4045
  codeGlobs: globs,
4046
4046
  domains: Array.isArray(data.domains) ? data.domains : [],
4047
+ capabilities: Array.isArray(data.capabilities) ? data.capabilities.map(String).filter(Boolean) : [],
4047
4048
  status: String(data.status ?? ""),
4048
4049
  phase: String(data.phase ?? ""),
4049
- initiative: String(data.initiative ?? data.source_initiative ?? "")
4050
+ initiative: String(data.initiative ?? data.source_initiative ?? ""),
4051
+ source: data.source ? String(data.source) : ""
4050
4052
  };
4051
4053
  } catch {
4052
4054
  return null;
@@ -4071,6 +4073,94 @@ function readArtifacts(archDir) {
4071
4073
  return artifacts;
4072
4074
  }
4073
4075
 
4076
+ // src/core/lifecycle.ts
4077
+ var LIFECYCLE_STATES = [
4078
+ "draft",
4079
+ "ready",
4080
+ "in-progress",
4081
+ "blocked",
4082
+ "completed",
4083
+ "archived"
4084
+ ];
4085
+ var ACTIVE_STATES = ["draft", "ready", "in-progress", "blocked"];
4086
+ var LEGACY_STATUS_MAP = {
4087
+ done: "completed",
4088
+ cancelled: "archived",
4089
+ canceled: "archived",
4090
+ "in-progress": "in-progress"
4091
+ };
4092
+ var STATE_SET = new Set(LIFECYCLE_STATES);
4093
+ function isLifecycleState(s) {
4094
+ return STATE_SET.has(s);
4095
+ }
4096
+ function isActiveState(s) {
4097
+ return ACTIVE_STATES.includes(s);
4098
+ }
4099
+ function lifecycleFolderOf(filePath) {
4100
+ const p2 = filePath.replace(/\\/g, "/");
4101
+ const m = p2.match(/\/delivery\/work-items\/([^/]+)\//);
4102
+ if (m && isLifecycleState(m[1])) return m[1];
4103
+ return null;
4104
+ }
4105
+ function lifecycleStateOf(item) {
4106
+ const status = (item.status ?? "").trim().toLowerCase();
4107
+ if (isLifecycleState(status)) return status;
4108
+ if (status && LEGACY_STATUS_MAP[status]) return LEGACY_STATUS_MAP[status];
4109
+ const folder = lifecycleFolderOf(item.filePath ?? "");
4110
+ if (folder) return folder;
4111
+ return "ready";
4112
+ }
4113
+ function emptyLifecycleCounts() {
4114
+ return Object.fromEntries(LIFECYCLE_STATES.map((s) => [s, 0]));
4115
+ }
4116
+ function lifecycleCounts(states) {
4117
+ const counts = emptyLifecycleCounts();
4118
+ for (const s of states) counts[s]++;
4119
+ return counts;
4120
+ }
4121
+
4122
+ // src/services/knowledge-artifacts.ts
4123
+ var ARCH_DIR2 = "knowledge";
4124
+ function toPosix(p2) {
4125
+ return p2.replace(/\\/g, "/");
4126
+ }
4127
+ function relativeTo(dir, filePath) {
4128
+ const base = toPosix(dir).replace(/\/+$/, "");
4129
+ const full = toPosix(filePath);
4130
+ return full.startsWith(base + "/") ? full.slice(base.length + 1) : full;
4131
+ }
4132
+ function isWorkItemArtifact(a) {
4133
+ return toPosix(a.filePath).includes("/delivery/work-items/") && Boolean(a.type);
4134
+ }
4135
+ function classifyLayer(posixPath) {
4136
+ if (posixPath.includes("/knowledge/business/")) return "business";
4137
+ if (posixPath.includes("/knowledge/product/")) return "product";
4138
+ if (posixPath.includes("/knowledge/tech/modules/") || posixPath.includes("/knowledge/modules/"))
4139
+ return "module";
4140
+ if (posixPath.includes("/knowledge/tech/")) return "tech";
4141
+ if (posixPath.includes("/knowledge/delivery/")) return "delivery";
4142
+ return "unknown";
4143
+ }
4144
+ function enrich(dir, a) {
4145
+ const posix = toPosix(a.filePath);
4146
+ const isWi = isWorkItemArtifact(a);
4147
+ return {
4148
+ ...a,
4149
+ relPath: relativeTo(dir, a.filePath),
4150
+ layer: classifyLayer(posix),
4151
+ isWorkItem: isWi,
4152
+ lifecycle: isWi ? lifecycleStateOf({ status: a.status, filePath: a.filePath }) : void 0
4153
+ };
4154
+ }
4155
+ function discoverKnowledge(dir) {
4156
+ const archDir = join(dir, ARCH_DIR2);
4157
+ if (!exists(archDir)) return [];
4158
+ return readArtifacts(archDir).map((a) => enrich(dir, a));
4159
+ }
4160
+ function discoverWorkItems(dir) {
4161
+ return discoverKnowledge(dir).filter((a) => a.isWorkItem);
4162
+ }
4163
+
4074
4164
  // src/core/diff-analysis.ts
4075
4165
  import { minimatch } from "minimatch";
4076
4166
  function normalizePath(p2) {
@@ -4463,7 +4553,7 @@ function collectMatchedDomains(matchedArtifactDomains) {
4463
4553
  }
4464
4554
 
4465
4555
  // src/commands/guard.ts
4466
- var ARCH_DIR2 = "knowledge";
4556
+ var ARCH_DIR3 = "knowledge";
4467
4557
  var CONFIG_PATH2 = ".kaddo/config.yml";
4468
4558
  function loadConfig2(dir) {
4469
4559
  const configPath = join(dir, CONFIG_PATH2);
@@ -4607,12 +4697,12 @@ async function runGuard(opts = {}) {
4607
4697
  console.log("kaddo guard: no modified files detected.");
4608
4698
  return;
4609
4699
  }
4610
- const archDir = join(dir, ARCH_DIR2);
4700
+ const archDir = join(dir, ARCH_DIR3);
4611
4701
  if (!exists(archDir)) {
4612
4702
  console.log("kaddo guard: no knowledge/ directory found. Run `kaddo init` first.");
4613
4703
  return;
4614
4704
  }
4615
- const artifacts = readArtifacts(archDir);
4705
+ const artifacts = discoverKnowledge(dir);
4616
4706
  const result = analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership);
4617
4707
  const pluginSignals = runPlugins(plugins, currentFiles, (filePath) => {
4618
4708
  const abs = join(dir, filePath);
@@ -4724,6 +4814,110 @@ function runIgnoreRemove(artifactId) {
4724
4814
  import matter2 from "gray-matter";
4725
4815
  import { parse as parseYaml8 } from "yaml";
4726
4816
 
4817
+ // src/core/delivery-phase.ts
4818
+ function layer(layers, name) {
4819
+ return layers.find((l) => l.layer === name)?.status ?? "Missing";
4820
+ }
4821
+ function baseComplete(layers) {
4822
+ return layer(layers, "Business") !== "Missing" && layer(layers, "Product") !== "Missing" && layer(layers, "Tech") !== "Missing";
4823
+ }
4824
+ function determinePhase(input) {
4825
+ const { roadmap, workItems } = input;
4826
+ const active = workItems.byState.draft + workItems.byState.ready + workItems.byState["in-progress"] + workItems.byState.blocked;
4827
+ if (!baseComplete(input.layers)) return "Discovery";
4828
+ if (!roadmap.present) return "Planning";
4829
+ if (workItems.total === 0) return "Delivery Preparation";
4830
+ if (active > 0) return "Active Delivery";
4831
+ return "Maintenance";
4832
+ }
4833
+ function ownershipPct(o) {
4834
+ if (o.workItemsTotal === 0) return 100;
4835
+ return Math.round(o.workItemsWithOwnership / o.workItemsTotal * 100);
4836
+ }
4837
+ function buildReasons(input) {
4838
+ const reasons = [];
4839
+ reasons.push(input.roadmap.present ? "Roadmap available" : "No roadmap yet");
4840
+ if (input.roadmap.present) {
4841
+ reasons.push(`${input.roadmap.materialized} materialized work item(s)`);
4842
+ if (input.roadmap.remaining > 0) reasons.push(`${input.roadmap.remaining} roadmap candidate(s) remaining`);
4843
+ }
4844
+ const active = LIFECYCLE_STATES.filter((s) => input.workItems.byState[s] > 0 && s !== "completed" && s !== "archived");
4845
+ if (active.length > 0) {
4846
+ reasons.push(active.map((s) => `${s}: ${input.workItems.byState[s]}`).join(", "));
4847
+ }
4848
+ if (input.workItems.total > 0) reasons.push(`Ownership coverage ${ownershipPct(input.ownership)}%`);
4849
+ return reasons;
4850
+ }
4851
+ function firstMissingLayerAgent(layers) {
4852
+ if (layer(layers, "Business") === "Missing")
4853
+ return { agent: "business-agent", step: "Use business-agent to create knowledge/business/business.md" };
4854
+ if (layer(layers, "Product") === "Missing")
4855
+ return { agent: "capability-agent", step: "Use capability-agent to create knowledge/product/capabilities.md" };
4856
+ return { agent: "architecture-agent", step: "Use architecture-agent to create knowledge/tech/current-state.md" };
4857
+ }
4858
+ function assessPhase(input) {
4859
+ const phase = determinePhase(input);
4860
+ const reasons = buildReasons(input);
4861
+ const bs = input.workItems.byState;
4862
+ const items = input.workItems.items;
4863
+ const firstOf = (s) => items.find((i) => i.lifecycle === s);
4864
+ let recommendedAgents2 = [];
4865
+ let nextStep = "";
4866
+ switch (phase) {
4867
+ case "Discovery": {
4868
+ const m = firstMissingLayerAgent(input.layers);
4869
+ recommendedAgents2 = [m.agent];
4870
+ nextStep = m.step;
4871
+ break;
4872
+ }
4873
+ case "Planning": {
4874
+ recommendedAgents2 = ["roadmap-agent"];
4875
+ nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
4876
+ break;
4877
+ }
4878
+ case "Delivery Preparation": {
4879
+ recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
4880
+ const n = input.roadmap.remaining || input.roadmap.candidates;
4881
+ nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
4882
+ break;
4883
+ }
4884
+ case "Active Delivery": {
4885
+ if (bs.ready > 0) {
4886
+ const wi = firstOf("ready");
4887
+ recommendedAgents2 = ["implementation-agent"];
4888
+ nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
4889
+ } else if (bs["in-progress"] > 0) {
4890
+ const wi = firstOf("in-progress");
4891
+ recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
4892
+ nextStep = wi ? `Continue ${wi.id} \u2014 ${wi.title}; then run kaddo scan, owners suggest, guard` : "Continue the in-progress Work Item; then scan, owners suggest, guard";
4893
+ } else if (bs.draft > 0) {
4894
+ const wi = firstOf("draft");
4895
+ recommendedAgents2 = ["work-item-agent"];
4896
+ nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
4897
+ } else if (bs.blocked > 0) {
4898
+ const wi = firstOf("blocked");
4899
+ recommendedAgents2 = ["work-item-agent"];
4900
+ nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
4901
+ }
4902
+ if (input.ownership.workItemsMissingOwnership > 0) {
4903
+ recommendedAgents2.push("kaddo owners suggest");
4904
+ }
4905
+ break;
4906
+ }
4907
+ case "Maintenance": {
4908
+ if (input.roadmap.remaining > 0) {
4909
+ recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
4910
+ nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
4911
+ } else {
4912
+ recommendedAgents2 = ["roadmap-agent"];
4913
+ nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
4914
+ }
4915
+ break;
4916
+ }
4917
+ }
4918
+ return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
4919
+ }
4920
+
4727
4921
  // src/core/knowledge-discovery.ts
4728
4922
  var KNOWLEDGE = "knowledge";
4729
4923
  var CONSOLIDATED_TYPE = {
@@ -4787,29 +4981,29 @@ function discoverLayers(dir) {
4787
4981
  const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
4788
4982
  for (const a of artifacts) {
4789
4983
  const type = a.type;
4790
- const layer = layerForType(type) ?? layerFromPath(a.filePath);
4791
- if (!layer) continue;
4792
- const slot = acc[layer];
4984
+ const layer2 = layerForType(type) ?? layerFromPath(a.filePath);
4985
+ if (!layer2) continue;
4986
+ const slot = acc[layer2];
4793
4987
  slot.detected.add(basename(a.filePath));
4794
- if (layer === "Delivery") {
4988
+ if (layer2 === "Delivery") {
4795
4989
  if (type === "roadmap" || basename(a.filePath) === "roadmap.md") slot.hasRoadmap = true;
4796
4990
  if (WORK_ITEM_TYPES2.has(type) || a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) {
4797
4991
  if (a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) slot.hasWorkItem = true;
4798
4992
  }
4799
4993
  continue;
4800
4994
  }
4801
- if (type === CONSOLIDATED_TYPE[layer]) slot.consolidated = true;
4802
- else if (STRUCTURED_TYPES[layer].has(type)) slot.structured = true;
4995
+ if (type === CONSOLIDATED_TYPE[layer2]) slot.consolidated = true;
4996
+ else if (STRUCTURED_TYPES[layer2].has(type)) slot.structured = true;
4803
4997
  else if (!type) {
4804
4998
  slot.consolidated = true;
4805
4999
  }
4806
5000
  if (type === "adr" || type === "decision") slot.hasDecision = true;
4807
5001
  }
4808
5002
  if (existsDirWithMd(join(dir, KNOWLEDGE, "tech", "decisions"))) acc.Tech.structured = true;
4809
- return ["Business", "Product", "Tech", "Delivery"].map((layer) => ({
4810
- layer,
4811
- status: statusFor(layer, acc[layer]),
4812
- detected: [...acc[layer].detected].sort()
5003
+ return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
5004
+ layer: layer2,
5005
+ status: statusFor(layer2, acc[layer2]),
5006
+ detected: [...acc[layer2].detected].sort()
4813
5007
  }));
4814
5008
  }
4815
5009
  function blank() {
@@ -4830,8 +5024,8 @@ function existsDirWithMd(d) {
4830
5024
  return false;
4831
5025
  }
4832
5026
  }
4833
- function statusFor(layer, a) {
4834
- if (layer === "Delivery") {
5027
+ function statusFor(layer2, a) {
5028
+ if (layer2 === "Delivery") {
4835
5029
  if (a.hasWorkItem) return "Traceable";
4836
5030
  if (a.hasRoadmap) return "Partial";
4837
5031
  return "Missing";
@@ -4840,40 +5034,15 @@ function statusFor(layer, a) {
4840
5034
  if (a.consolidated) return "Consolidated";
4841
5035
  return "Missing";
4842
5036
  }
4843
- function roadmapHasUnmaterializedCandidates(dir) {
4844
- const roadmap = join(dir, KNOWLEDGE, "delivery", "roadmap.md");
4845
- if (!exists(roadmap)) return false;
4846
- let hasCandidates = false;
4847
- try {
4848
- hasCandidates = /WI-[A-Z0-9-]*\d/i.test(readFile(roadmap));
4849
- } catch {
4850
- return false;
4851
- }
4852
- if (!hasCandidates) return false;
4853
- const wiDir = join(dir, KNOWLEDGE, "delivery", "work-items");
4854
- return !existsDirWithMd(wiDir);
4855
- }
4856
5037
 
4857
5038
  // src/core/layers.ts
4858
5039
  function knowledgeLayers(dir) {
4859
5040
  return discoverLayers(dir);
4860
5041
  }
4861
- var COMPLETE = {
4862
- Business: (s) => s !== "Missing",
4863
- Product: (s) => s !== "Missing",
4864
- Tech: (s) => s !== "Missing",
4865
- Delivery: (s) => s === "Traceable"
4866
- };
4867
- function currentPhase(layers) {
4868
- for (const l of layers) {
4869
- if (!COMPLETE[l.layer](l.status)) return l.layer;
4870
- }
4871
- return "Delivery";
4872
- }
4873
5042
  function renderLayersMarkdown(layers) {
4874
5043
  const lines = [];
4875
- for (const { layer, status, detected } of layers) {
4876
- lines.push(`### ${layer} \u2014 ${status}`);
5044
+ for (const { layer: layer2, status, detected } of layers) {
5045
+ lines.push(`### ${layer2} \u2014 ${status}`);
4877
5046
  if (detected.length > 0) for (const d of detected) lines.push(`- \u2713 ${d}`);
4878
5047
  else lines.push("- \u2717 none");
4879
5048
  lines.push("");
@@ -4881,54 +5050,8 @@ function renderLayersMarkdown(layers) {
4881
5050
  return lines.join("\n").trimEnd();
4882
5051
  }
4883
5052
 
4884
- // src/core/lifecycle.ts
4885
- var LIFECYCLE_STATES = [
4886
- "draft",
4887
- "ready",
4888
- "in-progress",
4889
- "blocked",
4890
- "completed",
4891
- "archived"
4892
- ];
4893
- var ACTIVE_STATES = ["draft", "ready", "in-progress", "blocked"];
4894
- var LEGACY_STATUS_MAP = {
4895
- done: "completed",
4896
- cancelled: "archived",
4897
- canceled: "archived",
4898
- "in-progress": "in-progress"
4899
- };
4900
- var STATE_SET = new Set(LIFECYCLE_STATES);
4901
- function isLifecycleState(s) {
4902
- return STATE_SET.has(s);
4903
- }
4904
- function isActiveState(s) {
4905
- return ACTIVE_STATES.includes(s);
4906
- }
4907
- function lifecycleFolderOf(filePath) {
4908
- const p2 = filePath.replace(/\\/g, "/");
4909
- const m = p2.match(/\/delivery\/work-items\/([^/]+)\//);
4910
- if (m && isLifecycleState(m[1])) return m[1];
4911
- return null;
4912
- }
4913
- function lifecycleStateOf(item) {
4914
- const status = (item.status ?? "").trim().toLowerCase();
4915
- if (isLifecycleState(status)) return status;
4916
- if (status && LEGACY_STATUS_MAP[status]) return LEGACY_STATUS_MAP[status];
4917
- const folder = lifecycleFolderOf(item.filePath ?? "");
4918
- if (folder) return folder;
4919
- return "ready";
4920
- }
4921
- function emptyLifecycleCounts() {
4922
- return Object.fromEntries(LIFECYCLE_STATES.map((s) => [s, 0]));
4923
- }
4924
- function lifecycleCounts(states) {
4925
- const counts = emptyLifecycleCounts();
4926
- for (const s of states) counts[s]++;
4927
- return counts;
4928
- }
4929
-
4930
5053
  // src/core/project-explain.ts
4931
- var ARCH_DIR3 = "knowledge";
5054
+ var ARCH_DIR4 = "knowledge";
4932
5055
  function first(values) {
4933
5056
  if (Array.isArray(values)) {
4934
5057
  const v = values.find((x) => typeof x === "string" && x);
@@ -4959,7 +5082,7 @@ function loadScan(dir) {
4959
5082
  }
4960
5083
  }
4961
5084
  function hasAgents(dir) {
4962
- const agentsDir = join(dir, ARCH_DIR3, "agents");
5085
+ const agentsDir = join(dir, ARCH_DIR4, "agents");
4963
5086
  if (!exists(agentsDir)) return false;
4964
5087
  function hasAgentMd(d) {
4965
5088
  for (const e of readDir(d)) {
@@ -4996,7 +5119,7 @@ function buildProjectExplanation(dir) {
4996
5119
  const layerStatus = (name) => layers.find((l) => l.layer === name)?.status ?? "Missing";
4997
5120
  const knowledge = {
4998
5121
  hasScan: scan2 !== null,
4999
- hasInventory: exists(join(dir, ARCH_DIR3, "inventory.md")),
5122
+ hasInventory: exists(join(dir, ARCH_DIR4, "inventory.md")),
5000
5123
  hasContextPack: exists(join(dir, ".kaddo", "context-pack.md")),
5001
5124
  hasUnderstand: exists(join(dir, ".kaddo", "understand.md")),
5002
5125
  hasCapabilities: layerStatus("Product") !== "Missing",
@@ -5004,12 +5127,7 @@ function buildProjectExplanation(dir) {
5004
5127
  hasRoadmap: layerStatus("Delivery") !== "Missing",
5005
5128
  hasAgents: hasAgents(dir)
5006
5129
  };
5007
- const archDir = join(dir, ARCH_DIR3);
5008
- const allArtifacts = exists(archDir) ? readArtifacts(archDir) : [];
5009
- const workItemArtifacts = allArtifacts.filter((a) => {
5010
- const p2 = a.filePath.replace(/\\/g, "/");
5011
- return p2.includes("/delivery/work-items/") && Boolean(a.type);
5012
- });
5130
+ const workItemArtifacts = discoverWorkItems(dir);
5013
5131
  const items = workItemArtifacts.map((a) => ({
5014
5132
  id: a.id || a.title,
5015
5133
  title: a.title,
@@ -5051,7 +5169,7 @@ function buildProjectExplanation(dir) {
5051
5169
  workItemsMissingOwnership: items.length - withOwnership
5052
5170
  };
5053
5171
  const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
5054
- const roadmapPath = join(dir, ARCH_DIR3, "delivery", "roadmap.md");
5172
+ const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
5055
5173
  const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
5056
5174
  const roadmap = roadmapStats(roadmapMd, items.length);
5057
5175
  const mappedModules = loadMappedModules(dir);
@@ -5232,6 +5350,15 @@ function renderExplanationHuman(exp) {
5232
5350
  for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
5233
5351
  lines.push("");
5234
5352
  }
5353
+ const assessment = assessPhase(exp);
5354
+ lines.push("## Phase");
5355
+ lines.push(`- Phase: ${assessment.phase}`);
5356
+ if (assessment.reasons.length > 0) {
5357
+ lines.push("- Reason:");
5358
+ for (const r of assessment.reasons) lines.push(` - ${r}`);
5359
+ }
5360
+ if (assessment.nextStep) lines.push(`- Next step: ${assessment.nextStep}`);
5361
+ lines.push("");
5235
5362
  if (exp.suggestedNextSteps.length > 0) {
5236
5363
  lines.push("## Suggested Next Steps");
5237
5364
  exp.suggestedNextSteps.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
@@ -5245,10 +5372,10 @@ function renderExplanationAgent(exp) {
5245
5372
  }
5246
5373
 
5247
5374
  // src/commands/explain.ts
5248
- var ARCH_DIR4 = "knowledge";
5375
+ var ARCH_DIR5 = "knowledge";
5249
5376
  var CONFIG_PATH3 = ".kaddo/config.yml";
5250
5377
  function readKnowledge(dir) {
5251
- const knowledgePath = join(dir, ARCH_DIR4, "knowledge.md");
5378
+ const knowledgePath = join(dir, ARCH_DIR5, "knowledge.md");
5252
5379
  if (!exists(knowledgePath)) return null;
5253
5380
  try {
5254
5381
  const raw = readFile(knowledgePath);
@@ -5259,7 +5386,7 @@ function readKnowledge(dir) {
5259
5386
  }
5260
5387
  }
5261
5388
  function readRoadmap(dir) {
5262
- const roadmapPath = join(dir, ARCH_DIR4, "roadmap.md");
5389
+ const roadmapPath = join(dir, ARCH_DIR5, "roadmap.md");
5263
5390
  if (!exists(roadmapPath)) return null;
5264
5391
  try {
5265
5392
  const raw = readFile(roadmapPath);
@@ -5408,7 +5535,7 @@ function runExplain(opts) {
5408
5535
  explainProject(dir, mode);
5409
5536
  return;
5410
5537
  }
5411
- const archDir = join(dir, ARCH_DIR4);
5538
+ const archDir = join(dir, ARCH_DIR5);
5412
5539
  if (!exists(archDir)) {
5413
5540
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
5414
5541
  process.exit(1);
@@ -5433,7 +5560,7 @@ function runExplain(opts) {
5433
5560
  // src/core/context-pack.ts
5434
5561
  import matter3 from "gray-matter";
5435
5562
  var CONTEXT_PACK_VERSION = "1";
5436
- var ARCH_DIR5 = "knowledge";
5563
+ var ARCH_DIR6 = "knowledge";
5437
5564
  function readScanJson(dir) {
5438
5565
  const scanPath = join(dir, ".kaddo", "scan.json");
5439
5566
  if (!exists(scanPath)) return null;
@@ -5449,7 +5576,7 @@ function firstParagraph(markdown) {
5449
5576
  return para ?? "";
5450
5577
  }
5451
5578
  function readMarkdownSummary(dir, file) {
5452
- const p2 = join(dir, ARCH_DIR5, file);
5579
+ const p2 = join(dir, ARCH_DIR6, file);
5453
5580
  if (!exists(p2)) return null;
5454
5581
  try {
5455
5582
  return firstParagraph(readFile(p2));
@@ -5527,7 +5654,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5527
5654
  missing.push("Scan baseline missing. Run `kaddo scan` for better context.");
5528
5655
  }
5529
5656
  const detected = scanJson?.detected ?? {};
5530
- const inventoryAvailable = exists(join(dir, ARCH_DIR5, "inventory.md"));
5657
+ const inventoryAvailable = exists(join(dir, ARCH_DIR6, "inventory.md"));
5531
5658
  if (!inventoryAvailable) {
5532
5659
  missing.push("No technical inventory found. Run `kaddo scan` to generate it.");
5533
5660
  }
@@ -5539,8 +5666,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5539
5666
  if (!roadmapSummary) {
5540
5667
  missing.push("No roadmap baseline found.");
5541
5668
  }
5542
- const archPath = join(dir, ARCH_DIR5);
5543
- const allArtifacts = exists(archPath) ? readArtifacts(archPath) : [];
5669
+ const allArtifacts = discoverKnowledge(dir);
5544
5670
  const workItems = allArtifacts.filter(
5545
5671
  (a) => isDeliveryWorkItem(a) && isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))
5546
5672
  );
@@ -5548,7 +5674,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5548
5674
  missing.push("No work items found.");
5549
5675
  }
5550
5676
  const state = config.project.state;
5551
- const roadmapPath = join(dir, ARCH_DIR5, "delivery", "roadmap.md");
5677
+ const roadmapPath = join(dir, ARCH_DIR6, "delivery", "roadmap.md");
5552
5678
  const materialized = allArtifacts.filter(
5553
5679
  (a) => a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type)
5554
5680
  ).length;
@@ -5562,6 +5688,28 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5562
5688
  }
5563
5689
  const mappedModules = loadMappedModules(dir);
5564
5690
  const layers = knowledgeLayers(dir);
5691
+ const allWorkItems = allArtifacts.filter((a) => a.isWorkItem);
5692
+ const wiWithOwnership = allWorkItems.filter((a) => a.codeGlobs.length > 0).length;
5693
+ const phase = assessPhase({
5694
+ layers,
5695
+ roadmap,
5696
+ workItems: {
5697
+ total: allWorkItems.length,
5698
+ byState: lifecycleCounts(
5699
+ allWorkItems.map((a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }))
5700
+ ),
5701
+ items: allWorkItems.map((a) => ({
5702
+ id: a.id || a.title,
5703
+ title: a.title,
5704
+ lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath })
5705
+ }))
5706
+ },
5707
+ ownership: {
5708
+ workItemsTotal: allWorkItems.length,
5709
+ workItemsWithOwnership: wiWithOwnership,
5710
+ workItemsMissingOwnership: allWorkItems.length - wiWithOwnership
5711
+ }
5712
+ });
5565
5713
  return {
5566
5714
  version: CONTEXT_PACK_VERSION,
5567
5715
  generatedAt: now.toISOString(),
@@ -5590,6 +5738,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5590
5738
  },
5591
5739
  layers,
5592
5740
  roadmap,
5741
+ phase,
5593
5742
  deliveryMix,
5594
5743
  mappedModules,
5595
5744
  missing,
@@ -5627,6 +5776,21 @@ function renderContextPack(pack) {
5627
5776
  `- Structure: ${project.structure}`
5628
5777
  ].join("\n") + "\n"
5629
5778
  );
5779
+ parts.push("## Current Phase\n");
5780
+ parts.push(`Phase: ${pack.phase.phase}
5781
+ `);
5782
+ if (pack.phase.reasons.length > 0) {
5783
+ parts.push("Reason:\n");
5784
+ parts.push(pack.phase.reasons.map((r) => `- ${r}`).join("\n") + "\n");
5785
+ }
5786
+ if (pack.phase.recommendedAgents.length > 0) {
5787
+ parts.push(`Recommended next: ${pack.phase.recommendedAgents.join(", ")}
5788
+ `);
5789
+ }
5790
+ if (pack.phase.nextStep) {
5791
+ parts.push(`Next step: ${pack.phase.nextStep}
5792
+ `);
5793
+ }
5630
5794
  parts.push("## Knowledge Layers\n");
5631
5795
  parts.push(
5632
5796
  "Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
@@ -5942,19 +6106,12 @@ import { parse as parseYaml9 } from "yaml";
5942
6106
  function slugify2(s) {
5943
6107
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5944
6108
  }
5945
- function isWorkItem(a) {
5946
- return a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type);
5947
- }
5948
6109
  function toActive(a) {
5949
6110
  const id = a.id || a.title || "WI";
5950
6111
  return { id, title: a.title || id, type: a.type, slug: slugify2(a.title || id) };
5951
6112
  }
5952
6113
  function activeWorkItems(dir) {
5953
- const archDir = join(dir, "knowledge");
5954
- if (!exists(archDir)) return [];
5955
- return readArtifacts(archDir).filter(
5956
- (a) => isWorkItem(a) && lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "in-progress"
5957
- ).map(toActive);
6114
+ return discoverWorkItems(dir).filter((a) => a.lifecycle === "in-progress").map(toActive);
5958
6115
  }
5959
6116
  function branchPrefix(type) {
5960
6117
  switch (type) {
@@ -6033,64 +6190,19 @@ function runUnderstand() {
6033
6190
  );
6034
6191
  }
6035
6192
  console.log(renderUnderstandTerminal(plan));
6036
- const phase = currentPhase(knowledgeLayers(dir));
6037
- const group = phase.toLowerCase();
6038
- const groupAgents = (AGENT_GROUPS[group] ?? []).map((a) => a.replace(/\.md$/, ""));
6039
- const NEXT_STEPS = {
6040
- business: ["Use business-agent \u2192 refine knowledge/business/business.md"],
6041
- product: [
6042
- "Use bootstrap-agent \u2192 refine knowledge/product/product.md",
6043
- "Use capability-agent \u2192 knowledge/product/capabilities.md"
6044
- ],
6045
- tech: [
6046
- "Use architecture-agent \u2192 knowledge/tech/current-state.md (reality)",
6047
- "Use codebase-agent \u2192 knowledge/tech/codebase.md (intent)",
6048
- "Record decisions as ADRs in knowledge/tech/decisions/"
6049
- ],
6050
- delivery: [
6051
- "Use roadmap-agent \u2192 knowledge/delivery/roadmap.md",
6052
- "Run `kaddo create --from roadmap` (candidates \u2192 Work Items)",
6053
- "Use work-item-agent \u2192 refine into knowledge/delivery/work-items/",
6054
- "Use implementation-agent \u2192 implement, then `kaddo scan`, `kaddo owners suggest`, `kaddo guard`"
6055
- ],
6056
- utilities: ["Use legacy-agent to surface risks and unknowns"]
6057
- };
6058
- if (roadmapHasUnmaterializedCandidates(dir)) {
6059
- const { candidates, materialized, remaining } = pack.roadmap;
6060
- console.log("");
6061
- console.log(
6062
- `The roadmap has ${remaining} unmaterialized Work Item candidate(s) (${candidates} candidate(s), ${materialized} materialized).`
6063
- );
6064
- console.log(" \u2192 Run `kaddo create --from roadmap`, or use the work-item-agent to");
6065
- console.log(" materialize them into knowledge/delivery/work-items/.");
6066
- }
6193
+ const exp = buildProjectExplanation(dir);
6194
+ const assessment = assessPhase(exp);
6067
6195
  console.log("");
6068
- console.log(`Current phase: ${phase}`);
6069
- console.log("Recommended next steps:");
6070
- console.log(" 1. Run `kaddo scan` (technical signals \u2192 knowledge/inventory.md)");
6071
- console.log(" 2. Run `kaddo context` (package knowledge for your LLM)");
6072
- let n = 3;
6073
- for (const step of NEXT_STEPS[group] ?? []) console.log(` ${n++}. ${step}`);
6074
- if (groupAgents.length > 0) {
6075
- console.log(`Agents for this phase: ${groupAgents.join(", ")}`);
6196
+ console.log(`Current phase: ${assessment.phase}`);
6197
+ if (assessment.reasons.length > 0) {
6198
+ console.log("Reason:");
6199
+ for (const r of assessment.reasons) console.log(` - ${r}`);
6076
6200
  }
6077
- const exp = buildProjectExplanation(dir);
6078
- if (exp.workItems.total > 0) {
6079
- const bs = exp.workItems.byState;
6080
- console.log("");
6081
- console.log("Current active work:");
6082
- console.log(` Draft: ${bs.draft} Ready: ${bs.ready} In Progress: ${bs["in-progress"]} Blocked: ${bs.blocked}`);
6083
- const firstReady = exp.workItems.items.find((i) => i.lifecycle === "ready");
6084
- const firstInProgress = exp.workItems.items.find((i) => i.lifecycle === "in-progress");
6085
- if (firstInProgress) {
6086
- console.log(` \u2192 Continue ${firstInProgress.id} \u2014 ${firstInProgress.title} (in progress).`);
6087
- } else if (firstReady) {
6088
- console.log(` \u2192 Recommended next step: start ${firstReady.id} \u2014 ${firstReady.title}.`);
6089
- } else if (bs.draft > 0) {
6090
- console.log(" \u2192 Refine a draft Work Item to `ready` (scope + acceptance defined).");
6091
- } else if (bs.blocked > 0) {
6092
- console.log(" \u2192 All active work is blocked. Resolve the blockers to move forward.");
6093
- }
6201
+ if (assessment.recommendedAgents.length > 0) {
6202
+ console.log(`Recommended: ${assessment.recommendedAgents.join(", ")}`);
6203
+ }
6204
+ if (assessment.nextStep) {
6205
+ console.log(`Next step: ${assessment.nextStep}`);
6094
6206
  }
6095
6207
  const active = activeWorkItems(dir);
6096
6208
  if (active.length > 0) {
@@ -6257,9 +6369,9 @@ function classify(declaredType, declaredLevel, touchedFiles) {
6257
6369
  }
6258
6370
 
6259
6371
  // src/commands/classify.ts
6260
- var ARCH_DIR6 = "knowledge";
6372
+ var ARCH_DIR7 = "knowledge";
6261
6373
  function findActiveWorkItem(dir) {
6262
- const archDir = join(dir, ARCH_DIR6);
6374
+ const archDir = join(dir, ARCH_DIR7);
6263
6375
  if (!exists(archDir)) return null;
6264
6376
  const artifacts = readArtifacts(archDir);
6265
6377
  return artifacts.filter((a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap").sort((a, b) => {
@@ -6325,7 +6437,7 @@ async function runClassify(opts = {}) {
6325
6437
 
6326
6438
  // src/commands/status.ts
6327
6439
  import { parse as parseYaml10 } from "yaml";
6328
- var ARCH_DIR7 = "knowledge";
6440
+ var ARCH_DIR8 = "knowledge";
6329
6441
  var CONFIG_PATH4 = ".kaddo/config.yml";
6330
6442
  function loadConfig3(dir) {
6331
6443
  const p2 = join(dir, CONFIG_PATH4);
@@ -6338,12 +6450,12 @@ function loadConfig3(dir) {
6338
6450
  }
6339
6451
  function runStatus() {
6340
6452
  const dir = cwd();
6341
- if (!exists(join(dir, ARCH_DIR7))) {
6453
+ if (!exists(join(dir, ARCH_DIR8))) {
6342
6454
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
6343
6455
  process.exit(1);
6344
6456
  }
6345
6457
  const config = loadConfig3(dir);
6346
- const artifacts = readArtifacts(join(dir, ARCH_DIR7));
6458
+ const artifacts = readArtifacts(join(dir, ARCH_DIR8));
6347
6459
  const ignores = loadIgnores(dir);
6348
6460
  const workItems = artifacts.filter(
6349
6461
  (a) => a.type !== "current-state" && a.type !== "roadmap"
@@ -6389,7 +6501,7 @@ function runStatus() {
6389
6501
 
6390
6502
  // src/commands/learn.ts
6391
6503
  import matter4 from "gray-matter";
6392
- var ARCH_DIR8 = "knowledge";
6504
+ var ARCH_DIR9 = "knowledge";
6393
6505
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
6394
6506
  function findWorkItemFile(dir, id) {
6395
6507
  const wiDir = join(dir, WORK_ITEMS_DIR2);
@@ -6430,12 +6542,12 @@ ${learning.trim()}
6430
6542
  }
6431
6543
  async function runLearn(artifactId) {
6432
6544
  const dir = cwd();
6433
- if (!exists(join(dir, ARCH_DIR8))) {
6545
+ if (!exists(join(dir, ARCH_DIR9))) {
6434
6546
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
6435
6547
  process.exit(1);
6436
6548
  }
6437
6549
  intro2("kaddo learn");
6438
- const artifacts = readArtifacts(join(dir, ARCH_DIR8));
6550
+ const artifacts = readArtifacts(join(dir, ARCH_DIR9));
6439
6551
  const inProgress = artifacts.filter(
6440
6552
  (a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap"
6441
6553
  );
@@ -6478,7 +6590,7 @@ async function runLearn(artifactId) {
6478
6590
  }
6479
6591
 
6480
6592
  // src/commands/history.ts
6481
- var ARCH_DIR9 = "knowledge";
6593
+ var ARCH_DIR10 = "knowledge";
6482
6594
  function formatRow(a) {
6483
6595
  const id = (a.id || "").padEnd(8);
6484
6596
  const type = a.type.padEnd(18);
@@ -6489,11 +6601,11 @@ function formatRow(a) {
6489
6601
  }
6490
6602
  function runHistory(opts = {}) {
6491
6603
  const dir = cwd();
6492
- if (!exists(join(dir, ARCH_DIR9))) {
6604
+ if (!exists(join(dir, ARCH_DIR10))) {
6493
6605
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
6494
6606
  process.exit(1);
6495
6607
  }
6496
- let artifacts = readArtifacts(join(dir, ARCH_DIR9)).filter(
6608
+ let artifacts = readArtifacts(join(dir, ARCH_DIR10)).filter(
6497
6609
  (a) => a.type !== "current-state" && a.type !== "roadmap"
6498
6610
  );
6499
6611
  if (opts.domain) {
@@ -6654,42 +6766,23 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
6654
6766
 
6655
6767
  // src/core/ownership-suggest.ts
6656
6768
  import matter5 from "gray-matter";
6657
- var WORK_ITEMS_DIR3 = "knowledge/delivery/work-items";
6658
6769
  var SCAN_PATH = ".kaddo/scan.json";
6659
6770
  function toStringArray3(value) {
6660
6771
  return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
6661
6772
  }
6662
- function parseOwnershipArtifact(filePath, relPath, raw) {
6663
- try {
6664
- const { data } = matter5(raw);
6665
- const codeGlobs = toStringArray3(data.code);
6666
- return {
6667
- filePath,
6668
- relPath: relPath.split("\\").join("/"),
6669
- id: String(data.id ?? ""),
6670
- title: String(data.title ?? ""),
6671
- type: String(data.type ?? ""),
6672
- domains: toStringArray3(data.domains),
6673
- capabilities: toStringArray3(data.capabilities),
6674
- codeGlobs,
6675
- source: data.source ? String(data.source) : void 0,
6676
- hasOwnership: codeGlobs.length > 0
6677
- };
6678
- } catch {
6679
- return null;
6680
- }
6681
- }
6682
6773
  function findWorkItemArtifacts(dir) {
6683
- const wiDir = join(dir, WORK_ITEMS_DIR3);
6684
- if (!exists(wiDir)) return [];
6685
- const artifacts = [];
6686
- for (const entry of readDir(wiDir)) {
6687
- const full = join(wiDir, entry);
6688
- if (!isFile(full) || !entry.endsWith(".md")) continue;
6689
- const parsed = parseOwnershipArtifact(full, `${WORK_ITEMS_DIR3}/${entry}`, readFile(full));
6690
- if (parsed) artifacts.push(parsed);
6691
- }
6692
- return artifacts;
6774
+ return discoverWorkItems(dir).map((a) => ({
6775
+ filePath: a.filePath,
6776
+ relPath: a.relPath,
6777
+ id: a.id,
6778
+ title: a.title,
6779
+ type: a.type,
6780
+ domains: a.domains,
6781
+ capabilities: a.capabilities,
6782
+ codeGlobs: a.codeGlobs,
6783
+ source: a.source || void 0,
6784
+ hasOwnership: a.codeGlobs.length > 0
6785
+ }));
6693
6786
  }
6694
6787
  function artifactsMissingOwnership(artifacts) {
6695
6788
  return artifacts.filter((a) => !a.hasOwnership);
@@ -6833,16 +6926,24 @@ async function runOwnersSuggest() {
6833
6926
  intro2("kaddo owners suggest");
6834
6927
  const artifacts = findWorkItemArtifacts(dir);
6835
6928
  if (artifacts.length === 0) {
6836
- log2.warn("No knowledge artifacts found. Create a Work Item first.");
6929
+ log2.warn(
6930
+ "No Work Items found under knowledge/delivery/work-items/ (searched recursively, including draft/ ready/ in-progress/ \u2026)."
6931
+ );
6932
+ log2.info("Create one with `kaddo create` or `kaddo create --from roadmap`.");
6837
6933
  outro2("Nothing to do.");
6838
6934
  return;
6839
6935
  }
6840
6936
  const missing = artifactsMissingOwnership(artifacts);
6841
6937
  if (missing.length === 0) {
6842
- log2.info("All detected artifacts already declare code ownership.");
6938
+ log2.success(
6939
+ `Found ${artifacts.length} Work Item${artifacts.length === 1 ? "" : "s"}; all already declare code ownership.`
6940
+ );
6843
6941
  outro2("Nothing to do.");
6844
6942
  return;
6845
6943
  }
6944
+ log2.info(
6945
+ `Found ${artifacts.length} Work Item${artifacts.length === 1 ? "" : "s"}; ${missing.length} missing code ownership.`
6946
+ );
6846
6947
  const relPath = missing.length === 1 ? missing[0].relPath : await select2({
6847
6948
  message: "Which artifact needs ownership?",
6848
6949
  options: missing.map((a) => ({
@@ -8769,7 +8870,7 @@ async function runBootstrap(dir = cwd()) {
8769
8870
  const result = bootstrap(dir);
8770
8871
  console.log("");
8771
8872
  console.log("Bootstrap layers:");
8772
- for (const layer of result.layers) console.log(` \u2713 ${layer}`);
8873
+ for (const layer2 of result.layers) console.log(` \u2713 ${layer2}`);
8773
8874
  console.log("");
8774
8875
  if (result.written.length > 0) {
8775
8876
  console.log("Created:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.9.0",
3
+ "version": "3.10.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {