@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,585 @@
1
+ // @bun
2
+ // src/attachments.ts
3
+ import {
4
+ lstat,
5
+ readdir,
6
+ realpath
7
+ } from "fs/promises";
8
+ import {
9
+ dirname,
10
+ extname,
11
+ isAbsolute,
12
+ relative,
13
+ resolve,
14
+ sep
15
+ } from "path";
16
+ var MAX_ATTACHMENT_REFERENCES = 1e4;
17
+ var MAX_ATTACHMENT_SOURCE_BYTES = 16 * 1024 * 1024;
18
+ var MAX_ATTACHMENT_PATH_BYTES = 16 * 1024;
19
+ var MAX_ATTACHMENT_SCAN_ENTRIES = 1e5;
20
+ var supportedExtensions = new Set([
21
+ ".apng",
22
+ ".avif",
23
+ ".bmp",
24
+ ".gif",
25
+ ".heic",
26
+ ".heif",
27
+ ".jpeg",
28
+ ".jpg",
29
+ ".pdf",
30
+ ".png",
31
+ ".svg",
32
+ ".tldr",
33
+ ".tldraw",
34
+ ".webp"
35
+ ]);
36
+ var externalPattern = /^(?:[a-z][a-z\d+.-]*:|\/\/)/iu;
37
+ var windowsAbsolutePattern = /^[a-z]:[\\/]/iu;
38
+ var ignoredInventoryDirectories = new Set([
39
+ ".git",
40
+ ".next",
41
+ ".turbo",
42
+ ".vercel",
43
+ "coverage",
44
+ "dist",
45
+ "node_modules"
46
+ ]);
47
+ function hasControl(value) {
48
+ return [...value].some((character) => {
49
+ const code = character.codePointAt(0) ?? 0;
50
+ return code <= 31 || code === 127;
51
+ });
52
+ }
53
+ function attachmentExtension(target) {
54
+ return extname(target).toLocaleLowerCase("en-US");
55
+ }
56
+ function isSupportedAttachment(target) {
57
+ return supportedExtensions.has(attachmentExtension(target));
58
+ }
59
+ function issueFor(reference, kind, message, candidates) {
60
+ return Object.freeze({
61
+ kind,
62
+ source: reference.source,
63
+ line: reference.line,
64
+ syntax: reference.syntax,
65
+ target: reference.rawTarget,
66
+ message,
67
+ ...candidates === undefined ? {} : { candidates: Object.freeze([...candidates]) }
68
+ });
69
+ }
70
+ function decodeLocalTarget(rawTarget, reference) {
71
+ const withoutDecoration = rawTarget.trim().split(/[?#^]/u, 1)[0] ?? "";
72
+ if (withoutDecoration === "" || withoutDecoration.startsWith("#")) {
73
+ return { external: true };
74
+ }
75
+ let decoded;
76
+ try {
77
+ decoded = decodeURIComponent(withoutDecoration).replace(/\\([\\()[\] ])/gu, "$1").normalize("NFC");
78
+ } catch {
79
+ if (!isSupportedAttachment(withoutDecoration))
80
+ return { external: true };
81
+ return {
82
+ external: false,
83
+ issue: issueFor(reference, "malformed-target", "Attachment target has invalid percent encoding.")
84
+ };
85
+ }
86
+ if (windowsAbsolutePattern.test(decoded)) {
87
+ if (!isSupportedAttachment(decoded))
88
+ return { external: true };
89
+ return {
90
+ external: false,
91
+ issue: issueFor(reference, "traversal", "Attachment targets must be vault-relative.")
92
+ };
93
+ }
94
+ if (externalPattern.test(decoded))
95
+ return { external: true };
96
+ if (!isSupportedAttachment(decoded))
97
+ return { external: true };
98
+ if (decoded === "" || hasControl(decoded)) {
99
+ return {
100
+ external: false,
101
+ issue: issueFor(reference, "malformed-target", "Attachment target is empty or contains control characters.")
102
+ };
103
+ }
104
+ if (Buffer.byteLength(decoded, "utf8") > MAX_ATTACHMENT_PATH_BYTES) {
105
+ return {
106
+ external: false,
107
+ issue: issueFor(reference, "budget", `Attachment targets may use at most ${MAX_ATTACHMENT_PATH_BYTES.toLocaleString("en-US")} UTF-8 bytes.`)
108
+ };
109
+ }
110
+ return { target: decoded, external: false };
111
+ }
112
+ function markdownDestination(value) {
113
+ const trimmed = value.trim();
114
+ if (trimmed.startsWith("<")) {
115
+ const end = trimmed.indexOf(">");
116
+ return end === -1 ? trimmed : trimmed.slice(1, end);
117
+ }
118
+ const match = /^(?:\\.|[^\s])+/u.exec(trimmed);
119
+ return match?.[0] ?? "";
120
+ }
121
+ function normalizedReferenceLabel(value) {
122
+ return value.trim().replace(/\s+/gu, " ").toLocaleLowerCase("en-US");
123
+ }
124
+ function inlineMarkdownLinks(line) {
125
+ const links = [];
126
+ for (let cursor = 0;cursor < line.length; cursor += 1) {
127
+ const image = line[cursor] === "!" && line[cursor + 1] === "[";
128
+ if (line[cursor] !== "[" && !image)
129
+ continue;
130
+ const opening = image ? cursor + 1 : cursor;
131
+ if (line[opening - 1] === "\\" || line[opening + 1] === "[")
132
+ continue;
133
+ let closing = opening + 1;
134
+ for (;closing < line.length; closing += 1) {
135
+ if (line[closing] === "]" && line[closing - 1] !== "\\")
136
+ break;
137
+ }
138
+ if (closing >= line.length || line[closing + 1] !== "(")
139
+ continue;
140
+ let depth = 1;
141
+ let destinationEnd = closing + 2;
142
+ let angle = false;
143
+ for (;destinationEnd < line.length; destinationEnd += 1) {
144
+ const character = line[destinationEnd];
145
+ if (character === "\\") {
146
+ destinationEnd += 1;
147
+ continue;
148
+ }
149
+ if (character === "<" && depth === 1)
150
+ angle = true;
151
+ else if (character === ">" && angle)
152
+ angle = false;
153
+ else if (!angle && character === "(")
154
+ depth += 1;
155
+ else if (!angle && character === ")") {
156
+ depth -= 1;
157
+ if (depth === 0)
158
+ break;
159
+ }
160
+ }
161
+ if (depth !== 0)
162
+ continue;
163
+ links.push({
164
+ image,
165
+ rawTarget: markdownDestination(line.slice(closing + 2, destinationEnd))
166
+ });
167
+ cursor = destinationEnd;
168
+ }
169
+ return links;
170
+ }
171
+ function maskInlineCode(value) {
172
+ const characters = [...value];
173
+ let cursor = 0;
174
+ while (cursor < characters.length) {
175
+ if (characters[cursor] !== "`") {
176
+ cursor += 1;
177
+ continue;
178
+ }
179
+ let ticks = 1;
180
+ while (characters[cursor + ticks] === "`")
181
+ ticks += 1;
182
+ const delimiter = "`".repeat(ticks);
183
+ const remainder = characters.slice(cursor + ticks).join("");
184
+ const closingOffset = remainder.indexOf(delimiter);
185
+ if (closingOffset === -1)
186
+ break;
187
+ const end = cursor + ticks + [...remainder.slice(0, closingOffset)].length + ticks;
188
+ for (let index = cursor;index < end; index += 1)
189
+ characters[index] = " ";
190
+ cursor = end;
191
+ }
192
+ return characters.join("");
193
+ }
194
+ function maskHtmlComments(value, startsInsideComment) {
195
+ let cursor = 0;
196
+ let insideComment = startsInsideComment;
197
+ let masked = "";
198
+ while (cursor < value.length) {
199
+ if (insideComment) {
200
+ const closing = value.indexOf("-->", cursor);
201
+ if (closing === -1) {
202
+ masked += " ".repeat(value.length - cursor);
203
+ return { line: masked, insideComment: true };
204
+ }
205
+ masked += " ".repeat(closing + 3 - cursor);
206
+ cursor = closing + 3;
207
+ insideComment = false;
208
+ continue;
209
+ }
210
+ const opening = value.indexOf("<!--", cursor);
211
+ if (opening === -1) {
212
+ masked += value.slice(cursor);
213
+ break;
214
+ }
215
+ masked += value.slice(cursor, opening);
216
+ masked += " ".repeat(4);
217
+ cursor = opening + 4;
218
+ insideComment = true;
219
+ }
220
+ return { line: masked, insideComment };
221
+ }
222
+ function normalizedSource(source) {
223
+ const normalized = source.replaceAll("\\", "/").normalize("NFC");
224
+ if (normalized === "" || isAbsolute(normalized) || windowsAbsolutePattern.test(normalized) || hasControl(normalized) || normalized.split("/").some((part, index) => part === ".." || part === "" || part === "." && index !== 0)) {
225
+ throw new TypeError("Attachment source paths must be normalized vault-relative paths.");
226
+ }
227
+ return normalized.replace(/^\.\//u, "");
228
+ }
229
+ function parseLocalAttachmentReferences(sourceInput, markdown, options = {}) {
230
+ const source = normalizedSource(sourceInput);
231
+ const maxReferences = options.maxReferences ?? MAX_ATTACHMENT_REFERENCES;
232
+ const maxBytes = options.maxBytes ?? MAX_ATTACHMENT_SOURCE_BYTES;
233
+ if (!Number.isSafeInteger(maxReferences) || maxReferences < 1 || maxReferences > MAX_ATTACHMENT_REFERENCES) {
234
+ throw new RangeError(`Attachment reference limit must be from 1 through ${MAX_ATTACHMENT_REFERENCES}.`);
235
+ }
236
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > MAX_ATTACHMENT_SOURCE_BYTES) {
237
+ throw new RangeError(`Attachment source byte limit must be from 1 through ${MAX_ATTACHMENT_SOURCE_BYTES}.`);
238
+ }
239
+ if (Buffer.byteLength(markdown, "utf8") > maxBytes) {
240
+ throw new RangeError(`Attachment source exceeds the ${maxBytes.toLocaleString("en-US")}-byte limit.`);
241
+ }
242
+ const references = [];
243
+ const issues = [];
244
+ let truncated = false;
245
+ let fence = null;
246
+ let htmlComment = false;
247
+ let frontmatter = false;
248
+ const add = (line, syntax, rawTarget) => {
249
+ if (references.length + issues.length >= maxReferences) {
250
+ truncated = true;
251
+ return;
252
+ }
253
+ const shell = { source, line, syntax, rawTarget };
254
+ const decoded = decodeLocalTarget(rawTarget, shell);
255
+ if (decoded.external)
256
+ return;
257
+ if (decoded.issue !== undefined) {
258
+ issues.push(decoded.issue);
259
+ return;
260
+ }
261
+ const target = decoded.target;
262
+ if (target === undefined)
263
+ return;
264
+ references.push(Object.freeze({ ...shell, target }));
265
+ };
266
+ const lines = markdown.split(/\r?\n/u).map((rawLine, index) => {
267
+ if (index === 0 && /^\uFEFF?---\s*$/u.test(rawLine)) {
268
+ frontmatter = true;
269
+ return { index, line: "", definition: true };
270
+ }
271
+ if (frontmatter) {
272
+ if (/^(?:---|\.\.\.)\s*$/u.test(rawLine))
273
+ frontmatter = false;
274
+ return { index, line: "", definition: true };
275
+ }
276
+ if (fence !== null) {
277
+ const fenceMatch2 = /^\s*(`{3,}|~{3,})/u.exec(rawLine);
278
+ const marker = fenceMatch2?.[1]?.[0];
279
+ if (marker === fence)
280
+ fence = null;
281
+ return { index, line: "", definition: true };
282
+ }
283
+ const inlineMasked = htmlComment ? rawLine : maskInlineCode(rawLine);
284
+ const commentMasked = maskHtmlComments(inlineMasked, htmlComment);
285
+ htmlComment = commentMasked.insideComment;
286
+ const fenceMatch = /^\s*(`{3,}|~{3,})/u.exec(commentMasked.line);
287
+ if (fenceMatch !== null) {
288
+ const marker = fenceMatch[1]?.[0];
289
+ if (marker !== undefined)
290
+ fence = marker;
291
+ return { index, line: "", definition: true };
292
+ }
293
+ if (/^(?: {4}|\t)/u.test(rawLine)) {
294
+ return { index, line: "", definition: true };
295
+ }
296
+ return {
297
+ index,
298
+ line: htmlComment ? commentMasked.line : maskInlineCode(commentMasked.line),
299
+ definition: false
300
+ };
301
+ });
302
+ const definitions = new Map;
303
+ for (const entry of lines) {
304
+ const match = /^ {0,3}\[([^\]\n]+)\]:\s*(.*)$/u.exec(entry.line);
305
+ if (match === null)
306
+ continue;
307
+ entry.definition = true;
308
+ const label = normalizedReferenceLabel(match[1] ?? "");
309
+ if (label !== "" && !definitions.has(label)) {
310
+ definitions.set(label, markdownDestination(match[2] ?? ""));
311
+ }
312
+ }
313
+ for (const { index, line, definition } of lines) {
314
+ if (definition || truncated)
315
+ continue;
316
+ for (const match of inlineMarkdownLinks(line)) {
317
+ add(index + 1, match.image ? "markdown-image" : "markdown-link", match.rawTarget);
318
+ }
319
+ for (const match of line.matchAll(/(!?)\[([^\]\n]*)\](?:\[([^\]\n]*)\])?/gu)) {
320
+ const matchIndex = match.index;
321
+ if (matchIndex === undefined)
322
+ continue;
323
+ const whole = match[0] ?? "";
324
+ if (line[matchIndex - 1] === "[" || line[matchIndex + whole.length] === "(")
325
+ continue;
326
+ const explicitLabel = match[3];
327
+ const label = normalizedReferenceLabel(explicitLabel === undefined || explicitLabel === "" ? match[2] ?? "" : explicitLabel);
328
+ const rawTarget = definitions.get(label);
329
+ if (rawTarget !== undefined) {
330
+ add(index + 1, match[1] === "!" ? "markdown-image" : "markdown-link", rawTarget);
331
+ }
332
+ }
333
+ for (const match of line.matchAll(/(!?)\[\[([^\]\n]+)\]\]/gu)) {
334
+ const rawTarget = (match[2] ?? "").split("|", 1)[0]?.trim() ?? "";
335
+ add(index + 1, match[1] === "!" ? "obsidian-embed" : "obsidian-link", rawTarget);
336
+ }
337
+ }
338
+ if (truncated) {
339
+ issues.push(Object.freeze({
340
+ kind: "budget",
341
+ source,
342
+ line: 1,
343
+ syntax: "markdown-link",
344
+ target: "",
345
+ message: `Attachment source exceeds the ${maxReferences.toLocaleString("en-US")}-reference limit.`
346
+ }));
347
+ }
348
+ return Object.freeze({
349
+ references: Object.freeze(references),
350
+ issues: Object.freeze(issues),
351
+ truncated
352
+ });
353
+ }
354
+ function insideRoot(root, candidate) {
355
+ const path = relative(root, candidate);
356
+ return path === "" || !path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path);
357
+ }
358
+ function folded(value) {
359
+ return value.normalize("NFC").toLocaleLowerCase("en-US");
360
+ }
361
+ async function inventoryAttachments(root, maximumEntries) {
362
+ const files = [];
363
+ const basename = new Map;
364
+ const collisions = new Map;
365
+ const pending = [""];
366
+ let entries = 0;
367
+ while (pending.length > 0) {
368
+ const directory = pending.shift();
369
+ if (directory === undefined)
370
+ break;
371
+ const absoluteDirectory = resolve(root, directory);
372
+ const children = await readdir(absoluteDirectory, { withFileTypes: true });
373
+ const siblings = new Map;
374
+ for (const child of children) {
375
+ entries += 1;
376
+ if (entries > maximumEntries) {
377
+ throw new RangeError(`Attachment inventory exceeds the ${maximumEntries.toLocaleString("en-US")}-entry limit.`);
378
+ }
379
+ const relativePath = directory === "" ? child.name : `${directory}/${child.name}`;
380
+ const key = folded(child.name);
381
+ const matches = siblings.get(key) ?? [];
382
+ matches.push(relativePath);
383
+ siblings.set(key, matches);
384
+ if (isSupportedAttachment(child.name)) {
385
+ const names = basename.get(key) ?? [];
386
+ names.push(relativePath);
387
+ basename.set(key, names);
388
+ }
389
+ if (child.isSymbolicLink())
390
+ continue;
391
+ if (child.isDirectory()) {
392
+ if (!child.name.startsWith(".") && !ignoredInventoryDirectories.has(child.name)) {
393
+ pending.push(relativePath);
394
+ }
395
+ } else if (child.isFile() && isSupportedAttachment(child.name)) {
396
+ files.push(relativePath);
397
+ }
398
+ }
399
+ for (const [key, matches] of siblings) {
400
+ if (matches.length > 1)
401
+ collisions.set(`${directory}\x00${key}`, matches.toSorted());
402
+ }
403
+ }
404
+ return Object.freeze({
405
+ files: Object.freeze(files.toSorted()),
406
+ basename: new Map([...basename].map(([key, values]) => [key, Object.freeze(values.toSorted())])),
407
+ collisions: new Map([...collisions].map(([key, values]) => [key, Object.freeze(values)]))
408
+ });
409
+ }
410
+ async function inspectExactPath(root, candidateRelative, inventory, reference) {
411
+ const parts = candidateRelative.split("/").filter(Boolean);
412
+ let directory = "";
413
+ for (const [index, part] of parts.entries()) {
414
+ const key = `${directory}\x00${folded(part)}`;
415
+ const collision = inventory.collisions.get(key);
416
+ if (collision !== undefined) {
417
+ return {
418
+ issue: issueFor(reference, "case-collision", "Attachment path is ambiguous under case-insensitive or Unicode-normalized lookup.", collision)
419
+ };
420
+ }
421
+ const children = await readdir(resolve(root, directory), { withFileTypes: true });
422
+ const equivalent = children.filter(({ name }) => folded(name) === folded(part));
423
+ const exact = equivalent.find(({ name }) => name === part);
424
+ if (exact === undefined) {
425
+ if (equivalent.length > 0) {
426
+ const actual = equivalent.map(({ name }) => directory === "" ? name : `${directory}/${name}`);
427
+ return {
428
+ issue: issueFor(reference, "case-mismatch", "Attachment target casing or Unicode normalization does not match the filesystem.", actual)
429
+ };
430
+ }
431
+ return { issue: issueFor(reference, "missing", "Attachment target does not exist.") };
432
+ }
433
+ const next = directory === "" ? exact.name : `${directory}/${exact.name}`;
434
+ const status2 = await lstat(resolve(root, next));
435
+ if (status2.isSymbolicLink()) {
436
+ return { issue: issueFor(reference, "symlink", "Attachment paths may not traverse symbolic links.") };
437
+ }
438
+ if (index < parts.length - 1 && !status2.isDirectory()) {
439
+ return { issue: issueFor(reference, "non-regular", "An attachment path ancestor is not a directory.") };
440
+ }
441
+ directory = next;
442
+ }
443
+ const status = await lstat(resolve(root, candidateRelative));
444
+ if (!status.isFile()) {
445
+ return { issue: issueFor(reference, "non-regular", "Attachment target must be a regular file.") };
446
+ }
447
+ if (status.nlink !== 1) {
448
+ return { issue: issueFor(reference, "hardlink", "Attachment target may not be a hard-linked file.") };
449
+ }
450
+ return {
451
+ attachment: Object.freeze({
452
+ ...reference,
453
+ path: candidateRelative,
454
+ bytes: status.size
455
+ })
456
+ };
457
+ }
458
+ function candidateFor(root, reference, inventory) {
459
+ const slashTarget = reference.target.replaceAll("\\", "/");
460
+ if (isAbsolute(slashTarget)) {
461
+ return { issue: issueFor(reference, "traversal", "Attachment targets must be vault-relative.") };
462
+ }
463
+ let candidateRelative;
464
+ if (reference.syntax === "markdown-image" || reference.syntax === "markdown-link") {
465
+ const absolute = resolve(root, dirname(reference.source), slashTarget);
466
+ if (!insideRoot(root, absolute)) {
467
+ return { issue: issueFor(reference, "traversal", "Attachment target escapes the vault root.") };
468
+ }
469
+ candidateRelative = relative(root, absolute).split(sep).join("/");
470
+ } else if (slashTarget.includes("/")) {
471
+ const absolute = resolve(root, slashTarget);
472
+ if (!insideRoot(root, absolute) || slashTarget.split("/").includes("..")) {
473
+ return { issue: issueFor(reference, "traversal", "Obsidian attachment paths must be vault-relative without traversal.") };
474
+ }
475
+ candidateRelative = relative(root, absolute).split(sep).join("/");
476
+ } else {
477
+ const matches = inventory.basename.get(folded(slashTarget)) ?? [];
478
+ if (matches.length === 0) {
479
+ return { issue: issueFor(reference, "missing", "Obsidian attachment basename was not found in the vault.") };
480
+ }
481
+ const collidingMatches = matches.flatMap((path) => {
482
+ const separator = path.lastIndexOf("/");
483
+ const directory = separator === -1 ? "" : path.slice(0, separator);
484
+ return inventory.collisions.get(`${directory}\x00${folded(slashTarget)}`) ?? [];
485
+ });
486
+ if (collidingMatches.length > 0) {
487
+ return {
488
+ issue: issueFor(reference, "case-collision", "Obsidian attachment basename collides under case-insensitive or Unicode-normalized lookup.", [...new Set(collidingMatches)].toSorted())
489
+ };
490
+ }
491
+ if (matches.length > 1) {
492
+ return {
493
+ issue: issueFor(reference, "ambiguous", "Obsidian attachment basename resolves to more than one vault file.", matches)
494
+ };
495
+ }
496
+ candidateRelative = matches[0] ?? "";
497
+ const actualBasename = candidateRelative.slice(candidateRelative.lastIndexOf("/") + 1);
498
+ if (actualBasename !== slashTarget) {
499
+ return {
500
+ issue: issueFor(reference, "case-mismatch", "Obsidian attachment basename casing or Unicode normalization does not match the filesystem.", [candidateRelative])
501
+ };
502
+ }
503
+ }
504
+ return { path: candidateRelative };
505
+ }
506
+ async function validateAttachmentReferences(options) {
507
+ if (options.references.length > MAX_ATTACHMENT_REFERENCES) {
508
+ throw new RangeError(`Attachment validation accepts at most ${MAX_ATTACHMENT_REFERENCES} references.`);
509
+ }
510
+ if ((options.parseIssues?.length ?? 0) > MAX_ATTACHMENT_REFERENCES + 1) {
511
+ throw new RangeError(`Attachment validation accepts at most ${MAX_ATTACHMENT_REFERENCES + 1} parse issues.`);
512
+ }
513
+ const maxEntries = options.maxEntries ?? MAX_ATTACHMENT_SCAN_ENTRIES;
514
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1 || maxEntries > MAX_ATTACHMENT_SCAN_ENTRIES) {
515
+ throw new RangeError(`Attachment inventory limit must be from 1 through ${MAX_ATTACHMENT_SCAN_ENTRIES}.`);
516
+ }
517
+ const requestedRootStatus = await lstat(options.root);
518
+ if (requestedRootStatus.isSymbolicLink()) {
519
+ throw new TypeError("Attachment root may not be a symbolic link.");
520
+ }
521
+ const root = await realpath(options.root);
522
+ const rootStatus = await lstat(root);
523
+ if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
524
+ throw new TypeError("Attachment root must be a real directory.");
525
+ }
526
+ const inventory = await inventoryAttachments(root, maxEntries);
527
+ const attachments = [];
528
+ const issues = [...options.parseIssues ?? []];
529
+ for (const reference of options.references) {
530
+ const candidate = candidateFor(root, reference, inventory);
531
+ if (candidate.issue !== undefined) {
532
+ issues.push(candidate.issue);
533
+ continue;
534
+ }
535
+ const candidatePath = candidate.path;
536
+ if (candidatePath === undefined)
537
+ continue;
538
+ const inspected = await inspectExactPath(root, candidatePath, inventory, reference);
539
+ if (inspected.issue !== undefined)
540
+ issues.push(inspected.issue);
541
+ else if (inspected.attachment !== undefined)
542
+ attachments.push(inspected.attachment);
543
+ }
544
+ return Object.freeze({
545
+ root,
546
+ references: Object.freeze([...options.references]),
547
+ attachments: Object.freeze(attachments),
548
+ issues: Object.freeze(issues),
549
+ truncated: options.parseTruncated === true
550
+ });
551
+ }
552
+ async function validateMarkdownAttachments(options) {
553
+ const maximum = options.maxReferences ?? MAX_ATTACHMENT_REFERENCES;
554
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > MAX_ATTACHMENT_REFERENCES) {
555
+ throw new RangeError(`Attachment reference limit must be from 1 through ${MAX_ATTACHMENT_REFERENCES}.`);
556
+ }
557
+ const references = [];
558
+ const issues = [];
559
+ let truncated = false;
560
+ for (const document of options.documents) {
561
+ const remaining = maximum - references.length - issues.length;
562
+ if (remaining <= 0) {
563
+ truncated = true;
564
+ break;
565
+ }
566
+ const parsed = parseLocalAttachmentReferences(document.path, document.content, {
567
+ maxReferences: remaining
568
+ });
569
+ references.push(...parsed.references);
570
+ issues.push(...parsed.issues);
571
+ truncated ||= parsed.truncated;
572
+ if (parsed.truncated)
573
+ break;
574
+ }
575
+ const report = await validateAttachmentReferences({
576
+ root: options.root,
577
+ references,
578
+ parseIssues: issues,
579
+ parseTruncated: truncated,
580
+ ...options.maxEntries === undefined ? {} : { maxEntries: options.maxEntries }
581
+ });
582
+ return Object.freeze({ ...report, truncated: truncated || report.truncated });
583
+ }
584
+
585
+ export { MAX_ATTACHMENT_REFERENCES, MAX_ATTACHMENT_SOURCE_BYTES, MAX_ATTACHMENT_PATH_BYTES, MAX_ATTACHMENT_SCAN_ENTRIES, parseLocalAttachmentReferences, validateAttachmentReferences, validateMarkdownAttachments };
@@ -0,0 +1,44 @@
1
+ // @bun
2
+ import {
3
+ defineWorkflow
4
+ } from "./index-3v2z4f0q.js";
5
+
6
+ // src/workflows/explain-change.ts
7
+ var explainChangeWorkflow = defineWorkflow({
8
+ id: "explain-change",
9
+ nodes: [
10
+ {
11
+ id: "rationale",
12
+ resource: "qmd",
13
+ run: ({ input, kb }) => kb.search({
14
+ query: input.query,
15
+ history: false,
16
+ ...input.resultLimit === undefined ? {} : { limit: input.resultLimit }
17
+ })
18
+ },
19
+ {
20
+ id: "evolution",
21
+ resource: "git",
22
+ run: ({ input, kb }) => kb.searchHistory({
23
+ query: input.historyQuery ?? input.query,
24
+ ...input.historyLimit === undefined ? {} : { limit: input.historyLimit }
25
+ })
26
+ },
27
+ {
28
+ id: "neighborhood",
29
+ run: ({ input, kb }) => input.note === undefined ? null : kb.links(input.note, { direction: "both", depth: 1, limit: 20 })
30
+ },
31
+ {
32
+ id: "assemble",
33
+ needs: ["rationale", "evolution", "neighborhood"],
34
+ run: ({ result }) => ({
35
+ rationale: result("rationale"),
36
+ evolution: result("evolution"),
37
+ neighborhood: result("neighborhood")
38
+ })
39
+ }
40
+ ],
41
+ output: "assemble"
42
+ });
43
+
44
+ export { explainChangeWorkflow };