crewforth 3.1.0 → 3.1.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 CHANGED
@@ -5,6 +5,19 @@ 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.1.1] — 2026-10-12
9
+
10
+ ### Fixed
11
+
12
+ - **An update could write this machine's path into `.claude/git-shim`, a file projects commit.** The updater knew
13
+ the kit's own hooks directory only when `core.hooksPath` was spelled `.claude/hooks`. Spelled as an absolute path
14
+ (seen in a real project) it was taken for a hook chain of the project's own, and the shim named
15
+ `$ROOT//Users/<name>/…/.claude/hooks`: a path that does not exist, so nothing ran twice, but the user name and
16
+ the folder layout went into the repository. `core.hooksPath` is now read for what it names (the same directory,
17
+ whatever the spelling), a chain inside the repository is named from the root, and a chain outside it is kept in
18
+ the clone's git config (`crewforth.hookschain`) and read by the shim when it runs. A shim an earlier update
19
+ wrote for that case is removed by the next update.
20
+
8
21
  ## [3.1.0] — 2026-10-12
9
22
 
10
23
  ### Security
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0
1
+ 3.1.1
package/adopt.sh CHANGED
@@ -524,12 +524,53 @@ fi
524
524
  rowm 'git' "$GITKIND" # every GITKIND value is a literal with its own table row
525
525
 
526
526
  # existing hook system (decision #5 — single-hooksPath clash with husky/lefthook)
527
- HOOKSYS="none"; SHIM_CHAIN=""
527
+ # WHAT core.hooksPath NAMES, however it is spelled. It was matched as text against `.claude/hooks`, so the same
528
+ # directory written as an absolute path (seen in a real project) was read as a hook chain of the project's own:
529
+ # the shim then named `$ROOT//Users/<name>/…/.claude/hooks`, a path that does not exist and that carries the
530
+ # machine's user name into a file projects commit; had it existed, every hook would have run twice.
531
+ # Asked of the file system and of git, not of the spelling: `-ef` says whether two names are one directory, and
532
+ # `git rev-parse --show-prefix` says where a directory is inside the repository.
533
+ _hp_class(){ # $1 = a hooks path as configured -> HP_KIND: none | own | shim | in (HP_REL, from the root) | out (HP_ABS)
534
+ local p="$1" top pfx r
535
+ HP_KIND=none; HP_REL=""; HP_ABS=""
536
+ [ -n "$p" ] || return 0
537
+ case "$p" in '~/'*) p="$HOME/${p#\~/}" ;; esac
538
+ while :; do case "$p" in ?*/) p="${p%/}" ;; *) break ;; esac; done
539
+ case "$p" in
540
+ /*|[A-Za-z]:[/\\]*)
541
+ if [ -d "$p" ]; then
542
+ if [ -d .claude/hooks ] && [ "$p" -ef .claude/hooks ]; then HP_KIND=own; return 0; fi
543
+ if [ -d .claude/git-shim ] && [ "$p" -ef .claude/git-shim ]; then HP_KIND=shim; return 0; fi
544
+ top="$(git -C "$p" rev-parse --show-toplevel 2>/dev/null || true)"
545
+ if [ -n "$top" ] && [ "$top" -ef . ]; then
546
+ pfx="$(git -C "$p" rev-parse --show-prefix 2>/dev/null || true)"; pfx="${pfx%/}"
547
+ if [ -n "$pfx" ]; then HP_KIND=in; HP_REL="$pfx"; return 0; fi
548
+ fi
549
+ fi
550
+ # Not there, or git cannot place it (.git/hooks is not in the work tree): the spelling is what is left.
551
+ for r in "$PWD" "$(pwd -P 2>/dev/null || true)"; do
552
+ [ -n "$r" ] || continue
553
+ case "$p" in "$r"/?*) p="${p#"$r"/}"
554
+ case "$p" in .claude/hooks) HP_KIND=own ;; .claude/git-shim) HP_KIND=shim ;; *) HP_KIND=in; HP_REL="$p" ;; esac
555
+ return 0 ;;
556
+ esac
557
+ done
558
+ HP_KIND=out; HP_ABS="$p" ;;
559
+ *)
560
+ while :; do case "$p" in ./?*) p="${p#./}" ;; *) break ;; esac; done
561
+ case "$p" in .claude/hooks) HP_KIND=own; return 0 ;; .claude/git-shim) HP_KIND=shim; return 0 ;; esac
562
+ if [ -d "$p" ] && [ -d .claude/hooks ] && [ "$p" -ef .claude/hooks ]; then HP_KIND=own; return 0; fi
563
+ if [ -d "$p" ] && [ -d .claude/git-shim ] && [ "$p" -ef .claude/git-shim ]; then HP_KIND=shim; return 0; fi
564
+ HP_KIND=in; HP_REL="$p" ;;
565
+ esac
566
+ }
567
+ HOOKSYS="none"; SHIM_CHAIN=""; SHIM_OUT=0; CUR_OUT=0
528
568
  CURHP="$(git config --get core.hooksPath 2>/dev/null || true)"
529
- case "$CURHP" in
530
- "") : ;;
531
- .claude/hooks) HOOKSYS="Crewforth (already armed)"; CURHP="" ;; # kit's OWN path — not a foreign chain (re-adopt must not shim itself)
532
- .claude/git-shim)
569
+ _hp_class "$CURHP"
570
+ case "$HP_KIND" in
571
+ none) : ;;
572
+ own) HOOKSYS="Crewforth (already armed)"; CURHP="" ;; # kit's OWN path — not a foreign chain (re-adopt must not shim itself)
573
+ shim)
533
574
  # Crewforth's shim, from an earlier run: git is pointed at it, and IT holds the project's own chain. That chain
534
575
  # is read back from the shim, or the next lines would see "no chain" and point git straight at .claude/hooks:
535
576
  # measured, the second update of a project with core.hooksPath=myhooks left its own pre-commit never running
@@ -540,14 +581,23 @@ case "$CURHP" in
540
581
  [ -f "$_sf" ] || continue
541
582
  while IFS= read -r _sl || [ -n "$_sl" ]; do
542
583
  _sl="${_sl%$'\r'}"
584
+ # (A shim written before this was fixed can hold `P="$ROOT//abs/path/$H"`: what is left after `$ROOT/` is
585
+ # the absolute path, and it is classed like any other.)
543
586
  case "$_sl" in 'P="'*'/$H"') _sl="${_sl#P=\"}"; _sl="${_sl%/\$H\"}"; _sl="${_sl#\$ROOT/}"
544
- case "$_sl" in ''|.claude/hooks|.claude/git-shim) ;; *) SHIM_CHAIN="$_sl" ;; esac ;;
587
+ _hp_class "$_sl"
588
+ case "$HP_KIND" in in) SHIM_CHAIN="$HP_REL"; SHIM_OUT=0 ;; out) SHIM_CHAIN="$HP_ABS"; SHIM_OUT=1 ;; esac ;;
545
589
  esac
546
590
  done < "$_sf"
547
591
  [ -n "$SHIM_CHAIN" ] && break
548
592
  done
593
+ # A chain outside the repository is not written in the shim: it is in this clone's git config.
594
+ if [ -z "$SHIM_CHAIN" ]; then
595
+ _hp_class "$(git config --get crewforth.hookschain 2>/dev/null || true)"
596
+ case "$HP_KIND" in in) SHIM_CHAIN="$HP_REL"; SHIM_OUT=0 ;; out) SHIM_CHAIN="$HP_ABS"; SHIM_OUT=1 ;; esac
597
+ fi
549
598
  [ -n "$SHIM_CHAIN" ] && HOOKSYS="core.hooksPath=$SHIM_CHAIN" ;;
550
- *) HOOKSYS="core.hooksPath=$CURHP" ;;
599
+ in) CURHP="$HP_REL"; HOOKSYS="core.hooksPath=$CURHP" ;;
600
+ out) CURHP="$HP_ABS"; CUR_OUT=1; HOOKSYS="core.hooksPath=$CURHP" ;;
551
601
  esac
552
602
  [ -d .husky ] && HOOKSYS="husky (.husky/)"
553
603
  { [ -f lefthook.yml ] || [ -f .lefthook.yml ]; } && HOOKSYS="lefthook"
@@ -1627,9 +1677,9 @@ else HAND_SET="hook-aware merge (Crewforth hooks REFRESHED to current — new ev
1627
1677
  h1m 'Stage 4 — arm the git gates (SHIM via husky) + PROOF'
1628
1678
 
1629
1679
  # 4a) location of the existing hook chain (the shim calls this too)
1630
- ORIG_HOOKS=""
1631
- if [ -n "$CURHP" ]; then ORIG_HOOKS="$CURHP"
1632
- elif [ -n "$SHIM_CHAIN" ]; then ORIG_HOOKS="$SHIM_CHAIN" # the chain the shim of an earlier run already runs
1680
+ ORIG_HOOKS=""; ORIG_OUT=0
1681
+ if [ -n "$CURHP" ]; then ORIG_HOOKS="$CURHP"; ORIG_OUT="$CUR_OUT"
1682
+ elif [ -n "$SHIM_CHAIN" ]; then ORIG_HOOKS="$SHIM_CHAIN"; ORIG_OUT="$SHIM_OUT" # the chain the shim of an earlier run already runs
1633
1683
  elif [ -d .husky ]; then ORIG_HOOKS=".husky"
1634
1684
  elif [ -x .git/hooks/pre-commit ] || [ -x .git/hooks/commit-msg ]; then ORIG_HOOKS=".git/hooks"; fi
1635
1685
  # never shim the kit onto its OWN hooks (re-adopt) — the shim would exec itself and recurse on every commit
@@ -1637,9 +1687,28 @@ case "$ORIG_HOOKS" in .claude/hooks|.claude/git-shim) ORIG_HOOKS="" ;; esac
1637
1687
 
1638
1688
  if [ -z "$ORIG_HOOKS" ]; then
1639
1689
  git config core.hooksPath .claude/hooks
1690
+ git config --unset-all crewforth.hookschain 2>/dev/null || true
1691
+ # A shim of an earlier run has no chain to run any more. It is Crewforth's own generated file (its second line
1692
+ # says so), and one written before the fix above can hold a path of this machine: it goes, and nothing else does.
1693
+ for hk in pre-commit commit-msg; do
1694
+ if [ -f ".claude/git-shim/$hk" ] && grep -q '^# kit git-shim:' ".claude/git-shim/$hk" 2>/dev/null; then rm -f ".claude/git-shim/$hk"; fi
1695
+ done
1696
+ [ -d .claude/git-shim ] && rmdir .claude/git-shim 2>/dev/null || true
1640
1697
  say 'core.hooksPath -> .claude/hooks (no existing hook chain)'
1641
1698
  else
1642
1699
  mkdir -p .claude/git-shim
1700
+ # THE SHIM HOLDS NO PATH OF THIS MACHINE. A chain inside the repository is named from the root. One outside it
1701
+ # (a hooks directory in the user's home) is kept in this clone's git config, which is not committed, and the shim
1702
+ # reads it when it runs.
1703
+ if [ "$ORIG_OUT" = 1 ]; then
1704
+ git config crewforth.hookschain "$ORIG_HOOKS"
1705
+ _shp='C="$(git config --get crewforth.hookschain 2>/dev/null || true)"
1706
+ P=""
1707
+ [ -z "$C" ] || P="$C/$H"'
1708
+ else
1709
+ git config --unset-all crewforth.hookschain 2>/dev/null || true
1710
+ _shp="P=\"\$ROOT/$ORIG_HOOKS/\$H\""
1711
+ fi
1643
1712
  for hk in pre-commit commit-msg; do
1644
1713
  cat > ".claude/git-shim/$hk" <<SHIM
1645
1714
  #!/usr/bin/env bash
@@ -1648,7 +1717,7 @@ set -e
1648
1717
  H="\$(basename "\$0")"
1649
1718
  ROOT="\$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
1650
1719
  K="\$ROOT/.claude/hooks/\$H"; [ -x "\$K" ] && "\$K" "\$@"
1651
- P="\$ROOT/$ORIG_HOOKS/\$H"
1720
+ $_shp
1652
1721
  if [ -x "\$P" ]; then "\$P" "\$@"
1653
1722
  elif [ -f "\$P" ]; then bash "\$P" "\$@"; fi
1654
1723
  exit 0
@@ -1,9 +1,10 @@
1
- # Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.0.4 (below VERSION 3.1.0) — do not edit by hand.
1
+ # Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.1.0 (below VERSION 3.1.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
5
  .claude/AGENT_TEMPLATE.md 3fdc551d221ee3665b21ef8cc3563de861a9abe6 v1.4.0 v1.6.3
6
6
  .claude/AGENT_TEMPLATE.md 686fed306c873b45c245c77a95a8e6de6ee383d5 v1.7.0 v2.11.0
7
+ .claude/AGENT_TEMPLATE.md 7ab19417b6ff3ac732e2c894be17ffb3ec091f7d v3.1.0 v3.1.0
7
8
  .claude/AGENT_TEMPLATE.md 981df9ea27b6f1c5ab5cb509d40875a07603858d v2.12.0 v2.13.0
8
9
  .claude/AGENT_TEMPLATE.md b38b9c9b799156e07280ca886230524563acc75c v1.0.5 v1.3.0
9
10
  .claude/AGENT_TEMPLATE.md faec09e45543b75763c43bb24114a7b6355649ec v1.0.0 v1.0.4
@@ -25,6 +26,7 @@
25
26
  .claude/DISCIPLINE.md 76d73a4e25c559ea361618862cde8480b1a6d8c2 v3.0.0 v3.0.0
26
27
  .claude/DISCIPLINE.md b1ab7549b571dd2fc80d5777e0ae3185d29bd7b6 v2.13.0 v2.13.0
27
28
  .claude/DISCIPLINE.md b7e1066451a8590e0a2607ad0a6fe80b21599c53 v1.1.3 v1.1.8
29
+ .claude/DISCIPLINE.md b90a067ee323a08146862b889293f8feb49f8e7e v3.1.0 v3.1.0
28
30
  .claude/DISCIPLINE.md bf062c2b51a9cf3cc4d0caac389c148cf6fdc977 v2.12.0 v2.12.0
29
31
  .claude/DISCIPLINE.md c693f1bd5832ce570058c6eedcacb5dff13d89e8 v1.10.0 v1.10.0
30
32
  .claude/DISCIPLINE.md d63a01a183c9a80d739c840a511d338dc2765820 v1.4.0 v1.4.4
@@ -41,3 +43,4 @@
41
43
  .claude/README.md 97d4dd1f7f1695052cc1d06cc43f5bcdc603c989 v1.0.0 v1.0.4
42
44
  .claude/README.md a3419891cfda7385564008e4befc1007bc5364cb v1.1.1 v1.7.0
43
45
  .claude/README.md a3b76e0fa31267610d28b742c122a962f6db59c4 v3.0.1 v3.0.4
46
+ .claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.1.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewforth",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
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"