@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.
Files changed (226) hide show
  1. package/dist/brain/brain.d.ts +14 -50
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +207 -16
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +86 -0
  6. package/dist/brain/intelligence.d.ts.map +1 -0
  7. package/dist/brain/intelligence.js +771 -0
  8. package/dist/brain/intelligence.js.map +1 -0
  9. package/dist/brain/types.d.ts +197 -0
  10. package/dist/brain/types.d.ts.map +1 -0
  11. package/dist/brain/types.js +2 -0
  12. package/dist/brain/types.js.map +1 -0
  13. package/dist/cognee/client.d.ts +35 -0
  14. package/dist/cognee/client.d.ts.map +1 -0
  15. package/dist/cognee/client.js +291 -0
  16. package/dist/cognee/client.js.map +1 -0
  17. package/dist/cognee/types.d.ts +46 -0
  18. package/dist/cognee/types.d.ts.map +1 -0
  19. package/dist/cognee/types.js +3 -0
  20. package/dist/cognee/types.js.map +1 -0
  21. package/dist/control/identity-manager.d.ts +22 -0
  22. package/dist/control/identity-manager.d.ts.map +1 -0
  23. package/dist/control/identity-manager.js +233 -0
  24. package/dist/control/identity-manager.js.map +1 -0
  25. package/dist/control/intent-router.d.ts +32 -0
  26. package/dist/control/intent-router.d.ts.map +1 -0
  27. package/dist/control/intent-router.js +242 -0
  28. package/dist/control/intent-router.js.map +1 -0
  29. package/dist/control/types.d.ts +68 -0
  30. package/dist/control/types.d.ts.map +1 -0
  31. package/dist/control/types.js +9 -0
  32. package/dist/control/types.js.map +1 -0
  33. package/dist/curator/curator.d.ts +29 -0
  34. package/dist/curator/curator.d.ts.map +1 -1
  35. package/dist/curator/curator.js +142 -5
  36. package/dist/curator/curator.js.map +1 -1
  37. package/dist/facades/types.d.ts +1 -1
  38. package/dist/governance/governance.d.ts +42 -0
  39. package/dist/governance/governance.d.ts.map +1 -0
  40. package/dist/governance/governance.js +488 -0
  41. package/dist/governance/governance.js.map +1 -0
  42. package/dist/governance/index.d.ts +3 -0
  43. package/dist/governance/index.d.ts.map +1 -0
  44. package/dist/governance/index.js +2 -0
  45. package/dist/governance/index.js.map +1 -0
  46. package/dist/governance/types.d.ts +102 -0
  47. package/dist/governance/types.d.ts.map +1 -0
  48. package/dist/governance/types.js +3 -0
  49. package/dist/governance/types.js.map +1 -0
  50. package/dist/index.d.ts +35 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +32 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/llm/llm-client.d.ts.map +1 -1
  55. package/dist/llm/llm-client.js +9 -2
  56. package/dist/llm/llm-client.js.map +1 -1
  57. package/dist/logging/logger.d.ts +37 -0
  58. package/dist/logging/logger.d.ts.map +1 -0
  59. package/dist/logging/logger.js +145 -0
  60. package/dist/logging/logger.js.map +1 -0
  61. package/dist/logging/types.d.ts +19 -0
  62. package/dist/logging/types.d.ts.map +1 -0
  63. package/dist/logging/types.js +2 -0
  64. package/dist/logging/types.js.map +1 -0
  65. package/dist/loop/loop-manager.d.ts +49 -0
  66. package/dist/loop/loop-manager.d.ts.map +1 -0
  67. package/dist/loop/loop-manager.js +105 -0
  68. package/dist/loop/loop-manager.js.map +1 -0
  69. package/dist/loop/types.d.ts +35 -0
  70. package/dist/loop/types.d.ts.map +1 -0
  71. package/dist/loop/types.js +8 -0
  72. package/dist/loop/types.js.map +1 -0
  73. package/dist/planning/gap-analysis.d.ts +29 -0
  74. package/dist/planning/gap-analysis.d.ts.map +1 -0
  75. package/dist/planning/gap-analysis.js +265 -0
  76. package/dist/planning/gap-analysis.js.map +1 -0
  77. package/dist/planning/gap-types.d.ts +29 -0
  78. package/dist/planning/gap-types.d.ts.map +1 -0
  79. package/dist/planning/gap-types.js +28 -0
  80. package/dist/planning/gap-types.js.map +1 -0
  81. package/dist/planning/planner.d.ts +150 -1
  82. package/dist/planning/planner.d.ts.map +1 -1
  83. package/dist/planning/planner.js +365 -2
  84. package/dist/planning/planner.js.map +1 -1
  85. package/dist/project/project-registry.d.ts +79 -0
  86. package/dist/project/project-registry.d.ts.map +1 -0
  87. package/dist/project/project-registry.js +276 -0
  88. package/dist/project/project-registry.js.map +1 -0
  89. package/dist/project/types.d.ts +28 -0
  90. package/dist/project/types.d.ts.map +1 -0
  91. package/dist/project/types.js +5 -0
  92. package/dist/project/types.js.map +1 -0
  93. package/dist/runtime/admin-extra-ops.d.ts +13 -0
  94. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  95. package/dist/runtime/admin-extra-ops.js +284 -0
  96. package/dist/runtime/admin-extra-ops.js.map +1 -0
  97. package/dist/runtime/admin-ops.d.ts +15 -0
  98. package/dist/runtime/admin-ops.d.ts.map +1 -0
  99. package/dist/runtime/admin-ops.js +322 -0
  100. package/dist/runtime/admin-ops.js.map +1 -0
  101. package/dist/runtime/capture-ops.d.ts +15 -0
  102. package/dist/runtime/capture-ops.d.ts.map +1 -0
  103. package/dist/runtime/capture-ops.js +345 -0
  104. package/dist/runtime/capture-ops.js.map +1 -0
  105. package/dist/runtime/core-ops.d.ts +7 -3
  106. package/dist/runtime/core-ops.d.ts.map +1 -1
  107. package/dist/runtime/core-ops.js +646 -15
  108. package/dist/runtime/core-ops.js.map +1 -1
  109. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  110. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  111. package/dist/runtime/curator-extra-ops.js +59 -0
  112. package/dist/runtime/curator-extra-ops.js.map +1 -0
  113. package/dist/runtime/domain-ops.d.ts.map +1 -1
  114. package/dist/runtime/domain-ops.js +59 -13
  115. package/dist/runtime/domain-ops.js.map +1 -1
  116. package/dist/runtime/grading-ops.d.ts +14 -0
  117. package/dist/runtime/grading-ops.d.ts.map +1 -0
  118. package/dist/runtime/grading-ops.js +105 -0
  119. package/dist/runtime/grading-ops.js.map +1 -0
  120. package/dist/runtime/loop-ops.d.ts +13 -0
  121. package/dist/runtime/loop-ops.d.ts.map +1 -0
  122. package/dist/runtime/loop-ops.js +179 -0
  123. package/dist/runtime/loop-ops.js.map +1 -0
  124. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  125. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  126. package/dist/runtime/memory-cross-project-ops.js +165 -0
  127. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  128. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  129. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  130. package/dist/runtime/memory-extra-ops.js +173 -0
  131. package/dist/runtime/memory-extra-ops.js.map +1 -0
  132. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  133. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  134. package/dist/runtime/orchestrate-ops.js +240 -0
  135. package/dist/runtime/orchestrate-ops.js.map +1 -0
  136. package/dist/runtime/planning-extra-ops.d.ts +17 -0
  137. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  138. package/dist/runtime/planning-extra-ops.js +300 -0
  139. package/dist/runtime/planning-extra-ops.js.map +1 -0
  140. package/dist/runtime/project-ops.d.ts +15 -0
  141. package/dist/runtime/project-ops.d.ts.map +1 -0
  142. package/dist/runtime/project-ops.js +181 -0
  143. package/dist/runtime/project-ops.js.map +1 -0
  144. package/dist/runtime/runtime.d.ts.map +1 -1
  145. package/dist/runtime/runtime.js +48 -1
  146. package/dist/runtime/runtime.js.map +1 -1
  147. package/dist/runtime/types.d.ts +23 -0
  148. package/dist/runtime/types.d.ts.map +1 -1
  149. package/dist/runtime/vault-extra-ops.d.ts +9 -0
  150. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  151. package/dist/runtime/vault-extra-ops.js +195 -0
  152. package/dist/runtime/vault-extra-ops.js.map +1 -0
  153. package/dist/telemetry/telemetry.d.ts +48 -0
  154. package/dist/telemetry/telemetry.d.ts.map +1 -0
  155. package/dist/telemetry/telemetry.js +87 -0
  156. package/dist/telemetry/telemetry.js.map +1 -0
  157. package/dist/vault/vault.d.ts +94 -0
  158. package/dist/vault/vault.d.ts.map +1 -1
  159. package/dist/vault/vault.js +340 -1
  160. package/dist/vault/vault.js.map +1 -1
  161. package/package.json +1 -1
  162. package/src/__tests__/admin-extra-ops.test.ts +420 -0
  163. package/src/__tests__/admin-ops.test.ts +271 -0
  164. package/src/__tests__/brain-intelligence.test.ts +828 -0
  165. package/src/__tests__/brain.test.ts +396 -27
  166. package/src/__tests__/capture-ops.test.ts +509 -0
  167. package/src/__tests__/cognee-client.test.ts +524 -0
  168. package/src/__tests__/core-ops.test.ts +341 -49
  169. package/src/__tests__/curator-extra-ops.test.ts +359 -0
  170. package/src/__tests__/curator.test.ts +126 -31
  171. package/src/__tests__/domain-ops.test.ts +111 -9
  172. package/src/__tests__/governance.test.ts +522 -0
  173. package/src/__tests__/grading-ops.test.ts +340 -0
  174. package/src/__tests__/identity-manager.test.ts +243 -0
  175. package/src/__tests__/intent-router.test.ts +222 -0
  176. package/src/__tests__/logger.test.ts +200 -0
  177. package/src/__tests__/loop-ops.test.ts +398 -0
  178. package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
  179. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  180. package/src/__tests__/orchestrate-ops.test.ts +284 -0
  181. package/src/__tests__/planner.test.ts +331 -0
  182. package/src/__tests__/planning-extra-ops.test.ts +548 -0
  183. package/src/__tests__/project-ops.test.ts +367 -0
  184. package/src/__tests__/runtime.test.ts +13 -11
  185. package/src/__tests__/vault-extra-ops.test.ts +407 -0
  186. package/src/brain/brain.ts +308 -72
  187. package/src/brain/intelligence.ts +1230 -0
  188. package/src/brain/types.ts +214 -0
  189. package/src/cognee/client.ts +352 -0
  190. package/src/cognee/types.ts +62 -0
  191. package/src/control/identity-manager.ts +354 -0
  192. package/src/control/intent-router.ts +326 -0
  193. package/src/control/types.ts +102 -0
  194. package/src/curator/curator.ts +265 -15
  195. package/src/governance/governance.ts +698 -0
  196. package/src/governance/index.ts +18 -0
  197. package/src/governance/types.ts +111 -0
  198. package/src/index.ts +128 -3
  199. package/src/llm/llm-client.ts +18 -24
  200. package/src/logging/logger.ts +154 -0
  201. package/src/logging/types.ts +21 -0
  202. package/src/loop/loop-manager.ts +130 -0
  203. package/src/loop/types.ts +44 -0
  204. package/src/planning/gap-analysis.ts +506 -0
  205. package/src/planning/gap-types.ts +58 -0
  206. package/src/planning/planner.ts +478 -2
  207. package/src/project/project-registry.ts +358 -0
  208. package/src/project/types.ts +31 -0
  209. package/src/runtime/admin-extra-ops.ts +307 -0
  210. package/src/runtime/admin-ops.ts +329 -0
  211. package/src/runtime/capture-ops.ts +385 -0
  212. package/src/runtime/core-ops.ts +747 -26
  213. package/src/runtime/curator-extra-ops.ts +71 -0
  214. package/src/runtime/domain-ops.ts +65 -13
  215. package/src/runtime/grading-ops.ts +121 -0
  216. package/src/runtime/loop-ops.ts +194 -0
  217. package/src/runtime/memory-cross-project-ops.ts +192 -0
  218. package/src/runtime/memory-extra-ops.ts +186 -0
  219. package/src/runtime/orchestrate-ops.ts +272 -0
  220. package/src/runtime/planning-extra-ops.ts +327 -0
  221. package/src/runtime/project-ops.ts +196 -0
  222. package/src/runtime/runtime.ts +54 -1
  223. package/src/runtime/types.ts +23 -0
  224. package/src/runtime/vault-extra-ops.ts +225 -0
  225. package/src/telemetry/telemetry.ts +118 -0
  226. package/src/vault/vault.ts +412 -1
