@kaddo/cli 3.16.4 → 3.17.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/README.md +21 -0
- package/dist/index.js +267 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -437,6 +437,26 @@ contracts, risks, ADRs, owners — never source code or secrets); refine it with
|
|
|
437
437
|
the capsules (warning when one looks stale). See the
|
|
438
438
|
[Knowledge Capsules guide](https://kaddo.trycatch.tv/knowledge-capsules/).
|
|
439
439
|
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### `kaddo graph export`
|
|
443
|
+
|
|
444
|
+
Export the connections that already exist between your knowledge artifacts as a **lightweight,
|
|
445
|
+
file-based knowledge graph** — for onboarding, impact analysis and context selection.
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
kaddo graph export # → .kaddo/graph.json + .kaddo/graph.mmd
|
|
449
|
+
kaddo graph export --scope all # include every artifact (default: active)
|
|
450
|
+
kaddo graph export --format mermaid # mermaid only (or --format json)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Nodes come from knowledge layers, Work Items, code globs, capabilities, ADRs and Knowledge
|
|
454
|
+
Capsules; edges come from front matter (`code`, `capabilities`, `decisions`, `source_id`,
|
|
455
|
+
`source_initiative`) and the external registry. It never reads `src/`, never reads source code
|
|
456
|
+
and never calls an LLM. `kaddo explain` and `kaddo context` show a graph **summary** once it has
|
|
457
|
+
been exported (they never generate it). See the
|
|
458
|
+
[Knowledge Graph Export guide](https://kaddo.trycatch.tv/knowledge-graph-export/).
|
|
459
|
+
|
|
440
460
|
## Roadmap
|
|
441
461
|
|
|
442
462
|
The full knowledge loop ships today: `scan → context → agents → understand → roadmap →
|
|
@@ -478,6 +498,7 @@ create --from roadmap → owners → guard → explain`.
|
|
|
478
498
|
| v3.14 | Project knowledge language (`project.language: en\|es`): knowledge in your language, CLI stays English; all agents respect it |
|
|
479
499
|
| 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
500
|
| v3.16 | Knowledge Capsules: `kaddo capsule export/add`, External Knowledge in context/explain, new `capsule-agent` |
|
|
501
|
+
| v3.17 | Knowledge Graph Export: `kaddo graph export` (`.kaddo/graph.json` + `.mmd`, `--scope`/`--format`); graph summary in context/explain |
|
|
481
502
|
|
|
482
503
|
**Optional modules (installed with `kaddo add`):**
|
|
483
504
|
|
package/dist/index.js
CHANGED
|
@@ -680,6 +680,10 @@ var COMMAND_HELP = {
|
|
|
680
680
|
"capsule add": {
|
|
681
681
|
question: "How do I consume another system as external context?",
|
|
682
682
|
next: "kaddo context (the pack now includes External Knowledge)"
|
|
683
|
+
},
|
|
684
|
+
"graph export": {
|
|
685
|
+
question: "How is my project knowledge connected?",
|
|
686
|
+
next: "Open .kaddo/graph.mmd, or run kaddo explain for a summary"
|
|
683
687
|
}
|
|
684
688
|
};
|
|
685
689
|
function commandFooterLines(name) {
|
|
@@ -4157,8 +4161,8 @@ async function runCreate(type, opts = {}) {
|
|
|
4157
4161
|
answers[question.frontMatterField] = answer.trim();
|
|
4158
4162
|
}
|
|
4159
4163
|
const id = nextWorkItemId(dir);
|
|
4160
|
-
const
|
|
4161
|
-
const fileName = `${id}-${
|
|
4164
|
+
const slug3 = slugify(title);
|
|
4165
|
+
const fileName = `${id}-${slug3}.md`;
|
|
4162
4166
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
4163
4167
|
const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
|
|
4164
4168
|
const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
|
|
@@ -4198,8 +4202,8 @@ async function runCreateModule(dir, modType) {
|
|
|
4198
4202
|
answers[q.frontMatterField] = answer.trim();
|
|
4199
4203
|
}
|
|
4200
4204
|
const id = nextWorkItemId(dir);
|
|
4201
|
-
const
|
|
4202
|
-
const fileName = `${id}-${
|
|
4205
|
+
const slug3 = slugify(title);
|
|
4206
|
+
const fileName = `${id}-${slug3}.md`;
|
|
4203
4207
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
4204
4208
|
const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
|
|
4205
4209
|
const body = buildModuleBody(modType, title.trim(), answers);
|
|
@@ -4331,8 +4335,8 @@ function buildRoadmapWorkItem(opts) {
|
|
|
4331
4335
|
const { id, type, level, candidate } = opts;
|
|
4332
4336
|
const answers = opts.answers ?? {};
|
|
4333
4337
|
const title = candidate.title.trim();
|
|
4334
|
-
const
|
|
4335
|
-
const fileName = `${id}-${
|
|
4338
|
+
const slug3 = slugify(title);
|
|
4339
|
+
const fileName = `${id}-${slug3}.md`;
|
|
4336
4340
|
const qualityGate = getLevel(level).qualityGate;
|
|
4337
4341
|
const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
|
|
4338
4342
|
const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
|
|
@@ -4480,7 +4484,8 @@ function parseArtifact(filePath, raw) {
|
|
|
4480
4484
|
phase: String(data.phase ?? ""),
|
|
4481
4485
|
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4482
4486
|
source: data.source ? String(data.source) : "",
|
|
4483
|
-
sourceId: String(data.source_id ?? "")
|
|
4487
|
+
sourceId: String(data.source_id ?? ""),
|
|
4488
|
+
decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : []
|
|
4484
4489
|
};
|
|
4485
4490
|
} catch {
|
|
4486
4491
|
return null;
|
|
@@ -5585,8 +5590,194 @@ function loadExternalCapsules(dir) {
|
|
|
5585
5590
|
return out;
|
|
5586
5591
|
}
|
|
5587
5592
|
|
|
5588
|
-
// src/core/
|
|
5593
|
+
// src/core/graph.ts
|
|
5589
5594
|
var KNOWLEDGE2 = "knowledge";
|
|
5595
|
+
function toPosix2(p2) {
|
|
5596
|
+
return p2.replace(/\\/g, "/");
|
|
5597
|
+
}
|
|
5598
|
+
function slug(s) {
|
|
5599
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5600
|
+
}
|
|
5601
|
+
function isAdr(a) {
|
|
5602
|
+
return toPosix2(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
5603
|
+
}
|
|
5604
|
+
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
5605
|
+
const scope = opts.scope ?? "active";
|
|
5606
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
5607
|
+
const edges = [];
|
|
5608
|
+
const edgeKeys = /* @__PURE__ */ new Set();
|
|
5609
|
+
const addNode = (node) => {
|
|
5610
|
+
if (!nodes.has(node.id)) nodes.set(node.id, node);
|
|
5611
|
+
};
|
|
5612
|
+
const addEdge = (from, to, type) => {
|
|
5613
|
+
const key = `${from}|${to}|${type}`;
|
|
5614
|
+
if (edgeKeys.has(key)) return;
|
|
5615
|
+
edgeKeys.add(key);
|
|
5616
|
+
edges.push({ from, to, type });
|
|
5617
|
+
};
|
|
5618
|
+
const layerDocs = [
|
|
5619
|
+
{ id: "business:business", type: "business", label: "Business", files: ["business/business.md"] },
|
|
5620
|
+
{ id: "product:product", type: "product", label: "Product", files: ["product/product.md", "product/capabilities.md"] },
|
|
5621
|
+
{ id: "tech:tech", type: "tech", label: "Tech", files: ["tech/current-state.md", "tech/codebase.md"] },
|
|
5622
|
+
{ id: "delivery:delivery", type: "delivery", label: "Delivery", files: ["delivery/roadmap.md"] }
|
|
5623
|
+
];
|
|
5624
|
+
const presentLayers = [];
|
|
5625
|
+
for (const layer2 of layerDocs) {
|
|
5626
|
+
const path5 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
5627
|
+
if (path5) {
|
|
5628
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path5 });
|
|
5629
|
+
presentLayers.push(layer2.id);
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
for (let i = 0; i < presentLayers.length - 1; i++) {
|
|
5633
|
+
addEdge(presentLayers[i], presentLayers[i + 1], "informs");
|
|
5634
|
+
}
|
|
5635
|
+
const all = discoverKnowledge(dir);
|
|
5636
|
+
const workItems = all.filter((a) => a.isWorkItem);
|
|
5637
|
+
const selectedWIs = scope === "active" ? workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)) : workItems;
|
|
5638
|
+
for (const wi of selectedWIs) {
|
|
5639
|
+
const id = wi.id || wi.title;
|
|
5640
|
+
const wiNodeId = `wi:${id}`;
|
|
5641
|
+
addNode({
|
|
5642
|
+
id: wiNodeId,
|
|
5643
|
+
type: "work-item",
|
|
5644
|
+
label: `${id} ${wi.title}`.trim(),
|
|
5645
|
+
path: wi.relPath,
|
|
5646
|
+
status: wi.lifecycle,
|
|
5647
|
+
knowledge_level: wi.knowledgeLevel || void 0
|
|
5648
|
+
});
|
|
5649
|
+
for (const glob of wi.codeGlobs) {
|
|
5650
|
+
const codeId = `code:${glob}`;
|
|
5651
|
+
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
5652
|
+
addEdge(wiNodeId, codeId, "owns");
|
|
5653
|
+
}
|
|
5654
|
+
for (const cap of wi.capabilities) {
|
|
5655
|
+
const capId = `capability:${slug(cap) || cap}`;
|
|
5656
|
+
addNode({ id: capId, type: "capability", label: cap });
|
|
5657
|
+
addEdge(wiNodeId, capId, "implements");
|
|
5658
|
+
}
|
|
5659
|
+
for (const dec of wi.decisions) {
|
|
5660
|
+
const adrId = `adr:${dec}`;
|
|
5661
|
+
addNode({ id: adrId, type: "decision", label: dec });
|
|
5662
|
+
addEdge(wiNodeId, adrId, "depends_on");
|
|
5663
|
+
}
|
|
5664
|
+
if (wi.initiative) {
|
|
5665
|
+
const initId = `initiative:${slug(wi.initiative) || wi.initiative}`;
|
|
5666
|
+
addNode({ id: initId, type: "initiative", label: wi.initiative });
|
|
5667
|
+
addEdge(wiNodeId, initId, "belongs_to");
|
|
5668
|
+
}
|
|
5669
|
+
if (wi.source === "roadmap" && wi.sourceId) {
|
|
5670
|
+
const candId = `candidate:${wi.sourceId}`;
|
|
5671
|
+
addNode({ id: candId, type: "roadmap-candidate", label: wi.sourceId });
|
|
5672
|
+
addEdge(candId, wiNodeId, "materialized_as");
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
for (const adr of all.filter(isAdr)) {
|
|
5676
|
+
const adrId = `adr:${adr.id || adr.title}`;
|
|
5677
|
+
const referenced = nodes.has(adrId);
|
|
5678
|
+
if (scope === "all" || referenced) {
|
|
5679
|
+
nodes.set(adrId, {
|
|
5680
|
+
id: adrId,
|
|
5681
|
+
type: "decision",
|
|
5682
|
+
label: `${adr.id} ${adr.title}`.trim() || adr.id || adr.title,
|
|
5683
|
+
path: adr.relPath
|
|
5684
|
+
});
|
|
5685
|
+
for (const glob of adr.codeGlobs) {
|
|
5686
|
+
const codeId = `code:${glob}`;
|
|
5687
|
+
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
5688
|
+
addEdge(adrId, codeId, "governs");
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
const capsules = loadExternalRegistry(dir);
|
|
5693
|
+
if (capsules.length > 0) {
|
|
5694
|
+
const projId = `project:${slug(config.project.name) || "project"}`;
|
|
5695
|
+
addNode({ id: projId, type: "project", label: config.project.name });
|
|
5696
|
+
for (const cap of capsules) {
|
|
5697
|
+
const capId = `capsule:${cap.id}`;
|
|
5698
|
+
addNode({ id: capId, type: "knowledge-capsule", label: cap.id, path: cap.path });
|
|
5699
|
+
addEdge(capId, projId, "provides_external_context");
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
return {
|
|
5703
|
+
generated_at: now.toISOString(),
|
|
5704
|
+
project: {
|
|
5705
|
+
name: config.project.name,
|
|
5706
|
+
state: config.project.state,
|
|
5707
|
+
structure: config.project.structure
|
|
5708
|
+
},
|
|
5709
|
+
nodes: [...nodes.values()],
|
|
5710
|
+
edges
|
|
5711
|
+
};
|
|
5712
|
+
}
|
|
5713
|
+
var RELATIONSHIP_EDGES = /* @__PURE__ */ new Set([
|
|
5714
|
+
"belongs_to",
|
|
5715
|
+
"materialized_as",
|
|
5716
|
+
"owns",
|
|
5717
|
+
"implements",
|
|
5718
|
+
"depends_on",
|
|
5719
|
+
"governs",
|
|
5720
|
+
"provides_external_context"
|
|
5721
|
+
]);
|
|
5722
|
+
function graphIsSparse(graph) {
|
|
5723
|
+
return !graph.edges.some((e) => RELATIONSHIP_EDGES.has(e.type));
|
|
5724
|
+
}
|
|
5725
|
+
function serializeGraphJson(graph) {
|
|
5726
|
+
return JSON.stringify(graph, null, 2) + "\n";
|
|
5727
|
+
}
|
|
5728
|
+
function renderGraphMermaid(graph) {
|
|
5729
|
+
const safeIds = /* @__PURE__ */ new Map();
|
|
5730
|
+
const used = /* @__PURE__ */ new Set();
|
|
5731
|
+
const safe = (id) => {
|
|
5732
|
+
const existing = safeIds.get(id);
|
|
5733
|
+
if (existing) return existing;
|
|
5734
|
+
let base = id.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
5735
|
+
if (!base) base = "n";
|
|
5736
|
+
let candidate = base;
|
|
5737
|
+
let i = 1;
|
|
5738
|
+
while (used.has(candidate)) candidate = `${base}_${i++}`;
|
|
5739
|
+
used.add(candidate);
|
|
5740
|
+
safeIds.set(id, candidate);
|
|
5741
|
+
return candidate;
|
|
5742
|
+
};
|
|
5743
|
+
const escapeLabel = (s) => s.replace(/"/g, "'");
|
|
5744
|
+
const lines = ["flowchart LR"];
|
|
5745
|
+
for (const node of graph.nodes) {
|
|
5746
|
+
lines.push(` ${safe(node.id)}["${escapeLabel(node.label)}"]`);
|
|
5747
|
+
}
|
|
5748
|
+
if (graph.edges.length > 0) lines.push("");
|
|
5749
|
+
for (const edge of graph.edges) {
|
|
5750
|
+
lines.push(` ${safe(edge.from)} -->|${edge.type}| ${safe(edge.to)}`);
|
|
5751
|
+
}
|
|
5752
|
+
return lines.join("\n") + "\n";
|
|
5753
|
+
}
|
|
5754
|
+
var ACTIVE_WI_STATES = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
5755
|
+
function loadGraphSummary(dir) {
|
|
5756
|
+
const p2 = join(dir, ".kaddo", "graph.json");
|
|
5757
|
+
if (!exists(p2)) return null;
|
|
5758
|
+
try {
|
|
5759
|
+
const graph = JSON.parse(readFile(p2));
|
|
5760
|
+
const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
|
|
5761
|
+
const edges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
5762
|
+
const activeWiIds = new Set(
|
|
5763
|
+
nodes.filter((n) => n.type === "work-item" && (!n.status || ACTIVE_WI_STATES.has(n.status))).map((n) => n.id)
|
|
5764
|
+
);
|
|
5765
|
+
const connected = new Set(
|
|
5766
|
+
edges.filter((e) => e.type === "owns" && activeWiIds.has(e.from)).map((e) => e.from)
|
|
5767
|
+
);
|
|
5768
|
+
return {
|
|
5769
|
+
generatedAt: String(graph.generated_at ?? ""),
|
|
5770
|
+
nodes: nodes.length,
|
|
5771
|
+
edges: edges.length,
|
|
5772
|
+
activeWorkItemsConnectedToCode: connected.size
|
|
5773
|
+
};
|
|
5774
|
+
} catch {
|
|
5775
|
+
return null;
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
|
|
5779
|
+
// src/core/knowledge-discovery.ts
|
|
5780
|
+
var KNOWLEDGE3 = "knowledge";
|
|
5590
5781
|
var CONSOLIDATED_TYPE = {
|
|
5591
5782
|
Business: "business",
|
|
5592
5783
|
Product: "product",
|
|
@@ -5628,10 +5819,10 @@ function layerForType(type) {
|
|
|
5628
5819
|
}
|
|
5629
5820
|
function layerFromPath(filePath) {
|
|
5630
5821
|
const p2 = filePath.replace(/\\/g, "/");
|
|
5631
|
-
if (p2.includes(`/${
|
|
5632
|
-
if (p2.includes(`/${
|
|
5633
|
-
if (p2.includes(`/${
|
|
5634
|
-
if (p2.includes(`/${
|
|
5822
|
+
if (p2.includes(`/${KNOWLEDGE3}/business/`)) return "Business";
|
|
5823
|
+
if (p2.includes(`/${KNOWLEDGE3}/product/`)) return "Product";
|
|
5824
|
+
if (p2.includes(`/${KNOWLEDGE3}/tech/`)) return "Tech";
|
|
5825
|
+
if (p2.includes(`/${KNOWLEDGE3}/delivery/`)) return "Delivery";
|
|
5635
5826
|
return null;
|
|
5636
5827
|
}
|
|
5637
5828
|
function basename(p2) {
|
|
@@ -5644,7 +5835,7 @@ function discoverLayers(dir) {
|
|
|
5644
5835
|
Tech: blank(),
|
|
5645
5836
|
Delivery: blank()
|
|
5646
5837
|
};
|
|
5647
|
-
const archDir = join(dir,
|
|
5838
|
+
const archDir = join(dir, KNOWLEDGE3);
|
|
5648
5839
|
const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
5649
5840
|
for (const a of artifacts) {
|
|
5650
5841
|
const type = a.type;
|
|
@@ -5666,7 +5857,7 @@ function discoverLayers(dir) {
|
|
|
5666
5857
|
}
|
|
5667
5858
|
if (type === "adr" || type === "decision") slot.hasDecision = true;
|
|
5668
5859
|
}
|
|
5669
|
-
if (existsDirWithMd(join(dir,
|
|
5860
|
+
if (existsDirWithMd(join(dir, KNOWLEDGE3, "tech", "decisions"))) acc.Tech.structured = true;
|
|
5670
5861
|
return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
|
|
5671
5862
|
layer: layer2,
|
|
5672
5863
|
status: statusFor(layer2, acc[layer2]),
|
|
@@ -5915,6 +6106,7 @@ function buildProjectExplanation(dir) {
|
|
|
5915
6106
|
domains,
|
|
5916
6107
|
duplicateWorkItems,
|
|
5917
6108
|
externalCapsules: loadExternalCapsules(dir),
|
|
6109
|
+
graph: loadGraphSummary(dir),
|
|
5918
6110
|
layers,
|
|
5919
6111
|
roadmap,
|
|
5920
6112
|
mappedModules,
|
|
@@ -6054,6 +6246,13 @@ function renderExplanationHuman(exp) {
|
|
|
6054
6246
|
}
|
|
6055
6247
|
lines.push("");
|
|
6056
6248
|
}
|
|
6249
|
+
if (exp.graph) {
|
|
6250
|
+
lines.push("## Knowledge Graph");
|
|
6251
|
+
lines.push(`- Nodes: ${exp.graph.nodes}`);
|
|
6252
|
+
lines.push(`- Edges: ${exp.graph.edges}`);
|
|
6253
|
+
if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
|
|
6254
|
+
lines.push("");
|
|
6255
|
+
}
|
|
6057
6256
|
if (exp.missingKnowledge.length > 0) {
|
|
6058
6257
|
lines.push("## Missing Knowledge");
|
|
6059
6258
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
@@ -6461,6 +6660,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
6461
6660
|
phase,
|
|
6462
6661
|
deliveryMix,
|
|
6463
6662
|
external: loadExternalCapsules(dir),
|
|
6663
|
+
graph: loadGraphSummary(dir),
|
|
6464
6664
|
mappedModules,
|
|
6465
6665
|
missing,
|
|
6466
6666
|
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
@@ -6626,6 +6826,18 @@ function renderContextPack(pack) {
|
|
|
6626
6826
|
parts.push(lines.join("\n") + "\n");
|
|
6627
6827
|
}
|
|
6628
6828
|
}
|
|
6829
|
+
if (pack.graph) {
|
|
6830
|
+
parts.push("## Knowledge Graph\n");
|
|
6831
|
+
parts.push(
|
|
6832
|
+
[
|
|
6833
|
+
"- Available: yes",
|
|
6834
|
+
`- Nodes: ${pack.graph.nodes}`,
|
|
6835
|
+
`- Edges: ${pack.graph.edges}`,
|
|
6836
|
+
`- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`
|
|
6837
|
+
].join("\n") + "\n"
|
|
6838
|
+
);
|
|
6839
|
+
parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
|
|
6840
|
+
}
|
|
6629
6841
|
parts.push("## Missing Context\n");
|
|
6630
6842
|
if (missing.length > 0) {
|
|
6631
6843
|
parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
|
|
@@ -8067,7 +8279,7 @@ ${QUALITY}
|
|
|
8067
8279
|
- [ ] Capabilities describe outcomes, not implementation.
|
|
8068
8280
|
- [ ] Each capability cites evidence or is flagged as an assumption.
|
|
8069
8281
|
`;
|
|
8070
|
-
var
|
|
8282
|
+
var KNOWLEDGE4 = `---
|
|
8071
8283
|
type: current-state
|
|
8072
8284
|
updated_at: YYYY-MM-DD
|
|
8073
8285
|
---
|
|
@@ -9077,7 +9289,7 @@ var KADDO_TEMPLATES = [
|
|
|
9077
9289
|
description: "What is true about the product right now.",
|
|
9078
9290
|
whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
|
|
9079
9291
|
relatedCommand: "kaddo init",
|
|
9080
|
-
content:
|
|
9292
|
+
content: KNOWLEDGE4
|
|
9081
9293
|
},
|
|
9082
9294
|
// business / product (bootstrap — consolidated, minimal)
|
|
9083
9295
|
{
|
|
@@ -9729,7 +9941,7 @@ async function runBootstrap(dir = cwd()) {
|
|
|
9729
9941
|
}
|
|
9730
9942
|
|
|
9731
9943
|
// src/commands/capsule.ts
|
|
9732
|
-
function
|
|
9944
|
+
function slug2(s) {
|
|
9733
9945
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
9734
9946
|
}
|
|
9735
9947
|
function runCapsuleExport() {
|
|
@@ -9737,7 +9949,7 @@ function runCapsuleExport() {
|
|
|
9737
9949
|
const config = requireConfig(dir);
|
|
9738
9950
|
intro2("kaddo capsule export");
|
|
9739
9951
|
const capsule = buildCapsule(dir, config);
|
|
9740
|
-
const name =
|
|
9952
|
+
const name = slug2(config.project.name);
|
|
9741
9953
|
const mdPath = join(".kaddo", "exports", `${name}.capsule.md`);
|
|
9742
9954
|
const jsonPath = join(".kaddo", "exports", `${name}.capsule.json`);
|
|
9743
9955
|
writeFile(join(dir, mdPath), renderCapsuleMarkdown(capsule));
|
|
@@ -9773,6 +9985,39 @@ function runCapsuleAdd(srcPath) {
|
|
|
9773
9985
|
outro2("External capsule registered.");
|
|
9774
9986
|
}
|
|
9775
9987
|
|
|
9988
|
+
// src/commands/graph.ts
|
|
9989
|
+
function runGraphExport(opts = {}) {
|
|
9990
|
+
const dir = cwd();
|
|
9991
|
+
const config = requireConfig(dir);
|
|
9992
|
+
intro2("kaddo graph export");
|
|
9993
|
+
const scope = opts.scope === "all" ? "all" : "active";
|
|
9994
|
+
const format = opts.format;
|
|
9995
|
+
const writeJson = format !== "mermaid";
|
|
9996
|
+
const writeMermaid = format !== "json";
|
|
9997
|
+
const graph = buildGraph(dir, config, { scope });
|
|
9998
|
+
const written = [];
|
|
9999
|
+
if (writeJson) {
|
|
10000
|
+
const rel = join(".kaddo", "graph.json");
|
|
10001
|
+
writeFile(join(dir, rel), serializeGraphJson(graph));
|
|
10002
|
+
written.push(rel.replace(/\\/g, "/"));
|
|
10003
|
+
}
|
|
10004
|
+
if (writeMermaid) {
|
|
10005
|
+
const rel = join(".kaddo", "graph.mmd");
|
|
10006
|
+
writeFile(join(dir, rel), renderGraphMermaid(graph));
|
|
10007
|
+
written.push(rel.replace(/\\/g, "/"));
|
|
10008
|
+
}
|
|
10009
|
+
if (graphIsSparse(graph)) {
|
|
10010
|
+
log2.warn("Knowledge graph exported with limited relationships.");
|
|
10011
|
+
log2.info("Tip: add `code`, `capabilities`, `decisions` or `source_id` front matter to improve graph quality.");
|
|
10012
|
+
} else {
|
|
10013
|
+
log2.success("Knowledge graph exported.");
|
|
10014
|
+
}
|
|
10015
|
+
log2.info(`Scope: ${scope} \xB7 Nodes: ${graph.nodes.length} \xB7 Edges: ${graph.edges.length}`);
|
|
10016
|
+
for (const f of written) log2.info(`- ${f}`);
|
|
10017
|
+
printCommandFooter("graph export");
|
|
10018
|
+
outro2("Knowledge graph ready.");
|
|
10019
|
+
}
|
|
10020
|
+
|
|
9776
10021
|
// src/index.ts
|
|
9777
10022
|
var require2 = createRequire(import.meta.url);
|
|
9778
10023
|
var { version } = require2("../package.json");
|
|
@@ -9797,6 +10042,10 @@ capsuleCmd.command("export").description("Write a Knowledge Capsule about this p
|
|
|
9797
10042
|
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path5) => {
|
|
9798
10043
|
runCapsuleAdd(path5);
|
|
9799
10044
|
});
|
|
10045
|
+
var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
|
|
10046
|
+
graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
|
|
10047
|
+
runGraphExport(opts);
|
|
10048
|
+
});
|
|
9800
10049
|
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) => {
|
|
9801
10050
|
await runGuard(opts);
|
|
9802
10051
|
});
|