nlm-memory 0.5.0 → 0.5.1
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/README.md +72 -34
- package/dist/cli/nlm.js +2 -1
- package/dist/cli/nlm.js.map +1 -1
- package/dist/http/app.js +2 -1
- package/dist/http/app.js.map +1 -1
- package/dist/mcp/server.js +20 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/ui/assets/{index-C8cpwbYJ.css → index-Beo8psd-.css} +1 -1
- package/dist/ui/assets/{index-CB50QnL-.js → index-CSPTTeeM.js} +8 -8
- package/dist/ui/index.html +2 -2
- package/package.json +26 -1
- package/.agents/plugins/marketplace.json +0 -20
- package/.github/workflows/ci.yml +0 -30
- package/docs/methodology/re-derivation-rate.md +0 -112
- package/docs/methodology/useful-hit-rate.md +0 -79
- package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
- package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
- package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
- package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
- package/docs/plans/desktop-product.md +0 -69
- package/docs/plans/factstore-design.md +0 -236
- package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1575
- package/logs/CHANGELOG/CHANGELOG.md +0 -209
- package/migrations/000_initial_schema.sql +0 -174
- package/migrations/001_entity_type_rename.sql +0 -17
- package/migrations/002_adapter_state_extend.sql +0 -12
- package/migrations/003_session_embeddings.sql +0 -11
- package/migrations/004_facts.sql +0 -46
- package/migrations/005_sources.sql +0 -31
- package/migrations/006_providers.sql +0 -33
- package/migrations/007_source_tokens.sql +0 -17
- package/migrations/008_fts_rebuild.sql +0 -9
- package/migrations/009_session_embedding_chunks.sql +0 -46
- package/migrations/010_sources_opencode.sql +0 -30
- package/migrations/011_sources_hermes_agent.sql +0 -30
- package/migrations/012_sources_aider.sql +0 -30
- package/migrations/013_adapter_state_failure_count.sql +0 -12
- package/migrations/014_sources_cursor.sql +0 -30
- package/migrations/015_sources_windsurf.sql +0 -30
- package/plugin-hermes-agent/README.md +0 -49
- package/plugin-hermes-agent/__init__.py +0 -75
- package/plugin-hermes-agent/plugin.yaml +0 -15
- package/scripts/backfill-citations.mjs +0 -0
- package/scripts/build-codex-plugin.mjs +0 -61
- package/scripts/deepseek-probe.mjs +0 -67
- package/scripts/extract-triples.mjs +0 -207
- package/scripts/longmemeval/embedding-cache.ts +0 -77
- package/scripts/longmemeval/fetch-dataset.sh +0 -25
- package/scripts/longmemeval/run-harness.ts +0 -315
- package/scripts/longmemeval/scorer.ts +0 -99
- package/scripts/longmemeval/tsconfig.json +0 -9
- package/scripts/longmemeval/types.ts +0 -35
- package/scripts/nlm-daily-digest.py +0 -239
- package/scripts/nlm-daily-digest.sh +0 -28
- package/src/cli/classify-parity.ts +0 -257
- package/src/cli/launchctl-helpers.ts +0 -49
- package/src/cli/nlm.ts +0 -1078
- package/src/core/actions/actions-log.ts +0 -118
- package/src/core/actions/overlay.ts +0 -117
- package/src/core/adapters/aider.ts +0 -205
- package/src/core/adapters/claude-code.ts +0 -293
- package/src/core/adapters/common.ts +0 -54
- package/src/core/adapters/cursor.ts +0 -486
- package/src/core/adapters/from-source.ts +0 -67
- package/src/core/adapters/hermes-agent.ts +0 -240
- package/src/core/adapters/hermes.ts +0 -277
- package/src/core/adapters/jsonl-generic.ts +0 -208
- package/src/core/adapters/opencode.ts +0 -281
- package/src/core/adapters/pi.ts +0 -264
- package/src/core/adapters/windsurf.ts +0 -386
- package/src/core/classifier/prompt.ts +0 -200
- package/src/core/dataset/build-dataset.ts +0 -463
- package/src/core/embedding/chunk-body.ts +0 -76
- package/src/core/embedding/embed-backfill.ts +0 -210
- package/src/core/embedding/embed-normalize.ts +0 -135
- package/src/core/facts/backfill-facts.ts +0 -254
- package/src/core/facts/extract-facts.ts +0 -50
- package/src/core/hook/citation-detect.ts +0 -124
- package/src/core/hook/cite-memo.ts +0 -68
- package/src/core/hook/claude-settings.ts +0 -187
- package/src/core/hook/gate.ts +0 -25
- package/src/core/hook/hook-log.ts +0 -41
- package/src/core/hook/memo-sweep.ts +0 -164
- package/src/core/hook/memo.ts +0 -67
- package/src/core/hook/pointer-block.ts +0 -26
- package/src/core/hook/select.ts +0 -32
- package/src/core/hook/transcript.ts +0 -121
- package/src/core/ingest/ingest-session.ts +0 -111
- package/src/core/providers/provider-models.ts +0 -100
- package/src/core/providers/provider-registry.ts +0 -196
- package/src/core/recall/citation-log.ts +0 -108
- package/src/core/recall/filter.ts +0 -27
- package/src/core/recall/index.ts +0 -6
- package/src/core/recall/match-fields.ts +0 -40
- package/src/core/recall/query-log.ts +0 -149
- package/src/core/recall/query-shape.ts +0 -66
- package/src/core/recall/recall-service.ts +0 -320
- package/src/core/recall/recent-log.ts +0 -59
- package/src/core/recall/tokenize.ts +0 -18
- package/src/core/recall/useful-scan.ts +0 -336
- package/src/core/recall-facts/fact-query-log.ts +0 -150
- package/src/core/recall-facts/fact-recall-service.ts +0 -327
- package/src/core/scheduler/scan-once.ts +0 -142
- package/src/core/scheduler/scheduler.ts +0 -225
- package/src/core/sources/source-registry.ts +0 -278
- package/src/core/storage/db-restore.ts +0 -133
- package/src/core/storage/live-status.ts +0 -45
- package/src/core/storage/migrate.ts +0 -72
- package/src/core/storage/sqlite-fact-store.ts +0 -304
- package/src/core/storage/sqlite-session-store.ts +0 -810
- package/src/hook/hook-auth.ts +0 -18
- package/src/hook/prompt-recall-hook.ts +0 -180
- package/src/hook/session-end-hook.ts +0 -81
- package/src/hook/session-start-hook.ts +0 -168
- package/src/hook/stop-hook.ts +0 -239
- package/src/http/app.ts +0 -1215
- package/src/install/claude-code.ts +0 -128
- package/src/install/codex.ts +0 -367
- package/src/install/cursor.ts +0 -68
- package/src/install/hermes-agent.ts +0 -76
- package/src/install/hermes.ts +0 -78
- package/src/install/nlm-dir-perms.ts +0 -55
- package/src/install/ollama.ts +0 -284
- package/src/install/setup.ts +0 -489
- package/src/install/windsurf.ts +0 -68
- package/src/llm/classifier-box.ts +0 -64
- package/src/llm/deepseek-client.ts +0 -150
- package/src/llm/env-autoload.ts +0 -55
- package/src/llm/ollama-client.ts +0 -189
- package/src/mcp/server.ts +0 -534
- package/src/ports/fact-store.ts +0 -102
- package/src/ports/llm-client.ts +0 -52
- package/src/ports/logger.ts +0 -16
- package/src/ports/session-store.ts +0 -45
- package/src/ports/transcript-adapter.ts +0 -55
- package/src/shared/types.ts +0 -149
- package/src/ui/App.tsx +0 -58
- package/src/ui/components/PromoteOpenButton.tsx +0 -65
- package/src/ui/components/SessionDrawer.tsx +0 -199
- package/src/ui/components/SideNav.tsx +0 -162
- package/src/ui/components/Skeleton.tsx +0 -107
- package/src/ui/index.html +0 -13
- package/src/ui/lib/actions.ts +0 -30
- package/src/ui/lib/api.ts +0 -92
- package/src/ui/lib/dataset.ts +0 -141
- package/src/ui/lib/registries.ts +0 -155
- package/src/ui/lib/view-settings.ts +0 -41
- package/src/ui/main.tsx +0 -15
- package/src/ui/pages/Live.tsx +0 -229
- package/src/ui/pages/Pulse.tsx +0 -415
- package/src/ui/pages/Recall.tsx +0 -190
- package/src/ui/pages/River.tsx +0 -354
- package/src/ui/pages/Search.tsx +0 -386
- package/src/ui/pages/Stub.tsx +0 -9
- package/src/ui/pages/Thread.tsx +0 -473
- package/src/ui/pages/settings/Classifier.tsx +0 -227
- package/src/ui/pages/settings/Data.tsx +0 -190
- package/src/ui/pages/settings/Index.tsx +0 -65
- package/src/ui/pages/settings/Labels.tsx +0 -224
- package/src/ui/pages/settings/Providers.tsx +0 -305
- package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
- package/src/ui/pages/settings/Sources.tsx +0 -326
- package/src/ui/pages/settings/Views.tsx +0 -96
- package/src/ui/styles.css +0 -1890
- package/src/ui/tsconfig.json +0 -21
- package/src/ui/vite.config.ts +0 -19
- package/tests/fixtures/claude_code/short_session.jsonl +0 -2
- package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
- package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
- package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
- package/tests/fixtures/facts.ts +0 -17
- package/tests/fixtures/golden-corpus.ts +0 -85
- package/tests/fixtures/hermes/paired_request_dump.json +0 -24
- package/tests/fixtures/hermes/paired_session.json +0 -23
- package/tests/fixtures/hermes/request_dump.json +0 -28
- package/tests/fixtures/hermes/session_iso.json +0 -38
- package/tests/fixtures/hermes/session_unix.json +0 -38
- package/tests/fixtures/hermes/system_only.json +0 -18
- package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
- package/tests/fixtures/pi/short-successful.jsonl +0 -5
- package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
- package/tests/fixtures/sessions.ts +0 -22
- package/tests/integration/backfill-facts.test.ts +0 -362
- package/tests/integration/citation-explicit.test.ts +0 -111
- package/tests/integration/cite-event.test.ts +0 -169
- package/tests/integration/cite-memo.test.ts +0 -87
- package/tests/integration/db-restore.test.ts +0 -153
- package/tests/integration/embed-backfill.test.ts +0 -176
- package/tests/integration/fact-supersedence.test.ts +0 -313
- package/tests/integration/fts-index.test.ts +0 -60
- package/tests/integration/getbyids-sqlite.test.ts +0 -100
- package/tests/integration/hermes-agent-hooks.test.ts +0 -248
- package/tests/integration/hook-claude-settings.test.ts +0 -218
- package/tests/integration/hook-log.test.ts +0 -54
- package/tests/integration/hook-memo.test.ts +0 -68
- package/tests/integration/hook-pre-compact.test.ts +0 -105
- package/tests/integration/hook-subagent-start.test.ts +0 -102
- package/tests/integration/http.test.ts +0 -401
- package/tests/integration/keyword-search-fts.test.ts +0 -66
- package/tests/integration/mcp-recall-logging.test.ts +0 -88
- package/tests/integration/mcp.test.ts +0 -260
- package/tests/integration/memo-sweep.test.ts +0 -91
- package/tests/integration/prompt-recall-hook.test.ts +0 -88
- package/tests/integration/provider-registry.test.ts +0 -107
- package/tests/integration/recall-golden.test.ts +0 -59
- package/tests/integration/recall-sqlite.test.ts +0 -169
- package/tests/integration/scheduler.test.ts +0 -391
- package/tests/integration/session-end-hook.test.ts +0 -48
- package/tests/integration/session-start-hook.test.ts +0 -126
- package/tests/integration/source-registry.test.ts +0 -122
- package/tests/integration/sqlite-fact-store.test.ts +0 -346
- package/tests/integration/stop-hook.test.ts +0 -560
- package/tests/integration/wal-checkpoint.test.ts +0 -49
- package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
- package/tests/unit/core/adapters/aider.test.ts +0 -230
- package/tests/unit/core/adapters/claude-code.test.ts +0 -118
- package/tests/unit/core/adapters/cursor.test.ts +0 -485
- package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
- package/tests/unit/core/adapters/hermes.test.ts +0 -81
- package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
- package/tests/unit/core/adapters/opencode.test.ts +0 -354
- package/tests/unit/core/adapters/pi.test.ts +0 -110
- package/tests/unit/core/adapters/windsurf.test.ts +0 -416
- package/tests/unit/core/classifier/prompt.test.ts +0 -126
- package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
- package/tests/unit/core/facts/extract-facts.test.ts +0 -117
- package/tests/unit/core/filter.test.ts +0 -40
- package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
- package/tests/unit/core/hook/citation-detect.test.ts +0 -124
- package/tests/unit/core/hook/gate.test.ts +0 -29
- package/tests/unit/core/hook/pointer-block.test.ts +0 -22
- package/tests/unit/core/hook/select.test.ts +0 -66
- package/tests/unit/core/match-fields.test.ts +0 -39
- package/tests/unit/core/mcp-cite-session.test.ts +0 -51
- package/tests/unit/core/providers/provider-models.test.ts +0 -101
- package/tests/unit/core/query-shape.test.ts +0 -92
- package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
- package/tests/unit/core/recall-service.test.ts +0 -200
- package/tests/unit/core/storage/live-status.test.ts +0 -54
- package/tests/unit/core/tokenize.test.ts +0 -32
- package/tests/unit/core/useful-scan.test.ts +0 -537
- package/tests/unit/llm/embed.test.ts +0 -93
- package/tests/unit/llm/ollama-client.test.ts +0 -124
- package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
- package/tsconfig.json +0 -31
- package/tsconfig.test.json +0 -11
- package/vitest.config.ts +0 -22
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SqliteFactStore — the canonical FactStore implementation, sharing the same
|
|
3
|
-
* better-sqlite3 connection as SqliteSessionStore so session+facts ingest can
|
|
4
|
-
* commit in one transaction (Section 5 of factstore-design.md).
|
|
5
|
-
*
|
|
6
|
-
* Constructor takes an already-opened, already-migrated Database handle from
|
|
7
|
-
* SqliteSessionStore.rawDb(). It does not open its own connection. This is
|
|
8
|
-
* the only way to get a single-writer SQLite to behave atomically across
|
|
9
|
-
* both stores without WAL ordering surprises.
|
|
10
|
-
*
|
|
11
|
-
* Surface evolution:
|
|
12
|
-
* B.1 — insert, getById, findCurrent, list, listBySession, markSuperseded
|
|
13
|
-
* B.2 — insertManyInTxn (atomic session+facts ingest), embedding write helper
|
|
14
|
-
* B.3 — listForRecall (pre-filter for FactRecallService), semanticSearch,
|
|
15
|
-
* getHistory (supersedence chain inspection)
|
|
16
|
-
* B.4 — auto-supersedence on (subject, predicate) collision (deferred)
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
type NeighborRow = { fact_id: string; distance: number };
|
|
20
|
-
|
|
21
|
-
import type Database from "better-sqlite3";
|
|
22
|
-
import type {
|
|
23
|
-
FactListFilter,
|
|
24
|
-
FactQuery,
|
|
25
|
-
FactSemanticNeighbor,
|
|
26
|
-
FactStore,
|
|
27
|
-
} from "@ports/fact-store.js";
|
|
28
|
-
import type { Fact, FactHistoryChain, FactKind } from "@shared/types.js";
|
|
29
|
-
|
|
30
|
-
type FactRow = {
|
|
31
|
-
id: string;
|
|
32
|
-
kind: FactKind;
|
|
33
|
-
subject: string;
|
|
34
|
-
predicate: string;
|
|
35
|
-
value: string;
|
|
36
|
-
source_session_id: string;
|
|
37
|
-
source_quote: string | null;
|
|
38
|
-
created_at: string;
|
|
39
|
-
superseded_by: string | null;
|
|
40
|
-
confidence: number;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export class SqliteFactStore implements FactStore {
|
|
44
|
-
constructor(private readonly db: Database.Database) {}
|
|
45
|
-
|
|
46
|
-
async insert(fact: Fact): Promise<void> {
|
|
47
|
-
this.insertStmt().run(this.toRow(fact));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async insertMany(facts: ReadonlyArray<Fact>): Promise<void> {
|
|
51
|
-
if (facts.length === 0) return;
|
|
52
|
-
const stmt = this.insertStmt();
|
|
53
|
-
const txn = this.db.transaction((rows: ReadonlyArray<FactRow>) => {
|
|
54
|
-
for (const row of rows) stmt.run(row);
|
|
55
|
-
});
|
|
56
|
-
txn(facts.map((f) => this.toRow(f)));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Insert facts inside an already-open transaction (no own txn opened).
|
|
61
|
-
* Callable only from code that has already begun a transaction on the same
|
|
62
|
-
* connection — currently SqliteSessionStore.insertSession. Phase B.2: this
|
|
63
|
-
* is how session+facts ingest commits atomically (Section 5 of the plan).
|
|
64
|
-
*/
|
|
65
|
-
insertManyInTxn(facts: ReadonlyArray<Fact>): void {
|
|
66
|
-
if (facts.length === 0) return;
|
|
67
|
-
const stmt = this.insertStmt();
|
|
68
|
-
for (const f of facts) stmt.run(this.toRow(f));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async getById(id: string): Promise<Fact | null> {
|
|
72
|
-
const row = this.db
|
|
73
|
-
.prepare<[string], FactRow>(
|
|
74
|
-
`SELECT id, kind, subject, predicate, value, source_session_id,
|
|
75
|
-
source_quote, created_at, superseded_by, confidence
|
|
76
|
-
FROM facts WHERE id = ?`,
|
|
77
|
-
)
|
|
78
|
-
.get(id);
|
|
79
|
-
return row ? this.rowToFact(row) : null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async findCurrent(subject: string, predicate: string): Promise<Fact | null> {
|
|
83
|
-
const row = this.db
|
|
84
|
-
.prepare<[string, string], FactRow>(
|
|
85
|
-
`SELECT id, kind, subject, predicate, value, source_session_id,
|
|
86
|
-
source_quote, created_at, superseded_by, confidence
|
|
87
|
-
FROM facts
|
|
88
|
-
WHERE subject = ? AND predicate = ? AND superseded_by IS NULL
|
|
89
|
-
ORDER BY created_at DESC
|
|
90
|
-
LIMIT 1`,
|
|
91
|
-
)
|
|
92
|
-
.get(subject, predicate);
|
|
93
|
-
return row ? this.rowToFact(row) : null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async list(query: FactQuery): Promise<ReadonlyArray<Fact>> {
|
|
97
|
-
const limit = Math.max(1, Math.trunc(query.limit ?? 50));
|
|
98
|
-
const includeSuperseded = query.includeSuperseded === true;
|
|
99
|
-
|
|
100
|
-
const where: string[] = ["subject = ?"];
|
|
101
|
-
const params: Array<string | number> = [query.subject];
|
|
102
|
-
if (query.predicate !== undefined) {
|
|
103
|
-
where.push("predicate = ?");
|
|
104
|
-
params.push(query.predicate);
|
|
105
|
-
}
|
|
106
|
-
if (!includeSuperseded) where.push("superseded_by IS NULL");
|
|
107
|
-
params.push(limit);
|
|
108
|
-
|
|
109
|
-
const rows = this.db
|
|
110
|
-
.prepare<Array<string | number>, FactRow>(
|
|
111
|
-
`SELECT id, kind, subject, predicate, value, source_session_id,
|
|
112
|
-
source_quote, created_at, superseded_by, confidence
|
|
113
|
-
FROM facts
|
|
114
|
-
WHERE ${where.join(" AND ")}
|
|
115
|
-
ORDER BY created_at DESC
|
|
116
|
-
LIMIT ?`,
|
|
117
|
-
)
|
|
118
|
-
.all(...params);
|
|
119
|
-
return rows.map((r) => this.rowToFact(r));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async listBySession(sessionId: string): Promise<ReadonlyArray<Fact>> {
|
|
123
|
-
const rows = this.db
|
|
124
|
-
.prepare<[string], FactRow>(
|
|
125
|
-
`SELECT id, kind, subject, predicate, value, source_session_id,
|
|
126
|
-
source_quote, created_at, superseded_by, confidence
|
|
127
|
-
FROM facts
|
|
128
|
-
WHERE source_session_id = ?
|
|
129
|
-
ORDER BY created_at ASC`,
|
|
130
|
-
)
|
|
131
|
-
.all(sessionId);
|
|
132
|
-
return rows.map((r) => this.rowToFact(r));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async listForRecall(filter: FactListFilter): Promise<ReadonlyArray<Fact>> {
|
|
136
|
-
const where: string[] = [];
|
|
137
|
-
const params: Array<string | number> = [];
|
|
138
|
-
if (filter.subject !== undefined) {
|
|
139
|
-
where.push("subject = ?");
|
|
140
|
-
params.push(filter.subject);
|
|
141
|
-
}
|
|
142
|
-
if (filter.predicate !== undefined) {
|
|
143
|
-
where.push("predicate = ?");
|
|
144
|
-
params.push(filter.predicate);
|
|
145
|
-
}
|
|
146
|
-
if (filter.kind !== undefined) {
|
|
147
|
-
where.push("kind = ?");
|
|
148
|
-
params.push(filter.kind);
|
|
149
|
-
}
|
|
150
|
-
if (filter.minConfidence !== undefined) {
|
|
151
|
-
where.push("confidence >= ?");
|
|
152
|
-
params.push(filter.minConfidence);
|
|
153
|
-
}
|
|
154
|
-
if (filter.includeSuperseded !== true) {
|
|
155
|
-
where.push("superseded_by IS NULL");
|
|
156
|
-
}
|
|
157
|
-
const limit = Math.max(1, Math.trunc(filter.limit ?? 500));
|
|
158
|
-
params.push(limit);
|
|
159
|
-
const sql = `
|
|
160
|
-
SELECT id, kind, subject, predicate, value, source_session_id,
|
|
161
|
-
source_quote, created_at, superseded_by, confidence
|
|
162
|
-
FROM facts
|
|
163
|
-
${where.length > 0 ? "WHERE " + where.join(" AND ") : ""}
|
|
164
|
-
ORDER BY created_at DESC
|
|
165
|
-
LIMIT ?
|
|
166
|
-
`;
|
|
167
|
-
const rows = this.db
|
|
168
|
-
.prepare<Array<string | number>, FactRow>(sql)
|
|
169
|
-
.all(...params);
|
|
170
|
-
return rows.map((r) => this.rowToFact(r));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async semanticSearch(
|
|
174
|
-
queryVector: Float32Array,
|
|
175
|
-
limit: number,
|
|
176
|
-
): Promise<ReadonlyArray<FactSemanticNeighbor>> {
|
|
177
|
-
const k = Math.max(1, Math.trunc(limit));
|
|
178
|
-
const blob = Buffer.from(
|
|
179
|
-
queryVector.buffer,
|
|
180
|
-
queryVector.byteOffset,
|
|
181
|
-
queryVector.byteLength,
|
|
182
|
-
);
|
|
183
|
-
const rows = this.db
|
|
184
|
-
.prepare<[Buffer, number], NeighborRow>(`
|
|
185
|
-
SELECT fact_id, distance
|
|
186
|
-
FROM fact_embeddings
|
|
187
|
-
WHERE embedding MATCH ?
|
|
188
|
-
AND k = ?
|
|
189
|
-
ORDER BY distance
|
|
190
|
-
`)
|
|
191
|
-
.all(blob, k);
|
|
192
|
-
return rows.map((r) => ({ factId: r.fact_id, distance: r.distance }));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async getHistory(
|
|
196
|
-
subject: string,
|
|
197
|
-
predicate?: string,
|
|
198
|
-
): Promise<ReadonlyArray<FactHistoryChain>> {
|
|
199
|
-
const sql = predicate
|
|
200
|
-
? `SELECT id, kind, subject, predicate, value, source_session_id,
|
|
201
|
-
source_quote, created_at, superseded_by, confidence
|
|
202
|
-
FROM facts
|
|
203
|
-
WHERE subject = ? AND predicate = ?
|
|
204
|
-
ORDER BY predicate ASC, created_at DESC`
|
|
205
|
-
: `SELECT id, kind, subject, predicate, value, source_session_id,
|
|
206
|
-
source_quote, created_at, superseded_by, confidence
|
|
207
|
-
FROM facts
|
|
208
|
-
WHERE subject = ?
|
|
209
|
-
ORDER BY predicate ASC, created_at DESC`;
|
|
210
|
-
const rows = predicate
|
|
211
|
-
? this.db.prepare<[string, string], FactRow>(sql).all(subject, predicate)
|
|
212
|
-
: this.db.prepare<[string], FactRow>(sql).all(subject);
|
|
213
|
-
|
|
214
|
-
const byPred = new Map<string, Fact[]>();
|
|
215
|
-
for (const r of rows) {
|
|
216
|
-
const fact = this.rowToFact(r);
|
|
217
|
-
const bucket = byPred.get(fact.predicate);
|
|
218
|
-
if (bucket) bucket.push(fact);
|
|
219
|
-
else byPred.set(fact.predicate, [fact]);
|
|
220
|
-
}
|
|
221
|
-
const chains: FactHistoryChain[] = [];
|
|
222
|
-
for (const [pred, history] of byPred.entries()) {
|
|
223
|
-
chains.push({ subject, predicate: pred, history });
|
|
224
|
-
}
|
|
225
|
-
return chains;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Insert (or replace) the embedding row for a fact. Best-effort: callers
|
|
230
|
-
* trap embedder errors so an unreachable Ollama doesn't roll back ingest.
|
|
231
|
-
* vec0 doesn't UPDATE, so this is a DELETE+INSERT pair.
|
|
232
|
-
*/
|
|
233
|
-
upsertEmbedding(factId: string, vector: Float32Array): void {
|
|
234
|
-
const blob = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
235
|
-
this.db.prepare("DELETE FROM fact_embeddings WHERE fact_id = ?").run(factId);
|
|
236
|
-
this.db
|
|
237
|
-
.prepare("INSERT INTO fact_embeddings (fact_id, embedding) VALUES (?, ?)")
|
|
238
|
-
.run(factId, blob);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async markSuperseded(oldId: string, newId: string | null): Promise<void> {
|
|
242
|
-
if (newId !== null && oldId === newId) {
|
|
243
|
-
throw new Error("A fact cannot supersede itself");
|
|
244
|
-
}
|
|
245
|
-
const txn = this.db.transaction(() => {
|
|
246
|
-
const old = this.db
|
|
247
|
-
.prepare<[string], { id: string }>("SELECT id FROM facts WHERE id = ?")
|
|
248
|
-
.get(oldId);
|
|
249
|
-
if (!old) throw new Error(`Fact ${oldId} not found`);
|
|
250
|
-
if (newId !== null) {
|
|
251
|
-
const next = this.db
|
|
252
|
-
.prepare<[string], { id: string }>("SELECT id FROM facts WHERE id = ?")
|
|
253
|
-
.get(newId);
|
|
254
|
-
if (!next) throw new Error(`Fact ${newId} not found`);
|
|
255
|
-
}
|
|
256
|
-
this.db
|
|
257
|
-
.prepare("UPDATE facts SET superseded_by = ? WHERE id = ?")
|
|
258
|
-
.run(newId, oldId);
|
|
259
|
-
});
|
|
260
|
-
txn();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
private insertStmt() {
|
|
264
|
-
return this.db.prepare<FactRow>(`
|
|
265
|
-
INSERT INTO facts (
|
|
266
|
-
id, kind, subject, predicate, value, source_session_id,
|
|
267
|
-
source_quote, created_at, superseded_by, confidence
|
|
268
|
-
) VALUES (
|
|
269
|
-
@id, @kind, @subject, @predicate, @value, @source_session_id,
|
|
270
|
-
@source_quote, @created_at, @superseded_by, @confidence
|
|
271
|
-
)
|
|
272
|
-
`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private toRow(fact: Fact): FactRow {
|
|
276
|
-
return {
|
|
277
|
-
id: fact.id,
|
|
278
|
-
kind: fact.kind,
|
|
279
|
-
subject: fact.subject,
|
|
280
|
-
predicate: fact.predicate,
|
|
281
|
-
value: fact.value,
|
|
282
|
-
source_session_id: fact.sourceSessionId,
|
|
283
|
-
source_quote: fact.sourceQuote,
|
|
284
|
-
created_at: fact.createdAt,
|
|
285
|
-
superseded_by: fact.supersededBy,
|
|
286
|
-
confidence: fact.confidence,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private rowToFact(row: FactRow): Fact {
|
|
291
|
-
return {
|
|
292
|
-
id: row.id,
|
|
293
|
-
kind: row.kind,
|
|
294
|
-
subject: row.subject,
|
|
295
|
-
predicate: row.predicate,
|
|
296
|
-
value: row.value,
|
|
297
|
-
sourceSessionId: row.source_session_id,
|
|
298
|
-
sourceQuote: row.source_quote,
|
|
299
|
-
createdAt: row.created_at,
|
|
300
|
-
supersededBy: row.superseded_by,
|
|
301
|
-
confidence: row.confidence,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
}
|