@soleri/core 2.5.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/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/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 +1 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +48 -99
- package/dist/curator/curator.js.map +1 -1
- 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/persistence/index.d.ts +2 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +1 -0
- package/dist/persistence/index.js.map +1 -1
- 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 +5 -2
- package/dist/persistence/sqlite-provider.d.ts.map +1 -1
- package/dist/persistence/sqlite-provider.js +39 -1
- package/dist/persistence/sqlite-provider.js.map +1 -1
- package/dist/persistence/types.d.ts +23 -1
- package/dist/persistence/types.d.ts.map +1 -1
- 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 +25 -50
- package/dist/project/project-registry.js.map +1 -1
- package/dist/runtime/admin-extra-ops.d.ts +3 -3
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +29 -3
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/core-ops.d.ts +4 -4
- package/dist/runtime/core-ops.js +4 -4
- package/dist/runtime/runtime.js +1 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +3 -2
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +40 -2
- package/dist/runtime/vault-extra-ops.js.map +1 -1
- package/dist/vault/vault.d.ts +21 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +99 -0
- package/dist/vault/vault.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/admin-extra-ops.test.ts +2 -2
- package/src/__tests__/core-ops.test.ts +8 -2
- package/src/__tests__/persistence.test.ts +66 -0
- package/src/__tests__/postgres-provider.test.ts +58 -0
- package/src/__tests__/vault-extra-ops.test.ts +2 -2
- package/src/__tests__/vault.test.ts +184 -0
- package/src/brain/intelligence.ts +258 -307
- package/src/control/identity-manager.ts +77 -75
- package/src/control/intent-router.ts +55 -57
- package/src/curator/curator.ts +124 -145
- package/src/governance/governance.ts +90 -107
- package/src/index.ts +2 -0
- package/src/persistence/index.ts +2 -0
- package/src/persistence/postgres-provider.ts +157 -0
- package/src/persistence/sqlite-provider.ts +55 -2
- package/src/persistence/types.ts +31 -1
- package/src/project/project-registry.ts +69 -74
- package/src/runtime/admin-extra-ops.ts +36 -3
- package/src/runtime/core-ops.ts +4 -4
- package/src/runtime/runtime.ts +1 -1
- package/src/runtime/vault-extra-ops.ts +42 -2
- package/src/vault/vault.ts +118 -0
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Project Registry — SQLite-backed registry for tracking projects,
|
|
3
3
|
* rules, and cross-project links.
|
|
4
4
|
*
|
|
5
|
-
* Uses the Vault's underlying
|
|
5
|
+
* Uses the Vault's underlying database via PersistenceProvider.
|
|
6
6
|
* Tables are created lazily on first access.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type
|
|
9
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
10
10
|
import type { RegisteredProject, ProjectRule, ProjectLink, LinkType } from './types.js';
|
|
11
11
|
|
|
12
12
|
/** Row shape for the projects table. */
|
|
@@ -82,15 +82,15 @@ function rowToLink(row: LinkRow): ProjectLink {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
export class ProjectRegistry {
|
|
85
|
-
private
|
|
85
|
+
private provider: PersistenceProvider;
|
|
86
86
|
|
|
87
|
-
constructor(
|
|
88
|
-
this.
|
|
87
|
+
constructor(provider: PersistenceProvider) {
|
|
88
|
+
this.provider = provider;
|
|
89
89
|
this.initTables();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
private initTables(): void {
|
|
93
|
-
this.
|
|
93
|
+
this.provider.execSql(`
|
|
94
94
|
CREATE TABLE IF NOT EXISTS registered_projects (
|
|
95
95
|
id TEXT PRIMARY KEY,
|
|
96
96
|
path TEXT UNIQUE NOT NULL,
|
|
@@ -128,19 +128,19 @@ export class ProjectRegistry {
|
|
|
128
128
|
const id = pathToId(path);
|
|
129
129
|
const now = Date.now();
|
|
130
130
|
|
|
131
|
-
const existing = this.
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
const existing = this.provider.get<ProjectRow>(
|
|
132
|
+
'SELECT * FROM registered_projects WHERE id = ?',
|
|
133
|
+
[id],
|
|
134
|
+
);
|
|
134
135
|
|
|
135
136
|
if (existing) {
|
|
136
137
|
// Update lastAccessedAt, and optionally name/metadata if provided
|
|
137
138
|
const newName = name ?? existing.name;
|
|
138
139
|
const newMetadata = metadata ? JSON.stringify(metadata) : existing.metadata;
|
|
139
|
-
this.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.run(now, newName, newMetadata, id);
|
|
140
|
+
this.provider.run(
|
|
141
|
+
'UPDATE registered_projects SET last_accessed_at = ?, name = ?, metadata = ? WHERE id = ?',
|
|
142
|
+
[now, newName, newMetadata, id],
|
|
143
|
+
);
|
|
144
144
|
return rowToProject({
|
|
145
145
|
...existing,
|
|
146
146
|
last_accessed_at: now,
|
|
@@ -157,11 +157,10 @@ export class ProjectRegistry {
|
|
|
157
157
|
last_accessed_at: now,
|
|
158
158
|
metadata: JSON.stringify(metadata ?? {}),
|
|
159
159
|
};
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.run(row.id, row.path, row.name, row.registered_at, row.last_accessed_at, row.metadata);
|
|
160
|
+
this.provider.run(
|
|
161
|
+
'INSERT INTO registered_projects (id, path, name, registered_at, last_accessed_at, metadata) VALUES (?, ?, ?, ?, ?, ?)',
|
|
162
|
+
[row.id, row.path, row.name, row.registered_at, row.last_accessed_at, row.metadata],
|
|
163
|
+
);
|
|
165
164
|
|
|
166
165
|
return rowToProject(row);
|
|
167
166
|
}
|
|
@@ -170,9 +169,9 @@ export class ProjectRegistry {
|
|
|
170
169
|
* Get a project by ID.
|
|
171
170
|
*/
|
|
172
171
|
get(projectId: string): RegisteredProject | null {
|
|
173
|
-
const row = this.
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
const row = this.provider.get<ProjectRow>('SELECT * FROM registered_projects WHERE id = ?', [
|
|
173
|
+
projectId,
|
|
174
|
+
]);
|
|
176
175
|
return row ? rowToProject(row) : null;
|
|
177
176
|
}
|
|
178
177
|
|
|
@@ -180,9 +179,9 @@ export class ProjectRegistry {
|
|
|
180
179
|
* Get a project by its filesystem path.
|
|
181
180
|
*/
|
|
182
181
|
getByPath(path: string): RegisteredProject | null {
|
|
183
|
-
const row = this.
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
const row = this.provider.get<ProjectRow>('SELECT * FROM registered_projects WHERE path = ?', [
|
|
183
|
+
path,
|
|
184
|
+
]);
|
|
186
185
|
return row ? rowToProject(row) : null;
|
|
187
186
|
}
|
|
188
187
|
|
|
@@ -190,9 +189,9 @@ export class ProjectRegistry {
|
|
|
190
189
|
* List all registered projects.
|
|
191
190
|
*/
|
|
192
191
|
list(): RegisteredProject[] {
|
|
193
|
-
const rows = this.
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
const rows = this.provider.all<ProjectRow>(
|
|
193
|
+
'SELECT * FROM registered_projects ORDER BY last_accessed_at DESC',
|
|
194
|
+
);
|
|
196
195
|
return rows.map(rowToProject);
|
|
197
196
|
}
|
|
198
197
|
|
|
@@ -200,25 +199,26 @@ export class ProjectRegistry {
|
|
|
200
199
|
* Unregister a project by ID. Also removes associated rules and links.
|
|
201
200
|
*/
|
|
202
201
|
unregister(projectId: string): boolean {
|
|
203
|
-
|
|
204
|
-
this.
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
202
|
+
return this.provider.transaction(() => {
|
|
203
|
+
this.provider.run('DELETE FROM project_rules WHERE project_id = ?', [projectId]);
|
|
204
|
+
this.provider.run(
|
|
205
|
+
'DELETE FROM project_links WHERE source_project_id = ? OR target_project_id = ?',
|
|
206
|
+
[projectId, projectId],
|
|
207
|
+
);
|
|
208
208
|
return (
|
|
209
|
-
this.
|
|
209
|
+
this.provider.run('DELETE FROM registered_projects WHERE id = ?', [projectId]).changes > 0
|
|
210
210
|
);
|
|
211
|
-
})
|
|
212
|
-
return result;
|
|
211
|
+
});
|
|
213
212
|
}
|
|
214
213
|
|
|
215
214
|
/**
|
|
216
215
|
* Update the lastAccessedAt timestamp for a project.
|
|
217
216
|
*/
|
|
218
217
|
touch(projectId: string): void {
|
|
219
|
-
this.
|
|
220
|
-
.
|
|
221
|
-
|
|
218
|
+
this.provider.run('UPDATE registered_projects SET last_accessed_at = ? WHERE id = ?', [
|
|
219
|
+
Date.now(),
|
|
220
|
+
projectId,
|
|
221
|
+
]);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
// ─── Rules ──────────────────────────────────────────────────────────
|
|
@@ -232,11 +232,10 @@ export class ProjectRegistry {
|
|
|
232
232
|
): ProjectRule {
|
|
233
233
|
const id = `rule-${projectId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
234
234
|
const now = Date.now();
|
|
235
|
-
this.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
.run(id, projectId, rule.category, rule.text, rule.priority, now);
|
|
235
|
+
this.provider.run(
|
|
236
|
+
'INSERT INTO project_rules (id, project_id, category, text, priority, created_at) VALUES (?, ?, ?, ?, ?, ?)',
|
|
237
|
+
[id, projectId, rule.category, rule.text, rule.priority, now],
|
|
238
|
+
);
|
|
240
239
|
|
|
241
240
|
return {
|
|
242
241
|
id,
|
|
@@ -252,11 +251,10 @@ export class ProjectRegistry {
|
|
|
252
251
|
* Get all rules for a project.
|
|
253
252
|
*/
|
|
254
253
|
getRules(projectId: string): ProjectRule[] {
|
|
255
|
-
const rows = this.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
.all(projectId) as RuleRow[];
|
|
254
|
+
const rows = this.provider.all<RuleRow>(
|
|
255
|
+
'SELECT * FROM project_rules WHERE project_id = ? ORDER BY priority DESC, created_at ASC',
|
|
256
|
+
[projectId],
|
|
257
|
+
);
|
|
260
258
|
return rows.map(rowToRule);
|
|
261
259
|
}
|
|
262
260
|
|
|
@@ -264,7 +262,7 @@ export class ProjectRegistry {
|
|
|
264
262
|
* Remove a rule by ID.
|
|
265
263
|
*/
|
|
266
264
|
removeRule(ruleId: string): boolean {
|
|
267
|
-
return this.
|
|
265
|
+
return this.provider.run('DELETE FROM project_rules WHERE id = ?', [ruleId]).changes > 0;
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
/**
|
|
@@ -285,20 +283,18 @@ export class ProjectRegistry {
|
|
|
285
283
|
*/
|
|
286
284
|
link(sourceId: string, targetId: string, linkType: LinkType): ProjectLink {
|
|
287
285
|
const now = Date.now();
|
|
288
|
-
const info = this.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
.run(sourceId, targetId, linkType, now);
|
|
286
|
+
const info = this.provider.run(
|
|
287
|
+
'INSERT OR IGNORE INTO project_links (source_project_id, target_project_id, link_type, created_at) VALUES (?, ?, ?, ?)',
|
|
288
|
+
[sourceId, targetId, linkType, now],
|
|
289
|
+
);
|
|
293
290
|
|
|
294
291
|
// If insert was ignored (duplicate), fetch existing
|
|
295
292
|
if (info.changes === 0) {
|
|
296
|
-
const existing = this.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return rowToLink(existing);
|
|
293
|
+
const existing = this.provider.get<LinkRow>(
|
|
294
|
+
'SELECT * FROM project_links WHERE source_project_id = ? AND target_project_id = ? AND link_type = ?',
|
|
295
|
+
[sourceId, targetId, linkType],
|
|
296
|
+
);
|
|
297
|
+
return rowToLink(existing!);
|
|
302
298
|
}
|
|
303
299
|
|
|
304
300
|
return {
|
|
@@ -316,26 +312,25 @@ export class ProjectRegistry {
|
|
|
316
312
|
*/
|
|
317
313
|
unlink(sourceId: string, targetId: string, linkType?: LinkType): number {
|
|
318
314
|
if (linkType) {
|
|
319
|
-
return this.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
.run(sourceId, targetId, linkType).changes;
|
|
315
|
+
return this.provider.run(
|
|
316
|
+
'DELETE FROM project_links WHERE source_project_id = ? AND target_project_id = ? AND link_type = ?',
|
|
317
|
+
[sourceId, targetId, linkType],
|
|
318
|
+
).changes;
|
|
324
319
|
}
|
|
325
|
-
return this.
|
|
326
|
-
|
|
327
|
-
|
|
320
|
+
return this.provider.run(
|
|
321
|
+
'DELETE FROM project_links WHERE source_project_id = ? AND target_project_id = ?',
|
|
322
|
+
[sourceId, targetId],
|
|
323
|
+
).changes;
|
|
328
324
|
}
|
|
329
325
|
|
|
330
326
|
/**
|
|
331
327
|
* Get all links for a project (both outgoing and incoming).
|
|
332
328
|
*/
|
|
333
329
|
getLinks(projectId: string): ProjectLink[] {
|
|
334
|
-
const rows = this.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
.all(projectId, projectId) as LinkRow[];
|
|
330
|
+
const rows = this.provider.all<LinkRow>(
|
|
331
|
+
'SELECT * FROM project_links WHERE source_project_id = ? OR target_project_id = ? ORDER BY created_at DESC',
|
|
332
|
+
[projectId, projectId],
|
|
333
|
+
);
|
|
339
334
|
return rows.map(rowToLink);
|
|
340
335
|
}
|
|
341
336
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extended admin operations —
|
|
2
|
+
* Extended admin operations — 24 ops for production readiness.
|
|
3
3
|
*
|
|
4
4
|
* Groups: telemetry (3), permissions (1), vault analytics (1),
|
|
5
5
|
* search insights (1), module status (1), env (1), gc (1), export config (1),
|
|
6
6
|
* key pool (4), profiles (5), plugins (2), instruction validation (1),
|
|
7
|
-
* hot reload (1).
|
|
7
|
+
* hot reload (1), persistence (1).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { z } from 'zod';
|
|
@@ -33,7 +33,7 @@ interface PluginInfo {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Create
|
|
36
|
+
* Create 24 extended admin operations for production observability.
|
|
37
37
|
*/
|
|
38
38
|
export function createAdminExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
39
39
|
const { vault, brain, cognee, telemetry } = runtime;
|
|
@@ -648,5 +648,38 @@ export function createAdminExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
648
648
|
}
|
|
649
649
|
},
|
|
650
650
|
},
|
|
651
|
+
|
|
652
|
+
// ─── Persistence ────────────────────────────────────────────────
|
|
653
|
+
{
|
|
654
|
+
name: 'admin_persistence_info',
|
|
655
|
+
description: 'Get persistence backend info: type, connection status, and table row counts.',
|
|
656
|
+
auth: 'read',
|
|
657
|
+
schema: z.object({}),
|
|
658
|
+
handler: async () => {
|
|
659
|
+
const provider = runtime.vault.getProvider();
|
|
660
|
+
const backend = provider.backend;
|
|
661
|
+
|
|
662
|
+
const tables: Record<string, number> = {};
|
|
663
|
+
const tableNames = [
|
|
664
|
+
'entries',
|
|
665
|
+
'entries_archive',
|
|
666
|
+
'memories',
|
|
667
|
+
'projects',
|
|
668
|
+
'brain_vocabulary',
|
|
669
|
+
'brain_feedback',
|
|
670
|
+
];
|
|
671
|
+
|
|
672
|
+
for (const table of tableNames) {
|
|
673
|
+
try {
|
|
674
|
+
const row = provider.get<{ count: number }>(`SELECT COUNT(*) as count FROM ${table}`);
|
|
675
|
+
tables[table] = row?.count ?? 0;
|
|
676
|
+
} catch {
|
|
677
|
+
tables[table] = -1; // Table doesn't exist
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return { backend, tables };
|
|
682
|
+
},
|
|
683
|
+
},
|
|
651
684
|
];
|
|
652
685
|
}
|
package/src/runtime/core-ops.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generic core operations factory —
|
|
2
|
+
* Generic core operations factory — 207 ops that every agent gets.
|
|
3
3
|
*
|
|
4
4
|
* These ops are agent-agnostic (no persona, no activation).
|
|
5
5
|
* The 5 agent-specific ops (health, identity, activate, inject_claude_md, setup)
|
|
@@ -30,14 +30,14 @@ import { createCogneeSyncOps } from './cognee-sync-ops.js';
|
|
|
30
30
|
import { createIntakeOps } from './intake-ops.js';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Create the
|
|
33
|
+
* Create the 207 generic core operations for an agent runtime.
|
|
34
34
|
*
|
|
35
35
|
* Groups: search/vault (4), memory (4), export (1), planning (5),
|
|
36
36
|
* brain (8), brain intelligence (11), cognee (5),
|
|
37
37
|
* llm (2), curator (8), control (8), governance (5),
|
|
38
38
|
* playbook (5), prompt templates (2),
|
|
39
|
-
* planning-extra (22), memory-extra (8), vault-extra (
|
|
40
|
-
* admin (8), admin-extra (
|
|
39
|
+
* planning-extra (22), memory-extra (8), vault-extra (23),
|
|
40
|
+
* admin (8), admin-extra (24), loop (9), orchestrate (5),
|
|
41
41
|
* grading (5), capture (4), curator-extra (5), project (12),
|
|
42
42
|
* cognee-sync (3), intake (4).
|
|
43
43
|
*/
|
package/src/runtime/runtime.ts
CHANGED
|
@@ -94,7 +94,7 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
94
94
|
const telemetry = new Telemetry();
|
|
95
95
|
|
|
96
96
|
// Project Registry — multi-project tracking with rules and links
|
|
97
|
-
const projectRegistry = new ProjectRegistry(vault.
|
|
97
|
+
const projectRegistry = new ProjectRegistry(vault.getProvider());
|
|
98
98
|
|
|
99
99
|
// Template Manager — prompt templates with variable substitution
|
|
100
100
|
const templatesDir = config.templatesDir ?? join(agentHome, 'templates');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extra vault operations —
|
|
2
|
+
* Extra vault operations — 23 ops that extend the 4 base vault ops in core-ops.ts.
|
|
3
3
|
*
|
|
4
4
|
* Groups: single-entry CRUD (3), bulk (2), discovery (3), import/export (3),
|
|
5
|
-
* analytics (1), seed canonical (1), knowledge lifecycle (4), temporal (3)
|
|
5
|
+
* analytics (1), seed canonical (1), knowledge lifecycle (4), temporal (3),
|
|
6
|
+
* archival (3).
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { z } from 'zod';
|
|
@@ -546,6 +547,45 @@ export function createVaultExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
546
547
|
return { entries, count: entries.length };
|
|
547
548
|
},
|
|
548
549
|
},
|
|
550
|
+
|
|
551
|
+
// ─── Archival ───────────────────────────────────────────────────
|
|
552
|
+
{
|
|
553
|
+
name: 'vault_archive',
|
|
554
|
+
description:
|
|
555
|
+
'Archive entries older than N days to entries_archive table. Keeps active table lean.',
|
|
556
|
+
auth: 'write',
|
|
557
|
+
schema: z.object({
|
|
558
|
+
olderThanDays: z.number().describe('Archive entries not updated in this many days'),
|
|
559
|
+
reason: z.string().optional().describe('Reason for archiving'),
|
|
560
|
+
}),
|
|
561
|
+
handler: async (params) => {
|
|
562
|
+
return vault.archive({
|
|
563
|
+
olderThanDays: params.olderThanDays as number,
|
|
564
|
+
reason: params.reason as string | undefined,
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: 'vault_restore',
|
|
570
|
+
description: 'Restore an archived entry back to the active entries table.',
|
|
571
|
+
auth: 'write',
|
|
572
|
+
schema: z.object({
|
|
573
|
+
id: z.string().describe('ID of the archived entry to restore'),
|
|
574
|
+
}),
|
|
575
|
+
handler: async (params) => {
|
|
576
|
+
const restored = vault.restore(params.id as string);
|
|
577
|
+
return { restored, id: params.id };
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: 'vault_optimize',
|
|
582
|
+
description: 'Optimize the vault database: VACUUM (SQLite), ANALYZE, and FTS index rebuild.',
|
|
583
|
+
auth: 'write',
|
|
584
|
+
schema: z.object({}),
|
|
585
|
+
handler: async () => {
|
|
586
|
+
return vault.optimize();
|
|
587
|
+
},
|
|
588
|
+
},
|
|
549
589
|
];
|
|
550
590
|
}
|
|
551
591
|
|
package/src/vault/vault.ts
CHANGED
|
@@ -53,6 +53,7 @@ export class Vault {
|
|
|
53
53
|
// SQLite-specific pragmas
|
|
54
54
|
this.provider.run('PRAGMA journal_mode = WAL');
|
|
55
55
|
this.provider.run('PRAGMA foreign_keys = ON');
|
|
56
|
+
this.provider.run('PRAGMA synchronous = NORMAL');
|
|
56
57
|
} else {
|
|
57
58
|
this.provider = providerOrPath;
|
|
58
59
|
this.sqliteProvider =
|
|
@@ -99,6 +100,23 @@ export class Vault {
|
|
|
99
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);
|
|
100
101
|
INSERT INTO entries_fts(rowid,id,title,description,context,tags) VALUES(new.rowid,new.id,new.title,new.description,new.context,new.tags);
|
|
101
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
|
+
);
|
|
102
120
|
CREATE TABLE IF NOT EXISTS projects (
|
|
103
121
|
path TEXT PRIMARY KEY,
|
|
104
122
|
name TEXT NOT NULL,
|
|
@@ -151,6 +169,11 @@ export class Vault {
|
|
|
151
169
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
152
170
|
);
|
|
153
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);
|
|
154
177
|
`);
|
|
155
178
|
this.migrateBrainSchema();
|
|
156
179
|
this.migrateTemporalSchema();
|
|
@@ -863,6 +886,98 @@ export class Vault {
|
|
|
863
886
|
}
|
|
864
887
|
}
|
|
865
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
|
+
|
|
866
981
|
/**
|
|
867
982
|
* Get the underlying persistence provider.
|
|
868
983
|
*/
|
|
@@ -875,6 +990,9 @@ export class Vault {
|
|
|
875
990
|
* Throws if the provider is not SQLite.
|
|
876
991
|
*/
|
|
877
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
|
+
}
|
|
878
996
|
if (this.sqliteProvider) {
|
|
879
997
|
return this.sqliteProvider.getDatabase();
|
|
880
998
|
}
|