@hraness/kb 0.17.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/DISCLOSURE +43 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +3646 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +39 -0
- package/dist/index-01jj6rbv.js +1103 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-0kavxzqj.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1n418kb9.js +827 -0
- package/dist/index-1vrd1rmn.js +56 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-3v2z4f0q.js +328 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6g2pv9d2.js +370 -0
- package/dist/index-7fzc32gf.js +712 -0
- package/dist/index-84x0vjjp.js +432 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-cv6fh7z5.js +462 -0
- package/dist/index-cxfrakt7.js +1067 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dycjbhw2.js +1943 -0
- package/dist/index-dyqwejk5.js +531 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-jsmvyyvf.js +1166 -0
- package/dist/index-kh0pwvdd.js +1974 -0
- package/dist/index-mqx4nd6v.js +119 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n5dd7r0v.js +711 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-qry4vhxk.js +1041 -0
- package/dist/index-s2gw5aw9.js +199 -0
- package/dist/index-vvdj6kca.js +70 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-zr53sf63.js +44 -0
- package/dist/index-zxdy5pby.js +2728 -0
- package/dist/index-zzhgcwyt.js +966 -0
- package/dist/index.js +453 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +24 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +409 -0
- package/skills/kb/AGENTS.md +19 -0
- package/skills/kb/SKILL.md +82 -0
- package/skills/kb/agents/openai.yaml +4 -0
- package/skills/kb/references/pdf-review.md +51 -0
- package/skills/kb/references/percolate.md +144 -0
- package/skills/kb/references/plan-structure.md +105 -0
- package/skills/kb/references/plan.md +115 -0
- package/skills/kb/references/query.md +231 -0
- package/skills/kb/references/refresh.md +133 -0
- package/skills/kb/references/save-pdf.md +125 -0
- package/skills/kb/references/save-url.md +178 -0
- package/skills/kb/references/url-authentication.md +75 -0
- package/skills/kb/references/url-platforms.md +25 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring.ts +1725 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli.ts +3660 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1487 -0
- package/src/index.ts +20 -0
- package/src/init.ts +123 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +877 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow.ts +653 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
expandSearchRequest,
|
|
4
|
+
parseSearchRules,
|
|
5
|
+
prioritizeSearchHits
|
|
6
|
+
} from "./index-adx6khj5.js";
|
|
7
|
+
import {
|
|
8
|
+
openSemanticSearchSession,
|
|
9
|
+
recommendedEmbeddingModel,
|
|
10
|
+
scanVault
|
|
11
|
+
} from "./index-zxdy5pby.js";
|
|
12
|
+
import {
|
|
13
|
+
UntrustedContentBudgetError,
|
|
14
|
+
createUntrustedToolResult
|
|
15
|
+
} from "./index-4j3tt0c3.js";
|
|
16
|
+
import {
|
|
17
|
+
GitHistoryError,
|
|
18
|
+
gitHistoryForNotes,
|
|
19
|
+
indexGitHistory,
|
|
20
|
+
searchGitHistory,
|
|
21
|
+
validateGitHistoryForNotesOptions,
|
|
22
|
+
validateGitHistoryForNotesRequest,
|
|
23
|
+
validateSearchGitHistoryOptions
|
|
24
|
+
} from "./index-1gwbassd.js";
|
|
25
|
+
import {
|
|
26
|
+
buildGraphContext,
|
|
27
|
+
fuseRankedCandidates,
|
|
28
|
+
searchExactVault,
|
|
29
|
+
validateSearchQuery
|
|
30
|
+
} from "./index-cv6fh7z5.js";
|
|
31
|
+
import {
|
|
32
|
+
NavigationBudgetError,
|
|
33
|
+
navigateLinks
|
|
34
|
+
} from "./index-d13v9ckt.js";
|
|
35
|
+
import {
|
|
36
|
+
queryVault
|
|
37
|
+
} from "./index-48pz4jpc.js";
|
|
38
|
+
import {
|
|
39
|
+
lookupNote
|
|
40
|
+
} from "./index-cxfrakt7.js";
|
|
41
|
+
|
|
42
|
+
// src/sdk.ts
|
|
43
|
+
import { resolve } from "path";
|
|
44
|
+
var MAX_SEARCH_RESULTS = 100;
|
|
45
|
+
var MAX_SEARCH_CANDIDATES = 500;
|
|
46
|
+
var DEFAULT_SEARCH_RESULTS = 10;
|
|
47
|
+
var MAX_READ_BYTES = 64 * 1024;
|
|
48
|
+
var DEFAULT_CONTEXT_BYTES = 24 * 1024;
|
|
49
|
+
var MAX_CONTEXT_BYTES = 64 * 1024;
|
|
50
|
+
var MIN_UNTRUSTED_CONTEXT_BYTES = 512;
|
|
51
|
+
var DEFAULT_SEARCH_HISTORY_NOTES = 5;
|
|
52
|
+
var MAX_SEARCH_HISTORY_NOTES = 20;
|
|
53
|
+
var MAX_SEARCH_RELATED_SEEDS = 5;
|
|
54
|
+
var MAX_SEARCH_NOTE_REFERENCE_BYTES = 16 * 1024;
|
|
55
|
+
function checkedLimit(value, fallback, maximum, label) {
|
|
56
|
+
const limit = value ?? fallback;
|
|
57
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > maximum) {
|
|
58
|
+
throw new RangeError(`${label} must be an integer from 1 through ${maximum}.`);
|
|
59
|
+
}
|
|
60
|
+
return limit;
|
|
61
|
+
}
|
|
62
|
+
function noteOrThrow(notes, query) {
|
|
63
|
+
const result = lookupNote(notes, query);
|
|
64
|
+
if (result.kind === "found")
|
|
65
|
+
return result.note;
|
|
66
|
+
if (result.kind === "ambiguous") {
|
|
67
|
+
throw new Error(`Knowledge-base note ${JSON.stringify(query)} is ambiguous: ` + result.candidates.map(({ path }) => path).join(", "));
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Knowledge-base note ${JSON.stringify(query)} was not found.`);
|
|
70
|
+
}
|
|
71
|
+
function errorMessage(error) {
|
|
72
|
+
return error instanceof Error ? error.message : String(error);
|
|
73
|
+
}
|
|
74
|
+
function utf8Prefix(value, maximumBytes) {
|
|
75
|
+
let bytes = 0;
|
|
76
|
+
let end = 0;
|
|
77
|
+
for (const character of value) {
|
|
78
|
+
const width = Buffer.byteLength(character, "utf8");
|
|
79
|
+
if (bytes + width > maximumBytes) {
|
|
80
|
+
return { value: value.slice(0, end), truncated: true, bytes };
|
|
81
|
+
}
|
|
82
|
+
bytes += width;
|
|
83
|
+
end += character.length;
|
|
84
|
+
}
|
|
85
|
+
return { value, truncated: false, bytes };
|
|
86
|
+
}
|
|
87
|
+
function unavailableHistory(root, reason) {
|
|
88
|
+
return {
|
|
89
|
+
status: "unavailable",
|
|
90
|
+
repository: "",
|
|
91
|
+
root,
|
|
92
|
+
vaultPrefix: "",
|
|
93
|
+
reason
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function limitedHistoryMessage(commits) {
|
|
97
|
+
const count = commits.length;
|
|
98
|
+
const limit = commits[0]?.pathLimit ?? 0;
|
|
99
|
+
return `${count} Git commit${count === 1 ? "" : "s"} exceeded the ${limit.toLocaleString("en-US")} changed-path detail limit; co-change evidence is incomplete.`;
|
|
100
|
+
}
|
|
101
|
+
function qmdMode(mode) {
|
|
102
|
+
return mode === "exact" ? null : mode;
|
|
103
|
+
}
|
|
104
|
+
function checkedSearchMode(value) {
|
|
105
|
+
const mode = value ?? "hybrid";
|
|
106
|
+
if (mode !== "exact" && mode !== "hybrid" && mode !== "keyword" && mode !== "semantic") {
|
|
107
|
+
throw new Error('Knowledge-base search mode must be "exact", "hybrid", "keyword", or "semantic".');
|
|
108
|
+
}
|
|
109
|
+
return mode;
|
|
110
|
+
}
|
|
111
|
+
function checkedSearchOrdering(value) {
|
|
112
|
+
const ordering = value ?? "relevance";
|
|
113
|
+
if (ordering !== "relevance" && ordering !== "priority-then-relevance") {
|
|
114
|
+
throw new TypeError('Knowledge-base search ordering must be "relevance" or "priority-then-relevance".');
|
|
115
|
+
}
|
|
116
|
+
return ordering;
|
|
117
|
+
}
|
|
118
|
+
function checkedGraphOptions(value) {
|
|
119
|
+
if (value === false)
|
|
120
|
+
return null;
|
|
121
|
+
if (value !== undefined && (typeof value !== "object" || value === null || Array.isArray(value))) {
|
|
122
|
+
throw new TypeError("Search graph options must be false or an options object.");
|
|
123
|
+
}
|
|
124
|
+
const options = value ?? {};
|
|
125
|
+
const related = options.related ?? [];
|
|
126
|
+
if (!Array.isArray(related)) {
|
|
127
|
+
throw new TypeError("Search related-note seeds must be an array.");
|
|
128
|
+
}
|
|
129
|
+
if (related.length > MAX_SEARCH_RELATED_SEEDS) {
|
|
130
|
+
throw new RangeError(`Hybrid search accepts at most ${MAX_SEARCH_RELATED_SEEDS} explicit related-note seeds.`);
|
|
131
|
+
}
|
|
132
|
+
const checkedRelated = [];
|
|
133
|
+
for (const [index, seed] of related.entries()) {
|
|
134
|
+
if (typeof seed !== "string") {
|
|
135
|
+
throw new TypeError(`Search related-note seed ${index + 1} must be a string.`);
|
|
136
|
+
}
|
|
137
|
+
if (seed.trim() === "") {
|
|
138
|
+
throw new TypeError(`Search related-note seed ${index + 1} must not be empty.`);
|
|
139
|
+
}
|
|
140
|
+
if (Buffer.byteLength(seed, "utf8") > MAX_SEARCH_NOTE_REFERENCE_BYTES) {
|
|
141
|
+
throw new RangeError(`Search related-note seed ${index + 1} must be at most ` + `${MAX_SEARCH_NOTE_REFERENCE_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
|
|
142
|
+
}
|
|
143
|
+
checkedRelated.push(seed);
|
|
144
|
+
}
|
|
145
|
+
return Object.freeze({
|
|
146
|
+
related: Object.freeze(checkedRelated),
|
|
147
|
+
depth: checkedLimit(options.depth, 1, 2, "Graph context depth"),
|
|
148
|
+
neighborsPerSeed: checkedLimit(options.neighborsPerSeed, 3, 20, "Graph neighbors per seed"),
|
|
149
|
+
limit: checkedLimit(options.limit, 20, 100, "Graph context limit")
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function resolvedGraphSeeds(notes, seeds) {
|
|
153
|
+
return seeds.map((seed) => {
|
|
154
|
+
const lookup = lookupNote(notes, seed);
|
|
155
|
+
if (lookup.kind === "missing") {
|
|
156
|
+
throw new Error(`Graph context seed ${JSON.stringify(seed)} was not found.`);
|
|
157
|
+
}
|
|
158
|
+
if (lookup.kind === "ambiguous") {
|
|
159
|
+
throw new Error(`Graph context seed ${JSON.stringify(seed)} is ambiguous: ` + lookup.candidates.map(({ path }) => path).join(", "));
|
|
160
|
+
}
|
|
161
|
+
return lookup.note.id;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function checkedHistoryRequest(value) {
|
|
165
|
+
if (value === undefined || value === false)
|
|
166
|
+
return { enabled: false };
|
|
167
|
+
let options;
|
|
168
|
+
let required;
|
|
169
|
+
if (value === "auto") {
|
|
170
|
+
options = {};
|
|
171
|
+
required = false;
|
|
172
|
+
} else if (value === "required") {
|
|
173
|
+
options = {};
|
|
174
|
+
required = true;
|
|
175
|
+
} else if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
176
|
+
throw new Error('Search history must be false, "auto", "required", or an options object.');
|
|
177
|
+
} else {
|
|
178
|
+
options = value;
|
|
179
|
+
if (options.policy !== undefined && options.policy !== "auto" && options.policy !== "required") {
|
|
180
|
+
throw new Error('Search history policy must be "auto" or "required".');
|
|
181
|
+
}
|
|
182
|
+
required = options.policy === "required";
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
enabled: true,
|
|
186
|
+
required,
|
|
187
|
+
noteLimit: checkedLimit(options.noteLimit, DEFAULT_SEARCH_HISTORY_NOTES, MAX_SEARCH_HISTORY_NOTES, "Git history note limit"),
|
|
188
|
+
options: validateGitHistoryForNotesOptions(options)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function validateKnowledgeBaseSearchHistory(value) {
|
|
192
|
+
checkedHistoryRequest(value);
|
|
193
|
+
}
|
|
194
|
+
async function openKnowledgeBase(options, dependencies = {}) {
|
|
195
|
+
const searchRules = options.searchRules === undefined ? null : parseSearchRules(options.searchRules);
|
|
196
|
+
if (options.embeddingModelFile !== undefined && options.embeddingModelLease !== undefined) {
|
|
197
|
+
throw new TypeError("embeddingModelFile and embeddingModelLease are mutually exclusive.");
|
|
198
|
+
}
|
|
199
|
+
if (dependencies.semanticSession !== undefined && dependencies.openSemanticSearchSession !== undefined) {
|
|
200
|
+
throw new TypeError("semanticSession and openSemanticSearchSession dependencies are mutually exclusive.");
|
|
201
|
+
}
|
|
202
|
+
const snapshot = await (dependencies.scanVault ?? scanVault)(options.root, { ...options.scan ?? {}, mentionScope: false });
|
|
203
|
+
const notesById = new Map(snapshot.notes.map((note) => [note.id, note]));
|
|
204
|
+
const notesByPath = new Map(snapshot.notes.map((note) => [note.path, note]));
|
|
205
|
+
const injectedSemantic = dependencies.semanticSession;
|
|
206
|
+
if (injectedSemantic !== undefined && (resolve(injectedSemantic.root) !== resolve(snapshot.root) || options.database !== undefined && resolve(injectedSemantic.database) !== resolve(options.database) || injectedSemantic.model !== recommendedEmbeddingModel)) {
|
|
207
|
+
throw new TypeError("The preopened semantic session does not match the knowledge-base snapshot, database, and model.");
|
|
208
|
+
}
|
|
209
|
+
let closeRequested = false;
|
|
210
|
+
let closePromise;
|
|
211
|
+
let semanticPromise = injectedSemantic === undefined ? undefined : Promise.resolve(injectedSemantic);
|
|
212
|
+
let gitPromise;
|
|
213
|
+
const assertOpen = () => {
|
|
214
|
+
if (closeRequested)
|
|
215
|
+
throw new Error("Knowledge-base session is closed.");
|
|
216
|
+
};
|
|
217
|
+
const semantic = () => {
|
|
218
|
+
assertOpen();
|
|
219
|
+
semanticPromise ??= (dependencies.openSemanticSearchSession ?? openSemanticSearchSession)({
|
|
220
|
+
root: snapshot.root,
|
|
221
|
+
...options.database === undefined ? {} : { database: options.database },
|
|
222
|
+
...options.embeddingModelFile === undefined ? {} : { embeddingModelFile: options.embeddingModelFile },
|
|
223
|
+
...options.embeddingModelLease === undefined ? {} : { embeddingModelLease: options.embeddingModelLease },
|
|
224
|
+
...options.requireStoreLocalVectorBoundary === undefined ? {} : {
|
|
225
|
+
requireStoreLocalVectorBoundary: options.requireStoreLocalVectorBoundary
|
|
226
|
+
}
|
|
227
|
+
}, {
|
|
228
|
+
...dependencies.semantic ?? {},
|
|
229
|
+
scanVault: () => Promise.resolve(snapshot)
|
|
230
|
+
});
|
|
231
|
+
return semanticPromise;
|
|
232
|
+
};
|
|
233
|
+
const gitIndex = () => {
|
|
234
|
+
assertOpen();
|
|
235
|
+
if (options.repository === undefined) {
|
|
236
|
+
return Promise.resolve(unavailableHistory(snapshot.root, "No repository root was configured for this knowledge-base session."));
|
|
237
|
+
}
|
|
238
|
+
gitPromise ??= (dependencies.indexGitHistory ?? indexGitHistory)({
|
|
239
|
+
repository: options.repository,
|
|
240
|
+
root: snapshot.root,
|
|
241
|
+
notes: snapshot.notes
|
|
242
|
+
}, dependencies.git);
|
|
243
|
+
return gitPromise;
|
|
244
|
+
};
|
|
245
|
+
const grep = (grepOptions) => {
|
|
246
|
+
assertOpen();
|
|
247
|
+
return searchExactVault(snapshot.notes, snapshot.analysis, grepOptions);
|
|
248
|
+
};
|
|
249
|
+
const list = (queryOptions = {}) => {
|
|
250
|
+
assertOpen();
|
|
251
|
+
return queryVault(snapshot.notes, snapshot.analysis, queryOptions);
|
|
252
|
+
};
|
|
253
|
+
const read = (query, readOptions = {}) => {
|
|
254
|
+
assertOpen();
|
|
255
|
+
const maximumBytes = checkedLimit(readOptions.maxBytes, MAX_READ_BYTES, MAX_READ_BYTES, "Read byte limit");
|
|
256
|
+
const note = noteOrThrow(snapshot.notes, query);
|
|
257
|
+
const content = utf8Prefix(note.content, maximumBytes);
|
|
258
|
+
return {
|
|
259
|
+
id: note.id,
|
|
260
|
+
path: note.path,
|
|
261
|
+
title: note.title,
|
|
262
|
+
content: content.value,
|
|
263
|
+
truncated: content.truncated
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
const links = (query, linkOptions = {}) => {
|
|
267
|
+
assertOpen();
|
|
268
|
+
return navigateLinks(snapshot.notes, snapshot.analysis, noteOrThrow(snapshot.notes, query), linkOptions);
|
|
269
|
+
};
|
|
270
|
+
const search = async (searchOptions) => {
|
|
271
|
+
assertOpen();
|
|
272
|
+
const startedAt = performance.now();
|
|
273
|
+
const { query: requestedQuery } = validateSearchQuery(searchOptions.query);
|
|
274
|
+
const expansion = searchRules === null ? { request: searchOptions, alias: null } : expandSearchRequest(searchOptions, searchRules);
|
|
275
|
+
const effectiveOptions = expansion.request;
|
|
276
|
+
const { query: effectiveQuery } = validateSearchQuery(effectiveOptions.query);
|
|
277
|
+
const mode = checkedSearchMode(effectiveOptions.mode);
|
|
278
|
+
const ordering = checkedSearchOrdering(effectiveOptions.ordering);
|
|
279
|
+
const minScore = effectiveOptions.minScore;
|
|
280
|
+
if (minScore !== undefined && (!Number.isFinite(minScore) || minScore < 0 || minScore > 1)) {
|
|
281
|
+
throw new RangeError("Search minimum score must be a number from 0 through 1.");
|
|
282
|
+
}
|
|
283
|
+
if (mode === "exact" && minScore !== undefined) {
|
|
284
|
+
throw new Error("Search minimum score applies only to hybrid, keyword, or semantic mode.");
|
|
285
|
+
}
|
|
286
|
+
const limit = checkedLimit(effectiveOptions.limit, DEFAULT_SEARCH_RESULTS, MAX_SEARCH_RESULTS, "Search limit");
|
|
287
|
+
const filters = effectiveOptions.filters ?? [];
|
|
288
|
+
const tags = effectiveOptions.tags ?? [];
|
|
289
|
+
const repositoryScopes = effectiveOptions.repositoryScopes ?? [];
|
|
290
|
+
const filtered = filters.length > 0 || tags.length > 0 || repositoryScopes.length > 0;
|
|
291
|
+
const candidateLimit = checkedLimit(effectiveOptions.candidateLimit, filtered ? MAX_SEARCH_CANDIDATES : Math.max(40, limit * 4), MAX_SEARCH_CANDIDATES, "Search candidate limit");
|
|
292
|
+
if (candidateLimit < limit) {
|
|
293
|
+
throw new RangeError("Search candidate limit must be at least the result limit.");
|
|
294
|
+
}
|
|
295
|
+
const historyRequest = checkedHistoryRequest(effectiveOptions.history);
|
|
296
|
+
const graphOptions = checkedGraphOptions(effectiveOptions.graph);
|
|
297
|
+
const explicitSeeds = graphOptions === null ? [] : resolvedGraphSeeds(snapshot.notes, graphOptions.related);
|
|
298
|
+
const historyNoteLimit = historyRequest.enabled ? historyRequest.noteLimit : null;
|
|
299
|
+
const allowedIds = new Set(queryVault(snapshot.notes, snapshot.analysis, {
|
|
300
|
+
filters,
|
|
301
|
+
tags,
|
|
302
|
+
repositoryScopes
|
|
303
|
+
}).map(({ id }) => id));
|
|
304
|
+
const includeExact = mode === "hybrid" || mode === "exact";
|
|
305
|
+
const exact = includeExact ? searchExactVault(snapshot.notes, snapshot.analysis, {
|
|
306
|
+
query: effectiveQuery,
|
|
307
|
+
filters,
|
|
308
|
+
tags,
|
|
309
|
+
repositoryScopes,
|
|
310
|
+
limit: Math.min(MAX_SEARCH_CANDIDATES, candidateLimit)
|
|
311
|
+
}) : [];
|
|
312
|
+
const exactById = new Map(exact.map((hit, index) => [hit.id, { hit, rank: index + 1 }]));
|
|
313
|
+
const semanticById = new Map;
|
|
314
|
+
const diagnostics = includeExact ? [{ lane: "exact", status: "ready", results: exact.length }] : [];
|
|
315
|
+
let model = null;
|
|
316
|
+
let queryEmbedding = Object.freeze({
|
|
317
|
+
calls: 0,
|
|
318
|
+
inputTokens: 0,
|
|
319
|
+
durationMs: 0
|
|
320
|
+
});
|
|
321
|
+
const selectedQmdMode = qmdMode(mode);
|
|
322
|
+
if (selectedQmdMode !== null) {
|
|
323
|
+
if (allowedIds.size === 0) {
|
|
324
|
+
diagnostics.push({ lane: "qmd", status: "ready", results: 0 });
|
|
325
|
+
} else {
|
|
326
|
+
if (selectedQmdMode !== "keyword")
|
|
327
|
+
queryEmbedding = null;
|
|
328
|
+
try {
|
|
329
|
+
const session = await semantic();
|
|
330
|
+
model = session.model;
|
|
331
|
+
const semanticLimit = candidateLimit;
|
|
332
|
+
const result = await session.search({
|
|
333
|
+
query: effectiveQuery,
|
|
334
|
+
mode: selectedQmdMode,
|
|
335
|
+
limit: semanticLimit,
|
|
336
|
+
candidateLimit,
|
|
337
|
+
...minScore === undefined ? {} : { minScore }
|
|
338
|
+
});
|
|
339
|
+
queryEmbedding = result.queryEmbedding ?? (selectedQmdMode === "keyword" ? queryEmbedding : null);
|
|
340
|
+
let acceptedRank = 0;
|
|
341
|
+
let discardedCandidates = result.rawWindow?.discarded ?? 0;
|
|
342
|
+
for (const hit of result.results) {
|
|
343
|
+
const note = notesByPath.get(hit.path);
|
|
344
|
+
if (note === undefined || semanticById.has(note.id)) {
|
|
345
|
+
discardedCandidates += 1;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (!allowedIds.has(note.id)) {
|
|
349
|
+
discardedCandidates += 1;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
acceptedRank += 1;
|
|
353
|
+
semanticById.set(note.id, { hit, rank: acceptedRank });
|
|
354
|
+
}
|
|
355
|
+
const embeddingFailures = result.embedding?.failures?.length ?? 0;
|
|
356
|
+
const embeddingErrors = result.embedding?.errors ?? 0;
|
|
357
|
+
const embeddingDegraded = embeddingErrors > 0 || embeddingFailures > 0;
|
|
358
|
+
const requestedEligible = Math.min(limit, allowedIds.size);
|
|
359
|
+
const incompleteCandidateWindow = discardedCandidates > 0 && semanticById.size < requestedEligible;
|
|
360
|
+
const messages = [];
|
|
361
|
+
if (embeddingDegraded) {
|
|
362
|
+
messages.push(`QMD embedding reported ${embeddingErrors} error(s)` + ` and ${embeddingFailures} retained failure record(s).`);
|
|
363
|
+
}
|
|
364
|
+
if (incompleteCandidateWindow) {
|
|
365
|
+
messages.push(`QMD's bounded ${semanticLimit}-candidate retrieval discarded ` + `${discardedCandidates} row(s) during live reconciliation or metadata filtering ` + `and accepted ${semanticById.size} of ${requestedEligible} eligible requested ` + "result(s); this bounded reconciliation cannot certify a complete eligible result set.");
|
|
366
|
+
}
|
|
367
|
+
diagnostics.push({
|
|
368
|
+
lane: "qmd",
|
|
369
|
+
status: embeddingDegraded || incompleteCandidateWindow ? "degraded" : "ready",
|
|
370
|
+
results: semanticById.size,
|
|
371
|
+
...messages.length === 0 ? {} : { message: messages.join(" ") }
|
|
372
|
+
});
|
|
373
|
+
} catch (error) {
|
|
374
|
+
diagnostics.push({
|
|
375
|
+
lane: "qmd",
|
|
376
|
+
status: "unavailable",
|
|
377
|
+
results: 0,
|
|
378
|
+
message: errorMessage(error)
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const lanes = [
|
|
384
|
+
...includeExact ? [{ name: "exact", weight: 1, ids: exact.map(({ id }) => id) }] : [],
|
|
385
|
+
...selectedQmdMode === null ? [] : [{ name: "qmd", weight: 1, ids: [...semanticById.keys()] }]
|
|
386
|
+
];
|
|
387
|
+
const fused = fuseRankedCandidates(lanes);
|
|
388
|
+
const ordered = fused.toSorted((left, right) => Number(exactById.get(right.id)?.hit.identity ?? false) - Number(exactById.get(left.id)?.hit.identity ?? false) || left.rank - right.rank || left.id.localeCompare(right.id));
|
|
389
|
+
const shouldApplyPriority = ordering === "priority-then-relevance" && searchRules !== null && searchRules.priorityRules.length > 0;
|
|
390
|
+
const relevanceResults = (shouldApplyPriority ? ordered : ordered.slice(0, limit)).map((candidate, index) => {
|
|
391
|
+
const note = notesById.get(candidate.id);
|
|
392
|
+
if (note === undefined) {
|
|
393
|
+
throw new Error(`Fused retrieval returned unknown note ${JSON.stringify(candidate.id)}.`);
|
|
394
|
+
}
|
|
395
|
+
const exactMatch = exactById.get(candidate.id);
|
|
396
|
+
const semanticMatch = semanticById.get(candidate.id);
|
|
397
|
+
const evidence = [];
|
|
398
|
+
if (exactMatch !== undefined) {
|
|
399
|
+
evidence.push({
|
|
400
|
+
kind: "exact",
|
|
401
|
+
rank: exactMatch.rank,
|
|
402
|
+
identity: exactMatch.hit.identity,
|
|
403
|
+
matches: exactMatch.hit.matches
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (semanticMatch !== undefined) {
|
|
407
|
+
evidence.push({
|
|
408
|
+
kind: "qmd",
|
|
409
|
+
rank: semanticMatch.rank,
|
|
410
|
+
path: semanticMatch.hit.path,
|
|
411
|
+
...semanticMatch.hit.line === undefined ? {} : { line: semanticMatch.hit.line },
|
|
412
|
+
source: semanticMatch.hit.source,
|
|
413
|
+
score: semanticMatch.hit.score,
|
|
414
|
+
...semanticMatch.hit.signals === undefined ? {} : { signals: semanticMatch.hit.signals }
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const snippetSource = exactMatch?.hit.identity === true || semanticMatch === undefined ? exactMatch?.hit : semanticMatch.hit;
|
|
418
|
+
return {
|
|
419
|
+
id: note.id,
|
|
420
|
+
path: note.path,
|
|
421
|
+
title: note.title,
|
|
422
|
+
rank: index + 1,
|
|
423
|
+
score: candidate.score,
|
|
424
|
+
identity: exactMatch?.hit.identity ?? false,
|
|
425
|
+
...snippetSource?.line === undefined ? {} : { line: snippetSource.line },
|
|
426
|
+
snippet: snippetSource?.snippet ?? note.summary,
|
|
427
|
+
tags: note.tags,
|
|
428
|
+
metadata: note.metadata,
|
|
429
|
+
evidence,
|
|
430
|
+
contributions: candidate.contributions
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
let priorityTrace = null;
|
|
434
|
+
let results = relevanceResults.slice(0, limit);
|
|
435
|
+
if (shouldApplyPriority && searchRules !== null) {
|
|
436
|
+
const prioritized = prioritizeSearchHits(relevanceResults, searchRules, {
|
|
437
|
+
...options.vaultId === undefined ? {} : { vaultId: options.vaultId }
|
|
438
|
+
});
|
|
439
|
+
const selectedHits = prioritized.hits.slice(0, limit);
|
|
440
|
+
const selectedTrace = prioritized.trace.slice(0, limit);
|
|
441
|
+
if (selectedTrace.some(({ matchedRuleIds }) => matchedRuleIds.length > 0)) {
|
|
442
|
+
priorityTrace = Object.freeze(selectedTrace);
|
|
443
|
+
results = Object.freeze(selectedHits.map((hit, index) => hit.rank === index + 1 ? hit : { ...hit, rank: index + 1 }));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
let graph = null;
|
|
447
|
+
if (graphOptions !== null) {
|
|
448
|
+
try {
|
|
449
|
+
graph = buildGraphContext(snapshot.notes, snapshot.analysis, {
|
|
450
|
+
seeds: [...explicitSeeds, ...results.slice(0, 5).map(({ id }) => id)],
|
|
451
|
+
primaryIds: results.map(({ id }) => id),
|
|
452
|
+
depth: graphOptions.depth,
|
|
453
|
+
neighborsPerSeed: graphOptions.neighborsPerSeed,
|
|
454
|
+
limit: graphOptions.limit
|
|
455
|
+
});
|
|
456
|
+
diagnostics.push({
|
|
457
|
+
lane: "graph",
|
|
458
|
+
status: "ready",
|
|
459
|
+
results: graph.related.length
|
|
460
|
+
});
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (!(error instanceof NavigationBudgetError))
|
|
463
|
+
throw error;
|
|
464
|
+
diagnostics.push({
|
|
465
|
+
lane: "graph",
|
|
466
|
+
status: "unavailable",
|
|
467
|
+
results: 0,
|
|
468
|
+
message: error.message
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
let history = null;
|
|
473
|
+
if (historyRequest.enabled && historyNoteLimit !== null && results.length > 0) {
|
|
474
|
+
const outcome = await gitIndex().then((index2) => ({ status: "indexed", index: index2 }), (error) => ({ status: "failed", error }));
|
|
475
|
+
let index;
|
|
476
|
+
if (outcome.status === "failed") {
|
|
477
|
+
if (historyRequest.required)
|
|
478
|
+
throw outcome.error;
|
|
479
|
+
index = unavailableHistory(snapshot.root, errorMessage(outcome.error));
|
|
480
|
+
} else {
|
|
481
|
+
index = outcome.index;
|
|
482
|
+
}
|
|
483
|
+
if (historyRequest.required && index.status === "unavailable") {
|
|
484
|
+
throw new GitHistoryError("unavailable", `Required Git history is unavailable: ${index.reason}`);
|
|
485
|
+
}
|
|
486
|
+
history = gitHistoryForNotes(index, results.slice(0, historyNoteLimit).map(({ id }) => id), historyRequest.options);
|
|
487
|
+
const limitedCommits = history.status === "ready" ? history.limitedCommits ?? [] : [];
|
|
488
|
+
if (historyRequest.required && limitedCommits.length > 0) {
|
|
489
|
+
throw new GitHistoryError("budget", `Required Git history is incomplete: ${limitedHistoryMessage(limitedCommits)}`);
|
|
490
|
+
}
|
|
491
|
+
diagnostics.push(history.status === "ready" && limitedCommits.length === 0 ? {
|
|
492
|
+
lane: "git",
|
|
493
|
+
status: "ready",
|
|
494
|
+
results: history.notes.length
|
|
495
|
+
} : history.status === "ready" ? {
|
|
496
|
+
lane: "git",
|
|
497
|
+
status: "degraded",
|
|
498
|
+
results: history.notes.length,
|
|
499
|
+
message: limitedHistoryMessage(limitedCommits)
|
|
500
|
+
} : {
|
|
501
|
+
lane: "git",
|
|
502
|
+
status: "unavailable",
|
|
503
|
+
results: 0,
|
|
504
|
+
message: history.reason
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const priorityApplication = priorityTrace === null ? undefined : Object.freeze({
|
|
508
|
+
ordering: "priority-then-relevance",
|
|
509
|
+
trace: priorityTrace
|
|
510
|
+
});
|
|
511
|
+
const ruleApplication = expansion.alias === null ? priorityApplication === undefined ? undefined : Object.freeze({ priority: priorityApplication }) : Object.freeze({
|
|
512
|
+
alias: expansion.alias,
|
|
513
|
+
effectiveQuery,
|
|
514
|
+
...priorityApplication === undefined ? {} : { priority: priorityApplication }
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
query: expansion.alias === null ? effectiveQuery : requestedQuery,
|
|
518
|
+
mode,
|
|
519
|
+
results,
|
|
520
|
+
graph,
|
|
521
|
+
history,
|
|
522
|
+
partial: diagnostics.some(({ status }) => status !== "ready"),
|
|
523
|
+
...ruleApplication === undefined ? {} : { rules: ruleApplication },
|
|
524
|
+
diagnostics: {
|
|
525
|
+
notes: snapshot.notes.length,
|
|
526
|
+
model: selectedQmdMode === null ? null : model ?? recommendedEmbeddingModel,
|
|
527
|
+
elapsedMs: performance.now() - startedAt,
|
|
528
|
+
queryEmbedding,
|
|
529
|
+
lanes: diagnostics
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
return {
|
|
534
|
+
root: snapshot.root,
|
|
535
|
+
...options.repository === undefined ? {} : { repository: options.repository },
|
|
536
|
+
noteCount: snapshot.notes.length,
|
|
537
|
+
grep,
|
|
538
|
+
list,
|
|
539
|
+
read,
|
|
540
|
+
links,
|
|
541
|
+
backlinks: (query, linkOptions = {}) => links(query, {
|
|
542
|
+
...linkOptions,
|
|
543
|
+
direction: "in"
|
|
544
|
+
}),
|
|
545
|
+
search,
|
|
546
|
+
history: async (noteIds, historyOptions = {}) => {
|
|
547
|
+
assertOpen();
|
|
548
|
+
const request = validateGitHistoryForNotesRequest(noteIds, historyOptions);
|
|
549
|
+
return gitHistoryForNotes(await gitIndex(), request.noteIds, request.options);
|
|
550
|
+
},
|
|
551
|
+
searchHistory: async (historyOptions) => {
|
|
552
|
+
assertOpen();
|
|
553
|
+
const request = validateSearchGitHistoryOptions(historyOptions);
|
|
554
|
+
return searchGitHistory(await gitIndex(), {
|
|
555
|
+
query: request.query,
|
|
556
|
+
...request.allowedNoteIds === null ? {} : { allowedNoteIds: request.allowedNoteIds },
|
|
557
|
+
limit: request.limit,
|
|
558
|
+
commitsPerHit: request.commitsPerHit,
|
|
559
|
+
cochangedPathsPerCommit: request.cochangedPathsPerCommit
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
close: () => {
|
|
563
|
+
if (closePromise !== undefined)
|
|
564
|
+
return closePromise;
|
|
565
|
+
closeRequested = true;
|
|
566
|
+
closePromise = semanticPromise === undefined ? Promise.resolve() : semanticPromise.then((session) => session.close(), () => {
|
|
567
|
+
return;
|
|
568
|
+
});
|
|
569
|
+
return closePromise;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function utf8ContextWriter(maximumBytes) {
|
|
574
|
+
const chunks = [];
|
|
575
|
+
let bytes = 0;
|
|
576
|
+
let truncated = false;
|
|
577
|
+
return {
|
|
578
|
+
append: (value, final = false) => {
|
|
579
|
+
if (truncated)
|
|
580
|
+
return false;
|
|
581
|
+
const remaining = maximumBytes - bytes;
|
|
582
|
+
const prefix = utf8Prefix(value, remaining);
|
|
583
|
+
if (prefix.value !== "")
|
|
584
|
+
chunks.push(prefix.value);
|
|
585
|
+
bytes += prefix.bytes;
|
|
586
|
+
if (prefix.truncated || !final && bytes === maximumBytes) {
|
|
587
|
+
truncated = true;
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
},
|
|
592
|
+
result: () => ({ content: chunks.join(""), truncated })
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function packSearchContext(result, options = {}) {
|
|
596
|
+
const maximumBytes = checkedLimit(options.maxBytes, DEFAULT_CONTEXT_BYTES, MAX_CONTEXT_BYTES, "Context byte limit");
|
|
597
|
+
const writer = utf8ContextWriter(maximumBytes);
|
|
598
|
+
const append = (value) => writer.append(value);
|
|
599
|
+
if (!append(`# Knowledge-base context
|
|
600
|
+
|
|
601
|
+
Query: `))
|
|
602
|
+
return writer.result();
|
|
603
|
+
if (!append(result.query))
|
|
604
|
+
return writer.result();
|
|
605
|
+
if (!append(`
|
|
606
|
+
Mode: `))
|
|
607
|
+
return writer.result();
|
|
608
|
+
if (!append(result.mode))
|
|
609
|
+
return writer.result();
|
|
610
|
+
if (!append(`
|
|
611
|
+
`))
|
|
612
|
+
return writer.result();
|
|
613
|
+
for (const hit of result.results) {
|
|
614
|
+
if (!append(`
|
|
615
|
+
|
|
616
|
+
## `))
|
|
617
|
+
return writer.result();
|
|
618
|
+
if (!append(String(hit.rank)))
|
|
619
|
+
return writer.result();
|
|
620
|
+
if (!append(". "))
|
|
621
|
+
return writer.result();
|
|
622
|
+
if (!append(hit.title))
|
|
623
|
+
return writer.result();
|
|
624
|
+
if (!append(`
|
|
625
|
+
|
|
626
|
+
Path: `))
|
|
627
|
+
return writer.result();
|
|
628
|
+
if (!append(hit.path))
|
|
629
|
+
return writer.result();
|
|
630
|
+
if (hit.line !== undefined) {
|
|
631
|
+
if (!append(":"))
|
|
632
|
+
return writer.result();
|
|
633
|
+
if (!append(String(hit.line)))
|
|
634
|
+
return writer.result();
|
|
635
|
+
}
|
|
636
|
+
if (!append(`
|
|
637
|
+
|
|
638
|
+
Evidence: `))
|
|
639
|
+
return writer.result();
|
|
640
|
+
for (const [index, item] of hit.evidence.entries()) {
|
|
641
|
+
if (index > 0 && !append(", "))
|
|
642
|
+
return writer.result();
|
|
643
|
+
if (!append(item.kind))
|
|
644
|
+
return writer.result();
|
|
645
|
+
if (!append("#"))
|
|
646
|
+
return writer.result();
|
|
647
|
+
if (!append(String(item.rank)))
|
|
648
|
+
return writer.result();
|
|
649
|
+
}
|
|
650
|
+
if (!append(`
|
|
651
|
+
|
|
652
|
+
`))
|
|
653
|
+
return writer.result();
|
|
654
|
+
if (!append(hit.snippet))
|
|
655
|
+
return writer.result();
|
|
656
|
+
}
|
|
657
|
+
const graph = result.graph;
|
|
658
|
+
if (graph !== null && graph.related.length > 0) {
|
|
659
|
+
if (!append(`
|
|
660
|
+
|
|
661
|
+
## Related graph context`))
|
|
662
|
+
return writer.result();
|
|
663
|
+
for (const [hitIndex, hit] of graph.related.entries()) {
|
|
664
|
+
if (!append(hitIndex === 0 ? `
|
|
665
|
+
|
|
666
|
+
- ` : `
|
|
667
|
+
- `))
|
|
668
|
+
return writer.result();
|
|
669
|
+
if (!append(hit.path))
|
|
670
|
+
return writer.result();
|
|
671
|
+
if (!append(" ("))
|
|
672
|
+
return writer.result();
|
|
673
|
+
for (const [index, evidence] of hit.evidence.entries()) {
|
|
674
|
+
if (index > 0 && !append(", "))
|
|
675
|
+
return writer.result();
|
|
676
|
+
if (!append(evidence.kind))
|
|
677
|
+
return writer.result();
|
|
678
|
+
}
|
|
679
|
+
if (!append(")"))
|
|
680
|
+
return writer.result();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const history = result.history;
|
|
684
|
+
if (history?.status === "ready") {
|
|
685
|
+
if (!append(`
|
|
686
|
+
|
|
687
|
+
## Git provenance
|
|
688
|
+
|
|
689
|
+
`))
|
|
690
|
+
return writer.result();
|
|
691
|
+
const limitedCommits = history.limitedCommits ?? [];
|
|
692
|
+
if (limitedCommits.length > 0) {
|
|
693
|
+
if (!append("> Partial: "))
|
|
694
|
+
return writer.result();
|
|
695
|
+
if (!append(limitedHistoryMessage(limitedCommits)))
|
|
696
|
+
return writer.result();
|
|
697
|
+
if (!append(`
|
|
698
|
+
|
|
699
|
+
`))
|
|
700
|
+
return writer.result();
|
|
701
|
+
}
|
|
702
|
+
let commitIndex = 0;
|
|
703
|
+
for (const note of history.notes) {
|
|
704
|
+
for (const commit of note.commits) {
|
|
705
|
+
if (commitIndex > 0 && !append(`
|
|
706
|
+
`))
|
|
707
|
+
return writer.result();
|
|
708
|
+
if (!append("- "))
|
|
709
|
+
return writer.result();
|
|
710
|
+
if (!append(note.path))
|
|
711
|
+
return writer.result();
|
|
712
|
+
if (!append(": "))
|
|
713
|
+
return writer.result();
|
|
714
|
+
if (!append(commit.committedAt))
|
|
715
|
+
return writer.result();
|
|
716
|
+
if (!append(" "))
|
|
717
|
+
return writer.result();
|
|
718
|
+
if (!append(commit.hash.slice(0, 12)))
|
|
719
|
+
return writer.result();
|
|
720
|
+
if (!append(" "))
|
|
721
|
+
return writer.result();
|
|
722
|
+
if (!append(commit.subject))
|
|
723
|
+
return writer.result();
|
|
724
|
+
commitIndex += 1;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
writer.append(`
|
|
729
|
+
`, true);
|
|
730
|
+
return writer.result();
|
|
731
|
+
}
|
|
732
|
+
function inspectedDataRecord(value, label) {
|
|
733
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
734
|
+
throw new TypeError(`${label} must be a plain data object.`);
|
|
735
|
+
}
|
|
736
|
+
const prototype = Object.getPrototypeOf(value);
|
|
737
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
738
|
+
throw new TypeError(`${label} must be a plain data object.`);
|
|
739
|
+
}
|
|
740
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
741
|
+
const output = Object.create(null);
|
|
742
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
743
|
+
if (typeof key === "symbol")
|
|
744
|
+
throw new TypeError(`${label} contains a symbol property.`);
|
|
745
|
+
const descriptor = descriptors[key];
|
|
746
|
+
if (descriptor === undefined || !("value" in descriptor)) {
|
|
747
|
+
throw new TypeError(`${label} contains an accessor property.`);
|
|
748
|
+
}
|
|
749
|
+
if (!descriptor.enumerable) {
|
|
750
|
+
throw new TypeError(`${label} contains a non-enumerable property.`);
|
|
751
|
+
}
|
|
752
|
+
Object.defineProperty(output, key, {
|
|
753
|
+
configurable: false,
|
|
754
|
+
enumerable: true,
|
|
755
|
+
value: descriptor.value,
|
|
756
|
+
writable: false
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
return Object.freeze(output);
|
|
760
|
+
}
|
|
761
|
+
function inspectedDataArray(value, label, maximum) {
|
|
762
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
763
|
+
throw new TypeError(`${label} must be an ordinary array.`);
|
|
764
|
+
}
|
|
765
|
+
if (value.length > maximum) {
|
|
766
|
+
throw new RangeError(`${label} exceeds its ${maximum.toLocaleString("en-US")}-item limit.`);
|
|
767
|
+
}
|
|
768
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
769
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
770
|
+
if (typeof key === "symbol")
|
|
771
|
+
throw new TypeError(`${label} contains a symbol property.`);
|
|
772
|
+
if (key === "length")
|
|
773
|
+
continue;
|
|
774
|
+
const index = Number(key);
|
|
775
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= value.length || String(index) !== key) {
|
|
776
|
+
throw new TypeError(`${label} contains a non-index property.`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const output = [];
|
|
780
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
781
|
+
const descriptor = descriptors[String(index)];
|
|
782
|
+
if (descriptor === undefined || !("value" in descriptor) || !descriptor.enumerable) {
|
|
783
|
+
throw new TypeError(`${label} must be a dense array of enumerable data properties.`);
|
|
784
|
+
}
|
|
785
|
+
output.push(descriptor.value);
|
|
786
|
+
}
|
|
787
|
+
return Object.freeze(output);
|
|
788
|
+
}
|
|
789
|
+
function requiredData(record, key, label) {
|
|
790
|
+
if (!Object.hasOwn(record, key))
|
|
791
|
+
throw new TypeError(`${label}.${key} is required.`);
|
|
792
|
+
return record[key];
|
|
793
|
+
}
|
|
794
|
+
function dataString(value, label) {
|
|
795
|
+
if (typeof value !== "string")
|
|
796
|
+
throw new TypeError(`${label} must be a string.`);
|
|
797
|
+
return value;
|
|
798
|
+
}
|
|
799
|
+
function dataNumber(value, label) {
|
|
800
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
801
|
+
throw new TypeError(`${label} must be a finite number.`);
|
|
802
|
+
}
|
|
803
|
+
return value;
|
|
804
|
+
}
|
|
805
|
+
function dataBoolean(value, label) {
|
|
806
|
+
if (typeof value !== "boolean")
|
|
807
|
+
throw new TypeError(`${label} must be a boolean.`);
|
|
808
|
+
return value;
|
|
809
|
+
}
|
|
810
|
+
function optionalDataNumber(record, key, label) {
|
|
811
|
+
if (!Object.hasOwn(record, key))
|
|
812
|
+
return;
|
|
813
|
+
return dataNumber(record[key], `${label}.${key}`);
|
|
814
|
+
}
|
|
815
|
+
function searchEvidenceSummary(value, label) {
|
|
816
|
+
const evidence = inspectedDataRecord(value, label);
|
|
817
|
+
return Object.freeze({
|
|
818
|
+
kind: dataString(requiredData(evidence, "kind", label), `${label}.kind`),
|
|
819
|
+
rank: dataNumber(requiredData(evidence, "rank", label), `${label}.rank`)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
function searchHitContextRecord(value, index) {
|
|
823
|
+
const label = `Knowledge-base search result ${index + 1}`;
|
|
824
|
+
const hit = inspectedDataRecord(value, label);
|
|
825
|
+
const evidence = inspectedDataArray(requiredData(hit, "evidence", label), `${label}.evidence`, 32).map((entry, evidenceIndex) => searchEvidenceSummary(entry, `${label}.evidence[${evidenceIndex}]`));
|
|
826
|
+
const line = optionalDataNumber(hit, "line", label);
|
|
827
|
+
return Object.freeze({
|
|
828
|
+
kind: "search-result",
|
|
829
|
+
id: dataString(requiredData(hit, "id", label), `${label}.id`),
|
|
830
|
+
path: dataString(requiredData(hit, "path", label), `${label}.path`),
|
|
831
|
+
title: dataString(requiredData(hit, "title", label), `${label}.title`),
|
|
832
|
+
rank: dataNumber(requiredData(hit, "rank", label), `${label}.rank`),
|
|
833
|
+
score: dataNumber(requiredData(hit, "score", label), `${label}.score`),
|
|
834
|
+
identity: dataBoolean(requiredData(hit, "identity", label), `${label}.identity`),
|
|
835
|
+
...line === undefined ? {} : { line },
|
|
836
|
+
evidence: Object.freeze(evidence),
|
|
837
|
+
snippet: dataString(requiredData(hit, "snippet", label), `${label}.snippet`)
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
function graphContextRecords(value) {
|
|
841
|
+
if (value === null)
|
|
842
|
+
return [];
|
|
843
|
+
const graph = inspectedDataRecord(value, "Knowledge-base graph context");
|
|
844
|
+
const related = inspectedDataArray(requiredData(graph, "related", "Knowledge-base graph context"), "Knowledge-base graph context.related", 100);
|
|
845
|
+
return Object.freeze(related.map((entry, index) => {
|
|
846
|
+
const label = `Knowledge-base graph result ${index + 1}`;
|
|
847
|
+
const hit = inspectedDataRecord(entry, label);
|
|
848
|
+
const evidence = inspectedDataArray(requiredData(hit, "evidence", label), `${label}.evidence`, 100).map((item, evidenceIndex) => {
|
|
849
|
+
const evidenceLabel = `${label}.evidence[${evidenceIndex}]`;
|
|
850
|
+
const inspected = inspectedDataRecord(item, evidenceLabel);
|
|
851
|
+
return Object.freeze({
|
|
852
|
+
kind: dataString(requiredData(inspected, "kind", evidenceLabel), `${evidenceLabel}.kind`)
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
return Object.freeze({
|
|
856
|
+
kind: "graph-related",
|
|
857
|
+
id: dataString(requiredData(hit, "id", label), `${label}.id`),
|
|
858
|
+
path: dataString(requiredData(hit, "path", label), `${label}.path`),
|
|
859
|
+
title: dataString(requiredData(hit, "title", label), `${label}.title`),
|
|
860
|
+
distance: dataNumber(requiredData(hit, "distance", label), `${label}.distance`),
|
|
861
|
+
evidence: Object.freeze(evidence)
|
|
862
|
+
});
|
|
863
|
+
}));
|
|
864
|
+
}
|
|
865
|
+
function gitContextRecords(value) {
|
|
866
|
+
if (value === null)
|
|
867
|
+
return [];
|
|
868
|
+
const history = inspectedDataRecord(value, "Knowledge-base Git provenance");
|
|
869
|
+
const status = dataString(requiredData(history, "status", "Knowledge-base Git provenance"), "Knowledge-base Git provenance.status");
|
|
870
|
+
if (status === "unavailable") {
|
|
871
|
+
return Object.freeze([Object.freeze({
|
|
872
|
+
kind: "git-status",
|
|
873
|
+
status,
|
|
874
|
+
reason: dataString(requiredData(history, "reason", "Knowledge-base Git provenance"), "Knowledge-base Git provenance.reason")
|
|
875
|
+
})]);
|
|
876
|
+
}
|
|
877
|
+
if (status !== "ready")
|
|
878
|
+
throw new TypeError("Knowledge-base Git provenance.status is invalid.");
|
|
879
|
+
const output = [Object.freeze({
|
|
880
|
+
kind: "git-status",
|
|
881
|
+
status,
|
|
882
|
+
head: dataString(requiredData(history, "head", "Knowledge-base Git provenance"), "Knowledge-base Git provenance.head")
|
|
883
|
+
})];
|
|
884
|
+
const notes = inspectedDataArray(requiredData(history, "notes", "Knowledge-base Git provenance"), "Knowledge-base Git provenance.notes", MAX_SEARCH_HISTORY_NOTES);
|
|
885
|
+
for (const [noteIndex, entry] of notes.entries()) {
|
|
886
|
+
const noteLabel = `Knowledge-base Git provenance note ${noteIndex + 1}`;
|
|
887
|
+
const note = inspectedDataRecord(entry, noteLabel);
|
|
888
|
+
const noteId = dataString(requiredData(note, "id", noteLabel), `${noteLabel}.id`);
|
|
889
|
+
const path = dataString(requiredData(note, "path", noteLabel), `${noteLabel}.path`);
|
|
890
|
+
const commits = inspectedDataArray(requiredData(note, "commits", noteLabel), `${noteLabel}.commits`, 100);
|
|
891
|
+
for (const [commitIndex, commitValue] of commits.entries()) {
|
|
892
|
+
const commitLabel = `${noteLabel}.commits[${commitIndex}]`;
|
|
893
|
+
const commit = inspectedDataRecord(commitValue, commitLabel);
|
|
894
|
+
output.push(Object.freeze({
|
|
895
|
+
kind: "git-provenance",
|
|
896
|
+
noteId,
|
|
897
|
+
path,
|
|
898
|
+
hash: dataString(requiredData(commit, "hash", commitLabel), `${commitLabel}.hash`),
|
|
899
|
+
committedAt: dataString(requiredData(commit, "committedAt", commitLabel), `${commitLabel}.committedAt`),
|
|
900
|
+
subject: dataString(requiredData(commit, "subject", commitLabel), `${commitLabel}.subject`)
|
|
901
|
+
}));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (Object.hasOwn(history, "limitedCommits")) {
|
|
905
|
+
const limited = inspectedDataArray(history.limitedCommits, "Knowledge-base Git provenance.limitedCommits", 100);
|
|
906
|
+
if (limited.length > 0) {
|
|
907
|
+
const first = inspectedDataRecord(limited[0], "Knowledge-base Git limited commit");
|
|
908
|
+
output.push(Object.freeze({
|
|
909
|
+
kind: "git-coverage",
|
|
910
|
+
limitedCommits: limited.length,
|
|
911
|
+
pathLimit: dataNumber(requiredData(first, "pathLimit", "Knowledge-base Git limited commit"), "Knowledge-base Git limited commit.pathLimit")
|
|
912
|
+
}));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return Object.freeze(output);
|
|
916
|
+
}
|
|
917
|
+
function untrustedSearchRecords(result) {
|
|
918
|
+
const root = inspectedDataRecord(result, "Knowledge-base search result");
|
|
919
|
+
const records = [Object.freeze({
|
|
920
|
+
kind: "knowledge-base-search",
|
|
921
|
+
query: dataString(requiredData(root, "query", "Knowledge-base search result"), "Knowledge-base search result.query"),
|
|
922
|
+
mode: dataString(requiredData(root, "mode", "Knowledge-base search result"), "Knowledge-base search result.mode"),
|
|
923
|
+
partial: dataBoolean(requiredData(root, "partial", "Knowledge-base search result"), "Knowledge-base search result.partial")
|
|
924
|
+
})];
|
|
925
|
+
const results = inspectedDataArray(requiredData(root, "results", "Knowledge-base search result"), "Knowledge-base search result.results", MAX_SEARCH_RESULTS);
|
|
926
|
+
records.push(...results.map(searchHitContextRecord));
|
|
927
|
+
records.push(...graphContextRecords(requiredData(root, "graph", "Knowledge-base search result")));
|
|
928
|
+
records.push(...gitContextRecords(requiredData(root, "history", "Knowledge-base search result")));
|
|
929
|
+
return Object.freeze(records);
|
|
930
|
+
}
|
|
931
|
+
function packUntrustedSearchContext(result, options = {}) {
|
|
932
|
+
const maximumBytes = checkedLimit(options.maxBytes, DEFAULT_CONTEXT_BYTES, MAX_CONTEXT_BYTES, "Untrusted context byte limit");
|
|
933
|
+
if (maximumBytes < MIN_UNTRUSTED_CONTEXT_BYTES) {
|
|
934
|
+
throw new RangeError(`Untrusted context byte limit must be at least ${MIN_UNTRUSTED_CONTEXT_BYTES}.`);
|
|
935
|
+
}
|
|
936
|
+
const accepted = [];
|
|
937
|
+
let truncated = false;
|
|
938
|
+
for (const record of untrustedSearchRecords(result)) {
|
|
939
|
+
try {
|
|
940
|
+
createUntrustedToolResult([...accepted, record], {
|
|
941
|
+
maxBytes: maximumBytes,
|
|
942
|
+
truncated: false
|
|
943
|
+
});
|
|
944
|
+
accepted.push(record);
|
|
945
|
+
} catch (error) {
|
|
946
|
+
if (!(error instanceof UntrustedContentBudgetError))
|
|
947
|
+
throw error;
|
|
948
|
+
truncated = true;
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (accepted.length === 0) {
|
|
953
|
+
throw new RangeError(`Untrusted context byte limit ${maximumBytes} cannot hold the required search header.`);
|
|
954
|
+
}
|
|
955
|
+
const toolResult = createUntrustedToolResult(accepted, {
|
|
956
|
+
maxBytes: maximumBytes,
|
|
957
|
+
truncated
|
|
958
|
+
});
|
|
959
|
+
return Object.freeze({
|
|
960
|
+
content: toolResult.content[0].text,
|
|
961
|
+
truncated,
|
|
962
|
+
structuredContent: toolResult.structuredContent
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
export { MAX_SEARCH_RESULTS, MAX_SEARCH_CANDIDATES, DEFAULT_SEARCH_RESULTS, MIN_UNTRUSTED_CONTEXT_BYTES, MAX_SEARCH_RELATED_SEEDS, MAX_SEARCH_NOTE_REFERENCE_BYTES, validateKnowledgeBaseSearchHistory, openKnowledgeBase, packSearchContext, packUntrustedSearchContext };
|