| 171 | } |
| 172 | |
| 173 | private async loadKeywordIndex(): Promise<void> { |
| 174 | try { |
| 175 | const indexPath = path.join(this.rootPath, CODEBASE_CONTEXT_DIRNAME, KEYWORD_INDEX_FILENAME); |
| 176 | const content = await fs.readFile(indexPath, 'utf-8'); |
| 177 | const parsed = JSON.parse(content) as unknown; |
| 178 | |
| 179 | if (Array.isArray(parsed)) { |
| 180 | throw new IndexCorruptedError( |
| 181 | 'Legacy keyword index format detected (missing header). Rebuild required.' |
| 182 | ); |
| 183 | } |
| 184 | |
| 185 | const parsedObj = parsed as { chunks?: unknown }; |
| 186 | const chunks = |
| 187 | parsedObj && typeof parsedObj === 'object' && Array.isArray(parsedObj.chunks) |
| 188 | ? (parsedObj.chunks as CodeChunk[]) |
| 189 | : null; |
| 190 | if (!chunks) { |
| 191 | throw new IndexCorruptedError('Keyword index corrupted: expected { header, chunks }'); |
| 192 | } |
| 193 | |
| 194 | this.chunks = chunks; |
| 195 | |
| 196 | this.fuseIndex = new Fuse(this.chunks, { |
| 197 | keys: [ |
| 198 | { name: 'content', weight: 0.4 }, |
| 199 | { name: 'metadata.componentName', weight: 0.25 }, |
| 200 | { name: 'filePath', weight: 0.15 }, |
| 201 | { name: 'relativePath', weight: 0.15 }, |
| 202 | { name: 'componentType', weight: 0.15 }, |
| 203 | { name: 'layer', weight: 0.1 }, |
| 204 | { name: 'tags', weight: 0.15 } |
| 205 | ], |
| 206 | includeScore: true, |
| 207 | threshold: 0.4, |
| 208 | useExtendedSearch: true, |
| 209 | ignoreLocation: true |
| 210 | }); |
| 211 | } catch (error) { |
| 212 | if (error instanceof IndexCorruptedError) { |
| 213 | throw error; |
| 214 | } |
| 215 | throw new IndexCorruptedError( |
| 216 | `Keyword index load failed (rebuild required): ${error instanceof Error ? error.message : String(error)}` |
| 217 | ); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | /** |
| 222 | * Load pattern intelligence for trend detection and warnings |