@kaddo/cli 3.43.0 → 3.45.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/README.md +2 -0
- package/dist/index.js +495 -180
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -542,6 +542,8 @@ create --from roadmap → owners → guard → explain`.
|
|
|
542
542
|
| v3.41 | Tech knowledge structure: `knowledge/tech/discovery/` for architecture-notes/decision-candidates (core vs decisions vs discovery); `kaddo adr` reads discovery-first with legacy fallback; `kaddo tech organize` migrates safely; `explain` shows `## Tech Knowledge` |
|
|
543
543
|
| v3.42 | Agent & skill version metadata: installed agents/skills carry a `version:`; `kaddo agents status` / `kaddo skills status` classify up-to-date/outdated/unknown-version/modified/missing; `agents update` / `skills update` refresh outdated safely (never overwrite edits without `--force`); MCP `kaddo://installed-assets` |
|
|
544
544
|
| v3.43 | Capability-grounded roadmap: each `RM-xxx` candidate is graded on related domain / capability / source signals; `roadmap_quality` surfaced in `explain`/`context`/`understand`; `create --from roadmap` preserves `source_roadmap_candidate` + related metadata into the Work Item; roadmap-agent emits grounded fields (never materializes Work Items); MCP `kaddo://roadmap-quality` |
|
|
545
|
+
| v3.44 | Roadmap counting alignment + materialization quality: `explain`/`context` separate **Roadmap initiatives** from **Work Item candidates** (`## Roadmap Status`); two-level `roadmapQuality` (initiatives + work_item_candidates); `create --from roadmap` normalizes metadata — fills `domains` from `related_domain`, splits comma-joined `related_capabilities` into a real list, carries `source_roadmap_initiative`/`source_work_item_candidate`/`source_signals`/`decision_candidates`, improved Source + Context-From-Roadmap body + ADR warning; MCP `kaddo://work-item-candidates` |
|
|
546
|
+
| v3.45 | State-aware next step: `resolveNextStep` decides from the real delivery state (draft/ready/in-progress, ownership, ADRs, adapters) instead of always suggesting `create --from roadmap` — draft → work-item-agent, ready → adapter/implementation-agent, in-progress → guard; parallel **secondary** recommendations (ownership, ADRs, remaining candidates); `deliveryState` + recommendation in `explain`/`context`; MCP `kaddo://next-step` |
|
|
545
547
|
|
|
546
548
|
**Optional modules (installed with `kaddo add`):**
|
|
547
549
|
|
package/dist/index.js
CHANGED
|
@@ -3032,13 +3032,22 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
|
|
|
3032
3032
|
without surfacing it. -->
|
|
3033
3033
|
\`\`\`
|
|
3034
3034
|
|
|
3035
|
-
### Preserve roadmap metadata (VS-077)
|
|
3035
|
+
### Preserve roadmap metadata (VS-077 / VS-078)
|
|
3036
3036
|
|
|
3037
|
-
When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3037
|
+
When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries the trace
|
|
3038
|
+
back to the roadmap. **Keep and refine \u2014 never delete** these fields:
|
|
3039
|
+
|
|
3040
|
+
- \`source_roadmap_initiative\` and \`source_work_item_candidate\` (the RM-xxx initiative and the
|
|
3041
|
+
WI-CANDIDATE-xxx it was materialized from)
|
|
3042
|
+
- \`related_domain\` and \`domains\` (keep them consistent \u2014 \`domains\` must not be empty when
|
|
3043
|
+
\`related_domain\` exists)
|
|
3044
|
+
- \`related_capabilities\` (a real list, one capability per item \u2014 never a single comma-joined string)
|
|
3045
|
+
- \`expected_value\`, \`risks\`, \`dependencies\`
|
|
3046
|
+
- \`source_signals\` (do **not** invent them \u2014 if absent, leave them absent)
|
|
3047
|
+
- \`decision_candidates\` and \`related_decisions\` (when the work depends on a technical decision)
|
|
3048
|
+
|
|
3049
|
+
Do not drop the trace back to the capability domain and source signals. If a Work Item depends on a
|
|
3050
|
+
tech decision candidate with no ADR yet, keep the warning surfaced in the body.
|
|
3042
3051
|
|
|
3043
3052
|
## Where to Save the Result
|
|
3044
3053
|
|
|
@@ -4530,7 +4539,12 @@ function splitInitiatives(markdown) {
|
|
|
4530
4539
|
return blocks;
|
|
4531
4540
|
}
|
|
4532
4541
|
function parseBlock(block) {
|
|
4533
|
-
const meta = {
|
|
4542
|
+
const meta = {
|
|
4543
|
+
relatedCapabilities: [],
|
|
4544
|
+
dependencies: [],
|
|
4545
|
+
openQuestions: [],
|
|
4546
|
+
sourceSignals: []
|
|
4547
|
+
};
|
|
4534
4548
|
const rawCandidates = [];
|
|
4535
4549
|
let listField = null;
|
|
4536
4550
|
let inCandidateSection = false;
|
|
@@ -4580,7 +4594,7 @@ function parseBlock(block) {
|
|
|
4580
4594
|
meta.impact = value || void 0;
|
|
4581
4595
|
} else if (key === "risk") {
|
|
4582
4596
|
meta.risk = value || void 0;
|
|
4583
|
-
} else if (key === "project area / domain" || key === "domain" || key === "project area") {
|
|
4597
|
+
} else if (key === "project area / domain" || key === "domain" || key === "project area" || key === "related domain") {
|
|
4584
4598
|
meta.domain = value || void 0;
|
|
4585
4599
|
} else if (key === "related capabilities") {
|
|
4586
4600
|
listField = "relatedCapabilities";
|
|
@@ -4591,6 +4605,9 @@ function parseBlock(block) {
|
|
|
4591
4605
|
} else if (key === "open questions") {
|
|
4592
4606
|
listField = "openQuestions";
|
|
4593
4607
|
if (value) meta.openQuestions.push(value);
|
|
4608
|
+
} else if (key === "source signals" || key === "source signal") {
|
|
4609
|
+
listField = "sourceSignals";
|
|
4610
|
+
if (value) meta.sourceSignals.push(value);
|
|
4594
4611
|
}
|
|
4595
4612
|
continue;
|
|
4596
4613
|
}
|
|
@@ -4608,6 +4625,7 @@ function parseBlock(block) {
|
|
|
4608
4625
|
}
|
|
4609
4626
|
}
|
|
4610
4627
|
flush();
|
|
4628
|
+
const decisionCandidates = decisionCandidatesFromSignals(meta.sourceSignals);
|
|
4611
4629
|
return rawCandidates.map((c) => ({
|
|
4612
4630
|
...c,
|
|
4613
4631
|
initiative: block.initiative.id || block.initiative.title ? { ...block.initiative } : void 0,
|
|
@@ -4616,9 +4634,23 @@ function parseBlock(block) {
|
|
|
4616
4634
|
impact: meta.impact,
|
|
4617
4635
|
risk: meta.risk,
|
|
4618
4636
|
dependencies: meta.dependencies.length ? [...meta.dependencies] : void 0,
|
|
4619
|
-
openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0
|
|
4637
|
+
openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0,
|
|
4638
|
+
sourceSignals: meta.sourceSignals.length ? [...meta.sourceSignals] : void 0,
|
|
4639
|
+
decisionCandidates: decisionCandidates.length ? decisionCandidates : void 0
|
|
4620
4640
|
}));
|
|
4621
4641
|
}
|
|
4642
|
+
function decisionCandidatesFromSignals(signals) {
|
|
4643
|
+
const out = [];
|
|
4644
|
+
for (const sig of signals) {
|
|
4645
|
+
const m = sig.match(/decision candidate\s*:?\s*(.+)$/i);
|
|
4646
|
+
if (!m) continue;
|
|
4647
|
+
const rest = m[1].trim();
|
|
4648
|
+
const token = rest.match(/\b[A-Z][A-Z0-9_]{2,}\b/);
|
|
4649
|
+
const value = token ? token[0] : rest.replace(/[.。]+$/, "").trim();
|
|
4650
|
+
if (value && !out.includes(value)) out.push(value);
|
|
4651
|
+
}
|
|
4652
|
+
return out;
|
|
4653
|
+
}
|
|
4622
4654
|
var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
|
|
4623
4655
|
var HEADING_RE = /^#{2,4}\s+(.*)$/;
|
|
4624
4656
|
var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
|
|
@@ -4700,10 +4732,51 @@ function parseRoadmapCandidates(markdown) {
|
|
|
4700
4732
|
if (strict.length > 0) return strict;
|
|
4701
4733
|
return parseFlexible(markdown);
|
|
4702
4734
|
}
|
|
4735
|
+
function countRoadmapInitiatives(markdown) {
|
|
4736
|
+
if (markdown == null) return 0;
|
|
4737
|
+
let n = 0;
|
|
4738
|
+
for (const line of markdown.split(/\r?\n/)) if (INITIATIVE_RE.test(line)) n++;
|
|
4739
|
+
return n;
|
|
4740
|
+
}
|
|
4703
4741
|
function roadmapStats(markdown, materialized) {
|
|
4704
|
-
if (markdown == null)
|
|
4742
|
+
if (markdown == null)
|
|
4743
|
+
return {
|
|
4744
|
+
present: false,
|
|
4745
|
+
initiatives: 0,
|
|
4746
|
+
work_item_candidates: 0,
|
|
4747
|
+
materialized_work_items: materialized,
|
|
4748
|
+
remaining_work_item_candidates: 0,
|
|
4749
|
+
candidates: 0,
|
|
4750
|
+
materialized,
|
|
4751
|
+
remaining: 0
|
|
4752
|
+
};
|
|
4705
4753
|
const candidates = parseRoadmapCandidates(markdown).length;
|
|
4706
|
-
|
|
4754
|
+
const remaining = Math.max(0, candidates - materialized);
|
|
4755
|
+
return {
|
|
4756
|
+
present: true,
|
|
4757
|
+
initiatives: countRoadmapInitiatives(markdown),
|
|
4758
|
+
work_item_candidates: candidates,
|
|
4759
|
+
materialized_work_items: materialized,
|
|
4760
|
+
remaining_work_item_candidates: remaining,
|
|
4761
|
+
candidates,
|
|
4762
|
+
materialized,
|
|
4763
|
+
remaining
|
|
4764
|
+
};
|
|
4765
|
+
}
|
|
4766
|
+
function normalizeCapabilityList(raw) {
|
|
4767
|
+
if (!raw || raw.length === 0) return [];
|
|
4768
|
+
const out = [];
|
|
4769
|
+
for (const entry of raw) {
|
|
4770
|
+
for (const part of entry.split(/[,;]/)) {
|
|
4771
|
+
const cap = part.replace(/[.。]+\s*$/, "").trim();
|
|
4772
|
+
if (cap && !out.includes(cap)) out.push(cap);
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
return out;
|
|
4776
|
+
}
|
|
4777
|
+
function domainsFromRelated(domain) {
|
|
4778
|
+
const d = domain?.trim();
|
|
4779
|
+
return d ? [d] : [];
|
|
4707
4780
|
}
|
|
4708
4781
|
|
|
4709
4782
|
// src/commands/create.ts
|
|
@@ -4999,32 +5072,44 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
|
|
|
4999
5072
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5000
5073
|
const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
|
|
5001
5074
|
const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
|
|
5075
|
+
const q = (s) => s.replace(/"/g, "'");
|
|
5076
|
+
const domains = domainsFromRelated(candidate.domain);
|
|
5077
|
+
const relatedCapabilities = normalizeCapabilityList(candidate.relatedCapabilities);
|
|
5078
|
+
const risks = candidate.risk ? [candidate.risk] : [];
|
|
5079
|
+
const dependencies = candidate.dependencies ?? [];
|
|
5080
|
+
const sourceSignals = candidate.sourceSignals ?? [];
|
|
5081
|
+
const decisionCandidates = candidate.decisionCandidates ?? [];
|
|
5082
|
+
const yamlList2 = (key, items) => items.length === 0 ? [] : [`${key}:`, ...items.map((i) => ` - "${q(i)}"`)];
|
|
5002
5083
|
const lines = [
|
|
5003
5084
|
"---",
|
|
5004
5085
|
`type: ${type}`,
|
|
5005
5086
|
`id: ${id}`,
|
|
5006
|
-
`title: "${title}"`,
|
|
5087
|
+
`title: "${q(title)}"`,
|
|
5007
5088
|
`knowledge_level: ${level}`,
|
|
5008
5089
|
`status: draft`,
|
|
5009
5090
|
`phase: now`,
|
|
5010
|
-
`initiative: "${initiative
|
|
5011
|
-
|
|
5091
|
+
`initiative: "${q(initiative)}"`,
|
|
5092
|
+
...yamlList2("domains", domains),
|
|
5093
|
+
...domains.length === 0 ? ["domains: []"] : [],
|
|
5012
5094
|
`code: []`,
|
|
5013
5095
|
`created_at: ${today2}`,
|
|
5014
5096
|
`source: roadmap`,
|
|
5015
5097
|
`source_id: ${candidate.id}`,
|
|
5016
5098
|
`source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
|
|
5017
|
-
// Capability-grounded traceability (VS-077):
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
...candidate.expectedValue ? [`expected_value: "${candidate.expectedValue
|
|
5025
|
-
...
|
|
5026
|
-
...
|
|
5027
|
-
|
|
5099
|
+
// Capability-grounded traceability (VS-077 / VS-078): distinguish the parent roadmap initiative
|
|
5100
|
+
// from the specific Work Item candidate it was materialized from.
|
|
5101
|
+
`source_roadmap_initiative: ${candidate.initiative?.id ?? "unknown"}`,
|
|
5102
|
+
`source_work_item_candidate: ${candidate.id}`,
|
|
5103
|
+
...candidate.initiative?.title ? [`source_initiative_title: "${q(candidate.initiative.title)}"`] : [],
|
|
5104
|
+
...candidate.domain ? [`related_domain: "${q(candidate.domain)}"`] : [],
|
|
5105
|
+
...yamlList2("related_capabilities", relatedCapabilities),
|
|
5106
|
+
...candidate.expectedValue ? [`expected_value: "${q(candidate.expectedValue)}"`] : [],
|
|
5107
|
+
...yamlList2("risks", risks),
|
|
5108
|
+
...yamlList2("dependencies", dependencies),
|
|
5109
|
+
...yamlList2("source_signals", sourceSignals),
|
|
5110
|
+
...yamlList2("decision_candidates", decisionCandidates),
|
|
5111
|
+
...decisionCandidates.length > 0 ? ["related_decisions: []"] : [],
|
|
5112
|
+
`summary: "${q(summary)}"`,
|
|
5028
5113
|
"---"
|
|
5029
5114
|
];
|
|
5030
5115
|
return lines.join("\n");
|
|
@@ -5036,14 +5121,24 @@ function buildRoadmapBody(type, level, title, candidate, answers, qualityGate) {
|
|
|
5036
5121
|
sections.push(`> Type: ${type} \xB7 Level: ${level}
|
|
5037
5122
|
`);
|
|
5038
5123
|
const initiativeLabel = candidate.initiative ? `${candidate.initiative.id ?? ""}${candidate.initiative.title ? ` \u2014 ${candidate.initiative.title}` : ""}`.trim() : "unknown";
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
);
|
|
5124
|
+
const relatedCapabilities = normalizeCapabilityList(candidate.relatedCapabilities);
|
|
5125
|
+
const sourceLines = [
|
|
5126
|
+
"## Source\n",
|
|
5127
|
+
`- Source: roadmap`,
|
|
5128
|
+
`- Roadmap Initiative: ${initiativeLabel || "unknown"}`,
|
|
5129
|
+
`- Work Item Candidate: ${candidate.id}`
|
|
5130
|
+
];
|
|
5131
|
+
if (candidate.domain) sourceLines.push(`- Related domain: ${candidate.domain}`);
|
|
5132
|
+
if (relatedCapabilities.length) {
|
|
5133
|
+
sourceLines.push("- Related capabilities:");
|
|
5134
|
+
relatedCapabilities.forEach((c) => sourceLines.push(` - ${c}`));
|
|
5135
|
+
}
|
|
5136
|
+
sections.push(sourceLines.join("\n") + "\n");
|
|
5137
|
+
if (candidate.decisionCandidates?.length) {
|
|
5138
|
+
sections.push(
|
|
5139
|
+
"> Warning: This Work Item is related to technical decision candidates that have not been materialized as ADRs.\n"
|
|
5140
|
+
);
|
|
5141
|
+
}
|
|
5047
5142
|
if (answers.problem) sections.push(`## Problem
|
|
5048
5143
|
|
|
5049
5144
|
${answers.problem}
|
|
@@ -5053,22 +5148,22 @@ ${answers.problem}
|
|
|
5053
5148
|
|
|
5054
5149
|
${expectedValue}
|
|
5055
5150
|
`);
|
|
5056
|
-
const
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
if (candidate.
|
|
5060
|
-
if (candidate.impact) ctx.push(`**Impact:** ${candidate.impact}`);
|
|
5061
|
-
if (candidate.risk) ctx.push(`**Risk:** ${candidate.risk}`);
|
|
5151
|
+
const initiativeRef = candidate.initiative?.id ? `roadmap initiative ${candidate.initiative.id}` : "a roadmap candidate";
|
|
5152
|
+
const ctx = [`This Work Item was materialized from ${initiativeRef}.`, ""];
|
|
5153
|
+
if (candidate.expectedValue) ctx.push(`**Expected value:** ${candidate.expectedValue}`);
|
|
5154
|
+
if (candidate.risk) ctx.push(`**Risks:** ${candidate.risk}`);
|
|
5062
5155
|
if (candidate.dependencies?.length)
|
|
5063
|
-
ctx.push(`**Dependencies:**
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5156
|
+
ctx.push(`**Dependencies:** ${candidate.dependencies.join("; ")}`);
|
|
5157
|
+
if (candidate.sourceSignals?.length) {
|
|
5158
|
+
ctx.push(`**Source signals:**
|
|
5159
|
+
${formatList(candidate.sourceSignals)}`);
|
|
5160
|
+
} else {
|
|
5161
|
+
ctx.push("**Source signals:** _Not provided in roadmap._");
|
|
5162
|
+
}
|
|
5163
|
+
sections.push(`## Context From Roadmap
|
|
5068
5164
|
|
|
5069
|
-
${
|
|
5070
|
-
`
|
|
5071
|
-
);
|
|
5165
|
+
${ctx.join("\n\n")}
|
|
5166
|
+
`);
|
|
5072
5167
|
if (answers.acceptance_criteria) {
|
|
5073
5168
|
const items = answers.acceptance_criteria.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
5074
5169
|
sections.push(`## Acceptance Criteria
|
|
@@ -8018,7 +8113,124 @@ function buildSharedFileStatuses(statuses) {
|
|
|
8018
8113
|
}));
|
|
8019
8114
|
}
|
|
8020
8115
|
|
|
8116
|
+
// src/core/decisions.ts
|
|
8117
|
+
var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
|
|
8118
|
+
var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
|
|
8119
|
+
var DECISIONS_DIR = "knowledge/tech/decisions";
|
|
8120
|
+
function resolveCandidatesPath(dir) {
|
|
8121
|
+
const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
|
|
8122
|
+
const legacy = exists(join(dir, CANDIDATES_LEGACY));
|
|
8123
|
+
if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
|
|
8124
|
+
if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
|
|
8125
|
+
return { path: null, legacy: false, bothExist: false };
|
|
8126
|
+
}
|
|
8127
|
+
function cleanCandidateTitle(title) {
|
|
8128
|
+
return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
|
|
8129
|
+
}
|
|
8130
|
+
function slugify2(s) {
|
|
8131
|
+
return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
|
|
8132
|
+
}
|
|
8133
|
+
function parseDecisionCandidates(md) {
|
|
8134
|
+
const out = [];
|
|
8135
|
+
for (const line of md.split(/\r?\n/)) {
|
|
8136
|
+
const m = line.match(/^##\s+(.+?)\s*$/);
|
|
8137
|
+
if (m) {
|
|
8138
|
+
const raw = m[1].trim();
|
|
8139
|
+
if (!raw || /^_.*_$/.test(raw)) continue;
|
|
8140
|
+
const t = cleanCandidateTitle(raw);
|
|
8141
|
+
if (t) out.push(t);
|
|
8142
|
+
}
|
|
8143
|
+
}
|
|
8144
|
+
return out;
|
|
8145
|
+
}
|
|
8146
|
+
function countAdrs(dir) {
|
|
8147
|
+
const base = join(dir, DECISIONS_DIR);
|
|
8148
|
+
if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
|
|
8149
|
+
let total = 0;
|
|
8150
|
+
let draft = 0;
|
|
8151
|
+
let accepted = 0;
|
|
8152
|
+
for (const entry of readDir(base)) {
|
|
8153
|
+
if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
|
|
8154
|
+
const full = join(base, entry);
|
|
8155
|
+
if (!isFile(full)) continue;
|
|
8156
|
+
total += 1;
|
|
8157
|
+
let content = "";
|
|
8158
|
+
try {
|
|
8159
|
+
content = readFile(full);
|
|
8160
|
+
} catch {
|
|
8161
|
+
continue;
|
|
8162
|
+
}
|
|
8163
|
+
const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
|
|
8164
|
+
if (status === "accepted") accepted += 1;
|
|
8165
|
+
else draft += 1;
|
|
8166
|
+
}
|
|
8167
|
+
return { total, draft, accepted };
|
|
8168
|
+
}
|
|
8169
|
+
function buildTechDecisions(dir) {
|
|
8170
|
+
const resolved = resolveCandidatesPath(dir);
|
|
8171
|
+
let titles = [];
|
|
8172
|
+
if (resolved.path) {
|
|
8173
|
+
try {
|
|
8174
|
+
titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
|
|
8175
|
+
} catch {
|
|
8176
|
+
titles = [];
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
const { total, draft, accepted } = countAdrs(dir);
|
|
8180
|
+
const candidate_list = titles.map((title, i) => {
|
|
8181
|
+
const n = String(total + i + 1).padStart(3, "0");
|
|
8182
|
+
return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
|
|
8183
|
+
});
|
|
8184
|
+
let status;
|
|
8185
|
+
if (accepted > 0) status = "accepted-adrs";
|
|
8186
|
+
else if (total > 0) status = "draft-adrs";
|
|
8187
|
+
else if (titles.length > 0) status = "candidates";
|
|
8188
|
+
else status = "none";
|
|
8189
|
+
return {
|
|
8190
|
+
status,
|
|
8191
|
+
candidates: titles.length,
|
|
8192
|
+
adrs: total,
|
|
8193
|
+
draft_adrs: draft,
|
|
8194
|
+
accepted_adrs: accepted,
|
|
8195
|
+
candidate_list,
|
|
8196
|
+
candidates_source: resolved.path,
|
|
8197
|
+
candidates_legacy_location: resolved.legacy,
|
|
8198
|
+
candidates_both_exist: resolved.bothExist
|
|
8199
|
+
};
|
|
8200
|
+
}
|
|
8201
|
+
|
|
8021
8202
|
// src/core/next-step.ts
|
|
8203
|
+
function buildDeliveryState(dir) {
|
|
8204
|
+
const wis = discoverWorkItems(dir);
|
|
8205
|
+
const byState = (s) => wis.filter((w) => w.lifecycle === s).length;
|
|
8206
|
+
const total = wis.length;
|
|
8207
|
+
const withOwnership = wis.filter((w) => w.codeGlobs.length > 0).length;
|
|
8208
|
+
const td = buildTechDecisions(dir);
|
|
8209
|
+
const roadmapPath = join(dir, "knowledge/delivery/roadmap.md");
|
|
8210
|
+
const roadmapMd = exists(roadmapPath) ? safeRead(roadmapPath) : null;
|
|
8211
|
+
const stats = roadmapStats(roadmapMd, total);
|
|
8212
|
+
const adapters = installedAdapters(dir);
|
|
8213
|
+
return {
|
|
8214
|
+
phase: "",
|
|
8215
|
+
draft_work_items: byState("draft"),
|
|
8216
|
+
ready_work_items: byState("ready"),
|
|
8217
|
+
in_progress_work_items: byState("in-progress"),
|
|
8218
|
+
blocked_work_items: byState("blocked"),
|
|
8219
|
+
total_work_items: total,
|
|
8220
|
+
ownership_coverage: `${withOwnership}/${total}`,
|
|
8221
|
+
remaining_work_item_candidates: stats.remaining_work_item_candidates,
|
|
8222
|
+
decision_candidates: td.candidates,
|
|
8223
|
+
accepted_adrs: td.accepted_adrs,
|
|
8224
|
+
adapters_installed: adapters.length
|
|
8225
|
+
};
|
|
8226
|
+
}
|
|
8227
|
+
function safeRead(p2) {
|
|
8228
|
+
try {
|
|
8229
|
+
return readFile(p2);
|
|
8230
|
+
} catch {
|
|
8231
|
+
return null;
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8022
8234
|
function roadmapSignal(dir) {
|
|
8023
8235
|
const p2 = join(dir, "knowledge/delivery/roadmap.md");
|
|
8024
8236
|
if (!exists(p2)) return "missing";
|
|
@@ -8115,15 +8327,117 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
8115
8327
|
if (roadmap !== "has-candidates") {
|
|
8116
8328
|
return { id: "roadmap", phase: "Planning", label: "Use roadmap-agent to define roadmap candidates (`kaddo roadmap`).", command: "kaddo roadmap", agent: "roadmap-agent", target: "knowledge/delivery/roadmap.md", reason: "The roadmap has no candidates yet." };
|
|
8117
8329
|
}
|
|
8118
|
-
const
|
|
8119
|
-
|
|
8120
|
-
|
|
8330
|
+
const st = buildDeliveryState(dir);
|
|
8331
|
+
const secondary = buildSecondaryRecommendations(st);
|
|
8332
|
+
if (st.total_work_items === 0) {
|
|
8333
|
+
return {
|
|
8334
|
+
id: "create-work-item",
|
|
8335
|
+
phase: "Delivery Preparation",
|
|
8336
|
+
label: "Run `kaddo create --from roadmap` to materialize the first Work Item.",
|
|
8337
|
+
command: "kaddo create --from roadmap",
|
|
8338
|
+
reason: "The roadmap has candidates but no Work Item exists yet.",
|
|
8339
|
+
...secondary.length ? { secondary } : {}
|
|
8340
|
+
};
|
|
8121
8341
|
}
|
|
8122
|
-
const adapters =
|
|
8123
|
-
if (
|
|
8124
|
-
|
|
8342
|
+
const adapters = st.adapters_installed;
|
|
8343
|
+
if (st.ready_work_items > 0) {
|
|
8344
|
+
if (adapters === 0) {
|
|
8345
|
+
return {
|
|
8346
|
+
id: "install-adapter",
|
|
8347
|
+
phase: "Active Delivery",
|
|
8348
|
+
label: "Install or configure an adapter before implementation (`kaddo adapters list`).",
|
|
8349
|
+
command: "kaddo adapters list",
|
|
8350
|
+
reason: `${st.ready_work_items} Work Item(s) are ready but no adapter is installed.`,
|
|
8351
|
+
...secondary.length ? { secondary } : {}
|
|
8352
|
+
};
|
|
8353
|
+
}
|
|
8354
|
+
return {
|
|
8355
|
+
id: "implement",
|
|
8356
|
+
phase: "Active Delivery",
|
|
8357
|
+
label: "Use the implementation-agent or your installed adapter to plan implementation.",
|
|
8358
|
+
agent: "implementation-agent",
|
|
8359
|
+
reason: `${st.ready_work_items} Work Item(s) are ready and an adapter is installed.`,
|
|
8360
|
+
...secondary.length ? { secondary } : {}
|
|
8361
|
+
};
|
|
8125
8362
|
}
|
|
8126
|
-
|
|
8363
|
+
if (st.in_progress_work_items > 0) {
|
|
8364
|
+
return {
|
|
8365
|
+
id: "guard",
|
|
8366
|
+
phase: "Active Delivery",
|
|
8367
|
+
label: "Run `kaddo guard` and update affected knowledge after significant changes.",
|
|
8368
|
+
command: "kaddo guard",
|
|
8369
|
+
reason: `${st.in_progress_work_items} Work Item(s) are in progress.`,
|
|
8370
|
+
...secondary.length ? { secondary } : {}
|
|
8371
|
+
};
|
|
8372
|
+
}
|
|
8373
|
+
if (st.draft_work_items > 0) {
|
|
8374
|
+
return {
|
|
8375
|
+
id: "refine-work-item",
|
|
8376
|
+
phase: "Active Delivery",
|
|
8377
|
+
label: "Refine the existing draft Work Item with the work-item-agent.",
|
|
8378
|
+
agent: "work-item-agent",
|
|
8379
|
+
skill: "work-item-refinement",
|
|
8380
|
+
reason: `There ${st.draft_work_items === 1 ? "is" : "are"} ${st.draft_work_items} draft Work Item${st.draft_work_items === 1 ? "" : "s"} and no Work Item is ready.`,
|
|
8381
|
+
...secondary.length ? { secondary } : {}
|
|
8382
|
+
};
|
|
8383
|
+
}
|
|
8384
|
+
if (st.blocked_work_items > 0) {
|
|
8385
|
+
return {
|
|
8386
|
+
id: "resolve-blocker",
|
|
8387
|
+
phase: "Active Delivery",
|
|
8388
|
+
label: "Resolve the blocker on the blocked Work Item with the work-item-agent.",
|
|
8389
|
+
agent: "work-item-agent",
|
|
8390
|
+
reason: `${st.blocked_work_items} Work Item(s) are blocked.`,
|
|
8391
|
+
...secondary.length ? { secondary } : {}
|
|
8392
|
+
};
|
|
8393
|
+
}
|
|
8394
|
+
if (st.remaining_work_item_candidates > 0) {
|
|
8395
|
+
return {
|
|
8396
|
+
id: "materialize-more-work-items",
|
|
8397
|
+
phase: "Maintenance",
|
|
8398
|
+
label: `Materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
|
|
8399
|
+
command: "kaddo create --from roadmap",
|
|
8400
|
+
reason: "No active Work Items remain; roadmap candidates are still pending.",
|
|
8401
|
+
...secondary.length ? { secondary } : {}
|
|
8402
|
+
};
|
|
8403
|
+
}
|
|
8404
|
+
return {
|
|
8405
|
+
id: "plan-next",
|
|
8406
|
+
phase: "Maintenance",
|
|
8407
|
+
label: "Use the roadmap-agent to plan the next initiative.",
|
|
8408
|
+
agent: "roadmap-agent",
|
|
8409
|
+
reason: "No active Work Items and no remaining roadmap candidates."
|
|
8410
|
+
};
|
|
8411
|
+
}
|
|
8412
|
+
function buildSecondaryRecommendations(st) {
|
|
8413
|
+
const out = [];
|
|
8414
|
+
const [withOwnership] = st.ownership_coverage.split("/").map(Number);
|
|
8415
|
+
if (st.total_work_items > 0 && withOwnership < st.total_work_items) {
|
|
8416
|
+
out.push({
|
|
8417
|
+
id: "suggest-ownership",
|
|
8418
|
+
label: "Run `kaddo owners suggest` for Work Items without code ownership.",
|
|
8419
|
+
command: "kaddo owners suggest",
|
|
8420
|
+
reason: `Ownership coverage is ${st.ownership_coverage}.`
|
|
8421
|
+
});
|
|
8422
|
+
}
|
|
8423
|
+
if (st.total_work_items > 0 && st.decision_candidates > 0 && st.accepted_adrs === 0) {
|
|
8424
|
+
out.push({
|
|
8425
|
+
id: "materialize-adrs",
|
|
8426
|
+
label: "Use the adr-writing skill (`kaddo adr`) to materialize decision candidates into ADRs before implementing related technical Work Items.",
|
|
8427
|
+
command: "kaddo adr",
|
|
8428
|
+
skill: "adr-writing",
|
|
8429
|
+
reason: `There are ${st.decision_candidates} technical decision candidate(s) and ${st.accepted_adrs} accepted ADR(s).`
|
|
8430
|
+
});
|
|
8431
|
+
}
|
|
8432
|
+
if (st.total_work_items > 0 && st.remaining_work_item_candidates > 0) {
|
|
8433
|
+
out.push({
|
|
8434
|
+
id: "materialize-more-work-items",
|
|
8435
|
+
label: `Later, materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
|
|
8436
|
+
command: "kaddo create --from roadmap",
|
|
8437
|
+
reason: `There are ${st.remaining_work_item_candidates} remaining Work Item candidate(s).`
|
|
8438
|
+
});
|
|
8439
|
+
}
|
|
8440
|
+
return out;
|
|
8127
8441
|
}
|
|
8128
8442
|
|
|
8129
8443
|
// src/core/readiness.ts
|
|
@@ -8218,92 +8532,6 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
8218
8532
|
};
|
|
8219
8533
|
}
|
|
8220
8534
|
|
|
8221
|
-
// src/core/decisions.ts
|
|
8222
|
-
var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
|
|
8223
|
-
var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
|
|
8224
|
-
var DECISIONS_DIR = "knowledge/tech/decisions";
|
|
8225
|
-
function resolveCandidatesPath(dir) {
|
|
8226
|
-
const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
|
|
8227
|
-
const legacy = exists(join(dir, CANDIDATES_LEGACY));
|
|
8228
|
-
if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
|
|
8229
|
-
if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
|
|
8230
|
-
return { path: null, legacy: false, bothExist: false };
|
|
8231
|
-
}
|
|
8232
|
-
function cleanCandidateTitle(title) {
|
|
8233
|
-
return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
|
|
8234
|
-
}
|
|
8235
|
-
function slugify2(s) {
|
|
8236
|
-
return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
|
|
8237
|
-
}
|
|
8238
|
-
function parseDecisionCandidates(md) {
|
|
8239
|
-
const out = [];
|
|
8240
|
-
for (const line of md.split(/\r?\n/)) {
|
|
8241
|
-
const m = line.match(/^##\s+(.+?)\s*$/);
|
|
8242
|
-
if (m) {
|
|
8243
|
-
const raw = m[1].trim();
|
|
8244
|
-
if (!raw || /^_.*_$/.test(raw)) continue;
|
|
8245
|
-
const t = cleanCandidateTitle(raw);
|
|
8246
|
-
if (t) out.push(t);
|
|
8247
|
-
}
|
|
8248
|
-
}
|
|
8249
|
-
return out;
|
|
8250
|
-
}
|
|
8251
|
-
function countAdrs(dir) {
|
|
8252
|
-
const base = join(dir, DECISIONS_DIR);
|
|
8253
|
-
if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
|
|
8254
|
-
let total = 0;
|
|
8255
|
-
let draft = 0;
|
|
8256
|
-
let accepted = 0;
|
|
8257
|
-
for (const entry of readDir(base)) {
|
|
8258
|
-
if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
|
|
8259
|
-
const full = join(base, entry);
|
|
8260
|
-
if (!isFile(full)) continue;
|
|
8261
|
-
total += 1;
|
|
8262
|
-
let content = "";
|
|
8263
|
-
try {
|
|
8264
|
-
content = readFile(full);
|
|
8265
|
-
} catch {
|
|
8266
|
-
continue;
|
|
8267
|
-
}
|
|
8268
|
-
const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
|
|
8269
|
-
if (status === "accepted") accepted += 1;
|
|
8270
|
-
else draft += 1;
|
|
8271
|
-
}
|
|
8272
|
-
return { total, draft, accepted };
|
|
8273
|
-
}
|
|
8274
|
-
function buildTechDecisions(dir) {
|
|
8275
|
-
const resolved = resolveCandidatesPath(dir);
|
|
8276
|
-
let titles = [];
|
|
8277
|
-
if (resolved.path) {
|
|
8278
|
-
try {
|
|
8279
|
-
titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
|
|
8280
|
-
} catch {
|
|
8281
|
-
titles = [];
|
|
8282
|
-
}
|
|
8283
|
-
}
|
|
8284
|
-
const { total, draft, accepted } = countAdrs(dir);
|
|
8285
|
-
const candidate_list = titles.map((title, i) => {
|
|
8286
|
-
const n = String(total + i + 1).padStart(3, "0");
|
|
8287
|
-
return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
|
|
8288
|
-
});
|
|
8289
|
-
let status;
|
|
8290
|
-
if (accepted > 0) status = "accepted-adrs";
|
|
8291
|
-
else if (total > 0) status = "draft-adrs";
|
|
8292
|
-
else if (titles.length > 0) status = "candidates";
|
|
8293
|
-
else status = "none";
|
|
8294
|
-
return {
|
|
8295
|
-
status,
|
|
8296
|
-
candidates: titles.length,
|
|
8297
|
-
adrs: total,
|
|
8298
|
-
draft_adrs: draft,
|
|
8299
|
-
accepted_adrs: accepted,
|
|
8300
|
-
candidate_list,
|
|
8301
|
-
candidates_source: resolved.path,
|
|
8302
|
-
candidates_legacy_location: resolved.legacy,
|
|
8303
|
-
candidates_both_exist: resolved.bothExist
|
|
8304
|
-
};
|
|
8305
|
-
}
|
|
8306
|
-
|
|
8307
8535
|
// src/core/assets.ts
|
|
8308
8536
|
import matter4 from "gray-matter";
|
|
8309
8537
|
function canonicalAgents() {
|
|
@@ -8431,16 +8659,25 @@ function buildRoadmapQuality(dir) {
|
|
|
8431
8659
|
const items = parseRoadmapCandidateQuality(md);
|
|
8432
8660
|
const count = (pred) => items.filter(pred).length;
|
|
8433
8661
|
const grounded = count((i) => i.grounded);
|
|
8434
|
-
|
|
8435
|
-
|
|
8662
|
+
const initiatives = {
|
|
8663
|
+
total: items.length,
|
|
8436
8664
|
grounded,
|
|
8437
8665
|
with_related_domain: count((i) => i.hasRelatedDomain),
|
|
8438
8666
|
with_related_capability: count((i) => i.hasRelatedCapability),
|
|
8439
8667
|
with_source_signals: count((i) => i.hasSourceSignals),
|
|
8440
|
-
// Only "needs refinement" when there are
|
|
8668
|
+
// Only "needs refinement" when there are initiatives and at least one isn't grounded.
|
|
8441
8669
|
needs_refinement: items.length > 0 && grounded < items.length,
|
|
8442
8670
|
items
|
|
8443
8671
|
};
|
|
8672
|
+
const wiCandidates = md ? parseRoadmapCandidates(md) : [];
|
|
8673
|
+
const wcount = (pred) => wiCandidates.filter(pred).length;
|
|
8674
|
+
const work_item_candidates = {
|
|
8675
|
+
total: wiCandidates.length,
|
|
8676
|
+
with_source_initiative: wcount((c) => Boolean(c.initiative?.id || c.initiative?.title)),
|
|
8677
|
+
with_related_domain: wcount((c) => Boolean(c.domain)),
|
|
8678
|
+
with_related_capability: wcount((c) => Boolean(c.relatedCapabilities?.length))
|
|
8679
|
+
};
|
|
8680
|
+
return { initiatives, work_item_candidates };
|
|
8444
8681
|
}
|
|
8445
8682
|
|
|
8446
8683
|
// src/core/project-explain.ts
|
|
@@ -8723,10 +8960,11 @@ function renderExplanationHuman(exp) {
|
|
|
8723
8960
|
lines.push(`- Delivery: ${ls("Delivery")}`);
|
|
8724
8961
|
lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
|
|
8725
8962
|
if (exp.roadmap.present) {
|
|
8726
|
-
lines.push(`- Roadmap
|
|
8727
|
-
lines.push(`-
|
|
8728
|
-
|
|
8729
|
-
|
|
8963
|
+
lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
|
|
8964
|
+
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
8965
|
+
lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
|
|
8966
|
+
if (exp.roadmap.remaining_work_item_candidates > 0)
|
|
8967
|
+
lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
|
|
8730
8968
|
} else {
|
|
8731
8969
|
lines.push(`- Work items: ${exp.workItems.total}`);
|
|
8732
8970
|
}
|
|
@@ -8851,9 +9089,12 @@ function renderExplanationHuman(exp) {
|
|
|
8851
9089
|
}
|
|
8852
9090
|
lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
|
|
8853
9091
|
lines.push("");
|
|
8854
|
-
|
|
9092
|
+
const rec = exp.nextStepRecommendation;
|
|
9093
|
+
const secondary = rec.secondary ?? [];
|
|
9094
|
+
const steps = secondary.length > 0 || /Delivery|Active|Maintenance/.test(rec.phase) ? [rec.label, ...secondary.map((s2) => s2.label)] : exp.suggestedNextSteps;
|
|
9095
|
+
if (steps.length > 0) {
|
|
8855
9096
|
lines.push("## Suggested Next Steps");
|
|
8856
|
-
|
|
9097
|
+
steps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
|
|
8857
9098
|
lines.push("");
|
|
8858
9099
|
}
|
|
8859
9100
|
const r = exp.readiness;
|
|
@@ -8903,17 +9144,42 @@ function renderExplanationHuman(exp) {
|
|
|
8903
9144
|
lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
|
|
8904
9145
|
lines.push("");
|
|
8905
9146
|
}
|
|
9147
|
+
if (exp.roadmap.present) {
|
|
9148
|
+
lines.push("## Roadmap Status");
|
|
9149
|
+
lines.push(`- Initiatives: ${exp.roadmap.initiatives}`);
|
|
9150
|
+
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
9151
|
+
lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
|
|
9152
|
+
lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
|
|
9153
|
+
lines.push("");
|
|
9154
|
+
}
|
|
8906
9155
|
const rq = exp.roadmapQuality;
|
|
8907
|
-
|
|
9156
|
+
const rqi = rq.initiatives;
|
|
9157
|
+
const rqw = rq.work_item_candidates;
|
|
9158
|
+
if (rqi.total > 0 || rqw.total > 0) {
|
|
8908
9159
|
lines.push("## Roadmap Quality");
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
9160
|
+
if (rqi.total > 0) {
|
|
9161
|
+
lines.push("Initiatives:");
|
|
9162
|
+
lines.push(`- Candidates evaluated: ${rqi.total}`);
|
|
9163
|
+
lines.push(`- Grounded: ${rqi.grounded}/${rqi.total}`);
|
|
9164
|
+
lines.push(`- With related domain: ${rqi.with_related_domain}/${rqi.total}`);
|
|
9165
|
+
lines.push(`- With related capability: ${rqi.with_related_capability}/${rqi.total}`);
|
|
9166
|
+
lines.push(`- With source signals: ${rqi.with_source_signals}/${rqi.total}`);
|
|
9167
|
+
}
|
|
9168
|
+
if (rqw.total > 0) {
|
|
8915
9169
|
lines.push("");
|
|
8916
|
-
lines.push("
|
|
9170
|
+
lines.push("Work Item Candidates:");
|
|
9171
|
+
lines.push(`- Candidates: ${rqw.total}`);
|
|
9172
|
+
lines.push(`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`);
|
|
9173
|
+
lines.push(`- With related domain: ${rqw.with_related_domain}/${rqw.total}`);
|
|
9174
|
+
lines.push(`- With related capability: ${rqw.with_related_capability}/${rqw.total}`);
|
|
9175
|
+
}
|
|
9176
|
+
if (rqi.needs_refinement) {
|
|
9177
|
+
lines.push("");
|
|
9178
|
+
lines.push("Roadmap quality: needs refinement.");
|
|
9179
|
+
lines.push("Suggested: use roadmap-agent to add domain / capability / source signals.");
|
|
9180
|
+
} else if (rqw.total > 0) {
|
|
9181
|
+
lines.push("");
|
|
9182
|
+
lines.push("Work Item candidate quality: good.");
|
|
8917
9183
|
}
|
|
8918
9184
|
lines.push("");
|
|
8919
9185
|
}
|
|
@@ -9301,6 +9567,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
9301
9567
|
nextStep: nextStepRecommendation.label,
|
|
9302
9568
|
recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
|
|
9303
9569
|
};
|
|
9570
|
+
const deliveryState = { ...buildDeliveryState(dir), phase: nextStepRecommendation.phase };
|
|
9304
9571
|
return {
|
|
9305
9572
|
version: CONTEXT_PACK_VERSION,
|
|
9306
9573
|
generatedAt: now.toISOString(),
|
|
@@ -9333,6 +9600,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
9333
9600
|
roadmap,
|
|
9334
9601
|
phase: unifiedPhase,
|
|
9335
9602
|
nextStepRecommendation,
|
|
9603
|
+
deliveryState,
|
|
9336
9604
|
techDecisions,
|
|
9337
9605
|
techKnowledge,
|
|
9338
9606
|
roadmapQuality: buildRoadmapQuality(dir),
|
|
@@ -9397,6 +9665,31 @@ function renderContextPack(pack) {
|
|
|
9397
9665
|
parts.push(`Next step: ${pack.phase.nextStep}
|
|
9398
9666
|
`);
|
|
9399
9667
|
}
|
|
9668
|
+
const ds = pack.deliveryState;
|
|
9669
|
+
const rec = pack.nextStepRecommendation;
|
|
9670
|
+
if (ds.total_work_items > 0 || rec.phase !== "Setup") {
|
|
9671
|
+
parts.push("## Delivery State\n");
|
|
9672
|
+
parts.push(
|
|
9673
|
+
[
|
|
9674
|
+
`- Phase: ${ds.phase}`,
|
|
9675
|
+
`- Draft Work Items: ${ds.draft_work_items}`,
|
|
9676
|
+
`- Ready Work Items: ${ds.ready_work_items}`,
|
|
9677
|
+
`- In-progress Work Items: ${ds.in_progress_work_items}`,
|
|
9678
|
+
`- Ownership coverage: ${ds.ownership_coverage}`,
|
|
9679
|
+
`- Remaining Work Item candidates: ${ds.remaining_work_item_candidates}`
|
|
9680
|
+
].join("\n") + "\n"
|
|
9681
|
+
);
|
|
9682
|
+
parts.push("## Next Step Recommendation\n");
|
|
9683
|
+
const recLines = [`- ${rec.label}`, ` - id: ${rec.id}`, ` - reason: ${rec.reason}`];
|
|
9684
|
+
if (rec.agent) recLines.push(` - agent: ${rec.agent}`);
|
|
9685
|
+
if (rec.skill) recLines.push(` - skill: ${rec.skill}`);
|
|
9686
|
+
if (rec.command) recLines.push(` - command: \`${rec.command}\``);
|
|
9687
|
+
parts.push(recLines.join("\n") + "\n");
|
|
9688
|
+
if (rec.secondary && rec.secondary.length > 0) {
|
|
9689
|
+
parts.push("Also (secondary):\n");
|
|
9690
|
+
parts.push(rec.secondary.map((s) => `- ${s.label}`).join("\n") + "\n");
|
|
9691
|
+
}
|
|
9692
|
+
}
|
|
9400
9693
|
parts.push("## Knowledge Layers\n");
|
|
9401
9694
|
parts.push(
|
|
9402
9695
|
"Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
|
|
@@ -9434,35 +9727,52 @@ function renderContextPack(pack) {
|
|
|
9434
9727
|
}
|
|
9435
9728
|
parts.push("## Current Knowledge\n");
|
|
9436
9729
|
parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
|
|
9437
|
-
parts.push("## Roadmap\n");
|
|
9730
|
+
parts.push("## Roadmap Status\n");
|
|
9438
9731
|
if (pack.roadmap.present) {
|
|
9439
9732
|
parts.push(
|
|
9440
9733
|
[
|
|
9441
|
-
`-
|
|
9442
|
-
`-
|
|
9443
|
-
`-
|
|
9734
|
+
`- Initiatives: ${pack.roadmap.initiatives}`,
|
|
9735
|
+
`- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
|
|
9736
|
+
`- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
|
|
9737
|
+
`- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
|
|
9444
9738
|
].join("\n") + "\n"
|
|
9445
9739
|
);
|
|
9446
|
-
if (pack.roadmap.
|
|
9740
|
+
if (pack.roadmap.remaining_work_item_candidates > 0) {
|
|
9447
9741
|
parts.push(
|
|
9448
|
-
"
|
|
9742
|
+
"Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
|
|
9449
9743
|
);
|
|
9450
9744
|
}
|
|
9451
9745
|
}
|
|
9452
9746
|
parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
|
|
9453
9747
|
const rq = pack.roadmapQuality;
|
|
9454
|
-
|
|
9748
|
+
const rqi = rq.initiatives;
|
|
9749
|
+
const rqw = rq.work_item_candidates;
|
|
9750
|
+
if (rqi.total > 0 || rqw.total > 0) {
|
|
9455
9751
|
parts.push("## Roadmap Quality\n");
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9752
|
+
if (rqi.total > 0) {
|
|
9753
|
+
parts.push(
|
|
9754
|
+
[
|
|
9755
|
+
"Initiatives:",
|
|
9756
|
+
`- Candidates evaluated: ${rqi.total}`,
|
|
9757
|
+
`- Grounded: ${rqi.grounded}/${rqi.total}`,
|
|
9758
|
+
`- With related domain: ${rqi.with_related_domain}/${rqi.total}`,
|
|
9759
|
+
`- With related capability: ${rqi.with_related_capability}/${rqi.total}`,
|
|
9760
|
+
`- With source signals: ${rqi.with_source_signals}/${rqi.total}`
|
|
9761
|
+
].join("\n") + "\n"
|
|
9762
|
+
);
|
|
9763
|
+
}
|
|
9764
|
+
if (rqw.total > 0) {
|
|
9765
|
+
parts.push(
|
|
9766
|
+
[
|
|
9767
|
+
"Work Item Candidates:",
|
|
9768
|
+
`- Candidates: ${rqw.total}`,
|
|
9769
|
+
`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`,
|
|
9770
|
+
`- With related domain: ${rqw.with_related_domain}/${rqw.total}`,
|
|
9771
|
+
`- With related capability: ${rqw.with_related_capability}/${rqw.total}`
|
|
9772
|
+
].join("\n") + "\n"
|
|
9773
|
+
);
|
|
9774
|
+
}
|
|
9775
|
+
if (rqi.needs_refinement) {
|
|
9466
9776
|
parts.push(
|
|
9467
9777
|
"Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
|
|
9468
9778
|
);
|
|
@@ -9883,8 +10193,13 @@ function runUnderstand() {
|
|
|
9883
10193
|
for (const r of assessment.reasons) console.log(` - ${r}`);
|
|
9884
10194
|
}
|
|
9885
10195
|
if (rec.agent) console.log(`Recommended: ${rec.agent}`);
|
|
10196
|
+
if (rec.skill) console.log(`Recommended skill: ${rec.skill}`);
|
|
9886
10197
|
console.log(`Next step: ${rec.label}`);
|
|
9887
10198
|
if (rec.reason) console.log(`Why: ${rec.reason}`);
|
|
10199
|
+
if (rec.secondary && rec.secondary.length > 0) {
|
|
10200
|
+
console.log("Also:");
|
|
10201
|
+
for (const s of rec.secondary) console.log(` - ${s.label}`);
|
|
10202
|
+
}
|
|
9888
10203
|
const installedSkills = discoverInstalledSkills(dir);
|
|
9889
10204
|
if (installedSkills.length > 0 && assessment.recommendedAgents.length > 0) {
|
|
9890
10205
|
const recSkills = skillsForAgents(installedSkills, assessment.recommendedAgents);
|
|
@@ -9900,15 +10215,15 @@ function runUnderstand() {
|
|
|
9900
10215
|
console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
|
|
9901
10216
|
console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
|
|
9902
10217
|
}
|
|
9903
|
-
const
|
|
9904
|
-
if (
|
|
10218
|
+
const rqi = exp.roadmapQuality.initiatives;
|
|
10219
|
+
if (rqi.needs_refinement) {
|
|
9905
10220
|
console.log("");
|
|
9906
|
-
console.log(`Roadmap quality: ${
|
|
9907
|
-
console.log(" \u2192 Use roadmap-agent to ground roadmap
|
|
10221
|
+
console.log(`Roadmap quality: ${rqi.grounded}/${rqi.total} initiatives grounded.`);
|
|
10222
|
+
console.log(" \u2192 Use roadmap-agent to ground roadmap initiatives in capability domains, gaps and source signals");
|
|
9908
10223
|
console.log(" before `kaddo create --from roadmap`.");
|
|
9909
|
-
} else if (
|
|
10224
|
+
} else if (rqi.total > 0 && rqi.grounded === rqi.total && exp.roadmap.materialized_work_items === 0) {
|
|
9910
10225
|
console.log("");
|
|
9911
|
-
console.log("Roadmap
|
|
10226
|
+
console.log("Roadmap initiatives are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
|
|
9912
10227
|
}
|
|
9913
10228
|
const ia = exp.installedAssets;
|
|
9914
10229
|
const outdatedRecommended = ia.agents.items.filter(
|