@kaddo/cli 3.17.0 → 3.19.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 +7 -1
- package/dist/index.js +386 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -454,7 +454,12 @@ Nodes come from knowledge layers, Work Items, code globs, capabilities, ADRs and
|
|
|
454
454
|
Capsules; edges come from front matter (`code`, `capabilities`, `decisions`, `source_id`,
|
|
455
455
|
`source_initiative`) and the external registry. It never reads `src/`, never reads source code
|
|
456
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).
|
|
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
|
|
458
463
|
[Knowledge Graph Export guide](https://kaddo.trycatch.tv/knowledge-graph-export/).
|
|
459
464
|
|
|
460
465
|
## Roadmap
|
|
@@ -499,6 +504,7 @@ create --from roadmap → owners → guard → explain`.
|
|
|
499
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 |
|
|
500
505
|
| v3.16 | Knowledge Capsules: `kaddo capsule export/add`, External Knowledge in context/explain, new `capsule-agent` |
|
|
501
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 |
|
|
502
508
|
|
|
503
509
|
**Optional modules (installed with `kaddo add`):**
|
|
504
510
|
|
package/dist/index.js
CHANGED
|
@@ -1652,6 +1652,19 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
1652
1652
|
],
|
|
1653
1653
|
next: ["kaddo capsule export"]
|
|
1654
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
|
+
},
|
|
1655
1668
|
"ownership-agent": {
|
|
1656
1669
|
agent: "ownership-agent",
|
|
1657
1670
|
responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
|
|
@@ -3207,6 +3220,74 @@ edit the roadmap yourself).
|
|
|
3207
3220
|
- Duplicates, overlaps and dependencies are flagged.
|
|
3208
3221
|
- The response ends with a human-decision handoff \u2014 no agent is auto-executed.
|
|
3209
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
|
+
`;
|
|
3210
3291
|
var AGENT_PROMPTS = [
|
|
3211
3292
|
{ fileName: "capability-agent.md", content: CAPABILITY_AGENT },
|
|
3212
3293
|
{ fileName: "architecture-agent.md", content: ARCHITECTURE_AGENT },
|
|
@@ -3230,7 +3311,9 @@ var AGENT_PROMPTS = [
|
|
|
3230
3311
|
// Ownership proposals (precise code: globs — VS-052)
|
|
3231
3312
|
{ fileName: "ownership-agent.md", content: OWNERSHIP_AGENT },
|
|
3232
3313
|
// Knowledge Capsule refinement (external context — VS-054)
|
|
3233
|
-
{ 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 }
|
|
3234
3317
|
// Every official prompt ends with its responsibility boundaries + Agent Trace footer.
|
|
3235
3318
|
].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
|
|
3236
3319
|
|
|
@@ -3246,7 +3329,8 @@ var AGENT_GROUPS = {
|
|
|
3246
3329
|
"standards-agent.md",
|
|
3247
3330
|
"module-design-agent.md",
|
|
3248
3331
|
"adr-agent.md",
|
|
3249
|
-
"capsule-agent.md"
|
|
3332
|
+
"capsule-agent.md",
|
|
3333
|
+
"graph-agent.md"
|
|
3250
3334
|
],
|
|
3251
3335
|
delivery: [
|
|
3252
3336
|
"backlog-agent.md",
|
|
@@ -3391,7 +3475,8 @@ var agentReadme = {
|
|
|
3391
3475
|
"- `standards-agent.md` \u2014 propose lightweight coding/docs/architecture standards.",
|
|
3392
3476
|
"- `stack-agent.md` \u2014 document technologies and stack decisions.",
|
|
3393
3477
|
"- `module-design-agent.md` \u2014 document the design of a mapped module.",
|
|
3394
|
-
"- `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."
|
|
3395
3480
|
].join("\n")
|
|
3396
3481
|
};
|
|
3397
3482
|
var agentFiles = AGENT_PROMPTS.map((a) => ({
|
|
@@ -4161,8 +4246,8 @@ async function runCreate(type, opts = {}) {
|
|
|
4161
4246
|
answers[question.frontMatterField] = answer.trim();
|
|
4162
4247
|
}
|
|
4163
4248
|
const id = nextWorkItemId(dir);
|
|
4164
|
-
const
|
|
4165
|
-
const fileName = `${id}-${
|
|
4249
|
+
const slug4 = slugify(title);
|
|
4250
|
+
const fileName = `${id}-${slug4}.md`;
|
|
4166
4251
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
4167
4252
|
const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
|
|
4168
4253
|
const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
|
|
@@ -4202,8 +4287,8 @@ async function runCreateModule(dir, modType) {
|
|
|
4202
4287
|
answers[q.frontMatterField] = answer.trim();
|
|
4203
4288
|
}
|
|
4204
4289
|
const id = nextWorkItemId(dir);
|
|
4205
|
-
const
|
|
4206
|
-
const fileName = `${id}-${
|
|
4290
|
+
const slug4 = slugify(title);
|
|
4291
|
+
const fileName = `${id}-${slug4}.md`;
|
|
4207
4292
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
4208
4293
|
const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
|
|
4209
4294
|
const body = buildModuleBody(modType, title.trim(), answers);
|
|
@@ -4335,8 +4420,8 @@ function buildRoadmapWorkItem(opts) {
|
|
|
4335
4420
|
const { id, type, level, candidate } = opts;
|
|
4336
4421
|
const answers = opts.answers ?? {};
|
|
4337
4422
|
const title = candidate.title.trim();
|
|
4338
|
-
const
|
|
4339
|
-
const fileName = `${id}-${
|
|
4423
|
+
const slug4 = slugify(title);
|
|
4424
|
+
const fileName = `${id}-${slug4}.md`;
|
|
4340
4425
|
const qualityGate = getLevel(level).qualityGate;
|
|
4341
4426
|
const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
|
|
4342
4427
|
const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
|
|
@@ -4485,7 +4570,8 @@ function parseArtifact(filePath, raw) {
|
|
|
4485
4570
|
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4486
4571
|
source: data.source ? String(data.source) : "",
|
|
4487
4572
|
sourceId: String(data.source_id ?? ""),
|
|
4488
|
-
decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : []
|
|
4573
|
+
decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : [],
|
|
4574
|
+
capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : []
|
|
4489
4575
|
};
|
|
4490
4576
|
} catch {
|
|
4491
4577
|
return null;
|
|
@@ -5697,6 +5783,11 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
5697
5783
|
const capId = `capsule:${cap.id}`;
|
|
5698
5784
|
addNode({ id: capId, type: "knowledge-capsule", label: cap.id, path: cap.path });
|
|
5699
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
|
+
}
|
|
5700
5791
|
}
|
|
5701
5792
|
}
|
|
5702
5793
|
return {
|
|
@@ -5710,18 +5801,6 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
5710
5801
|
edges
|
|
5711
5802
|
};
|
|
5712
5803
|
}
|
|
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
5804
|
function serializeGraphJson(graph) {
|
|
5726
5805
|
return JSON.stringify(graph, null, 2) + "\n";
|
|
5727
5806
|
}
|
|
@@ -5776,8 +5855,237 @@ function loadGraphSummary(dir) {
|
|
|
5776
5855
|
}
|
|
5777
5856
|
}
|
|
5778
5857
|
|
|
5779
|
-
// src/core/
|
|
5858
|
+
// src/core/graph-hints.ts
|
|
5780
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";
|
|
5781
6089
|
var CONSOLIDATED_TYPE = {
|
|
5782
6090
|
Business: "business",
|
|
5783
6091
|
Product: "product",
|
|
@@ -5819,10 +6127,10 @@ function layerForType(type) {
|
|
|
5819
6127
|
}
|
|
5820
6128
|
function layerFromPath(filePath) {
|
|
5821
6129
|
const p2 = filePath.replace(/\\/g, "/");
|
|
5822
|
-
if (p2.includes(`/${
|
|
5823
|
-
if (p2.includes(`/${
|
|
5824
|
-
if (p2.includes(`/${
|
|
5825
|
-
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";
|
|
5826
6134
|
return null;
|
|
5827
6135
|
}
|
|
5828
6136
|
function basename(p2) {
|
|
@@ -5835,7 +6143,7 @@ function discoverLayers(dir) {
|
|
|
5835
6143
|
Tech: blank(),
|
|
5836
6144
|
Delivery: blank()
|
|
5837
6145
|
};
|
|
5838
|
-
const archDir = join(dir,
|
|
6146
|
+
const archDir = join(dir, KNOWLEDGE4);
|
|
5839
6147
|
const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
5840
6148
|
for (const a of artifacts) {
|
|
5841
6149
|
const type = a.type;
|
|
@@ -5857,7 +6165,7 @@ function discoverLayers(dir) {
|
|
|
5857
6165
|
}
|
|
5858
6166
|
if (type === "adr" || type === "decision") slot.hasDecision = true;
|
|
5859
6167
|
}
|
|
5860
|
-
if (existsDirWithMd(join(dir,
|
|
6168
|
+
if (existsDirWithMd(join(dir, KNOWLEDGE4, "tech", "decisions"))) acc.Tech.structured = true;
|
|
5861
6169
|
return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
|
|
5862
6170
|
layer: layer2,
|
|
5863
6171
|
status: statusFor(layer2, acc[layer2]),
|
|
@@ -6107,6 +6415,7 @@ function buildProjectExplanation(dir) {
|
|
|
6107
6415
|
duplicateWorkItems,
|
|
6108
6416
|
externalCapsules: loadExternalCapsules(dir),
|
|
6109
6417
|
graph: loadGraphSummary(dir),
|
|
6418
|
+
graphHints: loadGraphHints(dir),
|
|
6110
6419
|
layers,
|
|
6111
6420
|
roadmap,
|
|
6112
6421
|
mappedModules,
|
|
@@ -6250,6 +6559,10 @@ function renderExplanationHuman(exp) {
|
|
|
6250
6559
|
lines.push("## Knowledge Graph");
|
|
6251
6560
|
lines.push(`- Nodes: ${exp.graph.nodes}`);
|
|
6252
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
|
+
}
|
|
6253
6566
|
if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
|
|
6254
6567
|
lines.push("");
|
|
6255
6568
|
}
|
|
@@ -6661,6 +6974,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
6661
6974
|
deliveryMix,
|
|
6662
6975
|
external: loadExternalCapsules(dir),
|
|
6663
6976
|
graph: loadGraphSummary(dir),
|
|
6977
|
+
graphHints: loadGraphHints(dir),
|
|
6664
6978
|
mappedModules,
|
|
6665
6979
|
missing,
|
|
6666
6980
|
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
@@ -6838,6 +7152,22 @@ function renderContextPack(pack) {
|
|
|
6838
7152
|
);
|
|
6839
7153
|
parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
|
|
6840
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
|
+
}
|
|
6841
7171
|
parts.push("## Missing Context\n");
|
|
6842
7172
|
if (missing.length > 0) {
|
|
6843
7173
|
parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
|
|
@@ -7167,6 +7497,15 @@ function runUnderstand() {
|
|
|
7167
7497
|
}
|
|
7168
7498
|
console.log(" \u2192 Review the relevant capsule before changing integration behavior with it.");
|
|
7169
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
|
+
}
|
|
7170
7509
|
const active = activeWorkItems(dir);
|
|
7171
7510
|
if (active.length > 0) {
|
|
7172
7511
|
console.log("");
|
|
@@ -8279,7 +8618,7 @@ ${QUALITY}
|
|
|
8279
8618
|
- [ ] Capabilities describe outcomes, not implementation.
|
|
8280
8619
|
- [ ] Each capability cites evidence or is flagged as an assumption.
|
|
8281
8620
|
`;
|
|
8282
|
-
var
|
|
8621
|
+
var KNOWLEDGE5 = `---
|
|
8283
8622
|
type: current-state
|
|
8284
8623
|
updated_at: YYYY-MM-DD
|
|
8285
8624
|
---
|
|
@@ -9289,7 +9628,7 @@ var KADDO_TEMPLATES = [
|
|
|
9289
9628
|
description: "What is true about the product right now.",
|
|
9290
9629
|
whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
|
|
9291
9630
|
relatedCommand: "kaddo init",
|
|
9292
|
-
content:
|
|
9631
|
+
content: KNOWLEDGE5
|
|
9293
9632
|
},
|
|
9294
9633
|
// business / product (bootstrap — consolidated, minimal)
|
|
9295
9634
|
{
|
|
@@ -9941,7 +10280,7 @@ async function runBootstrap(dir = cwd()) {
|
|
|
9941
10280
|
}
|
|
9942
10281
|
|
|
9943
10282
|
// src/commands/capsule.ts
|
|
9944
|
-
function
|
|
10283
|
+
function slug3(s) {
|
|
9945
10284
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
9946
10285
|
}
|
|
9947
10286
|
function runCapsuleExport() {
|
|
@@ -9949,7 +10288,7 @@ function runCapsuleExport() {
|
|
|
9949
10288
|
const config = requireConfig(dir);
|
|
9950
10289
|
intro2("kaddo capsule export");
|
|
9951
10290
|
const capsule = buildCapsule(dir, config);
|
|
9952
|
-
const name =
|
|
10291
|
+
const name = slug3(config.project.name);
|
|
9953
10292
|
const mdPath = join(".kaddo", "exports", `${name}.capsule.md`);
|
|
9954
10293
|
const jsonPath = join(".kaddo", "exports", `${name}.capsule.json`);
|
|
9955
10294
|
writeFile(join(dir, mdPath), renderCapsuleMarkdown(capsule));
|
|
@@ -9995,6 +10334,7 @@ function runGraphExport(opts = {}) {
|
|
|
9995
10334
|
const writeJson = format !== "mermaid";
|
|
9996
10335
|
const writeMermaid = format !== "json";
|
|
9997
10336
|
const graph = buildGraph(dir, config, { scope });
|
|
10337
|
+
const hints = buildGraphHints(dir, graph);
|
|
9998
10338
|
const written = [];
|
|
9999
10339
|
if (writeJson) {
|
|
10000
10340
|
const rel = join(".kaddo", "graph.json");
|
|
@@ -10006,13 +10346,20 @@ function runGraphExport(opts = {}) {
|
|
|
10006
10346
|
writeFile(join(dir, rel), renderGraphMermaid(graph));
|
|
10007
10347
|
written.push(rel.replace(/\\/g, "/"));
|
|
10008
10348
|
}
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
log2.success("Knowledge graph exported.");
|
|
10014
|
-
}
|
|
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.");
|
|
10015
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
|
+
}
|
|
10016
10363
|
for (const f of written) log2.info(`- ${f}`);
|
|
10017
10364
|
printCommandFooter("graph export");
|
|
10018
10365
|
outro2("Knowledge graph ready.");
|