@kaddo/cli 3.15.0 → 3.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +41 -4
  2. package/dist/index.js +468 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -71,6 +71,10 @@ knowledge/
71
71
  config.yml ← project config
72
72
  ```
73
73
 
74
+ Asks for the project state (`new | pre-ai | legacy`), team size, repo structure and the
75
+ **project knowledge language** (`en | es`) — the language your knowledge artifacts are
76
+ written in; the CLI itself stays English.
77
+
74
78
  ---
75
79
 
76
80
  ### `kaddo scan`
@@ -265,12 +269,17 @@ concrete step (`kaddo scan` or `kaddo add agents`).
265
269
  Create a Work Item with the minimum context for its Knowledge Level.
266
270
 
267
271
  ```bash
268
- kaddo create feature # K2: 4 questions
269
- kaddo create bugfix # K2: 4 questions
270
- kaddo create hotfix # K1: 2 questions
271
- kaddo create spike # K3: 4 questions
272
+ kaddo create feature # K2: delivers a user-facing capability
273
+ kaddo create bugfix # K2: fixes a known defect
274
+ kaddo create hotfix # K1: urgent fix on a released version
275
+ kaddo create spike # K3: exploratory / reduce uncertainty
276
+ kaddo create chore # K1: maintenance, tooling, config, infra
272
277
  ```
273
278
 
279
+ New Work Items land in `knowledge/delivery/work-items/draft/` with `status: draft`
280
+ (lifecycle: `draft → ready → in-progress → blocked → completed → archived`). Aliases like
281
+ `setup`, `tooling`, `maintenance`, `infrastructure` or `refactor` resolve to `chore`.
282
+
274
283
  **From a roadmap candidate:**
275
284
 
276
285
  ```bash
@@ -409,6 +418,25 @@ architecture baseline, roadmap, agents), work items, **ownership coverage**, the
409
418
  `kaddo explain` summarizes what Kaddo already knows. The focused flags
410
419
  (`--scope`, `--type`, `--since`) still explain a subset of artifacts.
411
420
 
421
+ ---
422
+
423
+ ### `kaddo capsule`
424
+
425
+ Share or consume minimal, portable knowledge about a system — a **Knowledge Capsule** —
426
+ without mapping it as multirepo or reading its source.
427
+
428
+ ```bash
429
+ kaddo capsule export # → .kaddo/exports/<project>.capsule.md / .json
430
+ kaddo capsule add <path> # import an external capsule → external/<id>.capsule.md
431
+ ```
432
+
433
+ `export` builds a deterministic draft from `knowledge/` (purpose, capabilities, public
434
+ contracts, risks, ADRs, owners — never source code or secrets); refine it with the
435
+ `capsule-agent` before sharing. `add` registers the capsule in `.kaddo/external.yml`;
436
+ `kaddo context` then includes an **External Knowledge** section and `kaddo explain` lists
437
+ the capsules (warning when one looks stale). See the
438
+ [Knowledge Capsules guide](https://kaddo.trycatch.tv/knowledge-capsules/).
439
+
412
440
  ## Roadmap
413
441
 
414
442
  The full knowledge loop ships today: `scan → context → agents → understand → roadmap →
@@ -441,6 +469,15 @@ create --from roadmap → owners → guard → explain`.
441
469
  | v3.6 | Flexible roadmap parsing and roadmap candidate/materialized Work Item reporting |
442
470
  | v3.7 | Work Item lifecycle active workspace (`draft`, `ready`, `in-progress`, `blocked`, `completed`, `archived`) |
443
471
  | v3.7.1 | Context Efficiency positioning: Repository Exploration Tax and structured-knowledge narrative |
472
+ | v3.8 | Agent Trace & responsibility boundaries: every prompt declares Agent/Produced/Next; new `implementation-agent` (the only agent that may suggest a branch) |
473
+ | v3.9 | New `chore` Work Item type (+ aliases setup/tooling/maintenance/infra); explain Work Items by Type; context Delivery Mix |
474
+ | v3.9.1 | Unified knowledge artifact discovery: one service behind explain/context/owners/guard (fixes owners missing lifecycle subfolders) |
475
+ | v3.10 | State-aware recommendations: real phase model (Discovery → Planning → Delivery Preparation → Active Delivery → Maintenance) drives understand/context/explain |
476
+ | v3.11 | Command workflow clarity: official command matrix + "Question answered / Suggested next" footer on scan/context/explain/understand; `chore/` branch prefix |
477
+ | v3.13 | New `backlog-agent`: capture raw ideas into a Work Item draft or roadmap candidate (human decides the next step) |
478
+ | v3.14 | Project knowledge language (`project.language: en\|es`): knowledge in your language, CLI stays English; all agents respect it |
479
+ | v3.15 | Delivery context consistency: phase-based handoff + per-phase LLM instructions; assisted `owners suggest` (normalize/validate globs); new `ownership-agent`; guard untracked-files warning; duplicate Work Item detection |
480
+ | v3.16 | Knowledge Capsules: `kaddo capsule export/add`, External Knowledge in context/explain, new `capsule-agent` |
444
481
 
445
482
  **Optional modules (installed with `kaddo add`):**
446
483
 
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":
@@ -657,7 +672,15 @@ var COMMAND_HELP = {
657
672
  question: "Is knowledge drifting from code?",
658
673
  next: "Update the affected knowledge, then commit"
659
674
  },
660
- "add agents": { question: "Which agents are available?", next: "kaddo understand" }
675
+ "add agents": { question: "Which agents are available?", next: "kaddo understand" },
676
+ "capsule export": {
677
+ question: "How do I share this project as external context?",
678
+ next: "Refine with the capsule-agent, then share the capsule file"
679
+ },
680
+ "capsule add": {
681
+ question: "How do I consume another system as external context?",
682
+ next: "kaddo context (the pack now includes External Knowledge)"
683
+ }
661
684
  };
662
685
  function commandFooterLines(name) {
663
686
  const help = COMMAND_HELP[name];
@@ -1611,6 +1634,20 @@ var RESPONSIBILITY_MATRIX = {
1611
1634
  ],
1612
1635
  next: ["kaddo scan", "kaddo owners suggest", "kaddo guard", "kaddo explain"]
1613
1636
  },
1637
+ "capsule-agent": {
1638
+ agent: "capsule-agent",
1639
+ responsibleFor: ["Refining/validating a Knowledge Capsule for external sharing"],
1640
+ produces: [".kaddo/exports/<system>.capsule.md"],
1641
+ canSuggest: ["kaddo capsule export"],
1642
+ cannotSuggest: [
1643
+ "exporting secrets",
1644
+ "exporting source code",
1645
+ "inventing contracts",
1646
+ "code",
1647
+ "git"
1648
+ ],
1649
+ next: ["kaddo capsule export"]
1650
+ },
1614
1651
  "ownership-agent": {
1615
1652
  agent: "ownership-agent",
1616
1653
  responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
@@ -2932,6 +2969,85 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
2932
2969
  - Affected knowledge is updated.
2933
2970
  - Commit is suggested and awaits human confirmation \u2014 never run automatically.
2934
2971
  `;
2972
+ var CAPSULE_AGENT = `# Capsule Agent
2973
+
2974
+ ## Role
2975
+
2976
+ You are the Kaddo Capsule Agent. Your job is to refine and validate a **Knowledge Capsule** \u2014 a
2977
+ minimal, portable summary another project can consume as external context \u2014 before it is exported.
2978
+
2979
+ You do not write code, you never invent contracts, and you mark uncertainties. A capsule contains
2980
+ **knowledge, not code or secrets**.
2981
+
2982
+ ## When to Use
2983
+
2984
+ Use this agent before sharing a Knowledge Capsule (after \`kaddo capsule export\` produced a draft),
2985
+ to sharpen its purpose, capabilities, public contracts, risks, owners and out-of-scope.
2986
+
2987
+ ## Input Required
2988
+
2989
+ Provide \`.kaddo/context-pack.md\` plus \`knowledge/product/capabilities.md\`,
2990
+ \`knowledge/tech/current-state.md\`, \`knowledge/tech/decisions/\` and any contracts
2991
+ (\`knowledge/tech/contracts/\`) that exist. Also provide the draft capsule from
2992
+ \`.kaddo/exports/<system>.capsule.md\`.
2993
+
2994
+ ## Expected Output
2995
+
2996
+ A refined Markdown capsule intended to be saved as \`.kaddo/exports/<system>.capsule.md\`.
2997
+
2998
+ ## Instructions
2999
+
3000
+ 1. Summarize what the system does and the boundaries of this capsule.
3001
+ 2. List the **public contracts** consumers integrate with (APIs, events) \u2014 never invent them.
3002
+ 3. List exposed capabilities, dependencies and known integration risks.
3003
+ 4. Identify owners and relevant ADRs.
3004
+ 5. State what is **out of scope** for this capsule.
3005
+ 6. Mark any unknowns explicitly.
3006
+
3007
+ ## Constraints
3008
+
3009
+ - Do **not** export secrets, tokens, credentials, private keys, PII or internal sensitive URLs.
3010
+ - Do **not** export source code.
3011
+ - Do **not** invent contracts or integrations.
3012
+ - Summarize and mark boundaries; prefer "unknown" over guessing.
3013
+
3014
+ ## Output Format
3015
+
3016
+ \`\`\`markdown
3017
+ ---
3018
+ type: knowledge-capsule
3019
+ system: <system>
3020
+ version: 1
3021
+ updated_at: <YYYY-MM-DD>
3022
+ owner: <team>
3023
+ ---
3024
+
3025
+ # <System> \u2014 Knowledge Capsule
3026
+
3027
+ ## Purpose
3028
+ ## Responsibilities
3029
+ ## Exposed Capabilities
3030
+ ## Public Contracts
3031
+ ## Dependencies
3032
+ ## Known Risks
3033
+ ## Relevant ADRs
3034
+ ## Owners
3035
+ ## Out of Scope
3036
+ ## Usage Notes
3037
+ \`\`\`
3038
+
3039
+ ## Where to Save the Result
3040
+
3041
+ Save as \`.kaddo/exports/<system>.capsule.md\`. The human reviews the security checklist (no
3042
+ secrets, no source) before sharing.
3043
+
3044
+ ## Quality Checklist
3045
+
3046
+ - Purpose and boundaries are clear.
3047
+ - Public contracts are real (not invented) \u2014 unknowns are marked.
3048
+ - Capabilities, dependencies, risks, owners and out-of-scope are present.
3049
+ - No secrets, credentials, PII or source code are included.
3050
+ `;
2935
3051
  var OWNERSHIP_AGENT = `# Ownership Agent
2936
3052
 
2937
3053
  ## Role
@@ -3108,7 +3224,9 @@ var AGENT_PROMPTS = [
3108
3224
  // Backlog capture (idea → draft / roadmap candidate — VS-050)
3109
3225
  { fileName: "backlog-agent.md", content: BACKLOG_AGENT },
3110
3226
  // Ownership proposals (precise code: globs — VS-052)
3111
- { fileName: "ownership-agent.md", content: OWNERSHIP_AGENT }
3227
+ { fileName: "ownership-agent.md", content: OWNERSHIP_AGENT },
3228
+ // Knowledge Capsule refinement (external context — VS-054)
3229
+ { fileName: "capsule-agent.md", content: CAPSULE_AGENT }
3112
3230
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
3113
3231
  ].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
3114
3232
 
@@ -3123,7 +3241,8 @@ var AGENT_GROUPS = {
3123
3241
  "security-agent.md",
3124
3242
  "standards-agent.md",
3125
3243
  "module-design-agent.md",
3126
- "adr-agent.md"
3244
+ "adr-agent.md",
3245
+ "capsule-agent.md"
3127
3246
  ],
3128
3247
  delivery: [
3129
3248
  "backlog-agent.md",
@@ -3234,6 +3353,9 @@ var agentReadme = {
3234
3353
  "- **pre-ai** \u2192 capability-agent \u2192 architecture-agent \u2192 roadmap-agent",
3235
3354
  "- **legacy** \u2192 legacy-agent \u2192 architecture-agent \u2192 capability-agent \u2192 roadmap-agent",
3236
3355
  "",
3356
+ "Then, in delivery: backlog-agent (capture ideas) \u2192 work-item-agent (refine) \u2192",
3357
+ "ownership-agent (propose code: globs) \u2192 implementation-agent (build).",
3358
+ "",
3237
3359
  "## Installed agents",
3238
3360
  "",
3239
3361
  "### Bootstrap agents (new projects)",
@@ -3250,14 +3372,22 @@ var agentReadme = {
3250
3372
  "- `legacy-agent.md` \u2014 analyze risks/unknowns before changing legacy code.",
3251
3373
  "- `adr-agent.md` \u2014 propose candidate architecture decisions.",
3252
3374
  "",
3253
- "### Operational agents",
3375
+ "### Delivery agents",
3254
3376
  "",
3377
+ "- `backlog-agent.md` \u2014 capture raw ideas/notes into a Work Item draft or roadmap candidate.",
3255
3378
  "- `work-item-agent.md` \u2014 refine roadmap candidates or existing Work Items.",
3379
+ "- `implementation-agent.md` \u2014 implement a refined Work Item (the only agent that may",
3380
+ " suggest a branch; never runs git).",
3381
+ "- `ownership-agent.md` \u2014 propose precise `code:` globs (human applies with `kaddo owners suggest`).",
3256
3382
  "- `git-strategy-agent.md` \u2014 define branch/commit/tag/release strategy.",
3383
+ "",
3384
+ "### Operational agents",
3385
+ "",
3257
3386
  "- `security-agent.md` \u2014 document security considerations (no scanning).",
3258
3387
  "- `standards-agent.md` \u2014 propose lightweight coding/docs/architecture standards.",
3259
3388
  "- `stack-agent.md` \u2014 document technologies and stack decisions.",
3260
- "- `module-design-agent.md` \u2014 document the design of a mapped module."
3389
+ "- `module-design-agent.md` \u2014 document the design of a mapped module.",
3390
+ "- `capsule-agent.md` \u2014 refine a Knowledge Capsule for external sharing (no secrets/source)."
3261
3391
  ].join("\n")
3262
3392
  };
3263
3393
  var agentFiles = AGENT_PROMPTS.map((a) => ({
@@ -3844,7 +3974,7 @@ function formatList(items) {
3844
3974
  return items.map((i) => `- ${i.trim()}`).join("\n");
3845
3975
  }
3846
3976
  function buildFrontMatter(id, type, level, title, answers) {
3847
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3977
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3848
3978
  const lines = [
3849
3979
  "---",
3850
3980
  `type: ${type}`,
@@ -3856,7 +3986,7 @@ function buildFrontMatter(id, type, level, title, answers) {
3856
3986
  `initiative:`,
3857
3987
  `domains: []`,
3858
3988
  `code: []`,
3859
- `created_at: ${today}`,
3989
+ `created_at: ${today2}`,
3860
3990
  `summary: "${answers.problem?.split(".")[0] ?? title}"`,
3861
3991
  "---"
3862
3992
  ];
@@ -3926,7 +4056,7 @@ _What did we learn from this change? Update after completion._
3926
4056
  return sections.join("\n");
3927
4057
  }
3928
4058
  function buildModuleFrontMatter(id, modType, title, answers) {
3929
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4059
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3930
4060
  const extra = modType.extraFrontMatter ?? {};
3931
4061
  const extraLines = Object.entries(extra).map(
3932
4062
  ([k, v]) => `${k}: ${JSON.stringify(v)}`
@@ -3942,7 +4072,7 @@ function buildModuleFrontMatter(id, modType, title, answers) {
3942
4072
  `initiative:`,
3943
4073
  `domains: []`,
3944
4074
  `code: []`,
3945
- `created_at: ${today}`,
4075
+ `created_at: ${today2}`,
3946
4076
  `summary: "${title}"`,
3947
4077
  ...extraLines,
3948
4078
  "---"
@@ -4027,8 +4157,8 @@ async function runCreate(type, opts = {}) {
4027
4157
  answers[question.frontMatterField] = answer.trim();
4028
4158
  }
4029
4159
  const id = nextWorkItemId(dir);
4030
- const slug = slugify(title);
4031
- const fileName = `${id}-${slug}.md`;
4160
+ const slug2 = slugify(title);
4161
+ const fileName = `${id}-${slug2}.md`;
4032
4162
  const filePath = join(dir, DRAFT_DIR, fileName);
4033
4163
  const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
4034
4164
  const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
@@ -4068,8 +4198,8 @@ async function runCreateModule(dir, modType) {
4068
4198
  answers[q.frontMatterField] = answer.trim();
4069
4199
  }
4070
4200
  const id = nextWorkItemId(dir);
4071
- const slug = slugify(title);
4072
- const fileName = `${id}-${slug}.md`;
4201
+ const slug2 = slugify(title);
4202
+ const fileName = `${id}-${slug2}.md`;
4073
4203
  const filePath = join(dir, DRAFT_DIR, fileName);
4074
4204
  const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
4075
4205
  const body = buildModuleBody(modType, title.trim(), answers);
@@ -4091,7 +4221,7 @@ function resolveCandidateLevel(candidate, type) {
4091
4221
  return getLevelForType(type);
4092
4222
  }
4093
4223
  function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4094
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4224
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4095
4225
  const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
4096
4226
  const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
4097
4227
  const lines = [
@@ -4105,7 +4235,7 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4105
4235
  `initiative: "${initiative.replace(/"/g, "'")}"`,
4106
4236
  `domains: []`,
4107
4237
  `code: []`,
4108
- `created_at: ${today}`,
4238
+ `created_at: ${today2}`,
4109
4239
  `source: roadmap`,
4110
4240
  `source_id: ${candidate.id}`,
4111
4241
  `source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
@@ -4201,8 +4331,8 @@ function buildRoadmapWorkItem(opts) {
4201
4331
  const { id, type, level, candidate } = opts;
4202
4332
  const answers = opts.answers ?? {};
4203
4333
  const title = candidate.title.trim();
4204
- const slug = slugify(title);
4205
- const fileName = `${id}-${slug}.md`;
4334
+ const slug2 = slugify(title);
4335
+ const fileName = `${id}-${slug2}.md`;
4206
4336
  const qualityGate = getLevel(level).qualityGate;
4207
4337
  const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
4208
4338
  const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
@@ -5125,8 +5255,8 @@ function runIgnoreRemove(artifactId) {
5125
5255
  }
5126
5256
 
5127
5257
  // src/commands/explain.ts
5128
- import matter2 from "gray-matter";
5129
- import { parse as parseYaml8 } from "yaml";
5258
+ import matter3 from "gray-matter";
5259
+ import { parse as parseYaml9 } from "yaml";
5130
5260
 
5131
5261
  // src/core/delivery-phase.ts
5132
5262
  function layer(layers, name) {
@@ -5262,8 +5392,201 @@ function assessPhase(input) {
5262
5392
  return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
5263
5393
  }
5264
5394
 
5265
- // src/core/knowledge-discovery.ts
5395
+ // src/core/capsule.ts
5396
+ import matter2 from "gray-matter";
5397
+ import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
5266
5398
  var KNOWLEDGE = "knowledge";
5399
+ function today() {
5400
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5401
+ }
5402
+ function firstParagraph(md) {
5403
+ const body = md.replace(/^---\n[\s\S]*?\n---\n/, "");
5404
+ for (const block of body.split(/\n\s*\n/)) {
5405
+ const t = block.trim();
5406
+ if (t && !t.startsWith("#") && !t.startsWith(">")) return t.replace(/\s+/g, " ");
5407
+ }
5408
+ return "";
5409
+ }
5410
+ function readIf(dir, rel) {
5411
+ const p2 = join(dir, rel);
5412
+ return exists(p2) ? readFile(p2) : null;
5413
+ }
5414
+ function headings(md) {
5415
+ 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));
5416
+ }
5417
+ function buildCapsule(dir, config) {
5418
+ const ownerMap = loadOwners(dir);
5419
+ const owners = [...new Set(Object.values(ownerMap).flat())];
5420
+ const all = discoverKnowledge(dir);
5421
+ const purposeSrc = readIf(dir, `${KNOWLEDGE}/business/business.md`) ?? readIf(dir, `${KNOWLEDGE}/knowledge.md`) ?? "";
5422
+ const capsMd = readIf(dir, `${KNOWLEDGE}/product/capabilities.md`);
5423
+ const risksMd = readIf(dir, `${KNOWLEDGE}/legacy/risks.md`);
5424
+ const adrs = all.filter((a) => a.filePath.replace(/\\/g, "/").includes("/tech/decisions/") && Boolean(a.type)).map((a) => a.title || a.id).filter(Boolean);
5425
+ return {
5426
+ system: config.project.name,
5427
+ version: 1,
5428
+ updatedAt: today(),
5429
+ owner: owners[0] ?? "unknown",
5430
+ sourceProject: config.project.name,
5431
+ purpose: firstParagraph(purposeSrc),
5432
+ responsibilities: [],
5433
+ capabilities: capsMd ? headings(capsMd) : [],
5434
+ contracts: [],
5435
+ dependencies: [],
5436
+ knownRisks: risksMd ? headings(risksMd) : [],
5437
+ adrs,
5438
+ owners,
5439
+ outOfScope: [],
5440
+ usageNotes: []
5441
+ };
5442
+ }
5443
+ function section2(title, items, placeholder) {
5444
+ if (items.length === 0) return `## ${title}
5445
+
5446
+ _${placeholder}_
5447
+ `;
5448
+ return `## ${title}
5449
+
5450
+ ${items.map((i) => `- ${i}`).join("\n")}
5451
+ `;
5452
+ }
5453
+ function renderCapsuleMarkdown(c) {
5454
+ const fm = [
5455
+ "---",
5456
+ "type: knowledge-capsule",
5457
+ `system: ${c.system}`,
5458
+ `version: ${c.version}`,
5459
+ `updated_at: ${c.updatedAt}`,
5460
+ `owner: ${c.owner}`,
5461
+ `source_project: ${c.sourceProject}`,
5462
+ ...c.sourceCommit ? [`source_commit: ${c.sourceCommit}`] : [],
5463
+ "---"
5464
+ ].join("\n");
5465
+ const parts = [
5466
+ fm,
5467
+ "",
5468
+ `# ${c.system} \u2014 Knowledge Capsule`,
5469
+ "",
5470
+ `## Purpose
5471
+
5472
+ ${c.purpose || "_To be completed by the capsule-agent._"}
5473
+ `,
5474
+ section2("Responsibilities", c.responsibilities, "List what this system is responsible for."),
5475
+ section2("Exposed Capabilities", c.capabilities, "List the capabilities this system exposes."),
5476
+ section2("Public Contracts", c.contracts, "List public APIs and events (e.g. `POST /orders`, `OrderCreated`)."),
5477
+ section2("Dependencies", c.dependencies, "List systems this one depends on."),
5478
+ section2("Known Risks", c.knownRisks, "List integration risks consumers should know."),
5479
+ section2("Relevant ADRs", c.adrs, "List decisions that affect how to integrate."),
5480
+ section2("Owners", c.owners, "Who owns this system."),
5481
+ section2("Out of Scope", c.outOfScope, "What this capsule deliberately does not cover."),
5482
+ section2("Usage Notes", c.usageNotes, "How consumers should integrate."),
5483
+ "> Security: this capsule must never contain secrets, tokens, credentials, PII or source code.",
5484
+ ""
5485
+ ];
5486
+ return parts.join("\n");
5487
+ }
5488
+ function serializeCapsuleJson(c) {
5489
+ return JSON.stringify({ type: "knowledge-capsule", ...c }, null, 2) + "\n";
5490
+ }
5491
+ var EXTERNAL_PATH = ".kaddo/external.yml";
5492
+ function loadExternalRegistry(dir) {
5493
+ const p2 = join(dir, EXTERNAL_PATH);
5494
+ if (!exists(p2)) return [];
5495
+ try {
5496
+ const parsed = parseYaml8(readFile(p2));
5497
+ return Array.isArray(parsed?.external) ? parsed.external : [];
5498
+ } catch {
5499
+ return [];
5500
+ }
5501
+ }
5502
+ function serializeExternalRegistry(entries) {
5503
+ return stringifyYaml3({ external: entries });
5504
+ }
5505
+ function sectionList(md, title) {
5506
+ const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
5507
+ const lines = md.split(/\r?\n/);
5508
+ const start = lines.findIndex((l) => re.test(l));
5509
+ if (start < 0) return [];
5510
+ const out = [];
5511
+ for (let i = start + 1; i < lines.length; i++) {
5512
+ if (/^##\s+/.test(lines[i])) break;
5513
+ const m = lines[i].match(/^\s*-\s+(.+?)\s*$/);
5514
+ if (m && !/^_.*_$/.test(m[1])) out.push(m[1].trim());
5515
+ }
5516
+ return out;
5517
+ }
5518
+ function sectionParagraph(md, title) {
5519
+ const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
5520
+ const lines = md.split(/\r?\n/);
5521
+ const start = lines.findIndex((l) => re.test(l));
5522
+ if (start < 0) return "";
5523
+ for (let i = start + 1; i < lines.length; i++) {
5524
+ if (/^##\s+/.test(lines[i])) break;
5525
+ const t = lines[i].trim();
5526
+ if (t && !/^_.*_$/.test(t)) return t;
5527
+ }
5528
+ return "";
5529
+ }
5530
+ function parseCapsule(id, path5, md) {
5531
+ const { data } = matter2(md);
5532
+ const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
5533
+ let ageDays = null;
5534
+ if (updatedAt) {
5535
+ const d = Date.parse(updatedAt);
5536
+ if (!Number.isNaN(d)) ageDays = Math.max(0, Math.floor((Date.now() - d) / 864e5));
5537
+ }
5538
+ return {
5539
+ id,
5540
+ path: path5,
5541
+ system: data.system ? String(data.system) : id,
5542
+ owner: data.owner ? String(data.owner) : void 0,
5543
+ updatedAt,
5544
+ purpose: sectionParagraph(md, "Purpose"),
5545
+ capabilities: sectionList(md, "Exposed Capabilities"),
5546
+ contracts: sectionList(md, "Public Contracts"),
5547
+ knownRisks: sectionList(md, "Known Risks"),
5548
+ ageDays
5549
+ };
5550
+ }
5551
+ function slugId(s) {
5552
+ return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "external";
5553
+ }
5554
+ function addExternalCapsule(dir, sourceFile) {
5555
+ if (!exists(sourceFile)) throw new Error(`Capsule not found: ${sourceFile}`);
5556
+ const raw = readFile(sourceFile);
5557
+ const { data } = matter2(raw);
5558
+ const isCapsuleType = data.type === "knowledge-capsule";
5559
+ const fallbackName = sourceFile.split(/[\\/]/).pop()?.replace(/\.capsule\.md$/, "") ?? "external";
5560
+ const id = slugId(String(data.system ?? fallbackName));
5561
+ const destRel = join("external", `${id}.capsule.md`).replace(/\\/g, "/");
5562
+ writeFile(join(dir, destRel), raw);
5563
+ const entry = {
5564
+ id,
5565
+ type: "knowledge-capsule",
5566
+ path: destRel,
5567
+ owner: data.owner ? String(data.owner) : void 0,
5568
+ lastImportedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
5569
+ };
5570
+ const registry = loadExternalRegistry(dir).filter((e) => e.id !== id);
5571
+ registry.push(entry);
5572
+ writeFile(join(dir, EXTERNAL_PATH), serializeExternalRegistry(registry));
5573
+ return { id, destRel, entry, isCapsuleType };
5574
+ }
5575
+ function loadExternalCapsules(dir) {
5576
+ const out = [];
5577
+ for (const entry of loadExternalRegistry(dir)) {
5578
+ const full = join(dir, entry.path);
5579
+ if (!exists(full) || !isFile(full)) continue;
5580
+ try {
5581
+ out.push(parseCapsule(entry.id, entry.path, readFile(full)));
5582
+ } catch {
5583
+ }
5584
+ }
5585
+ return out;
5586
+ }
5587
+
5588
+ // src/core/knowledge-discovery.ts
5589
+ var KNOWLEDGE2 = "knowledge";
5267
5590
  var CONSOLIDATED_TYPE = {
5268
5591
  Business: "business",
5269
5592
  Product: "product",
@@ -5305,10 +5628,10 @@ function layerForType(type) {
5305
5628
  }
5306
5629
  function layerFromPath(filePath) {
5307
5630
  const p2 = filePath.replace(/\\/g, "/");
5308
- if (p2.includes(`/${KNOWLEDGE}/business/`)) return "Business";
5309
- if (p2.includes(`/${KNOWLEDGE}/product/`)) return "Product";
5310
- if (p2.includes(`/${KNOWLEDGE}/tech/`)) return "Tech";
5311
- if (p2.includes(`/${KNOWLEDGE}/delivery/`)) return "Delivery";
5631
+ if (p2.includes(`/${KNOWLEDGE2}/business/`)) return "Business";
5632
+ if (p2.includes(`/${KNOWLEDGE2}/product/`)) return "Product";
5633
+ if (p2.includes(`/${KNOWLEDGE2}/tech/`)) return "Tech";
5634
+ if (p2.includes(`/${KNOWLEDGE2}/delivery/`)) return "Delivery";
5312
5635
  return null;
5313
5636
  }
5314
5637
  function basename(p2) {
@@ -5321,7 +5644,7 @@ function discoverLayers(dir) {
5321
5644
  Tech: blank(),
5322
5645
  Delivery: blank()
5323
5646
  };
5324
- const archDir = join(dir, KNOWLEDGE);
5647
+ const archDir = join(dir, KNOWLEDGE2);
5325
5648
  const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
5326
5649
  for (const a of artifacts) {
5327
5650
  const type = a.type;
@@ -5343,7 +5666,7 @@ function discoverLayers(dir) {
5343
5666
  }
5344
5667
  if (type === "adr" || type === "decision") slot.hasDecision = true;
5345
5668
  }
5346
- if (existsDirWithMd(join(dir, KNOWLEDGE, "tech", "decisions"))) acc.Tech.structured = true;
5669
+ if (existsDirWithMd(join(dir, KNOWLEDGE2, "tech", "decisions"))) acc.Tech.structured = true;
5347
5670
  return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
5348
5671
  layer: layer2,
5349
5672
  status: statusFor(layer2, acc[layer2]),
@@ -5591,6 +5914,7 @@ function buildProjectExplanation(dir) {
5591
5914
  ownership,
5592
5915
  domains,
5593
5916
  duplicateWorkItems,
5917
+ externalCapsules: loadExternalCapsules(dir),
5594
5918
  layers,
5595
5919
  roadmap,
5596
5920
  mappedModules,
@@ -5719,6 +6043,17 @@ function renderExplanationHuman(exp) {
5719
6043
  lines.push("- Mapped modules: 0");
5720
6044
  lines.push("");
5721
6045
  }
6046
+ if (exp.externalCapsules.length > 0) {
6047
+ lines.push(`## External Knowledge Capsules: ${exp.externalCapsules.length}`);
6048
+ for (const cap of exp.externalCapsules) {
6049
+ const owner = cap.owner ? ` \u2014 owner: ${cap.owner}` : "";
6050
+ lines.push(`- ${cap.system}${owner}`);
6051
+ if (cap.ageDays !== null && cap.ageDays >= 90) {
6052
+ lines.push(` \u26A0 capsule last updated ${cap.ageDays} days ago \u2014 it may be stale.`);
6053
+ }
6054
+ }
6055
+ lines.push("");
6056
+ }
5722
6057
  if (exp.missingKnowledge.length > 0) {
5723
6058
  lines.push("## Missing Knowledge");
5724
6059
  for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
@@ -5762,7 +6097,7 @@ function readKnowledge(dir) {
5762
6097
  if (!exists(knowledgePath)) return null;
5763
6098
  try {
5764
6099
  const raw = readFile(knowledgePath);
5765
- const { data, content } = matter2(raw);
6100
+ const { data, content } = matter3(raw);
5766
6101
  return { content, data };
5767
6102
  } catch {
5768
6103
  return null;
@@ -5773,7 +6108,7 @@ function readRoadmap(dir) {
5773
6108
  if (!exists(roadmapPath)) return null;
5774
6109
  try {
5775
6110
  const raw = readFile(roadmapPath);
5776
- const { content } = matter2(raw);
6111
+ const { content } = matter3(raw);
5777
6112
  return content.trim();
5778
6113
  } catch {
5779
6114
  return null;
@@ -5783,7 +6118,7 @@ function readConfig(dir) {
5783
6118
  const configPath = join(dir, CONFIG_PATH3);
5784
6119
  if (!exists(configPath)) return {};
5785
6120
  try {
5786
- return parseYaml8(readFile(configPath));
6121
+ return parseYaml9(readFile(configPath));
5787
6122
  } catch {
5788
6123
  return {};
5789
6124
  }
@@ -5869,8 +6204,8 @@ function explainForAgent(dir, artifacts, opts) {
5869
6204
  since: opts.since ?? null
5870
6205
  };
5871
6206
  if (knowledge) {
5872
- const firstParagraph2 = knowledge.content.trim().split("\n\n").find((p2) => p2.trim() && !p2.startsWith("#"));
5873
- output.knowledge_summary = firstParagraph2?.trim() ?? "";
6207
+ const firstParagraph3 = knowledge.content.trim().split("\n\n").find((p2) => p2.trim() && !p2.startsWith("#"));
6208
+ output.knowledge_summary = firstParagraph3?.trim() ?? "";
5874
6209
  }
5875
6210
  const mappedArtifacts = artifacts.filter((a) => a.type !== "current-state" && a.type !== "roadmap").map((a) => ({
5876
6211
  id: a.id,
@@ -5942,7 +6277,7 @@ function runExplain(opts) {
5942
6277
  }
5943
6278
 
5944
6279
  // src/core/context-pack.ts
5945
- import matter3 from "gray-matter";
6280
+ import matter4 from "gray-matter";
5946
6281
  var CONTEXT_PACK_VERSION = "1";
5947
6282
  var ARCH_DIR6 = "knowledge";
5948
6283
  function readScanJson(dir) {
@@ -5954,8 +6289,8 @@ function readScanJson(dir) {
5954
6289
  return null;
5955
6290
  }
5956
6291
  }
5957
- function firstParagraph(markdown) {
5958
- const body = matter3(markdown).content.trim();
6292
+ function firstParagraph2(markdown) {
6293
+ const body = matter4(markdown).content.trim();
5959
6294
  const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
5960
6295
  return para ?? "";
5961
6296
  }
@@ -5963,7 +6298,7 @@ function readMarkdownSummary(dir, file) {
5963
6298
  const p2 = join(dir, ARCH_DIR6, file);
5964
6299
  if (!exists(p2)) return null;
5965
6300
  try {
5966
- return firstParagraph(readFile(p2));
6301
+ return firstParagraph2(readFile(p2));
5967
6302
  } catch {
5968
6303
  return null;
5969
6304
  }
@@ -6125,6 +6460,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
6125
6460
  roadmap,
6126
6461
  phase,
6127
6462
  deliveryMix,
6463
+ external: loadExternalCapsules(dir),
6128
6464
  mappedModules,
6129
6465
  missing,
6130
6466
  // VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
@@ -6275,6 +6611,21 @@ function renderContextPack(pack) {
6275
6611
  "Note: Kaddo does not scan secondary repositories during `context`. Mapped modules come from `.kaddo/modules.yml` and module artifacts only.\n"
6276
6612
  );
6277
6613
  }
6614
+ if (pack.external.length > 0) {
6615
+ parts.push("## External Knowledge\n");
6616
+ parts.push(
6617
+ "Imported Knowledge Capsules \u2014 minimal context about external systems (not their source).\n"
6618
+ );
6619
+ for (const cap of pack.external) {
6620
+ const lines = [`### ${cap.system}`, ""];
6621
+ if (cap.purpose) lines.push(`- Purpose: ${cap.purpose}`);
6622
+ if (cap.capabilities.length) lines.push(`- Capabilities: ${cap.capabilities.join(", ")}`);
6623
+ if (cap.contracts.length) lines.push(`- Contracts: ${cap.contracts.join(", ")}`);
6624
+ if (cap.owner) lines.push(`- Owner: ${cap.owner}`);
6625
+ if (cap.knownRisks.length) lines.push(`- Known risks: ${cap.knownRisks.join("; ")}`);
6626
+ parts.push(lines.join("\n") + "\n");
6627
+ }
6628
+ }
6278
6629
  parts.push("## Missing Context\n");
6279
6630
  if (missing.length > 0) {
6280
6631
  parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
@@ -6490,7 +6841,7 @@ function renderUnderstandTerminal(plan) {
6490
6841
  }
6491
6842
 
6492
6843
  // src/core/delivery.ts
6493
- import { parse as parseYaml9 } from "yaml";
6844
+ import { parse as parseYaml10 } from "yaml";
6494
6845
  function slugify2(s) {
6495
6846
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6496
6847
  }
@@ -6596,6 +6947,14 @@ function runUnderstand() {
6596
6947
  if (assessment.nextStep) {
6597
6948
  console.log(`Next step: ${assessment.nextStep}`);
6598
6949
  }
6950
+ if (exp.externalCapsules.length > 0) {
6951
+ console.log("");
6952
+ console.log("External knowledge:");
6953
+ for (const cap of exp.externalCapsules) {
6954
+ console.log(` - ${cap.system}${cap.owner ? ` (owner: ${cap.owner})` : ""}`);
6955
+ }
6956
+ console.log(" \u2192 Review the relevant capsule before changing integration behavior with it.");
6957
+ }
6599
6958
  const active = activeWorkItems(dir);
6600
6959
  if (active.length > 0) {
6601
6960
  console.log("");
@@ -6829,14 +7188,14 @@ async function runClassify(opts = {}) {
6829
7188
  }
6830
7189
 
6831
7190
  // src/commands/status.ts
6832
- import { parse as parseYaml10 } from "yaml";
7191
+ import { parse as parseYaml11 } from "yaml";
6833
7192
  var ARCH_DIR8 = "knowledge";
6834
7193
  var CONFIG_PATH4 = ".kaddo/config.yml";
6835
7194
  function loadConfig3(dir) {
6836
7195
  const p2 = join(dir, CONFIG_PATH4);
6837
7196
  if (!exists(p2)) return {};
6838
7197
  try {
6839
- return parseYaml10(readFile(p2));
7198
+ return parseYaml11(readFile(p2));
6840
7199
  } catch {
6841
7200
  return {};
6842
7201
  }
@@ -6893,7 +7252,7 @@ function runStatus() {
6893
7252
  }
6894
7253
 
6895
7254
  // src/commands/learn.ts
6896
- import matter4 from "gray-matter";
7255
+ import matter5 from "gray-matter";
6897
7256
  var ARCH_DIR9 = "knowledge";
6898
7257
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
6899
7258
  function findWorkItemFile(dir, id) {
@@ -6905,7 +7264,7 @@ function findWorkItemFile(dir, id) {
6905
7264
  }
6906
7265
  function updateWorkItemFile(filePath, learning) {
6907
7266
  const raw = readFile(filePath);
6908
- const { data, content } = matter4(raw);
7267
+ const { data, content } = matter5(raw);
6909
7268
  data.status = "done";
6910
7269
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6911
7270
  let updatedContent = content;
@@ -6930,7 +7289,7 @@ ${learning.trim()}
6930
7289
  ${learning.trim()}
6931
7290
  `;
6932
7291
  }
6933
- const newRaw = matter4.stringify(updatedContent, data);
7292
+ const newRaw = matter5.stringify(updatedContent, data);
6934
7293
  writeFile(filePath, newRaw);
6935
7294
  }
6936
7295
  async function runLearn(artifactId) {
@@ -7038,13 +7397,13 @@ function runHistory(opts = {}) {
7038
7397
  }
7039
7398
 
7040
7399
  // src/commands/add.ts
7041
- import { parse as parseYaml11, stringify as stringifyYaml3 } from "yaml";
7400
+ import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
7042
7401
  var CONFIG_PATH5 = ".kaddo/config.yml";
7043
7402
  function readProjectState(dir) {
7044
7403
  const configPath = join(dir, CONFIG_PATH5);
7045
7404
  if (!exists(configPath)) return void 0;
7046
7405
  try {
7047
- const config = parseYaml11(readFile(configPath));
7406
+ const config = parseYaml12(readFile(configPath));
7048
7407
  return config.project?.state;
7049
7408
  } catch {
7050
7409
  return void 0;
@@ -7062,12 +7421,12 @@ function markModuleInstalled(dir, configKey, moduleName) {
7062
7421
  const configPath = join(dir, CONFIG_PATH5);
7063
7422
  if (!exists(configPath)) return;
7064
7423
  try {
7065
- const config = parseYaml11(readFile(configPath));
7424
+ const config = parseYaml12(readFile(configPath));
7066
7425
  config[configKey] = { installed: true, installed_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
7067
7426
  const modules = config.modules ?? [];
7068
7427
  if (!modules.includes(moduleName)) modules.push(moduleName);
7069
7428
  config.modules = modules;
7070
- writeFile(configPath, stringifyYaml3(config));
7429
+ writeFile(configPath, stringifyYaml4(config));
7071
7430
  } catch {
7072
7431
  }
7073
7432
  }
@@ -7075,7 +7434,7 @@ function isModuleInstalled(dir, configKey) {
7075
7434
  const configPath = join(dir, CONFIG_PATH5);
7076
7435
  if (!exists(configPath)) return false;
7077
7436
  try {
7078
- const config = parseYaml11(readFile(configPath));
7437
+ const config = parseYaml12(readFile(configPath));
7079
7438
  const moduleConfig = config[configKey];
7080
7439
  return moduleConfig?.installed === true;
7081
7440
  } catch {
@@ -7158,7 +7517,7 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
7158
7517
  }
7159
7518
 
7160
7519
  // src/core/ownership-suggest.ts
7161
- import matter5 from "gray-matter";
7520
+ import matter6 from "gray-matter";
7162
7521
  var SCAN_PATH = ".kaddo/scan.json";
7163
7522
  function normalizeGlob(input) {
7164
7523
  let g = input.trim().replace(/\\/g, "/");
@@ -7280,11 +7639,11 @@ function suggestGlobs(artifact, signals) {
7280
7639
  return [...new Set(out)];
7281
7640
  }
7282
7641
  function applyOwnership(raw, globs, mode = "replace") {
7283
- const parsed = matter5(raw);
7642
+ const parsed = matter6(raw);
7284
7643
  const existing = toStringArray3(parsed.data.code);
7285
7644
  const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
7286
7645
  const data = { ...parsed.data, code: next };
7287
- return matter5.stringify(parsed.content, data);
7646
+ return matter6.stringify(parsed.content, data);
7288
7647
  }
7289
7648
 
7290
7649
  // src/commands/owners.ts
@@ -7458,19 +7817,19 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
7458
7817
  }
7459
7818
 
7460
7819
  // src/commands/module-descriptor.ts
7461
- import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
7820
+ import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
7462
7821
  var DESCRIPTOR_PATH2 = "knowledge/module.yml";
7463
7822
  function readDescriptor(dir) {
7464
7823
  const path5 = join(dir, DESCRIPTOR_PATH2);
7465
7824
  if (!exists(path5)) return null;
7466
7825
  try {
7467
- return parseYaml12(readFile(path5));
7826
+ return parseYaml13(readFile(path5));
7468
7827
  } catch {
7469
7828
  return null;
7470
7829
  }
7471
7830
  }
7472
7831
  function writeDescriptor(dir, descriptor) {
7473
- writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml4(descriptor));
7832
+ writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml5(descriptor));
7474
7833
  }
7475
7834
  async function runModuleDescriptor(opts) {
7476
7835
  const dir = cwd();
@@ -7565,7 +7924,7 @@ function printDescriptor(d) {
7565
7924
  }
7566
7925
 
7567
7926
  // src/commands/modules-map.ts
7568
- import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
7927
+ import { parse as parseYaml14, stringify as stringifyYaml6 } from "yaml";
7569
7928
 
7570
7929
  // src/templates/registry.ts
7571
7930
  var QUALITY = "## Quality checklist";
@@ -7708,7 +8067,7 @@ ${QUALITY}
7708
8067
  - [ ] Capabilities describe outcomes, not implementation.
7709
8068
  - [ ] Each capability cites evidence or is flagged as an assumption.
7710
8069
  `;
7711
- var KNOWLEDGE2 = `---
8070
+ var KNOWLEDGE3 = `---
7712
8071
  type: current-state
7713
8072
  updated_at: YYYY-MM-DD
7714
8073
  ---
@@ -8718,7 +9077,7 @@ var KADDO_TEMPLATES = [
8718
9077
  description: "What is true about the product right now.",
8719
9078
  whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
8720
9079
  relatedCommand: "kaddo init",
8721
- content: KNOWLEDGE2
9080
+ content: KNOWLEDGE3
8722
9081
  },
8723
9082
  // business / product (bootstrap — consolidated, minimal)
8724
9083
  {
@@ -9072,14 +9431,14 @@ function readModulesDescriptor(dir) {
9072
9431
  const path5 = join(dir, DESCRIPTOR_PATH3);
9073
9432
  if (!exists(path5)) return { version: 1, modules: [] };
9074
9433
  try {
9075
- const parsed = parseYaml13(readFile(path5));
9434
+ const parsed = parseYaml14(readFile(path5));
9076
9435
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
9077
9436
  } catch {
9078
9437
  return { version: 1, modules: [] };
9079
9438
  }
9080
9439
  }
9081
9440
  function writeModulesDescriptor(dir, descriptor) {
9082
- writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml5(descriptor));
9441
+ writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml6(descriptor));
9083
9442
  }
9084
9443
  function moduleDir(id) {
9085
9444
  return `knowledge/tech/modules/${id}`;
@@ -9369,6 +9728,51 @@ async function runBootstrap(dir = cwd()) {
9369
9728
  );
9370
9729
  }
9371
9730
 
9731
+ // src/commands/capsule.ts
9732
+ function slug(s) {
9733
+ return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
9734
+ }
9735
+ function runCapsuleExport() {
9736
+ const dir = cwd();
9737
+ const config = requireConfig(dir);
9738
+ intro2("kaddo capsule export");
9739
+ const capsule = buildCapsule(dir, config);
9740
+ const name = slug(config.project.name);
9741
+ const mdPath = join(".kaddo", "exports", `${name}.capsule.md`);
9742
+ const jsonPath = join(".kaddo", "exports", `${name}.capsule.json`);
9743
+ writeFile(join(dir, mdPath), renderCapsuleMarkdown(capsule));
9744
+ writeFile(join(dir, jsonPath), serializeCapsuleJson(capsule));
9745
+ log2.success(`Wrote ${mdPath}`);
9746
+ log2.success(`Wrote ${jsonPath}`);
9747
+ log2.info("Refine it with the capsule-agent before sharing \u2014 and never include secrets or source code.");
9748
+ printCommandFooter("capsule export");
9749
+ outro2("Knowledge Capsule exported.");
9750
+ }
9751
+ function runCapsuleAdd(srcPath) {
9752
+ const dir = cwd();
9753
+ requireConfig(dir);
9754
+ intro2("kaddo capsule add");
9755
+ if (!srcPath) {
9756
+ console.error("Usage: kaddo capsule add <path-to-capsule.md>");
9757
+ process.exit(1);
9758
+ }
9759
+ const abs = join(dir, srcPath);
9760
+ const source = exists(abs) ? abs : srcPath;
9761
+ if (!exists(source)) {
9762
+ console.error(`Capsule not found: ${srcPath}`);
9763
+ process.exit(1);
9764
+ }
9765
+ const result = addExternalCapsule(dir, source);
9766
+ if (!result.isCapsuleType) {
9767
+ log2.warn("This file is not marked as a knowledge-capsule (front matter `type: knowledge-capsule`).");
9768
+ }
9769
+ log2.success(`Imported capsule "${result.id}" \u2192 ${result.destRel}`);
9770
+ log2.success("Registered in .kaddo/external.yml");
9771
+ log2.info('Run `kaddo context` \u2014 the pack now includes an "External Knowledge" section.');
9772
+ printCommandFooter("capsule add");
9773
+ outro2("External capsule registered.");
9774
+ }
9775
+
9372
9776
  // src/index.ts
9373
9777
  var require2 = createRequire(import.meta.url);
9374
9778
  var { version } = require2("../package.json");
@@ -9386,6 +9790,13 @@ program.command("bootstrap").description("Build the initial knowledge base for a
9386
9790
  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) => {
9387
9791
  await runCreate(type ?? "", opts);
9388
9792
  });
9793
+ var capsuleCmd = program.command("capsule").description("Export this project as a Knowledge Capsule, or import an external one as context");
9794
+ capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
9795
+ runCapsuleExport();
9796
+ });
9797
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path5) => {
9798
+ runCapsuleAdd(path5);
9799
+ });
9389
9800
  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) => {
9390
9801
  await runGuard(opts);
9391
9802
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.15.0",
3
+ "version": "3.16.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {