* Extract code from a node's source file
(node: Node)
| 1190 | * Extract code from a node's source file |
| 1191 | */ |
| 1192 | private async extractNodeCode(node: Node): Promise<string | null> { |
| 1193 | // SECURITY (#383): a config-leaf node's on-disk line is `key = <secret>`. |
| 1194 | // Return the KEY only — never read the value off disk. This closes the |
| 1195 | // includeCode / buildContext code-block path, mirroring the explore source |
| 1196 | // renderer; an agent that genuinely needs a value can read the file itself. |
| 1197 | if (isConfigLeafNode(node)) { |
| 1198 | return node.signature || node.qualifiedName || node.name; |
| 1199 | } |
| 1200 | |
| 1201 | const filePath = validatePathWithinRoot(this.projectRoot, node.filePath); |
| 1202 | |
| 1203 | if (!filePath || !fs.existsSync(filePath)) { |
| 1204 | return null; |
| 1205 | } |
| 1206 | |
| 1207 | try { |
| 1208 | const content = fs.readFileSync(filePath, 'utf-8'); |
| 1209 | const lines = content.split('\n'); |
| 1210 | |
| 1211 | // Extract lines (1-indexed to 0-indexed) |
| 1212 | const startIdx = Math.max(0, node.startLine - 1); |
| 1213 | const endIdx = Math.min(lines.length, node.endLine); |
| 1214 | |
| 1215 | return lines.slice(startIdx, endIdx).join('\n'); |
| 1216 | } catch (error) { |
| 1217 | logDebug('Failed to extract code from node', { nodeId: node.id, filePath: node.filePath, error: String(error) }); |
| 1218 | return null; |
| 1219 | } |
| 1220 | } |
| 1221 | |
| 1222 | /** |
| 1223 | * Get entry points from a subgraph (the root nodes) |
no test coverage detected