@kaddo/cli 3.25.1 → 3.26.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 +1 -0
  2. package/dist/index.js +308 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -516,6 +516,7 @@ create --from roadmap → owners → guard → explain`.
516
516
  | v3.24 | Estimated Savings Model: `kaddo savings` (+ `savings init`, `.kaddo/savings.yml`); evidence-based time/value estimates, confidence; MCP resource + tool |
517
517
  | v3.25 | Guard history & drift trend: `kaddo guard --record`, `kaddo drift`; feeds impact Guard Activity + savings Drift Prevention; MCP drift/guard-history resources + tool |
518
518
  | v3.25.1 | Savings guard-history messaging fix: distinguishes "no history" from "history with 0 resolved warnings" (drift prevention available at 0 h) |
519
+ | v3.26 | Open-questions readiness gate: `kaddo questions`/`readiness`, blocking/important/deferred classification; MCP open-questions + roadmap-readiness resources + tool; roadmap/work-item/implementation/bootstrap agents check it |
519
520
 
520
521
  **Optional modules (installed with `kaddo add`):**
521
522
 
package/dist/index.js CHANGED
@@ -696,6 +696,10 @@ var COMMAND_HELP = {
696
696
  drift: {
697
697
  question: "Are code and knowledge drifting apart over time?",
698
698
  next: "Review open warnings; `kaddo guard --record` before commits"
699
+ },
700
+ questions: {
701
+ question: "Are there open questions to decide before the roadmap?",
702
+ next: "Resolve, assume or defer blocking questions, then generate the roadmap"
699
703
  }
700
704
  };
701
705
  function commandFooterLines(name) {
@@ -2401,6 +2405,21 @@ open question):
2401
2405
  - \`knowledge/knowledge.md\`
2402
2406
  - business priorities
2403
2407
 
2408
+ ## Readiness Gate (check first)
2409
+
2410
+ Before generating the roadmap, check **roadmap readiness** for open questions that affect scope,
2411
+ architecture or the MVP. Read \`kaddo://roadmap-readiness\` (MCP) or run \`kaddo questions\`.
2412
+
2413
+ If readiness is \`needs_decisions\` (there are **blocking** open questions), do **not** generate the
2414
+ roadmap yet. Instead, list the blocking questions, propose reasonable assumptions for each, and ask
2415
+ the user to confirm, e.g.:
2416
+
2417
+ > Before generating the roadmap I found blocking open questions that affect the MVP scope.
2418
+ > I can proceed with these assumptions: \u2026 Confirm and continue?
2419
+
2420
+ Only generate the roadmap once the user confirms the assumptions (or resolves/defers the
2421
+ questions). Record confirmed assumptions explicitly in the roadmap.
2422
+
2404
2423
  ## Expected Output
2405
2424
 
2406
2425
  A single Markdown artifact intended to be saved as \`knowledge/delivery/roadmap.md\`.
@@ -2709,6 +2728,13 @@ Work Items into clear, traceable units of work.
2709
2728
  You do not write code. You sharpen the problem, validate the Knowledge Level and make the
2710
2729
  Work Item actionable for a human.
2711
2730
 
2731
+ ## Readiness Gate (check first)
2732
+
2733
+ For high-impact Work Items, check \`kaddo://roadmap-readiness\` (or \`kaddo questions\`) for
2734
+ **blocking** open questions related to this Work Item's scope. If any are unresolved, surface them
2735
+ and propose assumptions for the user to confirm before refining \u2014 don't bake in invisible
2736
+ assumptions.
2737
+
2712
2738
  ## When to Use
2713
2739
 
2714
2740
  Use this agent after a roadmap exists (\`knowledge/delivery/roadmap.md\`) or when an existing Work
@@ -3210,6 +3236,13 @@ You are the Kaddo Bootstrap Agent. You guide the transition from business defini
3210
3236
  initial architecture direction, quality attributes, a roadmap and first Work Items for a
3211
3237
  new project. You propose; the human decides.
3212
3238
 
3239
+ ## Readiness Gate
3240
+
3241
+ Bootstrap surfaces \`## Open Questions\` in the knowledge files. Before moving to the roadmap,
3242
+ explicitly recommend reviewing them (\`kaddo questions\` / \`kaddo://roadmap-readiness\`) and turning
3243
+ **blocking** ones into resolved decisions or confirmed assumptions \u2014 so the roadmap isn't built on
3244
+ invisible assumptions.
3245
+
3213
3246
  ## When to Use
3214
3247
 
3215
3248
  Use this agent after \`kaddo bootstrap\` and after the business artifacts are drafted.
@@ -3321,6 +3354,13 @@ may suggest a Git branch, and only by respecting the project's Git strategy.
3321
3354
  You never run Git yourself. The Kaddo CLI never runs Git either. Every git action is the
3322
3355
  human's, and commits/pushes/merges happen only with explicit human confirmation.
3323
3356
 
3357
+ ## Readiness Gate (check first)
3358
+
3359
+ Before implementing a high-impact Work Item, check \`kaddo://roadmap-readiness\` (or
3360
+ \`kaddo questions\`) for **blocking** open questions about stack, architecture, persistence,
3361
+ authentication or the Work Item's scope. If any are unresolved, pause and ask the user to confirm
3362
+ assumptions or resolve them before writing code.
3363
+
3324
3364
  ## When to Use
3325
3365
 
3326
3366
  Use this agent after the work-item-agent has produced a clear, traceable Work Item under
@@ -8162,6 +8202,224 @@ function renderDeliveryLifecycle(wi) {
8162
8202
  ];
8163
8203
  }
8164
8204
 
8205
+ // src/core/open-questions.ts
8206
+ var SOURCES = [
8207
+ "knowledge/business/business.md",
8208
+ "knowledge/product/product.md",
8209
+ "knowledge/tech/codebase.md",
8210
+ "knowledge/delivery/roadmap.md"
8211
+ ];
8212
+ var BLOCKING = [
8213
+ "mvp",
8214
+ "stack",
8215
+ "arquitect",
8216
+ "architecture",
8217
+ "api",
8218
+ "web app",
8219
+ "webapp",
8220
+ "cli",
8221
+ "backend",
8222
+ "auth",
8223
+ "autenticaci",
8224
+ "persist",
8225
+ "base de datos",
8226
+ "database",
8227
+ "roadmap",
8228
+ "alcance",
8229
+ "scope",
8230
+ "primera versi\xF3n",
8231
+ "primera version",
8232
+ "first version",
8233
+ "fastify",
8234
+ "nestjs",
8235
+ "framework"
8236
+ ];
8237
+ var DEFERRED = [
8238
+ "integraci",
8239
+ "integration",
8240
+ "analytics",
8241
+ "notificaci",
8242
+ "notification",
8243
+ "google calendar",
8244
+ "eventbrite",
8245
+ "meetup",
8246
+ "dashboard",
8247
+ "multi-tenant",
8248
+ "multitenant",
8249
+ "mobile",
8250
+ "m\xF3vil",
8251
+ "pago",
8252
+ "payment",
8253
+ "ticketing",
8254
+ "multiple tracks",
8255
+ "m\xFAltiples tracks",
8256
+ "webhook"
8257
+ ];
8258
+ var IMPORTANT = [
8259
+ "validaci",
8260
+ "validation",
8261
+ "paginaci",
8262
+ "pagination",
8263
+ "soft delete",
8264
+ "sponsor",
8265
+ "agenda",
8266
+ "ubicaci",
8267
+ "location",
8268
+ "rol",
8269
+ "role",
8270
+ "permis",
8271
+ "permission"
8272
+ ];
8273
+ function matchesAny(text3, keywords) {
8274
+ return keywords.some((k) => text3.includes(k));
8275
+ }
8276
+ function classifyQuestion(question) {
8277
+ const t = question.toLowerCase();
8278
+ if (matchesAny(t, BLOCKING)) {
8279
+ return { classification: "blocking", reason: "Affects scope, architecture or the first Work Items." };
8280
+ }
8281
+ if (matchesAny(t, DEFERRED) && !matchesAny(t, IMPORTANT)) {
8282
+ return { classification: "deferred", reason: "Can be moved to a later phase." };
8283
+ }
8284
+ if (matchesAny(t, IMPORTANT)) {
8285
+ return { classification: "important", reason: "Relevant, but a temporary assumption can unblock progress." };
8286
+ }
8287
+ return { classification: "important", reason: "Relevant, but a temporary assumption can unblock progress." };
8288
+ }
8289
+ function extractFromMarkdown(md) {
8290
+ const lines = md.split(/\r?\n/);
8291
+ const start = lines.findIndex((l) => /^#{1,6}\s+open questions\s*$/i.test(l.trim()) || /^#{1,6}\s+preguntas abiertas\s*$/i.test(l.trim()));
8292
+ if (start < 0) return [];
8293
+ const out = [];
8294
+ for (let i = start + 1; i < lines.length; i++) {
8295
+ const line = lines[i];
8296
+ if (/^#{1,6}\s+/.test(line)) break;
8297
+ const m = line.match(/^\s*(?:[-*]|\d+\.)\s+(.+?)\s*$/);
8298
+ if (m) {
8299
+ const q = m[1].trim();
8300
+ if (q && !/^_.*_$/.test(q)) out.push(q);
8301
+ }
8302
+ }
8303
+ return out;
8304
+ }
8305
+ function suggestedAssumption(question) {
8306
+ const t = question.toLowerCase();
8307
+ if (matchesAny(t, ["api", "web app", "webapp", "cli", "backend"])) {
8308
+ return "Start as a backend API to keep the initial scope small.";
8309
+ }
8310
+ if (matchesAny(t, ["auth", "autenticaci"])) {
8311
+ return "No authentication in the MVP.";
8312
+ }
8313
+ if (matchesAny(t, ["fastify", "nestjs", "framework", "stack"])) {
8314
+ return "Choose the lighter stack option to minimize MVP scope.";
8315
+ }
8316
+ if (matchesAny(t, ["persist", "base de datos", "database"])) {
8317
+ return "Use a simple local datastore (e.g. SQLite) for the MVP.";
8318
+ }
8319
+ return "Proceed with the smallest reasonable MVP interpretation and revisit later.";
8320
+ }
8321
+ function buildOpenQuestionsReport(dir, now = /* @__PURE__ */ new Date()) {
8322
+ const project = loadConfig(dir)?.project.name ?? "unknown";
8323
+ const questions = [];
8324
+ let seq = 0;
8325
+ for (const source of SOURCES) {
8326
+ const p2 = join(dir, source);
8327
+ if (!exists(p2)) continue;
8328
+ let md;
8329
+ try {
8330
+ md = readFile(p2);
8331
+ } catch {
8332
+ continue;
8333
+ }
8334
+ for (const q of extractFromMarkdown(md)) {
8335
+ seq += 1;
8336
+ const { classification, reason } = classifyQuestion(q);
8337
+ questions.push({
8338
+ id: `OQ-${String(seq).padStart(3, "0")}`,
8339
+ source,
8340
+ section: "Open Questions",
8341
+ question: q,
8342
+ classification,
8343
+ reason,
8344
+ ...classification === "blocking" ? { suggested_assumption: suggestedAssumption(q) } : {}
8345
+ });
8346
+ }
8347
+ }
8348
+ const blocking = questions.filter((q) => q.classification === "blocking");
8349
+ const important = questions.filter((q) => q.classification === "important");
8350
+ const deferred = questions.filter((q) => q.classification === "deferred");
8351
+ let readiness;
8352
+ if (questions.length === 0) readiness = "unknown";
8353
+ else if (blocking.length > 0) readiness = "needs_decisions";
8354
+ else readiness = "ready";
8355
+ const suggested_assumptions = blocking.map((q) => q.suggested_assumption).filter(Boolean);
8356
+ const recommended_next_step = readiness === "needs_decisions" ? "Confirm suggested assumptions with the user (resolve, assume or defer blocking questions) before generating the roadmap." : readiness === "ready" ? "No blocking questions \u2014 you can proceed to the roadmap (review important questions when convenient)." : "No open questions found. Run `kaddo bootstrap` to capture early questions, or proceed.";
8357
+ return {
8358
+ generated_at: now.toISOString(),
8359
+ project,
8360
+ summary: {
8361
+ open_questions: questions.length,
8362
+ roadmap_readiness: readiness,
8363
+ blocking: blocking.length,
8364
+ important: important.length,
8365
+ deferred: deferred.length
8366
+ },
8367
+ questions,
8368
+ blocking_questions: blocking,
8369
+ important_questions: important,
8370
+ deferred_questions: deferred,
8371
+ suggested_assumptions,
8372
+ recommended_next_step
8373
+ };
8374
+ }
8375
+ function renderOpenQuestionsMarkdown(r) {
8376
+ const L = [];
8377
+ L.push("# Kaddo Open Questions Readiness Report", "");
8378
+ L.push(`Generated at: ${r.generated_at}`);
8379
+ L.push(`Project: ${r.project}`);
8380
+ L.push("");
8381
+ L.push("## Summary", "");
8382
+ L.push(`- Open questions found: ${r.summary.open_questions}`);
8383
+ L.push(`- Roadmap readiness: ${r.summary.roadmap_readiness}`);
8384
+ L.push(`- Blocking: ${r.summary.blocking}`);
8385
+ L.push(`- Important: ${r.summary.important}`);
8386
+ L.push(`- Deferred: ${r.summary.deferred}`);
8387
+ L.push("");
8388
+ if (r.summary.open_questions === 0) {
8389
+ L.push("No `## Open Questions` sections found in business / product / codebase / roadmap.", "");
8390
+ L.push("## Recommended Next Step", "", r.recommended_next_step, "");
8391
+ return L.join("\n");
8392
+ }
8393
+ L.push("## Blocking Questions", "");
8394
+ if (r.blocking_questions.length === 0) L.push("_None._", "");
8395
+ for (const q of r.blocking_questions) {
8396
+ L.push(`### ${q.id}`, "");
8397
+ L.push(`- Source: \`${q.source}\``);
8398
+ L.push(`- Question: ${q.question}`);
8399
+ L.push(`- Reason: ${q.reason}`);
8400
+ if (q.suggested_assumption) L.push(`- Suggested assumption: ${q.suggested_assumption}`);
8401
+ L.push("");
8402
+ }
8403
+ L.push("## Important Questions", "");
8404
+ if (r.important_questions.length === 0) L.push("_None._");
8405
+ for (const q of r.important_questions) L.push(`- ${q.question}`);
8406
+ L.push("");
8407
+ L.push("## Deferred Questions", "");
8408
+ if (r.deferred_questions.length === 0) L.push("_None._");
8409
+ for (const q of r.deferred_questions) L.push(`- ${q.question}`);
8410
+ L.push("");
8411
+ if (r.suggested_assumptions.length > 0) {
8412
+ L.push("## Suggested Assumptions", "");
8413
+ for (const a of r.suggested_assumptions) L.push(`- ${a}`);
8414
+ L.push("");
8415
+ }
8416
+ L.push("## Recommended Next Step", "", r.recommended_next_step, "");
8417
+ return L.join("\n");
8418
+ }
8419
+ function serializeOpenQuestionsJson(r) {
8420
+ return JSON.stringify(r, null, 2) + "\n";
8421
+ }
8422
+
8165
8423
  // src/commands/understand.ts
