@kaddo/cli 3.15.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 +444 -54
  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":
@@ -1611,6 +1626,20 @@ 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
+ },
1614
1643
  "ownership-agent": {
1615
1644
  agent: "ownership-agent",
1616
1645
  responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
@@ -2932,6 +2961,85 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
2932
2961
  - Affected knowledge is updated.
2933
2962
  - Commit is suggested and awaits human confirmation \u2014 never run automatically.
2934
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
+ `;
2935
3043
  var OWNERSHIP_AGENT = `# Ownership Agent
2936
3044
 
2937
3045
  ## Role
@@ -3108,7 +3216,9 @@ var AGENT_PROMPTS = [
3108
3216
  // Backlog capture (idea → draft / roadmap candidate — VS-050)
3109
3217
  { fileName: "backlog-agent.md", content: BACKLOG_AGENT },
3110
3218
  // Ownership proposals (precise code: globs — VS-052)
3111
- { fileName: "ownership-agent.md", content: OWNERSHIP_AGENT }
3219
+ { fileName: "ownership-agent.md", content: OWNERSHIP_AGENT },
3220
+ // Knowledge Capsule refinement (external context — VS-054)
3221
+ { fileName: "capsule-agent.md", content: CAPSULE_AGENT }
3112
3222
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
3113
3223
  ].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
3114
3224
 
@@ -3123,7 +3233,8 @@ var AGENT_GROUPS = {
3123
3233
  "security-agent.md",
3124
3234
  "standards-agent.md",
3125
3235
  "module-design-agent.md",
3126
- "adr-agent.md"
3236
+ "adr-agent.md",
3237
+ "capsule-agent.md"
3127
3238
  ],
3128
3239
  delivery: [
3129
3240
  "backlog-agent.md",
@@ -3844,7 +3955,7 @@ function formatList(items) {
3844
3955
  return items.map((i) => `- ${i.trim()}`).join("\n");
3845
3956
  }
3846
3957
  function buildFrontMatter(id, type, level, title, answers) {
3847
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3958
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3848
3959
  const lines = [
3849
3960
  "---",
3850
3961
  `type: ${type}`,
@@ -3856,7 +3967,7 @@ function buildFrontMatter(id, type, level, title, answers) {
3856
3967
  `initiative:`,
3857
3968
  `domains: []`,
3858
3969
  `code: []`,
3859
- `created_at: ${today}`,
3970
+ `created_at: ${today2}`,
3860
3971
  `summary: "${answers.problem?.split(".")[0] ?? title}"`,
3861
3972
  "---"
3862
3973
  ];
@@ -3926,7 +4037,7 @@ _What did we learn from this change? Update after completion._
3926
4037
  return sections.join("\n");
3927
4038
  }
3928
4039
  function buildModuleFrontMatter(id, modType, title, answers) {
3929
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4040
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3930
4041
  const extra = modType.extraFrontMatter ?? {};
3931
4042
  const extraLines = Object.entries(extra).map(
3932
4043
  ([k, v]) => `${k}: ${JSON.stringify(v)}`
@@ -3942,7 +4053,7 @@ function buildModuleFrontMatter(id, modType, title, answers) {
3942
4053
  `initiative:`,
3943
4054
  `domains: []`,
3944
4055
  `code: []`,
3945
- `created_at: ${today}`,
4056
+ `created_at: ${today2}`,
3946
4057
  `summary: "${title}"`,
3947
4058
  ...extraLines,
3948
4059
  "---"
@@ -4027,8 +4138,8 @@ async function runCreate(type, opts = {}) {
4027
4138
  answers[question.frontMatterField] = answer.trim();
4028
4139
  }
4029
4140
  const id = nextWorkItemId(dir);
4030
- const slug = slugify(title);
4031
- const fileName = `${id}-${slug}.md`;
4141
+ const slug2 = slugify(title);
4142
+ const fileName = `${id}-${slug2}.md`;
4032
4143
  const filePath = join(dir, DRAFT_DIR, fileName);
4033
4144
  const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
4034
4145
  const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
@@ -4068,8 +4179,8 @@ async function runCreateModule(dir, modType) {
4068
4179
  answers[q.frontMatterField] = answer.trim();
4069
4180
  }
4070
4181
  const id = nextWorkItemId(dir);
4071
- const slug = slugify(title);
4072
- const fileName = `${id}-${slug}.md`;
4182
+ const slug2 = slugify(title);
4183
+ const fileName = `${id}-${slug2}.md`;
4073
4184
  const filePath = join(dir, DRAFT_DIR, fileName);
4074
4185
  const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
4075
4186
  const body = buildModuleBody(modType, title.trim(), answers);
@@ -4091,7 +4202,7 @@ function resolveCandidateLevel(candidate, type) {
4091
4202
  return getLevelForType(type);
4092
4203
  }
4093
4204
  function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4094
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4205
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4095
4206
  const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
4096
4207
  const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
4097
4208
  const lines = [
@@ -4105,7 +4216,7 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
4105
4216
  `initiative: "${initiative.replace(/"/g, "'")}"`,
4106
4217
  `domains: []`,
4107
4218
  `code: []`,
4108
- `created_at: ${today}`,
4219
+ `created_at: ${today2}`,
4109
4220
  `source: roadmap`,
4110
4221
  `source_id: ${candidate.id}`,
4111
4222
  `source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
@@ -4201,8 +4312,8 @@ function buildRoadmapWorkItem(opts) {
4201
4312
  const { id, type, level, candidate } = opts;
4202
4313
  const answers = opts.answers ?? {};
4203
4314
  const title = candidate.title.trim();
4204
- const slug = slugify(title);
4205
- const fileName = `${id}-${slug}.md`;
4315
+ const slug2 = slugify(title);
4316
+ const fileName = `${id}-${slug2}.md`;
4206
4317
  const qualityGate = getLevel(level).qualityGate;
4207
4318
  const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
4208
4319
  const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
@@ -5125,8 +5236,8 @@ function runIgnoreRemove(artifactId) {
5125
5236
  }
5126
5237
 
5127
5238
  // src/commands/explain.ts
5128
- import matter2 from "gray-matter";
5129
- import { parse as parseYaml8 } from "yaml";
5239
+ import matter3 from "gray-matter";
5240
+ import { parse as parseYaml9 } from "yaml";
5130
5241
 
5131
5242
  // src/core/delivery-phase.ts
5132
5243
  function layer(layers, name) {
@@ -5262,8 +5373,201 @@ function assessPhase(input) {
5262
5373
  return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
5263
5374
  }
5264
5375
 
5265
- // 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";
5266
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";
5267
5571
  var CONSOLIDATED_TYPE = {
5268
5572
  Business: "business",
5269
5573
  Product: "product",
@@ -5305,10 +5609,10 @@ function layerForType(type) {
5305
5609
  }
5306
5610
  function layerFromPath(filePath) {
5307
5611
  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";
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";
5312
5616
  return null;
5313
5617
  }
5314
5618
  function basename(p2) {
@@ -5321,7 +5625,7 @@ function discoverLayers(dir) {
5321
5625
  Tech: blank(),
5322
5626
  Delivery: blank()
5323
5627
  };
5324
- const archDir = join(dir, KNOWLEDGE);
5628
+ const archDir = join(dir, KNOWLEDGE2);
5325
5629
  const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
5326
5630
  for (const a of artifacts) {
5327
5631
  const type = a.type;
@@ -5343,7 +5647,7 @@ function discoverLayers(dir) {
5343
5647
  }
5344
5648
  if (type === "adr" || type === "decision") slot.hasDecision = true;
5345
5649
  }
5346
- if (existsDirWithMd(join(dir, KNOWLEDGE, "tech", "decisions"))) acc.Tech.structured = true;
5650
+ if (existsDirWithMd(join(dir, KNOWLEDGE2, "tech", "decisions"))) acc.Tech.structured = true;
5347
5651
  return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
5348
5652
  layer: layer2,
5349
5653
  status: statusFor(layer2, acc[layer2]),
@@ -5591,6 +5895,7 @@ function buildProjectExplanation(dir) {
5591
5895
  ownership,
5592
5896
  domains,
5593
5897
  duplicateWorkItems,
5898
+ externalCapsules: loadExternalCapsules(dir),
5594
5899
  layers,
5595
5900
  roadmap,
5596
5901
  mappedModules,
@@ -5719,6 +6024,17 @@ function renderExplanationHuman(exp) {
5719
6024
  lines.push("- Mapped modules: 0");
5720
6025
  lines.push("");
5721
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
+ }
5722
6038
  if (exp.missingKnowledge.length > 0) {
5723
6039
  lines.push("## Missing Knowledge");
5724
6040
  for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
@@ -5762,7 +6078,7 @@ function readKnowledge(dir) {
5762
6078
  if (!exists(knowledgePath)) return null;
5763
6079
  try {
5764
6080
  const raw = readFile(knowledgePath);
5765
- const { data, content } = matter2(raw);
6081
+ const { data, content } = matter3(raw);
5766
6082
  return { content, data };
5767
6083
  } catch {
5768
6084
  return null;
@@ -5773,7 +6089,7 @@ function readRoadmap(dir) {
5773
6089
  if (!exists(roadmapPath)) return null;
5774
6090
  try {
5775
6091
  const raw = readFile(roadmapPath);
5776
- const { content } = matter2(raw);
6092
+ const { content } = matter3(raw);
5777
6093
  return content.trim();
5778
6094
  } catch {
5779
6095
  return null;
@@ -5783,7 +6099,7 @@ function readConfig(dir) {
5783
6099
  const configPath = join(dir, CONFIG_PATH3);
5784
6100
  if (!exists(configPath)) return {};
5785
6101
  try {
5786
- return parseYaml8(readFile(configPath));
6102
+ return parseYaml9(readFile(configPath));
5787
6103
  } catch {
5788
6104
  return {};
5789
6105
  }
@@ -5869,8 +6185,8 @@ function explainForAgent(dir, artifacts, opts) {
5869
6185
  since: opts.since ?? null
5870
6186
  };
5871
6187
  if (knowledge) {
5872
- const firstParagraph2 = knowledge.content.trim().split("\n\n").find((p2) => p2.trim() && !p2.startsWith("#"));
5873
- 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() ?? "";
5874
6190
  }
5875
6191
  const mappedArtifacts = artifacts.filter((a) => a.type !== "current-state" && a.type !== "roadmap").map((a) => ({
5876
6192
  id: a.id,
@@ -5942,7 +6258,7 @@ function runExplain(opts) {
5942
6258
  }
5943
6259
 
5944
6260
  // src/core/context-pack.ts
5945
- import matter3 from "gray-matter";
6261
+ import matter4 from "gray-matter";
5946
6262
  var CONTEXT_PACK_VERSION = "1";
5947
6263
  var ARCH_DIR6 = "knowledge";
5948
6264
  function readScanJson(dir) {
@@ -5954,8 +6270,8 @@ function readScanJson(dir) {
5954
6270
  return null;
5955
6271
  }
5956
6272
  }
5957
- function firstParagraph(markdown) {
5958
- const body = matter3(markdown).content.trim();
6273
+ function firstParagraph2(markdown) {
6274
+ const body = matter4(markdown).content.trim();
5959
6275
  const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
5960
6276
  return para ?? "";
5961
6277
  }
@@ -5963,7 +6279,7 @@ function readMarkdownSummary(dir, file) {
5963
6279
  const p2 = join(dir, ARCH_DIR6, file);
5964
6280
  if (!exists(p2)) return null;
5965
6281
  try {
5966
- return firstParagraph(readFile(p2));
6282
+ return firstParagraph2(readFile(p2));
5967
6283
  } catch {
5968
6284
  return null;
5969
6285
  }
@@ -6125,6 +6441,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
6125
6441
  roadmap,
6126
6442
  phase,
6127
6443
  deliveryMix,
6444
+ external: loadExternalCapsules(dir),
6128
6445
  mappedModules,
6129
6446
  missing,
6130
6447
  // VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
@@ -6275,6 +6592,21 @@ function renderContextPack(pack) {
6275
6592
  "Note: Kaddo does not scan secondary repositories during `context`. Mapped modules come from `.kaddo/modules.yml` and module artifacts only.\n"
6276
6593
  );
6277
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
+ }
6278
6610
  parts.push("## Missing Context\n");
6279
6611
  if (missing.length > 0) {
6280
6612
  parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
@@ -6490,7 +6822,7 @@ function renderUnderstandTerminal(plan) {
6490
6822
  }
6491
6823
 
6492
6824
  // src/core/delivery.ts
6493
- import { parse as parseYaml9 } from "yaml";
6825
+ import { parse as parseYaml10 } from "yaml";
6494
6826
  function slugify2(s) {
6495
6827
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6496
6828
  }
@@ -6596,6 +6928,14 @@ function runUnderstand() {
6596
6928
  if (assessment.nextStep) {
6597
6929
  console.log(`Next step: ${assessment.nextStep}`);
6598
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
+ }
6599
6939
  const active = activeWorkItems(dir);
6600
6940
  if (active.length > 0) {
6601
6941
  console.log("");
@@ -6829,14 +7169,14 @@ async function runClassify(opts = {}) {
6829
7169
  }
6830
7170
 
6831
7171
  // src/commands/status.ts
6832
- import { parse as parseYaml10 } from "yaml";
7172
+ import { parse as parseYaml11 } from "yaml";
6833
7173
  var ARCH_DIR8 = "knowledge";
6834
7174
  var CONFIG_PATH4 = ".kaddo/config.yml";
6835
7175
  function loadConfig3(dir) {
6836
7176
  const p2 = join(dir, CONFIG_PATH4);
6837
7177
  if (!exists(p2)) return {};
6838
7178
  try {
6839
- return parseYaml10(readFile(p2));
7179
+ return parseYaml11(readFile(p2));
6840
7180
  } catch {
6841
7181
  return {};
6842
7182
  }
@@ -6893,7 +7233,7 @@ function runStatus() {
6893
7233
  }
6894
7234
 
6895
7235
  // src/commands/learn.ts
6896
- import matter4 from "gray-matter";
7236
+ import matter5 from "gray-matter";
6897
7237
  var ARCH_DIR9 = "knowledge";
6898
7238
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
6899
7239
  function findWorkItemFile(dir, id) {
@@ -6905,7 +7245,7 @@ function findWorkItemFile(dir, id) {
6905
7245
  }
6906
7246
  function updateWorkItemFile(filePath, learning) {
6907
7247
  const raw = readFile(filePath);
6908
- const { data, content } = matter4(raw);
7248
+ const { data, content } = matter5(raw);
6909
7249
  data.status = "done";
6910
7250
  data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6911
7251
  let updatedContent = content;
@@ -6930,7 +7270,7 @@ ${learning.trim()}
6930
7270
  ${learning.trim()}
6931
7271
  `;
6932
7272
  }
6933
- const newRaw = matter4.stringify(updatedContent, data);
7273
+ const newRaw = matter5.stringify(updatedContent, data);
6934
7274
  writeFile(filePath, newRaw);
6935
7275
  }
6936
7276
  async function runLearn(artifactId) {
@@ -7038,13 +7378,13 @@ function runHistory(opts = {}) {
7038
7378
  }
7039
7379
 
7040
7380
  // src/commands/add.ts
7041
- import { parse as parseYaml11, stringify as stringifyYaml3 } from "yaml";
7381
+ import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
7042
7382
  var CONFIG_PATH5 = ".kaddo/config.yml";
7043
7383
  function readProjectState(dir) {
7044
7384
  const configPath = join(dir, CONFIG_PATH5);
7045
7385
  if (!exists(configPath)) return void 0;
7046
7386
  try {
7047
- const config = parseYaml11(readFile(configPath));
7387
+ const config = parseYaml12(readFile(configPath));
7048
7388
  return config.project?.state;
7049
7389
  } catch {
7050
7390
  return void 0;
@@ -7062,12 +7402,12 @@ function markModuleInstalled(dir, configKey, moduleName) {
7062
7402
  const configPath = join(dir, CONFIG_PATH5);
7063
7403
  if (!exists(configPath)) return;
7064
7404
  try {
7065
- const config = parseYaml11(readFile(configPath));
7405
+ const config = parseYaml12(readFile(configPath));
7066
7406
  config[configKey] = { installed: true, installed_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
7067
7407
  const modules = config.modules ?? [];
7068
7408
  if (!modules.includes(moduleName)) modules.push(moduleName);
7069
7409
  config.modules = modules;
7070
- writeFile(configPath, stringifyYaml3(config));
7410
+ writeFile(configPath, stringifyYaml4(config));
7071
7411
  } catch {
7072
7412
  }
7073
7413
  }
@@ -7075,7 +7415,7 @@ function isModuleInstalled(dir, configKey) {
7075
7415
  const configPath = join(dir, CONFIG_PATH5);
7076
7416
  if (!exists(configPath)) return false;
7077
7417
  try {
7078
- const config = parseYaml11(readFile(configPath));
7418
+ const config = parseYaml12(readFile(configPath));
7079
7419
  const moduleConfig = config[configKey];
7080
7420
  return moduleConfig?.installed === true;
7081
7421
  } catch {
@@ -7158,7 +7498,7 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
7158
7498
  }
7159
7499
 
7160
7500
  // src/core/ownership-suggest.ts
7161
- import matter5 from "gray-matter";
7501
+ import matter6 from "gray-matter";
7162
7502
  var SCAN_PATH = ".kaddo/scan.json";
7163
7503
  function normalizeGlob(input) {
7164
7504
  let g = input.trim().replace(/\\/g, "/");
@@ -7280,11 +7620,11 @@ function suggestGlobs(artifact, signals) {
7280
7620
  return [...new Set(out)];
7281
7621
  }
7282
7622
  function applyOwnership(raw, globs, mode = "replace") {
7283
- const parsed = matter5(raw);
7623
+ const parsed = matter6(raw);
7284
7624
  const existing = toStringArray3(parsed.data.code);
7285
7625
  const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
7286
7626
  const data = { ...parsed.data, code: next };
7287
- return matter5.stringify(parsed.content, data);
7627
+ return matter6.stringify(parsed.content, data);
7288
7628
  }
7289
7629
 
7290
7630
  // src/commands/owners.ts
@@ -7458,19 +7798,19 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
7458
7798
  }
7459
7799
 
7460
7800
  // src/commands/module-descriptor.ts
7461
- import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
7801
+ import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
7462
7802
  var DESCRIPTOR_PATH2 = "knowledge/module.yml";
7463
7803
  function readDescriptor(dir) {
7464
7804
  const path5 = join(dir, DESCRIPTOR_PATH2);
7465
7805
  if (!exists(path5)) return null;
7466
7806
  try {
7467
- return parseYaml12(readFile(path5));
7807
+ return parseYaml13(readFile(path5));
7468
7808
  } catch {
7469
7809
  return null;
7470
7810
  }
7471
7811
  }
7472
7812
  function writeDescriptor(dir, descriptor) {
7473
- writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml4(descriptor));
7813
+ writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml5(descriptor));
7474
7814
  }
7475
7815
  async function runModuleDescriptor(opts) {
7476
7816
  const dir = cwd();
@@ -7565,7 +7905,7 @@ function printDescriptor(d) {
7565
7905
  }
7566
7906
 
7567
7907
  // src/commands/modules-map.ts
7568
- import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
7908
+ import { parse as parseYaml14, stringify as stringifyYaml6 } from "yaml";
7569
7909
 
7570
7910
  // src/templates/registry.ts
7571
7911
  var QUALITY = "## Quality checklist";
@@ -7708,7 +8048,7 @@ ${QUALITY}
7708
8048
  - [ ] Capabilities describe outcomes, not implementation.
7709
8049
  - [ ] Each capability cites evidence or is flagged as an assumption.
7710
8050
  `;
7711
- var KNOWLEDGE2 = `---
8051
+ var KNOWLEDGE3 = `---
7712
8052
  type: current-state
7713
8053
  updated_at: YYYY-MM-DD
7714
8054
  ---
@@ -8718,7 +9058,7 @@ var KADDO_TEMPLATES = [
8718
9058
  description: "What is true about the product right now.",
8719
9059
  whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
8720
9060
  relatedCommand: "kaddo init",
8721
- content: KNOWLEDGE2
9061
+ content: KNOWLEDGE3
8722
9062
  },
8723
9063
  // business / product (bootstrap — consolidated, minimal)
8724
9064
  {
@@ -9072,14 +9412,14 @@ function readModulesDescriptor(dir) {
9072
9412
  const path5 = join(dir, DESCRIPTOR_PATH3);
9073
9413
  if (!exists(path5)) return { version: 1, modules: [] };
9074
9414
  try {
9075
- const parsed = parseYaml13(readFile(path5));
9415
+ const parsed = parseYaml14(readFile(path5));
9076
9416
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
9077
9417
  } catch {
9078
9418
  return { version: 1, modules: [] };
9079
9419
  }
9080
9420
  }
9081
9421
  function writeModulesDescriptor(dir, descriptor) {
9082
- writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml5(descriptor));
9422
+ writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml6(descriptor));
9083
9423
  }
9084
9424
  function moduleDir(id) {
9085
9425
  return `knowledge/tech/modules/${id}`;
@@ -9369,6 +9709,49 @@ async function runBootstrap(dir = cwd()) {
9369
9709
  );
9370
9710
  }
9371
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
+
9372
9755
  // src/index.ts
9373
9756
  var require2 = createRequire(import.meta.url);
9374
9757
  var { version } = require2("../package.json");
@@ -9386,6 +9769,13 @@ program.command("bootstrap").description("Build the initial knowledge base for a
9386
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) => {
9387
9770
  await runCreate(type ?? "", opts);
9388
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
+ });
9389
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) => {
9390
9780
  await runGuard(opts);
9391
9781
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {