crewforth 3.2.0-rc.1 → 3.2.0-rc.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/CHANGELOG.md +133 -3
- package/README.md +2 -0
- package/VERSION +1 -1
- package/adopt.sh +55 -6
- package/bin/cli.js +3 -2
- package/kit/CLAUDE.md +6 -6
- package/kit/agents/crew-review-agent.md +2 -0
- package/kit/eval/doctor.sh +37 -3
- package/kit/eval/gate-report.sh +2 -0
- package/kit/eval/lib/crew-map/narrative.mjs +42 -8
- package/kit/eval/lib/crew-map/project.mjs +3 -2
- package/kit/eval/lib/crew-map/rules/tech.json +35 -0
- package/kit/eval/lib/crew-map/scan.mjs +56 -4
- package/kit/eval/lib/crew-map/stacks.mjs +114 -8
- package/kit/eval/lib/crew-map/surface.mjs +62 -1
- package/kit/eval/lib/gate-log.sh +52 -0
- package/kit/eval/lib/json-string.awk +27 -0
- package/kit/eval/lib/preset.sh +109 -0
- package/kit/eval/lib/presets/default.json +7 -0
- package/kit/eval/lib/presets/solo.json +7 -0
- package/kit/eval/lib/presets/strict.json +11 -0
- package/kit/eval/lib/preview.sh +150 -0
- package/kit/eval/lib/review-bench/bench.mjs +273 -0
- package/kit/eval/lib/review-prepass/prepass.mjs +203 -0
- package/kit/eval/lib/review-prepass/prepass.sh +23 -0
- package/kit/eval/lib/settings-default.sh +36 -0
- package/kit/eval/lib/settings-json.awk +14 -4
- package/kit/eval/lib/statusline.sh +4 -19
- package/kit/eval/lib/verify-cmd.sh +17 -0
- package/kit/eval/lib/version.sh +41 -0
- package/kit/eval/review-bench.sh +26 -0
- package/kit/eval/smoke-test.sh +251 -21
- package/kit/hooks/agent-outcome.sh +20 -9
- package/kit/hooks/board-sync.sh +2 -23
- package/kit/hooks/guard-agent-model.sh +114 -15
- package/kit/hooks/guard-bash.sh +36 -21
- package/kit/hooks/guard-write.sh +3 -7
- package/kit/hooks/precompact-snapshot.sh +75 -0
- package/kit/hooks/session-rehydrate.sh +25 -2
- package/kit/hooks/session-update-check.sh +9 -8
- package/kit/settings.json +12 -0
- package/kit/skills/crew-code-review/SKILL.md +16 -0
- package/kit/skills/crew-map/SKILL.md +16 -5
- package/kit/skills/crew-review/SKILL.md +2 -1
- package/kit/studio/README.md +21 -10
- package/kit/studio/server/index.js +85 -22
- package/kit/studio/server/lib/code-stamp.js +35 -0
- package/kit/studio/server/lib/graph.js +9 -1
- package/kit/studio/server/lib/kit-telemetry.js +30 -5
- package/kit/studio/server/lib/lan.js +87 -0
- package/kit/studio/server/lib/owned-stream.js +56 -0
- package/kit/studio/server/lib/projects.js +11 -4
- package/kit/studio/server/lib/redact.js +60 -0
- package/kit/studio/server/lib/remote.js +104 -18
- package/kit/studio/server/lib/session.js +1 -1
- package/kit/studio/server/lib/terminal.js +51 -13
- package/kit/studio/server/lib/transcript.js +32 -0
- package/kit/studio/web/app.js +79 -11
- package/kit/studio/web/canvas.js +14 -2
- package/kit/studio/web/chat.js +114 -24
- package/kit/studio/web/graph-plan.js +3 -2
- package/kit/studio/web/liveness.js +14 -0
- package/kit/studio/web/map-plan.js +72 -3
- package/kit/studio/web/map.js +103 -10
- package/kit/studio/web/qr.js +200 -0
- package/kit/studio/web/style.css +40 -0
- package/kit/studio/web/tokens.css +6 -0
- package/package.json +1 -1
- package/start.sh +14 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prepass.sh — the shell entry to prepass.mjs, the review's deterministic first pass. It finds a Node that RUNS
|
|
3
|
+
# and hands over; the arguments and exit codes are prepass.mjs's (0, 1, 2).
|
|
4
|
+
#
|
|
5
|
+
# bash prepass.sh [--root DIR] [--base REF] [--stdout]
|
|
6
|
+
# rc 3 = no usable Node here, so nothing was checked. Not a pass: the review then reads the diff without it.
|
|
7
|
+
#
|
|
8
|
+
# The Node is found the way findings.sh finds it (one rule): asked for its version, never trusted by name.
|
|
9
|
+
set -u
|
|
10
|
+
d="${BASH_SOURCE%[/\\]*}"; [ "$d" = "${BASH_SOURCE}" ] && d=.
|
|
11
|
+
FLOOR=18
|
|
12
|
+
usable(){ local v; v="$("$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null)" || return 1
|
|
13
|
+
case "$v" in ''|*[!0-9]*) return 1 ;; esac; [ "$v" -ge "$FLOOR" ]; }
|
|
14
|
+
NODE=""
|
|
15
|
+
if usable node; then NODE=node
|
|
16
|
+
elif [ -f "$d/../../../studio/ensure-node.sh" ]; then
|
|
17
|
+
n="$(bash "$d/../../../studio/ensure-node.sh" 2>/dev/null)" && [ -n "$n" ] && usable "$n" && NODE="$n"
|
|
18
|
+
fi
|
|
19
|
+
if [ -z "$NODE" ]; then
|
|
20
|
+
echo "review-prepass: no Node $FLOOR+ that runs was found, so nothing was checked (this is not a pass)" >&2
|
|
21
|
+
exit 3
|
|
22
|
+
fi
|
|
23
|
+
exec "$NODE" "$d/prepass.mjs" "$@"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# settings-default.sh — put one of Crewforth's defaults into the project's .claude/settings.json, only where nothing
|
|
3
|
+
# sets it yet. The project's settings win over the user's, so writing a default over a user's own choice would
|
|
4
|
+
# silently replace it: a value set in the project's settings, its local settings or the user's own is kept.
|
|
5
|
+
#
|
|
6
|
+
# bash settings-default.sh --install <path> <defaults.json> [ENV_VAR] from the project root
|
|
7
|
+
# <path> the setting, as a dot path: statusLine, env.CLAUDE_CODE_SUBAGENT_MODEL
|
|
8
|
+
# <defaults.json> a JSON object holding just that setting (data beside the caller: one definition)
|
|
9
|
+
# ENV_VAR also kept when this variable is set in the environment (a value exported in the shell)
|
|
10
|
+
#
|
|
11
|
+
# Prints one word: installed · kept (it is set) · none (no settings.json) · failed (the file is left as it was).
|
|
12
|
+
# A managed settings layer is not read. The merge is Crewforth's own JSON reader.
|
|
13
|
+
set -u
|
|
14
|
+
_here="${BASH_SOURCE%[/\\]*}"; [ "$_here" = "${BASH_SOURCE}" ] && _here=.
|
|
15
|
+
|
|
16
|
+
[ "${1:-}" = --install ] && [ -n "${2:-}" ] && [ -n "${3:-}" ] || { echo "usage: settings-default.sh --install <path> <defaults.json> [ENV_VAR]" >&2; exit 2; }
|
|
17
|
+
KEY="$2"; K="$3"; VAR="${4:-}"
|
|
18
|
+
P=".claude/settings.json"; SJ="$_here/settings-json.awk"
|
|
19
|
+
[ -f "$P" ] && [ -f "$SJ" ] || { echo none; exit 0; }
|
|
20
|
+
case "$VAR" in '') ;; [A-Za-z_]*) case "$VAR" in *[!A-Za-z0-9_]*) echo failed; exit 0 ;; esac; [ -n "${!VAR:-}" ] && { echo kept; exit 0; } ;; *) echo failed; exit 0 ;; esac
|
|
21
|
+
for f in "$P" ".claude/settings.local.json" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"; do
|
|
22
|
+
[ -f "$f" ] && awk -v op=get -v path="$KEY" -f "$SJ" "$f" >/dev/null 2>&1 && { echo kept; exit 0; }
|
|
23
|
+
done
|
|
24
|
+
[ -f "$K" ] || { echo failed; exit 0; }
|
|
25
|
+
t="$P.default.$$"
|
|
26
|
+
r=failed
|
|
27
|
+
# The project's file goes FIRST: the merge refreshes the first file's .claude/hooks/ entries from the first file,
|
|
28
|
+
# so put second it would lose every Crewforth hook (measured: the installed project's own smoke failed 30 checks).
|
|
29
|
+
# The second file holds only the default, which the first lacks: that is the one change (an object such as env is
|
|
30
|
+
# merged member by member, the project's own members kept). Written through the file, so a symlink and its mode
|
|
31
|
+
# survive.
|
|
32
|
+
if awk -v op=merge -v retired= -f "$SJ" "$P" "$K" > "$t" 2>/dev/null && [ -s "$t" ] \
|
|
33
|
+
&& awk -v op=validate -f "$SJ" "$t" 2>/dev/null && awk -v op=get -v path="$KEY" -f "$SJ" "$t" >/dev/null 2>&1; then
|
|
34
|
+
cat "$t" > "$P" && r=installed
|
|
35
|
+
fi
|
|
36
|
+
rm -f "$t"; echo "$r"
|
|
@@ -7,8 +7,13 @@
|
|
|
7
7
|
# awk -v op=gets -v path='a.b|c.d' -f settings-json.awk FILE several values in one read, tab-separated, "" if absent
|
|
8
8
|
# awk -v op=strings -v path=permissions.deny -f settings-json.awk FILE each string element of that array, one per
|
|
9
9
|
# line, quotes off, escapes as written (rc 1 = not an array)
|
|
10
|
+
# awk -v op=keys [-v path=P] -f settings-json.awk FILE the member names of the object there, one per line, quotes
|
|
11
|
+
# off, in file order (rc 1 = not an object)
|
|
10
12
|
# path = dot-separated keys, a number indexes an array (0-based), empty = the root. rc 1 = no such path
|
|
11
13
|
# (or, for len, a scalar there), 10 = FILE is not a JSON object.
|
|
14
|
+
# awk -v op=get -v path=P -v setk=M -v setv=RAWJSON -f settings-json.awk FILE the object at P with member M set
|
|
15
|
+
# to RAWJSON (added when absent): a payload rewritten in one field, every other field and escape kept
|
|
16
|
+
# (rc 1 = nothing at P, or not an object)
|
|
12
17
|
# awk -v op=overlay -v key=K [-v setk=M -v setv=RAWJSON] -f settings-json.awk FILE1 FILE2
|
|
13
18
|
# FILE1 with FILE1.K shallow-merged from FILE2.K (FILE2 wins, FILE1's other members and order kept), then
|
|
14
19
|
# member M set to RAWJSON. rc 10 = FILE1 (or its K) is not an object, 11 = FILE2 has no object at K.
|
|
@@ -70,6 +75,10 @@ function at(id,path, n,sg,i){ n=split(path,sg,".")
|
|
|
70
75
|
return id }
|
|
71
76
|
function mk(t){ NN++; T[NN]=t; N[NN]=0; return NN }
|
|
72
77
|
function add(o,k,c){ N[o]++; K[o,N[o]]=k; C[o,N[o]]=c }
|
|
78
|
+
# Member k of object o set to the raw JSON v, added at the end when o has none (overlay and get share it).
|
|
79
|
+
function setm(o,k,v, sn,i,x){ sn=mk("s"); V[sn]=v; x=0
|
|
80
|
+
for(i=1;i<=N[o];i++) if(K[o,i]=="\"" k "\""){ C[o,i]=sn; x=1 }
|
|
81
|
+
if(!x) add(o,"\"" k "\"",sn) }
|
|
73
82
|
function canon(id, i,j,n,s,t,ord){
|
|
74
83
|
if(T[id]=="s") return V[id]
|
|
75
84
|
if(T[id]=="a"){ s="["; for(i=1;i<=N[id];i++) s=s (i>1 ? "," : "") canon(C[id,i]); return s "]" }
|
|
@@ -130,17 +139,18 @@ END{
|
|
|
130
139
|
if(op=="gets"){ if(!(r=doc(1))) exit 10; n=split(path,pp,"|"); o=""
|
|
131
140
|
for(i=1;i<=n;i++){ x=at(r,pp[i]); o=o (i>1?"\t":"") (x ? put(x,"") : "") } print o; exit 0 }
|
|
132
141
|
if(op=="get" || op=="len"){ if(!(r=doc(1))) exit 10; if(!(r=at(r,path))) exit 1
|
|
133
|
-
if(op=="get"){
|
|
142
|
+
if(op=="get"){ if(setk!=""){ if(T[r]!="o") exit 1; setm(r,setk,setv) } print put(r,""); exit 0 }
|
|
143
|
+
if(T[r]=="s") exit 1; print N[r]; exit 0 }
|
|
134
144
|
if(op=="strings"){ if(!(r=doc(1))) exit 10; if(!(r=at(r,path))) exit 1; if(T[r]!="a") exit 1
|
|
135
145
|
for(i=1;i<=N[r];i++){ c=C[r,i]; if(T[c]=="s" && substr(V[c],1,1)=="\"") print substr(V[c],2,length(V[c])-2) }
|
|
136
146
|
exit 0 }
|
|
147
|
+
if(op=="keys"){ if(!(r=doc(1))) exit 10; if(!(r=at(r,path))) exit 1; if(T[r]!="o") exit 1
|
|
148
|
+
for(i=1;i<=N[r];i++){ k=K[r,i]; print substr(k,2,length(k)-2) } exit 0 }
|
|
137
149
|
if(op=="overlay"){ if(!(t=doc(1))) exit 10; if(!(q=doc(2))) exit 11; kk="\"" key "\""
|
|
138
150
|
qv=get(q,kk); if(T[qv]!="o") exit 11; tv=get(t,kk); if(tv && T[tv]!="o") exit 10
|
|
139
151
|
nv=mk("o"); for(i=1;i<=N[tv];i++){ x=get(qv,K[tv,i]); add(nv,K[tv,i], x ? x : C[tv,i]) }
|
|
140
152
|
for(i=1;i<=N[qv];i++) if(!get(nv,K[qv,i])) add(nv,K[qv,i],C[qv,i])
|
|
141
|
-
if(setk!="")
|
|
142
|
-
for(i=1;i<=N[nv];i++) if(K[nv,i]=="\"" setk "\""){ C[nv,i]=sn; x=1 }
|
|
143
|
-
if(!x) add(nv,"\"" setk "\"",sn) }
|
|
153
|
+
if(setk!="") setm(nv,setk,setv)
|
|
144
154
|
x=0; for(i=1;i<=N[t];i++) if(K[t,i]==kk){ C[t,i]=nv; x=1 }
|
|
145
155
|
if(!x) add(t,kk,nv)
|
|
146
156
|
print put(t,""); exit 0 }
|
|
@@ -25,24 +25,9 @@ _here="${BASH_SOURCE%[/\\]*}"; [ "$_here" = "${BASH_SOURCE}" ] && _here=.
|
|
|
25
25
|
# the project's setting would win over it. Prints one word: installed · kept (one is set) · none (no settings.json)
|
|
26
26
|
# · failed (the file is left as it was). A managed settings layer is not read. The merge is Crewforth's own JSON reader.
|
|
27
27
|
if [ "${1:-}" = --install ]; then
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
[ -f "$f" ] && awk -v op=get -v path=statusLine -f "$SJ" "$f" >/dev/null 2>&1 && { echo kept; exit 0; }
|
|
32
|
-
done
|
|
33
|
-
# The setting itself is data beside this file (statusline.settings.json): one definition, read here and nowhere else.
|
|
34
|
-
K="$_here/statusline.settings.json"; [ -f "$K" ] || { echo failed; exit 0; }
|
|
35
|
-
t="$P.statusline.$$"
|
|
36
|
-
r=failed
|
|
37
|
-
# The project's file goes FIRST: the merge refreshes the first file's .claude/hooks/ entries from the first file,
|
|
38
|
-
# so put second it would lose every Crewforth hook (measured: the installed project's own smoke failed 30
|
|
39
|
-
# checks). The second file has one member, statusLine, which the first lacks: that is the one change. Written
|
|
40
|
-
# through the file, so a symlink and its mode survive.
|
|
41
|
-
if awk -v op=merge -v retired= -f "$SJ" "$P" "$K" > "$t" 2>/dev/null && [ -s "$t" ] \
|
|
42
|
-
&& awk -v op=validate -f "$SJ" "$t" 2>/dev/null && awk -v op=get -v path=statusLine -f "$SJ" "$t" >/dev/null 2>&1; then
|
|
43
|
-
cat "$t" > "$P" && r=installed
|
|
44
|
-
fi
|
|
45
|
-
rm -f "$t"; echo "$r"; exit 0
|
|
28
|
+
# The setting itself is data beside this file (statusline.settings.json); the rule for putting a default in is
|
|
29
|
+
# one helper, shared with the subagent model's default.
|
|
30
|
+
exec bash "$_here/settings-default.sh" --install statusLine "$_here/statusline.settings.json"
|
|
46
31
|
fi
|
|
47
32
|
|
|
48
33
|
IFS= read -r -d '' IN || true
|
|
@@ -67,7 +52,7 @@ if [ -n "$SID" ] && [ -d .claude ]; then
|
|
|
67
52
|
fi
|
|
68
53
|
|
|
69
54
|
# 🛡 The gate's last word in this session.
|
|
70
|
-
GL="${
|
|
55
|
+
GL=""; [ -f "$_here/gate-log.sh" ] && { . "$_here/gate-log.sh"; crew_gate_log_read; GL="$_CREW_GL"; } # one rule (gate-log.sh)
|
|
71
56
|
if [ -n "$BASE" ] && [ -f "$GL" ] && [ "$GL" -nt "$BASE" ]; then
|
|
72
57
|
last="$(tail -n 1 "$GL" 2>/dev/null)"
|
|
73
58
|
IFS=$'\t' read -r verdict section _ <<< "$last"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Sourced, not run. How a card's verify command is read: one definition, for the gate that runs it
|
|
2
|
+
# (hooks/guard-agent-model.sh) and for doctor, which offers the line that would allow it.
|
|
3
|
+
#
|
|
4
|
+
# A verify command in a sub-folder: `cd apps/mobile && flutter test`, the usual shape in a repository of several
|
|
5
|
+
# projects (measured on one: 41 of 71 refused verify commands had it). Split into the folder and the one command,
|
|
6
|
+
# so the command is judged alone and run in that folder with no chain; a `verify <command>` rule names the command
|
|
7
|
+
# alone. Only a relative folder inside the project, with no .. step, that exists; anything else is left whole, and
|
|
8
|
+
# is refused as a chain.
|
|
9
|
+
crew_verify_split(){ # $1 = the command, $2 = the project root -> _AMVD the folder ("" = the root), _AMVC the command
|
|
10
|
+
local c="$1" d r root="${2:-.}"
|
|
11
|
+
_AMVD=""; _AMVC="$c"
|
|
12
|
+
case "$c" in 'cd '*' && '*) ;; *) return 0 ;; esac
|
|
13
|
+
d="${c#cd }"; d="${d%% && *}"; r="${c#cd "$d" && }"
|
|
14
|
+
case "$d" in ''|/*|~*|*[!A-Za-z0-9._/-]*|..|../*|*/..|*/../*) return 0 ;; esac
|
|
15
|
+
[ -n "$r" ] && [ -d "$root/$d" ] || return 0
|
|
16
|
+
_AMVD="$d"; _AMVC="$r"
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Sourced, not run. The ONE definition of how Crewforth reads and orders its own version numbers.
|
|
2
|
+
#
|
|
3
|
+
# An install's VERSION holds X.Y.Z, or X.Y.Z-rc.N when it came from a release candidate. An rc comes before its
|
|
4
|
+
# final (3.2.0-rc.1 < 3.2.0), so someone who tried the rc is offered the final when it ships. Read by the update
|
|
5
|
+
# check (hooks/session-update-check.sh), doctor and adopt.sh; each used to split on "." and read 3.2.0-rc.1 as 3.2.0.
|
|
6
|
+
#
|
|
7
|
+
# Builtins only: sourcing this costs no process, which matters in hooks on Git Bash.
|
|
8
|
+
|
|
9
|
+
# The version an install may hold in its own VERSION file: X.Y.Z or X.Y.Z-rc.N, nothing else; printed, or rc 1.
|
|
10
|
+
# A value off the network is not read with this: the update check holds that to X.Y.Z alone.
|
|
11
|
+
crew_local_version(){
|
|
12
|
+
local v="${1:-}" base n
|
|
13
|
+
v="${v//[!0-9A-Za-z.-]/}"
|
|
14
|
+
base="${v%%-rc.*}"
|
|
15
|
+
case "$base" in
|
|
16
|
+
*[!0-9.]*|'') return 1 ;;
|
|
17
|
+
[0-9]*.[0-9]*.[0-9]*.*) return 1 ;;
|
|
18
|
+
[0-9]*.[0-9]*.[0-9]*) ;;
|
|
19
|
+
*) return 1 ;;
|
|
20
|
+
esac
|
|
21
|
+
if [ "$base" != "$v" ]; then
|
|
22
|
+
n="${v#"$base"-rc.}"
|
|
23
|
+
case "$n" in ''|*[!0-9]*) return 1 ;; esac
|
|
24
|
+
fi
|
|
25
|
+
printf '%s' "$v"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# vcmp(a, b): 1 when a is newer, -1 when older, 0 when the same. Prepended to an awk program:
|
|
29
|
+
# awk -v a="$X" -v b="$Y" "$CREW_VCMP_AWK"' BEGIN { exit !(vcmp(a, b) > 0) }'
|
|
30
|
+
CREW_VCMP_AWK='
|
|
31
|
+
function vrc(v) { return match(v, /-rc\.[0-9]+$/) ? substr(v, RSTART + 4) + 0 : -1 }
|
|
32
|
+
function vcmp(a, b, x, y, i, ra, rb) {
|
|
33
|
+
ra = vrc(a); rb = vrc(b); sub(/-.*/, "", a); sub(/-.*/, "", b)
|
|
34
|
+
split(a, x, "."); split(b, y, ".")
|
|
35
|
+
for (i = 1; i <= 3; i++) { if (x[i] + 0 > y[i] + 0) return 1; if (x[i] + 0 < y[i] + 0) return -1 }
|
|
36
|
+
if (ra == rb) return 0
|
|
37
|
+
if (ra < 0) return 1
|
|
38
|
+
if (rb < 0) return -1
|
|
39
|
+
return ra > rb ? 1 : -1
|
|
40
|
+
}
|
|
41
|
+
'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# review-bench.sh — how right the review is, on labelled cases: precision, recall and every false alarm by name.
|
|
3
|
+
# The shell entry to eval/lib/review-bench/bench.mjs; it finds a Node that RUNS and hands over.
|
|
4
|
+
#
|
|
5
|
+
# bash review-bench.sh --cases DIR [--arm prepass|agent] [--split all|train|holdout] [--only id,id]
|
|
6
|
+
# [--model sonnet] [--advisor opus] [--case-budget 0.75] [--max-cost 12] [--floors FILE]
|
|
7
|
+
# [--out FILE] [--no-tools]
|
|
8
|
+
#
|
|
9
|
+
# --arm prepass (the default) runs no model and costs nothing. --arm agent runs Claude Code headless as
|
|
10
|
+
# crew-review-agent on every case and SPENDS TOKENS: each case is capped by --case-budget (US dollars) and the run
|
|
11
|
+
# by --max-cost; ask the person paying before running it. rc 3 = no usable Node here (not a pass).
|
|
12
|
+
set -u
|
|
13
|
+
d="${BASH_SOURCE%[/\\]*}"; [ "$d" = "${BASH_SOURCE}" ] && d=.
|
|
14
|
+
FLOOR=18
|
|
15
|
+
usable(){ local v; v="$("$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null)" || return 1
|
|
16
|
+
case "$v" in ''|*[!0-9]*) return 1 ;; esac; [ "$v" -ge "$FLOOR" ]; }
|
|
17
|
+
NODE=""
|
|
18
|
+
if usable node; then NODE=node
|
|
19
|
+
elif [ -f "$d/../studio/ensure-node.sh" ]; then
|
|
20
|
+
n="$(bash "$d/../studio/ensure-node.sh" 2>/dev/null)" && [ -n "$n" ] && usable "$n" && NODE="$n"
|
|
21
|
+
fi
|
|
22
|
+
if [ -z "$NODE" ]; then
|
|
23
|
+
echo "review-bench: no Node $FLOOR+ that runs was found, so nothing was measured (this is not a pass)" >&2
|
|
24
|
+
exit 3
|
|
25
|
+
fi
|
|
26
|
+
exec "$NODE" "$d/lib/review-bench/bench.mjs" "$@"
|