* Extract code from a node's source file
(node: Node)
| 1293 | * Extract code from a node's source file |
| 1294 | */ |
| 1295 | private async extractNodeCode(node: Node): Promise<string | null> { |
| 1296 | // SECURITY (#383): a config-leaf node's on-disk line is `key = <secret>`. |
| 1297 | // Return the KEY only — never read the value off disk. This closes the |
| 1298 | // includeCode / buildContext code-block path, mirroring the explore source |
| 1299 | // renderer; an agent that genuinely needs a value can read the file itself. |
| 1300 | if (isConfigLeafNode(node)) { |
| 1301 | return node.signature || node.qualifiedName || node.name; |
| 1302 | } |
| 1303 | |
| 1304 | const filePath = validatePathWithinRoot(this.projectRoot, node.filePath); |
| 1305 | |
| 1306 | if (!filePath || !fs.existsSync(filePath)) { |
| 1307 | return null; |
| 1308 | } |
| 1309 | |
| 1310 | try { |
| 1311 | const content = fs.readFileSync(filePath, 'utf-8'); |
| 1312 | const lines = content.split('\n'); |
| 1313 | |
| 1314 | // Extract lines (1-indexed to 0-indexed) |
| 1315 | const startIdx = Math.max(0, node.startLine - 1); |
| 1316 | const endIdx = Math.min(lines.length, node.endLine); |
| 1317 | |
| 1318 | return lines.slice(startIdx, endIdx).join('\n'); |
| 1319 | } catch (error) { |
| 1320 | logDebug('Failed to extract code from node', { nodeId: node.id, filePath: node.filePath, error: String(error) }); |
| 1321 | return null; |
| 1322 | } |
| 1323 | } |
| 1324 | |
| 1325 | /** |
| 1326 | * Get entry points from a subgraph (the root nodes) |
no test coverage detected