@garuda-agent/garuda 0.16.0 → 0.17.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 (54) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/README.md +56 -1074
  3. package/SECURITY.md +9 -2
  4. package/dist/{acpCommand-AJJBKAE5.js → acpCommand-7EY3NHPG.js} +18 -16
  5. package/dist/{anthropic-NQDBCHJU.js → anthropic-NQIZLD64.js} +12 -6
  6. package/dist/{chunk-WIJL72ZE.js → chunk-2DYMJMRQ.js} +1880 -982
  7. package/dist/{chunk-YQTGMCU6.js → chunk-3MRQJKUO.js} +29 -5
  8. package/dist/{chunk-WMOLXTBU.js → chunk-4LFJFDCF.js} +2 -1
  9. package/dist/{chunk-44QN5SWZ.js → chunk-55CFEEIN.js} +22 -2
  10. package/dist/{chunk-USCUXCAU.js → chunk-5B5HDTUR.js} +3 -3
  11. package/dist/{chunk-TUUEBHG2.js → chunk-6XHON3Q6.js} +2 -2
  12. package/dist/chunk-735BKABA.js +472 -0
  13. package/dist/{chunk-B6FA4I7Q.js → chunk-7TH3NHCQ.js} +2 -2
  14. package/dist/{chunk-OUCV3FCT.js → chunk-AOGFYUKG.js} +3 -3
  15. package/dist/{chunk-GXZACJUI.js → chunk-B4SJY7KT.js} +322 -39
  16. package/dist/{chunk-3EQU2GUX.js → chunk-BHZ6G65V.js} +5 -1
  17. package/dist/chunk-BSK5T2ZG.js +9 -0
  18. package/dist/chunk-J4346IDH.js +156 -0
  19. package/dist/{chunk-NP6RIC25.js → chunk-JRVWH27M.js} +7 -7
  20. package/dist/{chunk-HUIK3WXO.js → chunk-P6XWWIEK.js} +20 -3
  21. package/dist/{chunk-HE2K657U.js → chunk-TJIWD7R7.js} +47 -16
  22. package/dist/chunk-U65DQZDB.js +143 -0
  23. package/dist/chunk-UIRJOQQS.js +118 -0
  24. package/dist/{chunk-KKB5W47S.js → chunk-VTMD5IHD.js} +26 -2
  25. package/dist/{chunk-ENLDPN4S.js → chunk-Z66BWTMD.js} +81 -32
  26. package/dist/cli/index.js +99 -41
  27. package/dist/{create-VXIH2GUZ.js → create-W6BV75QX.js} +18 -12
  28. package/dist/{deadline-F5RJJ2QW.js → deadline-TP7ZGLT2.js} +4 -2
  29. package/dist/{evalCommand-OX2LQYEK.js → evalCommand-YVQAWUZE.js} +50 -29
  30. package/dist/{export-HN3CT7AV.js → export-SJ3DUP6W.js} +2 -2
  31. package/dist/{inkChat-YMMMGU7Y.js → inkChat-FNUBK43U.js} +14 -13
  32. package/dist/install-TKWN5AYH.js +19 -0
  33. package/dist/{job-3NYDMP2M.js → job-7JOI4YIQ.js} +6 -2
  34. package/dist/jobCommand-FE6XN3YD.js +33 -0
  35. package/dist/{launchd-SSX362VG.js → launchd-3A3ONEW5.js} +9 -8
  36. package/dist/{lspCommand-JRXKRSL7.js → lspCommand-ZZ6R4PA2.js} +7 -6
  37. package/dist/{manager-AWU4NHHK.js → manager-73MPYFTP.js} +7 -6
  38. package/dist/{nightCommand-65MUEV32.js → nightCommand-OODJNWDT.js} +26 -20
  39. package/dist/{openaiCompatible-CJWVJP5M.js → openaiCompatible-6TLDDW5U.js} +29 -15
  40. package/dist/{policy-EJPLEEXP.js → policy-ZJ7RMOFN.js} +10 -4
  41. package/dist/{remove-6GDSTAIO.js → remove-42WQNVPX.js} +8 -7
  42. package/dist/{server-KHHNHUYV.js → server-VEYIJHEU.js} +7 -3
  43. package/dist/{setupCommand-VA7D5MZK.js → setupCommand-GBQIVYJC.js} +45 -13
  44. package/dist/text-LS35WSRL.js +17 -0
  45. package/native/bin/garuda-reap +0 -0
  46. package/native/garuda-reap.c +113 -0
  47. package/package.json +39 -13
  48. package/dist/chunk-EIXUSVFX.js +0 -116
  49. package/dist/chunk-R7I2NBPX.js +0 -71
  50. package/dist/chunk-ULAJPMY7.js +0 -107
  51. package/dist/chunk-XOK5OZOC.js +0 -9
  52. package/dist/install-EWDSPFFZ.js +0 -18
  53. package/dist/jobCommand-ACSGSBQ4.js +0 -32
  54. package/dist/text-J2GD647J.js +0 -11
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ realpathOfNearestExisting
4
+ } from "./chunk-4LFJFDCF.js";
5
+
6
+ // src/permissions/writeScope.ts
7
+ import { existsSync, lstatSync, mkdirSync, realpathSync } from "fs";
8
+ import { realpath } from "fs/promises";
9
+ import { isAbsolute, join, relative, resolve, sep } from "path";
10
+ function scopeEntryProblem(entry) {
11
+ if (entry === "" || entry === "/" || entry === "." || entry === "./") {
12
+ return `"${entry}" is the whole worktree, not a scope`;
13
+ }
14
+ if (entry.startsWith("/") || /^[A-Za-z]:/.test(entry)) return `"${entry}" is not relative`;
15
+ const control = [...entry].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127);
16
+ if (entry.includes("\\") || control) {
17
+ return `"${entry}" has a backslash or a control character`;
18
+ }
19
+ const parts = entry.replace(/\/$/, "").split("/");
20
+ if (parts.some((p) => p === "" || p === "." || p === "..")) {
21
+ return `"${entry}" has an empty, "." or ".." part`;
22
+ }
23
+ if (parts[0] === ".git" || parts[0] === ".garuda") return `"${entry}" is a protected folder`;
24
+ return void 0;
25
+ }
26
+ function inWriteScope(path, scope) {
27
+ const clean = path.replace(/^\.\//, "").replace(/\/+$/, "");
28
+ return scope.some(
29
+ (entry) => entry.endsWith("/") ? clean === entry.slice(0, -1) || clean.startsWith(entry) : clean === entry
30
+ );
31
+ }
32
+ function scopePaths(root, scope) {
33
+ return scope.map((entry) => join(root, ...entry.replace(/\/$/, "").split("/")));
34
+ }
35
+ function prepareWriteScope(root, scope) {
36
+ for (const entry of scope) {
37
+ const problem = scopeEntryProblem(entry);
38
+ if (problem !== void 0) return problem;
39
+ }
40
+ const realRoot = realpathSync(root);
41
+ for (const [i, path] of scopePaths(root, scope).entries()) {
42
+ const entry = scope[i];
43
+ let current = root;
44
+ for (const part of entry.replace(/\/$/, "").split("/")) {
45
+ current = join(current, part);
46
+ if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
47
+ return `"${entry}" goes through a symbolic link (${current})`;
48
+ }
49
+ }
50
+ if (!existsSync(path) && entry.endsWith("/")) mkdirSync(path, { recursive: true });
51
+ if (existsSync(path)) {
52
+ const real = realpathSync(path);
53
+ if (real !== realRoot && !real.startsWith(`${realRoot}${sep}`)) {
54
+ return `"${entry}" is outside the worktree`;
55
+ }
56
+ }
57
+ }
58
+ return void 0;
59
+ }
60
+ var BUILD_OUTPUTS = [
61
+ { marker: "pom.xml", folders: ["target/"] },
62
+ { marker: "build.gradle", folders: ["build/", ".gradle/"] },
63
+ { marker: "build.gradle.kts", folders: ["build/", ".gradle/"] },
64
+ { marker: "package.json", folders: ["dist/", "coverage/"] },
65
+ { marker: "pyproject.toml", folders: ["build/", "dist/", ".pytest_cache/"] },
66
+ { marker: "setup.py", folders: ["build/", "dist/", ".pytest_cache/"] },
67
+ { marker: "Cargo.toml", folders: ["target/"] },
68
+ { marker: "go.mod", folders: [] }
69
+ ];
70
+ function proposeWriteScope(rules, root) {
71
+ const entries = /* @__PURE__ */ new Set();
72
+ for (const rule of rules) {
73
+ const m = /^(edit_file|write_file)\((.*)\)$/.exec(rule.trim());
74
+ if (m === null) continue;
75
+ const pattern = m[2].replace(/^\.\//, "");
76
+ const glob = pattern.search(/[*?[{]/);
77
+ const entry = glob === -1 ? pattern : pattern.slice(0, pattern.lastIndexOf("/", glob) + 1);
78
+ if (entry === "" || scopeEntryProblem(entry) !== void 0) {
79
+ return { scope: void 0, reason: `the rule ${rule} names no folder` };
80
+ }
81
+ entries.add(entry);
82
+ }
83
+ for (const { marker, folders } of BUILD_OUTPUTS) {
84
+ if (existsSync(join(root, marker))) for (const f of folders) entries.add(f);
85
+ }
86
+ return { scope: [...entries].sort() };
87
+ }
88
+ async function scopeDenial(root, path, scope) {
89
+ const absolute = isAbsolute(path) ? path : resolve(root, path);
90
+ const named = relative(root, absolute).split(sep).join("/");
91
+ const outside = (shown) => `${shown} is outside this job's write scope (${scope.join(", ")})`;
92
+ if (named.startsWith("..") || !inWriteScope(named, scope)) return outside(named);
93
+ let realRoot;
94
+ let real;
95
+ try {
96
+ realRoot = await realpath(root);
97
+ real = await realpathOfNearestExisting(absolute);
98
+ } catch {
99
+ return `${named}: its real place cannot be checked`;
100
+ }
101
+ const reached = relative(realRoot, real).split(sep).join("/");
102
+ if (reached === "" || reached.startsWith("..") || isAbsolute(reached)) {
103
+ return `${named} leads outside the worktree`;
104
+ }
105
+ if (!inWriteScope(reached, scope))
106
+ return `${named} leads to ${reached}, which ${outside(reached).slice(reached.length + 1)}`;
107
+ return void 0;
108
+ }
109
+
110
+ export {
111
+ scopeEntryProblem,
112
+ inWriteScope,
113
+ scopePaths,
114
+ prepareWriteScope,
115
+ proposeWriteScope,
116
+ scopeDenial
117
+ };
118
+ //# sourceMappingURL=chunk-UIRJOQQS.js.map
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ scopeEntryProblem
4
+ } from "./chunk-UIRJOQQS.js";
2
5
  import {
3
6
  parseRule
4
7
  } from "./chunk-3NJNVZTK.js";
@@ -29,6 +32,8 @@ var resultSchema = z.object({
29
32
  steps: z.number(),
30
33
  tokens: z.number(),
31
34
  costUsd: z.number().optional(),
35
+ /** The cost leaves out some spending: `costUsd` is a minimum (review of 1370184). */
36
+ costIncomplete: z.boolean().optional(),
32
37
  durationMs: z.number(),
33
38
  sessionId: z.string().optional(),
34
39
  commit: z.string().optional(),
@@ -55,7 +60,12 @@ var resultSchema = z.object({
55
60
  text: z.string(),
56
61
  costUsd: z.number().optional()
57
62
  }).optional(),
58
- reviewError: z.string().optional()
63
+ reviewError: z.string().optional(),
64
+ /**
65
+ * The review spent tokens with no reported usage (a failed attempt, an estimate): its cost is
66
+ * a minimum or unrecorded (review of 3a9f51b).
67
+ */
68
+ reviewCostIncomplete: z.boolean().optional()
59
69
  }).optional()
60
70
  });
