@kaddo/cli 3.5.1 → 3.7.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 +329 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1907,7 +1907,9 @@ existing Work Item file to refine.
1907
1907
 
1908
1908
  ## Expected Output
1909
1909
 
1910
- A refined Work Item intended to be saved as \`knowledge/delivery/work-items/*.md\`.
1910
+ A refined Work Item intended to be saved under the lifecycle workspace:
1911
+ \`knowledge/delivery/work-items/draft/\`, \`ready/\`, \`in-progress/\`, \`blocked/\`,
1912
+ \`completed/\` or \`archived/\`.
1911
1913
 
1912
1914
  ## Instructions
1913
1915
 
@@ -1915,9 +1917,10 @@ A refined Work Item intended to be saved as \`knowledge/delivery/work-items/*.md
1915
1917
  2. Split the candidate if it is too large for a single Work Item.
1916
1918
  3. Validate the Knowledge Level (K0\u2013K4) and propose a different one if needed.
1917
1919
  4. Propose acceptance criteria.
1918
- 5. Propose a Definition of Done.
1919
- 6. Identify open questions and assumptions.
1920
- 7. Suggest ownership candidates (code globs) if evident.
1920
+ 5. Propose Out of scope and Validation sections.
1921
+ 6. Propose a Definition of Done.
1922
+ 7. Identify open questions and assumptions.
1923
+ 8. Suggest ownership candidates (code globs) if evident.
1921
1924
 
1922
1925
  ## Constraints
1923
1926
 
@@ -1939,6 +1942,10 @@ A refined Work Item intended to be saved as \`knowledge/delivery/work-items/*.md
1939
1942
 
1940
1943
  **Acceptance criteria:**
1941
1944
 
1945
+ **Out of scope:**
1946
+
1947
+ **Validation:**
1948
+
1942
1949
  **Definition of Done:**
1943
1950
 
1944
1951
  **Open questions:**
@@ -1948,7 +1955,9 @@ A refined Work Item intended to be saved as \`knowledge/delivery/work-items/*.md
1948
1955
 
1949
1956
  ## Where to Save the Result
1950
1957
 
1951
- Save the output as a file under \`knowledge/delivery/work-items/\`.
1958
+ Save new output as a draft under \`knowledge/delivery/work-items/draft/\` unless a human
1959
+ explicitly asks for another lifecycle state. Treat only \`draft\`, \`ready\`, \`in-progress\`
1960
+ and \`blocked\` as active work; \`completed\` and \`archived\` are historical knowledge.
1952
1961
 
1953
1962
  ## Delivery workflow
1954
1963
 
@@ -3093,12 +3102,96 @@ function parseBlock(block) {
3093
3102
  openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0
3094
3103
  }));
3095
3104
  }
3105
+ var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
3106
+ var HEADING_RE = /^#{2,4}\s+(.*)$/;
3107
+ var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
3108
+ var SEPARATOR_CELL_RE = /^:?-{2,}:?$/;
3109
+ function splitRow(line) {
3110
+ return line.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((c) => c.trim());
3111
+ }
3112
+ function isSeparatorRow(cells) {
3113
+ return cells.length > 0 && cells.every((c) => SEPARATOR_CELL_RE.test(c) || c === "");
3114
+ }
3115
+ function initiativeFromHeading(text3) {
3116
+ const rm = text3.match(/^(RM-[\w.-]+)\s*[:\-–]?\s*(.*)$/);
3117
+ if (rm) return { id: rm[1], title: rm[2].trim() || void 0 };
3118
+ return { title: text3.trim() || void 0 };
3119
+ }
3120
+ function extractDeps(cell) {
3121
+ if (!cell) return void 0;
3122
+ const ids = cell.match(/WI-[A-Za-z0-9-]*\d/g);
3123
+ return ids && ids.length ? [...new Set(ids)] : void 0;
3124
+ }
3125
+ function parseFlexible(markdown) {
3126
+ const out = [];
3127
+ const seen = /* @__PURE__ */ new Set();
3128
+ let initiative;
3129
+ let cols = null;
3130
+ const push = (id, title, deps, raw) => {
3131
+ if (seen.has(id)) return;
3132
+ seen.add(id);
3133
+ out.push({
3134
+ id,
3135
+ title: title.trim() || id,
3136
+ dependencies: deps,
3137
+ initiative: initiative?.id || initiative?.title ? { ...initiative } : void 0,
3138
+ rawMarkdown: raw.trim()
3139
+ });
3140
+ };
3141
+ for (const line of markdown.split(/\r?\n/)) {
3142
+ const h = line.match(HEADING_RE);
3143
+ if (h) {
3144
+ initiative = initiativeFromHeading(h[1]);
3145
+ cols = null;
3146
+ continue;
3147
+ }
3148
+ if (line.includes("|")) {
3149
+ const cells = splitRow(line);
3150
+ if (isSeparatorRow(cells)) continue;
3151
+ const lower = cells.map((c) => c.toLowerCase());
3152
+ const idIdx = lower.findIndex((c) => c === "id" || c === "wi" || c === "work item id");
3153
+ if (idIdx >= 0 && !WI_ID_RE.test(line)) {
3154
+ cols = {
3155
+ id: idIdx,
3156
+ title: lower.findIndex((c) => /work item|title|item|name|description/.test(c)),
3157
+ deps: lower.findIndex((c) => /depend/.test(c))
3158
+ };
3159
+ continue;
3160
+ }
3161
+ if (WI_ID_RE.test(line)) {
3162
+ let idCell = cols && cols.id >= 0 ? cells[cols.id] : cells.find((c) => /^WI-/.test(c));
3163
+ const idMatch = (idCell ?? line).match(/WI-[A-Za-z0-9-]*\d/);
3164
+ if (!idMatch) continue;
3165
+ const id = idMatch[0];
3166
+ const title = cols && cols.title >= 0 ? cells[cols.title] ?? "" : cells.find((c) => !/^WI-/.test(c) && c) ?? "";
3167
+ const deps = cols && cols.deps >= 0 ? extractDeps(cells[cols.deps]) : void 0;
3168
+ push(id, title, deps, line);
3169
+ continue;
3170
+ }
3171
+ continue;
3172
+ }
3173
+ const b = line.match(FLEX_BULLET_RE);
3174
+ if (b) {
3175
+ push(b[1], b[2] ?? "", void 0, line);
3176
+ continue;
3177
+ }
3178
+ }
3179
+ return out;
3180
+ }
3096
3181
  function parseRoadmapCandidates(markdown) {
3097
- return splitInitiatives(markdown).flatMap(parseBlock);
3182
+ const strict = splitInitiatives(markdown).flatMap(parseBlock);
3183
+ if (strict.length > 0) return strict;
3184
+ return parseFlexible(markdown);
3185
+ }
3186
+ function roadmapStats(markdown, materialized) {
3187
+ if (markdown == null) return { present: false, candidates: 0, materialized, remaining: 0 };
3188
+ const candidates = parseRoadmapCandidates(markdown).length;
3189
+ return { present: true, candidates, materialized, remaining: Math.max(0, candidates - materialized) };
3098
3190
  }
3099
3191
 
3100
3192
  // src/commands/create.ts
3101
3193
  var WORK_ITEMS_DIR = "knowledge/delivery/work-items";
3194
+ var DRAFT_DIR = `${WORK_ITEMS_DIR}/draft`;
3102
3195
  var ROADMAP_PATH = "knowledge/delivery/roadmap.md";
3103
3196
  function printStateGuidance(dir) {
3104
3197
  let config;
@@ -3117,13 +3210,20 @@ function printStateGuidance(dir) {
3117
3210
  function nextWorkItemId(dir) {
3118
3211
  const wiDir = join(dir, WORK_ITEMS_DIR);
3119
3212
  if (!exists(wiDir)) return "WI-001";
3120
- const files = readDir(wiDir).filter((f) => f.endsWith(".md"));
3121
- const nums = files.map((f) => {
3122
- const m = f.match(/WI-(\d+)/);
3123
- return m ? parseInt(m[1], 10) : 0;
3124
- }).filter((n) => n > 0);
3125
- const next = nums.length > 0 ? Math.max(...nums) + 1 : 1;
3126
- return `WI-${String(next).padStart(3, "0")}`;
3213
+ let max = 0;
3214
+ const walk = (d) => {
3215
+ for (const entry of readDir(d)) {
3216
+ const full = join(d, entry);
3217
+ if (isFile(full)) {
3218
+ const m = entry.match(/WI-(\d+)/);
3219
+ if (m) max = Math.max(max, parseInt(m[1], 10));
3220
+ } else if (!entry.startsWith(".")) {
3221
+ walk(full);
3222
+ }
3223
+ }
3224
+ };
3225
+ walk(wiDir);
3226
+ return `WI-${String(max + 1).padStart(3, "0")}`;
3127
3227
  }
3128
3228
  function slugify(s) {
3129
3229
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
@@ -3139,7 +3239,9 @@ function buildFrontMatter(id, type, level, title, answers) {
3139
3239
  `id: ${id}`,
3140
3240
  `title: "${title}"`,
3141
3241
  `knowledge_level: ${level}`,
3142
- `status: in-progress`,
3242
+ `status: draft`,
3243
+ `phase: now`,
3244
+ `initiative:`,
3143
3245
  `domains: []`,
3144
3246
  `code: []`,
3145
3247
  `created_at: ${today}`,
@@ -3191,6 +3293,14 @@ ${answers.design}
3191
3293
  ${answers.risks}
3192
3294
  `);
