MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / fieldChannelEdges

Function fieldChannelEdges

src/resolution/callback-synthesizer.ts:179–236  ·  view source on GitHub ↗

Phase 1: field-backed observer channels (registrar/dispatcher share a store).

(queries: QueryBuilder, ctx: ResolutionContext, onYield: MaybeYield)

Source from the content-addressed store, hash-verified

177
178/** Phase 1: field-backed observer channels (registrar/dispatcher share a store). */
179async function fieldChannelEdges(queries: QueryBuilder, ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]> {
180 const registrars: Array<{ node: Node; field: string }> = [];
181 const dispatchers: Array<{ node: Node; field: string }> = [];
182
183 let scanned = 0;
184 for (const m of methodAndFunctionNodes(queries)) {
185 if ((++scanned & 255) === 0) await onYield(); // #1091: yield mid-scan on huge graphs
186 const isReg = REGISTRAR_NAME.test(m.name);
187 const isDisp = DISPATCHER_NAME.test(m.name);
188 if (!isReg && !isDisp) continue;
189 const content = ctx.readFile(m.filePath);
190 const src = content && sliceLines(content, m.startLine, m.endLine);
191 if (!src) continue;
192 if (isReg) { const f = registrarField(src); if (f) registrars.push({ node: m, field: f }); }
193 if (isDisp) { const f = dispatcherField(src); if (f) dispatchers.push({ node: m, field: f }); }
194 }
195
196 const edges: Edge[] = [];
197 const seen = new Set<string>();
198 for (const reg of registrars) {
199 const chDispatchers = dispatchers.filter(
200 (d) => d.node.filePath === reg.node.filePath && d.field === reg.field
201 );
202 if (chDispatchers.length === 0) continue;
203 const argRe = new RegExp(`${reg.node.name}\\s*\\(\\s*(?:this\\.)?(\\w+)`);
204 let added = 0;
205 for (const e of queries.getIncomingEdges(reg.node.id, ['calls'])) {
206 if (added >= MAX_CALLBACKS_PER_CHANNEL) break;
207 if (!e.line) continue;
208 const caller = queries.getNodeById(e.source);
209 if (!caller) continue;
210 const line = ctx.readFile(caller.filePath)?.split('\n')[e.line - 1];
211 const am = line?.match(argRe);
212 if (!am) continue;
213 const fn = ctx.getNodesByName(am[1]!).find((n) => n.kind === 'method' || n.kind === 'function');
214 if (!fn) continue;
215 for (const disp of chDispatchers) {
216 if (disp.node.id === fn.id) continue;
217 const key = `${disp.node.id}>${fn.id}`;
218 if (seen.has(key)) continue;
219 seen.add(key);
220 edges.push({
221 source: disp.node.id, target: fn.id, kind: 'calls', line: disp.node.startLine,
222 provenance: 'heuristic',
223 metadata: {
224 synthesizedBy: 'callback', via: reg.node.name, field: reg.field,
225 // Where the callback was wired up (`scene.onUpdate(this.triggerRender)`).
226 // This is the #1 thing an agent reads/greps to explain the flow — surface
227 // it so node/trace/context can show it without a callers() + Read round-trip.
228 registeredAt: `${caller.filePath}:${e.line}`,
229 },
230 });
231 added++;
232 }
233 }
234 }
235 return edges;
236}

Callers 1

Calls 9

methodAndFunctionNodesFunction · 0.85
sliceLinesFunction · 0.85
registrarFieldFunction · 0.85
dispatcherFieldFunction · 0.85
readFileMethod · 0.80
hasMethod · 0.80
getNodeByIdMethod · 0.65
getNodesByNameMethod · 0.65
getIncomingEdgesMethod · 0.45

Tested by

no test coverage detected