@orquentra/codebuddy 0.0.0-stage → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/ARCHITECTURE.md +269 -0
  2. package/CLAUDE_CODEX.md +433 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1118 -2
  5. package/WORKFLOW.md +152 -0
  6. package/dist/chunk-3ZELDCR3.js +204 -0
  7. package/dist/chunk-3ZELDCR3.js.map +1 -0
  8. package/dist/chunk-5OWZVZSO.js +476 -0
  9. package/dist/chunk-5OWZVZSO.js.map +1 -0
  10. package/dist/chunk-DSN5CEHO.js +175 -0
  11. package/dist/chunk-DSN5CEHO.js.map +1 -0
  12. package/dist/chunk-GDJH7XJC.js +119 -0
  13. package/dist/chunk-GDJH7XJC.js.map +1 -0
  14. package/dist/chunk-IBMJYIEP.js +1578 -0
  15. package/dist/chunk-IBMJYIEP.js.map +1 -0
  16. package/dist/chunk-IE2OUO54.js +199 -0
  17. package/dist/chunk-IE2OUO54.js.map +1 -0
  18. package/dist/chunk-IHQM2ZDV.js +19 -0
  19. package/dist/chunk-IHQM2ZDV.js.map +1 -0
  20. package/dist/chunk-ITGRANYW.js +63 -0
  21. package/dist/chunk-ITGRANYW.js.map +1 -0
  22. package/dist/chunk-K63EOA4B.js +416 -0
  23. package/dist/chunk-K63EOA4B.js.map +1 -0
  24. package/dist/chunk-PQK3GKZH.js +98 -0
  25. package/dist/chunk-PQK3GKZH.js.map +1 -0
  26. package/dist/chunk-PYLH4MH5.js +34 -0
  27. package/dist/chunk-PYLH4MH5.js.map +1 -0
  28. package/dist/chunk-PZ5AY32C.js +10 -0
  29. package/dist/chunk-PZ5AY32C.js.map +1 -0
  30. package/dist/chunk-SYHB3AMM.js +8580 -0
  31. package/dist/chunk-SYHB3AMM.js.map +1 -0
  32. package/dist/chunk-TI5TQD6D.js +324 -0
  33. package/dist/chunk-TI5TQD6D.js.map +1 -0
  34. package/dist/chunk-WHZSVY2L.js +144 -0
  35. package/dist/chunk-WHZSVY2L.js.map +1 -0
  36. package/dist/cli/index.d.ts +1 -0
  37. package/dist/cli/index.js +15 -0
  38. package/dist/cli/index.js.map +1 -0
  39. package/dist/codebuddy-D2xJ6hFk.d.ts +841 -0
  40. package/dist/config-S2TULOMK.js +11 -0
  41. package/dist/config-S2TULOMK.js.map +1 -0
  42. package/dist/index.d.ts +2527 -0
  43. package/dist/index.js +116 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/langgraph/index.d.ts +53 -0
  46. package/dist/langgraph/index.js +143 -0
  47. package/dist/langgraph/index.js.map +1 -0
  48. package/dist/lean-command-D6TZJEFX.js +13 -0
  49. package/dist/lean-command-D6TZJEFX.js.map +1 -0
  50. package/dist/main-ZZ3EQLYA.js +4575 -0
  51. package/dist/main-ZZ3EQLYA.js.map +1 -0
  52. package/dist/memory-NIBFAMY4.js +57 -0
  53. package/dist/memory-NIBFAMY4.js.map +1 -0
  54. package/dist/pretool-command-MQQ2UNQM.js +10 -0
  55. package/dist/pretool-command-MQQ2UNQM.js.map +1 -0
  56. package/dist/rerank-Q7MIFNKA.js +60 -0
  57. package/dist/rerank-Q7MIFNKA.js.map +1 -0
  58. package/dist/summaries-TSE44KUQ.js +18 -0
  59. package/dist/summaries-TSE44KUQ.js.map +1 -0
  60. package/dist/zvec-F27AR357.js +23 -0
  61. package/dist/zvec-F27AR357.js.map +1 -0
  62. package/docker-compose.yml +20 -0
  63. package/examples/claude-code/README.md +67 -0
  64. package/examples/claude-desktop/README.md +54 -0
  65. package/examples/claude-desktop/claude_desktop_config.json +13 -0
  66. package/examples/langgraph-agent/README.md +32 -0
  67. package/examples/langgraph-agent/index.ts +71 -0
  68. package/examples/multi-agent-handoff/README.md +35 -0
  69. package/examples/multi-agent-handoff/index.ts +68 -0
  70. package/package.json +108 -4
  71. package/src/db/migrations/0000_cultured_satana.sql +144 -0
  72. package/src/db/migrations/0001_embedding_lease.sql +3 -0
  73. package/src/db/migrations/0002_embedding_lease_token.sql +1 -0
  74. package/src/db/migrations/0003_embedding_space.sql +9 -0
  75. package/src/db/migrations/0004_markdown_reconcile.sql +5 -0
  76. package/src/db/migrations/0005_fact_propositions.sql +1 -0
  77. package/src/db/migrations/meta/0000_snapshot.json +1169 -0
  78. package/src/db/migrations/meta/0002_snapshot.json +1181 -0
  79. package/src/db/migrations/meta/0003_snapshot.json +1214 -0
  80. package/src/db/migrations/meta/0004_snapshot.json +1225 -0
  81. package/src/db/migrations/meta/0005_snapshot.json +1231 -0
  82. package/src/db/migrations/meta/_journal.json +48 -0
