@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.
- package/dist/index.js +444 -54
- 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
|
|
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: ${
|
|
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
|
|
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: ${
|
|
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
|
|
4031
|
-
const fileName = `${id}-${
|
|
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
|
|
4072
|
-
const fileName = `${id}-${
|
|
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
|
|
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: ${
|
|
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
|
|
4205
|
-
const fileName = `${id}-${
|
|
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
|
|
5129
|
-
import { parse as
|
|
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/
|
|
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(`/${
|
|
5309
|
-
if (p2.includes(`/${
|
|
5310
|
-
if (p2.includes(`/${
|
|
5311
|
-
if (p2.includes(`/${
|
|
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,
|
|
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,
|
|
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 } =
|
|
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 } =
|
|
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
|
|
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
|
|
5873
|
-
output.knowledge_summary =
|
|
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
|
|
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
|
|
5958
|
-
const body =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 } =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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,
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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),
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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),
|
|
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
|
});
|