@kaddo/cli 3.40.0 → 3.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +133 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -538,6 +538,8 @@ create --from roadmap → owners → guard → explain`.
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
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` |
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` |
541
543
 
542
544
  **Optional modules (installed with `kaddo add`):**
543
545
 
package/dist/index.js CHANGED
@@ -708,6 +708,10 @@ var COMMAND_HELP = {
708
708
  adr: {
709
709
  question: "Which technical decisions still need to become ADRs?",
710
710
  next: "Use the adr-writing skill to draft ADRs, then mark them accepted when confirmed"
711
+ },
712
+ "tech organize": {
713
+ question: "Is knowledge/tech/ cleanly separated (core vs discovery vs decisions)?",
714
+ next: "Discovery notes now live under knowledge/tech/discovery/"
711
715
  }
712
716
  };
713
717
  function commandFooterLines(name) {
@@ -2439,9 +2443,9 @@ Optionally provide: existing diagrams, infra config, README, dependency manifest
2439
2443
 
2440
2444
  Markdown artifacts intended to be saved as:
2441
2445
 
2442
- - \`knowledge/tech/current-state.md\`
2443
- - \`knowledge/tech/architecture-notes.md\`
2444
- - \`knowledge/tech/decision-candidates.md\`
2446
+ - \`knowledge/tech/current-state.md\` (core artifact)
2447
+ - \`knowledge/tech/discovery/architecture-notes.md\` (discovery note)
2448
+ - \`knowledge/tech/discovery/decision-candidates.md\` (discovery input for ADRs)
2445
2449
 
2446
2450
  ## Instructions
2447
2451
 
@@ -2487,10 +2491,12 @@ Generated from Kaddo Context Pack.
2487
2491
 
2488
2492
  ## Where to Save the Result
2489
2493
 
2490
- Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
2491
- \`knowledge/tech/architecture-notes.md\`, and decision candidates as
2492
- \`knowledge/tech/decision-candidates.md\`. Final ADRs always live under
2493
- \`knowledge/tech/decisions/\` \u2014 never directly in \`knowledge/tech/\`.
2494
+ Save the architecture overview as \`knowledge/tech/current-state.md\` (a **core** artifact). Save
2495
+ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decision candidates as
2496
+ \`knowledge/tech/discovery/decision-candidates.md\` \u2014 **discovery** inputs live under
2497
+ \`knowledge/tech/discovery/\`, not directly in \`knowledge/tech/\` (VS-075.2). Final ADRs always live
2498
+ under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
2499
+ compatibility, but new output should use \`discovery/\`.
2494
2500
 
2495
2501
  ## Quality Checklist
2496
2502
 
@@ -8123,18 +8129,31 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
8123
8129
  }
8124
8130
 
8125
8131
  // src/core/decisions.ts
8126
- var CANDIDATES_PATH = "knowledge/tech/decision-candidates.md";
8132
+ var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
8133
+ var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
8127
8134
  var DECISIONS_DIR = "knowledge/tech/decisions";
8135
+ function resolveCandidatesPath(dir) {
8136
+ const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
8137
+ const legacy = exists(join(dir, CANDIDATES_LEGACY));
8138
+ if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
8139
+ if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
8140
+ return { path: null, legacy: false, bothExist: false };
8141
+ }
8142
+ function cleanCandidateTitle(title) {
8143
+ return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
8144
+ }
8128
8145
  function slugify2(s) {
8129
- return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
8146
+ return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
8130
8147
  }
8131
8148
  function parseDecisionCandidates(md) {
8132
8149
  const out = [];
8133
8150
  for (const line of md.split(/\r?\n/)) {
8134
8151
  const m = line.match(/^##\s+(.+?)\s*$/);
8135
8152
  if (m) {
8136
- const t = m[1].trim();
8137
- if (t && !/^_.*_$/.test(t)) out.push(t);
8153
+ const raw = m[1].trim();
8154
+ if (!raw || /^_.*_$/.test(raw)) continue;
8155
+ const t = cleanCandidateTitle(raw);
8156
+ if (t) out.push(t);
8138
8157
  }
8139
8158
  }
8140
8159
  return out;
@@ -8163,11 +8182,11 @@ function countAdrs(dir) {
8163
8182
  return { total, draft, accepted };
8164
8183
  }
8165
8184
  function buildTechDecisions(dir) {
8166
- const candFile = join(dir, CANDIDATES_PATH);
8185
+ const resolved = resolveCandidatesPath(dir);
8167
8186
  let titles = [];
8168
- if (exists(candFile)) {
8187
+ if (resolved.path) {
8169
8188
  try {
8170
- titles = parseDecisionCandidates(readFile(candFile));
8189
+ titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
8171
8190
  } catch {
8172
8191
  titles = [];
8173
8192
  }
@@ -8175,7 +8194,7 @@ function buildTechDecisions(dir) {
8175
8194
  const { total, draft, accepted } = countAdrs(dir);
8176
8195
  const candidate_list = titles.map((title, i) => {
8177
8196
  const n = String(total + i + 1).padStart(3, "0");
8178
- return { title, source: CANDIDATES_PATH, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8197
+ return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify2(title)}.md` };
8179
8198
  });
