@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.
- package/dist/index.js +329 -38
- 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
|
|
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
|
|
1919
|
-
6.
|
|
1920
|
-
7.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3121
|
-
const
|
|
3122
|
-
const
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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 ${
|
|
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,
|
|
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 ${
|
|
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:
|
|
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,
|
|
3528
|
-
log2.success(`Created ${
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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("##
|
|
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
|
|
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:
|
|
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("##
|
|
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(
|
|
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(
|
|
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:
|
|
6656
|
+
status: draft
|
|
6375
6657
|
knowledge_level: K2
|
|
6376
6658
|
source: manual
|
|
6377
6659
|
source_id:
|
|
6378
|
-
|
|
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.
|
|
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
|
});
|