crewforth 3.1.0-rc.7 → 3.1.0-rc.8
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 +6 -0
- package/kit/eval/smoke-test.sh +41 -4
- package/kit/hooks/commit-msg +3 -1
- package/kit/hooks/pre-commit +26 -10
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -343,6 +343,12 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
343
343
|
|
|
344
344
|
### Fixed
|
|
345
345
|
|
|
346
|
+
- **On some Windows machines every commit was stopped: "PRIVATE-PATH SCANNER: … could not run".** The look for a
|
|
347
|
+
machine-private string used `grep -iF`, and Git Bash's grep 3.0 aborts for it (exit 134): in the C locale with
|
|
348
|
+
Git for Windows 2.53, under a Turkish locale with 2.55 (measured on one machine of each). The commit hook sets
|
|
349
|
+
the C locale, so on the first kind every look aborted, and the hook rightly stopped the commit. A private term
|
|
350
|
+
is now escaped and matched with `-iE`, which aborted in none of the 15 pairs of `LANG` and `LC_ALL` measured on
|
|
351
|
+
either machine. A term is still a literal: `.`, `\`, `[`, `(`, `+`, `|` and the rest match only themselves.
|
|
346
352
|
- **Critical work with a verify command was refused every time where the Agent tool has no `run_in_background`.**
|
|
347
353
|
The gate asked for the field to be `false`. Where background tasks are off the tool has no such field (measured on
|
|
348
354
|
Claude Code 2.1.296: the call arrives with description, prompt, subagent_type and model, and runs in the
|
package/kit/eval/smoke-test.sh
CHANGED
|
@@ -10102,8 +10102,10 @@ printf 'acme-internal-host\n' > "$_TH/r/.private-terms.txt"
|
|
|
10102
10102
|
_thbad=""
|
|
10103
10103
|
_th_hook "$_TH/r" pre-commit ""; { [ "$_thr" = 1 ] && grep -q 'PRIVATE-PATH SCANNER: a staged line contains' "$_TH/out"; } \
|
|
10104
10104
|
|| _thbad="$_thbad [FIXTURE: with a working grep the private string is not reported: rc=$_thr]"
|
|
10105
|
-
|
|
10106
|
-
|
|
10105
|
+
# A grep that aborts for -iF changes nothing: the look for a private string does not use it (measured on two
|
|
10106
|
+
# Windows machines: Git Bash's grep 3.0 aborts for -iF in the C locale on one and under tr_TR on the other).
|
|
10107
|
+
_th_hook "$_TH/r" pre-commit "" "$_TH/bin"; { [ "$_thr" = 1 ] && grep -q 'PRIVATE-PATH SCANNER: a staged line contains' "$_TH/out" && ! grep -q 'could not run' "$_TH/out"; } \
|
|
10108
|
+
|| _thbad="$_thbad [grep aborting for -iF: rc=$_thr, want 1 and the private string reported — $(sed -n 1p "$_TH/out" | cut -c1-120)]"
|
|
10107
10109
|
# the same for a scan by pattern: an abort is not "this pattern does not compile"
|
|
10108
10110
|
mkdir -p "$_TH/bin2"
|
|
10109
10111
|
printf '#!/bin/sh\ncase " $* " in *" -qiE "*) kill -ABRT $$ ;; esac\nexec "%s" "$@"\n' "$_thg" > "$_TH/bin2/grep"; chmod +x "$_TH/bin2/grep"
|
|
@@ -10112,6 +10114,41 @@ _th_hook "$_TH/e" pre-commit "" "$_TH/bin2"; { [ "$_thr" = 1 ] && grep -q 'TRACE
|
|
|
10112
10114
|
|| _thbad="$_thbad [pre-commit, grep aborting for -iE: rc=$_thr, want 1 and 'could not run' — $(sed -n 1p "$_TH/out" | cut -c1-120)]"
|
|
10113
10115
|
_th_hook "$_TH/e" commit-msg "" "$_TH/bin2" msg.txt; { [ "$_thr" = 1 ] && grep -q 'could not run' "$_TH/out"; } \
|
|
10114
10116
|
|| _thbad="$_thbad [commit-msg, grep aborting for -iE: rc=$_thr, want 1 and 'could not run' — $(sed -n 1p "$_TH/out" | cut -c1-120)]"
|
|
10117
|
+
# ...and the look for a private string, which is a scan by pattern now: an abort stops the commit and says so
|
|
10118
|
+
_th_hook "$_TH/r" pre-commit "" "$_TH/bin2"; { [ "$_thr" = 1 ] && grep -q 'PRIVATE-PATH SCANNER: the look for a machine-private string could not run' "$_TH/out"; } \
|
|
10119
|
+
|| _thbad="$_thbad [the private-string look, grep aborting for -iE: rc=$_thr, want 1 and 'could not run' — $(grep -m1 PRIVATE "$_TH/out" | cut -c1-120)]"
|
|
10120
|
+
# A term is a literal, whatever it holds: each is found as written and in another case, and none of its characters
|
|
10121
|
+
# works as a pattern (the line on the right is what it would match if one did).
|
|
10122
|
+
_thl=0
|
|
10123
|
+
while IFS= read -r _tl; do [ -z "$_tl" ] && continue
|
|
10124
|
+
_tt="${_tl%% @@ *}"; _tr="${_tl#* @@ }"; _th1="${_tr%% @@ *}"; _th0="${_tr#* @@ }"; _thl=$((_thl+1))
|
|
10125
|
+
_th_repo "$_TH/l"; printf '%s\n' "$_tt" > "$_TH/l/.private-terms.txt"
|
|
10126
|
+
( cd "$_TH/l" && printf 'x %s y\n' "$_th1" > n.md && git add n.md ) >/dev/null 2>&1
|
|
10127
|
+
_th_hook "$_TH/l" pre-commit ""; { [ "$_thr" = 1 ] && grep -q 'a staged line contains' "$_TH/out"; } || _thbad="$_thbad [the term $_tt is not found in: $_th1 (rc=$_thr)]"
|
|
10128
|
+
( cd "$_TH/l" && printf 'x %s y\n' "$_th0" > n.md && git add n.md ) >/dev/null 2>&1
|
|
10129
|
+
_th_hook "$_TH/l" pre-commit ""; [ "$_thr" = 0 ] || _thbad="$_thbad [the term $_tt is read as a pattern: it matched $_th0 (rc=$_thr, $(sed -n 1p "$_TH/out" | cut -c1-80))]"
|
|
10130
|
+
done <<'TL'
|
|
10131
|
+
acme.internal @@ ACME.Internal @@ acmeXinternal
|
|
10132
|
+
C:\Users\ada.l @@ c:\users\ADA.L @@ C:Usersada.l
|
|
10133
|
+
build[7] @@ BUILD[7] @@ build7
|
|
10134
|
+
a+b(c) @@ A+B(C) @@ aab(c)
|
|
10135
|
+
host|other @@ HOST|OTHER @@ host
|
|
10136
|
+
cost$ ^x* @@ COST$ ^X* @@ cost ^
|
|
10137
|
+
q{2}? @@ Q{2}? @@ qq
|
|
10138
|
+
TL
|
|
10139
|
+
[ "$_thl" = 7 ] || _thbad="$_thbad [FIXTURE: the literal-term table has $_thl rows, not 7]"
|
|
10140
|
+
# No hook looks for anything with -iF: a code line of a gate that calls grep with both flags is the defect itself.
|
|
10141
|
+
_thf="$(for _f in "$HOOKS"/*.sh "$HOOKS/pre-commit" "$HOOKS/commit-msg"; do grep -nE '^[^#]*(grep[[:space:]][^|;#]*-[A-Za-z]*(i[A-Za-z]*F|F[A-Za-z]*i)|_crew_(scan|any)[^#]*[[:space:]](iF|Fi)([[:space:]]|$))' "$_f" 2>/dev/null | sed "s#^#${_f##*/}:#"; done)"
|
|
10142
|
+
[ -z "$_thf" ] || _thbad="$_thbad [a hook calls grep with -i and -F together: $(printf '%s' "$_thf" | head -2 | cut -c1-140)]"
|
|
10143
|
+
# ...and the look itself names the C locale on the call (3 calls: _crew_scan in both git hooks, _crew_any): under
|
|
10144
|
+
# tr_TR `grep -iE` answers "no match" for i against I without aborting, so a look left to the session's locale
|
|
10145
|
+
# would miss a term in capitals, silently.
|
|
10146
|
+
_thc="$(grep -cE '^[[:space:]]*LC_ALL=C grep -q' "$HOOKS/pre-commit" "$HOOKS/commit-msg" | tr '\n' ' ')"
|
|
10147
|
+
_thu="$(grep -nE '^[[:space:]]*grep -q("\$1"|\$3) -- ' "$HOOKS/pre-commit" "$HOOKS/commit-msg" | head -2)"
|
|
10148
|
+
case "$_thc" in *"pre-commit:2 "*"commit-msg:1 "*) ;; *) _thbad="$_thbad [the first look does not name the C locale on the call: $_thc]" ;; esac
|
|
10149
|
+
[ -z "$_thu" ] || _thbad="$_thbad [a first look runs in whatever locale is exported: $(printf '%s' "$_thu" | cut -c1-120)]"
|
|
10150
|
+
printf 'x() { grep -qiF -- "$a" "$b"; }\n_crew_scan iF "$t" "$f"\n# grep -iF in a comment\n' > "$_TH/pin.sh"
|
|
10151
|
+
[ "$(grep -cE '^[^#]*(grep[[:space:]][^|;#]*-[A-Za-z]*(i[A-Za-z]*F|F[A-Za-z]*i)|_crew_(scan|any)[^#]*[[:space:]](iF|Fi)([[:space:]]|$))' "$_TH/pin.sh")" = 2 ] || _thbad="$_thbad [FIXTURE: the -iF pin does not see the two lines it is for]"
|
|
10115
10152
|
# and for the diff itself: the grep that picks the added lines aborting must not leave an empty file to scan
|
|
10116
10153
|
mkdir -p "$_TH/bin3"
|
|
10117
10154
|
printf '#!/bin/sh\n[ "$1" = -E ] && [ "$2" = "^\\+" ] && kill -ABRT $$\nexec "%s" "$@"\n' "$_thg" > "$_TH/bin3/grep"; chmod +x "$_TH/bin3/grep"
|
|
@@ -10124,7 +10161,7 @@ _th_hook "$_TH/b" pre-commit ""; { [ "$_thr" = 0 ] && grep -q 'is not a valid re
|
|
|
10124
10161
|
|| _thbad="$_thbad [pre-commit with a pattern that does not compile: rc=$_thr, want 0 and the warning]"
|
|
10125
10162
|
_th_hook "$_TH/b" commit-msg "" "" msg.txt; { [ "$_thr" = 0 ] && grep -q 'is not a valid regular expression' "$_TH/out"; } \
|
|
10126
10163
|
|| _thbad="$_thbad [commit-msg with a pattern that does not compile: rc=$_thr, want 0 and the warning]"
|
|
10127
|
-
[ -z "$_thbad" ] && pass "a scan that could not run stops the commit: with grep aborting for -
|
|
10164
|
+
[ -z "$_thbad" ] && pass "a scan that could not run stops the commit: with grep aborting for -iE (the scan by pattern and the look for a private string), or while the added lines are collected, pre-commit and commit-msg exit 1 and say so; a pattern that does not compile still only warns, in both; a grep that aborts for -iF changes nothing, since no hook calls it (pinned, with a twin the pin sees); the first look names the C locale on the call, in both git hooks; a private term is a literal: 7 terms with . \\ [ ] + ( ) | \$ ^ * { } ? are found as written, in another case, and match nothing else" \
|
|
10128
10165
|
|| fail "a scan that could not run:$_thbad"
|
|
10129
10166
|
|
|
10130
10167
|
# The command shown in the approval prompt is cut to 300 bytes a line and goes into JSON. `cut -c` did it: GNU cut
|
|
@@ -10207,7 +10244,7 @@ else
|
|
|
10207
10244
|
( cd "$_TH/n" && printf 'see ŞİRKET-GİZLİ\n' > n.md && git add n.md ) >/dev/null 2>&1
|
|
10208
10245
|
_th_hook "$_TH/n" pre-commit "$_th"; _thna="$_thr"
|
|
10209
10246
|
# required where this platform's grep folds such a letter under the locale at all (GNU grep does, BSD grep does not)
|
|
10210
|
-
if ( printf 'ŞİRKET\n' | LC_ALL=$_th grep -
|
|
10247
|
+
if ( printf 'ŞİRKET\n' | LC_ALL=$_th grep -qiE 'şirket' ) >/dev/null 2>&1; then
|
|
10211
10248
|
[ "$_thna" = 1 ] || _thbad="$_thbad [pre-commit, a private string of non-ASCII letters in another case: rc=$_thna, want 1 (this grep folds them under $_th)]"
|
|
10212
10249
|
fi
|
|
10213
10250
|
[ -z "$_thbad" ] && pass "under $_th the git hooks stop what they stop under C: a vendor name in another case in an added line and in the message, a staged key that holds an I, a private string in another case; an ordinary line and message pass (a private string of non-ASCII letters in another case: exit $_thna, 1 = caught by the second look)" \
|
package/kit/hooks/commit-msg
CHANGED
|
@@ -36,7 +36,9 @@ export LC_ALL=C
|
|
|
36
36
|
# the first look finds nothing and the match ignores case: in the C locale such a letter has no other case.
|
|
37
37
|
_crew_scan(){ # $1 = grep's flags after -q (iE, E, iF) · $2 = pattern · $3 = file
|
|
38
38
|
local r=0
|
|
39
|
-
|
|
39
|
+
# The first look is in the C locale, SAID ON THE CALL and not left to the export at the top of the file: under
|
|
40
|
+
# tr_TR `grep -iE` does not abort, it answers "no match" for i against I (measured in Git Bash), which is silent.
|
|
41
|
+
LC_ALL=C grep -q"$1" -- "$2" "$3" 2>/dev/null || r=$?
|
|
40
42
|
if [ "$r" = 1 ] && [ -n "$_CREW_LOCALE" ]; then
|
|
41
43
|
case "$1" in *i*) case "$2" in *[!$'\t'\ -~]*) r=0; LC_ALL="$_CREW_LOCALE" grep -q"$1" -- "$2" "$3" 2>/dev/null || r=$? ;; esac ;; esac
|
|
42
44
|
fi
|
package/kit/hooks/pre-commit
CHANGED
|
@@ -37,7 +37,9 @@ export LC_ALL=C
|
|
|
37
37
|
# the first look finds nothing and the match ignores case: in the C locale such a letter has no other case.
|
|
38
38
|
_crew_scan(){ # $1 = grep's flags after -q (iE, E, iF) · $2 = pattern · $3 = file
|
|
39
39
|
local r=0
|
|
40
|
-
|
|
40
|
+
# The first look is in the C locale, SAID ON THE CALL and not left to the export at the top of the file: under
|
|
41
|
+
# tr_TR `grep -iE` does not abort, it answers "no match" for i against I (measured in Git Bash), which is silent.
|
|
42
|
+
LC_ALL=C grep -q"$1" -- "$2" "$3" 2>/dev/null || r=$?
|
|
41
43
|
if [ "$r" = 1 ] && [ -n "$_CREW_LOCALE" ]; then
|
|
42
44
|
case "$1" in *i*) case "$2" in *[!$'\t'\ -~]*) r=0; LC_ALL="$_CREW_LOCALE" grep -q"$1" -- "$2" "$3" 2>/dev/null || r=$? ;; esac ;; esac
|
|
43
45
|
fi
|
|
@@ -352,18 +354,32 @@ done < <({ _floor_diff --cached; [ "${CREW_SCAN_UNSTAGED:-0}" = 1 ] && _floor_di
|
|
|
352
354
|
# escapes to top level): 8 mismatches, in BOTH directions — three clean messages newly rejected and two real
|
|
353
355
|
# secrets newly passed. Also checked structurally: no pattern in either shipped blocklist carries a bare
|
|
354
356
|
# top-level `|` (every one is inside a group) and none uses a back-reference, so joining cannot reshape them.
|
|
355
|
-
|
|
357
|
+
# A LITERAL TERM IS LOOKED FOR AS A PATTERN, NEVER WITH `grep -iF`. Measured on two Windows machines, both GNU grep
|
|
358
|
+
# 3.0 in Git Bash, and they fail in opposite places:
|
|
359
|
+
# Git for Windows 2.53 (msys bash 5.2) grep -iF aborts (exit 134) when the locale in effect is C or POSIX
|
|
360
|
+
# Git for Windows 2.55 (cygwin bash 5.3) grep -iF aborts (exit 134) when the locale in effect is tr_TR
|
|
361
|
+
# This file sets the C locale (above), so on the first of them every look for a private string aborted and every
|
|
362
|
+
# commit was stopped; with no locale set, the second one aborted for a Turkish user. No locale is safe for both.
|
|
363
|
+
# On both, -iE and -i did not abort in any of 15 pairs of LANG and LC_ALL. So the term is escaped and matched with
|
|
364
|
+
# -iE: _crew_lit puts a backslash before each character that means something in a pattern.
|
|
365
|
+
_crew_lit(){ # $1 = a literal string -> _CL, the pattern that matches exactly it
|
|
366
|
+
local s="$1" c; _CL=""
|
|
367
|
+
while [ -n "$s" ]; do
|
|
368
|
+
c="${s:0:1}"; s="${s:1}"
|
|
369
|
+
case "$c" in '\'|.|'['|']'|'^'|'$'|'*'|+|'?'|'('|')'|'{'|'}'|'|') _CL="$_CL\\$c" ;; *) _CL="$_CL$c" ;; esac
|
|
370
|
+
done
|
|
371
|
+
}
|
|
372
|
+
_crew_any(){ # $1 = pattern list $2 = corpus $3 = grep flags $4 = 1 when the list holds literal terms, not patterns
|
|
356
373
|
local pat alt="" sep="|" na=0 r=0
|
|
357
|
-
[ "$4" = 1 ] && sep="
|
|
358
|
-
"
|
|
359
374
|
while IFS= read -r pat || [ -n "$pat" ]; do
|
|
360
375
|
pat="${pat%$'\r'}"
|
|
361
376
|
case "$pat" in ''|\#*) continue;; esac
|
|
362
|
-
alt="${alt:+$alt$sep}$pat"
|
|
363
377
|
case "$pat" in *[!$'\t'\ -~]*) na=1 ;; esac
|
|
378
|
+
if [ "$4" = 1 ]; then _crew_lit "$pat"; pat="$_CL"; fi
|
|
379
|
+
alt="${alt:+$alt$sep}$pat"
|
|
364
380
|
done < "$1"
|
|
365
381
|
[ -n "$alt" ] || return 1
|
|
366
|
-
grep -q$3 -- "$alt" "$2" 2>/dev/null || r=$?
|
|
382
|
+
LC_ALL=C grep -q$3 -- "$alt" "$2" 2>/dev/null || r=$?
|
|
367
383
|
# Nothing found, but a pattern holds a letter outside ASCII and the match ignores case: the per-pattern loop looks
|
|
368
384
|
# again under the session's own locale (_crew_scan), so send the caller there.
|
|
369
385
|
if [ "$r" = 1 ] && [ "$na" = 1 ] && [ -n "$_CREW_LOCALE" ]; then case "$3" in *i*) return 4 ;; esac; fi
|
|
@@ -472,21 +488,21 @@ PAL=""; [ -n "$GITROOT" ] && [ -f "$GITROOT/.private-allowlist.txt" ] && PAL="$
|
|
|
472
488
|
fi
|
|
473
489
|
[ -n "$PTF" ] && sed -e 's/[[:space:]]*$//' -e '/^#/d' -e '/^$/d' "$PTF"
|
|
474
490
|
} > "$PTERM_F" 2>/dev/null || true
|
|
475
|
-
if _crew_any "$PTERM_F" "$SECRET_F"
|
|
491
|
+
if _crew_any "$PTERM_F" "$SECRET_F" iE 1; then _dec=0; else _dec=$?; fi
|
|
476
492
|
if [ -s "$PTERM_F" ] && [ -s "$SECRET_F" ] && [ "$_dec" != 1 ]; then
|
|
477
493
|
while IFS= read -r term || [ -n "$term" ]; do
|
|
478
494
|
term="${term%$'\r'}"
|
|
479
495
|
[ -n "$term" ] || continue
|
|
480
496
|
if [ -n "$PAL" ] && grep -qxF -- "$term" "$PAL"; then continue; fi
|
|
481
|
-
_gr=0; _crew_scan
|
|
497
|
+
_crew_lit "$term"; _gr=0; _crew_scan iE "$_CL" "$SECRET_F" || _gr=$?
|
|
482
498
|
if [ "$_gr" = 0 ]; then
|
|
483
499
|
# The term is echoed back deliberately: unlike a secret, the author has to see WHICH private string to
|
|
484
500
|
# replace, and it is already sitting in their own working tree.
|
|
485
501
|
echo "PRIVATE-PATH SCANNER: a staged line contains a machine-private string -> '$term' (§4.3)"
|
|
486
502
|
HIT=1
|
|
487
503
|
elif [ "$_gr" -ge 2 ]; then
|
|
488
|
-
# This loop used to read every exit status but 0 as "no match":
|
|
489
|
-
#
|
|
504
|
+
# This loop used to read every exit status but 0 as "no match": where grep aborted (134) for each term the
|
|
505
|
+
# commit went through with both private strings in it (measured).
|
|
490
506
|
echo "PRIVATE-PATH SCANNER: the look for a machine-private string could not run, so the staged lines are not known to be clean and the commit is stopped (§4.3)."
|
|
491
507
|
HIT=1
|
|
492
508
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewforth",
|
|
3
|
-
"version": "3.1.0-rc.
|
|
3
|
+
"version": "3.1.0-rc.8",
|
|
4
4
|
"description": "Crewforth — your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"crewforth": "bin/cli.js"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"git-hooks"
|
|
45
45
|
],
|
|
46
46
|
"author": "Barış Yerlikaya",
|
|
47
|
+
"homepage": "https://crewforth.com/",
|
|
47
48
|
"repository": {
|
|
48
49
|
"type": "git",
|
|
49
50
|
"url": "git+https://github.com/Crewforth/crewforth.git"
|