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 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
- ## [Unreleased] — 3.2.0
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-rc.8
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 (.trace-allowlist)") s='gevşet (.trace-allowlist)' ;;
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' 'loosen (.trace-allowlist)' 'co-author/sign-off present in git log — may be a convention'
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
- DEC3="$([ "$COAUTHOR" = 1 ] && echo loosen || echo keep)"
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)"
@@ -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'; do
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 —`; `above: <why>` may close the line. The
446
- # fields are then held to exactly the rules of the three-line card below, so nothing passes here that would not there.
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
- if [ "$FAILED" = 0 ] && [ -x "$DIR/commit-msg" ]; then
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-rc.8",
4
- "description": "Crewforth — your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
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": "Barış Yerlikaya",
45
+ "author": "Bar\u0131\u015f Yerlikaya",
47
46
  "homepage": "https://crewforth.com/",
48
47
  "repository": {
49
48
  "type": "git",