3193
3295
  }
3296
+ sections.push(`## Out of scope
3297
+
3298
+ _Not included in this Work Item._
3299
+ `);
3300
+ sections.push(`## Validation
3301
+
3302
+ _How will this be validated?_
3303
+ `);
3194
3304
  if (qualityGate.length > 0) {
3195
3305
  sections.push(`## Definition of Done
3196
3306
 
@@ -3215,7 +3325,9 @@ function buildModuleFrontMatter(id, modType, title, answers) {
3215
3325
  `id: ${id}`,
3216
3326
  `title: "${title}"`,
3217
3327
  `knowledge_level: ${modType.knowledgeLevel}`,
3218
- `status: in-progress`,
3328
+ `status: draft`,
3329
+ `phase: now`,
3330
+ `initiative:`,
3219
3331
  `domains: []`,
3220
3332
  `code: []`,
3221
3333
  `created_at: ${today}`,
@@ -3247,6 +3359,14 @@ ${answer}
3247
3359
  ${modType.qualityGate.map((g) => `- [ ] ${g}`).join("\n")}
3248
3360
  `);
3249
3361
  }
3362
+ sections.push(`## Out of scope
3363
+
3364
+ _Not included in this Work Item._
3365
+ `);
3366
+ sections.push(`## Validation
3367
+
3368
+ _How will this be validated?_
3369
+ `);
3250
3370
  sections.push(`## Learning
3251
3371
 
3252
3372
  _What did we learn from this change? Update after completion._
@@ -3296,7 +3416,7 @@ async function runCreate(type, opts = {}) {
3296
3416
  const id = nextWorkItemId(dir);
3297
3417
  const slug = slugify(title);
3298
3418
  const fileName = `${id}-${slug}.md`;
3299
- const filePath = join(dir, WORK_ITEMS_DIR, fileName);
3419
+ const filePath = join(dir, DRAFT_DIR, fileName);
3300
3420
  const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
3301
3421
  const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
3302
3422
  const content = `${frontMatter2}
@@ -3307,7 +3427,8 @@ ${body}`;
3307
3427
  process.exit(1);
3308
3428
  }
3309
3429
  writeFile(filePath, content);
3310
- log2.success(`Created ${WORK_ITEMS_DIR}/${fileName}`);
3430
+ log2.success(`Created ${DRAFT_DIR}/${fileName}`);
3431
+ log2.info(`New Work Items start in \`draft\`. Move to \`ready\` when scope and acceptance are set.`);
3311
3432
  log2.info(`Add code globs to the front matter \`code:\` field to enable Guard Lite.`);
