@gmickel/gno 1.17.0 → 1.19.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/README.md +18 -4
- package/assets/skill/SKILL.md +44 -1
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +3 -2
- package/spec/AGENTS.md +83 -0
- package/spec/CLAUDE.md +83 -0
- package/spec/bench-fixture.schema.json +137 -0
- package/spec/cli.md +2894 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +510 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2229 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +366 -0
- package/spec/output-schemas/backlinks.schema.json +131 -0
- package/spec/output-schemas/bench-result.schema.json +120 -0
- package/spec/output-schemas/capture-receipt.schema.json +143 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
- package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
- package/spec/output-schemas/context-list.schema.json +21 -0
- package/spec/output-schemas/doctor.schema.json +313 -0
- package/spec/output-schemas/error.schema.json +30 -0
- package/spec/output-schemas/expansion.schema.json +37 -0
- package/spec/output-schemas/get.schema.json +140 -0
- package/spec/output-schemas/graph-query.schema.json +99 -0
- package/spec/output-schemas/graph.schema.json +371 -0
- package/spec/output-schemas/links-list.schema.json +186 -0
- package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
- package/spec/output-schemas/mcp-http-error.schema.json +30 -0
- package/spec/output-schemas/mcp-job-list.schema.json +58 -0
- package/spec/output-schemas/mcp-job-status.schema.json +224 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
- package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
- package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
- package/spec/output-schemas/models-list.schema.json +93 -0
- package/spec/output-schemas/multi-get.schema.json +103 -0
- package/spec/output-schemas/process-status.schema.json +119 -0
- package/spec/output-schemas/query-diagnose.schema.json +123 -0
- package/spec/output-schemas/resident-status.schema.json +154 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
- package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
- package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
- package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
- package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
- package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
- package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
- package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
- package/spec/output-schemas/search-result.schema.json +154 -0
- package/spec/output-schemas/search-results.schema.json +338 -0
- package/spec/output-schemas/similar.schema.json +84 -0
- package/spec/output-schemas/status.schema.json +676 -0
- package/spec/output-schemas/tags-list.schema.json +48 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +15 -1
- package/src/cli/commands/ask.ts +106 -36
- package/src/cli/commands/context-build.ts +56 -9
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/get.ts +64 -3
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/query.ts +62 -23
- package/src/cli/commands/replay.ts +140 -0
- package/src/cli/commands/search.ts +48 -3
- package/src/cli/commands/shared.ts +3 -1
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/commands/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/detach.ts +37 -20
- package/src/cli/program.ts +329 -27
- package/src/config/index.ts +12 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +41 -0
- package/src/core/context-compiler.ts +11 -4
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/core/retrieval-qrels.ts +405 -0
- package/src/core/retrieval-replay-candidate.ts +368 -0
- package/src/core/retrieval-replay-types.ts +109 -0
- package/src/core/retrieval-replay-validation.ts +89 -0
- package/src/core/retrieval-replay.ts +441 -0
- package/src/core/retrieval-trace-evidence-origin.ts +175 -0
- package/src/core/retrieval-trace-export.ts +113 -0
- package/src/core/retrieval-trace-filter-normalization.ts +27 -0
- package/src/core/retrieval-trace-filters.ts +19 -0
- package/src/core/retrieval-trace-management-helpers.ts +247 -0
- package/src/core/retrieval-trace-management-types.ts +132 -0
- package/src/core/retrieval-trace-management.ts +422 -0
- package/src/core/retrieval-trace-request.ts +141 -0
- package/src/core/retrieval-trace-session.ts +494 -0
- package/src/core/retrieval-trace.ts +472 -0
- package/src/llm/cache.ts +13 -3
- package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
- package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
- package/src/mcp/context.ts +161 -0
- package/src/mcp/http-security.ts +477 -0
- package/src/mcp/http-session.ts +272 -0
- package/src/mcp/http-transport.ts +370 -0
- package/src/mcp/resources/index.ts +141 -134
- package/src/mcp/server.ts +19 -79
- package/src/mcp/tools/add-collection.ts +3 -1
- package/src/mcp/tools/capture.ts +3 -0
- package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
- package/src/mcp/tools/context.ts +68 -16
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +96 -2
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/pipeline/answer.ts +167 -26
- package/src/pipeline/graph-retrieval.ts +15 -1
- package/src/pipeline/hybrid.ts +151 -43
- package/src/pipeline/search.ts +36 -3
- package/src/pipeline/trace-metadata.ts +47 -0
- package/src/pipeline/types.ts +43 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +380 -71
- package/src/sdk/documents.ts +48 -1
- package/src/sdk/index.ts +17 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/background-runtime.ts +12 -212
- package/src/serve/context-capsule.ts +67 -8
- package/src/serve/embed-scheduler.ts +74 -43
- package/src/serve/index.ts +9 -0
- package/src/serve/jobs.ts +78 -80
- package/src/serve/public/app.tsx +12 -1
- package/src/serve/public/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Dashboard.tsx +11 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -0
- package/src/serve/resident-admission.ts +159 -0
- package/src/serve/resident-background-work.ts +39 -0
- package/src/serve/resident-request.ts +55 -0
- package/src/serve/resident-runtime.ts +490 -0
- package/src/serve/resident-status.ts +96 -0
- package/src/serve/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +629 -239
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +276 -37
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/index.ts +31 -0
- package/src/store/migrations/014-retrieval-traces.ts +303 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/retrieval-trace-codec.ts +384 -0
- package/src/store/sqlite/adapter.ts +179 -10
- package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
- package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
- package/src/store/sqlite/retrieval-trace-store.ts +515 -0
- package/src/store/types.ts +297 -0
- package/src/store/vector/sqlite-vec.ts +76 -1
- package/src/store/vector/types.ts +1 -1
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
planMoveRefactor,
|
|
28
28
|
planRenameRefactor,
|
|
29
29
|
} from "../../core/file-refactors";
|
|
30
|
+
import { recordContentMutation } from "../../core/mutation-generations";
|
|
30
31
|
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
31
32
|
import { runTool, type ToolResult } from "./index";
|
|
32
33
|
|
|
@@ -210,11 +211,12 @@ export function handleRenameNote(
|
|
|
210
211
|
const currentPath = join(collection.path, doc.relPath);
|
|
211
212
|
const nextPath = join(collection.path, plan.nextRelPath);
|
|
212
213
|
await renameFilePath(currentPath, nextPath);
|
|
213
|
-
await defaultSyncService.syncCollection(
|
|
214
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
214
215
|
collection,
|
|
215
216
|
ctx.store,
|
|
216
217
|
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
217
218
|
);
|
|
219
|
+
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
218
220
|
return {
|
|
219
221
|
uri: plan.nextUri,
|
|
220
222
|
relPath: plan.nextRelPath,
|
|
@@ -255,11 +257,12 @@ export function handleMoveNote(
|
|
|
255
257
|
const nextPath = join(collection.path, plan.nextRelPath);
|
|
256
258
|
await mkdir(dirname(nextPath), { recursive: true });
|
|
257
259
|
await renameFilePath(currentPath, nextPath);
|
|
258
|
-
await defaultSyncService.syncCollection(
|
|
260
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
259
261
|
collection,
|
|
260
262
|
ctx.store,
|
|
261
263
|
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
262
264
|
);
|
|
265
|
+
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
263
266
|
return {
|
|
264
267
|
uri: plan.nextUri,
|
|
265
268
|
relPath: plan.nextRelPath,
|
|
@@ -308,11 +311,12 @@ export function handleDuplicateNote(
|
|
|
308
311
|
const nextPath = join(collection.path, plan.nextRelPath);
|
|
309
312
|
await mkdir(dirname(nextPath), { recursive: true });
|
|
310
313
|
await copyFilePath(currentPath, nextPath);
|
|
311
|
-
await defaultSyncService.syncCollection(
|
|
314
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
312
315
|
collection,
|
|
313
316
|
ctx.store,
|
|
314
317
|
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
315
318
|
);
|
|
319
|
+
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
316
320
|
return {
|
|
317
321
|
uri: plan.nextUri,
|
|
318
322
|
relPath: plan.nextRelPath,
|
package/src/pipeline/answer.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module src/pipeline/answer
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
|
|
8
9
|
import type { GenerationPort } from "../llm/types";
|
|
9
10
|
import type { StorePort } from "../store/types";
|
|
10
11
|
import type {
|
|
@@ -15,6 +16,11 @@ import type {
|
|
|
15
16
|
} from "./types";
|
|
16
17
|
|
|
17
18
|
import { buildAnswerPrompt, type AnswerPromptSource } from "./answer-prompt";
|
|
19
|
+
import { attachCitationTraceMetadata } from "./trace-metadata";
|
|
20
|
+
import {
|
|
21
|
+
CITATION_TRACE_METADATA,
|
|
22
|
+
SEARCH_RESULT_PLANNER_METADATA,
|
|
23
|
+
} from "./types";
|
|
18
24
|
|
|
19
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
26
|
// Constants
|
|
@@ -24,6 +30,11 @@ import { buildAnswerPrompt, type AnswerPromptSource } from "./answer-prompt";
|
|
|
24
30
|
export const ABSTENTION_MESSAGE =
|
|
25
31
|
"I don't have enough information in the provided sources to answer this question.";
|
|
26
32
|
|
|
33
|
+
export const answerTraceTerminalStatus = (
|
|
34
|
+
citations: readonly Citation[] | undefined
|
|
35
|
+
): "completed" | "partial" =>
|
|
36
|
+
citations && citations.length > 0 ? "completed" : "partial";
|
|
37
|
+
|
|
27
38
|
/** Max characters per document (~8K tokens) */
|
|
28
39
|
const MAX_DOC_CHARS = 32_000;
|
|
29
40
|
|
|
@@ -86,6 +97,65 @@ interface SelectedSource {
|
|
|
86
97
|
reason: string;
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
interface ExactAnswerPassage {
|
|
101
|
+
text: string;
|
|
102
|
+
startLine: number;
|
|
103
|
+
endLine: number;
|
|
104
|
+
passageHash: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sha256 = (value: string): string =>
|
|
108
|
+
new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
109
|
+
|
|
110
|
+
const completeLinePrefix = (
|
|
111
|
+
content: string,
|
|
112
|
+
startLine: number,
|
|
113
|
+
maxChars: number
|
|
114
|
+
): ExactAnswerPassage | null => {
|
|
115
|
+
const lines = content.split("\n");
|
|
116
|
+
if (lines.length === 0) return null;
|
|
117
|
+
const selected: string[] = [];
|
|
118
|
+
let characters = 0;
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const addition = selected.length === 0 ? line.length : line.length + 1;
|
|
121
|
+
if (characters + addition > maxChars) break;
|
|
122
|
+
selected.push(line);
|
|
123
|
+
characters += addition;
|
|
124
|
+
}
|
|
125
|
+
if (selected.length === 0) return null;
|
|
126
|
+
const text = selected.join("\n");
|
|
127
|
+
if (!text.trim()) return null;
|
|
128
|
+
return {
|
|
129
|
+
text,
|
|
130
|
+
startLine,
|
|
131
|
+
endLine: startLine + selected.length - 1,
|
|
132
|
+
passageHash: sha256(text),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const anchoredCompleteLines = (
|
|
137
|
+
content: string,
|
|
138
|
+
metadata: SearchResult[typeof SEARCH_RESULT_PLANNER_METADATA]
|
|
139
|
+
): ExactAnswerPassage | null => {
|
|
140
|
+
if (
|
|
141
|
+
!metadata?.passageHash ||
|
|
142
|
+
metadata.startLine === undefined ||
|
|
143
|
+
metadata.endLine === undefined ||
|
|
144
|
+
metadata.endLine < metadata.startLine
|
|
145
|
+
) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const lines = content.split("\n");
|
|
149
|
+
const text = lines.slice(metadata.startLine - 1, metadata.endLine).join("\n");
|
|
150
|
+
if (
|
|
151
|
+
text.split("\n").length !== metadata.endLine - metadata.startLine + 1 ||
|
|
152
|
+
sha256(text) !== metadata.passageHash
|
|
153
|
+
) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return completeLinePrefix(text, metadata.startLine, MAX_DOC_CHARS);
|
|
157
|
+
};
|
|
158
|
+
|
|
89
159
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
160
|
// Citation Processing
|
|
91
161
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -423,53 +493,78 @@ export async function generateGroundedAnswer(
|
|
|
423
493
|
): Promise<AnswerGenerationResult | null> {
|
|
424
494
|
const { genPort, store } = deps;
|
|
425
495
|
const sourceSelection = selectAdaptiveSources(query, results);
|
|
496
|
+
const finalRanks = new Map(
|
|
497
|
+
results.map((result, index) => [result, index + 1] as const)
|
|
498
|
+
);
|
|
426
499
|
const promptSources: AnswerPromptSource[] = [];
|
|
427
500
|
const citations: Citation[] = [];
|
|
428
501
|
let citationIndex = 0;
|
|
429
502
|
|
|
430
503
|
for (const r of sourceSelection.selected) {
|
|
431
|
-
let
|
|
432
|
-
|
|
504
|
+
let passage: ExactAnswerPassage | null = null;
|
|
505
|
+
const plannerMetadata = r[SEARCH_RESULT_PLANNER_METADATA];
|
|
506
|
+
const sourceHash = r.source.sourceHash;
|
|
507
|
+
const mirrorHash = r.conversion?.mirrorHash;
|
|
433
508
|
|
|
434
509
|
// Try to fetch full document content if store available
|
|
435
|
-
if (store &&
|
|
436
|
-
const contentResult = await store.getContent(
|
|
510
|
+
if (store && mirrorHash) {
|
|
511
|
+
const contentResult = await store.getContent(mirrorHash);
|
|
437
512
|
if (contentResult.ok && contentResult.value) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
content = `${content.slice(0, MAX_DOC_CHARS)}\n\n[... truncated ...]`;
|
|
513
|
+
if (sha256(contentResult.value) === mirrorHash) {
|
|
514
|
+
passage =
|
|
515
|
+
anchoredCompleteLines(contentResult.value, plannerMetadata) ??
|
|
516
|
+
completeLinePrefix(contentResult.value, 1, MAX_DOC_CHARS);
|
|
443
517
|
}
|
|
444
518
|
}
|
|
445
519
|
}
|
|
446
520
|
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
521
|
+
// Snippet fallback is allowed only when the planner proves it is the exact
|
|
522
|
+
// complete canonical chunk, never an FTS marker/ellipsis presentation.
|
|
523
|
+
if (
|
|
524
|
+
!passage &&
|
|
525
|
+
plannerMetadata?.passageHash &&
|
|
526
|
+
plannerMetadata.startLine !== undefined &&
|
|
527
|
+
plannerMetadata.endLine !== undefined &&
|
|
528
|
+
sha256(r.snippet) === plannerMetadata.passageHash &&
|
|
529
|
+
r.snippet.split("\n").length ===
|
|
530
|
+
plannerMetadata.endLine - plannerMetadata.startLine + 1
|
|
531
|
+
) {
|
|
532
|
+
passage = completeLinePrefix(
|
|
533
|
+
r.snippet,
|
|
534
|
+
plannerMetadata.startLine,
|
|
535
|
+
MAX_SNIPPET_CHARS
|
|
536
|
+
);
|
|
456
537
|
}
|
|
538
|
+
if (!(passage && sourceHash && mirrorHash)) continue;
|
|
457
539
|
|
|
458
540
|
citationIndex += 1;
|
|
459
541
|
promptSources.push({
|
|
460
542
|
index: citationIndex,
|
|
461
543
|
docid: r.docid,
|
|
462
544
|
uri: r.uri,
|
|
463
|
-
content,
|
|
545
|
+
content: passage.text,
|
|
464
546
|
guidance: r.context,
|
|
465
547
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
548
|
+
citations.push(
|
|
549
|
+
attachCitationTraceMetadata(
|
|
550
|
+
{
|
|
551
|
+
docid: r.docid,
|
|
552
|
+
uri: r.uri,
|
|
553
|
+
startLine: passage.startLine,
|
|
554
|
+
endLine: passage.endLine,
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
sourceHash,
|
|
558
|
+
mirrorHash,
|
|
559
|
+
passageHash: passage.passageHash,
|
|
560
|
+
seq: plannerMetadata?.seq,
|
|
561
|
+
rank: finalRanks.get(r) ?? 1,
|
|
562
|
+
plannerRank: plannerMetadata?.retrievalRank,
|
|
563
|
+
sources: plannerMetadata?.sources,
|
|
564
|
+
graphExpanded: plannerMetadata?.graphExpanded,
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
);
|
|
473
568
|
}
|
|
474
569
|
|
|
475
570
|
if (promptSources.length === 0) {
|
|
@@ -528,3 +623,49 @@ export function processAnswerResult(rawResult: AnswerGenerationResult): {
|
|
|
528
623
|
answerContext: rawResult.answerContext,
|
|
529
624
|
};
|
|
530
625
|
}
|
|
626
|
+
|
|
627
|
+
/** Process first, then record only citations retained in the final answer. */
|
|
628
|
+
export async function processAnswerResultWithTrace(
|
|
629
|
+
rawResult: AnswerGenerationResult,
|
|
630
|
+
traceSession?: RetrievalTraceSession
|
|
631
|
+
): Promise<ReturnType<typeof processAnswerResult>> {
|
|
632
|
+
const processed = processAnswerResult(rawResult);
|
|
633
|
+
if (!traceSession || processed.citations.length === 0) return processed;
|
|
634
|
+
const evidence = processed.citations.flatMap((citation) => {
|
|
635
|
+
const metadata = citation[CITATION_TRACE_METADATA];
|
|
636
|
+
if (
|
|
637
|
+
!metadata ||
|
|
638
|
+
citation.startLine === undefined ||
|
|
639
|
+
citation.endLine === undefined
|
|
640
|
+
) {
|
|
641
|
+
return [];
|
|
642
|
+
}
|
|
643
|
+
return [
|
|
644
|
+
{
|
|
645
|
+
docid: citation.docid,
|
|
646
|
+
uri: citation.uri,
|
|
647
|
+
sourceHash: metadata.sourceHash,
|
|
648
|
+
mirrorHash: metadata.mirrorHash,
|
|
649
|
+
passageHash: metadata.passageHash,
|
|
650
|
+
startLine: citation.startLine,
|
|
651
|
+
endLine: citation.endLine,
|
|
652
|
+
rank: metadata.rank,
|
|
653
|
+
...(metadata.seq === undefined ? {} : { seq: metadata.seq }),
|
|
654
|
+
...(metadata.plannerRank === undefined
|
|
655
|
+
? {}
|
|
656
|
+
: { plannerRank: metadata.plannerRank }),
|
|
657
|
+
...(metadata.sources === undefined
|
|
658
|
+
? {}
|
|
659
|
+
: { sources: metadata.sources }),
|
|
660
|
+
...(metadata.graphExpanded === undefined
|
|
661
|
+
? {}
|
|
662
|
+
: { graphExpanded: metadata.graphExpanded }),
|
|
663
|
+
},
|
|
664
|
+
];
|
|
665
|
+
});
|
|
666
|
+
const recorded = await traceSession.recordEvidence("cite", evidence);
|
|
667
|
+
if (!recorded.ok) {
|
|
668
|
+
throw new Error(`Trace recording failed: ${recorded.error.message}`);
|
|
669
|
+
}
|
|
670
|
+
return processed;
|
|
671
|
+
}
|
|
@@ -62,6 +62,7 @@ const matchesDocumentFilters = (
|
|
|
62
62
|
until?: string;
|
|
63
63
|
categories?: string[];
|
|
64
64
|
author?: string;
|
|
65
|
+
relPathPrefix?: string;
|
|
65
66
|
}
|
|
66
67
|
): boolean => {
|
|
67
68
|
if (
|
|
@@ -201,6 +202,7 @@ export async function expandGraphCandidates(
|
|
|
201
202
|
until?: string;
|
|
202
203
|
categories?: string[];
|
|
203
204
|
author?: string;
|
|
205
|
+
relPathPrefix?: string;
|
|
204
206
|
} = {}
|
|
205
207
|
): Promise<GraphRetrievalResult> {
|
|
206
208
|
const maxCandidates = Math.max(
|
|
@@ -249,6 +251,13 @@ export async function expandGraphCandidates(
|
|
|
249
251
|
if (!doc.mirrorHash) {
|
|
250
252
|
continue;
|
|
251
253
|
}
|
|
254
|
+
if (
|
|
255
|
+
options.relPathPrefix !== undefined &&
|
|
256
|
+
doc.relPath !== options.relPathPrefix &&
|
|
257
|
+
!doc.relPath.startsWith(`${options.relPathPrefix}/`)
|
|
258
|
+
) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
252
261
|
const rank =
|
|
253
262
|
seedCandidates.findIndex(
|
|
254
263
|
(candidate) => candidate.mirrorHash === doc.mirrorHash
|
|
@@ -325,7 +334,12 @@ export async function expandGraphCandidates(
|
|
|
325
334
|
}
|
|
326
335
|
|
|
327
336
|
const metadataFilteredDocs = docsResult.value.filter(
|
|
328
|
-
(doc) =>
|
|
337
|
+
(doc) =>
|
|
338
|
+
doc.mirrorHash &&
|
|
339
|
+
(options.relPathPrefix === undefined ||
|
|
340
|
+
doc.relPath === options.relPathPrefix ||
|
|
341
|
+
doc.relPath.startsWith(`${options.relPathPrefix}/`)) &&
|
|
342
|
+
matchesDocumentFilters(doc, options)
|
|
329
343
|
);
|
|
330
344
|
const docs = await filterDocsByTags(store, metadataFilteredDocs, options);
|
|
331
345
|
const docByDocid = new Map(docs.map((doc) => [doc.docid, doc]));
|
package/src/pipeline/hybrid.ts
CHANGED
|
@@ -55,9 +55,10 @@ import {
|
|
|
55
55
|
shouldSortByRecency,
|
|
56
56
|
} from "./temporal";
|
|
57
57
|
import {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} from "./
|
|
58
|
+
attachSearchResultPlannerMetadata,
|
|
59
|
+
attachSearchResultsTraceMetadata,
|
|
60
|
+
} from "./trace-metadata";
|
|
61
|
+
import { DEFAULT_PIPELINE_CONFIG } from "./types";
|
|
61
62
|
|
|
62
63
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
64
|
// Dependencies
|
|
@@ -121,11 +122,13 @@ async function checkBm25Strength(
|
|
|
121
122
|
until?: string;
|
|
122
123
|
categories?: string[];
|
|
123
124
|
author?: string;
|
|
125
|
+
relPathPrefix?: string;
|
|
124
126
|
}
|
|
125
127
|
): Promise<boolean> {
|
|
126
128
|
const result = await store.searchFts(query, {
|
|
127
129
|
limit: 5,
|
|
128
130
|
collection: options?.collection,
|
|
131
|
+
relPathPrefix: options?.relPathPrefix,
|
|
129
132
|
language: options?.lang,
|
|
130
133
|
tagsAll: options?.tagsAll,
|
|
131
134
|
tagsAny: options?.tagsAny,
|
|
@@ -179,11 +182,13 @@ async function searchFtsChunks(
|
|
|
179
182
|
until?: string;
|
|
180
183
|
categories?: string[];
|
|
181
184
|
author?: string;
|
|
185
|
+
relPathPrefix?: string;
|
|
182
186
|
}
|
|
183
187
|
): Promise<FtsChunksResult> {
|
|
184
188
|
const result = await store.searchFts(query, {
|
|
185
189
|
limit: options.limit,
|
|
186
190
|
collection: options.collection,
|
|
191
|
+
relPathPrefix: options.relPathPrefix,
|
|
187
192
|
language: options.lang,
|
|
188
193
|
tagsAll: options.tagsAll,
|
|
189
194
|
tagsAny: options.tagsAny,
|
|
@@ -216,7 +221,11 @@ async function searchVectorChunks(
|
|
|
216
221
|
vectorIndex: VectorIndexPort,
|
|
217
222
|
embedPort: EmbeddingPort,
|
|
218
223
|
query: string,
|
|
219
|
-
options: {
|
|
224
|
+
options: {
|
|
225
|
+
limit: number;
|
|
226
|
+
minScore?: number;
|
|
227
|
+
allowedMirrorHashes?: string[];
|
|
228
|
+
}
|
|
220
229
|
): Promise<ChunkId[]> {
|
|
221
230
|
if (!vectorIndex.searchAvailable) {
|
|
222
231
|
return [];
|
|
@@ -234,7 +243,10 @@ async function searchVectorChunks(
|
|
|
234
243
|
const searchResult = await vectorIndex.searchNearest(
|
|
235
244
|
queryEmbedding,
|
|
236
245
|
options.limit,
|
|
237
|
-
{
|
|
246
|
+
{
|
|
247
|
+
minScore: options.minScore,
|
|
248
|
+
allowedMirrorHashes: options.allowedMirrorHashes,
|
|
249
|
+
}
|
|
238
250
|
);
|
|
239
251
|
|
|
240
252
|
if (!searchResult.ok) {
|
|
@@ -381,6 +393,7 @@ export async function searchHybrid(
|
|
|
381
393
|
until: temporalRange.until,
|
|
382
394
|
categories: options.categories,
|
|
383
395
|
author: options.author,
|
|
396
|
+
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
384
397
|
});
|
|
385
398
|
|
|
386
399
|
if (hasStrongSignal) {
|
|
@@ -397,6 +410,8 @@ export async function searchHybrid(
|
|
|
397
410
|
});
|
|
398
411
|
if (expandResult.ok) {
|
|
399
412
|
expansion = expandResult.value;
|
|
413
|
+
} else {
|
|
414
|
+
counters.fallbackEvents.push("expansion_error");
|
|
400
415
|
}
|
|
401
416
|
}
|
|
402
417
|
}
|
|
@@ -428,6 +443,7 @@ export async function searchHybrid(
|
|
|
428
443
|
until: temporalRange.until,
|
|
429
444
|
categories: options.categories,
|
|
430
445
|
author: options.author,
|
|
446
|
+
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
431
447
|
});
|
|
432
448
|
|
|
433
449
|
// Propagate FTS syntax errors as INVALID_INPUT
|
|
@@ -462,6 +478,7 @@ export async function searchHybrid(
|
|
|
462
478
|
until: temporalRange.until,
|
|
463
479
|
categories: options.categories,
|
|
464
480
|
author: options.author,
|
|
481
|
+
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
465
482
|
})
|
|
466
483
|
)
|
|
467
484
|
);
|
|
@@ -509,6 +526,7 @@ export async function searchHybrid(
|
|
|
509
526
|
query,
|
|
510
527
|
{
|
|
511
528
|
limit: limit * 2 * retrievalMultiplier,
|
|
529
|
+
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
512
530
|
}
|
|
513
531
|
);
|
|
514
532
|
|
|
@@ -552,7 +570,10 @@ export async function searchHybrid(
|
|
|
552
570
|
|
|
553
571
|
const searchResult = await vectorIndex.searchNearest(
|
|
554
572
|
new Float32Array(embedding),
|
|
555
|
-
variant.limit
|
|
573
|
+
variant.limit,
|
|
574
|
+
{
|
|
575
|
+
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
576
|
+
}
|
|
556
577
|
);
|
|
557
578
|
if (!searchResult.ok || searchResult.value.length === 0) {
|
|
558
579
|
continue;
|
|
@@ -615,6 +636,7 @@ export async function searchHybrid(
|
|
|
615
636
|
limit,
|
|
616
637
|
candidateLimit,
|
|
617
638
|
disabled: !options.graph || options.noGraph,
|
|
639
|
+
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
618
640
|
lang: options.lang,
|
|
619
641
|
tagsAll: options.tagsAll,
|
|
620
642
|
tagsAny: options.tagsAny,
|
|
@@ -735,6 +757,14 @@ export async function searchHybrid(
|
|
|
735
757
|
const matchesMetadataFilters = (
|
|
736
758
|
doc: (typeof docsResult.value)[number]
|
|
737
759
|
): boolean => {
|
|
760
|
+
const relPathPrefix = options.retrievalScope?.relPathPrefix;
|
|
761
|
+
if (
|
|
762
|
+
relPathPrefix !== undefined &&
|
|
763
|
+
doc.relPath !== relPathPrefix &&
|
|
764
|
+
!doc.relPath.startsWith(`${relPathPrefix}/`)
|
|
765
|
+
) {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
738
768
|
if (!isWithinTemporalRange(doc.sourceMtime, temporalRange)) {
|
|
739
769
|
return false;
|
|
740
770
|
}
|
|
@@ -923,42 +953,51 @@ export async function searchHybrid(
|
|
|
923
953
|
if (!docidMap.has(docidKey)) docidMap.set(docidKey, doc.docid);
|
|
924
954
|
const collectionPath = collectionPaths.get(doc.collection);
|
|
925
955
|
seenDocids.add(doc.docid);
|
|
926
|
-
results.push(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
956
|
+
results.push(
|
|
957
|
+
attachSearchResultPlannerMetadata(
|
|
958
|
+
{
|
|
959
|
+
docid: doc.docid,
|
|
960
|
+
score: candidate.blendedScore,
|
|
961
|
+
uri: doc.uri,
|
|
962
|
+
title: doc.title ?? undefined,
|
|
963
|
+
contentType: doc.contentType ?? undefined,
|
|
964
|
+
categories: doc.categories ?? undefined,
|
|
965
|
+
line: snippetChunk.startLine,
|
|
966
|
+
snippet,
|
|
967
|
+
snippetLanguage: chunk.language ?? undefined,
|
|
968
|
+
snippetRange,
|
|
969
|
+
source: {
|
|
970
|
+
relPath: doc.relPath,
|
|
971
|
+
absPath: collectionPath
|
|
972
|
+
? `${collectionPath}/${doc.relPath}`
|
|
973
|
+
: undefined,
|
|
974
|
+
mime: doc.sourceMime,
|
|
975
|
+
ext: doc.sourceExt,
|
|
976
|
+
modifiedAt: doc.sourceMtime,
|
|
977
|
+
documentDate: doc.frontmatterDate ?? undefined,
|
|
978
|
+
sizeBytes: doc.sourceSize,
|
|
979
|
+
sourceHash: doc.sourceHash,
|
|
980
|
+
},
|
|
981
|
+
conversion: {
|
|
982
|
+
mirrorHash: candidate.mirrorHash,
|
|
983
|
+
converterId: doc.converterId ?? undefined,
|
|
984
|
+
converterVersion: doc.converterVersion ?? undefined,
|
|
985
|
+
},
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
retrievalRank: candidateIndex + 1,
|
|
989
|
+
mirrorHash: candidate.mirrorHash,
|
|
990
|
+
seq: snippetChunk.seq,
|
|
991
|
+
sources: [...candidate.sources].sort(),
|
|
992
|
+
graphExpanded: candidate.sources.includes("graph"),
|
|
993
|
+
startLine: snippetChunk.startLine,
|
|
994
|
+
endLine: snippetChunk.endLine,
|
|
995
|
+
passageHash: new Bun.CryptoHasher("sha256")
|
|
996
|
+
.update(snippetChunk.text)
|
|
997
|
+
.digest("hex"),
|
|
998
|
+
}
|
|
999
|
+
)
|
|
1000
|
+
);
|
|
962
1001
|
}
|
|
963
1002
|
}
|
|
964
1003
|
timings.assemblyMs = performance.now() - assemblyStartedAt;
|
|
@@ -1002,7 +1041,7 @@ export async function searchHybrid(
|
|
|
1002
1041
|
const finalResults = results.slice(0, limit);
|
|
1003
1042
|
await attachSearchResultContexts(store, finalResults);
|
|
1004
1043
|
|
|
1005
|
-
|
|
1044
|
+
const output: SearchResults = {
|
|
1006
1045
|
results: finalResults,
|
|
1007
1046
|
meta: {
|
|
1008
1047
|
query,
|
|
@@ -1033,5 +1072,74 @@ export async function searchHybrid(
|
|
|
1033
1072
|
explain: explainData,
|
|
1034
1073
|
trace: diagnoseTrace,
|
|
1035
1074
|
},
|
|
1075
|
+
};
|
|
1076
|
+
const fallbackCodes = [...new Set(counters.fallbackEvents)].sort();
|
|
1077
|
+
const capabilityOutcomes = [
|
|
1078
|
+
{ capability: "lexical_search", status: "used" as const },
|
|
1079
|
+
vectorAvailable
|
|
1080
|
+
? fallbackCodes.includes("vector_embed_error")
|
|
1081
|
+
? {
|
|
1082
|
+
capability: "semantic_search",
|
|
1083
|
+
status: "failed" as const,
|
|
1084
|
+
reasonCode: "vector_embed_error",
|
|
1085
|
+
}
|
|
1086
|
+
: { capability: "semantic_search", status: "used" as const }
|
|
1087
|
+
: {
|
|
1088
|
+
capability: "semantic_search",
|
|
1089
|
+
status: "unavailable" as const,
|
|
1090
|
+
reasonCode: "vector_unavailable",
|
|
1091
|
+
},
|
|
1092
|
+
expansion !== null
|
|
1093
|
+
? { capability: "query_expansion", status: "used" as const }
|
|
1094
|
+
: {
|
|
1095
|
+
capability: "query_expansion",
|
|
1096
|
+
status:
|
|
1097
|
+
expansionStatus === "attempted"
|
|
1098
|
+
? ("failed" as const)
|
|
1099
|
+
: ("unavailable" as const),
|
|
1100
|
+
reasonCode:
|
|
1101
|
+
expansionStatus === "attempted"
|
|
1102
|
+
? "expansion_error"
|
|
1103
|
+
: expansionStatus === "skipped_strong"
|
|
1104
|
+
? "expansion_skipped_strong"
|
|
1105
|
+
: "expansion_disabled",
|
|
1106
|
+
},
|
|
1107
|
+
rerankResult.reranked
|
|
1108
|
+
? { capability: "reranking", status: "used" as const }
|
|
1109
|
+
: {
|
|
1110
|
+
capability: "reranking",
|
|
1111
|
+
status:
|
|
1112
|
+
rerankResult.fallbackReason === "error"
|
|
1113
|
+
? ("failed" as const)
|
|
1114
|
+
: ("unavailable" as const),
|
|
1115
|
+
reasonCode:
|
|
1116
|
+
rerankResult.fallbackReason === "error"
|
|
1117
|
+
? "rerank_error"
|
|
1118
|
+
: "rerank_disabled",
|
|
1119
|
+
},
|
|
1120
|
+
graphExpansion.meta.enabled
|
|
1121
|
+
? { capability: "graph_expansion", status: "used" as const }
|
|
1122
|
+
: {
|
|
1123
|
+
capability: "graph_expansion",
|
|
1124
|
+
status: "unavailable" as const,
|
|
1125
|
+
reasonCode:
|
|
1126
|
+
graphExpansion.meta.fallbackReasons[0] ?? "graph_disabled",
|
|
1127
|
+
},
|
|
1128
|
+
];
|
|
1129
|
+
attachSearchResultsTraceMetadata(output, {
|
|
1130
|
+
capabilityOutcomes,
|
|
1131
|
+
fallbackCodes,
|
|
1036
1132
|
});
|
|
1133
|
+
const traceResult = await options.traceSession?.recordRetrieval(
|
|
1134
|
+
output,
|
|
1135
|
+
timings.totalMs
|
|
1136
|
+
);
|
|
1137
|
+
if (traceResult && !traceResult.ok) {
|
|
1138
|
+
return err(
|
|
1139
|
+
"QUERY_FAILED",
|
|
1140
|
+
`Trace recording failed: ${traceResult.error.message}`,
|
|
1141
|
+
traceResult.error.cause
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
return ok(output);
|
|
1037
1145
|
}
|