@kaddo/mcp 3.78.0 → 3.80.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 +586 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -480,8 +480,8 @@ function loadConfig(dir) {
480
480
  const parsed = configSchema.safeParse(raw ?? {});
481
481
  if (!parsed.success) {
482
482
  const issues = parsed.error.issues.map((i) => {
483
- const path4 = i.path.join(".");
484
- return path4 ? ` - ${path4}: ${i.message}` : ` - ${i.message}`;
483
+ const path5 = i.path.join(".");
484
+ return path5 ? ` - ${path5}: ${i.message}` : ` - ${i.message}`;
485
485
  }).join("\n");
486
486
  throw new ConfigError(`Invalid .kaddo/config.yml:
487
487
  ${issues}`);
@@ -922,11 +922,11 @@ function moduleArtifactCoverage(dir, id) {
922
922
  };
923
923
  }
924
924
  function loadMappedModules(dir) {
925
- const path4 = join(dir, DESCRIPTOR_PATH);
926
- if (!exists(path4)) return [];
925
+ const path5 = join(dir, DESCRIPTOR_PATH);
926
+ if (!exists(path5)) return [];
927
927
  let parsed;
928
928
  try {
929
- parsed = parseYaml4(readFile(path4));
929
+ parsed = parseYaml4(readFile(path5));
930
930
  } catch {
931
931
  return [];
932
932
  }
@@ -2433,7 +2433,7 @@ function sectionParagraph2(md, title) {
2433
2433
  }
2434
2434
  return "";
2435
2435
  }
2436
- function parseCapsule(id, path4, md) {
2436
+ function parseCapsule(id, path5, md) {
2437
2437
  const { data } = matter4(md);
2438
2438
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
2439
2439
  let ageDays = null;
@@ -2443,7 +2443,7 @@ function parseCapsule(id, path4, md) {
2443
2443
  }
2444
2444
  return {
2445
2445
  id,
2446
- path: path4,
2446
+ path: path5,
2447
2447
  system: data.system ? String(data.system) : id,
2448
2448
  owner: data.owner ? String(data.owner) : void 0,
2449
2449
  updatedAt,
@@ -2505,9 +2505,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2505
2505
  ];
2506
2506
  const presentLayers = [];
2507
2507
  for (const layer2 of layerDocs) {
2508
- const path4 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2509
- if (path4) {
2510
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path4 });
2508
+ const path5 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2509
+ if (path5) {
2510
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path5 });
2511
2511
  presentLayers.push(layer2.id);
2512
2512
  }
2513
2513
  }