3312
3433
  outro2(`Work item ${id} created.`);
3313
3434
  }
@@ -3336,14 +3457,14 @@ async function runCreateModule(dir, modType) {
3336
3457
  const id = nextWorkItemId(dir);
3337
3458
  const slug = slugify(title);
3338
3459
  const fileName = `${id}-${slug}.md`;
3339
- const filePath = join(dir, WORK_ITEMS_DIR, fileName);
3460
+ const filePath = join(dir, DRAFT_DIR, fileName);
3340
3461
  const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
3341
3462
  const body = buildModuleBody(modType, title.trim(), answers);
3342
3463
  const content = `${frontMatter2}
3343
3464
 
3344
3465
  ${body}`;
3345
3466
  writeFile(filePath, content);
3346
- log2.success(`Created ${WORK_ITEMS_DIR}/${fileName}`);
3467
+ log2.success(`Created ${DRAFT_DIR}/${fileName}`);
3347
3468
  log2.info(`Add code globs to the front matter \`code:\` field to enable Guard Lite.`);
3348
3469
  outro2(`Work item ${id} created.`);
3349
3470
  }
@@ -3361,13 +3482,16 @@ function resolveCandidateLevel(candidate, type) {
3361
3482
  function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
3362
3483
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3363
3484
  const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
3485
+ const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
3364
3486
  const lines = [
3365
3487
  "---",
3366
3488
  `type: ${type}`,
3367
3489
  `id: ${id}`,
3368
3490
  `title: "${title}"`,
3369
3491
  `knowledge_level: ${level}`,
3370
- `status: in-progress`,
3492
+ `status: draft`,
3493
+ `phase: now`,
3494
+ `initiative: "${initiative.replace(/"/g, "'")}"`,
3371
3495
  `domains: []`,
3372
3496
  `code: []`,
3373
3497
  `created_at: ${today}`,
@@ -3442,6 +3566,14 @@ ${candidate.notes}
3442
3566
  sections.push(`## Open Questions
3443
3567
 
3444
3568
  ${formatList(candidate.openQuestions)}
3569
+ `);
3570
+ sections.push(`## Out of scope
3571
+
3572
+ _Not included in this Work Item._
3573
+ `);
3574
+ sections.push(`## Validation
3575
+
3576
+ _How will this be validated?_
3445
3577
  `);
3446
3578
  if (qualityGate.length > 0)
3447
3579
  sections.push(`## Definition of Done
@@ -3524,8 +3656,8 @@ async function runCreateFromRoadmap(dir, cliType) {
3524
3656
  }
3525
3657
  const id = nextWorkItemId(dir);
3526
3658
  const { fileName, content } = buildRoadmapWorkItem({ id, type, level, candidate, answers });
3527
- writeFile(join(dir, WORK_ITEMS_DIR, fileName), content);
3528
- log2.success(`Created ${WORK_ITEMS_DIR}/${fileName}`);
3659
+ writeFile(join(dir, DRAFT_DIR, fileName), content);
3660
+ log2.success(`Created ${DRAFT_DIR}/${fileName}`);
3529
3661
  log2.info(`Traced to roadmap candidate ${candidate.id}${candidate.initiative?.id ? ` (initiative ${candidate.initiative.id})` : ""}.`);
3530
3662
  log2.info(`Add code globs to the front matter \`code:\` field to enable Guard Lite.`);
3531
3663
  outro2(`Work item ${id} created from roadmap.`);
@@ -3593,7 +3725,9 @@ function parseArtifact(filePath, raw) {
3593
3725
  knowledgeLevel: String(data.knowledge_level ?? ""),
3594
3726
  codeGlobs: globs,
3595
3727
  domains: Array.isArray(data.domains) ? data.domains : [],
3596
- status: String(data.status ?? "")
3728
+ status: String(data.status ?? ""),
3729
+ phase: String(data.phase ?? ""),
3730
+ initiative: String(data.initiative ?? data.source_initiative ?? "")
3597
3731
  };
3598
3732
  } catch {
3599
3733
  return null;
@@ -4428,6 +4562,52 @@ function renderLayersMarkdown(layers) {
4428
4562
  return lines.join("\n").trimEnd();
4429
4563
  }
4430
4564
 
4565
+ // src/core/lifecycle.ts
4566
+ var LIFECYCLE_STATES = [
4567
+ "draft",
4568
+ "ready",
4569
+ "in-progress",
4570
+ "blocked",
4571
+ "completed",
4572
+ "archived"
4573
+ ];
4574
+ var ACTIVE_STATES = ["draft", "ready", "in-progress", "blocked"];
4575
+ var LEGACY_STATUS_MAP = {
4576
+ done: "completed",
4577
+ cancelled: "archived",
4578
+ canceled: "archived",
4579
+ "in-progress": "in-progress"
4580
+ };
4581
+ var STATE_SET = new Set(LIFECYCLE_STATES);
4582
+ function isLifecycleState(s) {
4583
+ return STATE_SET.has(s);
4584
+ }
4585
+ function isActiveState(s) {
4586
+ return ACTIVE_STATES.includes(s);
4587
+ }
4588
+ function lifecycleFolderOf(filePath) {
4589
+ const p2 = filePath.replace(/\\/g, "/");
4590
+ const m = p2.match(/\/delivery\/work-items\/([^/]+)\//);
4591
+ if (m && isLifecycleState(m[1])) return m[1];
4592
+ return null;
4593
+ }
4594
+ function lifecycleStateOf(item) {
4595
+ const status = (item.status ?? "").trim().toLowerCase();
4596
+ if (isLifecycleState(status)) return status;
4597
+ if (status && LEGACY_STATUS_MAP[status]) return LEGACY_STATUS_MAP[status];
4598
+ const folder = lifecycleFolderOf(item.filePath ?? "");
4599
+ if (folder) return folder;
4600
+ return "ready";
4601
+ }
4602
+ function emptyLifecycleCounts() {
4603
+ return Object.fromEntries(LIFECYCLE_STATES.map((s) => [s, 0]));
4604
+ }
4605
+ function lifecycleCounts(states) {
4606
+ const counts = emptyLifecycleCounts();
4607
+ for (const s of states) counts[s]++;
4608
+ return counts;
4609
+ }
4610
+
4431
4611
  // src/core/project-explain.ts
4432
4612
  var ARCH_DIR3 = "knowledge";
4433
4613
  function first(values) {
@@ -4515,15 +4695,27 @@ function buildProjectExplanation(dir) {
4515
4695
  id: a.id || a.title,
4516
4696
  title: a.title,
4517
4697
  status: a.status,
4698
+ lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath }),
4699
+ initiative: a.initiative,
4518
4700
  knowledgeLevel: a.knowledgeLevel,
4519
4701
  hasOwnership: a.codeGlobs.length > 0,
4520
4702
  domains: a.domains
4521
4703
  }));
4704
+ const byState = lifecycleCounts(items.map((i) => i.lifecycle));
4705
+ const initiativeMap = /* @__PURE__ */ new Map();
4706
+ for (const i of items) {
4707
+ const name = i.initiative || "Unassigned";
4708
+ if (!initiativeMap.has(name)) initiativeMap.set(name, emptyLifecycleCounts());
4709
+ initiativeMap.get(name)[i.lifecycle]++;
4710
+ }
4711
+ const initiatives = [...initiativeMap.entries()].map(([name, states]) => ({ name, states })).sort((a, b) => a.name.localeCompare(b.name));
4522
4712
  const workItems = {
4523
4713
  total: items.length,
4524
- inProgress: items.filter((i) => i.status === "in-progress").length,
4714
+ inProgress: items.filter((i) => i.lifecycle === "in-progress").length,
4525
4715
  done: items.filter((i) => i.status === "done").length,
4526
4716
  cancelled: items.filter((i) => i.status === "cancelled").length,
4717
+ byState,
4718
+ initiatives,
4527
4719
  items
4528
4720
  };
4529
4721
  const withOwnership = items.filter((i) => i.hasOwnership).length;
@@ -4533,6 +4725,9 @@ function buildProjectExplanation(dir) {
4533
4725
  workItemsMissingOwnership: items.length - withOwnership
4534
4726
  };
4535
4727
  const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
4728
+ const roadmapPath = join(dir, ARCH_DIR3, "delivery", "roadmap.md");
4729
+ const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
4730
+ const roadmap = roadmapStats(roadmapMd, items.length);
4536
4731
  const mappedModules = loadMappedModules(dir);
4537
4732
  const missingKnowledge = [];
4538
4733
  if (!knowledge.hasScan) missingKnowledge.push("Scan baseline (.kaddo/scan.json)");
@@ -4560,8 +4755,12 @@ function buildProjectExplanation(dir) {
4560
4755
  }
4561
4756
  if (!knowledge.hasRoadmap) {
4562
4757
  suggestedNextSteps.push("Use roadmap-agent to generate knowledge/delivery/roadmap.md.");
4758
+ } else if (roadmap.remaining > 0) {
4759
+ suggestedNextSteps.push(
4760
+ `Materialize ${roadmap.remaining} roadmap candidate(s) with \`kaddo create --from roadmap\`.`
4761
+ );
4563
4762
  }
4564
- if (items.length === 0) {
4763
+ if (items.length === 0 && !roadmap.present) {
4565
4764
  suggestedNextSteps.push("Create your first Work Item with `kaddo create`.");
4566
4765
  } else if (ownership.workItemsMissingOwnership > 0) {
4567
4766
  suggestedNextSteps.push(
@@ -4576,6 +4775,7 @@ function buildProjectExplanation(dir) {
4576
4775
  ownership,
4577
4776
  domains,
4578
4777
  layers,
4778
+ roadmap,
4579
4779
  mappedModules,
4580
4780
  missingKnowledge,
4581
4781
  suggestedNextSteps
@@ -4584,6 +4784,14 @@ function buildProjectExplanation(dir) {
4584
4784
  function stateLabel(state) {
4585
4785
  return state === "pre-ai" ? "pre-ai" : state;
4586
4786
  }
4787
+ var LIFECYCLE_LABEL = {
4788
+ draft: "Draft",
4789
+ ready: "Ready",
4790
+ "in-progress": "In Progress",
4791
+ blocked: "Blocked",
4792
+ completed: "Completed",
4793
+ archived: "Archived"
4794
+ };
4587
4795
  function renderExplanationHuman(exp) {
4588
4796
  const lines = [];
4589
4797
  lines.push("# Project Explanation");
@@ -4619,14 +4827,41 @@ function renderExplanationHuman(exp) {
4619
4827
  lines.push(`- Tech: ${ls("Tech")}`);
4620
4828
  lines.push(`- Delivery: ${ls("Delivery")}`);
4621
4829
  lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
4622
- lines.push(`- Work items: ${exp.workItems.total}`);
4830
+ if (exp.roadmap.present) {
4831
+ lines.push(`- Roadmap candidates: ${exp.roadmap.candidates}`);
4832
+ lines.push(`- Materialized work items: ${exp.roadmap.materialized}`);
4833
+ if (exp.roadmap.remaining > 0)
4834
+ lines.push(`- Remaining candidates: ${exp.roadmap.remaining}`);
4835
+ } else {
4836
+ lines.push(`- Work items: ${exp.workItems.total}`);
4837
+ }
4623
4838
  lines.push(
4624
4839
  `- Ownership coverage: ${exp.ownership.workItemsWithOwnership}/${exp.ownership.workItemsTotal} work items`
4625
4840
  );
4626
4841
  lines.push("");
4627
- const active = exp.workItems.items.filter((i) => i.status === "in-progress");
4842
+ if (exp.workItems.total > 0) {
4843
+ lines.push("## Work Items");
4844
+ for (const s of LIFECYCLE_STATES) {
4845
+ lines.push(`- ${LIFECYCLE_LABEL[s]}: ${exp.workItems.byState[s]}`);
4846
+ }
4847
+ lines.push("");
4848
+ const grouped = exp.workItems.initiatives.filter(
4849
+ (g) => LIFECYCLE_STATES.some((s) => g.states[s] > 0)
4850
+ );
4851
+ if (grouped.length > 0) {
4852
+ lines.push("## Work Items by Initiative");
4853
+ for (const g of grouped) {
4854
+ const parts = LIFECYCLE_STATES.filter((s) => g.states[s] > 0).map(
4855
+ (s) => `${LIFECYCLE_LABEL[s]}: ${g.states[s]}`
4856
+ );
4857
+ lines.push(`- ${g.name} \u2014 ${parts.join(" \xB7 ")}`);
4858
+ }
4859
+ lines.push("");
4860
+ }
4861
+ }
4862
+ const active = exp.workItems.items.filter((i) => i.lifecycle === "in-progress");
4628
4863
  if (active.length > 0) {
4629
- lines.push("## Active Work Items");
4864
+ lines.push("## In Progress");
4630
4865
  for (const wi of active) {
4631
4866
  const level = wi.knowledgeLevel ? ` [${wi.knowledgeLevel}]` : "";
4632
4867
  const owned = wi.hasOwnership ? " \u25CF" : " \u25CB";
@@ -4938,6 +5173,7 @@ function toContextWorkItem(a) {
4938
5173
  type: a.type,
4939
5174
  title: a.title,
4940
5175
  status: a.status,
5176
+ lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath }),
4941
5177
  knowledgeLevel: a.knowledgeLevel,
4942
5178
  domains: a.domains
4943
5179
  };
@@ -4945,6 +5181,9 @@ function toContextWorkItem(a) {
4945
5181
  function toContextArtifact(a) {
4946
5182
  return { id: a.id, type: a.type, title: a.title, summary: a.summary, codeGlobs: a.codeGlobs };
4947
5183
  }
5184
+ function isDeliveryWorkItem(a) {
5185
+ return a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type);
5186
+ }
4948
5187
  function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
4949
5188
  const missing = [];
4950
5189
  const scanJson = readScanJson(dir);
@@ -4967,12 +5206,17 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
4967
5206
  const archPath = join(dir, ARCH_DIR5);
4968
5207
  const allArtifacts = exists(archPath) ? readArtifacts(archPath) : [];
4969
5208
  const workItems = allArtifacts.filter(
4970
- (a) => a.type && a.type !== "current-state" && a.type !== "roadmap"
5209
+ (a) => isDeliveryWorkItem(a) && isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))
4971
5210
  );
4972
5211
  if (workItems.length === 0) {
4973
5212
  missing.push("No work items found.");
4974
5213
  }
4975
5214
  const state = config.project.state;
5215
+ const roadmapPath = join(dir, ARCH_DIR5, "delivery", "roadmap.md");
5216
+ const materialized = allArtifacts.filter(
5217
+ (a) => a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type)
5218
+ ).length;
5219
+ const roadmap = roadmapStats(exists(roadmapPath) ? readFile(roadmapPath) : null, materialized);
4976
5220
  const mappedModules = loadMappedModules(dir);
4977
5221
  const layers = knowledgeLayers(dir);
4978
5222
  return {
@@ -4999,9 +5243,10 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
4999
5243
  roadmapSummary,
5000
5244
  inventoryAvailable,
5001
5245
  workItems: workItems.map(toContextWorkItem),
5002
- artifacts: workItems.filter((a) => a.codeGlobs.length > 0).map(toContextArtifact)
5246
+ artifacts: allArtifacts.filter((a) => a.codeGlobs.length > 0).map(toContextArtifact)
5003
5247
  },
5004
5248
  layers,
5249
+ roadmap,
5005
5250
  mappedModules,
5006
5251
  missing,
5007
5252
  handoff: {
@@ -5076,18 +5321,32 @@ function renderContextPack(pack) {
5076
5321
  parts.push("## Current Knowledge\n");
5077
5322
  parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
5078
5323
  parts.push("## Roadmap\n");
5324
+ if (pack.roadmap.present) {
5325
+ parts.push(
5326
+ [
5327
+ `- Roadmap candidates: ${pack.roadmap.candidates}`,
5328
+ `- Materialized work items: ${pack.roadmap.materialized}`,
5329
+ `- Remaining candidates: ${pack.roadmap.remaining}`
5330
+ ].join("\n") + "\n"
5331
+ );
5332
+ if (pack.roadmap.remaining > 0) {
5333
+ parts.push(
5334
+ "Candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
5335
+ );
5336
+ }
5337
+ }
5079
5338
  parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
5080
- parts.push("## Existing Work Items\n");
5339
+ parts.push("## Active Work Items\n");
5081
5340
  if (knowledge.workItems.length > 0) {
5082
5341
  const lines = knowledge.workItems.map((wi) => {
5083
5342
  const level = wi.knowledgeLevel ? ` [${wi.knowledgeLevel}]` : "";
5084
- const status = wi.status ? ` (${wi.status})` : "";
5343
+ const status = wi.lifecycle ? ` (${wi.lifecycle})` : wi.status ? ` (${wi.status})` : "";
5085
5344
  const domains = wi.domains.length > 0 ? ` \xB7 domains: ${wi.domains.join(", ")}` : "";
5086
5345
  return `- ${wi.id || wi.title} [${wi.type}]${level}${status} \u2014 ${wi.title}${domains}`;
5087
5346
  });
5088
5347
  parts.push(lines.join("\n") + "\n");
5089
5348
  } else {
5090
- parts.push("No work items found.\n");
5349
+ parts.push("No active work items found.\n");
5091
5350
  }
5092
5351
  parts.push("## Artifacts and Ownership\n");
5093
5352
  if (knowledge.artifacts.length > 0) {
@@ -5339,7 +5598,9 @@ function toActive(a) {
5339
5598
  function activeWorkItems(dir) {
5340
5599
  const archDir = join(dir, "knowledge");
5341
5600
  if (!exists(archDir)) return [];
5342
- return readArtifacts(archDir).filter((a) => isWorkItem(a) && a.status === "in-progress").map(toActive);
5601
+ return readArtifacts(archDir).filter(
5602
+ (a) => isWorkItem(a) && lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "in-progress"
5603
+ ).map(toActive);
5343
5604
  }
5344
5605
  function branchPrefix(type) {
5345
5606
  switch (type) {
@@ -5439,8 +5700,11 @@ function runUnderstand() {
5439
5700
  utilities: ["Use legacy-agent to surface risks and unknowns"]
5440
5701
  };
5441
5702
  if (roadmapHasUnmaterializedCandidates(dir)) {
5703
+ const { candidates, materialized, remaining } = pack.roadmap;
5442
5704
  console.log("");
5443
- console.log("The roadmap has Work Item candidates that are not materialized yet.");
5705
+ console.log(
5706
+ `The roadmap has ${remaining} unmaterialized Work Item candidate(s) (${candidates} candidate(s), ${materialized} materialized).`
5707
+ );
5444
5708
  console.log(" \u2192 Run `kaddo create --from roadmap`, or use the work-item-agent to");
5445
5709
  console.log(" materialize them into knowledge/delivery/work-items/.");
5446
5710
  }
@@ -5454,6 +5718,24 @@ function runUnderstand() {
5454
5718
  if (groupAgents.length > 0) {
5455
5719
  console.log(`Agents for this phase: ${groupAgents.join(", ")}`);
5456
5720
  }
5721
+ const exp = buildProjectExplanation(dir);
5722
+ if (exp.workItems.total > 0) {
5723
+ const bs = exp.workItems.byState;
5724
+ console.log("");
5725
+ console.log("Current active work:");
5726
+ console.log(` Draft: ${bs.draft} Ready: ${bs.ready} In Progress: ${bs["in-progress"]} Blocked: ${bs.blocked}`);
5727
+ const firstReady = exp.workItems.items.find((i) => i.lifecycle === "ready");
5728
+ const firstInProgress = exp.workItems.items.find((i) => i.lifecycle === "in-progress");
5729
+ if (firstInProgress) {
5730
+ console.log(` \u2192 Continue ${firstInProgress.id} \u2014 ${firstInProgress.title} (in progress).`);
5731
+ } else if (firstReady) {
5732
+ console.log(` \u2192 Recommended next step: start ${firstReady.id} \u2014 ${firstReady.title}.`);
5733
+ } else if (bs.draft > 0) {
5734
+ console.log(" \u2192 Refine a draft Work Item to `ready` (scope + acceptance defined).");
5735
+ } else if (bs.blocked > 0) {
5736
+ console.log(" \u2192 All active work is blocked. Resolve the blockers to move forward.");
5737
+ }
5738
+ }
5457
5739
  const active = activeWorkItems(dir);
5458
5740
  if (active.length > 0) {
5459
5741
  console.log("");
@@ -6371,11 +6653,12 @@ var WORK_ITEM = `---
6371
6653
  type: feature
6372
6654
  id: WI-001
6373
6655
  title: "Short, action-oriented title"
6374
- status: in-progress
6656
+ status: draft
6375
6657
  knowledge_level: K2
6376
6658
  source: manual
6377
6659
  source_id:
6378
- source_initiative:
6660
+ phase: now
6661
+ initiative:
6379
6662
  domains: []
6380
6663
  capabilities: []
6381
6664
  code: []
@@ -6406,6 +6689,14 @@ _Optional. Key decisions or approach. Keep it minimal at low knowledge levels._
6406
6689
 
6407
6690
  _Optional. What could go wrong?_
6408
6691
 
6692
+ ## Out of scope
6693
+
6694
+ _What is intentionally not included in this Work Item?_
6695
+
6696
+ ## Validation
6697
+
6698
+ _How will this be validated?_
6699
+
6409
6700
  ## Definition of Done
6410
6701
 
6411
6702
  - [ ] Code merged
@@ -8145,7 +8436,7 @@ async function runBootstrap(dir = cwd()) {
8145
8436
 
8146
8437
  // src/index.ts
8147
8438
  var program = new Command();
8148
- program.name("kaddo").description("Knowledge Driven Development toolkit").version("3.5.1");
8439
+ program.name("kaddo").description("Knowledge Driven Development toolkit").version("3.6.0");
8149
8440
  program.command("init").description("Initialize Kaddo in the current project").action(async () => {
8150
8441
  await runInit();
8151
8442
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.5.1",
3
+ "version": "3.7.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {