claude-brain 0.25.2 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/package.json +1 -1
- package/src/routing/router.ts +158 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.27.0
|
package/package.json
CHANGED
package/src/routing/router.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { Logger } from 'pino'
|
|
14
14
|
import { IntentClassifier, type ClassificationResult } from './intent-classifier'
|
|
15
15
|
import { BrainEntityExtractor, type BrainExtractedEntities } from './entity-extractor'
|
|
16
|
-
import { ResponseFilter, type BrainResponse, type TierResults, formatCompactResponse, formatDetailResponse, formatTimeline, groupByDay } from './response-filter'
|
|
16
|
+
import { ResponseFilter, type BrainResponse, type TierResults, type FilterableResult, formatCompactResponse, formatDetailResponse, formatTimeline, groupByDay } from './response-filter'
|
|
17
17
|
import { SearchEngine } from './search-engine'
|
|
18
18
|
import type { NormalizedResult } from './types'
|
|
19
19
|
import {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
getEpisodeService,
|
|
25
25
|
getSessionTracker,
|
|
26
26
|
getCodeLinker,
|
|
27
|
+
getCodeQuery,
|
|
27
28
|
isServicesInitialized
|
|
28
29
|
} from '@/server/services'
|
|
29
30
|
import { timed } from '@/utils/timing'
|
|
@@ -275,6 +276,21 @@ export class BrainRouter {
|
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
// Phase 32: Include code file map in session context if code index is available
|
|
280
|
+
try {
|
|
281
|
+
const codeQuery = getCodeQuery()
|
|
282
|
+
if (codeQuery && project) {
|
|
283
|
+
const fileMap = codeQuery.getFileMap(project, 50)
|
|
284
|
+
if (fileMap.length > 0) {
|
|
285
|
+
const formatted = codeQuery.formatFileMap(fileMap)
|
|
286
|
+
parts.push('\n---\n## Code Structure\n')
|
|
287
|
+
parts.push(formatted)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
// Code intelligence not available
|
|
292
|
+
}
|
|
293
|
+
|
|
278
294
|
// C8: Register with episode manager
|
|
279
295
|
this.registerEpisodeMessage(message, project, 'session_start')
|
|
280
296
|
|
|
@@ -411,6 +427,12 @@ export class BrainRouter {
|
|
|
411
427
|
})
|
|
412
428
|
}
|
|
413
429
|
|
|
430
|
+
// Phase 32: Code intelligence — query code index for matching symbols/files
|
|
431
|
+
const codeTier = this.queryCodeIntelligence(query, searchProject)
|
|
432
|
+
if (codeTier) {
|
|
433
|
+
tiers.push(codeTier)
|
|
434
|
+
}
|
|
435
|
+
|
|
414
436
|
const response = this.responseFilter.synthesize(tiers, message, displayProject)
|
|
415
437
|
|
|
416
438
|
// P0: When no results found and message looks like a plain statement (not a question),
|
|
@@ -1052,6 +1074,12 @@ export class BrainRouter {
|
|
|
1052
1074
|
})
|
|
1053
1075
|
}
|
|
1054
1076
|
|
|
1077
|
+
// Phase 32: Code intelligence — synchronous SQLite query, runs alongside async searches
|
|
1078
|
+
const codeTier = this.queryCodeIntelligence(query, searchProject)
|
|
1079
|
+
if (codeTier) {
|
|
1080
|
+
tiers.push(codeTier)
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1055
1083
|
// Parallel: patterns + corrections + graph
|
|
1056
1084
|
const [patternResults, correctionResults, graphResults] = await Promise.all([
|
|
1057
1085
|
this.searchEngine.searchPatterns(query, { project: searchProject, limit: 3 }),
|
|
@@ -1376,6 +1404,12 @@ export class BrainRouter {
|
|
|
1376
1404
|
})
|
|
1377
1405
|
}
|
|
1378
1406
|
|
|
1407
|
+
// Phase 32: Code intelligence for exploration queries
|
|
1408
|
+
const codeTier = this.queryCodeIntelligence(query, searchProject)
|
|
1409
|
+
if (codeTier) {
|
|
1410
|
+
tiers.push(codeTier)
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1379
1413
|
return this.responseFilter.synthesize(tiers, message, displayProject, 'analyzed')
|
|
1380
1414
|
}
|
|
1381
1415
|
|
|
@@ -1675,6 +1709,129 @@ export class BrainRouter {
|
|
|
1675
1709
|
|
|
1676
1710
|
// ===== Helpers =====
|
|
1677
1711
|
|
|
1712
|
+
/**
|
|
1713
|
+
* Phase 32: Query code intelligence for symbols and files matching a query.
|
|
1714
|
+
* Returns a TierResults if matches found, null otherwise.
|
|
1715
|
+
* Fast operation — direct SQLite queries, no network calls.
|
|
1716
|
+
*/
|
|
1717
|
+
private queryCodeIntelligence(query: string, project: string | undefined): TierResults | null {
|
|
1718
|
+
try {
|
|
1719
|
+
const codeQuery = getCodeQuery()
|
|
1720
|
+
if (!codeQuery) return null
|
|
1721
|
+
|
|
1722
|
+
// Extract meaningful keywords from query for code search
|
|
1723
|
+
const keywords = this.extractCodeKeywords(query)
|
|
1724
|
+
if (keywords.length === 0) return null
|
|
1725
|
+
|
|
1726
|
+
const results: FilterableResult[] = []
|
|
1727
|
+
|
|
1728
|
+
for (const keyword of keywords) {
|
|
1729
|
+
// Search symbols across all indexed projects (project may not match code index project name)
|
|
1730
|
+
const symbolResults = codeQuery.findSymbols(keyword, project || '', 5)
|
|
1731
|
+
|
|
1732
|
+
// If no results with project filter, try without (code index may use path-based project names)
|
|
1733
|
+
const symbols = symbolResults.length > 0
|
|
1734
|
+
? symbolResults
|
|
1735
|
+
: codeQuery.findSymbols(keyword, '', 5)
|
|
1736
|
+
|
|
1737
|
+
for (const sym of symbols.slice(0, 3)) {
|
|
1738
|
+
const location = sym.lineEnd
|
|
1739
|
+
? `${sym.filePath}:${sym.lineStart}-${sym.lineEnd}`
|
|
1740
|
+
: `${sym.filePath}:${sym.lineStart}`
|
|
1741
|
+
const sigPart = sym.signature ? ` — \`${sym.signature}\`` : ''
|
|
1742
|
+
results.push({
|
|
1743
|
+
content: `**${sym.symbol}** (${sym.type}) at \`${location}\`${sigPart}`,
|
|
1744
|
+
score: sym.confidence * 0.6, // Scale down so memories rank higher
|
|
1745
|
+
source: 'Code Index',
|
|
1746
|
+
metadata: {
|
|
1747
|
+
filePath: sym.filePath,
|
|
1748
|
+
lineStart: sym.lineStart,
|
|
1749
|
+
lineEnd: sym.lineEnd,
|
|
1750
|
+
symbolType: sym.type,
|
|
1751
|
+
}
|
|
1752
|
+
})
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// Also search files by name/summary
|
|
1756
|
+
const fileResults = codeQuery.findFiles(keyword, project || '')
|
|
1757
|
+
const files = fileResults.length > 0
|
|
1758
|
+
? fileResults
|
|
1759
|
+
: codeQuery.findFiles(keyword, '')
|
|
1760
|
+
|
|
1761
|
+
for (const file of files.slice(0, 3)) {
|
|
1762
|
+
// Skip if we already have symbol results from this file
|
|
1763
|
+
if (results.some(r => (r.metadata as any)?.filePath === file.filePath)) continue
|
|
1764
|
+
const summaryPart = file.summary ? ` — ${file.summary}` : ''
|
|
1765
|
+
results.push({
|
|
1766
|
+
content: `**${file.filePath}** (${file.language || 'unknown'}, ${file.symbolCount} symbols, ${file.lineCount} lines)${summaryPart}`,
|
|
1767
|
+
score: 0.4, // File matches are lower confidence
|
|
1768
|
+
source: 'Code Index',
|
|
1769
|
+
metadata: {
|
|
1770
|
+
filePath: file.filePath,
|
|
1771
|
+
symbolCount: file.symbolCount,
|
|
1772
|
+
}
|
|
1773
|
+
})
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// Deduplicate by filePath
|
|
1778
|
+
const seen = new Set<string>()
|
|
1779
|
+
const deduped = results.filter(r => {
|
|
1780
|
+
const fp = (r.metadata as any)?.filePath
|
|
1781
|
+
if (fp && seen.has(fp)) return false
|
|
1782
|
+
if (fp) seen.add(fp)
|
|
1783
|
+
return true
|
|
1784
|
+
})
|
|
1785
|
+
|
|
1786
|
+
if (deduped.length === 0) return null
|
|
1787
|
+
|
|
1788
|
+
return {
|
|
1789
|
+
label: 'Code Intelligence',
|
|
1790
|
+
results: deduped.slice(0, 5)
|
|
1791
|
+
}
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
this.logger.debug({ error }, 'Code intelligence query failed (non-fatal)')
|
|
1794
|
+
return null
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
/**
|
|
1799
|
+
* Extract meaningful keywords from a natural language query for code search.
|
|
1800
|
+
* Filters out stop words and common filler, returns words likely to match code symbols.
|
|
1801
|
+
*/
|
|
1802
|
+
private extractCodeKeywords(query: string): string[] {
|
|
1803
|
+
const STOP_WORDS = new Set([
|
|
1804
|
+
'how', 'what', 'where', 'when', 'why', 'who', 'which', 'are', 'is', 'was',
|
|
1805
|
+
'were', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did',
|
|
1806
|
+
'doing', 'will', 'would', 'could', 'should', 'shall', 'can', 'may', 'might',
|
|
1807
|
+
'must', 'the', 'a', 'an', 'and', 'but', 'or', 'nor', 'for', 'yet', 'so',
|
|
1808
|
+
'in', 'on', 'at', 'to', 'from', 'by', 'with', 'about', 'into', 'through',
|
|
1809
|
+
'during', 'before', 'after', 'above', 'below', 'between', 'out', 'off', 'over',
|
|
1810
|
+
'under', 'again', 'further', 'then', 'once', 'here', 'there', 'all', 'each',
|
|
1811
|
+
'every', 'both', 'few', 'more', 'most', 'other', 'some', 'such', 'only', 'own',
|
|
1812
|
+
'same', 'than', 'too', 'very', 'just', 'because', 'not', 'this', 'that', 'these',
|
|
1813
|
+
'those', 'its', 'our', 'their', 'your', 'my', 'me', 'we', 'you', 'they', 'them',
|
|
1814
|
+
'know', 'tell', 'show', 'find', 'get', 'make', 'use', 'using', 'used', 'managing',
|
|
1815
|
+
'managed', 'handle', 'handling', 'handled', 'work', 'working', 'works', 'implement',
|
|
1816
|
+
'implementing', 'implemented', 'doing', 'done', 'project', 'code', 'file', 'function',
|
|
1817
|
+
])
|
|
1818
|
+
|
|
1819
|
+
const words = query.toLowerCase()
|
|
1820
|
+
.replace(/[^a-z0-9\s-_]/g, ' ')
|
|
1821
|
+
.split(/\s+/)
|
|
1822
|
+
.filter(w => w.length > 2 && !STOP_WORDS.has(w))
|
|
1823
|
+
|
|
1824
|
+
// Also try multi-word phrases (camelCase/kebab-case patterns the user might reference)
|
|
1825
|
+
const phrases: string[] = []
|
|
1826
|
+
for (let i = 0; i < words.length - 1; i++) {
|
|
1827
|
+
// Create camelCase combo: "brain router" → "brainRouter"
|
|
1828
|
+
phrases.push(words[i] + words[i + 1].charAt(0).toUpperCase() + words[i + 1].slice(1))
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// Return unique keywords, single words first then phrases
|
|
1832
|
+
return [...new Set([...words, ...phrases])].slice(0, 5)
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1678
1835
|
/**
|
|
1679
1836
|
* Phase 29: Link a stored observation to code files (non-blocking, fire-and-forget).
|
|
1680
1837
|
*/
|