@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/curator/curator.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Vault } from '../vault/vault.js';
|
|
2
2
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
3
|
+
import type { CogneeClient } from '../cognee/client.js';
|
|
4
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
3
5
|
import {
|
|
4
6
|
tokenize,
|
|
5
7
|
calculateTfIdf,
|
|
@@ -49,9 +51,13 @@ const DEFAULT_TAG_ALIASES: Array<[string, string]> = [
|
|
|
49
51
|
|
|
50
52
|
export class Curator {
|
|
51
53
|
private vault: Vault;
|
|
54
|
+
private cognee: CogneeClient | undefined;
|
|
55
|
+
private provider: PersistenceProvider;
|
|
52
56
|
|
|
53
|
-
constructor(vault: Vault) {
|
|
57
|
+
constructor(vault: Vault, cognee?: CogneeClient) {
|
|
54
58
|
this.vault = vault;
|
|
59
|
+
this.cognee = cognee;
|
|
60
|
+
this.provider = vault.getProvider();
|
|
55
61
|
this.initializeTables();
|
|
56
62
|
this.seedDefaultAliases();
|
|
57
63
|
}
|
|
@@ -59,8 +65,7 @@ export class Curator {
|
|
|
59
65
|
// ─── Schema ─────────────────────────────────────────────────────
|
|
60
66
|
|
|
61
67
|
private initializeTables(): void {
|
|
62
|
-
|
|
63
|
-
db.exec(`
|
|
68
|
+
this.provider.execSql(`
|
|
64
69
|
CREATE TABLE IF NOT EXISTS curator_entry_state (
|
|
65
70
|
entry_id TEXT PRIMARY KEY,
|
|
66
71
|
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'stale', 'archived')),
|
|
@@ -112,41 +117,39 @@ export class Curator {
|
|
|
112
117
|
resolved_at INTEGER,
|
|
113
118
|
UNIQUE(pattern_id, antipattern_id)
|
|
114
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);
|
|
115
122
|
`);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
private seedDefaultAliases(): void {
|
|
119
|
-
|
|
120
|
-
const insertCanonical = db.prepare(
|
|
121
|
-
'INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)',
|
|
122
|
-
);
|
|
123
|
-
const insertAlias = db.prepare(
|
|
124
|
-
'INSERT OR IGNORE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)',
|
|
125
|
-
);
|
|
126
|
-
const tx = db.transaction(() => {
|
|
126
|
+
this.provider.transaction(() => {
|
|
127
127
|
const canonicals = new Set(DEFAULT_TAG_ALIASES.map(([, c]) => c));
|
|
128
128
|
for (const tag of canonicals) {
|
|
129
|
-
|
|
129
|
+
this.provider.run('INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)', [tag]);
|
|
130
130
|
}
|
|
131
131
|
for (const [alias, canonical] of DEFAULT_TAG_ALIASES) {
|
|
132
|
-
|
|
132
|
+
this.provider.run(
|
|
133
|
+
'INSERT OR IGNORE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)',
|
|
134
|
+
[alias, canonical],
|
|
135
|
+
);
|
|
133
136
|
}
|
|
134
137
|
});
|
|
135
|
-
tx();
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
// ─── Status ─────────────────────────────────────────────────────
|
|
139
141
|
|
|
140
142
|
getStatus(): CuratorStatus {
|
|
141
|
-
const db = this.vault.getDb();
|
|
142
143
|
const tableCount = (table: string): number =>
|
|
143
|
-
(
|
|
144
|
+
(
|
|
145
|
+
this.provider.get<{ count: number }>(`SELECT COUNT(*) as count FROM ${table}`) ?? {
|
|
146
|
+
count: 0,
|
|
147
|
+
}
|
|
148
|
+
).count;
|
|
144
149
|
|
|
145
|
-
const lastGroomed =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
.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 };
|
|
150
153
|
|
|
151
154
|
return {
|
|
152
155
|
initialized: true,
|
|
@@ -164,11 +167,11 @@ export class Curator {
|
|
|
164
167
|
// ─── Tag Normalization ──────────────────────────────────────────
|
|
165
168
|
|
|
166
169
|
normalizeTag(tag: string): TagNormalizationResult {
|
|
167
|
-
const db = this.vault.getDb();
|
|
168
170
|
const lower = tag.toLowerCase().trim();
|
|
169
|
-
const row =
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
const row = this.provider.get<{ canonical: string }>(
|
|
172
|
+
'SELECT canonical FROM curator_tag_alias WHERE alias = ?',
|
|
173
|
+
[lower],
|
|
174
|
+
);
|
|
172
175
|
if (row) {
|
|
173
176
|
return { original: tag, normalized: row.canonical, wasAliased: true };
|
|
174
177
|
}
|
|
@@ -192,11 +195,10 @@ export class Curator {
|
|
|
192
195
|
|
|
193
196
|
if (changed) {
|
|
194
197
|
const dedupedTags = [...new Set(normalizedTags)];
|
|
195
|
-
|
|
196
|
-
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 = ?', [
|
|
197
199
|
JSON.stringify(dedupedTags),
|
|
198
200
|
entryId,
|
|
199
|
-
);
|
|
201
|
+
]);
|
|
200
202
|
this.logChange(
|
|
201
203
|
'normalize_tags',
|
|
202
204
|
entryId,
|
|
@@ -210,26 +212,28 @@ export class Curator {
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
addTagAlias(alias: string, canonical: string): void {
|
|
213
|
-
const db = this.vault.getDb();
|
|
214
215
|
const lower = alias.toLowerCase().trim();
|
|
215
216
|
const canonicalLower = canonical.toLowerCase().trim();
|
|
216
|
-
|
|
217
|
-
|
|
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 (?, ?)', [
|
|
218
221
|
lower,
|
|
219
222
|
canonicalLower,
|
|
220
|
-
);
|
|
223
|
+
]);
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
getCanonicalTags(): CanonicalTag[] {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
);
|
|
233
237
|
|
|
234
238
|
return rows.map((row) => ({
|
|
235
239
|
tag: row.tag,
|
|
@@ -240,11 +244,11 @@ export class Curator {
|
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
private countTagUsage(tag: string): number {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
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;
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
// ─── Duplicate Detection ────────────────────────────────────────
|
|
@@ -312,14 +316,8 @@ export class Curator {
|
|
|
312
316
|
if (antipatterns.length === 0 || patterns.length === 0) return [];
|
|
313
317
|
|
|
314
318
|
const vocabulary = this.buildVocabulary(entries);
|
|
315
|
-
const db = this.vault.getDb();
|
|
316
319
|
const detected: Contradiction[] = [];
|
|
317
320
|
|
|
318
|
-
const insertStmt = db.prepare(
|
|
319
|
-
`INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity)
|
|
320
|
-
VALUES (?, ?, ?)`,
|
|
321
|
-
);
|
|
322
|
-
|
|
323
321
|
for (const ap of antipatterns) {
|
|
324
322
|
// Stage 1: FTS5 candidate retrieval (fall back to all patterns if FTS returns empty)
|
|
325
323
|
let candidates: IntelligenceEntry[];
|
|
@@ -340,14 +338,16 @@ export class Curator {
|
|
|
340
338
|
const similarity = cosineSimilarity(apVec, pVec);
|
|
341
339
|
|
|
342
340
|
if (similarity >= effectiveThreshold) {
|
|
343
|
-
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
|
+
);
|
|
344
345
|
if (result.changes > 0) {
|
|
345
|
-
const row =
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
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));
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
}
|
|
@@ -357,27 +357,100 @@ export class Curator {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
getContradictions(status?: ContradictionStatus): Contradiction[] {
|
|
360
|
-
const db = this.vault.getDb();
|
|
361
360
|
const query = status
|
|
362
361
|
? 'SELECT * FROM curator_contradictions WHERE status = ? ORDER BY similarity DESC'
|
|
363
362
|
: 'SELECT * FROM curator_contradictions ORDER BY similarity DESC';
|
|
364
|
-
const rows = (status ?
|
|
365
|
-
Record<string, unknown>
|
|
366
|
-
>;
|
|
363
|
+
const rows = this.provider.all<Record<string, unknown>>(query, status ? [status] : undefined);
|
|
367
364
|
return rows.map((r) => this.rowToContradiction(r));
|
|
368
365
|
}
|
|
369
366
|
|
|
370
367
|
resolveContradiction(id: number, resolution: 'resolved' | 'dismissed'): Contradiction | null {
|
|
371
|
-
|
|
372
|
-
db.prepare(
|
|
368
|
+
this.provider.run(
|
|
373
369
|
'UPDATE curator_contradictions SET status = ?, resolved_at = unixepoch() WHERE id = ?',
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
370
|
+
[resolution, id],
|
|
371
|
+
);
|
|
372
|
+
const row = this.provider.get<Record<string, unknown>>(
|
|
373
|
+
'SELECT * FROM curator_contradictions WHERE id = ?',
|
|
374
|
+
[id],
|
|
375
|
+
);
|
|
378
376
|
return row ? this.rowToContradiction(row) : null;
|
|
379
377
|
}
|
|
380
378
|
|
|
379
|
+
async detectContradictionsHybrid(threshold?: number): Promise<{
|
|
380
|
+
contradictions: Contradiction[];
|
|
381
|
+
cogneeAvailable: boolean;
|
|
382
|
+
method: 'hybrid' | 'tfidf-only';
|
|
383
|
+
}> {
|
|
384
|
+
const effectiveThreshold = threshold ?? DEFAULT_CONTRADICTION_THRESHOLD;
|
|
385
|
+
const entries = this.vault.list({ limit: 100000 });
|
|
386
|
+
const antipatterns = entries.filter((e) => e.type === 'anti-pattern');
|
|
387
|
+
const patterns = entries.filter((e) => e.type === 'pattern');
|
|
388
|
+
|
|
389
|
+
if (antipatterns.length === 0 || patterns.length === 0) {
|
|
390
|
+
return { contradictions: [], cogneeAvailable: false, method: 'tfidf-only' };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const vocabulary = this.buildVocabulary(entries);
|
|
394
|
+
const detected: Contradiction[] = [];
|
|
395
|
+
|
|
396
|
+
const cogneeAvailable = this.cognee?.isAvailable ?? false;
|
|
397
|
+
|
|
398
|
+
for (const ap of antipatterns) {
|
|
399
|
+
let candidates: IntelligenceEntry[];
|
|
400
|
+
try {
|
|
401
|
+
const searchResults = this.vault.search(ap.title, { type: 'pattern', limit: 20 });
|
|
402
|
+
candidates = searchResults.length > 0 ? searchResults.map((r) => r.entry) : patterns;
|
|
403
|
+
} catch {
|
|
404
|
+
candidates = patterns;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const apText = [ap.title, ap.description, ap.context ?? ''].join(' ');
|
|
408
|
+
const apVec = calculateTfIdf(tokenize(apText), vocabulary);
|
|
409
|
+
|
|
410
|
+
for (const pattern of candidates) {
|
|
411
|
+
const pText = [pattern.title, pattern.description, pattern.context ?? ''].join(' ');
|
|
412
|
+
const pVec = calculateTfIdf(tokenize(pText), vocabulary);
|
|
413
|
+
const tfidfScore = cosineSimilarity(apVec, pVec);
|
|
414
|
+
|
|
415
|
+
let finalScore = tfidfScore;
|
|
416
|
+
if (cogneeAvailable && this.cognee) {
|
|
417
|
+
try {
|
|
418
|
+
const cogneeResults = await this.cognee.search(`${ap.title} ${pattern.title}`, {
|
|
419
|
+
limit: 5,
|
|
420
|
+
});
|
|
421
|
+
const cogneeScore =
|
|
422
|
+
cogneeResults.length > 0
|
|
423
|
+
? cogneeResults.reduce((sum, r) => sum + r.score, 0) / cogneeResults.length
|
|
424
|
+
: 0;
|
|
425
|
+
finalScore = 0.6 * tfidfScore + 0.4 * cogneeScore;
|
|
426
|
+
} catch {
|
|
427
|
+
finalScore = tfidfScore;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (finalScore >= effectiveThreshold) {
|
|
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
|
+
);
|
|
436
|
+
if (result.changes > 0) {
|
|
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));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
contradictions: detected,
|
|
449
|
+
cogneeAvailable,
|
|
450
|
+
method: cogneeAvailable ? 'hybrid' : 'tfidf-only',
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
381
454
|
// ─── Grooming ───────────────────────────────────────────────────
|
|
382
455
|
|
|
383
456
|
groomEntry(entryId: string): GroomResult | null {
|
|
@@ -387,21 +460,22 @@ export class Curator {
|
|
|
387
460
|
const tagsNormalized = this.normalizeTags(entryId);
|
|
388
461
|
|
|
389
462
|
// Check staleness based on entry's updated_at timestamp
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
463
|
+
const row = this.provider.get<{ updated_at: number }>(
|
|
464
|
+
'SELECT updated_at FROM entries WHERE id = ?',
|
|
465
|
+
[entryId],
|
|
466
|
+
);
|
|
394
467
|
const now = Math.floor(Date.now() / 1000);
|
|
395
468
|
const stale = row ? now - row.updated_at > DEFAULT_STALE_DAYS * 86400 : false;
|
|
396
469
|
|
|
397
470
|
const status = stale ? 'stale' : 'active';
|
|
398
471
|
|
|
399
472
|
// Upsert entry state
|
|
400
|
-
|
|
473
|
+
this.provider.run(
|
|
401
474
|
`INSERT INTO curator_entry_state (entry_id, status, last_groomed_at)
|
|
402
475
|
VALUES (?, ?, unixepoch())
|
|
403
476
|
ON CONFLICT(entry_id) DO UPDATE SET status = excluded.status, last_groomed_at = unixepoch()`,
|
|
404
|
-
|
|
477
|
+
[entryId, status],
|
|
478
|
+
);
|
|
405
479
|
|
|
406
480
|
this.logChange('groom', entryId, null, `status=${status}`, 'Routine grooming');
|
|
407
481
|
|
|
@@ -450,12 +524,12 @@ export class Curator {
|
|
|
450
524
|
const duplicates = this.detectDuplicates(undefined, duplicateThreshold);
|
|
451
525
|
|
|
452
526
|
// Detect stale entries
|
|
453
|
-
const db = this.vault.getDb();
|
|
454
527
|
const now = Math.floor(Date.now() / 1000);
|
|
455
528
|
const staleThreshold = now - staleDaysThreshold * 86400;
|
|
456
|
-
const staleRows =
|
|
457
|
-
|
|
458
|
-
|
|
529
|
+
const staleRows = this.provider.all<{ id: string }>(
|
|
530
|
+
'SELECT id FROM entries WHERE updated_at < ?',
|
|
531
|
+
[staleThreshold],
|
|
532
|
+
);
|
|
459
533
|
const staleEntries = staleRows.map((r) => r.id);
|
|
460
534
|
|
|
461
535
|
// Detect contradictions
|
|
@@ -466,11 +540,12 @@ export class Curator {
|
|
|
466
540
|
if (!dryRun) {
|
|
467
541
|
// Archive stale entries
|
|
468
542
|
for (const entryId of staleEntries) {
|
|
469
|
-
|
|
543
|
+
this.provider.run(
|
|
470
544
|
`INSERT INTO curator_entry_state (entry_id, status, last_groomed_at)
|
|
471
545
|
VALUES (?, 'archived', unixepoch())
|
|
472
546
|
ON CONFLICT(entry_id) DO UPDATE SET status = 'archived', last_groomed_at = unixepoch()`,
|
|
473
|
-
|
|
547
|
+
[entryId],
|
|
548
|
+
);
|
|
474
549
|
this.logChange(
|
|
475
550
|
'archive',
|
|
476
551
|
entryId,
|
|
@@ -514,12 +589,10 @@ export class Curator {
|
|
|
514
589
|
// ─── Changelog ──────────────────────────────────────────────────
|
|
515
590
|
|
|
516
591
|
getEntryHistory(entryId: string, limit?: number): ChangelogEntry[] {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
)
|
|
522
|
-
.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
|
+
);
|
|
523
596
|
return rows.map((r) => this.rowToChangelog(r));
|
|
524
597
|
}
|
|
525
598
|
|
|
@@ -566,13 +639,13 @@ export class Curator {
|
|
|
566
639
|
coverageScore = Math.max(0, coverageScore);
|
|
567
640
|
|
|
568
641
|
// Freshness: penalize stale entries
|
|
569
|
-
const db = this.vault.getDb();
|
|
570
642
|
const now = Math.floor(Date.now() / 1000);
|
|
571
643
|
const staleThreshold = now - DEFAULT_STALE_DAYS * 86400;
|
|
572
644
|
const staleCount = (
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
645
|
+
this.provider.get<{ count: number }>(
|
|
646
|
+
'SELECT COUNT(*) as count FROM entries WHERE updated_at < ?',
|
|
647
|
+
[staleThreshold],
|
|
648
|
+
) ?? { count: 0 }
|
|
576
649
|
).count;
|
|
577
650
|
const staleRatio = staleCount / entries.length;
|
|
578
651
|
const freshnessScore = 1 - staleRatio;
|
|
@@ -616,11 +689,9 @@ export class Curator {
|
|
|
616
689
|
|
|
617
690
|
// Penalize ungroomed entries
|
|
618
691
|
const groomedCount = (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
)
|
|
623
|
-
.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 }
|
|
624
695
|
).count;
|
|
625
696
|
if (groomedCount < entries.length) {
|
|
626
697
|
const ungroomed = entries.length - groomedCount;
|
|
@@ -657,19 +728,15 @@ export class Curator {
|
|
|
657
728
|
const entry = this.vault.get(entryId);
|
|
658
729
|
if (!entry) return { recorded: false, historyId: -1 };
|
|
659
730
|
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
.
|
|
663
|
-
|
|
664
|
-
)
|
|
665
|
-
.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
|
+
);
|
|
666
735
|
|
|
667
736
|
return { recorded: true, historyId: Number(result.lastInsertRowid) };
|
|
668
737
|
}
|
|
669
738
|
|
|
670
|
-
getVersionHistory(
|
|
671
|
-
entryId: string,
|
|
672
|
-
): Array<{
|
|
739
|
+
getVersionHistory(entryId: string): Array<{
|
|
673
740
|
historyId: number;
|
|
674
741
|
entryId: string;
|
|
675
742
|
snapshot: IntelligenceEntry;
|
|
@@ -677,12 +744,10 @@ export class Curator {
|
|
|
677
744
|
changeReason: string | null;
|
|
678
745
|
createdAt: number;
|
|
679
746
|
}> {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
)
|
|
685
|
-
.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
|
+
);
|
|
686
751
|
|
|
687
752
|
return rows.map((row) => ({
|
|
688
753
|
historyId: row.id as number,
|
|
@@ -704,17 +769,14 @@ export class Curator {
|
|
|
704
769
|
freshEntries: number;
|
|
705
770
|
avgDaysSinceGroom: number;
|
|
706
771
|
} {
|
|
707
|
-
const db = this.vault.getDb();
|
|
708
772
|
const totalEntries = (
|
|
709
|
-
|
|
773
|
+
this.provider.get<{ count: number }>('SELECT COUNT(*) as count FROM entries') ?? { count: 0 }
|
|
710
774
|
).count;
|
|
711
775
|
|
|
712
776
|
const groomedEntries = (
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
)
|
|
717
|
-
.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 }
|
|
718
780
|
).count;
|
|
719
781
|
|
|
720
782
|
const ungroomedEntries = totalEntries - groomedEntries;
|
|
@@ -724,28 +786,25 @@ export class Curator {
|
|
|
724
786
|
const freshThreshold = now - 7 * 86400;
|
|
725
787
|
|
|
726
788
|
const staleEntries = (
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
.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 }
|
|
732
793
|
).count;
|
|
733
794
|
|
|
734
795
|
const freshEntries = (
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
.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 }
|
|
740
800
|
).count;
|
|
741
801
|
|
|
742
802
|
let avgDaysSinceGroom = 0;
|
|
743
803
|
if (groomedEntries > 0) {
|
|
744
|
-
const sumRow =
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
.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 };
|
|
749
808
|
const totalSeconds = sumRow.total ?? 0;
|
|
750
809
|
avgDaysSinceGroom = Math.round((totalSeconds / groomedEntries / 86400) * 100) / 100;
|
|
751
810
|
}
|
|
@@ -762,9 +821,10 @@ export class Curator {
|
|
|
762
821
|
|
|
763
822
|
// ─── Metadata Enrichment ──────────────────────────────────────
|
|
764
823
|
|
|
765
|
-
enrichMetadata(
|
|
766
|
-
|
|
767
|
-
|
|
824
|
+
enrichMetadata(entryId: string): {
|
|
825
|
+
enriched: boolean;
|
|
826
|
+
changes: Array<{ field: string; before: string; after: string }>;
|
|
827
|
+
} {
|
|
768
828
|
const entry = this.vault.get(entryId);
|
|
769
829
|
if (!entry) return { enriched: false, changes: [] };
|
|
770
830
|
|
|
@@ -880,10 +940,10 @@ export class Curator {
|
|
|
880
940
|
afterValue: string | null,
|
|
881
941
|
reason: string,
|
|
882
942
|
): void {
|
|
883
|
-
|
|
884
|
-
db.prepare(
|
|
943
|
+
this.provider.run(
|
|
885
944
|
'INSERT INTO curator_changelog (action, entry_id, before_value, after_value, reason) VALUES (?, ?, ?, ?, ?)',
|
|
886
|
-
|
|
945
|
+
[action, entryId, beforeValue, afterValue, reason],
|
|
946
|
+
);
|
|
887
947
|
}
|
|
888
948
|
|
|
889
949
|
private rowToContradiction(row: Record<string, unknown>): Contradiction {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classifier — converts any thrown value into a typed SoleriError.
|
|
3
|
+
*
|
|
4
|
+
* Maps HTTP status codes, network error codes, and message patterns
|
|
5
|
+
* to the appropriate SoleriErrorCode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SoleriError, SoleriErrorCode } from './types.js';
|
|
9
|
+
|
|
10
|
+
interface ErrorLike {
|
|
11
|
+
message?: string;
|
|
12
|
+
status?: number;
|
|
13
|
+
statusCode?: number;
|
|
14
|
+
code?: string;
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toErrorLike(error: unknown): ErrorLike {
|
|
19
|
+
if (error instanceof SoleriError) return error;
|
|
20
|
+
if (error instanceof Error) return error as unknown as ErrorLike;
|
|
21
|
+
if (typeof error === 'object' && error !== null) return error as ErrorLike;
|
|
22
|
+
return { message: String(error) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getHttpStatus(e: ErrorLike): number | undefined {
|
|
26
|
+
return e.status ?? e.statusCode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function classifyByHttpStatus(status: number): SoleriErrorCode | undefined {
|
|
30
|
+
if (status === 401 || status === 403) return SoleriErrorCode.AUTH;
|
|
31
|
+
if (status === 404) return SoleriErrorCode.RESOURCE_NOT_FOUND;
|
|
32
|
+
if (status === 408) return SoleriErrorCode.TIMEOUT;
|
|
33
|
+
if (status === 429) return SoleriErrorCode.RATE_LIMIT;
|
|
34
|
+
if (status === 422) return SoleriErrorCode.VALIDATION;
|
|
35
|
+
if (status >= 500 && status < 600) return SoleriErrorCode.INTERNAL;
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const NETWORK_CODES = new Set(['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH']);
|
|
40
|
+
const TIMEOUT_CODES = new Set(['ETIMEDOUT', 'ESOCKETTIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT']);
|
|
41
|
+
|
|
42
|
+
function classifyByErrorCode(code: string | undefined): SoleriErrorCode | undefined {
|
|
43
|
+
if (!code) return undefined;
|
|
44
|
+
if (NETWORK_CODES.has(code)) return SoleriErrorCode.NETWORK;
|
|
45
|
+
if (TIMEOUT_CODES.has(code)) return SoleriErrorCode.TIMEOUT;
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const MESSAGE_PATTERNS: Array<[RegExp, SoleriErrorCode]> = [
|
|
50
|
+
[/overloaded|capacity|model.*busy/i, SoleriErrorCode.LLM_OVERLOAD],
|
|
51
|
+
[/timeout|timed?\s*out/i, SoleriErrorCode.TIMEOUT],
|
|
52
|
+
[/vault|database|sqlite/i, SoleriErrorCode.VAULT_UNREACHABLE],
|
|
53
|
+
[/invalid|validation|schema/i, SoleriErrorCode.VALIDATION],
|
|
54
|
+
[/config(uration)?|missing.*key|env/i, SoleriErrorCode.CONFIG_ERROR],
|
|
55
|
+
[/auth(entication|orization)?|forbidden|denied|unauthorized/i, SoleriErrorCode.AUTH],
|
|
56
|
+
[/not\s*found|404|no\s+such/i, SoleriErrorCode.RESOURCE_NOT_FOUND],
|
|
57
|
+
[/rate\s*limit|too\s+many\s+requests|throttl/i, SoleriErrorCode.RATE_LIMIT],
|
|
58
|
+
[/network|connect|socket|dns/i, SoleriErrorCode.NETWORK],
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function classifyByMessage(message: string | undefined): SoleriErrorCode | undefined {
|
|
62
|
+
if (!message) return undefined;
|
|
63
|
+
for (const [pattern, code] of MESSAGE_PATTERNS) {
|
|
64
|
+
if (pattern.test(message)) return code;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Classify any thrown value into a SoleriError.
|
|
71
|
+
* If the value is already a SoleriError, returns it as-is.
|
|
72
|
+
*/
|
|
73
|
+
export function classifyError(error: unknown): SoleriError {
|
|
74
|
+
if (error instanceof SoleriError) return error;
|
|
75
|
+
|
|
76
|
+
const e = toErrorLike(error);
|
|
77
|
+
const originalError = error instanceof Error ? error : undefined;
|
|
78
|
+
const message = e.message ?? 'Unknown error';
|
|
79
|
+
|
|
80
|
+
// 1. HTTP status code
|
|
81
|
+
const httpStatus = getHttpStatus(e);
|
|
82
|
+
if (httpStatus !== undefined) {
|
|
83
|
+
const code = classifyByHttpStatus(httpStatus);
|
|
84
|
+
if (code)
|
|
85
|
+
return new SoleriError(message, code, { cause: originalError, context: { httpStatus } });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Node.js error code
|
|
89
|
+
const errCode = classifyByErrorCode(e.code);
|
|
90
|
+
if (errCode)
|
|
91
|
+
return new SoleriError(message, errCode, {
|
|
92
|
+
cause: originalError,
|
|
93
|
+
context: { errorCode: e.code },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 3. Message pattern matching
|
|
97
|
+
const msgCode = classifyByMessage(message);
|
|
98
|
+
if (msgCode) return new SoleriError(message, msgCode, { cause: originalError });
|
|
99
|
+
|
|
100
|
+
// 4. Default: permanent INTERNAL
|
|
101
|
+
return new SoleriError(message, SoleriErrorCode.INTERNAL, { cause: originalError });
|
|
102
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SoleriErrorCode, SoleriError, ok, err, isOk, isErr } from './types.js';
|
|
2
|
+
export type { ErrorClassification, Result, SoleriErrorOptions } from './types.js';
|
|
3
|
+
export { classifyError } from './classify.js';
|
|
4
|
+
export { retryWithPreset, shouldRetry, getRetryDelay, RETRY_PRESETS } from './retry.js';
|
|
5
|
+
export type { RetryPreset, RetryConfig, RetryOptions } from './retry.js';
|