@kaddo/cli 3.14.0 → 3.15.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 +264 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -624,8 +624,8 @@ function createGuidanceForState(state) {
624
624
  }
625
625
  function describeProject(config) {
626
626
  const state = config.project.state;
627
- const stateLabel4 = state === "pre-ai" ? "pre-AI" : state;
628
- return `${stateLabel4} ${config.project.structure}`;
627
+ const stateLabel3 = state === "pre-ai" ? "pre-AI" : state;
628
+ return `${stateLabel3} ${config.project.structure}`;
629
629
  }
630
630
 
631
631
  // src/core/command-help.ts
@@ -1611,6 +1611,19 @@ var RESPONSIBILITY_MATRIX = {
1611
1611
  ],
1612
1612
  next: ["kaddo scan", "kaddo owners suggest", "kaddo guard", "kaddo explain"]
1613
1613
  },
1614
+ "ownership-agent": {
1615
+ agent: "ownership-agent",
1616
+ responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
1617
+ produces: ["proposed code: globs"],
1618
+ canSuggest: ["kaddo owners suggest", "kaddo guard"],
1619
+ cannotSuggest: [
1620
+ "code",
1621
+ "branches",
1622
+ "commits",
1623
+ "modifying files without confirmation"
1624
+ ],
1625
+ next: ["kaddo owners suggest", "kaddo guard"]
1626
+ },
1614
1627
  "guard-agent": {
1615
1628
  agent: "guard-agent",
1616
1629
  responsibleFor: ["Knowledge drift"],
@@ -1884,13 +1897,15 @@ Generated from Kaddo Context Pack.
1884
1897
 
1885
1898
  Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
1886
1899
  \`knowledge/tech/architecture-notes.md\`, and decision candidates as
1887
- \`knowledge/tech/decision-candidates.md\`.
1900
+ \`knowledge/tech/decision-candidates.md\`. Final ADRs always live under
1901
+ \`knowledge/tech/decisions/\` \u2014 never directly in \`knowledge/tech/\`.
1888
1902
 
1889
1903
  ## Quality Checklist
1890
1904
 
1891
1905
  - Every component is backed by evidence from the context pack.
1892
1906
  - Assumptions and confidence are explicit.
1893
1907
  - No final decisions are asserted \u2014 only candidates.
1908
+ - Final ADRs go to \`knowledge/tech/decisions/\`, not \`knowledge/tech/\`.
1894
1909
  - Open questions are listed.
1895
1910
  `;
1896
1911
  var ROADMAP_AGENT = `# Roadmap Agent
@@ -2209,12 +2224,16 @@ Generated from Kaddo Context Pack.
2209
2224
 
2210
2225
  ## Where to Save the Result
2211
2226
 
2212
- Save the output as \`knowledge/tech/decision-candidates.md\`.
2227
+ Save decision **candidates** as \`knowledge/tech/decision-candidates.md\`. When a candidate becomes
2228
+ a **final ADR**, it must live under \`knowledge/tech/decisions/\` (one file per decision, e.g.
2229
+ \`knowledge/tech/decisions/ADR-0001-<slug>.md\`) \u2014 **never** directly in \`knowledge/tech/\`.
2230
+ (Decision = the concept \xB7 ADR = the format \xB7 Path = \`knowledge/tech/decisions/\`.)
2213
2231
 
2214
2232
  ## Quality Checklist
2215
2233
 
2216
2234
  - Each candidate has context and alternatives.
2217
2235
  - No decision is asserted as final.
2236
+ - Final ADRs go to \`knowledge/tech/decisions/\`, never to \`knowledge/tech/\` directly.
2218
2237
  - Assumptions are marked.
2219
2238
  - Validation needs are explicit.
2220
2239
  `;
@@ -2913,6 +2932,69 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
2913
2932
  - Affected knowledge is updated.
2914
2933
  - Commit is suggested and awaits human confirmation \u2014 never run automatically.
2915
2934
  `;
2935
+ var OWNERSHIP_AGENT = `# Ownership Agent
2936
+
2937
+ ## Role
2938
+
2939
+ You are the Kaddo Ownership Agent. Your job is to propose **precise** \`code:\` ownership globs for
2940
+ Work Items and knowledge artifacts, so Guard can relate code changes to the right knowledge.
2941
+
2942
+ You do not write code, you do not modify files, and you never run Git. You propose; the human
2943
+ confirms and applies (with \`kaddo owners suggest\`).
2944
+
2945
+ ## When to Use
2946
+
2947
+ Use this agent after \`kaddo scan\` and \`kaddo context\`, when Work Items or artifacts are missing
2948
+ \`code:\` ownership, or when existing ownership is too broad or inaccurate.
2949
+
2950
+ ## Input Required
2951
+
2952
+ Provide \`.kaddo/context-pack.md\` as the primary input, plus the Work Items under
2953
+ \`knowledge/delivery/work-items/\`, \`knowledge/tech/codebase.md\` and \`knowledge/inventory.md\` when
2954
+ they exist (for the real source structure).
2955
+
2956
+ ## Expected Output
2957
+
2958
+ For each artifact, a precise set of \`code:\` globs.
2959
+
2960
+ ## Instructions
2961
+
2962
+ 1. Map each Work Item / artifact to the smallest set of paths that actually implement it.
2963
+ 2. Prefer **narrow** globs (e.g. \`src/payments/**\`) over broad ones (e.g. \`src/**\`).
2964
+ 3. Use real paths from the inventory/codebase \u2014 do not invent directories.
2965
+ 4. Include relevant root files (e.g. \`package.json\`, \`tsconfig.json\`) when they belong.
2966
+ 5. Flag artifacts where ownership is genuinely unclear instead of guessing broadly.
2967
+
2968
+ ## Constraints
2969
+
2970
+ - Do not implement code.
2971
+ - Do not modify files without confirmation \u2014 propose globs for the human to apply.
2972
+ - Do not create branches or commits; never run Git.
2973
+ - Prefer precision: broad globs reduce Guard usefulness.
2974
+
2975
+ ## Output Format
2976
+
2977
+ \`\`\`yaml
2978
+ # <Work Item id> \u2014 proposed ownership
2979
+ code:
2980
+ - package.json
2981
+ - tsconfig.json
2982
+ - src/cli/**
2983
+ - src/shared/**
2984
+ \`\`\`
2985
+
2986
+ ## Where to Save the Result
2987
+
2988
+ The human applies the proposed globs to the artifact's front matter with \`kaddo owners suggest\`
2989
+ (or by editing the \`code:\` field). This agent does not write files.
2990
+
2991
+ ## Quality Checklist
2992
+
2993
+ - Globs are narrow and based on real paths.
2994
+ - No \`src/**\`-style catch-alls unless truly justified.
2995
+ - Unclear ownership is flagged, not guessed.
2996
+ - Output is a proposal for human confirmation \u2014 nothing is applied automatically.
2997
+ `;
2916
2998
  var BACKLOG_AGENT = `# Backlog Agent
2917
2999
 
2918
3000
  ## Role
@@ -3024,7 +3106,9 @@ var AGENT_PROMPTS = [
3024
3106
  // Implementation (the only agent that may suggest a branch — VS-044)
3025
3107
  { fileName: "implementation-agent.md", content: IMPLEMENTATION_AGENT },
3026
3108
  // Backlog capture (idea → draft / roadmap candidate — VS-050)
3027
- { fileName: "backlog-agent.md", content: BACKLOG_AGENT }
3109
+ { fileName: "backlog-agent.md", content: BACKLOG_AGENT },
3110
+ // Ownership proposals (precise code: globs — VS-052)
3111
+ { fileName: "ownership-agent.md", content: OWNERSHIP_AGENT }
3028
3112
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
3029
3113
  ].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
3030
3114
 
@@ -3046,6 +3130,7 @@ var AGENT_GROUPS = {
3046
3130
  "roadmap-agent.md",
3047
3131
  "work-item-agent.md",
3048
3132
  "implementation-agent.md",
3133
+ "ownership-agent.md",
3049
3134
  "git-strategy-agent.md"
3050
3135
  ],
3051
3136
  utilities: ["legacy-agent.md"]
@@ -3079,7 +3164,8 @@ var RECOMMENDED_BY_STATE = {
3079
3164
  "roadmap-agent.md",
3080
3165
  "backlog-agent.md",
3081
3166
  "work-item-agent.md",
3082
- "implementation-agent.md"
3167
+ "implementation-agent.md",
3168
+ "ownership-agent.md"
3083
3169
  ],
3084
3170
  legacy: [
3085
3171
  "legacy-agent.md",
@@ -3088,7 +3174,8 @@ var RECOMMENDED_BY_STATE = {
3088
3174
  "roadmap-agent.md",
3089
3175
  "backlog-agent.md",
3090
3176
  "work-item-agent.md",
3091
- "implementation-agent.md"
3177
+ "implementation-agent.md",
3178
+ "ownership-agent.md"
3092
3179
  ]
3093
3180
  };
3094
3181
  function recommendedAgents(state) {
@@ -4234,6 +4321,14 @@ async function isGitRepo(dir) {
4234
4321
  return false;
4235
4322
  }
4236
4323
  }
4324
+ async function getUntrackedFiles() {
4325
+ try {
4326
+ const result = await execa("git", ["ls-files", "--others", "--exclude-standard"]);
4327
+ return result.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
4328
+ } catch {
4329
+ return [];
4330
+ }
4331
+ }
4237
4332
 
4238
4333
  // src/services/artifact-reader.ts
4239
4334
  import matter from "gray-matter";
@@ -4254,7 +4349,8 @@ function parseArtifact(filePath, raw) {
4254
4349
  status: String(data.status ?? ""),
4255
4350
  phase: String(data.phase ?? ""),
4256
4351
  initiative: String(data.initiative ?? data.source_initiative ?? ""),
4257
- source: data.source ? String(data.source) : ""
4352
+ source: data.source ? String(data.source) : "",
4353
+ sourceId: String(data.source_id ?? "")
4258
4354
  };
4259
4355
  } catch {
4260
4356
  return null;
@@ -4892,6 +4988,15 @@ async function runGuard(opts = {}) {
4892
4988
  const plugins = resolvePlugins(config.plugins ?? []);
4893
4989
  const mode = opts.staged ? "staged" : "head";
4894
4990
  const currentFiles = await getModifiedFiles(mode);
4991
+ const untrackedFiles = opts.staged ? [] : await getUntrackedFiles();
4992
+ const printUntracked = () => {
4993
+ if (untrackedFiles.length === 0 || jsonMode) return;
4994
+ console.log("");
4995
+ console.log(`Untracked files detected (${untrackedFiles.length}):`);
4996
+ for (const f of untrackedFiles.slice(0, 10)) console.log(` - ${f}`);
4997
+ if (untrackedFiles.length > 10) console.log(` \u2026and ${untrackedFiles.length - 10} more`);
4998
+ console.log("Guard may not fully evaluate these files until they are tracked. (FYI \u2014 non-blocking)");
4999
+ };
4895
5000
  let workspaceScan = null;
4896
5001
  if (opts.workspace) {
4897
5002
  workspaceScan = await collectWorkspaceChanges(dir, mode);
@@ -4901,6 +5006,7 @@ async function runGuard(opts = {}) {
4901
5006
  if (touchedFiles.length === 0) {
4902
5007
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
4903
5008
  console.log("kaddo guard: no modified files detected.");
5009
+ printUntracked();
4904
5010
  return;
4905
5011
  }
4906
5012
  const archDir = join(dir, ARCH_DIR3);
@@ -4940,10 +5046,12 @@ async function runGuard(opts = {}) {
4940
5046
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
4941
5047
  printHeader(touchedFiles);
4942
5048
  console.log(" No artifact ownership matches found.");
5049
+ printUntracked();
4943
5050
  return;
4944
5051
  }
4945
5052
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
4946
5053
  printHeader(touchedFiles);
5054
+ printUntracked();
4947
5055
  for (const match of activeMatches) {
4948
5056
  printFYI(match, dir);
4949
5057
  }
@@ -5069,22 +5177,30 @@ function assessPhase(input) {
5069
5177
  const firstOf = (s) => items.find((i) => i.lifecycle === s);
5070
5178
  let recommendedAgents2 = [];
5071
5179
  let nextStep = "";
5180
+ let llmInstructions = [];
5072
5181
  switch (phase) {
5073
5182
  case "Discovery": {
5074
5183
  const m = firstMissingLayerAgent(input.layers);
5075
5184
  recommendedAgents2 = [m.agent];
5076
5185
  nextStep = m.step;
5186
+ llmInstructions = [`Use the ${m.agent} to fill the missing base knowledge.`, "Do not write code."];
5077
5187
  break;
5078
5188
  }
5079
5189
  case "Planning": {
5080
5190
  recommendedAgents2 = ["roadmap-agent"];
5081
5191
  nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
5192
+ llmInstructions = ["Use the roadmap-agent.", "Do not write code.", "Generate roadmap candidates."];
5082
5193
  break;
5083
5194
  }
5084
5195
  case "Delivery Preparation": {
5085
5196
  recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
5086
5197
  const n = input.roadmap.remaining || input.roadmap.candidates;
5087
5198
  nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
5199
+ llmInstructions = [
5200
+ "Materialize roadmap candidates with `kaddo create --from roadmap`.",
5201
+ "Use the work-item-agent to refine them.",
5202
+ "Do not implement yet."
5203
+ ];
5088
5204
  break;
5089
5205
  }
5090
5206
  case "Active Delivery": {
@@ -5092,21 +5208,41 @@ function assessPhase(input) {
5092
5208
  const wi = firstOf("ready");
5093
5209
  recommendedAgents2 = ["implementation-agent"];
5094
5210
  nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
5211
+ llmInstructions = [
5212
+ "Use the implementation-agent.",
5213
+ "Suggest a branch name only.",
5214
+ "Do not run git commands."
5215
+ ];
5095
5216
  } else if (bs["in-progress"] > 0) {
5096
5217
  const wi = firstOf("in-progress");
5097
5218
  recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
5098
5219
  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";
5220
+ llmInstructions = [
5221
+ "Continue the in-progress Work Item with the implementation-agent.",
5222
+ "After changes, run `kaddo scan`, `kaddo owners suggest` and `kaddo guard`.",
5223
+ "Do not commit, push or merge without explicit human confirmation."
5224
+ ];
5099
5225
  } else if (bs.draft > 0) {
5100
5226
  const wi = firstOf("draft");
5101
5227
  recommendedAgents2 = ["work-item-agent"];
5102
5228
  nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
5229
+ llmInstructions = [
5230
+ "Refine draft Work Items to ready.",
5231
+ "Use the work-item-agent.",
5232
+ "Do not implement unless the user explicitly asks."
5233
+ ];
5103
5234
  } else if (bs.blocked > 0) {
5104
5235
  const wi = firstOf("blocked");
5105
5236
  recommendedAgents2 = ["work-item-agent"];
5106
5237
  nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
5238
+ llmInstructions = [
5239
+ "Resolve the blockers with the work-item-agent.",
5240
+ "Do not implement blocked work."
5241
+ ];
5107
5242
  }
5108
5243
  if (input.ownership.workItemsMissingOwnership > 0) {
5109
5244
  recommendedAgents2.push("kaddo owners suggest");
5245
+ llmInstructions.push("Ownership is incomplete \u2014 propose `code:` globs (run `kaddo owners suggest`).");
5110
5246
  }
5111
5247
  break;
5112
5248
  }
@@ -5114,14 +5250,16 @@ function assessPhase(input) {
5114
5250
  if (input.roadmap.remaining > 0) {
5115
5251
  recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
5116
5252
  nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
5253
+ llmInstructions = ["Materialize the remaining roadmap candidates.", "Do not implement yet."];
5117
5254
  } else {
5118
5255
  recommendedAgents2 = ["roadmap-agent"];
5119
5256
  nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
5257
+ llmInstructions = ["No active work.", "Use the roadmap-agent to plan the next initiative."];
5120
5258
  }
5121
5259
  break;
5122
5260
  }
5123
5261
  }
5124
- return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
5262
+ return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
5125
5263
  }
5126
5264
 
5127
5265
  // src/core/knowledge-discovery.ts
@@ -5258,6 +5396,30 @@ function renderLayersMarkdown(layers) {
5258
5396
 
5259
5397
  // src/core/project-explain.ts
5260
5398
  var ARCH_DIR4 = "knowledge";
5399
+ function normalizeTitle(t) {
5400
+ return t.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, " ").trim();
5401
+ }
5402
+ function findDuplicateWorkItems(items) {
5403
+ const groups = [];
5404
+ const seen = /* @__PURE__ */ new Set();
5405
+ const bucket = (key, reason, pred) => {
5406
+ const matched = items.filter(pred);
5407
+ if (matched.length > 1) {
5408
+ const id = `${reason}:${key}:${matched.map((m) => m.id).sort().join(",")}`;
5409
+ if (!seen.has(id)) {
5410
+ seen.add(id);
5411
+ groups.push({ reason, items: matched.map((m) => ({ id: m.id, title: m.title })) });
5412
+ }
5413
+ }
5414
+ };
5415
+ for (const sid of new Set(items.map((i) => i.sourceId).filter(Boolean))) {
5416
+ bucket(sid, `same source candidate (${sid})`, (i) => i.sourceId === sid);
5417
+ }
5418
+ for (const nt of new Set(items.map((i) => normalizeTitle(i.title)).filter(Boolean))) {
5419
+ bucket(nt, "same normalized title", (i) => normalizeTitle(i.title) === nt);
5420
+ }
5421
+ return groups;
5422
+ }
5261
5423
  function first(values) {
5262
5424
  if (Array.isArray(values)) {
5263
5425
  const v = values.find((x) => typeof x === "string" && x);
@@ -5376,6 +5538,9 @@ function buildProjectExplanation(dir) {
5376
5538
  workItemsMissingOwnership: items.length - withOwnership
5377
5539
  };
5378
5540
  const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
5541
+ const duplicateWorkItems = findDuplicateWorkItems(
5542
+ workItemArtifacts.map((a) => ({ id: a.id || a.title, title: a.title, sourceId: a.sourceId }))
5543
+ );
5379
5544
  const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
5380
5545
  const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
5381
5546
  const roadmap = roadmapStats(roadmapMd, items.length);
@@ -5425,6 +5590,7 @@ function buildProjectExplanation(dir) {
5425
5590
  workItems,
5426
5591
  ownership,
5427
5592
  domains,
5593
+ duplicateWorkItems,
5428
5594
  layers,
5429
5595
  roadmap,
5430
5596
  mappedModules,
@@ -5558,6 +5724,15 @@ function renderExplanationHuman(exp) {
5558
5724
  for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
5559
5725
  lines.push("");
5560
5726
  }
5727
+ if (exp.duplicateWorkItems.length > 0) {
5728
+ lines.push("## Possible Duplicate Work Items");
5729
+ for (const g of exp.duplicateWorkItems) {
5730
+ lines.push(`- ${g.reason}:`);
5731
+ for (const i of g.items) lines.push(` - ${i.id} \u2014 ${i.title}`);
5732
+ }
5733
+ lines.push("Review before continuing (non-blocking).");
5734
+ lines.push("");
5735
+ }
5561
5736
  const assessment = assessPhase(exp);
5562
5737
  lines.push("## Phase");
5563
5738
  lines.push(`- Phase: ${assessment.phase}`);
@@ -5952,10 +6127,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5952
6127
  deliveryMix,
5953
6128
  mappedModules,
5954
6129
  missing,
6130
+ // VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
6131
+ // contradicts the Current Phase block above it.
5955
6132
  handoff: {
5956
- recommendedAgents: recommendedAgentsForState(state),
5957
- nextSteps: nextStepsForState2(state),
5958
- instructions: LLM_INSTRUCTIONS,
6133
+ recommendedAgents: phase.recommendedAgents.length > 0 ? phase.recommendedAgents : recommendedAgentsForState(state),
6134
+ nextSteps: phase.nextStep ? [phase.nextStep] : nextStepsForState2(state),
6135
+ instructions: phase.llmInstructions.length > 0 ? phase.llmInstructions : LLM_INSTRUCTIONS,
5959
6136
  operatingRules: OPERATING_RULES
5960
6137
  }
5961
6138
  };
@@ -5965,9 +6142,6 @@ function serializeContextPackJson(pack) {
5965
6142
  }
5966
6143
 
5967
6144
  // src/templates/context-pack-template.ts
5968
- function stateLabel2(state) {
5969
- return state === "pre-ai" ? "pre-AI" : state;
5970
- }
5971
6145
  function renderContextPack(pack) {
5972
6146
  const { project, scan: scan2, knowledge, missing, handoff } = pack;
5973
6147
  const parts = [];
@@ -6108,11 +6282,11 @@ function renderContextPack(pack) {
6108
6282
  parts.push("_None \u2014 all expected context is present._\n");
6109
6283
  }
6110
6284
  parts.push("## Recommended Agent Handoff\n");
6111
- parts.push(`Recommended agents for a ${stateLabel2(project.state)} project:
6285
+ parts.push(`Recommended next for the **${pack.phase.phase}** phase:
6112
6286
  `);
6113
6287
  parts.push(handoff.recommendedAgents.map((a, i) => `${i + 1}. ${a}`).join("\n") + "\n");
6114
6288
  if (handoff.nextSteps.length > 0) {
6115
- parts.push("Next steps:\n");
6289
+ parts.push("Next step:\n");
6116
6290
  parts.push(handoff.nextSteps.map((s) => `- ${s}`).join("\n") + "\n");
6117
6291
  }
6118
6292
  parts.push("## Instructions for the LLM\n");
@@ -6211,7 +6385,7 @@ function buildUnderstandPlan(dir, config) {
6211
6385
  }
6212
6386
 
6213
6387
  // src/templates/understand-template.ts
6214
- function stateLabel3(state) {
6388
+ function stateLabel2(state) {
6215
6389
  return state === "pre-ai" ? "pre-AI" : state;
6216
6390
  }
6217
6391
  function agentName(file) {
@@ -6234,7 +6408,7 @@ function renderUnderstand(plan) {
6234
6408
  ].join("\n") + "\n"
6235
6409
  );
6236
6410
  parts.push("## Recommended Agent Flow\n");
6237
- parts.push(`Recommended order for a ${stateLabel3(project.state)} project:
6411
+ parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
6238
6412
  `);
6239
6413
  parts.push(
6240
6414
  steps.map((s, i) => {
@@ -6986,6 +7160,64 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
6986
7160
  // src/core/ownership-suggest.ts
6987
7161
  import matter5 from "gray-matter";
6988
7162
  var SCAN_PATH = ".kaddo/scan.json";
7163
+ function normalizeGlob(input) {
7164
+ let g = input.trim().replace(/\\/g, "/");
7165
+ if (!g) return g;
7166
+ if (g.endsWith("/")) return `${g.slice(0, -1)}/**`;
7167
+ if (!g.includes("*")) {
7168
+ const base = g.split("/").pop() ?? "";
7169
+ const looksLikeFile = /\.[A-Za-z0-9]+$/.test(base);
7170
+ if (!looksLikeFile) return `${g}/**`;
7171
+ }
7172
+ return g;
7173
+ }
7174
+ function levenshtein(a, b) {
7175
+ const m = a.length;
7176
+ const n = b.length;
7177
+ const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
7178
+ for (let j = 0; j <= n; j++) d[0][j] = j;
7179
+ for (let i = 1; i <= m; i++) {
7180
+ for (let j = 1; j <= n; j++) {
7181
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
7182
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
7183
+ }
7184
+ }
7185
+ return d[m][n];
7186
+ }
7187
+ function analyzeGlob(dir, input) {
7188
+ const normalized = normalizeGlob(input);
7189
+ const warnings = [];
7190
+ let suggestion;
7191
+ if (/^\*+$/.test(normalized) || /^[^/*]+\/\*\*$/.test(normalized)) {
7192
+ warnings.push(
7193
+ "This glob is broad and may reduce Guard usefulness. Prefer narrower ownership when possible."
7194
+ );
7195
+ }
7196
+ const starIdx = normalized.indexOf("*");
7197
+ const literal = (starIdx >= 0 ? normalized.slice(0, starIdx) : normalized).replace(/\/+$/, "");
7198
+ if (literal && !exists(join(dir, literal))) {
7199
+ const slash = literal.lastIndexOf("/");
7200
+ const parent = slash >= 0 ? literal.slice(0, slash) : "";
7201
+ const target = slash >= 0 ? literal.slice(slash + 1) : literal;
7202
+ const parentFull = parent ? join(dir, parent) : dir;
7203
+ let near;
7204
+ try {
7205
+ if (exists(parentFull)) {
7206
+ near = readDir(parentFull).find(
7207
+ (e) => e !== target && levenshtein(e.toLowerCase(), target.toLowerCase()) <= 2
7208
+ );
7209
+ }
7210
+ } catch {
7211
+ }
7212
+ if (near) {
7213
+ suggestion = `${parent ? parent + "/" : ""}${near}${normalized.slice(literal.length)}`;
7214
+ warnings.push(`Path does not exist. Did you mean ${suggestion}?`);
7215
+ } else {
7216
+ warnings.push(`Path "${literal}" does not exist in the project.`);
7217
+ }
7218
+ }
7219
+ return { normalized, warnings, suggestion };
7220
+ }
6989
7221
  function toStringArray3(value) {
6990
7222
  return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
6991
7223
  }
@@ -7131,7 +7363,19 @@ async function chooseGlobs(suggested) {
7131
7363
  placeholder: "src/payments/**",
7132
7364
  validate: (v) => v.trim().length === 0 ? "A glob is required." : void 0
7133
7365
  });
7134
- chosen.push(glob.trim());
7366
+ const analysis = analyzeGlob(cwd(), glob);
7367
+ for (const w of analysis.warnings) log2.warn(w);
7368
+ let value = analysis.normalized;
7369
+ if (analysis.suggestion) {
7370
+ const useSuggested = await confirm2({
7371
+ message: `Use ${analysis.suggestion} instead?`,
7372
+ initialValue: true
7373
+ });
7374
+ if (useSuggested) value = analysis.suggestion;
7375
+ } else if (analysis.normalized !== glob.trim()) {
7376
+ log2.info(`Normalized to ${analysis.normalized}`);
7377
+ }
7378
+ chosen.push(value);
7135
7379
  addMore = await confirm2({ message: "Add another glob?", initialValue: false });
7136
7380
  }
7137
7381
  return [...new Set(chosen)];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {