nlm-memory 0.15.0 → 0.18.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/cli/nlm.js +39 -1
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/classifier/hierarchical-classify.d.ts +5 -0
- package/dist/core/classifier/hierarchical-classify.js +53 -0
- package/dist/core/classifier/hierarchical-classify.js.map +1 -0
- package/dist/core/hook/pointer-block.d.ts +7 -1
- package/dist/core/hook/pointer-block.js +15 -3
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +39 -0
- package/dist/core/ingest/reclassify-oversized.js +104 -0
- package/dist/core/ingest/reclassify-oversized.js.map +1 -0
- package/dist/core/recall/recall-service.d.ts +7 -0
- package/dist/core/recall/recall-service.js +31 -0
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/related-exemplars.d.ts +14 -0
- package/dist/core/recall/related-exemplars.js +25 -0
- package/dist/core/recall/related-exemplars.js.map +1 -0
- package/dist/core/storage/pg-code-exemplar-store.d.ts +2 -1
- package/dist/core/storage/pg-code-exemplar-store.js +28 -2
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +2 -1
- package/dist/core/storage/sqlite-code-exemplar-store.js +28 -2
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +2 -1
- package/dist/hook/prompt-recall-hook.js +1 -1
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +5 -1
- package/dist/hook/recall-over-http.js +14 -5
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/http/app.js +40 -2
- package/dist/http/app.js.map +1 -1
- package/dist/mcp/server.js +22 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/code-exemplar-store.d.ts +15 -1
- package/dist/shared/types.d.ts +30 -0
- package/migrations/023_code_exemplars_supersedence.sql +10 -0
- package/migrations/pg/001_initial.sql +3 -1
- package/nlm/index.js +24 -11
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/prompt-recall-hook.mjs +24 -11
- package/plugin/scripts/session-start-hook.mjs +12 -5
- package/migrations/pg/018_repair_self_supersede.sql +0 -16
- package/migrations/pg/020_collapse_duplicate_active_facts.sql +0 -46
- package/migrations/pg/021_code_exemplars.sql +0 -45
- package/migrations/pg/022_facts_retired_at.sql +0 -12
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
* vec lane is optional — inserts succeed without an embedding; search
|
|
8
8
|
* requires one.
|
|
9
9
|
*/
|
|
10
|
-
import type { CodeExemplar, CodeExemplarHit, CodeExemplarInput } from "../shared/types.js";
|
|
10
|
+
import type { CodeExemplar, CodeExemplarHit, CodeExemplarInput, CodeExemplarOutcome } from "../shared/types.js";
|
|
11
|
+
export type ExemplarVerdictSource = "llm" | "human";
|
|
12
|
+
export interface ExemplarVerdictPatch {
|
|
13
|
+
readonly retired?: boolean;
|
|
14
|
+
readonly outcome?: CodeExemplarOutcome;
|
|
15
|
+
}
|
|
16
|
+
export interface ExemplarVerdictResult {
|
|
17
|
+
readonly status: "applied" | "not_found" | "human_locked";
|
|
18
|
+
}
|
|
11
19
|
export interface CodeExemplarSearchFilter {
|
|
12
20
|
readonly installScope: string;
|
|
13
21
|
readonly repo?: string;
|
|
@@ -42,4 +50,10 @@ export interface CodeExemplarStore {
|
|
|
42
50
|
pruneReverted(installScope: string): Promise<number>;
|
|
43
51
|
/** Delete exemplars with ts < olderThanTs (optional clock-based escape hatch). */
|
|
44
52
|
pruneOlderThan(olderThanTs: string): Promise<number>;
|
|
53
|
+
/**
|
|
54
|
+
* Apply an operator/LLM verdict (retire/un-retire and/or relabel outcome).
|
|
55
|
+
* Human-wins: a `source: "llm"` call is a no-op when the row is already
|
|
56
|
+
* `label_source: "human"`. Returns the outcome so callers can surface it.
|
|
57
|
+
*/
|
|
58
|
+
setVerdict(id: string, patch: ExemplarVerdictPatch, source: ExemplarVerdictSource): Promise<ExemplarVerdictResult>;
|
|
45
59
|
}
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -77,6 +77,14 @@ export interface RecallQuery {
|
|
|
77
77
|
* via NLM_HOOK_INJECT_FACTS=false. Spec G.2.
|
|
78
78
|
*/
|
|
79
79
|
readonly withRelatedFacts?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* If true, RecallService attaches a `relatedExemplars` array of nearest
|
|
82
|
+
* code exemplars from the CodeRankEmbed space. Allows the recall block
|
|
83
|
+
* to include concrete code examples as a third dimension alongside
|
|
84
|
+
* session pointers and facts. Off by default; the HTTP hook caller may
|
|
85
|
+
* set this true to inject code examples into the pointer block.
|
|
86
|
+
*/
|
|
87
|
+
readonly withRelatedExemplars?: boolean;
|
|
80
88
|
/**
|
|
81
89
|
* If true, superseded sessions are included in recall results, down-ranked
|
|
82
90
|
* and carrying a `supersededBy` pointer to their successor. Replaced
|
|
@@ -125,6 +133,13 @@ export interface RecallResult {
|
|
|
125
133
|
* "Known facts" section beneath the session pointer list.
|
|
126
134
|
*/
|
|
127
135
|
readonly relatedFacts?: ReadonlyArray<RelatedFact>;
|
|
136
|
+
/**
|
|
137
|
+
* Optional array of nearest code exemplars matched via CodeRankEmbed.
|
|
138
|
+
* Present only when the caller requested `withRelatedExemplars`.
|
|
139
|
+
* Provides concrete code examples as a third dimension of context
|
|
140
|
+
* alongside session pointers and facts.
|
|
141
|
+
*/
|
|
142
|
+
readonly relatedExemplars?: ReadonlyArray<RelatedExemplar>;
|
|
128
143
|
}
|
|
129
144
|
/**
|
|
130
145
|
* Fact — a single normalized claim derived from a session.
|
|
@@ -262,6 +277,8 @@ export interface CodeExemplarInput {
|
|
|
262
277
|
export interface CodeExemplar extends CodeExemplarInput {
|
|
263
278
|
readonly id: string;
|
|
264
279
|
readonly createdAt: string;
|
|
280
|
+
readonly retiredAt: string | null;
|
|
281
|
+
readonly labelSource: "llm" | "human";
|
|
265
282
|
}
|
|
266
283
|
/** One result row from recall_code. */
|
|
267
284
|
export interface CodeExemplarHit {
|
|
@@ -276,3 +293,16 @@ export interface CodeExemplarHit {
|
|
|
276
293
|
readonly gitSha: string | null;
|
|
277
294
|
readonly distance: number;
|
|
278
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Lean pointer to a related code exemplar, returned by `pickRelatedExemplars`.
|
|
298
|
+
* Carries just the metadata needed for the recall block to reference the example
|
|
299
|
+
* without embedding the full code (which is retrieved on demand via `recall_code`).
|
|
300
|
+
*/
|
|
301
|
+
export interface RelatedExemplar {
|
|
302
|
+
readonly id: string;
|
|
303
|
+
readonly outcome: CodeExemplarOutcome;
|
|
304
|
+
readonly lang: string | null;
|
|
305
|
+
readonly repo: string;
|
|
306
|
+
readonly taskContext: string;
|
|
307
|
+
readonly distance: number;
|
|
308
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Migration 023: code_exemplars supersedence — operator/LLM verdict on exemplars.
|
|
2
|
+
--
|
|
3
|
+
-- retired_at: non-null = excluded from recall (the "verdict"), like facts.retired_at.
|
|
4
|
+
-- label_source: who last set the verdict/outcome ('llm' at capture; 'human' on
|
|
5
|
+
-- operator override). A human verdict is sticky — see CodeExemplarStore.setVerdict.
|
|
6
|
+
|
|
7
|
+
ALTER TABLE code_exemplars ADD COLUMN retired_at TEXT;
|
|
8
|
+
ALTER TABLE code_exemplars ADD COLUMN label_source TEXT NOT NULL DEFAULT 'llm';
|
|
9
|
+
|
|
10
|
+
INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (23, '023_code_exemplars_supersedence');
|
|
@@ -237,7 +237,9 @@ CREATE TABLE IF NOT EXISTS code_exemplars (
|
|
|
237
237
|
git_sha TEXT,
|
|
238
238
|
survived INTEGER,
|
|
239
239
|
ts TEXT NOT NULL,
|
|
240
|
-
created_at TEXT NOT NULL DEFAULT (now()::text)
|
|
240
|
+
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
241
|
+
retired_at TEXT,
|
|
242
|
+
label_source TEXT NOT NULL DEFAULT 'llm'
|
|
241
243
|
);
|
|
242
244
|
CREATE INDEX IF NOT EXISTS idx_exemplars_scope_repo ON code_exemplars(install_scope, repo, outcome);
|
|
243
245
|
CREATE INDEX IF NOT EXISTS idx_exemplars_ts ON code_exemplars(ts);
|
package/nlm/index.js
CHANGED
|
@@ -193,9 +193,9 @@ var RECALL_LIMIT = 5;
|
|
|
193
193
|
var RECALL_TIMEOUT_MS = 2e3;
|
|
194
194
|
async function recallOverHttp(prompt, runtime, conversationId) {
|
|
195
195
|
const query = extractRecallQuery(prompt);
|
|
196
|
-
if (query === null) return { hits: [], facts: [] };
|
|
196
|
+
if (query === null) return { hits: [], facts: [], exemplars: [] };
|
|
197
197
|
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
198
|
-
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=keyword&limit=${RECALL_LIMIT}&withFacts=true` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
198
|
+
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=keyword&limit=${RECALL_LIMIT}&withFacts=true&withExemplars=true` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
199
199
|
const controller = new AbortController();
|
|
200
200
|
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
201
201
|
try {
|
|
@@ -205,12 +205,12 @@ async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
205
205
|
headers: hookAuthHeaders(extra),
|
|
206
206
|
signal: controller.signal
|
|
207
207
|
});
|
|
208
|
-
if (!res.ok) return { hits: [], facts: [] };
|
|
208
|
+
if (!res.ok) return { hits: [], facts: [], exemplars: [] };
|
|
209
209
|
let body;
|
|
210
210
|
try {
|
|
211
211
|
body = await res.json();
|
|
212
212
|
} catch {
|
|
213
|
-
return { hits: [], facts: [] };
|
|
213
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
214
214
|
}
|
|
215
215
|
const hits = (body.results ?? []).map((r) => ({
|
|
216
216
|
id: r.id,
|
|
@@ -225,7 +225,13 @@ async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
225
225
|
value: f.value,
|
|
226
226
|
corroborationCount: f.corroborationCount
|
|
227
227
|
}));
|
|
228
|
-
|
|
228
|
+
const exemplars = (body.relatedExemplars ?? []).map((e) => ({
|
|
229
|
+
outcome: e.outcome,
|
|
230
|
+
lang: e.lang,
|
|
231
|
+
repo: e.repo,
|
|
232
|
+
taskContext: e.taskContext
|
|
233
|
+
}));
|
|
234
|
+
return { hits, facts, exemplars };
|
|
229
235
|
} finally {
|
|
230
236
|
clearTimeout(timer);
|
|
231
237
|
}
|
|
@@ -295,8 +301,8 @@ function recordSurfaced(conversationId, ids) {
|
|
|
295
301
|
}
|
|
296
302
|
|
|
297
303
|
// src/core/hook/pointer-block.ts
|
|
298
|
-
function formatPointerBlock(hits, facts = []) {
|
|
299
|
-
if (hits.length === 0 && facts.length === 0) return "";
|
|
304
|
+
function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
305
|
+
if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
|
|
300
306
|
const out = [];
|
|
301
307
|
if (hits.length > 0) {
|
|
302
308
|
out.push("## Possibly-relevant prior sessions (nlm-memory)");
|
|
@@ -317,9 +323,16 @@ function formatPointerBlock(hits, facts = []) {
|
|
|
317
323
|
out.push(`- ${f.subject} ${f.predicate}: ${f.value}${tag}`);
|
|
318
324
|
}
|
|
319
325
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
326
|
+
if (exemplars.length > 0) {
|
|
327
|
+
if (out.length > 0) out.push("");
|
|
328
|
+
out.push("## Related code exemplars (nlm-memory)");
|
|
329
|
+
for (const e of exemplars) {
|
|
330
|
+
const langPart = e.lang ? `${e.lang} \xB7 ` : "";
|
|
331
|
+
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext.slice(0, 120)}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const tools = exemplars.length > 0 ? "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved), recall_code (pull the full code for a related exemplar)." : "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved).";
|
|
335
|
+
out.push(tools);
|
|
323
336
|
return out.join("\n");
|
|
324
337
|
}
|
|
325
338
|
|
|
@@ -378,7 +391,7 @@ async function runHook(input, deps) {
|
|
|
378
391
|
perFireCap: PER_FIRE_CAP,
|
|
379
392
|
perConversationCap: PER_CONVERSATION_CAP
|
|
380
393
|
});
|
|
381
|
-
const block = formatPointerBlock(selected, fetched.facts);
|
|
394
|
+
const block = formatPointerBlock(selected, fetched.facts, fetched.exemplars);
|
|
382
395
|
const estTokens = Math.ceil(block.length / 4);
|
|
383
396
|
appendHookLog({
|
|
384
397
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nlm-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Local-first persistent memory for AI coding agents. Recalls relevant prior sessions on every prompt; learns which sessions actually get cited. Owner-edited timeline beats append-only memory layers.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Edward Chalupa",
|
|
@@ -64,8 +64,8 @@ function recordSurfaced(conversationId, ids) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// src/core/hook/pointer-block.ts
|
|
67
|
-
function formatPointerBlock(hits, facts = []) {
|
|
68
|
-
if (hits.length === 0 && facts.length === 0) return "";
|
|
67
|
+
function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
68
|
+
if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
|
|
69
69
|
const out = [];
|
|
70
70
|
if (hits.length > 0) {
|
|
71
71
|
out.push("## Possibly-relevant prior sessions (nlm-memory)");
|
|
@@ -86,9 +86,16 @@ function formatPointerBlock(hits, facts = []) {
|
|
|
86
86
|
out.push(`- ${f.subject} ${f.predicate}: ${f.value}${tag}`);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (exemplars.length > 0) {
|
|
90
|
+
if (out.length > 0) out.push("");
|
|
91
|
+
out.push("## Related code exemplars (nlm-memory)");
|
|
92
|
+
for (const e of exemplars) {
|
|
93
|
+
const langPart = e.lang ? `${e.lang} \xB7 ` : "";
|
|
94
|
+
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext.slice(0, 120)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const tools = exemplars.length > 0 ? "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved), recall_code (pull the full code for a related exemplar)." : "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved).";
|
|
98
|
+
out.push(tools);
|
|
92
99
|
return out.join("\n");
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -298,9 +305,9 @@ var RECALL_LIMIT = 5;
|
|
|
298
305
|
var RECALL_TIMEOUT_MS = 2e3;
|
|
299
306
|
async function recallOverHttp(prompt, runtime, conversationId) {
|
|
300
307
|
const query = extractRecallQuery(prompt);
|
|
301
|
-
if (query === null) return { hits: [], facts: [] };
|
|
308
|
+
if (query === null) return { hits: [], facts: [], exemplars: [] };
|
|
302
309
|
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
303
|
-
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=keyword&limit=${RECALL_LIMIT}&withFacts=true` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
310
|
+
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=keyword&limit=${RECALL_LIMIT}&withFacts=true&withExemplars=true` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
304
311
|
const controller = new AbortController();
|
|
305
312
|
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
306
313
|
try {
|
|
@@ -310,12 +317,12 @@ async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
310
317
|
headers: hookAuthHeaders(extra),
|
|
311
318
|
signal: controller.signal
|
|
312
319
|
});
|
|
313
|
-
if (!res.ok) return { hits: [], facts: [] };
|
|
320
|
+
if (!res.ok) return { hits: [], facts: [], exemplars: [] };
|
|
314
321
|
let body;
|
|
315
322
|
try {
|
|
316
323
|
body = await res.json();
|
|
317
324
|
} catch {
|
|
318
|
-
return { hits: [], facts: [] };
|
|
325
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
319
326
|
}
|
|
320
327
|
const hits = (body.results ?? []).map((r) => ({
|
|
321
328
|
id: r.id,
|
|
@@ -330,7 +337,13 @@ async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
330
337
|
value: f.value,
|
|
331
338
|
corroborationCount: f.corroborationCount
|
|
332
339
|
}));
|
|
333
|
-
|
|
340
|
+
const exemplars = (body.relatedExemplars ?? []).map((e) => ({
|
|
341
|
+
outcome: e.outcome,
|
|
342
|
+
lang: e.lang,
|
|
343
|
+
repo: e.repo,
|
|
344
|
+
taskContext: e.taskContext
|
|
345
|
+
}));
|
|
346
|
+
return { hits, facts, exemplars };
|
|
334
347
|
} finally {
|
|
335
348
|
clearTimeout(timer);
|
|
336
349
|
}
|
|
@@ -380,7 +393,7 @@ async function runHook(input, deps) {
|
|
|
380
393
|
perFireCap: PER_FIRE_CAP,
|
|
381
394
|
perConversationCap: PER_CONVERSATION_CAP
|
|
382
395
|
});
|
|
383
|
-
const block = formatPointerBlock(selected, fetched.facts);
|
|
396
|
+
const block = formatPointerBlock(selected, fetched.facts, fetched.exemplars);
|
|
384
397
|
const estTokens = Math.ceil(block.length / 4);
|
|
385
398
|
appendHookLog({
|
|
386
399
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -53,8 +53,8 @@ function recordSurfaced(conversationId, ids) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// src/core/hook/pointer-block.ts
|
|
56
|
-
function formatPointerBlock(hits, facts = []) {
|
|
57
|
-
if (hits.length === 0 && facts.length === 0) return "";
|
|
56
|
+
function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
57
|
+
if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
|
|
58
58
|
const out = [];
|
|
59
59
|
if (hits.length > 0) {
|
|
60
60
|
out.push("## Possibly-relevant prior sessions (nlm-memory)");
|
|
@@ -75,9 +75,16 @@ function formatPointerBlock(hits, facts = []) {
|
|
|
75
75
|
out.push(`- ${f.subject} ${f.predicate}: ${f.value}${tag}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (exemplars.length > 0) {
|
|
79
|
+
if (out.length > 0) out.push("");
|
|
80
|
+
out.push("## Related code exemplars (nlm-memory)");
|
|
81
|
+
for (const e of exemplars) {
|
|
82
|
+
const langPart = e.lang ? `${e.lang} \xB7 ` : "";
|
|
83
|
+
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext.slice(0, 120)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const tools = exemplars.length > 0 ? "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved), recall_code (pull the full code for a related exemplar)." : "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved).";
|
|
87
|
+
out.push(tools);
|
|
81
88
|
return out.join("\n");
|
|
82
89
|
}
|
|
83
90
|
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
-- PG parity for SQLite migration 018: repair self-supersedence damage.
|
|
2
|
-
--
|
|
3
|
-
-- One-shot repair, applied manually by an operator against a PG canonical
|
|
4
|
-
-- store (PgStorage.init only runs 001_initial.sql, there is no version-gated
|
|
5
|
-
-- runner on the PG side). Mirrors migrations/018_repair_self_supersede.sql.
|
|
6
|
-
--
|
|
7
|
-
-- Removes self-loop 'supersedes' edges (id -> id) written by the unguarded
|
|
8
|
-
-- scan path, then restores sessions wrongly marked superseded — those left
|
|
9
|
-
-- without any real incoming 'supersedes' edge.
|
|
10
|
-
|
|
11
|
-
DELETE FROM session_edges WHERE from_session = to_session AND kind = 'supersedes';
|
|
12
|
-
|
|
13
|
-
UPDATE sessions
|
|
14
|
-
SET status = 'closed', updated_at = NOW()
|
|
15
|
-
WHERE status = 'superseded'
|
|
16
|
-
AND id NOT IN (SELECT to_session FROM session_edges WHERE kind = 'supersedes');
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
-- PG parity for SQLite migration 020: collapse duplicate active facts (#301).
|
|
2
|
-
--
|
|
3
|
-
-- One-shot repair, applied manually by an operator against a PG canonical store
|
|
4
|
-
-- (PgStorage.init only runs 001_initial.sql; there is no version-gated runner on
|
|
5
|
-
-- the PG side). Mirrors migrations/020_collapse_duplicate_active_facts.sql.
|
|
6
|
-
--
|
|
7
|
-
-- Within each conflicting (subject, predicate) group, the newest active fact
|
|
8
|
-
-- (created_at, then id) stays active; the older actives get superseded_by set
|
|
9
|
-
-- to that newest fact's id. The PG ingest path already collapsed all priors
|
|
10
|
-
-- set-wise, so this only repairs rows the SQLite-era single-prior loop stranded
|
|
11
|
-
-- in a corpus migrated to PG.
|
|
12
|
-
|
|
13
|
-
BEGIN;
|
|
14
|
-
|
|
15
|
-
WITH winners AS (
|
|
16
|
-
SELECT subject, predicate, id AS winner_id
|
|
17
|
-
FROM (
|
|
18
|
-
SELECT
|
|
19
|
-
subject, predicate, id,
|
|
20
|
-
ROW_NUMBER() OVER (
|
|
21
|
-
PARTITION BY subject, predicate
|
|
22
|
-
ORDER BY created_at DESC, id DESC
|
|
23
|
-
) AS rn
|
|
24
|
-
FROM facts
|
|
25
|
-
WHERE superseded_by IS NULL
|
|
26
|
-
) ranked
|
|
27
|
-
WHERE rn = 1
|
|
28
|
-
),
|
|
29
|
-
conflicts AS (
|
|
30
|
-
SELECT subject, predicate
|
|
31
|
-
FROM facts
|
|
32
|
-
WHERE superseded_by IS NULL
|
|
33
|
-
GROUP BY subject, predicate
|
|
34
|
-
HAVING COUNT(*) > 1
|
|
35
|
-
)
|
|
36
|
-
UPDATE facts f
|
|
37
|
-
SET superseded_by = w.winner_id
|
|
38
|
-
FROM winners w, conflicts c
|
|
39
|
-
WHERE f.subject = w.subject
|
|
40
|
-
AND f.predicate = w.predicate
|
|
41
|
-
AND f.subject = c.subject
|
|
42
|
-
AND f.predicate = c.predicate
|
|
43
|
-
AND f.superseded_by IS NULL
|
|
44
|
-
AND f.id != w.winner_id;
|
|
45
|
-
|
|
46
|
-
COMMIT;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
-- PG parity for SQLite migration 021: code_exemplars lane.
|
|
2
|
-
--
|
|
3
|
-
-- Mirrors migrations/021_code_exemplars.sql. Uses pgvector `vector(768)`
|
|
4
|
-
-- instead of sqlite-vec `vec0`. Applied manually by an operator.
|
|
5
|
-
|
|
6
|
-
BEGIN;
|
|
7
|
-
|
|
8
|
-
CREATE TABLE IF NOT EXISTS code_exemplars (
|
|
9
|
-
id TEXT PRIMARY KEY,
|
|
10
|
-
install_scope TEXT NOT NULL,
|
|
11
|
-
signal_id TEXT,
|
|
12
|
-
session_id TEXT,
|
|
13
|
-
repo TEXT NOT NULL,
|
|
14
|
-
model TEXT NOT NULL,
|
|
15
|
-
lang TEXT,
|
|
16
|
-
task_context TEXT NOT NULL,
|
|
17
|
-
code TEXT NOT NULL,
|
|
18
|
-
code_hash TEXT NOT NULL,
|
|
19
|
-
outcome TEXT NOT NULL CHECK (outcome IN ('pass','fail','fix','exhausted')),
|
|
20
|
-
git_sha TEXT,
|
|
21
|
-
survived SMALLINT,
|
|
22
|
-
ts TEXT NOT NULL,
|
|
23
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
CREATE INDEX IF NOT EXISTS idx_exemplars_scope_repo
|
|
27
|
-
ON code_exemplars(install_scope, repo, outcome);
|
|
28
|
-
|
|
29
|
-
CREATE INDEX IF NOT EXISTS idx_exemplars_ts
|
|
30
|
-
ON code_exemplars(ts);
|
|
31
|
-
|
|
32
|
-
CREATE INDEX IF NOT EXISTS idx_exemplars_code_hash
|
|
33
|
-
ON code_exemplars(install_scope, code_hash);
|
|
34
|
-
|
|
35
|
-
-- Vector lane. 768 dims matches CodeRankEmbed-137M output.
|
|
36
|
-
CREATE TABLE IF NOT EXISTS code_exemplars_vec (
|
|
37
|
-
exemplar_id TEXT PRIMARY KEY,
|
|
38
|
-
embedding vector(768) NOT NULL
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
CREATE INDEX IF NOT EXISTS code_exemplars_vec_embedding_idx
|
|
42
|
-
ON code_exemplars_vec USING ivfflat (embedding vector_l2_ops)
|
|
43
|
-
WITH (lists = 10);
|
|
44
|
-
|
|
45
|
-
COMMIT;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
-- PG parity for SQLite migration 022: facts.retired_at.
|
|
2
|
-
--
|
|
3
|
-
-- Mirrors migrations/022_facts_retired_at.sql. Distinct nullable marker for
|
|
4
|
-
-- operator retirement of incorrect facts (NLM #326). Recall excludes retired
|
|
5
|
-
-- facts; getHistory / includeSuperseded still surface them. Applied manually
|
|
6
|
-
-- by an operator.
|
|
7
|
-
|
|
8
|
-
BEGIN;
|
|
9
|
-
|
|
10
|
-
ALTER TABLE facts ADD COLUMN IF NOT EXISTS retired_at TEXT;
|
|
11
|
-
|
|
12
|
-
COMMIT;
|