@@ -4088,10 +4088,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4088
4088
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
4089
4089
  const resolveAgent = (agent) => {
4090
4090
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
4091
- const path4 = agentInstallPath(file);
4092
- const installed = isFile(join(dir, path4));
4091
+ const path5 = agentInstallPath(file);
4092
+ const installed = isFile(join(dir, path5));
4093
4093
  const group = agentGroupOf(file);
4094
- return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4094
+ return { agentPath: path5, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4095
4095
  };
4096
4096
  const moduleRepo = isModule(config);
4097
4097
  const coreRepo = isCore(config);
@@ -9131,7 +9131,7 @@ function buildGuardHistory(dir) {
9131
9131
  const d3 = hotspotDir(t.code_path);
9132
9132
  hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
9133
9133
  }
9134
- const hotspots = [...hotspotMap.entries()].map(([path4, warnings]) => ({ path: path4, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9134
+ const hotspots = [...hotspotMap.entries()].map(([path5, warnings]) => ({ path: path5, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9135
9135
  return {
9136
9136
  available: true,
9137
9137
  total_runs: sorted.length,
@@ -11140,6 +11140,520 @@ ${list2(data.owners)}
11140
11140
  ].join("\n");
11141
11141
  }
11142
11142
 
11143
+ // ../cli/src/core/system-topology.ts
11144
+ import { parse as parseYaml11, stringify as stringifyYaml5 } from "yaml";
11145
+ import fs4 from "fs";
11146
+ import path4 from "path";
11147
+ import crypto from "crypto";
11148
+ var TOPOLOGY_FILE = "knowledge/tech/system-topology.yml";
11149
+ var SYSTEM_ENTITY_KINDS = /* @__PURE__ */ new Set([
11150
+ "system",
11151
+ "application",
11152
+ "service",
11153
+ "component",
11154
+ "api",
11155
+ "interface",
11156
+ "datastore",
11157
+ "queue",
11158
+ "job",
11159
+ "external-system",
11160
+ "module",
11161
+ "unknown"
11162
+ ]);
11163
+ var TECHNICAL_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
11164
+ "contains",
11165
+ "depends-on",
11166
+ "calls",
11167
+ "reads-from",
11168
+ "writes-to",
11169
+ "publishes-to",
11170
+ "subscribes-to",
11171
+ "integrates-with",
11172
+ "runs-on",
11173
+ "implemented-by"
11174
+ ]);
11175
+ var PROVENANCE = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewed"]);
11176
+ function isRelative(p) {
11177
+ return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
11178
+ }
11179
+ function strList(v) {
11180
+ return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
11181
+ }
11182
+ function loadSystemTopology(dir) {
11183
+ const filePath = join(dir, TOPOLOGY_FILE);
11184
+ if (!exists(filePath)) return { entities: [], relationships: [], findings: [], declared: false };
11185
+ const parsed = parseTopologyContent(readFile(filePath), dir);
11186
+ return { ...parsed, declared: true };
11187
+ }
11188
+ function parseTopologyContent(raw, dir) {
11189
+ let parsed;
11190
+ try {
11191
+ parsed = parseYaml11(raw) ?? {};
11192
+ } catch {
11193
+ return { entities: [], relationships: [], findings: [{ level: "blocking", message: "The topology could not be parsed." }] };
11194
+ }
11195
+ const sourceRevision = typeof parsed.source_revision === "string" ? parsed.source_revision : void 0;
11196
+ const findings = [];
11197
+ const validModules = /* @__PURE__ */ new Set(["core", ...loadMappedModules(dir).map((m2) => m2.id)]);
11198
+ const rawEntities = Array.isArray(parsed.entities) ? parsed.entities : [];
11199
+ const entities = [];
11200
+ const seenIds = /* @__PURE__ */ new Set();
11201
+ for (const raw2 of rawEntities) {
11202
+ if (!raw2 || typeof raw2 !== "object") continue;
11203
+ const e = raw2;
11204
+ const id = typeof e.id === "string" ? e.id.trim() : "";
11205
+ if (!id) {
11206
+ findings.push({ level: "warning", message: "A topology entity is missing an id and was skipped." });
11207
+ continue;
11208
+ }
11209
+ if (seenIds.has(id)) {
11210
+ findings.push({ level: "blocking", message: `Duplicate topology entity id "${id}".` });
11211
+ continue;
11212
+ }
11213
+ const kind = typeof e.kind === "string" ? e.kind.trim() : "unknown";
11214
+ if (!SYSTEM_ENTITY_KINDS.has(kind)) {
11215
+ findings.push({ level: "warning", message: `Entity "${id}" has an unknown kind "${kind}"; treated as unknown.` });
11216
+ }
11217
+ const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
11218
+ if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
11219
+ const rawImpl = e.implementation ?? e.implementation_refs;
11220
+ const implementation = strList(rawImpl).filter((p) => {
11221
+ if (isRelative(p)) return true;
11222
+ findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
11223
+ return false;
11224
+ });
11225
+ const prov = e.provenance;
11226
+ const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
11227
+ const provenance = provOrigin && PROVENANCE.has(provOrigin) ? provOrigin : void 0;
11228
+ const evidence = strList(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative);
11229
+ seenIds.add(id);
11230
+ entities.push({
11231
+ id,
11232
+ kind: SYSTEM_ENTITY_KINDS.has(kind) ? kind : "unknown",
11233
+ label: typeof e.label === "string" && e.label.trim() ? e.label.trim() : id,
11234
+ ...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
11235
+ ...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
11236
+ implementationRefs: implementation,
11237
+ knowledgeRefs: strList(e.knowledge ?? e.knowledge_refs),
11238
+ ...provenance ? { provenance } : {},
11239
+ evidence
11240
+ });
11241
+ }
11242
+ const entityIds = new Set(entities.map((e) => e.id));
11243
+ const rawRels = Array.isArray(parsed.relationships) ? parsed.relationships : [];
11244
+ const relationships = [];
11245
+ const seenRels = /* @__PURE__ */ new Set();
11246
+ for (const raw2 of rawRels) {
11247
+ if (!raw2 || typeof raw2 !== "object") continue;
11248
+ const r2 = raw2;
11249
+ const from = typeof r2.from === "string" ? r2.from.trim() : typeof r2.source === "string" ? r2.source.trim() : "";
11250
+ const to = typeof r2.to === "string" ? r2.to.trim() : typeof r2.target === "string" ? r2.target.trim() : "";
11251
+ const type = typeof r2.type === "string" ? r2.type.trim() : "";
11252
+ if (!from || !to || !type) {
11253
+ findings.push({ level: "warning", message: "A topology relationship is missing from/to/type and was skipped." });
11254
+ continue;
11255
+ }
11256
+ if (!TECHNICAL_RELATIONSHIP_TYPES.has(type)) {
11257
+ findings.push({ level: "warning", message: `Relationship type "${type}" is not recognized and was skipped.` });
11258
+ continue;
11259
+ }
11260
+ if (!entityIds.has(from) || !entityIds.has(to)) {
11261
+ findings.push({ level: "blocking", message: `Relationship ${from} \u2192 ${to} references an unknown entity endpoint.` });
11262
+ continue;
11263
+ }
11264
+ const key = `${from}~${type}~${to}`;
11265
+ if (seenRels.has(key)) {
11266
+ findings.push({ level: "warning", message: `Duplicate relationship ${from} ${type} ${to} was skipped.` });
11267
+ continue;
11268
+ }
11269
+ seenRels.add(key);
11270
+ const prov = r2.provenance;
11271
+ relationships.push({ from, to, type, evidence: strList(r2.evidence ?? prov?.evidence_refs).filter(isRelative) });
11272
+ }
11273
+ return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
11274
+ }
11275
+
11276
+ // ../cli/src/core/system-map.ts
11277
+ function dimensionOf(type) {
11278
+ switch (type) {
11279
+ case "module":
11280
+ case "application":
11281
+ case "service":
11282
+ case "component":
11283
+ case "api":
11284
+ case "interface":
11285
+ case "database":
11286
+ case "queue":
11287
+ case "job":
11288
+ case "external-system":
11289
+ return "system";
11290
+ case "business":
11291
+ case "product":
11292
+ case "tech":
11293
+ case "delivery":
11294
+ case "capability":
11295
+ case "decision":
11296
+ case "project":
11297
+ case "knowledge-capsule":
11298
+ return "knowledge";
11299
+ case "work-item":
11300
+ case "initiative":
11301
+ case "roadmap-candidate":
11302
+ case "release":
11303
+ return "delivery";
11304
+ case "code-glob":
11305
+ case "file":
11306
+ case "directory":
11307
+ case "migration":
11308
+ case "configuration-artifact":
11309
+ return "implementation";
11310
+ default:
11311
+ return "unknown";
11312
+ }
11313
+ }
11314
+ var EDGE_LABELS = {
11315
+ informs: "informs",
11316
+ belongs_to: "belongs to",
11317
+ materialized_as: "materialized as",
11318
+ owns: "owns",
11319
+ implements: "implements",
11320
+ depends_on: "depends on",
11321
+ governs: "governs",
11322
+ provides_external_context: "provides external context",
11323
+ uses_external_knowledge: "uses external knowledge"
11324
+ };
11325
+ function emptyDimensions() {
11326
+ return { system: 0, knowledge: 0, delivery: 0, implementation: 0, unknown: 0 };
11327
+ }
11328
+ function emptyProjection(name, structure) {
11329
+ return {
11330
+ system: { name },
11331
+ nodes: [],
11332
+ relationships: [],
11333
+ groups: [],
11334
+ metadata: {
11335
+ projectName: name,
11336
+ structure,
11337
+ nodeCount: 0,
11338
+ relationshipCount: 0,
11339
+ coverage: "empty",
11340
+ available: false,
11341
+ dimensions: emptyDimensions(),
11342
+ topologyAvailable: false,
11343
+ topologyStatus: "unavailable",
11344
+ semanticEntityCount: 0,
11345
+ technicalRelationshipCount: 0,
11346
+ topologyFindings: []
11347
+ }
11348
+ };
11349
+ }
11350
+ function getSystemMapProjection(dir) {
11351
+ const config = loadConfig(dir);
11352
+ if (!config) return emptyProjection("unknown", "unknown");
11353
+ const graph = buildGraph(dir, config, { scope: "all" });
11354
+ const hints = buildGraphHints(dir, graph);
11355
+ const knowledgeByPath = /* @__PURE__ */ new Map();
11356
+ for (const a2 of discoverKnowledge(dir).filter((a3) => !a3.isWorkItem)) {
11357
+ const id = a2.id || a2.relPath.replace(/[/\\]/g, "-").replace(/\.md$/, "");
11358
+ const layer2 = a2.layer === "module" ? "tech" : a2.layer;
11359
+ knowledgeByPath.set(a2.relPath, { id, layer: layer2 });
11360
+ }
11361
+ const wiModules = /* @__PURE__ */ new Map();
11362
+ for (const wi of discoverWorkItems(dir)) wiModules.set(wi.id || wi.title, wi.affectedModules);
11363
+ const mapped = loadMappedModules(dir);
11364
+ const groupIds = /* @__PURE__ */ new Set(["core", ...mapped.map((m2) => m2.id)]);
11365
+ const nodes = graph.nodes.map((n) => toNode(n, knowledgeByPath, wiModules, groupIds));
11366
+ const relationships = graph.edges.map((e) => ({
11367
+ id: `${e.from}~${e.type}~${e.to}`,
11368
+ source: e.from,
11369
+ target: e.to,
11370
+ type: e.type,
11371
+ label: EDGE_LABELS[e.type] ?? e.type.replace(/_/g, " ")
11372
+ }));
11373
+ const knowledgeById = /* @__PURE__ */ new Map();
11374
+ for (const ref of knowledgeByPath.values()) knowledgeById.set(ref.id, ref);
11375
+ const topology = loadSystemTopology(dir);
11376
+ const existingIds = new Set(nodes.map((n) => n.id));
11377
+ for (const e of topology.entities) {
11378
+ const nodeId = `sys:${e.id}`;
11379
+ const node = {
11380
+ id: nodeId,
11381
+ type: e.kind,
11382
+ label: e.label,
11383
+ dimension: "system",
11384
+ implementationRefs: e.implementationRefs,
11385
+ knowledgeRefs: e.knowledgeRefs.map((k2) => knowledgeById.get(k2)).filter(Boolean),
11386
+ evidence: e.evidence
11387
+ };
11388
+ if (e.purpose) node.purpose = e.purpose;
11389
+ if (e.moduleId) node.moduleId = e.moduleId;
11390
+ if (e.provenance) node.provenance = e.provenance;
11391
+ nodes.push(node);
11392
+ existingIds.add(nodeId);
11393
+ for (const ref of e.implementationRefs) {
11394
+ const fileId = `file:${ref}`;
11395
+ if (!existingIds.has(fileId)) {
11396
+ nodes.push({ id: fileId, type: "file", label: ref.split("/").pop() || ref, dimension: "implementation", path: ref });
11397
+ existingIds.add(fileId);
11398
+ }
11399
+ relationships.push({ id: `${nodeId}~implemented-by~${fileId}`, source: nodeId, target: fileId, type: "implemented-by", label: "implemented by" });
11400
+ }
11401
+ }
11402
+ for (const r2 of topology.relationships) {
11403
+ relationships.push({ id: `sys:${r2.from}~${r2.type}~sys:${r2.to}`, source: `sys:${r2.from}`, target: `sys:${r2.to}`, type: r2.type, label: r2.type.replace(/-/g, " ") });
11404
+ }
11405
+ const usedGroups = new Set(nodes.map((n) => n.moduleId).filter(Boolean));
11406
+ const allGroups = [
11407
+ { id: "core", label: "core", repositoryId: "core", available: true },
11408
+ // A mapped module is available when its repository path resolves relative to the project.
11409
+ ...mapped.map((m2) => ({ id: m2.id, label: m2.id, repositoryId: m2.id, available: m2.repoPath ? exists(join(dir, m2.repoPath)) : false }))
11410
+ ];
11411
+ const groups = allGroups.filter((g) => usedGroups.has(g.id) || g.available === false);
11412
+ const dimensions = emptyDimensions();
11413
+ for (const n of nodes) dimensions[n.dimension]++;
11414
+ const semanticEntityCount = topology.entities.length;
11415
+ const technicalRelationshipCount = relationships.filter((r2) => TECHNICAL_RELATIONSHIP_TYPES.has(r2.type)).length;
11416
+ const topologyStatus = semanticEntityCount === 0 ? "unavailable" : topology.relationships.length > 0 ? "available" : "partial";
11417
+ return {
11418
+ system: { name: config.project.name },
11419
+ nodes,
11420
+ relationships,
11421
+ groups,
11422
+ metadata: {
11423
+ projectName: config.project.name,
11424
+ structure: config.project.structure,
11425
+ nodeCount: nodes.length,
11426
+ relationshipCount: relationships.length,
11427
+ coverage: hints.quality,
11428
+ available: nodes.length > 0,
11429
+ dimensions,
11430
+ topologyAvailable: dimensions.system > 0,
11431
+ topologyStatus,
11432
+ semanticEntityCount,
11433
+ technicalRelationshipCount,
11434
+ topologyFindings: topology.findings
11435
+ }
11436
+ };
11437
+ }
11438
+ function getSystemNodeContext(dir, nodeId) {
11439
+ const projection = getSystemMapProjection(dir);
11440
+ const byId = new Map(projection.nodes.map((n) => [n.id, n]));
11441
+ const node = byId.get(nodeId);
11442
+ if (!node) return null;
11443
+ const incoming = [];
11444
+ const outgoing = [];
11445
+ for (const r2 of projection.relationships) {
11446
+ if (r2.target === nodeId) {
11447
+ const s = byId.get(r2.source);
11448
+ if (s) incoming.push({ relationship: r2, node: s });
11449
+ }
11450
+ if (r2.source === nodeId) {
11451
+ const t = byId.get(r2.target);
11452
+ if (t) outgoing.push({ relationship: r2, node: t });
11453
+ }
11454
+ }
11455
+ return { node, incoming, outgoing };
11456
+ }
11457
+ var DEFAULT_MAX_DEPTH = 2;
11458
+ var DEFAULT_MAX_NODES = 50;
11459
+ function searchSystemNodes(dir, query) {
11460
+ const q3 = query.trim().toLowerCase();
11461
+ if (!q3) return [];
11462
+ const rank = (n) => n.dimension === "system" ? 0 : n.dimension === "knowledge" ? 1 : n.dimension === "delivery" ? 2 : n.dimension === "unknown" ? 3 : 4;
11463
+ return getSystemMapProjection(dir).nodes.filter(
11464
+ (n) => n.label.toLowerCase().includes(q3) || n.type.toLowerCase().includes(q3) || (n.moduleId ?? "").toLowerCase().includes(q3) || (n.purpose ?? "").toLowerCase().includes(q3)
11465
+ ).sort((a2, b2) => rank(a2) - rank(b2));
11466
+ }
11467
+ function getSystemNeighbors(dir, nodeId, opts = {}) {
11468
+ const projection = getSystemMapProjection(dir);
11469
+ const byId = new Map(projection.nodes.map((n) => [n.id, n]));
11470
+ if (!byId.has(nodeId)) return null;
11471
+ const maxDepth = Math.max(1, opts.maxDepth ?? DEFAULT_MAX_DEPTH);
11472
+ const maxNodes = Math.max(1, opts.maxNodes ?? DEFAULT_MAX_NODES);
11473
+ const relTypes = opts.relationshipTypes && opts.relationshipTypes.length ? new Set(opts.relationshipTypes) : null;
11474
+ const modules = opts.moduleFilter && opts.moduleFilter.length ? new Set(opts.moduleFilter) : null;
11475
+ const includeNode = (n) => !modules || n.moduleId != null && modules.has(n.moduleId) || n.id === nodeId;
11476
+ const visited = /* @__PURE__ */ new Set([nodeId]);
11477
+ const nodes = [];
11478
+ const rels = [];
11479
+ let frontier = [nodeId];
11480
+ let truncated = false;
11481
+ for (let depth = 0; depth < maxDepth && frontier.length > 0 && !truncated; depth++) {
11482
+ const next = [];
11483
+ for (const current of frontier) {
11484
+ for (const r2 of projection.relationships) {
11485
+ if (relTypes && !relTypes.has(r2.type)) continue;
11486
+ const other = r2.source === current ? r2.target : r2.target === current ? r2.source : null;
11487
+ if (!other) continue;
11488
+ const otherNode = byId.get(other);
11489
+ if (!otherNode || !includeNode(otherNode)) continue;
11490
+ if (!rels.some((x2) => x2.id === r2.id)) rels.push(r2);
11491
+ if (!visited.has(other)) {
11492
+ if (nodes.length >= maxNodes) {
11493
+ truncated = true;
11494
+ break;
11495
+ }
11496
+ visited.add(other);
11497
+ nodes.push(otherNode);
11498
+ next.push(other);
11499
+ }
11500
+ }
11501
+ if (truncated) break;
11502
+ }
11503
+ frontier = next;
11504
+ }
11505
+ return { seed: nodeId, nodes, relationships: rels, truncated };
11506
+ }
11507
+ function findSystemPaths(dir, source, target, opts = {}) {
11508
+ const projection = getSystemMapProjection(dir);
11509
+ const byId = new Map(projection.nodes.map((n) => [n.id, n]));
11510
+ if (!byId.has(source) || !byId.has(target)) return [];
11511
+ const maxDepth = Math.max(1, opts.maxDepth ?? 4);
11512
+ const out = /* @__PURE__ */ new Map();
11513
+ for (const r2 of projection.relationships) {
11514
+ if (!out.has(r2.source)) out.set(r2.source, []);
11515
+ out.get(r2.source).push(r2.target);
11516
+ }
11517
+ const paths = [];
11518
+ const walk = (node, path5) => {
11519
+ if (paths.length >= 20) return;
11520
+ if (node === target && path5.length > 1) {
11521
+ paths.push([...path5]);
11522
+ return;
11523
+ }
11524
+ if (path5.length > maxDepth) return;
11525
+ for (const nxt of out.get(node) ?? []) {
11526
+ if (path5.includes(nxt)) continue;
11527
+ walk(nxt, [...path5, nxt]);
11528
+ }
11529
+ };
11530
+ walk(source, [source]);
11531
+ return paths;
11532
+ }
11533
+ function getImpactCandidates(dir, seedIds, opts = {}) {
11534
+ const projection = getSystemMapProjection(dir);
11535
+ const byId = new Map(projection.nodes.map((n) => [n.id, n]));
11536
+ const seeds = seedIds.filter((s) => byId.has(s));
11537
+ const candidates = /* @__PURE__ */ new Map();
11538
+ let truncated = false;
11539
+ for (const seed of seeds) {
11540
+ const neighborhood = getSystemNeighbors(dir, seed, opts);
11541
+ if (!neighborhood) continue;
11542
+ if (neighborhood.truncated) truncated = true;
11543
+ for (const n of neighborhood.nodes) {
11544
+ if (seeds.includes(n.id)) continue;
11545
+ const rel = neighborhood.relationships.find((r2) => r2.source === seed && r2.target === n.id || r2.target === seed && r2.source === n.id);
11546
+ const reason = rel ? { relationship: rel.label, from: byId.get(rel.source)?.label ?? rel.source, to: byId.get(rel.target)?.label ?? rel.target } : { relationship: "related to", from: byId.get(seed)?.label ?? seed, to: n.label };
11547
+ const existing = candidates.get(n.id);
11548
+ if (existing) {
11549
+ existing.reason.push(reason);
11550
+ continue;
11551
+ }
11552
+ const path5 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
11553
+ candidates.set(n.id, {
11554
+ nodeId: n.id,
11555
+ label: n.label,
11556
+ kind: n.type,
11557
+ ...n.moduleId ? { moduleId: n.moduleId } : {},
11558
+ reason: [reason],
11559
+ ...path5 ? { graphPath: path5.map((id) => byId.get(id)?.label ?? id) } : {},
11560
+ ...n.implementationRefs?.length ? { implementationRefs: n.implementationRefs } : {},
11561
+ ...n.knowledgeRefs?.length ? { knowledgeRefs: n.knowledgeRefs } : {},
11562
+ status: "candidate",
11563
+ provenance: { source: "graph-assisted" }
11564
+ });
11565
+ }
11566
+ }
11567
+ return { seeds, candidates: [...candidates.values()], truncated, topologyStatus: projection.metadata.topologyStatus };
11568
+ }
11569
+ function toNode(n, knowledgeByPath, wiModules, groupIds) {
11570
+ const node = { id: n.id, type: n.type, label: n.label, dimension: dimensionOf(n.type) };
11571
+ if (n.status) node.status = n.status;
11572
+ if (n.path && !/^([a-zA-Z]:[\\/]|\/)/.test(n.path)) node.path = n.path;
11573
+ if (n.type === "work-item") {
11574
+ const wiId = n.id.startsWith("wi:") ? n.id.slice(3) : n.id;
11575
+ node.workItemRef = wiId;
11576
+ const affected = (wiModules.get(wiId) ?? []).filter((m2) => groupIds.has(m2));
11577
+ if (affected.length === 1) node.moduleId = affected[0];
11578
+ }
11579
+ if (n.path) {
11580
+ const ref = knowledgeByPath.get(n.path);
11581
+ if (ref) node.knowledgeRef = ref;
11582
+ }
11583
+ return node;
11584
+ }
11585
+
11586
+ // src/system.ts
11587
+ var ok3 = (data) => ({ ok: true, data });
11588
+ var CANDIDATE_NOTE = "Graph-derived entities are IMPACT CANDIDATES, not confirmed scope. Inspect each in the repository and classify it (affected / reviewed-not-affected / unknown). A missing Graph edge does not mean no impact.";
11589
+ function systemSearchTool(root, args) {
11590
+ const results = searchSystemNodes(root, args.query ?? "");
11591
+ return ok3({
11592
+ query: args.query ?? "",
11593
+ count: results.length,
11594
+ results: results.map((n) => ({ nodeId: n.id, label: n.label, kind: n.type, moduleId: n.moduleId ?? null, dimension: n.dimension })),
11595
+ note: CANDIDATE_NOTE
11596
+ });
11597
+ }
11598
+ function systemNodeTool(root, args) {
11599
+ const ctx = getSystemNodeContext(root, args.nodeId);
11600
+ if (!ctx) return ok3({ nodeId: args.nodeId, found: false, note: "No such system entity in the current topology." });
11601
+ return ok3({
11602
+ nodeId: args.nodeId,
11603
+ found: true,
11604
+ node: { nodeId: ctx.node.id, label: ctx.node.label, kind: ctx.node.type, moduleId: ctx.node.moduleId ?? null, dimension: ctx.node.dimension },
11605
+ incoming: ctx.incoming.map((e) => ({ nodeId: e.node.id, label: e.node.label, kind: e.node.type, relationship: e.relationship.type })),
11606
+ outgoing: ctx.outgoing.map((e) => ({ nodeId: e.node.id, label: e.node.label, kind: e.node.type, relationship: e.relationship.type })),
11607
+ note: CANDIDATE_NOTE
11608
+ });
11609
+ }
11610
+ function systemNeighborsTool(root, args) {
11611
+ const res = getSystemNeighbors(root, args.nodeId, {
11612
+ maxDepth: args.maxDepth,
11613
+ maxNodes: args.maxNodes,
11614
+ relationshipTypes: args.relationshipTypes,
11615
+ moduleFilter: args.moduleId ? [args.moduleId] : void 0
11616
+ });
11617
+ if (!res) return ok3({ nodeId: args.nodeId, found: false, note: "No such system entity in the current topology." });
11618
+ return ok3({
11619
+ seed: res.seed,
11620
+ truncated: res.truncated,
11621
+ nodes: res.nodes.map((n) => ({ nodeId: n.id, label: n.label, kind: n.type, moduleId: n.moduleId ?? null })),
11622
+ relationships: res.relationships.map((r2) => ({ from: r2.source, to: r2.target, type: r2.type })),
11623
+ note: res.truncated ? `Neighborhood truncated by bounds \u2014 more entities may be reachable. ${CANDIDATE_NOTE}` : CANDIDATE_NOTE
11624
+ });
11625
+ }
11626
+ function systemPathsTool(root, args) {
11627
+ const paths = findSystemPaths(root, args.from, args.to, { maxDepth: args.maxDepth });
11628
+ return ok3({ from: args.from, to: args.to, count: paths.length, paths, note: CANDIDATE_NOTE });
11629
+ }
11630
+ function systemImpactCandidatesTool(root, args) {
11631
+ const res = getImpactCandidates(root, args.seeds ?? [], {
11632
+ maxDepth: args.maxDepth,
11633
+ maxNodes: args.maxNodes,
11634
+ relationshipTypes: args.relationshipTypes,
11635
+ moduleFilter: args.moduleId ? [args.moduleId] : void 0
11636
+ });
11637
+ return ok3({
11638
+ seeds: res.seeds,
11639
+ topologyStatus: res.topologyStatus,
11640
+ truncated: res.truncated,
11641
+ count: res.candidates.length,
11642
+ candidates: res.candidates.map((c) => ({
11643
+ nodeId: c.nodeId,
11644
+ label: c.label,
11645
+ kind: c.kind,
11646
+ moduleId: c.moduleId ?? null,
11647
+ status: c.status,
11648
+ // always 'candidate'
11649
+ reason: c.reason,
11650
+ graphPath: c.graphPath ?? null,
11651
+ provenance: c.provenance
11652
+ })),
11653
+ note: res.topologyStatus === "unavailable" ? "No semantic topology available \u2014 refine impact using the repository and Knowledge only." : `${CANDIDATE_NOTE}${res.truncated ? " Traversal was bounded; some candidates may be missing." : ""}`
11654
+ });
11655
+ }
11656
+
11143
11657
  // ../cli/src/core/context-pack.ts
11144
11658
  import matter12 from "gray-matter";
11145
11659
  var CONTEXT_PACK_VERSION = "1";
@@ -12472,6 +12986,64 @@ function createServer(root) {
12472
12986
  },
12473
12987
  async (args) => toolText(guarded(root, () => modulesDiscoverTool(root, args)))
12474
12988
  );
12989
+ const CANDIDATE_DESC = "Read-only. Results are IMPACT CANDIDATES to inspect in the repository, never confirmed scope; a missing edge does not mean no impact.";
12990
+ server.registerTool(
12991
+ "kaddo_system_search",
12992
+ {
12993
+ title: "Search system entities",
12994
+ description: `Search the semantic system topology by label / kind / module / purpose (concepts before implementation). ${CANDIDATE_DESC}`,
12995
+ inputSchema: { query: z3.string() }
12996
+ },
12997
+ async (args) => toolText(guarded(root, () => systemSearchTool(root, args)))
12998
+ );
12999
+ server.registerTool(
13000
+ "kaddo_system_node",
13001
+ {
13002
+ title: "Get system entity context",
13003
+ description: `Resolve one system entity plus its incoming and outgoing relationships. ${CANDIDATE_DESC}`,
13004
+ inputSchema: { nodeId: z3.string() }
13005
+ },
13006
+ async (args) => toolText(guarded(root, () => systemNodeTool(root, args)))
13007
+ );
13008
+ server.registerTool(
13009
+ "kaddo_system_neighbors",
13010
+ {
13011
+ title: "Get bounded system neighborhood",
13012
+ description: `Bounded BFS neighborhood around a seed entity (maxDepth/maxNodes, optional relationshipTypes/moduleId). Reports truncation. ${CANDIDATE_DESC}`,
13013
+ inputSchema: {
13014
+ nodeId: z3.string(),
13015
+ maxDepth: z3.number().int().positive().optional(),
13016
+ maxNodes: z3.number().int().positive().optional(),
13017
+ relationshipTypes: z3.array(z3.string()).optional(),
13018
+ moduleId: z3.string().optional()
13019
+ }
13020
+ },
13021
+ async (args) => toolText(guarded(root, () => systemNeighborsTool(root, args)))
13022
+ );
13023
+ server.registerTool(
13024
+ "kaddo_system_paths",
13025
+ {
13026
+ title: "Find system paths",
13027
+ description: `Directed, acyclic, bounded paths between two system entities. ${CANDIDATE_DESC}`,
13028
+ inputSchema: { from: z3.string(), to: z3.string(), maxDepth: z3.number().int().positive().optional() }
13029
+ },
13030
+ async (args) => toolText(guarded(root, () => systemPathsTool(root, args)))
13031
+ );
13032
+ server.registerTool(
13033
+ "kaddo_system_impact_candidates",
13034
+ {
13035
+ title: "Get graph-assisted impact candidates",
13036
+ description: `Graph-assisted IMPACT CANDIDATES reachable from one or more seed entities, with reasons and graph paths. Widens what to investigate before deciding scope; it never decides scope, mutates the Work Item, runs an LLM or touches git. ${CANDIDATE_DESC}`,
13037
+ inputSchema: {
13038
+ seeds: z3.array(z3.string()),
13039
+ maxDepth: z3.number().int().positive().optional(),
13040
+ maxNodes: z3.number().int().positive().optional(),
13041
+ relationshipTypes: z3.array(z3.string()).optional(),
13042
+ moduleId: z3.string().optional()
13043
+ }
13044
+ },
13045
+ async (args) => toolText(guarded(root, () => systemImpactCandidatesTool(root, args)))
13046
+ );
12475
13047
  let installedSkills = [];
12476
13048
  try {
12477
13049
  assertKaddoProject(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.78.0",
3
+ "version": "3.80.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": {