pitroom 0.26.0 → 0.27.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.
package/dist/pitroom.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import fs43 from "node:fs";
4
+ import fs44 from "node:fs";
5
5
 
6
6
  // src/core/errors.ts
7
7
  var UserError = class extends Error {
@@ -558,7 +558,7 @@ function denyCommandRules() {
558
558
  "command(eval)"
559
559
  ];
560
560
  }
561
- function workerSettings(workspace, workerHome3, reads = []) {
561
+ function workerSettings(workspace, workerHome3, reads = [], writes = false) {
562
562
  const real = (p) => {
563
563
  try {
564
564
  return fs6.realpathSync.native(p);
@@ -582,6 +582,8 @@ function workerSettings(workspace, workerHome3, reads = []) {
582
582
  allow: [
583
583
  "command(*)",
584
584
  ...both(path5.join(dataDir(workerHome3), "brain")).map((p) => `write_file(${rulePath(p)})`),
585
+ // agy 1.4 asks for edits in the workspace too, --mode accept-edits or not, and a headless run cannot ask
586
+ ...writes ? both(workspace).map((p) => `write_file(${rulePath(p)})`) : [],
585
587
  ...toolchains().flatMap(both).map((p) => `read_file(${rulePath(p)})`),
586
588
  // what the task names outside the workspace: a headless agy cannot ask, and would end the turn unanswered
587
589
  ...reads.map((r) => r.path).flatMap(both).map((p) => `read_file(${rulePath(p)})`)
@@ -707,12 +709,12 @@ function baseSettings() {
707
709
  }
708
710
  var rulePath = (p) => p.replace(/^[A-Za-z]:(?=[\\/])/, "").replace(/\\/g, "/");
709
711
  var HOMES_KEPT_DAYS = 14;
710
- function workerHome(workspace, reads = []) {
712
+ function workerHome(workspace, reads = [], writes = false) {
711
713
  const root = path5.join(home(), "antigravity-home");
712
714
  const id2 = crypto3.createHash("sha256").update(path5.resolve(workspace)).digest("hex").slice(0, 16);
713
715
  const dir = path5.join(root, id2);
714
716
  fs6.mkdirSync(dataDir(dir), { recursive: true });
715
- writeIfChanged(path5.join(dataDir(dir), "settings.json"), `${JSON.stringify(workerSettings(workspace, dir, reads), null, 2)}
717
+ writeIfChanged(path5.join(dataDir(dir), "settings.json"), `${JSON.stringify(workerSettings(workspace, dir, reads, writes), null, 2)}
716
718
  `);
717
719
  if (process.platform === "darwin") {
718
720
  fs6.mkdirSync(path5.join(dir, "Library"), { recursive: true });
@@ -769,7 +771,7 @@ function invocation(req) {
769
771
  if (model) args.push("--model", model);
770
772
  if (effort) args.push("--effort", effort);
771
773
  const { command, prefix } = resolveCommand(binary());
772
- const workerDir = workerHome(req.cwd, outsideReads(prompt, req.cwd, req.project?.root));
774
+ const workerDir = workerHome(req.cwd, outsideReads(prompt, req.cwd, req.project?.root), req.mode !== "read");
773
775
  const env = { HOME: workerDir };
774
776
  if (process.platform === "win32") env.USERPROFILE = workerDir;
775
777
  return { command, args: [...prefix, ...args], env };
@@ -826,9 +828,10 @@ function doctor({ models, hasFallback }) {
826
828
  }
827
829
  const checks = [
828
830
  { level: "ok", message: `Antigravity CLI ${version.out.trim().split("\n")[0]} at ${binary()}` },
829
- {
831
+ // checked on macOS (agy 1.4.7, scripts/check-worker.mjs); Linux and Windows not run yet
832
+ process.platform === "darwin" ? { level: "ok", message: "agy has no read-only mode for headless runs, so its read runs always read a snapshot of the project; prefer -i for changes" } : {
830
833
  level: "warn",
831
- message: "the Antigravity worker is in beta (checked against agy 1.3.5). agy has no read-only mode for headless runs, so its read runs always read a snapshot of the project; prefer -i for changes"
834
+ message: `the Antigravity worker is in beta on ${process.platform === "win32" ? "Windows" : "Linux"} (checked on macOS only; node scripts/check-worker.mjs antigravity tries it here). agy has no read-only mode for headless runs, so its read runs always read a snapshot of the project; prefer -i for changes`
832
835
  }
833
836
  ];
834
837
  if (process.platform === "win32") {
@@ -2343,7 +2346,8 @@ var SCHEMA = {
2343
2346
  priceFeedUrl: "string",
2344
2347
  priceFeedHours: "number",
2345
2348
  budgetDaily: "number",
2346
- rankWorkers: "boolean"
2349
+ rankWorkers: "boolean",
2350
+ autoFallback: "boolean"
2347
2351
  };
2348
2352
  function configPath() {
2349
2353
  if (process.env.PITROOM_CONFIG) return path12.resolve(process.env.PITROOM_CONFIG);
@@ -2453,6 +2457,7 @@ function effective(flags = {}) {
2453
2457
  priceFeedUrl: setting(void 0, e.PITROOM_PRICE_FEED_URL?.trim() || void 0, c.priceFeedUrl?.trim() || void 0, "https://models.dev/api.json"),
2454
2458
  priceFeedHours: setting(void 0, positiveInt(e.PITROOM_PRICE_FEED_HOURS), positiveInt(c.priceFeedHours), 24),
2455
2459
  rankWorkers: setting(void 0, flag01(e.PITROOM_RANK_WORKERS), c.rankWorkers, false),
2460
+ autoFallback: setting(void 0, flag01(e.PITROOM_AUTO_FALLBACK), c.autoFallback, false),
2456
2461
  budgetDaily: setting(void 0, amount(e.PITROOM_BUDGET_DAILY), amount(c.budgetDaily), void 0)
2457
2462
  };
2458
2463
  }
@@ -2639,9 +2644,9 @@ function effortFlag(p) {
2639
2644
  import readline from "node:readline";
2640
2645
 
2641
2646
  // src/core/run.ts
2642
- import { spawn as spawn4, spawnSync as spawnSync9 } from "node:child_process";
2643
- import fs31 from "node:fs";
2644
- import path27 from "node:path";
2647
+ import { spawn as spawn4, spawnSync as spawnSync10 } from "node:child_process";
2648
+ import fs32 from "node:fs";
2649
+ import path28 from "node:path";
2645
2650
 
2646
2651
  // src/vcs/git.ts
2647
2652
  import { spawnSync as spawnSync8 } from "node:child_process";
@@ -2772,7 +2777,29 @@ function applyPatch(root, patchFile, reverse) {
2772
2777
  function reviewDiff(root, a, b) {
2773
2778
  return `${must(root, [...DIFF, "--stat", a, b]).trim()}
2774
2779
 
2775
- ${must(root, [...DIFF, "-U10", a, b])}`;
2780
+ ${numberDiff(must(root, [...DIFF, "-U10", a, b]))}`;
2781
+ }
2782
+ var NUMBERED = "Each line of a hunk starts with its line number in the file after the change (a removed line has none): cite those.";
2783
+ function numberDiff(diff) {
2784
+ let next = 0;
2785
+ let width = 0;
2786
+ let inHunk = false;
2787
+ return diff.split("\n").map((l) => {
2788
+ if (l.startsWith("diff --git")) {
2789
+ inHunk = false;
2790
+ return l;
2791
+ }
2792
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(l);
2793
+ if (hunk) {
2794
+ inHunk = true;
2795
+ next = Number(hunk[1]);
2796
+ width = String(next + Number(hunk[2] ?? 1)).length;
2797
+ return l;
2798
+ }
2799
+ if (!inHunk || !l || !"+- ".includes(l[0])) return l;
2800
+ if (l[0] === "-") return `-${" ".repeat(width)}| ${l.slice(1)}`;
2801
+ return `${l[0]}${String(next++).padStart(width)}| ${l.slice(1)}`;
2802
+ }).join("\n");
2776
2803
  }
2777
2804
  function rangeDiff(root, a, b) {
2778
2805
  const range = `${a}..${b}`;
@@ -2787,7 +2814,9 @@ ${log2 || "(none)"}`,
2787
2814
  ${must(root, [...DIFF, "--stat", since2]).trim() || "(none)"}`,
2788
2815
  `## DIFF
2789
2816
 
2790
- ${must(root, [...DIFF, "-U10", since2])}`
2817
+ ${NUMBERED}
2818
+
2819
+ ${numberDiff(must(root, [...DIFF, "-U10", since2]))}`
2791
2820
  ].join("\n\n");
2792
2821
  }
2793
2822
  function commitOf(root, ref) {
@@ -2799,6 +2828,10 @@ function gitDir(dir) {
2799
2828
  return r.code === 0 ? r.stdout.trim() : void 0;
2800
2829
  }
2801
2830
 
2831
+ // src/core/chain.ts
2832
+ import fs15 from "node:fs";
2833
+ import path14 from "node:path";
2834
+
2802
2835
  // src/core/target.ts
2803
2836
  function parseTarget(spec, defaultBackend) {
2804
2837
  const s = spec.trim();
@@ -2857,17 +2890,52 @@ function resolveChain(flags = {}) {
2857
2890
  warnings2.push(`fallback ${s} skipped: ${e.message}`);
2858
2891
  }
2859
2892
  }
2893
+ if (!fallback.length && eff.autoFallback.value) fallback.push(...autoFallback(worker, withDefault));
2860
2894
  }
2861
2895
  return { worker, fallback, warnings: warnings2 };
2862
2896
  }
2897
+ var AUTO_MAX = 4;
2898
+ var bare = (t) => `${t.backend}:${(t.model ?? "").split("#")[0]}`;
2899
+ function installed(backend) {
2900
+ try {
2901
+ const bin = getBackend(backend).binary();
2902
+ return path14.isAbsolute(bin) && fs15.statSync(bin).isFile();
2903
+ } catch {
2904
+ return false;
2905
+ }
2906
+ }
2907
+ function autoFallback(worker, withDefault = (t) => t) {
2908
+ const eff = effective();
2909
+ const named = [
2910
+ ...Object.values(eff.tiers.value),
2911
+ ...Object.entries(eff.models.value).map(([backend, model]) => `${backend}:${model}`),
2912
+ eff.worker.value
2913
+ ];
2914
+ const costs = eff.costs.value;
2915
+ const out = [];
2916
+ for (const spec of named) {
2917
+ let t;
2918
+ try {
2919
+ t = withDefault(parseTarget(spec, worker.backend));
2920
+ getBackend(t.backend);
2921
+ } catch {
2922
+ continue;
2923
+ }
2924
+ if (bare(t) === bare(worker) || out.some((o) => bare(o) === bare(t)) || !installed(t.backend)) continue;
2925
+ out.push(t);
2926
+ }
2927
+ const cost = (t) => costs[bare(t)] ?? Infinity;
2928
+ return out.map((t, i) => ({ t, i })).sort((a, b) => cost(a.t) - cost(b.t) || a.i - b.i).slice(0, AUTO_MAX).map((x) => x.t);
2929
+ }
2863
2930
 
2864
2931
  // src/core/process.ts
2865
2932
  import { spawn } from "node:child_process";
2866
- import fs16 from "node:fs";
2933
+ import fs17 from "node:fs";
2867
2934
 
2868
2935
  // src/vcs/guard.ts
2869
- import fs15 from "node:fs";
2870
- import path14 from "node:path";
2936
+ import { spawnSync as spawnSync9 } from "node:child_process";
2937
+ import fs16 from "node:fs";
2938
+ import path15 from "node:path";
2871
2939
  var VERSION = 2;
2872
2940
  var WINDOWS = process.platform === "win32";
2873
2941
  var fwd = (p) => WINDOWS ? p.replace(/\\/g, "/") : p;
@@ -2944,38 +3012,38 @@ esac
2944
3012
  exec "$real" "$@"
2945
3013
  `;
2946
3014
  function shimDir() {
2947
- return path14.join(home(), "shim", `v${VERSION}`);
3015
+ return path15.join(home(), "shim", `v${VERSION}`);
2948
3016
  }
2949
3017
  function scoopTarget(exe) {
2950
3018
  try {
2951
- const text = fs15.readFileSync(exe.replace(/\.exe$/i, ".shim"), "utf8");
3019
+ const text = fs16.readFileSync(exe.replace(/\.exe$/i, ".shim"), "utf8");
2952
3020
  const target = /^\s*path\s*=\s*"?([^"\r\n]+?)"?\s*$/m.exec(text)?.[1];
2953
- return target && fs15.statSync(target).isFile() ? target : void 0;
3021
+ return target && fs16.statSync(target).isFile() ? target : void 0;
2954
3022
  } catch {
2955
3023
  return void 0;
2956
3024
  }
2957
3025
  }
2958
3026
  function findRealGit(skip) {
2959
3027
  const same2 = (a, b) => WINDOWS ? a.toLowerCase() === b.toLowerCase() : a === b;
2960
- for (const dir of (process.env.PATH ?? "").split(path14.delimiter).filter(Boolean)) {
2961
- if (same2(path14.resolve(dir), skip)) continue;
2962
- const p = path14.join(dir, WINDOWS ? "git.exe" : "git");
3028
+ for (const dir of (process.env.PATH ?? "").split(path15.delimiter).filter(Boolean)) {
3029
+ if (same2(path15.resolve(dir), skip)) continue;
3030
+ const p = path15.join(dir, WINDOWS ? "git.exe" : "git");
2963
3031
  try {
2964
- fs15.accessSync(p, fs15.constants.X_OK);
2965
- if (fs15.statSync(p).isFile()) return WINDOWS && scoopTarget(p) || p;
3032
+ fs16.accessSync(p, fs16.constants.X_OK);
3033
+ if (fs16.statSync(p).isFile()) return WINDOWS && scoopTarget(p) || p;
2966
3034
  } catch {
2967
3035
  }
2968
3036
  }
2969
3037
  return void 0;
2970
3038
  }
2971
3039
  function findGitSh(realGit) {
2972
- let dir = path14.dirname(realGit);
3040
+ let dir = path15.dirname(realGit);
2973
3041
  for (let i = 0; i < 4; i++) {
2974
3042
  for (const rel of ["bin/sh.exe", "usr/bin/sh.exe"]) {
2975
- const p = path14.join(dir, rel);
2976
- if (fs15.existsSync(p)) return p;
3043
+ const p = path15.join(dir, rel);
3044
+ if (fs16.existsSync(p)) return p;
2977
3045
  }
2978
- const up = path14.dirname(dir);
3046
+ const up = path15.dirname(dir);
2979
3047
  if (up === dir) break;
2980
3048
  dir = up;
2981
3049
  }
@@ -3003,6 +3071,63 @@ var cmdShim = (sh) => [
3003
3071
  "exit /b %errorlevel%",
3004
3072
  ""
3005
3073
  ].join("\r\n");
3074
+ var launcherSource = (sh) => `// pitroom git guard: git.exe launcher v${VERSION} (generated; edit src/vcs/guard.ts)
3075
+ using System;
3076
+ using System.Diagnostics;
3077
+ using System.IO;
3078
+
3079
+ static class PitroomGit {
3080
+ const string Sh = @"${sh.replace(/"/g, '""')}";
3081
+
3082
+ static int Main() {
3083
+ string line = Environment.CommandLine;
3084
+ // this program's own name, as Windows reads it: up to the closing quote when it starts with one, else to a space
3085
+ int i = 0;
3086
+ if (line.Length > 0 && line[0] == '"') { int q = line.IndexOf('"', 1); i = q < 0 ? line.Length : q + 1; }
3087
+ else { while (i < line.Length && line[i] != ' ' && line[i] != '\\t') i++; }
3088
+ string script = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "git");
3089
+ ProcessStartInfo start = new ProcessStartInfo(Sh, "\\"" + script + "\\"" + line.Substring(i));
3090
+ start.UseShellExecute = false;
3091
+ start.EnvironmentVariables["MSYS2_ARG_CONV_EXCL"] = "*";
3092
+ // Ctrl+C reaches git too: wait for it to end and pass its exit code on
3093
+ Console.CancelKeyPress += delegate (object o, ConsoleCancelEventArgs e) { e.Cancel = true; };
3094
+ using (Process git = Process.Start(start)) {
3095
+ git.WaitForExit();
3096
+ return git.ExitCode;
3097
+ }
3098
+ }
3099
+ }
3100
+ `;
3101
+ function findCsc() {
3102
+ const windir = process.env.WINDIR ?? process.env.SystemRoot ?? "C:\\Windows";
3103
+ for (const fw of ["Framework64", "Framework"]) {
3104
+ const p = path15.join(windir, "Microsoft.NET", fw, "v4.0.30319", "csc.exe");
3105
+ if (fs16.existsSync(p)) return p;
3106
+ }
3107
+ return void 0;
3108
+ }
3109
+ function buildLauncher(dir, sh) {
3110
+ const source = launcherSource(sh);
3111
+ const src = path15.join(dir, "git-launcher.cs");
3112
+ const exe = path15.join(dir, "git.exe");
3113
+ if (fs16.existsSync(exe) && fs16.existsSync(src) && fs16.readFileSync(src, "utf8") === source) return;
3114
+ const csc = findCsc();
3115
+ if (!csc) return;
3116
+ const tmp = path15.join(dir, `git.${process.pid}.tmp.exe`);
3117
+ const tmpSrc = `${src}.${process.pid}.tmp`;
3118
+ try {
3119
+ fs16.mkdirSync(dir, { recursive: true });
3120
+ fs16.writeFileSync(tmpSrc, source);
3121
+ const r = spawnSync9(csc, ["/nologo", "/optimize+", "/target:exe", `/out:${tmp}`, tmpSrc], { encoding: "utf8", timeout: 6e4, windowsHide: true });
3122
+ if (r.status !== 0 || !fs16.existsSync(tmp)) return;
3123
+ renameOver(tmp, exe);
3124
+ renameOver(tmpSrc, src);
3125
+ } catch {
3126
+ } finally {
3127
+ fs16.rmSync(tmp, { force: true });
3128
+ fs16.rmSync(tmpSrc, { force: true });
3129
+ }
3130
+ }
3006
3131
  var HOOK_VERSION = 1;
3007
3132
  var REF_HOOK = `#!/bin/sh
3008
3133
  # pitroom git guard (layer 2, v${HOOK_VERSION}): refuse ref updates made by workers.
@@ -3010,12 +3135,12 @@ var REF_HOOK = `#!/bin/sh
3010
3135
  echo "pitroom: git ref updates (commit, reset, branch, tag, stash, rebase, merge) are blocked for workers" >&2
3011
3136
  exit 1
3012
3137
  `;
3013
- var hooksDir = () => path14.join(home(), "git-hooks", `v${HOOK_VERSION}`);
3138
+ var hooksDir = () => path15.join(home(), "git-hooks", `v${HOOK_VERSION}`);
3014
3139
  function writeIfChanged2(file2, content) {
3015
- if (fs15.existsSync(file2) && fs15.readFileSync(file2, "utf8") === content) return;
3016
- fs15.mkdirSync(path14.dirname(file2), { recursive: true });
3140
+ if (fs16.existsSync(file2) && fs16.readFileSync(file2, "utf8") === content) return;
3141
+ fs16.mkdirSync(path15.dirname(file2), { recursive: true });
3017
3142
  const tmp = `${file2}.${process.pid}.tmp`;
3018
- fs15.writeFileSync(tmp, content, { mode: 493 });
3143
+ fs16.writeFileSync(tmp, content, { mode: 493 });
3019
3144
  renameOver(tmp, file2);
3020
3145
  }
3021
3146
  function pathKey(env) {
@@ -3047,7 +3172,7 @@ function guardEnv(env) {
3047
3172
  GIT_PAGER: "cat",
3048
3173
  PAGER: "cat"
3049
3174
  };
3050
- writeIfChanged2(path14.join(hooksDir(), "reference-transaction"), REF_HOOK);
3175
+ writeIfChanged2(path15.join(hooksDir(), "reference-transaction"), REF_HOOK);
3051
3176
  env = withGitConfig(onePath(env), [
3052
3177
  ["core.hooksPath", fwd(hooksDir())],
3053
3178
  ["url.pitroom-push-blocked://.pushInsteadOf", ""]
@@ -3059,11 +3184,12 @@ function guardEnv(env) {
3059
3184
  if (WINDOWS) {
3060
3185
  sh = findGitSh(real);
3061
3186
  if (!sh) return { ...env, ...quiet };
3062
- writeIfChanged2(path14.join(dir, "git.cmd"), cmdShim(sh));
3187
+ writeIfChanged2(path15.join(dir, "git.cmd"), cmdShim(sh));
3188
+ buildLauncher(dir, sh);
3063
3189
  }
3064
- writeIfChanged2(path14.join(dir, "git"), SHIM);
3190
+ writeIfChanged2(path15.join(dir, "git"), SHIM);
3065
3191
  const key = pathKey(env);
3066
- return { ...env, ...quiet, PITROOM_REAL_GIT: fwd(real), [key]: `${dir}${path14.delimiter}${env[key] ?? ""}` };
3192
+ return { ...env, ...quiet, PITROOM_REAL_GIT: fwd(real), [key]: `${dir}${path15.delimiter}${env[key] ?? ""}` };
3067
3193
  }
3068
3194
  function shimReady() {
3069
3195
  const env = guardEnv({ ...process.env });
@@ -3072,8 +3198,8 @@ function shimReady() {
3072
3198
 
3073
3199
  // src/core/process.ts
3074
3200
  async function spawnWorker(inv, opts) {
3075
- const out = fs16.openSync(opts.stdoutFile, "w");
3076
- const err = fs16.openSync(opts.stderrFile, "w");
3201
+ const out = fs17.openSync(opts.stdoutFile, "w");
3202
+ const err = fs17.openSync(opts.stderrFile, "w");
3077
3203
  const res = { code: null, timedOut: false, stopped: false };
3078
3204
  const child = spawn(inv.command, inv.args, {
3079
3205
  cwd: opts.cwd,
@@ -3096,7 +3222,7 @@ async function spawnWorker(inv, opts) {
3096
3222
  }, opts.timeoutSec * 1e3);
3097
3223
  res.code = await new Promise((resolve2) => {
3098
3224
  child.on("error", (e) => {
3099
- res.spawnError = e.code === "ENOENT" ? fs16.existsSync(opts.cwd) ? `${inv.command} not found` : `the working directory ${opts.cwd} does not exist` : e.message;
3225
+ res.spawnError = e.code === "ENOENT" ? fs17.existsSync(opts.cwd) ? `${inv.command} not found` : `the working directory ${opts.cwd} does not exist` : e.message;
3100
3226
  resolve2(127);
3101
3227
  });
3102
3228
  child.on("close", (c) => resolve2(c));
@@ -3105,8 +3231,8 @@ async function spawnWorker(inv, opts) {
3105
3231
  if (killer) clearTimeout(killer);
3106
3232
  process.off("SIGINT", stop);
3107
3233
  process.off("SIGTERM", stop);
3108
- fs16.closeSync(out);
3109
- fs16.closeSync(err);
3234
+ fs17.closeSync(out);
3235
+ fs17.closeSync(err);
3110
3236
  return res;
3111
3237
  }
3112
3238
 
@@ -3138,13 +3264,13 @@ function parseAudit(text) {
3138
3264
  }
3139
3265
 
3140
3266
  // src/core/cooldown.ts
3141
- import fs17 from "node:fs";
3142
- import path15 from "node:path";
3267
+ import fs18 from "node:fs";
3268
+ import path16 from "node:path";
3143
3269
  var MAX_MS = 24 * 36e5;
3144
3270
  var OVERLOAD_MS = 20 * 6e4;
3145
3271
  var USAGE_MS = 36e5;
3146
3272
  var DAILY_MS = 4 * 36e5;
3147
- var file = () => path15.join(home(), "cooldowns.json");
3273
+ var file = () => path16.join(home(), "cooldowns.json");
3148
3274
  function cooldownKey(target, backend) {
3149
3275
  const model = (target.model ?? backend.defaultModel?.() ?? "").split("#")[0];
3150
3276
  return `${target.backend}:${model}`;
@@ -3198,16 +3324,16 @@ function cooldownMs(message, now = Date.now()) {
3198
3324
  }
3199
3325
  function read() {
3200
3326
  try {
3201
- const raw = JSON.parse(fs17.readFileSync(file(), "utf8"));
3327
+ const raw = JSON.parse(fs18.readFileSync(file(), "utf8"));
3202
3328
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
3203
3329
  } catch {
3204
3330
  return {};
3205
3331
  }
3206
3332
  }
3207
3333
  function write(all) {
3208
- fs17.mkdirSync(path15.dirname(file()), { recursive: true });
3334
+ fs18.mkdirSync(path16.dirname(file()), { recursive: true });
3209
3335
  const tmp = `${file()}.${process.pid}.tmp`;
3210
- fs17.writeFileSync(tmp, `${JSON.stringify(all, null, 2)}
3336
+ fs18.writeFileSync(tmp, `${JSON.stringify(all, null, 2)}
3211
3337
  `);
3212
3338
  renameOver(tmp, file());
3213
3339
  }
@@ -3237,15 +3363,15 @@ function untilText(c, now = Date.now()) {
3237
3363
  }
3238
3364
 
3239
3365
  // src/core/snapshot.ts
3240
- import fs18 from "node:fs";
3241
- import path16 from "node:path";
3366
+ import fs19 from "node:fs";
3367
+ import path17 from "node:path";
3242
3368
  import crypto5 from "node:crypto";
3243
3369
  var READ_IN = ["auto", "snapshot", "project"];
3244
3370
  var KEEP_MS = 24 * 36e5;
3245
3371
  var PRUNE_AGE_MS = 3 * 24 * 36e5;
3246
3372
  var KEEP_NEWEST = 6;
3247
3373
  var JUST_USED_MS = 10 * 6e4;
3248
- var snapshotsDir = () => path16.join(home(), "snapshots");
3374
+ var snapshotsDir = () => path17.join(home(), "snapshots");
3249
3375
  function wantSnapshot(readIn2, root, dir) {
3250
3376
  if (!root || readIn2 === "project") return void 0;
3251
3377
  const secrets = findSecretFiles(dir);
@@ -3254,8 +3380,8 @@ function wantSnapshot(readIn2, root, dir) {
3254
3380
  function readSnapshot(root, owner) {
3255
3381
  const drop2 = findSecretFilesInTree(root, root);
3256
3382
  const tree = snapshotTree(root, [], drop2);
3257
- const dest = path16.join(snapshotsDir(), owner ? `${tree}-${owner}` : tree);
3258
- if (fs18.existsSync(path16.join(dest, ".git"))) {
3383
+ const dest = path17.join(snapshotsDir(), owner ? `${tree}-${owner}` : tree);
3384
+ if (fs19.existsSync(path17.join(dest, ".git"))) {
3259
3385
  touch(dest);
3260
3386
  return { dir: dest, tree, created: false };
3261
3387
  }
@@ -3264,19 +3390,19 @@ function readSnapshot(root, owner) {
3264
3390
  return { dir: dest, tree, created: true };
3265
3391
  }
3266
3392
  function restoreSnapshot(root, dir, tree) {
3267
- const rel = path16.relative(snapshotsDir(), dir);
3268
- if (!rel || rel.startsWith("..") || path16.isAbsolute(rel)) throw new Error(`not a snapshot: ${dir}`);
3269
- if (!fs18.existsSync(path16.join(dir, ".git"))) materialize(root, tree, dir);
3393
+ const rel = path17.relative(snapshotsDir(), dir);
3394
+ if (!rel || rel.startsWith("..") || path17.isAbsolute(rel)) throw new Error(`not a snapshot: ${dir}`);
3395
+ if (!fs19.existsSync(path17.join(dir, ".git"))) materialize(root, tree, dir);
3270
3396
  }
3271
3397
  function materialize(root, tree, dest) {
3272
- const tmp = path16.join(snapshotsDir(), `.tmp-${process.pid}-${crypto5.randomBytes(3).toString("hex")}`);
3398
+ const tmp = path17.join(snapshotsDir(), `.tmp-${process.pid}-${crypto5.randomBytes(3).toString("hex")}`);
3273
3399
  try {
3274
3400
  createIsolatedCopy(root, tree, tmp);
3275
3401
  readOnly(tmp);
3276
3402
  try {
3277
- fs18.renameSync(tmp, dest);
3403
+ fs19.renameSync(tmp, dest);
3278
3404
  } catch (e) {
3279
- if (!fs18.existsSync(path16.join(dest, ".git"))) throw e;
3405
+ if (!fs19.existsSync(path17.join(dest, ".git"))) throw e;
3280
3406
  removeIsolatedCopy(tmp, snapshotsDir());
3281
3407
  }
3282
3408
  } catch (e) {
@@ -3290,17 +3416,17 @@ function materialize(root, tree, dest) {
3290
3416
  function touch(dir) {
3291
3417
  try {
3292
3418
  const now = /* @__PURE__ */ new Date();
3293
- fs18.utimesSync(dir, now, now);
3419
+ fs19.utimesSync(dir, now, now);
3294
3420
  } catch {
3295
3421
  }
3296
3422
  }
3297
3423
  function readOnly(dir) {
3298
3424
  const walk = (d) => {
3299
- for (const e of fs18.readdirSync(d, { withFileTypes: true })) {
3425
+ for (const e of fs19.readdirSync(d, { withFileTypes: true })) {
3300
3426
  if (e.name === ".git") continue;
3301
- const p = path16.join(d, e.name);
3427
+ const p = path17.join(d, e.name);
3302
3428
  if (e.isDirectory()) walk(p);
3303
- else if (e.isFile()) fs18.chmodSync(p, fs18.statSync(p).mode & ~146);
3429
+ else if (e.isFile()) fs19.chmodSync(p, fs19.statSync(p).mode & ~146);
3304
3430
  }
3305
3431
  };
3306
3432
  try {
@@ -3311,7 +3437,7 @@ function readOnly(dir) {
3311
3437
  function prune(now = Date.now(), maxAgeMs = PRUNE_AGE_MS, keepNewest = KEEP_NEWEST) {
3312
3438
  let names;
3313
3439
  try {
3314
- names = fs18.readdirSync(snapshotsDir());
3440
+ names = fs19.readdirSync(snapshotsDir());
3315
3441
  } catch {
3316
3442
  return 0;
3317
3443
  }
@@ -3319,14 +3445,14 @@ function prune(now = Date.now(), maxAgeMs = PRUNE_AGE_MS, keepNewest = KEEP_NEWE
3319
3445
  for (const id2 of listRunIds()) {
3320
3446
  try {
3321
3447
  const m = readMeta(id2);
3322
- if (m.snapshot?.dir && isActive(m.state)) inUse.add(path16.resolve(m.snapshot.dir));
3448
+ if (m.snapshot?.dir && isActive(m.state)) inUse.add(path17.resolve(m.snapshot.dir));
3323
3449
  } catch {
3324
3450
  }
3325
3451
  }
3326
3452
  const entries = names.map((n) => {
3327
- const dir = path16.join(snapshotsDir(), n);
3453
+ const dir = path17.join(snapshotsDir(), n);
3328
3454
  try {
3329
- return { n, dir, age: now - fs18.statSync(dir).mtimeMs };
3455
+ return { n, dir, age: now - fs19.statSync(dir).mtimeMs };
3330
3456
  } catch {
3331
3457
  return void 0;
3332
3458
  }
@@ -3334,7 +3460,7 @@ function prune(now = Date.now(), maxAgeMs = PRUNE_AGE_MS, keepNewest = KEEP_NEWE
3334
3460
  let removed = 0;
3335
3461
  const rank = { shared: 0, own: 0 };
3336
3462
  for (const e of entries) {
3337
- if (inUse.has(path16.resolve(e.dir))) continue;
3463
+ if (inUse.has(path17.resolve(e.dir))) continue;
3338
3464
  const half = e.n.startsWith(".tmp-");
3339
3465
  const old = e.age >= (half ? KEEP_MS : maxAgeMs);
3340
3466
  const surplus = !half && ++rank[e.n.includes("-") ? "own" : "shared"] > keepNewest && e.age >= JUST_USED_MS;
@@ -3350,10 +3476,10 @@ function prune(now = Date.now(), maxAgeMs = PRUNE_AGE_MS, keepNewest = KEEP_NEWE
3350
3476
  }
3351
3477
  function makeWritable(dir) {
3352
3478
  const walk = (d) => {
3353
- for (const e of fs18.readdirSync(d, { withFileTypes: true })) {
3354
- const p = path16.join(d, e.name);
3479
+ for (const e of fs19.readdirSync(d, { withFileTypes: true })) {
3480
+ const p = path17.join(d, e.name);
3355
3481
  if (e.isDirectory()) walk(p);
3356
- else if (e.isFile()) fs18.chmodSync(p, fs18.statSync(p).mode | 128);
3482
+ else if (e.isFile()) fs19.chmodSync(p, fs19.statSync(p).mode | 128);
3357
3483
  }
3358
3484
  };
3359
3485
  try {
@@ -3364,17 +3490,17 @@ function makeWritable(dir) {
3364
3490
  function backToProject(text, snapshotDir, root) {
3365
3491
  let real = snapshotDir;
3366
3492
  try {
3367
- real = fs18.realpathSync(snapshotDir);
3493
+ real = fs19.realpathSync(snapshotDir);
3368
3494
  } catch {
3369
3495
  }
3370
3496
  let out = text;
3371
- for (const dir of /* @__PURE__ */ new Set([real, snapshotDir])) out = out.split(`${dir}${path16.sep}`).join(`${root}${path16.sep}`).split(dir).join(root);
3497
+ for (const dir of /* @__PURE__ */ new Set([real, snapshotDir])) out = out.split(`${dir}${path17.sep}`).join(`${root}${path17.sep}`).split(dir).join(root);
3372
3498
  return out;
3373
3499
  }
3374
3500
 
3375
3501
  // src/core/audit.ts
3376
3502
  import crypto6 from "node:crypto";
3377
- import path17 from "node:path";
3503
+ import path18 from "node:path";
3378
3504
  var ANSWER_MAX = 8e3;
3379
3505
  function auditRate(meta) {
3380
3506
  return meta.auditRate ?? effective().audit.value;
@@ -3414,12 +3540,12 @@ function auditors(meta) {
3414
3540
  const tiers = eff.tiers.value;
3415
3541
  const def = parseTarget(eff.worker.value, DEFAULT_BACKEND).backend;
3416
3542
  const ran = meta.ran ?? meta.worker;
3417
- const bare = (t) => ({ backend: t.backend, model: t.model?.split("#")[0] || void 0 });
3543
+ const bare2 = (t) => ({ backend: t.backend, model: t.model?.split("#")[0] || void 0 });
3418
3544
  const candidates = [tiers.audit, tiers.cheap, ...eff.fallback.value, eff.worker.value].filter((s) => !!s);
3419
3545
  const out = [];
3420
3546
  for (const c of candidates) {
3421
- const t = bare(parseTarget(c, def));
3422
- if (sameTarget(t, bare(ran)) || out.some((o) => sameTarget(bare(parseTarget(o, def)), t))) continue;
3547
+ const t = bare2(parseTarget(c, def));
3548
+ if (sameTarget(t, bare2(ran)) || out.some((o) => sameTarget(bare2(parseTarget(o, def)), t))) continue;
3423
3549
  out.push(c);
3424
3550
  }
3425
3551
  return out;
@@ -3428,9 +3554,9 @@ function relativeToAuditor(answer, meta) {
3428
3554
  let out = answer;
3429
3555
  const roots = [...new Set([meta.dir, meta.repoRoot].filter((r) => !!r))].sort((a, b) => b.length - a.length);
3430
3556
  for (const root of roots) {
3431
- const rel = path17.relative(meta.dir, root);
3432
- const prefix = rel ? `${rel.split(path17.sep).join("/")}/` : "";
3433
- out = out.split(`${root}${path17.sep}`).join(prefix).replace(new RegExp(`${root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w.-])`, "g"), rel || ".");
3557
+ const rel = path18.relative(meta.dir, root);
3558
+ const prefix = rel ? `${rel.split(path18.sep).join("/")}/` : "";
3559
+ out = out.split(`${root}${path18.sep}`).join(prefix).replace(new RegExp(`${root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w.-])`, "g"), rel || ".");
3434
3560
  }
3435
3561
  return out;
3436
3562
  }
@@ -3500,19 +3626,19 @@ function auditBadge(meta) {
3500
3626
  }
3501
3627
 
3502
3628
  // src/core/history.ts
3503
- import fs22 from "node:fs";
3629
+ import fs23 from "node:fs";
3504
3630
  import { createRequire } from "node:module";
3505
- import path20 from "node:path";
3631
+ import path21 from "node:path";
3506
3632
  import zlib from "node:zlib";
3507
3633
 
3508
3634
  // src/core/receipt.ts
3509
- import fs20 from "node:fs";
3510
- import path19 from "node:path";
3635
+ import fs21 from "node:fs";
3636
+ import path20 from "node:path";
3511
3637
 
3512
3638
  // src/core/prices.ts
3513
3639
  import { spawn as spawn2 } from "node:child_process";
3514
- import fs19 from "node:fs";
3515
- import path18 from "node:path";
3640
+ import fs20 from "node:fs";
3641
+ import path19 from "node:path";
3516
3642
  var DEFAULT_URL = "https://models.dev/api.json";
3517
3643
  var RETRY_AFTER_FAILURE_MS = 60 * 6e4;
3518
3644
  var MIN_MODELS = 100;
@@ -3525,8 +3651,8 @@ function catalogLabel() {
3525
3651
  return url;
3526
3652
  }
3527
3653
  }
3528
- var catalogFile = () => path18.join(home(), "prices.json");
3529
- var attemptFile = () => path18.join(home(), "prices.attempt");
3654
+ var catalogFile = () => path19.join(home(), "prices.json");
3655
+ var attemptFile = () => path19.join(home(), "prices.attempt");
3530
3656
  function trim(raw) {
3531
3657
  const out = {};
3532
3658
  if (!raw || typeof raw !== "object") return out;
@@ -3544,9 +3670,9 @@ var cached2;
3544
3670
  function readCatalog() {
3545
3671
  const file2 = catalogFile();
3546
3672
  try {
3547
- const { mtimeMs } = fs19.statSync(file2);
3673
+ const { mtimeMs } = fs20.statSync(file2);
3548
3674
  if (cached2?.file === file2 && cached2.mtimeMs === mtimeMs) return cached2.catalog;
3549
- const parsed = JSON.parse(fs19.readFileSync(file2, "utf8"));
3675
+ const parsed = JSON.parse(fs20.readFileSync(file2, "utf8"));
3550
3676
  const catalog6 = parsed && typeof parsed.models === "object" && parsed.models && Number.isFinite(Date.parse(parsed.fetchedAt)) ? parsed : void 0;
3551
3677
  cached2 = { file: file2, mtimeMs, catalog: catalog6 };
3552
3678
  return catalog6;
@@ -3556,7 +3682,7 @@ function readCatalog() {
3556
3682
  }
3557
3683
  var ageMs = (file2) => {
3558
3684
  try {
3559
- return Date.now() - fs19.statSync(file2).mtimeMs;
3685
+ return Date.now() - fs20.statSync(file2).mtimeMs;
3560
3686
  } catch {
3561
3687
  return Infinity;
3562
3688
  }
@@ -3575,17 +3701,17 @@ async function refreshCatalog() {
3575
3701
  const models = trim(await r.json());
3576
3702
  const n = Object.keys(models).length;
3577
3703
  if (n < MIN_MODELS) throw new Error(`only ${n} priced models in it (a catalog has thousands): not used`);
3578
- fs19.mkdirSync(home(), { recursive: true });
3704
+ fs20.mkdirSync(home(), { recursive: true });
3579
3705
  const tmp = `${catalogFile()}.${process.pid}.tmp`;
3580
3706
  const catalog6 = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), source: url, models };
3581
- fs19.writeFileSync(tmp, JSON.stringify(catalog6));
3707
+ fs20.writeFileSync(tmp, JSON.stringify(catalog6));
3582
3708
  renameOver(tmp, catalogFile());
3583
- fs19.rmSync(attemptFile(), { force: true });
3709
+ fs20.rmSync(attemptFile(), { force: true });
3584
3710
  return { state: "updated", models: n };
3585
3711
  } catch (e) {
3586
3712
  try {
3587
- fs19.mkdirSync(home(), { recursive: true });
3588
- fs19.writeFileSync(attemptFile(), `${(/* @__PURE__ */ new Date()).toISOString()} ${e.message}
3713
+ fs20.mkdirSync(home(), { recursive: true });
3714
+ fs20.writeFileSync(attemptFile(), `${(/* @__PURE__ */ new Date()).toISOString()} ${e.message}
3589
3715
  `);
3590
3716
  } catch {
3591
3717
  }
@@ -3597,8 +3723,8 @@ function refreshInBackground() {
3597
3723
  if (!refreshDue()) return;
3598
3724
  const script = process.argv[1];
3599
3725
  if (!script) return;
3600
- fs19.mkdirSync(home(), { recursive: true });
3601
- fs19.writeFileSync(attemptFile(), `${(/* @__PURE__ */ new Date()).toISOString()} refresh started
3726
+ fs20.mkdirSync(home(), { recursive: true });
3727
+ fs20.writeFileSync(attemptFile(), `${(/* @__PURE__ */ new Date()).toISOString()} refresh started
3602
3728
  `);
3603
3729
  const child = spawn2(process.execPath, [script, "prices", "--refresh", "--quiet"], { detached: true, stdio: "ignore", env: process.env });
3604
3730
  child.on("error", () => void 0);
@@ -3628,7 +3754,7 @@ function catalogStatus() {
3628
3754
  const c = readCatalog();
3629
3755
  let lastFailure;
3630
3756
  try {
3631
- const t = fs19.readFileSync(attemptFile(), "utf8").trim();
3757
+ const t = fs20.readFileSync(attemptFile(), "utf8").trim();
3632
3758
  if (t && !t.endsWith("refresh started")) lastFailure = t;
3633
3759
  } catch {
3634
3760
  }
@@ -3708,8 +3834,8 @@ function record(meta) {
3708
3834
  saved: meta.savedUsd ?? 0,
3709
3835
  price: primaryPrice().name
3710
3836
  };
3711
- fs20.mkdirSync(path19.dirname(ledgerFile()), { recursive: true });
3712
- fs20.appendFileSync(ledgerFile(), `${JSON.stringify(entry)}
3837
+ fs21.mkdirSync(path20.dirname(ledgerFile()), { recursive: true });
3838
+ fs21.appendFileSync(ledgerFile(), `${JSON.stringify(entry)}
3713
3839
  `);
3714
3840
  return true;
3715
3841
  }
@@ -3736,9 +3862,9 @@ function* finishedRuns() {
3736
3862
  }
3737
3863
  }
3738
3864
  function readLedger(sinceMs2) {
3739
- if (!fs20.existsSync(ledgerFile())) return [];
3865
+ if (!fs21.existsSync(ledgerFile())) return [];
3740
3866
  const seen = /* @__PURE__ */ new Set();
3741
- return fs20.readFileSync(ledgerFile(), "utf8").split("\n").filter(Boolean).flatMap((l) => {
3867
+ return fs21.readFileSync(ledgerFile(), "utf8").split("\n").filter(Boolean).flatMap((l) => {
3742
3868
  try {
3743
3869
  const e = JSON.parse(l);
3744
3870
  if (seen.has(e.id)) return [];
@@ -3816,7 +3942,7 @@ ${tile(0, compact(t.tokens), "Tokens offloaded")}${tile(1, t.ratio ? `${Math.rou
3816
3942
  }
3817
3943
 
3818
3944
  // src/core/ui.ts
3819
- import fs21 from "node:fs";
3945
+ import fs22 from "node:fs";
3820
3946
  var WEEK_MS = 7 * 24 * 3600 * 1e3;
3821
3947
  var RECENT = 40;
3822
3948
  var RECENT_HOURS = 1;
@@ -3907,9 +4033,9 @@ function cardsFor(m) {
3907
4033
  const out = [];
3908
4034
  for (const [phase, text] of phases) {
3909
4035
  const marker = runFile(m.id, `card-${phase}`);
3910
- if (fs21.existsSync(marker)) continue;
4036
+ if (fs22.existsSync(marker)) continue;
3911
4037
  if (!(phase === "started" && !isActive(m.state))) out.push(text());
3912
- fs21.writeFileSync(marker, "");
4038
+ fs22.writeFileSync(marker, "");
3913
4039
  }
3914
4040
  return out;
3915
4041
  }
@@ -3920,10 +4046,10 @@ function hookCards(input) {
3920
4046
  const response = event.tool_response;
3921
4047
  const output = typeof response === "string" ? response : [response?.stdout, response?.stderr].filter(Boolean).join("\n");
3922
4048
  const ids = [...new Set(`${command}
3923
- ${output}`.match(RUN_ID) ?? [])].filter((id2) => fs21.existsSync(`${runsDir()}/${id2}`));
4049
+ ${output}`.match(RUN_ID) ?? [])].filter((id2) => fs22.existsSync(`${runsDir()}/${id2}`));
3924
4050
  const since2 = Date.now() - RECENT_HOURS * 36e5;
3925
4051
  for (const id2 of listRunIds().slice(-RECENT)) {
3926
- if (ids.includes(id2) || fs21.existsSync(runFile(id2, "card-ended"))) continue;
4052
+ if (ids.includes(id2) || fs22.existsSync(runFile(id2, "card-ended"))) continue;
3927
4053
  try {
3928
4054
  const m = readMeta(id2);
3929
4055
  if (!isActive(m.state) && Date.parse(m.endedAt ?? m.startedAt) > since2) ids.push(id2);
@@ -3945,7 +4071,7 @@ var SCHEMA_VERSION = 1;
3945
4071
  var PATCH_MAX = 1e6;
3946
4072
  var ANSWER_MAX2 = 2e5;
3947
4073
  var cached3;
3948
- var historyFile = () => path20.join(home(), "history.db");
4074
+ var historyFile = () => path21.join(home(), "history.db");
3949
4075
  function openDb() {
3950
4076
  const file2 = historyFile();
3951
4077
  if (cached3?.file === file2) return cached3.db;
@@ -3959,7 +4085,7 @@ function openDb() {
3959
4085
  } finally {
3960
4086
  process.emitWarning = emit;
3961
4087
  }
3962
- fs22.mkdirSync(path20.dirname(file2), { recursive: true });
4088
+ fs23.mkdirSync(path21.dirname(file2), { recursive: true });
3963
4089
  db = new DatabaseSync(file2);
3964
4090
  db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;");
3965
4091
  migrate(db);
@@ -3994,31 +4120,31 @@ function migrate(db) {
3994
4120
  function readRunFile(id2, name) {
3995
4121
  const f = runFile(id2, name);
3996
4122
  try {
3997
- if (fs22.existsSync(f)) return fs22.readFileSync(f, "utf8");
3998
- if (fs22.existsSync(`${f}.gz`)) return zlib.gunzipSync(fs22.readFileSync(`${f}.gz`)).toString("utf8");
4123
+ if (fs23.existsSync(f)) return fs23.readFileSync(f, "utf8");
4124
+ if (fs23.existsSync(`${f}.gz`)) return zlib.gunzipSync(fs23.readFileSync(`${f}.gz`)).toString("utf8");
3999
4125
  } catch {
4000
4126
  }
4001
4127
  return void 0;
4002
4128
  }
4003
4129
  function gzipFile(f) {
4004
- if (!fs22.existsSync(f)) return;
4005
- fs22.writeFileSync(`${f}.gz`, zlib.gzipSync(fs22.readFileSync(f)));
4006
- fs22.rmSync(f);
4130
+ if (!fs23.existsSync(f)) return;
4131
+ fs23.writeFileSync(`${f}.gz`, zlib.gzipSync(fs23.readFileSync(f)));
4132
+ fs23.rmSync(f);
4007
4133
  }
4008
4134
  function compactRun(meta) {
4009
4135
  if (!TERMINAL.includes(meta.state)) return;
4010
- const dir = path20.dirname(runFile(meta.id, "meta.json"));
4136
+ const dir = path21.dirname(runFile(meta.id, "meta.json"));
4011
4137
  let names;
4012
4138
  try {
4013
- names = fs22.readdirSync(dir);
4139
+ names = fs23.readdirSync(dir);
4014
4140
  } catch {
4015
4141
  return;
4016
4142
  }
4017
4143
  for (const n of names) {
4018
- if (/^events(\.attempt-\d+)?\.jsonl$/.test(n)) gzipFile(path20.join(dir, n));
4144
+ if (/^events(\.attempt-\d+)?\.jsonl$/.test(n)) gzipFile(path21.join(dir, n));
4019
4145
  else if (/^stderr(\.attempt-\d+)?\.log$/.test(n)) {
4020
- if (meta.state === "done" && !/attempt/.test(n)) fs22.rmSync(path20.join(dir, n), { force: true });
4021
- else gzipFile(path20.join(dir, n));
4146
+ if (meta.state === "done" && !/attempt/.test(n)) fs23.rmSync(path21.join(dir, n), { force: true });
4147
+ else gzipFile(path21.join(dir, n));
4022
4148
  }
4023
4149
  }
4024
4150
  }
@@ -4338,7 +4464,7 @@ function historyStats(sinceMs2) {
4338
4464
  }
4339
4465
 
4340
4466
  // src/core/budget.ts
4341
- import fs23 from "node:fs";
4467
+ import fs24 from "node:fs";
4342
4468
  var startOfDay = (now = /* @__PURE__ */ new Date()) => new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
4343
4469
  function activeSpend(sinceMs2) {
4344
4470
  let total = 0;
@@ -4348,8 +4474,8 @@ function activeSpend(sinceMs2) {
4348
4474
  if (!isActive(m.state) || Date.parse(m.startedAt) < sinceMs2) continue;
4349
4475
  const target = m.ran ?? m.worker;
4350
4476
  const events = runFile(id2, "events.jsonl");
4351
- if (!fs23.existsSync(events)) continue;
4352
- const run2 = getBackend(target.backend).parse(fs23.readFileSync(events, "utf8"));
4477
+ if (!fs24.existsSync(events)) continue;
4478
+ const run2 = getBackend(target.backend).parse(fs24.readFileSync(events, "utf8"));
4353
4479
  if (run2.usage.cost !== void 0) total += run2.usage.cost;
4354
4480
  else {
4355
4481
  const price = workerPrice(target.backend, run2.model, target.model);
@@ -4418,15 +4544,15 @@ function rankChain(chain, recordOf, work) {
4418
4544
  // src/core/notify.ts
4419
4545
  import { spawn as spawn3 } from "node:child_process";
4420
4546
  import crypto7 from "node:crypto";
4421
- import fs26 from "node:fs";
4422
- import path22 from "node:path";
4547
+ import fs27 from "node:fs";
4548
+ import path23 from "node:path";
4423
4549
 
4424
4550
  // src/core/report.ts
4425
- import fs25 from "node:fs";
4551
+ import fs26 from "node:fs";
4426
4552
 
4427
4553
  // src/core/plan.ts
4428
- import fs24 from "node:fs";
4429
- import path21 from "node:path";
4554
+ import fs25 from "node:fs";
4555
+ import path22 from "node:path";
4430
4556
  var FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
4431
4557
  var RULE = /^ {0,3}([-*_])(\s*\1){2,}\s*$/;
4432
4558
  function structure(lines) {
@@ -4473,9 +4599,9 @@ function parsePlan(text, file2 = "") {
4473
4599
  return { file: file2, title: titleHeading?.text ?? "", header, constraints, tasks };
4474
4600
  }
4475
4601
  function loadPlan(file2) {
4476
- const abs = path21.resolve(file2);
4477
- if (!fs24.existsSync(abs) || !fs24.statSync(abs).isFile()) throw new UserError(`plan not found: ${file2}`);
4478
- const plan = parsePlan(fs24.readFileSync(abs, "utf8"), fs24.realpathSync(abs));
4602
+ const abs = path22.resolve(file2);
4603
+ if (!fs25.existsSync(abs) || !fs25.statSync(abs).isFile()) throw new UserError(`plan not found: ${file2}`);
4604
+ const plan = parsePlan(fs25.readFileSync(abs, "utf8"), fs25.realpathSync(abs));
4479
4605
  if (!plan.tasks.length) throw new UserError(`${file2} has no "Task N" headings (see pitroom-writing-plans)`);
4480
4606
  return plan;
4481
4607
  }
@@ -4494,7 +4620,7 @@ function brief(plan, task) {
4494
4620
  ].filter(Boolean).join("\n\n")}
4495
4621
  `;
4496
4622
  }
4497
- var planName = (file2) => path21.basename(file2).replace(/\.md$/i, "");
4623
+ var planName = (file2) => path22.basename(file2).replace(/\.md$/i, "");
4498
4624
 
4499
4625
  // src/core/style.ts
4500
4626
  var enabled = () => {
@@ -4554,7 +4680,7 @@ function duration(meta) {
4554
4680
  }
4555
4681
  function readSummary(meta) {
4556
4682
  const f = runFile(meta.id, "summary.md");
4557
- return fs25.existsSync(f) ? fs25.readFileSync(f, "utf8").trim() : archivedRun(meta.id)?.answer ?? "";
4683
+ return fs26.existsSync(f) ? fs26.readFileSync(f, "utf8").trim() : archivedRun(meta.id)?.answer ?? "";
4558
4684
  }
4559
4685
  function formatReport(meta, finalText = readSummary(meta), maxLines = 400) {
4560
4686
  const out = [];
@@ -4640,8 +4766,8 @@ function formatReport(meta, finalText = readSummary(meta), maxLines = 400) {
4640
4766
  }
4641
4767
  function live(meta) {
4642
4768
  const f = runFile(meta.id, "events.jsonl");
4643
- if (!fs25.existsSync(f)) return { steps: 0, toolCalls: 0 };
4644
- const p = getBackend((meta.ran ?? meta.worker).backend).parse(fs25.readFileSync(f, "utf8"));
4769
+ if (!fs26.existsSync(f)) return { steps: 0, toolCalls: 0 };
4770
+ const p = getBackend((meta.ran ?? meta.worker).backend).parse(fs26.readFileSync(f, "utf8"));
4645
4771
  return { steps: p.usage.steps, toolCalls: p.usage.toolCalls, last: p.lastActivity };
4646
4772
  }
4647
4773
  function progress(meta) {
@@ -4813,12 +4939,12 @@ function claimGroup(group) {
4813
4939
  try {
4814
4940
  const ids = groupIds(group).sort();
4815
4941
  if (ids.length < 2) return true;
4816
- const dir = path22.join(home(), "notified");
4817
- fs26.mkdirSync(dir, { recursive: true });
4818
- for (const f of fs26.readdirSync(dir)) {
4819
- if (Date.now() - fs26.statSync(path22.join(dir, f)).mtimeMs > 30 * 864e5) fs26.rmSync(path22.join(dir, f), { force: true });
4942
+ const dir = path23.join(home(), "notified");
4943
+ fs27.mkdirSync(dir, { recursive: true });
4944
+ for (const f of fs27.readdirSync(dir)) {
4945
+ if (Date.now() - fs27.statSync(path23.join(dir, f)).mtimeMs > 30 * 864e5) fs27.rmSync(path23.join(dir, f), { force: true });
4820
4946
  }
4821
- fs26.writeFileSync(path22.join(dir, crypto7.createHash("sha1").update(ids.join(",")).digest("hex").slice(0, 16)), "", { flag: "wx" });
4947
+ fs27.writeFileSync(path23.join(dir, crypto7.createHash("sha1").update(ids.join(",")).digest("hex").slice(0, 16)), "", { flag: "wx" });
4822
4948
  return true;
4823
4949
  } catch {
4824
4950
  return false;
@@ -4852,30 +4978,30 @@ function notifyEnded(meta) {
4852
4978
  }
4853
4979
 
4854
4980
  // src/core/templates.ts
4855
- import fs28 from "node:fs";
4856
- import path24 from "node:path";
4981
+ import fs29 from "node:fs";
4982
+ import path25 from "node:path";
4857
4983
 
4858
4984
  // src/core/install.ts
4859
- import fs27 from "node:fs";
4985
+ import fs28 from "node:fs";
4860
4986
  import os11 from "node:os";
4861
- import path23 from "node:path";
4987
+ import path24 from "node:path";
4862
4988
  import { fileURLToPath as fileURLToPath3 } from "node:url";
4863
4989
  var LEGACY = ["pitroom", "opencode-worker"];
4864
4990
  function packageRoot() {
4865
- return path23.resolve(path23.dirname(fileURLToPath3(import.meta.url)), "..");
4991
+ return path24.resolve(path24.dirname(fileURLToPath3(import.meta.url)), "..");
4866
4992
  }
4867
4993
  function skillNames(root = packageRoot()) {
4868
- const dir = path23.join(root, "skills");
4869
- if (!fs27.existsSync(dir)) return [];
4870
- return fs27.readdirSync(dir).filter((d) => fs27.existsSync(path23.join(dir, d, "SKILL.md"))).sort();
4994
+ const dir = path24.join(root, "skills");
4995
+ if (!fs28.existsSync(dir)) return [];
4996
+ return fs28.readdirSync(dir).filter((d) => fs28.existsSync(path24.join(dir, d, "SKILL.md"))).sort();
4871
4997
  }
4872
4998
  function skillTargets() {
4873
4999
  const home3 = os11.homedir();
4874
- const targets = [path23.join(home3, ".agents", "skills")];
4875
- if (fs27.existsSync(path23.join(home3, ".claude"))) targets.push(path23.join(home3, ".claude", "skills"));
5000
+ const targets = [path24.join(home3, ".agents", "skills")];
5001
+ if (fs28.existsSync(path24.join(home3, ".claude"))) targets.push(path24.join(home3, ".claude", "skills"));
4876
5002
  return targets;
4877
5003
  }
4878
- var launcherPath = () => path23.join(os11.homedir(), ".local", "bin", process.platform === "win32" ? "pitroom.cmd" : "pitroom");
5004
+ var launcherPath = () => path24.join(os11.homedir(), ".local", "bin", process.platform === "win32" ? "pitroom.cmd" : "pitroom");
4879
5005
  var LAUNCHER_MARK = "# pitroom launcher";
4880
5006
  function launcherScript(bundle) {
4881
5007
  if (process.platform === "win32") {
@@ -4905,7 +5031,7 @@ exit 127
4905
5031
  function isOurLauncher(file2, root) {
4906
5032
  if (linksInto(file2, root)) return true;
4907
5033
  try {
4908
- const s = fs27.readFileSync(file2, "utf8");
5034
+ const s = fs28.readFileSync(file2, "utf8");
4909
5035
  return s.includes(LAUNCHER_MARK) && s.includes(root);
4910
5036
  } catch {
4911
5037
  return false;
@@ -4913,64 +5039,64 @@ function isOurLauncher(file2, root) {
4913
5039
  }
4914
5040
  function isPitroomLauncher(file2) {
4915
5041
  try {
4916
- return fs27.readFileSync(file2, "utf8").includes(LAUNCHER_MARK);
5042
+ return fs28.readFileSync(file2, "utf8").includes(LAUNCHER_MARK);
4917
5043
  } catch {
4918
5044
  return false;
4919
5045
  }
4920
5046
  }
4921
5047
  function placeLauncher(bundle, root, force) {
4922
5048
  const dest = launcherPath();
4923
- fs27.mkdirSync(path23.dirname(dest), { recursive: true });
4924
- const exists = fs27.lstatSync(dest, { throwIfNoEntry: false });
5049
+ fs28.mkdirSync(path24.dirname(dest), { recursive: true });
5050
+ const exists = fs28.lstatSync(dest, { throwIfNoEntry: false });
4925
5051
  if (exists && !isOurLauncher(dest, root)) {
4926
5052
  if (!force) return `! ${dest} exists and is not Pitroom's launcher; kept (use --force to back it up and replace)`;
4927
- fs27.renameSync(dest, `${dest}.bak-${Date.now()}`);
5053
+ fs28.renameSync(dest, `${dest}.bak-${Date.now()}`);
4928
5054
  } else if (exists) {
4929
- fs27.rmSync(dest, { force: true });
5055
+ fs28.rmSync(dest, { force: true });
4930
5056
  }
4931
- fs27.writeFileSync(dest, launcherScript(bundle), { mode: 493 });
5057
+ fs28.writeFileSync(dest, launcherScript(bundle), { mode: 493 });
4932
5058
  return `\u2714 ${dest} \u2192 launcher for ${bundle} (Node 22.13+)`;
4933
5059
  }
4934
5060
  function linksInto(link, dir) {
4935
- const st = fs27.lstatSync(link, { throwIfNoEntry: false });
5061
+ const st = fs28.lstatSync(link, { throwIfNoEntry: false });
4936
5062
  if (!st?.isSymbolicLink()) return false;
4937
- const target = path23.resolve(path23.dirname(link), fs27.readlinkSync(link));
4938
- const rel = path23.relative(dir, target);
4939
- return !rel.startsWith("..") && !path23.isAbsolute(rel);
5063
+ const target = path24.resolve(path24.dirname(link), fs28.readlinkSync(link));
5064
+ const rel = path24.relative(dir, target);
5065
+ return !rel.startsWith("..") && !path24.isAbsolute(rel);
4940
5066
  }
4941
5067
  function place(src, dest, opts) {
4942
- fs27.mkdirSync(path23.dirname(dest), { recursive: true });
4943
- const st = fs27.lstatSync(dest, { throwIfNoEntry: false });
5068
+ fs28.mkdirSync(path24.dirname(dest), { recursive: true });
5069
+ const st = fs28.lstatSync(dest, { throwIfNoEntry: false });
4944
5070
  if (st && !st.isSymbolicLink()) {
4945
5071
  if (!opts.force) return `! ${dest} exists and is not a link; kept (use --force to back it up and replace)`;
4946
- fs27.renameSync(dest, `${dest}.bak-${Date.now()}`);
5072
+ fs28.renameSync(dest, `${dest}.bak-${Date.now()}`);
4947
5073
  } else if (st) {
4948
- fs27.unlinkSync(dest);
5074
+ fs28.unlinkSync(dest);
4949
5075
  }
4950
- if (opts.copy) fs27.cpSync(src, dest, { recursive: true });
4951
- else fs27.symlinkSync(src, dest, process.platform === "win32" ? "junction" : fs27.statSync(src).isDirectory() ? "dir" : "file");
5076
+ if (opts.copy) fs28.cpSync(src, dest, { recursive: true });
5077
+ else fs28.symlinkSync(src, dest, process.platform === "win32" ? "junction" : fs28.statSync(src).isDirectory() ? "dir" : "file");
4952
5078
  return `\u2714 ${dest} \u2192 ${opts.copy ? "copied" : src}`;
4953
5079
  }
4954
5080
  function install(opts) {
4955
5081
  const root = packageRoot();
4956
5082
  const skills2 = opts.skills === false ? [] : skillNames(root);
4957
- if (opts.skills !== false && !skills2.length) throw new Error(`no skills found under ${path23.join(root, "skills")}`);
5083
+ if (opts.skills !== false && !skills2.length) throw new Error(`no skills found under ${path24.join(root, "skills")}`);
4958
5084
  const out = [];
4959
5085
  for (const base2 of opts.skills === false ? [] : skillTargets()) {
4960
5086
  for (const legacy of LEGACY) {
4961
- const l = path23.join(base2, legacy);
5087
+ const l = path24.join(base2, legacy);
4962
5088
  if (!skills2.includes(legacy) && linksInto(l, root)) {
4963
- fs27.unlinkSync(l);
5089
+ fs28.unlinkSync(l);
4964
5090
  out.push(`\u2714 removed old link ${l}`);
4965
5091
  }
4966
5092
  }
4967
- for (const name of skills2) out.push(place(path23.join(root, "skills", name), path23.join(base2, name), opts));
5093
+ for (const name of skills2) out.push(place(path24.join(root, "skills", name), path24.join(base2, name), opts));
4968
5094
  }
4969
- const bundle = path23.join(root, "dist", "pitroom.mjs");
4970
- if (fs27.existsSync(bundle)) {
5095
+ const bundle = path24.join(root, "dist", "pitroom.mjs");
5096
+ if (fs28.existsSync(bundle)) {
4971
5097
  out.push(placeLauncher(bundle, root, opts.force));
4972
- const onPath = (process.env.PATH ?? "").split(path23.delimiter).some((d) => path23.resolve(d) === path23.dirname(launcherPath()));
4973
- if (!onPath) out.push(`! ${path23.dirname(launcherPath())} is not on PATH; add it, or run ${launcherPath()} directly`);
5098
+ const onPath = (process.env.PATH ?? "").split(path24.delimiter).some((d) => path24.resolve(d) === path24.dirname(launcherPath()));
5099
+ if (!onPath) out.push(`! ${path24.dirname(launcherPath())} is not on PATH; add it, or run ${launcherPath()} directly`);
4974
5100
  }
4975
5101
  return out;
4976
5102
  }
@@ -4978,24 +5104,24 @@ function uninstall() {
4978
5104
  const root = packageRoot();
4979
5105
  const out = [];
4980
5106
  for (const base2 of skillTargets()) {
4981
- if (!fs27.existsSync(base2)) continue;
4982
- for (const name of fs27.readdirSync(base2)) {
4983
- const l = path23.join(base2, name);
5107
+ if (!fs28.existsSync(base2)) continue;
5108
+ for (const name of fs28.readdirSync(base2)) {
5109
+ const l = path24.join(base2, name);
4984
5110
  if (linksInto(l, root)) {
4985
- fs27.unlinkSync(l);
5111
+ fs28.unlinkSync(l);
4986
5112
  out.push(`\u2714 removed ${l}`);
4987
5113
  }
4988
5114
  }
4989
5115
  }
4990
5116
  if (isOurLauncher(launcherPath(), root)) {
4991
- fs27.rmSync(launcherPath(), { force: true });
5117
+ fs28.rmSync(launcherPath(), { force: true });
4992
5118
  out.push(`\u2714 removed ${launcherPath()}`);
4993
5119
  }
4994
5120
  return out.length ? out : ["nothing to remove"];
4995
5121
  }
4996
5122
  function installedSkills() {
4997
5123
  const names = skillNames();
4998
- return skillTargets().map((base2) => ({ base: base2, names: names.filter((n) => fs27.existsSync(path23.join(base2, n, "SKILL.md"))) }));
5124
+ return skillTargets().map((base2) => ({ base: base2, names: names.filter((n) => fs28.existsSync(path24.join(base2, n, "SKILL.md"))) }));
4999
5125
  }
5000
5126
 
5001
5127
  // src/core/templates.ts
@@ -5006,9 +5132,9 @@ var FILES = {
5006
5132
  "code-reviewer": "pitroom-review/code-reviewer.md"
5007
5133
  };
5008
5134
  function loadTemplate(name, root = packageRoot()) {
5009
- const file2 = path24.join(root, "skills", FILES[name]);
5010
- if (!fs28.existsSync(file2)) throw new UserError(`template missing: ${file2} (broken install? run pitroom doctor)`, 3);
5011
- return fs28.readFileSync(file2, "utf8").replace(/^\s*<!--[\s\S]*?-->\s*/, "");
5135
+ const file2 = path25.join(root, "skills", FILES[name]);
5136
+ if (!fs29.existsSync(file2)) throw new UserError(`template missing: ${file2} (broken install? run pitroom doctor)`, 3);
5137
+ return fs29.readFileSync(file2, "utf8").replace(/^\s*<!--[\s\S]*?-->\s*/, "");
5012
5138
  }
5013
5139
  function fill(template, values) {
5014
5140
  const missing = [...template.matchAll(/\{\{([A-Z_]+)\}\}/g)].map((m) => m[1]).filter((k) => !(k in values));
@@ -5018,10 +5144,10 @@ function fill(template, values) {
5018
5144
 
5019
5145
  // src/core/slots.ts
5020
5146
  import crypto8 from "node:crypto";
5021
- import fs29 from "node:fs";
5022
- import path25 from "node:path";
5023
- var slotsDir = () => path25.join(home(), "slots");
5024
- var locksDir = () => path25.join(home(), "locks");
5147
+ import fs30 from "node:fs";
5148
+ import path26 from "node:path";
5149
+ var slotsDir = () => path26.join(home(), "slots");
5150
+ var locksDir = () => path26.join(home(), "locks");
5025
5151
  var STARTUP_GRACE_MS = 3e4;
5026
5152
  function holderActive(runId, self) {
5027
5153
  if (!runId || runId === self) return false;
@@ -5035,23 +5161,23 @@ function holderActive(runId, self) {
5035
5161
  }
5036
5162
  }
5037
5163
  function tryClaim(file2, runId) {
5038
- fs29.mkdirSync(path25.dirname(file2), { recursive: true });
5164
+ fs30.mkdirSync(path26.dirname(file2), { recursive: true });
5039
5165
  try {
5040
- fs29.writeFileSync(file2, runId, { flag: "wx" });
5166
+ fs30.writeFileSync(file2, runId, { flag: "wx" });
5041
5167
  return true;
5042
5168
  } catch (e) {
5043
5169
  if (e.code !== "EEXIST") throw e;
5044
5170
  }
5045
5171
  let owner = "";
5046
5172
  try {
5047
- owner = fs29.readFileSync(file2, "utf8").trim();
5173
+ owner = fs30.readFileSync(file2, "utf8").trim();
5048
5174
  } catch {
5049
5175
  }
5050
5176
  if (owner === runId) return true;
5051
5177
  if (holderActive(owner, runId)) return false;
5052
- fs29.rmSync(file2, { force: true });
5178
+ fs30.rmSync(file2, { force: true });
5053
5179
  try {
5054
- fs29.writeFileSync(file2, runId, { flag: "wx" });
5180
+ fs30.writeFileSync(file2, runId, { flag: "wx" });
5055
5181
  return true;
5056
5182
  } catch {
5057
5183
  return false;
@@ -5060,25 +5186,25 @@ function tryClaim(file2, runId) {
5060
5186
  function releaseIfOwner(file2, runId) {
5061
5187
  if (!file2) return;
5062
5188
  try {
5063
- if (fs29.readFileSync(file2, "utf8").trim() === runId) fs29.rmSync(file2, { force: true });
5189
+ if (fs30.readFileSync(file2, "utf8").trim() === runId) fs30.rmSync(file2, { force: true });
5064
5190
  } catch {
5065
5191
  }
5066
5192
  }
5067
5193
  function tryAcquireSlot(runId, maxParallel) {
5068
5194
  for (let n = 0; n < Math.max(1, maxParallel); n++) {
5069
- const file2 = path25.join(slotsDir(), `slot-${n}`);
5195
+ const file2 = path26.join(slotsDir(), `slot-${n}`);
5070
5196
  if (tryClaim(file2, runId)) return file2;
5071
5197
  }
5072
5198
  return void 0;
5073
5199
  }
5074
5200
  var releaseSlot = (file2, runId) => releaseIfOwner(file2, runId);
5075
- var lockFile = (repoRoot2) => path25.join(locksDir(), `write-${crypto8.createHash("sha1").update(path25.resolve(repoRoot2)).digest("hex").slice(0, 16)}`);
5201
+ var lockFile = (repoRoot2) => path26.join(locksDir(), `write-${crypto8.createHash("sha1").update(path26.resolve(repoRoot2)).digest("hex").slice(0, 16)}`);
5076
5202
  function acquireWriteLock(repoRoot2, runId) {
5077
5203
  const file2 = lockFile(repoRoot2);
5078
5204
  if (tryClaim(file2, runId)) return;
5079
5205
  let owner = "";
5080
5206
  try {
5081
- owner = fs29.readFileSync(file2, "utf8").trim();
5207
+ owner = fs30.readFileSync(file2, "utf8").trim();
5082
5208
  } catch {
5083
5209
  }
5084
5210
  throw new UserError(`another --write run (${owner}) is active in this repo; use --isolate for parallel changes`, 3);
@@ -5128,13 +5254,14 @@ ${task}`;
5128
5254
  }
5129
5255
 
5130
5256
  // src/core/refs.ts
5131
- import fs30 from "node:fs";
5132
- import path26 from "node:path";
5257
+ import fs31 from "node:fs";
5258
+ import path27 from "node:path";
5133
5259
  var FILE_EXTENSIONS = new Set(
5134
5260
  "ts tsx mts cts js jsx mjs cjs json jsonc json5 md mdx txt rst py pyi rb go rs java kt kts swift c h cc cpp cxx hpp hh cs fs php lua r jl sh bash zsh fish ps1 bat yml yaml toml ini cfg conf env html htm css scss sass less vue svelte astro sql graphql gql proto lock xml svg csv tsv gradle tf hcl dart ex exs erl hs ml scala clj vim el mk cmake dockerfile gitignore gitattributes editorconfig npmrc nvmrc".split(" ")
5135
5261
  );
5136
5262
  var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", ".nuxt", "target", "vendor", ".venv", "venv", "__pycache__", "coverage", ".turbo", ".cache"]);
5137
5263
  var MAX_INDEXED = 6e4;
5264
+ var MAX_SKIPPED = 3e5;
5138
5265
  var EXTENSIONLESS = "Makefile|Dockerfile|Containerfile|Gemfile|Rakefile|Procfile|Justfile|Vagrantfile|BUILD|WORKSPACE";
5139
5266
  var REF = new RegExp(
5140
5267
  String.raw`(?<![\w/:.-])(\.{0,2}/?(?:[\w@.+-]+/)*(?:[\w@+-][\w@.+-]*\.[A-Za-z][A-Za-z0-9]{0,7}|${EXTENSIONLESS})):(\d+)(?:[-–](\d+))?`,
@@ -5180,26 +5307,30 @@ function extractRefs(answer) {
5180
5307
  return [...seen.values()];
5181
5308
  }
5182
5309
  function verifyRefs(refs, dirs) {
5183
- const roots = [...new Set(dirs.filter(Boolean).map((d) => path26.resolve(d)))];
5310
+ const roots = [...new Set(dirs.filter(Boolean).map((d) => path27.resolve(d)))];
5184
5311
  const invalid = [];
5185
5312
  const lineCache = /* @__PURE__ */ new Map();
5186
5313
  const load = (file2) => {
5187
5314
  if (!lineCache.has(file2)) {
5188
- const size = fs30.statSync(file2).size;
5189
- const lines = size > MAX_BYTES ? null : fs30.readFileSync(file2, "utf8").split("\n");
5315
+ const size = fs31.statSync(file2).size;
5316
+ const lines = size > MAX_BYTES ? null : fs31.readFileSync(file2, "utf8").split("\n");
5190
5317
  if (lines && lines[lines.length - 1] === "") lines.pop();
5191
5318
  lineCache.set(file2, lines);
5192
5319
  }
5193
5320
  return lineCache.get(file2);
5194
5321
  };
5195
5322
  let index;
5323
+ let skipped;
5196
5324
  const byName = () => index ??= indexFiles(roots);
5325
+ const inSkipped = () => skipped ??= indexFiles(roots, true);
5197
5326
  const candidates = (ref) => {
5198
5327
  const direct = resolve(ref.file, roots);
5199
5328
  if (direct) return [direct];
5200
- if (path26.isAbsolute(ref.file)) return [];
5329
+ if (path27.isAbsolute(ref.file)) return [];
5201
5330
  const wanted = ref.file.replace(/^(\.{1,2}\/)+/, "");
5202
- return (byName().get(path26.basename(wanted)) ?? []).filter((f) => f.split(path26.sep).join("/").endsWith(`/${wanted}`) || path26.basename(f) === wanted).slice(0, 20);
5331
+ const fits = (f) => path27.basename(f) === wanted || foldersInOrder(f.split(path27.sep).join("/"), wanted);
5332
+ const own = (byName().get(path27.basename(wanted)) ?? []).filter(fits);
5333
+ return (own.length || !isFileName(wanted) ? own : (inSkipped().get(path27.basename(wanted)) ?? []).filter(fits)).slice(0, 20);
5203
5334
  };
5204
5335
  const check = (file2, ref) => {
5205
5336
  const lines = load(file2);
@@ -5214,7 +5345,7 @@ function verifyRefs(refs, dirs) {
5214
5345
  if (!files.length) {
5215
5346
  if (!ref.file.includes("/") && !isFileName(ref.file)) continue;
5216
5347
  total++;
5217
- invalid.push({ ref: ref.text, reason: path26.isAbsolute(ref.file) && !inside2(ref.file, roots) ? "outside the project" : "file not found" });
5348
+ invalid.push({ ref: ref.text, reason: path27.isAbsolute(ref.file) && !inside2(ref.file, roots) ? "outside the project" : "file not found" });
5218
5349
  continue;
5219
5350
  }
5220
5351
  total++;
@@ -5235,39 +5366,54 @@ function enclosedBy(lines, ref) {
5235
5366
  return lines.slice(Math.max(0, ref.start - 1 - DEFINITION_LOOKBACK), ref.start).some((l) => def.test(l));
5236
5367
  }
5237
5368
  var isFileName = (name) => {
5238
- const base2 = path26.basename(name);
5369
+ const base2 = path27.basename(name);
5239
5370
  return !base2.includes(".") || new RegExp(`^(${EXTENSIONLESS})$`).test(base2) || FILE_EXTENSIONS.has(base2.split(".").pop().toLowerCase());
5240
5371
  };
5241
- function indexFiles(roots) {
5372
+ function foldersInOrder(file2, wanted) {
5373
+ const want = wanted.split("/");
5374
+ const have = file2.split("/");
5375
+ if (have.at(-1) !== want.at(-1)) return false;
5376
+ let i = have.length - 2;
5377
+ for (let j = want.length - 2; j >= 0; j--) {
5378
+ while (i >= 0 && have[i] !== want[j]) i--;
5379
+ if (i < 0) return false;
5380
+ i--;
5381
+ }
5382
+ return true;
5383
+ }
5384
+ function indexFiles(roots, skipped = false) {
5242
5385
  const index = /* @__PURE__ */ new Map();
5386
+ const cap = skipped ? MAX_SKIPPED : MAX_INDEXED;
5243
5387
  let count = 0;
5244
- const walk = (dir) => {
5388
+ const walk = (dir, within) => {
5245
5389
  let entries;
5246
5390
  try {
5247
- entries = fs30.readdirSync(dir, { withFileTypes: true });
5391
+ entries = fs31.readdirSync(dir, { withFileTypes: true });
5248
5392
  } catch {
5249
5393
  return;
5250
5394
  }
5251
5395
  for (const e of entries) {
5252
- if (count >= MAX_INDEXED) return;
5396
+ if (count >= cap) return;
5253
5397
  if (e.isDirectory()) {
5254
- if (!SKIP_DIRS2.has(e.name)) walk(path26.join(dir, e.name));
5255
- } else if (e.isFile()) {
5398
+ if (e.name === ".git") continue;
5399
+ const skip = SKIP_DIRS2.has(e.name);
5400
+ if (skipped || !skip) walk(path27.join(dir, e.name), within || skip);
5401
+ } else if (e.isFile() && within === skipped) {
5256
5402
  count++;
5257
5403
  const list2 = index.get(e.name);
5258
- if (list2) list2.push(path26.join(dir, e.name));
5259
- else index.set(e.name, [path26.join(dir, e.name)]);
5404
+ if (list2) list2.push(path27.join(dir, e.name));
5405
+ else index.set(e.name, [path27.join(dir, e.name)]);
5260
5406
  }
5261
5407
  }
5262
5408
  };
5263
- for (const r of roots) walk(r);
5409
+ for (const r of roots) walk(r, false);
5264
5410
  return index;
5265
5411
  }
5266
5412
  function resolve(ref, roots) {
5267
- const candidates = path26.isAbsolute(ref) ? inside2(ref, roots) ? [ref] : [] : roots.map((r) => path26.join(r, ref));
5413
+ const candidates = path27.isAbsolute(ref) ? inside2(ref, roots) ? [ref] : [] : roots.map((r) => path27.join(r, ref));
5268
5414
  return candidates.find((c) => {
5269
5415
  try {
5270
- return fs30.statSync(c).isFile() && inside2(c, roots);
5416
+ return fs31.statSync(c).isFile() && inside2(c, roots);
5271
5417
  } catch {
5272
5418
  return false;
5273
5419
  }
@@ -5275,14 +5421,14 @@ function resolve(ref, roots) {
5275
5421
  }
5276
5422
  function inside2(file2, roots) {
5277
5423
  return roots.some((r) => {
5278
- const rel = path26.relative(r, path26.resolve(file2));
5279
- return rel !== "" && !rel.startsWith("..") && !path26.isAbsolute(rel);
5424
+ const rel = path27.relative(r, path27.resolve(file2));
5425
+ return rel !== "" && !rel.startsWith("..") && !path27.isAbsolute(rel);
5280
5426
  });
5281
5427
  }
5282
5428
  var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5283
5429
 
5284
5430
  // src/core/run.ts
5285
- var VERSION2 = true ? "0.26.0" : "0.0.0-dev";
5431
+ var VERSION2 = true ? "0.27.0" : "0.0.0-dev";
5286
5432
  function planWork(o) {
5287
5433
  if (!o.plan) return { task: o.task.trim(), tier: o.tier };
5288
5434
  if (o.continueFrom) throw new UserError("a follow-up continues its parent's task; drop --plan/--step");
@@ -5314,7 +5460,7 @@ function prepareRun(o) {
5314
5460
  parent = readMeta(o.continueFrom);
5315
5461
  if (o.worker || o.tier || o.effort) throw new UserError("a follow-up runs on the same worker as its parent; drop --worker/--tier/--effort");
5316
5462
  if (!parent.sessionId) throw new UserError(`run ${parent.id} has no worker session to continue`, 3);
5317
- if (parent.mode === "isolate" && (!parent.worktree || !fs31.existsSync(parent.worktree))) {
5463
+ if (parent.mode === "isolate" && (!parent.worktree || !fs32.existsSync(parent.worktree))) {
5318
5464
  throw new UserError(`the isolated copy of run ${parent.id} is gone (applied or discarded)`, 3);
5319
5465
  }
5320
5466
  const ran = parent.ran ?? parent.worker;
@@ -5344,22 +5490,22 @@ function prepareRun(o) {
5344
5490
  throw new UserError(`the ${backend.name} worker cannot attach files; put the content in the task`);
5345
5491
  }
5346
5492
  const mode = parent?.mode ?? o.mode;
5347
- const dir = canonical(path27.resolve(parent?.dir ?? o.dir));
5348
- if (!fs31.existsSync(dir) || !fs31.statSync(dir).isDirectory()) throw new UserError(`not a directory: ${dir}`);
5493
+ const dir = canonical(path28.resolve(parent?.dir ?? o.dir));
5494
+ if (!fs32.existsSync(dir) || !fs32.statSync(dir).isDirectory()) throw new UserError(`not a directory: ${dir}`);
5349
5495
  const root = repoRoot(dir);
5350
5496
  if (mode === "isolate" && !root) throw new UserError("--isolate needs a git repository", 3);
5351
5497
  if (mode === "write" && !root && !o.allowNonGit) {
5352
5498
  throw new UserError("--write outside a git repo cannot be tracked or reverted; pass --allow-non-git to accept that", 3);
5353
5499
  }
5354
- const files = o.files.map((f) => path27.resolve(f));
5355
- for (const f of files) if (!fs31.existsSync(f)) throw new UserError(`file not found: ${f}`);
5500
+ const files = o.files.map((f) => path28.resolve(f));
5501
+ for (const f of files) if (!fs32.existsSync(f)) throw new UserError(`file not found: ${f}`);
5356
5502
  const readIn2 = READ_IN.includes(effective().readIn.value) ? effective().readIn.value : "auto";
5357
5503
  const untrusted = [worker, ...fallback].map((t) => getBackend(t.backend)).find(readsOwnSnapshot);
5358
5504
  const snap = mode === "read" && !o.review && !o.inPlace && !parent ? wantSnapshot(readIn2, root, dir) : void 0;
5359
5505
  if (untrusted && mode === "read" && !parent && (o.review || o.inPlace || !root)) {
5360
5506
  warnings2.push(`this read run is not in a snapshot${root ? "" : " (not a git repository)"}, and ${untrusted.name} does not reliably stay read-only: it could change files here`);
5361
5507
  }
5362
- if (parent?.snapshot?.dir && !fs31.existsSync(parent.snapshot.dir)) {
5508
+ if (parent?.snapshot?.dir && !fs32.existsSync(parent.snapshot.dir)) {
5363
5509
  let restored = false;
5364
5510
  if (parent.snapshot.private && root && parent.snapshot.tree) {
5365
5511
  try {
@@ -5416,13 +5562,13 @@ function prepareRun(o) {
5416
5562
  try {
5417
5563
  acquireWriteLock(root, meta.id);
5418
5564
  } catch (e) {
5419
- fs31.rmSync(runDir(meta.id), { recursive: true, force: true });
5565
+ fs32.rmSync(runDir(meta.id), { recursive: true, force: true });
5420
5566
  throw e;
5421
5567
  }
5422
5568
  }
5423
- fs31.writeFileSync(runFile(meta.id, "task.md"), `${meta.task}
5569
+ fs32.writeFileSync(runFile(meta.id, "task.md"), `${meta.task}
5424
5570
  `);
5425
- if (work.brief) fs31.writeFileSync(runFile(meta.id, "brief.md"), work.brief);
5571
+ if (work.brief) fs32.writeFileSync(runFile(meta.id, "brief.md"), work.brief);
5426
5572
  return meta;
5427
5573
  }
5428
5574
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -5523,7 +5669,7 @@ async function execute(meta) {
5523
5669
  (meta.attempts ??= []).push({ target: describeTarget(target), error: why.message, ...why.partial ? { partial: true } : {} });
5524
5670
  for (const f of ["events.jsonl", "stderr.log"]) {
5525
5671
  const from = runFile(meta.id, f);
5526
- if (fs31.existsSync(from)) fs31.renameSync(from, runFile(meta.id, f.replace(".", `.attempt-${i + 1}.`)));
5672
+ if (fs32.existsSync(from)) fs32.renameSync(from, runFile(meta.id, f.replace(".", `.attempt-${i + 1}.`)));
5527
5673
  }
5528
5674
  writeMeta(meta);
5529
5675
  }
@@ -5620,9 +5766,9 @@ function prepareTree(meta) {
5620
5766
  if (root && meta.mode === "write") meta.baseTree = snapshotTree(root);
5621
5767
  if (root && meta.mode === "isolate" && !meta.worktree) {
5622
5768
  meta.baseTree = snapshotTree(root);
5623
- meta.worktree = path27.join(worktreesDir(), meta.id);
5769
+ meta.worktree = path28.join(worktreesDir(), meta.id);
5624
5770
  createIsolatedCopy(root, meta.baseTree, meta.worktree);
5625
- meta.cwd = path27.join(meta.worktree, path27.relative(root, meta.dir));
5771
+ meta.cwd = path28.join(meta.worktree, path28.relative(root, meta.dir));
5626
5772
  const linked = linkIntoWorktree(root, meta.worktree, meta.link);
5627
5773
  if (linked.length) meta.warnings.push(`linked into the isolated copy (shared with your tree): ${linked.join(", ")}`);
5628
5774
  }
@@ -5653,8 +5799,8 @@ function ownSnapshot(meta, root, backend, planned, attemptNo) {
5653
5799
  try {
5654
5800
  const s = readSnapshot(root, planned.own ? `${meta.id}-${attemptNo}` : meta.id);
5655
5801
  meta.snapshot = { dir: s.dir, tree: s.tree, left, private: true };
5656
- meta.cwd = path27.join(s.dir, path27.relative(root, meta.dir));
5657
- if (!fs31.existsSync(meta.cwd)) meta.cwd = s.dir;
5802
+ meta.cwd = path28.join(s.dir, path28.relative(root, meta.dir));
5803
+ if (!fs32.existsSync(meta.cwd)) meta.cwd = s.dir;
5658
5804
  planned.own = { snapshot: { ...meta.snapshot }, cwd: meta.cwd };
5659
5805
  const note = `${backend.name} reads a snapshot of your project of its own (it does not reliably stay read-only)`;
5660
5806
  if (!meta.warnings.some((w) => w.startsWith(note))) {
@@ -5686,8 +5832,8 @@ function sharedSnapshot(meta, root) {
5686
5832
  const s = readSnapshot(root);
5687
5833
  snapshot.dir = s.dir;
5688
5834
  snapshot.tree = s.tree;
5689
- meta.cwd = path27.join(s.dir, path27.relative(root, meta.dir));
5690
- if (!fs31.existsSync(meta.cwd)) meta.cwd = s.dir;
5835
+ meta.cwd = path28.join(s.dir, path28.relative(root, meta.dir));
5836
+ if (!fs32.existsSync(meta.cwd)) meta.cwd = s.dir;
5691
5837
  if (shown) meta.warnings.push(`read in a clean snapshot of your project: the secret-looking files (${shown}) and git-ignored files are not in it; --in-place reads the directory itself`);
5692
5838
  } catch (e) {
5693
5839
  meta.snapshot = void 0;
@@ -5715,7 +5861,7 @@ ${meta.handoff}` : meta.task, !!meta.parent, !!meta.auditOf),
5715
5861
  timeoutSec: meta.timeoutSec
5716
5862
  });
5717
5863
  }
5718
- var read2 = (f) => fs31.existsSync(f) ? fs31.readFileSync(f, "utf8") : "";
5864
+ var read2 = (f) => fs32.existsSync(f) ? fs32.readFileSync(f, "utf8") : "";
5719
5865
  function retryableFailure(meta, backend, res) {
5720
5866
  const run2 = backend.parse(read2(runFile(meta.id, "events.jsonl")));
5721
5867
  const f = backend.failure(run2, read2(runFile(meta.id, "stderr.log")), res.code);
@@ -5754,12 +5900,12 @@ function finalize(meta, res) {
5754
5900
  if (meta.snapshot?.dir && meta.repoRoot) run2.finalText = backToProject(run2.finalText, meta.snapshot.dir, meta.repoRoot);
5755
5901
  meta.sessionId = run2.sessionId ?? meta.sessionId;
5756
5902
  meta.usage = run2.usage;
5757
- fs31.writeFileSync(runFile(meta.id, "summary.md"), `${run2.finalText}
5903
+ fs32.writeFileSync(runFile(meta.id, "summary.md"), `${run2.finalText}
5758
5904
  `);
5759
5905
  if (meta.plan && !meta.reviewOf) meta.taskStatus = parseStatus(run2.finalText);
5760
5906
  if (meta.reviewOf) {
5761
5907
  meta.verdict = parseVerdict(run2.finalText);
5762
- if (meta.packageFile) fs31.rmSync(meta.packageFile, { force: true });
5908
+ if (meta.packageFile) fs32.rmSync(meta.packageFile, { force: true });
5763
5909
  }
5764
5910
  for (const w of backend.warnings?.(run2, read2(runFile(meta.id, "stderr.log"))) ?? []) {
5765
5911
  if (!meta.warnings.includes(w)) meta.warnings.push(w);
@@ -5824,16 +5970,16 @@ function captureChanges(meta) {
5824
5970
  const d = diffTrees(target, meta.baseTree, meta.afterTree);
5825
5971
  meta.changes = d.changes;
5826
5972
  meta.stats = d.stats;
5827
- fs31.writeFileSync(runFile(meta.id, "changes.patch"), d.patch);
5973
+ fs32.writeFileSync(runFile(meta.id, "changes.patch"), d.patch);
5828
5974
  }
5829
5975
  function verifyPath() {
5830
- const extra = [path27.dirname(process.execPath), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"];
5831
- const parts = (process.env.PATH ?? "").split(path27.delimiter).filter(Boolean);
5832
- for (const d of process.platform === "win32" ? [path27.dirname(process.execPath)] : extra) if (!parts.includes(d)) parts.push(d);
5833
- return parts.join(path27.delimiter);
5976
+ const extra = [path28.dirname(process.execPath), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"];
5977
+ const parts = (process.env.PATH ?? "").split(path28.delimiter).filter(Boolean);
5978
+ for (const d of process.platform === "win32" ? [path28.dirname(process.execPath)] : extra) if (!parts.includes(d)) parts.push(d);
5979
+ return parts.join(path28.delimiter);
5834
5980
  }
5835
5981
  function runVerify(meta) {
5836
- const r = spawnSync9(meta.verify, {
5982
+ const r = spawnSync10(meta.verify, {
5837
5983
  cwd: meta.cwd,
5838
5984
  shell: true,
5839
5985
  encoding: "utf8",
@@ -5843,7 +5989,7 @@ function runVerify(meta) {
5843
5989
  env: { ...process.env, PATH: verifyPath(), PWD: meta.cwd, PITROOM_ACTIVE: "1" }
5844
5990
  });
5845
5991
  const output = `${r.stdout ?? ""}${r.stderr ?? ""}`;
5846
- fs31.writeFileSync(runFile(meta.id, "verify.log"), output);
5992
+ fs32.writeFileSync(runFile(meta.id, "verify.log"), output);
5847
5993
  const notFound = process.platform === "win32" && r.status === 1 && /is not recognized as an internal or external command/.test(output);
5848
5994
  return { ok: r.status === 0, code: notFound ? 127 : r.status, tail: output.trimEnd().split("\n").slice(-25).join("\n") };
5849
5995
  }
@@ -5898,12 +6044,12 @@ function discardRun(meta) {
5898
6044
  return `discarded the isolated copy of ${meta.id}; the patch stays in ${runFile(meta.id, "changes.patch")}`;
5899
6045
  }
5900
6046
  function cleanupWorktree(meta) {
5901
- if (meta.worktree && fs31.existsSync(meta.worktree)) removeIsolatedCopy(meta.worktree, worktreesDir());
6047
+ if (meta.worktree && fs32.existsSync(meta.worktree)) removeIsolatedCopy(meta.worktree, worktreesDir());
5902
6048
  }
5903
6049
 
5904
6050
  // src/cli/mcp-extras.ts
5905
- import fs32 from "node:fs";
5906
- import path28 from "node:path";
6051
+ import fs33 from "node:fs";
6052
+ import path29 from "node:path";
5907
6053
 
5908
6054
  // src/cli/mcp-support.ts
5909
6055
  import { spawn as spawn5 } from "node:child_process";
@@ -6188,7 +6334,7 @@ function skillPrompts() {
6188
6334
  const root = packageRoot();
6189
6335
  for (const name of skillNames(root)) {
6190
6336
  try {
6191
- const text = fs32.readFileSync(path28.join(root, "skills", name, "SKILL.md"), "utf8");
6337
+ const text = fs33.readFileSync(path29.join(root, "skills", name, "SKILL.md"), "utf8");
6192
6338
  const front = /^---\n([\s\S]*?)\n---\n/.exec(text);
6193
6339
  const body = (front ? text.slice(front[0].length) : text).trim();
6194
6340
  const description = /^description:\s*(.+)$/m.exec(front?.[1] ?? "")?.[1]?.trim() ?? `The ${name} skill.`;
@@ -6709,22 +6855,22 @@ async function serveMcp() {
6709
6855
 
6710
6856
  // src/cli/mcp-http.ts
6711
6857
  import crypto10 from "node:crypto";
6712
- import fs34 from "node:fs";
6858
+ import fs35 from "node:fs";
6713
6859
  import http2 from "node:http";
6714
- import path30 from "node:path";
6860
+ import path31 from "node:path";
6715
6861
 
6716
6862
  // src/core/dash.ts
6717
6863
  import { spawn as spawn6 } from "node:child_process";
6718
6864
  import crypto9 from "node:crypto";
6719
- import fs33 from "node:fs";
6865
+ import fs34 from "node:fs";
6720
6866
  import http from "node:http";
6721
- import path29 from "node:path";
6867
+ import path30 from "node:path";
6722
6868
  import { fileURLToPath as fileURLToPath4 } from "node:url";
6723
6869
 
6724
6870
  // src/core/file-diff.ts
6725
6871
  var PREVIEW_LINES = 300;
6726
6872
  function gitPath(value, prefix = true) {
6727
- let path38 = value;
6873
+ let path39 = value;
6728
6874
  if (value.startsWith('"') && value.endsWith('"')) {
6729
6875
  const bytes = [];
6730
6876
  const escapes = { t: " ", n: "\n", r: "\r", b: "\b", f: "\f", v: "\v" };
@@ -6732,9 +6878,9 @@ function gitPath(value, prefix = true) {
6732
6878
  if (match[1] && /^[0-7]+$/.test(match[1])) bytes.push(parseInt(match[1], 8));
6733
6879
  else bytes.push(...new TextEncoder().encode(match[2] ?? escapes[match[1]] ?? match[1]));
6734
6880
  }
6735
- path38 = new TextDecoder().decode(new Uint8Array(bytes));
6881
+ path39 = new TextDecoder().decode(new Uint8Array(bytes));
6736
6882
  }
6737
- return prefix ? path38.replace(/^[ab]\//, "") : path38;
6883
+ return prefix ? path39.replace(/^[ab]\//, "") : path39;
6738
6884
  }
6739
6885
  function headerPath(header) {
6740
6886
  const quoted = header.match(/^diff --git ("(?:[^"\\]|\\.)*"|\S+) ("(?:[^"\\]|\\.)*"|\S+)$/);
@@ -7015,11 +7161,11 @@ var assetsDir = () => fileURLToPath4(new URL("./ui/", import.meta.url));
7015
7161
  var assetCache = /* @__PURE__ */ new Map();
7016
7162
  function readAsset(route) {
7017
7163
  try {
7018
- const file2 = path29.join(assetsDir(), path29.basename(route));
7019
- const { mtimeMs } = fs33.statSync(file2);
7164
+ const file2 = path30.join(assetsDir(), path30.basename(route));
7165
+ const { mtimeMs } = fs34.statSync(file2);
7020
7166
  const hit = assetCache.get(route);
7021
7167
  if (hit && hit.mtimeMs === mtimeMs) return hit.data;
7022
- const data = fs33.readFileSync(file2);
7168
+ const data = fs34.readFileSync(file2);
7023
7169
  assetCache.set(route, { mtimeMs, data });
7024
7170
  return data;
7025
7171
  } catch {
@@ -7148,7 +7294,7 @@ function startDash(opts) {
7148
7294
  });
7149
7295
  });
7150
7296
  }
7151
- var registryFile = () => path29.join(home(), "dash.json");
7297
+ var registryFile = () => path30.join(home(), "dash.json");
7152
7298
  async function ping(port) {
7153
7299
  try {
7154
7300
  const r = await fetch(`http://127.0.0.1:${port}/api/state?limit=1`, { signal: AbortSignal.timeout(1500) });
@@ -7160,12 +7306,12 @@ async function ping(port) {
7160
7306
  async function runningDash() {
7161
7307
  let r;
7162
7308
  try {
7163
- r = JSON.parse(fs33.readFileSync(registryFile(), "utf8"));
7309
+ r = JSON.parse(fs34.readFileSync(registryFile(), "utf8"));
7164
7310
  } catch {
7165
7311
  return void 0;
7166
7312
  }
7167
7313
  if (!isAlive(r.pid)) {
7168
- fs33.rmSync(registryFile(), { force: true });
7314
+ fs34.rmSync(registryFile(), { force: true });
7169
7315
  return void 0;
7170
7316
  }
7171
7317
  return await ping(r.port) ? { ...r, url: `http://127.0.0.1:${r.port}/` } : void 0;
@@ -7182,7 +7328,7 @@ async function dashCommand(o) {
7182
7328
  if (o.stop) {
7183
7329
  if (!existing) throw new UserError("pitroom dash is not running");
7184
7330
  process.kill(existing.pid, "SIGTERM");
7185
- fs33.rmSync(registryFile(), { force: true });
7331
+ fs34.rmSync(registryFile(), { force: true });
7186
7332
  o.log(`stopped pitroom dash (${existing.url})`);
7187
7333
  return 0;
7188
7334
  }
@@ -7220,9 +7366,9 @@ async function dashCommand(o) {
7220
7366
  if (explicit || e.code !== "EADDRINUSE") throw new UserError(`pitroom dash: ${e.message}`);
7221
7367
  dash = await startDash({ port: 0, idleMs: o.idleMs });
7222
7368
  }
7223
- fs33.mkdirSync(home(), { recursive: true });
7369
+ fs34.mkdirSync(home(), { recursive: true });
7224
7370
  const tmp = `${registryFile()}.${process.pid}.tmp`;
7225
- fs33.writeFileSync(tmp, JSON.stringify({ pid: process.pid, port: dash.port }));
7371
+ fs34.writeFileSync(tmp, JSON.stringify({ pid: process.pid, port: dash.port }));
7226
7372
  renameOver(tmp, registryFile());
7227
7373
  const stop = () => void dash.close();
7228
7374
  process.once("SIGINT", stop);
@@ -7232,7 +7378,7 @@ async function dashCommand(o) {
7232
7378
  if (o.open) openBrowser(dash.url);
7233
7379
  await dash.closed;
7234
7380
  try {
7235
- if (JSON.parse(fs33.readFileSync(registryFile(), "utf8")).pid === process.pid) fs33.rmSync(registryFile(), { force: true });
7381
+ if (JSON.parse(fs34.readFileSync(registryFile(), "utf8")).pid === process.pid) fs34.rmSync(registryFile(), { force: true });
7236
7382
  } catch {
7237
7383
  }
7238
7384
  return 0;
@@ -7244,10 +7390,10 @@ var MAX_BODY = 4e6;
7244
7390
  var MAX_SESSIONS = 64;
7245
7391
  var IDLE_MS = 60 * 6e4;
7246
7392
  var PATH = "/mcp";
7247
- var tokenFile = () => path30.join(home(), "mcp-token");
7393
+ var tokenFile = () => path31.join(home(), "mcp-token");
7248
7394
  var readToken = (file2) => {
7249
7395
  try {
7250
- const kept = fs34.readFileSync(file2, "utf8").trim();
7396
+ const kept = fs35.readFileSync(file2, "utf8").trim();
7251
7397
  return kept.length >= 16 ? kept : void 0;
7252
7398
  } catch {
7253
7399
  return void 0;
@@ -7262,10 +7408,10 @@ function mcpToken() {
7262
7408
  const file2 = tokenFile();
7263
7409
  let token = readToken(file2);
7264
7410
  if (!token) {
7265
- fs34.mkdirSync(path30.dirname(file2), { recursive: true });
7411
+ fs35.mkdirSync(path31.dirname(file2), { recursive: true });
7266
7412
  token = crypto10.randomBytes(32).toString("hex");
7267
7413
  try {
7268
- fs34.writeFileSync(file2, `${token}
7414
+ fs35.writeFileSync(file2, `${token}
7269
7415
  `, { mode: 384, flag: "wx" });
7270
7416
  } catch (e) {
7271
7417
  if (e.code !== "EEXIST") throw e;
@@ -7274,7 +7420,7 @@ function mcpToken() {
7274
7420
  }
7275
7421
  }
7276
7422
  try {
7277
- fs34.chmodSync(file2, 384);
7423
+ fs35.chmodSync(file2, 384);
7278
7424
  } catch {
7279
7425
  }
7280
7426
  return { token, from: file2 };
@@ -7455,17 +7601,17 @@ async function serveMcpHttp(opts) {
7455
7601
  }
7456
7602
 
7457
7603
  // src/cli/commands.ts
7458
- import { spawnSync as spawnSync12 } from "node:child_process";
7459
- import fs41 from "node:fs";
7460
- import path36 from "node:path";
7604
+ import { spawnSync as spawnSync13 } from "node:child_process";
7605
+ import fs42 from "node:fs";
7606
+ import path37 from "node:path";
7461
7607
  import { fileURLToPath as fileURLToPath5 } from "node:url";
7462
7608
 
7463
7609
  // src/core/init.ts
7464
- import fs35 from "node:fs";
7465
- import path31 from "node:path";
7610
+ import fs36 from "node:fs";
7611
+ import path32 from "node:path";
7466
7612
  var FREE = /-free$/;
7467
7613
  var TIER_ORDER = [["cheap", "opencode"], ["standard", "codex"], ["capable", "claude"]];
7468
- var isFound = (binary8) => path31.isAbsolute(binary8) && fs35.existsSync(binary8);
7614
+ var isFound = (binary8) => path32.isAbsolute(binary8) && fs36.existsSync(binary8);
7469
7615
  function planInit(opts = {}) {
7470
7616
  const file2 = configPath();
7471
7617
  const workers = backendIds().map((id2) => {
@@ -7511,15 +7657,15 @@ function planInit(opts = {}) {
7511
7657
  if (Object.keys(tiers).length > 1) config.tiers = tiers;
7512
7658
  if (!workers.some((w) => w.found)) blocked = "no worker CLI found: install OpenCode (https://opencode.ai), Codex CLI, Claude Code or Gemini CLI first";
7513
7659
  else if (!blocked && !Object.keys(config).length) notes.push("nothing to propose: the default model and the single worker need no config");
7514
- return { path: file2, exists: fs35.existsSync(file2), workers, opencode: opencode2, config, blocked, notes };
7660
+ return { path: file2, exists: fs36.existsSync(file2), workers, opencode: opencode2, config, blocked, notes };
7515
7661
  }
7516
7662
  function writeInit(plan, force) {
7517
7663
  if (plan.blocked) throw new UserError(plan.blocked);
7518
7664
  if (!Object.keys(plan.config).length) throw new UserError("nothing to write: the proposal is empty");
7519
- if (plan.exists && !force) throw new UserError(`${plan.path} already exists: pass --force to replace it (the old file is kept as ${path31.basename(plan.path)}.bak)`);
7520
- fs35.mkdirSync(path31.dirname(plan.path), { recursive: true });
7521
- if (plan.exists) fs35.copyFileSync(plan.path, `${plan.path}.bak`);
7522
- fs35.writeFileSync(plan.path, `${JSON.stringify(plan.config, null, 2)}
7665
+ if (plan.exists && !force) throw new UserError(`${plan.path} already exists: pass --force to replace it (the old file is kept as ${path32.basename(plan.path)}.bak)`);
7666
+ fs36.mkdirSync(path32.dirname(plan.path), { recursive: true });
7667
+ if (plan.exists) fs36.copyFileSync(plan.path, `${plan.path}.bak`);
7668
+ fs36.writeFileSync(plan.path, `${JSON.stringify(plan.config, null, 2)}
7523
7669
  `);
7524
7670
  return plan.path;
7525
7671
  }
@@ -7539,18 +7685,18 @@ function formatInit(plan, written) {
7539
7685
  }
7540
7686
 
7541
7687
  // src/core/mcp-install.ts
7542
- import { spawnSync as spawnSync10 } from "node:child_process";
7543
- import fs36 from "node:fs";
7688
+ import { spawnSync as spawnSync11 } from "node:child_process";
7689
+ import fs37 from "node:fs";
7544
7690
  import os12 from "node:os";
7545
- import path32 from "node:path";
7691
+ import path33 from "node:path";
7546
7692
  var CLIENT_IDS = ["claude-code", "codex", "gemini", "cursor", "claude-desktop"];
7547
7693
  var NAME = "pitroom";
7548
7694
  function mcpCommand() {
7549
7695
  const launcher = launcherPath();
7550
7696
  if (isPitroomLauncher(launcher)) return { command: launcher, args: ["mcp"] };
7551
- for (const dir of (process.env.PATH ?? "").split(path32.delimiter).filter(Boolean)) {
7552
- const p = path32.join(dir, process.platform === "win32" ? "pitroom.cmd" : "pitroom");
7553
- if (fs36.existsSync(p)) return { command: path32.resolve(p), args: ["mcp"] };
7697
+ for (const dir of (process.env.PATH ?? "").split(path33.delimiter).filter(Boolean)) {
7698
+ const p = path33.join(dir, process.platform === "win32" ? "pitroom.cmd" : "pitroom");
7699
+ if (fs37.existsSync(p)) return { command: path33.resolve(p), args: ["mcp"] };
7554
7700
  }
7555
7701
  return { command: process.execPath, args: [process.argv[1] ?? "pitroom", "mcp"] };
7556
7702
  }
@@ -7560,13 +7706,13 @@ var same = (a, b) => {
7560
7706
  };
7561
7707
  function run(backendId, args) {
7562
7708
  const { command, prefix } = resolveCommand(getBackend(backendId).binary());
7563
- const r = spawnSync10(command, [...prefix, ...args], { encoding: "utf8", timeout: 6e4, stdio: ["ignore", "pipe", "pipe"] });
7709
+ const r = spawnSync11(command, [...prefix, ...args], { encoding: "utf8", timeout: 6e4, stdio: ["ignore", "pipe", "pipe"] });
7564
7710
  return { ok: r.status === 0 && !r.error, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
7565
7711
  }
7566
7712
  var runnable = (backendId) => run(backendId, ["--version"]).ok;
7567
7713
  var readJson = (file2) => {
7568
7714
  try {
7569
- return JSON.parse(fs36.readFileSync(file2, "utf8"));
7715
+ return JSON.parse(fs37.readFileSync(file2, "utf8"));
7570
7716
  } catch {
7571
7717
  return void 0;
7572
7718
  }
@@ -7576,7 +7722,7 @@ var jsonState = (file2, c) => {
7576
7722
  return entry === void 0 ? "absent" : same(c, entry) ? "same" : "different";
7577
7723
  };
7578
7724
  var home2 = () => os12.homedir();
7579
- var geminiDir = () => path32.join(process.env.GEMINI_CLI_HOME ?? home2(), ".gemini");
7725
+ var geminiDir = () => path33.join(process.env.GEMINI_CLI_HOME ?? home2(), ".gemini");
7580
7726
  function cliClient(id2, backend, name, where, o) {
7581
7727
  return {
7582
7728
  id: id2,
@@ -7599,43 +7745,43 @@ function jsonClient(id2, name, file2, dir) {
7599
7745
  return {
7600
7746
  id: id2,
7601
7747
  name,
7602
- found: () => fs36.existsSync(dir()),
7748
+ found: () => fs37.existsSync(dir()),
7603
7749
  state: (c) => jsonState(file2(), c),
7604
7750
  add: (c) => {
7605
7751
  let config = {};
7606
- if (fs36.existsSync(file2())) {
7752
+ if (fs37.existsSync(file2())) {
7607
7753
  config = readJson(file2());
7608
7754
  if (!config || typeof config !== "object" || Array.isArray(config)) return { ok: false, message: `${rel()} is not valid JSON: left alone` };
7609
7755
  const backup = `${file2()}.bak-pitroom`;
7610
- if (!fs36.existsSync(backup)) fs36.copyFileSync(file2(), backup);
7756
+ if (!fs37.existsSync(backup)) fs37.copyFileSync(file2(), backup);
7611
7757
  }
7612
7758
  config.mcpServers = { ...config.mcpServers ?? {}, [NAME]: { command: c.command, args: c.args } };
7613
- fs36.mkdirSync(path32.dirname(file2()), { recursive: true });
7759
+ fs37.mkdirSync(path33.dirname(file2()), { recursive: true });
7614
7760
  const tmp = `${file2()}.${process.pid}.tmp`;
7615
- fs36.writeFileSync(tmp, `${JSON.stringify(config, null, 2)}
7761
+ fs37.writeFileSync(tmp, `${JSON.stringify(config, null, 2)}
7616
7762
  `);
7617
- fs36.renameSync(tmp, file2());
7763
+ fs37.renameSync(tmp, file2());
7618
7764
  return { ok: true, message: rel() };
7619
7765
  },
7620
7766
  remove: () => {
7621
7767
  const config = readJson(file2());
7622
7768
  if (!config?.mcpServers?.[NAME]) return { ok: true, message: rel() };
7623
7769
  delete config.mcpServers[NAME];
7624
- fs36.writeFileSync(file2(), `${JSON.stringify(config, null, 2)}
7770
+ fs37.writeFileSync(file2(), `${JSON.stringify(config, null, 2)}
7625
7771
  `);
7626
7772
  return { ok: true, message: rel() };
7627
7773
  }
7628
7774
  };
7629
7775
  }
7630
7776
  function desktopFile() {
7631
- if (process.platform === "darwin") return path32.join(home2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
7632
- if (process.platform === "win32") return path32.join(process.env.APPDATA ?? path32.join(home2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
7633
- return path32.join(process.env.XDG_CONFIG_HOME ?? path32.join(home2(), ".config"), "Claude", "claude_desktop_config.json");
7777
+ if (process.platform === "darwin") return path33.join(home2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
7778
+ if (process.platform === "win32") return path33.join(process.env.APPDATA ?? path33.join(home2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
7779
+ return path33.join(process.env.XDG_CONFIG_HOME ?? path33.join(home2(), ".config"), "Claude", "claude_desktop_config.json");
7634
7780
  }
7635
7781
  function codexState(c) {
7636
7782
  let text;
7637
7783
  try {
7638
- text = fs36.readFileSync(path32.join(process.env.CODEX_HOME ?? path32.join(home2(), ".codex"), "config.toml"), "utf8");
7784
+ text = fs37.readFileSync(path33.join(process.env.CODEX_HOME ?? path33.join(home2(), ".codex"), "config.toml"), "utf8");
7639
7785
  } catch {
7640
7786
  return "absent";
7641
7787
  }
@@ -7653,7 +7799,7 @@ function codexState(c) {
7653
7799
  function clients() {
7654
7800
  return [
7655
7801
  cliClient("claude-code", "claude", "Claude Code", "user settings (~/.claude.json)", {
7656
- state: (c) => jsonState(path32.join(home2(), ".claude.json"), c),
7802
+ state: (c) => jsonState(path33.join(home2(), ".claude.json"), c),
7657
7803
  add: (c) => ["mcp", "add", "--scope", "user", NAME, "--", c.command, ...c.args],
7658
7804
  remove: () => ["mcp", "remove", NAME, "--scope", "user"]
7659
7805
  }),
@@ -7663,12 +7809,12 @@ function clients() {
7663
7809
  remove: () => ["mcp", "remove", NAME]
7664
7810
  }),
7665
7811
  cliClient("gemini", "gemini", "Gemini CLI", "user settings (~/.gemini/settings.json)", {
7666
- state: (c) => jsonState(path32.join(geminiDir(), "settings.json"), c),
7812
+ state: (c) => jsonState(path33.join(geminiDir(), "settings.json"), c),
7667
7813
  add: (c) => ["mcp", "add", "--scope", "user", NAME, c.command, ...c.args],
7668
7814
  remove: () => ["mcp", "remove", "--scope", "user", NAME]
7669
7815
  }),
7670
- jsonClient("cursor", "Cursor", () => path32.join(home2(), ".cursor", "mcp.json"), () => path32.join(home2(), ".cursor")),
7671
- jsonClient("claude-desktop", "Claude Desktop", desktopFile, () => path32.dirname(desktopFile()))
7816
+ jsonClient("cursor", "Cursor", () => path33.join(home2(), ".cursor", "mcp.json"), () => path33.join(home2(), ".cursor")),
7817
+ jsonClient("claude-desktop", "Claude Desktop", desktopFile, () => path33.dirname(desktopFile()))
7672
7818
  ];
7673
7819
  }
7674
7820
  function attempt2(f) {
@@ -7739,16 +7885,16 @@ var parseClients = (value) => {
7739
7885
 
7740
7886
  // src/core/plan-status.ts
7741
7887
  import crypto11 from "node:crypto";
7742
- import fs37 from "node:fs";
7743
- import path33 from "node:path";
7888
+ import fs38 from "node:fs";
7889
+ import path34 from "node:path";
7744
7890
  function notesFile(plan) {
7745
7891
  const id2 = crypto11.createHash("sha1").update(plan.file).digest("hex").slice(0, 12);
7746
- return path33.join(home(), "plans", id2, "notes.md");
7892
+ return path34.join(home(), "plans", id2, "notes.md");
7747
7893
  }
7748
7894
  function readNotes(plan) {
7749
7895
  const f = notesFile(plan);
7750
- if (!fs37.existsSync(f)) return [];
7751
- return fs37.readFileSync(f, "utf8").split("\n").slice(1).filter(Boolean).map((l) => {
7896
+ if (!fs38.existsSync(f)) return [];
7897
+ return fs38.readFileSync(f, "utf8").split("\n").slice(1).filter(Boolean).map((l) => {
7752
7898
  const m = /^(\d{4}-\d\d-\d\d \d\d:\d\d) (.*)$/.exec(l);
7753
7899
  return m ? { at: m[1], text: m[2] } : { at: "", text: l };
7754
7900
  });
@@ -7756,11 +7902,11 @@ function readNotes(plan) {
7756
7902
  function addNote(planFile, text) {
7757
7903
  const plan = loadPlan(planFile);
7758
7904
  const f = notesFile(plan);
7759
- fs37.mkdirSync(path33.dirname(f), { recursive: true });
7760
- if (!fs37.existsSync(f)) fs37.writeFileSync(f, `# plan: ${plan.file}
7905
+ fs38.mkdirSync(path34.dirname(f), { recursive: true });
7906
+ if (!fs38.existsSync(f)) fs38.writeFileSync(f, `# plan: ${plan.file}
7761
7907
  `);
7762
7908
  const at = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
7763
- fs37.appendFileSync(f, `${at} ${text.replace(/\s+/g, " ").trim()}
7909
+ fs38.appendFileSync(f, `${at} ${text.replace(/\s+/g, " ").trim()}
7764
7910
  `);
7765
7911
  return f;
7766
7912
  }
@@ -7828,8 +7974,8 @@ ${rulings}`,
7828
7974
 
7829
7975
  // src/core/cache.ts
7830
7976
  import crypto12 from "node:crypto";
7831
- import fs38 from "node:fs";
7832
- import path34 from "node:path";
7977
+ import fs39 from "node:fs";
7978
+ import path35 from "node:path";
7833
7979
  function preferredReadsOwnSnapshot(o) {
7834
7980
  try {
7835
7981
  const preferred = effective().worker.value;
@@ -7842,20 +7988,20 @@ var SCAN2 = 500;
7842
7988
  function cacheKey(o) {
7843
7989
  if (o.verify || o.mode !== "read" || o.continueFrom || o.web || o.plan || o.review || o.audit || o.auditRate === 1) return void 0;
7844
7990
  if (!(effective().cacheDays.value > 0)) return void 0;
7845
- const dir = canonical(path34.resolve(o.dir));
7991
+ const dir = canonical(path35.resolve(o.dir));
7846
7992
  const root = repoRoot(dir);
7847
7993
  if (!root) return void 0;
7848
7994
  try {
7849
7995
  const tree = snapshotTree(root);
7850
7996
  const head = commitOf(root, "HEAD") ?? "";
7851
7997
  const files = o.files.map((f) => {
7852
- const abs = path34.resolve(f);
7853
- return [abs, crypto12.createHash("sha256").update(fs38.readFileSync(abs)).digest("hex")];
7998
+ const abs = path35.resolve(f);
7999
+ return [abs, crypto12.createHash("sha256").update(fs39.readFileSync(abs)).digest("hex")];
7854
8000
  });
7855
8001
  const question = o.task.replace(/\s+/g, " ").trim();
7856
8002
  const readIn2 = READ_IN.includes(effective().readIn.value) ? effective().readIn.value : "auto";
7857
8003
  const where = !o.inPlace && (preferredReadsOwnSnapshot(o) || wantSnapshot(readIn2, root, dir)) ? "snapshot" : "project";
7858
- const key = crypto12.createHash("sha256").update(JSON.stringify([question, path34.relative(root, dir), files, where])).digest("hex");
8004
+ const key = crypto12.createHash("sha256").update(JSON.stringify([question, path35.relative(root, dir), files, where])).digest("hex");
7859
8005
  return { key, state: `${head}:${tree}`, where };
7860
8006
  } catch {
7861
8007
  return void 0;
@@ -7898,13 +8044,13 @@ function ago(iso, now = Date.now()) {
7898
8044
  var cachedNote = (m) => `pitroom \u27F2 cached answer \xB7 the same question on the same code as run ${m.id} (${ago(m.endedAt ?? m.startedAt)}) \xB7 no worker ran \xB7 --fresh asks one again`;
7899
8045
 
7900
8046
  // src/core/eval.ts
7901
- import { spawnSync as spawnSync11 } from "node:child_process";
7902
- import fs39 from "node:fs";
8047
+ import { spawnSync as spawnSync12 } from "node:child_process";
8048
+ import fs40 from "node:fs";
7903
8049
  var KINDS = ["definition", "count", "set"];
7904
8050
  function loadEval(file2) {
7905
8051
  let raw;
7906
8052
  try {
7907
- raw = JSON.parse(fs39.readFileSync(file2, "utf8"));
8053
+ raw = JSON.parse(fs40.readFileSync(file2, "utf8"));
7908
8054
  } catch (e) {
7909
8055
  throw new UserError(`cannot read ${file2}: ${e.message}`);
7910
8056
  }
@@ -7931,7 +8077,7 @@ function loadEval(file2) {
7931
8077
  }
7932
8078
  var clean = (p) => p.replace(/\\/g, "/").replace(/^\.?\//, "");
7933
8079
  function gitGrep(root, flags, t) {
7934
- const r = spawnSync11("git", ["-C", root, "grep", ...flags, t.regex ? "-E" : "-F", ...t.word ? ["-w"] : [], "-e", t.grep, "--", t.dir ?? "."], {
8080
+ const r = spawnSync12("git", ["-C", root, "grep", ...flags, t.regex ? "-E" : "-F", ...t.word ? ["-w"] : [], "-e", t.grep, "--", t.dir ?? "."], {
7935
8081
  encoding: "utf8",
7936
8082
  maxBuffer: 64 * 1024 * 1024
7937
8083
  });
@@ -8027,13 +8173,16 @@ function formatEval(rows, group) {
8027
8173
 
8028
8174
  // src/core/review.ts
8029
8175
  import crypto13 from "node:crypto";
8030
- import fs40 from "node:fs";
8031
- import path35 from "node:path";
8176
+ import fs41 from "node:fs";
8177
+ import path36 from "node:path";
8032
8178
  var TEMPLATE2 = { task: "task-reviewer", fix: "re-review", range: "code-reviewer" };
8033
8179
  var section = (title, body) => `## ${title}
8034
8180
 
8035
8181
  ${body.trim() || "(empty)"}
8036
8182
  `;
8183
+ var numbered = (diff) => diff.trim() ? `${NUMBERED}
8184
+
8185
+ ${/^[+ -]\s*\d+\| /m.test(diff) ? diff : numberDiff(diff)}` : diff;
8037
8186
  function ancestors(m) {
8038
8187
  const out = [];
8039
8188
  for (let id2 = m.parent; id2; ) {
@@ -8046,7 +8195,7 @@ function ancestors(m) {
8046
8195
  function briefOf(m) {
8047
8196
  const root = [m, ...ancestors(m)].at(-1);
8048
8197
  const f = runFile(root.id, "brief.md");
8049
- return fs40.existsSync(f) ? fs40.readFileSync(f, "utf8") : root.task;
8198
+ return fs41.existsSync(f) ? fs41.readFileSync(f, "utf8") : root.task;
8050
8199
  }
8051
8200
  function latestReview(ids) {
8052
8201
  for (const id2 of listRunIds().reverse()) {
@@ -8060,7 +8209,7 @@ function latestReview(ids) {
8060
8209
  }
8061
8210
  function diffOf(m, from = m.baseTree) {
8062
8211
  const where = m.mode === "isolate" ? m.worktree : m.repoRoot;
8063
- if (where && fs40.existsSync(where) && from && m.afterTree) return reviewDiff(where, from, m.afterTree);
8212
+ if (where && fs41.existsSync(where) && from && m.afterTree) return reviewDiff(where, from, m.afterTree);
8064
8213
  if (from !== m.baseTree) {
8065
8214
  throw new UserError(`the isolated copy of ${m.id} is gone, so its fix round cannot be shown on its own; review a run that is not applied yet`, 3);
8066
8215
  }
@@ -8074,7 +8223,7 @@ function runReview(id2) {
8074
8223
  if (!m.changes?.length) throw new UserError(`run ${m.id} made no changes; nothing to review`);
8075
8224
  const chain = ancestors(m);
8076
8225
  const previous = latestReview(chain.map((a) => a.id));
8077
- const dir = m.mode === "isolate" && m.worktree && fs40.existsSync(m.worktree) ? m.cwd : m.dir;
8226
+ const dir = m.mode === "isolate" && m.worktree && fs41.existsSync(m.worktree) ? m.cwd : m.dir;
8078
8227
  const job = { of: m.id, dir, implementer: m.ran ?? m.worker, group: m.group, plan: m.plan };
8079
8228
  const title = m.plan ? `Task ${m.plan.step}: ${m.plan.title}` : `run ${m.id}`;
8080
8229
  if (!previous) {
@@ -8082,7 +8231,7 @@ function runReview(id2) {
8082
8231
  ...job,
8083
8232
  kind: "task",
8084
8233
  package: [`# Review package \xB7 ${title}
8085
- `, section("BRIEF", briefOf(m)), section("REPORT", readSummary(m)), section("DIFF", diffOf(m))].join("\n")
8234
+ `, section("BRIEF", briefOf(m)), section("REPORT", readSummary(m)), section("DIFF", numbered(diffOf(m)))].join("\n")
8086
8235
  };
8087
8236
  }
8088
8237
  const reviewed = chain.find((a) => a.id === previous.reviewOf);
@@ -8096,7 +8245,7 @@ function runReview(id2) {
8096
8245
  section("BRIEF", briefOf(m)),
8097
8246
  section("PREVIOUS FINDINGS", readSummary(previous)),
8098
8247
  section("FIX REPORT", readSummary(m)),
8099
- section("FIX DIFF", diffOf(m, from))
8248
+ section("FIX DIFF", numbered(diffOf(m, from)))
8100
8249
  ].join("\n")
8101
8250
  };
8102
8251
  }
@@ -8150,19 +8299,19 @@ function writePackage(job) {
8150
8299
  const root = repoRoot(job.dir);
8151
8300
  if (!g || !root) throw new UserError(`not a git repository: ${job.dir}`);
8152
8301
  const name = `review-${crypto13.randomBytes(4).toString("hex")}.md`;
8153
- const inside3 = !path35.relative(fs40.realpathSync(root), fs40.realpathSync(g)).startsWith("..");
8154
- const file2 = inside3 ? path35.join(g, "pitroom", name) : path35.join(root, ".pitroom", name);
8302
+ const inside3 = !path36.relative(fs41.realpathSync(root), fs41.realpathSync(g)).startsWith("..");
8303
+ const file2 = inside3 ? path36.join(g, "pitroom", name) : path36.join(root, ".pitroom", name);
8155
8304
  if (!inside3) ignoreInGit(root, "/.pitroom/");
8156
- fs40.mkdirSync(path35.dirname(file2), { recursive: true });
8157
- fs40.writeFileSync(file2, job.package);
8305
+ fs41.mkdirSync(path36.dirname(file2), { recursive: true });
8306
+ fs41.writeFileSync(file2, job.package);
8158
8307
  return file2;
8159
8308
  }
8160
8309
  function ignoreInGit(root, pattern) {
8161
- const exclude = path35.resolve(root, git(root, ["rev-parse", "--git-path", "info/exclude"]).stdout.trim());
8162
- const text = fs40.existsSync(exclude) ? fs40.readFileSync(exclude, "utf8") : "";
8310
+ const exclude = path36.resolve(root, git(root, ["rev-parse", "--git-path", "info/exclude"]).stdout.trim());
8311
+ const text = fs41.existsSync(exclude) ? fs41.readFileSync(exclude, "utf8") : "";
8163
8312
  if (text.split(/\r?\n/).includes(pattern)) return;
8164
- fs40.mkdirSync(path35.dirname(exclude), { recursive: true });
8165
- fs40.appendFileSync(exclude, `${text && !text.endsWith("\n") ? "\n" : ""}# Pitroom review packages in worktrees (removed after each review)
8313
+ fs41.mkdirSync(path36.dirname(exclude), { recursive: true });
8314
+ fs41.appendFileSync(exclude, `${text && !text.endsWith("\n") ? "\n" : ""}# Pitroom review packages in worktrees (removed after each review)
8166
8315
  ${pattern}
8167
8316
  `);
8168
8317
  }
@@ -8318,10 +8467,10 @@ async function cmdReview(p) {
8318
8467
  writeMeta(meta);
8319
8468
  }
8320
8469
  } catch (e) {
8321
- fs41.rmSync(packageFile, { force: true });
8470
+ fs42.rmSync(packageFile, { force: true });
8322
8471
  throw e;
8323
8472
  }
8324
- fs41.writeFileSync(runFile(meta.id, "package.md"), job.package);
8473
+ fs42.writeFileSync(runFile(meta.id, "package.md"), job.package);
8325
8474
  return launch(p, meta);
8326
8475
  }
8327
8476
  async function cmdAudit(p) {
@@ -8436,7 +8585,7 @@ function cmdPlan(p) {
8436
8585
  }
8437
8586
  function cmdCrew(p) {
8438
8587
  const file2 = flag(p, "task-file");
8439
- const tasks = (file2 ? fs41.readFileSync(file2, "utf8").split(/^\s*---\s*$/m) : p.positional).map((t) => t.trim()).filter(Boolean);
8588
+ const tasks = (file2 ? fs42.readFileSync(file2, "utf8").split(/^\s*---\s*$/m) : p.positional).map((t) => t.trim()).filter(Boolean);
8440
8589
  if (!tasks.length) throw new UserError('crew needs tasks: pitroom crew -g NAME "task 1" "task 2" (or --task-file with --- separators)');
8441
8590
  if (has(p, "write") && tasks.length > 1) {
8442
8591
  throw new UserError("parallel --write runs would edit the same tree; use --isolate (each worker gets its own isolated copy)");
@@ -8463,7 +8612,7 @@ async function cmdEval(p) {
8463
8612
  if (has(p, "write") || has(p, "isolate") || has(p, "continue") || has(p, "plan")) throw new UserError("eval asks read questions: drop -w/-i/--continue/--plan");
8464
8613
  const questions = loadEval(file2);
8465
8614
  const base2 = runOptions(p, "eval");
8466
- const root = repoRoot(path36.resolve(base2.dir));
8615
+ const root = repoRoot(path37.resolve(base2.dir));
8467
8616
  const truths = new Map(questions.map((q) => [q.id, truthOf(q, root)]));
8468
8617
  const workers = [...new Set(p.flags.get("worker") ?? [effective().worker.value])];
8469
8618
  const group = flag(p, "group") ?? `eval-${(/* @__PURE__ */ new Date()).toISOString().slice(11, 19).replace(/:/g, "")}`;
@@ -8666,13 +8815,13 @@ function sinceMs(s) {
8666
8815
  if (!m) throw new UserError("--since takes 7d, 30d, \u2026 or all");
8667
8816
  return Date.now() - Number(m[1]) * 864e5;
8668
8817
  }
8669
- var readStdin = () => process.stdin.isTTY ? "" : fs41.readFileSync(0, "utf8");
8818
+ var readStdin = () => process.stdin.isTTY ? "" : fs42.readFileSync(0, "utf8");
8670
8819
  function cmdStatusline(p) {
8671
8820
  const input = readStdin();
8672
8821
  const lines = [];
8673
8822
  const then = flag(p, "then");
8674
8823
  if (then) {
8675
- const r = process.platform === "win32" ? spawnSync12(then, { shell: true, input, encoding: "utf8", timeout: 5e3 }) : spawnSync12("/bin/sh", ["-c", then], { input, encoding: "utf8", timeout: 5e3 });
8824
+ const r = process.platform === "win32" ? spawnSync13(then, { shell: true, input, encoding: "utf8", timeout: 5e3 }) : spawnSync13("/bin/sh", ["-c", then], { input, encoding: "utf8", timeout: 5e3 });
8676
8825
  if (r.stdout?.trimEnd()) lines.push(r.stdout.trimEnd());
8677
8826
  }
8678
8827
  try {
@@ -8706,8 +8855,8 @@ function cmdModels(p) {
8706
8855
  return 0;
8707
8856
  }
8708
8857
  function cmdHookStart() {
8709
- const script = path36.join(path36.dirname(path36.dirname(fileURLToPath5(import.meta.url))), "hooks", "session-start.mjs");
8710
- if (fs41.existsSync(script)) spawnSync12(process.execPath, [script], { stdio: ["ignore", "inherit", "ignore"], env: process.env });
8858
+ const script = path37.join(path37.dirname(path37.dirname(fileURLToPath5(import.meta.url))), "hooks", "session-start.mjs");
8859
+ if (fs42.existsSync(script)) spawnSync13(process.execPath, [script], { stdio: ["ignore", "inherit", "ignore"], env: process.env });
8711
8860
  return 0;
8712
8861
  }
8713
8862
  function cmdInit(p) {
@@ -8749,8 +8898,8 @@ function cmdSavings(p) {
8749
8898
  est. saved ${usd(t.saved)} (vs ${primaryPrice().name} list prices)`);
8750
8899
  const out = flag(p, "card");
8751
8900
  if (out) {
8752
- fs41.writeFileSync(out, card(t, period));
8753
- console.log(`card written to ${path36.resolve(out)}`);
8901
+ fs42.writeFileSync(out, card(t, period));
8902
+ console.log(`card written to ${path37.resolve(out)}`);
8754
8903
  }
8755
8904
  if (has(p, "badge")) console.log(`![pitroom](${badgeUrl(t)})`);
8756
8905
  return 0;
@@ -8792,7 +8941,7 @@ function cmdConfig(p) {
8792
8941
  console.log(JSON.stringify({ path: configPath(), settings: eff, warnings: warnings2 }, null, 2));
8793
8942
  return 0;
8794
8943
  }
8795
- console.log(`config file: ${configPath()}${fs41.existsSync(configPath()) ? "" : " (not present)"}`);
8944
+ console.log(`config file: ${configPath()}${fs42.existsSync(configPath()) ? "" : " (not present)"}`);
8796
8945
  for (const [key, s] of Object.entries(eff)) {
8797
8946
  const v = Array.isArray(s.value) ? s.value.join(", ") || "\u2014" : s.value && typeof s.value === "object" ? Object.entries(s.value).map(([k, m]) => `${k}=${m}`).join(", ") || "\u2014" : s.value === void 0 ? key === "model" ? "the worker's default" : "\u2014" : String(s.value);
8798
8947
  console.log(` ${key.padEnd(15)} ${v} (${s.source})`);
@@ -8816,7 +8965,7 @@ function cmdClean(p) {
8816
8965
  const m = readMeta(id2);
8817
8966
  if (m.mode === "isolate" && !m.discarded && !m.applied) discardRun(m);
8818
8967
  recordRun(m);
8819
- fs41.rmSync(runDir(id2), { recursive: true, force: true });
8968
+ fs42.rmSync(runDir(id2), { recursive: true, force: true });
8820
8969
  }
8821
8970
  const snapshots = prune(Date.now(), 0);
8822
8971
  console.log(`removed ${old.length} run(s)${snapshots ? ` and ${snapshots} read snapshot${snapshots === 1 ? "" : "s"}` : ""}; the history and the savings ledger are kept`);
@@ -8826,10 +8975,10 @@ var cmdRevert = (p) => (console.log(revertRun(freshMeta(resolveRun(p.positional[
8826
8975
  var cmdDiscard = (p) => (console.log(discardRun(freshMeta(resolveRun(p.positional[0])))), 0);
8827
8976
 
8828
8977
  // src/core/doctor.ts
8829
- import { spawnSync as spawnSync13 } from "node:child_process";
8830
- import fs42 from "node:fs";
8978
+ import { spawnSync as spawnSync14 } from "node:child_process";
8979
+ import fs43 from "node:fs";
8831
8980
  import os13 from "node:os";
8832
- import path37 from "node:path";
8981
+ import path38 from "node:path";
8833
8982
  var MARK2 = { ok: () => green("\u2714"), warn: () => yellow("!"), fail: () => red("\u2718") };
8834
8983
  var NEXT = [
8835
8984
  { when: /no default model|no fallback workers/, command: "pitroom init", why: "propose a starter config (fallback models from your catalogue)" },
@@ -8852,14 +9001,14 @@ function usageLine() {
8852
9001
  return `${ids.length} run${ids.length === 1 ? "" : "s"} so far${when2} (pitroom savings shows what they saved)`;
8853
9002
  }
8854
9003
  function firstNodeOnPath() {
8855
- for (const dir of (process.env.PATH ?? "").split(path37.delimiter).filter(Boolean)) {
8856
- const file2 = path37.join(dir, process.platform === "win32" ? "node.exe" : "node");
9004
+ for (const dir of (process.env.PATH ?? "").split(path38.delimiter).filter(Boolean)) {
9005
+ const file2 = path38.join(dir, process.platform === "win32" ? "node.exe" : "node");
8857
9006
  try {
8858
- if (!fs42.statSync(file2).isFile()) continue;
9007
+ if (!fs43.statSync(file2).isFile()) continue;
8859
9008
  } catch {
8860
9009
  continue;
8861
9010
  }
8862
- const r = spawnSync13(file2, ["-p", "process.versions.node"], { encoding: "utf8", timeout: 5e3 });
9011
+ const r = spawnSync14(file2, ["-p", "process.versions.node"], { encoding: "utf8", timeout: 5e3 });
8863
9012
  return r.status === 0 ? { path: file2, version: r.stdout.trim() } : { path: file2, version: "" };
8864
9013
  }
8865
9014
  return void 0;
@@ -8883,14 +9032,16 @@ function doctor8(probe2) {
8883
9032
  const addAll = (list2) => list2.forEach((c) => add2(c.level, c.message));
8884
9033
  add2("ok", `pitroom ${VERSION2} \xB7 node ${process.versions.node} \xB7 state in ${home()}`);
8885
9034
  const onPath = firstNodeOnPath();
8886
- if (onPath && onPath.version && tooOld(onPath.version) && path37.resolve(onPath.path) !== path37.resolve(process.execPath)) {
9035
+ if (onPath && onPath.version && tooOld(onPath.version) && path38.resolve(onPath.path) !== path38.resolve(process.execPath)) {
8887
9036
  add2("warn", `the first \`node\` on PATH is v${onPath.version} (${onPath.path}), older than the 22.13 Pitroom needs: shells that agent apps start may use it (the pitroom command finds a newer Node itself; other tools may not)`);
8888
9037
  }
8889
9038
  add2(gitAvailable() ? "ok" : "warn", gitAvailable() ? "git available" : "git not found: --write/--isolate tracking disabled");
8890
9039
  const cfg = loadConfig();
8891
- add2(cfg.warnings.length ? "warn" : "ok", `config: ${configPath()}${fs42.existsSync(configPath()) ? "" : " (not present, defaults in use)"}`);
9040
+ add2(cfg.warnings.length ? "warn" : "ok", `config: ${configPath()}${fs43.existsSync(configPath()) ? "" : " (not present, defaults in use)"}`);
8892
9041
  for (const w of cfg.warnings) add2("warn", w);
8893
- if (shimReady()) add2("ok", "git guard shim ready");
9042
+ if (shimReady() && process.platform === "win32" && !fs43.existsSync(path38.join(shimDir(), "git.exe"))) {
9043
+ add2("warn", "git guard shim ready as git.cmd only (no C# compiler of .NET Framework 4 to build git.exe): a program that starts git without a shell skips it, and PowerShell's unquoted & | < > reach git changed");
9044
+ } else if (shimReady()) add2("ok", "git guard shim ready");
8894
9045
  else if (process.platform === "win32") add2("warn", "git guard: the ref and push guard is on, but the git shim needs the sh.exe of Git for Windows next to git (not found), so a worker's `git add`, `restore` or `clean` is not blocked; prefer --isolate and check the patch before apply");
8895
9046
  else add2("warn", "git guard unavailable (git not on PATH)");
8896
9047
  section2("Worker chain");
@@ -8902,7 +9053,8 @@ function doctor8(probe2) {
8902
9053
  } catch (e) {
8903
9054
  add2("fail", e.message);
8904
9055
  }
8905
- if (chain.length) add2("ok", `worker chain: ${chain.map(describeTarget).join(" \u2192 ")}`);
9056
+ const auto = chain.length > 1 && !effective().fallback.value.length;
9057
+ if (chain.length) add2("ok", `worker chain: ${chain.map(describeTarget).join(" \u2192 ")}${auto ? ' (fallbacks from "autoFallback": the other workers your config names)' : ""}`);
8906
9058
  const rate2 = effective().audit.value;
8907
9059
  if (rate2 > 0 && chain.length) {
8908
9060
  const auditor = pickAuditor({ worker: chain[0], fallback: chain.slice(1) });
@@ -9021,12 +9173,12 @@ function liveProbe(target) {
9021
9173
  } catch (e) {
9022
9174
  return { level: "fail", message: e.message };
9023
9175
  }
9024
- const scratch = readsOwnSnapshot(backend) ? fs42.mkdtempSync(path37.join(os13.tmpdir(), "pitroom-probe-")) : void 0;
9176
+ const scratch = readsOwnSnapshot(backend) ? fs43.mkdtempSync(path38.join(os13.tmpdir(), "pitroom-probe-")) : void 0;
9025
9177
  const cwd = scratch ?? process.cwd();
9026
9178
  try {
9027
9179
  return probe(backend, target, cwd);
9028
9180
  } finally {
9029
- if (scratch) fs42.rmSync(scratch, { recursive: true, force: true });
9181
+ if (scratch) fs43.rmSync(scratch, { recursive: true, force: true });
9030
9182
  }
9031
9183
  }
9032
9184
  function probe(backend, target, cwd) {
@@ -9040,7 +9192,7 @@ function probe(backend, target, cwd) {
9040
9192
  title: "pitroom doctor probe"
9041
9193
  });
9042
9194
  const t0 = Date.now();
9043
- const r = spawnSync13(inv.command, inv.args, {
9195
+ const r = spawnSync14(inv.command, inv.args, {
9044
9196
  encoding: "utf8",
9045
9197
  timeout: 3e5,
9046
9198
  stdio: ["ignore", "pipe", "pipe"],
@@ -9071,20 +9223,20 @@ function skillChecks() {
9071
9223
  else if (!viaPlugin) checks.push({ level: "warn", message: `no Pitroom skills in ${base2}; run \`pitroom install\`` });
9072
9224
  }
9073
9225
  if (geminiExtensionInstalled()) {
9074
- const linked = installedSkills().some((i) => i.base.includes(`${path37.sep}.agents${path37.sep}`) && i.names.length);
9226
+ const linked = installedSkills().some((i) => i.base.includes(`${path38.sep}.agents${path38.sep}`) && i.names.length);
9075
9227
  checks.push(
9076
9228
  linked ? { level: "warn", message: "Pitroom is installed as a Gemini CLI extension and linked into ~/.agents/skills: Gemini loads the skills twice; run `pitroom uninstall` or `gemini extensions uninstall pitroom`" } : { level: "ok", message: "Gemini CLI extension installed (skills + context)" }
9077
9229
  );
9078
9230
  }
9079
9231
  if (viaPlugin) {
9080
- const linked = installedSkills().some((i) => i.base.includes(`${path37.sep}.claude${path37.sep}`) && i.names.length);
9232
+ const linked = installedSkills().some((i) => i.base.includes(`${path38.sep}.claude${path38.sep}`) && i.names.length);
9081
9233
  checks.push(
9082
9234
  linked ? { level: "warn", message: "Pitroom is installed as a Claude Code plugin and linked into ~/.claude/skills: skills load twice; run `pitroom uninstall` or remove the plugin" } : { level: "ok", message: "Claude Code plugin installed (skills + session-start hook)" }
9083
9235
  );
9084
9236
  }
9085
9237
  const launcher = launcherPath();
9086
- if (fs42.existsSync(launcher)) {
9087
- const r = spawnSync13(launcher, ["--version"], { encoding: "utf8", timeout: 3e4, stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
9238
+ if (fs43.existsSync(launcher)) {
9239
+ const r = spawnSync14(launcher, ["--version"], { encoding: "utf8", timeout: 3e4, stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
9088
9240
  checks.push(
9089
9241
  r.status === 0 ? { level: "ok", message: `launcher ${launcher} \u2192 pitroom ${r.stdout.trim()}` } : { level: "fail", message: `launcher ${launcher} does not start: ${(r.stderr || r.stdout).trim().slice(0, 200)}` }
9090
9242
  );
@@ -9095,12 +9247,12 @@ function skillChecks() {
9095
9247
  }
9096
9248
  function geminiExtensionInstalled() {
9097
9249
  const home3 = process.env.GEMINI_CLI_HOME ?? os13.homedir();
9098
- return fs42.existsSync(path37.join(home3, ".gemini", "extensions", "pitroom"));
9250
+ return fs43.existsSync(path38.join(home3, ".gemini", "extensions", "pitroom"));
9099
9251
  }
9100
9252
  function pluginInstalled() {
9101
9253
  try {
9102
- const f = path37.join(os13.homedir(), ".claude", "plugins", "installed_plugins.json");
9103
- const plugins = JSON.parse(fs42.readFileSync(f, "utf8")).plugins ?? {};
9254
+ const f = path38.join(os13.homedir(), ".claude", "plugins", "installed_plugins.json");
9255
+ const plugins = JSON.parse(fs43.readFileSync(f, "utf8")).plugins ?? {};
9104
9256
  return Object.keys(plugins).some((k) => k.startsWith("pitroom@"));
9105
9257
  } catch {
9106
9258
  return false;
@@ -9117,24 +9269,24 @@ function superpowersActive() {
9117
9269
  for (const p of openCodePlugins()) {
9118
9270
  if (/superpowers/i.test(p)) found.push(`OpenCode plugin ${p}`);
9119
9271
  }
9120
- for (const base2 of [path37.join(os13.homedir(), ".agents", "skills"), path37.join(os13.homedir(), ".claude", "skills")]) {
9121
- const dir = path37.join(base2, "using-superpowers");
9122
- if (fs42.existsSync(path37.join(dir, "SKILL.md"))) found.push(dir);
9272
+ for (const base2 of [path38.join(os13.homedir(), ".agents", "skills"), path38.join(os13.homedir(), ".claude", "skills")]) {
9273
+ const dir = path38.join(base2, "using-superpowers");
9274
+ if (fs43.existsSync(path38.join(dir, "SKILL.md"))) found.push(dir);
9123
9275
  }
9124
9276
  return found;
9125
9277
  }
9126
9278
  function claudePlugins() {
9127
9279
  try {
9128
- const f = path37.join(os13.homedir(), ".claude", "plugins", "installed_plugins.json");
9129
- return Object.keys(JSON.parse(fs42.readFileSync(f, "utf8")).plugins ?? {});
9280
+ const f = path38.join(os13.homedir(), ".claude", "plugins", "installed_plugins.json");
9281
+ return Object.keys(JSON.parse(fs43.readFileSync(f, "utf8")).plugins ?? {});
9130
9282
  } catch {
9131
9283
  return [];
9132
9284
  }
9133
9285
  }
9134
9286
  function claudePluginEnabled(key) {
9135
9287
  try {
9136
- const f = path37.join(os13.homedir(), ".claude", "settings.json");
9137
- return JSON.parse(fs42.readFileSync(f, "utf8")).enabledPlugins?.[key] !== false;
9288
+ const f = path38.join(os13.homedir(), ".claude", "settings.json");
9289
+ return JSON.parse(fs43.readFileSync(f, "utf8")).enabledPlugins?.[key] !== false;
9138
9290
  } catch {
9139
9291
  return true;
9140
9292
  }
@@ -9143,7 +9295,7 @@ function codexPlugins() {
9143
9295
  const plugins = /* @__PURE__ */ new Map();
9144
9296
  let text;
9145
9297
  try {
9146
- text = fs42.readFileSync(path37.join(process.env.CODEX_HOME ?? path37.join(os13.homedir(), ".codex"), "config.toml"), "utf8");
9298
+ text = fs43.readFileSync(path38.join(process.env.CODEX_HOME ?? path38.join(os13.homedir(), ".codex"), "config.toml"), "utf8");
9147
9299
  } catch {
9148
9300
  return plugins;
9149
9301
  }
@@ -9161,11 +9313,11 @@ function codexPlugins() {
9161
9313
  return plugins;
9162
9314
  }
9163
9315
  function openCodePlugins() {
9164
- const dir = path37.join(process.env.XDG_CONFIG_HOME ?? path37.join(os13.homedir(), ".config"), "opencode");
9316
+ const dir = path38.join(process.env.XDG_CONFIG_HOME ?? path38.join(os13.homedir(), ".config"), "opencode");
9165
9317
  const plugins = [];
9166
9318
  for (const name of ["opencode.json", "opencode.jsonc"]) {
9167
9319
  try {
9168
- const text = fs42.readFileSync(path37.join(dir, name), "utf8").replace(/\/\*[\s\S]*?\*\/|^\s*\/\/.*$/gm, "");
9320
+ const text = fs43.readFileSync(path38.join(dir, name), "utf8").replace(/\/\*[\s\S]*?\*\/|^\s*\/\/.*$/gm, "");
9169
9321
  const list2 = JSON.parse(text).plugin;
9170
9322
  if (Array.isArray(list2)) plugins.push(...list2.filter((p) => typeof p === "string"));
9171
9323
  } catch {
@@ -9173,7 +9325,7 @@ function openCodePlugins() {
9173
9325
  }
9174
9326
  for (const folder of ["plugin", "plugins"]) {
9175
9327
  try {
9176
- for (const f of fs42.readdirSync(path37.join(dir, folder))) plugins.push(path37.join(dir, folder, f));
9328
+ for (const f of fs43.readdirSync(path38.join(dir, folder))) plugins.push(path38.join(dir, folder, f));
9177
9329
  } catch {
9178
9330
  }
9179
9331
  }
@@ -9300,7 +9452,7 @@ var COMMANDS = {
9300
9452
  if (port !== void 0 && !(/^\d+$/.test(port) && Number(port) <= 65535)) throw new UserError("--port takes a number from 0 to 65535 (0 picks a free one)");
9301
9453
  const dir = flag(p, "dir");
9302
9454
  if (dir !== void 0) {
9303
- if (!fs43.statSync(dir, { throwIfNoEntry: false })?.isDirectory()) throw new UserError(`-d ${dir}: no such directory`);
9455
+ if (!fs44.statSync(dir, { throwIfNoEntry: false })?.isDirectory()) throw new UserError(`-d ${dir}: no such directory`);
9304
9456
  process.chdir(dir);
9305
9457
  process.env.PWD = process.cwd();
9306
9458
  }