@kaddo/cli 3.39.1 → 3.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +191 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -537,6 +537,8 @@ create --from roadmap → owners → guard → explain`.
537
537
  | v3.38 | Open-question source locations: `kaddo questions` shows each question's `Source` (path:line), `Status`, `Severity`, `Note` + a copy/paste resolution example and localized how-to-resolve guide; JSON/report/MCP carry `sourcePath`, `line`, `raw`, `note` |
538
538
  | v3.39 | Existing capability discovery: state-aware `capabilities.md` (pre-ai/legacy = evidence-backed Capability Inventory + Gaps + Roadmap Candidate Signals; legacy adds criticality/change-risk/modernization); capability-agent discovers with evidence, roadmap-agent treats capabilities as primary source |
539
539
  | v3.39.1 | Domain-oriented capability inventory: pre-ai/legacy `capabilities.md` groups capabilities under `## Capability Domains` (functional domains, not technical folders); gaps/candidates name their `Domain`; roadmap-agent reads it as a domain map; work-item-agent recommends `related_domain` + `related_capability` |
540
+ | v3.40 | ADR materialization: `kaddo adr` (alias `decisions`) detects decision candidates + ADRs and hands off the ADR files to create; `tech_decisions` status (none/candidates/draft-adrs/accepted-adrs) surfaced in `explain`/`context`/`understand`; adr-writing skill formalized |
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` |
540
542
 
541
543
  **Optional modules (installed with `kaddo add`):**
542
544
 
package/dist/index.js CHANGED
@@ -704,6 +704,10 @@ var COMMAND_HELP = {
704
704
  "adapters install codex": {
705
705
  question: "How does Codex work with this Kaddo project?",
706
706
  next: "Open AGENTS.md, or regenerate it after knowledge changes"
707
+ },
708
+ adr: {
709
+ question: "Which technical decisions still need to become ADRs?",
710
+ next: "Use the adr-writing skill to draft ADRs, then mark them accepted when confirmed"
707
711
  }
708
712
  };
709
713
  function commandFooterLines(name) {
@@ -1546,37 +1550,56 @@ The context pack, the relevant Work Item or architecture note, and the decision
1546
1550
 
1547
1551
  ## Output
1548
1552
 
1549
- A single ADR containing: context, the decision, alternatives considered, consequences, the code
1550
- paths it governs (\`code:\` globs when known) and the decision status (proposed / accepted /
1551
- superseded).
1553
+ A single ADR (saved under \`knowledge/tech/decisions/\`) with front matter and the standard sections:
1554
+ context, options considered, the decision, consequences, related capabilities and related Work Items.
1555
+ Status is one of \`draft\`, \`accepted\`, \`superseded\`, \`deprecated\`.
1556
+
1557
+ ## Materializing from decision candidates (VS-075)
1558
+
1559
+ When \`knowledge/tech/decisions/\` is empty but \`knowledge/tech/decision-candidates.md\` holds
1560
+ candidates, materialize each candidate as an ADR **draft** \u2014 copy its context and options, leave the
1561
+ decision and consequences as \`[open]\` for human confirmation, and record its origin with
1562
+ \`created_from: knowledge/tech/decision-candidates.md\`. Never mark a materialized draft \`accepted\`;
1563
+ acceptance is a human decision.
1552
1564
 
1553
1565
  ## Rules
1554
1566
 
1555
1567
  - One ADR = one decision. Never mix unrelated decisions.
1556
1568
  - Never invent decisions; never write an ADR without a clear reason.
1557
- - Record alternatives honestly, including the one chosen and why.
1569
+ - Record alternatives honestly, including the one chosen and why (or \`[open]\` when undecided).
1558
1570
  - Prefer narrow governed \`code:\` globs over broad ones.
1559
1571
 
1560
1572
  ## Quality checklist
1561
1573
 
1562
1574
  - Context explains why the decision was needed.
1563
- - The decision and its alternatives are explicit.
1575
+ - The decision and its alternatives are explicit (or explicitly \`[open]\`).
1564
1576
  - Consequences (positive and negative) are stated.
1565
1577
  - Status and governed paths are present.
1566
1578
 
1567
1579
  ## Example output
1568
1580
 
1569
1581
  \`\`\`md
1570
- # ADR-0007 \u2014 Use SQLite for local persistence
1571
- Status: accepted
1582
+ ---
1583
+ type: adr
1584
+ status: draft | accepted | superseded | deprecated
1585
+ date: YYYY-MM-DD
1586
+ created_from: knowledge/tech/decision-candidates.md
1587
+ ---
1588
+
1589
+ # ADR-001 \u2014 Use INTERNAL_CRON_SECRET for internal endpoint protection
1590
+
1572
1591
  ## Context
1573
1592
  ...
1574
- ## Decision
1575
- ...
1576
- ## Alternatives considered
1593
+ ## Options Considered
1577
1594
  ...
1595
+ ## Decision
1596
+ [open]
1578
1597
  ## Consequences
1579
- ...
1598
+ [open]
1599
+ ## Related Capabilities
1600
+ - <domain / capability>
1601
+ ## Related Work Items
1602
+ - <WI id>
1580
1603
  \`\`\`
1581
1604
  `
