@kaddo/mcp 3.79.0 → 3.81.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/dist/index.js +586 -14
- 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
|
|
484
|
-
return
|
|
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
|
|
926
|
-
if (!exists(
|
|
925
|
+
const path5 = join(dir, DESCRIPTOR_PATH);
|
|
926
|
+
if (!exists(path5)) return [];
|
|
927
927
|
let parsed;
|
|
928
928
|
try {
|
|
929
|
-
parsed = parseYaml4(readFile(
|
|
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,
|
|
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:
|
|
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
|
|
2509
|
-
if (
|
|
2510
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
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
|
|
4092
|
-
const installed = isFile(join(dir,
|
|
4091
|
+
const path5 = agentInstallPath(file);
|
|
4092
|
+
const installed = isFile(join(dir, path5));
|
|
4093
4093
|
const group = agentGroupOf(file);
|
|
4094
|
-
return { agentPath:
|
|
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(([
|
|
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