@kaddo/cli 3.16.5 → 3.18.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 +27 -0
- package/dist/index.js +617 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -437,6 +437,31 @@ 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).
|
|
458
|
+
|
|
459
|
+
Every export also rates **relationship quality** and writes non-blocking metadata hints
|
|
460
|
+
(`.kaddo/graph-hints.md` + `.json`) — detecting active Work Items without `code`/`capabilities`,
|
|
461
|
+
ADRs without governed `code`, capabilities/capsules with no Work Item link, and more. The
|
|
462
|
+
`graph-agent` turns those hints into precise front matter you confirm and apply. See the
|
|
463
|
+
[Knowledge Graph Export guide](https://kaddo.trycatch.tv/knowledge-graph-export/).
|
|
464
|
+
|
|
440
465
|
## Roadmap
|
|
441
466
|
|
|
442
467
|
The full knowledge loop ships today: `scan → context → agents → understand → roadmap →
|
|
@@ -478,6 +503,8 @@ create --from roadmap → owners → guard → explain`.
|
|
|
478
503
|
| v3.14 | Project knowledge language (`project.language: en\|es`): knowledge in your language, CLI stays English; all agents respect it |
|
|
479
504
|
| 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
505
|
| v3.16 | Knowledge Capsules: `kaddo capsule export/add`, External Knowledge in context/explain, new `capsule-agent` |
|
|
506
|
+
| v3.17 | Knowledge Graph Export: `kaddo graph export` (`.kaddo/graph.json` + `.mmd`, `--scope`/`--format`); graph summary in context/explain |
|
|
507
|
+
| v3.18 | Graph relationship quality & metadata hints: `graph-hints.md`/`.json`, quality levels, `graph-agent`; hints in context/explain/understand |
|
|
481
508
|
|
|
482
509
|
**Optional modules (installed with `kaddo add`):**
|
|
483
510
|
|
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) {
|
|
@@ -1648,6 +1652,19 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
1648
1652
|
],
|
|
1649
1653
|
next: ["kaddo capsule export"]
|
|
1650
1654
|
},
|
|
1655
|
+
"graph-agent": {
|
|
1656
|
+
agent: "graph-agent",
|
|
1657
|
+
responsibleFor: ["Reviewing graph hints", "Proposing precise relationship front matter"],
|
|
1658
|
+
produces: ["proposed front matter (code/capabilities/decisions/source/capsules)"],
|
|
1659
|
+
canSuggest: ["kaddo graph export", "kaddo owners suggest"],
|
|
1660
|
+
cannotSuggest: [
|
|
1661
|
+
"code",
|
|
1662
|
+
"git",
|
|
1663
|
+
"modifying files without confirmation",
|
|
1664
|
+
"inventing relationships"
|
|
1665
|
+
],
|
|
1666
|
+
next: ["kaddo graph export"]
|
|
1667
|
+
},
|
|
1651
1668
|
"ownership-agent": {
|
|
1652
1669
|
agent: "ownership-agent",
|
|
1653
1670
|
responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
|
|
@@ -3203,6 +3220,74 @@ edit the roadmap yourself).
|
|
|
3203
3220
|
- Duplicates, overlaps and dependencies are flagged.
|
|
3204
3221
|
- The response ends with a human-decision handoff \u2014 no agent is auto-executed.
|
|
3205
3222
|
`;
|
|
3223
|
+
var GRAPH_AGENT = `# Graph Agent
|
|
3224
|
+
|
|
3225
|
+
## Role
|
|
3226
|
+
|
|
3227
|
+
You are the Kaddo Graph Agent. Your job is to review the **graph hints** produced by
|
|
3228
|
+
\`kaddo graph export\` and propose **precise relationship front matter** so the knowledge graph
|
|
3229
|
+
becomes more connected and useful.
|
|
3230
|
+
|
|
3231
|
+
You do not write code, you do not modify files, and you never run Git. You propose; the human
|
|
3232
|
+
confirms and edits the artifact front matter, then re-runs \`kaddo graph export\`.
|
|
3233
|
+
|
|
3234
|
+
## When to Use
|
|
3235
|
+
|
|
3236
|
+
Use this agent when \`kaddo graph export\` reports relationship quality \`partial\`, \`sparse\` or
|
|
3237
|
+
\`empty\`, or when \`kaddo understand\` recommends reviewing graph hints during Active Delivery.
|
|
3238
|
+
|
|
3239
|
+
## Input Required
|
|
3240
|
+
|
|
3241
|
+
Provide \`.kaddo/context-pack.md\`, \`.kaddo/graph.json\` and \`.kaddo/graph-hints.md\` as the primary
|
|
3242
|
+
inputs, plus the Work Items under \`knowledge/delivery/work-items/\`, the ADRs under
|
|
3243
|
+
\`knowledge/tech/decisions/\` and \`knowledge/product/capabilities.md\` when they exist.
|
|
3244
|
+
|
|
3245
|
+
## Expected Output
|
|
3246
|
+
|
|
3247
|
+
For each hint, a concrete front matter proposal for the affected artifact, e.g.:
|
|
3248
|
+
|
|
3249
|
+
\`\`\`yaml
|
|
3250
|
+
code:
|
|
3251
|
+
- src/cli/**
|
|
3252
|
+
capabilities:
|
|
3253
|
+
- task-management
|
|
3254
|
+
decisions:
|
|
3255
|
+
- ADR-001
|
|
3256
|
+
\`\`\`
|
|
3257
|
+
|
|
3258
|
+
## Instructions
|
|
3259
|
+
|
|
3260
|
+
1. Work through the hints in \`.kaddo/graph-hints.md\` one artifact at a time.
|
|
3261
|
+
2. Propose only relationships you can justify from existing knowledge \u2014 never invent paths,
|
|
3262
|
+
capabilities, ADRs or capsules.
|
|
3263
|
+
3. Prefer narrow, accurate values (e.g. \`src/payments/**\`, not \`src/**\`).
|
|
3264
|
+
4. Mark uncertain proposals explicitly and ask the human to confirm.
|
|
3265
|
+
5. Tell the human to apply the front matter and re-run \`kaddo graph export\` to verify.
|
|
3266
|
+
|
|
3267
|
+
## Constraints
|
|
3268
|
+
|
|
3269
|
+
- Do **not** modify files \u2014 propose front matter for the human to apply.
|
|
3270
|
+
- Do **not** invent relationships, paths or IDs.
|
|
3271
|
+
- Do **not** read the full source tree; rely on declared knowledge and the inventory.
|
|
3272
|
+
- Do **not** run Git or make commits.
|
|
3273
|
+
|
|
3274
|
+
## Output Format
|
|
3275
|
+
|
|
3276
|
+
Per artifact: the artifact id, the proposed front matter block, and a one-line reason. End with a
|
|
3277
|
+
note to re-run \`kaddo graph export\`.
|
|
3278
|
+
|
|
3279
|
+
## Where to Save the Result
|
|
3280
|
+
|
|
3281
|
+
Nothing is saved automatically. The human edits the affected artifact front matter (Work Items,
|
|
3282
|
+
ADRs) and re-runs \`kaddo graph export\`.
|
|
3283
|
+
|
|
3284
|
+
## Quality Checklist
|
|
3285
|
+
|
|
3286
|
+
- Every proposal maps to a real artifact, path, capability, ADR or capsule.
|
|
3287
|
+
- Globs are narrow and accurate; uncertainty is marked.
|
|
3288
|
+
- No files were modified; no Git was run.
|
|
3289
|
+
- The human is asked to confirm and re-export the graph.
|
|
3290
|
+
`;
|
|
3206
3291
|
var AGENT_PROMPTS = [
|
|
3207
3292
|
{ fileName: "capability-agent.md", content: CAPABILITY_AGENT },
|
|
3208
3293
|
{ fileName: "architecture-agent.md", content: ARCHITECTURE_AGENT },
|
|
@@ -3226,7 +3311,9 @@ var AGENT_PROMPTS = [
|
|
|
3226
3311
|
// Ownership proposals (precise code: globs — VS-052)
|
|
3227
3312
|
{ fileName: "ownership-agent.md", content: OWNERSHIP_AGENT },
|
|
3228
3313
|
// Knowledge Capsule refinement (external context — VS-054)
|
|
3229
|
-
{ fileName: "capsule-agent.md", content: CAPSULE_AGENT }
|
|
3314
|
+
{ fileName: "capsule-agent.md", content: CAPSULE_AGENT },
|
|
3315
|
+
// Graph relationship quality (metadata hints → precise front matter — VS-056)
|
|
3316
|
+
{ fileName: "graph-agent.md", content: GRAPH_AGENT }
|
|
3230
3317
|
// Every official prompt ends with its responsibility boundaries + Agent Trace footer.
|
|
3231
3318
|
].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
|
|
3232
3319
|
|
|
@@ -3242,7 +3329,8 @@ var AGENT_GROUPS = {
|
|
|
3242
3329
|
"standards-agent.md",
|
|
3243
3330
|
"module-design-agent.md",
|
|
3244
3331
|
"adr-agent.md",
|
|
3245
|
-
"capsule-agent.md"
|
|
3332
|
+
"capsule-agent.md",
|
|
3333
|
+
"graph-agent.md"
|
|
3246
3334
|
],
|
|
3247
3335
|
delivery: [
|
|
3248
3336
|
"backlog-agent.md",
|
|
@@ -3387,7 +3475,8 @@ var agentReadme = {
|
|
|
3387
3475
|
"- `standards-agent.md` \u2014 propose lightweight coding/docs/architecture standards.",
|
|
3388
3476
|
"- `stack-agent.md` \u2014 document technologies and stack decisions.",
|
|
3389
3477
|
"- `module-design-agent.md` \u2014 document the design of a mapped module.",
|
|
3390
|
-
"- `capsule-agent.md` \u2014 refine a Knowledge Capsule for external sharing (no secrets/source)."
|
|
3478
|
+
"- `capsule-agent.md` \u2014 refine a Knowledge Capsule for external sharing (no secrets/source).",
|
|
3479
|
+
"- `graph-agent.md` \u2014 review `kaddo graph export` hints and propose precise relationship front matter."
|
|
3391
3480
|
].join("\n")
|
|
3392
3481
|
};
|
|
3393
3482
|
var agentFiles = AGENT_PROMPTS.map((a) => ({
|
|
@@ -4157,8 +4246,8 @@ async function runCreate(type, opts = {}) {
|
|
|
4157
4246
|
answers[question.frontMatterField] = answer.trim();
|
|
4158
4247
|
}
|
|
4159
4248
|
const id = nextWorkItemId(dir);
|
|
4160
|
-
const
|
|
4161
|
-
const fileName = `${id}-${
|
|
4249
|
+
const slug4 = slugify(title);
|
|
4250
|
+
const fileName = `${id}-${slug4}.md`;
|
|
4162
4251
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
4163
4252
|
const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
|
|
4164
4253
|
const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
|
|
@@ -4198,8 +4287,8 @@ async function runCreateModule(dir, modType) {
|
|
|
4198
4287
|
answers[q.frontMatterField] = answer.trim();
|
|
4199
4288
|
}
|
|
4200
4289
|
const id = nextWorkItemId(dir);
|
|
4201
|
-
const
|
|
4202
|
-
const fileName = `${id}-${
|
|
4290
|
+
const slug4 = slugify(title);
|
|
4291
|
+
const fileName = `${id}-${slug4}.md`;
|
|
4203
4292
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
4204
4293
|
const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
|
|
4205
4294
|
const body = buildModuleBody(modType, title.trim(), answers);
|
|
@@ -4331,8 +4420,8 @@ function buildRoadmapWorkItem(opts) {
|
|
|
4331
4420
|
const { id, type, level, candidate } = opts;
|
|
4332
4421
|
const answers = opts.answers ?? {};
|
|
4333
4422
|
const title = candidate.title.trim();
|
|
4334
|
-
const
|
|
4335
|
-
const fileName = `${id}-${
|
|
4423
|
+
const slug4 = slugify(title);
|
|
4424
|
+
const fileName = `${id}-${slug4}.md`;
|
|
4336
4425
|
const qualityGate = getLevel(level).qualityGate;
|
|
4337
4426
|
const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
|
|
4338
4427
|
const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
|
|
@@ -4480,7 +4569,9 @@ function parseArtifact(filePath, raw) {
|
|
|
4480
4569
|
phase: String(data.phase ?? ""),
|
|
4481
4570
|
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4482
4571
|
source: data.source ? String(data.source) : "",
|
|
4483
|
-
sourceId: String(data.source_id ?? "")
|
|
4572
|
+
sourceId: String(data.source_id ?? ""),
|
|
4573
|
+
decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : [],
|
|
4574
|
+
capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : []
|
|
4484
4575
|
};
|
|
4485
4576
|
} catch {
|
|
4486
4577
|
return null;
|
|
@@ -5585,8 +5676,416 @@ function loadExternalCapsules(dir) {
|
|
|
5585
5676
|
return out;
|
|
5586
5677
|
}
|
|
5587
5678
|
|
|
5588
|
-
// src/core/
|
|
5679
|
+
// src/core/graph.ts
|
|
5589
5680
|
var KNOWLEDGE2 = "knowledge";
|
|
5681
|
+
function toPosix2(p2) {
|
|
5682
|
+
return p2.replace(/\\/g, "/");
|
|
5683
|
+
}
|
|
5684
|
+
function slug(s) {
|
|
5685
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5686
|
+
}
|
|
5687
|
+
function isAdr(a) {
|
|
5688
|
+
return toPosix2(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
5689
|
+
}
|
|
5690
|
+
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
5691
|
+
const scope = opts.scope ?? "active";
|
|
5692
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
5693
|
+
const edges = [];
|
|
5694
|
+
const edgeKeys = /* @__PURE__ */ new Set();
|
|
5695
|
+
const addNode = (node) => {
|
|
5696
|
+
if (!nodes.has(node.id)) nodes.set(node.id, node);
|
|
5697
|
+
};
|
|
5698
|
+
const addEdge = (from, to, type) => {
|
|
5699
|
+
const key = `${from}|${to}|${type}`;
|
|
5700
|
+
if (edgeKeys.has(key)) return;
|
|
5701
|
+
edgeKeys.add(key);
|
|
5702
|
+
edges.push({ from, to, type });
|
|
5703
|
+
};
|
|
5704
|
+
const layerDocs = [
|
|
5705
|
+
{ id: "business:business", type: "business", label: "Business", files: ["business/business.md"] },
|
|
5706
|
+
{ id: "product:product", type: "product", label: "Product", files: ["product/product.md", "product/capabilities.md"] },
|
|
5707
|
+
{ id: "tech:tech", type: "tech", label: "Tech", files: ["tech/current-state.md", "tech/codebase.md"] },
|
|
5708
|
+
{ id: "delivery:delivery", type: "delivery", label: "Delivery", files: ["delivery/roadmap.md"] }
|
|
5709
|
+
];
|
|
5710
|
+
const presentLayers = [];
|
|
5711
|
+
for (const layer2 of layerDocs) {
|
|
5712
|
+
const path5 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
5713
|
+
if (path5) {
|
|
5714
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path5 });
|
|
5715
|
+
presentLayers.push(layer2.id);
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
for (let i = 0; i < presentLayers.length - 1; i++) {
|
|
5719
|
+
addEdge(presentLayers[i], presentLayers[i + 1], "informs");
|
|
5720
|
+
}
|
|
5721
|
+
const all = discoverKnowledge(dir);
|
|
5722
|
+
const workItems = all.filter((a) => a.isWorkItem);
|
|
5723
|
+
const selectedWIs = scope === "active" ? workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)) : workItems;
|
|
5724
|
+
for (const wi of selectedWIs) {
|
|
5725
|
+
const id = wi.id || wi.title;
|
|
5726
|
+
const wiNodeId = `wi:${id}`;
|
|
5727
|
+
addNode({
|
|
5728
|
+
id: wiNodeId,
|
|
5729
|
+
type: "work-item",
|
|
5730
|
+
label: `${id} ${wi.title}`.trim(),
|
|
5731
|
+
path: wi.relPath,
|
|
5732
|
+
status: wi.lifecycle,
|
|
5733
|
+
knowledge_level: wi.knowledgeLevel || void 0
|
|
5734
|
+
});
|
|
5735
|
+
for (const glob of wi.codeGlobs) {
|
|
5736
|
+
const codeId = `code:${glob}`;
|
|
5737
|
+
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
5738
|
+
addEdge(wiNodeId, codeId, "owns");
|
|
5739
|
+
}
|
|
5740
|
+
for (const cap of wi.capabilities) {
|
|
5741
|
+
const capId = `capability:${slug(cap) || cap}`;
|
|
5742
|
+
addNode({ id: capId, type: "capability", label: cap });
|
|
5743
|
+
addEdge(wiNodeId, capId, "implements");
|
|
5744
|
+
}
|
|
5745
|
+
for (const dec of wi.decisions) {
|
|
5746
|
+
const adrId = `adr:${dec}`;
|
|
5747
|
+
addNode({ id: adrId, type: "decision", label: dec });
|
|
5748
|
+
addEdge(wiNodeId, adrId, "depends_on");
|
|
5749
|
+
}
|
|
5750
|
+
if (wi.initiative) {
|
|
5751
|
+
const initId = `initiative:${slug(wi.initiative) || wi.initiative}`;
|
|
5752
|
+
addNode({ id: initId, type: "initiative", label: wi.initiative });
|
|
5753
|
+
addEdge(wiNodeId, initId, "belongs_to");
|
|
5754
|
+
}
|
|
5755
|
+
if (wi.source === "roadmap" && wi.sourceId) {
|
|
5756
|
+
const candId = `candidate:${wi.sourceId}`;
|
|
5757
|
+
addNode({ id: candId, type: "roadmap-candidate", label: wi.sourceId });
|
|
5758
|
+
addEdge(candId, wiNodeId, "materialized_as");
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
for (const adr of all.filter(isAdr)) {
|
|
5762
|
+
const adrId = `adr:${adr.id || adr.title}`;
|
|
5763
|
+
const referenced = nodes.has(adrId);
|
|
5764
|
+
if (scope === "all" || referenced) {
|
|
5765
|
+
nodes.set(adrId, {
|
|
5766
|
+
id: adrId,
|
|
5767
|
+
type: "decision",
|
|
5768
|
+
label: `${adr.id} ${adr.title}`.trim() || adr.id || adr.title,
|
|
5769
|
+
path: adr.relPath
|
|
5770
|
+
});
|
|
5771
|
+
for (const glob of adr.codeGlobs) {
|
|
5772
|
+
const codeId = `code:${glob}`;
|
|
5773
|
+
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
5774
|
+
addEdge(adrId, codeId, "governs");
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
const capsules = loadExternalRegistry(dir);
|
|
5779
|
+
if (capsules.length > 0) {
|
|
5780
|
+
const projId = `project:${slug(config.project.name) || "project"}`;
|
|
5781
|
+
addNode({ id: projId, type: "project", label: config.project.name });
|
|
5782
|
+
for (const cap of capsules) {
|
|
5783
|
+
const capId = `capsule:${cap.id}`;
|
|
5784
|
+
addNode({ id: capId, type: "knowledge-capsule", label: cap.id, path: cap.path });
|
|
5785
|
+
addEdge(capId, projId, "provides_external_context");
|
|
5786
|
+
for (const wi of selectedWIs) {
|
|
5787
|
+
if (wi.capsules.includes(cap.id)) {
|
|
5788
|
+
addEdge(`wi:${wi.id || wi.title}`, capId, "uses_external_knowledge");
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
return {
|
|
5794
|
+
generated_at: now.toISOString(),
|
|
5795
|
+
project: {
|
|
5796
|
+
name: config.project.name,
|
|
5797
|
+
state: config.project.state,
|
|
5798
|
+
structure: config.project.structure
|
|
5799
|
+
},
|
|
5800
|
+
nodes: [...nodes.values()],
|
|
5801
|
+
edges
|
|
5802
|
+
};
|
|
5803
|
+
}
|
|
5804
|
+
function serializeGraphJson(graph) {
|
|
5805
|
+
return JSON.stringify(graph, null, 2) + "\n";
|
|
5806
|
+
}
|
|
5807
|
+
function renderGraphMermaid(graph) {
|
|
5808
|
+
const safeIds = /* @__PURE__ */ new Map();
|
|
5809
|
+
const used = /* @__PURE__ */ new Set();
|
|
5810
|
+
const safe = (id) => {
|
|
5811
|
+
const existing = safeIds.get(id);
|
|
5812
|
+
if (existing) return existing;
|
|
5813
|
+
let base = id.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
5814
|
+
if (!base) base = "n";
|
|
5815
|
+
let candidate = base;
|
|
5816
|
+
let i = 1;
|
|
5817
|
+
while (used.has(candidate)) candidate = `${base}_${i++}`;
|
|
5818
|
+
used.add(candidate);
|
|
5819
|
+
safeIds.set(id, candidate);
|
|
5820
|
+
return candidate;
|
|
5821
|
+
};
|
|
5822
|
+
const escapeLabel = (s) => s.replace(/"/g, "'");
|
|
5823
|
+
const lines = ["flowchart LR"];
|
|
5824
|
+
for (const node of graph.nodes) {
|
|
5825
|
+
lines.push(` ${safe(node.id)}["${escapeLabel(node.label)}"]`);
|
|
5826
|
+
}
|
|
5827
|
+
if (graph.edges.length > 0) lines.push("");
|
|
5828
|
+
for (const edge of graph.edges) {
|
|
5829
|
+
lines.push(` ${safe(edge.from)} -->|${edge.type}| ${safe(edge.to)}`);
|
|
5830
|
+
}
|
|
5831
|
+
return lines.join("\n") + "\n";
|
|
5832
|
+
}
|
|
5833
|
+
var ACTIVE_WI_STATES = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
5834
|
+
function loadGraphSummary(dir) {
|
|
5835
|
+
const p2 = join(dir, ".kaddo", "graph.json");
|
|
5836
|
+
if (!exists(p2)) return null;
|
|
5837
|
+
try {
|
|
5838
|
+
const graph = JSON.parse(readFile(p2));
|
|
5839
|
+
const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
|
|
5840
|
+
const edges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
5841
|
+
const activeWiIds = new Set(
|
|
5842
|
+
nodes.filter((n) => n.type === "work-item" && (!n.status || ACTIVE_WI_STATES.has(n.status))).map((n) => n.id)
|
|
5843
|
+
);
|
|
5844
|
+
const connected = new Set(
|
|
5845
|
+
edges.filter((e) => e.type === "owns" && activeWiIds.has(e.from)).map((e) => e.from)
|
|
5846
|
+
);
|
|
5847
|
+
return {
|
|
5848
|
+
generatedAt: String(graph.generated_at ?? ""),
|
|
5849
|
+
nodes: nodes.length,
|
|
5850
|
+
edges: edges.length,
|
|
5851
|
+
activeWorkItemsConnectedToCode: connected.size
|
|
5852
|
+
};
|
|
5853
|
+
} catch {
|
|
5854
|
+
return null;
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
|
|
5858
|
+
// src/core/graph-hints.ts
|
|
5859
|
+
var KNOWLEDGE3 = "knowledge";
|
|
5860
|
+
function toPosix3(p2) {
|
|
5861
|
+
return p2.replace(/\\/g, "/");
|
|
5862
|
+
}
|
|
5863
|
+
function slug2(s) {
|
|
5864
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5865
|
+
}
|
|
5866
|
+
function isAdr2(a) {
|
|
5867
|
+
return toPosix3(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
5868
|
+
}
|
|
5869
|
+
function capabilityHeadings(dir) {
|
|
5870
|
+
const p2 = join(dir, KNOWLEDGE3, "product", "capabilities.md");
|
|
5871
|
+
if (!exists(p2)) return [];
|
|
5872
|
+
return readFile(p2).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|capabilities|capacidades)$/i.test(h));
|
|
5873
|
+
}
|
|
5874
|
+
function humanMissing(field) {
|
|
5875
|
+
switch (field) {
|
|
5876
|
+
case "code":
|
|
5877
|
+
return "code ownership";
|
|
5878
|
+
case "capabilities":
|
|
5879
|
+
return "linked capability";
|
|
5880
|
+
case "decisions":
|
|
5881
|
+
return "linked decision";
|
|
5882
|
+
case "source":
|
|
5883
|
+
return "roadmap link";
|
|
5884
|
+
default:
|
|
5885
|
+
return field;
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5888
|
+
function buildGraphHints(dir, graph, now = /* @__PURE__ */ new Date()) {
|
|
5889
|
+
const artifacts = discoverKnowledge(dir);
|
|
5890
|
+
const workItems = artifacts.filter((a) => a.isWorkItem);
|
|
5891
|
+
const activeWIs = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle));
|
|
5892
|
+
const adrs = artifacts.filter(isAdr2);
|
|
5893
|
+
const capsules = loadExternalRegistry(dir);
|
|
5894
|
+
const hints = [];
|
|
5895
|
+
const referencedCapabilitySlugs = new Set(
|
|
5896
|
+
workItems.flatMap((w) => w.capabilities.map((c) => slug2(c)))
|
|
5897
|
+
);
|
|
5898
|
+
const referencedCapsuleIds = new Set(workItems.flatMap((w) => w.capsules.map((c) => c)));
|
|
5899
|
+
let activeWithoutCode = 0;
|
|
5900
|
+
let activeWithoutCapabilities = 0;
|
|
5901
|
+
let wisWithoutSource = 0;
|
|
5902
|
+
for (const wi of activeWIs) {
|
|
5903
|
+
const id = wi.id || wi.title;
|
|
5904
|
+
const missing = [];
|
|
5905
|
+
const suggested = {};
|
|
5906
|
+
if (wi.codeGlobs.length === 0) {
|
|
5907
|
+
missing.push("code");
|
|
5908
|
+
suggested.code = ["src/<area>/**"];
|
|
5909
|
+
activeWithoutCode++;
|
|
5910
|
+
}
|
|
5911
|
+
if (wi.capabilities.length === 0) {
|
|
5912
|
+
missing.push("capabilities");
|
|
5913
|
+
suggested.capabilities = ["<capability>"];
|
|
5914
|
+
activeWithoutCapabilities++;
|
|
5915
|
+
}
|
|
5916
|
+
if (wi.decisions.length === 0) {
|
|
5917
|
+
missing.push("decisions");
|
|
5918
|
+
suggested.decisions = ["ADR-XXX"];
|
|
5919
|
+
}
|
|
5920
|
+
const hasSource = Boolean(wi.sourceId) || Boolean(wi.initiative);
|
|
5921
|
+
if (!hasSource) {
|
|
5922
|
+
missing.push("source");
|
|
5923
|
+
wisWithoutSource++;
|
|
5924
|
+
}
|
|
5925
|
+
if (missing.length === 0) continue;
|
|
5926
|
+
hints.push({
|
|
5927
|
+
artifact_id: id,
|
|
5928
|
+
artifact_type: "work-item",
|
|
5929
|
+
path: wi.relPath,
|
|
5930
|
+
severity: "info",
|
|
5931
|
+
missing,
|
|
5932
|
+
reason: "Active Work Item has limited graph relationships.",
|
|
5933
|
+
message: `${id} has no ${missing.map(humanMissing).join(", ")}.`,
|
|
5934
|
+
suggested_front_matter: Object.keys(suggested).length > 0 ? suggested : void 0
|
|
5935
|
+
});
|
|
5936
|
+
}
|
|
5937
|
+
let adrsWithoutCode = 0;
|
|
5938
|
+
for (const adr of adrs) {
|
|
5939
|
+
if (adr.codeGlobs.length > 0) continue;
|
|
5940
|
+
adrsWithoutCode++;
|
|
5941
|
+
const id = adr.id || adr.title;
|
|
5942
|
+
hints.push({
|
|
5943
|
+
artifact_id: id,
|
|
5944
|
+
artifact_type: "decision",
|
|
5945
|
+
path: adr.relPath,
|
|
5946
|
+
severity: "info",
|
|
5947
|
+
missing: ["code"],
|
|
5948
|
+
reason: "This ADR defines technical decisions but does not declare which paths it governs.",
|
|
5949
|
+
message: `${id} has no governed code paths.`,
|
|
5950
|
+
suggested_front_matter: { code: ["<path/to/code>"] }
|
|
5951
|
+
});
|
|
5952
|
+
}
|
|
5953
|
+
for (const cap of capabilityHeadings(dir)) {
|
|
5954
|
+
if (referencedCapabilitySlugs.has(slug2(cap))) continue;
|
|
5955
|
+
hints.push({
|
|
5956
|
+
artifact_id: cap,
|
|
5957
|
+
artifact_type: "capability",
|
|
5958
|
+
severity: "info",
|
|
5959
|
+
missing: ["work-item"],
|
|
5960
|
+
reason: "This capability is declared but no Work Item references it yet.",
|
|
5961
|
+
message: `Capability "${cap}" is not linked to any Work Item.`
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
let capsulesWithoutWi = 0;
|
|
5965
|
+
for (const cap of capsules) {
|
|
5966
|
+
if (referencedCapsuleIds.has(cap.id)) continue;
|
|
5967
|
+
capsulesWithoutWi++;
|
|
5968
|
+
hints.push({
|
|
5969
|
+
artifact_id: cap.id,
|
|
5970
|
+
artifact_type: "knowledge-capsule",
|
|
5971
|
+
path: cap.path,
|
|
5972
|
+
severity: "info",
|
|
5973
|
+
missing: ["work-item"],
|
|
5974
|
+
reason: "This Knowledge Capsule is available but no Work Item declares `capsules:` for it.",
|
|
5975
|
+
message: `Knowledge Capsule "${cap.id}" is not linked to any Work Item.`
|
|
5976
|
+
});
|
|
5977
|
+
}
|
|
5978
|
+
const inEdge = /* @__PURE__ */ new Set();
|
|
5979
|
+
for (const e of graph.edges) {
|
|
5980
|
+
inEdge.add(e.from);
|
|
5981
|
+
inEdge.add(e.to);
|
|
5982
|
+
}
|
|
5983
|
+
const nodes = graph.nodes.length;
|
|
5984
|
+
const connected = graph.nodes.filter((n) => inEdge.has(n.id)).length;
|
|
5985
|
+
const relationshipEdges = graph.edges.filter((e) => e.type !== "informs").length;
|
|
5986
|
+
const metrics = {
|
|
5987
|
+
nodes_count: nodes,
|
|
5988
|
+
edges_count: graph.edges.length,
|
|
5989
|
+
connected_nodes_count: connected,
|
|
5990
|
+
isolated_nodes_count: nodes - connected,
|
|
5991
|
+
active_work_items_without_code: activeWithoutCode,
|
|
5992
|
+
active_work_items_without_capabilities: activeWithoutCapabilities,
|
|
5993
|
+
work_items_without_source: wisWithoutSource,
|
|
5994
|
+
adrs_without_code: adrsWithoutCode,
|
|
5995
|
+
capsules_without_related_work_items: capsulesWithoutWi
|
|
5996
|
+
};
|
|
5997
|
+
const quality = assessQuality(nodes, relationshipEdges, hints.length);
|
|
5998
|
+
return {
|
|
5999
|
+
generated_at: now.toISOString(),
|
|
6000
|
+
quality,
|
|
6001
|
+
summary: { nodes, edges: graph.edges.length, hints: hints.length },
|
|
6002
|
+
metrics,
|
|
6003
|
+
hints
|
|
6004
|
+
};
|
|
6005
|
+
}
|
|
6006
|
+
function assessQuality(nodes, relationshipEdges, hintCount) {
|
|
6007
|
+
if (nodes === 0 || relationshipEdges === 0) return "empty";
|
|
6008
|
+
if (relationshipEdges / nodes < 0.25) return "sparse";
|
|
6009
|
+
if (hintCount > 0) return "partial";
|
|
6010
|
+
return "good";
|
|
6011
|
+
}
|
|
6012
|
+
var QUALITY_NOTE = {
|
|
6013
|
+
good: "Most active artifacts have meaningful relationships.",
|
|
6014
|
+
partial: "Some active artifacts have missing relationship metadata.",
|
|
6015
|
+
sparse: "The graph has many nodes but few meaningful edges.",
|
|
6016
|
+
empty: "The graph has almost no relationships."
|
|
6017
|
+
};
|
|
6018
|
+
function yamlBlock(fm) {
|
|
6019
|
+
const lines = ["```yaml"];
|
|
6020
|
+
for (const [k, vals] of Object.entries(fm)) {
|
|
6021
|
+
lines.push(`${k}:`);
|
|
6022
|
+
for (const v of vals) lines.push(` - ${v}`);
|
|
6023
|
+
}
|
|
6024
|
+
lines.push("```");
|
|
6025
|
+
return lines;
|
|
6026
|
+
}
|
|
6027
|
+
function renderGraphHintsMarkdown(report) {
|
|
6028
|
+
const lines = [];
|
|
6029
|
+
lines.push("# Kaddo Graph Hints");
|
|
6030
|
+
lines.push("");
|
|
6031
|
+
lines.push("Generated by `kaddo graph export`. Suggestions only \u2014 Kaddo never edits your artifacts.");
|
|
6032
|
+
lines.push("");
|
|
6033
|
+
lines.push("## Summary");
|
|
6034
|
+
lines.push("");
|
|
6035
|
+
lines.push(`- Relationship quality: ${report.quality} \u2014 ${QUALITY_NOTE[report.quality]}`);
|
|
6036
|
+
lines.push(`- Nodes: ${report.summary.nodes}`);
|
|
6037
|
+
lines.push(`- Edges: ${report.summary.edges}`);
|
|
6038
|
+
lines.push(`- Hints: ${report.summary.hints}`);
|
|
6039
|
+
lines.push("");
|
|
6040
|
+
if (report.hints.length === 0) {
|
|
6041
|
+
lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
|
|
6042
|
+
lines.push("");
|
|
6043
|
+
return lines.join("\n");
|
|
6044
|
+
}
|
|
6045
|
+
lines.push("## Hints");
|
|
6046
|
+
lines.push("");
|
|
6047
|
+
for (const h of report.hints) {
|
|
6048
|
+
lines.push(`### ${h.artifact_id}${h.path ? ` \u2014 \`${h.path}\`` : ""}`);
|
|
6049
|
+
lines.push("");
|
|
6050
|
+
lines.push("Missing metadata:");
|
|
6051
|
+
lines.push("");
|
|
6052
|
+
for (const m of h.missing) lines.push(`- \`${m}\``);
|
|
6053
|
+
lines.push("");
|
|
6054
|
+
if (h.suggested_front_matter) {
|
|
6055
|
+
lines.push("Suggested front matter:");
|
|
6056
|
+
lines.push("");
|
|
6057
|
+
lines.push(...yamlBlock(h.suggested_front_matter));
|
|
6058
|
+
lines.push("");
|
|
6059
|
+
}
|
|
6060
|
+
lines.push(`Reason: ${h.reason}`);
|
|
6061
|
+
lines.push("");
|
|
6062
|
+
}
|
|
6063
|
+
lines.push("> Use the `graph-agent` to turn these hints into precise front matter \u2014 you confirm and apply.");
|
|
6064
|
+
lines.push("");
|
|
6065
|
+
return lines.join("\n");
|
|
6066
|
+
}
|
|
6067
|
+
function serializeGraphHintsJson(report) {
|
|
6068
|
+
return JSON.stringify(report, null, 2) + "\n";
|
|
6069
|
+
}
|
|
6070
|
+
function loadGraphHints(dir) {
|
|
6071
|
+
const p2 = join(dir, ".kaddo", "graph-hints.json");
|
|
6072
|
+
if (!exists(p2)) return null;
|
|
6073
|
+
try {
|
|
6074
|
+
const report = JSON.parse(readFile(p2));
|
|
6075
|
+
const hints = Array.isArray(report.hints) ? report.hints : [];
|
|
6076
|
+
return {
|
|
6077
|
+
quality: report.quality ?? "empty",
|
|
6078
|
+
totalHints: hints.length,
|
|
6079
|
+
activeWorkItemHints: hints.filter((h) => h.artifact_type === "work-item").length,
|
|
6080
|
+
messages: hints.map((h) => h.message).filter(Boolean)
|
|
6081
|
+
};
|
|
6082
|
+
} catch {
|
|
6083
|
+
return null;
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
6086
|
+
|
|
6087
|
+
// src/core/knowledge-discovery.ts
|
|
6088
|
+
var KNOWLEDGE4 = "knowledge";
|
|
5590
6089
|
var CONSOLIDATED_TYPE = {
|
|
5591
6090
|
Business: "business",
|
|
5592
6091
|
Product: "product",
|
|
@@ -5628,10 +6127,10 @@ function layerForType(type) {
|
|
|
5628
6127
|
}
|
|
5629
6128
|
function layerFromPath(filePath) {
|
|
5630
6129
|
const p2 = filePath.replace(/\\/g, "/");
|
|
5631
|
-
if (p2.includes(`/${
|
|
5632
|
-
if (p2.includes(`/${
|
|
5633
|
-
if (p2.includes(`/${
|
|
5634
|
-
if (p2.includes(`/${
|
|
6130
|
+
if (p2.includes(`/${KNOWLEDGE4}/business/`)) return "Business";
|
|
6131
|
+
if (p2.includes(`/${KNOWLEDGE4}/product/`)) return "Product";
|
|
6132
|
+
if (p2.includes(`/${KNOWLEDGE4}/tech/`)) return "Tech";
|
|
6133
|
+
if (p2.includes(`/${KNOWLEDGE4}/delivery/`)) return "Delivery";
|
|
5635
6134
|
return null;
|
|
5636
6135
|
}
|
|
5637
6136
|
function basename(p2) {
|
|
@@ -5644,7 +6143,7 @@ function discoverLayers(dir) {
|
|
|
5644
6143
|
Tech: blank(),
|
|
5645
6144
|
Delivery: blank()
|
|
5646
6145
|
};
|
|
5647
|
-
const archDir = join(dir,
|
|
6146
|
+
const archDir = join(dir, KNOWLEDGE4);
|
|
5648
6147
|
const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
5649
6148
|
for (const a of artifacts) {
|
|
5650
6149
|
const type = a.type;
|
|
@@ -5666,7 +6165,7 @@ function discoverLayers(dir) {
|
|
|
5666
6165
|
}
|
|
5667
6166
|
if (type === "adr" || type === "decision") slot.hasDecision = true;
|
|
5668
6167
|
}
|
|
5669
|
-
if (existsDirWithMd(join(dir,
|
|
6168
|
+
if (existsDirWithMd(join(dir, KNOWLEDGE4, "tech", "decisions"))) acc.Tech.structured = true;
|
|
5670
6169
|
return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
|
|
5671
6170
|
layer: layer2,
|
|
5672
6171
|
status: statusFor(layer2, acc[layer2]),
|
|
@@ -5915,6 +6414,8 @@ function buildProjectExplanation(dir) {
|
|
|
5915
6414
|
domains,
|
|
5916
6415
|
duplicateWorkItems,
|
|
5917
6416
|
externalCapsules: loadExternalCapsules(dir),
|
|
6417
|
+
graph: loadGraphSummary(dir),
|
|
6418
|
+
graphHints: loadGraphHints(dir),
|
|
5918
6419
|
layers,
|
|
5919
6420
|
roadmap,
|
|
5920
6421
|
mappedModules,
|
|
@@ -6054,6 +6555,17 @@ function renderExplanationHuman(exp) {
|
|
|
6054
6555
|
}
|
|
6055
6556
|
lines.push("");
|
|
6056
6557
|
}
|
|
6558
|
+
if (exp.graph) {
|
|
6559
|
+
lines.push("## Knowledge Graph");
|
|
6560
|
+
lines.push(`- Nodes: ${exp.graph.nodes}`);
|
|
6561
|
+
lines.push(`- Edges: ${exp.graph.edges}`);
|
|
6562
|
+
if (exp.graphHints) {
|
|
6563
|
+
lines.push(`- Quality: ${exp.graphHints.quality}`);
|
|
6564
|
+
lines.push(`- Hints: ${exp.graphHints.totalHints}`);
|
|
6565
|
+
}
|
|
6566
|
+
if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
|
|
6567
|
+
lines.push("");
|
|
6568
|
+
}
|
|
6057
6569
|
if (exp.missingKnowledge.length > 0) {
|
|
6058
6570
|
lines.push("## Missing Knowledge");
|
|
6059
6571
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
@@ -6461,6 +6973,8 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
6461
6973
|
phase,
|
|
6462
6974
|
deliveryMix,
|
|
6463
6975
|
external: loadExternalCapsules(dir),
|
|
6976
|
+
graph: loadGraphSummary(dir),
|
|
6977
|
+
graphHints: loadGraphHints(dir),
|
|
6464
6978
|
mappedModules,
|
|
6465
6979
|
missing,
|
|
6466
6980
|
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
@@ -6626,6 +7140,34 @@ function renderContextPack(pack) {
|
|
|
6626
7140
|
parts.push(lines.join("\n") + "\n");
|
|
6627
7141
|
}
|
|
6628
7142
|
}
|
|
7143
|
+
if (pack.graph) {
|
|
7144
|
+
parts.push("## Knowledge Graph\n");
|
|
7145
|
+
parts.push(
|
|
7146
|
+
[
|
|
7147
|
+
"- Available: yes",
|
|
7148
|
+
`- Nodes: ${pack.graph.nodes}`,
|
|
7149
|
+
`- Edges: ${pack.graph.edges}`,
|
|
7150
|
+
`- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`
|
|
7151
|
+
].join("\n") + "\n"
|
|
7152
|
+
);
|
|
7153
|
+
parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
|
|
7154
|
+
}
|
|
7155
|
+
if (pack.graphHints && pack.graphHints.totalHints > 0) {
|
|
7156
|
+
parts.push("## Graph Hints\n");
|
|
7157
|
+
parts.push(
|
|
7158
|
+
[
|
|
7159
|
+
`Graph relationship quality: ${pack.graphHints.quality}`,
|
|
7160
|
+
`Active hints: ${pack.graphHints.activeWorkItemHints}`,
|
|
7161
|
+
"Suggested agent: graph-agent"
|
|
7162
|
+
].join("\n") + "\n"
|
|
7163
|
+
);
|
|
7164
|
+
const shown = pack.graphHints.messages.slice(0, 3);
|
|
7165
|
+
parts.push(shown.map((m) => `- ${m}`).join("\n") + "\n");
|
|
7166
|
+
if (pack.graphHints.totalHints > shown.length) {
|
|
7167
|
+
parts.push(`(+${pack.graphHints.totalHints - shown.length} more in \`.kaddo/graph-hints.md\`)
|
|
7168
|
+
`);
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
6629
7171
|
parts.push("## Missing Context\n");
|
|
6630
7172
|
if (missing.length > 0) {
|
|
6631
7173
|
parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
|
|
@@ -6955,6 +7497,15 @@ function runUnderstand() {
|
|
|
6955
7497
|
}
|
|
6956
7498
|
console.log(" \u2192 Review the relevant capsule before changing integration behavior with it.");
|
|
6957
7499
|
}
|
|
7500
|
+
const graphHints = loadGraphHints(dir);
|
|
7501
|
+
if (assessment.phase === "Active Delivery" && graphHints && graphHints.activeWorkItemHints > 0) {
|
|
7502
|
+
console.log("");
|
|
7503
|
+
console.log(
|
|
7504
|
+
`Graph hints: ${graphHints.activeWorkItemHints} active Work Item(s) have limited graph relationships (quality: ${graphHints.quality}).`
|
|
7505
|
+
);
|
|
7506
|
+
console.log(" \u2192 Review graph hints before continuing with implementation.");
|
|
7507
|
+
console.log(" Suggested agent: graph-agent (see .kaddo/graph-hints.md)");
|
|
7508
|
+
}
|
|
6958
7509
|
const active = activeWorkItems(dir);
|
|
6959
7510
|
if (active.length > 0) {
|
|
6960
7511
|
console.log("");
|
|
@@ -8067,7 +8618,7 @@ ${QUALITY}
|
|
|
8067
8618
|
- [ ] Capabilities describe outcomes, not implementation.
|
|
8068
8619
|
- [ ] Each capability cites evidence or is flagged as an assumption.
|
|
8069
8620
|
`;
|
|
8070
|
-
var
|
|
8621
|
+
var KNOWLEDGE5 = `---
|
|
8071
8622
|
type: current-state
|
|
8072
8623
|
updated_at: YYYY-MM-DD
|
|
8073
8624
|
---
|
|
@@ -9077,7 +9628,7 @@ var KADDO_TEMPLATES = [
|
|
|
9077
9628
|
description: "What is true about the product right now.",
|
|
9078
9629
|
whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
|
|
9079
9630
|
relatedCommand: "kaddo init",
|
|
9080
|
-
content:
|
|
9631
|
+
content: KNOWLEDGE5
|
|
9081
9632
|
},
|
|
9082
9633
|
// business / product (bootstrap — consolidated, minimal)
|
|
9083
9634
|
{
|
|
@@ -9729,7 +10280,7 @@ async function runBootstrap(dir = cwd()) {
|
|
|
9729
10280
|
}
|
|
9730
10281
|
|
|
9731
10282
|
// src/commands/capsule.ts
|
|
9732
|
-
function
|
|
10283
|
+
function slug3(s) {
|
|
9733
10284
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
9734
10285
|
}
|
|
9735
10286
|
function runCapsuleExport() {
|
|
@@ -9737,7 +10288,7 @@ function runCapsuleExport() {
|
|
|
9737
10288
|
const config = requireConfig(dir);
|
|
9738
10289
|
intro2("kaddo capsule export");
|
|
9739
10290
|
const capsule = buildCapsule(dir, config);
|
|
9740
|
-
const name =
|
|
10291
|
+
const name = slug3(config.project.name);
|
|
9741
10292
|
const mdPath = join(".kaddo", "exports", `${name}.capsule.md`);
|
|
9742
10293
|
const jsonPath = join(".kaddo", "exports", `${name}.capsule.json`);
|
|
9743
10294
|
writeFile(join(dir, mdPath), renderCapsuleMarkdown(capsule));
|
|
@@ -9773,6 +10324,47 @@ function runCapsuleAdd(srcPath) {
|
|
|
9773
10324
|
outro2("External capsule registered.");
|
|
9774
10325
|
}
|
|
9775
10326
|
|
|
10327
|
+
// src/commands/graph.ts
|
|
10328
|
+
function runGraphExport(opts = {}) {
|
|
10329
|
+
const dir = cwd();
|
|
10330
|
+
const config = requireConfig(dir);
|
|
10331
|
+
intro2("kaddo graph export");
|
|
10332
|
+
const scope = opts.scope === "all" ? "all" : "active";
|
|
10333
|
+
const format = opts.format;
|
|
10334
|
+
const writeJson = format !== "mermaid";
|
|
10335
|
+
const writeMermaid = format !== "json";
|
|
10336
|
+
const graph = buildGraph(dir, config, { scope });
|
|
10337
|
+
const hints = buildGraphHints(dir, graph);
|
|
10338
|
+
const written = [];
|
|
10339
|
+
if (writeJson) {
|
|
10340
|
+
const rel = join(".kaddo", "graph.json");
|
|
10341
|
+
writeFile(join(dir, rel), serializeGraphJson(graph));
|
|
10342
|
+
written.push(rel.replace(/\\/g, "/"));
|
|
10343
|
+
}
|
|
10344
|
+
if (writeMermaid) {
|
|
10345
|
+
const rel = join(".kaddo", "graph.mmd");
|
|
10346
|
+
writeFile(join(dir, rel), renderGraphMermaid(graph));
|
|
10347
|
+
written.push(rel.replace(/\\/g, "/"));
|
|
10348
|
+
}
|
|
10349
|
+
writeFile(join(dir, ".kaddo", "graph-hints.md"), renderGraphHintsMarkdown(hints));
|
|
10350
|
+
writeFile(join(dir, ".kaddo", "graph-hints.json"), serializeGraphHintsJson(hints));
|
|
10351
|
+
written.push(".kaddo/graph-hints.md", ".kaddo/graph-hints.json");
|
|
10352
|
+
log2.success("Knowledge graph exported.");
|
|
10353
|
+
log2.info(`Scope: ${scope} \xB7 Nodes: ${graph.nodes.length} \xB7 Edges: ${graph.edges.length}`);
|
|
10354
|
+
log2.info(`Relationship quality: ${hints.quality}`);
|
|
10355
|
+
if (hints.hints.length > 0) {
|
|
10356
|
+
const shown = hints.hints.slice(0, 5);
|
|
10357
|
+
log2.warn(`${hints.hints.length} metadata hint(s) available:`);
|
|
10358
|
+
for (const h of shown) log2.info(`- ${h.message}`);
|
|
10359
|
+
if (hints.hints.length > shown.length) {
|
|
10360
|
+
log2.info(`\u2026and ${hints.hints.length - shown.length} more \u2014 see .kaddo/graph-hints.md`);
|
|
10361
|
+
}
|
|
10362
|
+
}
|
|
10363
|
+
for (const f of written) log2.info(`- ${f}`);
|
|
10364
|
+
printCommandFooter("graph export");
|
|
10365
|
+
outro2("Knowledge graph ready.");
|
|
10366
|
+
}
|
|
10367
|
+
|
|
9776
10368
|
// src/index.ts
|
|
9777
10369
|
var require2 = createRequire(import.meta.url);
|
|
9778
10370
|
var { version } = require2("../package.json");
|
|
@@ -9797,6 +10389,10 @@ capsuleCmd.command("export").description("Write a Knowledge Capsule about this p
|
|
|
9797
10389
|
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path5) => {
|
|
9798
10390
|
runCapsuleAdd(path5);
|
|
9799
10391
|
});
|
|
10392
|
+
var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
|
|
10393
|
+
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) => {
|
|
10394
|
+
runGraphExport(opts);
|
|
10395
|
+
});
|
|
9800
10396
|
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
10397
|
await runGuard(opts);
|
|
9802
10398
|
});
|