@kaddo/cli 3.36.0 → 3.37.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 +269 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -532,6 +532,8 @@ create --from roadmap → owners → guard → explain`.
532
532
  | v3.34 | Pre-AI onboarding: `kaddo onboarding` (alias `onboard`) / `kaddo report onboarding` — read-only diagnosis of an existing project's readiness (scan/understand/knowledge/questions/roadmap/work-items/adapters) with a single recommended next step; `--json` |
533
533
  | v3.35 | Folded onboarding into `kaddo explain`: removed `kaddo onboarding`/`onboard`/`report onboarding`; project readiness + single recommended next step now live in `kaddo explain` (human `## Project Readiness` + `readiness` in agent JSON) |
534
534
  | v3.36 | State-aware bootstrap: `kaddo bootstrap` creates the full knowledge baseline for any `project.state` (new/pre-ai/legacy) with state-specific templates — no more new-only warning; idempotent, never overwrites, ensures `tech/decisions/` and `delivery/work-items/` |
535
+ | v3.37 | Placeholder-aware readiness: knowledge files are classified missing/placeholder/weak/useful; a bootstrap file isn't treated as ready knowledge. Layers downgrade to Placeholder/Weak, a new Knowledge Refinement phase recommends the right agent, and `create --from roadmap` is never suggested with 0 candidates |
536
+ | v3.37.1 | Unified next-step: one shared resolver (`core/next-step.ts`) powers `context`, `understand` and `explain` (Phase + Readiness) so they never diverge; agent JSON exposes `nextStepRecommendation`. Fixes duplicate `capabilities` line in explain |
535
537
 
536
538
  **Optional modules (installed with `kaddo add`):**
537
539
 
package/dist/index.js CHANGED
@@ -6041,14 +6041,23 @@ import { parse as parseYaml9 } from "yaml";
6041
6041
  function layer(layers, name) {
6042
6042
  return layers.find((l) => l.layer === name)?.status ?? "Missing";
6043
6043
  }
6044
+ var NOT_READY_LAYER = ["Missing", "Placeholder", "Weak"];
6044
6045
  function baseComplete(layers) {
6045
6046
  return layer(layers, "Business") !== "Missing" && layer(layers, "Product") !== "Missing" && layer(layers, "Tech") !== "Missing";
6046
6047
  }
6048
+ function baseUseful(layers) {
6049
+ return ["Business", "Product", "Tech"].every((n) => !NOT_READY_LAYER.includes(layer(layers, n)));
6050
+ }
6051
+ function roadmapHasCandidates(roadmap) {
6052
+ return roadmap.candidates > 0 || roadmap.remaining > 0;
6053
+ }
6047
6054
  function determinePhase(input) {
6048
6055
  const { roadmap, workItems } = input;
6049
6056
  const active = workItems.byState.draft + workItems.byState.ready + workItems.byState["in-progress"] + workItems.byState.blocked;
6050
6057
  if (!baseComplete(input.layers)) return "Discovery";
6058
+ if (!baseUseful(input.layers)) return "Knowledge Refinement";
6051
6059
  if (!roadmap.present) return "Planning";
6060
+ if (!roadmapHasCandidates(roadmap) && workItems.total === 0) return "Planning";
6052
6061
  if (workItems.total === 0) return "Delivery Preparation";
6053
6062
  if (active > 0) return "Active Delivery";
6054
6063
  return "Maintenance";
@@ -6078,6 +6087,14 @@ function firstMissingLayerAgent(layers) {
6078
6087
  return { agent: "capability-agent", step: "Use capability-agent to create knowledge/product/capabilities.md" };
6079
6088
  return { agent: "architecture-agent", step: "Use architecture-agent to create knowledge/tech/current-state.md" };
6080
6089
  }
6090
+ function firstUnrefinedLayerAgent(layers) {
6091
+ const isThin = (n) => NOT_READY_LAYER.includes(layer(layers, n));
6092
+ if (isThin("Business"))
6093
+ return { agent: "business-agent", step: "Use business-agent to complete knowledge/business/business.md" };
6094
+ if (isThin("Product"))
6095
+ return { agent: "capability-agent", step: "Use capability-agent to complete knowledge/product/capabilities.md" };
6096
+ return { agent: "architecture-agent", step: "Use architecture-agent to complete knowledge/tech/current-state.md" };
6097
+ }
6081
6098
  function assessPhase(input) {
6082
6099
  const phase = determinePhase(input);
6083
6100
  const reasons = buildReasons(input);
@@ -6095,6 +6112,17 @@ function assessPhase(input) {
6095
6112
  llmInstructions = [`Use the ${m.agent} to fill the missing base knowledge.`, "Do not write code."];
6096
6113
  break;
6097
6114
  }
6115
+ case "Knowledge Refinement": {
6116
+ const m = firstUnrefinedLayerAgent(input.layers);
6117
+ recommendedAgents2 = [m.agent];
6118
+ nextStep = m.step;
6119
+ llmInstructions = [
6120
+ "The baseline files exist but still look like bootstrap placeholders.",
6121
+ `Use the ${m.agent} to replace the placeholders with real, project-specific knowledge.`,
6122
+ "Do not write code."
6123
+ ];
6124
+ break;
6125
+ }
6098
6126
  case "Planning": {
6099
6127
  recommendedAgents2 = ["roadmap-agent"];
6100
6128
  nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
@@ -6957,9 +6985,97 @@ function statusFor(layer2, a) {
6957
6985
  return "Missing";
6958
6986
  }
6959
6987
 
6988
+ // src/core/artifact-quality.ts
6989
+ function isPlaceholderLine(line) {
6990
+ const t = line.trim();
6991
+ if (/^_.+_$/.test(t)) return true;
6992
+ if (/^[-*]\s+(\[[^\]]+\]\s*)?_.+_$/.test(t)) return true;
6993
+ if (/^[-*]\s+\[[^\]]+\]\s*$/.test(t)) return true;
6994
+ if (/^_(?:Describe|List|Document|What|Which|Who|Use|To be defined|No production code)\b/i.test(t)) return true;
6995
+ return false;
6996
+ }
6997
+ function isStructuralLine(line, inFrontMatter) {
6998
+ const t = line.trim();
6999
+ if (inFrontMatter) return true;
7000
+ if (t === "") return true;
7001
+ if (/^#{1,6}\s/.test(t)) return true;
7002
+ if (/^<!--/.test(t)) return true;
7003
+ if (/^>/.test(t)) return true;
7004
+ return false;
7005
+ }
7006
+ function analyzeContent(md) {
7007
+ const lines = md.split(/\r?\n/);
7008
+ let inFrontMatter = false;
7009
+ let seenFmFence = 0;
7010
+ let currentSectionHasUseful = false;
7011
+ const sectionsWithUseful = /* @__PURE__ */ new Set();
7012
+ let sectionIndex = 0;
7013
+ const contentLines = [];
7014
+ const usefulLines = [];
7015
+ for (const raw of lines) {
7016
+ const t = raw.trim();
7017
+ if (t === "---" && seenFmFence < 2) {
7018
+ seenFmFence += 1;
7019
+ inFrontMatter = seenFmFence === 1;
7020
+ continue;
7021
+ }
7022
+ if (seenFmFence === 1) continue;
7023
+ if (/^#{1,6}\s/.test(t)) {
7024
+ sectionIndex += 1;
7025
+ currentSectionHasUseful = false;
7026
+ continue;
7027
+ }
7028
+ if (isStructuralLine(raw, false)) continue;
7029
+ contentLines.push(t);
7030
+ if (!isPlaceholderLine(raw)) {
7031
+ usefulLines.push(t);
7032
+ if (!currentSectionHasUseful) {
7033
+ currentSectionHasUseful = true;
7034
+ sectionsWithUseful.add(sectionIndex);
7035
+ }
7036
+ }
7037
+ }
7038
+ if (usefulLines.length === 0) return "placeholder";
7039
+ const usefulWordCount = usefulLines.join(" ").split(/\s+/).filter(Boolean).length;
7040
+ const placeholderRatio = contentLines.length > 0 ? (contentLines.length - usefulLines.length) / contentLines.length : 1;
7041
+ if (usefulWordCount >= 80 && placeholderRatio < 0.25 && sectionsWithUseful.size >= 2) return "useful";
7042
+ return "weak";
7043
+ }
7044
+ function analyzeKnowledgeArtifact(dir, rel) {
7045
+ const p2 = join(dir, rel);
7046
+ if (!exists(p2)) return "missing";
7047
+ try {
7048
+ return analyzeContent(readFile(p2));
7049
+ } catch {
7050
+ return "missing";
7051
+ }
7052
+ }
7053
+
6960
7054
  // src/core/layers.ts
7055
+ var LAYER_BASELINE = {
7056
+ Business: ["knowledge/business/business.md"],
7057
+ Product: ["knowledge/product/product.md", "knowledge/product/capabilities.md"],
7058
+ Tech: ["knowledge/tech/codebase.md", "knowledge/tech/current-state.md"],
7059
+ Delivery: ["knowledge/delivery/roadmap.md"]
7060
+ };
7061
+ function layerQuality(dir, layer2) {
7062
+ const order = ["missing", "placeholder", "weak", "useful"];
7063
+ let worst = null;
7064
+ for (const rel of LAYER_BASELINE[layer2]) {
7065
+ const q = analyzeKnowledgeArtifact(dir, rel);
7066
+ if (q === "missing") continue;
7067
+ if (worst === null || order.indexOf(q) < order.indexOf(worst)) worst = q;
7068
+ }
7069
+ return worst;
7070
+ }
6961
7071
  function knowledgeLayers(dir) {
6962
- return discoverLayers(dir);
7072
+ return discoverLayers(dir).map((l) => {
7073
+ if (l.status !== "Consolidated" && l.status !== "Structured") return l;
7074
+ const q = layerQuality(dir, l.layer);
7075
+ if (q === "placeholder") return { ...l, status: "Placeholder" };
7076
+ if (q === "weak") return { ...l, status: "Weak" };
7077
+ return l;
7078
+ });
6963
7079
  }
6964
7080
  function renderLayersMarkdown(layers) {
6965
7081
  const lines = [];
@@ -7629,26 +7745,7 @@ function buildSharedFileStatuses(statuses) {
7629
7745
  }));
7630
7746
  }
7631
7747
 
7632
- // src/core/readiness.ts
7633
- var KNOWLEDGE_FILES = [
7634
- { key: "current_state", path: "knowledge/tech/current-state.md", label: "knowledge/tech/current-state.md" },
7635
- { key: "codebase", path: "knowledge/tech/codebase.md", label: "knowledge/tech/codebase.md" },
7636
- { key: "capabilities", path: "knowledge/product/capabilities.md", label: "knowledge/product/capabilities.md" },
7637
- { key: "product", path: "knowledge/product/product.md", label: "knowledge/product/product.md" },
7638
- { key: "business", path: "knowledge/business/business.md", label: "knowledge/business/business.md" }
7639
- ];
7640
- function knowledgePresence(dir, rel) {
7641
- const p2 = join(dir, rel);
7642
- if (!exists(p2)) return "missing";
7643
- let md;
7644
- try {
7645
- md = readFile(p2);
7646
- } catch {
7647
- return "missing";
7648
- }
7649
- const body = md.replace(/^---[\s\S]*?---/m, "").split(/\r?\n/).filter((l) => !/^\s*#{1,6}\s/.test(l) && !/^\s*$/.test(l) && !/^\s*<!--/.test(l)).join(" ").trim();
7650
- return body.length >= 40 ? "present" : "weak";
7651
- }
7748
+ // src/core/next-step.ts
7652
7749
  function roadmapSignal(dir) {
7653
7750
  const p2 = join(dir, "knowledge/delivery/roadmap.md");
7654
7751
  if (!exists(p2)) return "missing";
@@ -7679,8 +7776,91 @@ function installedAdapters(dir) {
7679
7776
  (s) => s.state === "injected" || s.state === "legacy-injected" || s.state === "full-generated" && (s.originAdapter === s.id || s.originAdapter === null)
7680
7777
  ).map((s) => s.id);
7681
7778
  }
7779
+ var B = "knowledge/business/business.md";
7780
+ var P = "knowledge/product/product.md";
7781
+ var CAP = "knowledge/product/capabilities.md";
7782
+ var CS = "knowledge/tech/current-state.md";
7783
+ var CB = "knowledge/tech/codebase.md";
7784
+ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
7785
+ const config = loadConfig(dir);
7786
+ if (!config) {
7787
+ return { id: "init", phase: "Setup", label: "Run `kaddo init` to initialize Kaddo.", command: "kaddo init", reason: "Kaddo is not initialized in this project." };
7788
+ }
7789
+ const state = config.project.state;
7790
+ const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
7791
+ const qBusiness = q(B), qProduct = q(P), qCap = q(CAP), qCurrentState = q(CS), qCodebase = q(CB);
7792
+ if (qBusiness === "missing" || qProduct === "missing") {
7793
+ return { id: "bootstrap", phase: "Setup", label: "Run `kaddo bootstrap` to create the project knowledge baseline.", command: "kaddo bootstrap", reason: "The knowledge baseline is incomplete." };
7794
+ }
7795
+ const ctx = buildCodexAdapterContext(dir);
7796
+ if (!ctx.hasAgents) {
7797
+ return { id: "add-agents", phase: "Setup", label: "Run `kaddo add agents` to install the Kaddo agent prompt packs.", command: "kaddo add agents", reason: "No Kaddo agents are installed yet." };
7798
+ }
7799
+ if (!ctx.hasSkills) {
7800
+ return { id: "add-skills", phase: "Setup", label: "Run `kaddo add skills` to install reusable Kaddo skills.", command: "kaddo add skills", reason: "No Kaddo skills are installed yet." };
7801
+ }
7802
+ if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
7803
+ return { id: "scan", phase: "Discovery", label: "Run `kaddo scan` to capture deterministic signals from the existing code.", command: "kaddo scan", reason: "No scan baseline exists for this existing project yet." };
7804
+ }
7805
+ if (!exists(join(dir, ".kaddo", "context-pack.md"))) {
7806
+ return { id: "context", phase: "Discovery", label: "Run `kaddo context` to prepare the LLM context pack.", command: "kaddo context", reason: "No context pack has been generated yet." };
7807
+ }
7808
+ if (!exists(join(dir, ".kaddo", "understand.md"))) {
7809
+ return { id: "understand", phase: "Discovery", label: "Run `kaddo understand` to summarize the project context.", command: "kaddo understand", reason: "No understand handoff has been generated yet." };
7810
+ }
7811
+ const refine = (id, agent, target, quality) => ({
7812
+ id,
7813
+ phase: "Knowledge Refinement",
7814
+ label: `Use ${agent} to complete \`${target}\`.`,
7815
+ agent,
7816
+ target,
7817
+ reason: `${target} is ${quality === "missing" ? "missing" : "still a bootstrap placeholder or too thin"}.`,
7818
+ instructions: [
7819
+ "The baseline file exists but does not yet hold real, project-specific knowledge.",
7820
+ `Use the ${agent} to replace the placeholders with real knowledge.`,
7821
+ "Do not write code."
7822
+ ]
7823
+ });
7824
+ if (qBusiness !== "useful") return refine("refine-business", "business-agent", B, qBusiness);
7825
+ if (qProduct !== "useful" || qCap !== "useful") {
7826
+ const target = qCap !== "useful" ? CAP : P;
7827
+ return refine("refine-product", "capability-agent", target, qCap !== "useful" ? qCap : qProduct);
7828
+ }
7829
+ if (qCurrentState !== "useful") return refine("refine-current-state", "architecture-agent", CS, qCurrentState);
7830
+ if (qCodebase !== "useful") {
7831
+ const agent = ctx.agents.includes("codebase-agent") ? "codebase-agent" : "architecture-agent";
7832
+ return refine("refine-codebase", agent, CB, qCodebase);
7833
+ }
7834
+ const oq = buildOpenQuestionsReport(dir, now);
7835
+ if (oq.summary.blocking_open > 0) {
7836
+ return { id: "questions", phase: "Knowledge Refinement", label: "Resolve, assume or defer the blocking open questions (`kaddo questions`).", command: "kaddo questions", reason: `${oq.summary.blocking_open} blocking open question(s) remain.` };
7837
+ }
7838
+ const roadmap = roadmapSignal(dir);
7839
+ if (roadmap !== "has-candidates") {
7840
+ 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." };
7841
+ }
7842
+ const wi = workItemsSignal(dir);
7843
+ if (wi === "none" || wi === "none-ready") {
7844
+ return { id: "create-work-item", phase: "Delivery Preparation", label: "Run `kaddo create --from roadmap` to materialize the first Work Item.", command: "kaddo create --from roadmap", reason: "The roadmap has candidates but no Work Item is ready." };
7845
+ }
7846
+ const adapters = installedAdapters(dir);
7847
+ if (adapters.length === 0) {
7848
+ return { id: "install-adapter", phase: "Active Delivery", label: "Run `kaddo adapters list` and install the adapter for your preferred agent.", command: "kaddo adapters list", reason: "A Work Item is ready but no adapter is installed." };
7849
+ }
7850
+ return { id: "implement", phase: "Active Delivery", label: "Implement the ready Work Item and run `kaddo guard`.", reason: "A Work Item is ready and an adapter is installed." };
7851
+ }
7852
+
7853
+ // src/core/readiness.ts
7854
+ var KNOWLEDGE_FILES = [
7855
+ { key: "current_state", path: "knowledge/tech/current-state.md", label: "knowledge/tech/current-state.md", agent: "architecture-agent" },
7856
+ { key: "codebase", path: "knowledge/tech/codebase.md", label: "knowledge/tech/codebase.md", agent: "codebase-agent" },
7857
+ { key: "capabilities", path: "knowledge/product/capabilities.md", label: "knowledge/product/capabilities.md", agent: "capability-agent" },
7858
+ { key: "product", path: "knowledge/product/product.md", label: "knowledge/product/product.md", agent: "product-agent" },
7859
+ { key: "business", path: "knowledge/business/business.md", label: "knowledge/business/business.md", agent: "business-agent" }
7860
+ ];
7682
7861
  function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
7683
7862
  const config = loadConfig(dir);
7863
+ const rec = resolveNextStep(dir, now);
7684
7864
  const stub = (overall2, label, command) => ({
7685
7865
  project_name: config?.project.name ?? "unknown",
7686
7866
  project_type: config?.project.state ?? "unknown",
@@ -7704,14 +7884,15 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
7704
7884
  resolved_questions: 0,
7705
7885
  deferred_questions: 0
7706
7886
  },
7707
- recommended_next_step: { label, ...command ? { command } : {} }
7887
+ recommended_next_step: { label, ...command ? { command } : {} },
7888
+ nextStepRecommendation: rec
7708
7889
  });
7709
- if (!config) return stub("not-initialized", "Run `kaddo init` to initialize Kaddo.", "kaddo init");
7890
+ if (!config) return stub("not-initialized", rec.label, rec.command);
7710
7891
  if (config.project.state === "new") return stub("not-applicable", "This project uses the standard new-project Kaddo flow.");
7711
7892
  if (config.project.state === "legacy") return stub("legacy-project", "Use the legacy project flow.");
7712
7893
  const scan2 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
7713
7894
  const understand = exists(join(dir, ".kaddo", "understand.md")) ? "available" : "missing";
7714
- const presence = Object.fromEntries(KNOWLEDGE_FILES.map((f) => [f.key, knowledgePresence(dir, f.path)]));
7895
+ const presence = Object.fromEntries(KNOWLEDGE_FILES.map((f) => [f.key, analyzeKnowledgeArtifact(dir, f.path)]));
7715
7896
  const ctx = buildCodexAdapterContext(dir);
7716
7897
  const agents = ctx.hasAgents ? "present" : "missing";
7717
7898
  const skills = ctx.hasSkills ? "present" : "missing";
@@ -7739,41 +7920,26 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
7739
7920
  resolved_questions: oq.summary.resolution.resolved,
7740
7921
  deferred_questions: oq.summary.resolution.deferred
7741
7922
  };
7923
+ const firstWeak = KNOWLEDGE_FILES.find((f) => presence[f.key] !== "useful");
7742
7924
  let overall;
7743
- let next;
7744
- const firstWeak = KNOWLEDGE_FILES.find((f) => presence[f.key] !== "present");
7745
- if (scan2 === "missing") {
7746
- overall = "initialized";
7747
- next = { label: "Run `kaddo scan` to capture deterministic signals from the existing code.", command: "kaddo scan" };
7748
- } else if (bootstrapBaseline === "incomplete") {
7749
- overall = "bootstrap-incomplete";
7750
- next = { label: "Run `kaddo bootstrap` to create the project knowledge baseline.", command: "kaddo bootstrap" };
7751
- } else if (agents === "missing") {
7752
- overall = "agents-missing";
7753
- next = { label: "Run `kaddo add agents` to install the Kaddo agent prompt packs.", command: "kaddo add agents" };
7754
- } else if (skills === "missing") {
7755
- overall = "skills-missing";
7756
- next = { label: "Run `kaddo add skills` to install reusable Kaddo skills.", command: "kaddo add skills" };
7757
- } else if (understand === "missing") {
7758
- overall = "scanned";
7759
- next = { label: "Run `kaddo understand` to summarize the project context.", command: "kaddo understand" };
7760
- } else if (firstWeak) {
7761
- overall = "knowledge-incomplete";
7762
- next = { label: `Complete \`${firstWeak.label}\` (it is ${presence[firstWeak.key]}).` };
7763
- } else if (oq.summary.blocking_open > 0) {
7764
- overall = "needs-decisions";
7765
- next = { label: "Resolve, assume or defer the blocking open questions (`kaddo questions`).", command: "kaddo questions" };
7766
- } else if (roadmap !== "has-candidates") {
7767
- overall = "ready-for-roadmap";
7768
- next = { label: "Run `kaddo roadmap` to draft the roadmap from the current knowledge.", command: "kaddo roadmap" };
7769
- } else if (work_items === "none" || work_items === "none-ready") {
7770
- overall = "ready-for-work-item";
7771
- next = { label: "Run `kaddo create --from roadmap` to materialize the first Work Item.", command: "kaddo create --from roadmap" };
7772
- } else {
7773
- overall = "ready-for-implementation";
7774
- next = adapters.length > 0 ? { label: "Implement the ready Work Item and run `kaddo guard`." } : { label: "Run `kaddo adapters list` and install the adapter for your preferred agent.", command: "kaddo adapters list" };
7775
- }
7776
- return { project_name: config.project.name, project_type: config.project.state, overall, signals, recommended_next_step: next };
7925
+ if (scan2 === "missing") overall = "initialized";
7926
+ else if (bootstrapBaseline === "incomplete") overall = "bootstrap-incomplete";
7927
+ else if (agents === "missing") overall = "agents-missing";
7928
+ else if (skills === "missing") overall = "skills-missing";
7929
+ else if (understand === "missing") overall = "scanned";
7930
+ else if (firstWeak) overall = "knowledge-incomplete";
7931
+ else if (oq.summary.blocking_open > 0) overall = "needs-decisions";
7932
+ else if (roadmap !== "has-candidates") overall = "ready-for-roadmap";
7933
+ else if (work_items === "none" || work_items === "none-ready") overall = "ready-for-work-item";
7934
+ else overall = "ready-for-implementation";
7935
+ return {
7936
+ project_name: config.project.name,
7937
+ project_type: config.project.state,
7938
+ overall,
7939
+ signals,
7940
+ recommended_next_step: { label: rec.label, ...rec.command ? { command: rec.command } : {} },
7941
+ nextStepRecommendation: rec
7942
+ };
7777
7943
  }
