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
package/src/memory/patterns.ts
CHANGED
|
@@ -1,438 +1,438 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Recognition System
|
|
3
|
-
* Phase 12: Advanced Memory & Intelligent Automation
|
|
4
|
-
*
|
|
5
|
-
* Detects recurring patterns across projects and decisions
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Logger } from 'pino'
|
|
9
|
-
import type { MemoryManager } from './index'
|
|
10
|
-
import type { Decision as _Decision, MemorySearchResult as _MemorySearchResult } from './types'
|
|
11
|
-
|
|
12
|
-
export interface Pattern {
|
|
13
|
-
id: string
|
|
14
|
-
type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
15
|
-
description: string
|
|
16
|
-
occurrences: number
|
|
17
|
-
projects: string[]
|
|
18
|
-
examples: string[]
|
|
19
|
-
confidence: number
|
|
20
|
-
firstSeen: Date
|
|
21
|
-
lastSeen: Date
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface DecisionWithProject {
|
|
25
|
-
decision: string
|
|
26
|
-
reasoning?: string
|
|
27
|
-
project: string
|
|
28
|
-
timestamp: Date
|
|
29
|
-
tags?: string[]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export class PatternRecognizer {
|
|
33
|
-
private logger: Logger
|
|
34
|
-
private memory: MemoryManager
|
|
35
|
-
private patterns: Map<string, Pattern> = new Map()
|
|
36
|
-
|
|
37
|
-
/** SLM Upgrade: Optional inference router for model-based pattern classification */
|
|
38
|
-
private inferenceRouter:
|
|
39
|
-
|
|
40
|
-
constructor(logger: Logger, memory: MemoryManager) {
|
|
41
|
-
this.logger = logger.child({ component: 'pattern-recognizer' })
|
|
42
|
-
this.memory = memory
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** SLM Upgrade: Set optional inference router */
|
|
46
|
-
setInferenceRouter(router:
|
|
47
|
-
this.inferenceRouter = router
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Analyze all decisions to find patterns
|
|
52
|
-
*/
|
|
53
|
-
async analyzePatterns(): Promise<Pattern[]> {
|
|
54
|
-
this.logger.info('Analyzing patterns across all projects')
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
// Get all decisions from all projects
|
|
58
|
-
const allDecisions = await this.getAllDecisions()
|
|
59
|
-
|
|
60
|
-
if (allDecisions.length < 3) {
|
|
61
|
-
this.logger.info('Not enough decisions for pattern analysis')
|
|
62
|
-
return []
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Group by similarity
|
|
66
|
-
const clusters = await this.clusterDecisions(allDecisions)
|
|
67
|
-
|
|
68
|
-
// Extract patterns from clusters
|
|
69
|
-
const patterns = await this.extractPatterns(clusters)
|
|
70
|
-
|
|
71
|
-
// Store patterns
|
|
72
|
-
for (const pattern of patterns) {
|
|
73
|
-
this.patterns.set(pattern.id, pattern)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.logger.info(
|
|
77
|
-
{ patternCount: patterns.length },
|
|
78
|
-
'Pattern analysis complete'
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
return patterns
|
|
82
|
-
} catch (error) {
|
|
83
|
-
this.logger.error({ error }, 'Failed to analyze patterns')
|
|
84
|
-
return []
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get all decisions from all projects
|
|
90
|
-
*/
|
|
91
|
-
private async getAllDecisions(): Promise<DecisionWithProject[]> {
|
|
92
|
-
const projects = this.memory.store.getProjects()
|
|
93
|
-
const allDecisions: DecisionWithProject[] = []
|
|
94
|
-
|
|
95
|
-
for (const project of projects) {
|
|
96
|
-
const decisions = this.memory.store.getProjectDecisions(project)
|
|
97
|
-
|
|
98
|
-
for (const decision of decisions) {
|
|
99
|
-
// Get associated memory for timestamp
|
|
100
|
-
const memory = this.memory.store.getMemory(decision.memoryId)
|
|
101
|
-
|
|
102
|
-
allDecisions.push({
|
|
103
|
-
decision: decision.decision,
|
|
104
|
-
reasoning: decision.reasoning,
|
|
105
|
-
project,
|
|
106
|
-
timestamp: memory?.createdAt || new Date(),
|
|
107
|
-
tags: decision.tags
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return allDecisions
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Cluster similar decisions together
|
|
117
|
-
*/
|
|
118
|
-
private async clusterDecisions(decisions: DecisionWithProject[]): Promise<DecisionWithProject[][]> {
|
|
119
|
-
const clusters: DecisionWithProject[][] = []
|
|
120
|
-
const processed = new Set<number>()
|
|
121
|
-
|
|
122
|
-
for (let i = 0; i < decisions.length; i++) {
|
|
123
|
-
if (processed.has(i)) continue
|
|
124
|
-
|
|
125
|
-
const decision = decisions[i]
|
|
126
|
-
if (!decision) continue
|
|
127
|
-
const cluster: DecisionWithProject[] = [decision]
|
|
128
|
-
processed.add(i)
|
|
129
|
-
|
|
130
|
-
// Find similar decisions using semantic search
|
|
131
|
-
try {
|
|
132
|
-
const similar = await this.memory.searchRaw(
|
|
133
|
-
decision.decision,
|
|
134
|
-
{
|
|
135
|
-
limit: 10,
|
|
136
|
-
minSimilarity: 0.75
|
|
137
|
-
}
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
// Map similar results back to our decision array
|
|
141
|
-
for (const result of similar) {
|
|
142
|
-
if (result.decision) {
|
|
143
|
-
// Find matching decision in our array
|
|
144
|
-
for (let j = 0; j < decisions.length; j++) {
|
|
145
|
-
const candidate = decisions[j]
|
|
146
|
-
if (!processed.has(j) && candidate &&
|
|
147
|
-
candidate.decision === result.decision.decision) {
|
|
148
|
-
cluster.push(candidate)
|
|
149
|
-
processed.add(j)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} catch (error) {
|
|
155
|
-
this.logger.warn({ error }, 'Error finding similar decisions')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Only add cluster if it has enough items
|
|
159
|
-
if (cluster.length >= 3) {
|
|
160
|
-
clusters.push(cluster)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return clusters
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Extract patterns from decision clusters
|
|
169
|
-
*/
|
|
170
|
-
private async extractPatterns(clusters: DecisionWithProject[][]): Promise<Pattern[]> {
|
|
171
|
-
const patterns: Pattern[] = []
|
|
172
|
-
|
|
173
|
-
for (const cluster of clusters) {
|
|
174
|
-
const pattern = await this.analyzeCluster(cluster)
|
|
175
|
-
|
|
176
|
-
if (pattern) {
|
|
177
|
-
patterns.push(pattern)
|
|
178
|
-
// SLM Phase 1A: Log classification for training data
|
|
179
|
-
this._logPatternTraining(pattern.description, pattern.type)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return patterns
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Analyze a cluster to find the common pattern
|
|
188
|
-
*/
|
|
189
|
-
private async analyzeCluster(cluster: DecisionWithProject[]): Promise<Pattern | null> {
|
|
190
|
-
if (cluster.length < 3) return null
|
|
191
|
-
|
|
192
|
-
// Extract common keywords
|
|
193
|
-
const keywords = this.extractCommonKeywords(cluster)
|
|
194
|
-
|
|
195
|
-
if (keywords.length === 0) return null
|
|
196
|
-
|
|
197
|
-
// Determine pattern type — SLM model first, regex fallback
|
|
198
|
-
let type: Pattern['type']
|
|
199
|
-
if (this.inferenceRouter) {
|
|
200
|
-
try {
|
|
201
|
-
// Use representative text from cluster for model classification
|
|
202
|
-
const representative = cluster.slice(0, 3)
|
|
203
|
-
.map(d => d.decision + (d.reasoning ? ` ${d.reasoning}` : ''))
|
|
204
|
-
.join('. ')
|
|
205
|
-
type = await this.inferenceRouter.classifyPatternType(representative)
|
|
206
|
-
} catch {
|
|
207
|
-
type = this.determinePatternType(cluster)
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
type = this.determinePatternType(cluster)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Create pattern description
|
|
214
|
-
const description = this.generatePatternDescription(keywords, type)
|
|
215
|
-
|
|
216
|
-
// Get unique projects
|
|
217
|
-
const projects = [...new Set(cluster.map(d => d.project))]
|
|
218
|
-
|
|
219
|
-
// Get timestamps
|
|
220
|
-
const timestamps = cluster.map(d => d.timestamp.getTime())
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
id: this.generatePatternId(description),
|
|
224
|
-
type,
|
|
225
|
-
description,
|
|
226
|
-
occurrences: cluster.length,
|
|
227
|
-
projects,
|
|
228
|
-
examples: cluster.slice(0, 3).map(d => d.decision),
|
|
229
|
-
confidence: this.calculateConfidence(cluster),
|
|
230
|
-
firstSeen: new Date(Math.min(...timestamps)),
|
|
231
|
-
lastSeen: new Date(Math.max(...timestamps))
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Extract common keywords from decisions
|
|
237
|
-
*/
|
|
238
|
-
private extractCommonKeywords(decisions: DecisionWithProject[]): string[] {
|
|
239
|
-
const wordFreq: Map<string, number> = new Map()
|
|
240
|
-
|
|
241
|
-
// Stop words to filter out
|
|
242
|
-
const stopWords = new Set([
|
|
243
|
-
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
244
|
-
'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
|
|
245
|
-
'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
246
|
-
'could', 'should', 'may', 'might', 'must', 'shall', 'can', 'need',
|
|
247
|
-
'this', 'that', 'these', 'those', 'it', 'its', 'we', 'our', 'you',
|
|
248
|
-
'your', 'they', 'their', 'use', 'using', 'because', 'since'
|
|
249
|
-
])
|
|
250
|
-
|
|
251
|
-
for (const decision of decisions) {
|
|
252
|
-
const words = decision.decision
|
|
253
|
-
.toLowerCase()
|
|
254
|
-
.split(/\W+/)
|
|
255
|
-
.filter(w => w.length > 4 && !stopWords.has(w))
|
|
256
|
-
|
|
257
|
-
for (const word of words) {
|
|
258
|
-
wordFreq.set(word, (wordFreq.get(word) || 0) + 1)
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Return top keywords that appear in multiple decisions
|
|
263
|
-
return Array.from(wordFreq.entries())
|
|
264
|
-
.filter(([_, count]) => count >= 2)
|
|
265
|
-
.sort((a, b) => b[1] - a[1])
|
|
266
|
-
.slice(0, 5)
|
|
267
|
-
.map(([word]) => word)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Determine pattern type from cluster
|
|
272
|
-
*/
|
|
273
|
-
private determinePatternType(cluster: DecisionWithProject[]): Pattern['type'] {
|
|
274
|
-
// Check for anti-pattern indicators
|
|
275
|
-
const hasNegative = cluster.some(d => {
|
|
276
|
-
const text = (d.reasoning || '').toLowerCase()
|
|
277
|
-
return text.includes('avoid') ||
|
|
278
|
-
text.includes("don't") ||
|
|
279
|
-
text.includes('problem') ||
|
|
280
|
-
text.includes('mistake') ||
|
|
281
|
-
text.includes('bad')
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
if (hasNegative) return 'anti-pattern'
|
|
285
|
-
|
|
286
|
-
// Check for best practice indicators
|
|
287
|
-
const hasBestPractice = cluster.some(d => {
|
|
288
|
-
const text = (d.reasoning || '').toLowerCase()
|
|
289
|
-
return text.includes('best practice') ||
|
|
290
|
-
text.includes('recommended') ||
|
|
291
|
-
text.includes('standard') ||
|
|
292
|
-
d.tags?.includes('best-practice')
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
if (hasBestPractice) return 'best-practice'
|
|
296
|
-
|
|
297
|
-
// Check for issue indicators
|
|
298
|
-
const hasIssue = cluster.some(d => {
|
|
299
|
-
const text = (d.reasoning || '').toLowerCase()
|
|
300
|
-
return text.includes('issue') ||
|
|
301
|
-
text.includes('bug') ||
|
|
302
|
-
text.includes('fix')
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
if (hasIssue) return 'common-issue'
|
|
306
|
-
|
|
307
|
-
return 'solution'
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* SLM Phase 1A: Log pattern type classification for training data.
|
|
312
|
-
* Called from analyzeCluster when a pattern type is determined.
|
|
313
|
-
*/
|
|
314
|
-
private _logPatternTraining(description: string, patternType: Pattern['type']): void {
|
|
315
|
-
try {
|
|
316
|
-
const { logTrainingData } = require('@/training/data-store')
|
|
317
|
-
logTrainingData({
|
|
318
|
-
task: 'pattern' as const,
|
|
319
|
-
input: description,
|
|
320
|
-
output: JSON.stringify({ label: patternType }),
|
|
321
|
-
})
|
|
322
|
-
} catch {
|
|
323
|
-
// Non-critical
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Generate pattern description
|
|
329
|
-
*/
|
|
330
|
-
private generatePatternDescription(keywords: string[], type: Pattern['type']): string {
|
|
331
|
-
const keywordPhrase = keywords.join(', ')
|
|
332
|
-
|
|
333
|
-
switch (type) {
|
|
334
|
-
case 'anti-pattern':
|
|
335
|
-
return `Avoid: ${keywordPhrase} - identified as anti-pattern`
|
|
336
|
-
case 'best-practice':
|
|
337
|
-
return `Best practice: ${keywordPhrase}`
|
|
338
|
-
case 'common-issue':
|
|
339
|
-
return `Common issue: ${keywordPhrase}`
|
|
340
|
-
case 'solution':
|
|
341
|
-
default:
|
|
342
|
-
return `Common solution: ${keywordPhrase}`
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Calculate confidence score for pattern
|
|
348
|
-
*/
|
|
349
|
-
private calculateConfidence(cluster: DecisionWithProject[]): number {
|
|
350
|
-
// More occurrences = higher confidence
|
|
351
|
-
// More projects = higher confidence
|
|
352
|
-
const projects = new Set(cluster.map(d => d.project))
|
|
353
|
-
|
|
354
|
-
const occurrenceScore = Math.min(cluster.length / 10, 1)
|
|
355
|
-
const projectScore = Math.min(projects.size / 5, 1)
|
|
356
|
-
|
|
357
|
-
return (occurrenceScore + projectScore) / 2
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Generate unique ID for pattern
|
|
362
|
-
*/
|
|
363
|
-
private generatePatternId(description: string): string {
|
|
364
|
-
return description
|
|
365
|
-
.toLowerCase()
|
|
366
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
367
|
-
.slice(0, 50)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Get patterns matching query
|
|
372
|
-
*/
|
|
373
|
-
async getRelevantPatterns(query: string): Promise<Pattern[]> {
|
|
374
|
-
const allPatterns = Array.from(this.patterns.values())
|
|
375
|
-
|
|
376
|
-
if (allPatterns.length === 0) {
|
|
377
|
-
// Try to analyze patterns first
|
|
378
|
-
await this.analyzePatterns()
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Score patterns by relevance
|
|
382
|
-
const scored = Array.from(this.patterns.values()).map(pattern => ({
|
|
383
|
-
pattern,
|
|
384
|
-
score: this.scorePatternRelevance(pattern, query)
|
|
385
|
-
}))
|
|
386
|
-
|
|
387
|
-
// Return top patterns
|
|
388
|
-
return scored
|
|
389
|
-
.filter(p => p.score > 0.3)
|
|
390
|
-
.sort((a, b) => b.score - a.score)
|
|
391
|
-
.slice(0, 5)
|
|
392
|
-
.map(p => p.pattern)
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Score pattern relevance to query
|
|
397
|
-
*/
|
|
398
|
-
private scorePatternRelevance(pattern: Pattern, query: string): number {
|
|
399
|
-
const queryLower = query.toLowerCase()
|
|
400
|
-
const descLower = pattern.description.toLowerCase()
|
|
401
|
-
|
|
402
|
-
// Exact match
|
|
403
|
-
if (descLower.includes(queryLower)) return 1.0
|
|
404
|
-
|
|
405
|
-
// Word overlap
|
|
406
|
-
const queryWords = queryLower.split(/\W+/).filter(w => w.length > 2)
|
|
407
|
-
const descWords = descLower.split(/\W+/).filter(w => w.length > 2)
|
|
408
|
-
|
|
409
|
-
if (queryWords.length === 0) return 0
|
|
410
|
-
|
|
411
|
-
const overlap = queryWords.filter(w => descWords.includes(w)).length
|
|
412
|
-
|
|
413
|
-
return overlap / queryWords.length
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Get all detected patterns
|
|
418
|
-
*/
|
|
419
|
-
getAllPatterns(): Pattern[] {
|
|
420
|
-
return Array.from(this.patterns.values())
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Get patterns by type
|
|
425
|
-
*/
|
|
426
|
-
getPatternsByType(type: Pattern['type']): Pattern[] {
|
|
427
|
-
return Array.from(this.patterns.values())
|
|
428
|
-
.filter(p => p.type === type)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Clear all patterns (useful for re-analysis)
|
|
433
|
-
*/
|
|
434
|
-
clearPatterns(): void {
|
|
435
|
-
this.patterns.clear()
|
|
436
|
-
this.logger.info('Patterns cleared')
|
|
437
|
-
}
|
|
438
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Recognition System
|
|
3
|
+
* Phase 12: Advanced Memory & Intelligent Automation
|
|
4
|
+
*
|
|
5
|
+
* Detects recurring patterns across projects and decisions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Logger } from 'pino'
|
|
9
|
+
import type { MemoryManager } from './index'
|
|
10
|
+
import type { Decision as _Decision, MemorySearchResult as _MemorySearchResult } from './types'
|
|
11
|
+
|
|
12
|
+
export interface Pattern {
|
|
13
|
+
id: string
|
|
14
|
+
type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
15
|
+
description: string
|
|
16
|
+
occurrences: number
|
|
17
|
+
projects: string[]
|
|
18
|
+
examples: string[]
|
|
19
|
+
confidence: number
|
|
20
|
+
firstSeen: Date
|
|
21
|
+
lastSeen: Date
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface DecisionWithProject {
|
|
25
|
+
decision: string
|
|
26
|
+
reasoning?: string
|
|
27
|
+
project: string
|
|
28
|
+
timestamp: Date
|
|
29
|
+
tags?: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class PatternRecognizer {
|
|
33
|
+
private logger: Logger
|
|
34
|
+
private memory: MemoryManager
|
|
35
|
+
private patterns: Map<string, Pattern> = new Map()
|
|
36
|
+
|
|
37
|
+
/** SLM Upgrade: Optional inference router for model-based pattern classification */
|
|
38
|
+
private inferenceRouter: import('@/intelligence/inference-router').InferenceRouter | null = null
|
|
39
|
+
|
|
40
|
+
constructor(logger: Logger, memory: MemoryManager) {
|
|
41
|
+
this.logger = logger.child({ component: 'pattern-recognizer' })
|
|
42
|
+
this.memory = memory
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** SLM Upgrade: Set optional inference router */
|
|
46
|
+
setInferenceRouter(router: import('@/intelligence/inference-router').InferenceRouter): void {
|
|
47
|
+
this.inferenceRouter = router
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Analyze all decisions to find patterns
|
|
52
|
+
*/
|
|
53
|
+
async analyzePatterns(): Promise<Pattern[]> {
|
|
54
|
+
this.logger.info('Analyzing patterns across all projects')
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Get all decisions from all projects
|
|
58
|
+
const allDecisions = await this.getAllDecisions()
|
|
59
|
+
|
|
60
|
+
if (allDecisions.length < 3) {
|
|
61
|
+
this.logger.info('Not enough decisions for pattern analysis')
|
|
62
|
+
return []
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Group by similarity
|
|
66
|
+
const clusters = await this.clusterDecisions(allDecisions)
|
|
67
|
+
|
|
68
|
+
// Extract patterns from clusters
|
|
69
|
+
const patterns = await this.extractPatterns(clusters)
|
|
70
|
+
|
|
71
|
+
// Store patterns
|
|
72
|
+
for (const pattern of patterns) {
|
|
73
|
+
this.patterns.set(pattern.id, pattern)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.logger.info(
|
|
77
|
+
{ patternCount: patterns.length },
|
|
78
|
+
'Pattern analysis complete'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return patterns
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.logger.error({ error }, 'Failed to analyze patterns')
|
|
84
|
+
return []
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all decisions from all projects
|
|
90
|
+
*/
|
|
91
|
+
private async getAllDecisions(): Promise<DecisionWithProject[]> {
|
|
92
|
+
const projects = this.memory.store.getProjects()
|
|
93
|
+
const allDecisions: DecisionWithProject[] = []
|
|
94
|
+
|
|
95
|
+
for (const project of projects) {
|
|
96
|
+
const decisions = this.memory.store.getProjectDecisions(project)
|
|
97
|
+
|
|
98
|
+
for (const decision of decisions) {
|
|
99
|
+
// Get associated memory for timestamp
|
|
100
|
+
const memory = this.memory.store.getMemory(decision.memoryId)
|
|
101
|
+
|
|
102
|
+
allDecisions.push({
|
|
103
|
+
decision: decision.decision,
|
|
104
|
+
reasoning: decision.reasoning,
|
|
105
|
+
project,
|
|
106
|
+
timestamp: memory?.createdAt || new Date(),
|
|
107
|
+
tags: decision.tags
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return allDecisions
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Cluster similar decisions together
|
|
117
|
+
*/
|
|
118
|
+
private async clusterDecisions(decisions: DecisionWithProject[]): Promise<DecisionWithProject[][]> {
|
|
119
|
+
const clusters: DecisionWithProject[][] = []
|
|
120
|
+
const processed = new Set<number>()
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < decisions.length; i++) {
|
|
123
|
+
if (processed.has(i)) continue
|
|
124
|
+
|
|
125
|
+
const decision = decisions[i]
|
|
126
|
+
if (!decision) continue
|
|
127
|
+
const cluster: DecisionWithProject[] = [decision]
|
|
128
|
+
processed.add(i)
|
|
129
|
+
|
|
130
|
+
// Find similar decisions using semantic search
|
|
131
|
+
try {
|
|
132
|
+
const similar = await this.memory.searchRaw(
|
|
133
|
+
decision.decision,
|
|
134
|
+
{
|
|
135
|
+
limit: 10,
|
|
136
|
+
minSimilarity: 0.75
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
// Map similar results back to our decision array
|
|
141
|
+
for (const result of similar) {
|
|
142
|
+
if (result.decision) {
|
|
143
|
+
// Find matching decision in our array
|
|
144
|
+
for (let j = 0; j < decisions.length; j++) {
|
|
145
|
+
const candidate = decisions[j]
|
|
146
|
+
if (!processed.has(j) && candidate &&
|
|
147
|
+
candidate.decision === result.decision.decision) {
|
|
148
|
+
cluster.push(candidate)
|
|
149
|
+
processed.add(j)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.logger.warn({ error }, 'Error finding similar decisions')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Only add cluster if it has enough items
|
|
159
|
+
if (cluster.length >= 3) {
|
|
160
|
+
clusters.push(cluster)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return clusters
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Extract patterns from decision clusters
|
|
169
|
+
*/
|
|
170
|
+
private async extractPatterns(clusters: DecisionWithProject[][]): Promise<Pattern[]> {
|
|
171
|
+
const patterns: Pattern[] = []
|
|
172
|
+
|
|
173
|
+
for (const cluster of clusters) {
|
|
174
|
+
const pattern = await this.analyzeCluster(cluster)
|
|
175
|
+
|
|
176
|
+
if (pattern) {
|
|
177
|
+
patterns.push(pattern)
|
|
178
|
+
// SLM Phase 1A: Log classification for training data
|
|
179
|
+
this._logPatternTraining(pattern.description, pattern.type)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return patterns
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Analyze a cluster to find the common pattern
|
|
188
|
+
*/
|
|
189
|
+
private async analyzeCluster(cluster: DecisionWithProject[]): Promise<Pattern | null> {
|
|
190
|
+
if (cluster.length < 3) return null
|
|
191
|
+
|
|
192
|
+
// Extract common keywords
|
|
193
|
+
const keywords = this.extractCommonKeywords(cluster)
|
|
194
|
+
|
|
195
|
+
if (keywords.length === 0) return null
|
|
196
|
+
|
|
197
|
+
// Determine pattern type — SLM model first, regex fallback
|
|
198
|
+
let type: Pattern['type']
|
|
199
|
+
if (this.inferenceRouter) {
|
|
200
|
+
try {
|
|
201
|
+
// Use representative text from cluster for model classification
|
|
202
|
+
const representative = cluster.slice(0, 3)
|
|
203
|
+
.map(d => d.decision + (d.reasoning ? ` ${d.reasoning}` : ''))
|
|
204
|
+
.join('. ')
|
|
205
|
+
type = await this.inferenceRouter.classifyPatternType(representative)
|
|
206
|
+
} catch {
|
|
207
|
+
type = this.determinePatternType(cluster)
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
type = this.determinePatternType(cluster)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Create pattern description
|
|
214
|
+
const description = this.generatePatternDescription(keywords, type)
|
|
215
|
+
|
|
216
|
+
// Get unique projects
|
|
217
|
+
const projects = [...new Set(cluster.map(d => d.project))]
|
|
218
|
+
|
|
219
|
+
// Get timestamps
|
|
220
|
+
const timestamps = cluster.map(d => d.timestamp.getTime())
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
id: this.generatePatternId(description),
|
|
224
|
+
type,
|
|
225
|
+
description,
|
|
226
|
+
occurrences: cluster.length,
|
|
227
|
+
projects,
|
|
228
|
+
examples: cluster.slice(0, 3).map(d => d.decision),
|
|
229
|
+
confidence: this.calculateConfidence(cluster),
|
|
230
|
+
firstSeen: new Date(Math.min(...timestamps)),
|
|
231
|
+
lastSeen: new Date(Math.max(...timestamps))
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Extract common keywords from decisions
|
|
237
|
+
*/
|
|
238
|
+
private extractCommonKeywords(decisions: DecisionWithProject[]): string[] {
|
|
239
|
+
const wordFreq: Map<string, number> = new Map()
|
|
240
|
+
|
|
241
|
+
// Stop words to filter out
|
|
242
|
+
const stopWords = new Set([
|
|
243
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
244
|
+
'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
|
|
245
|
+
'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
246
|
+
'could', 'should', 'may', 'might', 'must', 'shall', 'can', 'need',
|
|
247
|
+
'this', 'that', 'these', 'those', 'it', 'its', 'we', 'our', 'you',
|
|
248
|
+
'your', 'they', 'their', 'use', 'using', 'because', 'since'
|
|
249
|
+
])
|
|
250
|
+
|
|
251
|
+
for (const decision of decisions) {
|
|
252
|
+
const words = decision.decision
|
|
253
|
+
.toLowerCase()
|
|
254
|
+
.split(/\W+/)
|
|
255
|
+
.filter(w => w.length > 4 && !stopWords.has(w))
|
|
256
|
+
|
|
257
|
+
for (const word of words) {
|
|
258
|
+
wordFreq.set(word, (wordFreq.get(word) || 0) + 1)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Return top keywords that appear in multiple decisions
|
|
263
|
+
return Array.from(wordFreq.entries())
|
|
264
|
+
.filter(([_, count]) => count >= 2)
|
|
265
|
+
.sort((a, b) => b[1] - a[1])
|
|
266
|
+
.slice(0, 5)
|
|
267
|
+
.map(([word]) => word)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Determine pattern type from cluster
|
|
272
|
+
*/
|
|
273
|
+
private determinePatternType(cluster: DecisionWithProject[]): Pattern['type'] {
|
|
274
|
+
// Check for anti-pattern indicators
|
|
275
|
+
const hasNegative = cluster.some(d => {
|
|
276
|
+
const text = (d.reasoning || '').toLowerCase()
|
|
277
|
+
return text.includes('avoid') ||
|
|
278
|
+
text.includes("don't") ||
|
|
279
|
+
text.includes('problem') ||
|
|
280
|
+
text.includes('mistake') ||
|
|
281
|
+
text.includes('bad')
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (hasNegative) return 'anti-pattern'
|
|
285
|
+
|
|
286
|
+
// Check for best practice indicators
|
|
287
|
+
const hasBestPractice = cluster.some(d => {
|
|
288
|
+
const text = (d.reasoning || '').toLowerCase()
|
|
289
|
+
return text.includes('best practice') ||
|
|
290
|
+
text.includes('recommended') ||
|
|
291
|
+
text.includes('standard') ||
|
|
292
|
+
d.tags?.includes('best-practice')
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
if (hasBestPractice) return 'best-practice'
|
|
296
|
+
|
|
297
|
+
// Check for issue indicators
|
|
298
|
+
const hasIssue = cluster.some(d => {
|
|
299
|
+
const text = (d.reasoning || '').toLowerCase()
|
|
300
|
+
return text.includes('issue') ||
|
|
301
|
+
text.includes('bug') ||
|
|
302
|
+
text.includes('fix')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
if (hasIssue) return 'common-issue'
|
|
306
|
+
|
|
307
|
+
return 'solution'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* SLM Phase 1A: Log pattern type classification for training data.
|
|
312
|
+
* Called from analyzeCluster when a pattern type is determined.
|
|
313
|
+
*/
|
|
314
|
+
private _logPatternTraining(description: string, patternType: Pattern['type']): void {
|
|
315
|
+
try {
|
|
316
|
+
const { logTrainingData } = require('@/training/data-store')
|
|
317
|
+
logTrainingData({
|
|
318
|
+
task: 'pattern' as const,
|
|
319
|
+
input: description,
|
|
320
|
+
output: JSON.stringify({ label: patternType }),
|
|
321
|
+
})
|
|
322
|
+
} catch {
|
|
323
|
+
// Non-critical
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Generate pattern description
|
|
329
|
+
*/
|
|
330
|
+
private generatePatternDescription(keywords: string[], type: Pattern['type']): string {
|
|
331
|
+
const keywordPhrase = keywords.join(', ')
|
|
332
|
+
|
|
333
|
+
switch (type) {
|
|
334
|
+
case 'anti-pattern':
|
|
335
|
+
return `Avoid: ${keywordPhrase} - identified as anti-pattern`
|
|
336
|
+
case 'best-practice':
|
|
337
|
+
return `Best practice: ${keywordPhrase}`
|
|
338
|
+
case 'common-issue':
|
|
339
|
+
return `Common issue: ${keywordPhrase}`
|
|
340
|
+
case 'solution':
|
|
341
|
+
default:
|
|
342
|
+
return `Common solution: ${keywordPhrase}`
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Calculate confidence score for pattern
|
|
348
|
+
*/
|
|
349
|
+
private calculateConfidence(cluster: DecisionWithProject[]): number {
|
|
350
|
+
// More occurrences = higher confidence
|
|
351
|
+
// More projects = higher confidence
|
|
352
|
+
const projects = new Set(cluster.map(d => d.project))
|
|
353
|
+
|
|
354
|
+
const occurrenceScore = Math.min(cluster.length / 10, 1)
|
|
355
|
+
const projectScore = Math.min(projects.size / 5, 1)
|
|
356
|
+
|
|
357
|
+
return (occurrenceScore + projectScore) / 2
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Generate unique ID for pattern
|
|
362
|
+
*/
|
|
363
|
+
private generatePatternId(description: string): string {
|
|
364
|
+
return description
|
|
365
|
+
.toLowerCase()
|
|
366
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
367
|
+
.slice(0, 50)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get patterns matching query
|
|
372
|
+
*/
|
|
373
|
+
async getRelevantPatterns(query: string): Promise<Pattern[]> {
|
|
374
|
+
const allPatterns = Array.from(this.patterns.values())
|
|
375
|
+
|
|
376
|
+
if (allPatterns.length === 0) {
|
|
377
|
+
// Try to analyze patterns first
|
|
378
|
+
await this.analyzePatterns()
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Score patterns by relevance
|
|
382
|
+
const scored = Array.from(this.patterns.values()).map(pattern => ({
|
|
383
|
+
pattern,
|
|
384
|
+
score: this.scorePatternRelevance(pattern, query)
|
|
385
|
+
}))
|
|
386
|
+
|
|
387
|
+
// Return top patterns
|
|
388
|
+
return scored
|
|
389
|
+
.filter(p => p.score > 0.3)
|
|
390
|
+
.sort((a, b) => b.score - a.score)
|
|
391
|
+
.slice(0, 5)
|
|
392
|
+
.map(p => p.pattern)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Score pattern relevance to query
|
|
397
|
+
*/
|
|
398
|
+
private scorePatternRelevance(pattern: Pattern, query: string): number {
|
|
399
|
+
const queryLower = query.toLowerCase()
|
|
400
|
+
const descLower = pattern.description.toLowerCase()
|
|
401
|
+
|
|
402
|
+
// Exact match
|
|
403
|
+
if (descLower.includes(queryLower)) return 1.0
|
|
404
|
+
|
|
405
|
+
// Word overlap
|
|
406
|
+
const queryWords = queryLower.split(/\W+/).filter(w => w.length > 2)
|
|
407
|
+
const descWords = descLower.split(/\W+/).filter(w => w.length > 2)
|
|
408
|
+
|
|
409
|
+
if (queryWords.length === 0) return 0
|
|
410
|
+
|
|
411
|
+
const overlap = queryWords.filter(w => descWords.includes(w)).length
|
|
412
|
+
|
|
413
|
+
return overlap / queryWords.length
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get all detected patterns
|
|
418
|
+
*/
|
|
419
|
+
getAllPatterns(): Pattern[] {
|
|
420
|
+
return Array.from(this.patterns.values())
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get patterns by type
|
|
425
|
+
*/
|
|
426
|
+
getPatternsByType(type: Pattern['type']): Pattern[] {
|
|
427
|
+
return Array.from(this.patterns.values())
|
|
428
|
+
.filter(p => p.type === type)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Clear all patterns (useful for re-analysis)
|
|
433
|
+
*/
|
|
434
|
+
clearPatterns(): void {
|
|
435
|
+
this.patterns.clear()
|
|
436
|
+
this.logger.info('Patterns cleared')
|
|
437
|
+
}
|
|
438
|
+
}
|