claude-brain 0.3.6 → 0.4.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/README.md +1 -1
- package/VERSION +1 -1
- package/package.json +1 -1
- package/src/cli/commands/update.ts +20 -4
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/context/standards-manager.ts +20 -0
- package/src/memory/chroma/client.ts +3 -2
- package/src/memory/chroma/config.ts +8 -1
- package/src/memory/index.ts +59 -29
- package/src/memory/schema.ts +33 -1
- package/src/memory/store.ts +311 -1
- package/src/server/handlers/tools/analyze-decision-evolution.ts +4 -1
- package/src/server/handlers/tools/detect-trends.ts +4 -1
- package/src/server/handlers/tools/get-decision-timeline.ts +4 -1
- package/src/server/handlers/tools/get-recommendations.ts +4 -1
- package/src/server/handlers/tools/schemas.ts +1 -1
- package/src/server/handlers/tools/update-progress.ts +12 -0
- package/src/server/handlers/tools/what-if-analysis.ts +4 -1
- package/src/server/services.ts +2 -1
- package/src/setup/wizard.ts +6 -4
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ A locally-running development assistant that bridges Obsidian knowledge vaults w
|
|
|
28
28
|
# Install globally (requires Bun)
|
|
29
29
|
bun install -g claude-brain
|
|
30
30
|
|
|
31
|
-
# Interactive setup (vault path, log level, installs
|
|
31
|
+
# Interactive setup (vault path, log level, installs ~/.claude/CLAUDE.md)
|
|
32
32
|
claude-brain setup
|
|
33
33
|
|
|
34
34
|
# Register with Claude Code
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.4.0
|
package/package.json
CHANGED
|
@@ -69,17 +69,19 @@ export async function runUpdate() {
|
|
|
69
69
|
}
|
|
70
70
|
console.log()
|
|
71
71
|
|
|
72
|
-
// Step 2: Update CLAUDE.md
|
|
72
|
+
// Step 2: Update CLAUDE.md to ~/.claude/CLAUDE.md (where Claude Code reads it)
|
|
73
73
|
const sourcePath = path.join(PACKAGE_ROOT, 'assets', 'CLAUDE.md')
|
|
74
|
-
const
|
|
74
|
+
const claudeDir = path.join(os.homedir(), '.claude')
|
|
75
|
+
const destPath = path.join(claudeDir, 'CLAUDE.md')
|
|
75
76
|
|
|
76
77
|
if (!existsSync(sourcePath)) {
|
|
77
78
|
console.log(warningText(' CLAUDE.md asset not found in package, skipping'))
|
|
78
79
|
} else if (!existsSync(destPath)) {
|
|
79
80
|
await withSpinner('Installing CLAUDE.md', async () => {
|
|
81
|
+
await fs.mkdir(claudeDir, { recursive: true })
|
|
80
82
|
await fs.copyFile(sourcePath, destPath)
|
|
81
83
|
})
|
|
82
|
-
console.log(successText(' CLAUDE.md installed to
|
|
84
|
+
console.log(successText(' CLAUDE.md installed to ~/.claude/CLAUDE.md'))
|
|
83
85
|
} else {
|
|
84
86
|
const sourceContent = readFileSync(sourcePath, 'utf-8')
|
|
85
87
|
const destContent = readFileSync(destPath, 'utf-8')
|
|
@@ -90,7 +92,21 @@ export async function runUpdate() {
|
|
|
90
92
|
await withSpinner('Updating CLAUDE.md', async () => {
|
|
91
93
|
await fs.copyFile(sourcePath, destPath)
|
|
92
94
|
})
|
|
93
|
-
console.log(successText(' CLAUDE.md updated to
|
|
95
|
+
console.log(successText(' CLAUDE.md updated to ~/.claude/CLAUDE.md'))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Clean up old ~/CLAUDE.md if it exists (from previous versions)
|
|
100
|
+
const oldDestPath = path.join(os.homedir(), 'CLAUDE.md')
|
|
101
|
+
if (existsSync(oldDestPath)) {
|
|
102
|
+
try {
|
|
103
|
+
const oldContent = readFileSync(oldDestPath, 'utf-8')
|
|
104
|
+
if (oldContent.includes('Claude Brain')) {
|
|
105
|
+
await fs.unlink(oldDestPath)
|
|
106
|
+
console.log(dimText(' Removed old ~/CLAUDE.md (migrated to ~/.claude/)'))
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// ignore cleanup errors
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
|
package/src/config/defaults.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PartialConfig } from './schema'
|
|
|
3
3
|
/** Default configuration values for Claude Brain */
|
|
4
4
|
export const defaultConfig: PartialConfig = {
|
|
5
5
|
serverName: 'claude-brain',
|
|
6
|
-
serverVersion: '0.
|
|
6
|
+
serverVersion: '0.4.0',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|
package/src/config/schema.ts
CHANGED
|
@@ -196,7 +196,7 @@ export const ConfigSchema = z.object({
|
|
|
196
196
|
serverName: z.string().default('claude-brain'),
|
|
197
197
|
|
|
198
198
|
/** Server version in semver format */
|
|
199
|
-
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.
|
|
199
|
+
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.4.0'),
|
|
200
200
|
|
|
201
201
|
/** Logging level */
|
|
202
202
|
logLevel: LogLevelSchema.default('info'),
|
|
@@ -248,6 +248,26 @@ export class StandardsManager {
|
|
|
248
248
|
'Ignoring error handling',
|
|
249
249
|
'Deep nesting'
|
|
250
250
|
]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
language: 'JavaScript',
|
|
254
|
+
version: 'ES2022+',
|
|
255
|
+
style: 'standard',
|
|
256
|
+
linting: {},
|
|
257
|
+
conventions: [
|
|
258
|
+
'Use strict mode',
|
|
259
|
+
'Prefer const over let',
|
|
260
|
+
'Use async/await over promises',
|
|
261
|
+
'Use optional chaining and nullish coalescing',
|
|
262
|
+
'Add JSDoc comments for public APIs'
|
|
263
|
+
],
|
|
264
|
+
antiPatterns: [
|
|
265
|
+
'Using var',
|
|
266
|
+
'Callback hell',
|
|
267
|
+
'Ignoring error handling',
|
|
268
|
+
'Deep nesting',
|
|
269
|
+
'Mutating function arguments'
|
|
270
|
+
]
|
|
251
271
|
}
|
|
252
272
|
],
|
|
253
273
|
frameworks: [],
|
|
@@ -82,8 +82,9 @@ export class ChromaClientManager {
|
|
|
82
82
|
this.isConnected = true
|
|
83
83
|
this.logger.info('ChromaDB client initialized successfully')
|
|
84
84
|
} catch (heartbeatError) {
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
85
|
+
this.isConnected = false
|
|
86
|
+
this.logger.warn({ error: heartbeatError }, 'ChromaDB heartbeat failed')
|
|
87
|
+
throw new Error(`ChromaDB server unreachable: ${heartbeatError instanceof Error ? heartbeatError.message : String(heartbeatError)}`)
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
} catch (error) {
|
|
@@ -35,7 +35,14 @@ export function getChromaConfigFromEnv(): Partial<ChromaConfig> {
|
|
|
35
35
|
|
|
36
36
|
if (process.env.CHROMA_HOST) {
|
|
37
37
|
config.host = process.env.CHROMA_HOST
|
|
38
|
-
|
|
38
|
+
// Only default to cloud if CHROMA_MODE isn't explicitly set
|
|
39
|
+
if (!process.env.CHROMA_MODE) {
|
|
40
|
+
config.mode = 'cloud'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (process.env.CHROMA_MODE) {
|
|
45
|
+
config.mode = process.env.CHROMA_MODE as any
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
if (process.env.CHROMA_EMBEDDING_PROVIDER) {
|
package/src/memory/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export class MemoryManager {
|
|
|
55
55
|
private logger: Logger
|
|
56
56
|
private initialized: boolean = false
|
|
57
57
|
private useChromaDB: boolean = true
|
|
58
|
+
private onDecisionStoredCallbacks: ((input: any) => void)[] = []
|
|
58
59
|
|
|
59
60
|
constructor(
|
|
60
61
|
dbPath: string,
|
|
@@ -176,6 +177,24 @@ export class MemoryManager {
|
|
|
176
177
|
return this.database.healthCheck()
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Check if ChromaDB backend is enabled and connected
|
|
182
|
+
*/
|
|
183
|
+
isChromaDBEnabled(): boolean {
|
|
184
|
+
return this.useChromaDB
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Add a listener that fires when a decision is stored (from any backend)
|
|
189
|
+
*/
|
|
190
|
+
addDecisionStoredListener(callback: (input: any) => void): void {
|
|
191
|
+
this.onDecisionStoredCallbacks.push(callback)
|
|
192
|
+
// Also wire to ChromaDB listener if available
|
|
193
|
+
if (this.useChromaDB) {
|
|
194
|
+
this.chroma.store.addDecisionStoredListener(callback)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
179
198
|
async rememberDecision(
|
|
180
199
|
project: string,
|
|
181
200
|
context: string,
|
|
@@ -193,7 +212,7 @@ export class MemoryManager {
|
|
|
193
212
|
tags: options?.tags
|
|
194
213
|
})
|
|
195
214
|
}
|
|
196
|
-
|
|
215
|
+
const id = await this.store.storeDecision({
|
|
197
216
|
project,
|
|
198
217
|
context,
|
|
199
218
|
decision,
|
|
@@ -201,6 +220,13 @@ export class MemoryManager {
|
|
|
201
220
|
alternatives: options?.alternatives,
|
|
202
221
|
tags: options?.tags
|
|
203
222
|
})
|
|
223
|
+
// Notify listeners (e.g., knowledge graph builder) for SQLite path
|
|
224
|
+
for (const cb of this.onDecisionStoredCallbacks) {
|
|
225
|
+
try {
|
|
226
|
+
cb({ project, context, decision, reasoning, alternatives: options?.alternatives, tags: options?.tags, id })
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
return id
|
|
204
230
|
}
|
|
205
231
|
|
|
206
232
|
/**
|
|
@@ -281,7 +307,7 @@ export class MemoryManager {
|
|
|
281
307
|
}
|
|
282
308
|
|
|
283
309
|
/**
|
|
284
|
-
* Store a pattern in memory
|
|
310
|
+
* Store a pattern in memory — routes to ChromaDB or SQLite
|
|
285
311
|
*/
|
|
286
312
|
async storePattern(input: {
|
|
287
313
|
project: string
|
|
@@ -291,14 +317,14 @@ export class MemoryManager {
|
|
|
291
317
|
confidence: number
|
|
292
318
|
context?: string
|
|
293
319
|
}): Promise<string> {
|
|
294
|
-
if (
|
|
295
|
-
|
|
320
|
+
if (this.useChromaDB) {
|
|
321
|
+
return this.chroma.store.storePattern(input)
|
|
296
322
|
}
|
|
297
|
-
return this.
|
|
323
|
+
return this.store.storePattern(input)
|
|
298
324
|
}
|
|
299
325
|
|
|
300
326
|
/**
|
|
301
|
-
* Store a correction/lesson learned
|
|
327
|
+
* Store a correction/lesson learned — routes to ChromaDB or SQLite
|
|
302
328
|
*/
|
|
303
329
|
async storeCorrection(input: {
|
|
304
330
|
project: string
|
|
@@ -308,14 +334,14 @@ export class MemoryManager {
|
|
|
308
334
|
context?: string
|
|
309
335
|
confidence: number
|
|
310
336
|
}): Promise<string> {
|
|
311
|
-
if (
|
|
312
|
-
|
|
337
|
+
if (this.useChromaDB) {
|
|
338
|
+
return this.chroma.store.storeCorrection(input)
|
|
313
339
|
}
|
|
314
|
-
return this.
|
|
340
|
+
return this.store.storeCorrection(input)
|
|
315
341
|
}
|
|
316
342
|
|
|
317
343
|
/**
|
|
318
|
-
* Get patterns for a project
|
|
344
|
+
* Get patterns for a project — routes to ChromaDB or SQLite
|
|
319
345
|
*/
|
|
320
346
|
async getPatterns(
|
|
321
347
|
project?: string,
|
|
@@ -324,35 +350,39 @@ export class MemoryManager {
|
|
|
324
350
|
limit?: number
|
|
325
351
|
}
|
|
326
352
|
): Promise<any[]> {
|
|
327
|
-
if (
|
|
328
|
-
|
|
353
|
+
if (this.useChromaDB) {
|
|
354
|
+
if (project) {
|
|
355
|
+
return this.chroma.store.getPatternsByProject(project, options)
|
|
356
|
+
}
|
|
357
|
+
return this.chroma.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
329
358
|
}
|
|
330
359
|
if (project) {
|
|
331
|
-
return this.
|
|
360
|
+
return this.store.getPatternsByProject(project, options)
|
|
332
361
|
}
|
|
333
|
-
|
|
334
|
-
return this.chroma.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
362
|
+
return this.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
335
363
|
}
|
|
336
364
|
|
|
337
365
|
/**
|
|
338
|
-
* Get corrections for a project
|
|
366
|
+
* Get corrections for a project — routes to ChromaDB or SQLite
|
|
339
367
|
*/
|
|
340
368
|
async getCorrections(
|
|
341
369
|
project?: string,
|
|
342
370
|
options?: { limit?: number }
|
|
343
371
|
): Promise<any[]> {
|
|
344
|
-
if (
|
|
345
|
-
|
|
372
|
+
if (this.useChromaDB) {
|
|
373
|
+
if (project) {
|
|
374
|
+
return this.chroma.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
375
|
+
}
|
|
376
|
+
return this.chroma.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
346
377
|
}
|
|
347
378
|
if (project) {
|
|
348
|
-
return this.
|
|
379
|
+
return this.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
349
380
|
}
|
|
350
|
-
|
|
351
|
-
return this.chroma.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
381
|
+
return this.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
352
382
|
}
|
|
353
383
|
|
|
354
384
|
/**
|
|
355
|
-
* Search patterns by query
|
|
385
|
+
* Search patterns by query — routes to ChromaDB or SQLite
|
|
356
386
|
*/
|
|
357
387
|
async searchPatterns(
|
|
358
388
|
query: string,
|
|
@@ -363,14 +393,14 @@ export class MemoryManager {
|
|
|
363
393
|
minSimilarity?: number
|
|
364
394
|
}
|
|
365
395
|
): Promise<any[]> {
|
|
366
|
-
if (
|
|
367
|
-
|
|
396
|
+
if (this.useChromaDB) {
|
|
397
|
+
return this.chroma.store.searchPatterns(query, options)
|
|
368
398
|
}
|
|
369
|
-
return this.
|
|
399
|
+
return this.store.searchPatterns(query, options)
|
|
370
400
|
}
|
|
371
401
|
|
|
372
402
|
/**
|
|
373
|
-
* Search corrections by query
|
|
403
|
+
* Search corrections by query — routes to ChromaDB or SQLite
|
|
374
404
|
*/
|
|
375
405
|
async searchCorrections(
|
|
376
406
|
query: string,
|
|
@@ -380,10 +410,10 @@ export class MemoryManager {
|
|
|
380
410
|
minSimilarity?: number
|
|
381
411
|
}
|
|
382
412
|
): Promise<any[]> {
|
|
383
|
-
if (
|
|
384
|
-
|
|
413
|
+
if (this.useChromaDB) {
|
|
414
|
+
return this.chroma.store.searchCorrections(query, options)
|
|
385
415
|
}
|
|
386
|
-
return this.
|
|
416
|
+
return this.store.searchCorrections(query, options)
|
|
387
417
|
}
|
|
388
418
|
}
|
|
389
419
|
|
package/src/memory/schema.ts
CHANGED
|
@@ -41,12 +41,44 @@ CREATE TABLE IF NOT EXISTS decisions (
|
|
|
41
41
|
-- Index for decision lookups
|
|
42
42
|
CREATE INDEX IF NOT EXISTS idx_decisions_memory
|
|
43
43
|
ON decisions(memory_id);
|
|
44
|
+
|
|
45
|
+
-- Patterns table (Phase 12)
|
|
46
|
+
-- Stores reusable patterns with SQLite fallback
|
|
47
|
+
CREATE TABLE IF NOT EXISTS patterns (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
memory_id TEXT NOT NULL,
|
|
50
|
+
project TEXT NOT NULL,
|
|
51
|
+
pattern_type TEXT NOT NULL CHECK(pattern_type IN ('solution','anti-pattern','best-practice','common-issue')),
|
|
52
|
+
description TEXT NOT NULL,
|
|
53
|
+
example TEXT,
|
|
54
|
+
confidence REAL DEFAULT 0.8,
|
|
55
|
+
context TEXT,
|
|
56
|
+
created_at TEXT NOT NULL,
|
|
57
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_project ON patterns(project);
|
|
60
|
+
|
|
61
|
+
-- Corrections table (Phase 12)
|
|
62
|
+
-- Stores lessons learned with SQLite fallback
|
|
63
|
+
CREATE TABLE IF NOT EXISTS corrections (
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
memory_id TEXT NOT NULL,
|
|
66
|
+
project TEXT NOT NULL,
|
|
67
|
+
original TEXT NOT NULL,
|
|
68
|
+
correction TEXT NOT NULL,
|
|
69
|
+
reasoning TEXT NOT NULL,
|
|
70
|
+
context TEXT,
|
|
71
|
+
confidence REAL DEFAULT 0.9,
|
|
72
|
+
created_at TEXT NOT NULL,
|
|
73
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_corrections_project ON corrections(project);
|
|
44
76
|
`
|
|
45
77
|
|
|
46
78
|
/**
|
|
47
79
|
* Schema version for migrations
|
|
48
80
|
*/
|
|
49
|
-
export const SCHEMA_VERSION =
|
|
81
|
+
export const SCHEMA_VERSION = 2
|
|
50
82
|
|
|
51
83
|
/**
|
|
52
84
|
* Check if schema needs migration
|
package/src/memory/store.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
DecisionRow
|
|
18
18
|
} from './types'
|
|
19
19
|
import { EmbeddingService } from './embeddings'
|
|
20
|
-
import { embeddingToBuffer, bufferToEmbedding } from './embedding-utils'
|
|
20
|
+
import { embeddingToBuffer, bufferToEmbedding, cosineSimilarity } from './embedding-utils'
|
|
21
21
|
|
|
22
22
|
export class MemoryStore {
|
|
23
23
|
private db: Database
|
|
@@ -326,6 +326,316 @@ export class MemoryStore {
|
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Store a pattern (creates memory + pattern record)
|
|
331
|
+
*/
|
|
332
|
+
async storePattern(input: {
|
|
333
|
+
project: string
|
|
334
|
+
pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
335
|
+
description: string
|
|
336
|
+
example?: string
|
|
337
|
+
confidence: number
|
|
338
|
+
context?: string
|
|
339
|
+
}): Promise<string> {
|
|
340
|
+
try {
|
|
341
|
+
const content = `Pattern (${input.pattern_type}): ${input.description}${input.context ? `\nContext: ${input.context}` : ''}${input.example ? `\nExample: ${input.example}` : ''}`
|
|
342
|
+
|
|
343
|
+
const memoryId = await this.storeMemory({
|
|
344
|
+
project: input.project,
|
|
345
|
+
content,
|
|
346
|
+
metadata: {
|
|
347
|
+
type: 'pattern',
|
|
348
|
+
pattern_type: input.pattern_type,
|
|
349
|
+
confidence: input.confidence
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const patternId = randomUUID()
|
|
354
|
+
const now = new Date().toISOString()
|
|
355
|
+
const stmt = this.db.prepare(`
|
|
356
|
+
INSERT INTO patterns (id, memory_id, project, pattern_type, description, example, confidence, context, created_at)
|
|
357
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
358
|
+
`)
|
|
359
|
+
|
|
360
|
+
stmt.run(
|
|
361
|
+
patternId,
|
|
362
|
+
memoryId,
|
|
363
|
+
input.project,
|
|
364
|
+
input.pattern_type,
|
|
365
|
+
input.description,
|
|
366
|
+
input.example || null,
|
|
367
|
+
input.confidence,
|
|
368
|
+
input.context || null,
|
|
369
|
+
now
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
this.logger.info({ patternId, memoryId, project: input.project }, 'Pattern stored')
|
|
373
|
+
return patternId
|
|
374
|
+
} catch (error) {
|
|
375
|
+
this.logger.error({ error, input }, 'Failed to store pattern')
|
|
376
|
+
throw error
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Store a correction (creates memory + correction record)
|
|
382
|
+
*/
|
|
383
|
+
async storeCorrection(input: {
|
|
384
|
+
project: string
|
|
385
|
+
original: string
|
|
386
|
+
correction: string
|
|
387
|
+
reasoning: string
|
|
388
|
+
context?: string
|
|
389
|
+
confidence: number
|
|
390
|
+
}): Promise<string> {
|
|
391
|
+
try {
|
|
392
|
+
const content = `Correction: ${input.correction}\nOriginal: ${input.original}\nReasoning: ${input.reasoning}${input.context ? `\nContext: ${input.context}` : ''}`
|
|
393
|
+
|
|
394
|
+
const memoryId = await this.storeMemory({
|
|
395
|
+
project: input.project,
|
|
396
|
+
content,
|
|
397
|
+
metadata: {
|
|
398
|
+
type: 'correction',
|
|
399
|
+
confidence: input.confidence
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
const correctionId = randomUUID()
|
|
404
|
+
const now = new Date().toISOString()
|
|
405
|
+
const stmt = this.db.prepare(`
|
|
406
|
+
INSERT INTO corrections (id, memory_id, project, original, correction, reasoning, context, confidence, created_at)
|
|
407
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
408
|
+
`)
|
|
409
|
+
|
|
410
|
+
stmt.run(
|
|
411
|
+
correctionId,
|
|
412
|
+
memoryId,
|
|
413
|
+
input.project,
|
|
414
|
+
input.original,
|
|
415
|
+
input.correction,
|
|
416
|
+
input.reasoning,
|
|
417
|
+
input.context || null,
|
|
418
|
+
input.confidence,
|
|
419
|
+
now
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
this.logger.info({ correctionId, memoryId, project: input.project }, 'Correction stored')
|
|
423
|
+
return correctionId
|
|
424
|
+
} catch (error) {
|
|
425
|
+
this.logger.error({ error, input }, 'Failed to store correction')
|
|
426
|
+
throw error
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get patterns by project with optional type filter
|
|
432
|
+
*/
|
|
433
|
+
getPatternsByProject(project: string, options?: {
|
|
434
|
+
pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
435
|
+
limit?: number
|
|
436
|
+
}): any[] {
|
|
437
|
+
try {
|
|
438
|
+
let sql = 'SELECT * FROM patterns WHERE project = ?'
|
|
439
|
+
const params: any[] = [project]
|
|
440
|
+
|
|
441
|
+
if (options?.pattern_type) {
|
|
442
|
+
sql += ' AND pattern_type = ?'
|
|
443
|
+
params.push(options.pattern_type)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
sql += ' ORDER BY created_at DESC'
|
|
447
|
+
|
|
448
|
+
if (options?.limit) {
|
|
449
|
+
sql += ' LIMIT ?'
|
|
450
|
+
params.push(options.limit)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const stmt = this.db.prepare(sql)
|
|
454
|
+
const rows = stmt.all(...params) as any[]
|
|
455
|
+
|
|
456
|
+
return rows.map(row => ({
|
|
457
|
+
id: row.id,
|
|
458
|
+
description: row.description,
|
|
459
|
+
metadata: {
|
|
460
|
+
project: row.project,
|
|
461
|
+
pattern_type: row.pattern_type,
|
|
462
|
+
example: row.example || '',
|
|
463
|
+
confidence: row.confidence,
|
|
464
|
+
context: row.context || '',
|
|
465
|
+
created_at: row.created_at
|
|
466
|
+
}
|
|
467
|
+
}))
|
|
468
|
+
} catch (error) {
|
|
469
|
+
this.logger.error({ error, project }, 'Failed to get patterns by project')
|
|
470
|
+
return []
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get corrections by project
|
|
476
|
+
*/
|
|
477
|
+
getCorrectionsByProject(project: string, limit: number = 10): any[] {
|
|
478
|
+
try {
|
|
479
|
+
const stmt = this.db.prepare(
|
|
480
|
+
'SELECT * FROM corrections WHERE project = ? ORDER BY created_at DESC LIMIT ?'
|
|
481
|
+
)
|
|
482
|
+
const rows = stmt.all(project, limit) as any[]
|
|
483
|
+
|
|
484
|
+
return rows.map(row => ({
|
|
485
|
+
id: row.id,
|
|
486
|
+
correction: row.correction,
|
|
487
|
+
metadata: {
|
|
488
|
+
project: row.project,
|
|
489
|
+
original: row.original,
|
|
490
|
+
correction: row.correction,
|
|
491
|
+
reasoning: row.reasoning,
|
|
492
|
+
context: row.context || '',
|
|
493
|
+
confidence: row.confidence,
|
|
494
|
+
created_at: row.created_at
|
|
495
|
+
}
|
|
496
|
+
}))
|
|
497
|
+
} catch (error) {
|
|
498
|
+
this.logger.error({ error, project }, 'Failed to get corrections by project')
|
|
499
|
+
return []
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Search patterns using semantic search on underlying memories
|
|
505
|
+
*/
|
|
506
|
+
async searchPatterns(
|
|
507
|
+
query: string,
|
|
508
|
+
options?: {
|
|
509
|
+
project?: string
|
|
510
|
+
pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
511
|
+
limit?: number
|
|
512
|
+
minSimilarity?: number
|
|
513
|
+
}
|
|
514
|
+
): Promise<any[]> {
|
|
515
|
+
try {
|
|
516
|
+
// If no query, return all patterns for the project
|
|
517
|
+
if (!query && options?.project) {
|
|
518
|
+
return this.getPatternsByProject(options.project, {
|
|
519
|
+
pattern_type: options?.pattern_type,
|
|
520
|
+
limit: options?.limit
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Generate embedding for semantic search
|
|
525
|
+
const queryEmbedding = await this.embeddings.generateEmbedding(query || 'pattern')
|
|
526
|
+
|
|
527
|
+
let sql = `
|
|
528
|
+
SELECT p.*, m.embedding, m.content as memory_content
|
|
529
|
+
FROM patterns p
|
|
530
|
+
JOIN memories m ON p.memory_id = m.id
|
|
531
|
+
WHERE 1=1
|
|
532
|
+
`
|
|
533
|
+
const params: any[] = []
|
|
534
|
+
|
|
535
|
+
if (options?.project) {
|
|
536
|
+
sql += ' AND p.project = ?'
|
|
537
|
+
params.push(options.project)
|
|
538
|
+
}
|
|
539
|
+
if (options?.pattern_type) {
|
|
540
|
+
sql += ' AND p.pattern_type = ?'
|
|
541
|
+
params.push(options.pattern_type)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const stmt = this.db.prepare(sql)
|
|
545
|
+
const rows = stmt.all(...params) as any[]
|
|
546
|
+
|
|
547
|
+
const results = rows.map(row => {
|
|
548
|
+
const embedding = bufferToEmbedding(row.embedding)
|
|
549
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding)
|
|
550
|
+
return {
|
|
551
|
+
id: row.id,
|
|
552
|
+
content: row.description,
|
|
553
|
+
metadata: {
|
|
554
|
+
project: row.project,
|
|
555
|
+
pattern_type: row.pattern_type,
|
|
556
|
+
example: row.example || '',
|
|
557
|
+
confidence: row.confidence,
|
|
558
|
+
context: row.context || '',
|
|
559
|
+
created_at: row.created_at
|
|
560
|
+
},
|
|
561
|
+
similarity
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
.filter(r => r.similarity >= (options?.minSimilarity || 0.3))
|
|
565
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
566
|
+
.slice(0, options?.limit || 10)
|
|
567
|
+
|
|
568
|
+
return results
|
|
569
|
+
} catch (error) {
|
|
570
|
+
this.logger.error({ error, query }, 'Pattern search failed')
|
|
571
|
+
return []
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Search corrections using semantic search on underlying memories
|
|
577
|
+
*/
|
|
578
|
+
async searchCorrections(
|
|
579
|
+
query: string,
|
|
580
|
+
options?: {
|
|
581
|
+
project?: string
|
|
582
|
+
limit?: number
|
|
583
|
+
minSimilarity?: number
|
|
584
|
+
}
|
|
585
|
+
): Promise<any[]> {
|
|
586
|
+
try {
|
|
587
|
+
// If no query, return all corrections for the project
|
|
588
|
+
if (!query && options?.project) {
|
|
589
|
+
return this.getCorrectionsByProject(options.project, options?.limit || 10)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const queryEmbedding = await this.embeddings.generateEmbedding(query || 'correction')
|
|
593
|
+
|
|
594
|
+
let sql = `
|
|
595
|
+
SELECT c.*, m.embedding, m.content as memory_content
|
|
596
|
+
FROM corrections c
|
|
597
|
+
JOIN memories m ON c.memory_id = m.id
|
|
598
|
+
WHERE 1=1
|
|
599
|
+
`
|
|
600
|
+
const params: any[] = []
|
|
601
|
+
|
|
602
|
+
if (options?.project) {
|
|
603
|
+
sql += ' AND c.project = ?'
|
|
604
|
+
params.push(options.project)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const stmt = this.db.prepare(sql)
|
|
608
|
+
const rows = stmt.all(...params) as any[]
|
|
609
|
+
|
|
610
|
+
const results = rows.map(row => {
|
|
611
|
+
const embedding = bufferToEmbedding(row.embedding)
|
|
612
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding)
|
|
613
|
+
return {
|
|
614
|
+
id: row.id,
|
|
615
|
+
content: row.correction,
|
|
616
|
+
metadata: {
|
|
617
|
+
project: row.project,
|
|
618
|
+
original: row.original,
|
|
619
|
+
correction: row.correction,
|
|
620
|
+
reasoning: row.reasoning,
|
|
621
|
+
context: row.context || '',
|
|
622
|
+
confidence: row.confidence,
|
|
623
|
+
created_at: row.created_at
|
|
624
|
+
},
|
|
625
|
+
similarity
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
.filter(r => r.similarity >= (options?.minSimilarity || 0.3))
|
|
629
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
630
|
+
.slice(0, options?.limit || 10)
|
|
631
|
+
|
|
632
|
+
return results
|
|
633
|
+
} catch (error) {
|
|
634
|
+
this.logger.error({ error, query }, 'Correction search failed')
|
|
635
|
+
return []
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
329
639
|
/**
|
|
330
640
|
* Convert database row to Decision object
|
|
331
641
|
*/
|
|
@@ -35,7 +35,10 @@ export async function handleAnalyzeDecisionEvolution(
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
if (analysis.totalDecisions === 0) {
|
|
38
|
-
|
|
38
|
+
const chromaNote = !memory.isChromaDBEnabled()
|
|
39
|
+
? '\n\nNote: ChromaDB is not connected. Decision evolution analysis requires ChromaDB for semantic search. Start a ChromaDB server or switch to persistent mode.'
|
|
40
|
+
: ''
|
|
41
|
+
return ResponseFormatter.text(`No decisions found for topic: "${topic}"` + chromaNote)
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
const parts: string[] = []
|
|
@@ -33,7 +33,10 @@ export async function handleDetectTrends(
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
if (analysis.totalDecisionsAnalyzed === 0) {
|
|
36
|
-
|
|
36
|
+
const chromaNote = !memory.isChromaDBEnabled()
|
|
37
|
+
? '\n\nNote: ChromaDB is not connected. Trend detection requires ChromaDB for semantic search across decisions. Start a ChromaDB server or switch to persistent mode.'
|
|
38
|
+
: ''
|
|
39
|
+
return ResponseFormatter.text('No decisions found for trend analysis.' + chromaNote)
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
const parts: string[] = []
|
|
@@ -50,7 +50,10 @@ export async function handleGetDecisionTimeline(
|
|
|
50
50
|
})
|
|
51
51
|
|
|
52
52
|
if (timeline.entries.length === 0) {
|
|
53
|
-
|
|
53
|
+
const chromaNote = !memory.isChromaDBEnabled()
|
|
54
|
+
? '\n\nNote: ChromaDB is not connected. Decision timeline requires ChromaDB for semantic search. Start a ChromaDB server or switch to persistent mode.'
|
|
55
|
+
: ''
|
|
56
|
+
return ResponseFormatter.text('No decisions found for the specified criteria.' + chromaNote)
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// Format timeline
|
|
@@ -35,7 +35,10 @@ export async function handleGetRecommendations(
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
if (result.recommendations.length === 0) {
|
|
38
|
-
|
|
38
|
+
const chromaNote = !memory.isChromaDBEnabled()
|
|
39
|
+
? '\n\nNote: ChromaDB is not connected. Recommendations require ChromaDB for semantic search across patterns, corrections, and decisions. Start a ChromaDB server or switch to persistent mode.'
|
|
40
|
+
: ''
|
|
41
|
+
return ResponseFormatter.text(`No recommendations found for: "${query}"` + chromaNote)
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
const parts: string[] = []
|
|
@@ -204,7 +204,7 @@ export const GetRecommendationsSchema = z.object({
|
|
|
204
204
|
})
|
|
205
205
|
|
|
206
206
|
export const FindCrossProjectPatternsSchema = z.object({
|
|
207
|
-
min_projects: z.number().int().min(
|
|
207
|
+
min_projects: z.number().int().min(1).max(50).optional().default(2),
|
|
208
208
|
limit: z.number().int().min(1).max(100).optional().default(20),
|
|
209
209
|
query: z.string().optional()
|
|
210
210
|
})
|
|
@@ -50,6 +50,18 @@ export async function handleUpdateProgress(
|
|
|
50
50
|
completedAt: new Date()
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
+
// Calculate and update completion percentage
|
|
54
|
+
const progressState = await context.progress.getProgress(project_name)
|
|
55
|
+
const totalTasks = progressState.completedTasks.length + progressState.currentTasks.length
|
|
56
|
+
const completionPercentage = totalTasks > 0
|
|
57
|
+
? Math.round((progressState.completedTasks.length / totalTasks) * 100)
|
|
58
|
+
: 0
|
|
59
|
+
|
|
60
|
+
await context.progress.updateProgress(project_name, {
|
|
61
|
+
completionPercentage,
|
|
62
|
+
currentPhase: completionPercentage >= 100 ? 'complete' : 'active'
|
|
63
|
+
})
|
|
64
|
+
|
|
53
65
|
const progressFile = await vault.reader.readMarkdownFile(projectPaths.progress)
|
|
54
66
|
const updatedContent = updateNextStepsSection(progressFile.content, next_steps)
|
|
55
67
|
|
|
@@ -63,7 +63,10 @@ export async function handleWhatIfAnalysis(
|
|
|
63
63
|
return ResponseFormatter.text(withMemoryIndicator(content, totalAffected))
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
const chromaNote = !memory.isChromaDBEnabled()
|
|
67
|
+
? '\n\nNote: ChromaDB is not connected. What-if analysis requires ChromaDB for semantic search across decisions. Start a ChromaDB server or switch to persistent mode.'
|
|
68
|
+
: ''
|
|
69
|
+
return ResponseFormatter.text(content + chromaNote)
|
|
67
70
|
|
|
68
71
|
} catch (error) {
|
|
69
72
|
ErrorHandler.logError(logger, error, { tool: 'what_if_analysis' })
|
package/src/server/services.ts
CHANGED
|
@@ -157,7 +157,8 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// Hook builder into decision storage for real-time graph population
|
|
160
|
-
|
|
160
|
+
// Uses MemoryManager listener so it works for both ChromaDB and SQLite paths
|
|
161
|
+
memory.addDecisionStoredListener((input) => {
|
|
161
162
|
builder.processDecision(input)
|
|
162
163
|
})
|
|
163
164
|
|
package/src/setup/wizard.ts
CHANGED
|
@@ -122,7 +122,7 @@ export class SetupWizard {
|
|
|
122
122
|
{
|
|
123
123
|
type: 'confirm',
|
|
124
124
|
name: 'installClaudeMd',
|
|
125
|
-
message: 'Install CLAUDE.md protocol file to
|
|
125
|
+
message: 'Install CLAUDE.md protocol file to ~/.claude/CLAUDE.md?',
|
|
126
126
|
initial: true
|
|
127
127
|
}
|
|
128
128
|
])
|
|
@@ -244,7 +244,7 @@ SERVER_NAME=claude-brain
|
|
|
244
244
|
|
|
245
245
|
private async confirmClaudeMdInstall(): Promise<boolean> {
|
|
246
246
|
const sourcePath = path.join(PACKAGE_ROOT, 'assets', 'CLAUDE.md')
|
|
247
|
-
const destPath = path.join(os.homedir(), 'CLAUDE.md')
|
|
247
|
+
const destPath = path.join(os.homedir(), '.claude', 'CLAUDE.md')
|
|
248
248
|
|
|
249
249
|
if (!existsSync(sourcePath)) {
|
|
250
250
|
console.log(warningText('CLAUDE.md asset not found, skipping'))
|
|
@@ -255,7 +255,7 @@ SERVER_NAME=claude-brain
|
|
|
255
255
|
const { overwrite } = await prompts({
|
|
256
256
|
type: 'confirm',
|
|
257
257
|
name: 'overwrite',
|
|
258
|
-
message: '
|
|
258
|
+
message: '~/.claude/CLAUDE.md already exists. Overwrite?',
|
|
259
259
|
initial: false,
|
|
260
260
|
})
|
|
261
261
|
if (!overwrite) {
|
|
@@ -269,7 +269,9 @@ SERVER_NAME=claude-brain
|
|
|
269
269
|
|
|
270
270
|
private async copyClaudeMd(): Promise<void> {
|
|
271
271
|
const sourcePath = path.join(PACKAGE_ROOT, 'assets', 'CLAUDE.md')
|
|
272
|
-
const
|
|
272
|
+
const claudeDir = path.join(os.homedir(), '.claude')
|
|
273
|
+
const destPath = path.join(claudeDir, 'CLAUDE.md')
|
|
274
|
+
await fs.mkdir(claudeDir, { recursive: true })
|
|
273
275
|
await fs.copyFile(sourcePath, destPath)
|
|
274
276
|
}
|
|
275
277
|
|