8166
8424
  function runUnderstand() {
8167
8425
  const dir = cwd();
@@ -8234,6 +8492,12 @@ function runUnderstand() {
8234
8492
  console.log(" \u2192 Review graph hints before continuing with implementation.");
8235
8493
  console.log(" Suggested agent: graph-agent (see .kaddo/graph-hints.md)");
8236
8494
  }
8495
+ const oq = buildOpenQuestionsReport(dir);
8496
+ if (oq.summary.blocking > 0) {
8497
+ console.log("");
8498
+ console.log(`Open questions: ${oq.summary.blocking} blocking decision(s) before the roadmap (roadmap readiness: ${oq.summary.roadmap_readiness}).`);
8499
+ console.log(" \u2192 Run `kaddo questions` to review them, then resolve, assume or defer before `kaddo create --from roadmap`.");
8500
+ }
8237
8501
  const active = activeWorkItems(dir);
8238
8502
  if (active.length > 0) {
8239
8503
  console.log("");
@@ -8291,35 +8555,35 @@ var DEP_PATTERNS = [
8291
8555
  function normalizePath2(p2) {
8292
8556
  return p2.split(path5.sep).join("/");
8293
8557
  }
8294
- function matchesAny(file, patterns) {
8558
+ function matchesAny2(file, patterns) {
8295
8559
  const normalized = normalizePath2(file);
8296
8560
  return patterns.some((p2) => p2.test(normalized));
8297
8561
  }
8298
8562
  function detectSignals(touchedFiles) {
8299
8563
  const signals = [];
8300
8564
  for (const file of touchedFiles) {
8301
- if (matchesAny(file, MIGRATION_PATTERNS)) {
8565
+ if (matchesAny2(file, MIGRATION_PATTERNS)) {
8302
8566
  signals.push({
8303
8567
  name: "DB migration",
8304
8568
  file,
8305
8569
  implication: "Data schema change \u2014 typically K4 or Migration type",
8306
8570
  suggestedLevel: "K4"
8307
8571
  });
8308
- } else if (matchesAny(file, CONTRACT_PATTERNS)) {
8572
+ } else if (matchesAny2(file, CONTRACT_PATTERNS)) {
8309
8573
  signals.push({
8310
8574
  name: "API/event contract",
8311
8575
  file,
8312
8576
  implication: "Contract change affects consumers \u2014 typically K3/K4",
8313
8577
  suggestedLevel: "K3"
8314
8578
  });
8315
- } else if (matchesAny(file, INFRA_PATTERNS)) {
8579
+ } else if (matchesAny2(file, INFRA_PATTERNS)) {
8316
8580
  signals.push({
8317
8581
  name: "Infrastructure",
8318
8582
  file,
8319
8583
  implication: "Infrastructure change \u2014 typically K3/K4",
8320
8584
  suggestedLevel: "K3"
8321
8585
  });
8322
- } else if (matchesAny(file, DEP_PATTERNS)) {
8586
+ } else if (matchesAny2(file, DEP_PATTERNS)) {
8323
8587
  signals.push({
8324
8588
  name: "Dependency",
8325
8589
  file,
@@ -11995,6 +12259,42 @@ function runDrift(opts = {}) {
11995
12259
  console.log(content);
11996
12260
  }
11997
12261
 
12262
+ // src/commands/questions.ts
12263
+ function runQuestions(opts = {}) {
12264
+ const dir = cwd();
12265
+ requireConfig(dir);
12266
+ const report = buildOpenQuestionsReport(dir);
12267
+ if (opts.output) {
12268
+ intro2("kaddo questions");
12269
+ writeFile(join(dir, opts.output), opts.json ? serializeOpenQuestionsJson(report) : renderOpenQuestionsMarkdown(report));
12270
+ log2.success(`Wrote ${opts.output.replace(/\\/g, "/")}`);
12271
+ printCommandFooter("questions");
12272
+ outro2("Questions report written.");
12273
+ return;
12274
+ }
12275
+ if (opts.json) {
12276
+ console.log(serializeOpenQuestionsJson(report));
12277
+ return;
12278
+ }
12279
+ console.log("");
12280
+ console.log(`Open questions detected: ${report.summary.open_questions}`);
12281
+ console.log(`Roadmap readiness: ${report.summary.roadmap_readiness === "needs_decisions" ? "needs decisions" : report.summary.roadmap_readiness}`);
12282
+ const list2 = (title, qs) => {
12283
+ if (qs.length === 0) return;
12284
+ console.log("");
12285
+ console.log(`${title}:`);
12286
+ for (const q of qs.slice(0, 8)) console.log(`- ${q.question}`);
12287
+ if (qs.length > 8) console.log(` \u2026and ${qs.length - 8} more`);
12288
+ };
12289
+ list2("Blocking", report.blocking_questions);
12290
+ list2("Important", report.important_questions);
12291
+ list2("Deferred", report.deferred_questions);
12292
+ console.log("");
12293
+ console.log("Suggested next:");
12294
+ console.log(report.recommended_next_step);
12295
+ printCommandFooter("questions");
12296
+ }
12297
+
11998
12298
  // src/index.ts
11999
12299
  var require2 = createRequire(import.meta.url);
12000
12300
  var { version } = require2("../package.json");
@@ -12045,6 +12345,9 @@ reportCmd.command("drift").description("Drift Trend Report from recorded guard h
12045
12345
  program.command("drift").description("Drift Trend Report from recorded `kaddo guard --record` history").option("--json", "Output JSON instead of Markdown").option("--output <path>", "Write the report to a file").action((opts) => {
12046
12346
  runDrift(opts);
12047
12347
  });
12348
+ var questionsAction = (opts) => runQuestions(opts);
12349
+ 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);
12350
+ 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);
12048
12351
  program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").option("--include-archived", "Include archived Work Items in ownership matching (excluded by default)").option("--record", "Record this run to .kaddo/history/ for drift trend reporting").action(async (opts) => {
12049
12352
  await runGuard(opts);
12050
12353
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.25.1",
3
+ "version": "3.26.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {