@kaddo/cli 3.8.0 → 3.9.1

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 +222 -143
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -725,6 +725,17 @@ async function runScan() {
725
725
  }
726
726
 
727
727
  // src/core/knowledge-levels.ts
728
+ var WORK_ITEM_TYPES = ["feature", "bugfix", "hotfix", "spike", "chore"];
729
+ var TYPE_ALIASES = {
730
+ setup: "chore",
731
+ maintenance: "chore",
732
+ tooling: "chore",
733
+ infrastructure: "chore",
734
+ infra: "chore",
735
+ config: "chore",
736
+ configuration: "chore",
737
+ refactor: "chore"
738
+ };
728
739
  var LEVELS = {
729
740
  K0: {
730
741
  level: "K0",
@@ -879,7 +890,9 @@ var TYPE_TO_LEVEL = {
879
890
  hotfix: "K1",
880
891
  bugfix: "K2",
881
892
  feature: "K2",
882
- spike: "K3"
893
+ spike: "K3",
894
+ // Chores are maintenance/config/tooling work — low ceremony (problem + expected result).
895
+ chore: "K1"
883
896
  };
884
897
  function getLevel(level) {
885
898
  return LEVELS[level];
@@ -888,7 +901,13 @@ function getLevelForType(type) {
888
901
  return TYPE_TO_LEVEL[type];
889
902
  }
890
903
  function isValidType(type) {
891
- return ["feature", "bugfix", "hotfix", "spike"].includes(type);
904
+ return WORK_ITEM_TYPES.includes(type);
905
+ }
906
+ function normalizeType(raw) {
907
+ if (!raw) return null;
908
+ const t = raw.trim().toLowerCase();
909
+ if (isValidType(t)) return t;
910
+ return TYPE_ALIASES[t] ?? null;
892
911
  }
893
912
 
894
913
  // src/modules/adr.ts
@@ -1832,6 +1851,9 @@ Produce a roadmap where each initiative includes:
1832
1851
  7. Dependencies.
1833
1852
  8. Why this comes now.
1834
1853
  9. Candidate work items (each with type, suggested knowledge level, expected value, notes).
1854
+ Use only the official Work Item types: \`feature\`, \`bugfix\`, \`hotfix\`, \`spike\`, \`chore\`.
1855
+ Use \`chore\` for technical/maintenance/tooling/config/infra work (e.g. "Initialize
1856
+ TypeScript project", "Configure Vitest", "Setup CI") \u2014 do not label such work \`feature\`.
1835
1857
  10. Open questions.
1836
1858
 
1837
1859
  Then add a suggested execution order, risks and constraints, a "Not Now" list, and the
@@ -2133,12 +2155,14 @@ A refined Work Item intended to be saved under the lifecycle workspace:
2133
2155
 
2134
2156
  1. Restate the problem in one clear sentence.
2135
2157
  2. Split the candidate if it is too large for a single Work Item.
2136
- 3. Validate the Knowledge Level (K0\u2013K4) and propose a different one if needed.
2137
- 4. Propose acceptance criteria.
2138
- 5. Propose Out of scope and Validation sections.
2139
- 6. Propose a Definition of Done.
2140
- 7. Identify open questions and assumptions.
2141
- 8. Suggest ownership candidates (code globs) if evident.
2158
+ 3. Preserve the candidate's type (\`feature\`, \`bugfix\`, \`hotfix\`, \`spike\`, \`chore\`).
2159
+ Keep \`chore\` for maintenance/tooling/config/infra work \u2014 never upgrade a chore to a feature.
2160
+ 4. Validate the Knowledge Level (K0\u2013K4) and propose a different one if needed.
2161
+ 5. Propose acceptance criteria.
2162
+ 6. Propose Out of scope and Validation sections.
2163
+ 7. Propose a Definition of Done.
2164
+ 8. Identify open questions and assumptions.
2165
+ 9. Suggest ownership candidates (code globs) if evident.
2142
2166
 
2143
2167
  ## Constraints
2144
2168
 
@@ -3675,20 +3699,21 @@ async function runCreate(type, opts = {}) {
3675
3699
  }
3676
3700
  if (!type) {
3677
3701
  console.error("Missing work item type.");
3678
- console.error("Usage: kaddo create <feature|bugfix|hotfix|spike> [--from roadmap]");
3702
+ console.error("Usage: kaddo create <feature|bugfix|hotfix|spike|chore> [--from roadmap]");
3679
3703
  process.exit(1);
3680
3704
  }
3681
3705
  const modWorkItemType = findWorkItemType(type);
3682
3706
  if (modWorkItemType) {
3683
3707
  return runCreateModule(dir, modWorkItemType);
3684
3708
  }
3685
- if (!isValidType(type)) {
3709
+ const workItemType = normalizeType(type);
3710
+ if (!workItemType) {
3686
3711
  console.error(`Unknown work item type: "${type}"`);
3687
- console.error("Valid types: feature, bugfix, hotfix, spike");
3712
+ console.error("Valid types: feature, bugfix, hotfix, spike, chore");
3713
+ console.error("Aliases: setup, maintenance, tooling, infrastructure, refactor \u2192 chore");
3688
3714
  console.error("Module types: adr, incident, rfc, migration, legacy (run `kaddo add <module>` first)");
3689
3715
  process.exit(1);
3690
3716
  }
3691
- const workItemType = type;
3692
3717
  const level = getLevelForType(workItemType);
3693
3718
  const levelDef = getLevel(level);
3694
3719
  intro2(`kaddo create ${workItemType}`);
@@ -3696,7 +3721,7 @@ async function runCreate(type, opts = {}) {
3696
3721
  printStateGuidance(dir);
3697
3722
  const title = await text2({
3698
3723
  message: "Title for this work item",
3699
- 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" : "Explore caching strategies for API responses"}`,
3724
+ 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"}`,
3700
3725
  validate: (v) => v.trim().length === 0 ? "Title is required." : void 0
3701
3726
  });
3702
3727
  const answers = {};
@@ -3765,9 +3790,7 @@ ${body}`;
3765
3790
  }
3766
3791
  var K_LEVEL_RE = /^K[0-4]$/;
3767
3792
  function resolveCandidateType(candidate, cliType) {
3768
- if (candidate.type && isValidType(candidate.type)) return candidate.type;
3769
- if (cliType && isValidType(cliType)) return cliType;
3770
- return null;
3793
+ return normalizeType(candidate.type) ?? normalizeType(cliType);
3771
3794
  }
3772
3795
  function resolveCandidateLevel(candidate, type) {
3773
3796
  const suggested = candidate.suggestedKnowledgeLevel?.trim().toUpperCase();
@@ -3929,7 +3952,8 @@ async function runCreateFromRoadmap(dir, cliType) {
3929
3952
  { value: "feature", label: "feature" },
3930
3953
  { value: "bugfix", label: "bugfix" },
3931
3954
  { value: "hotfix", label: "hotfix" },
3932
- { value: "spike", label: "spike" }
3955
+ { value: "spike", label: "spike" },
3956
+ { value: "chore", label: "chore" }
3933
3957
  ]
3934
3958
  });
3935
3959
  }
@@ -4020,9 +4044,11 @@ function parseArtifact(filePath, raw) {
4020
4044
  knowledgeLevel: String(data.knowledge_level ?? ""),
4021
4045
  codeGlobs: globs,
4022
4046
  domains: Array.isArray(data.domains) ? data.domains : [],
4047
+ capabilities: Array.isArray(data.capabilities) ? data.capabilities.map(String).filter(Boolean) : [],
4023
4048
  status: String(data.status ?? ""),
4024
4049
  phase: String(data.phase ?? ""),
4025
- initiative: String(data.initiative ?? data.source_initiative ?? "")
4050
+ initiative: String(data.initiative ?? data.source_initiative ?? ""),
4051
+ source: data.source ? String(data.source) : ""
4026
4052
  };
4027
4053
  } catch {
4028
4054
  return null;
@@ -4047,6 +4073,94 @@ function readArtifacts(archDir) {
4047
4073
  return artifacts;
4048
4074
  }
4049
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
+
4050
4164
  // src/core/diff-analysis.ts
4051
4165
  import { minimatch } from "minimatch";
4052
4166
  function normalizePath(p2) {
@@ -4439,7 +4553,7 @@ function collectMatchedDomains(matchedArtifactDomains) {
4439
4553
  }
4440
4554
 
4441
4555
  // src/commands/guard.ts
4442
- var ARCH_DIR2 = "knowledge";
4556
+ var ARCH_DIR3 = "knowledge";
4443
4557
  var CONFIG_PATH2 = ".kaddo/config.yml";
4444
4558
  function loadConfig2(dir) {
4445
4559
  const configPath = join(dir, CONFIG_PATH2);
@@ -4583,12 +4697,12 @@ async function runGuard(opts = {}) {
4583
4697
  console.log("kaddo guard: no modified files detected.");
4584
4698
  return;
4585
4699
  }
4586
- const archDir = join(dir, ARCH_DIR2);
4700
+ const archDir = join(dir, ARCH_DIR3);
4587
4701
  if (!exists(archDir)) {
4588
4702
  console.log("kaddo guard: no knowledge/ directory found. Run `kaddo init` first.");
4589
4703
  return;
4590
4704
  }
4591
- const artifacts = readArtifacts(archDir);
4705
+ const artifacts = discoverKnowledge(dir);
4592
4706
  const result = analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership);
4593
4707
  const pluginSignals = runPlugins(plugins, currentFiles, (filePath) => {
4594
4708
  const abs = join(dir, filePath);
@@ -4725,7 +4839,7 @@ var STRUCTURED_TYPES = {
4725
4839
  ]),
4726
4840
  Delivery: /* @__PURE__ */ new Set([])
4727
4841
  };
4728
- var WORK_ITEM_TYPES = /* @__PURE__ */ new Set([
4842
+ var WORK_ITEM_TYPES2 = /* @__PURE__ */ new Set([
4729
4843
  "work-item",
4730
4844
  "feature",
4731
4845
  "bugfix",
@@ -4738,7 +4852,7 @@ function layerForType(type) {
4738
4852
  if (type === CONSOLIDATED_TYPE.Product || STRUCTURED_TYPES.Product.has(type)) return "Product";
4739
4853
  if (type === CONSOLIDATED_TYPE.Tech || STRUCTURED_TYPES.Tech.has(type) || type === "knowledge")
4740
4854
  return "Tech";
4741
- if (type === "roadmap" || WORK_ITEM_TYPES.has(type)) return "Delivery";
4855
+ if (type === "roadmap" || WORK_ITEM_TYPES2.has(type)) return "Delivery";
4742
4856
  return null;
4743
4857
  }
4744
4858
  function layerFromPath(filePath) {
@@ -4769,7 +4883,7 @@ function discoverLayers(dir) {
4769
4883
  slot.detected.add(basename(a.filePath));
4770
4884
  if (layer === "Delivery") {
4771
4885
  if (type === "roadmap" || basename(a.filePath) === "roadmap.md") slot.hasRoadmap = true;
4772
- if (WORK_ITEM_TYPES.has(type) || a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) {
4886
+ if (WORK_ITEM_TYPES2.has(type) || a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) {
4773
4887
  if (a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) slot.hasWorkItem = true;
4774
4888
  }
4775
4889
  continue;
@@ -4857,54 +4971,8 @@ function renderLayersMarkdown(layers) {
4857
4971
  return lines.join("\n").trimEnd();
4858
4972
  }
4859
4973
 
4860
- // src/core/lifecycle.ts
4861
- var LIFECYCLE_STATES = [
4862
- "draft",
4863
- "ready",
4864
- "in-progress",
4865
- "blocked",
4866
- "completed",
4867
- "archived"
4868
- ];
4869
- var ACTIVE_STATES = ["draft", "ready", "in-progress", "blocked"];
4870
- var LEGACY_STATUS_MAP = {
4871
- done: "completed",
4872
- cancelled: "archived",
4873
- canceled: "archived",
4874
- "in-progress": "in-progress"
4875
- };
4876
- var STATE_SET = new Set(LIFECYCLE_STATES);
4877
- function isLifecycleState(s) {
4878
- return STATE_SET.has(s);
4879
- }
4880
- function isActiveState(s) {
4881
- return ACTIVE_STATES.includes(s);
4882
- }
4883
- function lifecycleFolderOf(filePath) {
4884
- const p2 = filePath.replace(/\\/g, "/");
4885
- const m = p2.match(/\/delivery\/work-items\/([^/]+)\//);
4886
- if (m && isLifecycleState(m[1])) return m[1];
4887
- return null;
4888
- }
4889
- function lifecycleStateOf(item) {
4890
- const status = (item.status ?? "").trim().toLowerCase();
4891
- if (isLifecycleState(status)) return status;
4892
- if (status && LEGACY_STATUS_MAP[status]) return LEGACY_STATUS_MAP[status];
4893
- const folder = lifecycleFolderOf(item.filePath ?? "");
4894
- if (folder) return folder;
4895
- return "ready";
4896
- }
4897
- function emptyLifecycleCounts() {
4898
- return Object.fromEntries(LIFECYCLE_STATES.map((s) => [s, 0]));
4899
- }
4900
- function lifecycleCounts(states) {
4901
- const counts = emptyLifecycleCounts();
4902
- for (const s of states) counts[s]++;
4903
- return counts;
4904
- }
4905
-
4906
4974
  // src/core/project-explain.ts
4907
- var ARCH_DIR3 = "knowledge";
4975
+ var ARCH_DIR4 = "knowledge";
4908
4976
  function first(values) {
4909
4977
  if (Array.isArray(values)) {
4910
4978
  const v = values.find((x) => typeof x === "string" && x);
@@ -4935,7 +5003,7 @@ function loadScan(dir) {
4935
5003
  }
4936
5004
  }
4937
5005
  function hasAgents(dir) {
4938
- const agentsDir = join(dir, ARCH_DIR3, "agents");
5006
+ const agentsDir = join(dir, ARCH_DIR4, "agents");
4939
5007
  if (!exists(agentsDir)) return false;
4940
5008
  function hasAgentMd(d) {
4941
5009
  for (const e of readDir(d)) {
@@ -4972,7 +5040,7 @@ function buildProjectExplanation(dir) {
4972
5040
  const layerStatus = (name) => layers.find((l) => l.layer === name)?.status ?? "Missing";
4973
5041
  const knowledge = {
4974
5042
  hasScan: scan2 !== null,
4975
- hasInventory: exists(join(dir, ARCH_DIR3, "inventory.md")),
5043
+ hasInventory: exists(join(dir, ARCH_DIR4, "inventory.md")),
4976
5044
  hasContextPack: exists(join(dir, ".kaddo", "context-pack.md")),
4977
5045
  hasUnderstand: exists(join(dir, ".kaddo", "understand.md")),
4978
5046
  hasCapabilities: layerStatus("Product") !== "Missing",
@@ -4980,15 +5048,11 @@ function buildProjectExplanation(dir) {
4980
5048
  hasRoadmap: layerStatus("Delivery") !== "Missing",
4981
5049
  hasAgents: hasAgents(dir)
4982
5050
  };
4983
- const archDir = join(dir, ARCH_DIR3);
4984
- const allArtifacts = exists(archDir) ? readArtifacts(archDir) : [];
4985
- const workItemArtifacts = allArtifacts.filter((a) => {
4986
- const p2 = a.filePath.replace(/\\/g, "/");
4987
- return p2.includes("/delivery/work-items/") && Boolean(a.type);
4988
- });
5051
+ const workItemArtifacts = discoverWorkItems(dir);
4989
5052
  const items = workItemArtifacts.map((a) => ({
4990
5053
  id: a.id || a.title,
4991
5054
  title: a.title,
5055
+ type: a.type,
4992
5056
  status: a.status,
4993
5057
  lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath }),
4994
5058
  initiative: a.initiative,
@@ -4997,6 +5061,11 @@ function buildProjectExplanation(dir) {
4997
5061
  domains: a.domains
4998
5062
  }));
4999
5063
  const byState = lifecycleCounts(items.map((i) => i.lifecycle));
5064
+ const byType = {};
5065
+ for (const i of items) {
5066
+ const t = i.type || "unknown";
5067
+ byType[t] = (byType[t] ?? 0) + 1;
5068
+ }
5000
5069
  const initiativeMap = /* @__PURE__ */ new Map();
5001
5070
  for (const i of items) {
5002
5071
  const name = i.initiative || "Unassigned";
@@ -5010,6 +5079,7 @@ function buildProjectExplanation(dir) {
5010
5079
  done: items.filter((i) => i.status === "done").length,
5011
5080
  cancelled: items.filter((i) => i.status === "cancelled").length,
5012
5081
  byState,
5082
+ byType,
5013
5083
  initiatives,
5014
5084
  items
5015
5085
  };
@@ -5020,7 +5090,7 @@ function buildProjectExplanation(dir) {
5020
5090
  workItemsMissingOwnership: items.length - withOwnership
5021
5091
  };
5022
5092
  const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
5023
- const roadmapPath = join(dir, ARCH_DIR3, "delivery", "roadmap.md");
5093
+ const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
5024
5094
  const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
5025
5095
  const roadmap = roadmapStats(roadmapMd, items.length);
5026
5096
  const mappedModules = loadMappedModules(dir);
@@ -5079,6 +5149,10 @@ function buildProjectExplanation(dir) {
5079
5149
  function stateLabel(state) {
5080
5150
  return state === "pre-ai" ? "pre-ai" : state;
5081
5151
  }
5152
+ function typeLabel(type) {
5153
+ const cap = type.charAt(0).toUpperCase() + type.slice(1);
5154
+ return cap.endsWith("s") ? cap : `${cap}s`;
5155
+ }
5082
5156
  var LIFECYCLE_LABEL = {
5083
5157
  draft: "Draft",
5084
5158
  ready: "Ready",
@@ -5140,6 +5214,12 @@ function renderExplanationHuman(exp) {
5140
5214
  lines.push(`- ${LIFECYCLE_LABEL[s]}: ${exp.workItems.byState[s]}`);
5141
5215
  }
5142
5216
  lines.push("");
5217
+ const typeEntries = Object.entries(exp.workItems.byType).sort((a, b) => b[1] - a[1]);
5218
+ if (typeEntries.length > 0) {
5219
+ lines.push("## Work Items by Type");
5220
+ for (const [t, n] of typeEntries) lines.push(`- ${typeLabel(t)}: ${n}`);
5221
+ lines.push("");
5222
+ }
5143
5223
  const grouped = exp.workItems.initiatives.filter(
5144
5224
  (g) => LIFECYCLE_STATES.some((s) => g.states[s] > 0)
5145
5225
  );
@@ -5204,10 +5284,10 @@ function renderExplanationAgent(exp) {
5204
5284
  }
5205
5285
 
5206
5286
  // src/commands/explain.ts
5207
- var ARCH_DIR4 = "knowledge";
5287
+ var ARCH_DIR5 = "knowledge";
5208
5288
  var CONFIG_PATH3 = ".kaddo/config.yml";
5209
5289
  function readKnowledge(dir) {
5210
- const knowledgePath = join(dir, ARCH_DIR4, "knowledge.md");
5290
+ const knowledgePath = join(dir, ARCH_DIR5, "knowledge.md");
5211
5291
  if (!exists(knowledgePath)) return null;
5212
5292
  try {
5213
5293
  const raw = readFile(knowledgePath);
@@ -5218,7 +5298,7 @@ function readKnowledge(dir) {
5218
5298
  }
5219
5299
  }
5220
5300
  function readRoadmap(dir) {
5221
- const roadmapPath = join(dir, ARCH_DIR4, "roadmap.md");
5301
+ const roadmapPath = join(dir, ARCH_DIR5, "roadmap.md");
5222
5302
  if (!exists(roadmapPath)) return null;
5223
5303
  try {
5224
5304
  const raw = readFile(roadmapPath);
@@ -5367,7 +5447,7 @@ function runExplain(opts) {
5367
5447
  explainProject(dir, mode);
5368
5448
  return;
5369
5449
  }
5370
- const archDir = join(dir, ARCH_DIR4);
5450
+ const archDir = join(dir, ARCH_DIR5);
5371
5451
  if (!exists(archDir)) {
5372
5452
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
5373
5453
  process.exit(1);
@@ -5392,7 +5472,7 @@ function runExplain(opts) {
5392
5472
  // src/core/context-pack.ts
5393
5473
  import matter3 from "gray-matter";
5394
5474
  var CONTEXT_PACK_VERSION = "1";
5395
- var ARCH_DIR5 = "knowledge";
5475
+ var ARCH_DIR6 = "knowledge";
5396
5476
  function readScanJson(dir) {
5397
5477
  const scanPath = join(dir, ".kaddo", "scan.json");
5398
5478
  if (!exists(scanPath)) return null;
@@ -5408,7 +5488,7 @@ function firstParagraph(markdown) {
5408
5488
  return para ?? "";
5409
5489
  }
5410
5490
  function readMarkdownSummary(dir, file) {
5411
- const p2 = join(dir, ARCH_DIR5, file);
5491
+ const p2 = join(dir, ARCH_DIR6, file);
5412
5492
  if (!exists(p2)) return null;
5413
5493
  try {
5414
5494
  return firstParagraph(readFile(p2));
@@ -5486,7 +5566,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5486
5566
  missing.push("Scan baseline missing. Run `kaddo scan` for better context.");
5487
5567
  }
5488
5568
  const detected = scanJson?.detected ?? {};
5489
- const inventoryAvailable = exists(join(dir, ARCH_DIR5, "inventory.md"));
5569
+ const inventoryAvailable = exists(join(dir, ARCH_DIR6, "inventory.md"));
5490
5570
  if (!inventoryAvailable) {
5491
5571
  missing.push("No technical inventory found. Run `kaddo scan` to generate it.");
5492
5572
  }
@@ -5498,8 +5578,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5498
5578
  if (!roadmapSummary) {
5499
5579
  missing.push("No roadmap baseline found.");
5500
5580
  }
5501
- const archPath = join(dir, ARCH_DIR5);
5502
- const allArtifacts = exists(archPath) ? readArtifacts(archPath) : [];
5581
+ const allArtifacts = discoverKnowledge(dir);
5503
5582
  const workItems = allArtifacts.filter(
5504
5583
  (a) => isDeliveryWorkItem(a) && isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))
5505
5584
  );
@@ -5507,11 +5586,18 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5507
5586
  missing.push("No work items found.");
5508
5587
  }
5509
5588
  const state = config.project.state;
5510
- const roadmapPath = join(dir, ARCH_DIR5, "delivery", "roadmap.md");
5589
+ const roadmapPath = join(dir, ARCH_DIR6, "delivery", "roadmap.md");
5511
5590
  const materialized = allArtifacts.filter(
5512
5591
  (a) => a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type)
5513
5592
  ).length;
5514
5593
  const roadmap = roadmapStats(exists(roadmapPath) ? readFile(roadmapPath) : null, materialized);
5594
+ const deliveryMix = {};
5595
+ for (const a of allArtifacts) {
5596
+ const p2 = a.filePath.replace(/\\/g, "/");
5597
+ if (!a.type || !p2.includes("/delivery/work-items/")) continue;
5598
+ if (!isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))) continue;
5599
+ deliveryMix[a.type] = (deliveryMix[a.type] ?? 0) + 1;
5600
+ }
5515
5601
  const mappedModules = loadMappedModules(dir);
5516
5602
  const layers = knowledgeLayers(dir);
5517
5603
  return {
@@ -5542,6 +5628,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5542
5628
  },
5543
5629
  layers,
5544
5630
  roadmap,
5631
+ deliveryMix,
5545
5632
  mappedModules,
5546
5633
  missing,
5547
5634
  handoff: {
@@ -5643,6 +5730,16 @@ function renderContextPack(pack) {
5643
5730
  } else {
5644
5731
  parts.push("No active work items found.\n");
5645
5732
  }
5733
+ const mixEntries = Object.entries(pack.deliveryMix).sort((a, b) => b[1] - a[1]);
5734
+ if (mixEntries.length > 0) {
5735
+ parts.push("## Delivery Mix\n");
5736
+ parts.push("Active Work Items by type:\n");
5737
+ const label = (t) => {
5738
+ const c = t.charAt(0).toUpperCase() + t.slice(1);
5739
+ return c.endsWith("s") ? c : `${c}s`;
5740
+ };
5741
+ parts.push(mixEntries.map(([t, n]) => `- ${label(t)}: ${n}`).join("\n") + "\n");
5742
+ }
5646
5743
  parts.push("## Artifacts and Ownership\n");
5647
5744
  if (knowledge.artifacts.length > 0) {
5648
5745
  const lines = knowledge.artifacts.map(
@@ -5883,19 +5980,12 @@ import { parse as parseYaml9 } from "yaml";
5883
5980
  function slugify2(s) {
5884
5981
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5885
5982
  }
5886
- function isWorkItem(a) {
5887
- return a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type);
5888
- }
5889
5983
  function toActive(a) {
5890
5984
  const id = a.id || a.title || "WI";
5891
5985
  return { id, title: a.title || id, type: a.type, slug: slugify2(a.title || id) };
5892
5986
  }
5893
5987
  function activeWorkItems(dir) {
5894
- const archDir = join(dir, "knowledge");
5895
- if (!exists(archDir)) return [];
5896
- return readArtifacts(archDir).filter(
5897
- (a) => isWorkItem(a) && lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "in-progress"
5898
- ).map(toActive);
5988
+ return discoverWorkItems(dir).filter((a) => a.lifecycle === "in-progress").map(toActive);
5899
5989
  }
5900
5990
  function branchPrefix(type) {
5901
5991
  switch (type) {
@@ -6198,9 +6288,9 @@ function classify(declaredType, declaredLevel, touchedFiles) {
6198
6288
  }
6199
6289
 
6200
6290
  // src/commands/classify.ts
6201
- var ARCH_DIR6 = "knowledge";
6291
+ var ARCH_DIR7 = "knowledge";
6202
6292
  function findActiveWorkItem(dir) {
6203
- const archDir = join(dir, ARCH_DIR6);
6293
+ const archDir = join(dir, ARCH_DIR7);
6204
6294
  if (!exists(archDir)) return null;
6205
6295
  const artifacts = readArtifacts(archDir);
6206
6296
  return artifacts.filter((a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap").sort((a, b) => {
@@ -6266,7 +6356,7 @@ async function runClassify(opts = {}) {
6266
6356
 
6267
6357
  // src/commands/status.ts
6268
6358
  import { parse as parseYaml10 } from "yaml";
6269
- var ARCH_DIR7 = "knowledge";
6359
+ var ARCH_DIR8 = "knowledge";
6270
6360
  var CONFIG_PATH4 = ".kaddo/config.yml";
6271
6361
  function loadConfig3(dir) {
6272
6362
  const p2 = join(dir, CONFIG_PATH4);
@@ -6279,12 +6369,12 @@ function loadConfig3(dir) {
6279
6369
  }
6280
6370
  function runStatus() {
6281
6371
  const dir = cwd();
6282
- if (!exists(join(dir, ARCH_DIR7))) {
6372
+ if (!exists(join(dir, ARCH_DIR8))) {
6283
6373
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
6284
6374
  process.exit(1);
6285
6375
  }
6286
6376
  const config = loadConfig3(dir);
6287
- const artifacts = readArtifacts(join(dir, ARCH_DIR7));
6377
+ const artifacts = readArtifacts(join(dir, ARCH_DIR8));
6288
6378
  const ignores = loadIgnores(dir);
6289
6379
  const workItems = artifacts.filter(
6290
6380
  (a) => a.type !== "current-state" && a.type !== "roadmap"
@@ -6330,7 +6420,7 @@ function runStatus() {
6330
6420
 
6331
6421
  // src/commands/learn.ts
6332
6422
  import matter4 from "gray-matter";
6333
- var ARCH_DIR8 = "knowledge";
6423
+ var ARCH_DIR9 = "knowledge";
6334
6424
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
6335
6425
  function findWorkItemFile(dir, id) {
6336
6426
  const wiDir = join(dir, WORK_ITEMS_DIR2);
@@ -6371,12 +6461,12 @@ ${learning.trim()}
6371
6461
  }
6372
6462
  async function runLearn(artifactId) {
6373
6463
  const dir = cwd();
6374
- if (!exists(join(dir, ARCH_DIR8))) {
6464
+ if (!exists(join(dir, ARCH_DIR9))) {
6375
6465
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
6376
6466
  process.exit(1);
6377
6467
  }
6378
6468
  intro2("kaddo learn");
6379
- const artifacts = readArtifacts(join(dir, ARCH_DIR8));
6469
+ const artifacts = readArtifacts(join(dir, ARCH_DIR9));
6380
6470
  const inProgress = artifacts.filter(
6381
6471
  (a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap"
6382
6472
  );
@@ -6419,7 +6509,7 @@ async function runLearn(artifactId) {
6419
6509
  }
6420
6510
 
6421
6511
  // src/commands/history.ts
6422
- var ARCH_DIR9 = "knowledge";
6512
+ var ARCH_DIR10 = "knowledge";
6423
6513
  function formatRow(a) {
6424
6514
  const id = (a.id || "").padEnd(8);
6425
6515
  const type = a.type.padEnd(18);
@@ -6430,11 +6520,11 @@ function formatRow(a) {
6430
6520
  }
6431
6521
  function runHistory(opts = {}) {
6432
6522
  const dir = cwd();
6433
- if (!exists(join(dir, ARCH_DIR9))) {
6523
+ if (!exists(join(dir, ARCH_DIR10))) {
6434
6524
  console.error("No knowledge/ directory found. Run `kaddo init` first.");
6435
6525
  process.exit(1);
6436
6526
  }
6437
- let artifacts = readArtifacts(join(dir, ARCH_DIR9)).filter(
6527
+ let artifacts = readArtifacts(join(dir, ARCH_DIR10)).filter(
6438
6528
  (a) => a.type !== "current-state" && a.type !== "roadmap"
6439
6529
  );
6440
6530
  if (opts.domain) {
@@ -6595,42 +6685,23 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
6595
6685
 
6596
6686
  // src/core/ownership-suggest.ts
6597
6687
  import matter5 from "gray-matter";
6598
- var WORK_ITEMS_DIR3 = "knowledge/delivery/work-items";
6599
6688
  var SCAN_PATH = ".kaddo/scan.json";
6600
6689
  function toStringArray3(value) {
6601
6690
  return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
6602
6691
  }
6603
- function parseOwnershipArtifact(filePath, relPath, raw) {
6604
- try {
6605
- const { data } = matter5(raw);
6606
- const codeGlobs = toStringArray3(data.code);
6607
- return {
6608
- filePath,
6609
- relPath: relPath.split("\\").join("/"),
6610
- id: String(data.id ?? ""),
6611
- title: String(data.title ?? ""),
6612
- type: String(data.type ?? ""),
6613
- domains: toStringArray3(data.domains),
6614
- capabilities: toStringArray3(data.capabilities),
6615
- codeGlobs,
6616
- source: data.source ? String(data.source) : void 0,
6617
- hasOwnership: codeGlobs.length > 0
6618
- };
6619
- } catch {
6620
- return null;
6621
- }
6622
- }
6623
6692
  function findWorkItemArtifacts(dir) {
6624
- const wiDir = join(dir, WORK_ITEMS_DIR3);
6625
- if (!exists(wiDir)) return [];
6626
- const artifacts = [];
6627
- for (const entry of readDir(wiDir)) {
6628
- const full = join(wiDir, entry);
6629
- if (!isFile(full) || !entry.endsWith(".md")) continue;
6630
- const parsed = parseOwnershipArtifact(full, `${WORK_ITEMS_DIR3}/${entry}`, readFile(full));
6631
- if (parsed) artifacts.push(parsed);
6632
- }
6633
- return artifacts;
6693
+ return discoverWorkItems(dir).map((a) => ({
6694
+ filePath: a.filePath,
6695
+ relPath: a.relPath,
6696
+ id: a.id,
6697
+ title: a.title,
6698
+ type: a.type,
6699
+ domains: a.domains,
6700
+ capabilities: a.capabilities,
6701
+ codeGlobs: a.codeGlobs,
6702
+ source: a.source || void 0,
6703
+ hasOwnership: a.codeGlobs.length > 0
6704
+ }));
6634
6705
  }
6635
6706
  function artifactsMissingOwnership(artifacts) {
6636
6707
  return artifacts.filter((a) => !a.hasOwnership);
@@ -6774,16 +6845,24 @@ async function runOwnersSuggest() {
6774
6845
  intro2("kaddo owners suggest");
6775
6846
  const artifacts = findWorkItemArtifacts(dir);
6776
6847
  if (artifacts.length === 0) {
6777
- log2.warn("No knowledge artifacts found. Create a Work Item first.");
6848
+ log2.warn(
6849
+ "No Work Items found under knowledge/delivery/work-items/ (searched recursively, including draft/ ready/ in-progress/ \u2026)."
6850
+ );
6851
+ log2.info("Create one with `kaddo create` or `kaddo create --from roadmap`.");
6778
6852
  outro2("Nothing to do.");
6779
6853
  return;
6780
6854
  }
6781
6855
  const missing = artifactsMissingOwnership(artifacts);
6782
6856
  if (missing.length === 0) {
6783
- log2.info("All detected artifacts already declare code ownership.");
6857
+ log2.success(
6858
+ `Found ${artifacts.length} Work Item${artifacts.length === 1 ? "" : "s"}; all already declare code ownership.`
6859
+ );
6784
6860
  outro2("Nothing to do.");
6785
6861
  return;
6786
6862
  }
6863
+ log2.info(
6864
+ `Found ${artifacts.length} Work Item${artifacts.length === 1 ? "" : "s"}; ${missing.length} missing code ownership.`
6865
+ );
6787
6866
  const relPath = missing.length === 1 ? missing[0].relPath : await select2({
6788
6867
  message: "Which artifact needs ownership?",
6789
6868
  options: missing.map((a) => ({
@@ -8745,7 +8824,7 @@ program.command("scan").description("Detect project stack and suggest domains").
8745
8824
  program.command("bootstrap").description("Build the initial knowledge base for a new project (Business \u2192 Product \u2192 Tech \u2192 Delivery)").action(async () => {
8746
8825
  await runBootstrap();
8747
8826
  });
8748
- program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
8827
+ program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike, chore). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
8749
8828
  await runCreate(type ?? "", opts);
8750
8829
  });
8751
8830
  program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").action(async (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.8.0",
3
+ "version": "3.9.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {