@kaddo/cli 3.16.5 → 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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/index.js +267 -18
  3. 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 slug2 = slugify(title);
4161
- const fileName = `${id}-${slug2}.md`;
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 slug2 = slugify(title);
4202
- const fileName = `${id}-${slug2}.md`;
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 slug2 = slugify(title);
4335
- const fileName = `${id}-${slug2}.md`;
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/knowledge-discovery.ts
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(`/${KNOWLEDGE2}/business/`)) return "Business";
5632
- if (p2.includes(`/${KNOWLEDGE2}/product/`)) return "Product";
5633
- if (p2.includes(`/${KNOWLEDGE2}/tech/`)) return "Tech";
5634
- if (p2.includes(`/${KNOWLEDGE2}/delivery/`)) return "Delivery";
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, KNOWLEDGE2);
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, KNOWLEDGE2, "tech", "decisions"))) acc.Tech.structured = true;
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 KNOWLEDGE3 = `---
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: KNOWLEDGE3
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 slug(s) {
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 = slug(config.project.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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.16.5",
3
+ "version": "3.17.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {