@kaddo/cli 3.9.1 → 3.10.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 (2) hide show
  1. package/dist/index.js +189 -106
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4814,6 +4814,110 @@ function runIgnoreRemove(artifactId) {
4814
4814
  import matter2 from "gray-matter";
4815
4815
  import { parse as parseYaml8 } from "yaml";
4816
4816
 
4817
+ // src/core/delivery-phase.ts
4818
+ function layer(layers, name) {
4819
+ return layers.find((l) => l.layer === name)?.status ?? "Missing";
4820
+ }
4821
+ function baseComplete(layers) {
4822
+ return layer(layers, "Business") !== "Missing" && layer(layers, "Product") !== "Missing" && layer(layers, "Tech") !== "Missing";
4823
+ }
4824
+ function determinePhase(input) {
4825
+ const { roadmap, workItems } = input;
4826
+ const active = workItems.byState.draft + workItems.byState.ready + workItems.byState["in-progress"] + workItems.byState.blocked;
4827
+ if (!baseComplete(input.layers)) return "Discovery";
4828
+ if (!roadmap.present) return "Planning";
4829
+ if (workItems.total === 0) return "Delivery Preparation";
4830
+ if (active > 0) return "Active Delivery";
4831
+ return "Maintenance";
4832
+ }
4833
+ function ownershipPct(o) {
4834
+ if (o.workItemsTotal === 0) return 100;
4835
+ return Math.round(o.workItemsWithOwnership / o.workItemsTotal * 100);
4836
+ }
4837
+ function buildReasons(input) {
4838
+ const reasons = [];
4839
+ reasons.push(input.roadmap.present ? "Roadmap available" : "No roadmap yet");
4840
+ if (input.roadmap.present) {
4841
+ reasons.push(`${input.roadmap.materialized} materialized work item(s)`);
4842
+ if (input.roadmap.remaining > 0) reasons.push(`${input.roadmap.remaining} roadmap candidate(s) remaining`);
4843
+ }
4844
+ const active = LIFECYCLE_STATES.filter((s) => input.workItems.byState[s] > 0 && s !== "completed" && s !== "archived");
4845
+ if (active.length > 0) {
4846
+ reasons.push(active.map((s) => `${s}: ${input.workItems.byState[s]}`).join(", "));
4847
+ }
4848
+ if (input.workItems.total > 0) reasons.push(`Ownership coverage ${ownershipPct(input.ownership)}%`);
4849
+ return reasons;
4850
+ }
4851
+ function firstMissingLayerAgent(layers) {
4852
+ if (layer(layers, "Business") === "Missing")
4853
+ return { agent: "business-agent", step: "Use business-agent to create knowledge/business/business.md" };
4854
+ if (layer(layers, "Product") === "Missing")
4855
+ return { agent: "capability-agent", step: "Use capability-agent to create knowledge/product/capabilities.md" };
4856
+ return { agent: "architecture-agent", step: "Use architecture-agent to create knowledge/tech/current-state.md" };
4857
+ }
4858
+ function assessPhase(input) {
4859
+ const phase = determinePhase(input);
4860
+ const reasons = buildReasons(input);
4861
+ const bs = input.workItems.byState;
4862
+ const items = input.workItems.items;
4863
+ const firstOf = (s) => items.find((i) => i.lifecycle === s);
4864
+ let recommendedAgents2 = [];
4865
+ let nextStep = "";
4866
+ switch (phase) {
4867
+ case "Discovery": {
4868
+ const m = firstMissingLayerAgent(input.layers);
4869
+ recommendedAgents2 = [m.agent];
4870
+ nextStep = m.step;
4871
+ break;
4872
+ }
4873
+ case "Planning": {
4874
+ recommendedAgents2 = ["roadmap-agent"];
4875
+ nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
4876
+ break;
4877
+ }
4878
+ case "Delivery Preparation": {
4879
+ recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
4880
+ const n = input.roadmap.remaining || input.roadmap.candidates;
4881
+ nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
4882
+ break;
4883
+ }
4884
+ case "Active Delivery": {
4885
+ if (bs.ready > 0) {
4886
+ const wi = firstOf("ready");
4887
+ recommendedAgents2 = ["implementation-agent"];
4888
+ nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
4889
+ } else if (bs["in-progress"] > 0) {
4890
+ const wi = firstOf("in-progress");
4891
+ recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
4892
+ nextStep = wi ? `Continue ${wi.id} \u2014 ${wi.title}; then run kaddo scan, owners suggest, guard` : "Continue the in-progress Work Item; then scan, owners suggest, guard";
4893
+ } else if (bs.draft > 0) {
4894
+ const wi = firstOf("draft");
4895
+ recommendedAgents2 = ["work-item-agent"];
4896
+ nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
4897
+ } else if (bs.blocked > 0) {
4898
+ const wi = firstOf("blocked");
4899
+ recommendedAgents2 = ["work-item-agent"];
4900
+ nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
4901
+ }
4902
+ if (input.ownership.workItemsMissingOwnership > 0) {
4903
+ recommendedAgents2.push("kaddo owners suggest");
4904
+ }
4905
+ break;
4906
+ }
4907
+ case "Maintenance": {
4908
+ if (input.roadmap.remaining > 0) {
4909
+ recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
4910
+ nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
4911
+ } else {
4912
+ recommendedAgents2 = ["roadmap-agent"];
4913
+ nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
4914
+ }
4915
+ break;
4916
+ }
4917
+ }
4918
+ return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
4919
+ }
4920
+
4817
4921
  // src/core/knowledge-discovery.ts
4818
4922
  var KNOWLEDGE = "knowledge";
4819
4923
  var CONSOLIDATED_TYPE = {
@@ -4877,29 +4981,29 @@ function discoverLayers(dir) {
4877
4981
  const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
4878
4982
  for (const a of artifacts) {
4879
4983
  const type = a.type;
4880
- const layer = layerForType(type) ?? layerFromPath(a.filePath);
4881
- if (!layer) continue;
4882
- const slot = acc[layer];
4984
+ const layer2 = layerForType(type) ?? layerFromPath(a.filePath);
4985
+ if (!layer2) continue;
4986
+ const slot = acc[layer2];
4883
4987
  slot.detected.add(basename(a.filePath));
4884
- if (layer === "Delivery") {
4988
+ if (layer2 === "Delivery") {
4885
4989
  if (type === "roadmap" || basename(a.filePath) === "roadmap.md") slot.hasRoadmap = true;
4886
4990
  if (WORK_ITEM_TYPES2.has(type) || a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) {
4887
4991
  if (a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) slot.hasWorkItem = true;
4888
4992
  }
4889
4993
  continue;
4890
4994
  }
4891
- if (type === CONSOLIDATED_TYPE[layer]) slot.consolidated = true;
4892
- else if (STRUCTURED_TYPES[layer].has(type)) slot.structured = true;
4995
+ if (type === CONSOLIDATED_TYPE[layer2]) slot.consolidated = true;
4996
+ else if (STRUCTURED_TYPES[layer2].has(type)) slot.structured = true;
4893
4997
  else if (!type) {
4894
4998
  slot.consolidated = true;
4895
4999
  }
4896
5000
  if (type === "adr" || type === "decision") slot.hasDecision = true;
4897
5001
  }
4898
5002
  if (existsDirWithMd(join(dir, KNOWLEDGE, "tech", "decisions"))) acc.Tech.structured = true;
4899
- return ["Business", "Product", "Tech", "Delivery"].map((layer) => ({
4900
- layer,
4901
- status: statusFor(layer, acc[layer]),
4902
- detected: [...acc[layer].detected].sort()
5003
+ return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
5004
+ layer: layer2,
5005
+ status: statusFor(layer2, acc[layer2]),
5006
+ detected: [...acc[layer2].detected].sort()
4903
5007
  }));
4904
5008
  }
4905
5009
  function blank() {
@@ -4920,8 +5024,8 @@ function existsDirWithMd(d) {
4920
5024
  return false;
4921
5025
  }
4922
5026
  }
4923
- function statusFor(layer, a) {
4924
- if (layer === "Delivery") {
5027
+ function statusFor(layer2, a) {
5028
+ if (layer2 === "Delivery") {
4925
5029
  if (a.hasWorkItem) return "Traceable";
4926
5030
  if (a.hasRoadmap) return "Partial";
4927
5031
  return "Missing";
@@ -4930,40 +5034,15 @@ function statusFor(layer, a) {
4930
5034
  if (a.consolidated) return "Consolidated";
4931
5035
  return "Missing";
4932
5036
  }
4933
- function roadmapHasUnmaterializedCandidates(dir) {
4934
- const roadmap = join(dir, KNOWLEDGE, "delivery", "roadmap.md");
4935
- if (!exists(roadmap)) return false;
4936
- let hasCandidates = false;
4937
- try {
4938
- hasCandidates = /WI-[A-Z0-9-]*\d/i.test(readFile(roadmap));
4939
- } catch {
4940
- return false;
4941
- }
4942
- if (!hasCandidates) return false;
4943
- const wiDir = join(dir, KNOWLEDGE, "delivery", "work-items");
4944
- return !existsDirWithMd(wiDir);
4945
- }
4946
5037
 
4947
5038
  // src/core/layers.ts
4948
5039
  function knowledgeLayers(dir) {
4949
5040
  return discoverLayers(dir);
4950
5041
  }
4951
- var COMPLETE = {
4952
- Business: (s) => s !== "Missing",
4953
- Product: (s) => s !== "Missing",
4954
- Tech: (s) => s !== "Missing",
4955
- Delivery: (s) => s === "Traceable"
4956
- };
4957
- function currentPhase(layers) {
4958
- for (const l of layers) {
4959
- if (!COMPLETE[l.layer](l.status)) return l.layer;
4960
- }
4961
- return "Delivery";
4962
- }
4963
5042
  function renderLayersMarkdown(layers) {
4964
5043
  const lines = [];
4965
- for (const { layer, status, detected } of layers) {
4966
- lines.push(`### ${layer} \u2014 ${status}`);
5044
+ for (const { layer: layer2, status, detected } of layers) {
5045
+ lines.push(`### ${layer2} \u2014 ${status}`);
4967
5046
  if (detected.length > 0) for (const d of detected) lines.push(`- \u2713 ${d}`);
4968
5047
  else lines.push("- \u2717 none");
4969
5048
  lines.push("");
@@ -5271,6 +5350,15 @@ function renderExplanationHuman(exp) {
5271
5350
  for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
5272
5351
  lines.push("");
5273
5352
  }
5353
+ const assessment = assessPhase(exp);
5354
+ lines.push("## Phase");
5355
+ lines.push(`- Phase: ${assessment.phase}`);
5356
+ if (assessment.reasons.length > 0) {
5357
+ lines.push("- Reason:");
5358
+ for (const r of assessment.reasons) lines.push(` - ${r}`);
5359
+ }
5360
+ if (assessment.nextStep) lines.push(`- Next step: ${assessment.nextStep}`);
5361
+ lines.push("");
5274
5362
  if (exp.suggestedNextSteps.length > 0) {
5275
5363
  lines.push("## Suggested Next Steps");
5276
5364
  exp.suggestedNextSteps.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
@@ -5600,6 +5688,28 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5600
5688
  }
5601
5689
  const mappedModules = loadMappedModules(dir);
5602
5690
  const layers = knowledgeLayers(dir);
5691
+ const allWorkItems = allArtifacts.filter((a) => a.isWorkItem);
5692
+ const wiWithOwnership = allWorkItems.filter((a) => a.codeGlobs.length > 0).length;
5693
+ const phase = assessPhase({
5694
+ layers,
5695
+ roadmap,
5696
+ workItems: {
5697
+ total: allWorkItems.length,
5698
+ byState: lifecycleCounts(
5699
+ allWorkItems.map((a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }))
5700
+ ),
5701
+ items: allWorkItems.map((a) => ({
5702
+ id: a.id || a.title,
5703
+ title: a.title,
5704
+ lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath })
5705
+ }))
5706
+ },
5707
+ ownership: {
5708
+ workItemsTotal: allWorkItems.length,
5709
+ workItemsWithOwnership: wiWithOwnership,
5710
+ workItemsMissingOwnership: allWorkItems.length - wiWithOwnership
5711
+ }
5712
+ });
5603
5713
  return {
5604
5714
  version: CONTEXT_PACK_VERSION,
5605
5715
  generatedAt: now.toISOString(),
@@ -5628,6 +5738,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5628
5738
  },
5629
5739
  layers,
5630
5740
  roadmap,
5741
+ phase,
5631
5742
  deliveryMix,
5632
5743
  mappedModules,
5633
5744
  missing,
@@ -5665,6 +5776,21 @@ function renderContextPack(pack) {
5665
5776
  `- Structure: ${project.structure}`
5666
5777
  ].join("\n") + "\n"
5667
5778
  );
5779
+ parts.push("## Current Phase\n");
5780
+ parts.push(`Phase: ${pack.phase.phase}
5781
+ `);
5782
+ if (pack.phase.reasons.length > 0) {
5783
+ parts.push("Reason:\n");
5784
+ parts.push(pack.phase.reasons.map((r) => `- ${r}`).join("\n") + "\n");
5785
+ }
5786
+ if (pack.phase.recommendedAgents.length > 0) {
5787
+ parts.push(`Recommended next: ${pack.phase.recommendedAgents.join(", ")}
5788
+ `);
5789
+ }
5790
+ if (pack.phase.nextStep) {
5791
+ parts.push(`Next step: ${pack.phase.nextStep}
5792
+ `);
5793
+ }
5668
5794
  parts.push("## Knowledge Layers\n");
5669
5795
  parts.push(
5670
5796
  "Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
@@ -5850,7 +5976,7 @@ function flowForState(state) {
5850
5976
  }
5851
5977
  function buildUnderstandPlan(dir, config) {
5852
5978
  const state = config.project.state;
5853
- const flow = flowForState(state);
5979
+ const flow = flowForState(state).filter((s) => !exists(join(dir, s.output)));
5854
5980
  const steps = flow.map((s) => {
5855
5981
  const installed = agentIsInstalled(dir, s.agent);
5856
5982
  return { agent: s.agent, output: s.output, installed };
@@ -5951,24 +6077,26 @@ function renderUnderstandTerminal(plan) {
5951
6077
  lines.push(`Team: ${project.teamSize}`);
5952
6078
  lines.push(`Structure: ${project.structure}`);
5953
6079
  lines.push("");
5954
- lines.push("Recommended flow:");
5955
- steps.forEach((s, i) => {
5956
- const flag = s.installed ? "" : " (not installed)";
5957
- lines.push(` ${i + 1}. ${agentName(s.agent)} \u2192 ${s.output}${flag}`);
5958
- });
5959
- lines.push("");
5960
6080
  const first2 = steps[0];
5961
6081
  if (first2) {
6082
+ lines.push("Foundational knowledge still needed:");
6083
+ steps.forEach((s, i) => {
6084
+ const flag = s.installed ? "" : " (not installed)";
6085
+ lines.push(` ${i + 1}. ${agentName(s.agent)} \u2192 ${s.output}${flag}`);
6086
+ });
6087
+ lines.push("");
5962
6088
  lines.push(`First step: use ${agentName(first2.agent)}.`);
5963
6089
  lines.push("");
5964
6090
  lines.push(` Context: ${plan.contextPackPath}`);
5965
6091
  lines.push(` Agent prompt: knowledge/agents/${first2.agent}`);
5966
6092
  lines.push(` Expected output: ${first2.output}`);
5967
6093
  lines.push("");
6094
+ lines.push("Instructions:");
6095
+ lines.push(" Open your preferred LLM chat, paste the context pack, then paste the agent");
6096
+ lines.push(" prompt. Ask the LLM to generate the expected output and save it in the target file.");
6097
+ } else {
6098
+ lines.push("Foundational knowledge looks complete \u2014 see the phase recommendation below.");
5968
6099
  }
5969
- lines.push("Instructions:");
5970
- lines.push(" Open your preferred LLM chat, paste the context pack, then paste the agent");
5971
- lines.push(" prompt. Ask the LLM to generate the expected output and save it in the target file.");
5972
6100
  lines.push("");
5973
6101
  lines.push("Kaddo does not call an LLM. You stay in control of the interpretation.");
5974
6102
  lines.push("");
@@ -6064,64 +6192,19 @@ function runUnderstand() {
6064
6192
  );
6065
6193
  }
6066
6194
  console.log(renderUnderstandTerminal(plan));
6067
- const phase = currentPhase(knowledgeLayers(dir));
6068
- const group = phase.toLowerCase();
6069
- const groupAgents = (AGENT_GROUPS[group] ?? []).map((a) => a.replace(/\.md$/, ""));
6070
- const NEXT_STEPS = {
6071
- business: ["Use business-agent \u2192 refine knowledge/business/business.md"],
6072
- product: [
6073
- "Use bootstrap-agent \u2192 refine knowledge/product/product.md",
6074
- "Use capability-agent \u2192 knowledge/product/capabilities.md"
6075
- ],
6076
- tech: [
6077
- "Use architecture-agent \u2192 knowledge/tech/current-state.md (reality)",
6078
- "Use codebase-agent \u2192 knowledge/tech/codebase.md (intent)",
6079
- "Record decisions as ADRs in knowledge/tech/decisions/"
6080
- ],
6081
- delivery: [
6082
- "Use roadmap-agent \u2192 knowledge/delivery/roadmap.md",
6083
- "Run `kaddo create --from roadmap` (candidates \u2192 Work Items)",
6084
- "Use work-item-agent \u2192 refine into knowledge/delivery/work-items/",
6085
- "Use implementation-agent \u2192 implement, then `kaddo scan`, `kaddo owners suggest`, `kaddo guard`"
6086
- ],
6087
- utilities: ["Use legacy-agent to surface risks and unknowns"]
6088
- };
6089
- if (roadmapHasUnmaterializedCandidates(dir)) {
6090
- const { candidates, materialized, remaining } = pack.roadmap;
6091
- console.log("");
6092
- console.log(
6093
- `The roadmap has ${remaining} unmaterialized Work Item candidate(s) (${candidates} candidate(s), ${materialized} materialized).`
6094
- );
6095
- console.log(" \u2192 Run `kaddo create --from roadmap`, or use the work-item-agent to");
6096
- console.log(" materialize them into knowledge/delivery/work-items/.");
6097
- }
6195
+ const exp = buildProjectExplanation(dir);
6196
+ const assessment = assessPhase(exp);
6098
6197
  console.log("");
6099
- console.log(`Current phase: ${phase}`);
6100
- console.log("Recommended next steps:");
6101
- console.log(" 1. Run `kaddo scan` (technical signals \u2192 knowledge/inventory.md)");
6102
- console.log(" 2. Run `kaddo context` (package knowledge for your LLM)");
6103
- let n = 3;
6104
- for (const step of NEXT_STEPS[group] ?? []) console.log(` ${n++}. ${step}`);
6105
- if (groupAgents.length > 0) {
6106
- console.log(`Agents for this phase: ${groupAgents.join(", ")}`);
6198
+ console.log(`Current phase: ${assessment.phase}`);
6199
+ if (assessment.reasons.length > 0) {
6200
+ console.log("Reason:");
6201
+ for (const r of assessment.reasons) console.log(` - ${r}`);
6107
6202
  }
6108
- const exp = buildProjectExplanation(dir);
6109
- if (exp.workItems.total > 0) {
6110
- const bs = exp.workItems.byState;
6111
- console.log("");
6112
- console.log("Current active work:");
6113
- console.log(` Draft: ${bs.draft} Ready: ${bs.ready} In Progress: ${bs["in-progress"]} Blocked: ${bs.blocked}`);
6114
- const firstReady = exp.workItems.items.find((i) => i.lifecycle === "ready");
6115
- const firstInProgress = exp.workItems.items.find((i) => i.lifecycle === "in-progress");
6116
- if (firstInProgress) {
6117
- console.log(` \u2192 Continue ${firstInProgress.id} \u2014 ${firstInProgress.title} (in progress).`);
6118
- } else if (firstReady) {
6119
- console.log(` \u2192 Recommended next step: start ${firstReady.id} \u2014 ${firstReady.title}.`);
6120
- } else if (bs.draft > 0) {
6121
- console.log(" \u2192 Refine a draft Work Item to `ready` (scope + acceptance defined).");
6122
- } else if (bs.blocked > 0) {
6123
- console.log(" \u2192 All active work is blocked. Resolve the blockers to move forward.");
6124
- }
6203
+ if (assessment.recommendedAgents.length > 0) {
6204
+ console.log(`Recommended: ${assessment.recommendedAgents.join(", ")}`);
6205
+ }
6206
+ if (assessment.nextStep) {
6207
+ console.log(`Next step: ${assessment.nextStep}`);
6125
6208
  }
6126
6209
  const active = activeWorkItems(dir);
6127
6210
  if (active.length > 0) {
@@ -8789,7 +8872,7 @@ async function runBootstrap(dir = cwd()) {
8789
8872
  const result = bootstrap(dir);
8790
8873
  console.log("");
8791
8874
  console.log("Bootstrap layers:");
8792
- for (const layer of result.layers) console.log(` \u2713 ${layer}`);
8875
+ for (const layer2 of result.layers) console.log(` \u2713 ${layer2}`);
8793
8876
  console.log("");
8794
8877
  if (result.written.length > 0) {
8795
8878
  console.log("Created:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.9.1",
3
+ "version": "3.10.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {