@kaddo/cli 3.36.0 → 3.37.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 +149 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -532,6 +532,7 @@ 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 |
535
536
 
536
537
  **Optional modules (installed with `kaddo add`):**
537
538
 
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 = [];
@@ -7631,24 +7747,12 @@ function buildSharedFileStatuses(statuses) {
7631
7747
 
7632
7748
  // src/core/readiness.ts
7633
7749
  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" }
7750
+ { key: "current_state", path: "knowledge/tech/current-state.md", label: "knowledge/tech/current-state.md", agent: "architecture-agent" },
7751
+ { key: "codebase", path: "knowledge/tech/codebase.md", label: "knowledge/tech/codebase.md", agent: "codebase-agent" },
7752
+ { key: "capabilities", path: "knowledge/product/capabilities.md", label: "knowledge/product/capabilities.md", agent: "capability-agent" },
7753
+ { key: "product", path: "knowledge/product/product.md", label: "knowledge/product/product.md", agent: "product-agent" },
7754
+ { key: "business", path: "knowledge/business/business.md", label: "knowledge/business/business.md", agent: "business-agent" }
7639
7755
  ];
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
- }
7652
7756
  function roadmapSignal(dir) {
7653
7757
  const p2 = join(dir, "knowledge/delivery/roadmap.md");
7654
7758
  if (!exists(p2)) return "missing";
@@ -7711,7 +7815,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
7711
7815
  if (config.project.state === "legacy") return stub("legacy-project", "Use the legacy project flow.");
7712
7816
  const scan2 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
7713
7817
  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)]));
7818
+ const presence = Object.fromEntries(KNOWLEDGE_FILES.map((f) => [f.key, analyzeKnowledgeArtifact(dir, f.path)]));
7715
7819
  const ctx = buildCodexAdapterContext(dir);
7716
7820
  const agents = ctx.hasAgents ? "present" : "missing";
7717
7821
  const skills = ctx.hasSkills ? "present" : "missing";
@@ -7741,7 +7845,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
7741
7845
  };
7742
7846
  let overall;
7743
7847
  let next;
7744
- const firstWeak = KNOWLEDGE_FILES.find((f) => presence[f.key] !== "present");
7848
+ const firstWeak = KNOWLEDGE_FILES.find((f) => presence[f.key] !== "useful");
7745
7849
  if (scan2 === "missing") {
7746
7850
  overall = "initialized";
7747
7851
  next = { label: "Run `kaddo scan` to capture deterministic signals from the existing code.", command: "kaddo scan" };
@@ -7759,7 +7863,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
7759
7863
  next = { label: "Run `kaddo understand` to summarize the project context.", command: "kaddo understand" };
7760
7864
  } else if (firstWeak) {
7761
7865
  overall = "knowledge-incomplete";
7762
- next = { label: `Complete \`${firstWeak.label}\` (it is ${presence[firstWeak.key]}).` };
7866
+ next = { label: `Use ${firstWeak.agent} to complete \`${firstWeak.label}\` (it is ${presence[firstWeak.key]}).` };
7763
7867
  } else if (oq.summary.blocking_open > 0) {
7764
7868
  overall = "needs-decisions";
7765
7869
  next = { label: "Resolve, assume or defer the blocking open questions (`kaddo questions`).", command: "kaddo questions" };
@@ -8183,6 +8287,9 @@ function renderExplanationHuman(exp) {
8183
8287
  lines.push(`- understand: ${s.understand}`);
8184
8288
  lines.push(`- agents: ${s.agents}`);
8185
8289
  lines.push(`- skills: ${s.skills}`);
8290
+ lines.push(`- business: ${s.business}`);
8291
+ lines.push(`- product: ${s.product}`);
8292
+ lines.push(`- capabilities: ${s.capabilities}`);
8186
8293
  lines.push(`- current-state: ${s.current_state}`);
8187
8294
  lines.push(`- codebase: ${s.codebase}`);
8188
8295
  lines.push(`- capabilities: ${s.capabilities}`);
@@ -8522,6 +8629,24 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8522
8629
  }
8523
8630
  const mappedModules = loadMappedModules(dir);
8524
8631
  const layers = knowledgeLayers(dir);
8632
+ const qa = (rel) => analyzeKnowledgeArtifact(dir, rel);
8633
+ const qBusiness = qa("knowledge/business/business.md");
8634
+ const qProduct = qa("knowledge/product/product.md");
8635
+ const qCapabilities = qa("knowledge/product/capabilities.md");
8636
+ const qCodebase = qa("knowledge/tech/codebase.md");
8637
+ const qCurrentState = qa("knowledge/tech/current-state.md");
8638
+ const qRoadmap = qa("knowledge/delivery/roadmap.md");
8639
+ const layerStatusOf = (name) => layers.find((l) => l.layer === name)?.status ?? "Missing";
8640
+ const knowledgeQuality = {
8641
+ business: { status: layerStatusOf("Business"), artifacts: { "knowledge/business/business.md": qBusiness } },
8642
+ product: { status: layerStatusOf("Product"), artifacts: { "knowledge/product/product.md": qProduct, "knowledge/product/capabilities.md": qCapabilities } },
8643
+ tech: { status: layerStatusOf("Tech"), artifacts: { "knowledge/tech/codebase.md": qCodebase, "knowledge/tech/current-state.md": qCurrentState } },
8644
+ delivery: { status: layerStatusOf("Delivery"), artifacts: { "knowledge/delivery/roadmap.md": qRoadmap } }
8645
+ };
8646
+ if (qBusiness === "placeholder") missing.push("Business context exists but still looks like a bootstrap placeholder.");
8647
+ if (qProduct === "placeholder" || qCapabilities === "placeholder") missing.push("Product capabilities exist but still look like a bootstrap placeholder.");
8648
+ if (qCurrentState === "placeholder") missing.push("Current state exists but still looks like a bootstrap placeholder.");
8649
+ if (qCodebase === "placeholder") missing.push("Codebase map exists but still looks like a bootstrap placeholder.");
8525
8650
  const allWorkItems = allArtifacts.filter((a) => a.isWorkItem);
8526
8651
  const wiWithOwnership = allWorkItems.filter((a) => a.codeGlobs.length > 0).length;
8527
8652
  const phase = assessPhase({
@@ -8572,6 +8697,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
8572
8697
  artifacts: allArtifacts.filter((a) => a.codeGlobs.length > 0).map(toContextArtifact)
8573
8698
  },
8574
8699
  layers,
8700
+ knowledgeQuality,
8575
8701
  roadmap,
8576
8702
  phase,
8577
8703
  deliveryMix,
@@ -11846,6 +11972,8 @@ function fm(type, state) {
11846
11972
  return `---
11847
11973
  type: ${type}
11848
11974
  project_state: ${state}
11975
+ generated_by: kaddo-bootstrap
11976
+ template_version: 1
11849
11977
  ---
11850
11978
 
11851
11979
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.36.0",
3
+ "version": "3.37.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {