@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,743 @@
1
+ // @bun
2
+ // src/git.ts
3
+ import { spawn } from "child_process";
4
+ import { randomUUID } from "crypto";
5
+ import { realpath, stat } from "fs/promises";
6
+ import { isAbsolute, relative, resolve, sep } from "path";
7
+ var MAX_GIT_HISTORY_COMMITS = 1000;
8
+ var MAX_GIT_HISTORY_NOTES = 1e4;
9
+ var MAX_GIT_HISTORY_OUTPUT_BYTES = 32 * 1024 * 1024;
10
+ var MAX_GIT_HISTORY_TIMEOUT_MS = 30000;
11
+ var MAX_GIT_PATHS_PER_COMMIT = 2000;
12
+ var MAX_GIT_PATH_OBSERVATIONS = 1e5;
13
+ var MAX_GIT_NOTE_ID_UTF8_BYTES = 16 * 1024;
14
+ var MAX_GIT_NOTE_IDS_UTF8_BYTES = 16 * 1024 * 1024;
15
+ var DEFAULT_COMMITS = 200;
16
+ var DEFAULT_OUTPUT_BYTES = 8 * 1024 * 1024;
17
+ var DEFAULT_TIMEOUT_MS = 1e4;
18
+ var DEFAULT_COMMITS_PER_NOTE = 5;
19
+ var DEFAULT_COCHANGED_PATHS = 20;
20
+ var DEFAULT_SEARCH_LIMIT = 20;
21
+ var MAX_COMMITS_PER_NOTE = 50;
22
+ var MAX_COCHANGED_PATHS = 100;
23
+ var MAX_SEARCH_LIMIT = 100;
24
+ var MAX_QUERY_LENGTH = 500;
25
+ var commitMarker = "KB-GIT-HISTORY-V1";
26
+ var objectIdPattern = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u;
27
+
28
+ class GitHistoryError extends Error {
29
+ kind;
30
+ constructor(kind, message, options) {
31
+ super(message, options);
32
+ this.name = "GitHistoryError";
33
+ this.kind = kind;
34
+ }
35
+ }
36
+ var runGitCommand = (request) => new Promise((resolveResult) => {
37
+ let settled = false;
38
+ const bytes = { stdout: [], stderr: [], total: 0, exceeded: false };
39
+ let child;
40
+ try {
41
+ child = spawn("git", [...request.arguments], {
42
+ cwd: request.cwd,
43
+ env: {
44
+ ...process.env,
45
+ GIT_OPTIONAL_LOCKS: "0",
46
+ GIT_PAGER: "cat",
47
+ GIT_TERMINAL_PROMPT: "0",
48
+ LC_ALL: "C"
49
+ },
50
+ shell: false,
51
+ stdio: ["ignore", "pipe", "pipe"]
52
+ });
53
+ } catch (error) {
54
+ const code = errorCode(error);
55
+ resolveResult(code === "ENOENT" ? { status: "unavailable", message: "Git is not installed or is not on PATH." } : { status: "failed", message: errorMessage(error), reason: "exit" });
56
+ return;
57
+ }
58
+ const finish = (result) => {
59
+ if (settled)
60
+ return;
61
+ settled = true;
62
+ clearTimeout(timer);
63
+ resolveResult(result);
64
+ };
65
+ const append = (target, chunk) => {
66
+ if (settled || bytes.exceeded)
67
+ return;
68
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
69
+ bytes.total += buffer.byteLength;
70
+ if (bytes.total > request.maxOutputBytes) {
71
+ bytes.exceeded = true;
72
+ child.kill("SIGKILL");
73
+ return;
74
+ }
75
+ bytes[target].push(buffer);
76
+ };
77
+ child.stdout.on("data", (chunk) => append("stdout", chunk));
78
+ child.stderr.on("data", (chunk) => append("stderr", chunk));
79
+ child.once("error", (error) => {
80
+ finish(error.code === "ENOENT" ? { status: "unavailable", message: "Git is not installed or is not on PATH." } : { status: "failed", message: error.message, reason: "exit" });
81
+ });
82
+ child.once("close", (exitCode) => {
83
+ const stdout = Buffer.concat(bytes.stdout);
84
+ const stderr = Buffer.concat(bytes.stderr);
85
+ if (bytes.exceeded) {
86
+ finish({
87
+ status: "failed",
88
+ message: `Git output exceeded ${request.maxOutputBytes} bytes.`,
89
+ reason: "output-limit",
90
+ stderr
91
+ });
92
+ } else if (exitCode === 0) {
93
+ finish({ status: "ok", stdout, stderr });
94
+ } else {
95
+ const failed = {
96
+ status: "failed",
97
+ message: `Git exited with code ${exitCode ?? "unknown"}.`,
98
+ reason: "exit",
99
+ stderr,
100
+ ...exitCode === null ? {} : { exitCode }
101
+ };
102
+ finish(failed);
103
+ }
104
+ });
105
+ const timer = setTimeout(() => {
106
+ child.kill("SIGKILL");
107
+ finish({
108
+ status: "failed",
109
+ message: `Git exceeded the ${request.timeoutMs}ms timeout.`,
110
+ reason: "timeout"
111
+ });
112
+ }, request.timeoutMs);
113
+ });
114
+ function errorCode(error) {
115
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : undefined;
116
+ }
117
+ function errorMessage(error) {
118
+ return error instanceof Error ? error.message : String(error);
119
+ }
120
+ function checkedInteger(value, fallback, minimum, maximum, label) {
121
+ const selected = value ?? fallback;
122
+ if (!Number.isSafeInteger(selected) || selected < minimum || selected > maximum) {
123
+ throw new GitHistoryError("budget", `${label} must be an integer from ${minimum} through ${maximum}.`);
124
+ }
125
+ return selected;
126
+ }
127
+ function inside(parent, candidate) {
128
+ const path = relative(parent, candidate);
129
+ return path === "" || !path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path);
130
+ }
131
+ function posixRelative(parent, candidate) {
132
+ return relative(parent, candidate).split(sep).join("/");
133
+ }
134
+ function hasControlCharacter(value, allowTab) {
135
+ return [...value].some((character) => {
136
+ const code = character.codePointAt(0) ?? 0;
137
+ return code < 32 && !(allowTab && code === 9) || code === 127;
138
+ });
139
+ }
140
+ function checkedRepositoryPath(value, label) {
141
+ if (value === "" || hasControlCharacter(value, false) || value.includes("\\")) {
142
+ throw new GitHistoryError("malformed", `${label} is not a safe repository-relative path.`);
143
+ }
144
+ if (value.startsWith("/") || value.endsWith("/") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
145
+ throw new GitHistoryError("malformed", `${label} is not a normalized repository-relative path.`);
146
+ }
147
+ return value;
148
+ }
149
+ function checkedNoteIdentity(note) {
150
+ const path = checkedRepositoryPath(note.path, `Note path ${JSON.stringify(note.path)}`);
151
+ const id = checkedRepositoryPath(note.id, `Note ID ${JSON.stringify(note.id)}`);
152
+ if (!path.endsWith(".md") || path.slice(0, -3) !== id) {
153
+ throw new GitHistoryError("malformed", `Note ${JSON.stringify(note.id)} must use its extensionless Markdown path as its ID.`);
154
+ }
155
+ }
156
+ function decodeOutput(value, label) {
157
+ if (value === undefined)
158
+ return "";
159
+ try {
160
+ return typeof value === "string" ? value : new TextDecoder("utf-8", { fatal: true }).decode(value);
161
+ } catch (error) {
162
+ throw new GitHistoryError("malformed", `${label} was not valid UTF-8.`, { cause: error });
163
+ }
164
+ }
165
+ async function checkedCommand(provider, cwd, arguments_, budget, required, label) {
166
+ const timeoutMs = budget.timeoutMs - (Date.now() - budget.startedAt);
167
+ const maxOutputBytes = budget.outputLimit - budget.outputBytes;
168
+ if (timeoutMs <= 0)
169
+ throw new GitHistoryError("budget", "Git history indexing exceeded its timeout.");
170
+ if (maxOutputBytes <= 0) {
171
+ throw new GitHistoryError("budget", "Git history indexing exceeded its output budget.");
172
+ }
173
+ let result;
174
+ try {
175
+ result = await provider({ cwd, arguments: arguments_, timeoutMs, maxOutputBytes });
176
+ } catch (error) {
177
+ if (errorCode(error) === "ENOENT") {
178
+ result = { status: "unavailable", message: "Git is not installed or is not on PATH." };
179
+ } else {
180
+ throw new GitHistoryError("failed", `${label} failed: ${errorMessage(error)}`, { cause: error });
181
+ }
182
+ }
183
+ if (result.status === "unavailable") {
184
+ if (required)
185
+ throw new GitHistoryError("unavailable", result.message);
186
+ return {
187
+ status: "unavailable",
188
+ repository: cwd,
189
+ root: cwd,
190
+ vaultPrefix: "",
191
+ reason: result.message
192
+ };
193
+ }
194
+ if (result.status === "failed") {
195
+ const stderr = decodeOutput(result.stderr, "Git stderr").trim();
196
+ const detail = stderr === "" ? result.message : `${result.message} ${stderr}`;
197
+ throw new GitHistoryError(result.reason === "timeout" || result.reason === "output-limit" ? "budget" : "failed", `${label} failed: ${detail}`);
198
+ }
199
+ const stdoutBytes = typeof result.stdout === "string" ? Buffer.byteLength(result.stdout) : result.stdout.byteLength;
200
+ const stderrBytes = typeof result.stderr === "string" ? Buffer.byteLength(result.stderr) : result.stderr?.byteLength ?? 0;
201
+ budget.outputBytes += stdoutBytes + stderrBytes;
202
+ if (budget.outputBytes > budget.outputLimit) {
203
+ throw new GitHistoryError("budget", "Git history indexing exceeded its output budget.");
204
+ }
205
+ return { status: "ok", stdout: decodeOutput(result.stdout, label) };
206
+ }
207
+ function singleLine(value, label) {
208
+ const normalized = value.replace(/\r?\n$/u, "");
209
+ if (normalized === "" || /[\r\n\0]/u.test(normalized)) {
210
+ throw new GitHistoryError("malformed", `${label} was not one non-empty line.`);
211
+ }
212
+ return normalized;
213
+ }
214
+ function commitTime(timestamp) {
215
+ if (!/^(?:0|[1-9]\d*)$/u.test(timestamp)) {
216
+ throw new GitHistoryError("malformed", "Git returned a malformed commit timestamp.");
217
+ }
218
+ const value = Number(timestamp);
219
+ if (!Number.isSafeInteger(value) || value < 0) {
220
+ throw new GitHistoryError("malformed", "Git returned an out-of-range commit timestamp.");
221
+ }
222
+ return value;
223
+ }
224
+ function normalizedSubject(value) {
225
+ if (value.includes("\uFFFD") || hasControlCharacter(value, true)) {
226
+ throw new GitHistoryError("malformed", "Git returned a malformed commit subject.");
227
+ }
228
+ return [...value.normalize("NFC").trim()].slice(0, 1000).join("");
229
+ }
230
+ function parseGitHistoryRecords(output, expectedMarker, changedPathPolicy, pathBudget) {
231
+ if (output === "")
232
+ return Object.freeze([]);
233
+ if (expectedMarker === "" || expectedMarker.includes("\x00")) {
234
+ throw new GitHistoryError("malformed", "Git history record marker is invalid.");
235
+ }
236
+ if (output.includes("\uFFFD")) {
237
+ throw new GitHistoryError("malformed", "Git history output was not valid UTF-8.");
238
+ }
239
+ const tokens = output.split("\x00");
240
+ const records = [];
241
+ let index = 0;
242
+ while (index < tokens.length) {
243
+ let marker = tokens[index] ?? "";
244
+ marker = marker.replace(/^\r?\n/u, "");
245
+ if (marker === "") {
246
+ index += 1;
247
+ continue;
248
+ }
249
+ if (marker !== expectedMarker) {
250
+ throw new GitHistoryError("malformed", "Git history output contained an unexpected record marker.");
251
+ }
252
+ const hash = tokens[index + 1];
253
+ const timestamp = tokens[index + 2];
254
+ const subject = tokens[index + 3];
255
+ if (hash === undefined || timestamp === undefined || subject === undefined || !objectIdPattern.test(hash)) {
256
+ throw new GitHistoryError("malformed", "Git history output contained an incomplete commit record.");
257
+ }
258
+ index += 4;
259
+ const parsedTimestamp = commitTime(timestamp);
260
+ const parsedSubject = normalizedSubject(subject);
261
+ const changedPaths = [];
262
+ const seenPaths = new Set;
263
+ let firstPath = true;
264
+ let limited = false;
265
+ let observedPathRecords = 0;
266
+ while (index < tokens.length) {
267
+ const token = tokens[index] ?? "";
268
+ const possibleMarker = token.replace(/^\r?\n/u, "");
269
+ if (possibleMarker === expectedMarker)
270
+ break;
271
+ index += 1;
272
+ if (token === "" || token === `
273
+ ` || token === `\r
274
+ `)
275
+ continue;
276
+ const rawPath = firstPath ? token.replace(/^\r?\n/u, "") : token;
277
+ if (rawPath === "")
278
+ continue;
279
+ firstPath = false;
280
+ const path = checkedRepositoryPath(rawPath, "Git changed path");
281
+ observedPathRecords += 1;
282
+ pathBudget.observed += 1;
283
+ if (pathBudget.observed > MAX_GIT_PATH_OBSERVATIONS) {
284
+ throw new GitHistoryError("budget", `Git history exceeded the ${MAX_GIT_PATH_OBSERVATIONS} changed-path observation limit.`);
285
+ }
286
+ if (changedPathPolicy.kind === "select") {
287
+ if (!changedPathPolicy.paths.has(path) || seenPaths.has(path))
288
+ continue;
289
+ seenPaths.add(path);
290
+ changedPaths.push(path);
291
+ if (changedPaths.length > MAX_GIT_HISTORY_NOTES) {
292
+ throw new GitHistoryError("budget", `Git history selected more than ${MAX_GIT_HISTORY_NOTES} live-note paths in one commit.`);
293
+ }
294
+ continue;
295
+ }
296
+ if (!limited && seenPaths.has(path))
297
+ continue;
298
+ if (limited)
299
+ continue;
300
+ seenPaths.add(path);
301
+ changedPaths.push(path);
302
+ if (changedPaths.length > MAX_GIT_PATHS_PER_COMMIT) {
303
+ if (changedPathPolicy.kind === "reject") {
304
+ throw new GitHistoryError("budget", `A Git commit exceeded the ${MAX_GIT_PATHS_PER_COMMIT} changed-path limit.`);
305
+ }
306
+ limited = true;
307
+ changedPaths.length = 0;
308
+ seenPaths.clear();
309
+ }
310
+ }
311
+ records.push(limited ? {
312
+ status: "limited",
313
+ limit: Object.freeze({
314
+ hash,
315
+ timestamp: parsedTimestamp,
316
+ subject: parsedSubject,
317
+ reason: "changed-path-limit",
318
+ pathLimit: MAX_GIT_PATHS_PER_COMMIT,
319
+ observedPathRecords
320
+ })
321
+ } : {
322
+ status: "complete",
323
+ commit: Object.freeze({
324
+ hash,
325
+ timestamp: parsedTimestamp,
326
+ subject: parsedSubject,
327
+ changedPaths: Object.freeze(changedPaths)
328
+ })
329
+ });
330
+ if (records.length > MAX_GIT_HISTORY_COMMITS) {
331
+ throw new GitHistoryError("budget", `Git history exceeded the ${MAX_GIT_HISTORY_COMMITS} commit limit.`);
332
+ }
333
+ }
334
+ return Object.freeze(records);
335
+ }
336
+ function parseGitHistoryOutput(output, expectedMarker = commitMarker) {
337
+ return Object.freeze(parseGitHistoryRecords(output, expectedMarker, { kind: "reject" }, { observed: 0 }).map((record) => {
338
+ if (record.status === "limited") {
339
+ throw new GitHistoryError("budget", `A Git commit exceeded the ${MAX_GIT_PATHS_PER_COMMIT} changed-path limit.`);
340
+ }
341
+ return record.commit;
342
+ }));
343
+ }
344
+ function parseSelectedGitHistoryOutput(output, expectedMarker, paths, pathBudget) {
345
+ return Object.freeze(parseGitHistoryRecords(output, expectedMarker, { kind: "select", paths }, pathBudget).map((record) => {
346
+ if (record.status === "limited") {
347
+ throw new GitHistoryError("malformed", "Git live-note history was unexpectedly limited.");
348
+ }
349
+ return record.commit;
350
+ }));
351
+ }
352
+ function historyArguments(hashes, marker) {
353
+ return [
354
+ "show",
355
+ "--no-color",
356
+ "--no-ext-diff",
357
+ "--no-renames",
358
+ "--diff-merges=first-parent",
359
+ `--format=${marker}%x00%H%x00%ct%x00%s%x00`,
360
+ "--name-only",
361
+ "-z",
362
+ ...hashes,
363
+ "--"
364
+ ];
365
+ }
366
+ function logArguments(maxCommits, vaultPrefix, marker, revision) {
367
+ return [
368
+ "--literal-pathspecs",
369
+ "log",
370
+ "--no-color",
371
+ "--no-decorate",
372
+ "--no-renames",
373
+ "--diff-merges=first-parent",
374
+ "--first-parent",
375
+ `--max-count=${maxCommits}`,
376
+ `--format=${marker}%x00%H%x00%ct%x00%s%x00`,
377
+ "--name-only",
378
+ "-z",
379
+ revision,
380
+ "--",
381
+ vaultPrefix === "" ? "." : vaultPrefix
382
+ ];
383
+ }
384
+ function freezeCommit(commit, changedPathDetailsLimited) {
385
+ return Object.freeze({
386
+ hash: commit.hash,
387
+ committedAt: new Date(commit.timestamp * 1000).toISOString(),
388
+ subject: commit.subject,
389
+ changedPaths: commit.changedPaths,
390
+ ...changedPathDetailsLimited ? { changedPathDetailsLimited: true } : {}
391
+ });
392
+ }
393
+ function recordHash(record) {
394
+ return record.status === "complete" ? record.commit.hash : record.limit.hash;
395
+ }
396
+ async function confinedNotes(notes, repository, root, vaultPrefix) {
397
+ if (notes.length > MAX_GIT_HISTORY_NOTES) {
398
+ throw new GitHistoryError("budget", `Git history accepts at most ${MAX_GIT_HISTORY_NOTES} live notes.`);
399
+ }
400
+ const seenIds = new Set;
401
+ const seenPaths = new Set;
402
+ const confined = [];
403
+ for (const note of notes) {
404
+ checkedNoteIdentity(note);
405
+ if (seenIds.has(note.id) || seenPaths.has(note.path)) {
406
+ throw new GitHistoryError("malformed", `Duplicate live note identity ${JSON.stringify(note.id)}.`);
407
+ }
408
+ seenIds.add(note.id);
409
+ seenPaths.add(note.path);
410
+ let resolvedNote;
411
+ try {
412
+ resolvedNote = await realpath(resolve(root, note.path));
413
+ } catch (error) {
414
+ throw new GitHistoryError("confinement", `Live note ${JSON.stringify(note.path)} could not be resolved.`, { cause: error });
415
+ }
416
+ if (!inside(root, resolvedNote) || !inside(repository, resolvedNote)) {
417
+ throw new GitHistoryError("confinement", `Live note ${JSON.stringify(note.path)} resolves outside the vault or repository.`);
418
+ }
419
+ if (!(await stat(resolvedNote)).isFile()) {
420
+ throw new GitHistoryError("confinement", `Live note ${JSON.stringify(note.path)} is not a file.`);
421
+ }
422
+ confined.push(Object.freeze({
423
+ id: note.id,
424
+ path: note.path,
425
+ repositoryPath: vaultPrefix === "" ? note.path : `${vaultPrefix}/${note.path}`
426
+ }));
427
+ }
428
+ return Object.freeze(confined.toSorted((left, right) => left.id.localeCompare(right.id)));
429
+ }
430
+ async function indexGitHistory(options, dependencies = {}) {
431
+ const maxCommits = checkedInteger(options.maxCommits, DEFAULT_COMMITS, 1, MAX_GIT_HISTORY_COMMITS, "Git commit limit");
432
+ const timeoutMs = checkedInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, 1, MAX_GIT_HISTORY_TIMEOUT_MS, "Git timeout");
433
+ const outputLimit = checkedInteger(options.maxOutputBytes, DEFAULT_OUTPUT_BYTES, 1024, MAX_GIT_HISTORY_OUTPUT_BYTES, "Git output limit");
434
+ let repository;
435
+ let root;
436
+ try {
437
+ repository = await realpath(resolve(options.repository));
438
+ root = await realpath(resolve(options.root));
439
+ } catch (error) {
440
+ throw new GitHistoryError("confinement", "Repository and vault roots must resolve.", { cause: error });
441
+ }
442
+ if (!(await stat(repository)).isDirectory() || !(await stat(root)).isDirectory()) {
443
+ throw new GitHistoryError("confinement", "Repository and vault roots must be directories.");
444
+ }
445
+ if (!inside(repository, root)) {
446
+ throw new GitHistoryError("confinement", "Knowledge-base root must resolve inside the repository.");
447
+ }
448
+ const vaultPrefix = posixRelative(repository, root);
449
+ const provider = dependencies.runGit ?? runGitCommand;
450
+ const budget = {
451
+ startedAt: Date.now(),
452
+ timeoutMs,
453
+ outputLimit,
454
+ outputBytes: 0
455
+ };
456
+ const topLevel = await checkedCommand(provider, repository, ["rev-parse", "--show-toplevel"], budget, options.required ?? false, "Git repository discovery");
457
+ if (topLevel.status === "unavailable") {
458
+ return Object.freeze({ ...topLevel, repository, root, vaultPrefix });
459
+ }
460
+ let actualTopLevel;
461
+ try {
462
+ actualTopLevel = await realpath(singleLine(topLevel.stdout, "Git repository root"));
463
+ } catch (error) {
464
+ if (error instanceof GitHistoryError)
465
+ throw error;
466
+ throw new GitHistoryError("malformed", "Git returned an unresolved repository root.", { cause: error });
467
+ }
468
+ if (actualTopLevel !== repository) {
469
+ throw new GitHistoryError("confinement", `Git resolved ${JSON.stringify(actualTopLevel)} instead of the requested repository.`);
470
+ }
471
+ const headResult = await checkedCommand(provider, repository, ["rev-parse", "--verify", "HEAD"], budget, true, "Git HEAD discovery");
472
+ if (headResult.status === "unavailable") {
473
+ throw new GitHistoryError("unavailable", headResult.reason);
474
+ }
475
+ const head = singleLine(headResult.stdout, "Git HEAD");
476
+ if (!objectIdPattern.test(head)) {
477
+ throw new GitHistoryError("malformed", "Git returned a malformed HEAD object ID.");
478
+ }
479
+ const liveNotes = await confinedNotes(options.notes, repository, root, vaultPrefix);
480
+ if (liveNotes.length === 0) {
481
+ return Object.freeze({
482
+ status: "ready",
483
+ repository,
484
+ root,
485
+ vaultPrefix,
486
+ head,
487
+ scannedCommits: 0,
488
+ notes: Object.freeze([])
489
+ });
490
+ }
491
+ const marker = `${commitMarker}-${randomUUID()}`;
492
+ const pathBudget = { observed: 0 };
493
+ const metadataResult = await checkedCommand(provider, repository, logArguments(maxCommits, vaultPrefix, marker, head), budget, true, "Git vault history");
494
+ if (metadataResult.status === "unavailable") {
495
+ throw new GitHistoryError("unavailable", metadataResult.reason);
496
+ }
497
+ const noteIdByRepositoryPath = new Map(liveNotes.map(({ id, repositoryPath }) => [repositoryPath, id]));
498
+ const vaultCommits = parseSelectedGitHistoryOutput(metadataResult.stdout, marker, new Set(noteIdByRepositoryPath.keys()), pathBudget);
499
+ const detailsResult = vaultCommits.length === 0 ? null : await checkedCommand(provider, repository, historyArguments(vaultCommits.map(({ hash }) => hash), marker), budget, true, "Git commit path history");
500
+ if (detailsResult?.status === "unavailable") {
501
+ throw new GitHistoryError("unavailable", detailsResult.reason);
502
+ }
503
+ const detailRecords = detailsResult === null ? [] : parseGitHistoryRecords(detailsResult.stdout, marker, { kind: "limit" }, pathBudget);
504
+ if (detailRecords.length !== vaultCommits.length || detailRecords.some((record, index) => recordHash(record) !== vaultCommits[index]?.hash)) {
505
+ throw new GitHistoryError("malformed", "Git returned inconsistent vault and changed-path histories.");
506
+ }
507
+ const commits = [];
508
+ const limitedCommits = [];
509
+ detailRecords.forEach((detailRecord, index) => {
510
+ const vaultCommit = vaultCommits[index];
511
+ if (detailRecord.status === "complete") {
512
+ commits.push(freezeCommit(detailRecord.commit, false));
513
+ return;
514
+ }
515
+ const affectedNoteIds = Object.freeze(vaultCommit.changedPaths.map((path) => {
516
+ const noteId = noteIdByRepositoryPath.get(path);
517
+ if (noteId === undefined) {
518
+ throw new GitHistoryError("malformed", "Git selected a path outside the live-note set.");
519
+ }
520
+ return noteId;
521
+ }));
522
+ commits.push(freezeCommit({
523
+ hash: detailRecord.limit.hash,
524
+ timestamp: detailRecord.limit.timestamp,
525
+ subject: detailRecord.limit.subject,
526
+ changedPaths: vaultCommit.changedPaths
527
+ }, true));
528
+ limitedCommits.push(Object.freeze({
529
+ hash: detailRecord.limit.hash,
530
+ committedAt: new Date(detailRecord.limit.timestamp * 1000).toISOString(),
531
+ subject: detailRecord.limit.subject,
532
+ reason: detailRecord.limit.reason,
533
+ pathLimit: detailRecord.limit.pathLimit,
534
+ observedPathRecords: detailRecord.limit.observedPathRecords,
535
+ affectedNoteIds
536
+ }));
537
+ });
538
+ const commitsByPath = new Map;
539
+ for (const commit of commits) {
540
+ for (const path of commit.changedPaths) {
541
+ const existing = commitsByPath.get(path);
542
+ if (existing === undefined)
543
+ commitsByPath.set(path, [commit]);
544
+ else
545
+ existing.push(commit);
546
+ }
547
+ }
548
+ const indexedNotes = liveNotes.map((note) => Object.freeze({
549
+ ...note,
550
+ commits: Object.freeze([...commitsByPath.get(note.repositoryPath) ?? []])
551
+ }));
552
+ return Object.freeze({
553
+ status: "ready",
554
+ repository,
555
+ root,
556
+ vaultPrefix,
557
+ head,
558
+ scannedCommits: detailRecords.length,
559
+ notes: Object.freeze(indexedNotes),
560
+ ...limitedCommits.length === 0 ? {} : { limitedCommits: Object.freeze(limitedCommits) }
561
+ });
562
+ }
563
+ function limitedCommitsForNotes(limitedCommits, noteIds, commitHashes) {
564
+ if (limitedCommits === undefined || limitedCommits.length === 0)
565
+ return Object.freeze([]);
566
+ return Object.freeze(limitedCommits.flatMap((commit) => {
567
+ if (commitHashes !== null && !commitHashes.has(commit.hash))
568
+ return [];
569
+ const affectedNoteIds = Object.freeze(commit.affectedNoteIds.filter((id) => noteIds.has(id)));
570
+ return affectedNoteIds.length === 0 ? [] : [Object.freeze({ ...commit, affectedNoteIds })];
571
+ }));
572
+ }
573
+ function validateGitHistoryForNotesOptions(options = {}) {
574
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
575
+ throw new GitHistoryError("malformed", "Git history options must be an object.");
576
+ }
577
+ return Object.freeze({
578
+ commitsPerNote: checkedInteger(options.commitsPerNote, DEFAULT_COMMITS_PER_NOTE, 1, MAX_COMMITS_PER_NOTE, "Per-note commit limit"),
579
+ cochangedPathsPerCommit: checkedInteger(options.cochangedPathsPerCommit, DEFAULT_COCHANGED_PATHS, 0, MAX_COCHANGED_PATHS, "Cochanged-path limit")
580
+ });
581
+ }
582
+ function isIterableObject(value) {
583
+ return typeof value === "object" && value !== null && Symbol.iterator in value && typeof value[Symbol.iterator] === "function";
584
+ }
585
+ function checkedNoteIds(value, label, countMessage) {
586
+ if (!Array.isArray(value) && !isIterableObject(value)) {
587
+ throw new GitHistoryError("malformed", `${label}s must be provided as an array or set.`);
588
+ }
589
+ const noteIds = [];
590
+ let noteIdBytes = 0;
591
+ for (const noteId of value) {
592
+ if (noteIds.length >= MAX_GIT_HISTORY_NOTES) {
593
+ throw new GitHistoryError("budget", countMessage);
594
+ }
595
+ if (typeof noteId !== "string") {
596
+ throw new GitHistoryError("malformed", `${label} ${noteIds.length + 1} must be a string.`);
597
+ }
598
+ const noteIdByteLength = Buffer.byteLength(noteId, "utf8");
599
+ if (noteIdByteLength > MAX_GIT_NOTE_ID_UTF8_BYTES) {
600
+ throw new GitHistoryError("budget", `${label} ${noteIds.length + 1} must be at most ` + `${MAX_GIT_NOTE_ID_UTF8_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
601
+ }
602
+ noteIdBytes += noteIdByteLength;
603
+ if (noteIdBytes > MAX_GIT_NOTE_IDS_UTF8_BYTES) {
604
+ throw new GitHistoryError("budget", `${label}s must total at most ` + `${MAX_GIT_NOTE_IDS_UTF8_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
605
+ }
606
+ noteIds.push(noteId);
607
+ }
608
+ return Object.freeze(noteIds);
609
+ }
610
+ function validateGitHistoryForNotesRequest(noteIds, options = {}) {
611
+ return Object.freeze({
612
+ noteIds: checkedNoteIds(noteIds, "Git history note ID", `At most ${MAX_GIT_HISTORY_NOTES} note IDs may be requested.`),
613
+ options: validateGitHistoryForNotesOptions(options)
614
+ });
615
+ }
616
+ function noteCommit(commit, notePath, cochangedPathsPerCommit) {
617
+ return Object.freeze({
618
+ hash: commit.hash,
619
+ committedAt: commit.committedAt,
620
+ subject: commit.subject,
621
+ cochangedPaths: Object.freeze(commit.changedPaths.filter((path) => path !== notePath).slice(0, cochangedPathsPerCommit)),
622
+ ...commit.changedPathDetailsLimited === true ? { cochangeDetailsLimited: true } : {}
623
+ });
624
+ }
625
+ function gitHistoryForNotes(index, noteIds, options = {}) {
626
+ const request = validateGitHistoryForNotesRequest(noteIds, options);
627
+ if (index.status === "unavailable")
628
+ return index;
629
+ const allowed = new Set(request.noteIds);
630
+ const notes = index.notes.filter((note) => allowed.has(note.id)).map((note) => Object.freeze({
631
+ id: note.id,
632
+ path: note.path,
633
+ commits: Object.freeze(note.commits.slice(0, request.options.commitsPerNote).map((commit) => noteCommit(commit, note.repositoryPath, request.options.cochangedPathsPerCommit)))
634
+ }));
635
+ const returnedNoteIds = new Set(notes.map(({ id }) => id));
636
+ const returnedCommitHashes = new Set(notes.flatMap(({ commits }) => commits.map(({ hash }) => hash)));
637
+ const limitedCommits = limitedCommitsForNotes(index.limitedCommits, returnedNoteIds, returnedCommitHashes);
638
+ return Object.freeze({
639
+ status: "ready",
640
+ head: index.head,
641
+ notes: Object.freeze(notes),
642
+ ...limitedCommits.length === 0 ? {} : { limitedCommits }
643
+ });
644
+ }
645
+ function normalizedSearch(value) {
646
+ return value.normalize("NFC").toLocaleLowerCase("en-US");
647
+ }
648
+ function queryTerms(query) {
649
+ if (typeof query !== "string") {
650
+ throw new GitHistoryError("malformed", `Git history query must be one to ${MAX_QUERY_LENGTH} characters on one line.`);
651
+ }
652
+ const trimmed = query.trim();
653
+ if (trimmed === "" || trimmed.length > MAX_QUERY_LENGTH || /[\0\r\n]/u.test(trimmed)) {
654
+ throw new GitHistoryError("malformed", `Git history query must be one to ${MAX_QUERY_LENGTH} characters on one line.`);
655
+ }
656
+ const normalized = normalizedSearch(trimmed);
657
+ const terms = [...new Set(normalized.split(/[^\p{L}\p{N}_./-]+/u).filter(Boolean))].slice(0, 20);
658
+ return { normalized, terms: Object.freeze(terms) };
659
+ }
660
+ function validateSearchGitHistoryOptions(options) {
661
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
662
+ throw new GitHistoryError("malformed", "Git history search options must be an object.");
663
+ }
664
+ const limit = checkedInteger(options.limit, DEFAULT_SEARCH_LIMIT, 1, MAX_SEARCH_LIMIT, "Git search limit");
665
+ const provenance = validateGitHistoryForNotesOptions({
666
+ ...options.commitsPerHit === undefined ? {} : { commitsPerNote: options.commitsPerHit },
667
+ ...options.cochangedPathsPerCommit === undefined ? {} : { cochangedPathsPerCommit: options.cochangedPathsPerCommit }
668
+ });
669
+ const { normalized, terms } = queryTerms(options.query);
670
+ const checkedAllowed = options.allowedNoteIds === undefined ? null : checkedNoteIds(options.allowedNoteIds, "Git search allowed note ID", `Git search accepts at most ${MAX_GIT_HISTORY_NOTES} allowed note IDs.`);
671
+ return Object.freeze({
672
+ query: options.query.trim(),
673
+ normalizedQuery: normalized,
674
+ terms,
675
+ allowedNoteIds: checkedAllowed === null ? null : new Set(checkedAllowed),
676
+ limit,
677
+ commitsPerHit: provenance.commitsPerNote,
678
+ cochangedPathsPerCommit: provenance.cochangedPathsPerCommit
679
+ });
680
+ }
681
+ function matchStrength(value, normalizedQuery, terms) {
682
+ const normalized = normalizedSearch(value);
683
+ if (normalized.includes(normalizedQuery))
684
+ return 2 + normalizedQuery.length / 1000;
685
+ if (terms.length === 0)
686
+ return 0;
687
+ const matched = terms.filter((term) => normalized.includes(term)).length;
688
+ return matched / terms.length;
689
+ }
690
+ function scoredCommits(note, normalizedQuery, terms) {
691
+ const matches = [];
692
+ note.commits.forEach((commit, index) => {
693
+ const subjectStrength = matchStrength(commit.subject, normalizedQuery, terms);
694
+ const matchingPaths = commit.changedPaths.map((path) => ({ path, strength: matchStrength(path, normalizedQuery, terms) })).filter(({ strength }) => strength > 0).toSorted((left, right) => right.strength - left.strength || left.path.localeCompare(right.path));
695
+ const bestPath = matchingPaths[0]?.strength ?? 0;
696
+ if (subjectStrength === 0 && bestPath === 0)
697
+ return;
698
+ const recency = 1 / (100 + index);
699
+ matches.push({
700
+ score: subjectStrength * 4 + bestPath * 2 + recency,
701
+ commit,
702
+ matchedSubject: subjectStrength > 0,
703
+ matchedPaths: Object.freeze(matchingPaths.map(({ path }) => path))
704
+ });
705
+ });
706
+ return Object.freeze(matches.toSorted((left, right) => right.score - left.score || left.commit.hash.localeCompare(right.commit.hash)));
707
+ }
708
+ function searchGitHistory(index, options) {
709
+ const request = validateSearchGitHistoryOptions(options);
710
+ if (index.status === "unavailable")
711
+ return index;
712
+ const hits = [];
713
+ for (const note of index.notes) {
714
+ if (request.allowedNoteIds !== null && !request.allowedNoteIds.has(note.id))
715
+ continue;
716
+ const matches = scoredCommits(note, request.normalizedQuery, request.terms);
717
+ if (matches.length === 0)
718
+ continue;
719
+ const score = matches.reduce((total, match, matchIndex) => total + match.score / (matchIndex + 1), 0);
720
+ hits.push(Object.freeze({
721
+ id: note.id,
722
+ path: note.path,
723
+ score: Number(score.toFixed(6)),
724
+ commits: Object.freeze(matches.slice(0, request.commitsPerHit).map((match) => Object.freeze({
725
+ ...noteCommit(match.commit, note.repositoryPath, request.cochangedPathsPerCommit),
726
+ matchedSubject: match.matchedSubject,
727
+ matchedPaths: Object.freeze(match.matchedPaths.slice(0, request.cochangedPathsPerCommit))
728
+ })))
729
+ }));
730
+ }
731
+ hits.sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
732
+ const searchableNoteIds = new Set(index.notes.flatMap(({ id }) => request.allowedNoteIds === null || request.allowedNoteIds.has(id) ? [id] : []));
733
+ const limitedCommits = limitedCommitsForNotes(index.limitedCommits, searchableNoteIds, null);
734
+ return Object.freeze({
735
+ status: "ready",
736
+ head: index.head,
737
+ query: request.query,
738
+ hits: Object.freeze(hits.slice(0, request.limit)),
739
+ ...limitedCommits.length === 0 ? {} : { limitedCommits }
740
+ });
741
+ }
742
+
743
+ export { MAX_GIT_HISTORY_COMMITS, MAX_GIT_HISTORY_NOTES, MAX_GIT_HISTORY_OUTPUT_BYTES, MAX_GIT_HISTORY_TIMEOUT_MS, MAX_GIT_PATHS_PER_COMMIT, MAX_GIT_PATH_OBSERVATIONS, MAX_GIT_NOTE_ID_UTF8_BYTES, MAX_GIT_NOTE_IDS_UTF8_BYTES, GitHistoryError, runGitCommand, parseGitHistoryOutput, indexGitHistory, validateGitHistoryForNotesOptions, validateGitHistoryForNotesRequest, gitHistoryForNotes, validateSearchGitHistoryOptions, searchGitHistory };