@soleri/core 2.4.0 → 2.6.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/intelligence.d.ts +1 -0
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +164 -148
- package/dist/brain/intelligence.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/control/identity-manager.d.ts +3 -1
- package/dist/control/identity-manager.d.ts.map +1 -1
- package/dist/control/identity-manager.js +49 -51
- package/dist/control/identity-manager.js.map +1 -1
- package/dist/control/intent-router.d.ts +1 -0
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +32 -32
- package/dist/control/intent-router.js.map +1 -1
- package/dist/curator/curator.d.ts +9 -1
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +104 -92
- 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/governance/governance.d.ts +1 -0
- package/dist/governance/governance.d.ts.map +1 -1
- package/dist/governance/governance.js +51 -68
- package/dist/governance/governance.js.map +1 -1
- package/dist/index.d.ts +26 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -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 +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/postgres-provider.d.ts +46 -0
- package/dist/persistence/postgres-provider.d.ts.map +1 -0
- package/dist/persistence/postgres-provider.js +115 -0
- package/dist/persistence/postgres-provider.js.map +1 -0
- package/dist/persistence/sqlite-provider.d.ts +28 -0
- package/dist/persistence/sqlite-provider.d.ts.map +1 -0
- package/dist/persistence/sqlite-provider.js +97 -0
- package/dist/persistence/sqlite-provider.js.map +1 -0
- package/dist/persistence/types.d.ts +58 -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 +4 -4
- package/dist/project/project-registry.d.ts.map +1 -1
- package/dist/project/project-registry.js +30 -57
- 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 +348 -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 +28 -9
- 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 +4 -2
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +383 -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 +52 -32
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +300 -181
- package/dist/vault/vault.js.map +1 -1
- package/package.json +9 -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 +36 -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 +291 -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__/postgres-provider.test.ts +58 -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/__tests__/vault.test.ts +184 -0
- package/src/brain/brain.ts +71 -9
- package/src/brain/intelligence.ts +258 -307
- package/src/brain/types.ts +2 -2
- package/src/cognee/client.ts +18 -0
- package/src/cognee/sync-manager.ts +389 -0
- package/src/control/identity-manager.ts +77 -75
- package/src/control/intent-router.ts +55 -57
- package/src/curator/curator.ts +199 -139
- 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/governance/governance.ts +90 -107
- package/src/index.ts +116 -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 +9 -0
- package/src/persistence/postgres-provider.ts +157 -0
- package/src/persistence/sqlite-provider.ts +115 -0
- package/src/persistence/types.ts +74 -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 +81 -74
- 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 +391 -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 +36 -10
- package/src/runtime/types.ts +8 -0
- package/src/runtime/vault-extra-ops.ts +425 -4
- package/src/vault/playbook.ts +87 -0
- package/src/vault/vault.ts +419 -235
package/dist/vault/vault.js
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { mkdirSync } from 'node:fs';
|
|
3
|
-
import { dirname } from 'node:path';
|
|
1
|
+
import { SQLitePersistenceProvider } from '../persistence/sqlite-provider.js';
|
|
4
2
|
export class Vault {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
provider;
|
|
4
|
+
sqliteProvider;
|
|
5
|
+
syncManager = null;
|
|
6
|
+
/**
|
|
7
|
+
* Create a Vault with a PersistenceProvider or a SQLite path (backward compat).
|
|
8
|
+
*/
|
|
9
|
+
constructor(providerOrPath = ':memory:') {
|
|
10
|
+
if (typeof providerOrPath === 'string') {
|
|
11
|
+
const sqlite = new SQLitePersistenceProvider(providerOrPath);
|
|
12
|
+
this.provider = sqlite;
|
|
13
|
+
this.sqliteProvider = sqlite;
|
|
14
|
+
// SQLite-specific pragmas
|
|
15
|
+
this.provider.run('PRAGMA journal_mode = WAL');
|
|
16
|
+
this.provider.run('PRAGMA foreign_keys = ON');
|
|
17
|
+
this.provider.run('PRAGMA synchronous = NORMAL');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.provider = providerOrPath;
|
|
21
|
+
this.sqliteProvider =
|
|
22
|
+
providerOrPath instanceof SQLitePersistenceProvider ? providerOrPath : null;
|
|
23
|
+
}
|
|
12
24
|
this.initialize();
|
|
13
25
|
}
|
|
26
|
+
setSyncManager(mgr) {
|
|
27
|
+
this.syncManager = mgr;
|
|
28
|
+
}
|
|
29
|
+
/** Backward-compatible factory. */
|
|
30
|
+
static createWithSQLite(dbPath = ':memory:') {
|
|
31
|
+
return new Vault(dbPath);
|
|
32
|
+
}
|
|
14
33
|
initialize() {
|
|
15
|
-
this.
|
|
34
|
+
this.provider.execSql(`
|
|
16
35
|
CREATE TABLE IF NOT EXISTS entries (
|
|
17
36
|
id TEXT PRIMARY KEY,
|
|
18
|
-
type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule')),
|
|
37
|
+
type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule', 'playbook')),
|
|
19
38
|
domain TEXT NOT NULL,
|
|
20
39
|
title TEXT NOT NULL,
|
|
21
40
|
severity TEXT NOT NULL CHECK(severity IN ('critical', 'warning', 'suggestion')),
|
|
@@ -40,6 +59,23 @@ export class Vault {
|
|
|
40
59
|
INSERT INTO entries_fts(entries_fts,rowid,id,title,description,context,tags) VALUES('delete',old.rowid,old.id,old.title,old.description,old.context,old.tags);
|
|
41
60
|
INSERT INTO entries_fts(rowid,id,title,description,context,tags) VALUES(new.rowid,new.id,new.title,new.description,new.context,new.tags);
|
|
42
61
|
END;
|
|
62
|
+
CREATE TABLE IF NOT EXISTS entries_archive (
|
|
63
|
+
id TEXT PRIMARY KEY,
|
|
64
|
+
type TEXT NOT NULL,
|
|
65
|
+
domain TEXT NOT NULL,
|
|
66
|
+
title TEXT NOT NULL,
|
|
67
|
+
severity TEXT NOT NULL,
|
|
68
|
+
description TEXT NOT NULL,
|
|
69
|
+
context TEXT, example TEXT, counter_example TEXT, why TEXT,
|
|
70
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
71
|
+
applies_to TEXT DEFAULT '[]',
|
|
72
|
+
created_at INTEGER NOT NULL,
|
|
73
|
+
updated_at INTEGER NOT NULL,
|
|
74
|
+
valid_from INTEGER,
|
|
75
|
+
valid_until INTEGER,
|
|
76
|
+
archived_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
77
|
+
archive_reason TEXT
|
|
78
|
+
);
|
|
43
79
|
CREATE TABLE IF NOT EXISTS projects (
|
|
44
80
|
path TEXT PRIMARY KEY,
|
|
45
81
|
name TEXT NOT NULL,
|
|
@@ -92,23 +128,35 @@ export class Vault {
|
|
|
92
128
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
93
129
|
);
|
|
94
130
|
CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_entries_domain ON entries(domain);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(type);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_entries_severity ON entries(severity);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_path);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
95
136
|
`);
|
|
96
137
|
this.migrateBrainSchema();
|
|
138
|
+
this.migrateTemporalSchema();
|
|
139
|
+
}
|
|
140
|
+
migrateTemporalSchema() {
|
|
141
|
+
try {
|
|
142
|
+
this.provider.run('ALTER TABLE entries ADD COLUMN valid_from INTEGER');
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Column already exists
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
this.provider.run('ALTER TABLE entries ADD COLUMN valid_until INTEGER');
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Column already exists
|
|
152
|
+
}
|
|
97
153
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Migrate brain_feedback table from old schema (accepted/dismissed only)
|
|
100
|
-
* to new schema with source, confidence, duration, context, reason columns.
|
|
101
|
-
* Also adds extracted_at to brain_sessions if it exists.
|
|
102
|
-
*/
|
|
103
154
|
migrateBrainSchema() {
|
|
104
|
-
|
|
105
|
-
const columns = this.db.prepare('PRAGMA table_info(brain_feedback)').all();
|
|
155
|
+
const columns = this.provider.all('PRAGMA table_info(brain_feedback)');
|
|
106
156
|
const hasSource = columns.some((c) => c.name === 'source');
|
|
107
157
|
if (!hasSource && columns.length > 0) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.db
|
|
111
|
-
.prepare(`
|
|
158
|
+
this.provider.transaction(() => {
|
|
159
|
+
this.provider.run(`
|
|
112
160
|
CREATE TABLE brain_feedback_new (
|
|
113
161
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
162
|
query TEXT NOT NULL,
|
|
@@ -121,26 +169,20 @@ export class Vault {
|
|
|
121
169
|
reason TEXT,
|
|
122
170
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
123
171
|
)
|
|
124
|
-
`)
|
|
125
|
-
|
|
126
|
-
this.db
|
|
127
|
-
.prepare(`
|
|
172
|
+
`);
|
|
173
|
+
this.provider.run(`
|
|
128
174
|
INSERT INTO brain_feedback_new (id, query, entry_id, action, created_at)
|
|
129
175
|
SELECT id, query, entry_id, action, created_at FROM brain_feedback
|
|
130
|
-
`)
|
|
131
|
-
|
|
132
|
-
this.
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.run();
|
|
137
|
-
})();
|
|
138
|
-
}
|
|
139
|
-
// Add extracted_at to brain_sessions if it exists but lacks the column
|
|
176
|
+
`);
|
|
177
|
+
this.provider.run('DROP TABLE brain_feedback');
|
|
178
|
+
this.provider.run('ALTER TABLE brain_feedback_new RENAME TO brain_feedback');
|
|
179
|
+
this.provider.run('CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query)');
|
|
180
|
+
});
|
|
181
|
+
}
|
|
140
182
|
try {
|
|
141
|
-
const sessionCols = this.
|
|
183
|
+
const sessionCols = this.provider.all('PRAGMA table_info(brain_sessions)');
|
|
142
184
|
if (sessionCols.length > 0 && !sessionCols.some((c) => c.name === 'extracted_at')) {
|
|
143
|
-
this.
|
|
185
|
+
this.provider.run('ALTER TABLE brain_sessions ADD COLUMN extracted_at TEXT');
|
|
144
186
|
}
|
|
145
187
|
}
|
|
146
188
|
catch {
|
|
@@ -148,17 +190,17 @@ export class Vault {
|
|
|
148
190
|
}
|
|
149
191
|
}
|
|
150
192
|
seed(entries) {
|
|
151
|
-
const
|
|
152
|
-
INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to)
|
|
153
|
-
VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo)
|
|
193
|
+
const sql = `
|
|
194
|
+
INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to,valid_from,valid_until)
|
|
195
|
+
VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo,@validFrom,@validUntil)
|
|
154
196
|
ON CONFLICT(id) DO UPDATE SET type=excluded.type,domain=excluded.domain,title=excluded.title,severity=excluded.severity,
|
|
155
197
|
description=excluded.description,context=excluded.context,example=excluded.example,counter_example=excluded.counter_example,
|
|
156
|
-
why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,updated_at=unixepoch()
|
|
157
|
-
|
|
158
|
-
|
|
198
|
+
why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,valid_from=excluded.valid_from,valid_until=excluded.valid_until,updated_at=unixepoch()
|
|
199
|
+
`;
|
|
200
|
+
return this.provider.transaction(() => {
|
|
159
201
|
let count = 0;
|
|
160
|
-
for (const entry of
|
|
161
|
-
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
this.provider.run(sql, {
|
|
162
204
|
id: entry.id,
|
|
163
205
|
type: entry.type,
|
|
164
206
|
domain: entry.domain,
|
|
@@ -171,12 +213,16 @@ export class Vault {
|
|
|
171
213
|
why: entry.why ?? null,
|
|
172
214
|
tags: JSON.stringify(entry.tags),
|
|
173
215
|
appliesTo: JSON.stringify(entry.appliesTo ?? []),
|
|
216
|
+
validFrom: entry.validFrom ?? null,
|
|
217
|
+
validUntil: entry.validUntil ?? null,
|
|
174
218
|
});
|
|
175
219
|
count++;
|
|
220
|
+
if (this.syncManager) {
|
|
221
|
+
this.syncManager.enqueue('ingest', entry.id, entry);
|
|
222
|
+
}
|
|
176
223
|
}
|
|
177
224
|
return count;
|
|
178
225
|
});
|
|
179
|
-
return tx(entries);
|
|
180
226
|
}
|
|
181
227
|
search(query, options) {
|
|
182
228
|
const limit = options?.limit ?? 10;
|
|
@@ -194,11 +240,15 @@ export class Vault {
|
|
|
194
240
|
filters.push('e.severity = @severity');
|
|
195
241
|
fp.severity = options.severity;
|
|
196
242
|
}
|
|
243
|
+
if (!options?.includeExpired) {
|
|
244
|
+
const now = Math.floor(Date.now() / 1000);
|
|
245
|
+
filters.push('(e.valid_until IS NULL OR e.valid_until > @now)');
|
|
246
|
+
filters.push('(e.valid_from IS NULL OR e.valid_from <= @now)');
|
|
247
|
+
fp.now = now;
|
|
248
|
+
}
|
|
197
249
|
const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
198
250
|
try {
|
|
199
|
-
const rows = this.
|
|
200
|
-
.prepare(`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`)
|
|
201
|
-
.all({ query, limit, ...fp });
|
|
251
|
+
const rows = this.provider.all(`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`, { query, limit, ...fp });
|
|
202
252
|
return rows.map(rowToSearchResult);
|
|
203
253
|
}
|
|
204
254
|
catch {
|
|
@@ -206,7 +256,9 @@ export class Vault {
|
|
|
206
256
|
}
|
|
207
257
|
}
|
|
208
258
|
get(id) {
|
|
209
|
-
const row = this.
|
|
259
|
+
const row = this.provider.get('SELECT * FROM entries WHERE id = ?', [
|
|
260
|
+
id,
|
|
261
|
+
]);
|
|
210
262
|
return row ? rowToEntry(row) : null;
|
|
211
263
|
}
|
|
212
264
|
list(options) {
|
|
@@ -231,31 +283,35 @@ export class Vault {
|
|
|
231
283
|
});
|
|
232
284
|
filters.push(`(${c.join(' OR ')})`);
|
|
233
285
|
}
|
|
286
|
+
if (!options?.includeExpired) {
|
|
287
|
+
const now = Math.floor(Date.now() / 1000);
|
|
288
|
+
filters.push('(valid_until IS NULL OR valid_until > @now)');
|
|
289
|
+
filters.push('(valid_from IS NULL OR valid_from <= @now)');
|
|
290
|
+
params.now = now;
|
|
291
|
+
}
|
|
234
292
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
235
|
-
const rows = this.
|
|
236
|
-
.prepare(`SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`)
|
|
237
|
-
.all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
|
|
293
|
+
const rows = this.provider.all(`SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`, { ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
|
|
238
294
|
return rows.map(rowToEntry);
|
|
239
295
|
}
|
|
240
296
|
stats() {
|
|
241
|
-
const total = this.
|
|
297
|
+
const total = this.provider.get('SELECT COUNT(*) as count FROM entries').count;
|
|
242
298
|
return {
|
|
243
299
|
totalEntries: total,
|
|
244
|
-
byType: gc(this.
|
|
245
|
-
byDomain: gc(this.
|
|
246
|
-
bySeverity: gc(this.
|
|
300
|
+
byType: gc(this.provider, 'type'),
|
|
301
|
+
byDomain: gc(this.provider, 'domain'),
|
|
302
|
+
bySeverity: gc(this.provider, 'severity'),
|
|
247
303
|
};
|
|
248
304
|
}
|
|
249
305
|
add(entry) {
|
|
250
306
|
this.seed([entry]);
|
|
251
307
|
}
|
|
252
308
|
remove(id) {
|
|
253
|
-
|
|
309
|
+
const deleted = this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes > 0;
|
|
310
|
+
if (deleted && this.syncManager) {
|
|
311
|
+
this.syncManager.enqueue('delete', id);
|
|
312
|
+
}
|
|
313
|
+
return deleted;
|
|
254
314
|
}
|
|
255
|
-
/**
|
|
256
|
-
* Partial update of an existing entry's mutable fields.
|
|
257
|
-
* Returns the updated entry or null if not found.
|
|
258
|
-
*/
|
|
259
315
|
update(id, fields) {
|
|
260
316
|
const existing = this.get(id);
|
|
261
317
|
if (!existing)
|
|
@@ -264,26 +320,47 @@ export class Vault {
|
|
|
264
320
|
this.seed([merged]);
|
|
265
321
|
return this.get(id);
|
|
266
322
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
323
|
+
setTemporal(id, validFrom, validUntil) {
|
|
324
|
+
const sets = [];
|
|
325
|
+
const params = { id };
|
|
326
|
+
if (validFrom !== undefined) {
|
|
327
|
+
sets.push('valid_from = @validFrom');
|
|
328
|
+
params.validFrom = validFrom;
|
|
329
|
+
}
|
|
330
|
+
if (validUntil !== undefined) {
|
|
331
|
+
sets.push('valid_until = @validUntil');
|
|
332
|
+
params.validUntil = validUntil;
|
|
333
|
+
}
|
|
334
|
+
if (sets.length === 0)
|
|
335
|
+
return false;
|
|
336
|
+
sets.push('updated_at = unixepoch()');
|
|
337
|
+
return (this.provider.run(`UPDATE entries SET ${sets.join(', ')} WHERE id = @id`, params).changes > 0);
|
|
338
|
+
}
|
|
339
|
+
findExpiring(withinDays) {
|
|
340
|
+
const now = Math.floor(Date.now() / 1000);
|
|
341
|
+
const cutoff = now + withinDays * 86400;
|
|
342
|
+
const rows = this.provider.all('SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until > @now AND valid_until <= @cutoff ORDER BY valid_until ASC', { now, cutoff });
|
|
343
|
+
return rows.map(rowToEntry);
|
|
344
|
+
}
|
|
345
|
+
findExpired(limit = 50) {
|
|
346
|
+
const now = Math.floor(Date.now() / 1000);
|
|
347
|
+
const rows = this.provider.all('SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until <= @now ORDER BY valid_until DESC LIMIT @limit', { now, limit });
|
|
348
|
+
return rows.map(rowToEntry);
|
|
349
|
+
}
|
|
271
350
|
bulkRemove(ids) {
|
|
272
|
-
|
|
273
|
-
const tx = this.db.transaction((idList) => {
|
|
351
|
+
return this.provider.transaction(() => {
|
|
274
352
|
let count = 0;
|
|
275
|
-
for (const id of
|
|
276
|
-
count +=
|
|
353
|
+
for (const id of ids) {
|
|
354
|
+
count += this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes;
|
|
355
|
+
if (this.syncManager) {
|
|
356
|
+
this.syncManager.enqueue('delete', id);
|
|
357
|
+
}
|
|
277
358
|
}
|
|
278
359
|
return count;
|
|
279
360
|
});
|
|
280
|
-
return tx(ids);
|
|
281
361
|
}
|
|
282
|
-
/**
|
|
283
|
-
* List all unique tags with their occurrence counts.
|
|
284
|
-
*/
|
|
285
362
|
getTags() {
|
|
286
|
-
const rows = this.
|
|
363
|
+
const rows = this.provider.all('SELECT tags FROM entries');
|
|
287
364
|
const counts = new Map();
|
|
288
365
|
for (const row of rows) {
|
|
289
366
|
const tags = JSON.parse(row.tags || '[]');
|
|
@@ -295,41 +372,20 @@ export class Vault {
|
|
|
295
372
|
.map(([tag, count]) => ({ tag, count }))
|
|
296
373
|
.sort((a, b) => b.count - a.count);
|
|
297
374
|
}
|
|
298
|
-
/**
|
|
299
|
-
* List all domains with their entry counts.
|
|
300
|
-
*/
|
|
301
375
|
getDomains() {
|
|
302
|
-
|
|
303
|
-
.prepare('SELECT domain, COUNT(*) as count FROM entries GROUP BY domain ORDER BY count DESC')
|
|
304
|
-
.all();
|
|
305
|
-
return rows;
|
|
376
|
+
return this.provider.all('SELECT domain, COUNT(*) as count FROM entries GROUP BY domain ORDER BY count DESC');
|
|
306
377
|
}
|
|
307
|
-
/**
|
|
308
|
-
* Get recently added or updated entries, ordered by updated_at DESC.
|
|
309
|
-
*/
|
|
310
378
|
getRecent(limit = 20) {
|
|
311
|
-
const rows = this.
|
|
312
|
-
.prepare('SELECT * FROM entries ORDER BY updated_at DESC LIMIT ?')
|
|
313
|
-
.all(limit);
|
|
379
|
+
const rows = this.provider.all('SELECT * FROM entries ORDER BY updated_at DESC LIMIT ?', [limit]);
|
|
314
380
|
return rows.map(rowToEntry);
|
|
315
381
|
}
|
|
316
|
-
/**
|
|
317
|
-
* Export the entire vault as a JSON-serializable bundle.
|
|
318
|
-
*/
|
|
319
382
|
exportAll() {
|
|
320
|
-
const rows = this.
|
|
321
|
-
.prepare('SELECT * FROM entries ORDER BY domain, title')
|
|
322
|
-
.all();
|
|
383
|
+
const rows = this.provider.all('SELECT * FROM entries ORDER BY domain, title');
|
|
323
384
|
const entries = rows.map(rowToEntry);
|
|
324
385
|
return { entries, exportedAt: Math.floor(Date.now() / 1000), count: entries.length };
|
|
325
386
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Get entry age distribution — how old entries are, bucketed.
|
|
328
|
-
*/
|
|
329
387
|
getAgeReport() {
|
|
330
|
-
const rows = this.
|
|
331
|
-
.prepare('SELECT created_at, updated_at FROM entries')
|
|
332
|
-
.all();
|
|
388
|
+
const rows = this.provider.all('SELECT created_at, updated_at FROM entries');
|
|
333
389
|
const now = Math.floor(Date.now() / 1000);
|
|
334
390
|
const bucketDefs = [
|
|
335
391
|
{ label: 'today', minDays: 0, maxDays: 1 },
|
|
@@ -366,16 +422,14 @@ export class Vault {
|
|
|
366
422
|
const projectName = name ?? path.replace(/\/$/, '').split('/').pop() ?? path;
|
|
367
423
|
const existing = this.getProject(path);
|
|
368
424
|
if (existing) {
|
|
369
|
-
this.
|
|
370
|
-
.prepare('UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?')
|
|
371
|
-
.run(path);
|
|
425
|
+
this.provider.run('UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?', [path]);
|
|
372
426
|
return this.getProject(path);
|
|
373
427
|
}
|
|
374
|
-
this.
|
|
428
|
+
this.provider.run('INSERT INTO projects (path, name) VALUES (?, ?)', [path, projectName]);
|
|
375
429
|
return this.getProject(path);
|
|
376
430
|
}
|
|
377
431
|
getProject(path) {
|
|
378
|
-
const row = this.
|
|
432
|
+
const row = this.provider.get('SELECT * FROM projects WHERE path = ?', [path]);
|
|
379
433
|
if (!row)
|
|
380
434
|
return null;
|
|
381
435
|
return {
|
|
@@ -387,9 +441,7 @@ export class Vault {
|
|
|
387
441
|
};
|
|
388
442
|
}
|
|
389
443
|
listProjects() {
|
|
390
|
-
const rows = this.
|
|
391
|
-
.prepare('SELECT * FROM projects ORDER BY last_seen_at DESC')
|
|
392
|
-
.all();
|
|
444
|
+
const rows = this.provider.all('SELECT * FROM projects ORDER BY last_seen_at DESC');
|
|
393
445
|
return rows.map((row) => ({
|
|
394
446
|
path: row.path,
|
|
395
447
|
name: row.name,
|
|
@@ -400,9 +452,7 @@ export class Vault {
|
|
|
400
452
|
}
|
|
401
453
|
captureMemory(memory) {
|
|
402
454
|
const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
403
|
-
this.
|
|
404
|
-
.prepare(`INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`)
|
|
405
|
-
.run({
|
|
455
|
+
this.provider.run(`INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`, {
|
|
406
456
|
id,
|
|
407
457
|
projectPath: memory.projectPath,
|
|
408
458
|
type: memory.type,
|
|
@@ -428,9 +478,7 @@ export class Vault {
|
|
|
428
478
|
}
|
|
429
479
|
const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
430
480
|
try {
|
|
431
|
-
const rows = this.
|
|
432
|
-
.prepare(`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`)
|
|
433
|
-
.all({ query, limit, ...fp });
|
|
481
|
+
const rows = this.provider.all(`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`, { query, limit, ...fp });
|
|
434
482
|
return rows.map(rowToMemory);
|
|
435
483
|
}
|
|
436
484
|
catch {
|
|
@@ -449,19 +497,13 @@ export class Vault {
|
|
|
449
497
|
params.projectPath = options.projectPath;
|
|
450
498
|
}
|
|
451
499
|
const wc = `WHERE ${filters.join(' AND ')}`;
|
|
452
|
-
const rows = this.
|
|
453
|
-
.prepare(`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`)
|
|
454
|
-
.all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
|
|
500
|
+
const rows = this.provider.all(`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`, { ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
|
|
455
501
|
return rows.map(rowToMemory);
|
|
456
502
|
}
|
|
457
503
|
memoryStats() {
|
|
458
|
-
const total = this.
|
|
459
|
-
const byTypeRows = this.
|
|
460
|
-
|
|
461
|
-
.all();
|
|
462
|
-
const byProjectRows = this.db
|
|
463
|
-
.prepare('SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path')
|
|
464
|
-
.all();
|
|
504
|
+
const total = this.provider.get('SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL').count;
|
|
505
|
+
const byTypeRows = this.provider.all('SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type');
|
|
506
|
+
const byProjectRows = this.provider.all('SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path');
|
|
465
507
|
return {
|
|
466
508
|
total,
|
|
467
509
|
byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
|
|
@@ -469,11 +511,13 @@ export class Vault {
|
|
|
469
511
|
};
|
|
470
512
|
}
|
|
471
513
|
getMemory(id) {
|
|
472
|
-
const row = this.
|
|
514
|
+
const row = this.provider.get('SELECT * FROM memories WHERE id = ?', [
|
|
515
|
+
id,
|
|
516
|
+
]);
|
|
473
517
|
return row ? rowToMemory(row) : null;
|
|
474
518
|
}
|
|
475
519
|
deleteMemory(id) {
|
|
476
|
-
return this.
|
|
520
|
+
return this.provider.run('DELETE FROM memories WHERE id = ?', [id]).changes > 0;
|
|
477
521
|
}
|
|
478
522
|
memoryStatsDetailed(options) {
|
|
479
523
|
const filters = [];
|
|
@@ -491,21 +535,11 @@ export class Vault {
|
|
|
491
535
|
params.toDate = options.toDate;
|
|
492
536
|
}
|
|
493
537
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
494
|
-
const total = this.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
.get(params).count;
|
|
500
|
-
const byTypeRows = this.db
|
|
501
|
-
.prepare(`SELECT type as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY type`)
|
|
502
|
-
.all(params);
|
|
503
|
-
const byProjectRows = this.db
|
|
504
|
-
.prepare(`SELECT project_path as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY project_path`)
|
|
505
|
-
.all(params);
|
|
506
|
-
const dateRange = this.db
|
|
507
|
-
.prepare(`SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`)
|
|
508
|
-
.get(params);
|
|
538
|
+
const total = this.provider.get(`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`, params).count;
|
|
539
|
+
const archivedCount = this.provider.get(`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NOT NULL`, params).count;
|
|
540
|
+
const byTypeRows = this.provider.all(`SELECT type as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY type`, params);
|
|
541
|
+
const byProjectRows = this.provider.all(`SELECT project_path as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY project_path`, params);
|
|
542
|
+
const dateRange = this.provider.get(`SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`, params);
|
|
509
543
|
return {
|
|
510
544
|
total,
|
|
511
545
|
byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
|
|
@@ -530,21 +564,19 @@ export class Vault {
|
|
|
530
564
|
params.type = options.type;
|
|
531
565
|
}
|
|
532
566
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
533
|
-
const rows = this.
|
|
534
|
-
.prepare(`SELECT * FROM memories ${wc} ORDER BY created_at ASC`)
|
|
535
|
-
.all(params);
|
|
567
|
+
const rows = this.provider.all(`SELECT * FROM memories ${wc} ORDER BY created_at ASC`, Object.keys(params).length > 0 ? params : undefined);
|
|
536
568
|
return rows.map(rowToMemory);
|
|
537
569
|
}
|
|
538
570
|
importMemories(memories) {
|
|
539
|
-
const
|
|
571
|
+
const sql = `
|
|
540
572
|
INSERT OR IGNORE INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used, created_at, archived_at)
|
|
541
573
|
VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed, @createdAt, @archivedAt)
|
|
542
|
-
|
|
574
|
+
`;
|
|
543
575
|
let imported = 0;
|
|
544
576
|
let skipped = 0;
|
|
545
|
-
|
|
546
|
-
for (const m of
|
|
547
|
-
const result =
|
|
577
|
+
this.provider.transaction(() => {
|
|
578
|
+
for (const m of memories) {
|
|
579
|
+
const result = this.provider.run(sql, {
|
|
548
580
|
id: m.id,
|
|
549
581
|
projectPath: m.projectPath,
|
|
550
582
|
type: m.type,
|
|
@@ -562,20 +594,15 @@ export class Vault {
|
|
|
562
594
|
skipped++;
|
|
563
595
|
}
|
|
564
596
|
});
|
|
565
|
-
tx(memories);
|
|
566
597
|
return { imported, skipped };
|
|
567
598
|
}
|
|
568
599
|
pruneMemories(olderThanDays) {
|
|
569
600
|
const cutoff = Math.floor(Date.now() / 1000) - olderThanDays * 86400;
|
|
570
|
-
const result = this.
|
|
571
|
-
.prepare('DELETE FROM memories WHERE created_at < ? AND archived_at IS NULL')
|
|
572
|
-
.run(cutoff);
|
|
601
|
+
const result = this.provider.run('DELETE FROM memories WHERE created_at < ? AND archived_at IS NULL', [cutoff]);
|
|
573
602
|
return { pruned: result.changes };
|
|
574
603
|
}
|
|
575
604
|
deduplicateMemories() {
|
|
576
|
-
|
|
577
|
-
const dupeRows = this.db
|
|
578
|
-
.prepare(`
|
|
605
|
+
const dupeRows = this.provider.all(`
|
|
579
606
|
SELECT m1.id as id1, m2.id as id2
|
|
580
607
|
FROM memories m1
|
|
581
608
|
JOIN memories m2 ON m1.summary = m2.summary
|
|
@@ -584,9 +611,7 @@ export class Vault {
|
|
|
584
611
|
AND m1.id < m2.id
|
|
585
612
|
AND m1.archived_at IS NULL
|
|
586
613
|
AND m2.archived_at IS NULL
|
|
587
|
-
`)
|
|
588
|
-
.all();
|
|
589
|
-
// Group: keep the earliest (id1), remove all later duplicates
|
|
614
|
+
`);
|
|
590
615
|
const groupMap = new Map();
|
|
591
616
|
for (const row of dupeRows) {
|
|
592
617
|
if (!groupMap.has(row.id1))
|
|
@@ -604,19 +629,16 @@ export class Vault {
|
|
|
604
629
|
}
|
|
605
630
|
}
|
|
606
631
|
if (toRemove.size > 0) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
632
|
+
this.provider.transaction(() => {
|
|
633
|
+
for (const id of toRemove) {
|
|
634
|
+
this.provider.run('DELETE FROM memories WHERE id = ?', [id]);
|
|
635
|
+
}
|
|
611
636
|
});
|
|
612
|
-
tx([...toRemove]);
|
|
613
637
|
}
|
|
614
638
|
return { removed: toRemove.size, groups };
|
|
615
639
|
}
|
|
616
640
|
memoryTopics() {
|
|
617
|
-
const rows = this.
|
|
618
|
-
.prepare('SELECT topics FROM memories WHERE archived_at IS NULL')
|
|
619
|
-
.all();
|
|
641
|
+
const rows = this.provider.all('SELECT topics FROM memories WHERE archived_at IS NULL');
|
|
620
642
|
const topicCounts = new Map();
|
|
621
643
|
for (const row of rows) {
|
|
622
644
|
const topics = JSON.parse(row.topics || '[]');
|
|
@@ -629,13 +651,9 @@ export class Vault {
|
|
|
629
651
|
.sort((a, b) => b.count - a.count);
|
|
630
652
|
}
|
|
631
653
|
memoriesByProject() {
|
|
632
|
-
const rows = this.
|
|
633
|
-
.prepare('SELECT project_path as project, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path ORDER BY count DESC')
|
|
634
|
-
.all();
|
|
654
|
+
const rows = this.provider.all('SELECT project_path as project, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path ORDER BY count DESC');
|
|
635
655
|
return rows.map((row) => {
|
|
636
|
-
const memories = this.
|
|
637
|
-
.prepare('SELECT * FROM memories WHERE project_path = ? AND archived_at IS NULL ORDER BY created_at DESC')
|
|
638
|
-
.all(row.project);
|
|
656
|
+
const memories = this.provider.all('SELECT * FROM memories WHERE project_path = ? AND archived_at IS NULL ORDER BY created_at DESC', [row.project]);
|
|
639
657
|
return {
|
|
640
658
|
project: row.project,
|
|
641
659
|
count: row.count,
|
|
@@ -643,17 +661,116 @@ export class Vault {
|
|
|
643
661
|
};
|
|
644
662
|
});
|
|
645
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Rebuild the FTS5 index for the entries table.
|
|
666
|
+
* Useful after bulk operations or if the index gets out of sync.
|
|
667
|
+
*/
|
|
668
|
+
rebuildFtsIndex() {
|
|
669
|
+
try {
|
|
670
|
+
this.provider.run("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')");
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
// Graceful degradation — FTS rebuild failed (e.g. table doesn't exist yet)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Archive entries older than N days. Moves them to entries_archive.
|
|
678
|
+
*/
|
|
679
|
+
archive(options) {
|
|
680
|
+
const cutoff = Math.floor(Date.now() / 1000) - options.olderThanDays * 86400;
|
|
681
|
+
const reason = options.reason ?? `Archived: older than ${options.olderThanDays} days`;
|
|
682
|
+
return this.provider.transaction(() => {
|
|
683
|
+
// Find candidates
|
|
684
|
+
const candidates = this.provider.all('SELECT id FROM entries WHERE updated_at < ?', [cutoff]);
|
|
685
|
+
if (candidates.length === 0)
|
|
686
|
+
return { archived: 0 };
|
|
687
|
+
let archived = 0;
|
|
688
|
+
for (const { id } of candidates) {
|
|
689
|
+
// Copy to archive
|
|
690
|
+
this.provider.run(`INSERT OR IGNORE INTO entries_archive (id, type, domain, title, severity, description, context, example, counter_example, why, tags, applies_to, created_at, updated_at, valid_from, valid_until, archive_reason)
|
|
691
|
+
SELECT id, type, domain, title, severity, description, context, example, counter_example, why, tags, applies_to, created_at, updated_at, valid_from, valid_until, ?
|
|
692
|
+
FROM entries WHERE id = ?`, [reason, id]);
|
|
693
|
+
// Delete from active
|
|
694
|
+
const result = this.provider.run('DELETE FROM entries WHERE id = ?', [id]);
|
|
695
|
+
archived += result.changes;
|
|
696
|
+
}
|
|
697
|
+
return { archived };
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Restore an archived entry back to the active table.
|
|
702
|
+
*/
|
|
703
|
+
restore(id) {
|
|
704
|
+
return this.provider.transaction(() => {
|
|
705
|
+
const archived = this.provider.get('SELECT * FROM entries_archive WHERE id = ?', [id]);
|
|
706
|
+
if (!archived)
|
|
707
|
+
return false;
|
|
708
|
+
this.provider.run(`INSERT OR REPLACE INTO entries (id, type, domain, title, severity, description, context, example, counter_example, why, tags, applies_to, created_at, updated_at, valid_from, valid_until)
|
|
709
|
+
SELECT id, type, domain, title, severity, description, context, example, counter_example, why, tags, applies_to, created_at, updated_at, valid_from, valid_until
|
|
710
|
+
FROM entries_archive WHERE id = ?`, [id]);
|
|
711
|
+
this.provider.run('DELETE FROM entries_archive WHERE id = ?', [id]);
|
|
712
|
+
return true;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Optimize the database: VACUUM (SQLite only), ANALYZE, and FTS rebuild.
|
|
717
|
+
*/
|
|
718
|
+
optimize() {
|
|
719
|
+
let vacuumed = false;
|
|
720
|
+
let analyzed = false;
|
|
721
|
+
let ftsRebuilt = false;
|
|
722
|
+
// VACUUM only for SQLite
|
|
723
|
+
if (this.provider.backend === 'sqlite') {
|
|
724
|
+
try {
|
|
725
|
+
this.provider.execSql('VACUUM');
|
|
726
|
+
vacuumed = true;
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
// VACUUM may fail inside a transaction
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
this.provider.execSql('ANALYZE');
|
|
734
|
+
analyzed = true;
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// Non-critical
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
this.provider.ftsRebuild('entries');
|
|
741
|
+
this.provider.ftsRebuild('memories');
|
|
742
|
+
ftsRebuilt = true;
|
|
743
|
+
}
|
|
744
|
+
catch {
|
|
745
|
+
// Non-critical
|
|
746
|
+
}
|
|
747
|
+
return { vacuumed, analyzed, ftsRebuilt };
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Get the underlying persistence provider.
|
|
751
|
+
*/
|
|
752
|
+
getProvider() {
|
|
753
|
+
return this.provider;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Get the raw better-sqlite3 Database (backward compat).
|
|
757
|
+
* Throws if the provider is not SQLite.
|
|
758
|
+
*/
|
|
646
759
|
getDb() {
|
|
647
|
-
|
|
760
|
+
if (process.env.NODE_ENV !== 'test' && process.env.VITEST !== 'true') {
|
|
761
|
+
console.warn('Vault.getDb() is deprecated. Use vault.getProvider() instead.');
|
|
762
|
+
}
|
|
763
|
+
if (this.sqliteProvider) {
|
|
764
|
+
return this.sqliteProvider.getDatabase();
|
|
765
|
+
}
|
|
766
|
+
throw new Error('getDb() is only available with SQLite provider');
|
|
648
767
|
}
|
|
649
768
|
close() {
|
|
650
|
-
this.
|
|
769
|
+
this.provider.close();
|
|
651
770
|
}
|
|
652
771
|
}
|
|
653
|
-
function gc(
|
|
654
|
-
const rows =
|
|
655
|
-
.prepare(`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`)
|
|
656
|
-
.all();
|
|
772
|
+
function gc(provider, col) {
|
|
773
|
+
const rows = provider.all(`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`);
|
|
657
774
|
return Object.fromEntries(rows.map((r) => [r.key, r.count]));
|
|
658
775
|
}
|
|
659
776
|
function rowToEntry(row) {
|
|
@@ -670,6 +787,8 @@ function rowToEntry(row) {
|
|
|
670
787
|
why: row.why ?? undefined,
|
|
671
788
|
tags: JSON.parse(row.tags || '[]'),
|
|
672
789
|
appliesTo: JSON.parse(row.applies_to || '[]'),
|
|
790
|
+
validFrom: row.valid_from ?? undefined,
|
|
791
|
+
validUntil: row.valid_until ?? undefined,
|
|
673
792
|
};
|
|
674
793
|
}
|
|
675
794
|
function rowToSearchResult(row) {
|