1582
1605
  );
@@ -2935,6 +2958,13 @@ A refined Work Item intended to be saved under the lifecycle workspace:
2935
2958
  capability from knowledge/product/capabilities.md this Work Item advances, so work traces back to the
2936
2959
  system's functional map. Add \`related_domain: <domain>\` and \`related_capability: <name>\` to the front
2937
2960
  matter when known. -->
2961
+
2962
+ **Related decisions:** <!-- recommended (VS-075): if this Work Item is affected by a technical
2963
+ decision, reference the ADR under knowledge/tech/decisions/ as \`related_decisions: [ADR-001-...]\`. If
2964
+ the decision is still a candidate in knowledge/tech/decision-candidates.md with no ADR yet, **warn**
2965
+ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and record
2966
+ \`decision_candidates: [<title>]\` \u2014 do not implement work that depends on an unformalized decision
2967
+ without surfacing it. -->
2938
2968
  \`\`\`
2939
2969
 
2940
2970
  ## Where to Save the Result
@@ -3511,6 +3541,12 @@ architecture, persistence, authentication or the Work Item's scope. Block only o
3511
3541
  mention the relevant assumptions instead of pausing. If any blocking question is still open, pause and
3512
3542
  ask the user to confirm assumptions or resolve them before writing code.
3513
3543
 
3544
+ Also check **technical decisions** (VS-075): if the Work Item touches a decision that is still a
3545
+ candidate in \`knowledge/tech/decision-candidates.md\` with no ADR under \`knowledge/tech/decisions/\`
3546
+ (run \`kaddo adr\`), **warn** and recommend materializing it as an ADR (adr-writing skill) before
3547
+ implementing \u2014 do not silently implement work that depends on an unformalized architectural,
3548
+ security, data, integration or infrastructure decision.
3549
+
3514
3550
  ## When to Use
3515
3551
 
3516
3552
  Use this agent after the work-item-agent has produced a clear, traceable Work Item under
@@ -8086,6 +8122,81 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
8086
8122
  };
8087
8123
  }
8088
8124
 
8125
+ // src/core/decisions.ts
8126
+ var CANDIDATES_PATH = "knowledge/tech/decision-candidates.md";
8127
+ var DECISIONS_DIR = "knowledge/tech/decisions";
8128
+ function cleanCandidateTitle(title) {
8129
+ return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
8130
+ }
8131
+ function slugify2(s) {
8132
+ return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
8133
+ }
8134
+ function parseDecisionCandidates(md) {
8135
+ const out = [];
8136
+ for (const line of md.split(/\r?\n/)) {
8137
+ const m = line.match(/^##\s+(.+?)\s*$/);
8138
+ if (m) {
8139
+ const raw = m[1].trim();
8140
+ if (!raw || /^_.*_$/.test(raw)) continue;
8141
+ const t = cleanCandidateTitle(raw);
8142
+ if (t) out.push(t);
8143
+ }
8144
+ }
8145
+ return out;
8146
+ }
8147
+ function countAdrs(dir) {
8148
+ const base = join(dir, DECISIONS_DIR);
8149
+ if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
8150
+ let total = 0;
8151
+ let draft = 0;
8152
+ let accepted = 0;
8153
+ for (const entry of readDir(base)) {
8154
+ if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
8155
+ const full = join(base, entry);
8156
+ if (!isFile(full)) continue;
8157
+ total += 1;
8158
+ let content = "";
8159
+ try {
8160
+ content = readFile(full);
8161
+ } catch {
8162
+ continue;
8163
+ }
8164
+ const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
8165
+ if (status === "accepted") accepted += 1;
8166
+ else draft += 1;
8167
+ }
8168
+ return { total, draft, accepted };
8169
+ }
8170
+ function buildTechDecisions(dir) {
8171
+ const candFile = join(dir, CANDIDATES_PATH);
8172
+ let titles = [];
8173
+ if (exists(candFile)) {
8174
+ try {
8175
+ titles = parseDecisionCandidates(readFile(candFile));
8176
+ } catch {
8177
+ titles = [];
8178
+ }
8179
+ }
8180
+ const { total, draft, accepted } = countAdrs(dir);
8181
+ const candidate_list = titles.map((title, i) => {
8182
+ const n = String(total + i + 1).padStart(3, "0");
8183
+ return { title, source: CANDIDATES_PATH, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8184
+ });
8185
+ let status;
8186
+ if (accepted > 0) status = "accepted-adrs";
8187
+ else if (total > 0) status = "draft-adrs";
8188
+ else if (titles.length > 0) status = "candidates";
8189
+ else status = "none";
8190
+ return {
8191
+ status,
8192
+ candidates: titles.length,
8193
+ adrs: total,
8194
+ draft_adrs: draft,
8195
+ accepted_adrs: accepted,
8196
+ candidate_list
8197
+ };
8198
+ }
8199
+
8089
8200
  // src/core/project-explain.ts
8090
8201
  var ARCH_DIR4 = "knowledge";
8091
8202
  function normalizeTitle(t) {
@@ -8297,7 +8408,8 @@ function buildProjectExplanation(dir) {
8297
8408
  missingKnowledge,
8298
8409
  suggestedNextSteps,
8299
8410
  readiness,
8300
- nextStepRecommendation: readiness.nextStepRecommendation
8411
+ nextStepRecommendation: readiness.nextStepRecommendation,
8412
+ techDecisions: buildTechDecisions(dir)
8301
8413
  };
8302
8414
  }
8303
8415
  function stateLabel(state) {
@@ -8511,6 +8623,16 @@ function renderExplanationHuman(exp) {
8511
8623
  lines.push("### Recommended next step");
8512
8624
  lines.push(r.recommended_next_step.label);
8513
8625
  lines.push("");
8626
+ const td = exp.techDecisions;
8627
+ lines.push("## Tech Decisions");
8628
+ lines.push(`- Decision candidates: ${td.candidates}`);
8629
+ lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
8630
+ lines.push(`- Status: ${td.status}`);
8631
+ if (td.candidates > 0 && td.adrs === 0) {
8632
+ lines.push("");
8633
+ lines.push("Use the adr-writing skill to materialize decision candidates into ADRs (`kaddo adr`) before implementing related technical Work Items.");
8634
+ }
8635
+ lines.push("");
8514
8636
  return lines.join("\n").trimEnd() + "\n";
8515
8637
  }
8516
8638
  function renderExplanationAgent(exp) {
@@ -8855,6 +8977,10 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8855
8977
  }
8856
8978
  });
8857
8979
  const nextStepRecommendation = resolveNextStep(dir, now);
8980
+ const techDecisions = buildTechDecisions(dir);
8981
+ if (techDecisions.candidates > 0 && techDecisions.adrs === 0) {
8982
+ missing.push(`${techDecisions.candidates} technical decision candidate(s) not yet materialized as ADRs (run \`kaddo adr\`).`);
8983
+ }
8858
8984
  const unifiedPhase = {
8859
8985
  ...phase,
8860
8986
  nextStep: nextStepRecommendation.label,
@@ -8892,6 +9018,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8892
9018
  roadmap,
8893
9019
  phase: unifiedPhase,
8894
9020
  nextStepRecommendation,
9021
+ techDecisions,
8895
9022
  deliveryMix,
8896
9023
  external: loadExternalCapsules(dir),
8897
9024
  graph: loadGraphSummary(dir),
@@ -9315,12 +9442,12 @@ function renderUnderstandTerminal(plan) {
9315
9442
 
9316
9443
  // src/core/delivery.ts
9317
9444
  import { parse as parseYaml10 } from "yaml";
9318
- function slugify2(s) {
9445
+ function slugify3(s) {
9319
9446
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9320
9447
  }
9321
9448
  function toActive(a) {
9322
9449
  const id = a.id || a.title || "WI";
9323
- return { id, title: a.title || id, type: a.type, slug: slugify2(a.title || id) };
9450
+ return { id, title: a.title || id, type: a.type, slug: slugify3(a.title || id) };
9324
9451
  }
9325
9452
  function activeWorkItems(dir) {
9326
9453
  return discoverWorkItems(dir).filter((a) => a.lifecycle === "in-progress").map(toActive);
@@ -9426,6 +9553,13 @@ function runUnderstand() {
9426
9553
  for (const s of recSkills) console.log(` - ${s}`);
9427
9554
  }
9428
9555
  }
9556
+ const td = exp.techDecisions;
9557
+ if (td.candidates > 0 && td.adrs === 0) {
9558
+ console.log("");
9559
+ console.log(`Tech decisions: ${td.candidates} decision candidate(s) not yet materialized as ADRs.`);
9560
+ console.log(" \u2192 Use the adr-writing skill to create ADR drafts from `knowledge/tech/decision-candidates.md`");
9561
+ console.log(" into `knowledge/tech/decisions/` before implementing affected technical Work Items (`kaddo adr`).");
9562
+ }
9429
9563
  if (exp.externalCapsules.length > 0) {
9430
9564
  console.log("");
9431
9565
  console.log("External knowledge:");
@@ -11940,7 +12074,7 @@ var MODULE_TYPES = [
11940
12074
  "data",
11941
12075
  "unknown"
11942
12076
  ];
11943
- function slugify3(name) {
12077
+ function slugify4(name) {
11944
12078
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11945
12079
  }
11946
12080
  function readModulesDescriptor(dir) {
@@ -11960,7 +12094,7 @@ function moduleDir(id) {
11960
12094
  return `knowledge/tech/modules/${id}`;
11961
12095
  }
11962
12096
  function buildModule(input) {
11963
- const id = slugify3(input.name);
12097
+ const id = slugify4(input.name);
11964
12098
  const base = moduleDir(id);
11965
12099
  return {
11966
12100
  id,
@@ -13869,6 +14003,44 @@ function runAdaptersStatus(opts = {}) {
13869
14003
  }
13870
14004
  }
13871
14005
 
14006
+ // src/commands/adr.ts
14007
+ function runAdr(opts = {}) {
14008
+ const dir = cwd();
14009
+ requireConfig(dir);
14010
+ const td = buildTechDecisions(dir);
14011
+ if (opts.json) {
14012
+ console.log(JSON.stringify(td, null, 2));
14013
+ return;
14014
+ }
14015
+ console.log("");
14016
+ console.log("Tech Decisions");
14017
+ console.log(` Decision candidates: ${td.candidates}`);
14018
+ console.log(` ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
14019
+ console.log(` Status: ${td.status}`);
14020
+ if (td.candidate_list.length > 0 && td.adrs === 0) {
14021
+ console.log("");
14022
+ console.log("ADR candidates found:");
14023
+ td.candidate_list.forEach((c, i) => {
14024
+ console.log("");
14025
+ console.log(`${i + 1}. ${c.title}`);
14026
+ console.log(` Source: ${c.source}`);
14027
+ console.log(` Suggested ADR: ${c.suggestedAdrFile}`);
14028
+ });
14029
+ console.log("");
14030
+ console.log("Next:");
14031
+ console.log(" Use the adr-writing skill to create ADR drafts from these candidates");
14032
+ console.log(" (copy context + options; leave the decision/consequences as [open] for human review).");
14033
+ } else if (td.candidates === 0) {
14034
+ console.log("");
14035
+ console.log("No decision candidates found. Run the architecture-agent to produce");
14036
+ console.log("`knowledge/tech/decision-candidates.md`, or add ADRs under `knowledge/tech/decisions/`.");
14037
+ } else {
14038
+ console.log("");
14039
+ console.log("ADRs already exist. Review drafts and mark them `accepted` when confirmed.");
14040
+ }
14041
+ printCommandFooter("adr");
14042
+ }
14043
+
13872
14044
  // src/index.ts
13873
14045
  var require2 = createRequire(import.meta.url);
13874
14046
  var { version } = require2("../package.json");
@@ -13922,6 +14094,9 @@ program.command("drift").description("Drift Trend Report from recorded `kaddo gu
13922
14094
  var questionsAction = (opts) => runQuestions(opts);
13923
14095
  program.command("questions").description("Open-questions readiness gate: blocking/important/deferred decisions before the roadmap").option("--json", "Output JSON instead of a summary").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/questions-report.md)").action(questionsAction);
13924
14096
  program.command("readiness").description("Alias for `kaddo questions`").option("--json", "Output JSON instead of a summary").option("--output <path>", "Write the report to a file").action(questionsAction);
14097
+ program.command("adr").alias("decisions").description("List technical decision candidates and the ADR files to create from them (read-only)").option("--json", "Output JSON").action((opts) => {
14098
+ runAdr(opts);
14099
+ });
13925
14100
  var adaptersCmd = program.command("adapters").description("Generate adapters that project Kaddo knowledge for external coding agents");
13926
14101
  adaptersCmd.command("install <adapter>").description("Generate an adapter file (codex/opencode/antigravity/kiro \u2192 AGENTS.md, claude \u2192 CLAUDE.md) from Kaddo knowledge").option("--force", "Overwrite an existing output file").option("--inject", "Add or update only the Kaddo block in an existing file, preserving the rest").option("--dry-run", "Print the content without writing files").action((adapter, opts) => {
13927
14102
  runAdaptersInstall(adapter, opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.39.1",
3
+ "version": "3.40.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {