signal.sh 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. #!/usr/bin/env bash
  2. # fleet-ops/signal.sh — called by Claude sessions to signal lane status
  3. # Auto-detects the current branch. Refuses dirty trees.
  4. # Resolves .fleet/ via git common dir, so it works from inside worktrees.
  5. set -euo pipefail
  6. GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null || true)
  7. [[ -z "$GIT_COMMON_DIR" ]] && { echo "signal.sh ERROR: not in a git repo" >&2; exit 2; }
  8. # git-common-dir is .git/ at main repo root → parent is the main worktree
  9. MAIN_REPO_ROOT=$(cd "$GIT_COMMON_DIR/.." && pwd)
  10. LANES_DIR="$MAIN_REPO_ROOT/.claude/fleet/lanes"
  11. BRANCH=$(git branch --show-current 2>/dev/null || true)
  12. if [[ -z "$BRANCH" ]]; then
  13. echo "signal.sh ERROR: not on a branch (detached HEAD?)" >&2
  14. exit 2
  15. fi
  16. # Lane files are flat: encode '/' in branch names (feat/x, fleet/x) so they don't
  17. # nest into nonexistent subdirs. MUST match fleet.sh's encode_lane.
  18. encode_lane() { local s=${1//\%/%25}; printf '%s' "${s//\//%2F}"; }
  19. LANE_FILE="$LANES_DIR/$(encode_lane "$BRANCH")"
  20. if [[ ! -f "$LANE_FILE" ]]; then
  21. echo "signal.sh ERROR: branch '$BRANCH' is not a registered lane (run: fleet track $BRANCH)" >&2
  22. exit 2
  23. fi
  24. STATE=${1:-}
  25. case "$STATE" in
  26. READY)
  27. if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
  28. echo "signal.sh REFUSE: '$BRANCH' has uncommitted tracked changes — commit or stash before signaling READY" >&2
  29. git status --short >&2
  30. exit 1
  31. fi
  32. LOG=${2:-}
  33. if [[ -n "$LOG" ]]; then
  34. [[ -f "$LOG" ]] || { echo "signal.sh ERROR: test log '$LOG' not found" >&2; exit 1; }
  35. # crude pass detection — works for pytest, jest, go test, cargo test, mocha
  36. if grep -qiE "(failed|error|fail:)" "$LOG" && ! grep -qiE "0 (failed|errors)" "$LOG"; then
  37. echo "signal.sh REFUSE: test log '$LOG' shows failures" >&2
  38. grep -iE "(failed|error)" "$LOG" | head -5 >&2
  39. exit 1
  40. fi
  41. fi
  42. { echo "READY"; [[ -n "$LOG" ]] && echo "log=$LOG"; } > "$LANE_FILE"
  43. echo "signal: $BRANCH → READY"
  44. ;;
  45. CONFLICT)
  46. REASON=${2:-"unspecified"}
  47. { echo "CONFLICT"; echo "reason=$REASON"; } > "$LANE_FILE"
  48. echo "signal: $BRANCH → CONFLICT ($REASON)"
  49. ;;
  50. RUNNING)
  51. echo "RUNNING" > "$LANE_FILE"
  52. echo "signal: $BRANCH → RUNNING"
  53. ;;
  54. *)
  55. echo "usage: signal.sh READY [test-log] | CONFLICT [reason] | RUNNING" >&2
  56. exit 1
  57. ;;
  58. esac