crewforth 3.2.1 → 3.4.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 +115 -0
- package/README.md +6 -2
- package/VERSION +1 -1
- package/adopt.sh +7 -3
- package/bin/cli.js +55 -0
- package/kit/AGENT_TEMPLATE.md +1 -0
- package/kit/CLAUDE.md +9 -8
- package/kit/agents/crew-session-manager.md +5 -3
- package/kit/eval/doctor.sh +99 -45
- package/kit/eval/golden-routing.txt +3 -0
- package/kit/eval/lib/verify-cmd.sh +72 -0
- package/kit/eval/smoke-test.sh +460 -83
- package/kit/hooks/context-usage.sh +6 -3
- package/kit/hooks/guard-agent-model.sh +29 -8
- package/kit/hooks/guard-bash.sh +127 -2
- package/kit/hooks/guard-schedule.sh +4 -1
- package/kit/hooks/pre-commit +21 -0
- package/kit/hooks/prompt-approval.sh +80 -3
- package/kit/hooks/route-hint.sh +18 -1
- package/kit/hooks/session-guard.sh +41 -14
- package/kit/hooks/session-rehydrate.sh +6 -2
- package/kit/hooks/worktree-provision.sh +91 -0
- package/kit/owned-blobs.tsv +6 -4
- package/kit/settings.json +22 -0
- package/kit/skills/commit-message/SKILL.md +5 -0
- package/kit/skills/crew-allow-verify/SKILL.md +30 -0
- package/kit/skills/crew-doctor/SKILL.md +3 -1
- package/kit/skills/handoff/SKILL.md +3 -2
- package/kit/skills/trace-scan/SKILL.md +4 -3
- package/kit/studio/server/lib/model-outcomes.js +7 -1
- package/package.json +1 -1
- package/start.sh +6 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PreToolUse + PostToolUse on EnterWorktree — a worktree the session enters carries Crewforth with it.
|
|
3
|
+
#
|
|
4
|
+
# Why: `.claude/` is gitignored in a fresh install, so a worktree made with `git worktree add ../x` has none. While
|
|
5
|
+
# the session that entered it lives, its hooks keep running from the main checkout. But Claude Code records the
|
|
6
|
+
# session under the worktree once it enters one, and a session resumed there reads its settings from the worktree:
|
|
7
|
+
# with no `.claude/settings.json` there, NO hook is registered — no commit approval, no write gate, no fill line.
|
|
8
|
+
# Measured on Claude Code 2.1.309: a resumed session in such a worktree ran no project hook (0 of 1), the same
|
|
9
|
+
# worktree with `.claude/` copied in ran it (1 of 1). A field session ran four and a half hours that way, printing a
|
|
10
|
+
# fill it had made up. A worktree Claude Code creates under `.claude/worktrees/` is not affected (measured: the
|
|
11
|
+
# resumed session there still ran the main checkout's hooks), so it is left alone.
|
|
12
|
+
#
|
|
13
|
+
# What it does: before the move (PreToolUse, when the call names an existing path) and after it (PostToolUse, the
|
|
14
|
+
# path the tool reports), a linked worktree of THIS repository with no `.claude/settings.json` gets a copy of this
|
|
15
|
+
# checkout's `.claude/`, minus what belongs to one checkout only (`worktrees/`, `state/`, the review record, the
|
|
16
|
+
# gate log). If the copy cannot be made, the PreToolUse call is refused (exit 2) with the reason, and the
|
|
17
|
+
# PostToolUse call tells the user and the model, since the move already happened.
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
# ---- CREW-FAILCLOSED ---------------------------------------------------------------------------------------
|
|
20
|
+
# A gate that stops on an error of its own must refuse the call, not let it through. Claude Code blocks a tool call
|
|
21
|
+
# on exit 2 only; a hook that dies with 1, or runs on past the rule that broke, has allowed it. Both happened in
|
|
22
|
+
# one defect (measured, bash 5): a local array declared and not set is "unbound" under `set -u`, the rule that read
|
|
23
|
+
# it was abandoned in the middle, the script went on with the NEXT top-level command and left with 0, and
|
|
24
|
+
# `rm .claude/hooks/pre-commit` passed. bash does one of two things with such an error, by the kind of error:
|
|
25
|
+
# * it leaves the shell with a status that is neither 0 nor 2 -> the EXIT trap below turns that into a refusal;
|
|
26
|
+
# * it abandons the top-level command it was in and continues -> the whole gate IS one top-level command,
|
|
27
|
+
# _gate_main, which only ever ends by `exit`. If it RETURNS, it was abandoned, and the line after it refuses.
|
|
28
|
+
# Not covered, and said so: an error inside a command substitution (only that subshell ends), and a command
|
|
29
|
+
# that is not found (status 127, the script continues inside the same command).
|
|
30
|
+
# Byte-identical in every PreToolUse gate; the suite pins it. No process is started.
|
|
31
|
+
_crew_stop(){ # $1 = what happened
|
|
32
|
+
declare -F gatelog >/dev/null 2>&1 && gatelog BLOCK 4.5 "the gate stopped on an error of its own"
|
|
33
|
+
echo "GUARD (§4.5): this gate stopped on an error of its own ($1) before it finished judging the call, so the call is refused." >&2
|
|
34
|
+
echo "Nothing about the call itself was found. The error is the line above these two; it is a defect in Crewforth, not in what you ran. Run the doctor (bash .claude/eval/doctor.sh, Bash tool, not PowerShell) and report it." >&2
|
|
35
|
+
exit 2
|
|
36
|
+
}
|
|
37
|
+
trap '_crew_rc=$?; trap - EXIT; case "$_crew_rc" in 0|2) exit "$_crew_rc" ;; esac; _crew_stop "exit status $_crew_rc"' EXIT
|
|
38
|
+
# ---- /CREW-FAILCLOSED
|
|
39
|
+
unq(){ local p="${1#\"}"; p="${p%\"}"; p="${p//\\\\//}"; p="${p//\\//}"; printf '%s' "$p"; } # JSON string -> path; \ -> /
|
|
40
|
+
_gate_main(){
|
|
41
|
+
local IN HERE SRC READER V EV P_IN P_OUT T MINE THEIRS TOP ERR e WHY SAFE
|
|
42
|
+
IFS= read -r -d '' IN || true
|
|
43
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
44
|
+
SRC="$(cd "$HERE/.." && pwd)" # this checkout's .claude/
|
|
45
|
+
READER="$SRC/eval/lib/settings-json.awk"
|
|
46
|
+
[ -f "$READER" ] || exit 0 # no reader, nothing to read the payload with: fail open
|
|
47
|
+
|
|
48
|
+
V="$(printf '%s' "$IN" | awk -v op=gets -v path='hook_event_name|tool_input.path|tool_response.worktreePath' -f "$READER" - 2>/dev/null)"
|
|
49
|
+
EV="${V%%$'\t'*}"; V="${V#*$'\t'}"; P_IN="${V%%$'\t'*}"; V="${V#*$'\t'}"; P_OUT="${V%%$'\t'*}"
|
|
50
|
+
EV="$(unq "$EV")"
|
|
51
|
+
case "$EV" in
|
|
52
|
+
PreToolUse) T="$(unq "$P_IN")" ;;
|
|
53
|
+
PostToolUse) T="$(unq "$P_OUT")" ;;
|
|
54
|
+
*) exit 0 ;;
|
|
55
|
+
esac
|
|
56
|
+
[ -n "$T" ] && [ -d "$T" ] || exit 0 # no path (Claude creates one under .claude/worktrees/) or none yet
|
|
57
|
+
|
|
58
|
+
# Only a linked worktree of THIS repository, asked of git rather than compared as spelled (C:/ against /c/).
|
|
59
|
+
MINE="$(git -C "$SRC/.." rev-parse --git-common-dir 2>/dev/null)" || exit 0
|
|
60
|
+
THEIRS="$(git -C "$T" rev-parse --git-common-dir 2>/dev/null)" || exit 0
|
|
61
|
+
case "$MINE" in /*|?:*) ;; *) MINE="$SRC/../$MINE" ;; esac
|
|
62
|
+
case "$THEIRS" in /*|?:*) ;; *) THEIRS="$T/$THEIRS" ;; esac
|
|
63
|
+
[ "$MINE" -ef "$THEIRS" ] || exit 0
|
|
64
|
+
TOP="$(git -C "$T" rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
|
65
|
+
[ "$TOP" -ef "$SRC/.." ] && exit 0 # the main checkout itself
|
|
66
|
+
case "$(cd "$TOP" && pwd -P)/" in "$(cd "$SRC" && pwd -P)/worktrees/"*) exit 0 ;; esac # Claude Code's own: unaffected
|
|
67
|
+
[ -f "$TOP/.claude/settings.json" ] && exit 0 # already carries it (or .claude/ is tracked)
|
|
68
|
+
|
|
69
|
+
ERR=""
|
|
70
|
+
mkdir -p "$TOP/.claude" 2>/dev/null || ERR="could not create $TOP/.claude"
|
|
71
|
+
if [ -z "$ERR" ]; then
|
|
72
|
+
for e in "$SRC"/* "$SRC"/.[!.]*; do
|
|
73
|
+
[ -e "$e" ] || continue
|
|
74
|
+
case "${e##*/}" in worktrees|state|review-pass.json|gate-log.tsv) continue ;; esac
|
|
75
|
+
cp -R "$e" "$TOP/.claude/" 2>/dev/null || { ERR="could not copy ${e##*/}"; break; }
|
|
76
|
+
done
|
|
77
|
+
fi
|
|
78
|
+
[ -z "$ERR" ] && { [ -f "$TOP/.claude/settings.json" ] && [ -d "$TOP/.claude/hooks" ] || ERR="the copy has no settings.json or hooks/"; }
|
|
79
|
+
[ -z "$ERR" ] && exit 0
|
|
80
|
+
|
|
81
|
+
WHY="Crewforth could not put its .claude/ into the worktree $TOP ($ERR). A session resumed there would load no Crewforth hook: no commit approval, no write gate, no fill reading."
|
|
82
|
+
if [ "$EV" = PreToolUse ]; then
|
|
83
|
+
echo "$WHY Fix the cause above, then enter the worktree again." >&2
|
|
84
|
+
exit 2
|
|
85
|
+
fi
|
|
86
|
+
SAFE="${WHY//\\/\\\\}"; SAFE="${SAFE//\"/\\\"}"
|
|
87
|
+
printf '{"systemMessage":"⚠️ %s","hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"%s Tell the user, and do not resume this session in that worktree until it is fixed."}}\n' "$SAFE" "$SAFE"
|
|
88
|
+
exit 0
|
|
89
|
+
}
|
|
90
|
+
_gate_main "$@"
|
|
91
|
+
_crew_stop "a command of the gate was abandoned"
|
package/kit/owned-blobs.tsv
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.
|
|
1
|
+
# Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.3.0 (below VERSION 3.4.0) — 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
|
-
.claude/AGENT_TEMPLATE.md 2c8223d811245e3627a3011bb64fb9e4b0bf3b94 v3.2.0 v3.2.
|
|
5
|
+
.claude/AGENT_TEMPLATE.md 2c8223d811245e3627a3011bb64fb9e4b0bf3b94 v3.2.0 v3.2.1
|
|
6
6
|
.claude/AGENT_TEMPLATE.md 3fdc551d221ee3665b21ef8cc3563de861a9abe6 v1.4.0 v1.6.3
|
|
7
|
+
.claude/AGENT_TEMPLATE.md 59d3af63b0480696dec38136e124bd582066fb16 v3.3.0 v3.3.0
|
|
7
8
|
.claude/AGENT_TEMPLATE.md 686fed306c873b45c245c77a95a8e6de6ee383d5 v1.7.0 v2.11.0
|
|
8
9
|
.claude/AGENT_TEMPLATE.md 7ab19417b6ff3ac732e2c894be17ffb3ec091f7d v3.1.0 v3.1.1
|
|
9
10
|
.claude/AGENT_TEMPLATE.md 981df9ea27b6f1c5ab5cb509d40875a07603858d v2.12.0 v2.13.0
|
|
@@ -29,8 +30,9 @@
|
|
|
29
30
|
.claude/DISCIPLINE.md b7e1066451a8590e0a2607ad0a6fe80b21599c53 v1.1.3 v1.1.8
|
|
30
31
|
.claude/DISCIPLINE.md b90a067ee323a08146862b889293f8feb49f8e7e v3.1.0 v3.1.1
|
|
31
32
|
.claude/DISCIPLINE.md bf062c2b51a9cf3cc4d0caac389c148cf6fdc977 v2.12.0 v2.12.0
|
|
33
|
+
.claude/DISCIPLINE.md c04f53866b84c54aa0e60089cb54cdc7c9aa8df1 v3.3.0 v3.3.0
|
|
32
34
|
.claude/DISCIPLINE.md c693f1bd5832ce570058c6eedcacb5dff13d89e8 v1.10.0 v1.10.0
|
|
33
|
-
.claude/DISCIPLINE.md cf881a5229d85850194a98b8d0d0438a8eaaa9d6 v3.2.0 v3.2.
|
|
35
|
+
.claude/DISCIPLINE.md cf881a5229d85850194a98b8d0d0438a8eaaa9d6 v3.2.0 v3.2.1
|
|
34
36
|
.claude/DISCIPLINE.md d63a01a183c9a80d739c840a511d338dc2765820 v1.4.0 v1.4.4
|
|
35
37
|
.claude/DISCIPLINE.md dc1c41baa3a8b7d637214bc78553a78d94d14ac9 v1.6.2 v1.6.3
|
|
36
38
|
.claude/DISCIPLINE.md e42aefc9c1ffaeace8e5712d92df780959212280 v2.0.0 v2.1.0
|
|
@@ -45,4 +47,4 @@
|
|
|
45
47
|
.claude/README.md 97d4dd1f7f1695052cc1d06cc43f5bcdc603c989 v1.0.0 v1.0.4
|
|
46
48
|
.claude/README.md a3419891cfda7385564008e4befc1007bc5364cb v1.1.1 v1.7.0
|
|
47
49
|
.claude/README.md a3b76e0fa31267610d28b742c122a962f6db59c4 v3.0.1 v3.0.4
|
|
48
|
-
.claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.
|
|
50
|
+
.claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.3.0
|
package/kit/settings.json
CHANGED
|
@@ -119,6 +119,17 @@
|
|
|
119
119
|
"timeout": 600
|
|
120
120
|
}
|
|
121
121
|
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"matcher": "EnterWorktree",
|
|
125
|
+
"hooks": [
|
|
126
|
+
{
|
|
127
|
+
"type": "command",
|
|
128
|
+
"shell": "bash",
|
|
129
|
+
"command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/worktree-provision.sh",
|
|
130
|
+
"timeout": 600
|
|
131
|
+
}
|
|
132
|
+
]
|
|
122
133
|
}
|
|
123
134
|
],
|
|
124
135
|
"UserPromptSubmit": [
|
|
@@ -220,6 +231,17 @@
|
|
|
220
231
|
"timeout": 60
|
|
221
232
|
}
|
|
222
233
|
]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"matcher": "EnterWorktree",
|
|
237
|
+
"hooks": [
|
|
238
|
+
{
|
|
239
|
+
"type": "command",
|
|
240
|
+
"shell": "bash",
|
|
241
|
+
"command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/worktree-provision.sh",
|
|
242
|
+
"timeout": 60
|
|
243
|
+
}
|
|
244
|
+
]
|
|
223
245
|
}
|
|
224
246
|
],
|
|
225
247
|
"PreCompact": [
|
|
@@ -63,6 +63,11 @@ git add <specific/file>
|
|
|
63
63
|
```
|
|
64
64
|
Each commit focused on a single topic; "feat + fix + refactor" doesn't belong in one commit.
|
|
65
65
|
|
|
66
|
+
**The lines that describe a change belong to its commit**: its CHANGELOG entry, its README line, its docs. They are
|
|
67
|
+
part of the same logical change, not a second one, so stage them with it. A separate "docs: changelog" commit after
|
|
68
|
+
the code is a second commit, and so a second approval (§4.4): the approval covers what is staged and HEAD at that
|
|
69
|
+
moment, and a commit added later is not covered by it.
|
|
70
|
+
|
|
66
71
|
## Good / bad
|
|
67
72
|
|
|
68
73
|
Worked good-vs-bad commit examples: **`references/examples.md`**.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crew-allow-verify
|
|
3
|
+
description: Allow a verify command the hook was refused for want of a rule, or list the ones refused.
|
|
4
|
+
argument-hint: "[<command>] (nothing: list the refused ones)"
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
metadata:
|
|
7
|
+
kind: command
|
|
8
|
+
---
|
|
9
|
+
# /crew-allow-verify
|
|
10
|
+
Argument: $ARGUMENTS
|
|
11
|
+
|
|
12
|
+
The user typed this command. Typing it is the decision: `hooks/prompt-approval.sh` read the message itself. With a
|
|
13
|
+
command after it, it added `verify <command>` to `.claude/crew-model-rules`, the line that lets the hook run that
|
|
14
|
+
command when a card names it as its verify. With nothing after it, it listed the verify commands refused for want of
|
|
15
|
+
such a line, each as the whole message that allows it. Nothing in this text adds anything, and you cannot do this
|
|
16
|
+
for the user: the rules file is closed to you, and a prompt you schedule or hand to another session is refused.
|
|
17
|
+
|
|
18
|
+
Read the line Crewforth added to this turn and say what it says:
|
|
19
|
+
|
|
20
|
+
- **"verify command allowed"**: tell the user which command the hook may now run, from the next agent call.
|
|
21
|
+
- **"verify commands refused for want of a rule"**: show the lines it lists, one per line, as they stand. Each is a
|
|
22
|
+
message only the user can send; do not send one for them.
|
|
23
|
+
- **"verify command NOT allowed"**: tell the user the reason it gives (more than one command, a character it does not
|
|
24
|
+
write, a git commit or push, a rule that would let the hook run any command — `bash`, `node -e`, `npm exec` with
|
|
25
|
+
nothing that names the script or the test — a message too long, a rules file that is a link, the first message of
|
|
26
|
+
a session, a headless session).
|
|
27
|
+
- **No such line**: the message was not the command alone. Say that it has to be the whole message.
|
|
28
|
+
|
|
29
|
+
What this does not touch: a deny rule of the settings still stops the command, the hook still runs it alone with no
|
|
30
|
+
chain, and `/crew-doctor` lists the same refused commands.
|
|
@@ -22,7 +22,9 @@ Verify Crewforth is actually *active* in this project (not just present on disk)
|
|
|
22
22
|
drifted behind the code. Report the gaps as suggestions, not as failures, and never "fix" them unasked — adding
|
|
23
23
|
a devcontainer or an MCP server is the user's call.
|
|
24
24
|
5. Summarise: **healthy**, or the precise fixes applied/needed, plus the readiness score. If it's not a git repo,
|
|
25
|
-
note that the commit-time gates need `git init` + `
|
|
25
|
+
note that the commit-time gates need `git init` + the update (`npx crewforth update`), which arms them.
|
|
26
|
+
Where it lists verify commands never run, each `/crew-allow-verify <command>` line is a message only the user can
|
|
27
|
+
send: show them as they stand, and do not send one.
|
|
26
28
|
If the output contains a line starting with ⭐, pass it to the user verbatim as the LAST line of your summary —
|
|
27
29
|
it prints once per Crewforth version here (the installer has its own once), and it is meant for the user, not for you.
|
|
28
30
|
|
|
@@ -43,8 +43,9 @@ that never landed and has to be written down explicitly this time. Missing scrip
|
|
|
43
43
|
|
|
44
44
|
## Holding a board item? The handover is not finished until the TEAM has it
|
|
45
45
|
`docs/SESSION_STATE.md` is gitignored — it survives your `/clear`, and nobody else can read it. So when a
|
|
46
|
-
`teamboard` claim is open, **the handover is not complete until it is on the board**.
|
|
47
|
-
|
|
46
|
+
`teamboard` claim is open, **the handover is not complete until it is on the board**. Whether one is open is
|
|
47
|
+
read from `bash .claude/hooks/board.sh status` (Bash tool, not PowerShell; a row marked `<- you`), not guessed;
|
|
48
|
+
no board or no such row → the handover says nothing about the board. This is not an optional extra step:
|
|
48
49
|
- Still on it → `board.sh note <id> "<where it stands, and why the rejected path was rejected>"`.
|
|
49
50
|
- Not coming back → `board.sh drop <id> "<same>"`, which puts it back in circulation with your context attached.
|
|
50
51
|
- Anything settled here that changes how OTHER items must be done → `board.sh decide "<what>" "<why + what it
|
|
@@ -31,10 +31,11 @@ git diff --cached --unified=0 | grep -E '^\+' | grep -Ev '^\+\+\+' \
|
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Hook setup (full install only — a plugin cannot set `core.hooksPath`)
|
|
34
|
-
`start.sh` sets `
|
|
35
|
-
under `.claude` → they are in gitignore and stay local (§4.3)
|
|
34
|
+
`start.sh` sets `core.hooksPath` to the absolute path of `.claude/hooks` (if there is a git repo). The hooks live
|
|
35
|
+
under `.claude` → they are in gitignore and stay local (§4.3), so a relative path would leave a linked worktree with
|
|
36
|
+
no hook at all. To do it later for a repo, from its root:
|
|
36
37
|
```bash
|
|
37
|
-
git config core.hooksPath
|
|
38
|
+
git config core.hooksPath "$(git rev-parse --show-toplevel)/.claude/hooks"
|
|
38
39
|
chmod +x .claude/hooks/pre-commit .claude/hooks/commit-msg
|
|
39
40
|
```
|
|
40
41
|
|
|
@@ -145,8 +145,11 @@ const firstTry = (r) => r.verify === 'pass' && r.fixes === 0;
|
|
|
145
145
|
*/
|
|
146
146
|
export function floorOfRow(r, ruleFloors = [], floors = []) {
|
|
147
147
|
let f = rankOf(AGENT_FLOORS[r.agent]);
|
|
148
|
-
|
|
148
|
+
const commitAgent = r.agent === 'crew-commit-agent';
|
|
149
|
+
if (r.verify === 'none' && r.change !== 'text' && !commitAgent) f = Math.max(f, 2);
|
|
149
150
|
if (r.change === 'test-write' || r.change === 'audit') f = Math.max(f, 2);
|
|
151
|
+
// The table's sonnet row, as the gate holds it: feature, fix-known and refactor, except for the commit agent.
|
|
152
|
+
if (!commitAgent && (r.change === 'feature' || r.change === 'fix-known' || r.change === 'refactor')) f = Math.max(f, 2);
|
|
150
153
|
if (r.risk === 'critical' && r.change !== 'test-run') f = Math.max(f, 3);
|
|
151
154
|
const any = (want, v) => want === '*' || want === v;
|
|
152
155
|
for (const x of ruleFloors) if (any(x.agent, r.agent) && any(x.change, r.change) && any(x.risk, r.risk)) f = Math.max(f, rankOf(x.model));
|
|
@@ -250,6 +253,9 @@ export function suggestionsOf(rows, floors = [], loosened = [], need = LOOSEN_AF
|
|
|
250
253
|
const k = keyOf(f);
|
|
251
254
|
// Once for each raise: a floor whose lowering stands is not offered again (the same rule the gate reads it by).
|
|
252
255
|
if (loweringOf(f, loosened)) continue;
|
|
256
|
+
// Not below the class's own floor: /crew-loosen refuses that, since the gate would refuse the lowered model anyway
|
|
257
|
+
// (a feature class raised to sonnet by the record of haiku runs from before its own floor was sonnet).
|
|
258
|
+
if (rankOf(TIERS[tier - 1]) < floorOfRow({ agent: f.agent, change: f.change, risk: f.risk, verify: 'pass' })) continue;
|
|
253
259
|
const passed = passedLatest(rows, k, f.model, Date.parse(f.when ?? '') || 0, need);
|
|
254
260
|
if (!passed) continue;
|
|
255
261
|
out.push({ key: k, agent: f.agent, change: f.change, risk: f.risk, from: f.model, to: TIERS[tier - 1], passed, heldSince: f.when ?? null });
|
package/package.json
CHANGED
package/start.sh
CHANGED
|
@@ -760,10 +760,14 @@ fi
|
|
|
760
760
|
# `--show-prefix` is the question itself: empty at the work-tree root, `sub/dir/` below it, non-zero exit
|
|
761
761
|
# outside a repo — and it carries no path spelling to disagree about. Verified here at a normal root, a
|
|
762
762
|
# worktree root, a subdirectory and a non-repo.
|
|
763
|
-
|
|
763
|
+
# ABSOLUTE, from git's own answer for the toplevel. A relative value resolves against the work tree a commit runs
|
|
764
|
+
# in, and a linked worktree (one Claude Code opens included) has no .claude/ when it is gitignored: measured, the
|
|
765
|
+
# trace scan blocked a commit at the root and let the same commit through in a worktree, with no hook run at all.
|
|
766
|
+
if PFX="$(git rev-parse --show-prefix 2>/dev/null)" && [ -z "$PFX" ] && TOP="$(git rev-parse --show-toplevel 2>/dev/null)" \
|
|
767
|
+
&& [ -n "$TOP" ] && git config core.hooksPath "$TOP/.claude/hooks" 2>/dev/null; then
|
|
764
768
|
{ _mt 'trace scan: core.hooksPath -> .claude/hooks (§4.1/§4.2 commit gate active)'; echo " ${_M}"; }
|
|
765
769
|
else
|
|
766
|
-
{ _mt 'NOTE: no git repository at this level; after %s run: %s' "'git init'"
|
|
770
|
+
{ _mt 'NOTE: no git repository at this level; after %s run: %s' "'git init'" "git config core.hooksPath \"\$PWD/.claude/hooks\""; echo " ${_M}"; }
|
|
767
771
|
fi
|
|
768
772
|
rm -rf "$SRC"
|
|
769
773
|
echo
|