* Extract code from a node's source file
(node: Node)
| 1161 | * Extract code from a node's source file |
| 1162 | */ |
| 1163 | private async extractNodeCode(node: Node): Promise<string | null> { |
| 1164 | // SECURITY (#383): a config-leaf node's on-disk line is `key = <secret>`. |
| 1165 | // Return the KEY only — never read the value off disk. This closes the |
| 1166 | // includeCode / buildContext code-block path, mirroring the explore source |
| 1167 | // renderer; an agent that genuinely needs a value can read the file itself. |
| 1168 | if (isConfigLeafNode(node)) { |
| 1169 | return node.signature || node.qualifiedName || node.name; |
| 1170 | } |
| 1171 | |
| 1172 | const filePath = validatePathWithinRoot(this.projectRoot, node.filePath); |
| 1173 | |
| 1174 | if (!filePath || !fs.existsSync(filePath)) { |
| 1175 | return null; |
| 1176 | } |
| 1177 | |
| 1178 | try { |
| 1179 | const content = fs.readFileSync(filePath, 'utf-8'); |
| 1180 | const lines = content.split('\n'); |
| 1181 | |
| 1182 | // Extract lines (1-indexed to 0-indexed) |
| 1183 | const startIdx = Math.max(0, node.startLine - 1); |
| 1184 | const endIdx = Math.min(lines.length, node.endLine); |
| 1185 | |
| 1186 | return lines.slice(startIdx, endIdx).join('\n'); |
| 1187 | } catch (error) { |
| 1188 | logDebug('Failed to extract code from node', { nodeId: node.id, filePath: node.filePath, error: String(error) }); |
| 1189 | return null; |
| 1190 | } |
| 1191 | } |
| 1192 | |
| 1193 | /** |
| 1194 | * Get entry points from a subgraph (the root nodes) |
no test coverage detected