claude-brain 0.30.2 → 0.30.3
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 +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,310 +1,310 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Detector
|
|
3
|
-
* Phase 12: Advanced Memory & Intelligent Automation
|
|
4
|
-
*
|
|
5
|
-
* Automatically detects project from working directory
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises'
|
|
9
|
-
import path from 'path'
|
|
10
|
-
import type { Logger } from 'pino'
|
|
11
|
-
import type { VaultManager } from '@/vault'
|
|
12
|
-
|
|
13
|
-
export interface DetectedProject {
|
|
14
|
-
name: string
|
|
15
|
-
path: string
|
|
16
|
-
confidence: number
|
|
17
|
-
indicators: string[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class ProjectDetector {
|
|
21
|
-
private logger: Logger
|
|
22
|
-
private vault: VaultManager
|
|
23
|
-
|
|
24
|
-
constructor(logger: Logger, vault: VaultManager) {
|
|
25
|
-
this.logger = logger.child({ component: 'project-detector' })
|
|
26
|
-
this.vault = vault
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Detect project from working directory
|
|
31
|
-
*/
|
|
32
|
-
async detectProject(workingDir: string): Promise<DetectedProject | null> {
|
|
33
|
-
this.logger.debug({ workingDir }, 'Detecting project')
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
// Get all projects from vault
|
|
37
|
-
const vaultProjects = await this.vault.listProjects()
|
|
38
|
-
|
|
39
|
-
// Check for exact match by directory name
|
|
40
|
-
const dirName = path.basename(workingDir)
|
|
41
|
-
if (vaultProjects.includes(dirName)) {
|
|
42
|
-
this.logger.info({ project: dirName }, 'Exact directory match found')
|
|
43
|
-
return {
|
|
44
|
-
name: dirName,
|
|
45
|
-
path: workingDir,
|
|
46
|
-
confidence: 1.0,
|
|
47
|
-
indicators: ['exact-directory-match']
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Check for package.json or git config
|
|
52
|
-
const projectName = await this.detectFromFiles(workingDir)
|
|
53
|
-
if (projectName && vaultProjects.includes(projectName)) {
|
|
54
|
-
this.logger.info({ project: projectName }, 'Package/git match found')
|
|
55
|
-
return {
|
|
56
|
-
name: projectName,
|
|
57
|
-
path: workingDir,
|
|
58
|
-
confidence: 0.9,
|
|
59
|
-
indicators: ['package-json-name']
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Check for fuzzy match
|
|
64
|
-
const fuzzyMatch = this.fuzzyMatchProject(dirName, vaultProjects)
|
|
65
|
-
if (fuzzyMatch) {
|
|
66
|
-
this.logger.info({ project: fuzzyMatch.name, confidence: fuzzyMatch.confidence }, 'Fuzzy match found')
|
|
67
|
-
return {
|
|
68
|
-
name: fuzzyMatch.name,
|
|
69
|
-
path: workingDir,
|
|
70
|
-
confidence: fuzzyMatch.confidence,
|
|
71
|
-
indicators: ['fuzzy-match']
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check parent directories
|
|
76
|
-
const parentMatch = await this.checkParentDirectories(workingDir, vaultProjects)
|
|
77
|
-
if (parentMatch) {
|
|
78
|
-
return parentMatch
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
this.logger.debug({ workingDir }, 'No project detected')
|
|
82
|
-
return null
|
|
83
|
-
} catch (error) {
|
|
84
|
-
this.logger.warn({ error, workingDir }, 'Error detecting project')
|
|
85
|
-
return null
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Detect project name from files
|
|
91
|
-
*/
|
|
92
|
-
private async detectFromFiles(workingDir: string): Promise<string | null> {
|
|
93
|
-
// Check package.json
|
|
94
|
-
try {
|
|
95
|
-
const packagePath = path.join(workingDir, 'package.json')
|
|
96
|
-
const packageContent = await fs.readFile(packagePath, 'utf-8')
|
|
97
|
-
const packageJson = JSON.parse(packageContent)
|
|
98
|
-
|
|
99
|
-
if (packageJson.name) {
|
|
100
|
-
// Clean package name (remove scope)
|
|
101
|
-
const name = packageJson.name.replace(/^@[^/]+\//, '')
|
|
102
|
-
return name
|
|
103
|
-
}
|
|
104
|
-
} catch {
|
|
105
|
-
// No package.json or invalid JSON
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Check pyproject.toml for Python projects
|
|
109
|
-
try {
|
|
110
|
-
const pyprojectPath = path.join(workingDir, 'pyproject.toml')
|
|
111
|
-
const pyprojectContent = await fs.readFile(pyprojectPath, 'utf-8')
|
|
112
|
-
|
|
113
|
-
const nameMatch = pyprojectContent.match(/name\s*=\s*"([^"]+)"/)
|
|
114
|
-
if (nameMatch) {
|
|
115
|
-
return nameMatch[1] ?? null
|
|
116
|
-
}
|
|
117
|
-
} catch {
|
|
118
|
-
// No pyproject.toml
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check Cargo.toml for Rust projects
|
|
122
|
-
try {
|
|
123
|
-
const cargoPath = path.join(workingDir, 'Cargo.toml')
|
|
124
|
-
const cargoContent = await fs.readFile(cargoPath, 'utf-8')
|
|
125
|
-
|
|
126
|
-
const nameMatch = cargoContent.match(/name\s*=\s*"([^"]+)"/)
|
|
127
|
-
if (nameMatch) {
|
|
128
|
-
return nameMatch[1] ?? null
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
// No Cargo.toml
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Check .git/config for remote URL
|
|
135
|
-
try {
|
|
136
|
-
const gitConfigPath = path.join(workingDir, '.git', 'config')
|
|
137
|
-
const gitConfig = await fs.readFile(gitConfigPath, 'utf-8')
|
|
138
|
-
|
|
139
|
-
// Extract repo name from URL
|
|
140
|
-
const urlMatch = gitConfig.match(/url\s*=\s*.+\/([^/\n]+?)(?:\.git)?[\s\n]/)
|
|
141
|
-
if (urlMatch) {
|
|
142
|
-
return urlMatch[1]?.trim() ?? null
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
// No .git/config
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return null
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Fuzzy match project name
|
|
153
|
-
*/
|
|
154
|
-
private fuzzyMatchProject(
|
|
155
|
-
dirName: string,
|
|
156
|
-
projects: string[]
|
|
157
|
-
): { name: string; confidence: number } | null {
|
|
158
|
-
const dirLower = dirName.toLowerCase()
|
|
159
|
-
|
|
160
|
-
let bestMatch: { name: string; confidence: number } | null = null
|
|
161
|
-
|
|
162
|
-
for (const project of projects) {
|
|
163
|
-
const projectLower = project.toLowerCase()
|
|
164
|
-
|
|
165
|
-
// Check if directory contains project name
|
|
166
|
-
if (dirLower.includes(projectLower)) {
|
|
167
|
-
const confidence = projectLower.length / dirLower.length
|
|
168
|
-
if (!bestMatch || confidence > bestMatch.confidence) {
|
|
169
|
-
bestMatch = { name: project, confidence: Math.min(0.8, confidence) }
|
|
170
|
-
}
|
|
171
|
-
continue
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check if project name contains directory
|
|
175
|
-
if (projectLower.includes(dirLower)) {
|
|
176
|
-
const confidence = dirLower.length / projectLower.length
|
|
177
|
-
if (!bestMatch || confidence > bestMatch.confidence) {
|
|
178
|
-
bestMatch = { name: project, confidence: Math.min(0.7, confidence) }
|
|
179
|
-
}
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Check Levenshtein distance
|
|
184
|
-
const distance = this.levenshteinDistance(dirLower, projectLower)
|
|
185
|
-
const maxLen = Math.max(dirLower.length, projectLower.length)
|
|
186
|
-
|
|
187
|
-
if (distance <= 3 && distance < maxLen * 0.3) {
|
|
188
|
-
const confidence = 1 - (distance / maxLen)
|
|
189
|
-
if (!bestMatch || confidence > bestMatch.confidence) {
|
|
190
|
-
bestMatch = { name: project, confidence: Math.min(0.6, confidence) }
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return bestMatch
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Check parent directories for project match
|
|
200
|
-
*/
|
|
201
|
-
private async checkParentDirectories(
|
|
202
|
-
workingDir: string,
|
|
203
|
-
projects: string[]
|
|
204
|
-
): Promise<DetectedProject | null> {
|
|
205
|
-
let currentDir = workingDir
|
|
206
|
-
let depth = 0
|
|
207
|
-
const maxDepth = 3
|
|
208
|
-
|
|
209
|
-
while (depth < maxDepth) {
|
|
210
|
-
const parentDir = path.dirname(currentDir)
|
|
211
|
-
|
|
212
|
-
if (parentDir === currentDir) {
|
|
213
|
-
// Reached root
|
|
214
|
-
break
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const parentName = path.basename(parentDir)
|
|
218
|
-
|
|
219
|
-
if (projects.includes(parentName)) {
|
|
220
|
-
return {
|
|
221
|
-
name: parentName,
|
|
222
|
-
path: parentDir,
|
|
223
|
-
confidence: 0.7 - (depth * 0.1),
|
|
224
|
-
indicators: ['parent-directory-match', `depth-${depth + 1}`]
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
currentDir = parentDir
|
|
229
|
-
depth++
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return null
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Calculate Levenshtein distance between two strings
|
|
237
|
-
*/
|
|
238
|
-
private levenshteinDistance(a: string, b: string): number {
|
|
239
|
-
const matrix: number[][] = []
|
|
240
|
-
|
|
241
|
-
for (let i = 0; i <= b.length; i++) {
|
|
242
|
-
matrix[i] = [i]
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
for (let j = 0; j <= a.length; j++) {
|
|
246
|
-
matrix[0]![j] = j
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
for (let i = 1; i <= b.length; i++) {
|
|
250
|
-
for (let j = 1; j <= a.length; j++) {
|
|
251
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
252
|
-
matrix[i]![j] = matrix[i - 1]![j - 1]!
|
|
253
|
-
} else {
|
|
254
|
-
matrix[i]![j] = Math.min(
|
|
255
|
-
matrix[i - 1]![j - 1]! + 1,
|
|
256
|
-
matrix[i]![j - 1]! + 1,
|
|
257
|
-
matrix[i - 1]![j]! + 1
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return matrix[b.length]![a.length]!
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Check if a project has any existing memories.
|
|
268
|
-
* Returns true if no memories found (i.e., it's a new project).
|
|
269
|
-
*/
|
|
270
|
-
async isNewProject(projectName: string): Promise<boolean> {
|
|
271
|
-
try {
|
|
272
|
-
const projects = await this.vault.listProjects()
|
|
273
|
-
return !projects.includes(projectName)
|
|
274
|
-
} catch {
|
|
275
|
-
return true
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Get project suggestions based on working directory
|
|
281
|
-
*/
|
|
282
|
-
async getSuggestions(workingDir: string, limit: number = 3): Promise<DetectedProject[]> {
|
|
283
|
-
const suggestions: DetectedProject[] = []
|
|
284
|
-
const vaultProjects = await this.vault.listProjects()
|
|
285
|
-
|
|
286
|
-
const dirName = path.basename(workingDir).toLowerCase()
|
|
287
|
-
|
|
288
|
-
for (const project of vaultProjects) {
|
|
289
|
-
const projectLower = project.toLowerCase()
|
|
290
|
-
|
|
291
|
-
// Calculate similarity
|
|
292
|
-
const distance = this.levenshteinDistance(dirName, projectLower)
|
|
293
|
-
const maxLen = Math.max(dirName.length, projectLower.length)
|
|
294
|
-
const similarity = 1 - (distance / maxLen)
|
|
295
|
-
|
|
296
|
-
if (similarity > 0.3) {
|
|
297
|
-
suggestions.push({
|
|
298
|
-
name: project,
|
|
299
|
-
path: workingDir,
|
|
300
|
-
confidence: similarity,
|
|
301
|
-
indicators: ['suggestion']
|
|
302
|
-
})
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return suggestions
|
|
307
|
-
.sort((a, b) => b.confidence - a.confidence)
|
|
308
|
-
.slice(0, limit)
|
|
309
|
-
}
|
|
310
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Project Detector
|
|
3
|
+
* Phase 12: Advanced Memory & Intelligent Automation
|
|
4
|
+
*
|
|
5
|
+
* Automatically detects project from working directory
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs/promises'
|
|
9
|
+
import path from 'path'
|
|
10
|
+
import type { Logger } from 'pino'
|
|
11
|
+
import type { VaultManager } from '@/vault'
|
|
12
|
+
|
|
13
|
+
export interface DetectedProject {
|
|
14
|
+
name: string
|
|
15
|
+
path: string
|
|
16
|
+
confidence: number
|
|
17
|
+
indicators: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ProjectDetector {
|
|
21
|
+
private logger: Logger
|
|
22
|
+
private vault: VaultManager
|
|
23
|
+
|
|
24
|
+
constructor(logger: Logger, vault: VaultManager) {
|
|
25
|
+
this.logger = logger.child({ component: 'project-detector' })
|
|
26
|
+
this.vault = vault
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect project from working directory
|
|
31
|
+
*/
|
|
32
|
+
async detectProject(workingDir: string): Promise<DetectedProject | null> {
|
|
33
|
+
this.logger.debug({ workingDir }, 'Detecting project')
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Get all projects from vault
|
|
37
|
+
const vaultProjects = await this.vault.listProjects()
|
|
38
|
+
|
|
39
|
+
// Check for exact match by directory name
|
|
40
|
+
const dirName = path.basename(workingDir)
|
|
41
|
+
if (vaultProjects.includes(dirName)) {
|
|
42
|
+
this.logger.info({ project: dirName }, 'Exact directory match found')
|
|
43
|
+
return {
|
|
44
|
+
name: dirName,
|
|
45
|
+
path: workingDir,
|
|
46
|
+
confidence: 1.0,
|
|
47
|
+
indicators: ['exact-directory-match']
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for package.json or git config
|
|
52
|
+
const projectName = await this.detectFromFiles(workingDir)
|
|
53
|
+
if (projectName && vaultProjects.includes(projectName)) {
|
|
54
|
+
this.logger.info({ project: projectName }, 'Package/git match found')
|
|
55
|
+
return {
|
|
56
|
+
name: projectName,
|
|
57
|
+
path: workingDir,
|
|
58
|
+
confidence: 0.9,
|
|
59
|
+
indicators: ['package-json-name']
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for fuzzy match
|
|
64
|
+
const fuzzyMatch = this.fuzzyMatchProject(dirName, vaultProjects)
|
|
65
|
+
if (fuzzyMatch) {
|
|
66
|
+
this.logger.info({ project: fuzzyMatch.name, confidence: fuzzyMatch.confidence }, 'Fuzzy match found')
|
|
67
|
+
return {
|
|
68
|
+
name: fuzzyMatch.name,
|
|
69
|
+
path: workingDir,
|
|
70
|
+
confidence: fuzzyMatch.confidence,
|
|
71
|
+
indicators: ['fuzzy-match']
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check parent directories
|
|
76
|
+
const parentMatch = await this.checkParentDirectories(workingDir, vaultProjects)
|
|
77
|
+
if (parentMatch) {
|
|
78
|
+
return parentMatch
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.logger.debug({ workingDir }, 'No project detected')
|
|
82
|
+
return null
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.logger.warn({ error, workingDir }, 'Error detecting project')
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Detect project name from files
|
|
91
|
+
*/
|
|
92
|
+
private async detectFromFiles(workingDir: string): Promise<string | null> {
|
|
93
|
+
// Check package.json
|
|
94
|
+
try {
|
|
95
|
+
const packagePath = path.join(workingDir, 'package.json')
|
|
96
|
+
const packageContent = await fs.readFile(packagePath, 'utf-8')
|
|
97
|
+
const packageJson = JSON.parse(packageContent)
|
|
98
|
+
|
|
99
|
+
if (packageJson.name) {
|
|
100
|
+
// Clean package name (remove scope)
|
|
101
|
+
const name = packageJson.name.replace(/^@[^/]+\//, '')
|
|
102
|
+
return name
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// No package.json or invalid JSON
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check pyproject.toml for Python projects
|
|
109
|
+
try {
|
|
110
|
+
const pyprojectPath = path.join(workingDir, 'pyproject.toml')
|
|
111
|
+
const pyprojectContent = await fs.readFile(pyprojectPath, 'utf-8')
|
|
112
|
+
|
|
113
|
+
const nameMatch = pyprojectContent.match(/name\s*=\s*"([^"]+)"/)
|
|
114
|
+
if (nameMatch) {
|
|
115
|
+
return nameMatch[1] ?? null
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// No pyproject.toml
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check Cargo.toml for Rust projects
|
|
122
|
+
try {
|
|
123
|
+
const cargoPath = path.join(workingDir, 'Cargo.toml')
|
|
124
|
+
const cargoContent = await fs.readFile(cargoPath, 'utf-8')
|
|
125
|
+
|
|
126
|
+
const nameMatch = cargoContent.match(/name\s*=\s*"([^"]+)"/)
|
|
127
|
+
if (nameMatch) {
|
|
128
|
+
return nameMatch[1] ?? null
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// No Cargo.toml
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check .git/config for remote URL
|
|
135
|
+
try {
|
|
136
|
+
const gitConfigPath = path.join(workingDir, '.git', 'config')
|
|
137
|
+
const gitConfig = await fs.readFile(gitConfigPath, 'utf-8')
|
|
138
|
+
|
|
139
|
+
// Extract repo name from URL
|
|
140
|
+
const urlMatch = gitConfig.match(/url\s*=\s*.+\/([^/\n]+?)(?:\.git)?[\s\n]/)
|
|
141
|
+
if (urlMatch) {
|
|
142
|
+
return urlMatch[1]?.trim() ?? null
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// No .git/config
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fuzzy match project name
|
|
153
|
+
*/
|
|
154
|
+
private fuzzyMatchProject(
|
|
155
|
+
dirName: string,
|
|
156
|
+
projects: string[]
|
|
157
|
+
): { name: string; confidence: number } | null {
|
|
158
|
+
const dirLower = dirName.toLowerCase()
|
|
159
|
+
|
|
160
|
+
let bestMatch: { name: string; confidence: number } | null = null
|
|
161
|
+
|
|
162
|
+
for (const project of projects) {
|
|
163
|
+
const projectLower = project.toLowerCase()
|
|
164
|
+
|
|
165
|
+
// Check if directory contains project name
|
|
166
|
+
if (dirLower.includes(projectLower)) {
|
|
167
|
+
const confidence = projectLower.length / dirLower.length
|
|
168
|
+
if (!bestMatch || confidence > bestMatch.confidence) {
|
|
169
|
+
bestMatch = { name: project, confidence: Math.min(0.8, confidence) }
|
|
170
|
+
}
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if project name contains directory
|
|
175
|
+
if (projectLower.includes(dirLower)) {
|
|
176
|
+
const confidence = dirLower.length / projectLower.length
|
|
177
|
+
if (!bestMatch || confidence > bestMatch.confidence) {
|
|
178
|
+
bestMatch = { name: project, confidence: Math.min(0.7, confidence) }
|
|
179
|
+
}
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check Levenshtein distance
|
|
184
|
+
const distance = this.levenshteinDistance(dirLower, projectLower)
|
|
185
|
+
const maxLen = Math.max(dirLower.length, projectLower.length)
|
|
186
|
+
|
|
187
|
+
if (distance <= 3 && distance < maxLen * 0.3) {
|
|
188
|
+
const confidence = 1 - (distance / maxLen)
|
|
189
|
+
if (!bestMatch || confidence > bestMatch.confidence) {
|
|
190
|
+
bestMatch = { name: project, confidence: Math.min(0.6, confidence) }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return bestMatch
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check parent directories for project match
|
|
200
|
+
*/
|
|
201
|
+
private async checkParentDirectories(
|
|
202
|
+
workingDir: string,
|
|
203
|
+
projects: string[]
|
|
204
|
+
): Promise<DetectedProject | null> {
|
|
205
|
+
let currentDir = workingDir
|
|
206
|
+
let depth = 0
|
|
207
|
+
const maxDepth = 3
|
|
208
|
+
|
|
209
|
+
while (depth < maxDepth) {
|
|
210
|
+
const parentDir = path.dirname(currentDir)
|
|
211
|
+
|
|
212
|
+
if (parentDir === currentDir) {
|
|
213
|
+
// Reached root
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const parentName = path.basename(parentDir)
|
|
218
|
+
|
|
219
|
+
if (projects.includes(parentName)) {
|
|
220
|
+
return {
|
|
221
|
+
name: parentName,
|
|
222
|
+
path: parentDir,
|
|
223
|
+
confidence: 0.7 - (depth * 0.1),
|
|
224
|
+
indicators: ['parent-directory-match', `depth-${depth + 1}`]
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
currentDir = parentDir
|
|
229
|
+
depth++
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Calculate Levenshtein distance between two strings
|
|
237
|
+
*/
|
|
238
|
+
private levenshteinDistance(a: string, b: string): number {
|
|
239
|
+
const matrix: number[][] = []
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i <= b.length; i++) {
|
|
242
|
+
matrix[i] = [i]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (let j = 0; j <= a.length; j++) {
|
|
246
|
+
matrix[0]![j] = j
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (let i = 1; i <= b.length; i++) {
|
|
250
|
+
for (let j = 1; j <= a.length; j++) {
|
|
251
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
252
|
+
matrix[i]![j] = matrix[i - 1]![j - 1]!
|
|
253
|
+
} else {
|
|
254
|
+
matrix[i]![j] = Math.min(
|
|
255
|
+
matrix[i - 1]![j - 1]! + 1,
|
|
256
|
+
matrix[i]![j - 1]! + 1,
|
|
257
|
+
matrix[i - 1]![j]! + 1
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return matrix[b.length]![a.length]!
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if a project has any existing memories.
|
|
268
|
+
* Returns true if no memories found (i.e., it's a new project).
|
|
269
|
+
*/
|
|
270
|
+
async isNewProject(projectName: string): Promise<boolean> {
|
|
271
|
+
try {
|
|
272
|
+
const projects = await this.vault.listProjects()
|
|
273
|
+
return !projects.includes(projectName)
|
|
274
|
+
} catch {
|
|
275
|
+
return true
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get project suggestions based on working directory
|
|
281
|
+
*/
|
|
282
|
+
async getSuggestions(workingDir: string, limit: number = 3): Promise<DetectedProject[]> {
|
|
283
|
+
const suggestions: DetectedProject[] = []
|
|
284
|
+
const vaultProjects = await this.vault.listProjects()
|
|
285
|
+
|
|
286
|
+
const dirName = path.basename(workingDir).toLowerCase()
|
|
287
|
+
|
|
288
|
+
for (const project of vaultProjects) {
|
|
289
|
+
const projectLower = project.toLowerCase()
|
|
290
|
+
|
|
291
|
+
// Calculate similarity
|
|
292
|
+
const distance = this.levenshteinDistance(dirName, projectLower)
|
|
293
|
+
const maxLen = Math.max(dirName.length, projectLower.length)
|
|
294
|
+
const similarity = 1 - (distance / maxLen)
|
|
295
|
+
|
|
296
|
+
if (similarity > 0.3) {
|
|
297
|
+
suggestions.push({
|
|
298
|
+
name: project,
|
|
299
|
+
path: workingDir,
|
|
300
|
+
confidence: similarity,
|
|
301
|
+
indicators: ['suggestion']
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return suggestions
|
|
307
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
308
|
+
.slice(0, limit)
|
|
309
|
+
}
|
|
310
|
+
}
|