7778
7944
 
7779
7945
  // src/core/project-explain.ts
@@ -7965,6 +8131,7 @@ function buildProjectExplanation(dir) {
7965
8131
  "Run `kaddo owners suggest` for Work Items without code ownership."
7966
8132
  );
7967
8133
  }
8134
+ const readiness = buildReadinessReport(dir);
7968
8135
  return {
7969
8136
  project,
7970
8137
  stack,
@@ -7985,7 +8152,8 @@ function buildProjectExplanation(dir) {
7985
8152
  mappedModules,
7986
8153
  missingKnowledge,
7987
8154
  suggestedNextSteps,
7988
- readiness: buildReadinessReport(dir)
8155
+ readiness,
8156
+ nextStepRecommendation: readiness.nextStepRecommendation
7989
8157
  };
7990
8158
  }
7991
8159
  function stateLabel(state) {
@@ -8166,7 +8334,7 @@ function renderExplanationHuman(exp) {
8166
8334
  lines.push("- Reason:");
8167
8335
  for (const r2 of assessment.reasons) lines.push(` - ${r2}`);
8168
8336
  }
8169
- if (assessment.nextStep) lines.push(`- Next step: ${assessment.nextStep}`);
8337
+ lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
8170
8338
  lines.push("");
8171
8339
  if (exp.suggestedNextSteps.length > 0) {
8172
8340
  lines.push("## Suggested Next Steps");
@@ -8183,9 +8351,11 @@ function renderExplanationHuman(exp) {
8183
8351
  lines.push(`- understand: ${s.understand}`);
8184
8352
  lines.push(`- agents: ${s.agents}`);
8185
8353
  lines.push(`- skills: ${s.skills}`);
8354
+ lines.push(`- business: ${s.business}`);
8355
+ lines.push(`- product: ${s.product}`);
8356
+ lines.push(`- capabilities: ${s.capabilities}`);
8186
8357
  lines.push(`- current-state: ${s.current_state}`);
8187
8358
  lines.push(`- codebase: ${s.codebase}`);
8188
- lines.push(`- capabilities: ${s.capabilities}`);
8189
8359
  lines.push(`- roadmap: ${s.roadmap}`);
8190
8360
  lines.push(`- work-items: ${s.work_items}`);
8191
8361
  lines.push(`- adapters: ${s.adapters.length > 0 ? s.adapters.join(", ") + " installed" : "none installed"}`);
@@ -8429,28 +8599,6 @@ function recommendedAgentsForState(state) {
8429
8599
  return ["capability-agent", "architecture-agent", "roadmap-agent"];
8430
8600
  }
8431
8601
  }
8432
- function nextStepsForState2(state) {
8433
- switch (state) {
8434
- case "new":
8435
- return [
8436
- "Use roadmap-agent to shape an initial roadmap.",
8437
- "Use architecture-agent to outline the intended architecture."
8438
- ];
8439
- case "legacy":
8440
- return [
8441
- "Use legacy-agent to surface risks and unknowns before changing code.",
8442
- "Use architecture-agent to reconstruct the current architecture.",
8443
- "Use capability-agent to map existing capabilities."
8444
- ];
8445
- case "pre-ai":
8446
- default:
8447
- return [
8448
- "Use capability-agent to extract system capabilities.",
8449
- "Use architecture-agent to reconstruct the current architecture.",
8450
- "Use roadmap-agent to propose roadmap candidates."
8451
- ];
8452
- }
8453
- }
8454
8602
  var LLM_INSTRUCTIONS = [
8455
8603
  "Use this context pack as the project baseline.",
8456
8604
  "Do not write code yet.",
@@ -8522,6 +8670,24 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8522
8670
  }
8523
8671
  const mappedModules = loadMappedModules(dir);
8524
8672
  const layers = knowledgeLayers(dir);
8673
+ const qa = (rel) => analyzeKnowledgeArtifact(dir, rel);
8674
+ const qBusiness = qa("knowledge/business/business.md");
8675
+ const qProduct = qa("knowledge/product/product.md");
8676
+ const qCapabilities = qa("knowledge/product/capabilities.md");
8677
+ const qCodebase = qa("knowledge/tech/codebase.md");
8678
+ const qCurrentState = qa("knowledge/tech/current-state.md");
8679
+ const qRoadmap = qa("knowledge/delivery/roadmap.md");
8680
+ const layerStatusOf = (name) => layers.find((l) => l.layer === name)?.status ?? "Missing";
8681
+ const knowledgeQuality = {
8682
+ business: { status: layerStatusOf("Business"), artifacts: { "knowledge/business/business.md": qBusiness } },
8683
+ product: { status: layerStatusOf("Product"), artifacts: { "knowledge/product/product.md": qProduct, "knowledge/product/capabilities.md": qCapabilities } },
8684
+ tech: { status: layerStatusOf("Tech"), artifacts: { "knowledge/tech/codebase.md": qCodebase, "knowledge/tech/current-state.md": qCurrentState } },
8685
+ delivery: { status: layerStatusOf("Delivery"), artifacts: { "knowledge/delivery/roadmap.md": qRoadmap } }
8686
+ };
8687
+ if (qBusiness === "placeholder") missing.push("Business context exists but still looks like a bootstrap placeholder.");
8688
+ if (qProduct === "placeholder" || qCapabilities === "placeholder") missing.push("Product capabilities exist but still look like a bootstrap placeholder.");
8689
+ if (qCurrentState === "placeholder") missing.push("Current state exists but still looks like a bootstrap placeholder.");
8690
+ if (qCodebase === "placeholder") missing.push("Codebase map exists but still looks like a bootstrap placeholder.");
8525
8691
  const allWorkItems = allArtifacts.filter((a) => a.isWorkItem);
8526
8692
  const wiWithOwnership = allWorkItems.filter((a) => a.codeGlobs.length > 0).length;
8527
8693
  const phase = assessPhase({
@@ -8544,6 +8710,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8544
8710
  workItemsMissingOwnership: allWorkItems.length - wiWithOwnership
8545
8711
  }
8546
8712
  });
8713
+ const nextStepRecommendation = resolveNextStep(dir, now);
8714
+ const unifiedPhase = {
8715
+ ...phase,
8716
+ nextStep: nextStepRecommendation.label,
8717
+ recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
8718
+ };
8547
8719
  return {
8548
8720
  version: CONTEXT_PACK_VERSION,
8549
8721
  generatedAt: now.toISOString(),
@@ -8572,8 +8744,10 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8572
8744
  artifacts: allArtifacts.filter((a) => a.codeGlobs.length > 0).map(toContextArtifact)
8573
8745
  },
8574
8746
  layers,
8747
+ knowledgeQuality,
8575
8748
  roadmap,
8576
- phase,
8749
+ phase: unifiedPhase,
8750
+ nextStepRecommendation,
8577
8751
  deliveryMix,
8578
8752
  external: loadExternalCapsules(dir),
8579
8753
  graph: loadGraphSummary(dir),
@@ -8581,12 +8755,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8581
8755
  skills: discoverInstalledSkills(dir).map((s) => s.id),
8582
8756
  mappedModules,
8583
8757
  missing,
8584
- // VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
8585
- // contradicts the Current Phase block above it.
8758
+ // VS-052/VS-073.2: the handoff is driven by the unified next step, so the pack never contradicts
8759
+ // the Current Phase block above it.
8586
8760
  handoff: {
8587
- recommendedAgents: phase.recommendedAgents.length > 0 ? phase.recommendedAgents : recommendedAgentsForState(state),
8588
- nextSteps: phase.nextStep ? [phase.nextStep] : nextStepsForState2(state),
8589
- instructions: phase.llmInstructions.length > 0 ? phase.llmInstructions : LLM_INSTRUCTIONS,
8761
+ recommendedAgents: unifiedPhase.recommendedAgents.length > 0 ? unifiedPhase.recommendedAgents : recommendedAgentsForState(state),
8762
+ nextSteps: [nextStepRecommendation.label],
8763
+ instructions: unifiedPhase.llmInstructions.length > 0 ? unifiedPhase.llmInstructions : LLM_INSTRUCTIONS,
8590
8764
  operatingRules: OPERATING_RULES
8591
8765
  }
8592
8766
  };