@@ -8,59 +8,30 @@ import {
8
8
  cosineSimilarity,
9
9
  jaccardSimilarity,
10
10
  } from '../text/similarity.js';
11
-
12
- // ─── Types ───────────────────────────────────────────────────────────
13
-
14
- export interface ScoringWeights {
15
- semantic: number;
16
- severity: number;
17
- recency: number;
18
- tagOverlap: number;
19
- domainMatch: number;
20
- }
21
-
22
- export interface ScoreBreakdown {
23
- semantic: number;
24
- severity: number;
25
- recency: number;
26
- tagOverlap: number;
27
- domainMatch: number;
28
- total: number;
29
- }
30
-
31
- export interface RankedResult {
32
- entry: IntelligenceEntry;
33
- score: number;
34
- breakdown: ScoreBreakdown;
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 breakdown = this.scoreEntry(entry, queryTokens, queryTags, queryDomain, now);
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
- db.prepare('INSERT INTO brain_feedback (query, entry_id, action) VALUES (?, ?, ?)').run(
185
- query,
186
- entryId,
187
- action,
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
- getRelevantPatterns(context: QueryContext): RankedResult[] {
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 total =
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
- return { semantic, severity, recency, tagOverlap, domainMatch, total };
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('SELECT COUNT(*) as count FROM brain_feedback').get() as { count: number }
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
- const acceptRate = feedbackCount > 0 ? accepted / feedbackCount : 0.5;
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
- const remaining = 1.0 - newWeights.semantic;
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 +