fix(framework): per-session ticket files unblock parallel sessions [#439]

The ticket gate required the single .crush/active-ticket file, so two
concurrent sessions overwrote each other's ticket mid-work. The gate now
accepts any non-empty .crush/active-ticket* file; each session keeps its
own (e.g. active-ticket-plant, active-ticket-core). Also prune .crush/
session scratch from both shellcheck scanners so one session's throwaway
probe scripts cannot block the other session's commits. Documented in
AGENTS.md Task Tracking.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-27 13:01:25 -05:00
parent 5977eba503
commit ae8af3472b
4 changed files with 19 additions and 9 deletions
+4
View File
@@ -160,6 +160,10 @@ for a full audit or `--fast` for pre-commit speed. Bypass with `--no-verify`
The Crush hook (`hooks/ticket-gate.sh`) blocks modifying operations The Crush hook (`hooks/ticket-gate.sh`) blocks modifying operations
until this file exists. If no ticket exists, CREATE ONE FIRST via until this file exists. If no ticket exists, CREATE ONE FIRST via
redmine-cli, then set it. Clear when done: `> .crush/active-ticket`. redmine-cli, then set it. Clear when done: `> .crush/active-ticket`.
**Parallel sessions:** each session uses its OWN suffixed file (e.g.
`.crush/active-ticket-plant`, `.crush/active-ticket-core`) so concurrent
sessions never clobber each other's ticket; any non-empty
`.crush/active-ticket*` satisfies the gate.
- **WORKING.md** is the only in-repo task tracker — a scratchpad for the current - **WORKING.md** is the only in-repo task tracker — a scratchpad for the current
session. The pre-commit hook blocks commits while any task remains unchecked. session. The pre-commit hook blocks commits while any task remains unchecked.
- **Parallel sessions:** when two sessions share this repo (e.g. physical plant - **Parallel sessions:** when two sessions share this repo (e.g. physical plant
+12 -7
View File
@@ -9,7 +9,6 @@
# run audits, check status, etc.). # run audits, check status, etc.).
set -euo pipefail set -euo pipefail
TICKET_FILE="${CRUSH_PROJECT_DIR}/.crush/active-ticket"
TOOL="${CRUSH_TOOL_NAME:-}" TOOL="${CRUSH_TOOL_NAME:-}"
CMD="${CRUSH_TOOL_INPUT_COMMAND:-}" CMD="${CRUSH_TOOL_INPUT_COMMAND:-}"
@@ -54,12 +53,18 @@ case "$FILE_PATH" in
;; ;;
esac esac
# Check for active ticket # Check for active ticket. Parallel sessions each use their OWN file:
if [ -f "$TICKET_FILE" ] && [ -s "$TICKET_FILE" ]; then # .crush/active-ticket (default / single session)
TICKET=$(cat "$TICKET_FILE") # .crush/active-ticket-<name> (suffixed per session; any non-empty one
printf '{"context":"Active ticket: %s"}\n' "$TICKET" # satisfies the gate — sessions never fight
exit 0 # over one file)
fi GATE_OK=0
for f in "$CRUSH_PROJECT_DIR"/.crush/active-ticket*; do
[ -f "$f" ] && [ -s "$f" ] || continue
GATE_OK=1
printf '{"context":"Active ticket(s): %s -> %s"}\n' "$(basename "$f")" "$(cat "$f")"
done
[ "$GATE_OK" -eq 1 ] && exit 0
# No active ticket — block # No active ticket — block
cat >&2 <<'MSG' cat >&2 <<'MSG'
+1 -1
View File
@@ -41,7 +41,7 @@ TODAY="$(date +%Y-%m-%d)"
# Runs in Docker so the host stays clean (no native shellcheck required). # Runs in Docker so the host stays clean (no native shellcheck required).
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
$RULE_VERBOSE && log_step "Shell scripts (shellcheck)" $RULE_VERBOSE && log_step "Shell scripts (shellcheck)"
mapfile -d '' SH_FILES < <(find . -path ./.git -prune -o -path ./.tmp -prune -o -path ./vendor -prune -o -path ./archive -prune -o -path ./node_modules -prune -o \( -name '*.sh' -o -name '*.bash' \) -print0 2>/dev/null) mapfile -d '' SH_FILES < <(find . -path ./.git -prune -o -path ./.tmp -prune -o -path ./.crush -prune -o -path ./vendor -prune -o -path ./archive -prune -o -path ./node_modules -prune -o \( -name '*.sh' -o -name '*.bash' \) -print0 2>/dev/null)
if [ "${#SH_FILES[@]}" -gt 0 ]; then if [ "${#SH_FILES[@]}" -gt 0 ]; then
if have shellcheck; then if have shellcheck; then
if shellcheck "${SH_FILES[@]}" >/dev/null 2>&1; then if shellcheck "${SH_FILES[@]}" >/dev/null 2>&1; then
+2 -1
View File
@@ -36,7 +36,8 @@ mapfile -t FILES < <(
*) echo "$f_abs" ;; *) echo "$f_abs" ;;
esac esac
done < <(find "$ROOT" -type f -name '*.sh' \ done < <(find "$ROOT" -type f -name '*.sh' \
-not -path '*/.git/*' -not -path "$ROOT/vendor/*") -not -path '*/.git/*' -not -path "$ROOT/vendor/*" \
-not -path "$ROOT/.crush/*")
fi fi
) )