claude-brain 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -11
- package/bunfig.toml +8 -8
- package/package.json +82 -82
- 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 +341 -341
- package/src/automation/auto-context.ts +240 -240
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/index.ts +11 -11
- 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 +205 -205
- package/src/cli/auto-setup.ts +82 -82
- package/src/cli/bin.ts +209 -202
- package/src/cli/commands/chroma.ts +573 -573
- package/src/cli/commands/git-hook.ts +189 -189
- package/src/cli/commands/hooks.ts +213 -213
- package/src/cli/commands/init.ts +122 -122
- package/src/cli/commands/install-mcp.ts +92 -92
- package/src/cli/commands/pack.ts +197 -197
- package/src/cli/commands/refresh.ts +323 -0
- package/src/cli/commands/serve.ts +167 -173
- package/src/cli/commands/start.ts +42 -42
- package/src/cli/commands/uninstall-mcp.ts +41 -41
- package/src/cli/commands/update.ts +124 -121
- package/src/cli/diagnose.ts +4 -4
- package/src/cli/health-check.ts +4 -4
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/setup.ts +4 -4
- 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/config/defaults.ts +50 -50
- package/src/config/home.ts +55 -55
- package/src/config/index.ts +7 -7
- package/src/config/loader.ts +166 -166
- package/src/config/migration.ts +76 -76
- package/src/config/schema.ts +360 -360
- package/src/config/validator.ts +184 -184
- package/src/config/watcher.ts +86 -86
- package/src/context/assembler.ts +398 -398
- package/src/context/cache-manager.ts +101 -101
- package/src/context/formatter.ts +84 -84
- package/src/context/hierarchy.ts +85 -85
- package/src/context/index.ts +83 -83
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/types.ts +252 -252
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +123 -123
- package/src/health/index.ts +229 -229
- package/src/hooks/brain-hook.ts +128 -112
- package/src/hooks/capture.ts +168 -205
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +207 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +194 -194
- package/src/hooks/passive-classifier.ts +404 -723
- package/src/hooks/queue.ts +129 -129
- package/src/hooks/session-tracker.ts +312 -275
- package/src/hooks/types.ts +47 -47
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/affinity.ts +162 -162
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +13 -13
- package/src/intelligence/cross-project/transfer.ts +201 -201
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +207 -207
- package/src/intelligence/prediction/context-anticipator.ts +198 -198
- package/src/intelligence/prediction/decision-predictor.ts +184 -184
- package/src/intelligence/prediction/index.ts +13 -13
- package/src/intelligence/prediction/recommender.ts +268 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +247 -247
- package/src/intelligence/reasoning/counterfactual.ts +248 -248
- package/src/intelligence/reasoning/index.ts +13 -13
- package/src/intelligence/reasoning/synthesizer.ts +169 -169
- package/src/intelligence/temporal/evolution.ts +197 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +259 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/knowledge/entity-extractor.ts +416 -416
- 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 +168 -168
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +174 -174
- package/src/memory/chroma/collection-manager.ts +94 -94
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +155 -155
- 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 +315 -315
- package/src/memory/chroma/store.ts +741 -741
- package/src/memory/consolidation/archiver.ts +164 -164
- package/src/memory/consolidation/merger.ts +186 -186
- package/src/memory/consolidation/scorer.ts +138 -138
- package/src/memory/context-builder.ts +236 -236
- package/src/memory/database.ts +169 -169
- package/src/memory/embedding-utils.ts +156 -156
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +351 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/index.ts +582 -582
- package/src/memory/knowledge-extractor.ts +455 -455
- package/src/memory/learning.ts +378 -378
- package/src/memory/patterns.ts +396 -396
- package/src/memory/schema.ts +88 -88
- package/src/memory/search.ts +309 -309
- package/src/memory/store.ts +787 -787
- package/src/memory/types.ts +121 -121
- package/src/orchestrator/coordinator.ts +272 -272
- package/src/orchestrator/decision-logger.ts +228 -228
- package/src/orchestrator/event-emitter.ts +198 -198
- package/src/orchestrator/event-queue.ts +184 -184
- package/src/orchestrator/handlers/base-handler.ts +70 -70
- package/src/orchestrator/handlers/context-handler.ts +73 -73
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/orchestrator/handlers/index.ts +10 -10
- package/src/orchestrator/handlers/status-handler.ts +131 -131
- package/src/orchestrator/handlers/task-handler.ts +171 -171
- package/src/orchestrator/index.ts +275 -275
- package/src/orchestrator/task-parser.ts +284 -284
- package/src/orchestrator/types.ts +98 -98
- 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 -300
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +223 -223
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +223 -223
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +163 -163
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +198 -198
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +236 -236
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +188 -188
- package/src/retrieval/reranker/model.ts +95 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +428 -428
- package/src/routing/intent-classifier.ts +450 -436
- package/src/routing/response-filter.ts +261 -258
- package/src/routing/router.ts +1441 -1322
- package/src/routing/search-engine.ts +515 -475
- package/src/routing/types.ts +94 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/handlers/call-tool.ts +156 -156
- package/src/server/handlers/index.ts +9 -9
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/analyze-decision-evolution.ts +151 -151
- package/src/server/handlers/tools/auto-remember.ts +200 -200
- package/src/server/handlers/tools/brain.ts +85 -85
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/detect-trends.ts +144 -144
- package/src/server/handlers/tools/find-cross-project-patterns.ts +168 -168
- package/src/server/handlers/tools/get-activity-log.ts +194 -194
- package/src/server/handlers/tools/get-code-standards.ts +124 -124
- package/src/server/handlers/tools/get-corrections.ts +154 -154
- package/src/server/handlers/tools/get-decision-timeline.ts +172 -172
- package/src/server/handlers/tools/get-episode.ts +103 -103
- package/src/server/handlers/tools/get-patterns.ts +158 -158
- package/src/server/handlers/tools/get-phase12-status.ts +63 -63
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/get-recommendations.ts +145 -145
- package/src/server/handlers/tools/index.ts +31 -31
- package/src/server/handlers/tools/init-project.ts +757 -757
- package/src/server/handlers/tools/list-episodes.ts +90 -90
- package/src/server/handlers/tools/list-projects.ts +125 -125
- package/src/server/handlers/tools/rate-memory.ts +101 -101
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +126 -126
- package/src/server/handlers/tools/record-correction.ts +125 -125
- package/src/server/handlers/tools/remember-decision.ts +153 -153
- package/src/server/handlers/tools/schemas.ts +253 -253
- package/src/server/handlers/tools/search-knowledge-graph.ts +102 -102
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/handlers/tools/what-if-analysis.ts +135 -135
- package/src/server/http-api.ts +693 -693
- package/src/server/index.ts +40 -40
- package/src/server/mcp-server.ts +283 -283
- package/src/server/providers/index.ts +7 -7
- package/src/server/providers/prompts.ts +327 -327
- package/src/server/providers/resources.ts +622 -622
- package/src/server/services.ts +468 -468
- package/src/server/types.ts +39 -39
- package/src/server/utils/error-handler.ts +155 -155
- package/src/server/utils/index.ts +13 -13
- package/src/server/utils/memory-indicator.ts +83 -83
- package/src/server/utils/request-context.ts +122 -122
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/utils/validators.ts +210 -210
- package/src/setup/index.ts +48 -48
- package/src/setup/wizard.ts +461 -461
- package/src/tools/index.ts +24 -24
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.test.ts +30 -30
- package/src/tools/schemas.ts +617 -617
- package/src/tools/types.ts +412 -412
- package/src/utils/circuit-breaker.ts +130 -130
- package/src/utils/cleanup.ts +34 -34
- package/src/utils/error-handler.ts +132 -132
- package/src/utils/error-messages.ts +60 -60
- package/src/utils/fallback.ts +45 -45
- package/src/utils/index.ts +54 -54
- package/src/utils/logger-utils.ts +80 -80
- package/src/utils/logger.ts +88 -88
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/retry.ts +94 -94
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/frontmatter.ts +264 -264
- package/src/vault/index.ts +318 -318
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +422 -422
- package/src/vault/reader.ts +264 -264
- package/src/vault/templates.ts +186 -186
- package/src/vault/types.ts +73 -73
- package/src/vault/watcher.ts +277 -277
- package/src/vault/writer.ts +413 -413
- package/tsconfig.json +30 -30
- package/src/cli/auto-update.ts +0 -157
package/src/hooks/types.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 17: Passive Learning via Hooks — Shared Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/** Claude Code hook stdin JSON format */
|
|
6
|
-
export interface HookInput {
|
|
7
|
-
session_id: string
|
|
8
|
-
hook_event_name: 'PostToolUse' | 'Stop' | 'PreToolUse' | 'GitCommit'
|
|
9
|
-
cwd: string
|
|
10
|
-
tool_name?: string
|
|
11
|
-
tool_input?: Record<string, any>
|
|
12
|
-
tool_response?: {
|
|
13
|
-
content?: string | Array<{ type: string; text?: string }>
|
|
14
|
-
[key: string]: any
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Knowledge type classifications */
|
|
19
|
-
export type KnowledgeType = 'decision' | 'pattern' | 'correction' | 'progress'
|
|
20
|
-
|
|
21
|
-
/** A piece of knowledge captured from a hook event */
|
|
22
|
-
export interface CapturedKnowledge {
|
|
23
|
-
type: KnowledgeType
|
|
24
|
-
confidence: number
|
|
25
|
-
content: string
|
|
26
|
-
project?: string
|
|
27
|
-
technologies: string[]
|
|
28
|
-
metadata: Record<string, any>
|
|
29
|
-
source: 'hook-passive'
|
|
30
|
-
timestamp: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** What to do with captured knowledge before storage */
|
|
34
|
-
export type StoreAction =
|
|
35
|
-
| { action: 'store_new' }
|
|
36
|
-
| { action: 'merge'; existingId: string; mergedContent: string }
|
|
37
|
-
| { action: 'skip'; reason: string }
|
|
38
|
-
|
|
39
|
-
/** Hook event stats for status reporting */
|
|
40
|
-
export interface HookStats {
|
|
41
|
-
totalCaptured: number
|
|
42
|
-
totalSkipped: number
|
|
43
|
-
totalMerged: number
|
|
44
|
-
sessionsTracked: number
|
|
45
|
-
lastCaptureAt?: string
|
|
46
|
-
queueSize: number
|
|
47
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Phase 17: Passive Learning via Hooks — Shared Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Claude Code hook stdin JSON format */
|
|
6
|
+
export interface HookInput {
|
|
7
|
+
session_id: string
|
|
8
|
+
hook_event_name: 'PostToolUse' | 'Stop' | 'PreToolUse' | 'GitCommit'
|
|
9
|
+
cwd: string
|
|
10
|
+
tool_name?: string
|
|
11
|
+
tool_input?: Record<string, any>
|
|
12
|
+
tool_response?: {
|
|
13
|
+
content?: string | Array<{ type: string; text?: string }>
|
|
14
|
+
[key: string]: any
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Knowledge type classifications */
|
|
19
|
+
export type KnowledgeType = 'decision' | 'pattern' | 'correction' | 'progress'
|
|
20
|
+
|
|
21
|
+
/** A piece of knowledge captured from a hook event */
|
|
22
|
+
export interface CapturedKnowledge {
|
|
23
|
+
type: KnowledgeType
|
|
24
|
+
confidence: number
|
|
25
|
+
content: string
|
|
26
|
+
project?: string
|
|
27
|
+
technologies: string[]
|
|
28
|
+
metadata: Record<string, any>
|
|
29
|
+
source: 'hook-passive'
|
|
30
|
+
timestamp: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** What to do with captured knowledge before storage */
|
|
34
|
+
export type StoreAction =
|
|
35
|
+
| { action: 'store_new' }
|
|
36
|
+
| { action: 'merge'; existingId: string; mergedContent: string }
|
|
37
|
+
| { action: 'skip'; reason: string }
|
|
38
|
+
|
|
39
|
+
/** Hook event stats for status reporting */
|
|
40
|
+
export interface HookStats {
|
|
41
|
+
totalCaptured: number
|
|
42
|
+
totalSkipped: number
|
|
43
|
+
totalMerged: number
|
|
44
|
+
sessionsTracked: number
|
|
45
|
+
lastCaptureAt?: string
|
|
46
|
+
queueSize: number
|
|
47
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { runServe } from './cli/commands/serve'
|
|
3
|
-
|
|
4
|
-
runServe().catch((error) => {
|
|
5
|
-
console.error('Fatal error:', error)
|
|
6
|
-
process.exit(1)
|
|
7
|
-
})
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { runServe } from './cli/commands/serve'
|
|
3
|
+
|
|
4
|
+
runServe().catch((error) => {
|
|
5
|
+
console.error('Fatal error:', error)
|
|
6
|
+
process.exit(1)
|
|
7
|
+
})
|
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Affinity
|
|
3
|
-
* Computes similarity between projects based on decisions, patterns, and tech stack
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Logger } from 'pino'
|
|
7
|
-
import type { CollectionManager } from '@/memory/chroma/collection-manager'
|
|
8
|
-
import type { EmbeddingProvider } from '@/memory/chroma/embeddings'
|
|
9
|
-
|
|
10
|
-
export interface ProjectAffinity {
|
|
11
|
-
projectA: string
|
|
12
|
-
projectB: string
|
|
13
|
-
similarity: number
|
|
14
|
-
sharedTopics: string[]
|
|
15
|
-
sharedTechnologies: string[]
|
|
16
|
-
decisionOverlap: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class AffinityCalculator {
|
|
20
|
-
private logger: Logger
|
|
21
|
-
private collections: CollectionManager
|
|
22
|
-
private embeddings?: EmbeddingProvider
|
|
23
|
-
|
|
24
|
-
constructor(logger: Logger, collections: CollectionManager, embeddings?: EmbeddingProvider) {
|
|
25
|
-
this.logger = logger.child({ component: 'affinity-calculator' })
|
|
26
|
-
this.collections = collections
|
|
27
|
-
this.embeddings = embeddings
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Calculate affinity between all pairs of projects
|
|
32
|
-
*/
|
|
33
|
-
async calculateAffinities(options: {
|
|
34
|
-
minDecisions?: number
|
|
35
|
-
} = {}): Promise<ProjectAffinity[]> {
|
|
36
|
-
const { minDecisions = 2 } = options
|
|
37
|
-
|
|
38
|
-
// Get all decisions grouped by project
|
|
39
|
-
const byProject = await this.getDecisionsByProject()
|
|
40
|
-
|
|
41
|
-
// Filter out projects with too few decisions
|
|
42
|
-
const projects = Array.from(byProject.entries())
|
|
43
|
-
.filter(([_, decisions]) => decisions.length >= minDecisions)
|
|
44
|
-
|
|
45
|
-
const affinities: ProjectAffinity[] = []
|
|
46
|
-
|
|
47
|
-
// Compare all pairs
|
|
48
|
-
for (let i = 0; i < projects.length; i++) {
|
|
49
|
-
for (let j = i + 1; j < projects.length; j++) {
|
|
50
|
-
const [projectA, decisionsA] = projects[i]
|
|
51
|
-
const [projectB, decisionsB] = projects[j]
|
|
52
|
-
|
|
53
|
-
const affinity = this.compareProjects(projectA, decisionsA, projectB, decisionsB)
|
|
54
|
-
if (affinity.similarity > 0.1) {
|
|
55
|
-
affinities.push(affinity)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return affinities.sort((a, b) => b.similarity - a.similarity)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Find most similar projects to a given project
|
|
65
|
-
*/
|
|
66
|
-
async findSimilarProjects(project: string, limit: number = 5): Promise<ProjectAffinity[]> {
|
|
67
|
-
const affinities = await this.calculateAffinities()
|
|
68
|
-
|
|
69
|
-
return affinities
|
|
70
|
-
.filter(a => a.projectA === project || a.projectB === project)
|
|
71
|
-
.sort((a, b) => b.similarity - a.similarity)
|
|
72
|
-
.slice(0, limit)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private async getDecisionsByProject(): Promise<Map<string, Array<{ id: string; content: string; tags: string[] }>>> {
|
|
76
|
-
try {
|
|
77
|
-
const collection = await this.collections.getDecisions()
|
|
78
|
-
|
|
79
|
-
const results = await collection.get({
|
|
80
|
-
include: ['documents', 'metadatas']
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const byProject = new Map<string, Array<{ id: string; content: string; tags: string[] }>>()
|
|
84
|
-
|
|
85
|
-
for (let i = 0; i < results.ids.length; i++) {
|
|
86
|
-
const project = (results.metadatas![i] as any)?.project || ''
|
|
87
|
-
if (!project) continue
|
|
88
|
-
|
|
89
|
-
if (!byProject.has(project)) byProject.set(project, [])
|
|
90
|
-
|
|
91
|
-
byProject.get(project)!.push({
|
|
92
|
-
id: results.ids[i],
|
|
93
|
-
content: results.documents![i] as string || '',
|
|
94
|
-
tags: String((results.metadatas![i] as any)?.tags || '').split(',').filter(t => t.trim())
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return byProject
|
|
99
|
-
} catch (error) {
|
|
100
|
-
this.logger.warn({ error }, 'Failed to get decisions by project')
|
|
101
|
-
return new Map()
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private compareProjects(
|
|
106
|
-
projectA: string,
|
|
107
|
-
decisionsA: Array<{ id: string; content: string; tags: string[] }>,
|
|
108
|
-
projectB: string,
|
|
109
|
-
decisionsB: Array<{ id: string; content: string; tags: string[] }>
|
|
110
|
-
): ProjectAffinity {
|
|
111
|
-
// Term-based similarity
|
|
112
|
-
const termsA = this.extractAllTerms(decisionsA)
|
|
113
|
-
const termsB = this.extractAllTerms(decisionsB)
|
|
114
|
-
|
|
115
|
-
const sharedTopics = Array.from(termsA).filter(t => termsB.has(t))
|
|
116
|
-
const allTopics = new Set([...termsA, ...termsB])
|
|
117
|
-
|
|
118
|
-
const topicSimilarity = allTopics.size > 0 ? sharedTopics.length / allTopics.size : 0
|
|
119
|
-
|
|
120
|
-
// Tag-based similarity
|
|
121
|
-
const tagsA = new Set(decisionsA.flatMap(d => d.tags))
|
|
122
|
-
const tagsB = new Set(decisionsB.flatMap(d => d.tags))
|
|
123
|
-
|
|
124
|
-
const sharedTags = Array.from(tagsA).filter(t => tagsB.has(t))
|
|
125
|
-
const allTags = new Set([...tagsA, ...tagsB])
|
|
126
|
-
const tagSimilarity = allTags.size > 0 ? sharedTags.length / allTags.size : 0
|
|
127
|
-
|
|
128
|
-
// Combined similarity
|
|
129
|
-
const similarity = topicSimilarity * 0.7 + tagSimilarity * 0.3
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
projectA,
|
|
133
|
-
projectB,
|
|
134
|
-
similarity,
|
|
135
|
-
sharedTopics: sharedTopics.slice(0, 20),
|
|
136
|
-
sharedTechnologies: sharedTags.slice(0, 10),
|
|
137
|
-
decisionOverlap: sharedTopics.length
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private extractAllTerms(decisions: Array<{ content: string }>): Set<string> {
|
|
142
|
-
const terms = new Set<string>()
|
|
143
|
-
const stopWords = new Set([
|
|
144
|
-
'the', 'and', 'for', 'are', 'but', 'not', 'all', 'can', 'was',
|
|
145
|
-
'has', 'had', 'been', 'have', 'with', 'this', 'that', 'from',
|
|
146
|
-
'use', 'using', 'used', 'will', 'would', 'should', 'also',
|
|
147
|
-
'decision', 'decided', 'recommend', 'instead', 'because',
|
|
148
|
-
'project', 'context', 'reasoning', 'about', 'which', 'when'
|
|
149
|
-
])
|
|
150
|
-
|
|
151
|
-
for (const d of decisions) {
|
|
152
|
-
const words = d.content.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/)
|
|
153
|
-
for (const w of words) {
|
|
154
|
-
if (w.length > 3 && !stopWords.has(w)) {
|
|
155
|
-
terms.add(w)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return terms
|
|
161
|
-
}
|
|
162
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Project Affinity
|
|
3
|
+
* Computes similarity between projects based on decisions, patterns, and tech stack
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Logger } from 'pino'
|
|
7
|
+
import type { CollectionManager } from '@/memory/chroma/collection-manager'
|
|
8
|
+
import type { EmbeddingProvider } from '@/memory/chroma/embeddings'
|
|
9
|
+
|
|
10
|
+
export interface ProjectAffinity {
|
|
11
|
+
projectA: string
|
|
12
|
+
projectB: string
|
|
13
|
+
similarity: number
|
|
14
|
+
sharedTopics: string[]
|
|
15
|
+
sharedTechnologies: string[]
|
|
16
|
+
decisionOverlap: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class AffinityCalculator {
|
|
20
|
+
private logger: Logger
|
|
21
|
+
private collections: CollectionManager
|
|
22
|
+
private embeddings?: EmbeddingProvider
|
|
23
|
+
|
|
24
|
+
constructor(logger: Logger, collections: CollectionManager, embeddings?: EmbeddingProvider) {
|
|
25
|
+
this.logger = logger.child({ component: 'affinity-calculator' })
|
|
26
|
+
this.collections = collections
|
|
27
|
+
this.embeddings = embeddings
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Calculate affinity between all pairs of projects
|
|
32
|
+
*/
|
|
33
|
+
async calculateAffinities(options: {
|
|
34
|
+
minDecisions?: number
|
|
35
|
+
} = {}): Promise<ProjectAffinity[]> {
|
|
36
|
+
const { minDecisions = 2 } = options
|
|
37
|
+
|
|
38
|
+
// Get all decisions grouped by project
|
|
39
|
+
const byProject = await this.getDecisionsByProject()
|
|
40
|
+
|
|
41
|
+
// Filter out projects with too few decisions
|
|
42
|
+
const projects = Array.from(byProject.entries())
|
|
43
|
+
.filter(([_, decisions]) => decisions.length >= minDecisions)
|
|
44
|
+
|
|
45
|
+
const affinities: ProjectAffinity[] = []
|
|
46
|
+
|
|
47
|
+
// Compare all pairs
|
|
48
|
+
for (let i = 0; i < projects.length; i++) {
|
|
49
|
+
for (let j = i + 1; j < projects.length; j++) {
|
|
50
|
+
const [projectA, decisionsA] = projects[i]
|
|
51
|
+
const [projectB, decisionsB] = projects[j]
|
|
52
|
+
|
|
53
|
+
const affinity = this.compareProjects(projectA, decisionsA, projectB, decisionsB)
|
|
54
|
+
if (affinity.similarity > 0.1) {
|
|
55
|
+
affinities.push(affinity)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return affinities.sort((a, b) => b.similarity - a.similarity)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Find most similar projects to a given project
|
|
65
|
+
*/
|
|
66
|
+
async findSimilarProjects(project: string, limit: number = 5): Promise<ProjectAffinity[]> {
|
|
67
|
+
const affinities = await this.calculateAffinities()
|
|
68
|
+
|
|
69
|
+
return affinities
|
|
70
|
+
.filter(a => a.projectA === project || a.projectB === project)
|
|
71
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
72
|
+
.slice(0, limit)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async getDecisionsByProject(): Promise<Map<string, Array<{ id: string; content: string; tags: string[] }>>> {
|
|
76
|
+
try {
|
|
77
|
+
const collection = await this.collections.getDecisions()
|
|
78
|
+
|
|
79
|
+
const results = await collection.get({
|
|
80
|
+
include: ['documents', 'metadatas']
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const byProject = new Map<string, Array<{ id: string; content: string; tags: string[] }>>()
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < results.ids.length; i++) {
|
|
86
|
+
const project = (results.metadatas![i] as any)?.project || ''
|
|
87
|
+
if (!project) continue
|
|
88
|
+
|
|
89
|
+
if (!byProject.has(project)) byProject.set(project, [])
|
|
90
|
+
|
|
91
|
+
byProject.get(project)!.push({
|
|
92
|
+
id: results.ids[i],
|
|
93
|
+
content: results.documents![i] as string || '',
|
|
94
|
+
tags: String((results.metadatas![i] as any)?.tags || '').split(',').filter(t => t.trim())
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return byProject
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.logger.warn({ error }, 'Failed to get decisions by project')
|
|
101
|
+
return new Map()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private compareProjects(
|
|
106
|
+
projectA: string,
|
|
107
|
+
decisionsA: Array<{ id: string; content: string; tags: string[] }>,
|
|
108
|
+
projectB: string,
|
|
109
|
+
decisionsB: Array<{ id: string; content: string; tags: string[] }>
|
|
110
|
+
): ProjectAffinity {
|
|
111
|
+
// Term-based similarity
|
|
112
|
+
const termsA = this.extractAllTerms(decisionsA)
|
|
113
|
+
const termsB = this.extractAllTerms(decisionsB)
|
|
114
|
+
|
|
115
|
+
const sharedTopics = Array.from(termsA).filter(t => termsB.has(t))
|
|
116
|
+
const allTopics = new Set([...termsA, ...termsB])
|
|
117
|
+
|
|
118
|
+
const topicSimilarity = allTopics.size > 0 ? sharedTopics.length / allTopics.size : 0
|
|
119
|
+
|
|
120
|
+
// Tag-based similarity
|
|
121
|
+
const tagsA = new Set(decisionsA.flatMap(d => d.tags))
|
|
122
|
+
const tagsB = new Set(decisionsB.flatMap(d => d.tags))
|
|
123
|
+
|
|
124
|
+
const sharedTags = Array.from(tagsA).filter(t => tagsB.has(t))
|
|
125
|
+
const allTags = new Set([...tagsA, ...tagsB])
|
|
126
|
+
const tagSimilarity = allTags.size > 0 ? sharedTags.length / allTags.size : 0
|
|
127
|
+
|
|
128
|
+
// Combined similarity
|
|
129
|
+
const similarity = topicSimilarity * 0.7 + tagSimilarity * 0.3
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
projectA,
|
|
133
|
+
projectB,
|
|
134
|
+
similarity,
|
|
135
|
+
sharedTopics: sharedTopics.slice(0, 20),
|
|
136
|
+
sharedTechnologies: sharedTags.slice(0, 10),
|
|
137
|
+
decisionOverlap: sharedTopics.length
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private extractAllTerms(decisions: Array<{ content: string }>): Set<string> {
|
|
142
|
+
const terms = new Set<string>()
|
|
143
|
+
const stopWords = new Set([
|
|
144
|
+
'the', 'and', 'for', 'are', 'but', 'not', 'all', 'can', 'was',
|
|
145
|
+
'has', 'had', 'been', 'have', 'with', 'this', 'that', 'from',
|
|
146
|
+
'use', 'using', 'used', 'will', 'would', 'should', 'also',
|
|
147
|
+
'decision', 'decided', 'recommend', 'instead', 'because',
|
|
148
|
+
'project', 'context', 'reasoning', 'about', 'which', 'when'
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
for (const d of decisions) {
|
|
152
|
+
const words = d.content.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/)
|
|
153
|
+
for (const w of words) {
|
|
154
|
+
if (w.length > 3 && !stopWords.has(w)) {
|
|
155
|
+
terms.add(w)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return terms
|
|
161
|
+
}
|
|
162
|
+
}
|