@soleri/core 2.0.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain/brain.d.ts +14 -50
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +207 -16
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +86 -0
- package/dist/brain/intelligence.d.ts.map +1 -0
- package/dist/brain/intelligence.js +771 -0
- package/dist/brain/intelligence.js.map +1 -0
- package/dist/brain/types.d.ts +197 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +2 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/cognee/client.d.ts +35 -0
- package/dist/cognee/client.d.ts.map +1 -0
- package/dist/cognee/client.js +291 -0
- package/dist/cognee/client.js.map +1 -0
- package/dist/cognee/types.d.ts +46 -0
- package/dist/cognee/types.d.ts.map +1 -0
- package/dist/cognee/types.js +3 -0
- package/dist/cognee/types.js.map +1 -0
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +29 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +142 -5
- package/dist/curator/curator.js.map +1 -1
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +9 -2
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +49 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +105 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +35 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +8 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +29 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +265 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +150 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +365 -2
- package/dist/planning/planner.js.map +1 -1
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +276 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +13 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +284 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +322 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +345 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +7 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +646 -15
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +59 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +59 -13
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +13 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +179 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +240 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +17 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +300 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +181 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +48 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +9 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +195 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/vault.d.ts +94 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +340 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-extra-ops.test.ts +420 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +828 -0
- package/src/__tests__/brain.test.ts +396 -27
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/cognee-client.test.ts +524 -0
- package/src/__tests__/core-ops.test.ts +341 -49
- package/src/__tests__/curator-extra-ops.test.ts +359 -0
- package/src/__tests__/curator.test.ts +126 -31
- package/src/__tests__/domain-ops.test.ts +111 -9
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +340 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +398 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +284 -0
- package/src/__tests__/planner.test.ts +331 -0
- package/src/__tests__/planning-extra-ops.test.ts +548 -0
- package/src/__tests__/project-ops.test.ts +367 -0
- package/src/__tests__/runtime.test.ts +13 -11
- package/src/__tests__/vault-extra-ops.test.ts +407 -0
- package/src/brain/brain.ts +308 -72
- package/src/brain/intelligence.ts +1230 -0
- package/src/brain/types.ts +214 -0
- package/src/cognee/client.ts +352 -0
- package/src/cognee/types.ts +62 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +265 -15
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +128 -3
- package/src/llm/llm-client.ts +18 -24
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +130 -0
- package/src/loop/types.ts +44 -0
- package/src/planning/gap-analysis.ts +506 -0
- package/src/planning/gap-types.ts +58 -0
- package/src/planning/planner.ts +478 -2
- package/src/project/project-registry.ts +358 -0
- package/src/project/types.ts +31 -0
- package/src/runtime/admin-extra-ops.ts +307 -0
- package/src/runtime/admin-ops.ts +329 -0
- package/src/runtime/capture-ops.ts +385 -0
- package/src/runtime/core-ops.ts +747 -26
- package/src/runtime/curator-extra-ops.ts +71 -0
- package/src/runtime/domain-ops.ts +65 -13
- package/src/runtime/grading-ops.ts +121 -0
- package/src/runtime/loop-ops.ts +194 -0
- package/src/runtime/memory-cross-project-ops.ts +192 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +272 -0
- package/src/runtime/planning-extra-ops.ts +327 -0
- package/src/runtime/project-ops.ts +196 -0
- package/src/runtime/runtime.ts +54 -1
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-extra-ops.ts +225 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/vault.ts +412 -1
package/src/brain/brain.ts
CHANGED
|
@@ -8,59 +8,30 @@ import {
|
|
|
8
8
|
cosineSimilarity,
|
|
9
9
|
jaccardSimilarity,
|
|
10
10
|
} from '../text/similarity.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SearchOptions {
|
|
38
|
-
domain?: string;
|
|
39
|
-
type?: string;
|
|
40
|
-
severity?: string;
|
|
41
|
-
limit?: number;
|
|
42
|
-
tags?: string[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface CaptureResult {
|
|
46
|
-
captured: boolean;
|
|
47
|
-
id: string;
|
|
48
|
-
autoTags: string[];
|
|
49
|
-
duplicate?: { id: string; similarity: number };
|
|
50
|
-
blocked?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface BrainStats {
|
|
54
|
-
vocabularySize: number;
|
|
55
|
-
feedbackCount: number;
|
|
56
|
-
weights: ScoringWeights;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface QueryContext {
|
|
60
|
-
query: string;
|
|
61
|
-
domain?: string;
|
|
62
|
-
tags?: string[];
|
|
63
|
-
}
|
|
11
|
+
import type { CogneeClient } from '../cognee/client.js';
|
|
12
|
+
import type {
|
|
13
|
+
ScoringWeights,
|
|
14
|
+
ScoreBreakdown,
|
|
15
|
+
RankedResult,
|
|
16
|
+
SearchOptions,
|
|
17
|
+
CaptureResult,
|
|
18
|
+
BrainStats,
|
|
19
|
+
QueryContext,
|
|
20
|
+
FeedbackInput,
|
|
21
|
+
FeedbackEntry,
|
|
22
|
+
FeedbackStats,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
|
|
25
|
+
// Re-export types for backward compatibility
|
|
26
|
+
export type {
|
|
27
|
+
ScoringWeights,
|
|
28
|
+
ScoreBreakdown,
|
|
29
|
+
RankedResult,
|
|
30
|
+
SearchOptions,
|
|
31
|
+
CaptureResult,
|
|
32
|
+
BrainStats,
|
|
33
|
+
QueryContext,
|
|
34
|
+
} from './types.js';
|
|
64
35
|
|
|
65
36
|
// ─── Severity scoring ──────────────────────────────────────────────
|
|
66
37
|
|
|
@@ -74,12 +45,22 @@ const SEVERITY_SCORES: Record<string, number> = {
|
|
|
74
45
|
|
|
75
46
|
const DEFAULT_WEIGHTS: ScoringWeights = {
|
|
76
47
|
semantic: 0.4,
|
|
48
|
+
vector: 0.0,
|
|
77
49
|
severity: 0.15,
|
|
78
50
|
recency: 0.15,
|
|
79
51
|
tagOverlap: 0.15,
|
|
80
52
|
domainMatch: 0.15,
|
|
81
53
|
};
|
|
82
54
|
|
|
55
|
+
const COGNEE_WEIGHTS: ScoringWeights = {
|
|
56
|
+
semantic: 0.25,
|
|
57
|
+
vector: 0.35,
|
|
58
|
+
severity: 0.1,
|
|
59
|
+
recency: 0.1,
|
|
60
|
+
tagOverlap: 0.1,
|
|
61
|
+
domainMatch: 0.1,
|
|
62
|
+
};
|
|
63
|
+
|
|
83
64
|
const WEIGHT_BOUND = 0.15;
|
|
84
65
|
const FEEDBACK_THRESHOLD = 30;
|
|
85
66
|
const DUPLICATE_BLOCK_THRESHOLD = 0.8;
|
|
@@ -88,16 +69,18 @@ const RECENCY_HALF_LIFE_DAYS = 365;
|
|
|
88
69
|
|
|
89
70
|
export class Brain {
|
|
90
71
|
private vault: Vault;
|
|
72
|
+
private cognee: CogneeClient | undefined;
|
|
91
73
|
private vocabulary: Map<string, number> = new Map();
|
|
92
74
|
private weights: ScoringWeights = { ...DEFAULT_WEIGHTS };
|
|
93
75
|
|
|
94
|
-
constructor(vault: Vault) {
|
|
76
|
+
constructor(vault: Vault, cognee?: CogneeClient) {
|
|
95
77
|
this.vault = vault;
|
|
78
|
+
this.cognee = cognee;
|
|
96
79
|
this.rebuildVocabulary();
|
|
97
80
|
this.recomputeWeights();
|
|
98
81
|
}
|
|
99
82
|
|
|
100
|
-
intelligentSearch(query: string, options?: SearchOptions): RankedResult[] {
|
|
83
|
+
async intelligentSearch(query: string, options?: SearchOptions): Promise<RankedResult[]> {
|
|
101
84
|
const limit = options?.limit ?? 10;
|
|
102
85
|
const rawResults = this.vault.search(query, {
|
|
103
86
|
domain: options?.domain,
|
|
@@ -106,6 +89,97 @@ export class Brain {
|
|
|
106
89
|
limit: Math.max(limit * 3, 30),
|
|
107
90
|
});
|
|
108
91
|
|
|
92
|
+
// Cognee vector search (parallel, with timeout fallback)
|
|
93
|
+
let cogneeScoreMap: Map<string, number> = new Map();
|
|
94
|
+
const cogneeAvailable = this.cognee?.isAvailable ?? false;
|
|
95
|
+
if (cogneeAvailable && this.cognee) {
|
|
96
|
+
try {
|
|
97
|
+
const cogneeResults = await this.cognee.search(query, { limit: Math.max(limit * 2, 20) });
|
|
98
|
+
|
|
99
|
+
// Build title → entryIds reverse index from FTS results for text-based matching.
|
|
100
|
+
// Cognee assigns its own UUIDs to chunks and may strip embedded metadata during
|
|
101
|
+
// chunking, so we need multiple strategies to cross-reference results.
|
|
102
|
+
// Multiple entries can share a title, so map to arrays of IDs.
|
|
103
|
+
const titleToIds = new Map<string, string[]>();
|
|
104
|
+
for (const r of rawResults) {
|
|
105
|
+
const key = r.entry.title.toLowerCase().trim();
|
|
106
|
+
const ids = titleToIds.get(key) ?? [];
|
|
107
|
+
ids.push(r.entry.id);
|
|
108
|
+
titleToIds.set(key, ids);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const vaultIdPattern = /\[vault-id:([^\]]+)\]/;
|
|
112
|
+
const unmatchedCogneeResults: Array<{ text: string; score: number }> = [];
|
|
113
|
+
|
|
114
|
+
for (const cr of cogneeResults) {
|
|
115
|
+
const text = cr.text ?? '';
|
|
116
|
+
|
|
117
|
+
// Strategy 1: Extract vault ID from [vault-id:XXX] prefix (if Cognee preserved it)
|
|
118
|
+
const vaultIdMatch = text.match(vaultIdPattern);
|
|
119
|
+
if (vaultIdMatch) {
|
|
120
|
+
const vaultId = vaultIdMatch[1];
|
|
121
|
+
cogneeScoreMap.set(vaultId, Math.max(cogneeScoreMap.get(vaultId) ?? 0, cr.score));
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Strategy 2: Match first line of chunk text against known entry titles.
|
|
126
|
+
// serializeEntry() puts the title on the first line after the [vault-id:] prefix,
|
|
127
|
+
// and Cognee's chunking typically preserves this as the chunk start.
|
|
128
|
+
const firstLine = text.split('\n')[0]?.trim().toLowerCase() ?? '';
|
|
129
|
+
const matchedIds = firstLine ? titleToIds.get(firstLine) : undefined;
|
|
130
|
+
if (matchedIds) {
|
|
131
|
+
for (const id of matchedIds) {
|
|
132
|
+
cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, cr.score));
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Strategy 3: Check if any known title appears as a substring in the chunk.
|
|
138
|
+
// Handles cases where the title isn't on the first line (mid-document chunks).
|
|
139
|
+
const textLower = text.toLowerCase();
|
|
140
|
+
let found = false;
|
|
141
|
+
for (const [title, ids] of titleToIds) {
|
|
142
|
+
if (title.length >= 8 && textLower.includes(title)) {
|
|
143
|
+
for (const id of ids) {
|
|
144
|
+
cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, cr.score));
|
|
145
|
+
}
|
|
146
|
+
found = true;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!found && text.length > 0) {
|
|
151
|
+
unmatchedCogneeResults.push({ text, score: cr.score });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Strategy 4: For Cognee-only semantic matches (not in FTS results),
|
|
156
|
+
// use the first line as a vault FTS query to find the source entry.
|
|
157
|
+
// Preserve caller filters (domain/type/severity) to avoid reintroducing
|
|
158
|
+
// entries the original query excluded.
|
|
159
|
+
for (const unmatched of unmatchedCogneeResults) {
|
|
160
|
+
const searchTerm = unmatched.text.split('\n')[0]?.trim();
|
|
161
|
+
if (!searchTerm || searchTerm.length < 3) continue;
|
|
162
|
+
const vaultHits = this.vault.search(searchTerm, {
|
|
163
|
+
domain: options?.domain,
|
|
164
|
+
type: options?.type,
|
|
165
|
+
severity: options?.severity,
|
|
166
|
+
limit: 1,
|
|
167
|
+
});
|
|
168
|
+
if (vaultHits.length > 0) {
|
|
169
|
+
const id = vaultHits[0].entry.id;
|
|
170
|
+
cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, unmatched.score));
|
|
171
|
+
// Also add to FTS results pool if not already present
|
|
172
|
+
if (!rawResults.some((r) => r.entry.id === id)) {
|
|
173
|
+
rawResults.push(vaultHits[0]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
// Cognee failed — fall back to FTS5 only
|
|
179
|
+
cogneeScoreMap = new Map();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
109
183
|
if (rawResults.length === 0) return [];
|
|
110
184
|
|
|
111
185
|
const queryTokens = tokenize(query);
|
|
@@ -113,9 +187,22 @@ export class Brain {
|
|
|
113
187
|
const queryDomain = options?.domain;
|
|
114
188
|
const now = Math.floor(Date.now() / 1000);
|
|
115
189
|
|
|
190
|
+
// Use cognee-aware weights only if at least one ranked candidate has a vector score
|
|
191
|
+
const hasVectorCandidate = rawResults.some((r) => cogneeScoreMap.has(r.entry.id));
|
|
192
|
+
const activeWeights = hasVectorCandidate ? this.getCogneeWeights() : this.weights;
|
|
193
|
+
|
|
116
194
|
const ranked = rawResults.map((result) => {
|
|
117
195
|
const entry = result.entry;
|
|
118
|
-
const
|
|
196
|
+
const vectorScore = cogneeScoreMap.get(entry.id) ?? 0;
|
|
197
|
+
const breakdown = this.scoreEntry(
|
|
198
|
+
entry,
|
|
199
|
+
queryTokens,
|
|
200
|
+
queryTags,
|
|
201
|
+
queryDomain,
|
|
202
|
+
now,
|
|
203
|
+
vectorScore,
|
|
204
|
+
activeWeights,
|
|
205
|
+
);
|
|
119
206
|
return { entry, score: breakdown.total, breakdown };
|
|
120
207
|
});
|
|
121
208
|
|
|
@@ -166,6 +253,11 @@ export class Brain {
|
|
|
166
253
|
this.vault.add(fullEntry);
|
|
167
254
|
this.updateVocabularyIncremental(fullEntry);
|
|
168
255
|
|
|
256
|
+
// Fire-and-forget Cognee sync
|
|
257
|
+
if (this.cognee?.isAvailable) {
|
|
258
|
+
this.cognee.addEntries([fullEntry]).catch(() => {});
|
|
259
|
+
}
|
|
260
|
+
|
|
169
261
|
const result: CaptureResult = {
|
|
170
262
|
captured: true,
|
|
171
263
|
id: entry.id,
|
|
@@ -179,23 +271,144 @@ export class Brain {
|
|
|
179
271
|
return result;
|
|
180
272
|
}
|
|
181
273
|
|
|
182
|
-
recordFeedback(query: string, entryId: string, action: 'accepted' | 'dismissed'): void
|
|
274
|
+
recordFeedback(query: string, entryId: string, action: 'accepted' | 'dismissed'): void;
|
|
275
|
+
recordFeedback(input: FeedbackInput): FeedbackEntry;
|
|
276
|
+
recordFeedback(
|
|
277
|
+
queryOrInput: string | FeedbackInput,
|
|
278
|
+
entryId?: string,
|
|
279
|
+
action?: 'accepted' | 'dismissed',
|
|
280
|
+
): void | FeedbackEntry {
|
|
183
281
|
const db = this.vault.getDb();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
282
|
+
|
|
283
|
+
// Normalize to FeedbackInput
|
|
284
|
+
const input: FeedbackInput =
|
|
285
|
+
typeof queryOrInput === 'string'
|
|
286
|
+
? { query: queryOrInput, entryId: entryId!, action: action! }
|
|
287
|
+
: queryOrInput;
|
|
288
|
+
|
|
289
|
+
db.prepare(
|
|
290
|
+
`INSERT INTO brain_feedback (query, entry_id, action, source, confidence, duration, context, reason)
|
|
291
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
292
|
+
).run(
|
|
293
|
+
input.query,
|
|
294
|
+
input.entryId,
|
|
295
|
+
input.action,
|
|
296
|
+
input.source ?? 'search',
|
|
297
|
+
input.confidence ?? 0.6,
|
|
298
|
+
input.duration ?? null,
|
|
299
|
+
input.context ?? '{}',
|
|
300
|
+
input.reason ?? null,
|
|
188
301
|
);
|
|
189
302
|
this.recomputeWeights();
|
|
303
|
+
|
|
304
|
+
// Return FeedbackEntry only for the object overload
|
|
305
|
+
if (typeof queryOrInput !== 'string') {
|
|
306
|
+
const row = db
|
|
307
|
+
.prepare(
|
|
308
|
+
'SELECT * FROM brain_feedback WHERE query = ? AND entry_id = ? ORDER BY id DESC LIMIT 1',
|
|
309
|
+
)
|
|
310
|
+
.get(input.query, input.entryId) as {
|
|
311
|
+
id: number;
|
|
312
|
+
query: string;
|
|
313
|
+
entry_id: string;
|
|
314
|
+
action: string;
|
|
315
|
+
source: string;
|
|
316
|
+
confidence: number;
|
|
317
|
+
duration: number | null;
|
|
318
|
+
context: string;
|
|
319
|
+
reason: string | null;
|
|
320
|
+
created_at: number;
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
id: row.id,
|
|
324
|
+
query: row.query,
|
|
325
|
+
entryId: row.entry_id,
|
|
326
|
+
action: row.action as FeedbackEntry['action'],
|
|
327
|
+
source: row.source as FeedbackEntry['source'],
|
|
328
|
+
confidence: row.confidence,
|
|
329
|
+
duration: row.duration,
|
|
330
|
+
context: row.context,
|
|
331
|
+
reason: row.reason,
|
|
332
|
+
createdAt: row.created_at,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
190
335
|
}
|
|
191
336
|
|
|
192
|
-
|
|
337
|
+
getFeedbackStats(): FeedbackStats {
|
|
338
|
+
const db = this.vault.getDb();
|
|
339
|
+
|
|
340
|
+
const total = (
|
|
341
|
+
db.prepare('SELECT COUNT(*) as count FROM brain_feedback').get() as { count: number }
|
|
342
|
+
).count;
|
|
343
|
+
|
|
344
|
+
const byAction: Record<string, number> = {};
|
|
345
|
+
const actionRows = db
|
|
346
|
+
.prepare('SELECT action, COUNT(*) as count FROM brain_feedback GROUP BY action')
|
|
347
|
+
.all() as Array<{ action: string; count: number }>;
|
|
348
|
+
for (const row of actionRows) {
|
|
349
|
+
byAction[row.action] = row.count;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const bySource: Record<string, number> = {};
|
|
353
|
+
const sourceRows = db
|
|
354
|
+
.prepare('SELECT source, COUNT(*) as count FROM brain_feedback GROUP BY source')
|
|
355
|
+
.all() as Array<{ source: string; count: number }>;
|
|
356
|
+
for (const row of sourceRows) {
|
|
357
|
+
bySource[row.source] = row.count;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const accepted = byAction['accepted'] ?? 0;
|
|
361
|
+
const acceptanceRate = total > 0 ? accepted / total : 0;
|
|
362
|
+
|
|
363
|
+
const avgConf =
|
|
364
|
+
(
|
|
365
|
+
db.prepare('SELECT AVG(confidence) as avg FROM brain_feedback').get() as {
|
|
366
|
+
avg: number | null;
|
|
367
|
+
}
|
|
368
|
+
).avg ?? 0;
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
total,
|
|
372
|
+
byAction,
|
|
373
|
+
bySource,
|
|
374
|
+
acceptanceRate,
|
|
375
|
+
averageConfidence: avgConf,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async getRelevantPatterns(context: QueryContext): Promise<RankedResult[]> {
|
|
193
380
|
return this.intelligentSearch(context.query, {
|
|
194
381
|
domain: context.domain,
|
|
195
382
|
tags: context.tags,
|
|
196
383
|
});
|
|
197
384
|
}
|
|
198
385
|
|
|
386
|
+
async syncToCognee(): Promise<{ synced: number; cognified: boolean }> {
|
|
387
|
+
if (!this.cognee?.isAvailable) return { synced: 0, cognified: false };
|
|
388
|
+
|
|
389
|
+
const batchSize = 1000;
|
|
390
|
+
let offset = 0;
|
|
391
|
+
let totalSynced = 0;
|
|
392
|
+
|
|
393
|
+
while (true) {
|
|
394
|
+
const batch = this.vault.list({ limit: batchSize, offset });
|
|
395
|
+
if (batch.length === 0) break;
|
|
396
|
+
|
|
397
|
+
const { added } = await this.cognee.addEntries(batch);
|
|
398
|
+
totalSynced += added;
|
|
399
|
+
offset += batch.length;
|
|
400
|
+
|
|
401
|
+
if (batch.length < batchSize) break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (totalSynced === 0) return { synced: 0, cognified: false };
|
|
405
|
+
|
|
406
|
+
let cognified = false;
|
|
407
|
+
const cognifyResult = await this.cognee.cognify();
|
|
408
|
+
cognified = cognifyResult.status === 'ok';
|
|
409
|
+
return { synced: totalSynced, cognified };
|
|
410
|
+
}
|
|
411
|
+
|
|
199
412
|
rebuildVocabulary(): void {
|
|
200
413
|
const entries = this.vault.list({ limit: 100000 });
|
|
201
414
|
const docCount = entries.length;
|
|
@@ -249,7 +462,11 @@ export class Brain {
|
|
|
249
462
|
queryTags: string[],
|
|
250
463
|
queryDomain: string | undefined,
|
|
251
464
|
now: number,
|
|
465
|
+
vectorScore: number = 0,
|
|
466
|
+
activeWeights?: ScoringWeights,
|
|
252
467
|
): ScoreBreakdown {
|
|
468
|
+
const w = activeWeights ?? this.weights;
|
|
469
|
+
|
|
253
470
|
let semantic = 0;
|
|
254
471
|
if (this.vocabulary.size > 0 && queryTokens.length > 0) {
|
|
255
472
|
const entryText = [
|
|
@@ -274,14 +491,17 @@ export class Brain {
|
|
|
274
491
|
|
|
275
492
|
const domainMatch = queryDomain && entry.domain === queryDomain ? 1.0 : 0;
|
|
276
493
|
|
|
277
|
-
const
|
|
278
|
-
this.weights.semantic * semantic +
|
|
279
|
-
this.weights.severity * severity +
|
|
280
|
-
this.weights.recency * recency +
|
|
281
|
-
this.weights.tagOverlap * tagOverlap +
|
|
282
|
-
this.weights.domainMatch * domainMatch;
|
|
494
|
+
const vector = vectorScore;
|
|
283
495
|
|
|
284
|
-
|
|
496
|
+
const total =
|
|
497
|
+
w.semantic * semantic +
|
|
498
|
+
w.vector * vector +
|
|
499
|
+
w.severity * severity +
|
|
500
|
+
w.recency * recency +
|
|
501
|
+
w.tagOverlap * tagOverlap +
|
|
502
|
+
w.domainMatch * domainMatch;
|
|
503
|
+
|
|
504
|
+
return { semantic, vector, severity, recency, tagOverlap, domainMatch, total };
|
|
285
505
|
}
|
|
286
506
|
|
|
287
507
|
private generateTags(title: string, description: string, context?: string): string[] {
|
|
@@ -375,10 +595,17 @@ export class Brain {
|
|
|
375
595
|
tx();
|
|
376
596
|
}
|
|
377
597
|
|
|
598
|
+
private getCogneeWeights(): ScoringWeights {
|
|
599
|
+
return { ...COGNEE_WEIGHTS };
|
|
600
|
+
}
|
|
601
|
+
|
|
378
602
|
private recomputeWeights(): void {
|
|
379
603
|
const db = this.vault.getDb();
|
|
604
|
+
// Exclude 'failed' from weight computation — system errors don't indicate relevance
|
|
380
605
|
const feedbackCount = (
|
|
381
|
-
db.prepare(
|
|
606
|
+
db.prepare("SELECT COUNT(*) as count FROM brain_feedback WHERE action != 'failed'").get() as {
|
|
607
|
+
count: number;
|
|
608
|
+
}
|
|
382
609
|
).count;
|
|
383
610
|
if (feedbackCount < FEEDBACK_THRESHOLD) {
|
|
384
611
|
this.weights = { ...DEFAULT_WEIGHTS };
|
|
@@ -390,7 +617,13 @@ export class Brain {
|
|
|
390
617
|
.prepare("SELECT COUNT(*) as count FROM brain_feedback WHERE action = 'accepted'")
|
|
391
618
|
.get() as { count: number }
|
|
392
619
|
).count;
|
|
393
|
-
|
|
620
|
+
// 'modified' counts as 0.5 positive — user adjusted but didn't dismiss
|
|
621
|
+
const modified = (
|
|
622
|
+
db
|
|
623
|
+
.prepare("SELECT COUNT(*) as count FROM brain_feedback WHERE action = 'modified'")
|
|
624
|
+
.get() as { count: number }
|
|
625
|
+
).count;
|
|
626
|
+
const acceptRate = feedbackCount > 0 ? (accepted + modified * 0.5) / feedbackCount : 0.5;
|
|
394
627
|
|
|
395
628
|
const semanticDelta = (acceptRate - 0.5) * WEIGHT_BOUND * 2;
|
|
396
629
|
|
|
@@ -401,7 +634,10 @@ export class Brain {
|
|
|
401
634
|
DEFAULT_WEIGHTS.semantic + WEIGHT_BOUND,
|
|
402
635
|
);
|
|
403
636
|
|
|
404
|
-
|
|
637
|
+
// vector stays 0 in base weights (only active during hybrid search)
|
|
638
|
+
newWeights.vector = 0;
|
|
639
|
+
|
|
640
|
+
const remaining = 1.0 - newWeights.semantic - newWeights.vector;
|
|
405
641
|
const otherSum =
|
|
406
642
|
DEFAULT_WEIGHTS.severity +
|
|
407
643
|
DEFAULT_WEIGHTS.recency +
|