@@ -0,0 +1,324 @@
1
+ import {
2
+ readConfigFile,
3
+ writeAtomic
4
+ } from "./chunk-K63EOA4B.js";
5
+
6
+ // src/retrieval/zvec.ts
7
+ import { execFile, spawn } from "child_process";
8
+ import { mkdir, readFile, rm, stat } from "fs/promises";
9
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
10
+ var SUPPORTED = /^0\.2\.\d+$/;
11
+ var DEFAULT_TIMEOUT_MS = 1500;
12
+ var READY_CACHE_MS = 10 * 6e4;
13
+ var NOT_READY_CACHE_MS = 3e4;
14
+ var REFRESH_TIMEOUT_MS = 10 * 6e4;
15
+ var DEFAULT_ZVEC_MODEL = "local/potion-code-16m-v2";
16
+ var DOC_GLOBS = ["!**/*.md", "!**/*.mdx", "!docs/**"];
17
+ var TEST_GLOBS = [
18
+ "!**/*.test.*",
19
+ "!**/*.spec.*",
20
+ "!**/__tests__/**",
21
+ "!**/test/**",
22
+ "!**/tests/**"
23
+ ];
24
+ var ALWAYS_EXCLUDED_GLOBS = ["!evals/**", "!scripts/**", "!.codebuddy/**", "!**/fixtures/**"];
25
+ function zvecExcludeGlobs(task) {
26
+ return [
27
+ .../\b(docs?|readme|documentation|markdown)\b/i.test(task) ? [] : DOC_GLOBS,
28
+ .../\b(tests?|spec)\b/i.test(task) ? [] : TEST_GLOBS,
29
+ ...ALWAYS_EXCLUDED_GLOBS
30
+ ];
31
+ }
32
+ var EXCLUDED_PREFIXES = [".codebuddy/cache/", ".zvec-grep/", ".git/", "node_modules/"];
33
+ async function zvecCandidates(repositoryRoot, task, options = {}) {
34
+ const root = resolve(repositoryRoot);
35
+ const config = await zvecConfig(root);
36
+ if (!config.enabled) return none("disabled", "zvec-grep not enabled");
37
+ const run = (args) => runZg(config.binary, args, root, config.timeoutMs);
38
+ const cached = await readCachedReadiness(root, config.binary);
39
+ if (cached !== "ready") {
40
+ if (cached) return cached;
41
+ const readiness = await checkReadiness(run);
42
+ await markReadiness(root, config.binary, readiness);
43
+ if (readiness) return readiness;
44
+ }
45
+ const limit = Math.min(20, Math.max(1, Math.floor(options.limit ?? 10)));
46
+ const chunkLimit = limit * 8;
47
+ const globs = zvecExcludeGlobs(task);
48
+ const result = await run([
49
+ "query",
50
+ "--vector",
51
+ task,
52
+ "--mode",
53
+ "auto",
54
+ "--refresh",
55
+ "off",
56
+ "--limit",
57
+ String(chunkLimit),
58
+ ...globs.flatMap((glob) => ["-g", glob])
59
+ ]);
60
+ if (result.error) {
61
+ await resetZvecCache(root);
62
+ return fromError(result.error, "zvec-grep query");
63
+ }
64
+ const hits = parseHits(result.stdout).slice(0, chunkLimit);
65
+ if (hits.length === 0 && /\bzg index\b/.test(result.stdout)) {
66
+ const notReady = none("index-not-ready", NOT_READY);
67
+ await markReadiness(root, config.binary, notReady);
68
+ return notReady;
69
+ }
70
+ const indexedAt = await readIndexedAt(root);
71
+ const paths = [];
72
+ const candidates = [];
73
+ const seen = /* @__PURE__ */ new Set();
74
+ let stale = 0;
75
+ let changed = 0;
76
+ for (const hit of hits) {
77
+ const path = toRepoRelative(root, hit.path);
78
+ if (!path) continue;
79
+ if (seen.has(path)) {
80
+ if (paths.includes(path)) candidates.push({ ...hit, path });
81
+ continue;
82
+ }
83
+ if (paths.length >= limit) continue;
84
+ seen.add(path);
85
+ if (EXCLUDED_PREFIXES.some((prefix) => path.startsWith(prefix))) continue;
86
+ try {
87
+ const info = await stat(resolve(root, path));
88
+ if (!info.isFile()) continue;
89
+ if (indexedAt !== null && Math.max(info.mtimeMs, info.ctimeMs) > indexedAt) {
90
+ changed++;
91
+ continue;
92
+ }
93
+ } catch {
94
+ stale++;
95
+ continue;
96
+ }
97
+ paths.push(path);
98
+ candidates.push({ ...hit, path });
99
+ }
100
+ return {
101
+ status: "ok",
102
+ paths,
103
+ hits: candidates,
104
+ stale,
105
+ changed,
106
+ reason: `zvec-grep vector search: ${paths.length} file(s)` + (globs.length > 0 ? "; non-source paths excluded" : "") + (stale > 0 ? `, ${stale} stale hit(s) dropped` : "") + (changed > 0 ? `, ${changed} hit(s) changed since the zg index dropped` : "") + (indexedAt === null ? "; index age unknown, hits unverified (run `codebuddy zvec index`)" : "")
107
+ };
108
+ }
109
+ async function refreshZvecIndex(repositoryRoot, options = {}) {
110
+ const root = resolve(repositoryRoot);
111
+ const config = await zvecConfig(root);
112
+ if (!config.enabled) return { status: "disabled", reason: "zvec-grep not enabled" };
113
+ const readiness = await checkReadiness(
114
+ (args) => runZg(config.binary, args, root, config.timeoutMs)
115
+ );
116
+ if (readiness) return { status: readiness.status, reason: readiness.reason };
117
+ const startedAt = Date.now();
118
+ const result = await runZg(
119
+ config.binary,
120
+ ["index", root, "--mode", "auto"],
121
+ root,
122
+ REFRESH_TIMEOUT_MS,
123
+ options.signal
124
+ );
125
+ if (result.error) {
126
+ const failure = fromError(result.error, "zvec-grep refresh");
127
+ return { status: failure.status, reason: failure.reason };
128
+ }
129
+ await markIndexed(root, startedAt);
130
+ return { status: "ok", reason: "zvec-grep index refreshed" };
131
+ }
132
+ function parseHits(output) {
133
+ return [...output.matchAll(/^#(\d+) matchedBy=\S+ (.+?):(\d+)(?:-(\d+))?\s*$/gm)].flatMap((match) => {
134
+ const rank = Number(match[1]);
135
+ const startLine = Number(match[3]);
136
+ const endLine = Number(match[4] ?? match[3]);
137
+ if (startLine < 1 || endLine < startLine) return [];
138
+ return [
139
+ {
140
+ path: match[2],
141
+ rank,
142
+ startLine,
143
+ endLine,
144
+ source: "zvec",
145
+ freshness: "unverified"
146
+ }
147
+ ];
148
+ }).sort((left, right) => left.rank - right.rank);
149
+ }
150
+ async function resetZvecCache(repositoryRoot) {
151
+ await rm(readyPath(resolve(repositoryRoot)), { force: true });
152
+ }
153
+ async function zvecStatus(repositoryRoot) {
154
+ const root = resolve(repositoryRoot);
155
+ const config = await zvecConfig(root);
156
+ const run = (args) => runZg(config.binary, args, root, Math.max(config.timeoutMs, 5e3));
157
+ const version = await run(["version"]);
158
+ const found = version.error ? null : version.stdout.trim().split(/\s+/).pop() ?? null;
159
+ const supported = found !== null && SUPPORTED.test(found);
160
+ const readiness = supported ? await checkReadiness(run) : null;
161
+ const server = supported ? await run(["server", "status"]) : null;
162
+ return {
163
+ enabled: config.enabled,
164
+ binary: config.binary,
165
+ version: found,
166
+ supported,
167
+ indexReady: supported && readiness === null,
168
+ server: server === null || server.error ? "unknown" : /running|ready/i.test(server.stdout) ? "running" : "stopped",
169
+ reason: version.error ? fromError(version.error, "zvec-grep version check").reason : !supported ? `zvec-grep ${found ?? "unknown"} is not 0.2.x` : readiness?.reason ?? "ready"
170
+ };
171
+ }
172
+ async function buildZvecIndex(repositoryRoot, options = {}) {
173
+ const root = resolve(repositoryRoot);
174
+ const config = await zvecConfig(root);
175
+ const args = [
176
+ "index",
177
+ root,
178
+ "--embedding",
179
+ options.model ?? DEFAULT_ZVEC_MODEL,
180
+ "--mode",
181
+ "auto"
182
+ ];
183
+ if (options.rebuild) args.push("--rebuild");
184
+ const startedAt = Date.now();
185
+ const code = await new Promise((resolveRun, reject) => {
186
+ const child = spawn(config.binary, args, {
187
+ cwd: root,
188
+ stdio: options.quiet ? "ignore" : "inherit"
189
+ });
190
+ child.on("error", reject);
191
+ child.on("close", (exit) => resolveRun(exit ?? 1));
192
+ });
193
+ await resetZvecCache(root);
194
+ if (code === 0) await markIndexed(root, startedAt);
195
+ return code;
196
+ }
197
+ var NOT_READY = "zvec-grep index missing or not ready; run `codebuddy zvec index`";
198
+ async function checkReadiness(run) {
199
+ const version = await run(["version"]);
200
+ if (version.error) return fromError(version.error, "zvec-grep version check");
201
+ const found = version.stdout.trim().split(/\s+/).pop() ?? "";
202
+ if (!SUPPORTED.test(found)) {
203
+ return none("unsupported-version", `zvec-grep ${found || "unknown"} is not 0.2.x`);
204
+ }
205
+ const ready = await run(["status", "--check-ready", "--mode", "auto"]);
206
+ if (!ready.error) return null;
207
+ return ready.error.code === "ETIMEDOUT" ? none("timeout", "zvec-grep status timed out") : none("index-not-ready", NOT_READY);
208
+ }
209
+ function readyPath(root) {
210
+ return join(root, ".codebuddy", "cache", "zvec-ready.json");
211
+ }
212
+ async function readCachedReadiness(root, binary) {
213
+ let cached;
214
+ try {
215
+ cached = JSON.parse(await readFile(readyPath(root), "utf8"));
216
+ } catch {
217
+ return null;
218
+ }
219
+ const checkedAt = Date.parse(cached.checkedAt ?? "");
220
+ const age = Date.now() - checkedAt;
221
+ if (cached.binary !== binary || !(age >= 0)) return null;
222
+ if (!cached.failure) return age <= READY_CACHE_MS ? "ready" : null;
223
+ if (age > NOT_READY_CACHE_MS) return null;
224
+ const workspace = await stat(join(root, ".zvec-grep")).catch(() => null);
225
+ if (workspace && workspace.mtimeMs > checkedAt) return null;
226
+ const { status, reason } = cached.failure;
227
+ return none(status, `${reason}; cached check from ${Math.round(age / 1e3)}s ago`);
228
+ }
229
+ async function markReadiness(root, binary, failure) {
230
+ try {
231
+ const path = readyPath(root);
232
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
233
+ const record = {
234
+ binary,
235
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
236
+ ...failure ? { failure: { status: failure.status, reason: failure.reason } } : {}
237
+ };
238
+ await writeAtomic(path, `${JSON.stringify(record)}
239
+ `);
240
+ } catch {
241
+ }
242
+ }
243
+ function indexedPath(root) {
244
+ return join(root, ".codebuddy", "cache", "zvec-index.json");
245
+ }
246
+ async function readIndexedAt(root) {
247
+ try {
248
+ const parsed = JSON.parse(await readFile(indexedPath(root), "utf8"));
249
+ const at = Date.parse(parsed.indexedAt ?? "");
250
+ return Number.isFinite(at) ? at : null;
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+ async function markIndexed(root, at) {
256
+ try {
257
+ const path = indexedPath(root);
258
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
259
+ await writeAtomic(path, `${JSON.stringify({ indexedAt: new Date(at).toISOString() })}
260
+ `);
261
+ } catch {
262
+ }
263
+ }
264
+ async function zvecConfig(root) {
265
+ let file = {};
266
+ try {
267
+ file = await readConfigFile(root);
268
+ } catch {
269
+ }
270
+ const env = process.env.CODEBUDDY_ZVEC;
271
+ return {
272
+ enabled: env !== void 0 ? env === "1" || env === "true" : file.zvec?.enabled ?? false,
273
+ binary: file.zvec?.binary ?? "zg",
274
+ timeoutMs: file.zvec?.timeoutMs ?? DEFAULT_TIMEOUT_MS
275
+ };
276
+ }
277
+ function runZg(binary, args, cwd, timeoutMs, signal) {
278
+ return new Promise((resolveRun) => {
279
+ execFile(
280
+ binary,
281
+ args,
282
+ {
283
+ cwd,
284
+ timeout: timeoutMs,
285
+ maxBuffer: 1024 * 1024,
286
+ killSignal: "SIGKILL",
287
+ ...signal ? { signal } : {}
288
+ },
289
+ (error, stdout) => {
290
+ if (error && error.killed) {
291
+ resolveRun({ stdout: "", error: Object.assign(error, { code: "ETIMEDOUT" }) });
292
+ return;
293
+ }
294
+ resolveRun({ stdout: String(stdout), error });
295
+ }
296
+ );
297
+ });
298
+ }
299
+ function fromError(error, what) {
300
+ if (error.code === "ENOENT") return none("not-installed", "zvec-grep (`zg`) not found on PATH");
301
+ if (error.code === "ETIMEDOUT") return none("timeout", `${what} timed out`);
302
+ return none("failed", `${what} failed`);
303
+ }
304
+ function none(status, reason) {
305
+ return { status, paths: [], hits: [], reason, stale: 0, changed: 0 };
306
+ }
307
+ function toRepoRelative(root, path) {
308
+ const absolute = isAbsolute(path) ? path : resolve(root, path);
309
+ const rel = relative(root, absolute);
310
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;
311
+ return rel.split(sep).join("/");
312
+ }
313
+
314
+ export {
315
+ DEFAULT_ZVEC_MODEL,
316
+ zvecExcludeGlobs,
317
+ zvecCandidates,
318
+ refreshZvecIndex,
319
+ parseHits,
320
+ resetZvecCache,
321
+ zvecStatus,
322
+ buildZvecIndex
323
+ };
324
+ //# sourceMappingURL=chunk-TI5TQD6D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/retrieval/zvec.ts"],"sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { mkdir, readFile, rm, stat } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { readConfigFile } from \"../core/config-file.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\n\n/**\n * Optional semantic candidate source backed by zvec-grep (`zg`), a local\n * code-search CLI. It only *suggests* files: hits are normalized to\n * repository-relative paths, checked against the working tree, and fused with\n * the keyword ranking by the caller. Risk, policy and incident evidence are\n * never derived from it.\n *\n * Off unless `.codebuddy/config.json` sets `zvec.enabled` (or\n * `CODEBUDDY_ZVEC=1`). It never builds an index implicitly: an unready index,\n * a missing or unsupported binary, a timeout, or unparseable output all return\n * no candidates plus a reason, and callers fall back to keyword ranking.\n *\n * Queries exclude non-source paths (docs, Markdown, evals, scripts, tests,\n * CodeBuddy's own state) unless the task asks about them: otherwise prose that\n * quotes a task, such as audits or eval prompts, outranks the code it describes.\n *\n * Benchmarked: vector-only queries beat zg's default hybrid route on code\n * tasks, so only `--vector` is used. Results are parsed from zg's agent\n * markdown (`#N matchedBy=… path:start-end`), as 0.2.x has no JSON output.\n *\n * Queries use `--mode auto`: a running `zg server` answers in ~200ms, otherwise\n * zg searches directly (~400ms). Auto mode never starts the daemon. The\n * version/readiness check (~400ms) is cached on disk for a few minutes so the\n * per-prompt hook process doesn't repeat it. A failed check (not installed,\n * index not ready, timeout) is cached too, but only for 30 s and only until the\n * workspace's `.zvec-grep/` changes, so an unready index costs one probe per\n * window instead of one per call while a fresh `zg index` is still picked up.\n *\n * Freshness: queries keep `--refresh off` so the prompt path never waits on\n * embedding. Instead `refreshZvecIndex` (run by project sync after index\n * passes, off the request path) and `codebuddy zvec index` record when the zg\n * index was last brought up to date, and hits whose file changed after that are\n * dropped rather than presented with their old meaning.\n */\n\n/** zvec-grep versions whose CLI and output format this adapter was tested against. */\nconst SUPPORTED = /^0\\.2\\.\\d+$/;\nconst DEFAULT_TIMEOUT_MS = 1_500;\nconst READY_CACHE_MS = 10 * 60_000;\n/** A failed readiness check is reused this long, so a fresh `zg index` is noticed quickly. */\nconst NOT_READY_CACHE_MS = 30_000;\n/** An incremental `zg index` runs in the background; only a runaway is killed. */\nconst REFRESH_TIMEOUT_MS = 10 * 60_000;\n/** Small, fast local model; indexes this repo in seconds. */\nexport const DEFAULT_ZVEC_MODEL = \"local/potion-code-16m-v2\";\nconst DOC_GLOBS = [\"!**/*.md\", \"!**/*.mdx\", \"!docs/**\"];\nconst TEST_GLOBS = [\n \"!**/*.test.*\",\n \"!**/*.spec.*\",\n \"!**/__tests__/**\",\n \"!**/test/**\",\n \"!**/tests/**\",\n];\nconst ALWAYS_EXCLUDED_GLOBS = [\"!evals/**\", \"!scripts/**\", \"!.codebuddy/**\", \"!**/fixtures/**\"];\n\n/** zg `-g` exclusions for a task: docs and tests stay searchable when the task names them. */\nexport function zvecExcludeGlobs(task: string): string[] {\n return [\n ...(/\\b(docs?|readme|documentation|markdown)\\b/i.test(task) ? [] : DOC_GLOBS),\n ...(/\\b(tests?|spec)\\b/i.test(task) ? [] : TEST_GLOBS),\n ...ALWAYS_EXCLUDED_GLOBS,\n ];\n}\n\nconst EXCLUDED_PREFIXES = [\".codebuddy/cache/\", \".zvec-grep/\", \".git/\", \"node_modules/\"];\n\nexport type ZvecStatus =\n | \"ok\"\n | \"disabled\"\n | \"not-installed\"\n | \"unsupported-version\"\n | \"index-not-ready\"\n | \"timeout\"\n | \"failed\";\n\n/** zg provides no content hash: a valid range alone cannot prove index freshness. */\nexport type RetrievalHit = {\n path: string;\n startLine: number;\n endLine: number;\n rank: number;\n /** `anchor`: a line the task itself names (path:line, stack frame, error string). */\n source: \"zvec\" | \"anchor\";\n freshness: \"unverified\";\n};\n\nexport type ZvecCandidates = {\n status: ZvecStatus;\n /** Repo-relative paths of existing files, best first. */\n paths: string[];\n /** Every chunk hit inside `paths`, in rank order (several per file). */\n hits: RetrievalHit[];\n reason: string;\n /** Hits dropped because the file no longer exists (stale index). */\n stale: number;\n /** Hits dropped because the file changed after the zg index was refreshed. */\n changed: number;\n};\n\nexport async function zvecCandidates(\n repositoryRoot: string,\n task: string,\n options: { limit?: number } = {},\n): Promise<ZvecCandidates> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n if (!config.enabled) return none(\"disabled\", \"zvec-grep not enabled\");\n const run = (args: string[]) => runZg(config.binary, args, root, config.timeoutMs);\n\n const cached = await readCachedReadiness(root, config.binary);\n if (cached !== \"ready\") {\n if (cached) return cached;\n const readiness = await checkReadiness(run);\n await markReadiness(root, config.binary, readiness);\n if (readiness) return readiness;\n }\n\n // zg ranks chunks, and one file can own many of them: oversample chunks so\n // `limit` distinct files survive de-duplication.\n const limit = Math.min(20, Math.max(1, Math.floor(options.limit ?? 10)));\n const chunkLimit = limit * 8;\n const globs = zvecExcludeGlobs(task);\n const result = await run([\n \"query\",\n \"--vector\",\n task,\n \"--mode\",\n \"auto\",\n \"--refresh\",\n \"off\",\n \"--limit\",\n String(chunkLimit),\n ...globs.flatMap((glob) => [\"-g\", glob]),\n ]);\n if (result.error) {\n await resetZvecCache(root);\n return fromError(result.error, \"zvec-grep query\");\n }\n\n const hits = parseHits(result.stdout).slice(0, chunkLimit);\n // With no index, zg answers with a \"run zg index\" hint instead of hits.\n if (hits.length === 0 && /\\bzg index\\b/.test(result.stdout)) {\n const notReady = none(\"index-not-ready\", NOT_READY);\n await markReadiness(root, config.binary, notReady);\n return notReady;\n }\n const indexedAt = await readIndexedAt(root);\n const paths: string[] = [];\n const candidates: RetrievalHit[] = [];\n const seen = new Set<string>();\n let stale = 0;\n let changed = 0;\n for (const hit of hits) {\n const path = toRepoRelative(root, hit.path);\n if (!path) continue;\n // Later chunks of an accepted file keep their spans; a rejected file stays rejected.\n if (seen.has(path)) {\n if (paths.includes(path)) candidates.push({ ...hit, path });\n continue;\n }\n if (paths.length >= limit) continue;\n seen.add(path);\n if (EXCLUDED_PREFIXES.some((prefix) => path.startsWith(prefix))) continue;\n try {\n const info = await stat(resolve(root, path));\n if (!info.isFile()) continue;\n // ctime too: a rename over the path keeps the old mtime.\n if (indexedAt !== null && Math.max(info.mtimeMs, info.ctimeMs) > indexedAt) {\n changed++;\n continue;\n }\n } catch {\n stale++;\n continue;\n }\n paths.push(path);\n candidates.push({ ...hit, path });\n }\n return {\n status: \"ok\",\n paths,\n hits: candidates,\n stale,\n changed,\n reason:\n `zvec-grep vector search: ${paths.length} file(s)` +\n (globs.length > 0 ? \"; non-source paths excluded\" : \"\") +\n (stale > 0 ? `, ${stale} stale hit(s) dropped` : \"\") +\n (changed > 0 ? `, ${changed} hit(s) changed since the zg index dropped` : \"\") +\n (indexedAt === null\n ? \"; index age unknown, hits unverified (run `codebuddy zvec index`)\"\n : \"\"),\n };\n}\n\nexport type ZvecRefresh = { status: ZvecStatus; reason: string };\n\n/**\n * Incrementally refresh an existing zg index (no `--rebuild`, no embedding\n * flag) and record when it started, which is the freshness bound applied to\n * later hits. Never builds a missing index and never starts a zg server; every\n * failure is returned as a status for the caller to log.\n */\nexport async function refreshZvecIndex(\n repositoryRoot: string,\n options: { signal?: AbortSignal } = {},\n): Promise<ZvecRefresh> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n if (!config.enabled) return { status: \"disabled\", reason: \"zvec-grep not enabled\" };\n const readiness = await checkReadiness((args) =>\n runZg(config.binary, args, root, config.timeoutMs),\n );\n if (readiness) return { status: readiness.status, reason: readiness.reason };\n const startedAt = Date.now();\n const result = await runZg(\n config.binary,\n [\"index\", root, \"--mode\", \"auto\"],\n root,\n REFRESH_TIMEOUT_MS,\n options.signal,\n );\n if (result.error) {\n const failure = fromError(result.error, \"zvec-grep refresh\");\n return { status: failure.status, reason: failure.reason };\n }\n await markIndexed(root, startedAt);\n return { status: \"ok\", reason: \"zvec-grep index refreshed\" };\n}\n\n/** Extract bounded, validated line ranges in retrieval rank order. */\nexport function parseHits(output: string): RetrievalHit[] {\n return [...output.matchAll(/^#(\\d+) matchedBy=\\S+ (.+?):(\\d+)(?:-(\\d+))?\\s*$/gm)]\n .flatMap((match) => {\n const rank = Number(match[1]);\n const startLine = Number(match[3]);\n const endLine = Number(match[4] ?? match[3]);\n if (startLine < 1 || endLine < startLine) return [];\n return [\n {\n path: match[2] as string,\n rank,\n startLine,\n endLine,\n source: \"zvec\" as const,\n freshness: \"unverified\" as const,\n },\n ];\n })\n .sort((left, right) => left.rank - right.rank);\n}\n\n/** Forget the cached readiness check, e.g. after `zg index` or a failed query. */\nexport async function resetZvecCache(repositoryRoot: string): Promise<void> {\n await rm(readyPath(resolve(repositoryRoot)), { force: true });\n}\n\nexport type ZvecReport = {\n enabled: boolean;\n binary: string;\n version: string | null;\n supported: boolean;\n indexReady: boolean;\n server: \"running\" | \"stopped\" | \"unknown\";\n reason: string;\n};\n\n/** Everything `codebuddy zvec status` shows. Read-only; never builds an index. */\nexport async function zvecStatus(repositoryRoot: string): Promise<ZvecReport> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n const run = (args: string[]) =>\n runZg(config.binary, args, root, Math.max(config.timeoutMs, 5_000));\n const version = await run([\"version\"]);\n const found = version.error ? null : (version.stdout.trim().split(/\\s+/).pop() ?? null);\n const supported = found !== null && SUPPORTED.test(found);\n const readiness = supported ? await checkReadiness(run) : null;\n const server = supported ? await run([\"server\", \"status\"]) : null;\n return {\n enabled: config.enabled,\n binary: config.binary,\n version: found,\n supported,\n indexReady: supported && readiness === null,\n server:\n server === null || server.error\n ? \"unknown\"\n : /running|ready/i.test(server.stdout)\n ? \"running\"\n : \"stopped\",\n reason: version.error\n ? fromError(version.error, \"zvec-grep version check\").reason\n : !supported\n ? `zvec-grep ${found ?? \"unknown\"} is not 0.2.x`\n : (readiness?.reason ?? \"ready\"),\n };\n}\n\n/**\n * Build or incrementally refresh the zg index for this repository, streaming\n * zg's progress. Explicit by design: CodeBuddy never indexes implicitly.\n */\nexport async function buildZvecIndex(\n repositoryRoot: string,\n options: {\n model?: string;\n rebuild?: boolean /** Discard zg's progress output. */;\n quiet?: boolean;\n } = {},\n): Promise<number> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n const args = [\n \"index\",\n root,\n \"--embedding\",\n options.model ?? DEFAULT_ZVEC_MODEL,\n \"--mode\",\n \"auto\",\n ];\n if (options.rebuild) args.push(\"--rebuild\");\n const startedAt = Date.now();\n const code = await new Promise<number>((resolveRun, reject) => {\n const child = spawn(config.binary, args, {\n cwd: root,\n stdio: options.quiet ? \"ignore\" : \"inherit\",\n });\n child.on(\"error\", reject);\n child.on(\"close\", (exit) => resolveRun(exit ?? 1));\n });\n await resetZvecCache(root);\n if (code === 0) await markIndexed(root, startedAt);\n return code;\n}\n\nconst NOT_READY = \"zvec-grep index missing or not ready; run `codebuddy zvec index`\";\n\n/** Version + index readiness. Returns null when ready, else the failure. */\nasync function checkReadiness(\n run: (args: string[]) => Promise<RunResult>,\n): Promise<ZvecCandidates | null> {\n const version = await run([\"version\"]);\n if (version.error) return fromError(version.error, \"zvec-grep version check\");\n const found = version.stdout.trim().split(/\\s+/).pop() ?? \"\";\n if (!SUPPORTED.test(found)) {\n return none(\"unsupported-version\", `zvec-grep ${found || \"unknown\"} is not 0.2.x`);\n }\n const ready = await run([\"status\", \"--check-ready\", \"--mode\", \"auto\"]);\n if (!ready.error) return null;\n return ready.error.code === \"ETIMEDOUT\"\n ? none(\"timeout\", \"zvec-grep status timed out\")\n : none(\"index-not-ready\", NOT_READY);\n}\n\nfunction readyPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"zvec-ready.json\");\n}\n\ntype CachedReadiness = {\n binary?: string;\n checkedAt?: string;\n /** Absent in caches written before failures were cached: those were always ready. */\n failure?: { status: ZvecStatus; reason: string };\n};\n\n/**\n * \"ready\", a cached failure (reason marked as cached), or null when the check\n * must run. A failure is dropped once `.zvec-grep/` changed after it was\n * recorded, i.e. someone (re)built the index outside CodeBuddy.\n */\nasync function readCachedReadiness(\n root: string,\n binary: string,\n): Promise<\"ready\" | ZvecCandidates | null> {\n let cached: CachedReadiness;\n try {\n cached = JSON.parse(await readFile(readyPath(root), \"utf8\")) as CachedReadiness;\n } catch {\n return null;\n }\n const checkedAt = Date.parse(cached.checkedAt ?? \"\");\n const age = Date.now() - checkedAt;\n if (cached.binary !== binary || !(age >= 0)) return null;\n if (!cached.failure) return age <= READY_CACHE_MS ? \"ready\" : null;\n if (age > NOT_READY_CACHE_MS) return null;\n const workspace = await stat(join(root, \".zvec-grep\")).catch(() => null);\n if (workspace && workspace.mtimeMs > checkedAt) return null;\n const { status, reason } = cached.failure;\n return none(status, `${reason}; cached check from ${Math.round(age / 1000)}s ago`);\n}\n\n/** Cache a readiness result: null = ready. */\nasync function markReadiness(\n root: string,\n binary: string,\n failure: ZvecCandidates | null,\n): Promise<void> {\n try {\n const path = readyPath(root);\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n const record: CachedReadiness = {\n binary,\n checkedAt: new Date().toISOString(),\n ...(failure ? { failure: { status: failure.status, reason: failure.reason } } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(record)}\\n`);\n } catch {\n // A read-only cache only costs a repeated readiness check.\n }\n}\n\nfunction indexedPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"zvec-index.json\");\n}\n\n/** When the zg index was last refreshed by CodeBuddy, or null if unknown. */\nasync function readIndexedAt(root: string): Promise<number | null> {\n try {\n const parsed = JSON.parse(await readFile(indexedPath(root), \"utf8\")) as { indexedAt?: string };\n const at = Date.parse(parsed.indexedAt ?? \"\");\n return Number.isFinite(at) ? at : null;\n } catch {\n return null;\n }\n}\n\nasync function markIndexed(root: string, at: number): Promise<void> {\n try {\n const path = indexedPath(root);\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeAtomic(path, `${JSON.stringify({ indexedAt: new Date(at).toISOString() })}\\n`);\n } catch {\n // Without the record, later hits are reported as unverified, not trusted.\n }\n}\n\nasync function zvecConfig(\n root: string,\n): Promise<{ enabled: boolean; binary: string; timeoutMs: number }> {\n let file: Awaited<ReturnType<typeof readConfigFile>> = {};\n try {\n file = await readConfigFile(root);\n } catch {\n // A broken config must not break context assembly; treat zvec as disabled.\n }\n const env = process.env.CODEBUDDY_ZVEC;\n return {\n enabled: env !== undefined ? env === \"1\" || env === \"true\" : (file.zvec?.enabled ?? false),\n binary: file.zvec?.binary ?? \"zg\",\n timeoutMs: file.zvec?.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n };\n}\n\ntype RunResult = {\n stdout: string;\n error: (Error & { code?: string | number | null | undefined }) | null;\n};\n\nfunction runZg(\n binary: string,\n args: string[],\n cwd: string,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<RunResult> {\n return new Promise((resolveRun) => {\n execFile(\n binary,\n args,\n {\n cwd,\n timeout: timeoutMs,\n maxBuffer: 1024 * 1024,\n killSignal: \"SIGKILL\",\n ...(signal ? { signal } : {}),\n },\n (error, stdout) => {\n if (error && (error as { killed?: boolean }).killed) {\n resolveRun({ stdout: \"\", error: Object.assign(error, { code: \"ETIMEDOUT\" }) });\n return;\n }\n resolveRun({ stdout: String(stdout), error });\n },\n );\n });\n}\n\nfunction fromError(error: NonNullable<RunResult[\"error\"]>, what: string): ZvecCandidates {\n if (error.code === \"ENOENT\") return none(\"not-installed\", \"zvec-grep (`zg`) not found on PATH\");\n if (error.code === \"ETIMEDOUT\") return none(\"timeout\", `${what} timed out`);\n return none(\"failed\", `${what} failed`);\n}\n\nfunction none(status: ZvecStatus, reason: string): ZvecCandidates {\n return { status, paths: [], hits: [], reason, stale: 0, changed: 0 };\n}\n\nfunction toRepoRelative(root: string, path: string): string | null {\n const absolute = isAbsolute(path) ? path : resolve(root, path);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join(\"/\");\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,144 @@
1
+ import {
2
+ failSoft,
3
+ readHookInput,
4
+ writeStdout
5
+ } from "./chunk-PYLH4MH5.js";
6
+ import {
7
+ extractSymbolTable,
8
+ supportsSymbols
9
+ } from "./chunk-PQK3GKZH.js";
10
+
11
+ // src/hooks/pretool.ts
12
+ import { open, stat } from "fs/promises";
13
+ import { extname, isAbsolute, relative, resolve, sep } from "path";
14
+ var TOOL_GUARD = {
15
+ readLines: 1e3,
16
+ readBytes: 8e4,
17
+ /** Symbols shorter than this are noise in an outline (one-line constants). */
18
+ outlineMinSymbolLines: 3,
19
+ /** Fewer top-level symbols than this: list class methods too. */
20
+ minTopLevelEntries: 10,
21
+ outlineEntries: 40,
22
+ blockLines: 100,
23
+ grepHeadLimit: 100
24
+ };
25
+ var SPECIAL_READ = /* @__PURE__ */ new Set([
26
+ ".png",
27
+ ".jpg",
28
+ ".jpeg",
29
+ ".gif",
30
+ ".webp",
31
+ ".bmp",
32
+ ".svg",
33
+ ".pdf",
34
+ ".ipynb"
35
+ ]);
36
+ var MAX_GUARD_BYTES = 2e6;
37
+ async function decidePreTool(input, env = process.env) {
38
+ if (env.CODEBUDDY_TOOL_GUARD === "0") return null;
39
+ const toolInput = input.tool_input ?? {};
40
+ if (input.tool_name === "Grep") return capGrep(toolInput);
41
+ if (input.tool_name === "Read") return guardRead(resolve(input.cwd ?? process.cwd()), toolInput);
42
+ return null;
43
+ }
44
+ function capGrep(toolInput) {
45
+ if (toolInput.output_mode !== "content" || toolInput.head_limit !== void 0) return null;
46
+ return {
47
+ hookSpecificOutput: {
48
+ hookEventName: "PreToolUse",
49
+ updatedInput: { ...toolInput, head_limit: TOOL_GUARD.grepHeadLimit }
50
+ }
51
+ };
52
+ }
53
+ async function guardRead(root, toolInput) {
54
+ if (toolInput.offset !== void 0 || toolInput.limit !== void 0) return null;
55
+ const filePath = toolInput.file_path;
56
+ if (typeof filePath !== "string" || !filePath) return null;
57
+ const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);
58
+ const rel = relative(root, absolute);
59
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;
60
+ if (SPECIAL_READ.has(extname(absolute).toLowerCase())) return null;
61
+ let size;
62
+ try {
63
+ const info = await stat(absolute);
64
+ if (!info.isFile()) return null;
65
+ size = info.size;
66
+ } catch {
67
+ return null;
68
+ }
69
+ if (size > MAX_GUARD_BYTES) return null;
70
+ const text = await readText(absolute);
71
+ if (text === null) return null;
72
+ const lines = text.split(/\r?\n/);
73
+ if (lines.at(-1) === "") lines.pop();
74
+ if (lines.length <= TOOL_GUARD.readLines && size <= TOOL_GUARD.readBytes) return null;
75
+ const path = rel.replace(/\\/g, "/");
76
+ const outline = outlineOf(path, text, lines);
77
+ return {
78
+ hookSpecificOutput: {
79
+ hookEventName: "PreToolUse",
80
+ permissionDecision: "deny",
81
+ permissionDecisionReason: [
82
+ `CodeBuddy read guard: ${path} is ${lines.length} lines (~${Math.ceil(size / 4)} tokens), too large to read whole.`,
83
+ `Outline: ${outline.join("; ")}`,
84
+ "Read only the part you need with offset and limit (offset = first line, limit = line count).",
85
+ "To find specific text, use Grep with output_mode content and -n instead of reading the file.",
86
+ `To read the whole file anyway, call Read again with limit: ${lines.length}.`
87
+ ].join("\n")
88
+ }
89
+ };
90
+ }
91
+ async function readText(path) {
92
+ const handle = await open(path, "r");
93
+ try {
94
+ const content = await handle.readFile();
95
+ if (content.includes(0)) return null;
96
+ return content.toString("utf8");
97
+ } finally {
98
+ await handle.close();
99
+ }
100
+ }
101
+ function outlineOf(path, text, lines) {
102
+ const cap = (entries) => entries.length > TOOL_GUARD.outlineEntries ? [
103
+ ...entries.slice(0, TOOL_GUARD.outlineEntries),
104
+ `\u2026 ${entries.length - TOOL_GUARD.outlineEntries} more`
105
+ ] : entries;
106
+ if (supportsSymbols(path)) {
107
+ const table = extractSymbolTable(text).filter(
108
+ (symbol) => symbol.endLine - symbol.startLine + 1 >= TOOL_GUARD.outlineMinSymbolLines
109
+ );
110
+ const topLevel = table.filter((symbol) => symbol.kind !== "method");
111
+ const symbols = topLevel.length >= TOOL_GUARD.minTopLevelEntries ? topLevel : table;
112
+ if (symbols.length > 0) {
113
+ const covered = Math.max(...symbols.map((symbol) => symbol.endLine));
114
+ return [
115
+ ...cap(symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`)),
116
+ ...covered < lines.length ? [`not outlined L${covered + 1}-${lines.length}`] : []
117
+ ];
118
+ }
119
+ }
120
+ if (/\.mdx?$/i.test(path)) {
121
+ const headings = lines.flatMap(
122
+ (line, i) => /^#{1,4}\s+\S/.test(line) ? [`${line.trim().slice(0, 80)} L${i + 1}`] : []
123
+ );
124
+ if (headings.length > 0) return cap(headings);
125
+ }
126
+ const blocks = [];
127
+ for (let start = 1; start <= lines.length; start += TOOL_GUARD.blockLines) {
128
+ blocks.push(`L${start}-${Math.min(lines.length, start + TOOL_GUARD.blockLines - 1)}`);
129
+ }
130
+ return cap(blocks);
131
+ }
132
+
133
+ // src/hooks/pretool-command.ts
134
+ async function runPreToolHookCommand() {
135
+ await failSoft(async () => {
136
+ const decision = await decidePreTool(await readHookInput());
137
+ if (decision) await writeStdout(JSON.stringify(decision));
138
+ });
139
+ }
140
+
141
+ export {
142
+ runPreToolHookCommand
143
+ };
144
+ //# sourceMappingURL=chunk-WHZSVY2L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/pretool.ts","../src/hooks/pretool-command.ts"],"sourcesContent":["import { open, stat } from \"node:fs/promises\";\nimport { extname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { extractSymbolTable, supportsSymbols } from \"../indexer/symbols.js\";\nimport type { HookInput } from \"./runtime.js\";\n\n/**\n * PreToolUse tool guard (Claude Code): cut tool output that would otherwise\n * stay in context for the rest of the session, without touching permissions.\n *\n * - Read of a large file with no range → denied with an outline (symbols or\n * headings with line ranges) and how to read a range or the whole file.\n * The model sees the deny reason; any Read with offset/limit passes.\n * - Grep in content mode with no head_limit → the same input plus a limit.\n *\n * Never emits `permissionDecision: \"allow\"`: Claude Code checks permissions on\n * the (possibly rewritten) input as usual. Verified on Claude Code 2.1.287.\n */\n/**\n * Only files whose full read is clearly expensive: below this, an agent that\n * re-reads the whole file after the outline (observed live) costs a turn for\n * nothing.\n */\nexport const TOOL_GUARD = {\n readLines: 1_000,\n readBytes: 80_000,\n /** Symbols shorter than this are noise in an outline (one-line constants). */\n outlineMinSymbolLines: 3,\n /** Fewer top-level symbols than this: list class methods too. */\n minTopLevelEntries: 10,\n outlineEntries: 40,\n blockLines: 100,\n grepHeadLimit: 100,\n} as const;\n\n/** Read renders these itself (images, PDFs, notebooks); never guard them. */\nconst SPECIAL_READ = new Set([\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".gif\",\n \".webp\",\n \".bmp\",\n \".svg\",\n \".pdf\",\n \".ipynb\",\n]);\nconst MAX_GUARD_BYTES = 2_000_000;\n\nexport type PreToolDecision = {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\";\n permissionDecision?: \"deny\";\n permissionDecisionReason?: string;\n updatedInput?: Record<string, unknown>;\n };\n} | null;\n\nexport async function decidePreTool(\n input: HookInput,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<PreToolDecision> {\n if (env.CODEBUDDY_TOOL_GUARD === \"0\") return null;\n const toolInput = input.tool_input ?? {};\n if (input.tool_name === \"Grep\") return capGrep(toolInput);\n if (input.tool_name === \"Read\") return guardRead(resolve(input.cwd ?? process.cwd()), toolInput);\n return null;\n}\n\nfunction capGrep(toolInput: Record<string, unknown>): PreToolDecision {\n if (toolInput.output_mode !== \"content\" || toolInput.head_limit !== undefined) return null;\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n updatedInput: { ...toolInput, head_limit: TOOL_GUARD.grepHeadLimit },\n },\n };\n}\n\nasync function guardRead(\n root: string,\n toolInput: Record<string, unknown>,\n): Promise<PreToolDecision> {\n if (toolInput.offset !== undefined || toolInput.limit !== undefined) return null;\n const filePath = toolInput.file_path;\n if (typeof filePath !== \"string\" || !filePath) return null;\n const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n if (SPECIAL_READ.has(extname(absolute).toLowerCase())) return null;\n let size: number;\n try {\n const info = await stat(absolute);\n if (!info.isFile()) return null;\n size = info.size;\n } catch {\n return null;\n }\n if (size > MAX_GUARD_BYTES) return null; // Read refuses huge files itself.\n const text = await readText(absolute);\n if (text === null) return null;\n const lines = text.split(/\\r?\\n/);\n if (lines.at(-1) === \"\") lines.pop();\n if (lines.length <= TOOL_GUARD.readLines && size <= TOOL_GUARD.readBytes) return null;\n\n const path = rel.replace(/\\\\/g, \"/\");\n const outline = outlineOf(path, text, lines);\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: [\n `CodeBuddy read guard: ${path} is ${lines.length} lines (~${Math.ceil(size / 4)} tokens), too large to read whole.`,\n `Outline: ${outline.join(\"; \")}`,\n \"Read only the part you need with offset and limit (offset = first line, limit = line count).\",\n \"To find specific text, use Grep with output_mode content and -n instead of reading the file.\",\n `To read the whole file anyway, call Read again with limit: ${lines.length}.`,\n ].join(\"\\n\"),\n },\n };\n}\n\n/** Whole file as UTF-8 text, or null when it looks binary. */\nasync function readText(path: string): Promise<string | null> {\n const handle = await open(path, \"r\");\n try {\n const content = await handle.readFile();\n if (content.includes(0)) return null;\n return content.toString(\"utf8\");\n } finally {\n await handle.close();\n }\n}\n\nfunction outlineOf(path: string, text: string, lines: string[]): string[] {\n const cap = (entries: string[]) =>\n entries.length > TOOL_GUARD.outlineEntries\n ? [\n ...entries.slice(0, TOOL_GUARD.outlineEntries),\n `… ${entries.length - TOOL_GUARD.outlineEntries} more`,\n ]\n : entries;\n if (supportsSymbols(path)) {\n const table = extractSymbolTable(text).filter(\n (symbol) => symbol.endLine - symbol.startLine + 1 >= TOOL_GUARD.outlineMinSymbolLines,\n );\n const topLevel = table.filter((symbol) => symbol.kind !== \"method\");\n // One big class says little on its own; then list its methods (`Class.method`).\n const symbols = topLevel.length >= TOOL_GUARD.minTopLevelEntries ? topLevel : table;\n if (symbols.length > 0) {\n // The symbol table is capped, so say where the outline stops covering the file.\n const covered = Math.max(...symbols.map((symbol) => symbol.endLine));\n return [\n ...cap(symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`)),\n ...(covered < lines.length ? [`not outlined L${covered + 1}-${lines.length}`] : []),\n ];\n }\n }\n if (/\\.mdx?$/i.test(path)) {\n const headings = lines.flatMap((line, i) =>\n /^#{1,4}\\s+\\S/.test(line) ? [`${line.trim().slice(0, 80)} L${i + 1}`] : [],\n );\n if (headings.length > 0) return cap(headings);\n }\n const blocks: string[] = [];\n for (let start = 1; start <= lines.length; start += TOOL_GUARD.blockLines) {\n blocks.push(`L${start}-${Math.min(lines.length, start + TOOL_GUARD.blockLines - 1)}`);\n }\n return cap(blocks);\n}\n","import { decidePreTool } from \"./pretool.js\";\nimport { failSoft, readHookInput, writeStdout } from \"./stdio.js\";\n\n/** `codebuddy hooks pretool`: runs on every Read/Grep, so it loads only its own module. */\nexport async function runPreToolHookCommand(): Promise<void> {\n await failSoft(async () => {\n const decision = await decidePreTool(await readHookInput());\n if (decision) await writeStdout(JSON.stringify(decision));\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ var [command, subcommand] = process.argv.slice(2);
5
+ if (command === "hooks" && subcommand === "lean") {
6
+ const { runLeanHookCommand } = await import("../lean-command-D6TZJEFX.js");
7
+ await runLeanHookCommand();
8
+ } else if (command === "hooks" && subcommand === "pretool") {
9
+ const { runPreToolHookCommand } = await import("../pretool-command-MQQ2UNQM.js");
10
+ await runPreToolHookCommand();
11
+ } else {
12
+ const { createCli } = await import("../main-ZZ3EQLYA.js");
13
+ await createCli().parseAsync();
14
+ }
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Launcher. Hooks that fire on every prompt (lean) or every Read/Grep\n * (pretool) load only their own module; every other command loads the full CLI.\n */\nconst [command, subcommand] = process.argv.slice(2);\nif (command === \"hooks\" && subcommand === \"lean\") {\n const { runLeanHookCommand } = await import(\"../hooks/lean-command.js\");\n await runLeanHookCommand();\n} else if (command === \"hooks\" && subcommand === \"pretool\") {\n const { runPreToolHookCommand } = await import(\"../hooks/pretool-command.js\");\n await runPreToolHookCommand();\n} else {\n const { createCli } = await import(\"./main.js\");\n await createCli().parseAsync();\n}\n"],"mappings":";;;AAKA,IAAM,CAAC,SAAS,UAAU,IAAI,QAAQ,KAAK,MAAM,CAAC;AAClD,IAAI,YAAY,WAAW,eAAe,QAAQ;AAChD,QAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,6BAA0B;AACtE,QAAM,mBAAmB;AAC3B,WAAW,YAAY,WAAW,eAAe,WAAW;AAC1D,QAAM,EAAE,sBAAsB,IAAI,MAAM,OAAO,gCAA6B;AAC5E,QAAM,sBAAsB;AAC9B,OAAO;AACL,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,qBAAW;AAC9C,QAAM,UAAU,EAAE,WAAW;AAC/B;","names":[]}