@kaddo/cli 3.55.0 → 3.57.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 (2) hide show
  1. package/dist/index.js +216 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ import * as p from "@clack/prompts";
45
45
  var intro2 = (title) => p.intro(title);
46
46
  var outro2 = (msg) => p.outro(msg);
47
47
  var log2 = p.log;
48
+ var cancel2 = (msg) => p.cancel(msg);
48
49
  async function text2(opts) {
49
50
  const val = await p.text(opts);
50
51
  if (p.isCancel(val)) {
@@ -1145,8 +1146,8 @@ function loadConfig(dir) {
1145
1146
  const parsed = configSchema.safeParse(raw ?? {});
1146
1147
  if (!parsed.success) {
1147
1148
  const issues = parsed.error.issues.map((i) => {
1148
- const path6 = i.path.join(".");
1149
- return path6 ? ` - ${path6}: ${i.message}` : ` - ${i.message}`;
1149
+ const path7 = i.path.join(".");
1150
+ return path7 ? ` - ${path7}: ${i.message}` : ` - ${i.message}`;
1150
1151
  }).join("\n");
1151
1152
  throw new ConfigError(`Invalid .kaddo/config.yml:
1152
1153
  ${issues}`);
@@ -6000,7 +6001,7 @@ function parseArtifact(filePath, raw) {
6000
6001
  status: String(data.status ?? ""),
6001
6002
  phase: String(data.phase ?? ""),
6002
6003
  initiative: String(data.initiative ?? data.source_initiative ?? ""),
6003
- source: data.source ? String(data.source) : "",
6004
+ source: data.source && typeof data.source === "object" ? String(data.source.type ?? "") : data.source ? String(data.source) : "",
6004
6005
  sourceId: String(data.source_id ?? ""),
6005
6006
  rawFrontmatter: data,
6006
6007
  decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : [],
@@ -6192,10 +6193,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
6192
6193
  import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
6193
6194
  var IGNORE_FILE = ".kaddo/ignores.yml";
6194
6195
  function loadIgnores(dir) {
6195
- const path6 = join(dir, IGNORE_FILE);
6196
- if (!exists(path6)) return [];
6196
+ const path7 = join(dir, IGNORE_FILE);
6197
+ if (!exists(path7)) return [];
6197
6198
  try {
6198
- const raw = readFile(path6);
6199
+ const raw = readFile(path7);
6199
6200
  const parsed = parseYaml4(raw);
6200
6201
  return Array.isArray(parsed) ? parsed : [];
6201
6202
  } catch {
@@ -6203,7 +6204,7 @@ function loadIgnores(dir) {
6203
6204
  }
6204
6205
  }
6205
6206
  function saveIgnore(dir, entry) {
6206
- const path6 = join(dir, IGNORE_FILE);
6207
+ const path7 = join(dir, IGNORE_FILE);
6207
6208
  const existing = loadIgnores(dir);
6208
6209
  const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
6209
6210
  if (idx >= 0) {
@@ -6211,17 +6212,17 @@ function saveIgnore(dir, entry) {
6211
6212
  } else {
6212
6213
  existing.push(entry);
6213
6214
  }
6214
- writeFile(path6, stringifyYaml2(existing));
6215
+ writeFile(path7, stringifyYaml2(existing));
6215
6216
  }
6216
6217
  function isIgnored(ignores, artifactId) {
6217
6218
  return ignores.find((e) => e.artifact_id === artifactId);
6218
6219
  }
6219
6220
  function removeIgnore(dir, artifactId) {
6220
- const path6 = join(dir, IGNORE_FILE);
6221
+ const path7 = join(dir, IGNORE_FILE);
6221
6222
  const existing = loadIgnores(dir);
6222
6223
  const filtered = existing.filter((e) => e.artifact_id !== artifactId);
6223
6224
  if (filtered.length === existing.length) return false;
6224
- writeFile(path6, stringifyYaml2(filtered));
6225
+ writeFile(path7, stringifyYaml2(filtered));
6225
6226
  return true;
6226
6227
  }
6227
6228
 
@@ -6243,11 +6244,11 @@ function moduleArtifactCoverage(dir, id) {
6243
6244
  };
6244
6245
  }
6245
6246
  function loadMappedModules(dir) {
6246
- const path6 = join(dir, DESCRIPTOR_PATH);
6247
- if (!exists(path6)) return [];
6247
+ const path7 = join(dir, DESCRIPTOR_PATH);
6248
+ if (!exists(path7)) return [];
6248
6249
  let parsed;
6249
6250
  try {
6250
- parsed = parseYaml5(readFile(path6));
6251
+ parsed = parseYaml5(readFile(path7));
6251
6252
  } catch {
6252
6253
  return [];
6253
6254
  }
@@ -6676,7 +6677,7 @@ function buildGuardHistory(dir) {
6676
6677
  const d = hotspotDir(t.code_path);
6677
6678
  hotspotMap.set(d, (hotspotMap.get(d) ?? 0) + 1);
6678
6679
  }
6679
- const hotspots = [...hotspotMap.entries()].map(([path6, warnings]) => ({ path: path6, warnings })).sort((a, b) => b.warnings - a.warnings);
6680
+ const hotspots = [...hotspotMap.entries()].map(([path7, warnings]) => ({ path: path7, warnings })).sort((a, b) => b.warnings - a.warnings);
6680
6681
  return {
6681
6682
  available: true,
6682
6683
  total_runs: sorted.length,
@@ -7377,7 +7378,7 @@ function sectionParagraph(md, title) {
7377
7378
  }
7378
7379
  return "";
7379
7380
  }
7380
- function parseCapsule(id, path6, md) {
7381
+ function parseCapsule(id, path7, md) {
7381
7382
  const { data } = matter3(md);
7382
7383
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
7383
7384
  let ageDays = null;
@@ -7387,7 +7388,7 @@ function parseCapsule(id, path6, md) {
7387
7388
  }
7388
7389
  return {
7389
7390
  id,
7390
- path: path6,
7391
+ path: path7,
7391
7392
  system: data.system ? String(data.system) : id,
7392
7393
  owner: data.owner ? String(data.owner) : void 0,
7393
7394
  updatedAt,
@@ -7473,9 +7474,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
7473
7474
  ];
7474
7475
  const presentLayers = [];
7475
7476
  for (const layer2 of layerDocs) {
7476
- const path6 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
7477
- if (path6) {
7478
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path6 });
7477
+ const path7 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
7478
+ if (path7) {
7479
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path7 });
7479
7480
  presentLayers.push(layer2.id);
7480
7481
  }
7481
7482
  }
@@ -8983,10 +8984,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
8983
8984
  const discovery = state === "pre-ai" || state === "legacy";
8984
8985
  const resolveAgent = (agent) => {
8985
8986
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
8986
- const path6 = agentInstallPath(file);
8987
- const installed = isFile(join(dir, path6));
8987
+ const path7 = agentInstallPath(file);
8988
+ const installed = isFile(join(dir, path7));
8988
8989
  const group = agentGroupOf(file);
8989
- return { agentPath: path6, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
8990
+ return { agentPath: path7, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
8990
8991
  };
8991
8992
  const refine = (id, agent, target, quality, verb = "complete") => {
8992
8993
  const ar = resolveAgent(agent);
@@ -9026,12 +9027,23 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
9026
9027
  if (oq.summary.blocking_open > 0) {
9027
9028
  return { id: "questions", phase: "Knowledge Refinement", label: "Run `kaddo questions` to see source locations and resolution guidance, then resolve, assume or defer the blocking open questions.", command: "kaddo questions", reason: `${oq.summary.blocking_open} blocking open question(s) remain.` };
9028
9029
  }
9030
+ const st = buildDeliveryState(dir);
9031
+ const secondary = buildSecondaryRecommendations(st);
9029
9032
  const roadmap = roadmapSignal(dir);
9033
+ if (roadmap !== "has-candidates" && st.draft_work_items > 0) {
9034
+ return {
9035
+ id: "refine-work-item",
9036
+ phase: "Active Delivery",
9037
+ label: "Refine the existing draft Work Item with the work-item-agent.",
9038
+ agent: "work-item-agent",
9039
+ skill: "work-item-refinement",
9040
+ reason: `There ${st.draft_work_items === 1 ? "is" : "are"} ${st.draft_work_items} draft Work Item${st.draft_work_items === 1 ? "" : "s"}. Refine before defining roadmap candidates.`,
9041
+ ...secondary.length ? { secondary } : {}
9042
+ };
9043
+ }
9030
9044
  if (roadmap !== "has-candidates") {
9031
9045
  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." };
9032
9046
  }
9033
- const st = buildDeliveryState(dir);
9034
- const secondary = buildSecondaryRecommendations(st);
9035
9047
  if (st.total_work_items === 0) {
9036
9048
  return {
9037
9049
  id: "create-work-item",
@@ -9400,7 +9412,25 @@ function isValidSource(s) {
9400
9412
  return VALID_SOURCES.includes(s);
9401
9413
  }
9402
9414
  function parseWorkItemSource(frontmatter) {
9403
- const raw = frontmatter.source ? String(frontmatter.source) : "";
9415
+ const rawSource = frontmatter.source;
9416
+ if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
9417
+ const obj = rawSource;
9418
+ const t = optStr(obj.type);
9419
+ const type = t && isValidSource(t) ? t : "unknown";
9420
+ return {
9421
+ type,
9422
+ id: optStr(obj.id) ?? optStr(frontmatter.source_id),
9423
+ title: optStr(obj.title) ?? optStr(frontmatter.source_title),
9424
+ context: optStr(obj.context) ?? optStr(frontmatter.source_context),
9425
+ provider: optStr(obj.provider) ?? optStr(frontmatter.source_provider),
9426
+ url: optStr(obj.url) ?? optStr(frontmatter.source_url),
9427
+ imported_at: optStr(obj.imported_at) ?? optStr(frontmatter.source_imported_at),
9428
+ synced_at: optStr(obj.synced_at) ?? optStr(frontmatter.source_synced_at),
9429
+ inferred: obj.inferred === true || obj.inferred === "true",
9430
+ reason: t && !isValidSource(t) ? `Invalid source type in object: "${t}".` : void 0
9431
+ };
9432
+ }
9433
+ const raw = rawSource ? String(rawSource) : "";
9404
9434
  if (raw && isValidSource(raw)) {
9405
9435
  return {
9406
9436
  type: raw,
@@ -12731,10 +12761,10 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
12731
12761
  import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
12732
12762
  var DESCRIPTOR_PATH2 = "knowledge/module.yml";
12733
12763
  function readDescriptor(dir) {
12734
- const path6 = join(dir, DESCRIPTOR_PATH2);
12735
- if (!exists(path6)) return null;
12764
+ const path7 = join(dir, DESCRIPTOR_PATH2);
12765
+ if (!exists(path7)) return null;
12736
12766
  try {
12737
- return parseYaml13(readFile(path6));
12767
+ return parseYaml13(readFile(path7));
12738
12768
  } catch {
12739
12769
  return null;
12740
12770
  }
@@ -14339,10 +14369,10 @@ function slugify4(name) {
14339
14369
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
14340
14370
  }
14341
14371
  function readModulesDescriptor(dir) {
14342
- const path6 = join(dir, DESCRIPTOR_PATH3);
14343
- if (!exists(path6)) return { version: 1, modules: [] };
14372
+ const path7 = join(dir, DESCRIPTOR_PATH3);
14373
+ if (!exists(path7)) return { version: 1, modules: [] };
14344
14374
  try {
14345
- const parsed = parseYaml14(readFile(path6));
14375
+ const parsed = parseYaml14(readFile(path7));
14346
14376
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
14347
14377
  } catch {
14348
14378
  return { version: 1, modules: [] };
@@ -16410,6 +16440,156 @@ function runAssetsUpdate(kind, opts = {}) {
16410
16440
  outro2(`${LABEL[kind]} update complete.`);
16411
16441
  }
16412
16442
 
16443
+ // src/commands/ready.ts
16444
+ import fs4 from "fs";
16445
+ import path6 from "path";
16446
+ import matter11 from "gray-matter";
16447
+ function validateReadiness(content, frontmatter) {
16448
+ const warnings = [];
16449
+ const body = content.replace(/^---[\s\S]*?---/, "").trim();
16450
+ const lower = body.toLowerCase();
16451
+ if (!frontmatter.domains || Array.isArray(frontmatter.domains) && frontmatter.domains.length === 0) {
16452
+ warnings.push({ field: "domains", message: "No domains declared." });
16453
+ }
16454
+ const code = frontmatter.code;
16455
+ if (!code || Array.isArray(code) && code.length === 0) {
16456
+ warnings.push({ field: "code", message: "No code ownership globs declared." });
16457
+ }
16458
+ const hasAC = /## acceptance criteria|## criterios de aceptación/i.test(body);
16459
+ if (!hasAC) {
16460
+ warnings.push({ field: "acceptance_criteria", message: "No acceptance criteria section found." });
16461
+ } else {
16462
+ const acSection = extractSection(body, /## acceptance criteria|## criterios de aceptación/i);
16463
+ if (acSection && acSection.trim().length === 0) {
16464
+ warnings.push({ field: "acceptance_criteria", message: "Acceptance criteria section is empty." });
16465
+ }
16466
+ }
16467
+ const hasValidation = /## validation|## validación/i.test(body);
16468
+ if (!hasValidation) {
16469
+ warnings.push({ field: "validation", message: "No validation section found." });
16470
+ }
16471
+ const hasOQ = /## open questions|## preguntas abiertas/i.test(body);
16472
+ if (hasOQ) {
16473
+ const oqSection = extractSection(body, /## open questions|## preguntas abiertas/i);
16474
+ if (oqSection && !isResolvedSection(oqSection)) {
16475
+ warnings.push({ field: "open_questions", message: "Open questions are still present." });
16476
+ }
16477
+ }
16478
+ return warnings;
16479
+ }
16480
+ function extractSection(body, headerPattern) {
16481
+ const lines = body.split(/\r?\n/);
16482
+ let capture = false;
16483
+ const result = [];
16484
+ for (const line of lines) {
16485
+ if (capture) {
16486
+ if (/^#{1,3}\s/.test(line)) break;
16487
+ result.push(line);
16488
+ } else if (headerPattern.test(line)) {
16489
+ capture = true;
16490
+ }
16491
+ }
16492
+ return capture ? result.join("\n").trim() : null;
16493
+ }
16494
+ function isResolvedSection(text3) {
16495
+ const t = text3.trim().toLowerCase();
16496
+ return t === "" || /^(none|ninguna|resolved|resueltas|deferred|diferidas)\.?$/i.test(t);
16497
+ }
16498
+ function transitionToReady(filePath) {
16499
+ const raw = readFile(filePath);
16500
+ const { data, content } = matter11(raw);
16501
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
16502
+ data.status = "ready";
16503
+ data.ready_at = today2;
16504
+ const newContent = matter11.stringify(content, data);
16505
+ const posix = filePath.replace(/\\/g, "/");
16506
+ let newPath = filePath;
16507
+ if (posix.includes("/work-items/draft/")) {
16508
+ const readyDir = path6.dirname(filePath).replace(/[/\\]draft$/, path6.sep + "ready");
16509
+ ensureDir(readyDir);
16510
+ const fileName = path6.basename(filePath);
16511
+ newPath = path6.join(readyDir, fileName);
16512
+ }
16513
+ return { newPath, newContent };
16514
+ }
16515
+ function findWorkItem(dir, id) {
16516
+ const wis = discoverWorkItems(dir);
16517
+ return wis.find((w) => w.id.toLowerCase() === id.toLowerCase()) ?? null;
16518
+ }
16519
+ async function runReady(id, opts = {}) {
16520
+ const dir = process.cwd();
16521
+ intro2("kaddo ready " + id);
16522
+ const config = loadConfig(dir);
16523
+ if (!config) {
16524
+ log2.error("Kaddo is not initialized. Run `kaddo init` first.");
16525
+ process.exit(1);
16526
+ }
16527
+ const wi = findWorkItem(dir, id);
16528
+ if (!wi) {
16529
+ log2.error(`Work Item ${id} not found.`);
16530
+ log2.info("Run `kaddo history` or check knowledge/delivery/work-items/ for available IDs.");
16531
+ process.exit(1);
16532
+ }
16533
+ if (wi.lifecycle === "ready") {
16534
+ log2.info(`Work Item ${wi.id} is already ready.`);
16535
+ log2.info("Suggested next: kaddo context && kaddo understand");
16536
+ outro2("Nothing to do.");
16537
+ return;
16538
+ }
16539
+ if (wi.lifecycle !== "draft") {
16540
+ log2.error(`Work Item ${wi.id} is in state "${wi.lifecycle}", not "draft". Only draft Work Items can be marked as ready.`);
16541
+ process.exit(1);
16542
+ }
16543
+ const raw = readFile(wi.filePath);
16544
+ const { data } = matter11(raw);
16545
+ const source = parseWorkItemSource(data);
16546
+ const warnings = validateReadiness(raw, data);
16547
+ log2.info(`Work Item found:`);
16548
+ log2.info(` ${wi.id} \u2014 ${wi.title}`);
16549
+ log2.info("");
16550
+ log2.info(` Status: draft \u2192 ready`);
16551
+ log2.info(` Type: ${wi.type}`);
16552
+ log2.info(` Knowledge level: ${wi.knowledgeLevel || "unset"}`);
16553
+ log2.info(` Domains: ${wi.domains.length > 0 ? wi.domains.join(", ") : "none"}`);
16554
+ log2.info(` Source: ${renderSourceCompact(source)}`);
16555
+ if (wi.codeGlobs.length > 0) {
16556
+ log2.info(` Code ownership:`);
16557
+ for (const g of wi.codeGlobs) log2.info(` - ${g}`);
16558
+ } else {
16559
+ log2.info(` Code ownership: none`);
16560
+ }
16561
+ if (warnings.length > 0) {
16562
+ log2.warn("");
16563
+ log2.warn("This Work Item may not be ready:");
16564
+ for (const w of warnings) {
16565
+ log2.warn(` - ${w.message}`);
16566
+ }
16567
+ }
16568
+ if (!opts.yes) {
16569
+ log2.info("");
16570
+ const proceed = await confirm2({ message: "Mark this Work Item as ready for implementation?" });
16571
+ if (!proceed) {
16572
+ cancel2("Cancelled.");
16573
+ return;
16574
+ }
16575
+ }
16576
+ const { newPath, newContent } = transitionToReady(wi.filePath);
16577
+ if (newPath !== wi.filePath) {
16578
+ writeFile(newPath, newContent);
16579
+ fs4.unlinkSync(wi.filePath);
16580
+ log2.success(`Updated status: ready`);
16581
+ log2.success(`Moved file:`);
16582
+ log2.info(` ${path6.relative(dir, wi.filePath)}`);
16583
+ log2.info(` \u2192 ${path6.relative(dir, newPath)}`);
16584
+ } else {
16585
+ writeFile(wi.filePath, newContent);
16586
+ log2.success(`Updated status: ready`);
16587
+ }
16588
+ log2.info("");
16589
+ log2.info("Run `kaddo context && kaddo understand` to refresh the handoff.");
16590
+ outro2(`Work Item ${wi.id} is ready.`);
16591
+ }
16592
+
16413
16593
  // src/index.ts
16414
16594
  var require2 = createRequire(import.meta.url);
16415
16595
  var { version } = require2("../package.json");
@@ -16427,12 +16607,15 @@ program.command("bootstrap").description("Build the initial knowledge base for a
16427
16607
  program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike, chore). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
16428
16608
  await runCreate(type ?? "", opts);
16429
16609
  });
16610
+ program.command("ready <id>").description("Mark a draft Work Item as ready for implementation after human review").option("--yes", "Skip confirmation prompt").action(async (id, opts) => {
16611
+ await runReady(id, opts);
16612
+ });
16430
16613
  var capsuleCmd = program.command("capsule").description("Export this project as a Knowledge Capsule, or import an external one as context");
16431
16614
  capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
16432
16615
  runCapsuleExport();
16433
16616
  });
16434
- capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path6) => {
16435
- runCapsuleAdd(path6);
16617
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path7) => {
16618
+ runCapsuleAdd(path7);
16436
16619
  });
16437
16620
  var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
16438
16621
  graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.55.0",
3
+ "version": "3.57.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {