crewforth 3.2.0-rc.8 → 3.2.0
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 +14 -1
- package/VERSION +1 -1
- package/adopt.sh +8 -4
- package/kit/eval/smoke-test.sh +18 -3
- package/kit/hooks/guard-agent-model.sh +4 -3
- package/kit/hooks/guard-commit-scan.sh +10 -2
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,7 @@ 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
|
-
## [
|
|
8
|
+
## [3.2.0] — 2026-10-21
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
@@ -233,6 +233,19 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
233
233
|
- **`change: review` is read as `audit`.** The model table of the working rules names review with audit, and
|
|
234
234
|
`crew-review-agent` cards that said `change: review` were refused. It now takes the audit floor, on a critical path
|
|
235
235
|
too; a kind that is not on the list is still refused.
|
|
236
|
+
- **An install or an update never loosens the trace gate by itself.** The installer suggested exempting
|
|
237
|
+
the co-author trailer whenever the last 80 commits carried one, and with no terminal to ask in it took its own
|
|
238
|
+
suggestion: an update wrote the line into `.trace-allowlist.txt` and staged it, undoing a removal the team had
|
|
239
|
+
committed. The suggestion is now always to keep the gate, and the exemption is written only when you type
|
|
240
|
+
`loosen` in the interactive review.
|
|
241
|
+
- **A commit refusal names a trace in the message even when the staged content also fails.** The content gate
|
|
242
|
+
scanned the `-m` message only when the staged change was clean, so a refusal for a silenced lint check said nothing about
|
|
243
|
+
the session-link trailer in the same message, and the session showed the user the traced message again. Both are
|
|
244
|
+
named in one refusal now, and a trace in the message adds that a harness reminder to end a message with a
|
|
245
|
+
trailer does not override §4.1. The git `commit-msg` hook still stops the trailer at the commit itself.
|
|
246
|
+
- **A one-line task card may open with `Kart`.** Sessions that write in Turkish lead the card with `Kart —` as
|
|
247
|
+
others write `Card —`, and the call was refused as having no card. The Turkish word is read like the English one;
|
|
248
|
+
the fields after it are held to the same rules.
|
|
236
249
|
- **`above:` is read anywhere among a task card's three lines.** A card with `above: <why>` between `change:` and
|
|
237
250
|
`verify:` was refused ("line 3 of the task is not one of files:, change:, verify:") and the call was sent again
|
|
238
251
|
with the line moved down. It is now read in any position and keeps the model as it does after the three; the three
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.2.0
|
|
1
|
+
3.2.0
|
package/adopt.sh
CHANGED
|
@@ -199,8 +199,7 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
|
|
|
199
199
|
"project wins (fixed)") s='proje önde (sabit)' ;;
|
|
200
200
|
"on conflict the project's rules win; in DISCIPLINE.md §4 they can only tighten, never loosen (the one exception: the §4.1 trace allowlist you choose at adoption); Crewforth fills gaps (not overridable)") s="çakışmada projenin kuralı geçerli; DISCIPLINE.md §4'te proje yalnız sıkılaştırabilir, gevşetemez (tek istisna: kurulumda sizin seçtiğiniz §4.1 iz izin listesi); Crewforth boşlukları doldurur (değiştirilemez)" ;;
|
|
201
201
|
"3 Trace gate") s='3 İz kapısı' ;;
|
|
202
|
-
"loosen
|
|
203
|
-
"co-author/sign-off present in git log — may be a convention") s='git geçmişinde co-author/sign-off var — ekibin alışkanlığı olabilir' ;;
|
|
202
|
+
"co-author/sign-off present in git log — type loosen in the review to exempt it; never assumed") s='git geçmişinde co-author/sign-off var — muaf tutmak için incelemede loosen yazın; kendiliğinden yapılmaz' ;;
|
|
204
203
|
"keep") s='koru' ;;
|
|
205
204
|
"no co-author/sign-off convention seen") s='co-author/sign-off alışkanlığı görülmedi' ;;
|
|
206
205
|
"4 Share/hide") s='4 Paylaş/gizle' ;;
|
|
@@ -789,7 +788,7 @@ else
|
|
|
789
788
|
fi
|
|
790
789
|
propm '2 Precedence' 'project wins (fixed)' "on conflict the project's rules win; in DISCIPLINE.md §4 they can only tighten, never loosen (the one exception: the §4.1 trace allowlist you choose at adoption); Crewforth fills gaps (not overridable)"
|
|
791
790
|
if [ "$COAUTHOR" = 1 ]; then
|
|
792
|
-
propm '3 Trace gate' '
|
|
791
|
+
propm '3 Trace gate' 'keep' 'co-author/sign-off present in git log — type loosen in the review to exempt it; never assumed'
|
|
793
792
|
else
|
|
794
793
|
propm '3 Trace gate' 'keep' 'no co-author/sign-off convention seen'
|
|
795
794
|
fi
|
|
@@ -817,7 +816,12 @@ fi
|
|
|
817
816
|
# ============ COMPILE DECISIONS + OVERRIDE (Stage B) ============
|
|
818
817
|
DEC1="$([ "$N_PAGENTS" != 0 ] && echo keep || echo none)"
|
|
819
818
|
DEC2="project" # precedence is FIXED to project-wins (not overridable — reflected in the @import comment)
|
|
820
|
-
|
|
819
|
+
# The trace gate is loosened only when a person types `loosen` in the review below. Never a default: old co-author
|
|
820
|
+
# lines in the history are not a decision, and an install that cannot ask (no terminal) or an update has no person
|
|
821
|
+
# to make one. Measured on a real project, 2026-10-21: a non-interactive update found five old trailers in the last
|
|
822
|
+
# 80 commits, wrote the co-author trailer into .trace-allowlist.txt and staged it, undoing a removal the team had
|
|
823
|
+
# committed.
|
|
824
|
+
DEC3=keep
|
|
821
825
|
DEC4="$([ "$TRACKED" = 1 ] && echo share || { [ "$IGN" = 1 ] && echo hide || echo kit-default; })"
|
|
822
826
|
DEC6="baseline"
|
|
823
827
|
DEC7="$([ "$OFFREPO" = 1 ] && echo transfer || echo local)"
|
package/kit/eval/smoke-test.sh
CHANGED
|
@@ -5572,6 +5572,20 @@ $TRFX\""; then pass "multi-line AI trace in the commit message BLOCKED (§4.1)"
|
|
|
5572
5572
|
if csblk "git commit -F $MFX"; then pass "-F <file> carrying an AI trace BLOCKED (§4.1)"
|
|
5573
5573
|
else fail "-F <file> AI trace not blocked with rc=2 (§4.1 hole or the hook died)"; fi
|
|
5574
5574
|
rm -f "$MFX"
|
|
5575
|
+
# Both halves in ONE refusal: a staged secret and a session-link trailer in the message. The message used to be
|
|
5576
|
+
# scanned only when the staged content was clean, so the refusal named the first problem and the commit was
|
|
5577
|
+
# retried with the trailer still in it (a real project, 2026-10-21). Assembled at run time, as above.
|
|
5578
|
+
STFX="Claude-""Session: https://claude.ai/code/""session_""0123456789abcdef"
|
|
5579
|
+
( cd "$CS" && printf 'k = "%s"\n' "$SKFX" > c.txt && git add c.txt ) >/dev/null 2>&1
|
|
5580
|
+
_cso="$( cd "$CS" && printf '%s' "$(csj "git commit -m \"feat: z
|
|
5581
|
+
|
|
5582
|
+
$STFX\"")" | bash .claude/hooks/guard-commit-scan.sh 2>&1 >/dev/null )"; _csr=$?
|
|
5583
|
+
( cd "$CS" && git rm -q --cached c.txt && rm -f c.txt ) >/dev/null 2>&1
|
|
5584
|
+
case "$_cso" in *SECRET-SCANNER*'TRACE-SCANNER (message)'*'does not override'*)
|
|
5585
|
+
[ "$_csr" = 2 ] && pass "a staged secret and a trailer in the message are named in ONE refusal, with the harness-reminder line (§4.1)" \
|
|
5586
|
+
|| fail "both problems named but rc=$_csr, not 2" ;;
|
|
5587
|
+
*) fail "the refusal did not name both the staged secret and the message trailer (rc=$_csr): $(printf '%s' "$_cso" | tr '\n' ' ' | cut -c1-200)" ;;
|
|
5588
|
+
esac
|
|
5575
5589
|
# A QUOTED -F path with a space. The sed extraction that ran wherever python3 did not stopped at the space, read
|
|
5576
5590
|
# `-F "my msg.txt"` as `my`, found no such file and refused the commit — a clean one. The awk tokenizer is now
|
|
5577
5591
|
# the only path; both directions are asserted so an extraction that reads NOTHING cannot pass as "clean".
|
|
@@ -8601,7 +8615,8 @@ done
|
|
|
8601
8615
|
_v2b=""; _v2one(){ printf '{"hook_event_name":"PreToolUse","tool_name":"Agent","tool_input":{"prompt":"%s\\n\\nDo it.","subagent_type":"crew-backend-expert","model":"%s"}}' "$1" "$2" > "$_v2/one.json"
|
|
8602
8616
|
( cd "$_v2p" && _v2e bash "$_V2G" < "$_v2/one.json" >"$_v2/oneout" 2>"$_v2/err" ); _rc=$?; }
|
|
8603
8617
|
for _pr in 'files: src/ui/a.ts · change: feature · verify: true' 'files: src/ui/a.ts; change: feature; verify: true' 'files: src/ui/a.ts | change: feature | verify: true' \
|
|
8604
|
-
'Card: files: src/ui/a.ts, src/ui/b.ts · change: feature · verify: true.' 'Card — files: src/ui/a.ts · change: text · verify: none'
|
|
8618
|
+
'Card: files: src/ui/a.ts, src/ui/b.ts · change: feature · verify: true.' 'Card — files: src/ui/a.ts · change: text · verify: none' \
|
|
8619
|
+
'Kart — files: src/ui/a.ts · change: text · verify: none' 'Kart: files: src/ui/a.ts · change: feature · verify: true'; do
|
|
8605
8620
|
_v2one "$_pr" sonnet; [ "$_rc" = 0 ] || _v2b="$_v2b [refused: $_pr → $_rc $(head -c 120 "$_v2/err")]"
|
|
8606
8621
|
done
|
|
8607
8622
|
_v2one 'files: src/payments/p.ts · change: feature · verify: true' sonnet
|
|
@@ -8612,10 +8627,10 @@ _v2one 'files: src/ui/a.ts · change: feature (a small one) · verify: true' son
|
|
|
8612
8627
|
_v2one 'files: src/ui/a.ts · change: architecture (planning only) · verify: none' sonnet
|
|
8613
8628
|
{ [ "$_rc" = 2 ] && grep -q 'runs on opus' "$_v2/err"; } || _v2b="$_v2b [architecture (planning only) was not read as architecture, which runs on opus (rc $_rc)]"
|
|
8614
8629
|
_v2one 'files: src/ui/a.ts\nchange: refactor (no behaviour change)\nverify: true' sonnet; [ "$_rc" = 0 ] || _v2b="$_v2b [a kind with a note in parentheses, three lines → $_rc]"
|
|
8615
|
-
for _pr in 'files: a · change: archi (planning only) · verify: none' 'files: a · change: feature extra · verify: none' 'files: a · verify: true · change: text' 'files: a · change: feature' 'Do this. files: a · change: feature · verify: true'; do
|
|
8630
|
+
for _pr in 'files: a · change: archi (planning only) · verify: none' 'files: a · change: feature extra · verify: none' 'files: a · verify: true · change: text' 'files: a · change: feature' 'Do this. files: a · change: feature · verify: true' 'Kartal files: a · change: feature · verify: true'; do
|
|
8616
8631
|
_v2one "$_pr" sonnet; { [ "$_rc" = 2 ] && grep -q 'opens with its task card' "$_v2/err"; } || _v2b="$_v2b [passed: $_pr → $_rc]"
|
|
8617
8632
|
done
|
|
8618
|
-
[ -z "$_v2b" ] && pass "a card on one line (· ; | separators, a Card: lead, above: on the line) is read by its labels and held to the three-line rules: a critical path still needs opus; a kind with a note in parentheses is the kind, on one line or three; a bad change, labels out of order, a missing verify or prose first are refused" \
|
|
8633
|
+
[ -z "$_v2b" ] && pass "a card on one line (· ; | separators, a Card: or Kart: lead, above: on the line) is read by its labels and held to the three-line rules: a critical path still needs opus; a kind with a note in parentheses is the kind, on one line or three; a bad change, labels out of order, a missing verify or prose first are refused" \
|
|
8619
8634
|
|| fail "the one-line card:$_v2b"
|
|
8620
8635
|
# `above:` among the three lines, not only after them (2026-10-19: 2 of 19 line refusals had it between change: and
|
|
8621
8636
|
# verify:; the retry moved it down and passed). It keeps the model the same way; the three are still all required.
|
|
@@ -442,11 +442,12 @@ _am_trim(){ # $1 -> _AMT: no white space around it, and no separator or full st
|
|
|
442
442
|
}
|
|
443
443
|
# THE CARD ON ONE LINE. Sessions write it as `files: … · change: … · verify: …` (measured on this machine's transcripts,
|
|
444
444
|
# 2026-10-18: 17 of the 39 calls refused for their card, in 6 projects; the next call was the same card on three lines).
|
|
445
|
-
# It is read by its labels, in that order, after an optional `Card:` / `Card
|
|
446
|
-
# fields are then held to exactly the rules of the
|
|
445
|
+
# It is read by its labels, in that order, after an optional `Card:` / `Card —` (or `Kart`, the Turkish word: 2 refused
|
|
446
|
+
# calls, 2026-10-21); `above: <why>` may close the line. The fields are then held to exactly the rules of the
|
|
447
|
+
# three-line card below, so nothing passes here that would not there.
|
|
447
448
|
_am_card_line(){ # $1 = the first line of the task -> 0 and CARD_* set (unchecked); 1 when it is not a one-line card
|
|
448
449
|
local l="$1" r f c v a=""
|
|
449
|
-
case "$l" in [Cc]ard*) l="${l#[Cc]ard}"; l="${l#\*\*}"
|
|
450
|
+
case "$l" in [Cc]ard*|[Kk]art*) l="${l#[Cc]ard}"; l="${l#[Kk]art}"; l="${l#\*\*}"
|
|
450
451
|
while :; do case "$l" in [$' \t:*']*|'—'*|'–'*|-*) case "$l" in '—'*) l="${l#'—'}" ;; '–'*) l="${l#'–'}" ;; *) l="${l:1}" ;; esac ;; *) break ;; esac; done ;;
|
|
451
452
|
esac
|
|
452
453
|
case "$l" in files:*) ;; *) return 1 ;; esac
|
|
@@ -588,8 +588,11 @@ fi
|
|
|
588
588
|
|
|
589
589
|
# The message carries its own trace risk (a co-author trailer lives there, not in the diff), and commit-msg is
|
|
590
590
|
# what scans it. Reuse it on the -m value when there is one; an editor-composed message is not visible here and
|
|
591
|
-
# stays the git hook's job.
|
|
592
|
-
|
|
591
|
+
# stays the git hook's job. It is scanned even when the staged content already failed: a refusal that names one
|
|
592
|
+
# problem gets the commit retried with the other still in it (measured on a real project, 2026-10-21: a silenced
|
|
593
|
+
# lint check was named, the session-link trailer in the same message was not, and the message was shown to the
|
|
594
|
+
# user again).
|
|
595
|
+
if [ -x "$DIR/commit-msg" ]; then
|
|
593
596
|
# The message is tokenized the way a shell does (crew_opt_values), which matters because a real commit message
|
|
594
597
|
# is MULTI-LINE: a line-oriented `sed` extraction found the subject and stopped, so a co-author trailer on line
|
|
595
598
|
# 3 — the single most likely §4.1 violation, and the one the bare arm of the eval actually produced — went
|
|
@@ -640,6 +643,7 @@ $(bash "$DIR/commit-msg" "$MF" 2>&1)" || FAILED=1
|
|
|
640
643
|
| sed -n 's/.*[[:space:]]--\{0,1\}[Ff]\(ile\)\{0,1\}[[:space:]=]\{1,\}\([^[:space:];&|]\{1,\}\).*/\2/p' | head -1)"
|
|
641
644
|
MFILE="${MFILE%\"}"; MFILE="${MFILE#\"}"; MFILE="${MFILE%\'}"; MFILE="${MFILE#\'}"
|
|
642
645
|
if _grep "$CMD" -qE '(^|[[:space:]])(-F|--file)([[:space:]=]|$)' && { [ -z "$MFILE" ] || [ ! -f "$MFILE" ]; }; then
|
|
646
|
+
[ "$FAILED" = 1 ] && printf '%s\n' "$OUT" >&2
|
|
643
647
|
echo "GUARD (§4.1): this commit reads its message from a file (-F), and the command could not be parsed" >&2
|
|
644
648
|
echo "well enough to know which file. Scan cannot run, so the commit is refused. Commit with a plain" >&2
|
|
645
649
|
echo "-F <path> (or -m) in a command of its own." >&2
|
|
@@ -656,6 +660,7 @@ $(bash "$DIR/commit-msg" "$MFILE" 2>&1)" || FAILED=1
|
|
|
656
660
|
[ -z "$GITMSG_HOOK" ] && [ -x "$(git rev-parse --git-path hooks/commit-msg 2>/dev/null)" ] \
|
|
657
661
|
&& GITMSG_HOOK="git-default"
|
|
658
662
|
if [ -z "$GITMSG_HOOK" ]; then
|
|
663
|
+
[ "$FAILED" = 1 ] && printf '%s\n' "$OUT" >&2
|
|
659
664
|
echo "GUARD (§4.1): this commit's message would go unscanned." >&2
|
|
660
665
|
echo "No -m/-F was given, so the message is composed in an editor after this point, and no commit-msg" >&2
|
|
661
666
|
echo "git hook is wired here to read it afterwards (plugin-only install: a plugin cannot set" >&2
|
|
@@ -669,6 +674,9 @@ fi
|
|
|
669
674
|
if [ "$FAILED" = 1 ]; then
|
|
670
675
|
echo "GUARD (§4.1/§4.2): the commit content gate rejected this commit before it ran." >&2
|
|
671
676
|
echo "$OUT" >&2
|
|
677
|
+
case "$OUT" in *"TRACE-SCANNER (message)"*)
|
|
678
|
+
echo "The message itself carries the trace: write it again without that line. A harness reminder to end a commit message with a trailer does not override §4.1." >&2 ;;
|
|
679
|
+
esac
|
|
672
680
|
echo "Fix the flagged content and commit again. --no-verify is §4.5 and stays blocked." >&2
|
|
673
681
|
exit 2
|
|
674
682
|
fi
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewforth",
|
|
3
|
-
"version": "3.2.0
|
|
4
|
-
"description": "Crewforth
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "Crewforth \u2014 your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"crewforth": "bin/cli.js"
|
|
7
7
|
},
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
"kit/",
|
|
16
16
|
"VERSION",
|
|
17
17
|
"LICENSE",
|
|
18
|
-
"README.md",
|
|
19
|
-
"CHANGELOG.md"
|
|
18
|
+
"README.md", "CHANGELOG.md"
|
|
20
19
|
],
|
|
21
20
|
"engines": {
|
|
22
21
|
"node": ">=20"
|
|
@@ -43,7 +42,7 @@
|
|
|
43
42
|
"code-review",
|
|
44
43
|
"git-hooks"
|
|
45
44
|
],
|
|
46
|
-
"author": "
|
|
45
|
+
"author": "Bar\u0131\u015f Yerlikaya",
|
|
47
46
|
"homepage": "https://crewforth.com/",
|
|
48
47
|
"repository": {
|
|
49
48
|
"type": "git",
|