@kaddo/cli 3.14.0 → 3.16.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 +707 -73
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -600,6 +600,21 @@ ${issues}`);
600
600
  }
601
601
  return parsed.data;
602
602
  }
603
+ function requireConfig(dir) {
604
+ let config;
605
+ try {
606
+ config = loadConfig(dir);
607
+ } catch (err) {
608
+ const message = err instanceof ConfigError ? err.message : String(err);
609
+ console.error(message);
610
+ process.exit(1);
611
+ }
612
+ if (!config) {
613
+ console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
614
+ process.exit(1);
615
+ }
616
+ return config;
617
+ }
603
618
  function nextStepsForState(state) {
604
619
  switch (state) {
605
620
  case "new":
@@ -624,8 +639,8 @@ function createGuidanceForState(state) {
624
639
  }
625
640
  function describeProject(config) {
626
641
  const state = config.project.state;
627
- const stateLabel4 = state === "pre-ai" ? "pre-AI" : state;
628
- return `${stateLabel4} ${config.project.structure}`;
642
+ const stateLabel3 = state === "pre-ai" ? "pre-AI" : state;
643
+ return `${stateLabel3} ${config.project.structure}`;
629
644
  }
630
645
 
631
646
  // src/core/command-help.ts
@@ -1611,6 +1626,33 @@ var RESPONSIBILITY_MATRIX = {
1611
1626
  ],
1612
1627
  next: ["kaddo scan", "kaddo owners suggest", "kaddo guard", "kaddo explain"]
1613
1628
  },
1629
+ "capsule-agent": {
1630
+ agent: "capsule-agent",
1631
+ responsibleFor: ["Refining/validating a Knowledge Capsule for external sharing"],
1632
+ produces: [".kaddo/exports/<system>.capsule.md"],
1633
+ canSuggest: ["kaddo capsule export"],
1634
+ cannotSuggest: [
1635
+ "exporting secrets",
1636
+ "exporting source code",
1637
+ "inventing contracts",
1638
+ "code",
1639
+ "git"
1640
+ ],
1641
+ next: ["kaddo capsule export"]
1642
+ },
1643
+ "ownership-agent": {
1644
+ agent: "ownership-agent",
1645
+ responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
1646
+ produces: ["proposed code: globs"],
1647
+ canSuggest: ["kaddo owners suggest", "kaddo guard"],
1648
+ cannotSuggest: [
1649
+ "code",
1650
+ "branches",
1651
+ "commits",
1652
+ "modifying files without confirmation"
1653
+ ],
1654
+ next: ["kaddo owners suggest", "kaddo guard"]
1655
+ },
1614
1656
  "guard-agent": {
1615
1657
  agent: "guard-agent",
1616
1658
  responsibleFor: ["Knowledge drift"],
@@ -1884,13 +1926,15 @@ Generated from Kaddo Context Pack.
1884
1926
 
1885
1927
  Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
1886
1928
  \`knowledge/tech/architecture-notes.md\`, and decision candidates as
1887
- \`knowledge/tech/decision-candidates.md\`.
1929
+ \`knowledge/tech/decision-candidates.md\`. Final ADRs always live under
1930
+ \`knowledge/tech/decisions/\` \u2014 never directly in \`knowledge/tech/\`.
1888
1931
 
1889
1932
  ## Quality Checklist
1890
1933
 
1891
1934
  - Every component is backed by evidence from the context pack.
1892
1935
  - Assumptions and confidence are explicit.
1893
1936
  - No final decisions are asserted \u2014 only candidates.
1937
+ - Final ADRs go to \`knowledge/tech/decisions/\`, not \`knowledge/tech/\`.
1894
1938
  - Open questions are listed.
1895
1939
  `;
1896
1940
  var ROADMAP_AGENT = `# Roadmap Agent
@@ -2209,12 +2253,16 @@ Generated from Kaddo Context Pack.
2209
2253
 
2210
2254
  ## Where to Save the Result
2211
2255
 
2212
- Save the output as \`knowledge/tech/decision-candidates.md\`.
2256
+ Save decision **candidates** as \`knowledge/tech/decision-candidates.md\`. When a candidate becomes
2257
+ a **final ADR**, it must live under \`knowledge/tech/decisions/\` (one file per decision, e.g.
2258
+ \`knowledge/tech/decisions/ADR-0001-<slug>.md\`) \u2014 **never** directly in \`knowledge/tech/\`.
2259
+ (Decision = the concept \xB7 ADR = the format \xB7 Path = \`knowledge/tech/decisions/\`.)
2213
2260
 
2214
2261
  ## Quality Checklist
2215
2262
 
2216
2263
  - Each candidate has context and alternatives.
2217
2264
  - No decision is asserted as final.
2265
+ - Final ADRs go to \`knowledge/tech/decisions/\`, never to \`knowledge/tech/\` directly.
2218
2266
  - Assumptions are marked.
2219
2267
  - Validation needs are explicit.
2220
2268
  `;
@@ -2913,6 +2961,148 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
2913
2961
  - Affected knowledge is updated.
2914
2962
  - Commit is suggested and awaits human confirmation \u2014 never run automatically.
2915
2963
  `;
2964
+ var CAPSULE_AGENT = `# Capsule Agent
2965
+
2966
+ ## Role
2967
+
2968
+ You are the Kaddo Capsule Agent. Your job is to refine and validate a **Knowledge Capsule** \u2014 a
2969
+ minimal, portable summary another project can consume as external context \u2014 before it is exported.
2970
+
2971
+ You do not write code, you never invent contracts, and you mark uncertainties. A capsule contains
2972
+ **knowledge, not code or secrets**.
2973
+
2974
+ ## When to Use
2975
+
2976
+ Use this agent before sharing a Knowledge Capsule (after \`kaddo capsule export\` produced a draft),
2977
+ to sharpen its purpose, capabilities, public contracts, risks, owners and out-of-scope.
2978
+
2979
+ ## Input Required
2980
+
2981
+ Provide \`.kaddo/context-pack.md\` plus \`knowledge/product/capabilities.md\`,
2982
+ \`knowledge/tech/current-state.md\`, \`knowledge/tech/decisions/\` and any contracts
2983
+ (\`knowledge/tech/contracts/\`) that exist. Also provide the draft capsule from
2984
+ \`.kaddo/exports/<system>.capsule.md\`.
2985
+
2986
+ ## Expected Output
2987
+
2988
+ A refined Markdown capsule intended to be saved as \`.kaddo/exports/<system>.capsule.md\`.
2989
+
2990
+ ## Instructions
2991
+
2992
+ 1. Summarize what the system does and the boundaries of this capsule.
2993
+ 2. List the **public contracts** consumers integrate with (APIs, events) \u2014 never invent them.
2994
+ 3. List exposed capabilities, dependencies and known integration risks.
2995
+ 4. Identify owners and relevant ADRs.
2996
+ 5. State what is **out of scope** for this capsule.
2997
+ 6. Mark any unknowns explicitly.
2998
+
2999
+ ## Constraints
3000
+
3001
+ - Do **not** export secrets, tokens, credentials, private keys, PII or internal sensitive URLs.
3002
+ - Do **not** export source code.
3003
+ - Do **not** invent contracts or integrations.
3004
+ - Summarize and mark boundaries; prefer "unknown" over guessing.
3005
+
3006
+ ## Output Format
3007
+
3008
+ \`\`\`markdown
3009
+ ---
3010
+ type: knowledge-capsule
3011
+ system: <system>
3012
+ version: 1
3013
+ updated_at: <YYYY-MM-DD>
3014
+ owner: <team>
3015
+ ---
3016
+
3017
+ # <System> \u2014 Knowledge Capsule
3018
+
3019
+ ## Purpose
3020
+ ## Responsibilities
3021
+ ## Exposed Capabilities
3022
+ ## Public Contracts
3023
+ ## Dependencies
3024
+ ## Known Risks
3025
+ ## Relevant ADRs
3026
+ ## Owners
3027
+ ## Out of Scope
3028
+ ## Usage Notes
3029
+ \`\`\`
3030
+
3031
+ ## Where to Save the Result
3032
+
3033
+ Save as \`.kaddo/exports/<system>.capsule.md\`. The human reviews the security checklist (no
3034
+ secrets, no source) before sharing.
3035
+
3036
+ ## Quality Checklist
3037
+
3038
+ - Purpose and boundaries are clear.
3039
+ - Public contracts are real (not invented) \u2014 unknowns are marked.
3040
+ - Capabilities, dependencies, risks, owners and out-of-scope are present.
3041
+ - No secrets, credentials, PII or source code are included.
3042
+ `;
3043
+ var OWNERSHIP_AGENT = `# Ownership Agent
3044
+
3045
+ ## Role
3046
+
3047
+ You are the Kaddo Ownership Agent. Your job is to propose **precise** \`code:\` ownership globs for
3048
+ Work Items and knowledge artifacts, so Guard can relate code changes to the right knowledge.
3049
+
3050
+ You do not write code, you do not modify files, and you never run Git. You propose; the human
3051
+ confirms and applies (with \`kaddo owners suggest\`).
3052
+
3053
+ ## When to Use
3054
+
3055
+ Use this agent after \`kaddo scan\` and \`kaddo context\`, when Work Items or artifacts are missing
3056
+ \`code:\` ownership, or when existing ownership is too broad or inaccurate.
3057
+
3058
+ ## Input Required
3059
+
3060
+ Provide \`.kaddo/context-pack.md\` as the primary input, plus the Work Items under
3061
+ \`knowledge/delivery/work-items/\`, \`knowledge/tech/codebase.md\` and \`knowledge/inventory.md\` when
3062
+ they exist (for the real source structure).
3063
+
3064
+ ## Expected Output
3065
+
3066
+ For each artifact, a precise set of \`code:\` globs.
3067
+
3068
+ ## Instructions
3069
+
3070
+ 1. Map each Work Item / artifact to the smallest set of paths that actually implement it.
3071
+ 2. Prefer **narrow** globs (e.g. \`src/payments/**\`) over broad ones (e.g. \`src/**\`).
3072
+ 3. Use real paths from the inventory/codebase \u2014 do not invent directories.
3073
+ 4. Include relevant root files (e.g. \`package.json\`, \`tsconfig.json\`) when they belong.
3074
+ 5. Flag artifacts where ownership is genuinely unclear instead of guessing broadly.
3075
+
3076
+ ## Constraints
3077
+
3078
+ - Do not implement code.
3079
+ - Do not modify files without confirmation \u2014 propose globs for the human to apply.
3080
+ - Do not create branches or commits; never run Git.
3081
+ - Prefer precision: broad globs reduce Guard usefulness.
3082
+
3083
+ ## Output Format
3084
+
3085
+ \`\`\`yaml
3086
+ # <Work Item id> \u2014 proposed ownership
3087
+ code:
3088
+ - package.json
3089
+ - tsconfig.json
3090
+ - src/cli/**
3091
+ - src/shared/**
3092
+ \`\`\`
3093
+
3094
+ ## Where to Save the Result
3095
+
3096
+ The human applies the proposed globs to the artifact's front matter with \`kaddo owners suggest\`
3097
+ (or by editing the \`code:\` field). This agent does not write files.
3098
+
3099
+ ## Quality Checklist
3100
+
3101
+ - Globs are narrow and based on real paths.
3102
+ - No \`src/**\`-style catch-alls unless truly justified.
3103
+ - Unclear ownership is flagged, not guessed.
3104
+ - Output is a proposal for human confirmation \u2014 nothing is applied automatically.
3105
+ `;
2916
3106
  var BACKLOG_AGENT = `# Backlog Agent
2917
3107
 
2918
3108
  ## Role
@@ -3024,7 +3214,11 @@ var AGENT_PROMPTS = [
3024
3214
  // Implementation (the only agent that may suggest a branch — VS-044)
3025
3215
  { fileName: "implementation-agent.md", content: IMPLEMENTATION_AGENT },
3026
3216
  // Backlog capture (idea → draft / roadmap candidate — VS-050)
3027
- { fileName: "backlog-agent.md", content: BACKLOG_AGENT }
3217
+ { fileName: "backlog-agent.md", content: BACKLOG_AGENT },
3218
+ // Ownership proposals (precise code: globs — VS-052)
3219
+ { fileName: "ownership-agent.md", content: OWNERSHIP_AGENT },
3220
+ // Knowledge Capsule refinement (external context — VS-054)
3221
+ { fileName: "capsule-agent.md", content: CAPSULE_AGENT }
3028
3222
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
3029
3223
  ].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
3030
3224
 
@@ -3039,13 +3233,15 @@ var AGENT_GROUPS = {
3039
3233
  "security-agent.md",
3040
3234
  "standards-agent.md",
3041
3235
  "module-design-agent.md",
3042
- "adr-agent.md"
3236
+ "adr-agent.md",
3237
+ "capsule-agent.md"
3043
3238
  ],
3044
3239
  delivery: [
3045
3240
  "backlog-agent.md",
3046
3241
  "roadmap-agent.md",
3047
3242
  "work-item-agent.md",
3048
3243
  "implementation-agent.md",
3244
+ "ownership-agent.md",
3049
3245
  "git-strategy-agent.md"
3050
3246
  ],
3051
3247
  utilities: ["legacy-agent.md"]
@@ -3079,7 +3275,8 @@ var RECOMMENDED_BY_STATE = {
3079
3275
  "roadmap-agent.md",
3080
3276
  "backlog-agent.md",
3081
3277
  "work-item-agent.md",
3082
- "implementation-agent.md"
3278
+ "implementation-agent.md",
3279
+ "ownership-agent.md"
3083
3280
  ],
3084
3281
  legacy: [
3085
3282
  "legacy-agent.md",
@@ -3088,7 +3285,8 @@ var RECOMMENDED_BY_STATE = {
3088
3285
  "roadmap-agent.md",
3089
3286
  "backlog-agent.md",
3090
3287
  "work-item-agent.md",
3091
- "implementation-agent.md"
3288
+ "implementation-agent.md",
3289
+ "ownership-agent.md"
3092
3290
  ]
3093
3291
  };
3094
3292
  function recommendedAgents(state) {
@@ -3757,7 +3955,7 @@ function formatList(items) {
3757
3955
  return items.map((i) => `- ${i.trim()}`).join("\n");
3758
3956
  }
3759
3957
  function buildFrontMatter(id, type, level, title, answers) {
3760
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3958
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3761
3959
  const lines = [
3762
3960
  "---",
3763
3961
  `type: ${type}`,
@@ -3769,7 +3967,7 @@ function buildFrontMatter(id, type, level, title, answers) {
3769
3967
  `initiative:`,
3770
3968
  `domains: []`,
3771
3969
  `code: []`,
3772
- `created_at: ${today}`,
3970
+ `created_at: ${today2}`,
3773
3971
  `summary: "${answers.problem?.split(".")[0] ?? title}"`,
3774
3972
  "---"
3775
3973
  ];
@@ -3839,7 +4037,7 @@ _What did we learn from this change? Update after completion._
3839
4037
  return sections.join("\n");
3840
4038
  }
3841
4039
  function buildModuleFrontMatter(id, modType, title, answers) {
3842
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4040
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3843
4041
  const extra = modType.extraFrontMatter ?? {};
3844
4042
  const extraLines = Object.entries(extra).map(
3845
4043
  ([k, v]) => `${k}: ${JSON.stringify(v)}`
@@ -3855,7 +4053,7 @@ function buildModuleFrontMatter(id, modType, title, answers) {
3855
4053
  `initiative:`,
3856
4054
  `domains: []`,
3857
4055
  `code: []`,
3858
- `created_at: ${today}`,
4056
+ `created_at: ${today2}`,
3859
4057
  `summary: "${title}"`,
3860
4058
  ...extraLines,
3861
4059
  "---"
@@ -3940,8 +4138,8 @@ async function runCreate(type, opts = {}) {
3940
4138
  answers[question.frontMatterField] = answer.trim();
3941
4139
  }
3942
4140
  const id = nextWorkItemId(dir);
3943
- const slug = slugify(title);
3944
- const fileName = `${id}-${slug}.md`;
4141
+ const slug2 = slugify(title);
4142
+ const fileName = `${id}-${slug2}.md`;
3945
4143
  const filePath = join(dir, DRAFT_DIR, fileName);
3946
4144
  const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
3947
4145
  const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
@@ -3981,8 +4179,8 @@ async function runCreateModule(dir, modType) {
3981
4179
  answers[q.frontMatterField] = answer.trim();
3982
4180
  }
3983
4181
  const id = nextWorkItemId(dir);
3984
- const slug = slugify(title);
3985
- const fileName = `${id}-${slug}.md`;
4182
+ const slug2 = slugify(title);
4183
+ const fileName = `${id}-${slug2}.md`;
3986
4184
  const filePath = join(dir, DRAFT_DIR, fileName);
3987
4185
  const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
3988
4186
  const body = buildModuleBody(modType, title.trim(), answers);
@@ -4004,7 +4202,7 @@ function resolveCandidateLevel(candidate, type) {
4004
4202
  return getLevelForType(type);
4005
4203
  }
4006
4204
  function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4007
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4205
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4008
4206
  const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
4009
4207
  const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
4010
4208
  const lines = [
@@ -4018,7 +4216,7 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4018
4216
  `initiative: "${initiative.replace(/"/g, "'")}"`,
4019
4217
  `domains: []`,
4020
4218
  `code: []`,
4021
- `created_at: ${today}`,
4219
+ `created_at: ${today2}`,
4022
4220
  `source: roadmap`,
4023
4221
  `source_id: ${candidate.id}`,
4024
4222
  `source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
@@ -4114,8 +4312,8 @@ function buildRoadmapWorkItem(opts) {
4114
4312
  const { id, type, level, candidate } = opts;
4115
4313
  const answers = opts.answers ?? {};
4116
4314
  const title = candidate.title.trim();
4117
- const slug = slugify(title);
4118
- const fileName = `${id}-${slug}.md`;
4315
+ const slug2 = slugify(title);
4316
+ const fileName = `${id}-${slug2}.md`;
4119
4317
  const qualityGate = getLevel(level).qualityGate;
4120
4318
  const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
4121
4319
  const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
@@ -4234,6 +4432,14 @@ async function isGitRepo(dir) {
4234
4432
  return false;
4235
4433
  }
4236
4434
  }
4435
+ async function getUntrackedFiles() {
4436
+ try {
4437
+ const result = await execa("git", ["ls-files", "--others", "--exclude-standard"]);
4438
+ return result.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
4439
+ } catch {
4440
+ return [];
4441
+ }
4442
+ }
4237
4443
 
4238
4444
  // src/services/artifact-reader.ts
4239
4445
  import matter from "gray-matter";
@@ -4254,7 +4460,8 @@ function parseArtifact(filePath, raw) {
4254
4460
  status: String(data.status ?? ""),
4255
4461
  phase: String(data.phase ?? ""),
4256
4462
  initiative: String(data.initiative ?? data.source_initiative ?? ""),
4257
- source: data.source ? String(data.source) : ""
4463
+ source: data.source ? String(data.source) : "",
4464
+ sourceId: String(data.source_id ?? "")
4258
4465
  };
4259
4466
  } catch {
4260
4467
  return null;
@@ -4892,6 +5099,15 @@ async function runGuard(opts = {}) {
4892
5099
  const plugins = resolvePlugins(config.plugins ?? []);
4893
5100
  const mode = opts.staged ? "staged" : "head";
4894
5101
  const currentFiles = await getModifiedFiles(mode);
5102
+ const untrackedFiles = opts.staged ? [] : await getUntrackedFiles();
5103
+ const printUntracked = () => {
5104
+ if (untrackedFiles.length === 0 || jsonMode) return;
5105
+ console.log("");
5106
+ console.log(`Untracked files detected (${untrackedFiles.length}):`);
5107
+ for (const f of untrackedFiles.slice(0, 10)) console.log(` - ${f}`);
5108
+ if (untrackedFiles.length > 10) console.log(` \u2026and ${untrackedFiles.length - 10} more`);
5109
+ console.log("Guard may not fully evaluate these files until they are tracked. (FYI \u2014 non-blocking)");
5110
+ };
4895
5111
  let workspaceScan = null;
4896
5112
  if (opts.workspace) {
4897
5113
  workspaceScan = await collectWorkspaceChanges(dir, mode);
@@ -4901,6 +5117,7 @@ async function runGuard(opts = {}) {
4901
5117
  if (touchedFiles.length === 0) {
4902
5118
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
4903
5119
  console.log("kaddo guard: no modified files detected.");
5120
+ printUntracked();
4904
5121
  return;
4905
5122
  }
4906
5123
  const archDir = join(dir, ARCH_DIR3);
@@ -4940,10 +5157,12 @@ async function runGuard(opts = {}) {
4940
5157
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
4941
5158
  printHeader(touchedFiles);
4942
5159
  console.log(" No artifact ownership matches found.");
5160
+ printUntracked();
4943
5161
  return;
4944
5162
  }
4945
5163
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
4946
5164
  printHeader(touchedFiles);
5165
+ printUntracked();
4947
5166
  for (const match of activeMatches) {
4948
5167
  printFYI(match, dir);
4949
5168
  }
@@ -5017,8 +5236,8 @@ function runIgnoreRemove(artifactId) {
5017
5236
  }
5018
5237
 
5019
5238
  // src/commands/explain.ts
5020
- import matter2 from "gray-matter";
5021
- import { parse as parseYaml8 } from "yaml";
5239
+ import matter3 from "gray-matter";
5240
+ import { parse as parseYaml9 } from "yaml";
5022
5241
 
5023
5242
  // src/core/delivery-phase.ts
5024
5243
  function layer(layers, name) {
@@ -5069,22 +5288,30 @@ function assessPhase(input) {
5069
5288
  const firstOf = (s) => items.find((i) => i.lifecycle === s);
5070
5289
  let recommendedAgents2 = [];
5071
5290
  let nextStep = "";
5291
+ let llmInstructions = [];
5072
5292
  switch (phase) {
5073
5293
  case "Discovery": {
5074
5294
  const m = firstMissingLayerAgent(input.layers);
5075
5295
  recommendedAgents2 = [m.agent];
5076
5296
  nextStep = m.step;
5297
+ llmInstructions = [`Use the ${m.agent} to fill the missing base knowledge.`, "Do not write code."];
5077
5298
  break;
5078
5299
  }
5079
5300
  case "Planning": {
5080
5301
  recommendedAgents2 = ["roadmap-agent"];
5081
5302
  nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
5303
+ llmInstructions = ["Use the roadmap-agent.", "Do not write code.", "Generate roadmap candidates."];
5082
5304
  break;
5083
5305
  }
5084
5306
  case "Delivery Preparation": {
5085
5307
  recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
5086
5308
  const n = input.roadmap.remaining || input.roadmap.candidates;
5087
5309
  nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
5310
+ llmInstructions = [
5311
+ "Materialize roadmap candidates with `kaddo create --from roadmap`.",
5312
+ "Use the work-item-agent to refine them.",
5313
+ "Do not implement yet."
5314
+ ];
5088
5315
  break;
5089
5316
  }
5090
5317
  case "Active Delivery": {
@@ -5092,21 +5319,41 @@ function assessPhase(input) {
5092
5319
  const wi = firstOf("ready");
5093
5320
  recommendedAgents2 = ["implementation-agent"];
5094
5321
  nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
5322
+ llmInstructions = [
5323
+ "Use the implementation-agent.",
5324
+ "Suggest a branch name only.",
5325
+ "Do not run git commands."
5326
+ ];
5095
5327
  } else if (bs["in-progress"] > 0) {
5096
5328
  const wi = firstOf("in-progress");
5097
5329
  recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
5098
5330
  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";
5331
+ llmInstructions = [
5332
+ "Continue the in-progress Work Item with the implementation-agent.",
5333
+ "After changes, run `kaddo scan`, `kaddo owners suggest` and `kaddo guard`.",
5334
+ "Do not commit, push or merge without explicit human confirmation."
5335
+ ];
5099
5336
  } else if (bs.draft > 0) {
5100
5337
  const wi = firstOf("draft");
5101
5338
  recommendedAgents2 = ["work-item-agent"];
5102
5339
  nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
5340
+ llmInstructions = [
5341
+ "Refine draft Work Items to ready.",
5342
+ "Use the work-item-agent.",
5343
+ "Do not implement unless the user explicitly asks."
5344
+ ];
5103
5345
  } else if (bs.blocked > 0) {
5104
5346
  const wi = firstOf("blocked");
5105
5347
  recommendedAgents2 = ["work-item-agent"];
5106
5348
  nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
5349
+ llmInstructions = [
5350
+ "Resolve the blockers with the work-item-agent.",
5351
+ "Do not implement blocked work."
5352
+ ];
5107
5353
  }
5108
5354
  if (input.ownership.workItemsMissingOwnership > 0) {
5109
5355
  recommendedAgents2.push("kaddo owners suggest");
5356
+ llmInstructions.push("Ownership is incomplete \u2014 propose `code:` globs (run `kaddo owners suggest`).");
5110
5357
  }
5111
5358
  break;
5112
5359
  }
@@ -5114,18 +5361,213 @@ function assessPhase(input) {
5114
5361
  if (input.roadmap.remaining > 0) {
5115
5362
  recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
5116
5363
  nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
5364
+ llmInstructions = ["Materialize the remaining roadmap candidates.", "Do not implement yet."];
5117
5365
  } else {
5118
5366
  recommendedAgents2 = ["roadmap-agent"];
5119
5367
  nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
5368
+ llmInstructions = ["No active work.", "Use the roadmap-agent to plan the next initiative."];
5120
5369
  }
5121
5370
  break;
5122
5371
  }
5123
5372
  }
5124
- return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
5373
+ return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
5125
5374
  }
5126
5375
 
5127
- // src/core/knowledge-discovery.ts
5376
+ // src/core/capsule.ts
5377
+ import matter2 from "gray-matter";
5378
+ import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
5128
5379
  var KNOWLEDGE = "knowledge";
5380
+ function today() {
5381
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5382
+ }
5383
+ function firstParagraph(md) {
5384
+ const body = md.replace(/^---\n[\s\S]*?\n---\n/, "");
5385
+ for (const block of body.split(/\n\s*\n/)) {
5386
+ const t = block.trim();
5387
+ if (t && !t.startsWith("#") && !t.startsWith(">")) return t.replace(/\s+/g, " ");
5388
+ }
5389
+ return "";
5390
+ }
5391
+ function readIf(dir, rel) {
5392
+ const p2 = join(dir, rel);
5393
+ return exists(p2) ? readFile(p2) : null;
5394
+ }
5395
+ function headings(md) {
5396
+ return md.split(/\r?\n/).map((l) => l.match(/^#{2,3}\s+(.+?)\s*$/)).filter((m) => Boolean(m)).map((m) => m[1].trim()).filter((h) => !/^(summary|resumen|overview|risks|owners|out of scope)$/i.test(h));
5397
+ }
5398
+ function buildCapsule(dir, config) {
5399
+ const ownerMap = loadOwners(dir);
5400
+ const owners = [...new Set(Object.values(ownerMap).flat())];
5401
+ const all = discoverKnowledge(dir);
5402
+ const purposeSrc = readIf(dir, `${KNOWLEDGE}/business/business.md`) ?? readIf(dir, `${KNOWLEDGE}/knowledge.md`) ?? "";
5403
+ const capsMd = readIf(dir, `${KNOWLEDGE}/product/capabilities.md`);
5404
+ const risksMd = readIf(dir, `${KNOWLEDGE}/legacy/risks.md`);
5405
+ const adrs = all.filter((a) => a.filePath.replace(/\\/g, "/").includes("/tech/decisions/") && Boolean(a.type)).map((a) => a.title || a.id).filter(Boolean);
5406
+ return {
5407
+ system: config.project.name,
5408
+ version: 1,
5409
+ updatedAt: today(),
5410
+ owner: owners[0] ?? "unknown",
5411
+ sourceProject: config.project.name,
5412
+ purpose: firstParagraph(purposeSrc),
5413
+ responsibilities: [],
5414
+ capabilities: capsMd ? headings(capsMd) : [],
5415
+ contracts: [],
5416
+ dependencies: [],
5417
+ knownRisks: risksMd ? headings(risksMd) : [],
5418
+ adrs,
5419
+ owners,
5420
+ outOfScope: [],
5421
+ usageNotes: []
5422
+ };
5423
+ }
5424
+ function section2(title, items, placeholder) {
5425
+ if (items.length === 0) return `## ${title}
5426
+
5427
+ _${placeholder}_
5428
+ `;
5429
+ return `## ${title}
5430
+
5431
+ ${items.map((i) => `- ${i}`).join("\n")}
5432
+ `;
5433
+ }
5434
+ function renderCapsuleMarkdown(c) {
5435
+ const fm = [
5436
+ "---",
5437
+ "type: knowledge-capsule",
5438
+ `system: ${c.system}`,
5439
+ `version: ${c.version}`,
5440
+ `updated_at: ${c.updatedAt}`,
5441
+ `owner: ${c.owner}`,
5442
+ `source_project: ${c.sourceProject}`,
5443
+ ...c.sourceCommit ? [`source_commit: ${c.sourceCommit}`] : [],
5444
+ "---"
5445
+ ].join("\n");
5446
+ const parts = [
5447
+ fm,
5448
+ "",
5449
+ `# ${c.system} \u2014 Knowledge Capsule`,
5450
+ "",
5451
+ `## Purpose
5452
+
5453
+ ${c.purpose || "_To be completed by the capsule-agent._"}
5454
+ `,
5455
+ section2("Responsibilities", c.responsibilities, "List what this system is responsible for."),
5456
+ section2("Exposed Capabilities", c.capabilities, "List the capabilities this system exposes."),
5457
+ section2("Public Contracts", c.contracts, "List public APIs and events (e.g. `POST /orders`, `OrderCreated`)."),
5458
+ section2("Dependencies", c.dependencies, "List systems this one depends on."),
5459
+ section2("Known Risks", c.knownRisks, "List integration risks consumers should know."),
5460
+ section2("Relevant ADRs", c.adrs, "List decisions that affect how to integrate."),
5461
+ section2("Owners", c.owners, "Who owns this system."),
5462
+ section2("Out of Scope", c.outOfScope, "What this capsule deliberately does not cover."),
5463
+ section2("Usage Notes", c.usageNotes, "How consumers should integrate."),
5464
+ "> Security: this capsule must never contain secrets, tokens, credentials, PII or source code.",
5465
+ ""
5466
+ ];
5467
+ return parts.join("\n");
5468
+ }
5469
+ function serializeCapsuleJson(c) {
5470
+ return JSON.stringify({ type: "knowledge-capsule", ...c }, null, 2) + "\n";
5471
+ }
5472
+ var EXTERNAL_PATH = ".kaddo/external.yml";
5473
+ function loadExternalRegistry(dir) {
5474
+ const p2 = join(dir, EXTERNAL_PATH);
5475
+ if (!exists(p2)) return [];
5476
+ try {
5477
+ const parsed = parseYaml8(readFile(p2));
5478
+ return Array.isArray(parsed?.external) ? parsed.external : [];
5479
+ } catch {
5480
+ return [];
5481
+ }
5482
+ }
5483
+ function serializeExternalRegistry(entries) {
5484
+ return stringifyYaml3({ external: entries });
5485
+ }
5486
+ function sectionList(md, title) {
5487
+ const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
5488
+ const lines = md.split(/\r?\n/);
5489
+ const start = lines.findIndex((l) => re.test(l));
5490
+ if (start < 0) return [];
5491
+ const out = [];
5492
+ for (let i = start + 1; i < lines.length; i++) {
5493
+ if (/^##\s+/.test(lines[i])) break;
5494
+ const m = lines[i].match(/^\s*-\s+(.+?)\s*$/);
5495
+ if (m && !/^_.*_$/.test(m[1])) out.push(m[1].trim());
5496
+ }
5497
+ return out;
5498
+ }
5499
+ function sectionParagraph(md, title) {
5500
+ const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
5501
+ const lines = md.split(/\r?\n/);
5502
+ const start = lines.findIndex((l) => re.test(l));
5503
+ if (start < 0) return "";
5504
+ for (let i = start + 1; i < lines.length; i++) {
5505
+ if (/^##\s+/.test(lines[i])) break;
5506
+ const t = lines[i].trim();
5507
+ if (t && !/^_.*_$/.test(t)) return t;
5508
+ }
5509
+ return "";
5510
+ }
5511
+ function parseCapsule(id, path5, md) {
5512
+ const { data } = matter2(md);
5513
+ const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
5514
+ let ageDays = null;
5515
+ if (updatedAt) {
5516
+ const d = Date.parse(updatedAt);
5517
+ if (!Number.isNaN(d)) ageDays = Math.max(0, Math.floor((Date.now() - d) / 864e5));
5518
+ }
5519
+ return {
5520
+ id,
5521
+ path: path5,
5522
+ system: data.system ? String(data.system) : id,
5523
+ owner: data.owner ? String(data.owner) : void 0,
5524
+ updatedAt,
5525
+ purpose: sectionParagraph(md, "Purpose"),
5526
+ capabilities: sectionList(md, "Exposed Capabilities"),
5527
+ contracts: sectionList(md, "Public Contracts"),
5528
+ knownRisks: sectionList(md, "Known Risks"),
5529
+ ageDays
5530
+ };
5531
+ }
5532
+ function slugId(s) {
5533
+ return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "external";
5534
+ }
5535
+ function addExternalCapsule(dir, sourceFile) {
5536
+ if (!exists(sourceFile)) throw new Error(`Capsule not found: ${sourceFile}`);
5537
+ const raw = readFile(sourceFile);
5538
+ const { data } = matter2(raw);
5539
+ const isCapsuleType = data.type === "knowledge-capsule";
5540
+ const fallbackName = sourceFile.split(/[\\/]/).pop()?.replace(/\.capsule\.md$/, "") ?? "external";
5541
+ const id = slugId(String(data.system ?? fallbackName));
5542
+ const destRel = join("external", `${id}.capsule.md`).replace(/\\/g, "/");
5543
+ writeFile(join(dir, destRel), raw);
5544
+ const entry = {
5545
+ id,
5546
+ type: "knowledge-capsule",
5547
+ path: destRel,
5548
+ owner: data.owner ? String(data.owner) : void 0,
5549
+ lastImportedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
5550
+ };
5551
+ const registry = loadExternalRegistry(dir).filter((e) => e.id !== id);
5552
+ registry.push(entry);
5553
+ writeFile(join(dir, EXTERNAL_PATH), serializeExternalRegistry(registry));
5554
+ return { id, destRel, entry, isCapsuleType };
5555
+ }
5556
+ function loadExternalCapsules(dir) {
5557
+ const out = [];
5558
+ for (const entry of loadExternalRegistry(dir)) {
5559
+ const full = join(dir, entry.path);
5560
+ if (!exists(full) || !isFile(full)) continue;
5561
+ try {
5562
+ out.push(parseCapsule(entry.id, entry.path, readFile(full)));
5563
+ } catch {
5564
+ }
5565
+ }
5566
+ return out;
5567
+ }
5568
+
5569
+ // src/core/knowledge-discovery.ts
5570
+ var KNOWLEDGE2 = "knowledge";
5129
5571
  var CONSOLIDATED_TYPE = {
5130
5572
  Business: "business",
5131
5573
  Product: "product",
@@ -5167,10 +5609,10 @@ function layerForType(type) {
5167
5609
  }
5168
5610
  function layerFromPath(filePath) {
5169
5611
  const p2 = filePath.replace(/\\/g, "/");
5170
- if (p2.includes(`/${KNOWLEDGE}/business/`)) return "Business";
5171
- if (p2.includes(`/${KNOWLEDGE}/product/`)) return "Product";
5172
- if (p2.includes(`/${KNOWLEDGE}/tech/`)) return "Tech";
5173
- if (p2.includes(`/${KNOWLEDGE}/delivery/`)) return "Delivery";
5612
+ if (p2.includes(`/${KNOWLEDGE2}/business/`)) return "Business";
5613
+ if (p2.includes(`/${KNOWLEDGE2}/product/`)) return "Product";
5614
+ if (p2.includes(`/${KNOWLEDGE2}/tech/`)) return "Tech";
5615
+ if (p2.includes(`/${KNOWLEDGE2}/delivery/`)) return "Delivery";
5174
5616
  return null;
5175
5617
  }
5176
5618
  function basename(p2) {
@@ -5183,7 +5625,7 @@ function discoverLayers(dir) {
5183
5625
  Tech: blank(),
5184
5626
  Delivery: blank()
5185
5627
  };
5186
- const archDir = join(dir, KNOWLEDGE);
5628
+ const archDir = join(dir, KNOWLEDGE2);
5187
5629
  const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
5188
5630
  for (const a of artifacts) {
5189
5631
  const type = a.type;
@@ -5205,7 +5647,7 @@ function discoverLayers(dir) {
5205
5647
  }
5206
5648
  if (type === "adr" || type === "decision") slot.hasDecision = true;
5207
5649
  }
5208
- if (existsDirWithMd(join(dir, KNOWLEDGE, "tech", "decisions"))) acc.Tech.structured = true;
5650
+ if (existsDirWithMd(join(dir, KNOWLEDGE2, "tech", "decisions"))) acc.Tech.structured = true;
5209
5651
  return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
5210
5652
  layer: layer2,
5211
5653
  status: statusFor(layer2, acc[layer2]),
@@ -5258,6 +5700,30 @@ function renderLayersMarkdown(layers) {
5258
5700
 
5259
5701
  // src/core/project-explain.ts
5260
5702
  var ARCH_DIR4 = "knowledge";
5703
+ function normalizeTitle(t) {
5704
+ return t.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, " ").trim();
5705
+ }
5706
+ function findDuplicateWorkItems(items) {
5707
+ const groups = [];
5708
+ const seen = /* @__PURE__ */ new Set();
5709
+ const bucket = (key, reason, pred) => {
5710
+ const matched = items.filter(pred);
5711
+ if (matched.length > 1) {
5712
+ const id = `${reason}:${key}:${matched.map((m) => m.id).sort().join(",")}`;
5713
+ if (!seen.has(id)) {
5714
+ seen.add(id);
5715
+ groups.push({ reason, items: matched.map((m) => ({ id: m.id, title: m.title })) });
5716
+ }
5717
+ }
5718
+ };
5719
+ for (const sid of new Set(items.map((i) => i.sourceId).filter(Boolean))) {
5720
+ bucket(sid, `same source candidate (${sid})`, (i) => i.sourceId === sid);
5721
+ }
5722
+ for (const nt of new Set(items.map((i) => normalizeTitle(i.title)).filter(Boolean))) {
5723
+ bucket(nt, "same normalized title", (i) => normalizeTitle(i.title) === nt);
5724
+ }
5725
+ return groups;
5726
+ }
5261
5727
  function first(values) {
5262
5728
  if (Array.isArray(values)) {
5263
5729
  const v = values.find((x) => typeof x === "string" && x);
@@ -5376,6 +5842,9 @@ function buildProjectExplanation(dir) {
5376
5842
  workItemsMissingOwnership: items.length - withOwnership
5377
5843
  };
5378
5844
  const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
5845
+ const duplicateWorkItems = findDuplicateWorkItems(
5846
+ workItemArtifacts.map((a) => ({ id: a.id || a.title, title: a.title, sourceId: a.sourceId }))
5847
+ );
5379
5848
  const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
5380
5849
  const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
5381
5850
  const roadmap = roadmapStats(roadmapMd, items.length);
@@ -5425,6 +5894,8 @@ function buildProjectExplanation(dir) {
5425
5894
  workItems,
5426
5895
  ownership,
5427
5896
  domains,
5897
+ duplicateWorkItems,
5898
+ externalCapsules: loadExternalCapsules(dir),
5428
5899
  layers,
5429
5900
  roadmap,
5430
5901
  mappedModules,
@@ -5553,11 +6024,31 @@ function renderExplanationHuman(exp) {
5553
6024
  lines.push("- Mapped modules: 0");
5554
6025
  lines.push("");
5555
6026
  }
6027
+ if (exp.externalCapsules.length > 0) {
6028
+ lines.push(`## External Knowledge Capsules: ${exp.externalCapsules.length}`);
6029
+ for (const cap of exp.externalCapsules) {
6030
+ const owner = cap.owner ? ` \u2014 owner: ${cap.owner}` : "";
6031
+ lines.push(`- ${cap.system}${owner}`);
6032
+ if (cap.ageDays !== null && cap.ageDays >= 90) {
6033
+ lines.push(` \u26A0 capsule last updated ${cap.ageDays} days ago \u2014 it may be stale.`);
6034
+ }
6035
+ }
6036
+ lines.push("");
6037
+ }
5556
6038
  if (exp.missingKnowledge.length > 0) {
5557
6039
  lines.push("## Missing Knowledge");
5558
6040
  for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
5559
6041
  lines.push("");
5560
6042
  }
6043
+ if (exp.duplicateWorkItems.length > 0) {
6044
+ lines.push("## Possible Duplicate Work Items");
6045
+ for (const g of exp.duplicateWorkItems) {
6046
+ lines.push(`- ${g.reason}:`);
6047
+ for (const i of g.items) lines.push(` - ${i.id} \u2014 ${i.title}`);
6048
+ }
6049
+ lines.push("Review before continuing (non-blocking).");
6050
+ lines.push("");
6051
+ }
5561
6052
  const assessment = assessPhase(exp);
5562
6053
  lines.push("## Phase");
5563
6054
  lines.push(`- Phase: ${assessment.phase}`);
@@ -5587,7 +6078,7 @@ function readKnowledge(dir) {
5587
6078
  if (!exists(knowledgePath)) return null;
5588
6079
  try {
5589
6080
  const raw = readFile(knowledgePath);
5590
- const { data, content } = matter2(raw);
6081
+ const { data, content } = matter3(raw);
5591
6082
  return { content, data };
5592
6083
  } catch {
5593
6084
  return null;
@@ -5598,7 +6089,7 @@ function readRoadmap(dir) {
5598
6089
  if (!exists(roadmapPath)) return null;
5599
6090
  try {
5600
6091
  const raw = readFile(roadmapPath);
5601
- const { content } = matter2(raw);
6092
+ const { content } = matter3(raw);
5602
6093
  return content.trim();
5603
6094
  } catch {
5604
6095
  return null;
@@ -5608,7 +6099,7 @@ function readConfig(dir) {
5608
6099
  const configPath = join(dir, CONFIG_PATH3);
5609
6100
  if (!exists(configPath)) return {};
5610
6101
  try {
5611
- return parseYaml8(readFile(configPath));
6102
+ return parseYaml9(readFile(configPath));
5612
6103
  } catch {
5613
6104
  return {};
5614
6105
  }
@@ -5694,8 +6185,8 @@ function explainForAgent(dir, artifacts, opts) {
5694
6185
  since: opts.since ?? null
5695
6186
  };
5696
6187
  if (knowledge) {
5697
- const firstParagraph2 = knowledge.content.trim().split("\n\n").find((p2) => p2.trim() && !p2.startsWith("#"));
5698
- output.knowledge_summary = firstParagraph2?.trim() ?? "";
6188
+ const firstParagraph3 = knowledge.content.trim().split("\n\n").find((p2) => p2.trim() && !p2.startsWith("#"));
6189
+ output.knowledge_summary = firstParagraph3?.trim() ?? "";
5699
6190
  }
5700
6191
  const mappedArtifacts = artifacts.filter((a) => a.type !== "current-state" && a.type !== "roadmap").map((a) => ({
5701
6192
  id: a.id,
@@ -5767,7 +6258,7 @@ function runExplain(opts) {
5767
6258
  }
5768
6259
 
5769
6260
  // src/core/context-pack.ts
5770
- import matter3 from "gray-matter";
6261
+ import matter4 from "gray-matter";
5771
6262
  var CONTEXT_PACK_VERSION = "1";
5772
6263
  var ARCH_DIR6 = "knowledge";
5773
6264
  function readScanJson(dir) {
@@ -5779,8 +6270,8 @@ function readScanJson(dir) {
5779
6270
  return null;
5780
6271
  }
5781
6272
  }
5782
- function firstParagraph(markdown) {
5783
- const body = matter3(markdown).content.trim();
6273
+ function firstParagraph2(markdown) {
6274
+ const body = matter4(markdown).content.trim();
5784
6275
  const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
5785
6276
  return para ?? "";
5786
6277
  }
@@ -5788,7 +6279,7 @@ function readMarkdownSummary(dir, file) {
5788
6279
  const p2 = join(dir, ARCH_DIR6, file);
5789
6280
  if (!exists(p2)) return null;
5790
6281
  try {
5791
- return firstParagraph(readFile(p2));
6282
+ return firstParagraph2(readFile(p2));
5792
6283
  } catch {
5793
6284
  return null;
5794
6285
  }
@@ -5950,12 +6441,15 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
5950
6441
  roadmap,
5951
6442
  phase,
5952
6443
  deliveryMix,
6444
+ external: loadExternalCapsules(dir),
5953
6445
  mappedModules,
5954
6446
  missing,
6447
+ // VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
6448
+ // contradicts the Current Phase block above it.
5955
6449
  handoff: {
5956
- recommendedAgents: recommendedAgentsForState(state),
5957
- nextSteps: nextStepsForState2(state),
5958
- instructions: LLM_INSTRUCTIONS,
6450
+ recommendedAgents: phase.recommendedAgents.length > 0 ? phase.recommendedAgents : recommendedAgentsForState(state),
6451
+ nextSteps: phase.nextStep ? [phase.nextStep] : nextStepsForState2(state),
6452
+ instructions: phase.llmInstructions.length > 0 ? phase.llmInstructions : LLM_INSTRUCTIONS,
5959
6453
  operatingRules: OPERATING_RULES
5960
6454
  }
5961
6455
  };
@@ -5965,9 +6459,6 @@ function serializeContextPackJson(pack) {
5965
6459
  }
5966
6460
 
5967
6461
  // src/templates/context-pack-template.ts
5968
- function stateLabel2(state) {
5969
- return state === "pre-ai" ? "pre-AI" : state;
5970
- }
5971
6462
  function renderContextPack(pack) {
5972
6463
  const { project, scan: scan2, knowledge, missing, handoff } = pack;
5973
6464
  const parts = [];
@@ -6101,6 +6592,21 @@ function renderContextPack(pack) {
6101
6592
  "Note: Kaddo does not scan secondary repositories during `context`. Mapped modules come from `.kaddo/modules.yml` and module artifacts only.\n"
6102
6593
  );
6103
6594
  }
6595
+ if (pack.external.length > 0) {
6596
+ parts.push("## External Knowledge\n");
6597
+ parts.push(
6598
+ "Imported Knowledge Capsules \u2014 minimal context about external systems (not their source).\n"
6599
+ );
6600
+ for (const cap of pack.external) {
6601
+ const lines = [`### ${cap.system}`, ""];
6602
+ if (cap.purpose) lines.push(`- Purpose: ${cap.purpose}`);
6603
+ if (cap.capabilities.length) lines.push(`- Capabilities: ${cap.capabilities.join(", ")}`);
6604
+ if (cap.contracts.length) lines.push(`- Contracts: ${cap.contracts.join(", ")}`);
6605
+ if (cap.owner) lines.push(`- Owner: ${cap.owner}`);
6606
+ if (cap.knownRisks.length) lines.push(`- Known risks: ${cap.knownRisks.join("; ")}`);
6607
+ parts.push(lines.join("\n") + "\n");
6608
+ }
6609
+ }
6104
6610
  parts.push("## Missing Context\n");
6105
6611
  if (missing.length > 0) {
6106
6612
  parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
@@ -6108,11 +6614,11 @@ function renderContextPack(pack) {
6108
6614
  parts.push("_None \u2014 all expected context is present._\n");
6109
6615
  }
6110
6616
  parts.push("## Recommended Agent Handoff\n");
6111
- parts.push(`Recommended agents for a ${stateLabel2(project.state)} project:
6617
+ parts.push(`Recommended next for the **${pack.phase.phase}** phase:
6112
6618
  `);
6113
6619
  parts.push(handoff.recommendedAgents.map((a, i) => `${i + 1}. ${a}`).join("\n") + "\n");
6114
6620
  if (handoff.nextSteps.length > 0) {
6115
- parts.push("Next steps:\n");
6621
+ parts.push("Next step:\n");
6116
6622
  parts.push(handoff.nextSteps.map((s) => `- ${s}`).join("\n") + "\n");
6117
6623
  }
6118
6624
  parts.push("## Instructions for the LLM\n");
@@ -6211,7 +6717,7 @@ function buildUnderstandPlan(dir, config) {
6211
6717
  }
6212
6718
 
6213
6719
  // src/templates/understand-template.ts
6214
- function stateLabel3(state) {
6720
+ function stateLabel2(state) {
6215
6721
  return state === "pre-ai" ? "pre-AI" : state;
6216
6722
  }
6217
6723
  function agentName(file) {
@@ -6234,7 +6740,7 @@ function renderUnderstand(plan) {
6234
6740
  ].join("\n") + "\n"
6235
6741
  );
6236
6742
  parts.push("## Recommended Agent Flow\n");
6237
- parts.push(`Recommended order for a ${stateLabel3(project.state)} project:
6743
+ parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
6238
6744
  `);
6239
6745
  parts.push(
6240
6746
  steps.map((s, i) => {
@@ -6316,7 +6822,7 @@ function renderUnderstandTerminal(plan) {
6316
6822
  }
6317
6823
 
6318
6824
  // src/core/delivery.ts
6319
- import { parse as parseYaml9 } from "yaml";
6825
+ import { parse as parseYaml10 } from "yaml";
6320
6826
  function slugify2(s) {
6321
6827
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6322
6828
  }
@@ -6422,6 +6928,14 @@ function runUnderstand() {
6422
6928
  if (assessment.nextStep) {
6423
6929
  console.log(`Next step: ${assessment.nextStep}`);
6424
6930
  }
6931
+ if (exp.externalCapsules.length > 0) {
6932
+ console.log("");
6933
+ console.log("External knowledge:");
6934
+ for (const cap of exp.externalCapsules) {
6935
+ console.log(` - ${cap.system}${cap.owner ? ` (owner: ${cap.owner})` : ""}`);
6936
+ }
6937
+ console.log(" \u2192 Review the relevant capsule before changing integration behavior with it.");
6938
+ }
6425
6939
  const active = activeWorkItems(dir);
6426
6940
  if (active.length > 0) {
6427
6941
  console.log("");
@@ -6655,14 +7169,14 @@ async function runClassify(opts = {}) {
6655
7169
  }
6656
7170
 
6657
7171
  // src/commands/status.ts
6658
- import { parse as parseYaml10 } from "yaml";
7172
+ import { parse as parseYaml11 } from "yaml";
6659
7173
  var ARCH_DIR8 = "knowledge";
6660
7174
  var CONFIG_PATH4 = ".kaddo/config.yml";
6661
7175
  function loadConfig3(dir) {
6662
7176
  const p2 = join(dir, CONFIG_PATH4);
6663
7177
  if (!exists(p2)) return {};
6664
7178
  try {
6665
- return parseYaml10(readFile(p2));
7179
+ return parseYaml11(readFile(p2));
6666
7180
  } catch {
6667
7181
  return {};
6668
7182
  }
@@ -6719,7 +7233,7 @@ function runStatus() {
6719
7233
  }
6720
7234
 
6721
7235
  // src/commands/learn.ts
6722
- import matter4 from "gray-matter";
7236
+ import matter5 from "gray-matter";
6723
7237
  var ARCH_DIR9 = "knowledge";
6724
7238
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
6725
7239
  function findWorkItemFile(dir, id) {
@@ -6731,7 +7245,7 @@ function findWorkItemFile(dir, id) {
6731
7245
  }
6732
7246
  function updateWorkItemFile(filePath, learning) {
6733
7247
  const raw = readFile(filePath);
6734
- const { data, content } = matter4(raw);
7248
+ const { data, content } = matter5(raw);
6735
7249
  data.status = "done";
6736
7250
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6737
7251
  let updatedContent = content;
@@ -6756,7 +7270,7 @@ ${learning.trim()}
6756
7270
  ${learning.trim()}
6757
7271
  `;
6758
7272
  }
6759
- const newRaw = matter4.stringify(updatedContent, data);
7273
+ const newRaw = matter5.stringify(updatedContent, data);
6760
7274
  writeFile(filePath, newRaw);
6761
7275
  }
6762
7276
  async function runLearn(artifactId) {
@@ -6864,13 +7378,13 @@ function runHistory(opts = {}) {
6864
7378
  }
6865
7379
 
6866
7380
  // src/commands/add.ts
6867
- import { parse as parseYaml11, stringify as stringifyYaml3 } from "yaml";
7381
+ import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
6868
7382
  var CONFIG_PATH5 = ".kaddo/config.yml";
6869
7383
  function readProjectState(dir) {
6870
7384
  const configPath = join(dir, CONFIG_PATH5);
6871
7385
  if (!exists(configPath)) return void 0;
6872
7386
  try {
6873
- const config = parseYaml11(readFile(configPath));
7387
+ const config = parseYaml12(readFile(configPath));
6874
7388
  return config.project?.state;
6875
7389
  } catch {
6876
7390
  return void 0;
@@ -6888,12 +7402,12 @@ function markModuleInstalled(dir, configKey, moduleName) {
6888
7402
  const configPath = join(dir, CONFIG_PATH5);
6889
7403
  if (!exists(configPath)) return;
6890
7404
  try {
6891
- const config = parseYaml11(readFile(configPath));
7405
+ const config = parseYaml12(readFile(configPath));
6892
7406
  config[configKey] = { installed: true, installed_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
6893
7407
  const modules = config.modules ?? [];
6894
7408
  if (!modules.includes(moduleName)) modules.push(moduleName);
6895
7409
  config.modules = modules;
6896
- writeFile(configPath, stringifyYaml3(config));
7410
+ writeFile(configPath, stringifyYaml4(config));
6897
7411
  } catch {
6898
7412
  }
6899
7413
  }
@@ -6901,7 +7415,7 @@ function isModuleInstalled(dir, configKey) {
6901
7415
  const configPath = join(dir, CONFIG_PATH5);
6902
7416
  if (!exists(configPath)) return false;
6903
7417
  try {
6904
- const config = parseYaml11(readFile(configPath));
7418
+ const config = parseYaml12(readFile(configPath));
6905
7419
  const moduleConfig = config[configKey];
6906
7420
  return moduleConfig?.installed === true;
6907
7421
  } catch {
@@ -6984,8 +7498,66 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
6984
7498
  }
6985
7499
 
6986
7500
  // src/core/ownership-suggest.ts
6987
- import matter5 from "gray-matter";
7501
+ import matter6 from "gray-matter";
6988
7502
  var SCAN_PATH = ".kaddo/scan.json";
7503
+ function normalizeGlob(input) {
7504
+ let g = input.trim().replace(/\\/g, "/");
7505
+ if (!g) return g;
7506
+ if (g.endsWith("/")) return `${g.slice(0, -1)}/**`;
7507
+ if (!g.includes("*")) {
7508
+ const base = g.split("/").pop() ?? "";
7509
+ const looksLikeFile = /\.[A-Za-z0-9]+$/.test(base);
7510
+ if (!looksLikeFile) return `${g}/**`;
7511
+ }
7512
+ return g;
7513
+ }
7514
+ function levenshtein(a, b) {
7515
+ const m = a.length;
7516
+ const n = b.length;
7517
+ const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
7518
+ for (let j = 0; j <= n; j++) d[0][j] = j;
7519
+ for (let i = 1; i <= m; i++) {
7520
+ for (let j = 1; j <= n; j++) {
7521
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
7522
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
7523
+ }
7524
+ }
7525
+ return d[m][n];
7526
+ }
7527
+ function analyzeGlob(dir, input) {
7528
+ const normalized = normalizeGlob(input);
7529
+ const warnings = [];
7530
+ let suggestion;
7531
+ if (/^\*+$/.test(normalized) || /^[^/*]+\/\*\*$/.test(normalized)) {
7532
+ warnings.push(
7533
+ "This glob is broad and may reduce Guard usefulness. Prefer narrower ownership when possible."
7534
+ );
7535
+ }
7536
+ const starIdx = normalized.indexOf("*");
7537
+ const literal = (starIdx >= 0 ? normalized.slice(0, starIdx) : normalized).replace(/\/+$/, "");
7538
+ if (literal && !exists(join(dir, literal))) {
7539
+ const slash = literal.lastIndexOf("/");
7540
+ const parent = slash >= 0 ? literal.slice(0, slash) : "";
7541
+ const target = slash >= 0 ? literal.slice(slash + 1) : literal;
7542
+ const parentFull = parent ? join(dir, parent) : dir;
7543
+ let near;
7544
+ try {
7545
+ if (exists(parentFull)) {
7546
+ near = readDir(parentFull).find(
7547
+ (e) => e !== target && levenshtein(e.toLowerCase(), target.toLowerCase()) <= 2
7548
+ );
7549
+ }
7550
+ } catch {
7551
+ }
7552
+ if (near) {
7553
+ suggestion = `${parent ? parent + "/" : ""}${near}${normalized.slice(literal.length)}`;
7554
+ warnings.push(`Path does not exist. Did you mean ${suggestion}?`);
7555
+ } else {
7556
+ warnings.push(`Path "${literal}" does not exist in the project.`);
7557
+ }
7558
+ }
7559
+ return { normalized, warnings, suggestion };
7560
+ }
6989
7561
  function toStringArray3(value) {
6990
7562
  return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
6991
7563
  }
@@ -7048,11 +7620,11 @@ function suggestGlobs(artifact, signals) {
7048
7620
  return [...new Set(out)];
7049
7621
  }
7050
7622
  function applyOwnership(raw, globs, mode = "replace") {
7051
- const parsed = matter5(raw);
7623
+ const parsed = matter6(raw);
7052
7624
  const existing = toStringArray3(parsed.data.code);
7053
7625
  const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
7054
7626
  const data = { ...parsed.data, code: next };
7055
- return matter5.stringify(parsed.content, data);
7627
+ return matter6.stringify(parsed.content, data);
7056
7628
  }
7057
7629
 
7058
7630
  // src/commands/owners.ts
@@ -7131,7 +7703,19 @@ async function chooseGlobs(suggested) {
7131
7703
  placeholder: "src/payments/**",
7132
7704
  validate: (v) => v.trim().length === 0 ? "A glob is required." : void 0
7133
7705
  });
7134
- chosen.push(glob.trim());
7706
+ const analysis = analyzeGlob(cwd(), glob);
7707
+ for (const w of analysis.warnings) log2.warn(w);
7708
+ let value = analysis.normalized;
7709
+ if (analysis.suggestion) {
7710
+ const useSuggested = await confirm2({
7711
+ message: `Use ${analysis.suggestion} instead?`,
7712
+ initialValue: true
7713
+ });
7714
+ if (useSuggested) value = analysis.suggestion;
7715
+ } else if (analysis.normalized !== glob.trim()) {
7716
+ log2.info(`Normalized to ${analysis.normalized}`);
7717
+ }
7718
+ chosen.push(value);
7135
7719
  addMore = await confirm2({ message: "Add another glob?", initialValue: false });
7136
7720
  }
7137
7721
  return [...new Set(chosen)];
@@ -7214,19 +7798,19 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
7214
7798
  }
7215
7799
 
7216
7800
  // src/commands/module-descriptor.ts
7217
- import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
7801
+ import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
7218
7802
  var DESCRIPTOR_PATH2 = "knowledge/module.yml";
7219
7803
  function readDescriptor(dir) {
7220
7804
  const path5 = join(dir, DESCRIPTOR_PATH2);
7221
7805
  if (!exists(path5)) return null;
7222
7806
  try {
7223
- return parseYaml12(readFile(path5));
7807
+ return parseYaml13(readFile(path5));
7224
7808
  } catch {
7225
7809
  return null;
7226
7810
  }
7227
7811
  }
7228
7812
  function writeDescriptor(dir, descriptor) {
7229
- writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml4(descriptor));
7813
+ writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml5(descriptor));
7230
7814
  }
7231
7815
  async function runModuleDescriptor(opts) {
7232
7816
  const dir = cwd();
@@ -7321,7 +7905,7 @@ function printDescriptor(d) {
7321
7905
  }
7322
7906
 
7323
7907
  // src/commands/modules-map.ts
7324
- import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
7908
+ import { parse as parseYaml14, stringify as stringifyYaml6 } from "yaml";
7325
7909
 
7326
7910
  // src/templates/registry.ts
7327
7911
  var QUALITY = "## Quality checklist";
@@ -7464,7 +8048,7 @@ ${QUALITY}
7464
8048
  - [ ] Capabilities describe outcomes, not implementation.
7465
8049
  - [ ] Each capability cites evidence or is flagged as an assumption.
7466
8050
  `;
7467
- var KNOWLEDGE2 = `---
8051
+ var KNOWLEDGE3 = `---
7468
8052
  type: current-state
7469
8053
  updated_at: YYYY-MM-DD
7470
8054
  ---
@@ -8474,7 +9058,7 @@ var KADDO_TEMPLATES = [
8474
9058
  description: "What is true about the product right now.",
8475
9059
  whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
8476
9060
  relatedCommand: "kaddo init",
8477
- content: KNOWLEDGE2
9061
+ content: KNOWLEDGE3
8478
9062
  },
8479
9063
  // business / product (bootstrap — consolidated, minimal)
8480
9064
  {
@@ -8828,14 +9412,14 @@ function readModulesDescriptor(dir) {
8828
9412
  const path5 = join(dir, DESCRIPTOR_PATH3);
8829
9413
  if (!exists(path5)) return { version: 1, modules: [] };
8830
9414
  try {
8831
- const parsed = parseYaml13(readFile(path5));
9415
+ const parsed = parseYaml14(readFile(path5));
8832
9416
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
8833
9417
  } catch {
8834
9418
  return { version: 1, modules: [] };
8835
9419
  }
8836
9420
  }
8837
9421
  function writeModulesDescriptor(dir, descriptor) {
8838
- writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml5(descriptor));
9422
+ writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml6(descriptor));
8839
9423
  }
8840
9424
  function moduleDir(id) {
8841
9425
  return `knowledge/tech/modules/${id}`;
@@ -9125,6 +9709,49 @@ async function runBootstrap(dir = cwd()) {
9125
9709
  );
9126
9710
  }
9127
9711
 
9712
+ // src/commands/capsule.ts
9713
+ function slug(s) {
9714
+ return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
9715
+ }
9716
+ function runCapsuleExport() {
9717
+ const dir = cwd();
9718
+ const config = requireConfig(dir);
9719
+ intro2("kaddo capsule export");
9720
+ const capsule = buildCapsule(dir, config);
9721
+ const name = slug(config.project.name);
9722
+ const mdPath = join(".kaddo", "exports", `${name}.capsule.md`);
9723
+ const jsonPath = join(".kaddo", "exports", `${name}.capsule.json`);
9724
+ writeFile(join(dir, mdPath), renderCapsuleMarkdown(capsule));
9725
+ writeFile(join(dir, jsonPath), serializeCapsuleJson(capsule));
9726
+ log2.success(`Wrote ${mdPath}`);
9727
+ log2.success(`Wrote ${jsonPath}`);
9728
+ log2.info("Refine it with the capsule-agent before sharing \u2014 and never include secrets or source code.");
9729
+ outro2("Knowledge Capsule exported.");
9730
+ }
9731
+ function runCapsuleAdd(srcPath) {
9732
+ const dir = cwd();
9733
+ requireConfig(dir);
9734
+ intro2("kaddo capsule add");
9735
+ if (!srcPath) {
9736
+ console.error("Usage: kaddo capsule add <path-to-capsule.md>");
9737
+ process.exit(1);
9738
+ }
9739
+ const abs = join(dir, srcPath);
9740
+ const source = exists(abs) ? abs : srcPath;
9741
+ if (!exists(source)) {
9742
+ console.error(`Capsule not found: ${srcPath}`);
9743
+ process.exit(1);
9744
+ }
9745
+ const result = addExternalCapsule(dir, source);
9746
+ if (!result.isCapsuleType) {
9747
+ log2.warn("This file is not marked as a knowledge-capsule (front matter `type: knowledge-capsule`).");
9748
+ }
9749
+ log2.success(`Imported capsule "${result.id}" \u2192 ${result.destRel}`);
9750
+ log2.success("Registered in .kaddo/external.yml");
9751
+ log2.info('Run `kaddo context` \u2014 the pack now includes an "External Knowledge" section.');
9752
+ outro2("External capsule registered.");
9753
+ }
9754
+
9128
9755
  // src/index.ts
9129
9756
  var require2 = createRequire(import.meta.url);
9130
9757
  var { version } = require2("../package.json");
@@ -9142,6 +9769,13 @@ program.command("bootstrap").description("Build the initial knowledge base for a
9142
9769
  program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike, chore). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
9143
9770
  await runCreate(type ?? "", opts);
9144
9771
  });
9772
+ var capsuleCmd = program.command("capsule").description("Export this project as a Knowledge Capsule, or import an external one as context");
9773
+ capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
9774
+ runCapsuleExport();
9775
+ });
9776
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path5) => {
9777
+ runCapsuleAdd(path5);
9778
+ });
9145
9779
  program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").action(async (opts) => {
9146
9780
  await runGuard(opts);
9147
9781
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.14.0",
3
+ "version": "3.16.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {