@jphutchins/code-review 0.1.0-alpha.35 → 0.1.0-alpha.36

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/index.js CHANGED
@@ -773,8 +773,7 @@ var DEFAULT_RESERVE = {
773
773
  frac: 0.15,
774
774
  growth: 0.25,
775
775
  flatUsd: 0.02,
776
- flatMs: 12e4,
777
- flatMem: 2 * 1024 * 1024 * 1024
776
+ flatMs: 12e4
778
777
  };
779
778
  var SOFT_MULTIPLE = 2;
780
779
  var growingReserve = (used, limit, flat, r) => {
@@ -801,7 +800,6 @@ var decideBudget = (i) => {
801
800
  const worst = [costAxis(i), timeAxis(i)].filter((a) => a !== null).reduce((max, a) => Math.max(max, axisSeverity(a)), 0);
802
801
  return worst === 2 ? { kind: "hard" } : worst === 1 ? { kind: "soft" } : { kind: "ok" };
803
802
  };
804
- var memoryCritical = (availMemBytes, totalMemBytes, floorBytes) => availMemBytes !== null && totalMemBytes !== null && totalMemBytes > 0 && availMemBytes <= floorBytes;
805
803
  var pct = (n) => `${String(Math.round(n * 100))}%`;
806
804
  var money = (n) => `$${n.toFixed(2)}`;
807
805
  var mins = (ms) => `${(ms / 6e4).toFixed(1)}m`;
@@ -856,7 +854,6 @@ var lastValidPath = (draftPath) => {
856
854
  };
857
855
  var mainHasWrittenDraft = (draftMtimeMs, seedMarkerMtimeMs) => draftMtimeMs !== null && (seedMarkerMtimeMs === null || draftMtimeMs > seedMarkerMtimeMs);
858
856
  var spawnFloorMessage = (draftPath) => `Write your own first-pass findings to ${draftPath} before spawning subagents \u2014 a review must never depend on subagents alone, and a pre-seeded draft does not count until you have revised it yourself this run. Write ${draftPath} from what you have read so far (preliminary findings are fine), run \`code-review validate ${draftPath} --explain\` until it passes, then fan out; your subagents run in the background, so keep refining the draft as their reports arrive.`;
859
- var memoryPressureMessage = (draftPath) => `System memory is critically low right now, so another subagent can't be spawned \u2014 a fresh process is the fastest way to tip the runner into an out-of-memory kill that would lose the whole review. Don't wind down: keep reading code directly and keep folding the reports from subagents already running into ${draftPath}, then try spawning again in a moment \u2014 this clears as soon as running subagents finish and free their memory.`;
860
857
  var forceBackgroundSpawn = (toolInput) => ({
861
858
  hookSpecificOutput: {
862
859
  hookEventName: "PreToolUse",
@@ -902,8 +899,6 @@ var evaluateBudgetHook = (input, params) => {
902
899
  if (phase.kind === "hard" && blockedDuringConvergence(toolName, rec["tool_input"]))
903
900
  return denyPreTool(budgetMessage(inputs, phase, params.draftPath, isSubagent));
904
901
  if (SPAWN_TOOLS.has(toolName)) {
905
- if (memoryCritical(params.availMemBytes, params.totalMemBytes, params.reserve.flatMem))
906
- return denyPreTool(memoryPressureMessage(params.draftPath));
907
902
  if (!isSubagent && !params.mainDraftWritten)
908
903
  return denyPreTool(spawnFloorMessage(params.draftPath));
909
904
  return forceBackgroundSpawn(rec["tool_input"]);
@@ -950,21 +945,6 @@ var parseFraction = (raw, fallback) => {
950
945
  const n = Number.parseFloat(raw);
951
946
  return Number.isFinite(n) && n >= 0 && n <= 1 ? n : fallback;
952
947
  };
953
- var BYTE_UNIT = {
954
- "": 1,
955
- k: 1024,
956
- m: 1024 * 1024,
957
- g: 1024 * 1024 * 1024,
958
- t: 1024 * 1024 * 1024 * 1024
959
- };
960
- var parseByteSize = (raw) => {
961
- const m = /^(\d+(?:\.\d+)?)\s*([kmgt])?(?:i?b)?$/i.exec(raw.trim());
962
- if (m === null) return null;
963
- const [, num = "", unit = ""] = m;
964
- const n = Number.parseFloat(num);
965
- const mult = BYTE_UNIT[unit.toLowerCase()];
966
- return Number.isFinite(n) && mult !== void 0 ? n * mult : null;
967
- };
968
948
  var budgetHookCommand = (draftPath, opts) => [
969
949
  "code-review budget-hook --draft",
970
950
  shellQuote(draftPath),
@@ -974,8 +954,7 @@ var budgetHookCommand = (draftPath, opts) => [
974
954
  ...opts.reserveFrac ? ["--reserve-frac", shellQuote(opts.reserveFrac)] : [],
975
955
  ...opts.reserveGrowth ? ["--reserve-growth", shellQuote(opts.reserveGrowth)] : [],
976
956
  ...opts.reserveUsd ? ["--reserve-usd", shellQuote(opts.reserveUsd)] : [],
977
- ...opts.reserveWall ? ["--reserve-wall", shellQuote(opts.reserveWall)] : [],
978
- ...opts.reserveMem ? ["--reserve-mem", shellQuote(opts.reserveMem)] : []
957
+ ...opts.reserveWall ? ["--reserve-wall", shellQuote(opts.reserveWall)] : []
979
958
  ].join(" ");
980
959
 
981
960
  // src/format.ts
@@ -2736,6 +2715,32 @@ var composeReviewSettings = (opts) => {
2736
2715
  };
2737
2716
  };
2738
2717
 
2718
+ // src/sandbox.ts
2719
+ var GITHUB_HOSTS = ["api.github.com", "github.com"];
2720
+ var deriveModelHost = (apiBaseUrl) => {
2721
+ const host = URL.canParse(apiBaseUrl) ? new URL(apiBaseUrl).hostname : "";
2722
+ if (host === "") {
2723
+ throw new Error(
2724
+ `could not derive the model host from api_base_url ${JSON.stringify(apiBaseUrl)} \u2014 expected a URL like https://api.deepseek.com/anthropic`
2725
+ );
2726
+ }
2727
+ return host;
2728
+ };
2729
+ var parseExtraEndpoints = (extra) => extra.split(/\s+/).map((token) => token.replace(/:\d+$/, "")).filter((token) => token.length > 0);
2730
+ var buildSandboxConfig = (opts) => ({
2731
+ network: {
2732
+ allowedDomains: [
2733
+ .../* @__PURE__ */ new Set([
2734
+ deriveModelHost(opts.apiBaseUrl),
2735
+ ...GITHUB_HOSTS,
2736
+ ...parseExtraEndpoints(opts.extra ?? "")
2737
+ ])
2738
+ ],
2739
+ deniedDomains: []
2740
+ },
2741
+ filesystem: { allowRead: [], denyRead: [], allowWrite: ["/"], denyWrite: [] }
2742
+ });
2743
+
2739
2744
  // src/index.ts
2740
2745
  var readJSON = (path) => {
2741
2746
  try {
@@ -3032,18 +3037,6 @@ var snapshotIfValid = (draftPath) => {
3032
3037
  );
3033
3038
  }
3034
3039
  };
3035
- var readMemInfo = () => {
3036
- try {
3037
- const text = readFileSync("/proc/meminfo", "utf8");
3038
- const kb = (key2) => {
3039
- const m = new RegExp(`^${key2}:\\s+(\\d+)\\s+kB`, "m").exec(text);
3040
- return m?.[1] !== void 0 ? Number(m[1]) * 1024 : null;
3041
- };
3042
- return { availBytes: kb("MemAvailable"), totalBytes: kb("MemTotal") };
3043
- } catch {
3044
- return { availBytes: null, totalBytes: null };
3045
- }
3046
- };
3047
3040
  var budgetHookCmd = defineCommand({
3048
3041
  meta: {
3049
3042
  name: "budget-hook",
@@ -3082,17 +3075,12 @@ var budgetHookCmd = defineCommand({
3082
3075
  "reserve-wall": {
3083
3076
  type: "string",
3084
3077
  description: "Flat wall-clock wind-down floor (e.g. 2m, 120s), whichever is larger with --reserve-frac (default: 2m)"
3085
- },
3086
- "reserve-mem": {
3087
- type: "string",
3088
- description: "Free-RAM floor (e.g. 2g, 1536m) below which new subagent spawns are denied until memory recovers; not a convergence axis (default: 2g)"
3089
3078
  }
3090
3079
  },
3091
3080
  run: async ({ args }) => {
3092
3081
  try {
3093
3082
  const draftPath = resolve$1(args.draft);
3094
3083
  const input = readStdinJSON();
3095
- const mem = readMemInfo();
3096
3084
  const transcriptPath = transcriptPathOf(input);
3097
3085
  const tree = transcriptPath ? readTranscriptTree(resolve$1(transcriptPath)) : void 0;
3098
3086
  const usage = tree ? sumTranscriptUsage(tree.entries) : void 0;
@@ -3109,14 +3097,11 @@ var budgetHookCmd = defineCommand({
3109
3097
  nowMs: Date.now()
3110
3098
  }),
3111
3099
  wallMs,
3112
- availMemBytes: mem.availBytes,
3113
- totalMemBytes: mem.totalBytes,
3114
3100
  reserve: {
3115
3101
  frac: parseFraction(args["reserve-frac"], DEFAULT_RESERVE.frac),
3116
3102
  growth: parseFraction(args["reserve-growth"], DEFAULT_RESERVE.growth),
3117
3103
  flatUsd: parseBudgetUsd(args["reserve-usd"]) ?? DEFAULT_RESERVE.flatUsd,
3118
- flatMs: args["reserve-wall"] ? parseWallMs(args["reserve-wall"]) ?? DEFAULT_RESERVE.flatMs : DEFAULT_RESERVE.flatMs,
3119
- flatMem: args["reserve-mem"] ? parseByteSize(args["reserve-mem"]) ?? DEFAULT_RESERVE.flatMem : DEFAULT_RESERVE.flatMem
3104
+ flatMs: args["reserve-wall"] ? parseWallMs(args["reserve-wall"]) ?? DEFAULT_RESERVE.flatMs : DEFAULT_RESERVE.flatMs
3120
3105
  },
3121
3106
  draftPath,
3122
3107
  mainDraftWritten: mainHasWrittenDraft(
@@ -3193,10 +3178,6 @@ var printSettingsCmd = defineCommand({
3193
3178
  "reserve-wall": {
3194
3179
  type: "string",
3195
3180
  description: "Flat wall-clock wind-down floor (e.g. 2m), whichever is larger with --reserve-frac (default: 2m)"
3196
- },
3197
- "reserve-mem": {
3198
- type: "string",
3199
- description: "Free-RAM floor (e.g. 2g) below which new subagent spawns are denied until memory recovers (default: 2g)"
3200
3181
  }
3201
3182
  },
3202
3183
  run: async ({ args }) => {
@@ -3218,8 +3199,7 @@ var printSettingsCmd = defineCommand({
3218
3199
  reserveFrac: args["reserve-frac"],
3219
3200
  reserveGrowth: args["reserve-growth"],
3220
3201
  reserveUsd: args["reserve-usd"],
3221
- reserveWall: args["reserve-wall"],
3222
- reserveMem: args["reserve-mem"]
3202
+ reserveWall: args["reserve-wall"]
3223
3203
  }
3224
3204
  });
3225
3205
  process.stdout.write(`${JSON.stringify(settings)}
@@ -4189,6 +4169,37 @@ var awaitCiCmd = defineCommand({
4189
4169
  process.stdout.write(renderCiOutputs(outcome));
4190
4170
  }
4191
4171
  });
4172
+ var sandboxConfigCmd = defineCommand({
4173
+ meta: {
4174
+ name: "sandbox-config",
4175
+ description: "Emit the sandbox-runtime (srt) settings that jail the untrusted review agent's egress: allow the model host (derived from api_base_url), the GitHub API/host, and the consumer's extra_endpoints; deny all else; filesystem isolation off"
4176
+ },
4177
+ args: {
4178
+ "api-base-url": {
4179
+ type: "string",
4180
+ description: "The model endpoint the CLI dials (ANTHROPIC_BASE_URL) \u2014 its host is allowlisted",
4181
+ required: true
4182
+ },
4183
+ extra: {
4184
+ type: "string",
4185
+ description: "Whitespace-separated host[:port] list of extra domains to allow (the extra_endpoints input)"
4186
+ },
4187
+ out: {
4188
+ type: "string",
4189
+ description: "Write the settings JSON here instead of stdout"
4190
+ }
4191
+ },
4192
+ run: ({ args }) => {
4193
+ const config = buildSandboxConfig({ apiBaseUrl: args["api-base-url"], extra: args.extra });
4194
+ const json = `${JSON.stringify(config, null, 2)}
4195
+ `;
4196
+ if (args.out) {
4197
+ writeFileSync(resolve$1(args.out), json);
4198
+ } else {
4199
+ process.stdout.write(json);
4200
+ }
4201
+ }
4202
+ });
4192
4203
  var main = defineCommand({
4193
4204
  meta: {
4194
4205
  name: "code-review",
@@ -4218,7 +4229,8 @@ var main = defineCommand({
4218
4229
  "stop-gate": stopGateCmd,
4219
4230
  "budget-hook": budgetHookCmd,
4220
4231
  "print-settings": printSettingsCmd,
4221
- deadline: deadlineCmd
4232
+ deadline: deadlineCmd,
4233
+ "sandbox-config": sandboxConfigCmd
4222
4234
  }
4223
4235
  });
4224
4236
  if (!process.env["VITEST"]) {