(node: TaskNode, deps: SlicerDeps)
| 88 | * - Attach design tokens for component/style kinds. |
| 89 | */ |
| 90 | export async function buildTaskContext(node: TaskNode, deps: SlicerDeps): Promise<TaskContext> { |
| 91 | const slices: ContextSlice[] = []; |
| 92 | const typeDefs: ContextSlice[] = []; |
| 93 | const seenSlice = new Set<string>(); |
| 94 | |
| 95 | const pushSlice = (arr: ContextSlice[], s: ContextSlice) => { |
| 96 | const key = `${s.file}:${s.startLine ?? 0}:${s.symbol ?? ''}:${s.reason}`; |
| 97 | if (seenSlice.has(key)) return; |
| 98 | seenSlice.add(key); |
| 99 | arr.push(s); |
| 100 | }; |
| 101 | |
| 102 | // 1. TARGET slices — current content of files this node modifies. |
| 103 | const existingTargets: string[] = []; |
| 104 | for (const t of node.targetFiles) { |
| 105 | const content = await readFile(deps, t); |
| 106 | if (content == null) continue; // new file — nothing to show |
| 107 | existingTargets.push(t); |
| 108 | // For large target files, slice to the symbols the instruction references; |
| 109 | // otherwise include whole (a component file is usually small). |
| 110 | if (content.split('\n').length > 200) { |
| 111 | const chunks = await astChunkFile(t, content); |
| 112 | for (const c of chunks) { |
| 113 | pushSlice(slices, { file: t, text: c.text, reason: 'target', startLine: c.startLine, endLine: c.endLine, symbol: c.symbol }); |
| 114 | } |
| 115 | } else { |
| 116 | pushSlice(slices, { file: t, text: content, reason: 'target' }); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | // 2. TYPE dependencies — 1-hop import neighbors, type chunks only. |
| 121 | const graph = deps.graph ?? await buildImportGraphForFiles(deps, node); |
| 122 | const seeds = node.targetFiles.filter(f => graph.files.has(f)); |
| 123 | if (seeds.length > 0) { |
| 124 | const neighbors = expandViaGraph(graph, seeds, { hops: 1, maxFiles: 20, hubDamping: true }); |
| 125 | for (const n of neighbors) { |
| 126 | if (n.distance === 0) continue; // skip the targets themselves |
| 127 | const content = await readFile(deps, n.file); |
| 128 | if (!content) continue; |
| 129 | const chunks = await astChunkFile(n.file, content); |
| 130 | for (const c of chunks) { |
| 131 | if (isTypeChunk(c.symbol, n.file)) { |
| 132 | pushSlice(typeDefs, { file: n.file, text: c.text, reason: 'type-dependency', startLine: c.startLine, endLine: c.endLine, symbol: c.symbol }); |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // 3. Explicitly-requested symbols — find their defining chunk in the neighborhood. |
| 139 | if (node.contextSymbols?.length) { |
| 140 | const searchFiles = new Set<string>([...node.contextFiles, ...node.targetFiles]); |
| 141 | for (const f of node.contextFiles) { |
| 142 | for (const nbr of graph.out.get(f) ?? []) searchFiles.add(nbr); |
| 143 | } |
| 144 | for (const f of searchFiles) { |
| 145 | const content = await readFile(deps, f); |
| 146 | if (!content) continue; |
| 147 | const chunks = await astChunkFile(f, content); |
no test coverage detected