@review-agent/core 0.1.1 → 0.1.3

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.d.ts CHANGED
@@ -466,7 +466,7 @@ declare const RECORD_VERSION: 1;
466
466
  /** The version of this engine, on every record it writes. Kept in step with
467
467
  * `packages/core/package.json` by `record.test.ts` — a record whose engine version is a guess is
468
468
  * worth less than no record at all (design §4.7). */
469
- declare const ENGINE_VERSION = "0.1.1";
469
+ declare const ENGINE_VERSION = "0.1.3";
470
470
  type RecordedSkill = {
471
471
  id: string;
472
472
  path: string;
package/dist/index.js CHANGED
@@ -16,8 +16,61 @@ import { z as z3 } from "zod";
16
16
 
17
17
  // src/cli-process.ts
18
18
  import { spawn } from "child_process";
19
+ import { accessSync, constants, realpathSync, statSync } from "fs";
20
+ import { posix, win32 } from "path";
19
21
  import { z } from "zod";
20
- var claudeBinary = () => process.env.REVIEW_AGENT_CLAUDE_BIN ?? "claude";
22
+ var resolveClaudeBinary = (a) => {
23
+ const platform = a.platform ?? process.platform;
24
+ const path = platform === "win32" ? win32 : posix;
25
+ const isExecutableFile = a.isExecutableFile ?? executableFileTest(platform);
26
+ const real = a.realpath ?? realpathOrSelf;
27
+ const avoid = real(path.resolve(a.avoidDir));
28
+ const inside = (candidate) => {
29
+ const rel = path.relative(avoid, real(candidate));
30
+ return rel === "" || !(rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel));
31
+ };
32
+ const named = a.env.REVIEW_AGENT_CLAUDE_BIN;
33
+ if (named !== void 0 && named !== "") {
34
+ if (!path.isAbsolute(named)) {
35
+ throw new Error("REVIEW_AGENT_CLAUDE_BIN must be an absolute path to the Claude Code binary; a relative one could resolve inside the code under review");
36
+ }
37
+ if (inside(named)) {
38
+ throw new Error("REVIEW_AGENT_CLAUDE_BIN points inside the checkout of the code under review; refusing to run it");
39
+ }
40
+ return named;
41
+ }
42
+ const names = platform === "win32" ? ["claude.exe"] : ["claude"];
43
+ for (const dir of pathEntries(a.env, platform)) {
44
+ if (dir === "" || !path.isAbsolute(dir)) continue;
45
+ for (const name of names) {
46
+ const candidate = path.join(dir, name);
47
+ if (!isExecutableFile(candidate) || inside(candidate)) continue;
48
+ return candidate;
49
+ }
50
+ }
51
+ throw new Error(platform === "win32" ? "Claude Code was not found: no claude.exe in an absolute PATH directory. Install it with Anthropic's installer (irm https://claude.ai/install.ps1 | iex) or WinGet, or set REVIEW_AGENT_CLAUDE_BIN to the absolute path of claude.exe" : "Claude Code was not found: no executable `claude` in an absolute PATH directory. Install Claude Code, or set REVIEW_AGENT_CLAUDE_BIN to its absolute path");
52
+ };
53
+ var pathEntries = (env, platform) => {
54
+ const key = platform === "win32" ? Object.keys(env).find((k) => k.toUpperCase() === "PATH") : "PATH";
55
+ const value = key === void 0 ? void 0 : env[key];
56
+ return (value ?? "").split(platform === "win32" ? ";" : ":");
57
+ };
58
+ var executableFileTest = (platform) => (p) => {
59
+ try {
60
+ if (!statSync(p).isFile()) return false;
61
+ if (platform !== "win32") accessSync(p, constants.X_OK);
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ };
67
+ var realpathOrSelf = (p) => {
68
+ try {
69
+ return realpathSync(p);
70
+ } catch {
71
+ return p;
72
+ }
73
+ };
21
74
  var CHILD_ENV_ALLOWLIST = [
22
75
  "PATH",
23
76
  "HOME",
@@ -76,11 +129,21 @@ var spawnClaude = (a) => new Promise((resolve3, reject) => {
76
129
  reject(new Error(refusal));
77
130
  return;
78
131
  }
79
- const child = spawn(claudeBinary(), a.argv, {
132
+ let bin;
133
+ try {
134
+ bin = resolveClaudeBinary({ env: process.env, avoidDir: a.cwd });
135
+ } catch (e) {
136
+ reject(e instanceof Error ? e : new Error(String(e)));
137
+ return;
138
+ }
139
+ const viaNode = /\.(c|m)?js$/i.test(bin);
140
+ const child = spawn(viaNode ? process.execPath : bin, viaNode ? [bin, ...a.argv] : a.argv, {
80
141
  cwd: a.cwd,
81
142
  env: childEnv(a.extraEnv),
82
143
  signal: a.signal,
83
- stdio: ["pipe", "pipe", "pipe"]
144
+ stdio: ["pipe", "pipe", "pipe"],
145
+ // No console window flashing up when the reviewer runs from a GUI app or a service on Windows.
146
+ windowsHide: true
84
147
  });
85
148
  let out = "";
86
149
  let err = "";
@@ -311,7 +374,7 @@ var runEngine = async (i) => {
311
374
  // review in progress, and a login failure reporting it would send someone looking at budgets.
312
375
  cutShort: terminalReason !== "completed" && failure?.kind !== "cli-error",
313
376
  // Always false, and it is not an oversight: nothing can fire. The spike of 2026-09-13
314
- // (docs/spikes/wrap-up.md) proved the delivery half works — a PostToolUse hook passed through
377
+ // (a spike during development) proved the delivery half works — a PostToolUse hook passed through
315
378
  // --settings survives --restricted and demonstrably puts text in front of the model — and then
316
379
  // found there is no spend to trigger it on. The hook event carries no cost, no usage and no
317
380
  // token count; --no-session-persistence means the transcript it points at is never written; and
@@ -857,7 +920,7 @@ var runRecheckPass = async (a) => {
857
920
 
858
921
  // src/record.ts
859
922
  var RECORD_VERSION = 1;
860
- var ENGINE_VERSION = "0.1.1";
923
+ var ENGINE_VERSION = "0.1.3";
861
924
  var buildReviewRecord = (a) => {
862
925
  const { request, settings, result, startedAt, finishedAt } = a;
863
926
  return {
@@ -912,10 +975,10 @@ import {
912
975
  existsSync,
913
976
  mkdirSync,
914
977
  readdirSync,
915
- realpathSync,
978
+ realpathSync as realpathSync2,
916
979
  renameSync,
917
980
  rmSync,
918
- statSync
981
+ statSync as statSync2
919
982
  } from "fs";
920
983
  import { isAbsolute, join, relative as relative2, resolve as resolve2 } from "path";
921
984
  import { createHash as createHash2, randomUUID } from "crypto";
@@ -933,8 +996,8 @@ var SkillError = class extends Error {
933
996
  var materialiseDir = (source, id) => {
934
997
  if (!isAbsolute(source.path)) throw new SkillError(id, "path must be absolute");
935
998
  if (!existsSync(source.path)) throw new SkillError(id, "no such directory");
936
- const real = realpathSync(source.path);
937
- if (!statSync(real).isDirectory()) throw new SkillError(id, "not a directory");
999
+ const real = realpathSync2(source.path);
1000
+ if (!statSync2(real).isDirectory()) throw new SkillError(id, "not a directory");
938
1001
  return real;
939
1002
  };
940
1003
  var GITHUB_REPO = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@review-agent/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "engines": {