@soleri/core 2.4.0 → 2.5.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 +7 -0
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +56 -9
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/types.d.ts +2 -2
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/cognee/client.d.ts +3 -0
- package/dist/cognee/client.d.ts.map +1 -1
- package/dist/cognee/client.js +17 -0
- package/dist/cognee/client.js.map +1 -1
- package/dist/cognee/sync-manager.d.ts +94 -0
- package/dist/cognee/sync-manager.d.ts.map +1 -0
- package/dist/cognee/sync-manager.js +293 -0
- package/dist/cognee/sync-manager.js.map +1 -0
- package/dist/curator/curator.d.ts +8 -1
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +64 -1
- package/dist/curator/curator.js.map +1 -1
- package/dist/errors/classify.d.ts +13 -0
- package/dist/errors/classify.d.ts.map +1 -0
- package/dist/errors/classify.js +97 -0
- package/dist/errors/classify.js.map +1 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/retry.d.ts +40 -0
- package/dist/errors/retry.d.ts.map +1 -0
- package/dist/errors/retry.js +97 -0
- package/dist/errors/retry.js.map +1 -0
- package/dist/errors/types.d.ts +48 -0
- package/dist/errors/types.d.ts.map +1 -0
- package/dist/errors/types.js +59 -0
- package/dist/errors/types.js.map +1 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts +14 -0
- package/dist/intake/content-classifier.d.ts.map +1 -0
- package/dist/intake/content-classifier.js +125 -0
- package/dist/intake/content-classifier.js.map +1 -0
- package/dist/intake/dedup-gate.d.ts +17 -0
- package/dist/intake/dedup-gate.d.ts.map +1 -0
- package/dist/intake/dedup-gate.js +66 -0
- package/dist/intake/dedup-gate.js.map +1 -0
- package/dist/intake/intake-pipeline.d.ts +63 -0
- package/dist/intake/intake-pipeline.d.ts.map +1 -0
- package/dist/intake/intake-pipeline.js +373 -0
- package/dist/intake/intake-pipeline.js.map +1 -0
- package/dist/intake/types.d.ts +65 -0
- package/dist/intake/types.d.ts.map +1 -0
- package/dist/intake/types.js +3 -0
- package/dist/intake/types.js.map +1 -0
- package/dist/intelligence/loader.js +1 -1
- package/dist/intelligence/loader.js.map +1 -1
- package/dist/intelligence/types.d.ts +3 -1
- package/dist/intelligence/types.d.ts.map +1 -1
- package/dist/loop/loop-manager.d.ts +58 -7
- package/dist/loop/loop-manager.d.ts.map +1 -1
- package/dist/loop/loop-manager.js +280 -6
- package/dist/loop/loop-manager.js.map +1 -1
- package/dist/loop/types.d.ts +69 -1
- package/dist/loop/types.d.ts.map +1 -1
- package/dist/loop/types.js +4 -1
- package/dist/loop/types.js.map +1 -1
- package/dist/persistence/index.d.ts +3 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +2 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/sqlite-provider.d.ts +25 -0
- package/dist/persistence/sqlite-provider.d.ts.map +1 -0
- package/dist/persistence/sqlite-provider.js +59 -0
- package/dist/persistence/sqlite-provider.js.map +1 -0
- package/dist/persistence/types.d.ts +36 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +8 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +47 -4
- package/dist/planning/gap-analysis.d.ts.map +1 -1
- package/dist/planning/gap-analysis.js +190 -13
- package/dist/planning/gap-analysis.js.map +1 -1
- package/dist/planning/gap-types.d.ts +1 -1
- package/dist/planning/gap-types.d.ts.map +1 -1
- package/dist/planning/gap-types.js.map +1 -1
- package/dist/planning/planner.d.ts +277 -9
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +611 -46
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/generic/brainstorming.d.ts +9 -0
- package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
- package/dist/playbooks/generic/brainstorming.js +105 -0
- package/dist/playbooks/generic/brainstorming.js.map +1 -0
- package/dist/playbooks/generic/code-review.d.ts +11 -0
- package/dist/playbooks/generic/code-review.d.ts.map +1 -0
- package/dist/playbooks/generic/code-review.js +176 -0
- package/dist/playbooks/generic/code-review.js.map +1 -0
- package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
- package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
- package/dist/playbooks/generic/subagent-execution.js +68 -0
- package/dist/playbooks/generic/subagent-execution.js.map +1 -0
- package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
- package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
- package/dist/playbooks/generic/systematic-debugging.js +87 -0
- package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
- package/dist/playbooks/generic/tdd.d.ts +9 -0
- package/dist/playbooks/generic/tdd.d.ts.map +1 -0
- package/dist/playbooks/generic/tdd.js +70 -0
- package/dist/playbooks/generic/tdd.js.map +1 -0
- package/dist/playbooks/generic/verification.d.ts +9 -0
- package/dist/playbooks/generic/verification.d.ts.map +1 -0
- package/dist/playbooks/generic/verification.js +74 -0
- package/dist/playbooks/generic/verification.js.map +1 -0
- package/dist/playbooks/index.d.ts +4 -0
- package/dist/playbooks/index.d.ts.map +1 -0
- package/dist/playbooks/index.js +5 -0
- package/dist/playbooks/index.js.map +1 -0
- package/dist/playbooks/playbook-registry.d.ts +42 -0
- package/dist/playbooks/playbook-registry.d.ts.map +1 -0
- package/dist/playbooks/playbook-registry.js +227 -0
- package/dist/playbooks/playbook-registry.js.map +1 -0
- package/dist/playbooks/playbook-seeder.d.ts +47 -0
- package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
- package/dist/playbooks/playbook-seeder.js +104 -0
- package/dist/playbooks/playbook-seeder.js.map +1 -0
- package/dist/playbooks/playbook-types.d.ts +132 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -0
- package/dist/playbooks/playbook-types.js +12 -0
- package/dist/playbooks/playbook-types.js.map +1 -0
- package/dist/project/project-registry.d.ts.map +1 -1
- package/dist/project/project-registry.js +9 -11
- package/dist/project/project-registry.js.map +1 -1
- package/dist/prompts/index.d.ts +4 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/parser.d.ts +17 -0
- package/dist/prompts/parser.d.ts.map +1 -0
- package/dist/prompts/parser.js +47 -0
- package/dist/prompts/parser.js.map +1 -0
- package/dist/prompts/template-manager.d.ts +25 -0
- package/dist/prompts/template-manager.d.ts.map +1 -0
- package/dist/prompts/template-manager.js +71 -0
- package/dist/prompts/template-manager.js.map +1 -0
- package/dist/prompts/types.d.ts +26 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +5 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +5 -3
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +322 -11
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +10 -3
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +20 -2
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/cognee-sync-ops.d.ts +12 -0
- package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
- package/dist/runtime/cognee-sync-ops.js +55 -0
- package/dist/runtime/cognee-sync-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +8 -6
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +226 -9
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +2 -2
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +15 -3
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/domain-ops.js +2 -2
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts.map +1 -1
- package/dist/runtime/grading-ops.js.map +1 -1
- package/dist/runtime/intake-ops.d.ts +14 -0
- package/dist/runtime/intake-ops.d.ts.map +1 -0
- package/dist/runtime/intake-ops.js +110 -0
- package/dist/runtime/intake-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +5 -4
- package/dist/runtime/loop-ops.d.ts.map +1 -1
- package/dist/runtime/loop-ops.js +84 -12
- package/dist/runtime/loop-ops.js.map +1 -1
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -1
- package/dist/runtime/memory-cross-project-ops.js.map +1 -1
- package/dist/runtime/memory-extra-ops.js +5 -5
- package/dist/runtime/memory-extra-ops.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +8 -2
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts +13 -5
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +381 -18
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/playbook-ops.d.ts +14 -0
- package/dist/runtime/playbook-ops.d.ts.map +1 -0
- package/dist/runtime/playbook-ops.js +141 -0
- package/dist/runtime/playbook-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts.map +1 -1
- package/dist/runtime/project-ops.js +7 -2
- package/dist/runtime/project-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +27 -8
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +3 -2
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +345 -4
- package/dist/runtime/vault-extra-ops.js.map +1 -1
- package/dist/vault/playbook.d.ts +34 -0
- package/dist/vault/playbook.d.ts.map +1 -0
- package/dist/vault/playbook.js +60 -0
- package/dist/vault/playbook.js.map +1 -0
- package/dist/vault/vault.d.ts +31 -32
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +201 -181
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -3
- package/src/__tests__/admin-extra-ops.test.ts +62 -15
- package/src/__tests__/admin-ops.test.ts +2 -2
- package/src/__tests__/brain.test.ts +3 -3
- package/src/__tests__/cognee-integration.test.ts +80 -0
- package/src/__tests__/cognee-sync-manager.test.ts +103 -0
- package/src/__tests__/core-ops.test.ts +30 -4
- package/src/__tests__/curator-extra-ops.test.ts +24 -2
- package/src/__tests__/errors.test.ts +388 -0
- package/src/__tests__/grading-ops.test.ts +28 -7
- package/src/__tests__/intake-pipeline.test.ts +162 -0
- package/src/__tests__/loop-ops.test.ts +74 -3
- package/src/__tests__/memory-cross-project-ops.test.ts +3 -1
- package/src/__tests__/orchestrate-ops.test.ts +8 -3
- package/src/__tests__/persistence.test.ts +225 -0
- package/src/__tests__/planner.test.ts +99 -21
- package/src/__tests__/planning-extra-ops.test.ts +168 -10
- package/src/__tests__/playbook-registry.test.ts +326 -0
- package/src/__tests__/playbook-seeder.test.ts +163 -0
- package/src/__tests__/playbook.test.ts +389 -0
- package/src/__tests__/project-ops.test.ts +18 -4
- package/src/__tests__/template-manager.test.ts +222 -0
- package/src/__tests__/vault-extra-ops.test.ts +82 -7
- package/src/brain/brain.ts +71 -9
- package/src/brain/types.ts +2 -2
- package/src/cognee/client.ts +18 -0
- package/src/cognee/sync-manager.ts +389 -0
- package/src/curator/curator.ts +88 -7
- package/src/errors/classify.ts +102 -0
- package/src/errors/index.ts +5 -0
- package/src/errors/retry.ts +132 -0
- package/src/errors/types.ts +81 -0
- package/src/index.ts +114 -3
- package/src/intake/content-classifier.ts +146 -0
- package/src/intake/dedup-gate.ts +92 -0
- package/src/intake/intake-pipeline.ts +503 -0
- package/src/intake/types.ts +69 -0
- package/src/intelligence/loader.ts +1 -1
- package/src/intelligence/types.ts +3 -1
- package/src/loop/loop-manager.ts +325 -7
- package/src/loop/types.ts +72 -1
- package/src/persistence/index.ts +7 -0
- package/src/persistence/sqlite-provider.ts +62 -0
- package/src/persistence/types.ts +44 -0
- package/src/planning/gap-analysis.ts +286 -17
- package/src/planning/gap-types.ts +4 -1
- package/src/planning/planner.ts +828 -55
- package/src/playbooks/generic/brainstorming.ts +110 -0
- package/src/playbooks/generic/code-review.ts +181 -0
- package/src/playbooks/generic/subagent-execution.ts +74 -0
- package/src/playbooks/generic/systematic-debugging.ts +92 -0
- package/src/playbooks/generic/tdd.ts +75 -0
- package/src/playbooks/generic/verification.ts +79 -0
- package/src/playbooks/index.ts +27 -0
- package/src/playbooks/playbook-registry.ts +284 -0
- package/src/playbooks/playbook-seeder.ts +119 -0
- package/src/playbooks/playbook-types.ts +162 -0
- package/src/project/project-registry.ts +29 -17
- package/src/prompts/index.ts +3 -0
- package/src/prompts/parser.ts +59 -0
- package/src/prompts/template-manager.ts +77 -0
- package/src/prompts/types.ts +28 -0
- package/src/runtime/admin-extra-ops.ts +358 -13
- package/src/runtime/admin-ops.ts +17 -6
- package/src/runtime/capture-ops.ts +25 -6
- package/src/runtime/cognee-sync-ops.ts +63 -0
- package/src/runtime/core-ops.ts +258 -8
- package/src/runtime/curator-extra-ops.ts +17 -3
- package/src/runtime/domain-ops.ts +2 -2
- package/src/runtime/grading-ops.ts +11 -2
- package/src/runtime/intake-ops.ts +126 -0
- package/src/runtime/loop-ops.ts +96 -13
- package/src/runtime/memory-cross-project-ops.ts +1 -2
- package/src/runtime/memory-extra-ops.ts +5 -5
- package/src/runtime/orchestrate-ops.ts +8 -2
- package/src/runtime/planning-extra-ops.ts +414 -23
- package/src/runtime/playbook-ops.ts +169 -0
- package/src/runtime/project-ops.ts +9 -3
- package/src/runtime/runtime.ts +35 -9
- package/src/runtime/types.ts +8 -0
- package/src/runtime/vault-extra-ops.ts +385 -4
- package/src/vault/playbook.ts +87 -0
- package/src/vault/vault.ts +301 -235
package/src/vault/vault.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { dirname } from 'node:path';
|
|
1
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
2
|
+
import { SQLitePersistenceProvider } from '../persistence/sqlite-provider.js';
|
|
4
3
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
5
4
|
|
|
6
5
|
export interface SearchResult {
|
|
@@ -39,21 +38,43 @@ export interface MemoryStats {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
export class Vault {
|
|
42
|
-
private
|
|
41
|
+
private provider: PersistenceProvider;
|
|
42
|
+
private sqliteProvider: SQLitePersistenceProvider | null;
|
|
43
|
+
private syncManager: import('../cognee/sync-manager.js').CogneeSyncManager | null = null;
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Create a Vault with a PersistenceProvider or a SQLite path (backward compat).
|
|
47
|
+
*/
|
|
48
|
+
constructor(providerOrPath: PersistenceProvider | string = ':memory:') {
|
|
49
|
+
if (typeof providerOrPath === 'string') {
|
|
50
|
+
const sqlite = new SQLitePersistenceProvider(providerOrPath);
|
|
51
|
+
this.provider = sqlite;
|
|
52
|
+
this.sqliteProvider = sqlite;
|
|
53
|
+
// SQLite-specific pragmas
|
|
54
|
+
this.provider.run('PRAGMA journal_mode = WAL');
|
|
55
|
+
this.provider.run('PRAGMA foreign_keys = ON');
|
|
56
|
+
} else {
|
|
57
|
+
this.provider = providerOrPath;
|
|
58
|
+
this.sqliteProvider =
|
|
59
|
+
providerOrPath instanceof SQLitePersistenceProvider ? providerOrPath : null;
|
|
60
|
+
}
|
|
49
61
|
this.initialize();
|
|
50
62
|
}
|
|
51
63
|
|
|
64
|
+
setSyncManager(mgr: import('../cognee/sync-manager.js').CogneeSyncManager): void {
|
|
65
|
+
this.syncManager = mgr;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Backward-compatible factory. */
|
|
69
|
+
static createWithSQLite(dbPath: string = ':memory:'): Vault {
|
|
70
|
+
return new Vault(dbPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
private initialize(): void {
|
|
53
|
-
this.
|
|
74
|
+
this.provider.execSql(`
|
|
54
75
|
CREATE TABLE IF NOT EXISTS entries (
|
|
55
76
|
id TEXT PRIMARY KEY,
|
|
56
|
-
type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule')),
|
|
77
|
+
type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule', 'playbook')),
|
|
57
78
|
domain TEXT NOT NULL,
|
|
58
79
|
title TEXT NOT NULL,
|
|
59
80
|
severity TEXT NOT NULL CHECK(severity IN ('critical', 'warning', 'suggestion')),
|
|
@@ -132,25 +153,29 @@ export class Vault {
|
|
|
132
153
|
CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query);
|
|
133
154
|
`);
|
|
134
155
|
this.migrateBrainSchema();
|
|
156
|
+
this.migrateTemporalSchema();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private migrateTemporalSchema(): void {
|
|
160
|
+
try {
|
|
161
|
+
this.provider.run('ALTER TABLE entries ADD COLUMN valid_from INTEGER');
|
|
162
|
+
} catch {
|
|
163
|
+
// Column already exists
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
this.provider.run('ALTER TABLE entries ADD COLUMN valid_until INTEGER');
|
|
167
|
+
} catch {
|
|
168
|
+
// Column already exists
|
|
169
|
+
}
|
|
135
170
|
}
|
|
136
171
|
|
|
137
|
-
/**
|
|
138
|
-
* Migrate brain_feedback table from old schema (accepted/dismissed only)
|
|
139
|
-
* to new schema with source, confidence, duration, context, reason columns.
|
|
140
|
-
* Also adds extracted_at to brain_sessions if it exists.
|
|
141
|
-
*/
|
|
142
172
|
private migrateBrainSchema(): void {
|
|
143
|
-
|
|
144
|
-
const columns = this.db.prepare('PRAGMA table_info(brain_feedback)').all() as Array<{
|
|
145
|
-
name: string;
|
|
146
|
-
}>;
|
|
173
|
+
const columns = this.provider.all<{ name: string }>('PRAGMA table_info(brain_feedback)');
|
|
147
174
|
const hasSource = columns.some((c) => c.name === 'source');
|
|
148
175
|
|
|
149
176
|
if (!hasSource && columns.length > 0) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.db
|
|
153
|
-
.prepare(`
|
|
177
|
+
this.provider.transaction(() => {
|
|
178
|
+
this.provider.run(`
|
|
154
179
|
CREATE TABLE brain_feedback_new (
|
|
155
180
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
156
181
|
query TEXT NOT NULL,
|
|
@@ -163,29 +188,23 @@ export class Vault {
|
|
|
163
188
|
reason TEXT,
|
|
164
189
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
165
190
|
)
|
|
166
|
-
`)
|
|
167
|
-
|
|
168
|
-
this.db
|
|
169
|
-
.prepare(`
|
|
191
|
+
`);
|
|
192
|
+
this.provider.run(`
|
|
170
193
|
INSERT INTO brain_feedback_new (id, query, entry_id, action, created_at)
|
|
171
194
|
SELECT id, query, entry_id, action, created_at FROM brain_feedback
|
|
172
|
-
`)
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})();
|
|
195
|
+
`);
|
|
196
|
+
this.provider.run('DROP TABLE brain_feedback');
|
|
197
|
+
this.provider.run('ALTER TABLE brain_feedback_new RENAME TO brain_feedback');
|
|
198
|
+
this.provider.run(
|
|
199
|
+
'CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query)',
|
|
200
|
+
);
|
|
201
|
+
});
|
|
180
202
|
}
|
|
181
203
|
|
|
182
|
-
// Add extracted_at to brain_sessions if it exists but lacks the column
|
|
183
204
|
try {
|
|
184
|
-
const sessionCols = this.
|
|
185
|
-
name: string;
|
|
186
|
-
}>;
|
|
205
|
+
const sessionCols = this.provider.all<{ name: string }>('PRAGMA table_info(brain_sessions)');
|
|
187
206
|
if (sessionCols.length > 0 && !sessionCols.some((c) => c.name === 'extracted_at')) {
|
|
188
|
-
this.
|
|
207
|
+
this.provider.run('ALTER TABLE brain_sessions ADD COLUMN extracted_at TEXT');
|
|
189
208
|
}
|
|
190
209
|
} catch {
|
|
191
210
|
// brain_sessions table doesn't exist yet — BrainIntelligence will create it
|
|
@@ -193,17 +212,17 @@ export class Vault {
|
|
|
193
212
|
}
|
|
194
213
|
|
|
195
214
|
seed(entries: IntelligenceEntry[]): number {
|
|
196
|
-
const
|
|
197
|
-
INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to)
|
|
198
|
-
VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo)
|
|
215
|
+
const sql = `
|
|
216
|
+
INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to,valid_from,valid_until)
|
|
217
|
+
VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo,@validFrom,@validUntil)
|
|
199
218
|
ON CONFLICT(id) DO UPDATE SET type=excluded.type,domain=excluded.domain,title=excluded.title,severity=excluded.severity,
|
|
200
219
|
description=excluded.description,context=excluded.context,example=excluded.example,counter_example=excluded.counter_example,
|
|
201
|
-
why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,updated_at=unixepoch()
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,valid_from=excluded.valid_from,valid_until=excluded.valid_until,updated_at=unixepoch()
|
|
221
|
+
`;
|
|
222
|
+
return this.provider.transaction(() => {
|
|
204
223
|
let count = 0;
|
|
205
|
-
for (const entry of
|
|
206
|
-
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
this.provider.run(sql, {
|
|
207
226
|
id: entry.id,
|
|
208
227
|
type: entry.type,
|
|
209
228
|
domain: entry.domain,
|
|
@@ -216,21 +235,31 @@ export class Vault {
|
|
|
216
235
|
why: entry.why ?? null,
|
|
217
236
|
tags: JSON.stringify(entry.tags),
|
|
218
237
|
appliesTo: JSON.stringify(entry.appliesTo ?? []),
|
|
238
|
+
validFrom: entry.validFrom ?? null,
|
|
239
|
+
validUntil: entry.validUntil ?? null,
|
|
219
240
|
});
|
|
220
241
|
count++;
|
|
242
|
+
if (this.syncManager) {
|
|
243
|
+
this.syncManager.enqueue('ingest', entry.id, entry);
|
|
244
|
+
}
|
|
221
245
|
}
|
|
222
246
|
return count;
|
|
223
247
|
});
|
|
224
|
-
return tx(entries);
|
|
225
248
|
}
|
|
226
249
|
|
|
227
250
|
search(
|
|
228
251
|
query: string,
|
|
229
|
-
options?: {
|
|
252
|
+
options?: {
|
|
253
|
+
domain?: string;
|
|
254
|
+
type?: string;
|
|
255
|
+
severity?: string;
|
|
256
|
+
limit?: number;
|
|
257
|
+
includeExpired?: boolean;
|
|
258
|
+
},
|
|
230
259
|
): SearchResult[] {
|
|
231
260
|
const limit = options?.limit ?? 10;
|
|
232
261
|
const filters: string[] = [];
|
|
233
|
-
const fp: Record<string,
|
|
262
|
+
const fp: Record<string, unknown> = {};
|
|
234
263
|
if (options?.domain) {
|
|
235
264
|
filters.push('e.domain = @domain');
|
|
236
265
|
fp.domain = options.domain;
|
|
@@ -243,13 +272,18 @@ export class Vault {
|
|
|
243
272
|
filters.push('e.severity = @severity');
|
|
244
273
|
fp.severity = options.severity;
|
|
245
274
|
}
|
|
275
|
+
if (!options?.includeExpired) {
|
|
276
|
+
const now = Math.floor(Date.now() / 1000);
|
|
277
|
+
filters.push('(e.valid_until IS NULL OR e.valid_until > @now)');
|
|
278
|
+
filters.push('(e.valid_from IS NULL OR e.valid_from <= @now)');
|
|
279
|
+
fp.now = now;
|
|
280
|
+
}
|
|
246
281
|
const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
247
282
|
try {
|
|
248
|
-
const rows = this.
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.all({ query, limit, ...fp }) as Array<Record<string, unknown>>;
|
|
283
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
284
|
+
`SELECT e.*, -rank as score FROM entries_fts fts JOIN entries e ON e.rowid = fts.rowid WHERE entries_fts MATCH @query ${wc} ORDER BY score DESC LIMIT @limit`,
|
|
285
|
+
{ query, limit, ...fp },
|
|
286
|
+
);
|
|
253
287
|
return rows.map(rowToSearchResult);
|
|
254
288
|
} catch {
|
|
255
289
|
return [];
|
|
@@ -257,9 +291,9 @@ export class Vault {
|
|
|
257
291
|
}
|
|
258
292
|
|
|
259
293
|
get(id: string): IntelligenceEntry | null {
|
|
260
|
-
const row = this.
|
|
261
|
-
|
|
262
|
-
|
|
294
|
+
const row = this.provider.get<Record<string, unknown>>('SELECT * FROM entries WHERE id = ?', [
|
|
295
|
+
id,
|
|
296
|
+
]);
|
|
263
297
|
return row ? rowToEntry(row) : null;
|
|
264
298
|
}
|
|
265
299
|
|
|
@@ -270,6 +304,7 @@ export class Vault {
|
|
|
270
304
|
tags?: string[];
|
|
271
305
|
limit?: number;
|
|
272
306
|
offset?: number;
|
|
307
|
+
includeExpired?: boolean;
|
|
273
308
|
}): IntelligenceEntry[] {
|
|
274
309
|
const filters: string[] = [];
|
|
275
310
|
const params: Record<string, unknown> = {};
|
|
@@ -292,26 +327,29 @@ export class Vault {
|
|
|
292
327
|
});
|
|
293
328
|
filters.push(`(${c.join(' OR ')})`);
|
|
294
329
|
}
|
|
330
|
+
if (!options?.includeExpired) {
|
|
331
|
+
const now = Math.floor(Date.now() / 1000);
|
|
332
|
+
filters.push('(valid_until IS NULL OR valid_until > @now)');
|
|
333
|
+
filters.push('(valid_from IS NULL OR valid_from <= @now)');
|
|
334
|
+
params.now = now;
|
|
335
|
+
}
|
|
295
336
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
296
|
-
const rows = this.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
.all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 }) as Array<
|
|
301
|
-
Record<string, unknown>
|
|
302
|
-
>;
|
|
337
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
338
|
+
`SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`,
|
|
339
|
+
{ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 },
|
|
340
|
+
);
|
|
303
341
|
return rows.map(rowToEntry);
|
|
304
342
|
}
|
|
305
343
|
|
|
306
344
|
stats(): VaultStats {
|
|
307
|
-
const total = (
|
|
308
|
-
|
|
309
|
-
)
|
|
345
|
+
const total = this.provider.get<{ count: number }>(
|
|
346
|
+
'SELECT COUNT(*) as count FROM entries',
|
|
347
|
+
)!.count;
|
|
310
348
|
return {
|
|
311
349
|
totalEntries: total,
|
|
312
|
-
byType: gc(this.
|
|
313
|
-
byDomain: gc(this.
|
|
314
|
-
bySeverity: gc(this.
|
|
350
|
+
byType: gc(this.provider, 'type'),
|
|
351
|
+
byDomain: gc(this.provider, 'domain'),
|
|
352
|
+
bySeverity: gc(this.provider, 'severity'),
|
|
315
353
|
};
|
|
316
354
|
}
|
|
317
355
|
|
|
@@ -319,13 +357,13 @@ export class Vault {
|
|
|
319
357
|
this.seed([entry]);
|
|
320
358
|
}
|
|
321
359
|
remove(id: string): boolean {
|
|
322
|
-
|
|
360
|
+
const deleted = this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes > 0;
|
|
361
|
+
if (deleted && this.syncManager) {
|
|
362
|
+
this.syncManager.enqueue('delete', id);
|
|
363
|
+
}
|
|
364
|
+
return deleted;
|
|
323
365
|
}
|
|
324
366
|
|
|
325
|
-
/**
|
|
326
|
-
* Partial update of an existing entry's mutable fields.
|
|
327
|
-
* Returns the updated entry or null if not found.
|
|
328
|
-
*/
|
|
329
367
|
update(
|
|
330
368
|
id: string,
|
|
331
369
|
fields: Partial<
|
|
@@ -342,6 +380,8 @@ export class Vault {
|
|
|
342
380
|
| 'severity'
|
|
343
381
|
| 'type'
|
|
344
382
|
| 'domain'
|
|
383
|
+
| 'validFrom'
|
|
384
|
+
| 'validUntil'
|
|
345
385
|
>
|
|
346
386
|
>,
|
|
347
387
|
): IntelligenceEntry | null {
|
|
@@ -352,27 +392,58 @@ export class Vault {
|
|
|
352
392
|
return this.get(id);
|
|
353
393
|
}
|
|
354
394
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
395
|
+
setTemporal(id: string, validFrom?: number, validUntil?: number): boolean {
|
|
396
|
+
const sets: string[] = [];
|
|
397
|
+
const params: Record<string, unknown> = { id };
|
|
398
|
+
if (validFrom !== undefined) {
|
|
399
|
+
sets.push('valid_from = @validFrom');
|
|
400
|
+
params.validFrom = validFrom;
|
|
401
|
+
}
|
|
402
|
+
if (validUntil !== undefined) {
|
|
403
|
+
sets.push('valid_until = @validUntil');
|
|
404
|
+
params.validUntil = validUntil;
|
|
405
|
+
}
|
|
406
|
+
if (sets.length === 0) return false;
|
|
407
|
+
sets.push('updated_at = unixepoch()');
|
|
408
|
+
return (
|
|
409
|
+
this.provider.run(`UPDATE entries SET ${sets.join(', ')} WHERE id = @id`, params).changes > 0
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
findExpiring(withinDays: number): IntelligenceEntry[] {
|
|
414
|
+
const now = Math.floor(Date.now() / 1000);
|
|
415
|
+
const cutoff = now + withinDays * 86400;
|
|
416
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
417
|
+
'SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until > @now AND valid_until <= @cutoff ORDER BY valid_until ASC',
|
|
418
|
+
{ now, cutoff },
|
|
419
|
+
);
|
|
420
|
+
return rows.map(rowToEntry);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
findExpired(limit: number = 50): IntelligenceEntry[] {
|
|
424
|
+
const now = Math.floor(Date.now() / 1000);
|
|
425
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
426
|
+
'SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until <= @now ORDER BY valid_until DESC LIMIT @limit',
|
|
427
|
+
{ now, limit },
|
|
428
|
+
);
|
|
429
|
+
return rows.map(rowToEntry);
|
|
430
|
+
}
|
|
431
|
+
|
|
359
432
|
bulkRemove(ids: string[]): number {
|
|
360
|
-
|
|
361
|
-
const tx = this.db.transaction((idList: string[]) => {
|
|
433
|
+
return this.provider.transaction(() => {
|
|
362
434
|
let count = 0;
|
|
363
|
-
for (const id of
|
|
364
|
-
count +=
|
|
435
|
+
for (const id of ids) {
|
|
436
|
+
count += this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes;
|
|
437
|
+
if (this.syncManager) {
|
|
438
|
+
this.syncManager.enqueue('delete', id);
|
|
439
|
+
}
|
|
365
440
|
}
|
|
366
441
|
return count;
|
|
367
442
|
});
|
|
368
|
-
return tx(ids);
|
|
369
443
|
}
|
|
370
444
|
|
|
371
|
-
/**
|
|
372
|
-
* List all unique tags with their occurrence counts.
|
|
373
|
-
*/
|
|
374
445
|
getTags(): Array<{ tag: string; count: number }> {
|
|
375
|
-
const rows = this.
|
|
446
|
+
const rows = this.provider.all<{ tags: string }>('SELECT tags FROM entries');
|
|
376
447
|
const counts = new Map<string, number>();
|
|
377
448
|
for (const row of rows) {
|
|
378
449
|
const tags: string[] = JSON.parse(row.tags || '[]');
|
|
@@ -385,49 +456,37 @@ export class Vault {
|
|
|
385
456
|
.sort((a, b) => b.count - a.count);
|
|
386
457
|
}
|
|
387
458
|
|
|
388
|
-
/**
|
|
389
|
-
* List all domains with their entry counts.
|
|
390
|
-
*/
|
|
391
459
|
getDomains(): Array<{ domain: string; count: number }> {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return rows;
|
|
460
|
+
return this.provider.all<{ domain: string; count: number }>(
|
|
461
|
+
'SELECT domain, COUNT(*) as count FROM entries GROUP BY domain ORDER BY count DESC',
|
|
462
|
+
);
|
|
396
463
|
}
|
|
397
464
|
|
|
398
|
-
/**
|
|
399
|
-
* Get recently added or updated entries, ordered by updated_at DESC.
|
|
400
|
-
*/
|
|
401
465
|
getRecent(limit: number = 20): IntelligenceEntry[] {
|
|
402
|
-
const rows = this.
|
|
403
|
-
|
|
404
|
-
|
|
466
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
467
|
+
'SELECT * FROM entries ORDER BY updated_at DESC LIMIT ?',
|
|
468
|
+
[limit],
|
|
469
|
+
);
|
|
405
470
|
return rows.map(rowToEntry);
|
|
406
471
|
}
|
|
407
472
|
|
|
408
|
-
/**
|
|
409
|
-
* Export the entire vault as a JSON-serializable bundle.
|
|
410
|
-
*/
|
|
411
473
|
exportAll(): { entries: IntelligenceEntry[]; exportedAt: number; count: number } {
|
|
412
|
-
const rows = this.
|
|
413
|
-
|
|
414
|
-
|
|
474
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
475
|
+
'SELECT * FROM entries ORDER BY domain, title',
|
|
476
|
+
);
|
|
415
477
|
const entries = rows.map(rowToEntry);
|
|
416
478
|
return { entries, exportedAt: Math.floor(Date.now() / 1000), count: entries.length };
|
|
417
479
|
}
|
|
418
480
|
|
|
419
|
-
/**
|
|
420
|
-
* Get entry age distribution — how old entries are, bucketed.
|
|
421
|
-
*/
|
|
422
481
|
getAgeReport(): {
|
|
423
482
|
total: number;
|
|
424
483
|
buckets: Array<{ label: string; count: number; minDays: number; maxDays: number }>;
|
|
425
484
|
oldestTimestamp: number | null;
|
|
426
485
|
newestTimestamp: number | null;
|
|
427
486
|
} {
|
|
428
|
-
const rows = this.
|
|
429
|
-
|
|
430
|
-
|
|
487
|
+
const rows = this.provider.all<{ created_at: number; updated_at: number }>(
|
|
488
|
+
'SELECT created_at, updated_at FROM entries',
|
|
489
|
+
);
|
|
431
490
|
const now = Math.floor(Date.now() / 1000);
|
|
432
491
|
const bucketDefs = [
|
|
433
492
|
{ label: 'today', minDays: 0, maxDays: 1 },
|
|
@@ -463,21 +522,21 @@ export class Vault {
|
|
|
463
522
|
const projectName = name ?? path.replace(/\/$/, '').split('/').pop() ?? path;
|
|
464
523
|
const existing = this.getProject(path);
|
|
465
524
|
if (existing) {
|
|
466
|
-
this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
.run(path);
|
|
525
|
+
this.provider.run(
|
|
526
|
+
'UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?',
|
|
527
|
+
[path],
|
|
528
|
+
);
|
|
471
529
|
return this.getProject(path)!;
|
|
472
530
|
}
|
|
473
|
-
this.
|
|
531
|
+
this.provider.run('INSERT INTO projects (path, name) VALUES (?, ?)', [path, projectName]);
|
|
474
532
|
return this.getProject(path)!;
|
|
475
533
|
}
|
|
476
534
|
|
|
477
535
|
getProject(path: string): ProjectInfo | null {
|
|
478
|
-
const row = this.
|
|
479
|
-
|
|
480
|
-
|
|
536
|
+
const row = this.provider.get<Record<string, unknown>>(
|
|
537
|
+
'SELECT * FROM projects WHERE path = ?',
|
|
538
|
+
[path],
|
|
539
|
+
);
|
|
481
540
|
if (!row) return null;
|
|
482
541
|
return {
|
|
483
542
|
path: row.path as string,
|
|
@@ -489,9 +548,9 @@ export class Vault {
|
|
|
489
548
|
}
|
|
490
549
|
|
|
491
550
|
listProjects(): ProjectInfo[] {
|
|
492
|
-
const rows = this.
|
|
493
|
-
|
|
494
|
-
|
|
551
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
552
|
+
'SELECT * FROM projects ORDER BY last_seen_at DESC',
|
|
553
|
+
);
|
|
495
554
|
return rows.map((row) => ({
|
|
496
555
|
path: row.path as string,
|
|
497
556
|
name: row.name as string,
|
|
@@ -503,11 +562,9 @@ export class Vault {
|
|
|
503
562
|
|
|
504
563
|
captureMemory(memory: Omit<Memory, 'id' | 'createdAt' | 'archivedAt'>): Memory {
|
|
505
564
|
const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
506
|
-
this.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
)
|
|
510
|
-
.run({
|
|
565
|
+
this.provider.run(
|
|
566
|
+
`INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`,
|
|
567
|
+
{
|
|
511
568
|
id,
|
|
512
569
|
projectPath: memory.projectPath,
|
|
513
570
|
type: memory.type,
|
|
@@ -516,7 +573,8 @@ export class Vault {
|
|
|
516
573
|
topics: JSON.stringify(memory.topics),
|
|
517
574
|
filesModified: JSON.stringify(memory.filesModified),
|
|
518
575
|
toolsUsed: JSON.stringify(memory.toolsUsed),
|
|
519
|
-
}
|
|
576
|
+
},
|
|
577
|
+
);
|
|
520
578
|
return this.getMemory(id)!;
|
|
521
579
|
}
|
|
522
580
|
|
|
@@ -537,11 +595,10 @@ export class Vault {
|
|
|
537
595
|
}
|
|
538
596
|
const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
539
597
|
try {
|
|
540
|
-
const rows = this.
|
|
541
|
-
.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
.all({ query, limit, ...fp }) as Array<Record<string, unknown>>;
|
|
598
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
599
|
+
`SELECT m.* FROM memories_fts fts JOIN memories m ON m.rowid = fts.rowid WHERE memories_fts MATCH @query ${wc} ORDER BY rank LIMIT @limit`,
|
|
600
|
+
{ query, limit, ...fp },
|
|
601
|
+
);
|
|
545
602
|
return rows.map(rowToMemory);
|
|
546
603
|
} catch {
|
|
547
604
|
return [];
|
|
@@ -565,30 +622,23 @@ export class Vault {
|
|
|
565
622
|
params.projectPath = options.projectPath;
|
|
566
623
|
}
|
|
567
624
|
const wc = `WHERE ${filters.join(' AND ')}`;
|
|
568
|
-
const rows = this.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
>;
|
|
625
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
626
|
+
`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`,
|
|
627
|
+
{ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 },
|
|
628
|
+
);
|
|
573
629
|
return rows.map(rowToMemory);
|
|
574
630
|
}
|
|
575
631
|
|
|
576
632
|
memoryStats(): MemoryStats {
|
|
577
|
-
const total = (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
.all() as Array<{ key: string; count: number }>;
|
|
587
|
-
const byProjectRows = this.db
|
|
588
|
-
.prepare(
|
|
589
|
-
'SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path',
|
|
590
|
-
)
|
|
591
|
-
.all() as Array<{ key: string; count: number }>;
|
|
633
|
+
const total = this.provider.get<{ count: number }>(
|
|
634
|
+
'SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL',
|
|
635
|
+
)!.count;
|
|
636
|
+
const byTypeRows = this.provider.all<{ key: string; count: number }>(
|
|
637
|
+
'SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type',
|
|
638
|
+
);
|
|
639
|
+
const byProjectRows = this.provider.all<{ key: string; count: number }>(
|
|
640
|
+
'SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path',
|
|
641
|
+
);
|
|
592
642
|
return {
|
|
593
643
|
total,
|
|
594
644
|
byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
|
|
@@ -597,15 +647,14 @@ export class Vault {
|
|
|
597
647
|
}
|
|
598
648
|
|
|
599
649
|
getMemory(id: string): Memory | null {
|
|
600
|
-
const row = this.
|
|
601
|
-
|
|
602
|
-
|
|
650
|
+
const row = this.provider.get<Record<string, unknown>>('SELECT * FROM memories WHERE id = ?', [
|
|
651
|
+
id,
|
|
652
|
+
]);
|
|
603
653
|
return row ? rowToMemory(row) : null;
|
|
604
654
|
}
|
|
605
655
|
|
|
606
|
-
|
|
607
656
|
deleteMemory(id: string): boolean {
|
|
608
|
-
return this.
|
|
657
|
+
return this.provider.run('DELETE FROM memories WHERE id = ?', [id]).changes > 0;
|
|
609
658
|
}
|
|
610
659
|
|
|
611
660
|
memoryStatsDetailed(options?: {
|
|
@@ -629,35 +678,30 @@ export class Vault {
|
|
|
629
678
|
}
|
|
630
679
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
631
680
|
|
|
632
|
-
const total = (
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
)
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const dateRange = this.db
|
|
657
|
-
.prepare(
|
|
658
|
-
`SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
|
|
659
|
-
)
|
|
660
|
-
.get(params) as { oldest: number | null; newest: number | null };
|
|
681
|
+
const total = this.provider.get<{ count: number }>(
|
|
682
|
+
`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
|
|
683
|
+
params,
|
|
684
|
+
)!.count;
|
|
685
|
+
|
|
686
|
+
const archivedCount = this.provider.get<{ count: number }>(
|
|
687
|
+
`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NOT NULL`,
|
|
688
|
+
params,
|
|
689
|
+
)!.count;
|
|
690
|
+
|
|
691
|
+
const byTypeRows = this.provider.all<{ key: string; count: number }>(
|
|
692
|
+
`SELECT type as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY type`,
|
|
693
|
+
params,
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const byProjectRows = this.provider.all<{ key: string; count: number }>(
|
|
697
|
+
`SELECT project_path as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY project_path`,
|
|
698
|
+
params,
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const dateRange = this.provider.get<{ oldest: number | null; newest: number | null }>(
|
|
702
|
+
`SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
|
|
703
|
+
params,
|
|
704
|
+
)!;
|
|
661
705
|
|
|
662
706
|
return {
|
|
663
707
|
total,
|
|
@@ -688,22 +732,23 @@ export class Vault {
|
|
|
688
732
|
params.type = options.type;
|
|
689
733
|
}
|
|
690
734
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
691
|
-
const rows = this.
|
|
692
|
-
|
|
693
|
-
.
|
|
735
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
736
|
+
`SELECT * FROM memories ${wc} ORDER BY created_at ASC`,
|
|
737
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
738
|
+
);
|
|
694
739
|
return rows.map(rowToMemory);
|
|
695
740
|
}
|
|
696
741
|
|
|
697
742
|
importMemories(memories: Memory[]): { imported: number; skipped: number } {
|
|
698
|
-
const
|
|
743
|
+
const sql = `
|
|
699
744
|
INSERT OR IGNORE INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used, created_at, archived_at)
|
|
700
745
|
VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed, @createdAt, @archivedAt)
|
|
701
|
-
|
|
746
|
+
`;
|
|
702
747
|
let imported = 0;
|
|
703
748
|
let skipped = 0;
|
|
704
|
-
|
|
705
|
-
for (const m of
|
|
706
|
-
const result =
|
|
749
|
+
this.provider.transaction(() => {
|
|
750
|
+
for (const m of memories) {
|
|
751
|
+
const result = this.provider.run(sql, {
|
|
707
752
|
id: m.id,
|
|
708
753
|
projectPath: m.projectPath,
|
|
709
754
|
type: m.type,
|
|
@@ -719,22 +764,20 @@ export class Vault {
|
|
|
719
764
|
else skipped++;
|
|
720
765
|
}
|
|
721
766
|
});
|
|
722
|
-
tx(memories);
|
|
723
767
|
return { imported, skipped };
|
|
724
768
|
}
|
|
725
769
|
|
|
726
770
|
pruneMemories(olderThanDays: number): { pruned: number } {
|
|
727
771
|
const cutoff = Math.floor(Date.now() / 1000) - olderThanDays * 86400;
|
|
728
|
-
const result = this.
|
|
729
|
-
|
|
730
|
-
|
|
772
|
+
const result = this.provider.run(
|
|
773
|
+
'DELETE FROM memories WHERE created_at < ? AND archived_at IS NULL',
|
|
774
|
+
[cutoff],
|
|
775
|
+
);
|
|
731
776
|
return { pruned: result.changes };
|
|
732
777
|
}
|
|
733
778
|
|
|
734
779
|
deduplicateMemories(): { removed: number; groups: Array<{ kept: string; removed: string[] }> } {
|
|
735
|
-
|
|
736
|
-
const dupeRows = this.db
|
|
737
|
-
.prepare(`
|
|
780
|
+
const dupeRows = this.provider.all<{ id1: string; id2: string }>(`
|
|
738
781
|
SELECT m1.id as id1, m2.id as id2
|
|
739
782
|
FROM memories m1
|
|
740
783
|
JOIN memories m2 ON m1.summary = m2.summary
|
|
@@ -743,10 +786,8 @@ export class Vault {
|
|
|
743
786
|
AND m1.id < m2.id
|
|
744
787
|
AND m1.archived_at IS NULL
|
|
745
788
|
AND m2.archived_at IS NULL
|
|
746
|
-
`)
|
|
747
|
-
.all() as Array<{ id1: string; id2: string }>;
|
|
789
|
+
`);
|
|
748
790
|
|
|
749
|
-
// Group: keep the earliest (id1), remove all later duplicates
|
|
750
791
|
const groupMap = new Map<string, Set<string>>();
|
|
751
792
|
for (const row of dupeRows) {
|
|
752
793
|
if (!groupMap.has(row.id1)) groupMap.set(row.id1, new Set());
|
|
@@ -764,20 +805,20 @@ export class Vault {
|
|
|
764
805
|
}
|
|
765
806
|
|
|
766
807
|
if (toRemove.size > 0) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
808
|
+
this.provider.transaction(() => {
|
|
809
|
+
for (const id of toRemove) {
|
|
810
|
+
this.provider.run('DELETE FROM memories WHERE id = ?', [id]);
|
|
811
|
+
}
|
|
770
812
|
});
|
|
771
|
-
tx([...toRemove]);
|
|
772
813
|
}
|
|
773
814
|
|
|
774
815
|
return { removed: toRemove.size, groups };
|
|
775
816
|
}
|
|
776
817
|
|
|
777
818
|
memoryTopics(): Array<{ topic: string; count: number }> {
|
|
778
|
-
const rows = this.
|
|
779
|
-
|
|
780
|
-
|
|
819
|
+
const rows = this.provider.all<{ topics: string }>(
|
|
820
|
+
'SELECT topics FROM memories WHERE archived_at IS NULL',
|
|
821
|
+
);
|
|
781
822
|
|
|
782
823
|
const topicCounts = new Map<string, number>();
|
|
783
824
|
for (const row of rows) {
|
|
@@ -793,18 +834,15 @@ export class Vault {
|
|
|
793
834
|
}
|
|
794
835
|
|
|
795
836
|
memoriesByProject(): Array<{ project: string; count: number; memories: Memory[] }> {
|
|
796
|
-
const rows = this.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
)
|
|
800
|
-
.all() as Array<{ project: string; count: number }>;
|
|
837
|
+
const rows = this.provider.all<{ project: string; count: number }>(
|
|
838
|
+
'SELECT project_path as project, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path ORDER BY count DESC',
|
|
839
|
+
);
|
|
801
840
|
|
|
802
841
|
return rows.map((row) => {
|
|
803
|
-
const memories = this.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
.all(row.project) as Array<Record<string, unknown>>;
|
|
842
|
+
const memories = this.provider.all<Record<string, unknown>>(
|
|
843
|
+
'SELECT * FROM memories WHERE project_path = ? AND archived_at IS NULL ORDER BY created_at DESC',
|
|
844
|
+
[row.project],
|
|
845
|
+
);
|
|
808
846
|
return {
|
|
809
847
|
project: row.project,
|
|
810
848
|
count: row.count,
|
|
@@ -813,19 +851,45 @@ export class Vault {
|
|
|
813
851
|
});
|
|
814
852
|
}
|
|
815
853
|
|
|
816
|
-
|
|
817
|
-
|
|
854
|
+
/**
|
|
855
|
+
* Rebuild the FTS5 index for the entries table.
|
|
856
|
+
* Useful after bulk operations or if the index gets out of sync.
|
|
857
|
+
*/
|
|
858
|
+
rebuildFtsIndex(): void {
|
|
859
|
+
try {
|
|
860
|
+
this.provider.run("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')");
|
|
861
|
+
} catch {
|
|
862
|
+
// Graceful degradation — FTS rebuild failed (e.g. table doesn't exist yet)
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Get the underlying persistence provider.
|
|
868
|
+
*/
|
|
869
|
+
getProvider(): PersistenceProvider {
|
|
870
|
+
return this.provider;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Get the raw better-sqlite3 Database (backward compat).
|
|
875
|
+
* Throws if the provider is not SQLite.
|
|
876
|
+
*/
|
|
877
|
+
getDb(): import('better-sqlite3').Database {
|
|
878
|
+
if (this.sqliteProvider) {
|
|
879
|
+
return this.sqliteProvider.getDatabase();
|
|
880
|
+
}
|
|
881
|
+
throw new Error('getDb() is only available with SQLite provider');
|
|
818
882
|
}
|
|
819
883
|
|
|
820
884
|
close(): void {
|
|
821
|
-
this.
|
|
885
|
+
this.provider.close();
|
|
822
886
|
}
|
|
823
887
|
}
|
|
824
888
|
|
|
825
|
-
function gc(
|
|
826
|
-
const rows =
|
|
827
|
-
|
|
828
|
-
|
|
889
|
+
function gc(provider: PersistenceProvider, col: string): Record<string, number> {
|
|
890
|
+
const rows = provider.all<{ key: string; count: number }>(
|
|
891
|
+
`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`,
|
|
892
|
+
);
|
|
829
893
|
return Object.fromEntries(rows.map((r) => [r.key, r.count]));
|
|
830
894
|
}
|
|
831
895
|
|
|
@@ -843,6 +907,8 @@ function rowToEntry(row: Record<string, unknown>): IntelligenceEntry {
|
|
|
843
907
|
why: (row.why as string) ?? undefined,
|
|
844
908
|
tags: JSON.parse((row.tags as string) || '[]'),
|
|
845
909
|
appliesTo: JSON.parse((row.applies_to as string) || '[]'),
|
|
910
|
+
validFrom: (row.valid_from as number) ?? undefined,
|
|
911
|
+
validUntil: (row.valid_until as number) ?? undefined,
|
|
846
912
|
};
|
|
847
913
|
}
|
|
848
914
|
|