@kaddo/mcp 3.65.0 → 3.66.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 (2) hide show
  1. package/dist/index.js +125 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -521,7 +521,14 @@ function parseArtifact(filePath, raw) {
521
521
  sourceId: String(data.source_id ?? ""),
522
522
  rawFrontmatter: data,
523
523
  decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : [],
524
- capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : []
524
+ capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : [],
525
+ implementationStatus: String(data.implementation_status ?? ""),
526
+ validationStatus: String(data.validation_status ?? ""),
527
+ releaseStatus: String(data.release_status ?? ""),
528
+ affectedModules: Array.isArray(data.affected_modules) ? data.affected_modules.map(String).filter(Boolean) : [],
529
+ refinedBy: String(data.refined_by ?? ""),
530
+ implementedBy: String(data.implemented_by ?? ""),
531
+ closedBy: String(data.closed_by ?? "")
525
532
  };
526
533
  } catch {
527
534
  return null;
@@ -2413,6 +2420,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2413
2420
  const selectedWIs = workItems.filter((a2) => a2.lifecycle && includedSet.has(a2.lifecycle));
2414
2421
  for (const wi of selectedWIs) {
2415
2422
  const id = wi.id || wi.title;
2423
+ if (!id || !id.trim()) continue;
2416
2424
  const wiNodeId = `wi:${id}`;
2417
2425
  addNode({
2418
2426
  id: wiNodeId,
@@ -2423,16 +2431,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2423
2431
  knowledge_level: wi.knowledgeLevel || void 0
2424
2432
  });
2425
2433
  for (const glob of wi.codeGlobs) {
2434
+ if (!glob || !glob.trim()) continue;
2426
2435
  const codeId = `code:${glob}`;
2427
2436
  addNode({ id: codeId, type: "code-glob", label: glob });
2428
2437
  addEdge(wiNodeId, codeId, "owns");
2429
2438
  }
2430
2439
  for (const cap of wi.capabilities) {
2440
+ if (!cap || !cap.trim()) continue;
2431
2441
  const capId = `capability:${slug(cap) || cap}`;
2432
2442
  addNode({ id: capId, type: "capability", label: cap });
2433
2443
  addEdge(wiNodeId, capId, "implements");
2434
2444
  }
2435
2445
  for (const dec of wi.decisions) {
2446
+ if (!dec || !dec.trim()) continue;
2436
2447
  const adrId = `adr:${dec}`;
2437
2448
  addNode({ id: adrId, type: "decision", label: dec });
2438
2449
  addEdge(wiNodeId, adrId, "depends_on");
@@ -2449,16 +2460,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2449
2460
  }
2450
2461
  }
2451
2462
  for (const adr of all.filter(isAdr)) {
2452
- const adrId = `adr:${adr.id || adr.title}`;
2463
+ const adrLabel = adr.id || adr.title;
2464
+ if (!adrLabel || !adrLabel.trim()) continue;
2465
+ const adrId = `adr:${adrLabel}`;
2453
2466
  const referenced = nodes.has(adrId);
2454
2467
  if (scope === "all" || referenced) {
2455
2468
  nodes.set(adrId, {
2456
2469
  id: adrId,
2457
2470
  type: "decision",
2458
- label: `${adr.id} ${adr.title}`.trim() || adr.id || adr.title,
2471
+ label: `${adr.id} ${adr.title}`.trim() || adrLabel,
2459
2472
  path: adr.relPath
2460
2473
  });
2461
2474
  for (const glob of adr.codeGlobs) {
2475
+ if (!glob || !glob.trim()) continue;
2462
2476
  const codeId = `code:${glob}`;
2463
2477
  addNode({ id: codeId, type: "code-glob", label: glob });
2464
2478
  addEdge(adrId, codeId, "governs");
@@ -2514,13 +2528,20 @@ function renderGraphMermaid(graph) {
2514
2528
  safeIds.set(id, candidate);
2515
2529
  return candidate;
2516
2530
  };
2517
- const escapeLabel = (s) => s.replace(/"/g, "'");
2531
+ const escapeLabel = (s) => s.replace(/"/g, "'").replace(/\[/g, "(").replace(/\]/g, ")").replace(/\n/g, " ");
2532
+ const validNodes = graph.nodes.filter(
2533
+ (n) => n.id && n.id.trim() !== "" && n.label && n.label.trim() !== ""
2534
+ );
2535
+ const validNodeIds = new Set(validNodes.map((n) => n.id));
2518
2536
  const lines = ["flowchart LR"];
2519
- for (const node of graph.nodes) {
2537
+ for (const node of validNodes) {
2520
2538
  lines.push(` ${safe(node.id)}["${escapeLabel(node.label)}"]`);
2521
2539
  }
2522
- if (graph.edges.length > 0) lines.push("");
2523
- for (const edge of graph.edges) {
2540
+ const validEdges = graph.edges.filter(
2541
+ (e) => validNodeIds.has(e.from) && validNodeIds.has(e.to)
2542
+ );
2543
+ if (validEdges.length > 0) lines.push("");
2544
+ for (const edge of validEdges) {
2524
2545
  lines.push(` ${safe(edge.from)} -->|${edge.type}| ${safe(edge.to)}`);
2525
2546
  }
2526
2547
  return lines.join("\n") + "\n";
@@ -8030,6 +8051,33 @@ function analyzeMetadataHealth(dir) {
8030
8051
  if (fileDrifted) drifted++;
8031
8052
  else healthy++;
8032
8053
  }
8054
+ const wiDir = join(dir, "knowledge/delivery/work-items");
8055
+ if (exists(wiDir)) {
8056
+ const walkWIs = (d3) => {
8057
+ for (const entry of readDir(d3)) {
8058
+ const p = join(d3, entry);
8059
+ if (isFile(p) && entry.endsWith(".md")) {
8060
+ try {
8061
+ const raw = readFile(p);
8062
+ const fm = matter7(raw).data;
8063
+ if (fm.status === "done") {
8064
+ const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
8065
+ findings.push({
8066
+ file: rel,
8067
+ field: "status",
8068
+ issue: "inconsistent",
8069
+ detail: "Legacy status `done` detected. Canonical status is `completed`."
8070
+ });
8071
+ }
8072
+ } catch {
8073
+ }
8074
+ } else if (!isFile(p) && !entry.startsWith(".")) {
8075
+ walkWIs(p);
8076
+ }
8077
+ }
8078
+ };
8079
+ walkWIs(wiDir);
8080
+ }
8033
8081
  return { findings, healthy, drifted };
8034
8082
  }
8035
8083
 
@@ -8319,7 +8367,30 @@ function buildProjectExplanation(dir) {
8319
8367
  projectRoute: buildProjectRoute(dir),
8320
8368
  scanSignals: loadScanSignals(dir),
8321
8369
  metadataHealth: analyzeMetadataHealth(dir),
8322
- isModuleRepo: config != null && isModule(config)
8370
+ isModuleRepo: config != null && isModule(config),
8371
+ implementationEvidence: workItemArtifacts.filter((a2) => a2.affectedModules.length > 0).map((a2) => {
8372
+ const fm = a2.rawFrontmatter;
8373
+ const evidence = fm.implementation_evidence;
8374
+ const repos = evidence?.repositories ?? {};
8375
+ const repoEvidence = {};
8376
+ for (const [repoId, data] of Object.entries(repos)) {
8377
+ repoEvidence[repoId] = {
8378
+ role: String(data.role ?? (repoId === "core" ? "core" : "module")),
8379
+ status: String(data.status ?? "unknown")
8380
+ };
8381
+ }
8382
+ return {
8383
+ id: a2.id || a2.title,
8384
+ lifecycle: lifecycleStateOf({ status: a2.status, filePath: a2.filePath }),
8385
+ implementationStatus: a2.implementationStatus || "not-started",
8386
+ validationStatus: a2.validationStatus || "not-started",
8387
+ releaseStatus: a2.releaseStatus || "not-assessed",
8388
+ affectedModules: a2.affectedModules,
8389
+ releaseGates: Array.isArray(fm.release_gates) ? fm.release_gates : [],
8390
+ completionExceptions: Array.isArray(fm.completion_exceptions) ? fm.completion_exceptions : [],
8391
+ repoEvidence
8392
+ };
8393
+ })
8323
8394
  };
8324
8395
  }
8325
8396
  function stateLabel(state) {
@@ -8445,6 +8516,36 @@ function renderExplanationHuman(exp) {
8445
8516
  }
8446
8517
  lines.push("");
8447
8518
  }
8519
+ if (exp.implementationEvidence.length > 0) {
8520
+ lines.push("## Implementation Evidence");
8521
+ for (const ev of exp.implementationEvidence) {
8522
+ lines.push("");
8523
+ lines.push(`### ${ev.id}`);
8524
+ lines.push(`- Lifecycle: ${ev.lifecycle.charAt(0).toUpperCase() + ev.lifecycle.slice(1)}`);
8525
+ lines.push(`- Implementation: ${ev.implementationStatus}`);
8526
+ lines.push(`- Validation: ${ev.validationStatus}`);
8527
+ lines.push(`- Release: ${ev.releaseStatus}`);
8528
+ if (Object.keys(ev.repoEvidence).length > 0) {
8529
+ lines.push("Repositories:");
8530
+ for (const [repoId, info] of Object.entries(ev.repoEvidence)) {
8531
+ lines.push(`- ${repoId} \u2014 ${info.status}`);
8532
+ }
8533
+ } else if (ev.affectedModules.length > 0) {
8534
+ lines.push(`Affected modules: ${ev.affectedModules.join(", ")}`);
8535
+ }
8536
+ const pendingGates = ev.releaseGates.filter((g) => g.status === "blocked" || g.status === "pending");
8537
+ if (pendingGates.length > 0) {
8538
+ lines.push("Pending release gates:");
8539
+ for (const g of pendingGates) lines.push(`- ${g.id}${g.reason ? ` \u2014 ${g.reason}` : ""}`);
8540
+ }
8541
+ const acceptedExceptions = ev.completionExceptions.filter((e) => e.status === "accepted" || e.status === "deferred");
8542
+ if (acceptedExceptions.length > 0) {
8543
+ lines.push("Completion exceptions:");
8544
+ for (const e of acceptedExceptions) lines.push(`- ${e.id}: ${e.status}${e.reason ? ` \u2014 ${e.reason}` : ""}`);
8545
+ }
8546
+ }
8547
+ lines.push("");
8548
+ }
8448
8549
  if (exp.domains.length > 0) {
8449
8550
  lines.push("## Domains");
8450
8551
  lines.push(`- ${exp.domains.join(", ")}`);
@@ -9641,7 +9742,16 @@ function listWorkItems(root) {
9641
9742
  capsules: strArray(data.capsules),
9642
9743
  source,
9643
9744
  domains,
9644
- readiness
9745
+ readiness,
9746
+ ...data.implementation_status ? { implementation_status: String(data.implementation_status) } : {},
9747
+ ...data.validation_status ? { validation_status: String(data.validation_status) } : {},
9748
+ ...data.release_status ? { release_status: String(data.release_status) } : {},
9749
+ ...Array.isArray(data.affected_modules) && data.affected_modules.length > 0 ? { affected_modules: data.affected_modules.map(String) } : {},
9750
+ ...data.refined_by ? { refined_by: String(data.refined_by) } : {},
9751
+ ...data.implemented_by ? { implemented_by: String(data.implemented_by) } : {},
9752
+ ...data.closed_by ? { closed_by: String(data.closed_by) } : {},
9753
+ ...Array.isArray(data.release_gates) && data.release_gates.length > 0 ? { release_gates: data.release_gates } : {},
9754
+ ...Array.isArray(data.completion_exceptions) && data.completion_exceptions.length > 0 ? { completion_exceptions: data.completion_exceptions } : {}
9645
9755
  });
9646
9756
  }
9647
9757
  return items.sort((a2, b2) => a2.id.localeCompare(b2.id));
@@ -10766,7 +10876,7 @@ var OPERATING_RULES = [
10766
10876
  "Kaddo itself never calls an LLM and never runs git \u2014 every git action is the human\u2019s."
10767
10877
  ];
10768
10878
  function toContextWorkItem(a2) {
10769
- return {
10879
+ const wi = {
10770
10880
  id: a2.id,
10771
10881
  type: a2.type,
10772
10882
  title: a2.title,
@@ -10776,6 +10886,11 @@ function toContextWorkItem(a2) {
10776
10886
  domains: a2.domains,
10777
10887
  source: parseWorkItemSource(a2.rawFrontmatter)
10778
10888
  };
10889
+ if (a2.implementationStatus) wi.implementationStatus = a2.implementationStatus;
10890
+ if (a2.validationStatus) wi.validationStatus = a2.validationStatus;
10891
+ if (a2.releaseStatus) wi.releaseStatus = a2.releaseStatus;
10892
+ if (a2.affectedModules.length > 0) wi.affectedModules = a2.affectedModules;
10893
+ return wi;
10779
10894
  }
10780
10895
  function toContextArtifact(a2) {
10781
10896
  return { id: a2.id, type: a2.type, title: a2.title, summary: a2.summary, codeGlobs: a2.codeGlobs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.65.0",
3
+ "version": "3.66.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {