claude-brain 0.13.0 → 0.14.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/cli/commands/init.ts +24 -0
- package/src/cli/commands/serve.ts +11 -0
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/hooks/git-capture.ts +17 -2
- package/src/hooks/git-hook-installer.ts +11 -1
- package/src/routing/intent-classifier.ts +11 -3
- package/src/routing/router.ts +72 -20
- package/src/routing/search-engine.ts +14 -3
- package/src/server/http-api.ts +32 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.14.0
|
package/package.json
CHANGED
package/src/cli/commands/init.ts
CHANGED
|
@@ -32,6 +32,30 @@ export async function runInit() {
|
|
|
32
32
|
}
|
|
33
33
|
console.log()
|
|
34
34
|
|
|
35
|
+
// Phase 23b: Validate project directory
|
|
36
|
+
const { existsSync: dirExists } = await import('fs')
|
|
37
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || ''
|
|
38
|
+
if (repoPath === homeDir) {
|
|
39
|
+
console.log(warningText(' This is your home directory — not a project root.'))
|
|
40
|
+
console.log(dimText(' Run this inside a project directory, or pass a path: claude-brain init /path/to/project'))
|
|
41
|
+
console.log()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const hasGit = dirExists(path.join(repoPath, '.git'))
|
|
46
|
+
const hasPackageJson = dirExists(path.join(repoPath, 'package.json'))
|
|
47
|
+
const hasCargoToml = dirExists(path.join(repoPath, 'Cargo.toml'))
|
|
48
|
+
const hasPyproject = dirExists(path.join(repoPath, 'pyproject.toml'))
|
|
49
|
+
const hasGoMod = dirExists(path.join(repoPath, 'go.mod'))
|
|
50
|
+
const isProject = hasGit || hasPackageJson || hasCargoToml || hasPyproject || hasGoMod
|
|
51
|
+
|
|
52
|
+
if (!isProject && !force) {
|
|
53
|
+
console.log(warningText(' No project markers found (no .git, package.json, Cargo.toml, etc.)'))
|
|
54
|
+
console.log(dimText(' Use --force to scan anyway, or run inside a project directory.'))
|
|
55
|
+
console.log()
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
// Scan the repo
|
|
36
60
|
const context = await withSpinner('Scanning repository', () => scanRepo(repoPath))
|
|
37
61
|
|
|
@@ -18,6 +18,17 @@ export async function runServe() {
|
|
|
18
18
|
// Auto-initialize home directory on first run
|
|
19
19
|
ensureHomeDirectory()
|
|
20
20
|
|
|
21
|
+
// Auto-install Claude Code hooks (idempotent, non-fatal)
|
|
22
|
+
try {
|
|
23
|
+
const { installHooks } = await import('@/hooks/installer')
|
|
24
|
+
const hookResult = installHooks()
|
|
25
|
+
if (hookResult.message !== 'Hooks already installed') {
|
|
26
|
+
console.error(`[claude-brain] ${hookResult.message}`)
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`[claude-brain] Hook auto-install skipped: ${error instanceof Error ? error.message : 'unknown error'}`)
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
if (process.env.NODE_ENV !== 'production') {
|
|
22
33
|
console.error(BANNER)
|
|
23
34
|
}
|
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.14.0',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|
package/src/config/schema.ts
CHANGED
|
@@ -284,7 +284,7 @@ export const ConfigSchema = z.object({
|
|
|
284
284
|
serverName: z.string().default('claude-brain'),
|
|
285
285
|
|
|
286
286
|
/** Server version in semver format */
|
|
287
|
-
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.
|
|
287
|
+
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.14.0'),
|
|
288
288
|
|
|
289
289
|
/** Logging level */
|
|
290
290
|
logLevel: LogLevelSchema.default('info'),
|
package/src/hooks/git-capture.ts
CHANGED
|
@@ -9,13 +9,15 @@ import type { CapturedKnowledge } from './types'
|
|
|
9
9
|
|
|
10
10
|
export async function handleGitCapture(): Promise<void> {
|
|
11
11
|
// Parse positional args: project, branch, message, files, port
|
|
12
|
-
const [, , ,
|
|
12
|
+
const [, , , rawProject, branch, message, filesStr, portStr] = process.argv
|
|
13
13
|
|
|
14
|
-
if (!
|
|
14
|
+
if (!rawProject || !message) {
|
|
15
15
|
process.exit(0)
|
|
16
16
|
return
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Phase 23b: Resolve project name — strip scoped package prefix, validate
|
|
20
|
+
const project = resolveProjectName(rawProject)
|
|
19
21
|
const port = parseInt(portStr || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
|
|
20
22
|
const files = filesStr ? filesStr.split(',').filter(Boolean) : []
|
|
21
23
|
|
|
@@ -60,6 +62,19 @@ export async function handleGitCapture(): Promise<void> {
|
|
|
60
62
|
process.exit(0)
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Phase 23b: Resolve project name from raw input.
|
|
67
|
+
* Strips npm scoped prefixes (@scope/name → name) and normalizes.
|
|
68
|
+
*/
|
|
69
|
+
function resolveProjectName(raw: string): string {
|
|
70
|
+
let name = raw.trim()
|
|
71
|
+
// Strip npm scoped prefix: @scope/name → name
|
|
72
|
+
if (name.startsWith('@') && name.includes('/')) {
|
|
73
|
+
name = name.split('/').pop() || name
|
|
74
|
+
}
|
|
75
|
+
return name || raw
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
/**
|
|
64
79
|
* Detect technologies from file extensions.
|
|
65
80
|
*/
|
|
@@ -25,7 +25,17 @@ function buildHookScript(): string {
|
|
|
25
25
|
' BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)',
|
|
26
26
|
' MESSAGE=$(git log -1 --pretty=%B 2>/dev/null)',
|
|
27
27
|
' FILES=$(git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | tr "\\n" "," | sed "s/,$//")',
|
|
28
|
-
'
|
|
28
|
+
' REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)',
|
|
29
|
+
' # Phase 23b: Smart project name detection (package.json > init-context > basename)',
|
|
30
|
+
' if [ -f "$REPO_ROOT/package.json" ]; then',
|
|
31
|
+
' PROJECT=$(grep -m1 \'"name"\' "$REPO_ROOT/package.json" 2>/dev/null | sed \'s/.*"name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/\' | sed \'s/@[^/]*\\///\')',
|
|
32
|
+
' fi',
|
|
33
|
+
' if [ -z "$PROJECT" ] && [ -f "$REPO_ROOT/.claude-brain/init-context.md" ]; then',
|
|
34
|
+
' PROJECT=$(head -1 "$REPO_ROOT/.claude-brain/init-context.md" 2>/dev/null | sed \'s/^Project "\\([^"]*\\)".*/\\1/\')',
|
|
35
|
+
' fi',
|
|
36
|
+
' if [ -z "$PROJECT" ]; then',
|
|
37
|
+
' PROJECT=$(basename "$REPO_ROOT")',
|
|
38
|
+
' fi',
|
|
29
39
|
' PORT=${CLAUDE_BRAIN_PORT:-3000}',
|
|
30
40
|
' claude-brain git-capture "$PROJECT" "$BRANCH" "$MESSAGE" "$FILES" "$PORT" 2>/dev/null',
|
|
31
41
|
') &',
|
|
@@ -88,7 +88,12 @@ const PROGRESS_PHRASES = [
|
|
|
88
88
|
'built', 'created', 'added', 'fixed', 'resolved',
|
|
89
89
|
'shipped', 'deployed', 'merged', 'released',
|
|
90
90
|
"i'm working on", 'currently working', 'making progress',
|
|
91
|
-
'just did', 'just finished'
|
|
91
|
+
'just did', 'just finished',
|
|
92
|
+
// Phase 23b: present-tense statement patterns
|
|
93
|
+
"i'm implementing", "i'm building", "i'm adding",
|
|
94
|
+
"i'm fixing", "i'm refactoring", "i'm setting up",
|
|
95
|
+
"i'm migrating", "i'm updating", "i'm creating",
|
|
96
|
+
'started implementing', 'started building', 'started adding'
|
|
92
97
|
]
|
|
93
98
|
|
|
94
99
|
// Comparison indicators
|
|
@@ -156,10 +161,13 @@ const LIST_PHRASES = [
|
|
|
156
161
|
// Update/correct existing memory
|
|
157
162
|
const UPDATE_PHRASES = [
|
|
158
163
|
'actually,', 'actually ', 'correction:', 'update that',
|
|
159
|
-
'i changed my mind', 'change that to', 'instead of what i said',
|
|
164
|
+
'i changed my mind', 'changed my mind', 'change that to', 'instead of what i said',
|
|
160
165
|
'supersedes', 'override', 'revise that', 'amend that',
|
|
161
166
|
'that should be', 'update the decision', 'modify that',
|
|
162
|
-
'replace that with', 'no wait', 'scratch that, use'
|
|
167
|
+
'replace that with', 'no wait', 'scratch that, use',
|
|
168
|
+
// Phase 23b: additional update triggers
|
|
169
|
+
'switch to', 'instead use', 'use instead', 'no longer using',
|
|
170
|
+
'moved to', 'migrated to', 'replaced with'
|
|
163
171
|
]
|
|
164
172
|
|
|
165
173
|
// Delete/forget memory
|
package/src/routing/router.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getPhase12Service,
|
|
24
24
|
getKnowledgeGraphService,
|
|
25
25
|
getEpisodeService,
|
|
26
|
+
getSessionTracker,
|
|
26
27
|
isServicesInitialized
|
|
27
28
|
} from '@/server/services'
|
|
28
29
|
import { timed } from '@/utils/timing'
|
|
@@ -251,6 +252,7 @@ export class BrainRouter {
|
|
|
251
252
|
return this.servicesNotReady()
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
254
256
|
const query = entities.topic || message
|
|
255
257
|
const tiers: TierResults[] = []
|
|
256
258
|
const hasTemporal = classification?.secondary.includes('exploration') ||
|
|
@@ -258,7 +260,7 @@ export class BrainRouter {
|
|
|
258
260
|
|
|
259
261
|
// Use temporal search if temporal signals detected
|
|
260
262
|
if (hasTemporal) {
|
|
261
|
-
const { results } = await this.searchEngine.temporalSearch(query, { project, limit: 5 })
|
|
263
|
+
const { results } = await this.searchEngine.temporalSearch(query, { project: effectiveProject, limit: 5 })
|
|
262
264
|
if (results.length > 0) {
|
|
263
265
|
tiers.push({
|
|
264
266
|
label: 'Memories',
|
|
@@ -273,7 +275,7 @@ export class BrainRouter {
|
|
|
273
275
|
} else {
|
|
274
276
|
// Standard enhanced search
|
|
275
277
|
const searchResults = await this.searchEngine.enhancedSearch(query, {
|
|
276
|
-
project,
|
|
278
|
+
project: effectiveProject,
|
|
277
279
|
limit: 5,
|
|
278
280
|
minSimilarity: 0.3
|
|
279
281
|
})
|
|
@@ -291,7 +293,7 @@ export class BrainRouter {
|
|
|
291
293
|
}
|
|
292
294
|
|
|
293
295
|
// Also search patterns
|
|
294
|
-
const patternResults = await this.searchEngine.searchPatterns(query, { project, limit: 3 })
|
|
296
|
+
const patternResults = await this.searchEngine.searchPatterns(query, { project: effectiveProject, limit: 3 })
|
|
295
297
|
if (patternResults.length > 0) {
|
|
296
298
|
tiers.push({
|
|
297
299
|
label: 'Patterns',
|
|
@@ -304,7 +306,7 @@ export class BrainRouter {
|
|
|
304
306
|
})
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
return this.responseFilter.synthesize(tiers, message,
|
|
309
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject)
|
|
308
310
|
}
|
|
309
311
|
|
|
310
312
|
/**
|
|
@@ -811,6 +813,7 @@ export class BrainRouter {
|
|
|
811
813
|
return this.servicesNotReady()
|
|
812
814
|
}
|
|
813
815
|
|
|
816
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
814
817
|
const query = entities.topic || message
|
|
815
818
|
const tiers: TierResults[] = []
|
|
816
819
|
const hasTemporal = classification.secondary.includes('exploration') ||
|
|
@@ -822,26 +825,26 @@ export class BrainRouter {
|
|
|
822
825
|
// Main search — temporal or standard
|
|
823
826
|
let searchResults: NormalizedResult[]
|
|
824
827
|
if (hasTemporal) {
|
|
825
|
-
const { results } = await this.searchEngine.temporalSearch(query, { project, limit: 5 })
|
|
828
|
+
const { results } = await this.searchEngine.temporalSearch(query, { project: effectiveProject, limit: 5 })
|
|
826
829
|
searchResults = results
|
|
827
830
|
} else if (isComplex) {
|
|
828
831
|
// C7: Try multi-hop chain retrieval
|
|
829
|
-
const chainResult = await this.searchEngine.chainSearch(query, { project })
|
|
832
|
+
const chainResult = await this.searchEngine.chainSearch(query, { project: effectiveProject })
|
|
830
833
|
if (chainResult?.allResults?.length) {
|
|
831
834
|
searchResults = chainResult.allResults.map((r: any) => ({
|
|
832
835
|
id: r.id || '',
|
|
833
836
|
content: r.content || '',
|
|
834
837
|
score: r.similarity || 0,
|
|
835
838
|
source: 'decision' as const,
|
|
836
|
-
project: r.metadata?.project ||
|
|
839
|
+
project: r.metadata?.project || effectiveProject || '',
|
|
837
840
|
date: r.metadata?.created_at || '',
|
|
838
841
|
metadata: r.metadata || {}
|
|
839
842
|
}))
|
|
840
843
|
} else {
|
|
841
|
-
searchResults = await this.searchEngine.enhancedSearch(query, { project, limit: 5 })
|
|
844
|
+
searchResults = await this.searchEngine.enhancedSearch(query, { project: effectiveProject, limit: 5 })
|
|
842
845
|
}
|
|
843
846
|
} else {
|
|
844
|
-
searchResults = await this.searchEngine.enhancedSearch(query, { project, limit: 5 })
|
|
847
|
+
searchResults = await this.searchEngine.enhancedSearch(query, { project: effectiveProject, limit: 5 })
|
|
845
848
|
}
|
|
846
849
|
|
|
847
850
|
if (searchResults.length > 0) {
|
|
@@ -858,8 +861,8 @@ export class BrainRouter {
|
|
|
858
861
|
|
|
859
862
|
// Parallel: patterns + corrections + graph
|
|
860
863
|
const [patternResults, correctionResults, graphResults] = await Promise.all([
|
|
861
|
-
this.searchEngine.searchPatterns(query, { project, limit: 3 }),
|
|
862
|
-
this.searchEngine.searchCorrections(query, { project, limit: 3 }),
|
|
864
|
+
this.searchEngine.searchPatterns(query, { project: effectiveProject, limit: 3 }),
|
|
865
|
+
this.searchEngine.searchCorrections(query, { project: effectiveProject, limit: 3 }),
|
|
863
866
|
// C11: KnowledgeGraph enrichment for all questions
|
|
864
867
|
this.searchEngine.searchGraph(query, 5)
|
|
865
868
|
])
|
|
@@ -924,10 +927,33 @@ export class BrainRouter {
|
|
|
924
927
|
}
|
|
925
928
|
}
|
|
926
929
|
|
|
930
|
+
// Phase 23b Fix 5: Session recall — "what have we discussed" queries session tracker
|
|
931
|
+
if (this.isSessionRecallQuery(message)) {
|
|
932
|
+
try {
|
|
933
|
+
const tracker = getSessionTracker()
|
|
934
|
+
if (tracker) {
|
|
935
|
+
const stats = tracker.getStats()
|
|
936
|
+
if (stats.totalItems > 0) {
|
|
937
|
+
tiers.push({
|
|
938
|
+
label: 'Current Session',
|
|
939
|
+
results: [{
|
|
940
|
+
content: `Active sessions: ${stats.activeSessions}, items tracked: ${stats.totalItems}`,
|
|
941
|
+
score: 0.95,
|
|
942
|
+
source: 'Session Tracker',
|
|
943
|
+
metadata: {}
|
|
944
|
+
}]
|
|
945
|
+
})
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
} catch {
|
|
949
|
+
// Session tracker not available
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
927
953
|
// Register with episode
|
|
928
|
-
this.registerEpisodeMessage(message,
|
|
954
|
+
this.registerEpisodeMessage(message, effectiveProject, 'question')
|
|
929
955
|
|
|
930
|
-
return this.responseFilter.synthesize(tiers, message,
|
|
956
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject)
|
|
931
957
|
}
|
|
932
958
|
|
|
933
959
|
/**
|
|
@@ -945,6 +971,7 @@ export class BrainRouter {
|
|
|
945
971
|
return this.servicesNotReady()
|
|
946
972
|
}
|
|
947
973
|
|
|
974
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
948
975
|
const query = entities.topic || message
|
|
949
976
|
const lower = message.toLowerCase()
|
|
950
977
|
const tiers: TierResults[] = []
|
|
@@ -952,7 +979,7 @@ export class BrainRouter {
|
|
|
952
979
|
// C6: Timeline queries
|
|
953
980
|
if (lower.includes('timeline') || lower.includes('chronological') || lower.includes('history of')) {
|
|
954
981
|
const timeline = await this.searchEngine.buildTimeline({
|
|
955
|
-
project,
|
|
982
|
+
project: effectiveProject,
|
|
956
983
|
topic: query,
|
|
957
984
|
limit: 20
|
|
958
985
|
})
|
|
@@ -972,7 +999,7 @@ export class BrainRouter {
|
|
|
972
999
|
|
|
973
1000
|
// C6: Evolution queries
|
|
974
1001
|
if (lower.includes('evolution') || lower.includes('how has') || lower.includes('changed') || lower.includes('evolved')) {
|
|
975
|
-
const evolution = await this.searchEngine.analyzeEvolution(query, { project })
|
|
1002
|
+
const evolution = await this.searchEngine.analyzeEvolution(query, { project: effectiveProject })
|
|
976
1003
|
if (evolution?.timeline?.length) {
|
|
977
1004
|
const parts = []
|
|
978
1005
|
parts.push(`**Stability:** ${evolution.stability || 'unknown'}`)
|
|
@@ -995,7 +1022,7 @@ export class BrainRouter {
|
|
|
995
1022
|
|
|
996
1023
|
// C6: Trend queries
|
|
997
1024
|
if (lower.includes('trend') || lower.includes('emerging') || lower.includes('declining')) {
|
|
998
|
-
const trends = await this.searchEngine.detectTrends({ project })
|
|
1025
|
+
const trends = await this.searchEngine.detectTrends({ project: effectiveProject })
|
|
999
1026
|
if (trends?.topTrends?.length) {
|
|
1000
1027
|
tiers.push({
|
|
1001
1028
|
label: 'Trends',
|
|
@@ -1048,7 +1075,7 @@ export class BrainRouter {
|
|
|
1048
1075
|
|
|
1049
1076
|
// Also do a basic memory search as fallback
|
|
1050
1077
|
const searchResults = await this.searchEngine.enhancedSearch(query, {
|
|
1051
|
-
project,
|
|
1078
|
+
project: effectiveProject,
|
|
1052
1079
|
limit: 5,
|
|
1053
1080
|
minSimilarity: 0.2
|
|
1054
1081
|
})
|
|
@@ -1064,7 +1091,7 @@ export class BrainRouter {
|
|
|
1064
1091
|
})
|
|
1065
1092
|
}
|
|
1066
1093
|
|
|
1067
|
-
return this.responseFilter.synthesize(tiers, message,
|
|
1094
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject, 'analyzed')
|
|
1068
1095
|
}
|
|
1069
1096
|
|
|
1070
1097
|
private async handleComparison(
|
|
@@ -1076,12 +1103,13 @@ export class BrainRouter {
|
|
|
1076
1103
|
return this.servicesNotReady()
|
|
1077
1104
|
}
|
|
1078
1105
|
|
|
1106
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
1079
1107
|
const query = entities.topic || message
|
|
1080
1108
|
const tiers: TierResults[] = []
|
|
1081
1109
|
|
|
1082
1110
|
// Search for related decisions
|
|
1083
1111
|
const searchResults = await this.searchEngine.enhancedSearch(query, {
|
|
1084
|
-
project,
|
|
1112
|
+
project: effectiveProject,
|
|
1085
1113
|
limit: 5,
|
|
1086
1114
|
minSimilarity: 0.2
|
|
1087
1115
|
})
|
|
@@ -1111,7 +1139,7 @@ export class BrainRouter {
|
|
|
1111
1139
|
})
|
|
1112
1140
|
}
|
|
1113
1141
|
|
|
1114
|
-
return this.responseFilter.synthesize(tiers, message,
|
|
1142
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject, 'analyzed')
|
|
1115
1143
|
}
|
|
1116
1144
|
|
|
1117
1145
|
// ===== Helpers =====
|
|
@@ -1199,6 +1227,30 @@ export class BrainRouter {
|
|
|
1199
1227
|
}
|
|
1200
1228
|
}
|
|
1201
1229
|
|
|
1230
|
+
/**
|
|
1231
|
+
* Phase 23b: Detect session recall queries ("what have we discussed", "this session", etc.)
|
|
1232
|
+
*/
|
|
1233
|
+
private isSessionRecallQuery(message: string): boolean {
|
|
1234
|
+
const lower = message.toLowerCase()
|
|
1235
|
+
const SESSION_RECALL_PHRASES = [
|
|
1236
|
+
'what have we discussed',
|
|
1237
|
+
'what did we discuss',
|
|
1238
|
+
'what have we talked about',
|
|
1239
|
+
'what did we talk about',
|
|
1240
|
+
'this session',
|
|
1241
|
+
'current session',
|
|
1242
|
+
'session so far',
|
|
1243
|
+
'what have we done',
|
|
1244
|
+
'what did we do',
|
|
1245
|
+
'session summary',
|
|
1246
|
+
'summarize this session',
|
|
1247
|
+
'recap this session',
|
|
1248
|
+
'what happened this session',
|
|
1249
|
+
'what have we covered'
|
|
1250
|
+
]
|
|
1251
|
+
return SESSION_RECALL_PHRASES.some(p => lower.includes(p))
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1202
1254
|
/**
|
|
1203
1255
|
* C7: Detect complex multi-part questions
|
|
1204
1256
|
*/
|
|
@@ -39,6 +39,17 @@ export class SearchEngine {
|
|
|
39
39
|
this.logger = logger.child({ component: 'search-engine' })
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Sort results so same-project entries come first (safety net for cross-project noise).
|
|
44
|
+
*/
|
|
45
|
+
private prioritizeProject(results: NormalizedResult[], project?: string): NormalizedResult[] {
|
|
46
|
+
if (!project || results.length === 0) return results
|
|
47
|
+
const sameProject = results.filter(r => r.project === project)
|
|
48
|
+
const otherProject = results.filter(r => r.project !== project)
|
|
49
|
+
if (sameProject.length > 0) return [...sameProject, ...otherProject]
|
|
50
|
+
return results
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
/**
|
|
43
54
|
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
44
55
|
* falls back to plain searchRaw().
|
|
@@ -88,7 +99,7 @@ export class SearchEngine {
|
|
|
88
99
|
)
|
|
89
100
|
|
|
90
101
|
if (hybridResults.length > 0) {
|
|
91
|
-
const normalized = hybridResults.map((r: any) => ({
|
|
102
|
+
const normalized = this.prioritizeProject(hybridResults.map((r: any) => ({
|
|
92
103
|
id: r.id || '',
|
|
93
104
|
content: r.content || r.document || '',
|
|
94
105
|
score: r.score || r.similarity || 0,
|
|
@@ -96,7 +107,7 @@ export class SearchEngine {
|
|
|
96
107
|
project: r.metadata?.project || options?.project || '',
|
|
97
108
|
date: r.metadata?.created_at || '',
|
|
98
109
|
metadata: r.metadata || {}
|
|
99
|
-
}))
|
|
110
|
+
})), options?.project)
|
|
100
111
|
|
|
101
112
|
// Cache results
|
|
102
113
|
if (cache) {
|
|
@@ -135,7 +146,7 @@ export class SearchEngine {
|
|
|
135
146
|
SEARCH_TIMEOUT,
|
|
136
147
|
[]
|
|
137
148
|
)
|
|
138
|
-
const normalized = normalizeSearchResults(rawResults)
|
|
149
|
+
const normalized = this.prioritizeProject(normalizeSearchResults(rawResults), options?.project)
|
|
139
150
|
|
|
140
151
|
// Cache results
|
|
141
152
|
if (cache && cacheKey) {
|
package/src/server/http-api.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Hono } from 'hono'
|
|
|
7
7
|
import type { Logger } from 'pino'
|
|
8
8
|
import type { Config } from '@/config'
|
|
9
9
|
import { getMemoryService, getVaultService, isServicesInitialized } from '@/server/services'
|
|
10
|
+
import { ResourceProvider } from '@/server/providers/resources'
|
|
10
11
|
import type { MemoryManager } from '@/memory'
|
|
11
12
|
import type { CapturedKnowledge, HookStats } from '@/hooks/types'
|
|
12
13
|
import { SmartDeduplicator } from '@/hooks/deduplicator'
|
|
@@ -81,6 +82,9 @@ export class HttpApiServer {
|
|
|
81
82
|
this.app.post('/api/hooks/ingest', (c) => this.handleHookIngest(c))
|
|
82
83
|
this.app.post('/api/hooks/session-end', (c) => this.handleHookSessionEnd(c))
|
|
83
84
|
this.app.get('/api/hooks/status', () => this.handleHookStatus())
|
|
85
|
+
|
|
86
|
+
// Phase 23b: Expose brain://context/auto via HTTP for testability
|
|
87
|
+
this.app.get('/api/context/auto', () => this.handleContextAuto())
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
private async handleListProjects(): Promise<Response> {
|
|
@@ -458,6 +462,23 @@ export class HttpApiServer {
|
|
|
458
462
|
}
|
|
459
463
|
}
|
|
460
464
|
|
|
465
|
+
// ─── Phase 23b: Context Auto Endpoint ────────────────────
|
|
466
|
+
|
|
467
|
+
private async handleContextAuto(): Promise<Response> {
|
|
468
|
+
try {
|
|
469
|
+
const resourceProvider = new ResourceProvider(this.logger)
|
|
470
|
+
const result = await resourceProvider.readResource('brain://context/auto')
|
|
471
|
+
const text = result?.contents?.[0]?.text || ''
|
|
472
|
+
return Response.json({ success: true, data: { content: text } })
|
|
473
|
+
} catch (error) {
|
|
474
|
+
this.logger.error({ error }, 'Failed to get auto context')
|
|
475
|
+
return Response.json(
|
|
476
|
+
{ success: false, error: 'Failed to get auto context' },
|
|
477
|
+
{ status: 500 }
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
461
482
|
// ─── Phase 17: Hook Endpoints ────────────────────────────
|
|
462
483
|
|
|
463
484
|
private async handleHookIngest(c: any): Promise<Response> {
|
|
@@ -564,7 +585,7 @@ export class HttpApiServer {
|
|
|
564
585
|
...this.hookStats,
|
|
565
586
|
activeSessions: sessionStats?.activeSessions ?? 0,
|
|
566
587
|
sessionItems: sessionStats?.totalItems ?? 0,
|
|
567
|
-
hooksEnabled: this.config.hooks?.enabled ??
|
|
588
|
+
hooksEnabled: this.config.hooks?.enabled ?? true,
|
|
568
589
|
},
|
|
569
590
|
})
|
|
570
591
|
}
|
|
@@ -609,6 +630,16 @@ export class HttpApiServer {
|
|
|
609
630
|
break
|
|
610
631
|
|
|
611
632
|
case 'progress': {
|
|
633
|
+
// Store in ChromaDB so it's searchable via brain tool
|
|
634
|
+
await memoryService.rememberDecision(
|
|
635
|
+
project,
|
|
636
|
+
`Captured via hook: ${knowledge.metadata.action || knowledge.metadata.source || 'progress'}`,
|
|
637
|
+
knowledge.content,
|
|
638
|
+
`Auto-captured from ${knowledge.source}`,
|
|
639
|
+
{ tags: knowledge.technologies }
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
// Also update vault progress if project exists
|
|
612
643
|
const vaultService = getVaultService()
|
|
613
644
|
if (vaultService) {
|
|
614
645
|
try {
|