claude-brain 0.22.0 → 0.22.2
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/cli/commands/serve.ts +13 -8
- package/src/cli/commands/start.ts +5 -7
- package/src/cli/commands/status.ts +7 -6
- package/src/memory/fts5-search.ts +32 -6
- package/src/memory/index.ts +45 -12
- package/src/routing/response-filter.ts +1 -1
- package/src/routing/router.ts +121 -3
- package/src/server/http-api.ts +16 -2
- package/src/tools/schemas.ts +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.22.
|
|
1
|
+
0.22.2
|
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@ import { ClaudeBrainMCPServer } from '@/server'
|
|
|
5
5
|
import { initializeServices, shutdownServices, getVaultService, getMemoryService } from '@/server/services'
|
|
6
6
|
import { createOrchestrator, type Orchestrator } from '@/orchestrator'
|
|
7
7
|
import { ensureHomeDirectory } from '@/cli/auto-setup'
|
|
8
|
-
import { ensureChromaRunning } from '@/cli/commands/chroma'
|
|
9
8
|
|
|
10
9
|
const BANNER = `
|
|
11
10
|
╔═══════════════════════════════════════════════════════╗
|
|
@@ -29,12 +28,12 @@ export async function runServe() {
|
|
|
29
28
|
console.error(`[claude-brain] Hook auto-install skipped: ${error instanceof Error ? error.message : 'unknown error'}`)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
const config = await loadConfig()
|
|
32
|
+
|
|
33
|
+
if (config.logLevel === 'debug' || config.logLevel === 'info') {
|
|
33
34
|
console.error(BANNER)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
const config = await loadConfig()
|
|
37
|
-
|
|
38
37
|
const logger = createLogger(config.logLevel, config.logFilePath)
|
|
39
38
|
const mainLogger = createComponentLogger(logger, 'main')
|
|
40
39
|
|
|
@@ -54,10 +53,16 @@ export async function runServe() {
|
|
|
54
53
|
cacheSize: config.cacheSize
|
|
55
54
|
}, 'Configuration loaded')
|
|
56
55
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
// Only start ChromaDB if explicitly enabled in config
|
|
57
|
+
let chromaReady = false
|
|
58
|
+
if (config.chromadb?.enabled) {
|
|
59
|
+
mainLogger.info('ChromaDB enabled, ensuring it is available...')
|
|
60
|
+
const { ensureChromaRunning } = await import('@/cli/commands/chroma')
|
|
61
|
+
chromaReady = await ensureChromaRunning({ silent: process.env.NODE_ENV === 'production' })
|
|
62
|
+
mainLogger.info({ chromaReady }, chromaReady ? 'ChromaDB is ready' : 'ChromaDB not available, using SQLite fallback')
|
|
63
|
+
} else {
|
|
64
|
+
mainLogger.info('Using SQLite FTS5 storage (ChromaDB disabled)')
|
|
65
|
+
}
|
|
61
66
|
|
|
62
67
|
mainLogger.info('Initializing services...')
|
|
63
68
|
await initializeServices(config, logger)
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Start Command
|
|
3
|
-
* Starts
|
|
3
|
+
* Starts the Claude Brain server (MCP + HTTP)
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* claude-brain start Start
|
|
7
|
-
* claude-brain start --chroma-only Start only ChromaDB server
|
|
6
|
+
* claude-brain start Start server
|
|
7
|
+
* claude-brain start --chroma-only Start only ChromaDB server (if enabled)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { parseArgs } from 'citty'
|
|
11
11
|
import {
|
|
12
12
|
heading, successText, warningText, dimText,
|
|
13
13
|
} from '@/cli/ui/index.js'
|
|
14
|
-
import { ensureChromaRunning } from '@/cli/commands/chroma'
|
|
15
14
|
|
|
16
15
|
export async function runStart(): Promise<void> {
|
|
17
16
|
const args = parseArgs(process.argv.slice(3), {
|
|
@@ -20,6 +19,7 @@ export async function runStart(): Promise<void> {
|
|
|
20
19
|
const chromaOnly = args['chroma-only']
|
|
21
20
|
|
|
22
21
|
if (chromaOnly) {
|
|
22
|
+
const { ensureChromaRunning } = await import('@/cli/commands/chroma')
|
|
23
23
|
console.log()
|
|
24
24
|
console.log(heading('Starting ChromaDB'))
|
|
25
25
|
console.log()
|
|
@@ -36,9 +36,7 @@ export async function runStart(): Promise<void> {
|
|
|
36
36
|
return
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
// serve.ts already calls ensureChromaRunning(), so just delegate
|
|
41
|
-
console.error(dimText('Starting ChromaDB + MCP server...'))
|
|
39
|
+
console.error(dimText('Starting Claude Brain server...'))
|
|
42
40
|
console.error()
|
|
43
41
|
|
|
44
42
|
const { runServe } = await import('./serve')
|
|
@@ -39,16 +39,17 @@ export async function runStatus() {
|
|
|
39
39
|
const { Database } = await import('bun:sqlite')
|
|
40
40
|
const db = new Database(dbPath, { readonly: true })
|
|
41
41
|
|
|
42
|
-
// Count observations by category
|
|
42
|
+
// Count observations by category (dynamic — includes all categories)
|
|
43
43
|
const total = (db.prepare('SELECT COUNT(*) as cnt FROM observations WHERE archived = 0').get() as any)?.cnt ?? 0
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const catRows = db.prepare(
|
|
45
|
+
'SELECT category, COUNT(*) as cnt FROM observations WHERE archived = 0 GROUP BY category'
|
|
46
|
+
).all() as any[]
|
|
47
|
+
const breakdown = catRows.map((r: any) => `${r.cnt} ${r.category}s`).join(', ')
|
|
47
48
|
|
|
48
49
|
items.push({ label: 'Storage', value: 'SQLite FTS5', status: 'success' })
|
|
49
50
|
items.push({
|
|
50
51
|
label: 'Observations',
|
|
51
|
-
value: `${total}
|
|
52
|
+
value: `${total}${breakdown ? ` (${breakdown})` : ''}`,
|
|
52
53
|
status: total > 0 ? 'success' : 'warning'
|
|
53
54
|
})
|
|
54
55
|
|
|
@@ -79,7 +80,7 @@ export async function runStatus() {
|
|
|
79
80
|
const symbols = (codeDb.prepare('SELECT COUNT(*) as cnt FROM code_symbols').get() as any)?.cnt ?? 0
|
|
80
81
|
|
|
81
82
|
// Last indexed time
|
|
82
|
-
const latest = (codeDb.prepare('SELECT MAX(
|
|
83
|
+
const latest = (codeDb.prepare('SELECT MAX(last_indexed) as latest FROM code_files').get() as any)?.latest
|
|
83
84
|
const lastIndexed = latest ? formatAge(latest) : 'never'
|
|
84
85
|
|
|
85
86
|
items.push({
|
|
@@ -245,15 +245,21 @@ export class FTS5Search {
|
|
|
245
245
|
/**
|
|
246
246
|
* Fetch all observations for a project, optionally filtered by category.
|
|
247
247
|
*/
|
|
248
|
-
fetchAll(project
|
|
248
|
+
fetchAll(project?: string, category?: ObservationCategory): ObservationResult[] {
|
|
249
249
|
let sql: string
|
|
250
|
-
const params: any[] = [
|
|
250
|
+
const params: any[] = []
|
|
251
251
|
|
|
252
|
-
if (category) {
|
|
252
|
+
if (project && category) {
|
|
253
253
|
sql = `SELECT * FROM observations WHERE project = ? AND category = ? AND archived = 0 ORDER BY created_at DESC`
|
|
254
|
+
params.push(project, category)
|
|
255
|
+
} else if (project) {
|
|
256
|
+
sql = `SELECT * FROM observations WHERE project = ? AND archived = 0 ORDER BY created_at DESC`
|
|
257
|
+
params.push(project)
|
|
258
|
+
} else if (category) {
|
|
259
|
+
sql = `SELECT * FROM observations WHERE category = ? AND archived = 0 ORDER BY created_at DESC`
|
|
254
260
|
params.push(category)
|
|
255
261
|
} else {
|
|
256
|
-
sql = `SELECT * FROM observations WHERE
|
|
262
|
+
sql = `SELECT * FROM observations WHERE archived = 0 ORDER BY created_at DESC`
|
|
257
263
|
}
|
|
258
264
|
|
|
259
265
|
const rows = this.db.prepare(sql).all(...params) as any[]
|
|
@@ -337,6 +343,26 @@ export class FTS5Search {
|
|
|
337
343
|
this.recordAccess(id)
|
|
338
344
|
}
|
|
339
345
|
|
|
346
|
+
/**
|
|
347
|
+
* BUG-002: Search within a specific category, optionally scoped to a project.
|
|
348
|
+
* Returns results ordered by creation date (most recent first).
|
|
349
|
+
*/
|
|
350
|
+
searchByCategory(category: ObservationCategory, project?: string, limit: number = 10): ObservationResult[] {
|
|
351
|
+
let sql: string
|
|
352
|
+
const params: any[] = []
|
|
353
|
+
|
|
354
|
+
if (project) {
|
|
355
|
+
sql = `SELECT * FROM observations WHERE category = ? AND project = ? AND archived = 0 ORDER BY created_at DESC LIMIT ?`
|
|
356
|
+
params.push(category, project, limit)
|
|
357
|
+
} else {
|
|
358
|
+
sql = `SELECT * FROM observations WHERE category = ? AND archived = 0 ORDER BY created_at DESC LIMIT ?`
|
|
359
|
+
params.push(category, limit)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const rows = this.db.prepare(sql).all(...params) as any[]
|
|
363
|
+
return rows.map(row => this.rowToResult(row))
|
|
364
|
+
}
|
|
365
|
+
|
|
340
366
|
// --- Private helpers ---
|
|
341
367
|
|
|
342
368
|
/**
|
|
@@ -362,9 +388,9 @@ export class FTS5Search {
|
|
|
362
388
|
* Typical range: -20 (excellent) to 0 (poor).
|
|
363
389
|
*/
|
|
364
390
|
private normalizeBM25(score: number): number {
|
|
365
|
-
// Map
|
|
391
|
+
// Map BM25 range to 0.2-0.9 (wider range, allows low scores to be filtered)
|
|
366
392
|
const normalized = Math.min(1, Math.max(0, score / 20))
|
|
367
|
-
return 0.
|
|
393
|
+
return 0.2 + normalized * 0.7
|
|
368
394
|
}
|
|
369
395
|
|
|
370
396
|
/**
|
package/src/memory/index.ts
CHANGED
|
@@ -543,8 +543,8 @@ export class MemoryManager {
|
|
|
543
543
|
limit?: number
|
|
544
544
|
}
|
|
545
545
|
): Promise<any[]> {
|
|
546
|
-
// Phase 26: Try FTS5 first
|
|
547
|
-
if (this._fts5
|
|
546
|
+
// Phase 26: Try FTS5 first (BUG-002: works with or without project)
|
|
547
|
+
if (this._fts5) {
|
|
548
548
|
const results = this._fts5.fetchAll(project, 'pattern')
|
|
549
549
|
if (results.length > 0) {
|
|
550
550
|
return results.map(r => ({
|
|
@@ -580,8 +580,8 @@ export class MemoryManager {
|
|
|
580
580
|
project?: string,
|
|
581
581
|
options?: { limit?: number }
|
|
582
582
|
): Promise<any[]> {
|
|
583
|
-
// Phase 26: Try FTS5 first
|
|
584
|
-
if (this._fts5
|
|
583
|
+
// Phase 26: Try FTS5 first (BUG-002: works with or without project)
|
|
584
|
+
if (this._fts5) {
|
|
585
585
|
const results = this._fts5.fetchAll(project, 'correction')
|
|
586
586
|
if (results.length > 0) {
|
|
587
587
|
return results.map(r => ({
|
|
@@ -615,8 +615,8 @@ export class MemoryManager {
|
|
|
615
615
|
* Used by analytical tools that need bulk access to decision data
|
|
616
616
|
*/
|
|
617
617
|
async fetchAllDecisions(project?: string): Promise<any[]> {
|
|
618
|
-
// Phase 26: Try FTS5 first
|
|
619
|
-
if (this._fts5
|
|
618
|
+
// Phase 26: Try FTS5 first (BUG-002: works with or without project)
|
|
619
|
+
if (this._fts5) {
|
|
620
620
|
const results = this._fts5.fetchAll(project, 'decision')
|
|
621
621
|
if (results.length > 0) {
|
|
622
622
|
return results.map(r => ({
|
|
@@ -664,8 +664,8 @@ export class MemoryManager {
|
|
|
664
664
|
* Fetch all patterns with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
665
665
|
*/
|
|
666
666
|
async fetchAllPatterns(project?: string): Promise<any[]> {
|
|
667
|
-
// Phase 26: Try FTS5 first
|
|
668
|
-
if (this._fts5
|
|
667
|
+
// Phase 26: Try FTS5 first (BUG-002: works with or without project)
|
|
668
|
+
if (this._fts5) {
|
|
669
669
|
const results = this._fts5.fetchAll(project, 'pattern')
|
|
670
670
|
if (results.length > 0) {
|
|
671
671
|
return results.map(r => ({
|
|
@@ -713,8 +713,8 @@ export class MemoryManager {
|
|
|
713
713
|
* Fetch all corrections with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
714
714
|
*/
|
|
715
715
|
async fetchAllCorrections(project?: string): Promise<any[]> {
|
|
716
|
-
// Phase 26: Try FTS5 first
|
|
717
|
-
if (this._fts5
|
|
716
|
+
// Phase 26: Try FTS5 first (BUG-002: works with or without project)
|
|
717
|
+
if (this._fts5) {
|
|
718
718
|
const results = this._fts5.fetchAll(project, 'correction')
|
|
719
719
|
if (results.length > 0) {
|
|
720
720
|
return results.map(r => ({
|
|
@@ -873,14 +873,47 @@ export class MemoryManager {
|
|
|
873
873
|
reasoning: string,
|
|
874
874
|
options?: { alternatives?: string; tags?: string[] }
|
|
875
875
|
): Promise<string> {
|
|
876
|
-
//
|
|
876
|
+
// BUG-001: True in-place update using FTS5 (preserves original ID)
|
|
877
|
+
if (this._fts5) {
|
|
878
|
+
try {
|
|
879
|
+
this._fts5.update(oldId, {
|
|
880
|
+
content: decision,
|
|
881
|
+
reasoning,
|
|
882
|
+
context,
|
|
883
|
+
tags: options?.tags
|
|
884
|
+
})
|
|
885
|
+
this.logger.debug({ oldId }, 'Decision updated in-place via FTS5')
|
|
886
|
+
|
|
887
|
+
// Also update ChromaDB if available (best-effort)
|
|
888
|
+
if (this.useChromaDB) {
|
|
889
|
+
try {
|
|
890
|
+
await this.chroma.store.deleteDecision(oldId)
|
|
891
|
+
await this.chroma.store.storeDecision({
|
|
892
|
+
project,
|
|
893
|
+
context,
|
|
894
|
+
decision,
|
|
895
|
+
reasoning,
|
|
896
|
+
alternatives: options?.alternatives,
|
|
897
|
+
tags: options?.tags
|
|
898
|
+
})
|
|
899
|
+
} catch {
|
|
900
|
+
// ChromaDB sync failed, FTS5 is source of truth
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return oldId // SAME ID preserved
|
|
905
|
+
} catch (error) {
|
|
906
|
+
this.logger.warn({ error, oldId }, 'FTS5 in-place update failed, falling back to delete+store')
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Fallback: delete + store (legacy behavior for non-FTS5 backends)
|
|
877
911
|
try {
|
|
878
912
|
await this.deleteDecision(oldId)
|
|
879
913
|
this.logger.debug({ oldId }, 'Old decision deleted for update')
|
|
880
914
|
} catch (error) {
|
|
881
915
|
this.logger.warn({ error, oldId }, 'Failed to delete old decision during update, storing new version anyway')
|
|
882
916
|
}
|
|
883
|
-
// Store new version — fires onDecisionStoredCallbacks (graph rebuild)
|
|
884
917
|
const newId = await this.rememberDecision(project, context, decision, reasoning, options)
|
|
885
918
|
this.logger.debug({ oldId, newId }, 'Decision updated: old deleted, new stored')
|
|
886
919
|
return newId
|
package/src/routing/router.ts
CHANGED
|
@@ -33,8 +33,8 @@ import type { ObservationCompressor } from '@/memory/compression'
|
|
|
33
33
|
const DEFAULT_PROJECT = 'general'
|
|
34
34
|
|
|
35
35
|
/** Phase 19 D4: Minimum similarity for destructive operations */
|
|
36
|
-
const DELETE_MIN_SIMILARITY = 0.
|
|
37
|
-
const UPDATE_MIN_SIMILARITY = 0.
|
|
36
|
+
const DELETE_MIN_SIMILARITY = 0.3
|
|
37
|
+
const UPDATE_MIN_SIMILARITY = 0.3
|
|
38
38
|
|
|
39
39
|
export interface BrainInput {
|
|
40
40
|
message: string
|
|
@@ -879,7 +879,23 @@ export class BrainRouter {
|
|
|
879
879
|
relevantItems: 0
|
|
880
880
|
}
|
|
881
881
|
} else if (results.length > 0 && matchSimilarity < DELETE_MIN_SIMILARITY) {
|
|
882
|
-
// D4: No confident match
|
|
882
|
+
// D4: No confident match — try direct FTS5 content search as fallback
|
|
883
|
+
const memory = getMemoryService()
|
|
884
|
+
if (memory.fts5) {
|
|
885
|
+
const ftsResults = memory.fts5.search(topic, effectiveProject, 3)
|
|
886
|
+
if (ftsResults.length > 0) {
|
|
887
|
+
const ftsTarget = ftsResults[0]
|
|
888
|
+
await memory.deleteDecision(ftsTarget.id)
|
|
889
|
+
if (this.lastStoredId === ftsTarget.id) this.lastStoredId = null
|
|
890
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
891
|
+
return {
|
|
892
|
+
action: 'stored',
|
|
893
|
+
summary: `Deleted memory`,
|
|
894
|
+
content: `Deleted: "${ftsTarget.content.slice(0, 100)}" (ID: ${ftsTarget.id})`,
|
|
895
|
+
relevantItems: 0
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
883
899
|
return {
|
|
884
900
|
action: 'none',
|
|
885
901
|
summary: 'No confident match to delete',
|
|
@@ -936,6 +952,12 @@ export class BrainRouter {
|
|
|
936
952
|
return this.servicesNotReady()
|
|
937
953
|
}
|
|
938
954
|
|
|
955
|
+
// BUG-002: Detect category-based queries and route to fetchAll
|
|
956
|
+
const categoryIntent = this.detectCategoryIntent(message)
|
|
957
|
+
if (categoryIntent) {
|
|
958
|
+
return this.handleCategoryQuery(message, project, categoryIntent)
|
|
959
|
+
}
|
|
960
|
+
|
|
939
961
|
// Phase 25: Use undefined for search (no project filter = search all) when no project detected
|
|
940
962
|
const searchProject = project || undefined
|
|
941
963
|
const displayProject = project || DEFAULT_PROJECT
|
|
@@ -1832,6 +1854,102 @@ export class BrainRouter {
|
|
|
1832
1854
|
return { content }
|
|
1833
1855
|
}
|
|
1834
1856
|
|
|
1857
|
+
/**
|
|
1858
|
+
* BUG-002: Detect category-based intent from question messages.
|
|
1859
|
+
* Returns the category if the user is asking about a specific type of memory.
|
|
1860
|
+
*/
|
|
1861
|
+
private detectCategoryIntent(message: string): 'decision' | 'pattern' | 'correction' | null {
|
|
1862
|
+
const lower = message.toLowerCase()
|
|
1863
|
+
|
|
1864
|
+
// Decision-oriented queries
|
|
1865
|
+
if (
|
|
1866
|
+
lower.includes('what decisions') || lower.includes('my decisions') ||
|
|
1867
|
+
lower.includes('what have i decided') || lower.includes('what did i decide') ||
|
|
1868
|
+
lower.includes('what choices') || lower.includes('decisions i') ||
|
|
1869
|
+
lower.includes('list decisions') || lower.includes('show decisions') ||
|
|
1870
|
+
lower.includes('all decisions') || lower.includes('what did i choose')
|
|
1871
|
+
) {
|
|
1872
|
+
return 'decision'
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// Pattern-oriented queries
|
|
1876
|
+
if (
|
|
1877
|
+
lower.includes('what patterns') || lower.includes('my patterns') ||
|
|
1878
|
+
lower.includes('best practices') || lower.includes('conventions') ||
|
|
1879
|
+
lower.includes('list patterns') || lower.includes('show patterns') ||
|
|
1880
|
+
lower.includes('all patterns')
|
|
1881
|
+
) {
|
|
1882
|
+
return 'pattern'
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// Correction-oriented queries
|
|
1886
|
+
if (
|
|
1887
|
+
lower.includes('what mistakes') || lower.includes('my mistakes') ||
|
|
1888
|
+
lower.includes('what bugs') || lower.includes('lessons learned') ||
|
|
1889
|
+
lower.includes('what corrections') || lower.includes('my corrections') ||
|
|
1890
|
+
lower.includes('list corrections') || lower.includes('show corrections') ||
|
|
1891
|
+
lower.includes('all corrections') || lower.includes('what have i fixed') ||
|
|
1892
|
+
lower.includes('what did i fix')
|
|
1893
|
+
) {
|
|
1894
|
+
return 'correction'
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
return null
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
/**
|
|
1901
|
+
* BUG-002: Handle category-scoped queries by fetching all items of that category.
|
|
1902
|
+
*/
|
|
1903
|
+
private async handleCategoryQuery(
|
|
1904
|
+
message: string,
|
|
1905
|
+
project: string | undefined,
|
|
1906
|
+
category: 'decision' | 'pattern' | 'correction'
|
|
1907
|
+
): Promise<BrainResponse> {
|
|
1908
|
+
const memory = getMemoryService()
|
|
1909
|
+
const projectLabel = project || 'all projects'
|
|
1910
|
+
|
|
1911
|
+
let items: any[]
|
|
1912
|
+
switch (category) {
|
|
1913
|
+
case 'decision':
|
|
1914
|
+
items = await memory.fetchAllDecisions(project)
|
|
1915
|
+
break
|
|
1916
|
+
case 'pattern':
|
|
1917
|
+
items = await memory.fetchAllPatterns(project)
|
|
1918
|
+
break
|
|
1919
|
+
case 'correction':
|
|
1920
|
+
items = await memory.fetchAllCorrections(project)
|
|
1921
|
+
break
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
if (items.length === 0) {
|
|
1925
|
+
return {
|
|
1926
|
+
action: 'none',
|
|
1927
|
+
summary: `No ${category}s found`,
|
|
1928
|
+
content: `No ${category}s found for ${projectLabel}.`,
|
|
1929
|
+
relevantItems: 0
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// Format as compact items
|
|
1934
|
+
const allItems = items.map(item => ({
|
|
1935
|
+
id: item.id || item.decision_id,
|
|
1936
|
+
content: typeof (item.decision || item.description || item.correction || item.original || item.document || item.content) === 'string'
|
|
1937
|
+
? (item.decision || item.description || item.correction || item.original || item.document || item.content)
|
|
1938
|
+
: JSON.stringify(item.decision || item.description || item.correction || item.original || item.document || item.content || ''),
|
|
1939
|
+
category,
|
|
1940
|
+
project: item.project || project || 'general',
|
|
1941
|
+
created_at: item.created_at || item.date || '',
|
|
1942
|
+
}))
|
|
1943
|
+
|
|
1944
|
+
const compactContent = formatCompactResponse(allItems, `${category}s for ${projectLabel}`)
|
|
1945
|
+
return {
|
|
1946
|
+
action: 'retrieved',
|
|
1947
|
+
summary: `${items.length} ${category}s for ${projectLabel}`,
|
|
1948
|
+
content: compactContent,
|
|
1949
|
+
relevantItems: items.length
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1835
1953
|
private generateTaskId(title: string): string {
|
|
1836
1954
|
return title
|
|
1837
1955
|
.toLowerCase()
|
package/src/server/http-api.ts
CHANGED
|
@@ -595,7 +595,7 @@ export class HttpApiServer {
|
|
|
595
595
|
}
|
|
596
596
|
|
|
597
597
|
const body = await c.req.json()
|
|
598
|
-
const { projectPath, projectName } = body
|
|
598
|
+
const { projectPath, projectName, force } = body
|
|
599
599
|
|
|
600
600
|
if (!projectPath || !projectName) {
|
|
601
601
|
return Response.json(
|
|
@@ -604,6 +604,20 @@ export class HttpApiServer {
|
|
|
604
604
|
)
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
+
// BUG-004: Clear existing index data when force flag is set
|
|
608
|
+
if (force && this.codeQuery) {
|
|
609
|
+
try {
|
|
610
|
+
const db = (this.codeQuery as any).db
|
|
611
|
+
if (db) {
|
|
612
|
+
db.run('DELETE FROM code_files WHERE project = ?', projectName)
|
|
613
|
+
db.run('DELETE FROM code_symbols WHERE project = ?', projectName)
|
|
614
|
+
this.logger.info({ projectName }, 'Force reindex: cleared existing data')
|
|
615
|
+
}
|
|
616
|
+
} catch (error) {
|
|
617
|
+
this.logger.warn({ error, projectName }, 'Failed to clear index data for force reindex')
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
607
621
|
const result = await this.codeIndexer.indexProject(projectPath, projectName)
|
|
608
622
|
return Response.json({ success: true, data: result })
|
|
609
623
|
} catch (error) {
|
|
@@ -654,7 +668,7 @@ export class HttpApiServer {
|
|
|
654
668
|
)
|
|
655
669
|
}
|
|
656
670
|
|
|
657
|
-
const query = c.req.query('query') || ''
|
|
671
|
+
const query = c.req.query('query') || c.req.query('q') || ''
|
|
658
672
|
const project = c.req.query('project') || ''
|
|
659
673
|
const limit = parseInt(c.req.query('limit') || '20', 10)
|
|
660
674
|
|
package/src/tools/schemas.ts
CHANGED
|
@@ -600,7 +600,7 @@ export const TOOLS = {
|
|
|
600
600
|
},
|
|
601
601
|
project: {
|
|
602
602
|
type: 'string',
|
|
603
|
-
description: 'Project name (
|
|
603
|
+
description: 'Project name (IMPORTANT: pass this to scope memories correctly, e.g. "my-app"). Auto-detected from message if omitted, defaults to "general".'
|
|
604
604
|
}
|
|
605
605
|
},
|
|
606
606
|
required: ['message']
|