@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
package/src/curator/curator.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Vault } from '../vault/vault.js';
|
|
2
2
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
3
3
|
import type { CogneeClient } from '../cognee/client.js';
|
|
4
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
4
5
|
import {
|
|
5
6
|
tokenize,
|
|
6
7
|
calculateTfIdf,
|
|
@@ -51,10 +52,12 @@ const DEFAULT_TAG_ALIASES: Array<[string, string]> = [
|
|
|
51
52
|
export class Curator {
|
|
52
53
|
private vault: Vault;
|
|
53
54
|
private cognee: CogneeClient | undefined;
|
|
55
|
+
private provider: PersistenceProvider;
|
|
54
56
|
|
|
55
57
|
constructor(vault: Vault, cognee?: CogneeClient) {
|
|
56
58
|
this.vault = vault;
|
|
57
59
|
this.cognee = cognee;
|
|
60
|
+
this.provider = vault.getProvider();
|
|
58
61
|
this.initializeTables();
|
|
59
62
|
this.seedDefaultAliases();
|
|
60
63
|
}
|
|
@@ -62,8 +65,7 @@ export class Curator {
|
|
|
62
65
|
// ─── Schema ─────────────────────────────────────────────────────
|
|
63
66
|
|
|
64
67
|
private initializeTables(): void {
|
|
65
|
-
|
|
66
|
-
db.exec(`
|
|
68
|
+
this.provider.execSql(`
|
|
67
69
|
CREATE TABLE IF NOT EXISTS curator_entry_state (
|
|
68
70
|
entry_id TEXT PRIMARY KEY,
|
|
69
71
|
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'stale', 'archived')),
|
|
@@ -115,41 +117,39 @@ export class Curator {
|
|
|
115
117
|
resolved_at INTEGER,
|
|
116
118
|
UNIQUE(pattern_id, antipattern_id)
|
|
117
119
|
);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_curator_state_status ON curator_entry_state(status);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_curator_changelog_entry ON curator_changelog(entry_id);
|
|
118
122
|
`);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
private seedDefaultAliases(): void {
|
|
122
|
-
|
|
123
|
-
const insertCanonical = db.prepare(
|
|
124
|
-
'INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)',
|
|
125
|
-
);
|
|
126
|
-
const insertAlias = db.prepare(
|
|
127
|
-
'INSERT OR IGNORE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)',
|
|
128
|
-
);
|
|
129
|
-
const tx = db.transaction(() => {
|
|
126
|
+
this.provider.transaction(() => {
|
|
130
127
|
const canonicals = new Set(DEFAULT_TAG_ALIASES.map(([, c]) => c));
|
|
131
128
|
for (const tag of canonicals) {
|
|
132
|
-
|
|
129
|
+
this.provider.run('INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)', [tag]);
|
|
133
130
|
}
|
|
134
131
|
for (const [alias, canonical] of DEFAULT_TAG_ALIASES) {
|
|
135
|
-
|
|
132
|
+
this.provider.run(
|
|
133
|
+
'INSERT OR IGNORE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)',
|
|
134
|
+
[alias, canonical],
|
|
135
|
+
);
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
|
-
tx();
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
// ─── Status ─────────────────────────────────────────────────────
|
|
142
141
|
|
|
143
142
|
getStatus(): CuratorStatus {
|
|
144
|
-
const db = this.vault.getDb();
|
|
145
143
|
const tableCount = (table: string): number =>
|
|
146
|
-
(
|
|
144
|
+
(
|
|
145
|
+
this.provider.get<{ count: number }>(`SELECT COUNT(*) as count FROM ${table}`) ?? {
|
|
146
|
+
count: 0,
|
|
147
|
+
}
|
|
148
|
+
).count;
|
|
147
149
|
|
|
148
|
-
const lastGroomed =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
)
|
|
152
|
-
.get() as { ts: number | null };
|
|
150
|
+
const lastGroomed = this.provider.get<{ ts: number | null }>(
|
|
151
|
+
'SELECT MAX(last_groomed_at) as ts FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
152
|
+
) ?? { ts: null };
|
|
153
153
|
|
|
154
154
|
return {
|
|
155
155
|
initialized: true,
|
|
@@ -167,11 +167,11 @@ export class Curator {
|
|
|
167
167
|
// ─── Tag Normalization ──────────────────────────────────────────
|
|
168
168
|
|
|
169
169
|
normalizeTag(tag: string): TagNormalizationResult {
|
|
170
|
-
const db = this.vault.getDb();
|
|
171
170
|
const lower = tag.toLowerCase().trim();
|
|
172
|
-
const row =
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
const row = this.provider.get<{ canonical: string }>(
|
|
172
|
+
'SELECT canonical FROM curator_tag_alias WHERE alias = ?',
|
|
173
|
+
[lower],
|
|
174
|
+
);
|
|
175
175
|
if (row) {
|
|
176
176
|
return { original: tag, normalized: row.canonical, wasAliased: true };
|
|
177
177
|
}
|
|
@@ -195,11 +195,10 @@ export class Curator {
|
|
|
195
195
|
|
|
196
196
|
if (changed) {
|
|
197
197
|
const dedupedTags = [...new Set(normalizedTags)];
|
|
198
|
-
|
|
199
|
-
db.prepare('UPDATE entries SET tags = ?, updated_at = unixepoch() WHERE id = ?').run(
|
|
198
|
+
this.provider.run('UPDATE entries SET tags = ?, updated_at = unixepoch() WHERE id = ?', [
|
|
200
199
|
JSON.stringify(dedupedTags),
|
|
201
200
|
entryId,
|
|
202
|
-
);
|
|
201
|
+
]);
|
|
203
202
|
this.logChange(
|
|
204
203
|
'normalize_tags',
|
|
205
204
|
entryId,
|
|
@@ -213,26 +212,28 @@ export class Curator {
|
|
|
213
212
|
}
|
|
214
213
|
|
|
215
214
|
addTagAlias(alias: string, canonical: string): void {
|
|
216
|
-
const db = this.vault.getDb();
|
|
217
215
|
const lower = alias.toLowerCase().trim();
|
|
218
216
|
const canonicalLower = canonical.toLowerCase().trim();
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
this.provider.run('INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)', [
|
|
218
|
+
canonicalLower,
|
|
219
|
+
]);
|
|
220
|
+
this.provider.run('INSERT OR REPLACE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)', [
|
|
221
221
|
lower,
|
|
222
222
|
canonicalLower,
|
|
223
|
-
);
|
|
223
|
+
]);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
getCanonicalTags(): CanonicalTag[] {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
const rows = this.provider.all<{
|
|
228
|
+
tag: string;
|
|
229
|
+
description: string | null;
|
|
230
|
+
alias_count: number;
|
|
231
|
+
}>(
|
|
232
|
+
`SELECT c.tag, c.description,
|
|
233
|
+
(SELECT COUNT(*) FROM curator_tag_alias a WHERE a.canonical = c.tag) as alias_count
|
|
234
|
+
FROM curator_tag_canonical c
|
|
235
|
+
ORDER BY c.tag`,
|
|
236
|
+
);
|
|
236
237
|
|
|
237
238
|
return rows.map((row) => ({
|
|
238
239
|
tag: row.tag,
|
|
@@ -243,11 +244,11 @@ export class Curator {
|
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
private countTagUsage(tag: string): number {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return row
|
|
247
|
+
const row = this.provider.get<{ count: number }>(
|
|
248
|
+
'SELECT COUNT(*) as count FROM entries WHERE tags LIKE ?',
|
|
249
|
+
[`%"${tag}"%`],
|
|
250
|
+
);
|
|
251
|
+
return row?.count ?? 0;
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
// ─── Duplicate Detection ────────────────────────────────────────
|
|
@@ -315,14 +316,8 @@ export class Curator {
|
|
|
315
316
|
if (antipatterns.length === 0 || patterns.length === 0) return [];
|
|
316
317
|
|
|
317
318
|
const vocabulary = this.buildVocabulary(entries);
|
|
318
|
-
const db = this.vault.getDb();
|
|
319
319
|
const detected: Contradiction[] = [];
|
|
320
320
|
|
|
321
|
-
const insertStmt = db.prepare(
|
|
322
|
-
`INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity)
|
|
323
|
-
VALUES (?, ?, ?)`,
|
|
324
|
-
);
|
|
325
|
-
|
|
326
321
|
for (const ap of antipatterns) {
|
|
327
322
|
// Stage 1: FTS5 candidate retrieval (fall back to all patterns if FTS returns empty)
|
|
328
323
|
let candidates: IntelligenceEntry[];
|
|
@@ -343,14 +338,16 @@ export class Curator {
|
|
|
343
338
|
const similarity = cosineSimilarity(apVec, pVec);
|
|
344
339
|
|
|
345
340
|
if (similarity >= effectiveThreshold) {
|
|
346
|
-
const result =
|
|
341
|
+
const result = this.provider.run(
|
|
342
|
+
'INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity) VALUES (?, ?, ?)',
|
|
343
|
+
[pattern.id, ap.id, similarity],
|
|
344
|
+
);
|
|
347
345
|
if (result.changes > 0) {
|
|
348
|
-
const row =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
detected.push(this.rowToContradiction(row));
|
|
346
|
+
const row = this.provider.get<Record<string, unknown>>(
|
|
347
|
+
'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
|
|
348
|
+
[pattern.id, ap.id],
|
|
349
|
+
);
|
|
350
|
+
if (row) detected.push(this.rowToContradiction(row));
|
|
354
351
|
}
|
|
355
352
|
}
|
|
356
353
|
}
|
|
@@ -360,24 +357,22 @@ export class Curator {
|
|
|
360
357
|
}
|
|
361
358
|
|
|
362
359
|
getContradictions(status?: ContradictionStatus): Contradiction[] {
|
|
363
|
-
const db = this.vault.getDb();
|
|
364
360
|
const query = status
|
|
365
361
|
? 'SELECT * FROM curator_contradictions WHERE status = ? ORDER BY similarity DESC'
|
|
366
362
|
: 'SELECT * FROM curator_contradictions ORDER BY similarity DESC';
|
|
367
|
-
const rows = (status ?
|
|
368
|
-
Record<string, unknown>
|
|
369
|
-
>;
|
|
363
|
+
const rows = this.provider.all<Record<string, unknown>>(query, status ? [status] : undefined);
|
|
370
364
|
return rows.map((r) => this.rowToContradiction(r));
|
|
371
365
|
}
|
|
372
366
|
|
|
373
367
|
resolveContradiction(id: number, resolution: 'resolved' | 'dismissed'): Contradiction | null {
|
|
374
|
-
|
|
375
|
-
db.prepare(
|
|
368
|
+
this.provider.run(
|
|
376
369
|
'UPDATE curator_contradictions SET status = ?, resolved_at = unixepoch() WHERE id = ?',
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
370
|
+
[resolution, id],
|
|
371
|
+
);
|
|
372
|
+
const row = this.provider.get<Record<string, unknown>>(
|
|
373
|
+
'SELECT * FROM curator_contradictions WHERE id = ?',
|
|
374
|
+
[id],
|
|
375
|
+
);
|
|
381
376
|
return row ? this.rowToContradiction(row) : null;
|
|
382
377
|
}
|
|
383
378
|
|
|
@@ -396,14 +391,8 @@ export class Curator {
|
|
|
396
391
|
}
|
|
397
392
|
|
|
398
393
|
const vocabulary = this.buildVocabulary(entries);
|
|
399
|
-
const db = this.vault.getDb();
|
|
400
394
|
const detected: Contradiction[] = [];
|
|
401
395
|
|
|
402
|
-
const insertStmt = db.prepare(
|
|
403
|
-
`INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity)
|
|
404
|
-
VALUES (?, ?, ?)`,
|
|
405
|
-
);
|
|
406
|
-
|
|
407
396
|
const cogneeAvailable = this.cognee?.isAvailable ?? false;
|
|
408
397
|
|
|
409
398
|
for (const ap of antipatterns) {
|
|
@@ -440,14 +429,16 @@ export class Curator {
|
|
|
440
429
|
}
|
|
441
430
|
|
|
442
431
|
if (finalScore >= effectiveThreshold) {
|
|
443
|
-
const result =
|
|
432
|
+
const result = this.provider.run(
|
|
433
|
+
'INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity) VALUES (?, ?, ?)',
|
|
434
|
+
[pattern.id, ap.id, finalScore],
|
|
435
|
+
);
|
|
444
436
|
if (result.changes > 0) {
|
|
445
|
-
const row =
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
detected.push(this.rowToContradiction(row));
|
|
437
|
+
const row = this.provider.get<Record<string, unknown>>(
|
|
438
|
+
'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
|
|
439
|
+
[pattern.id, ap.id],
|
|
440
|
+
);
|
|
441
|
+
if (row) detected.push(this.rowToContradiction(row));
|
|
451
442
|
}
|
|
452
443
|
}
|
|
453
444
|
}
|
|
@@ -469,21 +460,22 @@ export class Curator {
|
|
|
469
460
|
const tagsNormalized = this.normalizeTags(entryId);
|
|
470
461
|
|
|
471
462
|
// Check staleness based on entry's updated_at timestamp
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
463
|
+
const row = this.provider.get<{ updated_at: number }>(
|
|
464
|
+
'SELECT updated_at FROM entries WHERE id = ?',
|
|
465
|
+
[entryId],
|
|
466
|
+
);
|
|
476
467
|
const now = Math.floor(Date.now() / 1000);
|
|
477
468
|
const stale = row ? now - row.updated_at > DEFAULT_STALE_DAYS * 86400 : false;
|
|
478
469
|
|
|
479
470
|
const status = stale ? 'stale' : 'active';
|
|
480
471
|
|
|
481
472
|
// Upsert entry state
|
|
482
|
-
|
|
473
|
+
this.provider.run(
|
|
483
474
|
`INSERT INTO curator_entry_state (entry_id, status, last_groomed_at)
|
|
484
475
|
VALUES (?, ?, unixepoch())
|
|
485
476
|
ON CONFLICT(entry_id) DO UPDATE SET status = excluded.status, last_groomed_at = unixepoch()`,
|
|
486
|
-
|
|
477
|
+
[entryId, status],
|
|
478
|
+
);
|
|
487
479
|
|
|
488
480
|
this.logChange('groom', entryId, null, `status=${status}`, 'Routine grooming');
|
|
489
481
|
|
|
@@ -532,12 +524,12 @@ export class Curator {
|
|
|
532
524
|
const duplicates = this.detectDuplicates(undefined, duplicateThreshold);
|
|
533
525
|
|
|
534
526
|
// Detect stale entries
|
|
535
|
-
const db = this.vault.getDb();
|
|
536
527
|
const now = Math.floor(Date.now() / 1000);
|
|
537
528
|
const staleThreshold = now - staleDaysThreshold * 86400;
|
|
538
|
-
const staleRows =
|
|
539
|
-
|
|
540
|
-
|
|
529
|
+
const staleRows = this.provider.all<{ id: string }>(
|
|
530
|
+
'SELECT id FROM entries WHERE updated_at < ?',
|
|
531
|
+
[staleThreshold],
|
|
532
|
+
);
|
|
541
533
|
const staleEntries = staleRows.map((r) => r.id);
|
|
542
534
|
|
|
543
535
|
// Detect contradictions
|
|
@@ -548,11 +540,12 @@ export class Curator {
|
|
|
548
540
|
if (!dryRun) {
|
|
549
541
|
// Archive stale entries
|
|
550
542
|
for (const entryId of staleEntries) {
|
|
551
|
-
|
|
543
|
+
this.provider.run(
|
|
552
544
|
`INSERT INTO curator_entry_state (entry_id, status, last_groomed_at)
|
|
553
545
|
VALUES (?, 'archived', unixepoch())
|
|
554
546
|
ON CONFLICT(entry_id) DO UPDATE SET status = 'archived', last_groomed_at = unixepoch()`,
|
|
555
|
-
|
|
547
|
+
[entryId],
|
|
548
|
+
);
|
|
556
549
|
this.logChange(
|
|
557
550
|
'archive',
|
|
558
551
|
entryId,
|
|
@@ -596,12 +589,10 @@ export class Curator {
|
|
|
596
589
|
// ─── Changelog ──────────────────────────────────────────────────
|
|
597
590
|
|
|
598
591
|
getEntryHistory(entryId: string, limit?: number): ChangelogEntry[] {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
)
|
|
604
|
-
.all(entryId, limit ?? 50) as Array<Record<string, unknown>>;
|
|
592
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
593
|
+
'SELECT * FROM curator_changelog WHERE entry_id = ? ORDER BY created_at DESC, id DESC LIMIT ?',
|
|
594
|
+
[entryId, limit ?? 50],
|
|
595
|
+
);
|
|
605
596
|
return rows.map((r) => this.rowToChangelog(r));
|
|
606
597
|
}
|
|
607
598
|
|
|
@@ -648,13 +639,13 @@ export class Curator {
|
|
|
648
639
|
coverageScore = Math.max(0, coverageScore);
|
|
649
640
|
|
|
650
641
|
// Freshness: penalize stale entries
|
|
651
|
-
const db = this.vault.getDb();
|
|
652
642
|
const now = Math.floor(Date.now() / 1000);
|
|
653
643
|
const staleThreshold = now - DEFAULT_STALE_DAYS * 86400;
|
|
654
644
|
const staleCount = (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
645
|
+
this.provider.get<{ count: number }>(
|
|
646
|
+
'SELECT COUNT(*) as count FROM entries WHERE updated_at < ?',
|
|
647
|
+
[staleThreshold],
|
|
648
|
+
) ?? { count: 0 }
|
|
658
649
|
).count;
|
|
659
650
|
const staleRatio = staleCount / entries.length;
|
|
660
651
|
const freshnessScore = 1 - staleRatio;
|
|
@@ -698,11 +689,9 @@ export class Curator {
|
|
|
698
689
|
|
|
699
690
|
// Penalize ungroomed entries
|
|
700
691
|
const groomedCount = (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
)
|
|
705
|
-
.get() as { count: number }
|
|
692
|
+
this.provider.get<{ count: number }>(
|
|
693
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
694
|
+
) ?? { count: 0 }
|
|
706
695
|
).count;
|
|
707
696
|
if (groomedCount < entries.length) {
|
|
708
697
|
const ungroomed = entries.length - groomedCount;
|
|
@@ -739,12 +728,10 @@ export class Curator {
|
|
|
739
728
|
const entry = this.vault.get(entryId);
|
|
740
729
|
if (!entry) return { recorded: false, historyId: -1 };
|
|
741
730
|
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
.
|
|
745
|
-
|
|
746
|
-
)
|
|
747
|
-
.run(entryId, JSON.stringify(entry), changedBy ?? 'system', changeReason ?? null);
|
|
731
|
+
const result = this.provider.run(
|
|
732
|
+
'INSERT INTO curator_entry_history (entry_id, snapshot, changed_by, change_reason, created_at) VALUES (?, ?, ?, ?, unixepoch())',
|
|
733
|
+
[entryId, JSON.stringify(entry), changedBy ?? 'system', changeReason ?? null],
|
|
734
|
+
);
|
|
748
735
|
|
|
749
736
|
return { recorded: true, historyId: Number(result.lastInsertRowid) };
|
|
750
737
|
}
|
|
@@ -757,12 +744,10 @@ export class Curator {
|
|
|
757
744
|
changeReason: string | null;
|
|
758
745
|
createdAt: number;
|
|
759
746
|
}> {
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
)
|
|
765
|
-
.all(entryId) as Array<Record<string, unknown>>;
|
|
747
|
+
const rows = this.provider.all<Record<string, unknown>>(
|
|
748
|
+
'SELECT * FROM curator_entry_history WHERE entry_id = ? ORDER BY created_at ASC, id ASC',
|
|
749
|
+
[entryId],
|
|
750
|
+
);
|
|
766
751
|
|
|
767
752
|
return rows.map((row) => ({
|
|
768
753
|
historyId: row.id as number,
|
|
@@ -784,17 +769,14 @@ export class Curator {
|
|
|
784
769
|
freshEntries: number;
|
|
785
770
|
avgDaysSinceGroom: number;
|
|
786
771
|
} {
|
|
787
|
-
const db = this.vault.getDb();
|
|
788
772
|
const totalEntries = (
|
|
789
|
-
|
|
773
|
+
this.provider.get<{ count: number }>('SELECT COUNT(*) as count FROM entries') ?? { count: 0 }
|
|
790
774
|
).count;
|
|
791
775
|
|
|
792
776
|
const groomedEntries = (
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
)
|
|
797
|
-
.get() as { count: number }
|
|
777
|
+
this.provider.get<{ count: number }>(
|
|
778
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
779
|
+
) ?? { count: 0 }
|
|
798
780
|
).count;
|
|
799
781
|
|
|
800
782
|
const ungroomedEntries = totalEntries - groomedEntries;
|
|
@@ -804,28 +786,25 @@ export class Curator {
|
|
|
804
786
|
const freshThreshold = now - 7 * 86400;
|
|
805
787
|
|
|
806
788
|
const staleEntries = (
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
.get(staleThreshold) as { count: number }
|
|
789
|
+
this.provider.get<{ count: number }>(
|
|
790
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at < ?',
|
|
791
|
+
[staleThreshold],
|
|
792
|
+
) ?? { count: 0 }
|
|
812
793
|
).count;
|
|
813
794
|
|
|
814
795
|
const freshEntries = (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
.get(freshThreshold) as { count: number }
|
|
796
|
+
this.provider.get<{ count: number }>(
|
|
797
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at >= ?',
|
|
798
|
+
[freshThreshold],
|
|
799
|
+
) ?? { count: 0 }
|
|
820
800
|
).count;
|
|
821
801
|
|
|
822
802
|
let avgDaysSinceGroom = 0;
|
|
823
803
|
if (groomedEntries > 0) {
|
|
824
|
-
const sumRow =
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
.get(now) as { total: number | null };
|
|
804
|
+
const sumRow = this.provider.get<{ total: number | null }>(
|
|
805
|
+
'SELECT SUM(? - last_groomed_at) as total FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
806
|
+
[now],
|
|
807
|
+
) ?? { total: 0 };
|
|
829
808
|
const totalSeconds = sumRow.total ?? 0;
|
|
830
809
|
avgDaysSinceGroom = Math.round((totalSeconds / groomedEntries / 86400) * 100) / 100;
|
|
831
810
|
}
|
|
@@ -961,10 +940,10 @@ export class Curator {
|
|
|
961
940
|
afterValue: string | null,
|
|
962
941
|
reason: string,
|
|
963
942
|
): void {
|
|
964
|
-
|
|
965
|
-
db.prepare(
|
|
943
|
+
this.provider.run(
|
|
966
944
|
'INSERT INTO curator_changelog (action, entry_id, before_value, after_value, reason) VALUES (?, ?, ?, ?, ?)',
|
|
967
|
-
|
|
945
|
+
[action, entryId, beforeValue, afterValue, reason],
|
|
946
|
+
);
|
|
968
947
|
}
|
|
969
948
|
|
|
970
949
|
private rowToContradiction(row: Record<string, unknown>): Contradiction {
|