crewforth 3.2.0 → 3.3.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.
@@ -15,3 +15,75 @@ crew_verify_split(){ # $1 = the command, $2 = the project root -> _AMVD the fol
15
15
  [ -n "$r" ] && [ -d "$root/$d" ] || return 0
16
16
  _AMVD="$d"; _AMVC="$r"
17
17
  }
18
+
19
+ # The verify commands the hook was refused for want of a rule, read from the record of results: one definition, for
20
+ # doctor, which lists them, and for /crew-allow-verify (hooks/prompt-approval.sh), which adds the one the user names.
21
+ # Two lists, one entry per line: a command (the one a `verify <command>` line would allow, without its cd), and a line
22
+ # that is no command at all: a parenthesis or ` + `, or a first word that is no path, no assignment and nothing this
23
+ # machine runs ("measured numbers"). A command a `verify` line of crew-model-rules already allows is left out: it was
24
+ # refused before the user allowed it. Each is counted every time it was refused, and listed once.
25
+ crew_verify_blocked(){ # $1 = project root -> _VB_N _VB_CMDS, _VB_PN _VB_SENT (lists end in a newline)
26
+ local root="${1:-.}" f l vb vw vc v1 vs nl='
27
+ ' allowed=""
28
+ _VB_N=0; _VB_CMDS=""; _VB_PN=0; _VB_SENT=""
29
+ if [ -f "$root/.claude/crew-model-rules" ]; then
30
+ while IFS= read -r l || [ -n "$l" ]; do l="${l%$'\r'}"; case "$l" in 'verify '*) allowed="$allowed${l#verify }$nl" ;; esac; done < "$root/.claude/crew-model-rules"
31
+ fi
32
+ for f in "$root"/.claude/state/crew-model/results/*; do
33
+ [ -f "$f" ] || continue
34
+ vb=0; vw=""; vc=""
35
+ while IFS= read -r l || [ -n "$l" ]; do
36
+ case "$l" in verify=blocked) vb=1 ;; whynot=*) vw="${l#whynot=}" ;; cmd=*) vc="${l#cmd=}" ;; esac
37
+ done < "$f"
38
+ [ "$vb" = 1 ] || continue
39
+ # Both ways of saying "nothing allows it": in auto/bypassPermissions (not on the runner list) and in the other
40
+ # modes (Claude Code itself would not run it unallowed). Measured on Windows: the second was not listed at all.
41
+ case "$vw" in *"not a test or build command on this hook's list"*|*"does not run a command the user has not allowed"*) ;; *) continue ;; esac
42
+ # A rule names the command alone: `cd sub && ./check.sh` is allowed by `verify ./check.sh` (measured on Windows:
43
+ # the line with the cd in it did not allow it).
44
+ crew_verify_split "$vc" "$root"; vc="$_AMVC"
45
+ [ -n "$vc" ] || continue
46
+ case "$nl$allowed" in *"$nl$vc$nl"*) continue ;; esac
47
+ v1="${vc%% *}"; vs=0
48
+ case "$vc" in *'('*|*' + '*) vs=1 ;; esac
49
+ case "$v1" in */*|*=*) ;; *) command -v -- "$v1" >/dev/null 2>&1 || vs=1 ;; esac
50
+ # A rule that would allow any command is not offered at all (see crew_verify_open_ended below).
51
+ [ "$vs" = 0 ] && declare -F crew_verify_open_ended >/dev/null && crew_verify_open_ended "$vc" && continue
52
+ if [ "$vs" = 1 ]; then
53
+ # Whole for a command (a rule cut short allows nothing); a sentence is shortened, at a word.
54
+ [ "${#vc}" -gt 80 ] && { vc="${vc:0:80}"; vc="${vc% *} …"; }
55
+ _VB_PN=$((_VB_PN+1)); case "$nl$_VB_SENT" in *"$nl$vc$nl"*) ;; *) _VB_SENT="$_VB_SENT$vc$nl" ;; esac
56
+ else
57
+ _VB_N=$((_VB_N+1)); case "$nl$_VB_CMDS" in *"$nl$vc$nl"*) ;; *) _VB_CMDS="$_VB_CMDS$vc$nl" ;; esac
58
+ fi
59
+ done
60
+ return 0
61
+ }
62
+
63
+ # A rule that would let the hook run ANY command. A `verify` rule allows its command and the same command with more
64
+ # words after it (guard-agent-model.sh `_am_starts`), so `verify bash` allows `bash evil.sh` and `verify node -e`
65
+ # allows any script (found in review). Such a rule is neither offered nor written by /crew-allow-verify: an
66
+ # interpreter, a shell, a wrapper that runs what follows it, or a package runner that fetches what it runs, when
67
+ # nothing after it names the file or the task, or when what follows is an option (`bash -c`, `node -e`).
68
+ crew_verify_open_ended(){ # $1 = command -> 0 when the rule would allow any command
69
+ local c="$1" w1 w2 w3 rest
70
+ # Leading assignments and `env` decide nothing about what runs: `env FOO=1 python3` is `python3` (found in review).
71
+ while :; do
72
+ w1="${c%% *}"
73
+ case "$w1" in env|*/env) [ "$c" = "$w1" ] && return 0; c="${c#"$w1" }" ;; [A-Za-z_]*=*) [ "$c" = "$w1" ] && return 0; c="${c#"$w1" }" ;; *) break ;; esac
74
+ done
75
+ rest="${c#"$w1"}"; rest="${rest# }"; w2="${rest%% *}"; rest="${rest#"$w2"}"; rest="${rest# }"; w3="${rest%% *}"
76
+ case "${w1##*/}" in
77
+ python|python2|python3)
78
+ [ -z "$w2" ] && return 0
79
+ # `python3 -m pytest` names the module it runs; `-c`, `-` or a bare `-m` do not.
80
+ case "$w2" in -m) [ -z "$w3" ] && return 0; case "$w3" in -*) return 0 ;; esac; return 1 ;; -*) return 0 ;; esac ;;
81
+ bash|sh|zsh|dash|ksh|fish|pwsh|powershell|cmd|xargs|sudo|doas|nice|nohup|timeout|command|exec|eval|time|watch|node|deno|bun|ruby|perl|php|lua|osascript|npx|pnpx|bunx|curl|wget|rm|git|make|ssh|docker|kubectl)
82
+ [ -z "$w2" ] && return 0
83
+ case "$w2" in -*) return 0 ;; esac ;;
84
+ npm|pnpm|yarn)
85
+ [ -z "$w2" ] && return 0
86
+ case "$w2" in -*|exec|x|dlx|install|i|add|ci|create|init) return 0 ;; esac ;;
87
+ esac
88
+ return 1
89
+ }