8180
8199
  let status;
8181
8200
  if (accepted > 0) status = "accepted-adrs";
@@ -8188,7 +8207,10 @@ function buildTechDecisions(dir) {
8188
8207
  adrs: total,
8189
8208
  draft_adrs: draft,
8190
8209
  accepted_adrs: accepted,
8191
- candidate_list
8210
+ candidate_list,
8211
+ candidates_source: resolved.path,
8212
+ candidates_legacy_location: resolved.legacy,
8213
+ candidates_both_exist: resolved.bothExist
8192
8214
  };
8193
8215
  }
8194
8216
 
@@ -8404,7 +8426,18 @@ function buildProjectExplanation(dir) {
8404
8426
  suggestedNextSteps,
8405
8427
  readiness,
8406
8428
  nextStepRecommendation: readiness.nextStepRecommendation,
8407
- techDecisions: buildTechDecisions(dir)
8429
+ techDecisions: buildTechDecisions(dir),
8430
+ techKnowledge: {
8431
+ core: {
8432
+ currentState: exists(join(dir, "knowledge/tech/current-state.md")),
8433
+ codebase: exists(join(dir, "knowledge/tech/codebase.md"))
8434
+ },
8435
+ discovery: {
8436
+ architectureNotes: exists(join(dir, "knowledge/tech/discovery/architecture-notes.md")) || exists(join(dir, "knowledge/tech/architecture-notes.md")),
8437
+ decisionCandidates: exists(join(dir, "knowledge/tech/discovery/decision-candidates.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md")),
8438
+ legacyLocation: exists(join(dir, "knowledge/tech/architecture-notes.md")) || exists(join(dir, "knowledge/tech/decision-candidates.md"))
8439
+ }
8440
+ }
8408
8441
  };
8409
8442
  }
8410
8443
  function stateLabel(state) {
@@ -8619,15 +8652,26 @@ function renderExplanationHuman(exp) {
8619
8652
  lines.push(r.recommended_next_step.label);
8620
8653
  lines.push("");
8621
8654
  const td = exp.techDecisions;
8622
- lines.push("## Tech Decisions");
8623
- lines.push(`- Decision candidates: ${td.candidates}`);
8624
- lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
8625
- lines.push(`- Status: ${td.status}`);
8655
+ const tk = exp.techKnowledge;
8656
+ const mark = (b) => b ? "\u2713" : "\u2717";
8657
+ lines.push("## Tech Knowledge");
8658
+ lines.push("Core:");
8659
+ lines.push(`- ${mark(tk.core.currentState)} current-state.md`);
8660
+ lines.push(`- ${mark(tk.core.codebase)} codebase.md`);
8661
+ lines.push("Decisions:");
8662
+ lines.push(`- ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs}) \xB7 status: ${td.status}`);
8663
+ lines.push("Discovery:");
8664
+ lines.push(`- ${mark(tk.discovery.architectureNotes)} architecture-notes.md`);
8665
+ lines.push(`- ${mark(tk.discovery.decisionCandidates)} decision-candidates.md`);
8666
+ lines.push("");
8626
8667
  if (td.candidates > 0 && td.adrs === 0) {
8627
- lines.push("");
8628
8668
  lines.push("Use the adr-writing skill to materialize decision candidates into ADRs (`kaddo adr`) before implementing related technical Work Items.");
8669
+ lines.push("");
8670
+ }
8671
+ if (tk.discovery.legacyLocation) {
8672
+ lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
8673
+ lines.push("");
8629
8674
  }
8630
- lines.push("");
8631
8675
  return lines.join("\n").trimEnd() + "\n";
8632
8676
  }
8633
8677
  function renderExplanationAgent(exp) {
@@ -8976,6 +9020,22 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8976
9020
  if (techDecisions.candidates > 0 && techDecisions.adrs === 0) {
8977
9021
  missing.push(`${techDecisions.candidates} technical decision candidate(s) not yet materialized as ADRs (run \`kaddo adr\`).`);
8978
9022
  }
9023
+ const techFile = (rel) => exists(join(dir, ARCH_DIR6, rel));
9024
+ const techKnowledge = {
9025
+ core: {
9026
+ "current-state.md": techFile("tech/current-state.md"),
9027
+ "codebase.md": techFile("tech/codebase.md")
9028
+ },
9029
+ decisions: { adrs: techDecisions.adrs, dir: exists(join(dir, ARCH_DIR6, "tech/decisions")) },
9030
+ discovery: {
9031
+ "architecture-notes.md": techFile("tech/discovery/architecture-notes.md") || techFile("tech/architecture-notes.md"),
9032
+ "decision-candidates.md": techFile("tech/discovery/decision-candidates.md") || techFile("tech/decision-candidates.md"),
9033
+ legacyLocation: techFile("tech/architecture-notes.md") || techFile("tech/decision-candidates.md")
9034
+ }
9035
+ };
9036
+ if (techKnowledge.discovery.legacyLocation) {
9037
+ missing.push("Tech discovery files are in the legacy `knowledge/tech/` root. Run `kaddo tech organize` to move them to `knowledge/tech/discovery/`.");
9038
+ }
8979
9039
  const unifiedPhase = {
8980
9040
  ...phase,
8981
9041
  nextStep: nextStepRecommendation.label,
@@ -9014,6 +9074,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
9014
9074
  phase: unifiedPhase,
9015
9075
  nextStepRecommendation,
9016
9076
  techDecisions,
9077
+ techKnowledge,
9017
9078
  deliveryMix,
9018
9079
  external: loadExternalCapsules(dir),
9019
9080
  graph: loadGraphSummary(dir),
@@ -12720,7 +12781,7 @@ var FILE_TARGETS = [
12720
12781
  { path: "knowledge/tech/current-state.md", kind: "current-state" },
12721
12782
  { path: "knowledge/delivery/roadmap.md", kind: "roadmap" }
12722
12783
  ];
12723
- var DIR_TARGETS = ["knowledge/tech/decisions", "knowledge/delivery/work-items"];
12784
+ var DIR_TARGETS = ["knowledge/tech/decisions", "knowledge/tech/discovery", "knowledge/delivery/work-items"];
12724
12785
  function withLanguageDirective(content, language) {
12725
12786
  if (language !== "es") return content;
12726
12787
  const note = "> Idioma del proyecto: **espa\xF1ol**. Escribe este conocimiento en espa\xF1ol. Mant\xE9n en ingl\xE9s el c\xF3digo, los nombres de archivo, los comandos y las claves de configuraci\xF3n.\n";
@@ -14012,6 +14073,15 @@ function runAdr(opts = {}) {
14012
14073
  console.log(` Decision candidates: ${td.candidates}`);
14013
14074
  console.log(` ADRs: ${td.adrs} (draft: ${td.draft_adrs}, accepted: ${td.accepted_adrs})`);
14014
14075
  console.log(` Status: ${td.status}`);
14076
+ if (td.candidates_source) console.log(` Source: ${td.candidates_source}`);
14077
+ if (td.candidates_both_exist) {
14078
+ console.log("");
14079
+ console.log(" Note: both decision-candidate files exist.");
14080
+ console.log(" Using: knowledge/tech/discovery/decision-candidates.md");
14081
+ console.log(" Legacy file also found: knowledge/tech/decision-candidates.md");
14082
+ } else if (td.candidates_legacy_location) {
14083
+ console.log(" (legacy location \u2014 consider `kaddo tech organize` to move it to knowledge/tech/discovery/)");
14084
+ }
14015
14085
  if (td.candidate_list.length > 0 && td.adrs === 0) {
14016
14086
  console.log("");
14017
14087
  console.log("ADR candidates found:");
@@ -14036,6 +14106,41 @@ function runAdr(opts = {}) {
14036
14106
  printCommandFooter("adr");
14037
14107
  }
14038
14108
 
14109
+ // src/commands/tech.ts
14110
+ import fs3 from "fs";
14111
+ var DISCOVERY_FILES = ["architecture-notes.md", "decision-candidates.md"];
14112
+ var DISCOVERY_DIR = "knowledge/tech/discovery";
14113
+ function runTechOrganize(dir = cwd()) {
14114
+ requireConfig(dir);
14115
+ intro2("kaddo tech organize");
14116
+ const moved = [];
14117
+ const skipped = [];
14118
+ for (const file of DISCOVERY_FILES) {
14119
+ const from = join(dir, "knowledge/tech", file);
14120
+ const to = join(dir, DISCOVERY_DIR, file);
14121
+ if (!exists(from)) continue;
14122
+ if (exists(to)) {
14123
+ skipped.push(file);
14124
+ continue;
14125
+ }
14126
+ ensureDir(join(dir, DISCOVERY_DIR));
14127
+ fs3.renameSync(from, to);
14128
+ moved.push(`knowledge/tech/${file} \u2192 ${DISCOVERY_DIR}/${file}`);
14129
+ }
14130
+ if (moved.length > 0) {
14131
+ log2.success("Moved:");
14132
+ for (const m of moved) console.log(` - ${m}`);
14133
+ }
14134
+ for (const file of skipped) {
14135
+ log2.warn(`Cannot move ${file} because knowledge/tech/discovery/${file} already exists. Review both files manually.`);
14136
+ }
14137
+ if (moved.length === 0 && skipped.length === 0) {
14138
+ log2.info("Nothing to organize \u2014 no discovery files in the legacy `knowledge/tech/` root.");
14139
+ }
14140
+ printCommandFooter("tech organize");
14141
+ outro2("Tech knowledge organized.");
14142
+ }
14143
+
14039
14144
  // src/index.ts
14040
14145
  var require2 = createRequire(import.meta.url);
14041
14146
  var { version } = require2("../package.json");
@@ -14089,6 +14194,10 @@ program.command("drift").description("Drift Trend Report from recorded `kaddo gu
14089
14194
  var questionsAction = (opts) => runQuestions(opts);
14090
14195
  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);
14091
14196
  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);
14197
+ var techCmd = program.command("tech").description("Organize the knowledge/tech/ structure (core vs discovery vs decisions)");
14198
+ techCmd.command("organize").description("Move discovery artifacts into knowledge/tech/discovery/ (never overwrites, no content change)").action(() => {
14199
+ runTechOrganize();
14200
+ });
14092
14201
  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) => {
14093
14202
  runAdr(opts);
14094
14203
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.40.0",
3
+ "version": "3.41.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {