@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/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,44 @@ 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
|
+
this.provider.run('PRAGMA synchronous = NORMAL');
|
|
57
|
+
} else {
|
|
58
|
+
this.provider = providerOrPath;
|
|
59
|
+
this.sqliteProvider =
|
|
60
|
+
providerOrPath instanceof SQLitePersistenceProvider ? providerOrPath : null;
|
|
61
|
+
}
|
|
49
62
|
this.initialize();
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
setSyncManager(mgr: import('../cognee/sync-manager.js').CogneeSyncManager): void {
|
|
66
|
+
this.syncManager = mgr;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Backward-compatible factory. */
|
|
70
|
+
static createWithSQLite(dbPath: string = ':memory:'): Vault {
|
|
71
|
+
return new Vault(dbPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
private initialize(): void {
|
|
53
|
-
this.
|
|
75
|
+
this.provider.execSql(`
|
|
54
76
|
CREATE TABLE IF NOT EXISTS entries (
|
|
55
77
|
id TEXT PRIMARY KEY,
|
|
56
|
-
type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule')),
|
|
78
|
+
type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule', 'playbook')),
|
|
57
79
|
domain TEXT NOT NULL,
|
|
58
80
|
title TEXT NOT NULL,
|
|
59
81
|
severity TEXT NOT NULL CHECK(severity IN ('critical', 'warning', 'suggestion')),
|
|
@@ -78,6 +100,23 @@ export class Vault {
|
|
|
78
100
|
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);
|
|
79
101
|
INSERT INTO entries_fts(rowid,id,title,description,context,tags) VALUES(new.rowid,new.id,new.title,new.description,new.context,new.tags);
|
|
80
102
|
END;
|
|
103
|
+
CREATE TABLE IF NOT EXISTS entries_archive (
|
|
104
|
+
id TEXT PRIMARY KEY,
|
|
105
|
+
type TEXT NOT NULL,
|
|
106
|
+
domain TEXT NOT NULL,
|
|
107
|
+
title TEXT NOT NULL,
|
|
108
|
+
severity TEXT NOT NULL,
|
|
109
|
+
description TEXT NOT NULL,
|
|
110
|
+
context TEXT, example TEXT, counter_example TEXT, why TEXT,
|
|
111
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
112
|
+
applies_to TEXT DEFAULT '[]',
|
|
113
|
+
created_at INTEGER NOT NULL,
|
|
114
|
+
updated_at INTEGER NOT NULL,
|
|
115
|
+
valid_from INTEGER,
|
|
116
|
+
valid_until INTEGER,
|
|
117
|
+
archived_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
118
|
+
archive_reason TEXT
|
|
119
|
+
);
|
|
81
120
|
CREATE TABLE IF NOT EXISTS projects (
|
|
82
121
|
path TEXT PRIMARY KEY,
|
|
83
122
|
name TEXT NOT NULL,
|
|
@@ -130,27 +169,36 @@ export class Vault {
|
|
|
130
169
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
131
170
|
);
|
|
132
171
|
CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_entries_domain ON entries(domain);
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(type);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_entries_severity ON entries(severity);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_path);
|
|
176
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
133
177
|
`);
|
|
134
178
|
this.migrateBrainSchema();
|
|
179
|
+
this.migrateTemporalSchema();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private migrateTemporalSchema(): void {
|
|
183
|
+
try {
|
|
184
|
+
this.provider.run('ALTER TABLE entries ADD COLUMN valid_from INTEGER');
|
|
185
|
+
} catch {
|
|
186
|
+
// Column already exists
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
this.provider.run('ALTER TABLE entries ADD COLUMN valid_until INTEGER');
|
|
190
|
+
} catch {
|
|
191
|
+
// Column already exists
|
|
192
|
+
}
|
|
135
193
|
}
|
|
136
194
|
|
|
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
195
|
private migrateBrainSchema(): void {
|
|
143
|
-
|
|
144
|
-
const columns = this.db.prepare('PRAGMA table_info(brain_feedback)').all() as Array<{
|
|
145
|
-
name: string;
|
|
146
|
-
}>;
|
|
196
|
+
const columns = this.provider.all<{ name: string }>('PRAGMA table_info(brain_feedback)');
|
|
147
197
|
const hasSource = columns.some((c) => c.name === 'source');
|
|
148
198
|
|
|
149
199
|
if (!hasSource && columns.length > 0) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.db
|
|
153
|
-
.prepare(`
|
|
200
|
+
this.provider.transaction(() => {
|
|
201
|
+
this.provider.run(`
|
|
154
202
|
CREATE TABLE brain_feedback_new (
|
|
155
203
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
156
204
|
query TEXT NOT NULL,
|
|
@@ -163,29 +211,23 @@ export class Vault {
|
|
|
163
211
|
reason TEXT,
|
|
164
212
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
165
213
|
)
|
|
166
|
-
`)
|
|
167
|
-
|
|
168
|
-
this.db
|
|
169
|
-
.prepare(`
|
|
214
|
+
`);
|
|
215
|
+
this.provider.run(`
|
|
170
216
|
INSERT INTO brain_feedback_new (id, query, entry_id, action, created_at)
|
|
171
217
|
SELECT id, query, entry_id, action, created_at FROM brain_feedback
|
|
172
|
-
`)
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})();
|
|
218
|
+
`);
|
|
219
|
+
this.provider.run('DROP TABLE brain_feedback');
|
|
220
|
+
this.provider.run('ALTER TABLE brain_feedback_new RENAME TO brain_feedback');
|
|
221
|
+
this.provider.run(
|
|
222
|
+
'CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query)',
|
|
223
|
+
);
|
|
224
|
+
});
|
|
180
225
|
}
|
|
181
226
|
|
|
182
|
-
// Add extracted_at to brain_sessions if it exists but lacks the column
|
|
183
227
|
try {
|
|
184
|
-
const sessionCols = this.
|
|
185
|
-
name: string;
|
|
186
|
-
}>;
|
|
228
|
+
const sessionCols = this.provider.all<{ name: string }>('PRAGMA table_info(brain_sessions)');
|
|
187
229
|
if (sessionCols.length > 0 && !sessionCols.some((c) => c.name === 'extracted_at')) {
|
|
188
|
-
this.
|
|
230
|
+
this.provider.run('ALTER TABLE brain_sessions ADD COLUMN extracted_at TEXT');
|
|
189
231
|
}
|
|
190
232
|
} catch {
|
|
191
233
|
// brain_sessions table doesn't exist yet — BrainIntelligence will create it
|
|
@@ -193,17 +235,17 @@ export class Vault {
|
|
|
193
235
|
}
|
|
194
236
|
|
|
195
237
|
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)
|
|
238
|
+
const sql = `
|
|
239
|
+
INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to,valid_from,valid_until)
|
|
240
|
+
VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo,@validFrom,@validUntil)
|
|
199
241
|
ON CONFLICT(id) DO UPDATE SET type=excluded.type,domain=excluded.domain,title=excluded.title,severity=excluded.severity,
|
|
200
242
|
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
|
-
|
|
243
|
+
why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,valid_from=excluded.valid_from,valid_until=excluded.valid_until,updated_at=unixepoch()
|
|
244
|
+
`;
|
|
245
|
+
return this.provider.transaction(() => {
|
|
204
246
|
let count = 0;
|
|
205
|
-
for (const entry of
|
|
206
|
-
|
|
247
|
+
for (const entry of entries) {
|
|
248
|
+
this.provider.run(sql, {
|
|
207
249
|
id: entry.id,
|
|
208
250
|
type: entry.type,
|
|
209
251
|
domain: entry.domain,
|
|
@@ -216,21 +258,31 @@ export class Vault {
|
|
|
216
258
|
why: entry.why ?? null,
|
|
217
259
|
tags: JSON.stringify(entry.tags),
|
|
218
260
|
appliesTo: JSON.stringify(entry.appliesTo ?? []),
|
|
261
|
+
validFrom: entry.validFrom ?? null,
|
|
262
|
+
validUntil: entry.validUntil ?? null,
|
|
219
263
|
});
|
|
220
264
|
count++;
|
|
265
|
+
if (this.syncManager) {
|
|
266
|
+
this.syncManager.enqueue('ingest', entry.id, entry);
|
|
267
|
+
}
|
|
221
268
|
}
|
|
222
269
|
return count;
|
|
223
270
|
});
|
|
224
|
-
return tx(entries);
|
|
225
271
|
}
|
|
226
272
|
|
|
227
273
|
search(
|
|
228
274
|
query: string,
|
|
229
|
-
options?: {
|
|
275
|
+
options?: {
|
|
276
|
+
domain?: string;
|
|
277
|
+
type?: string;
|
|
278
|
+
severity?: string;
|
|
279
|
+
limit?: number;
|
|
280
|
+
includeExpired?: boolean;
|
|
281
|
+
},
|
|
230
282
|
): SearchResult[] {
|
|
231
283
|
const limit = options?.limit ?? 10;
|
|
232
284
|
const filters: string[] = [];
|
|
233
|
-
const fp: Record<string,
|
|
285
|
+
const fp: Record<string, unknown> = {};
|
|
234
286
|
if (options?.domain) {
|
|
235
287
|
filters.push('e.domain = @domain');
|
|
236
288
|
fp.domain = options.domain;
|
|
@@ -243,13 +295,18 @@ export class Vault {
|
|
|
243
295
|
filters.push('e.severity = @severity');
|
|
244
296
|
fp.severity = options.severity;
|
|
245
297
|
}
|
|
298
|
+
if (!options?.includeExpired) {
|
|
299
|
+
const now = Math.floor(Date.now() / 1000);
|
|
300
|
+
filters.push('(e.valid_until IS NULL OR e.valid_until > @now)');
|
|
301
|
+
filters.push('(e.valid_from IS NULL OR e.valid_from <= @now)');
|
|
302
|
+
fp.now = now;
|
|
303
|
+
}
|
|
246
304
|
const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
247
305
|
try {
|
|
248
|
-
const rows = this.
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.all({ query, limit, ...fp }) as Array<Record<string, unknown>>;
|
|
306
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
307
|
+
`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`,
|
|
308
|
+
{ query, limit, ...fp },
|
|
309
|
+
);
|
|
253
310
|
return rows.map(rowToSearchResult);
|
|
254
311
|
} catch {
|
|
255
312
|
return [];
|
|
@@ -257,9 +314,9 @@ export class Vault {
|
|
|
257
314
|
}
|
|
258
315
|
|
|
259
316
|
get(id: string): IntelligenceEntry | null {
|
|
260
|
-
const row = this.
|
|
261
|
-
|
|
262
|
-
|
|
317
|
+
const row = this.provider.get<Record<string, unknown>>('SELECT * FROM entries WHERE id = ?', [
|
|
318
|
+
id,
|
|
319
|
+
]);
|
|
263
320
|
return row ? rowToEntry(row) : null;
|
|
264
321
|
}
|
|
265
322
|
|
|
@@ -270,6 +327,7 @@ export class Vault {
|
|
|
270
327
|
tags?: string[];
|
|
271
328
|
limit?: number;
|
|
272
329
|
offset?: number;
|
|
330
|
+
includeExpired?: boolean;
|
|
273
331
|
}): IntelligenceEntry[] {
|
|
274
332
|
const filters: string[] = [];
|
|
275
333
|
const params: Record<string, unknown> = {};
|
|
@@ -292,26 +350,29 @@ export class Vault {
|
|
|
292
350
|
});
|
|
293
351
|
filters.push(`(${c.join(' OR ')})`);
|
|
294
352
|
}
|
|
353
|
+
if (!options?.includeExpired) {
|
|
354
|
+
const now = Math.floor(Date.now() / 1000);
|
|
355
|
+
filters.push('(valid_until IS NULL OR valid_until > @now)');
|
|
356
|
+
filters.push('(valid_from IS NULL OR valid_from <= @now)');
|
|
357
|
+
params.now = now;
|
|
358
|
+
}
|
|
295
359
|
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
|
-
>;
|
|
360
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
361
|
+
`SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`,
|
|
362
|
+
{ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 },
|
|
363
|
+
);
|
|
303
364
|
return rows.map(rowToEntry);
|
|
304
365
|
}
|
|
305
366
|
|
|
306
367
|
stats(): VaultStats {
|
|
307
|
-
const total = (
|
|
308
|
-
|
|
309
|
-
)
|
|
368
|
+
const total = this.provider.get<{ count: number }>(
|
|
369
|
+
'SELECT COUNT(*) as count FROM entries',
|
|
370
|
+
)!.count;
|
|
310
371
|
return {
|
|
311
372
|
totalEntries: total,
|
|
312
|
-
byType: gc(this.
|
|
313
|
-
byDomain: gc(this.
|
|
314
|
-
bySeverity: gc(this.
|
|
373
|
+
byType: gc(this.provider, 'type'),
|
|
374
|
+
byDomain: gc(this.provider, 'domain'),
|
|
375
|
+
bySeverity: gc(this.provider, 'severity'),
|
|
315
376
|
};
|
|
316
377
|
}
|
|
317
378
|
|
|
@@ -319,13 +380,13 @@ export class Vault {
|
|
|
319
380
|
this.seed([entry]);
|
|
320
381
|
}
|
|
321
382
|
remove(id: string): boolean {
|
|
322
|
-
|
|
383
|
+
const deleted = this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes > 0;
|
|
384
|
+
if (deleted && this.syncManager) {
|
|
385
|
+
this.syncManager.enqueue('delete', id);
|
|
386
|
+
}
|
|
387
|
+
return deleted;
|
|
323
388
|
}
|
|
324
389
|
|
|
325
|
-
/**
|
|
326
|
-
* Partial update of an existing entry's mutable fields.
|
|
327
|
-
* Returns the updated entry or null if not found.
|
|
328
|
-
*/
|
|
329
390
|
update(
|
|
330
391
|
id: string,
|
|
331
392
|
fields: Partial<
|
|
@@ -342,6 +403,8 @@ export class Vault {
|
|
|
342
403
|
| 'severity'
|
|
343
404
|
| 'type'
|
|
344
405
|
| 'domain'
|
|
406
|
+
| 'validFrom'
|
|
407
|
+
| 'validUntil'
|
|
345
408
|
>
|
|
346
409
|
>,
|
|
347
410
|
): IntelligenceEntry | null {
|
|
@@ -352,27 +415,58 @@ export class Vault {
|
|
|
352
415
|
return this.get(id);
|
|
353
416
|
}
|
|
354
417
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
418
|
+
setTemporal(id: string, validFrom?: number, validUntil?: number): boolean {
|
|
419
|
+
const sets: string[] = [];
|
|
420
|
+
const params: Record<string, unknown> = { id };
|
|
421
|
+
if (validFrom !== undefined) {
|
|
422
|
+
sets.push('valid_from = @validFrom');
|
|
423
|
+
params.validFrom = validFrom;
|
|
424
|
+
}
|
|
425
|
+
if (validUntil !== undefined) {
|
|
426
|
+
sets.push('valid_until = @validUntil');
|
|
427
|
+
params.validUntil = validUntil;
|
|
428
|
+
}
|
|
429
|
+
if (sets.length === 0) return false;
|
|
430
|
+
sets.push('updated_at = unixepoch()');
|
|
431
|
+
return (
|
|
432
|
+
this.provider.run(`UPDATE entries SET ${sets.join(', ')} WHERE id = @id`, params).changes > 0
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
findExpiring(withinDays: number): IntelligenceEntry[] {
|
|
437
|
+
const now = Math.floor(Date.now() / 1000);
|
|
438
|
+
const cutoff = now + withinDays * 86400;
|
|
439
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
440
|
+
'SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until > @now AND valid_until <= @cutoff ORDER BY valid_until ASC',
|
|
441
|
+
{ now, cutoff },
|
|
442
|
+
);
|
|
443
|
+
return rows.map(rowToEntry);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
findExpired(limit: number = 50): IntelligenceEntry[] {
|
|
447
|
+
const now = Math.floor(Date.now() / 1000);
|
|
448
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
449
|
+
'SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until <= @now ORDER BY valid_until DESC LIMIT @limit',
|
|
450
|
+
{ now, limit },
|
|
451
|
+
);
|
|
452
|
+
return rows.map(rowToEntry);
|
|
453
|
+
}
|
|
454
|
+
|
|
359
455
|
bulkRemove(ids: string[]): number {
|
|
360
|
-
|
|
361
|
-
const tx = this.db.transaction((idList: string[]) => {
|
|
456
|
+
return this.provider.transaction(() => {
|
|
362
457
|
let count = 0;
|
|
363
|
-
for (const id of
|
|
364
|
-
count +=
|
|
458
|
+
for (const id of ids) {
|
|
459
|
+
count += this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes;
|
|
460
|
+
if (this.syncManager) {
|
|
461
|
+
this.syncManager.enqueue('delete', id);
|
|
462
|
+
}
|
|
365
463
|
}
|
|
366
464
|
return count;
|
|
367
465
|
});
|
|
368
|
-
return tx(ids);
|
|
369
466
|
}
|
|
370
467
|
|
|
371
|
-
/**
|
|
372
|
-
* List all unique tags with their occurrence counts.
|
|
373
|
-
*/
|
|
374
468
|
getTags(): Array<{ tag: string; count: number }> {
|
|
375
|
-
const rows = this.
|
|
469
|
+
const rows = this.provider.all<{ tags: string }>('SELECT tags FROM entries');
|
|
376
470
|
const counts = new Map<string, number>();
|
|
377
471
|
for (const row of rows) {
|
|
378
472
|
const tags: string[] = JSON.parse(row.tags || '[]');
|
|
@@ -385,49 +479,37 @@ export class Vault {
|
|
|
385
479
|
.sort((a, b) => b.count - a.count);
|
|
386
480
|
}
|
|
387
481
|
|
|
388
|
-
/**
|
|
389
|
-
* List all domains with their entry counts.
|
|
390
|
-
*/
|
|
391
482
|
getDomains(): Array<{ domain: string; count: number }> {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return rows;
|
|
483
|
+
return this.provider.all<{ domain: string; count: number }>(
|
|
484
|
+
'SELECT domain, COUNT(*) as count FROM entries GROUP BY domain ORDER BY count DESC',
|
|
485
|
+
);
|
|
396
486
|
}
|
|
397
487
|
|
|
398
|
-
/**
|
|
399
|
-
* Get recently added or updated entries, ordered by updated_at DESC.
|
|
400
|
-
*/
|
|
401
488
|
getRecent(limit: number = 20): IntelligenceEntry[] {
|
|
402
|
-
const rows = this.
|
|
403
|
-
|
|
404
|
-
|
|
489
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
490
|
+
'SELECT * FROM entries ORDER BY updated_at DESC LIMIT ?',
|
|
491
|
+
[limit],
|
|
492
|
+
);
|
|
405
493
|
return rows.map(rowToEntry);
|
|
406
494
|
}
|
|
407
495
|
|
|
408
|
-
/**
|
|
409
|
-
* Export the entire vault as a JSON-serializable bundle.
|
|
410
|
-
*/
|
|
411
496
|
exportAll(): { entries: IntelligenceEntry[]; exportedAt: number; count: number } {
|
|
412
|
-
const rows = this.
|
|
413
|
-
|
|
414
|
-
|
|
497
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
498
|
+
'SELECT * FROM entries ORDER BY domain, title',
|
|
499
|
+
);
|
|
415
500
|
const entries = rows.map(rowToEntry);
|
|
416
501
|
return { entries, exportedAt: Math.floor(Date.now() / 1000), count: entries.length };
|
|
417
502
|
}
|
|
418
503
|
|
|
419
|
-
/**
|
|
420
|
-
* Get entry age distribution — how old entries are, bucketed.
|
|
421
|
-
*/
|
|
422
504
|
getAgeReport(): {
|
|
423
505
|
total: number;
|
|
424
506
|
buckets: Array<{ label: string; count: number; minDays: number; maxDays: number }>;
|
|
425
507
|
oldestTimestamp: number | null;
|
|
426
508
|
newestTimestamp: number | null;
|
|
427
509
|
} {
|
|
428
|
-
const rows = this.
|
|
429
|
-
|
|
430
|
-
|
|
510
|
+
const rows = this.provider.all<{ created_at: number; updated_at: number }>(
|
|
511
|
+
'SELECT created_at, updated_at FROM entries',
|
|
512
|
+
);
|
|
431
513
|
const now = Math.floor(Date.now() / 1000);
|
|
432
514
|
const bucketDefs = [
|
|
433
515
|
{ label: 'today', minDays: 0, maxDays: 1 },
|
|
@@ -463,21 +545,21 @@ export class Vault {
|
|
|
463
545
|
const projectName = name ?? path.replace(/\/$/, '').split('/').pop() ?? path;
|
|
464
546
|
const existing = this.getProject(path);
|
|
465
547
|
if (existing) {
|
|
466
|
-
this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
.run(path);
|
|
548
|
+
this.provider.run(
|
|
549
|
+
'UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?',
|
|
550
|
+
[path],
|
|
551
|
+
);
|
|
471
552
|
return this.getProject(path)!;
|
|
472
553
|
}
|
|
473
|
-
this.
|
|
554
|
+
this.provider.run('INSERT INTO projects (path, name) VALUES (?, ?)', [path, projectName]);
|
|
474
555
|
return this.getProject(path)!;
|
|
475
556
|
}
|
|
476
557
|
|
|
477
558
|
getProject(path: string): ProjectInfo | null {
|
|
478
|
-
const row = this.
|
|
479
|
-
|
|
480
|
-
|
|
559
|
+
const row = this.provider.get<Record<string, unknown>>(
|
|
560
|
+
'SELECT * FROM projects WHERE path = ?',
|
|
561
|
+
[path],
|
|
562
|
+
);
|
|
481
563
|
if (!row) return null;
|
|
482
564
|
return {
|
|
483
565
|
path: row.path as string,
|
|
@@ -489,9 +571,9 @@ export class Vault {
|
|
|
489
571
|
}
|
|
490
572
|
|
|
491
573
|
listProjects(): ProjectInfo[] {
|
|
492
|
-
const rows = this.
|
|
493
|
-
|
|
494
|
-
|
|
574
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
575
|
+
'SELECT * FROM projects ORDER BY last_seen_at DESC',
|
|
576
|
+
);
|
|
495
577
|
return rows.map((row) => ({
|
|
496
578
|
path: row.path as string,
|
|
497
579
|
name: row.name as string,
|
|
@@ -503,11 +585,9 @@ export class Vault {
|
|
|
503
585
|
|
|
504
586
|
captureMemory(memory: Omit<Memory, 'id' | 'createdAt' | 'archivedAt'>): Memory {
|
|
505
587
|
const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
506
|
-
this.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
)
|
|
510
|
-
.run({
|
|
588
|
+
this.provider.run(
|
|
589
|
+
`INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`,
|
|
590
|
+
{
|
|
511
591
|
id,
|
|
512
592
|
projectPath: memory.projectPath,
|
|
513
593
|
type: memory.type,
|
|
@@ -516,7 +596,8 @@ export class Vault {
|
|
|
516
596
|
topics: JSON.stringify(memory.topics),
|
|
517
597
|
filesModified: JSON.stringify(memory.filesModified),
|
|
518
598
|
toolsUsed: JSON.stringify(memory.toolsUsed),
|
|
519
|
-
}
|
|
599
|
+
},
|
|
600
|
+
);
|
|
520
601
|
return this.getMemory(id)!;
|
|
521
602
|
}
|
|
522
603
|
|
|
@@ -537,11 +618,10 @@ export class Vault {
|
|
|
537
618
|
}
|
|
538
619
|
const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
539
620
|
try {
|
|
540
|
-
const rows = this.
|
|
541
|
-
.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
.all({ query, limit, ...fp }) as Array<Record<string, unknown>>;
|
|
621
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
622
|
+
`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`,
|
|
623
|
+
{ query, limit, ...fp },
|
|
624
|
+
);
|
|
545
625
|
return rows.map(rowToMemory);
|
|
546
626
|
} catch {
|
|
547
627
|
return [];
|
|
@@ -565,30 +645,23 @@ export class Vault {
|
|
|
565
645
|
params.projectPath = options.projectPath;
|
|
566
646
|
}
|
|
567
647
|
const wc = `WHERE ${filters.join(' AND ')}`;
|
|
568
|
-
const rows = this.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
>;
|
|
648
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
649
|
+
`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`,
|
|
650
|
+
{ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 },
|
|
651
|
+
);
|
|
573
652
|
return rows.map(rowToMemory);
|
|
574
653
|
}
|
|
575
654
|
|
|
576
655
|
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 }>;
|
|
656
|
+
const total = this.provider.get<{ count: number }>(
|
|
657
|
+
'SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL',
|
|
658
|
+
)!.count;
|
|
659
|
+
const byTypeRows = this.provider.all<{ key: string; count: number }>(
|
|
660
|
+
'SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type',
|
|
661
|
+
);
|
|
662
|
+
const byProjectRows = this.provider.all<{ key: string; count: number }>(
|
|
663
|
+
'SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path',
|
|
664
|
+
);
|
|
592
665
|
return {
|
|
593
666
|
total,
|
|
594
667
|
byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
|
|
@@ -597,15 +670,14 @@ export class Vault {
|
|
|
597
670
|
}
|
|
598
671
|
|
|
599
672
|
getMemory(id: string): Memory | null {
|
|
600
|
-
const row = this.
|
|
601
|
-
|
|
602
|
-
|
|
673
|
+
const row = this.provider.get<Record<string, unknown>>('SELECT * FROM memories WHERE id = ?', [
|
|
674
|
+
id,
|
|
675
|
+
]);
|
|
603
676
|
return row ? rowToMemory(row) : null;
|
|
604
677
|
}
|
|
605
678
|
|
|
606
|
-
|
|
607
679
|
deleteMemory(id: string): boolean {
|
|
608
|
-
return this.
|
|
680
|
+
return this.provider.run('DELETE FROM memories WHERE id = ?', [id]).changes > 0;
|
|
609
681
|
}
|
|
610
682
|
|
|
611
683
|
memoryStatsDetailed(options?: {
|
|
@@ -629,35 +701,30 @@ export class Vault {
|
|
|
629
701
|
}
|
|
630
702
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
631
703
|
|
|
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 };
|
|
704
|
+
const total = this.provider.get<{ count: number }>(
|
|
705
|
+
`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
|
|
706
|
+
params,
|
|
707
|
+
)!.count;
|
|
708
|
+
|
|
709
|
+
const archivedCount = this.provider.get<{ count: number }>(
|
|
710
|
+
`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NOT NULL`,
|
|
711
|
+
params,
|
|
712
|
+
)!.count;
|
|
713
|
+
|
|
714
|
+
const byTypeRows = this.provider.all<{ key: string; count: number }>(
|
|
715
|
+
`SELECT type as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY type`,
|
|
716
|
+
params,
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
const byProjectRows = this.provider.all<{ key: string; count: number }>(
|
|
720
|
+
`SELECT project_path as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY project_path`,
|
|
721
|
+
params,
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
const dateRange = this.provider.get<{ oldest: number | null; newest: number | null }>(
|
|
725
|
+
`SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
|
|
726
|
+
params,
|
|
727
|
+
)!;
|
|
661
728
|
|
|
662
729
|
return {
|
|
663
730
|
total,
|
|
@@ -688,22 +755,23 @@ export class Vault {
|
|
|
688
755
|
params.type = options.type;
|
|
689
756
|
}
|
|
690
757
|
const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
691
|
-
const rows = this.
|
|
692
|
-
|
|
693
|
-
.
|
|
758
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
759
|
+
`SELECT * FROM memories ${wc} ORDER BY created_at ASC`,
|
|
760
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
761
|
+
);
|
|
694
762
|
return rows.map(rowToMemory);
|
|
695
763
|
}
|
|
696
764
|
|
|
697
765
|
importMemories(memories: Memory[]): { imported: number; skipped: number } {
|
|
698
|
-
const
|
|
766
|
+
const sql = `
|
|
699
767
|
INSERT OR IGNORE INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used, created_at, archived_at)
|
|
700
768
|
VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed, @createdAt, @archivedAt)
|
|
701
|
-
|
|
769
|
+
`;
|
|
702
770
|
let imported = 0;
|
|
703
771
|
let skipped = 0;
|
|
704
|
-
|
|
705
|
-
for (const m of
|
|
706
|
-
const result =
|
|
772
|
+
this.provider.transaction(() => {
|
|
773
|
+
for (const m of memories) {
|
|
774
|
+
const result = this.provider.run(sql, {
|
|
707
775
|
id: m.id,
|
|
708
776
|
projectPath: m.projectPath,
|
|
709
777
|
type: m.type,
|
|
@@ -719,22 +787,20 @@ export class Vault {
|
|
|
719
787
|
else skipped++;
|
|
720
788
|
}
|
|
721
789
|
});
|
|
722
|
-
tx(memories);
|
|
723
790
|
return { imported, skipped };
|
|
724
791
|
}
|
|
725
792
|
|
|
726
793
|
pruneMemories(olderThanDays: number): { pruned: number } {
|
|
727
794
|
const cutoff = Math.floor(Date.now() / 1000) - olderThanDays * 86400;
|
|
728
|
-
const result = this.
|
|
729
|
-
|
|
730
|
-
|
|
795
|
+
const result = this.provider.run(
|
|
796
|
+
'DELETE FROM memories WHERE created_at < ? AND archived_at IS NULL',
|
|
797
|
+
[cutoff],
|
|
798
|
+
);
|
|
731
799
|
return { pruned: result.changes };
|
|
732
800
|
}
|
|
733
801
|
|
|
734
802
|
deduplicateMemories(): { removed: number; groups: Array<{ kept: string; removed: string[] }> } {
|
|
735
|
-
|
|
736
|
-
const dupeRows = this.db
|
|
737
|
-
.prepare(`
|
|
803
|
+
const dupeRows = this.provider.all<{ id1: string; id2: string }>(`
|
|
738
804
|
SELECT m1.id as id1, m2.id as id2
|
|
739
805
|
FROM memories m1
|
|
740
806
|
JOIN memories m2 ON m1.summary = m2.summary
|
|
@@ -743,10 +809,8 @@ export class Vault {
|
|
|
743
809
|
AND m1.id < m2.id
|
|
744
810
|
AND m1.archived_at IS NULL
|
|
745
811
|
AND m2.archived_at IS NULL
|
|
746
|
-
`)
|
|
747
|
-
.all() as Array<{ id1: string; id2: string }>;
|
|
812
|
+
`);
|
|
748
813
|
|
|
749
|
-
// Group: keep the earliest (id1), remove all later duplicates
|
|
750
814
|
const groupMap = new Map<string, Set<string>>();
|
|
751
815
|
for (const row of dupeRows) {
|
|
752
816
|
if (!groupMap.has(row.id1)) groupMap.set(row.id1, new Set());
|
|
@@ -764,20 +828,20 @@ export class Vault {
|
|
|
764
828
|
}
|
|
765
829
|
|
|
766
830
|
if (toRemove.size > 0) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
831
|
+
this.provider.transaction(() => {
|
|
832
|
+
for (const id of toRemove) {
|
|
833
|
+
this.provider.run('DELETE FROM memories WHERE id = ?', [id]);
|
|
834
|
+
}
|
|
770
835
|
});
|
|
771
|
-
tx([...toRemove]);
|
|
772
836
|
}
|
|
773
837
|
|
|
774
838
|
return { removed: toRemove.size, groups };
|
|
775
839
|
}
|
|
776
840
|
|
|
777
841
|
memoryTopics(): Array<{ topic: string; count: number }> {
|
|
778
|
-
const rows = this.
|
|
779
|
-
|
|
780
|
-
|
|
842
|
+
const rows = this.provider.all<{ topics: string }>(
|
|
843
|
+
'SELECT topics FROM memories WHERE archived_at IS NULL',
|
|
844
|
+
);
|
|
781
845
|
|
|
782
846
|
const topicCounts = new Map<string, number>();
|
|
783
847
|
for (const row of rows) {
|
|
@@ -793,18 +857,15 @@ export class Vault {
|
|
|
793
857
|
}
|
|
794
858
|
|
|
795
859
|
memoriesByProject(): Array<{ project: string; count: number; memories: Memory[] }> {
|
|
796
|
-
const rows = this.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
)
|
|
800
|
-
.all() as Array<{ project: string; count: number }>;
|
|
860
|
+
const rows = this.provider.all<{ project: string; count: number }>(
|
|
861
|
+
'SELECT project_path as project, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path ORDER BY count DESC',
|
|
862
|
+
);
|
|
801
863
|
|
|
802
864
|
return rows.map((row) => {
|
|
803
|
-
const memories = this.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
.all(row.project) as Array<Record<string, unknown>>;
|
|
865
|
+
const memories = this.provider.all<Record<string, unknown>>(
|
|
866
|
+
'SELECT * FROM memories WHERE project_path = ? AND archived_at IS NULL ORDER BY created_at DESC',
|
|
867
|
+
[row.project],
|
|
868
|
+
);
|
|
808
869
|
return {
|
|
809
870
|
project: row.project,
|
|
810
871
|
count: row.count,
|
|
@@ -813,19 +874,140 @@ export class Vault {
|
|
|
813
874
|
});
|
|
814
875
|
}
|
|
815
876
|
|
|
816
|
-
|
|
817
|
-
|
|
877
|
+
/**
|
|
878
|
+
* Rebuild the FTS5 index for the entries table.
|
|
879
|
+
* Useful after bulk operations or if the index gets out of sync.
|
|
880
|
+
*/
|
|
881
|
+
rebuildFtsIndex(): void {
|
|
882
|
+
try {
|
|
883
|
+
this.provider.run("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')");
|
|
884
|
+
} catch {
|
|
885
|
+
// Graceful degradation — FTS rebuild failed (e.g. table doesn't exist yet)
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Archive entries older than N days. Moves them to entries_archive.
|
|
891
|
+
*/
|
|
892
|
+
archive(options: { olderThanDays: number; reason?: string }): { archived: number } {
|
|
893
|
+
const cutoff = Math.floor(Date.now() / 1000) - options.olderThanDays * 86400;
|
|
894
|
+
const reason = options.reason ?? `Archived: older than ${options.olderThanDays} days`;
|
|
895
|
+
|
|
896
|
+
return this.provider.transaction(() => {
|
|
897
|
+
// Find candidates
|
|
898
|
+
const candidates = this.provider.all<{ id: string }>(
|
|
899
|
+
'SELECT id FROM entries WHERE updated_at < ?',
|
|
900
|
+
[cutoff],
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
if (candidates.length === 0) return { archived: 0 };
|
|
904
|
+
|
|
905
|
+
let archived = 0;
|
|
906
|
+
for (const { id } of candidates) {
|
|
907
|
+
// Copy to archive
|
|
908
|
+
this.provider.run(
|
|
909
|
+
`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)
|
|
910
|
+
SELECT id, type, domain, title, severity, description, context, example, counter_example, why, tags, applies_to, created_at, updated_at, valid_from, valid_until, ?
|
|
911
|
+
FROM entries WHERE id = ?`,
|
|
912
|
+
[reason, id],
|
|
913
|
+
);
|
|
914
|
+
// Delete from active
|
|
915
|
+
const result = this.provider.run('DELETE FROM entries WHERE id = ?', [id]);
|
|
916
|
+
archived += result.changes;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return { archived };
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Restore an archived entry back to the active table.
|
|
925
|
+
*/
|
|
926
|
+
restore(id: string): boolean {
|
|
927
|
+
return this.provider.transaction(() => {
|
|
928
|
+
const archived = this.provider.get<Record<string, unknown>>(
|
|
929
|
+
'SELECT * FROM entries_archive WHERE id = ?',
|
|
930
|
+
[id],
|
|
931
|
+
);
|
|
932
|
+
if (!archived) return false;
|
|
933
|
+
|
|
934
|
+
this.provider.run(
|
|
935
|
+
`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)
|
|
936
|
+
SELECT id, type, domain, title, severity, description, context, example, counter_example, why, tags, applies_to, created_at, updated_at, valid_from, valid_until
|
|
937
|
+
FROM entries_archive WHERE id = ?`,
|
|
938
|
+
[id],
|
|
939
|
+
);
|
|
940
|
+
this.provider.run('DELETE FROM entries_archive WHERE id = ?', [id]);
|
|
941
|
+
return true;
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Optimize the database: VACUUM (SQLite only), ANALYZE, and FTS rebuild.
|
|
947
|
+
*/
|
|
948
|
+
optimize(): { vacuumed: boolean; analyzed: boolean; ftsRebuilt: boolean } {
|
|
949
|
+
let vacuumed = false;
|
|
950
|
+
let analyzed = false;
|
|
951
|
+
let ftsRebuilt = false;
|
|
952
|
+
|
|
953
|
+
// VACUUM only for SQLite
|
|
954
|
+
if (this.provider.backend === 'sqlite') {
|
|
955
|
+
try {
|
|
956
|
+
this.provider.execSql('VACUUM');
|
|
957
|
+
vacuumed = true;
|
|
958
|
+
} catch {
|
|
959
|
+
// VACUUM may fail inside a transaction
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
try {
|
|
964
|
+
this.provider.execSql('ANALYZE');
|
|
965
|
+
analyzed = true;
|
|
966
|
+
} catch {
|
|
967
|
+
// Non-critical
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
try {
|
|
971
|
+
this.provider.ftsRebuild('entries');
|
|
972
|
+
this.provider.ftsRebuild('memories');
|
|
973
|
+
ftsRebuilt = true;
|
|
974
|
+
} catch {
|
|
975
|
+
// Non-critical
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return { vacuumed, analyzed, ftsRebuilt };
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Get the underlying persistence provider.
|
|
983
|
+
*/
|
|
984
|
+
getProvider(): PersistenceProvider {
|
|
985
|
+
return this.provider;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Get the raw better-sqlite3 Database (backward compat).
|
|
990
|
+
* Throws if the provider is not SQLite.
|
|
991
|
+
*/
|
|
992
|
+
getDb(): import('better-sqlite3').Database {
|
|
993
|
+
if (process.env.NODE_ENV !== 'test' && process.env.VITEST !== 'true') {
|
|
994
|
+
console.warn('Vault.getDb() is deprecated. Use vault.getProvider() instead.');
|
|
995
|
+
}
|
|
996
|
+
if (this.sqliteProvider) {
|
|
997
|
+
return this.sqliteProvider.getDatabase();
|
|
998
|
+
}
|
|
999
|
+
throw new Error('getDb() is only available with SQLite provider');
|
|
818
1000
|
}
|
|
819
1001
|
|
|
820
1002
|
close(): void {
|
|
821
|
-
this.
|
|
1003
|
+
this.provider.close();
|
|
822
1004
|
}
|
|
823
1005
|
}
|
|
824
1006
|
|
|
825
|
-
function gc(
|
|
826
|
-
const rows =
|
|
827
|
-
|
|
828
|
-
|
|
1007
|
+
function gc(provider: PersistenceProvider, col: string): Record<string, number> {
|
|
1008
|
+
const rows = provider.all<{ key: string; count: number }>(
|
|
1009
|
+
`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`,
|
|
1010
|
+
);
|
|
829
1011
|
return Object.fromEntries(rows.map((r) => [r.key, r.count]));
|
|
830
1012
|
}
|
|
831
1013
|
|
|
@@ -843,6 +1025,8 @@ function rowToEntry(row: Record<string, unknown>): IntelligenceEntry {
|
|
|
843
1025
|
why: (row.why as string) ?? undefined,
|
|
844
1026
|
tags: JSON.parse((row.tags as string) || '[]'),
|
|
845
1027
|
appliesTo: JSON.parse((row.applies_to as string) || '[]'),
|
|
1028
|
+
validFrom: (row.valid_from as number) ?? undefined,
|
|
1029
|
+
validUntil: (row.valid_until as number) ?? undefined,
|
|
846
1030
|
};
|
|
847
1031
|
}
|
|
848
1032
|
|