61
71
  var jobSchema = z.object({
@@ -76,6 +86,16 @@ var jobSchema = z.object({
76
86
  model: z.string().optional(),
77
87
  /** Permission rules for this job only, in the settings format ("edit_file(src/**)"). */
78
88
  allow: z.array(z.string()),
89
+ /**
90
+ * The write scope (0.17, review T6): the paths in the worktree that the job's commands and edits
91
+ * may change ("src/" is a folder, "README.md" one file). The OS sandbox enforces it for commands.
92
+ * Absent (a job made before 0.17, or a plan whose rules name no folder): the whole worktree.
93
+ */
94
+ writeScope: z.array(
95
+ z.string().refine((entry) => scopeEntryProblem(entry) === void 0, {
96
+ message: 'a scope entry is a path in the worktree, such as "src/" or "README.md"'
97
+ })
98
+ ).optional(),
79
99
  onUnapproved: z.literal("deny-and-continue"),
80
100
  /** "01:00" (local time) for `garuda run --at`, when the user gave one. */
81
101
  at: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/).optional(),
@@ -114,6 +134,9 @@ var jobSchema = z.object({
114
134
  endedAt: z.string().optional(),
115
135
  result: resultSchema.optional()
116
136
  });
137
+ function parseJob(json) {
138
+ return jobSchema.parse(json);
139
+ }
117
140
  function newJobId(now = /* @__PURE__ */ new Date()) {
118
141
  const pad = (n) => String(n).padStart(2, "0");
119
142
  const date = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`;
@@ -218,6 +241,7 @@ export {
218
241
  JOBS_DIR,
219
242
  DEFAULT_JOB_MAX_STEPS,
220
243
  JOB_STATUSES,
244
+ parseJob,
221
245
  newJobId,
222
246
  jobPath,
223
247
  failJobSync,
@@ -228,4 +252,4 @@ export {
228
252
  planPermissions,
229
253
  planTitle
230
254
  };
231
- //# sourceMappingURL=chunk-KKB5W47S.js.map
255
+ //# sourceMappingURL=chunk-VTMD5IHD.js.map
@@ -3,6 +3,7 @@ import {
3
3
  colorDiff
4
4
  } from "./chunk-4JSUUDFQ.js";
5
5
  import {
6
+ AuditDirError,
6
7
  SPECIALIST_SPECS,
7
8
  THINKING_WORDS,
8
9
  callersText,
@@ -16,12 +17,17 @@ import {
16
17
  referencesText,
17
18
  repoMapText,
18
19
  repoMapTool,
19
- verifyAuditFile
20
- } from "./chunk-WIJL72ZE.js";
20
+ verifyAuditDir
21
+ } from "./chunk-2DYMJMRQ.js";
22
+ import {
23
+ budgetSpent,
24
+ isCostIncomplete,
25
+ pendingChildTokens
26
+ } from "./chunk-735BKABA.js";
21
27
  import {
22
28
  WEB_SEARCH_USD,
23
29
  totalTokens
24
- } from "./chunk-R7I2NBPX.js";
30
+ } from "./chunk-J4346IDH.js";
25
31
  import {
26
32
  BUILTIN_COMMANDS
27
33
  } from "./chunk-23FXBNBB.js";
@@ -30,7 +36,7 @@ import {
30
36
  } from "./chunk-EU7PA45O.js";
31
37
  import {
32
38
  listJobs
33
- } from "./chunk-KKB5W47S.js";
39
+ } from "./chunk-VTMD5IHD.js";
34
40
 
35
41
  // src/cli/errors.ts
36
42
  function describeError(error) {
@@ -76,20 +82,32 @@ function formatTokens(n) {
76
82
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
77
83
  return `${(n / 1e6).toFixed(1)}M`;
78
84
  }
79
- function formatUsd(usd) {
85
+ function formatUsd(usd, incomplete = false) {
80
86
  if (usd === void 0) return "cost unknown";
81
- return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
87
+ const amount = usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
88
+ return incomplete ? `\u2265 ${amount} (incomplete)` : amount;
89
+ }
90
+ function budgetNote(session) {
91
+ const unreported = session.unreportedTokens;
92
+ const children = pendingChildTokens(session);
93
+ if (unreported <= 0 && children <= 0) return void 0;
94
+ const parts = [
95
+ ...unreported > 0 ? [`${formatTokens(unreported)} reserved without reported usage`] : [],
96
+ ...children > 0 ? [`${formatTokens(children)} from subagents with no report`] : []
97
+ ];
98
+ return `budget ${formatTokens(budgetSpent(session))} tokens (${parts.join(", ")})`;
82
99
  }
83
- function usageLine(result, session, runCostUsd, contextWindow) {
100
+ function usageLine(result, session, runCostUsd, contextWindow, runCostIncomplete = false) {
84
101
  const u = result.usage;
85
102
  const input = u.inputTokens + u.cacheReadTokens + u.cacheWriteTokens;
86
103
  const percent = Math.round(session.contextTokens / contextWindow * 100);
87
104
  return [
88
105
  `${result.stopReason} \xB7 ${result.steps} step(s)`,
89
- `${formatTokens(input)} in (${formatTokens(u.cacheReadTokens)} cached) / ${formatTokens(u.outputTokens)} out`,
90
- formatUsd(runCostUsd),
106
+ `${budgetNote(session) !== void 0 ? "reported " : ""}${formatTokens(input)} in (${formatTokens(u.cacheReadTokens)} cached) / ${formatTokens(u.outputTokens)} out`,
107
+ formatUsd(runCostUsd, runCostIncomplete),
91
108
  `context ${percent}% of ${formatTokens(contextWindow)}`,
92
- `session ${formatTokens(totalTokens(session.usage))} tokens, ${formatUsd(session.costUsd)}`
109
+ `session ${formatTokens(totalTokens(session.usage))} tokens, ${formatUsd(session.costUsd, isCostIncomplete(session))}`,
110
+ ...budgetNote(session) === void 0 ? [] : [budgetNote(session)]
93
111
  ].join(" \xB7 ");
94
112
  }
95
113
  function stopMessage(reason, limits) {
@@ -131,13 +149,19 @@ async function runTurn(runtime, approver, renderer, prompt, exitNow) {
131
149
  process.on("SIGINT", stop);
132
150
  approver.onInterrupt = stop;
133
151
  const costBefore = runtime.session?.costUsd ?? 0;
152
+ const unreportedBefore = runtime.session?.unreportedTokens ?? 0;
153
+ const incompleteBefore = runtime.session?.costIncomplete ?? false;
154
+ const pendingBefore = runtime.session === void 0 ? 0 : pendingChildTokens(runtime.session);
134
155
  try {
135
156
  const result = await runtime.runTurn(prompt, controller.signal);
136
157
  const session = runtime.session;
137
158
  if (session !== void 0) {
138
159
  const runCost = session.costUsd === void 0 ? void 0 : session.costUsd - costBefore;
139
- renderer.info(`
140
- [${usageLine(result, session, runCost, runtime.limits.contextWindow)}]`);
160
+ const runIncomplete = session.unreportedTokens > unreportedBefore || pendingChildTokens(session) > pendingBefore || session.costIncomplete && !incompleteBefore;
161
+ renderer.info(
162
+ `
163
+ [${usageLine(result, session, runCost, runtime.limits.contextWindow, runIncomplete)}]`
164
+ );
141
165
  }
142
166
  const message = stopMessage(result.stopReason, runtime.limits);
143
167
  if (message !== void 0) renderer.warn(message);
@@ -200,7 +224,7 @@ var HELP = [
200
224
  " /jobs the scheduled jobs of this project; /jobs <id> shows one; /jobs cancel|delete <id>; /jobs cancel night",
201
225
  " /where X where symbol X is defined (code index, no model call)",
202
226
  " /defs X alias of /where X (definitions of X)",
203
- " /refs X every use of symbol X (code index, no model call)",
227
+ " /refs X indexed uses of symbol X (approximate except JS/TS, no model call)",
204
228
  " /callers X who calls symbol X (code index, no model call)",
205
229
  " /impact X blast radius and affected tests for file or symbol X",
206
230
  " /map [dir] what each code file exports and imports",
@@ -388,7 +412,7 @@ async function sessionsCommand(runtime, renderer, arg, signal) {
388
412
  const facts = [
389
413
  s.id,
390
414
  `${s.turns} turn${s.turns === 1 ? "" : "s"}`,
391
- ...s.costUsd === void 0 || s.costUsd === 0 ? [] : [`$${s.costUsd.toFixed(2)}`],
415
+ ...s.costUsd === void 0 || s.costUsd === 0 && !s.costIncomplete ? [] : [`${s.costIncomplete ? "\u2265 " : ""}$${s.costUsd.toFixed(2)}`],
392
416
  ...s.model === void 0 ? [] : [s.model]
393
417
  ].join(" \xB7 ");
394
418
  return [
@@ -467,7 +491,7 @@ async function diffCommand(runtime, renderer, arg, output) {
467
491
  );
468
492
  }
469
493
  async function jobsCommand(runtime, renderer, id) {
470
- const { listJobs: listJobs2, loadJob, JOBS_DIR } = await import("./job-3NYDMP2M.js");
494
+ const { listJobs: listJobs2, loadJob, JOBS_DIR } = await import("./job-7JOI4YIQ.js");
471
495
  const cancel = /^cancel\s+(\S+)$/.exec(id);
472
496
  if (cancel !== null) {
473
497
  renderer.info(await runtime.cancelJob(cancel[1]));
@@ -481,7 +505,7 @@ async function jobsCommand(runtime, renderer, id) {
481
505
  if (id !== "") {
482
506
  try {
483
507
  const job = await loadJob(runtime.root, id);
484
- const { jobReport } = await import("./text-J2GD647J.js");
508
+ const { jobReport } = await import("./text-LS35WSRL.js");
485
509
  renderer.info(jobReport(job));
486
510
  } catch (error) {
487
511
  renderer.warn(error.message);
@@ -489,7 +513,7 @@ async function jobsCommand(runtime, renderer, id) {
489
513
  return;
490
514
  }
491
515
  const jobs = await listJobs2(runtime.root);
492
- const { defaultAgentEnv, nightAgentTime } = await import("./launchd-SSX362VG.js");
516
+ const { defaultAgentEnv, nightAgentTime } = await import("./launchd-3A3ONEW5.js");
493
517
  const night = nightAgentTime(runtime.root, defaultAgentEnv());
494
518
  const nightLine = night === void 0 ? [] : [
495
519
  `Night shift: launchd starts the queue at ${night.toTimeString().slice(0, 5)} on ${night.toDateString()} (/jobs cancel night removes it).`
@@ -527,7 +551,7 @@ async function exportCommand(runtime, renderer, file) {
527
551
  renderer.info("There is no conversation to export yet.");
528
552
  return;
529
553
  }
530
- const { sessionMarkdown, writeExport } = await import("./export-HN3CT7AV.js");
554
+ const { sessionMarkdown, writeExport } = await import("./export-SJ3DUP6W.js");
531
555
  const result = await writeExport(runtime.root, id, sessionMarkdown(records, id), file);
532
556
  if ("problem" in result) renderer.warn(result.problem);
533
557
  else
@@ -628,7 +652,7 @@ async function lookup(runtime, renderer, command, arg) {
628
652
  } else {
629
653
  text = repoMapText(await repoMapTool.run({ path: arg }, context));
630
654
  }
631
- renderer.info(text);
655
+ renderer.info(text.startsWith("Approximate:") ? `\u2248 ${text}` : text);
632
656
  } catch (error) {
633
657
  renderer.error(error.message);
634
658
  }
@@ -706,22 +730,46 @@ async function auditCommand(runtime, renderer, arg) {
706
730
  }
707
731
  }
708
732
  if (arg === "verify") {
709
- const files = await runtime.audit.files();
710
733
  lines.push("", `Audit files: ${runtime.audit.dir}`);
711
- if (files.length === 0) lines.push(" none yet");
734
+ let results;
735
+ try {
736
+ results = await verifyAuditDir(runtime.audit.dir);
737
+ } catch (error) {
738
+ if (!(error instanceof AuditDirError)) throw error;
739
+ lines.push(` BROKEN the audit folder: ${error.message}`);
740
+ renderer.info(lines.join("\n"));
741
+ return;
742
+ }
743
+ if (results.length === 0) lines.push(" none yet");
712
744
  let broken = 0;
713
- for (const file of files) {
714
- const result = await verifyAuditFile(file);
715
- const name = file.slice(runtime.audit.dir.length + 1);
716
- if (result.ok) {
717
- lines.push(` ok ${name} (${result.lines} lines)`);
745
+ let unchecked = 0;
746
+ for (const result of results) {
747
+ if ("missing" in result) {
748
+ broken++;
749
+ lines.push(` MISSING ${result.file}: the checkpoint names it, but the file is gone`);
750
+ } else if ("brokenCheckpoint" in result) {
751
+ broken++;
752
+ lines.push(` BROKEN ${result.file}: its checkpoint: ${result.brokenCheckpoint}`);
753
+ } else if (result.verdict.ok) {
754
+ if (!result.checkpoint) unchecked++;
755
+ lines.push(
756
+ ` ok ${result.file} (${result.verdict.lines} lines${result.checkpoint ? ", checkpoint" : ""})`
757
+ );
718
758
  } else {
719
759
  broken++;
720
- lines.push(` BROKEN ${name}: line ${result.line}: ${result.why}`);
760
+ lines.push(` BROKEN ${result.file}: line ${result.verdict.line}: ${result.verdict.why}`);
721
761
  }
722
762
  }
723
- if (broken > 0) lines.push(`${broken} file(s) were changed after Garuda wrote them.`);
724
- lines.push("This check cannot see lines cut from the end of a file, or a removed file.");
763
+ if (broken > 0)
764
+ lines.push(`${broken} file(s) were changed or removed after Garuda wrote them.`);
765
+ if (unchecked > 0) {
766
+ lines.push(
767
+ `${unchecked} file(s) have no checkpoint: this check cannot see lines cut from their end, or a removed file. The team policy can turn on "audit": { "checkpoint": true }.`
768
+ );
769
+ }
770
+ lines.push(
771
+ "Someone with your rights can rewrite a file and its checkpoint together; this check cannot see that."
772
+ );
725
773
  renderer.info(lines.join("\n"));
726
774
  return;
727
775
  }
@@ -767,11 +815,12 @@ function usageSummary(runtime) {
767
815
  const session = runtime.session;
768
816
  if (session === void 0) return "No session yet.";
769
817
  const u = session.usage;
770
- const cost = session.costUsd === void 0 ? "cost unknown" : `$${session.costUsd.toFixed(4)}`;
818
+ const cost = session.costUsd === void 0 ? "cost unknown" : isCostIncomplete(session) ? `\u2265 $${session.costUsd.toFixed(4)} (incomplete)` : `$${session.costUsd.toFixed(4)}`;
771
819
  return [
772
- `Session ${session.id}: ${formatTokens(totalTokens(u))} tokens, ${cost}`,
820
+ `Session ${session.id}: ${formatTokens(totalTokens(u))}${budgetNote(session) !== void 0 ? " reported" : ""} tokens, ${cost}`,
773
821
  ` input ${formatTokens(u.inputTokens)}, cache read ${formatTokens(u.cacheReadTokens)}, cache write ${formatTokens(u.cacheWriteTokens)}, output ${formatTokens(u.outputTokens)}`,
774
822
  ` context ${formatTokens(session.contextTokens)} of ${formatTokens(runtime.limits.contextWindow)}`,
823
+ ...budgetNote(session) === void 0 ? [] : [` ${budgetNote(session)}`],
775
824
  ...u.webSearches === void 0 ? [] : [
776
825
  ` Claude web searches: ${u.webSearches} ($${(u.webSearches * WEB_SEARCH_USD).toFixed(2)})`
777
826
  ]
@@ -1244,4 +1293,4 @@ export {
1244
1293
  statusOf,
1245
1294
  CommandArgs
1246
1295
  };
1247
- //# sourceMappingURL=chunk-ENLDPN4S.js.map
1296
+ //# sourceMappingURL=chunk-Z66BWTMD.js.map