@kaddo/cli 3.42.0 → 3.44.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 +361 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -541,6 +541,8 @@ create --from roadmap → owners → guard → explain`.
|
|
|
541
541
|
| v3.40.1 | ADR slug cleanup + MCP: suggested ADR filenames strip list/heading prefixes (no `ADR-001-1-…`) and normalize acronyms; new read-only MCP resource `kaddo://tech-decisions` sharing `buildTechDecisions` with `kaddo adr` |
|
|
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
|
+
| 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` |
|
|
544
546
|
|
|
545
547
|
**Optional modules (installed with `kaddo add`):**
|
|
546
548
|
|
package/dist/index.js
CHANGED
|
@@ -2645,6 +2645,24 @@ Adapt priorities to the project state from the context pack:
|
|
|
2645
2645
|
- **legacy** \u2014 prioritize risk reduction, unknowns and safe modernization before feature
|
|
2646
2646
|
delivery.
|
|
2647
2647
|
|
|
2648
|
+
## Grounding rules (VS-077)
|
|
2649
|
+
|
|
2650
|
+
Every candidate initiative must be **grounded** in the knowledge base \u2014 never a loose idea. For each
|
|
2651
|
+
\`### RM-xxx\` you must provide:
|
|
2652
|
+
|
|
2653
|
+
- **Related domain** \u2014 a domain from \`## Capability Domains\` in \`capabilities.md\` (or, if genuinely
|
|
2654
|
+
new, prefix it \`[new candidate domain] <name>\` \u2014 do not invent domains silently).
|
|
2655
|
+
- **Related capabilities** \u2014 one or more existing/partial capabilities.
|
|
2656
|
+
- **Source signals** \u2014 at least one traceable reason: Capability Gap, Roadmap Candidate Signal, Risk,
|
|
2657
|
+
Open Question, Assumption, Deferred Decision, Tech Decision Candidate, ADR, Business Goal,
|
|
2658
|
+
Operational Need or Legacy Modernization Signal.
|
|
2659
|
+
- **Expected value**, **Risks**, **Dependencies**, and **Suggested Work Items** (candidates only).
|
|
2660
|
+
|
|
2661
|
+
Do **not** emit a candidate with no source signal. Keep initiatives at initiative granularity (small
|
|
2662
|
+
tasks go under **Suggested Work Items**, not as their own RM). Every roadmap ends with a global
|
|
2663
|
+
**## Not Now** section. For pre-ai/legacy, prioritize stabilization, security, data, operations,
|
|
2664
|
+
architectural decisions and business-blocking gaps before expansive features.
|
|
2665
|
+
|
|
2648
2666
|
## Constraints
|
|
2649
2667
|
|
|
2650
2668
|
- Do not invent business priorities or business facts \u2014 mark them as assumptions when inferred.
|
|
@@ -2653,6 +2671,8 @@ Adapt priorities to the project state from the context pack:
|
|
|
2653
2671
|
implementation-agent, and only after Work Items are materialized. Your handoff is
|
|
2654
2672
|
\`kaddo create --from roadmap\` \u2192 work-item-agent.
|
|
2655
2673
|
- Do not create the work items themselves; only propose candidates.
|
|
2674
|
+
- **Never create files under \`knowledge/delivery/work-items/\`** \u2014 materialization is
|
|
2675
|
+
\`kaddo create --from roadmap\`, not the roadmap-agent.
|
|
2656
2676
|
- Make clear that initiatives and work items are **candidates**, not final decisions.
|
|
2657
2677
|
- Mark any uncertain information as an assumption or open question.
|
|
2658
2678
|
- Keep sequencing justified by dependencies and risk.
|
|
@@ -2686,31 +2706,38 @@ human review \u2014 not final commitments.
|
|
|
2686
2706
|
|
|
2687
2707
|
### RM-001: <Initiative Name>
|
|
2688
2708
|
|
|
2689
|
-
**
|
|
2709
|
+
**Status:** candidate <!-- candidate | selected | deferred | rejected -->
|
|
2690
2710
|
|
|
2691
|
-
**
|
|
2711
|
+
**Priority:** high / medium / low
|
|
2692
2712
|
|
|
2693
|
-
**
|
|
2713
|
+
**Suggested Knowledge Level:** K1 / K2 / K3 / K4
|
|
2694
2714
|
|
|
2695
|
-
**
|
|
2715
|
+
**Related domain:** <one of the ## Capability Domains from capabilities.md>
|
|
2696
2716
|
|
|
2697
|
-
**
|
|
2717
|
+
**Related capabilities:**
|
|
2718
|
+
- <existing or partial capability>
|
|
2698
2719
|
|
|
2699
|
-
**
|
|
2720
|
+
**Source signals:** <!-- REQUIRED: why this candidate exists (at least one) -->
|
|
2721
|
+
- Capability Gap: <...>
|
|
2722
|
+
- Roadmap Candidate Signal: <...>
|
|
2723
|
+
- Risk / Open Question / Assumption / Deferred Decision / Tech Decision Candidate / ADR / Business Goal: <...>
|
|
2700
2724
|
|
|
2701
|
-
**
|
|
2725
|
+
**Problem / opportunity:**
|
|
2726
|
+
|
|
2727
|
+
**Expected value:**
|
|
2702
2728
|
|
|
2703
|
-
**
|
|
2729
|
+
**Risks:**
|
|
2704
2730
|
|
|
2705
|
-
**
|
|
2731
|
+
**Dependencies:**
|
|
2706
2732
|
|
|
2733
|
+
**Suggested Work Items:**
|
|
2707
2734
|
- WI-CANDIDATE-001: <candidate work item>
|
|
2708
2735
|
- type:
|
|
2709
2736
|
- suggested knowledge level:
|
|
2710
2737
|
- expected value:
|
|
2711
2738
|
- notes:
|
|
2712
2739
|
|
|
2713
|
-
**
|
|
2740
|
+
**Not now:**
|
|
2714
2741
|
|
|
2715
2742
|
---
|
|
2716
2743
|
|
|
@@ -3005,6 +3032,23 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
|
|
|
3005
3032
|
without surfacing it. -->
|
|
3006
3033
|
\`\`\`
|
|
3007
3034
|
|
|
3035
|
+
### Preserve roadmap metadata (VS-077 / VS-078)
|
|
3036
|
+
|
|
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.
|
|
3051
|
+
|
|
3008
3052
|
## Where to Save the Result
|
|
3009
3053
|
|
|
3010
3054
|
Save new output as a draft under \`knowledge/delivery/work-items/draft/\` unless a human
|
|
@@ -4495,7 +4539,12 @@ function splitInitiatives(markdown) {
|
|
|
4495
4539
|
return blocks;
|
|
4496
4540
|
}
|
|
4497
4541
|
function parseBlock(block) {
|
|
4498
|
-
const meta = {
|
|
4542
|
+
const meta = {
|
|
4543
|
+
relatedCapabilities: [],
|
|
4544
|
+
dependencies: [],
|
|
4545
|
+
openQuestions: [],
|
|
4546
|
+
sourceSignals: []
|
|
4547
|
+
};
|
|
4499
4548
|
const rawCandidates = [];
|
|
4500
4549
|
let listField = null;
|
|
4501
4550
|
let inCandidateSection = false;
|
|
@@ -4545,7 +4594,7 @@ function parseBlock(block) {
|
|
|
4545
4594
|
meta.impact = value || void 0;
|
|
4546
4595
|
} else if (key === "risk") {
|
|
4547
4596
|
meta.risk = value || void 0;
|
|
4548
|
-
} 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") {
|
|
4549
4598
|
meta.domain = value || void 0;
|
|
4550
4599
|
} else if (key === "related capabilities") {
|
|
4551
4600
|
listField = "relatedCapabilities";
|
|
@@ -4556,6 +4605,9 @@ function parseBlock(block) {
|
|
|
4556
4605
|
} else if (key === "open questions") {
|
|
4557
4606
|
listField = "openQuestions";
|
|
4558
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);
|
|
4559
4611
|
}
|
|
4560
4612
|
continue;
|
|
4561
4613
|
}
|
|
@@ -4573,6 +4625,7 @@ function parseBlock(block) {
|
|
|
4573
4625
|
}
|
|
4574
4626
|
}
|
|
4575
4627
|
flush();
|
|
4628
|
+
const decisionCandidates = decisionCandidatesFromSignals(meta.sourceSignals);
|
|
4576
4629
|
return rawCandidates.map((c) => ({
|
|
4577
4630
|
...c,
|
|
4578
4631
|
initiative: block.initiative.id || block.initiative.title ? { ...block.initiative } : void 0,
|
|
@@ -4581,9 +4634,23 @@ function parseBlock(block) {
|
|
|
4581
4634
|
impact: meta.impact,
|
|
4582
4635
|
risk: meta.risk,
|
|
4583
4636
|
dependencies: meta.dependencies.length ? [...meta.dependencies] : void 0,
|
|
4584
|
-
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
|
|
4585
4640
|
}));
|
|
4586
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
|
+
}
|
|
4587
4654
|
var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
|
|
4588
4655
|
var HEADING_RE = /^#{2,4}\s+(.*)$/;
|
|
4589
4656
|
var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
|
|
@@ -4665,10 +4732,51 @@ function parseRoadmapCandidates(markdown) {
|
|
|
4665
4732
|
if (strict.length > 0) return strict;
|
|
4666
4733
|
return parseFlexible(markdown);
|
|
4667
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
|
+
}
|
|
4668
4741
|
function roadmapStats(markdown, materialized) {
|
|
4669
|
-
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
|
+
};
|
|
4670
4753
|
const candidates = parseRoadmapCandidates(markdown).length;
|
|
4671
|
-
|
|
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] : [];
|
|
4672
4780
|
}
|
|
4673
4781
|
|
|
4674
4782
|
// src/commands/create.ts
|
|
@@ -4964,22 +5072,44 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
|
|
|
4964
5072
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4965
5073
|
const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
|
|
4966
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)}"`)];
|
|
4967
5083
|
const lines = [
|
|
4968
5084
|
"---",
|
|
4969
5085
|
`type: ${type}`,
|
|
4970
5086
|
`id: ${id}`,
|
|
4971
|
-
`title: "${title}"`,
|
|
5087
|
+
`title: "${q(title)}"`,
|
|
4972
5088
|
`knowledge_level: ${level}`,
|
|
4973
5089
|
`status: draft`,
|
|
4974
5090
|
`phase: now`,
|
|
4975
|
-
`initiative: "${initiative
|
|
4976
|
-
|
|
5091
|
+
`initiative: "${q(initiative)}"`,
|
|
5092
|
+
...yamlList2("domains", domains),
|
|
5093
|
+
...domains.length === 0 ? ["domains: []"] : [],
|
|
4977
5094
|
`code: []`,
|
|
4978
5095
|
`created_at: ${today2}`,
|
|
4979
5096
|
`source: roadmap`,
|
|
4980
5097
|
`source_id: ${candidate.id}`,
|
|
4981
5098
|
`source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
|
|
4982
|
-
|
|
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)}"`,
|
|
4983
5113
|
"---"
|
|
4984
5114
|
];
|
|
4985
5115
|
return lines.join("\n");
|
|
@@ -4991,14 +5121,24 @@ function buildRoadmapBody(type, level, title, candidate, answers, qualityGate) {
|
|
|
4991
5121
|
sections.push(`> Type: ${type} \xB7 Level: ${level}
|
|
4992
5122
|
`);
|
|
4993
5123
|
const initiativeLabel = candidate.initiative ? `${candidate.initiative.id ?? ""}${candidate.initiative.title ? ` \u2014 ${candidate.initiative.title}` : ""}`.trim() : "unknown";
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
);
|
|
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
|
+
}
|
|
5002
5142
|
if (answers.problem) sections.push(`## Problem
|
|
5003
5143
|
|
|
5004
5144
|
${answers.problem}
|
|
@@ -5008,22 +5148,22 @@ ${answers.problem}
|
|
|
5008
5148
|
|
|
5009
5149
|
${expectedValue}
|
|
5010
5150
|
`);
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
if (candidate.
|
|
5015
|
-
if (candidate.impact) ctx.push(`**Impact:** ${candidate.impact}`);
|
|
5016
|
-
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}`);
|
|
5017
5155
|
if (candidate.dependencies?.length)
|
|
5018
|
-
ctx.push(`**Dependencies:**
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
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
|
|
5023
5164
|
|
|
5024
|
-
${
|
|
5025
|
-
`
|
|
5026
|
-
);
|
|
5165
|
+
${ctx.join("\n\n")}
|
|
5166
|
+
`);
|
|
5027
5167
|
if (answers.acceptance_criteria) {
|
|
5028
5168
|
const items = answers.acceptance_criteria.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
5029
5169
|
sections.push(`## Acceptance Criteria
|
|
@@ -8325,6 +8465,88 @@ function installedAssetsSummary(dir) {
|
|
|
8325
8465
|
return { version: KADDO_VERSION, agents: assetStatus(dir, "agent"), skills: assetStatus(dir, "skill") };
|
|
8326
8466
|
}
|
|
8327
8467
|
|
|
8468
|
+
// src/core/roadmap-quality.ts
|
|
8469
|
+
var ROADMAP_PATH2 = "knowledge/delivery/roadmap.md";
|
|
8470
|
+
var INITIATIVE_RE2 = /^#{2,4}\s+(RM-[\w.-]+)\s*[:\-–]?\s*(.*)$/;
|
|
8471
|
+
function fieldPresent(lines, label) {
|
|
8472
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8473
|
+
const m = lines[i].match(new RegExp(`^\\s*[-*]?\\s*(?:\\*\\*)?\\s*(${label.source})\\s*(?:\\*\\*)?\\s*:\\s*(.*)$`, "i"));
|
|
8474
|
+
if (!m) continue;
|
|
8475
|
+
const inline = m[2].trim();
|
|
8476
|
+
if (inline) return true;
|
|
8477
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
8478
|
+
if (/^\s{2,}[-*]\s+\S/.test(lines[j])) return true;
|
|
8479
|
+
if (/^\S/.test(lines[j]) || /^#{2,4}\s/.test(lines[j])) break;
|
|
8480
|
+
if (/^\s*[-*]\s+\S/.test(lines[j]) && !/^\s{2,}/.test(lines[j])) break;
|
|
8481
|
+
}
|
|
8482
|
+
}
|
|
8483
|
+
return false;
|
|
8484
|
+
}
|
|
8485
|
+
function parseRoadmapCandidateQuality(md) {
|
|
8486
|
+
const lines = md.split(/\r?\n/);
|
|
8487
|
+
const items = [];
|
|
8488
|
+
let cur = null;
|
|
8489
|
+
const flush = () => {
|
|
8490
|
+
if (!cur) return;
|
|
8491
|
+
const hasRelatedDomain = fieldPresent(cur.lines, /related domain/);
|
|
8492
|
+
const hasRelatedCapability = fieldPresent(cur.lines, /related capabilit(?:y|ies)/);
|
|
8493
|
+
const hasSourceSignals = fieldPresent(cur.lines, /source signals?/);
|
|
8494
|
+
items.push({
|
|
8495
|
+
id: cur.id,
|
|
8496
|
+
title: cur.title,
|
|
8497
|
+
hasRelatedDomain,
|
|
8498
|
+
hasRelatedCapability,
|
|
8499
|
+
hasSourceSignals,
|
|
8500
|
+
grounded: hasRelatedDomain && hasRelatedCapability && hasSourceSignals
|
|
8501
|
+
});
|
|
8502
|
+
cur = null;
|
|
8503
|
+
};
|
|
8504
|
+
for (const line of lines) {
|
|
8505
|
+
const m = line.match(INITIATIVE_RE2);
|
|
8506
|
+
if (m) {
|
|
8507
|
+
flush();
|
|
8508
|
+
cur = { id: m[1], title: m[2].trim(), lines: [] };
|
|
8509
|
+
continue;
|
|
8510
|
+
}
|
|
8511
|
+
if (cur) cur.lines.push(line);
|
|
8512
|
+
}
|
|
8513
|
+
flush();
|
|
8514
|
+
return items;
|
|
8515
|
+
}
|
|
8516
|
+
function buildRoadmapQuality(dir) {
|
|
8517
|
+
let md = "";
|
|
8518
|
+
const p2 = join(dir, ROADMAP_PATH2);
|
|
8519
|
+
if (exists(p2)) {
|
|
8520
|
+
try {
|
|
8521
|
+
md = readFile(p2);
|
|
8522
|
+
} catch {
|
|
8523
|
+
md = "";
|
|
8524
|
+
}
|
|
8525
|
+
}
|
|
8526
|
+
const items = parseRoadmapCandidateQuality(md);
|
|
8527
|
+
const count = (pred) => items.filter(pred).length;
|
|
8528
|
+
const grounded = count((i) => i.grounded);
|
|
8529
|
+
const initiatives = {
|
|
8530
|
+
total: items.length,
|
|
8531
|
+
grounded,
|
|
8532
|
+
with_related_domain: count((i) => i.hasRelatedDomain),
|
|
8533
|
+
with_related_capability: count((i) => i.hasRelatedCapability),
|
|
8534
|
+
with_source_signals: count((i) => i.hasSourceSignals),
|
|
8535
|
+
// Only "needs refinement" when there are initiatives and at least one isn't grounded.
|
|
8536
|
+
needs_refinement: items.length > 0 && grounded < items.length,
|
|
8537
|
+
items
|
|
8538
|
+
};
|
|
8539
|
+
const wiCandidates = md ? parseRoadmapCandidates(md) : [];
|
|
8540
|
+
const wcount = (pred) => wiCandidates.filter(pred).length;
|
|
8541
|
+
const work_item_candidates = {
|
|
8542
|
+
total: wiCandidates.length,
|
|
8543
|
+
with_source_initiative: wcount((c) => Boolean(c.initiative?.id || c.initiative?.title)),
|
|
8544
|
+
with_related_domain: wcount((c) => Boolean(c.domain)),
|
|
8545
|
+
with_related_capability: wcount((c) => Boolean(c.relatedCapabilities?.length))
|
|
8546
|
+
};
|
|
8547
|
+
return { initiatives, work_item_candidates };
|
|
8548
|
+
}
|
|
8549
|
+
|
|
8328
8550
|
// src/core/project-explain.ts
|
|
8329
8551
|
var ARCH_DIR4 = "knowledge";
|
|
8330
8552
|
function normalizeTitle(t) {
|
|
@@ -8549,7 +8771,8 @@ function buildProjectExplanation(dir) {
|
|
|
8549
8771
|
legacyLocation: exists(join(dir, "knowledge/tech/architecture-notes.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md"))
|
|
8550
8772
|
}
|
|
8551
8773
|
},
|
|
8552
|
-
installedAssets: installedAssetsSummary(dir)
|
|
8774
|
+
installedAssets: installedAssetsSummary(dir),
|
|
8775
|
+
roadmapQuality: buildRoadmapQuality(dir)
|
|
8553
8776
|
};
|
|
8554
8777
|
}
|
|
8555
8778
|
function stateLabel(state) {
|
|
@@ -8604,10 +8827,11 @@ function renderExplanationHuman(exp) {
|
|
|
8604
8827
|
lines.push(`- Delivery: ${ls("Delivery")}`);
|
|
8605
8828
|
lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
|
|
8606
8829
|
if (exp.roadmap.present) {
|
|
8607
|
-
lines.push(`- Roadmap
|
|
8608
|
-
lines.push(`-
|
|
8609
|
-
|
|
8610
|
-
|
|
8830
|
+
lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
|
|
8831
|
+
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
8832
|
+
lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
|
|
8833
|
+
if (exp.roadmap.remaining_work_item_candidates > 0)
|
|
8834
|
+
lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
|
|
8611
8835
|
} else {
|
|
8612
8836
|
lines.push(`- Work items: ${exp.workItems.total}`);
|
|
8613
8837
|
}
|
|
@@ -8784,6 +9008,45 @@ function renderExplanationHuman(exp) {
|
|
|
8784
9008
|
lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
|
|
8785
9009
|
lines.push("");
|
|
8786
9010
|
}
|
|
9011
|
+
if (exp.roadmap.present) {
|
|
9012
|
+
lines.push("## Roadmap Status");
|
|
9013
|
+
lines.push(`- Initiatives: ${exp.roadmap.initiatives}`);
|
|
9014
|
+
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
9015
|
+
lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
|
|
9016
|
+
lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
|
|
9017
|
+
lines.push("");
|
|
9018
|
+
}
|
|
9019
|
+
const rq = exp.roadmapQuality;
|
|
9020
|
+
const rqi = rq.initiatives;
|
|
9021
|
+
const rqw = rq.work_item_candidates;
|
|
9022
|
+
if (rqi.total > 0 || rqw.total > 0) {
|
|
9023
|
+
lines.push("## Roadmap Quality");
|
|
9024
|
+
if (rqi.total > 0) {
|
|
9025
|
+
lines.push("Initiatives:");
|
|
9026
|
+
lines.push(`- Candidates evaluated: ${rqi.total}`);
|
|
9027
|
+
lines.push(`- Grounded: ${rqi.grounded}/${rqi.total}`);
|
|
9028
|
+
lines.push(`- With related domain: ${rqi.with_related_domain}/${rqi.total}`);
|
|
9029
|
+
lines.push(`- With related capability: ${rqi.with_related_capability}/${rqi.total}`);
|
|
9030
|
+
lines.push(`- With source signals: ${rqi.with_source_signals}/${rqi.total}`);
|
|
9031
|
+
}
|
|
9032
|
+
if (rqw.total > 0) {
|
|
9033
|
+
lines.push("");
|
|
9034
|
+
lines.push("Work Item Candidates:");
|
|
9035
|
+
lines.push(`- Candidates: ${rqw.total}`);
|
|
9036
|
+
lines.push(`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`);
|
|
9037
|
+
lines.push(`- With related domain: ${rqw.with_related_domain}/${rqw.total}`);
|
|
9038
|
+
lines.push(`- With related capability: ${rqw.with_related_capability}/${rqw.total}`);
|
|
9039
|
+
}
|
|
9040
|
+
if (rqi.needs_refinement) {
|
|
9041
|
+
lines.push("");
|
|
9042
|
+
lines.push("Roadmap quality: needs refinement.");
|
|
9043
|
+
lines.push("Suggested: use roadmap-agent to add domain / capability / source signals.");
|
|
9044
|
+
} else if (rqw.total > 0) {
|
|
9045
|
+
lines.push("");
|
|
9046
|
+
lines.push("Work Item candidate quality: good.");
|
|
9047
|
+
}
|
|
9048
|
+
lines.push("");
|
|
9049
|
+
}
|
|
8787
9050
|
const ia = exp.installedAssets;
|
|
8788
9051
|
const agentsInstalled = ia.agents.total - ia.agents.missing;
|
|
8789
9052
|
const skillsInstalled = ia.skills.total - ia.skills.missing;
|
|
@@ -9202,6 +9465,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
9202
9465
|
nextStepRecommendation,
|
|
9203
9466
|
techDecisions,
|
|
9204
9467
|
techKnowledge,
|
|
9468
|
+
roadmapQuality: buildRoadmapQuality(dir),
|
|
9205
9469
|
installedAssets: (() => {
|
|
9206
9470
|
const s = installedAssetsSummary(dir);
|
|
9207
9471
|
const compact = (a) => ({ total: a.total, installed: a.total - a.missing, outdated: a.outdated, unknown_version: a.unknown_version, modified: a.modified });
|
|
@@ -9300,22 +9564,57 @@ function renderContextPack(pack) {
|
|
|
9300
9564
|
}
|
|
9301
9565
|
parts.push("## Current Knowledge\n");
|
|
9302
9566
|
parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
|
|
9303
|
-
parts.push("## Roadmap\n");
|
|
9567
|
+
parts.push("## Roadmap Status\n");
|
|
9304
9568
|
if (pack.roadmap.present) {
|
|
9305
9569
|
parts.push(
|
|
9306
9570
|
[
|
|
9307
|
-
`-
|
|
9308
|
-
`-
|
|
9309
|
-
`-
|
|
9571
|
+
`- Initiatives: ${pack.roadmap.initiatives}`,
|
|
9572
|
+
`- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
|
|
9573
|
+
`- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
|
|
9574
|
+
`- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
|
|
9310
9575
|
].join("\n") + "\n"
|
|
9311
9576
|
);
|
|
9312
|
-
if (pack.roadmap.
|
|
9577
|
+
if (pack.roadmap.remaining_work_item_candidates > 0) {
|
|
9313
9578
|
parts.push(
|
|
9314
|
-
"
|
|
9579
|
+
"Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
|
|
9315
9580
|
);
|
|
9316
9581
|
}
|
|
9317
9582
|
}
|
|
9318
9583
|
parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
|
|
9584
|
+
const rq = pack.roadmapQuality;
|
|
9585
|
+
const rqi = rq.initiatives;
|
|
9586
|
+
const rqw = rq.work_item_candidates;
|
|
9587
|
+
if (rqi.total > 0 || rqw.total > 0) {
|
|
9588
|
+
parts.push("## Roadmap Quality\n");
|
|
9589
|
+
if (rqi.total > 0) {
|
|
9590
|
+
parts.push(
|
|
9591
|
+
[
|
|
9592
|
+
"Initiatives:",
|
|
9593
|
+
`- Candidates evaluated: ${rqi.total}`,
|
|
9594
|
+
`- Grounded: ${rqi.grounded}/${rqi.total}`,
|
|
9595
|
+
`- With related domain: ${rqi.with_related_domain}/${rqi.total}`,
|
|
9596
|
+
`- With related capability: ${rqi.with_related_capability}/${rqi.total}`,
|
|
9597
|
+
`- With source signals: ${rqi.with_source_signals}/${rqi.total}`
|
|
9598
|
+
].join("\n") + "\n"
|
|
9599
|
+
);
|
|
9600
|
+
}
|
|
9601
|
+
if (rqw.total > 0) {
|
|
9602
|
+
parts.push(
|
|
9603
|
+
[
|
|
9604
|
+
"Work Item Candidates:",
|
|
9605
|
+
`- Candidates: ${rqw.total}`,
|
|
9606
|
+
`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`,
|
|
9607
|
+
`- With related domain: ${rqw.with_related_domain}/${rqw.total}`,
|
|
9608
|
+
`- With related capability: ${rqw.with_related_capability}/${rqw.total}`
|
|
9609
|
+
].join("\n") + "\n"
|
|
9610
|
+
);
|
|
9611
|
+
}
|
|
9612
|
+
if (rqi.needs_refinement) {
|
|
9613
|
+
parts.push(
|
|
9614
|
+
"Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
|
|
9615
|
+
);
|
|
9616
|
+
}
|
|
9617
|
+
}
|
|
9319
9618
|
parts.push("## Active Work Items\n");
|
|
9320
9619
|
if (knowledge.workItems.length > 0) {
|
|
9321
9620
|
const lines = knowledge.workItems.map((wi) => {
|
|
@@ -9748,6 +10047,16 @@ function runUnderstand() {
|
|
|
9748
10047
|
console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
|
|
9749
10048
|
console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
|
|
9750
10049
|
}
|
|
10050
|
+
const rqi = exp.roadmapQuality.initiatives;
|
|
10051
|
+
if (rqi.needs_refinement) {
|
|
10052
|
+
console.log("");
|
|
10053
|
+
console.log(`Roadmap quality: ${rqi.grounded}/${rqi.total} initiatives grounded.`);
|
|
10054
|
+
console.log(" \u2192 Use roadmap-agent to ground roadmap initiatives in capability domains, gaps and source signals");
|
|
10055
|
+
console.log(" before `kaddo create --from roadmap`.");
|
|
10056
|
+
} else if (rqi.total > 0 && rqi.grounded === rqi.total) {
|
|
10057
|
+
console.log("");
|
|
10058
|
+
console.log("Roadmap initiatives are grounded. \u2192 Run `kaddo create --from roadmap` to materialize the first Work Item.");
|
|
10059
|
+
}
|
|
9751
10060
|
const ia = exp.installedAssets;
|
|
9752
10061
|
const outdatedRecommended = ia.agents.items.filter(
|
|
9753
10062
|
(a) => (a.state === "outdated" || a.state === "unknown-version") && assessment.recommendedAgents.includes(a.name)
|