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 CHANGED
@@ -1 +1 @@
1
- 0.25.2
1
+ 0.27.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.25.2",
3
+ "version": "0.27.0",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -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
  */