* Dynamic-boundary surfacing (#687): when the flow among the agent's named * symbols does not fully connect, scan the disconnected symbols' bodies for * dynamic-dispatch sites (computed member calls, getattr, reflection, typed * message buses, runtime-keyed emits) and ANNOUNCE the boundary
(cg: CodeGraph, scanList: Node[], named: Map<string, Node>)
| 2231 | * connected flow never reaches this method. |
| 2232 | */ |
| 2233 | private buildDynamicBoundaries(cg: CodeGraph, scanList: Node[], named: Map<string, Node>): string { |
| 2234 | const MAX_NOTES = 4; // boundary bullets per explore |
| 2235 | const MAX_SCAN = 8; // bodies scanned |
| 2236 | const MAX_TOTAL_CHARS = 200_000; |
| 2237 | let projectRoot: string; |
| 2238 | try { projectRoot = cg.getProjectRoot(); } catch { return ''; } |
| 2239 | const notes: string[] = []; |
| 2240 | const seenNode = new Set<string>(); |
| 2241 | const seenSite = new Set<string>(); |
| 2242 | let scanned = 0, charsScanned = 0; |
| 2243 | for (const node of scanList) { |
| 2244 | if (notes.length >= MAX_NOTES || scanned >= MAX_SCAN || charsScanned > MAX_TOTAL_CHARS) break; |
| 2245 | if (seenNode.has(node.id) || !node.startLine || !node.endLine) continue; |
| 2246 | seenNode.add(node.id); |
| 2247 | const absPath = validatePathWithinRoot(projectRoot, node.filePath); |
| 2248 | if (!absPath || !existsSync(absPath)) continue; |
| 2249 | let content: string; |
| 2250 | try { content = readFileSync(absPath, 'utf-8'); } catch { continue; } |
| 2251 | const body = content.split('\n').slice(node.startLine - 1, node.endLine).join('\n'); |
| 2252 | scanned++; |
| 2253 | charsScanned += body.length; |
| 2254 | for (const m of scanDynamicDispatch(body, node.language || '', node.startLine)) { |
| 2255 | if (notes.length >= MAX_NOTES) break; |
| 2256 | const siteKey = `${node.filePath}:${m.line}:${m.form}`; |
| 2257 | if (seenSite.has(siteKey)) continue; |
| 2258 | seenSite.add(siteKey); |
| 2259 | const more = m.moreSites ? ` (+${m.moreSites} more such site${m.moreSites > 1 ? 's' : ''} in this body)` : ''; |
| 2260 | notes.push(`- \`${node.name}\` (${node.filePath}:${m.line}) — ${m.label}: \`${m.snippet}\`${more}`); |
| 2261 | if (m.key) { |
| 2262 | const cand = this.boundaryCandidates(cg, m.key, !!m.keyIsType, named, node.id); |
| 2263 | if (cand) notes.push(` ${cand}`); |
| 2264 | } |
| 2265 | } |
| 2266 | } |
| 2267 | if (notes.length === 0) return ''; |
| 2268 | return [ |
| 2269 | '**Dynamic boundaries (the static path ends at runtime dispatch)**', |
| 2270 | '', |
| 2271 | ...notes, |
| 2272 | '', |
| 2273 | '> These sites choose their call target at runtime (registry / bus / reflection) — the site shown IS where the flow continues. To follow it, run codegraph_explore or codegraph_node on a candidate; source for the sites above is included below.', |
| 2274 | '', |
| 2275 | ].join('\n'); |
| 2276 | } |
| 2277 | |
| 2278 | /** |
| 2279 | * Interface/registry-dispatch announcement — #687 extended to GRAPH-visible |
no test coverage detected