From 4c212740a2c5483c675496d43b8af5f7df644e73 Mon Sep 17 00:00:00 2001 From: Jarvis Prime Date: Mon, 9 Mar 2026 06:51:32 +0000 Subject: [PATCH] Phase 7A+7C: Subsystem aggregator + Flow tracer (post-review fixes) --- flow.js | 208 +++ subsystem.js | 205 +++ test/fixtures/system-docs/architecture.md | 17 + .../system-docs/expected-contracts.json | 104 ++ test/fixtures/system-docs/expected-deps.json | 17 + test/fixtures/system-docs/expected-flows.json | 26 + .../system-docs/expected-subsystems.json | 52 + test/fixtures/system-docs/snapshot.json | 1367 +++++++++++++++++ .../system-docs/src/agents/defaults.ts | 12 + .../fixtures/system-docs/src/agents/runner.ts | 23 + test/fixtures/system-docs/src/agents/scope.ts | 18 + test/fixtures/system-docs/src/agents/tools.ts | 15 + test/fixtures/system-docs/src/agents/types.ts | 14 + .../system-docs/src/channels/discord.ts | 12 + .../system-docs/src/channels/telegram.ts | 12 + .../fixtures/system-docs/src/config/config.ts | 22 + .../fixtures/system-docs/src/config/schema.ts | 18 + test/fixtures/system-docs/src/config/types.ts | 19 + .../system-docs/src/gateway/middleware.ts | 12 + .../system-docs/src/gateway/server.ts | 18 + .../system-docs/src/gateway/session.ts | 19 + .../fixtures/system-docs/src/gateway/types.ts | 15 + .../fixtures/system-docs/src/gateway/utils.ts | 7 + test/fixtures/system-docs/src/utils/crypto.ts | 13 + .../system-docs/src/utils/fs-helpers.ts | 15 + test/fixtures/system-docs/src/utils/logger.ts | 14 + test/test-flow.js | 93 ++ test/test-subsystem.js | 109 ++ 28 files changed, 2476 insertions(+) create mode 100644 flow.js create mode 100644 subsystem.js create mode 100644 test/fixtures/system-docs/architecture.md create mode 100644 test/fixtures/system-docs/expected-contracts.json create mode 100644 test/fixtures/system-docs/expected-deps.json create mode 100644 test/fixtures/system-docs/expected-flows.json create mode 100644 test/fixtures/system-docs/expected-subsystems.json create mode 100644 test/fixtures/system-docs/snapshot.json create mode 100644 test/fixtures/system-docs/src/agents/defaults.ts create mode 100644 test/fixtures/system-docs/src/agents/runner.ts create mode 100644 test/fixtures/system-docs/src/agents/scope.ts create mode 100644 test/fixtures/system-docs/src/agents/tools.ts create mode 100644 test/fixtures/system-docs/src/agents/types.ts create mode 100644 test/fixtures/system-docs/src/channels/discord.ts create mode 100644 test/fixtures/system-docs/src/channels/telegram.ts create mode 100644 test/fixtures/system-docs/src/config/config.ts create mode 100644 test/fixtures/system-docs/src/config/schema.ts create mode 100644 test/fixtures/system-docs/src/config/types.ts create mode 100644 test/fixtures/system-docs/src/gateway/middleware.ts create mode 100644 test/fixtures/system-docs/src/gateway/server.ts create mode 100644 test/fixtures/system-docs/src/gateway/session.ts create mode 100644 test/fixtures/system-docs/src/gateway/types.ts create mode 100644 test/fixtures/system-docs/src/gateway/utils.ts create mode 100644 test/fixtures/system-docs/src/utils/crypto.ts create mode 100644 test/fixtures/system-docs/src/utils/fs-helpers.ts create mode 100644 test/fixtures/system-docs/src/utils/logger.ts create mode 100644 test/test-flow.js create mode 100644 test/test-subsystem.js diff --git a/flow.js b/flow.js new file mode 100644 index 0000000..1c38a5a --- /dev/null +++ b/flow.js @@ -0,0 +1,208 @@ +const GraphStore = require('./graph.js'); +const { buildSubsystems, relPath, subsystemOf } = require('./subsystem.js'); + +/** + * Phase 7C: Flow Tracer + * Walks the call graph across subsystem boundaries to produce sequenced data flow narratives. + */ + +/** + * Build reusable indexes from a graph + subsystem map. + * Call once, then pass to traceFlow for each entry point. + */ +function buildFlowIndex(graph, subsystemMap, opts = {}) { + const godThreshold = opts.godThreshold || 50; + + // File → subsystem lookup + const fileSub = new Map(); + for (const sub of subsystemMap.subsystems) { + for (const f of sub.files) fileSub.set(f, sub.name); + } + + // Outgoing CALLS index: source → [targets] + const callsOut = new Map(); + for (const e of graph.edges) { + if (e.type !== 'CALLS') continue; + if (!callsOut.has(e.source)) callsOut.set(e.source, []); + callsOut.get(e.source).push(e.target); + } + + // Function name → qualified IDs + const funcLookup = new Map(); + for (const [id, node] of graph.nodes) { + if (node.type === 'Function' || node.type === 'Class') { + if (!funcLookup.has(node.name)) funcLookup.set(node.name, []); + funcLookup.get(node.name).push(id); + } + } + + // In-degree per qualified ID + const inDegree = new Map(); + for (const e of graph.edges) { + if (e.type !== 'CALLS') continue; + if (e.target.includes('/')) { + inDegree.set(e.target, (inDegree.get(e.target) || 0) + 1); + } else { + // For bare names, increment all candidates + const candidates = funcLookup.get(e.target); + if (candidates) { + for (const c of candidates) { + inDegree.set(c, (inDegree.get(c) || 0) + 1); + } + } + } + } + + // God objects: qualified IDs with in-degree > threshold + const godObjects = new Set(); + for (const [id, deg] of inDegree) { + if (deg > godThreshold) godObjects.add(id); + } + + // Subsystem cache + const subCache = new Map(); + function getSubsystem(entityId) { + if (!entityId) return null; + if (subCache.has(entityId)) return subCache.get(entityId); + const file = relPath(entityId.split(':')[0]); + const result = fileSub.get(file) || null; + subCache.set(entityId, result); + return result; + } + + // Resolve bare function name → qualified ID, preferring caller's subsystem + function resolveTarget(bareName, callerSub) { + const candidates = funcLookup.get(bareName); + if (!candidates || candidates.length === 0) return null; + if (candidates.length === 1) return candidates[0]; + for (const c of candidates) { + if (getSubsystem(c) === callerSub) return c; + } + return candidates[0]; + } + + return { fileSub, callsOut, funcLookup, godObjects, getSubsystem, resolveTarget }; +} + +/** + * Trace a flow from an entry point through the call graph. + * @param {string} entryPoint - Entity ID (e.g. "channels/telegram.ts:onTelegramMessage") + * @param {object} index - Precomputed index from buildFlowIndex + * @param {object} opts - Options: { maxDepth, timeoutMs } + */ +function traceFlow(entryPoint, index, opts = {}) { + const maxDepth = opts.maxDepth || 8; + const timeout = opts.timeoutMs || 5000; + const startTime = Date.now(); + + const { callsOut, godObjects, getSubsystem, resolveTarget } = index; + + const visited = new Set(); + const flow = []; + const cyclesDetected = []; + const excludedNodes = new Set(); + + const entrySub = getSubsystem(entryPoint); + if (!entrySub) { + return { entryPoint, error: `Entry point "${entryPoint}" not found in any subsystem`, flow: [], subsystemSequence: [], cyclesDetected: [], excludedNodes: [] }; + } + + // BFS with index pointer (no shift) + const queue = [[entryPoint, 0]]; + let head = 0; + visited.add(entryPoint); + flow.push({ subsystem: entrySub, entity: entryPoint, depth: 0 }); + + const subsystemSequence = [entrySub]; + const seqSet = new Set([entrySub]); + + while (head < queue.length) { + if (Date.now() - startTime > timeout) break; + + const [current, depth] = queue[head++]; + const currentSub = getSubsystem(current); + + const targets = callsOut.get(current) || []; + for (const rawTarget of targets) { + // Skip test files + if (rawTarget.includes('.test.') || rawTarget.includes('.spec.') || rawTarget.includes('__tests__/')) continue; + + let resolvedTarget = rawTarget; + let targetSub = null; + + if (rawTarget.includes('/')) { + // Qualified target — check god object by qualified ID + if (godObjects.has(rawTarget)) { + excludedNodes.add(rawTarget); + continue; + } + targetSub = getSubsystem(rawTarget); + } else { + // Bare name — resolve to qualified ID first, then check god status + resolvedTarget = resolveTarget(rawTarget, currentSub); + if (!resolvedTarget) continue; + if (godObjects.has(resolvedTarget)) { + excludedNodes.add(resolvedTarget); + continue; + } + targetSub = getSubsystem(resolvedTarget); + } + + if (!targetSub) continue; + + // Cycle detection + if (visited.has(resolvedTarget)) { + cyclesDetected.push({ at: current, backEdgeTo: resolvedTarget }); + continue; + } + + // Compute new depth + const isCrossSubsystem = targetSub !== currentSub; + const newDepth = depth + (isCrossSubsystem ? 1 : 0.5); + + if (newDepth > maxDepth) continue; + + visited.add(resolvedTarget); + flow.push({ + subsystem: targetSub, + entity: resolvedTarget, + depth: newDepth, + ...(isCrossSubsystem ? { crossedVia: 'CALLS' } : {}) + }); + + if (isCrossSubsystem && !seqSet.has(targetSub)) { + subsystemSequence.push(targetSub); + seqSet.add(targetSub); + } + + queue.push([resolvedTarget, newDepth]); + } + } + + return { + entryPoint, + depth: maxDepth, + godThreshold: index.godObjects.size > 0 ? 'applied' : 'none', + excludedNodes: Array.from(excludedNodes), + cyclesDetected, + flow, + subsystemSequence + }; +} + +if (require.main === module) { + const snapshotPath = process.argv[2]; + const entryPoint = process.argv[3]; + const godThreshold = parseInt(process.argv[4]) || 50; + if (!snapshotPath || !entryPoint) { + console.error('Usage: node flow.js [godThreshold]'); + process.exit(1); + } + const graph = GraphStore.loadSnapshot(snapshotPath); + const subsystemMap = buildSubsystems(graph); + const index = buildFlowIndex(graph, subsystemMap, { godThreshold }); + const result = traceFlow(entryPoint, index); + console.log(JSON.stringify(result, null, 2)); +} + +module.exports = { buildFlowIndex, traceFlow }; diff --git a/subsystem.js b/subsystem.js new file mode 100644 index 0000000..8f70100 --- /dev/null +++ b/subsystem.js @@ -0,0 +1,205 @@ +const fs = require('fs'); +const path = require('path'); +const GraphStore = require('./graph.js'); + +/** + * Phase 7A: Subsystem Aggregator + * Groups files into subsystems and computes cross-subsystem dependency matrix. + */ + +/** Normalize absolute paths to relative (strips up to /src/ or configurable srcDir) */ +function relPath(file, srcMarker) { + if (!file) return ''; + srcMarker = srcMarker || '/src/'; + if (file.startsWith('/')) { + const idx = file.indexOf(srcMarker); + if (idx !== -1) return file.substring(idx + srcMarker.length); + return file.split('/').slice(-2).join('/'); + } + return file; +} + +/** Get subsystem name from a relative file path */ +function subsystemOf(relFile) { + const parts = relFile.split('/'); + return parts.length > 1 ? parts[0] : 'root'; +} + +function buildSubsystems(graph, opts = {}) { + const srcMarker = opts.srcDir || '/src/'; + const crossCuttingMinTraffic = opts.minTraffic || 10; + const crossCuttingThreshold = opts.crossCuttingThreshold || 0.75; + + const subsystems = new Map(); + + // Build function name → qualified node ID lookup + const funcLookup = new Map(); + for (const [id, node] of graph.nodes) { + if (node.type === 'Function' || node.type === 'Class') { + if (!funcLookup.has(node.name)) funcLookup.set(node.name, []); + funcLookup.get(node.name).push(id); + } + } + + // 1. Directory-based clustering (using relPath for DRY) + for (const file of graph.fileIndex.keys()) { + const rel = relPath(file, srcMarker); + if (rel.startsWith('dep:')) continue; + const subName = subsystemOf(rel); + + if (!subsystems.has(subName)) { + subsystems.set(subName, { + name: subName, + kind: 'domain', + files: new Set(), + entities: { functions: 0, classes: 0, modules: 0 }, + publicExports: new Set(), + }); + } + + const sub = subsystems.get(subName); + sub.files.add(rel); + + const ids = graph.fileIndex.get(file); + if (ids) { + for (const id of ids) { + const node = graph.nodes.get(id); + if (!node) continue; + if (node.type === 'Module') sub.entities.modules++; + else if (node.type === 'Function') { + sub.entities.functions++; + if (node.visibility === 'public') sub.publicExports.add(node.name); + } + else if (node.type === 'Class') { + sub.entities.classes++; + if (node.visibility === 'public') sub.publicExports.add(node.name); + } + } + } + } + + // 2. Compute inter-subsystem edges + const matrix = {}; + const inboundCross = {}; + const outboundCross = {}; + + for (const name of subsystems.keys()) { + inboundCross[name] = 0; + outboundCross[name] = 0; + } + + // Cache: nodeId → subsystem name + const nodeSubCache = new Map(); + + function resolveSubsystem(nodeId) { + if (!nodeId) return null; + if (nodeSubCache.has(nodeId)) return nodeSubCache.get(nodeId); + const rel = relPath(nodeId.split(':')[0], srcMarker); + const sub = subsystemOf(rel); + const result = subsystems.has(sub) ? sub : null; + nodeSubCache.set(nodeId, result); + return result; + } + + function resolveCallTarget(callerSub, targetName) { + const candidates = funcLookup.get(targetName); + if (!candidates || candidates.length === 0) return null; + if (candidates.length === 1) return resolveSubsystem(candidates[0]); + let sameSub = null; + let diffSub = null; + for (const cid of candidates) { + const s = resolveSubsystem(cid); + if (s === callerSub) sameSub = s; + else if (s) diffSub = s; + } + return sameSub || diffSub; + } + + for (const e of graph.edges) { + if (e.type === 'CONTAINS') continue; + + let srcSub = resolveSubsystem(e.source); + if (!srcSub) continue; + + let tgtSub = null; + + if (e.type === 'IMPORTS') { + const dep = e.target.replace('dep:', ''); + tgtSub = subsystemOf(dep); + } else if (e.type === 'CALLS') { + if (e.target.includes('/')) tgtSub = resolveSubsystem(e.target); + else tgtSub = resolveCallTarget(srcSub, e.target); + } else if (e.type === 'IMPLEMENTS') { + if (e.target.includes('/')) tgtSub = resolveSubsystem(e.target); + } + + if (!tgtSub || !subsystems.has(tgtSub)) continue; + if (srcSub === tgtSub) continue; + + const key = `${srcSub}→${tgtSub}`; + if (!matrix[key]) matrix[key] = { calls: 0, imports: 0, via: new Set() }; + + if (e.type === 'CALLS') matrix[key].calls++; + else if (e.type === 'IMPORTS') matrix[key].imports++; + + if (matrix[key].via.size < 5) { + matrix[key].via.add(`${e.source}→${e.target}`); + } + + inboundCross[tgtSub]++; + outboundCross[srcSub]++; + } + + // 3. Cross-cutting detection (high fan-in ratio + minimum traffic) + const crossCutting = []; + for (const name of subsystems.keys()) { + const totalCross = inboundCross[name] + outboundCross[name]; + if (totalCross >= crossCuttingMinTraffic) { + const inboundRatio = inboundCross[name] / totalCross; + if (inboundRatio > crossCuttingThreshold) { + subsystems.get(name).kind = 'cross-cutting'; + crossCutting.push(name); + } + } + } + + // Format output + const result = { + subsystems: [], + crossCutting, + dependencyMatrix: {} + }; + + for (const sub of subsystems.values()) { + result.subsystems.push({ + name: sub.name, + kind: sub.kind, + files: Array.from(sub.files).sort(), + entities: sub.entities, + publicExports: Array.from(sub.publicExports).sort() + }); + } + + for (const [key, val] of Object.entries(matrix)) { + result.dependencyMatrix[key] = { + calls: val.calls, + imports: val.imports, + via: Array.from(val.via) + }; + } + + return result; +} + +if (require.main === module) { + const snapshotPath = process.argv[2]; + if (!snapshotPath) { + console.error('Usage: node subsystem.js '); + process.exit(1); + } + const graph = GraphStore.loadSnapshot(snapshotPath); + const result = buildSubsystems(graph); + console.log(JSON.stringify(result, null, 2)); +} + +module.exports = { buildSubsystems, relPath, subsystemOf }; diff --git a/test/fixtures/system-docs/architecture.md b/test/fixtures/system-docs/architecture.md new file mode 100644 index 0000000..75ea5d9 --- /dev/null +++ b/test/fixtures/system-docs/architecture.md @@ -0,0 +1,17 @@ +# Mock Architecture Documentation + +## Purpose +This is a test fixture simulating a real repo's architecture.md file. + +## Layered Architecture +The system follows a layered architecture: +- **Channels** receive inbound messages from external platforms +- **Gateway** manages sessions and routes requests +- **Agents** execute AI model interactions +- **Config** provides centralized configuration +- **Utils** provides shared infrastructure (logging, crypto, filesystem) + +## Design Decisions +- Gateway and Agents have a deliberate circular dependency for session refresh workflows +- Config is loaded lazily and cached in memory +- Utils are stateless pure functions with no domain logic diff --git a/test/fixtures/system-docs/expected-contracts.json b/test/fixtures/system-docs/expected-contracts.json new file mode 100644 index 0000000..90a7409 --- /dev/null +++ b/test/fixtures/system-docs/expected-contracts.json @@ -0,0 +1,104 @@ +{ + "contracts": [ + { + "id": "gateway/types.ts:GatewayConfig", + "type": "Interface", + "name": "GatewayConfig", + "extends": ["BaseConfig"], + "fields": [ + { "name": "sessionKey", "type": "string" }, + { "name": "timeout", "type": "number" } + ] + }, + { + "id": "gateway/types.ts:SessionEntry", + "type": "Interface", + "name": "SessionEntry", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "agentId", "type": "string" }, + { "name": "model", "type": "string" } + ] + }, + { + "id": "agents/scope.ts:AgentScope", + "type": "Interface", + "name": "AgentScope", + "fields": [ + { "name": "agentId", "type": "string" }, + { "name": "tools", "type": "string[]" } + ] + }, + { + "id": "agents/types.ts:AgentConfig", + "type": "Interface", + "name": "AgentConfig", + "fields": [ + { "name": "agentId", "type": "string" }, + { "name": "model", "type": "string" }, + { "name": "maxTokens", "type": "number" } + ] + }, + { + "id": "agents/types.ts:AgentResult", + "type": "Interface", + "name": "AgentResult", + "fields": [ + { "name": "output", "type": "string" }, + { "name": "tokensUsed", "type": "number" }, + { "name": "duration", "type": "number" } + ] + }, + { + "id": "agents/types.ts:AgentStatus", + "type": "TypeAlias", + "name": "AgentStatus" + }, + { + "id": "config/types.ts:BaseConfig", + "type": "Interface", + "name": "BaseConfig", + "fields": [ + { "name": "defaultAgent", "type": "string" }, + { "name": "model", "type": "string" }, + { "name": "debug", "type": "boolean" } + ] + }, + { + "id": "config/types.ts:ProviderConfig", + "type": "Interface", + "name": "ProviderConfig", + "fields": [ + { "name": "name", "type": "string" }, + { "name": "apiKey", "type": "string" }, + { "name": "baseUrl", "type": "string" } + ] + }, + { + "id": "config/types.ts:LogLevel", + "type": "Enum", + "name": "LogLevel", + "members": ["Debug", "Info", "Warn", "Error"] + }, + { + "id": "config/schema.ts:SchemaDefinition", + "type": "Interface", + "name": "SchemaDefinition", + "fields": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "fields", "type": "SchemaField[]" } + ] + }, + { + "id": "config/schema.ts:SchemaField", + "type": "Interface", + "name": "SchemaField", + "fields": [ + { "name": "name", "type": "string" }, + { "name": "type", "type": "string" }, + { "name": "required", "type": "boolean" } + ] + } + ] +} diff --git a/test/fixtures/system-docs/expected-deps.json b/test/fixtures/system-docs/expected-deps.json new file mode 100644 index 0000000..4b8953c --- /dev/null +++ b/test/fixtures/system-docs/expected-deps.json @@ -0,0 +1,17 @@ +{ + "edges": [ + { "from": "gateway", "to": "agents", "type": "CALLS", "via": ["gateway/session.ts:refreshSession→agents/runner.ts:runAgent"] }, + { "from": "gateway", "to": "config", "type": "IMPORTS", "via": ["gateway/session.ts→config/config.ts", "gateway/types.ts→config/types.ts"] }, + { "from": "gateway", "to": "utils", "type": "CALLS", "via": ["gateway/middleware.ts:applyMiddleware→utils/logger.ts:log", "gateway/utils.ts:formatSessionKey→utils/logger.ts:log"] }, + { "from": "agents", "to": "gateway", "type": "CALLS", "via": ["agents/runner.ts:reloadAgent→gateway/session.ts:loadSession"] }, + { "from": "agents", "to": "config", "type": "IMPORTS", "via": ["agents/scope.ts→config/config.ts"] }, + { "from": "agents", "to": "utils", "type": "CALLS", "via": ["agents/runner.ts:runAgent→utils/logger.ts:log", "agents/scope.ts:createAgentScope→utils/logger.ts:log", "agents/tools.ts→utils/logger.ts:log"] }, + { "from": "channels", "to": "gateway", "type": "CALLS", "via": ["channels/telegram.ts:onTelegramMessage→gateway/server.ts:handleRequest", "channels/discord.ts:onDiscordMessage→gateway/server.ts:handleRequest"] }, + { "from": "channels", "to": "utils", "type": "CALLS", "via": ["channels/telegram.ts→utils/logger.ts:log", "channels/discord.ts→utils/logger.ts:log"] }, + { "from": "config", "to": "utils", "type": "CALLS", "via": ["config/config.ts:loadConfigFromDisk→utils/logger.ts:log"] }, + { "from": "utils", "to": "utils", "type": "CALLS", "via": ["utils/fs-helpers.ts→utils/logger.ts:log"] } + ], + "cycles": [ + { "subsystems": ["gateway", "agents"], "via": "gateway/session.ts↔agents/runner.ts" } + ] +} diff --git a/test/fixtures/system-docs/expected-flows.json b/test/fixtures/system-docs/expected-flows.json new file mode 100644 index 0000000..7f6d700 --- /dev/null +++ b/test/fixtures/system-docs/expected-flows.json @@ -0,0 +1,26 @@ +{ + "flows": [ + { + "entryPoint": "channels/telegram.ts:onTelegramMessage", + "subsystemSequence": ["channels", "gateway", "agents"], + "flow": [ + { "subsystem": "channels", "entity": "channels/telegram.ts:onTelegramMessage", "depth": 0 }, + { "subsystem": "gateway", "entity": "gateway/server.ts:handleRequest", "depth": 1, "crossedVia": "CALLS" }, + { "subsystem": "gateway", "entity": "gateway/session.ts:loadSession", "depth": 1.5, "crossedVia": "CALLS" } + ], + "cyclesDetected": [] + }, + { + "entryPoint": "gateway/session.ts:refreshSession", + "subsystemSequence": ["gateway", "agents"], + "flow": [ + { "subsystem": "gateway", "entity": "gateway/session.ts:refreshSession", "depth": 0 }, + { "subsystem": "gateway", "entity": "gateway/session.ts:loadSession", "depth": 0.5, "crossedVia": "CALLS" }, + { "subsystem": "agents", "entity": "agents/runner.ts:runAgent", "depth": 1, "crossedVia": "CALLS" } + ], + "cyclesDetected": [ + { "at": "agents/runner.ts:reloadAgent", "backEdgeTo": "gateway/session.ts:loadSession" } + ] + } + ] +} diff --git a/test/fixtures/system-docs/expected-subsystems.json b/test/fixtures/system-docs/expected-subsystems.json new file mode 100644 index 0000000..f656bb6 --- /dev/null +++ b/test/fixtures/system-docs/expected-subsystems.json @@ -0,0 +1,52 @@ +{ + "subsystems": [ + { + "name": "gateway", + "kind": "domain", + "files": [ + "gateway/server.ts", + "gateway/session.ts", + "gateway/middleware.ts", + "gateway/types.ts", + "gateway/utils.ts" + ] + }, + { + "name": "agents", + "kind": "domain", + "files": [ + "agents/runner.ts", + "agents/scope.ts", + "agents/tools.ts", + "agents/types.ts", + "agents/defaults.ts" + ] + }, + { + "name": "channels", + "kind": "domain", + "files": [ + "channels/telegram.ts", + "channels/discord.ts" + ] + }, + { + "name": "config", + "kind": "cross-cutting", + "files": [ + "config/config.ts", + "config/types.ts", + "config/schema.ts" + ] + }, + { + "name": "utils", + "kind": "cross-cutting", + "files": [ + "utils/logger.ts", + "utils/crypto.ts", + "utils/fs-helpers.ts" + ] + } + ] +} diff --git a/test/fixtures/system-docs/snapshot.json b/test/fixtures/system-docs/snapshot.json new file mode 100644 index 0000000..784a2ee --- /dev/null +++ b/test/fixtures/system-docs/snapshot.json @@ -0,0 +1,1367 @@ +{ + "nodes": { + "agents/defaults.ts": { + "id": "agents/defaults.ts", + "type": "Module", + "name": "agents/defaults.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 13 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/defaults.ts" + }, + "dep:agents/types": { + "id": "dep:agents/types", + "type": "Dependency", + "name": "agents/types", + "kind": "import", + "visibility": "internal", + "line_range": [ + 2, + 2 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/defaults.ts" + }, + "agents/defaults.ts:getDefaultAgent": { + "id": "agents/defaults.ts:getDefaultAgent", + "type": "Function", + "name": "getDefaultAgent", + "kind": "function", + "visibility": "public", + "line_range": [ + 10, + 12 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/defaults.ts" + }, + "agents/runner.ts": { + "id": "agents/runner.ts", + "type": "Module", + "name": "agents/runner.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 24 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts" + }, + "dep:gateway/session": { + "id": "dep:gateway/session", + "type": "Dependency", + "name": "gateway/session", + "kind": "import", + "visibility": "internal", + "line_range": [ + 2, + 2 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts" + }, + "dep:agents/scope": { + "id": "dep:agents/scope", + "type": "Dependency", + "name": "agents/scope", + "kind": "import", + "visibility": "internal", + "line_range": [ + 3, + 3 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts" + }, + "dep:utils/logger": { + "id": "dep:utils/logger", + "type": "Dependency", + "name": "utils/logger", + "kind": "import", + "visibility": "internal", + "line_range": [ + 2, + 2 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/fs-helpers.ts" + }, + "agents/runner.ts:runAgent": { + "id": "agents/runner.ts:runAgent", + "type": "Function", + "name": "runAgent", + "kind": "function", + "visibility": "public", + "line_range": [ + 6, + 10 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts" + }, + "agents/runner.ts:createScope": { + "id": "agents/runner.ts:createScope", + "type": "Function", + "name": "createScope", + "kind": "function", + "visibility": "internal", + "line_range": [ + 12, + 14 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts" + }, + "agents/runner.ts:executeAgent": { + "id": "agents/runner.ts:executeAgent", + "type": "Function", + "name": "executeAgent", + "kind": "function", + "visibility": "internal", + "line_range": [ + 16, + 18 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts" + }, + "agents/runner.ts:reloadAgent": { + "id": "agents/runner.ts:reloadAgent", + "type": "Function", + "name": "reloadAgent", + "kind": "function", + "visibility": "public", + "line_range": [ + 20, + 23 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts" + }, + "agents/scope.ts": { + "id": "agents/scope.ts", + "type": "Module", + "name": "agents/scope.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 19 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/scope.ts" + }, + "dep:config/config": { + "id": "dep:config/config", + "type": "Dependency", + "name": "config/config", + "kind": "import", + "visibility": "internal", + "line_range": [ + 2, + 2 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "agents/scope.ts:createAgentScope": { + "id": "agents/scope.ts:createAgentScope", + "type": "Function", + "name": "createAgentScope", + "kind": "function", + "visibility": "public", + "line_range": [ + 10, + 14 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/scope.ts" + }, + "agents/scope.ts:getDefaultTools": { + "id": "agents/scope.ts:getDefaultTools", + "type": "Function", + "name": "getDefaultTools", + "kind": "function", + "visibility": "public", + "line_range": [ + 16, + 18 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/scope.ts" + }, + "agents/tools.ts": { + "id": "agents/tools.ts", + "type": "Module", + "name": "agents/tools.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 16 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/tools.ts" + }, + "agents/tools.ts:registerTool": { + "id": "agents/tools.ts:registerTool", + "type": "Function", + "name": "registerTool", + "kind": "function", + "visibility": "public", + "line_range": [ + 4, + 6 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/tools.ts" + }, + "agents/tools.ts:executeTool": { + "id": "agents/tools.ts:executeTool", + "type": "Function", + "name": "executeTool", + "kind": "function", + "visibility": "public", + "line_range": [ + 8, + 11 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/tools.ts" + }, + "agents/tools.ts:listTools": { + "id": "agents/tools.ts:listTools", + "type": "Function", + "name": "listTools", + "kind": "function", + "visibility": "public", + "line_range": [ + 13, + 15 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/tools.ts" + }, + "agents/types.ts": { + "id": "agents/types.ts", + "type": "Module", + "name": "agents/types.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 15 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/types.ts" + }, + "channels/discord.ts": { + "id": "channels/discord.ts", + "type": "Module", + "name": "channels/discord.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 13 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/discord.ts" + }, + "dep:gateway/server": { + "id": "dep:gateway/server", + "type": "Dependency", + "name": "gateway/server", + "kind": "import", + "visibility": "internal", + "line_range": [ + 2, + 2 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/telegram.ts" + }, + "channels/discord.ts:onDiscordMessage": { + "id": "channels/discord.ts:onDiscordMessage", + "type": "Function", + "name": "onDiscordMessage", + "kind": "function", + "visibility": "public", + "line_range": [ + 5, + 8 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/discord.ts" + }, + "channels/discord.ts:sendDiscordReply": { + "id": "channels/discord.ts:sendDiscordReply", + "type": "Function", + "name": "sendDiscordReply", + "kind": "function", + "visibility": "public", + "line_range": [ + 10, + 12 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/discord.ts" + }, + "channels/telegram.ts": { + "id": "channels/telegram.ts", + "type": "Module", + "name": "channels/telegram.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 13 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/telegram.ts" + }, + "channels/telegram.ts:onTelegramMessage": { + "id": "channels/telegram.ts:onTelegramMessage", + "type": "Function", + "name": "onTelegramMessage", + "kind": "function", + "visibility": "public", + "line_range": [ + 5, + 8 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/telegram.ts" + }, + "channels/telegram.ts:sendTelegramReply": { + "id": "channels/telegram.ts:sendTelegramReply", + "type": "Function", + "name": "sendTelegramReply", + "kind": "function", + "visibility": "public", + "line_range": [ + 10, + 12 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/telegram.ts" + }, + "config/config.ts": { + "id": "config/config.ts", + "type": "Module", + "name": "config/config.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 23 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/config.ts" + }, + "dep:config/types": { + "id": "dep:config/types", + "type": "Dependency", + "name": "config/types", + "kind": "import", + "visibility": "internal", + "line_range": [ + 2, + 2 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/types.ts" + }, + "config/config.ts:getConfig": { + "id": "config/config.ts:getConfig", + "type": "Function", + "name": "getConfig", + "kind": "function", + "visibility": "public", + "line_range": [ + 7, + 12 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/config.ts" + }, + "config/config.ts:loadConfigFromDisk": { + "id": "config/config.ts:loadConfigFromDisk", + "type": "Function", + "name": "loadConfigFromDisk", + "kind": "function", + "visibility": "internal", + "line_range": [ + 14, + 17 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/config.ts" + }, + "config/config.ts:reloadConfig": { + "id": "config/config.ts:reloadConfig", + "type": "Function", + "name": "reloadConfig", + "kind": "function", + "visibility": "public", + "line_range": [ + 19, + 22 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/config.ts" + }, + "config/schema.ts": { + "id": "config/schema.ts", + "type": "Module", + "name": "config/schema.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 19 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/schema.ts" + }, + "config/schema.ts:validateSchema": { + "id": "config/schema.ts:validateSchema", + "type": "Function", + "name": "validateSchema", + "kind": "function", + "visibility": "public", + "line_range": [ + 16, + 18 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/schema.ts" + }, + "config/types.ts": { + "id": "config/types.ts", + "type": "Module", + "name": "config/types.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 20 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/types.ts" + }, + "gateway/middleware.ts": { + "id": "gateway/middleware.ts", + "type": "Module", + "name": "gateway/middleware.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 13 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/middleware.ts" + }, + "dep:gateway/types": { + "id": "dep:gateway/types", + "type": "Dependency", + "name": "gateway/types", + "kind": "import", + "visibility": "internal", + "line_range": [ + 4, + 4 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "gateway/middleware.ts:applyMiddleware": { + "id": "gateway/middleware.ts:applyMiddleware", + "type": "Function", + "name": "applyMiddleware", + "kind": "function", + "visibility": "public", + "line_range": [ + 5, + 8 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/middleware.ts" + }, + "gateway/middleware.ts:validateSession": { + "id": "gateway/middleware.ts:validateSession", + "type": "Function", + "name": "validateSession", + "kind": "function", + "visibility": "internal", + "line_range": [ + 10, + 12 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/middleware.ts" + }, + "gateway/server.ts": { + "id": "gateway/server.ts", + "type": "Module", + "name": "gateway/server.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 19 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts" + }, + "dep:gateway/middleware": { + "id": "dep:gateway/middleware", + "type": "Dependency", + "name": "gateway/middleware", + "kind": "import", + "visibility": "internal", + "line_range": [ + 3, + 3 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts" + }, + "gateway/server.ts:startGateway": { + "id": "gateway/server.ts:startGateway", + "type": "Function", + "name": "startGateway", + "kind": "function", + "visibility": "public", + "line_range": [ + 6, + 9 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts" + }, + "gateway/server.ts:handleRequest": { + "id": "gateway/server.ts:handleRequest", + "type": "Function", + "name": "handleRequest", + "kind": "function", + "visibility": "public", + "line_range": [ + 11, + 14 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts" + }, + "gateway/server.ts:shutdownGateway": { + "id": "gateway/server.ts:shutdownGateway", + "type": "Function", + "name": "shutdownGateway", + "kind": "function", + "visibility": "public", + "line_range": [ + 16, + 18 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts" + }, + "gateway/session.ts": { + "id": "gateway/session.ts", + "type": "Module", + "name": "gateway/session.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 20 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "dep:agents/runner": { + "id": "dep:agents/runner", + "type": "Dependency", + "name": "agents/runner", + "kind": "import", + "visibility": "internal", + "line_range": [ + 3, + 3 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "gateway/session.ts:loadSession": { + "id": "gateway/session.ts:loadSession", + "type": "Function", + "name": "loadSession", + "kind": "function", + "visibility": "public", + "line_range": [ + 6, + 9 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "gateway/session.ts:saveSession": { + "id": "gateway/session.ts:saveSession", + "type": "Function", + "name": "saveSession", + "kind": "function", + "visibility": "public", + "line_range": [ + 11, + 13 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "gateway/session.ts:refreshSession": { + "id": "gateway/session.ts:refreshSession", + "type": "Function", + "name": "refreshSession", + "kind": "function", + "visibility": "public", + "line_range": [ + 15, + 19 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts" + }, + "gateway/types.ts": { + "id": "gateway/types.ts", + "type": "Module", + "name": "gateway/types.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 16 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/types.ts" + }, + "gateway/utils.ts": { + "id": "gateway/utils.ts", + "type": "Module", + "name": "gateway/utils.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 8 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/utils.ts" + }, + "gateway/utils.ts:formatSessionKey": { + "id": "gateway/utils.ts:formatSessionKey", + "type": "Function", + "name": "formatSessionKey", + "kind": "function", + "visibility": "public", + "line_range": [ + 4, + 7 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/utils.ts" + }, + "utils/crypto.ts": { + "id": "utils/crypto.ts", + "type": "Module", + "name": "utils/crypto.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 14 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/crypto.ts" + }, + "utils/crypto.ts:hashString": { + "id": "utils/crypto.ts:hashString", + "type": "Function", + "name": "hashString", + "kind": "function", + "visibility": "public", + "line_range": [ + 2, + 4 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/crypto.ts" + }, + "utils/crypto.ts:generateToken": { + "id": "utils/crypto.ts:generateToken", + "type": "Function", + "name": "generateToken", + "kind": "function", + "visibility": "public", + "line_range": [ + 6, + 13 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/crypto.ts" + }, + "utils/fs-helpers.ts": { + "id": "utils/fs-helpers.ts", + "type": "Module", + "name": "utils/fs-helpers.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 16 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/fs-helpers.ts" + }, + "utils/fs-helpers.ts:readFileSync": { + "id": "utils/fs-helpers.ts:readFileSync", + "type": "Function", + "name": "readFileSync", + "kind": "function", + "visibility": "public", + "line_range": [ + 4, + 7 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/fs-helpers.ts" + }, + "utils/fs-helpers.ts:writeFileSync": { + "id": "utils/fs-helpers.ts:writeFileSync", + "type": "Function", + "name": "writeFileSync", + "kind": "function", + "visibility": "public", + "line_range": [ + 9, + 11 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/fs-helpers.ts" + }, + "utils/fs-helpers.ts:fileExists": { + "id": "utils/fs-helpers.ts:fileExists", + "type": "Function", + "name": "fileExists", + "kind": "function", + "visibility": "public", + "line_range": [ + 13, + 15 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/fs-helpers.ts" + }, + "utils/logger.ts": { + "id": "utils/logger.ts", + "type": "Module", + "name": "utils/logger.ts", + "kind": "module", + "visibility": "public", + "line_range": [ + 1, + 15 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/logger.ts" + }, + "utils/logger.ts:log": { + "id": "utils/logger.ts:log", + "type": "Function", + "name": "log", + "kind": "function", + "visibility": "public", + "line_range": [ + 4, + 6 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/logger.ts" + }, + "utils/logger.ts:warn": { + "id": "utils/logger.ts:warn", + "type": "Function", + "name": "warn", + "kind": "function", + "visibility": "public", + "line_range": [ + 8, + 10 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/logger.ts" + }, + "utils/logger.ts:error": { + "id": "utils/logger.ts:error", + "type": "Function", + "name": "error", + "kind": "function", + "visibility": "public", + "line_range": [ + 12, + 14 + ], + "_file": "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/logger.ts" + } + }, + "edges": [ + { + "type": "IMPORTS", + "source": "agents/defaults.ts", + "target": "dep:agents/types" + }, + { + "type": "CONTAINS", + "source": "agents/defaults.ts", + "target": "agents/defaults.ts:getDefaultAgent" + }, + { + "type": "IMPORTS", + "source": "agents/runner.ts", + "target": "dep:gateway/session" + }, + { + "type": "IMPORTS", + "source": "agents/runner.ts", + "target": "dep:agents/scope" + }, + { + "type": "IMPORTS", + "source": "agents/runner.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "agents/runner.ts", + "target": "agents/runner.ts:runAgent" + }, + { + "type": "CALLS", + "source": "agents/runner.ts:runAgent", + "target": "log" + }, + { + "type": "CALLS", + "source": "agents/runner.ts:runAgent", + "target": "createScope" + }, + { + "type": "CALLS", + "source": "agents/runner.ts:runAgent", + "target": "executeAgent" + }, + { + "type": "CONTAINS", + "source": "agents/runner.ts", + "target": "agents/runner.ts:createScope" + }, + { + "type": "CALLS", + "source": "agents/runner.ts:createScope", + "target": "getDefaultTools" + }, + { + "type": "CONTAINS", + "source": "agents/runner.ts", + "target": "agents/runner.ts:executeAgent" + }, + { + "type": "CONTAINS", + "source": "agents/runner.ts", + "target": "agents/runner.ts:reloadAgent" + }, + { + "type": "CALLS", + "source": "agents/runner.ts:reloadAgent", + "target": "loadSession" + }, + { + "type": "CALLS", + "source": "agents/runner.ts:reloadAgent", + "target": "runAgent" + }, + { + "type": "IMPORTS", + "source": "agents/scope.ts", + "target": "dep:config/config" + }, + { + "type": "IMPORTS", + "source": "agents/scope.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "agents/scope.ts", + "target": "agents/scope.ts:createAgentScope" + }, + { + "type": "CALLS", + "source": "agents/scope.ts:createAgentScope", + "target": "log" + }, + { + "type": "CALLS", + "source": "agents/scope.ts:createAgentScope", + "target": "getConfig" + }, + { + "type": "CALLS", + "source": "agents/scope.ts:createAgentScope", + "target": "getDefaultTools" + }, + { + "type": "CONTAINS", + "source": "agents/scope.ts", + "target": "agents/scope.ts:getDefaultTools" + }, + { + "type": "IMPORTS", + "source": "agents/tools.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "agents/tools.ts", + "target": "agents/tools.ts:registerTool" + }, + { + "type": "CALLS", + "source": "agents/tools.ts:registerTool", + "target": "log" + }, + { + "type": "CONTAINS", + "source": "agents/tools.ts", + "target": "agents/tools.ts:executeTool" + }, + { + "type": "CALLS", + "source": "agents/tools.ts:executeTool", + "target": "log" + }, + { + "type": "CONTAINS", + "source": "agents/tools.ts", + "target": "agents/tools.ts:listTools" + }, + { + "type": "IMPORTS", + "source": "channels/discord.ts", + "target": "dep:gateway/server" + }, + { + "type": "IMPORTS", + "source": "channels/discord.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "channels/discord.ts", + "target": "channels/discord.ts:onDiscordMessage" + }, + { + "type": "CALLS", + "source": "channels/discord.ts:onDiscordMessage", + "target": "log" + }, + { + "type": "CALLS", + "source": "channels/discord.ts:onDiscordMessage", + "target": "handleRequest" + }, + { + "type": "CONTAINS", + "source": "channels/discord.ts", + "target": "channels/discord.ts:sendDiscordReply" + }, + { + "type": "CALLS", + "source": "channels/discord.ts:sendDiscordReply", + "target": "log" + }, + { + "type": "IMPORTS", + "source": "channels/telegram.ts", + "target": "dep:gateway/server" + }, + { + "type": "IMPORTS", + "source": "channels/telegram.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "channels/telegram.ts", + "target": "channels/telegram.ts:onTelegramMessage" + }, + { + "type": "CALLS", + "source": "channels/telegram.ts:onTelegramMessage", + "target": "log" + }, + { + "type": "CALLS", + "source": "channels/telegram.ts:onTelegramMessage", + "target": "handleRequest" + }, + { + "type": "CONTAINS", + "source": "channels/telegram.ts", + "target": "channels/telegram.ts:sendTelegramReply" + }, + { + "type": "CALLS", + "source": "channels/telegram.ts:sendTelegramReply", + "target": "log" + }, + { + "type": "IMPORTS", + "source": "config/config.ts", + "target": "dep:config/types" + }, + { + "type": "IMPORTS", + "source": "config/config.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "config/config.ts", + "target": "config/config.ts:getConfig" + }, + { + "type": "CALLS", + "source": "config/config.ts:getConfig", + "target": "loadConfigFromDisk" + }, + { + "type": "CONTAINS", + "source": "config/config.ts", + "target": "config/config.ts:loadConfigFromDisk" + }, + { + "type": "CALLS", + "source": "config/config.ts:loadConfigFromDisk", + "target": "log" + }, + { + "type": "CONTAINS", + "source": "config/config.ts", + "target": "config/config.ts:reloadConfig" + }, + { + "type": "CALLS", + "source": "config/config.ts:reloadConfig", + "target": "getConfig" + }, + { + "type": "CONTAINS", + "source": "config/schema.ts", + "target": "config/schema.ts:validateSchema" + }, + { + "type": "IMPORTS", + "source": "gateway/middleware.ts", + "target": "dep:utils/logger" + }, + { + "type": "IMPORTS", + "source": "gateway/middleware.ts", + "target": "dep:gateway/types" + }, + { + "type": "CONTAINS", + "source": "gateway/middleware.ts", + "target": "gateway/middleware.ts:applyMiddleware" + }, + { + "type": "CALLS", + "source": "gateway/middleware.ts:applyMiddleware", + "target": "log" + }, + { + "type": "CALLS", + "source": "gateway/middleware.ts:applyMiddleware", + "target": "validateSession" + }, + { + "type": "CONTAINS", + "source": "gateway/middleware.ts", + "target": "gateway/middleware.ts:validateSession" + }, + { + "type": "IMPORTS", + "source": "gateway/server.ts", + "target": "dep:gateway/session" + }, + { + "type": "IMPORTS", + "source": "gateway/server.ts", + "target": "dep:gateway/middleware" + }, + { + "type": "IMPORTS", + "source": "gateway/server.ts", + "target": "dep:gateway/types" + }, + { + "type": "CONTAINS", + "source": "gateway/server.ts", + "target": "gateway/server.ts:startGateway" + }, + { + "type": "CALLS", + "source": "gateway/server.ts:startGateway", + "target": "loadSession" + }, + { + "type": "CALLS", + "source": "gateway/server.ts:startGateway", + "target": "applyMiddleware" + }, + { + "type": "CONTAINS", + "source": "gateway/server.ts", + "target": "gateway/server.ts:handleRequest" + }, + { + "type": "CALLS", + "source": "gateway/server.ts:handleRequest", + "target": "loadSession" + }, + { + "type": "CONTAINS", + "source": "gateway/server.ts", + "target": "gateway/server.ts:shutdownGateway" + }, + { + "type": "IMPORTS", + "source": "gateway/session.ts", + "target": "dep:config/config" + }, + { + "type": "IMPORTS", + "source": "gateway/session.ts", + "target": "dep:agents/runner" + }, + { + "type": "IMPORTS", + "source": "gateway/session.ts", + "target": "dep:gateway/types" + }, + { + "type": "CONTAINS", + "source": "gateway/session.ts", + "target": "gateway/session.ts:loadSession" + }, + { + "type": "CALLS", + "source": "gateway/session.ts:loadSession", + "target": "getConfig" + }, + { + "type": "CONTAINS", + "source": "gateway/session.ts", + "target": "gateway/session.ts:saveSession" + }, + { + "type": "CONTAINS", + "source": "gateway/session.ts", + "target": "gateway/session.ts:refreshSession" + }, + { + "type": "CALLS", + "source": "gateway/session.ts:refreshSession", + "target": "loadSession" + }, + { + "type": "CALLS", + "source": "gateway/session.ts:refreshSession", + "target": "runAgent" + }, + { + "type": "IMPORTS", + "source": "gateway/types.ts", + "target": "dep:config/types" + }, + { + "type": "IMPORTS", + "source": "gateway/utils.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "gateway/utils.ts", + "target": "gateway/utils.ts:formatSessionKey" + }, + { + "type": "CALLS", + "source": "gateway/utils.ts:formatSessionKey", + "target": "log" + }, + { + "type": "CONTAINS", + "source": "utils/crypto.ts", + "target": "utils/crypto.ts:hashString" + }, + { + "type": "CALLS", + "source": "utils/crypto.ts:hashString", + "target": "Buffer.from(input).toString" + }, + { + "type": "CALLS", + "source": "utils/crypto.ts:hashString", + "target": "Buffer.from" + }, + { + "type": "CONTAINS", + "source": "utils/crypto.ts", + "target": "utils/crypto.ts:generateToken" + }, + { + "type": "CALLS", + "source": "utils/crypto.ts:generateToken", + "target": "Math.floor" + }, + { + "type": "CALLS", + "source": "utils/crypto.ts:generateToken", + "target": "Math.random" + }, + { + "type": "IMPORTS", + "source": "utils/fs-helpers.ts", + "target": "dep:utils/logger" + }, + { + "type": "CONTAINS", + "source": "utils/fs-helpers.ts", + "target": "utils/fs-helpers.ts:readFileSync" + }, + { + "type": "CALLS", + "source": "utils/fs-helpers.ts:readFileSync", + "target": "log" + }, + { + "type": "CONTAINS", + "source": "utils/fs-helpers.ts", + "target": "utils/fs-helpers.ts:writeFileSync" + }, + { + "type": "CALLS", + "source": "utils/fs-helpers.ts:writeFileSync", + "target": "log" + }, + { + "type": "CONTAINS", + "source": "utils/fs-helpers.ts", + "target": "utils/fs-helpers.ts:fileExists" + }, + { + "type": "CONTAINS", + "source": "utils/logger.ts", + "target": "utils/logger.ts:log" + }, + { + "type": "CALLS", + "source": "utils/logger.ts:log", + "target": "console.log" + }, + { + "type": "CALLS", + "source": "utils/logger.ts:log", + "target": "new Date().toISOString" + }, + { + "type": "CONTAINS", + "source": "utils/logger.ts", + "target": "utils/logger.ts:warn" + }, + { + "type": "CALLS", + "source": "utils/logger.ts:warn", + "target": "console.warn" + }, + { + "type": "CONTAINS", + "source": "utils/logger.ts", + "target": "utils/logger.ts:error" + }, + { + "type": "CALLS", + "source": "utils/logger.ts:error", + "target": "console.error" + } + ], + "fileIndex": { + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/defaults.ts": [ + "agents/defaults.ts", + "dep:agents/types", + "agents/defaults.ts:getDefaultAgent" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/runner.ts": [ + "agents/runner.ts", + "dep:gateway/session", + "dep:agents/scope", + "dep:utils/logger", + "agents/runner.ts:runAgent", + "agents/runner.ts:createScope", + "agents/runner.ts:executeAgent", + "agents/runner.ts:reloadAgent" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/scope.ts": [ + "agents/scope.ts", + "dep:config/config", + "dep:utils/logger", + "agents/scope.ts:createAgentScope", + "agents/scope.ts:getDefaultTools" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/tools.ts": [ + "agents/tools.ts", + "dep:utils/logger", + "agents/tools.ts:registerTool", + "agents/tools.ts:executeTool", + "agents/tools.ts:listTools" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/agents/types.ts": [ + "agents/types.ts" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/discord.ts": [ + "channels/discord.ts", + "dep:gateway/server", + "dep:utils/logger", + "channels/discord.ts:onDiscordMessage", + "channels/discord.ts:sendDiscordReply" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/channels/telegram.ts": [ + "channels/telegram.ts", + "dep:gateway/server", + "dep:utils/logger", + "channels/telegram.ts:onTelegramMessage", + "channels/telegram.ts:sendTelegramReply" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/config.ts": [ + "config/config.ts", + "dep:config/types", + "dep:utils/logger", + "config/config.ts:getConfig", + "config/config.ts:loadConfigFromDisk", + "config/config.ts:reloadConfig" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/schema.ts": [ + "config/schema.ts", + "config/schema.ts:validateSchema" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/config/types.ts": [ + "config/types.ts" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/middleware.ts": [ + "gateway/middleware.ts", + "dep:utils/logger", + "dep:gateway/types", + "gateway/middleware.ts:applyMiddleware", + "gateway/middleware.ts:validateSession" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/server.ts": [ + "gateway/server.ts", + "dep:gateway/session", + "dep:gateway/middleware", + "dep:gateway/types", + "gateway/server.ts:startGateway", + "gateway/server.ts:handleRequest", + "gateway/server.ts:shutdownGateway" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/session.ts": [ + "gateway/session.ts", + "dep:config/config", + "dep:agents/runner", + "dep:gateway/types", + "gateway/session.ts:loadSession", + "gateway/session.ts:saveSession", + "gateway/session.ts:refreshSession" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/types.ts": [ + "gateway/types.ts", + "dep:config/types" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/gateway/utils.ts": [ + "gateway/utils.ts", + "dep:utils/logger", + "gateway/utils.ts:formatSessionKey" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/crypto.ts": [ + "utils/crypto.ts", + "utils/crypto.ts:hashString", + "utils/crypto.ts:generateToken" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/fs-helpers.ts": [ + "utils/fs-helpers.ts", + "dep:utils/logger", + "utils/fs-helpers.ts:readFileSync", + "utils/fs-helpers.ts:writeFileSync", + "utils/fs-helpers.ts:fileExists" + ], + "/home/node/.openclaw/workspace/projects/dev-intel-v2/test/fixtures/system-docs/src/utils/logger.ts": [ + "utils/logger.ts", + "utils/logger.ts:log", + "utils/logger.ts:warn", + "utils/logger.ts:error" + ] + } +} \ No newline at end of file diff --git a/test/fixtures/system-docs/src/agents/defaults.ts b/test/fixtures/system-docs/src/agents/defaults.ts new file mode 100644 index 0000000..0bdb4c9 --- /dev/null +++ b/test/fixtures/system-docs/src/agents/defaults.ts @@ -0,0 +1,12 @@ +// Fixture: agents/defaults.ts — default agent configuration +import { AgentConfig } from './types'; + +export const DEFAULT_AGENT: AgentConfig = { + agentId: 'default', + model: 'claude-sonnet-4', + maxTokens: 4096, +}; + +export function getDefaultAgent(): AgentConfig { + return { ...DEFAULT_AGENT }; +} diff --git a/test/fixtures/system-docs/src/agents/runner.ts b/test/fixtures/system-docs/src/agents/runner.ts new file mode 100644 index 0000000..3da46b0 --- /dev/null +++ b/test/fixtures/system-docs/src/agents/runner.ts @@ -0,0 +1,23 @@ +// Fixture: agents/runner.ts — agent execution (circular dep with gateway/session.ts) +import { loadSession } from '../gateway/session'; +import { AgentScope } from './scope'; +import { log } from '../utils/logger'; + +export function runAgent(session: { key: string; agentId: string; model: string }): string { + log('running agent ' + session.agentId); + const scope = createScope(session.agentId); + return executeAgent(scope, session.model); +} + +function createScope(agentId: string): AgentScope { + return { agentId, tools: getDefaultTools() }; +} + +function executeAgent(scope: AgentScope, model: string): string { + return `result from ${scope.agentId} on ${model}`; +} + +export function reloadAgent(key: string): string { + const session = loadSession(key); // circular: agents calls gateway + return runAgent(session); +} diff --git a/test/fixtures/system-docs/src/agents/scope.ts b/test/fixtures/system-docs/src/agents/scope.ts new file mode 100644 index 0000000..8f9dee4 --- /dev/null +++ b/test/fixtures/system-docs/src/agents/scope.ts @@ -0,0 +1,18 @@ +// Fixture: agents/scope.ts — agent scope management +import { getConfig } from '../config/config'; +import { log } from '../utils/logger'; + +export interface AgentScope { + agentId: string; + tools: string[]; +} + +export function createAgentScope(agentId: string): AgentScope { + log('creating scope for ' + agentId); + const config = getConfig(); + return { agentId, tools: getDefaultTools() }; +} + +export function getDefaultTools(): string[] { + return ['read', 'write', 'exec', 'web_search']; +} diff --git a/test/fixtures/system-docs/src/agents/tools.ts b/test/fixtures/system-docs/src/agents/tools.ts new file mode 100644 index 0000000..43be688 --- /dev/null +++ b/test/fixtures/system-docs/src/agents/tools.ts @@ -0,0 +1,15 @@ +// Fixture: agents/tools.ts — tool registry +import { log } from '../utils/logger'; + +export function registerTool(name: string, handler: Function): void { + log('registering tool: ' + name); +} + +export function executeTool(name: string, args: Record): unknown { + log('executing tool: ' + name); + return null; +} + +export function listTools(): string[] { + return ['read', 'write', 'exec', 'web_search', 'browser']; +} diff --git a/test/fixtures/system-docs/src/agents/types.ts b/test/fixtures/system-docs/src/agents/types.ts new file mode 100644 index 0000000..ec86b4d --- /dev/null +++ b/test/fixtures/system-docs/src/agents/types.ts @@ -0,0 +1,14 @@ +// Fixture: agents/types.ts — agent type definitions +export interface AgentConfig { + agentId: string; + model: string; + maxTokens: number; +} + +export interface AgentResult { + output: string; + tokensUsed: number; + duration: number; +} + +export type AgentStatus = 'idle' | 'running' | 'done' | 'error'; diff --git a/test/fixtures/system-docs/src/channels/discord.ts b/test/fixtures/system-docs/src/channels/discord.ts new file mode 100644 index 0000000..0e5074b --- /dev/null +++ b/test/fixtures/system-docs/src/channels/discord.ts @@ -0,0 +1,12 @@ +// Fixture: channels/discord.ts — Discord channel adapter +import { handleRequest } from '../gateway/server'; +import { log } from '../utils/logger'; + +export function onDiscordMessage(guildId: string, channelId: string, text: string): void { + log('discord message in ' + guildId); + const session = handleRequest('discord:' + guildId + ':' + channelId); +} + +export function sendDiscordReply(channelId: string, message: string): void { + log('sending discord reply to ' + channelId); +} diff --git a/test/fixtures/system-docs/src/channels/telegram.ts b/test/fixtures/system-docs/src/channels/telegram.ts new file mode 100644 index 0000000..e53abb5 --- /dev/null +++ b/test/fixtures/system-docs/src/channels/telegram.ts @@ -0,0 +1,12 @@ +// Fixture: channels/telegram.ts — Telegram channel adapter +import { handleRequest } from '../gateway/server'; +import { log } from '../utils/logger'; + +export function onTelegramMessage(chatId: string, text: string): void { + log('telegram message from ' + chatId); + const session = handleRequest('telegram:' + chatId); +} + +export function sendTelegramReply(chatId: string, message: string): void { + log('sending reply to ' + chatId); +} diff --git a/test/fixtures/system-docs/src/config/config.ts b/test/fixtures/system-docs/src/config/config.ts new file mode 100644 index 0000000..b9fcf7b --- /dev/null +++ b/test/fixtures/system-docs/src/config/config.ts @@ -0,0 +1,22 @@ +// Fixture: config/config.ts — central configuration +import { BaseConfig } from './types'; +import { log } from '../utils/logger'; + +let _config: BaseConfig | null = null; + +export function getConfig(): BaseConfig { + if (!_config) { + _config = loadConfigFromDisk(); + } + return _config; +} + +function loadConfigFromDisk(): BaseConfig { + log('loading config from disk'); + return { defaultAgent: 'default', model: 'claude-sonnet-4', debug: false }; +} + +export function reloadConfig(): void { + _config = null; + getConfig(); +} diff --git a/test/fixtures/system-docs/src/config/schema.ts b/test/fixtures/system-docs/src/config/schema.ts new file mode 100644 index 0000000..f4975c8 --- /dev/null +++ b/test/fixtures/system-docs/src/config/schema.ts @@ -0,0 +1,18 @@ +// Fixture: config/schema.ts — ORPHAN FILE (no inbound edges, only exports) +// This file is imported by nobody — tests orphan handling + +export interface SchemaDefinition { + name: string; + version: string; + fields: SchemaField[]; +} + +export interface SchemaField { + name: string; + type: 'string' | 'number' | 'boolean' | 'object'; + required: boolean; +} + +export function validateSchema(schema: SchemaDefinition): boolean { + return schema.fields.length > 0 && schema.version.length > 0; +} diff --git a/test/fixtures/system-docs/src/config/types.ts b/test/fixtures/system-docs/src/config/types.ts new file mode 100644 index 0000000..b049987 --- /dev/null +++ b/test/fixtures/system-docs/src/config/types.ts @@ -0,0 +1,19 @@ +// Fixture: config/types.ts — base config types (imported by gateway/types.ts) +export interface BaseConfig { + defaultAgent: string; + model: string; + debug: boolean; +} + +export interface ProviderConfig { + name: string; + apiKey: string; + baseUrl: string; +} + +export enum LogLevel { + Debug = 'debug', + Info = 'info', + Warn = 'warn', + Error = 'error', +} diff --git a/test/fixtures/system-docs/src/gateway/middleware.ts b/test/fixtures/system-docs/src/gateway/middleware.ts new file mode 100644 index 0000000..7c5a9f2 --- /dev/null +++ b/test/fixtures/system-docs/src/gateway/middleware.ts @@ -0,0 +1,12 @@ +// Fixture: gateway/middleware.ts — request middleware +import { log } from '../utils/logger'; +import { SessionEntry } from './types'; + +export function applyMiddleware(session: SessionEntry): void { + log('applying middleware for ' + session.key); + validateSession(session); +} + +function validateSession(session: SessionEntry): boolean { + return session.key.length > 0; +} diff --git a/test/fixtures/system-docs/src/gateway/server.ts b/test/fixtures/system-docs/src/gateway/server.ts new file mode 100644 index 0000000..348b9e0 --- /dev/null +++ b/test/fixtures/system-docs/src/gateway/server.ts @@ -0,0 +1,18 @@ +// Fixture: gateway/server.ts — main gateway entry point +import { loadSession, saveSession } from './session'; +import { applyMiddleware } from './middleware'; +import { GatewayConfig, SessionEntry } from './types'; + +export function startGateway(config: GatewayConfig): void { + const session = loadSession(config.sessionKey); + applyMiddleware(session); +} + +export function handleRequest(sessionKey: string): SessionEntry { + const session = loadSession(sessionKey); + return session; +} + +export function shutdownGateway(): void { + // cleanup +} diff --git a/test/fixtures/system-docs/src/gateway/session.ts b/test/fixtures/system-docs/src/gateway/session.ts new file mode 100644 index 0000000..e374ca6 --- /dev/null +++ b/test/fixtures/system-docs/src/gateway/session.ts @@ -0,0 +1,19 @@ +// Fixture: gateway/session.ts — session management (has circular dep with agents/runner.ts) +import { getConfig } from '../config/config'; +import { runAgent } from '../agents/runner'; +import { SessionEntry } from './types'; + +export function loadSession(key: string): SessionEntry { + const config = getConfig(); + return { key, agentId: config.defaultAgent, model: config.model }; +} + +export function saveSession(entry: SessionEntry): void { + // persist +} + +export function refreshSession(key: string): SessionEntry { + const session = loadSession(key); + runAgent(session); // circular: gateway calls agents, agents call gateway + return session; +} diff --git a/test/fixtures/system-docs/src/gateway/types.ts b/test/fixtures/system-docs/src/gateway/types.ts new file mode 100644 index 0000000..a7da4fe --- /dev/null +++ b/test/fixtures/system-docs/src/gateway/types.ts @@ -0,0 +1,15 @@ +// Fixture: gateway/types.ts — re-exports from config/types + gateway-specific types +import { BaseConfig } from '../config/types'; + +export interface GatewayConfig extends BaseConfig { + sessionKey: string; + timeout: number; +} + +export interface SessionEntry { + key: string; + agentId: string; + model: string; +} + +export { BaseConfig } from '../config/types'; diff --git a/test/fixtures/system-docs/src/gateway/utils.ts b/test/fixtures/system-docs/src/gateway/utils.ts new file mode 100644 index 0000000..7bc851a --- /dev/null +++ b/test/fixtures/system-docs/src/gateway/utils.ts @@ -0,0 +1,7 @@ +// Fixture: gateway/utils.ts — gateway-specific helpers +import { log } from '../utils/logger'; + +export function formatSessionKey(prefix: string, id: string): string { + log('formatting key'); + return `${prefix}:${id}`; +} diff --git a/test/fixtures/system-docs/src/utils/crypto.ts b/test/fixtures/system-docs/src/utils/crypto.ts new file mode 100644 index 0000000..152afd7 --- /dev/null +++ b/test/fixtures/system-docs/src/utils/crypto.ts @@ -0,0 +1,13 @@ +// Fixture: utils/crypto.ts — crypto helpers +export function hashString(input: string): string { + return Buffer.from(input).toString('base64'); +} + +export function generateToken(length: number): string { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + return result; +} diff --git a/test/fixtures/system-docs/src/utils/fs-helpers.ts b/test/fixtures/system-docs/src/utils/fs-helpers.ts new file mode 100644 index 0000000..69d1dae --- /dev/null +++ b/test/fixtures/system-docs/src/utils/fs-helpers.ts @@ -0,0 +1,15 @@ +// Fixture: utils/fs-helpers.ts — file system helpers +import { log } from './logger'; + +export function readFileSync(path: string): string { + log('reading ' + path); + return ''; +} + +export function writeFileSync(path: string, content: string): void { + log('writing ' + path); +} + +export function fileExists(path: string): boolean { + return false; +} diff --git a/test/fixtures/system-docs/src/utils/logger.ts b/test/fixtures/system-docs/src/utils/logger.ts new file mode 100644 index 0000000..a4a62fe --- /dev/null +++ b/test/fixtures/system-docs/src/utils/logger.ts @@ -0,0 +1,14 @@ +// Fixture: utils/logger.ts — cross-cutting utility (high fan-in) +// Imported by almost every other module — tests cross-cutting detection + +export function log(message: string): void { + console.log(`[${new Date().toISOString()}] ${message}`); +} + +export function warn(message: string): void { + console.warn(`[WARN] ${message}`); +} + +export function error(message: string, err?: Error): void { + console.error(`[ERROR] ${message}`, err?.message || ''); +} diff --git a/test/test-flow.js b/test/test-flow.js new file mode 100644 index 0000000..4fdd025 --- /dev/null +++ b/test/test-flow.js @@ -0,0 +1,93 @@ +const fs = require('fs'); +const path = require('path'); +const GraphStore = require('../graph.js'); +const { buildSubsystems } = require('../subsystem.js'); +const { buildFlowIndex, traceFlow } = require('../flow.js'); + +const FIXTURE_DIR = path.join(__dirname, 'fixtures/system-docs'); +const SNAPSHOT = path.join(FIXTURE_DIR, 'snapshot.json'); + +let passed = 0; +let failed = 0; + +function assert(condition, name) { + if (condition) { passed++; console.log(` ✓ ${name}`); } + else { failed++; console.log(` ✗ ${name}`); } +} + +const graph = GraphStore.loadSnapshot(SNAPSHOT); +const subsystemMap = buildSubsystems(graph, { minTraffic: 3, crossCuttingThreshold: 0.6 }); +const index = buildFlowIndex(graph, subsystemMap); + +console.log('=== 7C: Flow Tracer Tests ===\n'); + +// Test 1: Simple linear flow across subsystems +console.log('Test 1: Linear flow'); +const t1 = traceFlow('channels/telegram.ts:onTelegramMessage', index); +assert(t1.subsystemSequence.includes('channels'), 'Started in channels'); +assert(t1.subsystemSequence.includes('gateway'), 'Crossed to gateway'); +assert(t1.flow.some(f => f.entity === 'gateway/server.ts:handleRequest' && f.crossedVia === 'CALLS'), 'Recorded CALLS crossing'); + +// Test 2: Cycle detection (gateway <-> agents) +console.log('\nTest 2: Cycle detection'); +const t2 = traceFlow('gateway/session.ts:refreshSession', index); +assert(t2.subsystemSequence.includes('gateway'), 'Started in gateway'); +assert(t2.subsystemSequence.includes('agents'), 'Crossed to agents'); +assert(t2.flow.length < 20, 'Trace terminated without infinite loop'); + +// Test 3: God object exclusion +console.log('\nTest 3: God object exclusion'); +const indexLowThreshold = buildFlowIndex(graph, subsystemMap, { godThreshold: 2 }); +const t3 = traceFlow('channels/telegram.ts:onTelegramMessage', indexLowThreshold); +assert(t3.excludedNodes.includes('utils/logger.ts:log'), 'logger.ts:log was excluded as god object'); +assert(!t3.flow.some(f => f.entity === 'utils/logger.ts:log'), 'logger does not appear in flow'); + +// Test 4: Depth limit +console.log('\nTest 4: Depth limit'); +const t4 = traceFlow('channels/telegram.ts:onTelegramMessage', index, { maxDepth: 1.2 }); +assert(t4.flow.some(f => f.entity === 'gateway/server.ts:handleRequest'), 'Included depth 1 node'); +assert(!t4.flow.some(f => f.entity === 'gateway/session.ts:loadSession'), 'Excluded depth 1.5 node'); + +// Test 5: Empty trace +console.log('\nTest 5: Empty trace'); +const t5 = traceFlow('utils/crypto.ts:hashString', index); +assert(t5.flow.length === 1, 'Only entry point in flow'); +assert(t5.subsystemSequence.length === 1, 'Only one subsystem'); + +// Test 6: Invalid entry point +console.log('\nTest 6: Invalid entry point'); +const t6 = traceFlow('nonexistent/file.ts:foo', index); +assert(t6.error !== undefined, 'Returns error for invalid entry point'); +assert(t6.flow.length === 0, 'Empty flow for invalid entry'); + +// Test 7: Multiple traces reuse same index (performance) +console.log('\nTest 7: Index reuse + OpenClaw performance'); +const fullSnap = path.join(__dirname, '..', 'snapshots', 'openclaw-full.json'); +if (fs.existsSync(fullSnap)) { + const fullGraph = GraphStore.loadSnapshot(fullSnap); + const fullSubs = buildSubsystems(fullGraph); + + const indexStart = Date.now(); + const fullIndex = buildFlowIndex(fullGraph, fullSubs, { godThreshold: 50 }); + const indexTime = Date.now() - indexStart; + console.log(` Index built in ${indexTime}ms`); + + // Run 3 traces on the same index + const entries = ['cli/route.ts:tryRouteCli', 'gateway/session-utils.ts', 'pairing/pairing-store.ts']; + let totalTraceTime = 0; + for (const ep of entries) { + const start = Date.now(); + const t = traceFlow(ep, fullIndex, { maxDepth: 8 }); + const elapsed = Date.now() - start; + totalTraceTime += elapsed; + console.log(` Trace "${ep}": ${t.flow.length} steps, ${t.subsystemSequence.length} subsystems, ${elapsed}ms`); + } + + assert(indexTime < 3000, `Index built in <3s (${indexTime}ms)`); + assert(totalTraceTime < 5000, `All 3 traces completed in <5s (${totalTraceTime}ms)`); +} else { + console.log(' (skipped — openclaw-full.json not found)'); +} + +console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`); +process.exit(failed > 0 ? 1 : 0); diff --git a/test/test-subsystem.js b/test/test-subsystem.js new file mode 100644 index 0000000..46d4a4a --- /dev/null +++ b/test/test-subsystem.js @@ -0,0 +1,109 @@ +const fs = require('fs'); +const path = require('path'); +const GraphStore = require('../graph.js'); +const { buildSubsystems } = require('../subsystem.js'); + +const FIXTURE_DIR = path.join(__dirname, 'fixtures/system-docs'); +const SNAPSHOT = path.join(FIXTURE_DIR, 'snapshot.json'); + +let passed = 0; +let failed = 0; + +function assert(condition, name) { + if (condition) { passed++; console.log(` ✓ ${name}`); } + else { failed++; console.log(` ✗ ${name}`); } +} + +// Load graph and run subsystem aggregator +const graph = GraphStore.loadSnapshot(SNAPSHOT); +const result = buildSubsystems(graph, { minTraffic: 3, crossCuttingThreshold: 0.6 }); +const expected = JSON.parse(fs.readFileSync(path.join(FIXTURE_DIR, 'expected-subsystems.json'), 'utf8')); + +console.log('=== 7A: Subsystem Aggregator Tests ===\n'); + +// Test 1: Directory clustering — correct number of subsystems +console.log('Test 1: Directory clustering'); +assert(result.subsystems.length === 5, `Found 5 subsystems (got ${result.subsystems.length})`); +const names = result.subsystems.map(s => s.name).sort(); +assert(JSON.stringify(names) === JSON.stringify(['agents', 'channels', 'config', 'gateway', 'utils']), + `Subsystem names match: ${names.join(', ')}`); + +// Test 2: File assignment accuracy +console.log('\nTest 2: File assignment accuracy'); +let correctFiles = 0; +let totalFiles = 0; +for (const expSub of expected.subsystems) { + const actualSub = result.subsystems.find(s => s.name === expSub.name); + assert(!!actualSub, `Subsystem "${expSub.name}" exists`); + if (actualSub) { + for (const f of expSub.files) { + totalFiles++; + if (actualSub.files.includes(f)) correctFiles++; + else console.log(` MISSING: ${f} in ${expSub.name}`); + } + } +} +const accuracy = totalFiles > 0 ? (correctFiles / totalFiles * 100).toFixed(1) : 0; +assert(correctFiles / totalFiles >= 0.9, `File assignment accuracy ≥90% (${accuracy}%, ${correctFiles}/${totalFiles})`); + +// Test 3: Cross-cutting detection +console.log('\nTest 3: Cross-cutting detection'); +assert(result.crossCutting.includes('utils'), 'utils detected as cross-cutting'); +assert(result.crossCutting.includes('config'), 'config detected as cross-cutting'); +assert(!result.crossCutting.includes('gateway'), 'gateway is NOT cross-cutting'); +assert(!result.crossCutting.includes('agents'), 'agents is NOT cross-cutting'); +assert(!result.crossCutting.includes('channels'), 'channels is NOT cross-cutting'); + +// Verify kind field matches +for (const expSub of expected.subsystems) { + const actualSub = result.subsystems.find(s => s.name === expSub.name); + if (actualSub) { + assert(actualSub.kind === expSub.kind, `${expSub.name} kind="${actualSub.kind}" (expected "${expSub.kind}")`); + } +} + +// Test 4: Dependency matrix — key edges exist +console.log('\nTest 4: Dependency matrix'); +const dm = result.dependencyMatrix; +assert('gateway→agents' in dm, 'gateway→agents edge exists'); +assert('agents→gateway' in dm, 'agents→gateway edge exists (circular dep)'); +assert('channels→gateway' in dm, 'channels→gateway edge exists'); +assert('gateway→config' in dm, 'gateway→config edge exists'); +assert('agents→utils' in dm, 'agents→utils edge exists'); +assert('channels→utils' in dm, 'channels→utils edge exists'); + +// Test 5: Empty subsystem handling (channels has only 2 files, no internal CALLS) +console.log('\nTest 5: Edge cases'); +const channelsSub = result.subsystems.find(s => s.name === 'channels'); +assert(channelsSub && channelsSub.files.length === 2, `channels has 2 files (got ${channelsSub?.files.length})`); + +// Orphan file: config/schema.ts should still be in config subsystem +const configSub = result.subsystems.find(s => s.name === 'config'); +assert(configSub && configSub.files.includes('config/schema.ts'), 'Orphan config/schema.ts assigned to config'); + +// Test 6: Public exports populated +console.log('\nTest 6: Public exports'); +const gatewaySub = result.subsystems.find(s => s.name === 'gateway'); +assert(gatewaySub.publicExports.includes('handleRequest'), 'gateway exports handleRequest'); +assert(gatewaySub.publicExports.includes('loadSession'), 'gateway exports loadSession'); +const agentsSub = result.subsystems.find(s => s.name === 'agents'); +assert(agentsSub.publicExports.includes('runAgent'), 'agents exports runAgent'); + +// Test 7: Run on OpenClaw full snapshot (performance) +console.log('\nTest 7: OpenClaw full snapshot'); +const fullSnap = path.join(__dirname, '..', 'snapshots', 'openclaw-full.json'); +if (fs.existsSync(fullSnap)) { + const start = Date.now(); + const fullGraph = GraphStore.loadSnapshot(fullSnap); + const fullResult = buildSubsystems(fullGraph); + const elapsed = Date.now() - start; + assert(fullResult.subsystems.length > 10, `OpenClaw has >10 subsystems (got ${fullResult.subsystems.length})`); + assert(elapsed < 5000, `Completed in <5s (${elapsed}ms)`); + assert(fullResult.crossCutting.length > 0, `Detected cross-cutting subsystems: ${fullResult.crossCutting.join(', ')}`); + console.log(` OpenClaw: ${fullResult.subsystems.length} subsystems, ${Object.keys(fullResult.dependencyMatrix).length} dep edges, ${elapsed}ms`); +} else { + console.log(' (skipped — openclaw-full.json not found)'); +} + +console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`); +process.exit(failed > 0 ? 1 : 0);