renor-engine 0.0.0-stage → 1.0.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/LICENSE +661 -0
- package/NOTICE +73 -0
- package/README.md +105 -2
- package/dist/BUILD.json +6 -0
- package/dist/api/apiAuth.js +43 -0
- package/dist/api/server.js +172 -0
- package/dist/buildIdentity.js +132 -0
- package/dist/cli-extractors.js +46 -0
- package/dist/cli.js +491 -0
- package/dist/config.js +65 -0
- package/dist/context/composer.js +320 -0
- package/dist/context/readHonesty.js +478 -0
- package/dist/context/rowStore.js +238 -0
- package/dist/conversation/formatter.js +81 -0
- package/dist/conversation/parser.js +189 -0
- package/dist/conversation/types.js +2 -0
- package/dist/conversation/writer.js +42 -0
- package/dist/db/client.js +147 -0
- package/dist/db/indexSalvage.js +81 -0
- package/dist/db/migrations.js +642 -0
- package/dist/db/readCache.js +109 -0
- package/dist/db/schema.js +5 -0
- package/dist/db/statements.js +89 -0
- package/dist/digests/digestCache.js +142 -0
- package/dist/digests/digests.js +597 -0
- package/dist/digests/worker.js +348 -0
- package/dist/embeddings/caching.js +337 -0
- package/dist/embeddings/coalescing.js +177 -0
- package/dist/embeddings/interactive.js +103 -0
- package/dist/embeddings/local.js +68 -0
- package/dist/embeddings/openai.js +105 -0
- package/dist/embeddings/rebuild.js +166 -0
- package/dist/embeddings/simdScan.js +258 -0
- package/dist/embeddings/space.js +21 -0
- package/dist/embeddings/types.js +14 -0
- package/dist/embeddings/vectorIndex.js +675 -0
- package/dist/embeddings/vertex.js +224 -0
- package/dist/eval/artifacts.js +42 -0
- package/dist/eval/cacheReport.js +139 -0
- package/dist/eval/comparison.js +182 -0
- package/dist/eval/context700-candidate.js +145 -0
- package/dist/eval/evalCache.js +525 -0
- package/dist/eval/hashEmbedding.js +35 -0
- package/dist/eval/longmemeval.js +56 -0
- package/dist/eval/metrics.js +181 -0
- package/dist/eval/renorbenchBaseline.js +486 -0
- package/dist/eval/renorbenchDocuments.js +134 -0
- package/dist/eval/renorbenchProduct.js +182 -0
- package/dist/eval/runLedger.js +279 -0
- package/dist/eval/runManifest.js +479 -0
- package/dist/eval/scoring.js +197 -0
- package/dist/evidence/attach.js +267 -0
- package/dist/evidence/background.js +147 -0
- package/dist/evidence/evidenceCache.js +182 -0
- package/dist/evidence/gate.js +37 -0
- package/dist/evidence/keyPool.js +85 -0
- package/dist/evidence/ollamaChat.js +84 -0
- package/dist/evidence/optin.js +165 -0
- package/dist/evidence/options.js +130 -0
- package/dist/evidence/secondPass.js +456 -0
- package/dist/extraction/classifyCapture.js +34 -0
- package/dist/extraction/decision-model.js +174 -0
- package/dist/extraction/eventExtractor.js +113 -0
- package/dist/extraction/extractionClient.js +122 -0
- package/dist/extraction/factExtractor.js +46 -0
- package/dist/extraction/factsWritePath.js +179 -0
- package/dist/extraction/preferenceExtractor.js +153 -0
- package/dist/extraction/secretGuard.js +176 -0
- package/dist/extraction/secretScanner.js +665 -0
- package/dist/extraction/typedFacts.js +224 -0
- package/dist/extraction/writePathAdjudication.js +110 -0
- package/dist/importing/apply.js +644 -0
- package/dist/importing/bundle-write.js +80 -0
- package/dist/importing/cli.js +407 -0
- package/dist/importing/existing.js +49 -0
- package/dist/importing/facts.js +123 -0
- package/dist/importing/forgetImported.js +79 -0
- package/dist/importing/hosted.js +467 -0
- package/dist/importing/index.js +29 -0
- package/dist/importing/journalLog.js +96 -0
- package/dist/importing/markdown.js +98 -0
- package/dist/importing/plan.js +206 -0
- package/dist/importing/secretScreen.js +113 -0
- package/dist/importing/serialize.js +188 -0
- package/dist/importing/source.js +463 -0
- package/dist/importing/types.js +6 -0
- package/dist/indexer/chunker.js +319 -0
- package/dist/indexer/dailyEntries.js +63 -0
- package/dist/indexer/hasher.js +10 -0
- package/dist/indexer/laneRuns.js +48 -0
- package/dist/indexer/o200k.js +481 -0
- package/dist/indexer/o200k.json +8 -0
- package/dist/indexer/outsideRoot.js +77 -0
- package/dist/indexer/parser.js +167 -0
- package/dist/indexer/pipeline.js +1168 -0
- package/dist/indexer/staleness.js +201 -0
- package/dist/indexer/tokenizer.js +77 -0
- package/dist/indexer/walker.js +184 -0
- package/dist/mcp/hive.js +88 -0
- package/dist/mcp/requestErrors.js +47 -0
- package/dist/mcp/server.js +396 -0
- package/dist/memory/agentLease.js +43 -0
- package/dist/memory/confinedWrite.js +315 -0
- package/dist/memory/controls.js +73 -0
- package/dist/memory/dailyNote.js +265 -0
- package/dist/memory/durableAppend.js +57 -0
- package/dist/memory/factConflicts.js +203 -0
- package/dist/memory/factTerms.js +321 -0
- package/dist/memory/hiveNameHeader.cjs +10 -0
- package/dist/memory/inventory.js +55 -0
- package/dist/memory/invisibleText.js +20 -0
- package/dist/memory/lifecycle.js +1756 -0
- package/dist/memory/limits.js +13 -0
- package/dist/memory/requests.js +183 -0
- package/dist/memory/sourceName.js +200 -0
- package/dist/memory/whatsNew.js +158 -0
- package/dist/memory/writer.js +175 -0
- package/dist/memory/writerConflicts.js +85 -0
- package/dist/memorymap/memoryMap.js +1568 -0
- package/dist/memorymap/replacedJournal.js +58 -0
- package/dist/memorymap/replacements.js +405 -0
- package/dist/mvp/activity.js +86 -0
- package/dist/mvp/agentMemory.js +1744 -0
- package/dist/mvp/auth.js +531 -0
- package/dist/mvp/cli.js +73 -0
- package/dist/mvp/client-store.js +462 -0
- package/dist/mvp/compileCache.js +249 -0
- package/dist/mvp/compileSeed.js +27 -0
- package/dist/mvp/config.js +288 -0
- package/dist/mvp/connectionStatus.js +80 -0
- package/dist/mvp/contextRelevance.js +413 -0
- package/dist/mvp/engine.js +1065 -0
- package/dist/mvp/exportAll.js +359 -0
- package/dist/mvp/factLanePack.js +77 -0
- package/dist/mvp/factMarkers.js +49 -0
- package/dist/mvp/factsLane.js +203 -0
- package/dist/mvp/hiddenSpans.js +585 -0
- package/dist/mvp/history.js +413 -0
- package/dist/mvp/hitDates.js +31 -0
- package/dist/mvp/hitFloor.js +36 -0
- package/dist/mvp/identity.js +18 -0
- package/dist/mvp/idle.js +113 -0
- package/dist/mvp/memoryDir.js +98 -0
- package/dist/mvp/posthogMcp.js +210 -0
- package/dist/mvp/readScope.js +104 -0
- package/dist/mvp/readUsefulness.js +222 -0
- package/dist/mvp/sanitize.js +48 -0
- package/dist/mvp/schemaRejections.js +91 -0
- package/dist/mvp/scope.js +127 -0
- package/dist/mvp/server.js +2089 -0
- package/dist/mvp/serverCard.js +29 -0
- package/dist/mvp/storedSize.js +139 -0
- package/dist/mvp/telemetry.js +441 -0
- package/dist/retrieval/adviceLines.js +195 -0
- package/dist/retrieval/adviceQuestion.js +44 -0
- package/dist/retrieval/conversationShape.js +44 -0
- package/dist/retrieval/dailyEntryTime.js +55 -0
- package/dist/retrieval/entityLookup.js +113 -0
- package/dist/retrieval/factsFirst.js +188 -0
- package/dist/retrieval/keyword.js +84 -0
- package/dist/retrieval/ledgerEventTime.js +51 -0
- package/dist/retrieval/ledgerLiveness.js +83 -0
- package/dist/retrieval/ledgerPresentation.js +161 -0
- package/dist/retrieval/noteIdentity.js +57 -0
- package/dist/retrieval/orchestrator.js +1086 -0
- package/dist/retrieval/queryHelpers.js +142 -0
- package/dist/retrieval/questionSpan.js +475 -0
- package/dist/retrieval/readerEvents.js +690 -0
- package/dist/retrieval/readerLayout.js +503 -0
- package/dist/retrieval/relativeTimeHints.js +113 -0
- package/dist/retrieval/semantic.js +67 -0
- package/dist/router/routerCache.js +106 -0
- package/dist/router/sessionRouter.js +726 -0
- package/dist/toolRefusal.js +120 -0
- package/dist/watcher/watcher.js +75 -0
- package/package.json +120 -4
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProvenanceError = exports.DEFAULT_BUDGET_TOKENS = exports.DEFAULT_SECTION_CAP = exports.SECTION_DROP_ORDER = exports.SECTION_ORDER = void 0;
|
|
4
|
+
exports.estimateTokens = estimateTokens;
|
|
5
|
+
exports.structuredContextEnabled = structuredContextEnabled;
|
|
6
|
+
exports.queryScopedRowsEnabled = queryScopedRowsEnabled;
|
|
7
|
+
exports.resolveBudgetTokens = resolveBudgetTokens;
|
|
8
|
+
exports.filterRowsByQueryTerms = filterRowsByQueryTerms;
|
|
9
|
+
exports.composeQueryContext = composeQueryContext;
|
|
10
|
+
exports.composeQueryContextFromDb = composeQueryContextFromDb;
|
|
11
|
+
const gate_1 = require("../evidence/gate");
|
|
12
|
+
const rowStore_1 = require("./rowStore");
|
|
13
|
+
/** Declared render order (ADR-0003 §5.1). */
|
|
14
|
+
exports.SECTION_ORDER = ['excerpts', 'evidence', 'facts', 'chronicle', 'prefs'];
|
|
15
|
+
/** Declared drop order on overflow: reverse of fill priority, excerpts last-standing. */
|
|
16
|
+
exports.SECTION_DROP_ORDER = ['prefs', 'chronicle', 'facts', 'evidence'];
|
|
17
|
+
exports.DEFAULT_SECTION_CAP = 40;
|
|
18
|
+
exports.DEFAULT_BUDGET_TOKENS = 6000;
|
|
19
|
+
class ProvenanceError extends Error {
|
|
20
|
+
}
|
|
21
|
+
exports.ProvenanceError = ProvenanceError;
|
|
22
|
+
/** Deterministic token estimate (chars/4, ceil). Never model-dependent. */
|
|
23
|
+
function estimateTokens(text) {
|
|
24
|
+
if (text.length === 0)
|
|
25
|
+
return 0;
|
|
26
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
27
|
+
}
|
|
28
|
+
/** RENOR_STRUCTURED_CONTEXT (default OFF). Opt in with 1/true/yes/on. */
|
|
29
|
+
function structuredContextEnabled(env = process.env) {
|
|
30
|
+
const raw = (env.RENOR_STRUCTURED_CONTEXT ?? '').trim().toLowerCase();
|
|
31
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* RENOR_QUERY_SCOPED_ROWS (default OFF; C5 rollback flag — never flipped here).
|
|
35
|
+
* Opt in with 1/true/yes/on. Enables read-path query-term-overlap filtering of
|
|
36
|
+
* structured-row tables before composition (efficiency only; gold-blind).
|
|
37
|
+
*/
|
|
38
|
+
function queryScopedRowsEnabled(env = process.env) {
|
|
39
|
+
const raw = (env.RENOR_QUERY_SCOPED_ROWS ?? '').trim().toLowerCase();
|
|
40
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
|
|
41
|
+
}
|
|
42
|
+
function resolveBudgetTokens(env = process.env) {
|
|
43
|
+
const raw = Number(env.RENOR_CONTEXT_BUDGET_TOKENS);
|
|
44
|
+
if (Number.isFinite(raw) && raw > 0)
|
|
45
|
+
return Math.floor(raw);
|
|
46
|
+
return exports.DEFAULT_BUDGET_TOKENS;
|
|
47
|
+
}
|
|
48
|
+
function queryTerms(query) {
|
|
49
|
+
return Array.from(new Set((0, rowStore_1.splitWords)(query).filter((t) => t.length >= 2)));
|
|
50
|
+
}
|
|
51
|
+
function rowSearchText(row) {
|
|
52
|
+
const p = row.payload;
|
|
53
|
+
switch (row.rowKind) {
|
|
54
|
+
case 'fact': {
|
|
55
|
+
const f = p;
|
|
56
|
+
return [f.value, f.unit, f.verb, f.object, f.event].filter(Boolean).join(' ');
|
|
57
|
+
}
|
|
58
|
+
case 'event': {
|
|
59
|
+
const e = p;
|
|
60
|
+
return [e.when, e.event, ...(e.entities ?? [])].filter(Boolean).join(' ');
|
|
61
|
+
}
|
|
62
|
+
case 'preference': {
|
|
63
|
+
const pr = p;
|
|
64
|
+
return [pr.statement, pr.kind].filter(Boolean).join(' ');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Relevance = total query-term occurrences; ties broken by valid_from desc (NULL last), then source_path, then dedupe_key. */
|
|
69
|
+
function rankRows(rows, terms) {
|
|
70
|
+
const counted = rows.map((row) => {
|
|
71
|
+
const text = (0, rowStore_1.matchForm)(rowSearchText(row));
|
|
72
|
+
let relevance = 0;
|
|
73
|
+
for (const term of terms) {
|
|
74
|
+
let idx = text.indexOf(term);
|
|
75
|
+
while (idx !== -1) {
|
|
76
|
+
relevance += 1;
|
|
77
|
+
idx = text.indexOf(term, idx + term.length);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { row, relevance };
|
|
81
|
+
});
|
|
82
|
+
counted.sort((a, b) => {
|
|
83
|
+
if (a.relevance !== b.relevance)
|
|
84
|
+
return b.relevance - a.relevance;
|
|
85
|
+
const aDate = a.row.validFrom;
|
|
86
|
+
const bDate = b.row.validFrom;
|
|
87
|
+
if (aDate === null && bDate !== null)
|
|
88
|
+
return 1; // NULL last
|
|
89
|
+
if (bDate === null && aDate !== null)
|
|
90
|
+
return -1;
|
|
91
|
+
if (aDate !== bDate)
|
|
92
|
+
return aDate < bDate ? 1 : -1; // desc
|
|
93
|
+
if (a.row.sourcePath !== b.row.sourcePath)
|
|
94
|
+
return a.row.sourcePath < b.row.sourcePath ? -1 : 1;
|
|
95
|
+
return a.row.dedupeKey < b.row.dedupeKey ? -1 : a.row.dedupeKey > b.row.dedupeKey ? 1 : a.row.id - b.row.id;
|
|
96
|
+
});
|
|
97
|
+
return counted.map((c) => c.row);
|
|
98
|
+
}
|
|
99
|
+
function provenanceSuffix(row) {
|
|
100
|
+
return ` (source: ${row.sourcePath}${row.validFrom ? `, ${row.validFrom}` : ', date unknown'})`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* C5 query-scoped row filtering (HYP-20260921-query-scoped-rows).
|
|
104
|
+
*
|
|
105
|
+
* Read-path-only efficiency filter: keep rows whose text shares at least one
|
|
106
|
+
* query term. Pure function of (query, rows) — zero model calls, zero access
|
|
107
|
+
* to benchmark categories, gold labels, or any flag beyond the caller's
|
|
108
|
+
* enabled decision (the flag is resolved OUTSIDE; nothing gold is passable).
|
|
109
|
+
*
|
|
110
|
+
* Gold-blind aggregate-intent detection reuses the product's EVIDENCE_INTENT
|
|
111
|
+
* regex on QUERY TEXT ONLY (an AGGREGATE_INTENT-style cue; widened from the
|
|
112
|
+
* eval runner's pattern). On aggregate intent the full facts table is retained
|
|
113
|
+
* (H9 precedent: aggregates need the whole table to count/sum); chronicle and
|
|
114
|
+
* prefs rows are ALWAYS term-filtered, even for aggregate queries.
|
|
115
|
+
*
|
|
116
|
+
* Determinism: overlap test is set membership on term sets; the caller (rank)
|
|
117
|
+
* supplies the stable composer tie-breaks, so relative order is unchanged.
|
|
118
|
+
*/
|
|
119
|
+
function filterRowsByQueryTerms(kind, query, rows) {
|
|
120
|
+
if (kind === 'fact' && (0, gate_1.hasEvidenceIntent)(query))
|
|
121
|
+
return rows;
|
|
122
|
+
const terms = new Set(queryTerms(query));
|
|
123
|
+
if (terms.size === 0)
|
|
124
|
+
return rows; // empty query cannot scope: keep full table
|
|
125
|
+
return rows.filter((row) => {
|
|
126
|
+
const text = (0, rowStore_1.matchForm)(rowSearchText(row));
|
|
127
|
+
for (const term of terms) {
|
|
128
|
+
if (text.includes(term))
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function renderRow(row) {
|
|
135
|
+
const p = row.payload;
|
|
136
|
+
switch (row.rowKind) {
|
|
137
|
+
case 'fact': {
|
|
138
|
+
const f = p;
|
|
139
|
+
const value = [f.value, f.unit].filter(Boolean).join(' ');
|
|
140
|
+
return `- ${[f.verb, f.object].filter(Boolean).join(' ')}: ${value}${provenanceSuffix(row)}`;
|
|
141
|
+
}
|
|
142
|
+
case 'event': {
|
|
143
|
+
const e = p;
|
|
144
|
+
return `- ${e.when}: ${e.event}${provenanceSuffix(row)}`;
|
|
145
|
+
}
|
|
146
|
+
case 'preference': {
|
|
147
|
+
const pr = p;
|
|
148
|
+
return `- ${pr.statement}${provenanceSuffix(row)}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const FALLBACKS = {
|
|
153
|
+
facts: 'Cite only values present in this table; if the answer is not here, use the excerpts instead.',
|
|
154
|
+
chronicle: 'Use only events listed in this table; otherwise rely on the excerpts.',
|
|
155
|
+
prefs: 'When the question touches a preference or constraint, apply the matching entry to your answer as a standing rule. If nothing here is relevant, use the excerpts instead.',
|
|
156
|
+
};
|
|
157
|
+
function renderStructuredSection(name, rows) {
|
|
158
|
+
if (rows.length === 0)
|
|
159
|
+
return null;
|
|
160
|
+
const lines = [`## ${name === 'prefs' ? 'Preferences' : name[0].toUpperCase() + name.slice(1)}`, '', FALLBACKS[name], ''];
|
|
161
|
+
for (const row of rows)
|
|
162
|
+
lines.push(renderRow(row));
|
|
163
|
+
const text = lines.join('\n');
|
|
164
|
+
return { name, text, tokens: estimateTokens(text), rowCount: rows.length };
|
|
165
|
+
}
|
|
166
|
+
function renderEvidence(evidence) {
|
|
167
|
+
if (!evidence.items || evidence.items.length === 0)
|
|
168
|
+
return null;
|
|
169
|
+
const lines = ['## Evidence', ''];
|
|
170
|
+
for (const item of evidence.items) {
|
|
171
|
+
const parts = [item.item];
|
|
172
|
+
if (item.action)
|
|
173
|
+
parts.push(`action: ${item.action}`);
|
|
174
|
+
if (item.amount)
|
|
175
|
+
parts.push(`amount: ${item.amount}`);
|
|
176
|
+
if (item.date)
|
|
177
|
+
parts.push(`date: ${item.date}`);
|
|
178
|
+
lines.push(`- ${parts.join(' | ')}`);
|
|
179
|
+
}
|
|
180
|
+
const text = lines.join('\n');
|
|
181
|
+
return { name: 'evidence', text, tokens: estimateTokens(text), rowCount: evidence.items.length };
|
|
182
|
+
}
|
|
183
|
+
function renderExcerpts(hits) {
|
|
184
|
+
if (hits.length === 0)
|
|
185
|
+
return null;
|
|
186
|
+
const lines = ['## Excerpts', ''];
|
|
187
|
+
for (const hit of hits) {
|
|
188
|
+
lines.push(`[${hit.score.toFixed(3)}] ${hit.notePath}${hit.session_date ? ` (${hit.session_date})` : ''}`);
|
|
189
|
+
lines.push(hit.text);
|
|
190
|
+
lines.push('');
|
|
191
|
+
}
|
|
192
|
+
// Trim the trailing blank so join behavior is stable.
|
|
193
|
+
while (lines.length > 0 && lines[lines.length - 1] === '')
|
|
194
|
+
lines.pop();
|
|
195
|
+
const text = lines.join('\n');
|
|
196
|
+
return { name: 'excerpts', text, tokens: estimateTokens(text), rowCount: hits.length };
|
|
197
|
+
}
|
|
198
|
+
/** Excerpts overflow: deterministic prefix trim (never reorders, never drops the section head). */
|
|
199
|
+
function trimExcerpts(hits, budgetTokens) {
|
|
200
|
+
const kept = [];
|
|
201
|
+
let used = estimateTokens('## Excerpts');
|
|
202
|
+
for (const hit of hits) {
|
|
203
|
+
const block = `[${hit.score.toFixed(3)}] ${hit.notePath}${hit.session_date ? ` (${hit.session_date})` : ''}\n${hit.text}`;
|
|
204
|
+
const cost = estimateTokens(block) + 1;
|
|
205
|
+
if (used + cost > budgetTokens)
|
|
206
|
+
break;
|
|
207
|
+
kept.push(hit);
|
|
208
|
+
used += cost;
|
|
209
|
+
}
|
|
210
|
+
return { hits: kept, tokens: used };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Compose the bounded, deterministically ordered answer context. Structured
|
|
214
|
+
* sections fill first; on total-budget overflow whole sections drop in
|
|
215
|
+
* declared reverse order (prefs → chronicle → facts → evidence) — never
|
|
216
|
+
* silent row-level truncation inside a table. Excerpts take the remaining
|
|
217
|
+
* budget in product retrieval order.
|
|
218
|
+
*/
|
|
219
|
+
function composeQueryContext(input, options = {}) {
|
|
220
|
+
const budget = options.budgetTokens ?? resolveBudgetTokens(options.env);
|
|
221
|
+
const cap = options.sectionCap ?? exports.DEFAULT_SECTION_CAP;
|
|
222
|
+
const { query, hits, evidence } = input;
|
|
223
|
+
// Provenance guard: any structured row without source_path + source_chunk_id
|
|
224
|
+
// is rejected from composition — fail loudly, never silently strip.
|
|
225
|
+
for (const row of input.rows ?? []) {
|
|
226
|
+
if (!row.sourcePath || !row.sourcePath.trim()) {
|
|
227
|
+
throw new ProvenanceError(`structured row ${row.id} (${row.rowKind}) rejected: missing source_path`);
|
|
228
|
+
}
|
|
229
|
+
if (!Number.isInteger(row.sourceChunkId) || row.sourceChunkId <= 0) {
|
|
230
|
+
throw new ProvenanceError(`structured row ${row.id} (${row.rowKind}) rejected: missing source_chunk_id`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Product-observable join: row ↔ query iff its source note is a retrieval hit.
|
|
234
|
+
const hitPaths = new Set(hits.map((h) => h.notePath));
|
|
235
|
+
const terms = queryTerms(query);
|
|
236
|
+
const scoped = queryScopedRowsEnabled(options.env);
|
|
237
|
+
const byKind = (kind) => {
|
|
238
|
+
const kindRows = (input.rows ?? []).filter((r) => r.rowKind === kind && hitPaths.has(r.sourcePath));
|
|
239
|
+
// C5: term-overlap scope BEFORE ranking/cap; budget is still enforced
|
|
240
|
+
// downstream (overflow drop + excerpts trim), so no cap reduction here.
|
|
241
|
+
const scopedRows = scoped
|
|
242
|
+
? filterRowsByQueryTerms(kind, query, kindRows)
|
|
243
|
+
: kindRows;
|
|
244
|
+
return rankRows(scopedRows, terms).slice(0, cap);
|
|
245
|
+
};
|
|
246
|
+
const factsSection = renderStructuredSection('facts', byKind('fact'));
|
|
247
|
+
const chronicleSection = renderStructuredSection('chronicle', byKind('event'));
|
|
248
|
+
const prefsSection = renderStructuredSection('prefs', byKind('preference'));
|
|
249
|
+
const evidenceSection = evidence ? renderEvidence(evidence) : null;
|
|
250
|
+
const excerptsSection = renderExcerpts(hits);
|
|
251
|
+
const structured = [factsSection, chronicleSection, prefsSection, evidenceSection];
|
|
252
|
+
// Fill structured + excerpts, then drop whole sections in declared reverse
|
|
253
|
+
// order until the total fits (excerpts are prefix-trimmed, not dropped).
|
|
254
|
+
const included = structured.filter((s) => s !== null);
|
|
255
|
+
let excerpts = excerptsSection;
|
|
256
|
+
let dropped = [];
|
|
257
|
+
const total = () => included.reduce((sum, s) => sum + s.tokens, 0) + (excerpts?.tokens ?? 0);
|
|
258
|
+
if (total() > budget) {
|
|
259
|
+
for (const name of exports.SECTION_DROP_ORDER) {
|
|
260
|
+
if (total() <= budget)
|
|
261
|
+
break;
|
|
262
|
+
const idx = included.findIndex((s) => s.name === name);
|
|
263
|
+
if (idx !== -1) {
|
|
264
|
+
included.splice(idx, 1);
|
|
265
|
+
dropped.push(name);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (excerpts && total() > budget) {
|
|
269
|
+
const remaining = budget - included.reduce((sum, s) => sum + s.tokens, 0);
|
|
270
|
+
const trimmed = trimExcerpts(hits, Math.max(0, remaining));
|
|
271
|
+
excerpts = trimmed.hits.length > 0
|
|
272
|
+
? { name: 'excerpts', text: renderExcerpts(trimmed.hits).text, tokens: trimmed.tokens, rowCount: trimmed.hits.length }
|
|
273
|
+
: null;
|
|
274
|
+
if (excerpts === null)
|
|
275
|
+
dropped.push('excerpts');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Render in declared section order.
|
|
279
|
+
const ordered = [...included].sort((a, b) => exports.SECTION_ORDER.indexOf(a.name) - exports.SECTION_ORDER.indexOf(b.name));
|
|
280
|
+
const parts = [];
|
|
281
|
+
if (excerpts)
|
|
282
|
+
parts.push(excerpts.text);
|
|
283
|
+
for (const s of ordered)
|
|
284
|
+
parts.push(s.text);
|
|
285
|
+
const sections = [];
|
|
286
|
+
for (const name of exports.SECTION_ORDER) {
|
|
287
|
+
if (name === 'excerpts') {
|
|
288
|
+
if (excerpts) {
|
|
289
|
+
sections.push({ name, included: true, rowCount: excerpts.rowCount, tokens: excerpts.tokens, text: excerpts.text });
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
sections.push({ name, included: false, rowCount: 0, tokens: 0, text: '' });
|
|
293
|
+
}
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const found = ordered.find((s) => s.name === name);
|
|
297
|
+
if (found) {
|
|
298
|
+
sections.push({ name, included: true, rowCount: found.rowCount, tokens: found.tokens, text: found.text });
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
sections.push({ name, included: false, rowCount: 0, tokens: 0, text: '' });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const text = parts.join('\n\n');
|
|
305
|
+
const includedSections = sections.filter((s) => s.included).map((s) => s.name);
|
|
306
|
+
return {
|
|
307
|
+
query,
|
|
308
|
+
budgetTokens: budget,
|
|
309
|
+
usedTokens: parts.reduce((sum, p) => sum + estimateTokens(p), 0),
|
|
310
|
+
sections,
|
|
311
|
+
includedSections,
|
|
312
|
+
droppedSections: dropped,
|
|
313
|
+
text,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/** Convenience wiring helper: read current rows for the query's hits and compose. */
|
|
317
|
+
function composeQueryContextFromDb(db, input, options = {}) {
|
|
318
|
+
const rows = (0, rowStore_1.readCurrentRowsForNotes)(db, input.hits.map((h) => h.notePath));
|
|
319
|
+
return composeQueryContext({ ...input, rows }, options);
|
|
320
|
+
}
|