@@ -9090,24 +9264,16 @@ function runUnderstand() {
9090
9264
  console.log(`Project language: ${pack.project.language} (knowledge artifacts are written in this language)`);
9091
9265
  const exp = buildProjectExplanation(dir);
9092
9266
  const assessment = assessPhase(exp);
9267
+ const rec = exp.nextStepRecommendation;
9093
9268
  console.log("");
9094
9269
  console.log(`Current phase: ${assessment.phase}`);
9095
9270
  if (assessment.reasons.length > 0) {
9096
9271
  console.log("Reason:");
9097
9272
  for (const r of assessment.reasons) console.log(` - ${r}`);
9098
9273
  }
9099
- if (assessment.recommendedAgents.length > 0) {
9100
- console.log(`Recommended: ${assessment.recommendedAgents.join(", ")}`);
9101
- }
9102
- if (assessment.nextStep) {
9103
- console.log(`Next step: ${assessment.nextStep}`);
9104
- }
9105
- const readiness = exp.readiness;
9106
- if (readiness.overall === "initialized" || readiness.overall === "bootstrap-incomplete") {
9107
- console.log("");
9108
- console.log(`Project readiness: ${readiness.overall}.`);
9109
- console.log(` \u2192 ${readiness.recommended_next_step.label}`);
9110
- }
9274
+ if (rec.agent) console.log(`Recommended: ${rec.agent}`);
9275
+ console.log(`Next step: ${rec.label}`);
9276
+ if (rec.reason) console.log(`Why: ${rec.reason}`);
9111
9277
  const installedSkills = discoverInstalledSkills(dir);
9112
9278
  if (installedSkills.length > 0 && assessment.recommendedAgents.length > 0) {
9113
9279
  const recSkills = skillsForAgents(installedSkills, assessment.recommendedAgents);
@@ -11846,6 +12012,8 @@ function fm(type, state) {
11846
12012
  return `---
11847
12013
  type: ${type}
11848
12014
  project_state: ${state}
12015
+ generated_by: kaddo-bootstrap
12016
+ template_version: 1
11849
12017
  ---
11850
12018
 
11851
12019
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.36.0",
3
+ "version": "3.37.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {