@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.
Files changed (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,488 @@
1
+ // @bun
2
+ import {
3
+ metadataAtPath,
4
+ validateQueryOptions
5
+ } from "./index-48pz4jpc.js";
6
+ import {
7
+ analyzeAuthoredRepositoryScopes,
8
+ validateRepositoryScopeSelection
9
+ } from "./index-06c9ctr6.js";
10
+
11
+ // src/search-rules.ts
12
+ var SEARCH_RULES_SCHEMA_VERSION = 1;
13
+ var MAX_SEARCH_RULE_ALIASES = 64;
14
+ var MAX_SEARCH_PRIORITY_RULES = 128;
15
+ var MAX_SEARCH_RULE_FILTERS = 64;
16
+ var MAX_SEARCH_RULE_TAGS = 64;
17
+ var MAX_SEARCH_RULE_SCOPES = 64;
18
+ var MAX_SEARCH_RULE_TEXT_BYTES = 16 * 1024;
19
+ var MAX_SEARCH_RULE_CONFIG_BYTES = 128 * 1024;
20
+ var MAX_SEARCH_RULE_TIER = 32;
21
+ var MAX_SEARCH_RULE_NODES = 2048;
22
+ var MAX_METADATA_PATH_SEGMENTS = 32;
23
+ var aliasNamePattern = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
24
+ var ruleIdPattern = aliasNamePattern;
25
+ var vaultIdPattern = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?(?:\/[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?)?$/u;
26
+ var EMPTY_SEARCH_RULES = Object.freeze({
27
+ schemaVersion: SEARCH_RULES_SCHEMA_VERSION,
28
+ aliases: Object.freeze({}),
29
+ priorityRules: Object.freeze([])
30
+ });
31
+ function record(value, label) {
32
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
33
+ throw new TypeError(`${label} must be an object.`);
34
+ }
35
+ const prototype = Object.getPrototypeOf(value);
36
+ if (prototype !== Object.prototype && prototype !== null) {
37
+ throw new TypeError(`${label} must be a plain object.`);
38
+ }
39
+ return value;
40
+ }
41
+ function strictKeys(value, allowed, label) {
42
+ const allowedKeys = new Set(allowed);
43
+ for (const key of Reflect.ownKeys(value)) {
44
+ if (typeof key !== "string" || !allowedKeys.has(key)) {
45
+ throw new TypeError(`${label} contains unsupported key ${String(key)}.`);
46
+ }
47
+ }
48
+ }
49
+ function countNode(budget, count = 1) {
50
+ budget.nodes += count;
51
+ if (budget.nodes > MAX_SEARCH_RULE_NODES) {
52
+ throw new RangeError(`Search rules may contain at most ${MAX_SEARCH_RULE_NODES} structured entries.`);
53
+ }
54
+ }
55
+ function countText(budget, value, label) {
56
+ const bytes = Buffer.byteLength(value, "utf8");
57
+ if (bytes > MAX_SEARCH_RULE_TEXT_BYTES) {
58
+ throw new RangeError(`${label} must be at most ${MAX_SEARCH_RULE_TEXT_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
59
+ }
60
+ budget.bytes += bytes;
61
+ if (budget.bytes > MAX_SEARCH_RULE_CONFIG_BYTES) {
62
+ throw new RangeError(`Search rules may contain at most ${MAX_SEARCH_RULE_CONFIG_BYTES.toLocaleString("en-US")} UTF-8 bytes of text.`);
63
+ }
64
+ }
65
+ function boundedString(value, label, budget, options = {}) {
66
+ if (typeof value !== "string")
67
+ throw new TypeError(`${label} must be a string.`);
68
+ const checked = options.trim === true ? value.trim() : value;
69
+ if (checked === "")
70
+ throw new TypeError(`${label} must not be empty.`);
71
+ if (options.trim === true && checked !== value) {
72
+ throw new TypeError(`${label} must not contain surrounding whitespace.`);
73
+ }
74
+ if (options.pattern !== undefined && !options.pattern.test(checked)) {
75
+ throw new TypeError(`${label} is not canonical.`);
76
+ }
77
+ countText(budget, checked, label);
78
+ return checked;
79
+ }
80
+ function metadataScalar(value, label, budget) {
81
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
82
+ throw new TypeError(`${label} must be a metadata scalar.`);
83
+ }
84
+ if (typeof value === "number" && !Number.isFinite(value)) {
85
+ throw new TypeError(`${label} must be a finite metadata number.`);
86
+ }
87
+ if (typeof value === "string")
88
+ countText(budget, value, label);
89
+ countNode(budget);
90
+ return value;
91
+ }
92
+ function metadataPath(value, label, budget) {
93
+ if (typeof value === "string") {
94
+ countText(budget, value, label);
95
+ return value;
96
+ }
97
+ if (!Array.isArray(value)) {
98
+ throw new TypeError(`${label} must be a string or an array of path segments.`);
99
+ }
100
+ if (value.length === 0 || value.length > MAX_METADATA_PATH_SEGMENTS) {
101
+ throw new RangeError(`${label} must contain from 1 through ${MAX_METADATA_PATH_SEGMENTS} segments.`);
102
+ }
103
+ countNode(budget, value.length);
104
+ return Object.freeze(value.map((segment, index) => {
105
+ if (typeof segment !== "string") {
106
+ throw new TypeError(`${label} segment ${index + 1} must be a string.`);
107
+ }
108
+ countText(budget, segment, `${label} segment ${index + 1}`);
109
+ return segment;
110
+ }));
111
+ }
112
+ function parseFilter(value, label, budget) {
113
+ const input = record(value, label);
114
+ countNode(budget);
115
+ if (input.kind === "exists") {
116
+ strictKeys(input, ["kind", "path"], label);
117
+ return Object.freeze({
118
+ kind: "exists",
119
+ path: metadataPath(input.path, `${label} path`, budget)
120
+ });
121
+ }
122
+ if (input.kind === "equals") {
123
+ strictKeys(input, ["kind", "path", "value"], label);
124
+ return Object.freeze({
125
+ kind: "equals",
126
+ path: metadataPath(input.path, `${label} path`, budget),
127
+ value: metadataScalar(input.value, `${label} value`, budget)
128
+ });
129
+ }
130
+ if (input.kind === "one-of") {
131
+ strictKeys(input, ["kind", "path", "values"], label);
132
+ if (!Array.isArray(input.values)) {
133
+ throw new TypeError(`${label} values must be an array.`);
134
+ }
135
+ if (input.values.length === 0 || input.values.length > MAX_SEARCH_RULE_FILTERS) {
136
+ throw new RangeError(`${label} values must contain from 1 through ${MAX_SEARCH_RULE_FILTERS} entries.`);
137
+ }
138
+ countNode(budget, input.values.length);
139
+ return Object.freeze({
140
+ kind: "one-of",
141
+ path: metadataPath(input.path, `${label} path`, budget),
142
+ values: Object.freeze(input.values.map((candidate, index) => metadataScalar(candidate, `${label} value ${index + 1}`, budget)))
143
+ });
144
+ }
145
+ throw new TypeError(`${label} must be an equals, exists, or one-of filter.`);
146
+ }
147
+ function parseFilters(value, label, budget) {
148
+ if (value === undefined)
149
+ return Object.freeze([]);
150
+ if (!Array.isArray(value))
151
+ throw new TypeError(`${label} must be an array.`);
152
+ if (value.length > MAX_SEARCH_RULE_FILTERS) {
153
+ throw new RangeError(`${label} may contain at most ${MAX_SEARCH_RULE_FILTERS} entries.`);
154
+ }
155
+ const filters = Object.freeze(value.map((filter, index) => parseFilter(filter, `${label} ${index + 1}`, budget)));
156
+ validateQueryOptions({ filters });
157
+ return filters;
158
+ }
159
+ function normalizedTag(value) {
160
+ return value.trim().replace(/^#+/u, "").normalize("NFC").toLocaleLowerCase("en-US");
161
+ }
162
+ function parseTags(value, label, budget, requireNonempty = false) {
163
+ if (value === undefined)
164
+ return Object.freeze([]);
165
+ if (!Array.isArray(value))
166
+ throw new TypeError(`${label} must be an array.`);
167
+ if (value.length > MAX_SEARCH_RULE_TAGS || requireNonempty && value.length === 0) {
168
+ const minimum = requireNonempty ? "from 1 through" : "at most";
169
+ throw new RangeError(`${label} may contain ${minimum} ${MAX_SEARCH_RULE_TAGS} entries.`);
170
+ }
171
+ const seen = new Set;
172
+ const tags = [];
173
+ for (const [index, candidate] of value.entries()) {
174
+ if (typeof candidate !== "string") {
175
+ throw new TypeError(`${label} ${index + 1} must be a string.`);
176
+ }
177
+ countText(budget, candidate, `${label} ${index + 1}`);
178
+ countNode(budget);
179
+ const tag = normalizedTag(candidate);
180
+ if (tag === "")
181
+ throw new TypeError(`${label} ${index + 1} must not be empty.`);
182
+ if (!seen.has(tag)) {
183
+ seen.add(tag);
184
+ tags.push(tag);
185
+ }
186
+ }
187
+ return Object.freeze(tags);
188
+ }
189
+ function parseScopes(value, label, budget) {
190
+ if (value === undefined)
191
+ return Object.freeze([]);
192
+ if (!Array.isArray(value))
193
+ throw new TypeError(`${label} must be an array.`);
194
+ if (value.length > MAX_SEARCH_RULE_SCOPES) {
195
+ throw new RangeError(`${label} may contain at most ${MAX_SEARCH_RULE_SCOPES} entries.`);
196
+ }
197
+ for (const [index, candidate] of value.entries()) {
198
+ if (typeof candidate !== "string") {
199
+ throw new TypeError(`${label} ${index + 1} must be a string.`);
200
+ }
201
+ countText(budget, candidate, `${label} ${index + 1}`);
202
+ countNode(budget);
203
+ }
204
+ return Object.freeze([...validateRepositoryScopeSelection(value)]);
205
+ }
206
+ function parseMode(value, label) {
207
+ if (value === undefined)
208
+ return;
209
+ if (value !== "exact" && value !== "hybrid" && value !== "keyword" && value !== "semantic") {
210
+ throw new TypeError(`${label} must be exact, hybrid, keyword, or semantic.`);
211
+ }
212
+ return value;
213
+ }
214
+ function parseAlias(value, name, budget) {
215
+ const input = record(value, `Search alias @${name}`);
216
+ strictKeys(input, ["filters", "mode", "query", "repositoryScopes", "tags"], `Search alias @${name}`);
217
+ countNode(budget);
218
+ const query = input.query === undefined ? undefined : boundedString(input.query, `Search alias @${name} query`, budget, { trim: true });
219
+ const mode = parseMode(input.mode, `Search alias @${name} mode`);
220
+ const filters = parseFilters(input.filters, `Search alias @${name} filters`, budget);
221
+ const tags = parseTags(input.tags, `Search alias @${name} tags`, budget);
222
+ const repositoryScopes = parseScopes(input.repositoryScopes, `Search alias @${name} repository scopes`, budget);
223
+ return Object.freeze({
224
+ ...query === undefined ? {} : { query },
225
+ ...mode === undefined ? {} : { mode },
226
+ filters,
227
+ tags,
228
+ repositoryScopes
229
+ });
230
+ }
231
+ function pathPrefix(value, label, budget) {
232
+ const prefix = boundedString(value, label, budget, { trim: true });
233
+ if (prefix.startsWith("/") || prefix.includes("\\") || prefix.includes("\x00") || prefix.includes("//") || prefix.split("/").some((segment) => segment === "." || segment === "..")) {
234
+ throw new TypeError(`${label} must be a canonical repository-relative prefix.`);
235
+ }
236
+ return prefix;
237
+ }
238
+ function vaultId(value, label, budget) {
239
+ return boundedString(value, label, budget, { trim: true, pattern: vaultIdPattern });
240
+ }
241
+ function parsePriorityRule(value, index, budget) {
242
+ const label = `Search priority rule ${index + 1}`;
243
+ const input = record(value, label);
244
+ strictKeys(input, [
245
+ "id",
246
+ "metadata",
247
+ "pathPrefix",
248
+ "repositoryScope",
249
+ "tagsAll",
250
+ "tier",
251
+ "vaultId"
252
+ ], label);
253
+ countNode(budget);
254
+ const id = boundedString(input.id, `${label} ID`, budget, {
255
+ trim: true,
256
+ pattern: ruleIdPattern
257
+ });
258
+ if (!Number.isSafeInteger(input.tier) || input.tier < 1 || input.tier > MAX_SEARCH_RULE_TIER) {
259
+ throw new RangeError(`${label} tier must be an integer from 1 through ${MAX_SEARCH_RULE_TIER}.`);
260
+ }
261
+ const checkedPathPrefix = input.pathPrefix === undefined ? undefined : pathPrefix(input.pathPrefix, `${label} path prefix`, budget);
262
+ const tagsAll = input.tagsAll === undefined ? undefined : parseTags(input.tagsAll, `${label} required tags`, budget, true);
263
+ let repositoryScope;
264
+ if (input.repositoryScope !== undefined) {
265
+ if (typeof input.repositoryScope !== "string") {
266
+ throw new TypeError(`${label} repository scope must be a string.`);
267
+ }
268
+ countText(budget, input.repositoryScope, `${label} repository scope`);
269
+ repositoryScope = validateRepositoryScopeSelection([input.repositoryScope])[0];
270
+ }
271
+ const metadata = input.metadata === undefined ? undefined : parseFilters(input.metadata, `${label} metadata filters`, budget);
272
+ if (metadata !== undefined && metadata.length === 0) {
273
+ throw new RangeError(`${label} metadata filters must not be empty when provided.`);
274
+ }
275
+ const checkedVaultId = input.vaultId === undefined ? undefined : vaultId(input.vaultId, `${label} vault ID`, budget);
276
+ if (checkedPathPrefix === undefined && tagsAll === undefined && repositoryScope === undefined && metadata === undefined && checkedVaultId === undefined) {
277
+ throw new TypeError(`${label} must contain at least one match condition.`);
278
+ }
279
+ return Object.freeze({
280
+ id,
281
+ tier: input.tier,
282
+ ...checkedPathPrefix === undefined ? {} : { pathPrefix: checkedPathPrefix },
283
+ ...tagsAll === undefined ? {} : { tagsAll },
284
+ ...repositoryScope === undefined ? {} : { repositoryScope },
285
+ ...metadata === undefined ? {} : { metadata },
286
+ ...checkedVaultId === undefined ? {} : { vaultId: checkedVaultId }
287
+ });
288
+ }
289
+ function parseSearchRules(value) {
290
+ const input = record(value, "Search rules");
291
+ strictKeys(input, ["aliases", "priorityRules", "schemaVersion"], "Search rules");
292
+ if (input.schemaVersion !== SEARCH_RULES_SCHEMA_VERSION) {
293
+ throw new TypeError(`Search rules schemaVersion must be ${SEARCH_RULES_SCHEMA_VERSION}.`);
294
+ }
295
+ const budget = { bytes: 0, nodes: 1 };
296
+ const aliasInput = input.aliases === undefined ? Object.freeze({}) : record(input.aliases, "Search rule aliases");
297
+ if (Reflect.ownKeys(aliasInput).some((key) => typeof key !== "string")) {
298
+ throw new TypeError("Search rule aliases may contain only string alias names.");
299
+ }
300
+ const aliasEntries = Object.entries(aliasInput);
301
+ if (aliasEntries.length > MAX_SEARCH_RULE_ALIASES) {
302
+ throw new RangeError(`Search rules may define at most ${MAX_SEARCH_RULE_ALIASES} aliases.`);
303
+ }
304
+ const aliases = [];
305
+ for (const [rawName, definition] of aliasEntries) {
306
+ const name = boundedString(rawName, "Search alias name", budget, {
307
+ trim: true,
308
+ pattern: aliasNamePattern
309
+ });
310
+ aliases.push([name, parseAlias(definition, name, budget)]);
311
+ }
312
+ const rawPriorityRules = input.priorityRules ?? [];
313
+ if (!Array.isArray(rawPriorityRules)) {
314
+ throw new TypeError("Search priorityRules must be an array.");
315
+ }
316
+ if (rawPriorityRules.length > MAX_SEARCH_PRIORITY_RULES) {
317
+ throw new RangeError(`Search rules may define at most ${MAX_SEARCH_PRIORITY_RULES} priority rules.`);
318
+ }
319
+ const priorityRules = Object.freeze(rawPriorityRules.map((rule, index) => parsePriorityRule(rule, index, budget)));
320
+ const ids = new Set;
321
+ for (const rule of priorityRules) {
322
+ if (ids.has(rule.id))
323
+ throw new TypeError(`Search priority rule ID ${rule.id} is duplicated.`);
324
+ ids.add(rule.id);
325
+ }
326
+ return Object.freeze({
327
+ schemaVersion: SEARCH_RULES_SCHEMA_VERSION,
328
+ aliases: Object.freeze(Object.fromEntries(aliases)),
329
+ priorityRules
330
+ });
331
+ }
332
+ function leadingAlias(query) {
333
+ const leading = query.trimStart();
334
+ if (!leading.startsWith("@"))
335
+ return null;
336
+ const whitespace = leading.search(/\s/u);
337
+ const token = whitespace < 0 ? leading : leading.slice(0, whitespace);
338
+ const name = token.slice(1);
339
+ if (!aliasNamePattern.test(name)) {
340
+ throw new TypeError("A leading search alias must use canonical @alias syntax.");
341
+ }
342
+ const rest = leading.slice(token.length).trim();
343
+ if (rest.startsWith("@")) {
344
+ throw new TypeError("A search query may contain only one leading @alias.");
345
+ }
346
+ return { name, rest };
347
+ }
348
+ function callerArray(value, label) {
349
+ if (value === undefined)
350
+ return [];
351
+ if (!Array.isArray(value))
352
+ throw new TypeError(`${label} must be an array.`);
353
+ return value;
354
+ }
355
+ function expandSearchRequest(request, rules) {
356
+ if (typeof request.query !== "string")
357
+ throw new TypeError("Search query must be a string.");
358
+ if (Object.keys(rules.aliases).length === 0) {
359
+ return Object.freeze({
360
+ request,
361
+ alias: null
362
+ });
363
+ }
364
+ const parsed = leadingAlias(request.query);
365
+ if (parsed === null) {
366
+ return Object.freeze({
367
+ request,
368
+ alias: null
369
+ });
370
+ }
371
+ if (!Object.hasOwn(rules.aliases, parsed.name)) {
372
+ throw new TypeError(`Unknown search alias @${parsed.name}.`);
373
+ }
374
+ const alias = rules.aliases[parsed.name];
375
+ if (alias === undefined)
376
+ throw new TypeError(`Unknown search alias @${parsed.name}.`);
377
+ const query = [alias.query, parsed.rest].filter((part) => part !== undefined && part !== "").join(" ");
378
+ if (query === "")
379
+ throw new TypeError(`Search alias @${parsed.name} must produce a nonempty query.`);
380
+ if (Buffer.byteLength(query, "utf8") > MAX_SEARCH_RULE_TEXT_BYTES) {
381
+ throw new RangeError(`Expanded search query must be at most ${MAX_SEARCH_RULE_TEXT_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
382
+ }
383
+ const callerFilters = callerArray(request.filters, "Search request filters");
384
+ const callerTags = callerArray(request.tags, "Search request tags");
385
+ const callerScopes = callerArray(request.repositoryScopes, "Search request repository scopes");
386
+ const aliasScopeFilters = callerScopes.length > 0 && alias.repositoryScopes.length > 0 ? [Object.freeze({
387
+ kind: "one-of",
388
+ path: "repository_scopes",
389
+ values: alias.repositoryScopes
390
+ })] : [];
391
+ const expandedScopes = callerScopes.length > 0 ? callerScopes : alias.repositoryScopes;
392
+ const mode = request.mode ?? alias.mode;
393
+ if (request.mode !== undefined)
394
+ parseMode(request.mode, "Search request mode");
395
+ const expanded = Object.freeze({
396
+ ...request,
397
+ query,
398
+ ...mode === undefined ? {} : { mode },
399
+ ...callerFilters.length === 0 && alias.filters.length === 0 && aliasScopeFilters.length === 0 ? {} : { filters: Object.freeze([...callerFilters, ...alias.filters, ...aliasScopeFilters]) },
400
+ ...callerTags.length === 0 && alias.tags.length === 0 ? {} : { tags: Object.freeze([...callerTags, ...alias.tags]) },
401
+ ...expandedScopes.length === 0 ? {} : { repositoryScopes: Object.freeze([...expandedScopes]) }
402
+ });
403
+ return Object.freeze({ request: expanded, alias: parsed.name });
404
+ }
405
+ function isMetadataObject(value) {
406
+ return value !== null && typeof value === "object" && !Array.isArray(value);
407
+ }
408
+ function normalizedString(value) {
409
+ return value.normalize("NFC").toLocaleLowerCase("en-US");
410
+ }
411
+ function scalarMatches(value, expected) {
412
+ if (Array.isArray(value))
413
+ return value.some((candidate) => scalarMatches(candidate, expected));
414
+ if (isMetadataObject(value))
415
+ return false;
416
+ if (typeof value === "string" && typeof expected === "string") {
417
+ return normalizedString(value) === normalizedString(expected);
418
+ }
419
+ return Object.is(value, expected);
420
+ }
421
+ function metadataMatches(metadata, filter) {
422
+ const lookup = metadataAtPath(metadata, filter.path);
423
+ if (filter.kind === "exists")
424
+ return lookup.found;
425
+ if (!lookup.found)
426
+ return false;
427
+ if (filter.kind === "one-of") {
428
+ return filter.values.some((expected) => scalarMatches(lookup.value, expected));
429
+ }
430
+ return scalarMatches(lookup.value, filter.value);
431
+ }
432
+ function scopeMatches(hit, expected) {
433
+ if (hit.repositoryScopes !== undefined)
434
+ return hit.repositoryScopes.includes(expected);
435
+ const authored = analyzeAuthoredRepositoryScopes(hit.metadata);
436
+ return authored.present && authored.valid && authored.scopes.includes(expected);
437
+ }
438
+ function ruleMatches(hit, rule, inheritedVaultId) {
439
+ if (rule.pathPrefix !== undefined && !hit.path.startsWith(rule.pathPrefix))
440
+ return false;
441
+ if (rule.tagsAll !== undefined) {
442
+ const tags = new Set(hit.tags.map(normalizedTag));
443
+ if (!rule.tagsAll.every((tag) => tags.has(tag)))
444
+ return false;
445
+ }
446
+ if (rule.repositoryScope !== undefined && !scopeMatches(hit, rule.repositoryScope))
447
+ return false;
448
+ if (rule.metadata !== undefined && !rule.metadata.every((filter) => metadataMatches(hit.metadata, filter)))
449
+ return false;
450
+ if (rule.vaultId !== undefined && (hit.vaultId ?? inheritedVaultId) !== rule.vaultId)
451
+ return false;
452
+ return true;
453
+ }
454
+ function prioritizeSearchHits(hits, rules, context = {}) {
455
+ if (rules.priorityRules.length === 0) {
456
+ return Object.freeze({
457
+ hits,
458
+ trace: Object.freeze(hits.map((hit, index) => Object.freeze({
459
+ id: hit.id,
460
+ relevanceRank: index + 1,
461
+ matchedRuleIds: Object.freeze([]),
462
+ tier: null
463
+ })))
464
+ });
465
+ }
466
+ const inheritedVaultId = context.vaultId === undefined ? undefined : vaultId(context.vaultId, "Search priority context vault ID", { bytes: 0, nodes: 0 });
467
+ const candidates = hits.map((hit, index) => {
468
+ const matched = rules.priorityRules.filter((rule) => ruleMatches(hit, rule, inheritedVaultId));
469
+ const tier = matched.length === 0 ? null : Math.min(...matched.map((rule) => rule.tier));
470
+ return {
471
+ hit,
472
+ trace: Object.freeze({
473
+ id: hit.id,
474
+ relevanceRank: index + 1,
475
+ matchedRuleIds: Object.freeze(matched.map(({ id }) => id)),
476
+ tier
477
+ })
478
+ };
479
+ });
480
+ const ordered = candidates.toSorted((left, right) => Number(right.hit.identity) - Number(left.hit.identity) || (left.trace.tier ?? Number.POSITIVE_INFINITY) - (right.trace.tier ?? Number.POSITIVE_INFINITY) || left.trace.relevanceRank - right.trace.relevanceRank);
481
+ const unchanged = ordered.every((candidate, index) => candidate === candidates[index]);
482
+ return Object.freeze({
483
+ hits: unchanged ? hits : Object.freeze(ordered.map(({ hit }) => hit)),
484
+ trace: Object.freeze(ordered.map(({ trace }) => trace))
485
+ });
486
+ }
487
+
488
+ export { SEARCH_RULES_SCHEMA_VERSION, MAX_SEARCH_RULE_ALIASES, MAX_SEARCH_PRIORITY_RULES, MAX_SEARCH_RULE_FILTERS, MAX_SEARCH_RULE_TAGS, MAX_SEARCH_RULE_SCOPES, MAX_SEARCH_RULE_TEXT_BYTES, MAX_SEARCH_RULE_CONFIG_BYTES, MAX_SEARCH_RULE_TIER, EMPTY_SEARCH_RULES, parseSearchRules, expandSearchRequest, prioritizeSearchHits };