crewforth 3.2.0 → 3.2.1
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 +26 -0
- package/VERSION +1 -1
- package/kit/eval/doctor.sh +27 -0
- package/kit/eval/lib/node-advisory.tsv +9 -0
- package/kit/eval/smoke-test.sh +41 -1
- package/kit/hooks/guard-agent-model.sh +8 -0
- package/kit/owned-blobs.tsv +4 -2
- package/kit/studio/ensure-node.sh +1 -1
- package/kit/studio/server/index.js +3 -0
- package/kit/studio/server/lib/node-advisory.js +46 -0
- package/kit/studio/web/models-plan.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ Crewforth was named Claude Starter Kit until 3.0.0.
|
|
|
5
5
|
Notable changes to this project are recorded here. Format follows [Keep a Changelog](https://keepachangelog.com/en/),
|
|
6
6
|
versioning follows [SemVer](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [3.2.1] — 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Doctor and Studio say when Node is missing a published security fix.** Node's October 2026 security releases fix,
|
|
13
|
+
among others, a Brotli decoder write that a crafted HTTP response can reach: Studio's `fetch` decodes a `br`
|
|
14
|
+
response even though it does not ask for one (measured), so a Studio with `--peer` over plain HTTP on a network
|
|
15
|
+
someone else can answer on was exposed on an unpatched Node. `/crew-doctor` and Studio's start now read one table
|
|
16
|
+
of fixed releases (22.23.4, 24.22.1, 26.12.1) and warn below them, or on a line that gets no fixes (20.x); neither
|
|
17
|
+
refuses anything. `ensure-node.sh`'s fallback, used only when nodejs.org cannot be asked for the current LTS, moves
|
|
18
|
+
from 24.21.0 to 24.22.1.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Studio says what the model gate does to an agent that is not crew-\*.** Its inspector said the gate chose nothing
|
|
23
|
+
for such an agent and that it ran on the model its call asked for, while the same panel showed it had asked for opus
|
|
24
|
+
and run on sonnet. The gate does hold the expensive end there: a call that asks for opus or fable, or names no model
|
|
25
|
+
and would take the session's, runs on sonnet unless its task carries an `above: <why>` line. The inspector and the
|
|
26
|
+
Studio page now say that.
|
|
27
|
+
- **Critical work sent to an agent that is not crew-\* is no longer moved to a cheaper model.** Outside the crew the
|
|
28
|
+
model gate holds the expensive end: opus or fable, or a session model the call would inherit, runs on sonnet unless
|
|
29
|
+
the task carries `above: <why>`. It did that even when the task opened with a card naming critical work (`change:
|
|
30
|
+
fix-unknown`, `security`, `migration`, `architecture`, or a file on a critical path), which a crew agent would run on
|
|
31
|
+
opus at least, so going round the crew was the cheaper way. Such a call now keeps the model it asked for; nothing is
|
|
32
|
+
raised. An `above:` at the end of a one-line card is now read there too, and Studio's inspector and page say so.
|
|
33
|
+
|
|
8
34
|
## [3.2.0] — 2026-10-21
|
|
9
35
|
|
|
10
36
|
### Added
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.2.
|
|
1
|
+
3.2.1
|
package/kit/eval/doctor.sh
CHANGED
|
@@ -154,6 +154,9 @@ _mt() {
|
|
|
154
154
|
'CLAUDE.md does not import .claude/DISCIPLINE.md — the discipline is on disk but never loaded') s="CLAUDE.md .claude/DISCIPLINE.md'yi içe aktarmıyor — disiplin diskte duruyor ama hiç yüklenmiyor" ;;
|
|
155
155
|
'add this as its own line at the top of CLAUDE.md: @.claude/DISCIPLINE.md') s="CLAUDE.md'nin en üstüne şunu ayrı bir satır olarak ekleyin: @.claude/DISCIPLINE.md" ;;
|
|
156
156
|
' · panel needs Node 18+ — .claude/studio/ensure-node.sh --plan fetches one') s=' · panel Node 18+ ister — .claude/studio/ensure-node.sh --plan bir tane indirir' ;;
|
|
157
|
+
'Node %s is on a line that no longer gets security fixes (%s fixes %s) — update Node') s="Node %s artık güvenlik düzeltmesi almayan bir hatta (%s şunları düzeltiyor: %s) — Node'u güncelleyin" ;;
|
|
158
|
+
'Node %s is below %s, the release of its line with the fixes of %s — update Node') s="Node %s, %s altında; o hattın %s düzeltmelerini taşıyan sürümü bu — Node'u güncelleyin" ;;
|
|
159
|
+
'Node %s carries the security fixes on record') s='Node %s kayıttaki güvenlik düzeltmelerini taşıyor' ;;
|
|
157
160
|
'%s is set — its 3.0 name is CREW_%s (the old name works until 4.0)') s="%s ayarlı — 3.0'daki adı CREW_%s (eski ad 4.0'a kadar çalışır)" ;;
|
|
158
161
|
'%s is set but no longer read — set CREW_%s instead') s='%s ayarlı ama artık okunmuyor — yerine CREW_%s ayarlayın' ;;
|
|
159
162
|
'DOCTOR: healthy ✅%s') s='DOCTOR: sağlıklı ✅%s' ;;
|
|
@@ -661,6 +664,30 @@ for _v in $(compgen -e); do
|
|
|
661
664
|
esac ;;
|
|
662
665
|
esac
|
|
663
666
|
done
|
|
667
|
+
# A Node missing a published security fix (eval/lib/node-advisory.tsv, the table the Studio server reads too).
|
|
668
|
+
# Warned, never counted: the gates are bash, Node runs the panel and the map, and the user updates it.
|
|
669
|
+
_na="$(dirname "$0")/lib/node-advisory.tsv"; [ -f "$_na" ] || _na=".claude/eval/lib/node-advisory.tsv"
|
|
670
|
+
if [ -f "$_na" ] && _nv="$(node -p 'process.versions.node' 2>/dev/null)"; then
|
|
671
|
+
_nu=""; _nfix=""; _nall=""; _ntop=0; _nmaj="${_nv%%.*}"
|
|
672
|
+
while IFS=$'\t' read -r _k _f || [ -n "$_k" ]; do
|
|
673
|
+
_f="${_f%$'\r'}"
|
|
674
|
+
case "$_k" in advisory) _nu="$_f" ;; *[!0-9]*|'') ;;
|
|
675
|
+
*) _nall="${_nall:+$_nall, }$_f"; [ "$_k" -gt "$_ntop" ] && _ntop="$_k"; [ "$_k" = "$_nmaj" ] && _nfix="$_f" ;; esac
|
|
676
|
+
done < "$_na"
|
|
677
|
+
case "$_nv" in
|
|
678
|
+
[0-9]*.[0-9]*.[0-9]*)
|
|
679
|
+
if [ "$_ntop" -gt 0 ] && [ "$_nmaj" -le "$_ntop" ]; then
|
|
680
|
+
if [ -z "$_nfix" ]; then
|
|
681
|
+
warn "Node %s is on a line that no longer gets security fixes (%s fixes %s) — update Node" "$_nv" "$_nu" "$_nall"
|
|
682
|
+
else
|
|
683
|
+
IFS=. read -r _a1 _a2 _a3 <<< "$_nv"; IFS=. read -r _b1 _b2 _b3 <<< "$_nfix"; _a3="${_a3%%[!0-9]*}"
|
|
684
|
+
if [ "$_a2" -lt "$_b2" ] || { [ "$_a2" = "$_b2" ] && [ "${_a3:-0}" -lt "$_b3" ]; }; then
|
|
685
|
+
warn "Node %s is below %s, the release of its line with the fixes of %s — update Node" "$_nv" "$_nfix" "$_nu"
|
|
686
|
+
else ok "Node %s carries the security fixes on record" "$_nv"; fi
|
|
687
|
+
fi
|
|
688
|
+
fi ;;
|
|
689
|
+
esac
|
|
690
|
+
fi
|
|
664
691
|
if [ "$FAIL" -eq 0 ]; then _mt "DOCTOR: healthy ✅%s" "$PANEL_NOTE"; echo "$_M"
|
|
665
692
|
# Healthy verdict: the star line, once per kit version under doctor's own marker (the installers keep theirs).
|
|
666
693
|
# /crew-update runs this with CREW_NO_STAR=1, so an update shows it once, not twice. Text/URL/silence: lib/star.sh.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Node release lines and the first release of each that carries the fixes of Node's latest security advisory.
|
|
2
|
+
# Read by eval/doctor.sh and the Studio server (studio/server/lib/node-advisory.js); one table, two readers.
|
|
3
|
+
# A Node below its line's release, or on a line older than the newest listed that is not listed (it gets no fixes),
|
|
4
|
+
# is warned about, never refused: the gates are bash, Node runs the panel and the map, and the user updates it.
|
|
5
|
+
# A line newer than every listed one is not judged. Update this file when Node publishes a new advisory.
|
|
6
|
+
advisory https://nodejs.org/en/blog/vulnerability/october-2026-security-releases
|
|
7
|
+
22 22.23.4
|
|
8
|
+
24 24.22.1
|
|
9
|
+
26 26.12.1
|
package/kit/eval/smoke-test.sh
CHANGED
|
@@ -4760,6 +4760,28 @@ case "$_d9o" in *"needs the Anthropic API"*) ;; *) _d9b="$_d9b bedrock-not-seen"
|
|
|
4760
4760
|
rm -rf "$_d9"
|
|
4761
4761
|
[ -z "$_d9b" ] && pass "doctor 9c: both editions on is named, one is ok, no CLI is not measured; the advisor off, on, below the critical tier, env-disabled and on Bedrock each read right; none changes the verdict" \
|
|
4762
4762
|
|| fail "doctor 9c:$_d9b"
|
|
4763
|
+
# A Node missing a published security fix is warned about, never counted (eval/lib/node-advisory.tsv, read by the
|
|
4764
|
+
# Studio server too). Driven through a stub `node` per version; the minor is compared as a number (22.9 < 22.23),
|
|
4765
|
+
# a line older than the table's newest that it does not list gets no fixes, a newer line is not judged, and a node
|
|
4766
|
+
# that cannot run says nothing. No case changes the verdict.
|
|
4767
|
+
_dn="$(mktemp -d)"; _dnb=""; _dnn=0
|
|
4768
|
+
_dnrun(){ printf '#!/bin/sh\ncase "$*" in *split*) echo "%s" | cut -d. -f1 ;; *) echo "%s" ;; esac\n' "$1" "$1" > "$_dn/node"; chmod +x "$_dn/node"
|
|
4769
|
+
_dno="$( cd "$DOC" && CREW_LANG="${2:-en}" PATH="$_dn:$PATH" bash "$ROOT/eval/doctor.sh" 2>&1 )"; _dnrc=$?; _dnn=$((_dnn+1))
|
|
4770
|
+
[ "$_dnrc" = 0 ] || _dnb="$_dnb [$1 changed the verdict: rc $_dnrc]"; }
|
|
4771
|
+
_dnrun 24.21.0; case "$_dno" in *"Node 24.21.0 is below 24.22.1, the release of its line with the fixes of https://nodejs.org/"*) ;; *) _dnb="$_dnb [24.21.0 not warned]" ;; esac
|
|
4772
|
+
_dnrun 22.9.10; case "$_dno" in *"Node 22.9.10 is below 22.23.4"*) ;; *) _dnb="$_dnb [22.9.10 not warned: minor compared as text?]" ;; esac
|
|
4773
|
+
_dnrun 26.12.0; case "$_dno" in *"Node 26.12.0 is below 26.12.1"*) ;; *) _dnb="$_dnb [26.12.0 not warned]" ;; esac
|
|
4774
|
+
_dnrun 20.19.0; case "$_dno" in *"Node 20.19.0 is on a line that no longer gets security fixes"*"22.23.4, 24.22.1, 26.12.1"*) ;; *) _dnb="$_dnb [20.x not warned]" ;; esac
|
|
4775
|
+
for _v in 24.22.1 24.23.0 26.12.1; do _dnrun "$_v"; case "$_dno" in *"Node $_v carries the security fixes on record"*) ;; *) _dnb="$_dnb [$_v not ok]" ;; esac
|
|
4776
|
+
case "$_dno" in *"is below"*|*"no longer gets"*) _dnb="$_dnb [$_v warned]" ;; esac; done
|
|
4777
|
+
_dnrun 28.0.0; case "$_dno" in *"Node 28.0.0"*) _dnb="$_dnb [a newer line was judged]" ;; esac
|
|
4778
|
+
_dnrun 24.21.0 tr; case "$_dno" in *"Node 24.21.0, 24.22.1 altında"*) ;; *) _dnb="$_dnb [no Turkish line]" ;; esac
|
|
4779
|
+
printf '#!/bin/sh\nexit 127\n' > "$_dn/node"; _dno="$( cd "$DOC" && CREW_LANG=en PATH="$_dn:$PATH" bash "$ROOT/eval/doctor.sh" 2>&1 )"; _dnn=$((_dnn+1))
|
|
4780
|
+
case "$_dno" in *"Node "*"security fixes"*|*"is below"*) _dnb="$_dnb [a node that cannot run was judged]" ;; esac
|
|
4781
|
+
rm -rf "$_dn"
|
|
4782
|
+
if [ "$_dnn" != 10 ]; then fail "FIXTURE: the Node advisory block ran $_dnn doctor runs, not 10"
|
|
4783
|
+
elif [ -z "$_dnb" ]; then pass "doctor warns about a Node missing a published security fix: below its line's release (24.21.0, 26.12.0, 22.9.10 with the minor compared as a number), on a line that gets none (20.x); says ok on or above it; judges no newer line and no node that cannot run; Turkish too; never changes the verdict (10 runs)"
|
|
4784
|
+
else fail "doctor Node advisory:$_dnb"; fi
|
|
4763
4785
|
# THE MANIFEST'S LINE ENDINGS MUST NOT CHANGE WHO OWNS A SKILL. Measured before the fix: the same install with
|
|
4764
4786
|
# one project skill counted 1 on an LF manifest and 2 on a CRLF one, because `grep -qxF` wants a whole line and
|
|
4765
4787
|
# `skills/handoff\r` is not `skills/handoff` — so every KIT skill read as project-owned. A Windows checkout with
|
|
@@ -8568,7 +8590,25 @@ case "$(_v2ot general-purpose "$_v2/tr-opus.jsonl")" in *'"updatedInput":'*'"mod
|
|
|
8568
8590
|
[ -z "$(_v2ot general-purpose "$_v2/tr-haiku.jsonl")" ] || _v2b="$_v2b [a haiku session's general-purpose was raised to sonnet]"
|
|
8569
8591
|
[ -z "$(_v2ot Explore "$_v2/tr-opus.jsonl")$(_v2ot cheap "$_v2/tr-opus.jsonl")" ] || _v2b="$_v2b [an agent with a model of its own was given one]"
|
|
8570
8592
|
case "$(_v2ot mine "$_v2/tr-opus.jsonl")" in *'"model":"sonnet"'*) ;; *) _v2b="$_v2b [a project agent with no model line was not given sonnet]" ;; esac
|
|
8571
|
-
|
|
8593
|
+
# A task that opens with a card naming critical work is not lowered outside the crew either: a crew agent would have
|
|
8594
|
+
# had an opus floor for it. Seen on a real session: a one-line card with change: fix-unknown to general-purpose on opus
|
|
8595
|
+
# ran on sonnet. Nothing is raised, and a normal card, or a card that is not one, is lowered as before.
|
|
8596
|
+
_v2oc(){ printf '{"session_id":"s","permission_mode":"auto","hook_event_name":"PreToolUse","tool_name":"Agent","transcript_path":"%s","tool_input":{"description":"d","prompt":"%s\\n\\nFind X.","subagent_type":"general-purpose"%s},"tool_use_id":"tu%s"}' "$_v2/tr-opus.jsonl" "$1" "${2:-}" "$RANDOM" | ( cd "$_v2p" && _v2e bash "$_V2G" 2>/dev/null ) | tr -d ' '; }
|
|
8597
|
+
_v2kept=0
|
|
8598
|
+
for _c in 'files: none in repo (scratch scripts only)\nchange: fix-unknown\nverify: none' \
|
|
8599
|
+
'Card — files: none in repo (scratch scripts only) · change: fix-unknown · verify: none' \
|
|
8600
|
+
'files: db/migrations/001.sql\nchange: feature\nverify: none' \
|
|
8601
|
+
'files: src/payments/p.ts\nchange: feature\nverify: none' \
|
|
8602
|
+
'files: src/ui/a.ts · change: feature · verify: none · above: a design question'; do
|
|
8603
|
+
[ -z "$(_v2oc "$_c" ',"model":"opus"')" ] && _v2kept=$((_v2kept+1)) || _v2b="$_v2b [kept on opus: ${_c:0:50}]"
|
|
8604
|
+
done
|
|
8605
|
+
[ -z "$(_v2oc 'files: x\nchange: security\nverify: none')" ] && _v2kept=$((_v2kept+1)) || _v2b="$_v2b [critical card naming no model was lowered]"
|
|
8606
|
+
[ -z "$(_v2oc 'files: x\nchange: security\nverify: none' ',"model":"sonnet"')" ] && _v2kept=$((_v2kept+1)) || _v2b="$_v2b [critical card on sonnet was raised]"
|
|
8607
|
+
for _c in 'files: src/ui/a.ts\nchange: feature\nverify: none' 'files: src/ui/a.ts\nchange: fix-unknown' 'change: fix-unknown is what this is'; do
|
|
8608
|
+
case "$(_v2oc "$_c" ',"model":"opus"')" in *'"updatedInput":'*'"model":"sonnet"'*) _v2kept=$((_v2kept+1)) ;; *) _v2b="$_v2b [not lowered: ${_c:0:50}]" ;; esac
|
|
8609
|
+
done
|
|
8610
|
+
[ "$_v2kept" = 10 ] || _v2b="$_v2b [critical-card cases: $_v2kept of 10]"
|
|
8611
|
+
[ -z "$_v2b" ] && pass "above the floor runs on the floor: opus for a sonnet card is rewritten to sonnet with every other field kept and recorded as lowered; 'above: <why>' keeps opus; a call on its floor is untouched; checkable text work goes to haiku; an agent outside the crew asking for opus, or inheriting an opus session's model, runs on sonnet unless its task says why; a haiku session and an agent with its own model are left alone; a task whose card names critical work (fix-unknown, a migration, a payments file, on one line or three, or naming no model) is not lowered and not raised, while a normal card, a broken card and an inline above: behave as before (10 cases)" \
|
|
8572
8612
|
|| fail "model on the floor:$_v2b"
|
|
8573
8613
|
# The refusal names the way forward that exists (the field was sent, so it can be sent as false), and a call that
|
|
8574
8614
|
# left the field out and was launched in the background is told so when the launch comes back.
|
|
@@ -533,6 +533,9 @@ _am_digest(){ # after _am_card -> CARD_ID: 12 hex of what the card says, so "th
|
|
|
533
533
|
# A call to an agent that is not Crewforth's (general-purpose, a project's own): no card and no floor, so only the
|
|
534
534
|
# expensive end is held, the same rewrite as a crew call above its floor.
|
|
535
535
|
# * It names opus or fable, and its task has no `above: <why>` line: it runs on sonnet.
|
|
536
|
+
# * Either way, a task that opens with a card naming critical work (the same reading as a crew call: change:
|
|
537
|
+
# fix-unknown, security, migration, architecture, or a file on a critical path) is not lowered: a crew agent
|
|
538
|
+
# would have had an opus floor for it, so the way round the crew must not be the cheaper one. Nothing is raised.
|
|
536
539
|
# * It names no model, and the agent takes the session's (general-purpose, or a project or user agent whose
|
|
537
540
|
# definition has no model of its own): it runs on sonnet, unless the session itself is on haiku, read from the
|
|
538
541
|
# end of its transcript. An agent whose definition names a model (Explore, claude-code-guide) is left to it:
|
|
@@ -572,6 +575,11 @@ _am_outside(){ # $1 = the tool_input text, $2 = the agent's name
|
|
|
572
575
|
_json_slice "$1" model >/dev/null; _json_unescape "$_JS" >/dev/null; _am_tier "$_JU"; m="$_AMT"
|
|
573
576
|
case "$m" in opus|fable) ;; *) return 0 ;; esac
|
|
574
577
|
fi
|
|
578
|
+
if _am_card "$pr"; then
|
|
579
|
+
[ -z "$CARD_ABOVE" ] || return 0
|
|
580
|
+
_am_risk
|
|
581
|
+
if [ "$CARD_RISK" = critical ]; then _am_log "note: $name (not a crew agent) kept $m: its card names critical work ($CARD_RISKWHY)"; return 0; fi
|
|
582
|
+
fi
|
|
575
583
|
ui="$(printf '%s' "$INPUT" | awk -v op=get -v path=tool_input -v setk=model -v setv="\"$to\"" -f "$_AM_HERE/../eval/lib/settings-json.awk" - 2>/dev/null)" || return 0
|
|
576
584
|
case "$ui" in '{'*'}') ;; *) return 0 ;; esac
|
|
577
585
|
_am_log "note: $name (not a crew agent) asked for $m, run on $to; add 'above: <why>' to its task to keep a model above it"
|
package/kit/owned-blobs.tsv
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.
|
|
1
|
+
# Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.2.0 (below VERSION 3.2.1) — do not edit by hand.
|
|
2
2
|
# The flat files Crewforth owns: a copy whose bytes (CR aside) are one of these ids was never edited.
|
|
3
3
|
# install_path blob_sha1 first_tag last_tag
|
|
4
4
|
.claude/AGENT_TEMPLATE.md 075e2c26706c06f56af01652cd24c36918513bee v3.0.0 v3.0.4
|
|
5
|
+
.claude/AGENT_TEMPLATE.md 2c8223d811245e3627a3011bb64fb9e4b0bf3b94 v3.2.0 v3.2.0
|
|
5
6
|
.claude/AGENT_TEMPLATE.md 3fdc551d221ee3665b21ef8cc3563de861a9abe6 v1.4.0 v1.6.3
|
|
6
7
|
.claude/AGENT_TEMPLATE.md 686fed306c873b45c245c77a95a8e6de6ee383d5 v1.7.0 v2.11.0
|
|
7
8
|
.claude/AGENT_TEMPLATE.md 7ab19417b6ff3ac732e2c894be17ffb3ec091f7d v3.1.0 v3.1.1
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
.claude/DISCIPLINE.md b90a067ee323a08146862b889293f8feb49f8e7e v3.1.0 v3.1.1
|
|
30
31
|
.claude/DISCIPLINE.md bf062c2b51a9cf3cc4d0caac389c148cf6fdc977 v2.12.0 v2.12.0
|
|
31
32
|
.claude/DISCIPLINE.md c693f1bd5832ce570058c6eedcacb5dff13d89e8 v1.10.0 v1.10.0
|
|
33
|
+
.claude/DISCIPLINE.md cf881a5229d85850194a98b8d0d0438a8eaaa9d6 v3.2.0 v3.2.0
|
|
32
34
|
.claude/DISCIPLINE.md d63a01a183c9a80d739c840a511d338dc2765820 v1.4.0 v1.4.4
|
|
33
35
|
.claude/DISCIPLINE.md dc1c41baa3a8b7d637214bc78553a78d94d14ac9 v1.6.2 v1.6.3
|
|
34
36
|
.claude/DISCIPLINE.md e42aefc9c1ffaeace8e5712d92df780959212280 v2.0.0 v2.1.0
|
|
@@ -43,4 +45,4 @@
|
|
|
43
45
|
.claude/README.md 97d4dd1f7f1695052cc1d06cc43f5bcdc603c989 v1.0.0 v1.0.4
|
|
44
46
|
.claude/README.md a3419891cfda7385564008e4befc1007bc5364cb v1.1.1 v1.7.0
|
|
45
47
|
.claude/README.md a3b76e0fa31267610d28b742c122a962f6db59c4 v3.0.1 v3.0.4
|
|
46
|
-
.claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.
|
|
48
|
+
.claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.2.0
|
|
@@ -29,7 +29,7 @@ _crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
|
|
|
29
29
|
FLOOR=18
|
|
30
30
|
DIST=https://nodejs.org/dist
|
|
31
31
|
# Used only when nodejs.org cannot be reached to ask which release is current.
|
|
32
|
-
FALLBACK_VERSION=v24.
|
|
32
|
+
FALLBACK_VERSION=v24.22.1 # carries the fixes of eval/lib/node-advisory.tsv; raise it with that table
|
|
33
33
|
|
|
34
34
|
# Windows caps a full file path at 260 characters and PowerShell's Expand-Archive enforces it.
|
|
35
35
|
# Measured: a 141-character target failed with PathTooLongException at __generated__\google\api\…
|
|
@@ -43,6 +43,7 @@ import { addViewer, listViewers, viewerByToken, revokeViewer, noteRequest, noteR
|
|
|
43
43
|
import { redact, transcriptOnly } from './lib/redact.js';
|
|
44
44
|
import { lanListener } from './lib/lan.js';
|
|
45
45
|
import { randomUUID } from 'node:crypto';
|
|
46
|
+
import { nodeAdvisoryNote } from './lib/node-advisory.js';
|
|
46
47
|
import { spawn } from 'node:child_process';
|
|
47
48
|
|
|
48
49
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -1104,6 +1105,8 @@ async function main() {
|
|
|
1104
1105
|
process.stdout.write(TOKEN_GENERATED
|
|
1105
1106
|
? ' (loopback only; token generated for this run)\n'
|
|
1106
1107
|
: ' (loopback only; token from CREW_STUDIO_TOKEN)\n');
|
|
1108
|
+
const nodeNote = nodeAdvisoryNote();
|
|
1109
|
+
if (nodeNote) process.stdout.write(` warning: ${nodeNote}\n`);
|
|
1107
1110
|
|
|
1108
1111
|
// Recorded only after listen() succeeds, so the file never claims a port this process did not get. Failure
|
|
1109
1112
|
// to write is not fatal: the panel works, the next session simply cannot find it, which is where we started.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Is the Node running this panel missing a published security fix? The table is eval/lib/node-advisory.tsv, the
|
|
2
|
+
// same file eval/doctor.sh reads, so the two cannot disagree. A warning only: the panel still starts.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const TABLE = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'eval', 'lib', 'node-advisory.tsv');
|
|
8
|
+
|
|
9
|
+
/** The table's text as { url, lines: Map(major -> 'x.y.z') }; null when it holds no release line. */
|
|
10
|
+
export function parseAdvisory(text) {
|
|
11
|
+
let url = '';
|
|
12
|
+
const lines = new Map();
|
|
13
|
+
for (const raw of String(text ?? '').split(/\r?\n/)) {
|
|
14
|
+
if (!raw || raw.startsWith('#')) continue;
|
|
15
|
+
const [k, v] = raw.split('\t');
|
|
16
|
+
if (k === 'advisory') url = v ?? '';
|
|
17
|
+
else if (/^\d+$/.test(k) && /^\d+\.\d+\.\d+$/.test(v ?? '')) lines.set(Number(k), v);
|
|
18
|
+
}
|
|
19
|
+
return lines.size ? { url, lines } : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const parts = (v) => String(v).replace(/^v/, '').split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
23
|
+
const below = (a, b) => { const x = parts(a), y = parts(b); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i]; return false; };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The sentence to print for `version` (process.versions.node), or null when there is nothing to say: it carries
|
|
27
|
+
* its line's fixes, its line is newer than the table, or the table could not be read.
|
|
28
|
+
*/
|
|
29
|
+
export function judgeNode(version, advisory) {
|
|
30
|
+
if (!advisory || !/^\d+\.\d+\.\d+/.test(String(version ?? ''))) return null;
|
|
31
|
+
const major = parts(version)[0];
|
|
32
|
+
const newest = Math.max(...advisory.lines.keys());
|
|
33
|
+
if (major > newest) return null;
|
|
34
|
+
const fixed = advisory.lines.get(major);
|
|
35
|
+
if (fixed && !below(version, fixed)) return null;
|
|
36
|
+
return fixed
|
|
37
|
+
? `Node ${version} is below ${fixed}, the release of its line with the fixes of ${advisory.url} — update Node`
|
|
38
|
+
: `Node ${version} is on a line that no longer gets security fixes (${advisory.url} fixes ${[...advisory.lines.values()].join(', ')}) — update Node`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** judgeNode for the running process, reading the table from disk; null when the table is not there. */
|
|
42
|
+
export function nodeAdvisoryNote(version = process.versions.node) {
|
|
43
|
+
let text;
|
|
44
|
+
try { text = fs.readFileSync(TABLE, 'utf8'); } catch { return null; }
|
|
45
|
+
return judgeNode(version, parseAdvisory(text));
|
|
46
|
+
}
|
|
@@ -65,12 +65,12 @@ export function workSteps(row) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* What is said about an agent
|
|
69
|
-
*
|
|
68
|
+
* What is said about an agent that is not crew-*: it has no floor and no line in the record, but the model gate still
|
|
69
|
+
* holds its expensive end, except for a card naming critical work (_am_outside in hooks/guard-agent-model.sh). Null for a crew agent or an unknown type.
|
|
70
70
|
*/
|
|
71
71
|
export function outsideRouting(agentType) {
|
|
72
72
|
if (typeof agentType !== 'string' || !agentType || agentType.startsWith('crew-')) return null;
|
|
73
|
-
return 'Outside model routing: not a crew agent, so
|
|
73
|
+
return 'Outside model routing: not a crew agent, so it has no floor and Crewforth keeps no outcome line for it. The gate only holds the expensive end: a call that asks for opus or fable, or names no model and would take the session\u2019s, is held at sonnet unless its task carries an above: <why> line or opens with a card naming critical work. An agent whose own definition names a model keeps it.';
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|