#!/usr/bin/env bash
# .claude/hooks/check-progress-file.sh
#
# Trigger: SubagentStop + TeammateIdle (Claude Code Agent Teams runtime)
# Purpose: 强制 Self-Reporting Pattern —— 执行层 teammate 在退出/idle 前必须已经
#          append 到 progress/<role>.md（含至少一个 markdown 二级标题 ## 任务条目），
#          否则 exit 2 阻断退出，提示按 .claude/standards/ac-lifecycle.md 流程补写。
#
# 强制对象（execution-layer roles）：
#   backend-dev / frontend-dev / ai-agent-dev / ml-engineer / miniapp-dev
#
# 豁免规则（exit 0 放行）：
#   - 当前 teammate 不在执行层强制名单（lead / reviewer / qa / designer / writer / analyst）
#   - 无法从 hook payload 提取出 teammate role（payload shape 变化时不误杀）
#   - 当前 team 的 task list 里没有任何任务分给该 role（standby 状态）
#   - progress/<role>.md 存在且含至少一条 ## 二级标题
#
# 阻断规则（exit 2 + stderr 提示）：
#   - 该 role 在执行层名单 + 有任务分配 + progress/<role>.md 不存在或无 ## 条目
#
# 注册位置：.claude/settings.json → hooks.SubagentStop / hooks.TeammateIdle
# 参考：https://code.claude.com/docs/en/agent-teams#enforce-quality-gates-with-hooks
# Pattern 定义：.claude/standards/ac-lifecycle.md "Self-Reporting Pattern"

set -uo pipefail

INPUT=$(cat || true)

# 执行层强制名单（与 .claude/standards/ac-lifecycle.md "强制对象" 对齐）
EXECUTION_ROLES=(
  "backend-dev"
  "frontend-dev"
  "ai-agent-dev"
  "ml-engineer"
  "miniapp-dev"
)

# 防御式提取 role / teammate name（payload shape 因 hook 类型而异）
# Single jq call walks the entire fallback chain (was N separate forks per call).
extract_role() {
  if ! command -v jq >/dev/null 2>&1; then
    # jq 不可用 → 回退到 grep（粗暴但够用）
    printf '%s' "$INPUT" \
      | grep -oE '"(subagent_type|agent_name|teammate|name|role)"[[:space:]]*:[[:space:]]*"[^"]+"' \
      | head -1 \
      | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/' \
      || echo ""
    return
  fi
  printf '%s' "$INPUT" | jq -r '
    [.subagent_type, .teammate.name, .teammate.role,
     .agent.name, .agent.subagent_type, .name, .role]
    | map(select(. != null and . != ""))
    | .[0] // ""
  ' 2>/dev/null
}

extract_team() {
  if ! command -v jq >/dev/null 2>&1; then
    printf '%s' "$INPUT" \
      | grep -oE '"team[Nn]ame"[[:space:]]*:[[:space:]]*"[^"]+"' \
      | head -1 \
      | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/' \
      || echo ""
    return
  fi
  printf '%s' "$INPUT" | jq -r '
    [.teamName, .team.name, .team]
    | map(select(. != null and . != ""))
    | .[0] // ""
  ' 2>/dev/null
}

ROLE=$(extract_role)
TEAM=$(extract_team)

# 提不出 role → 不阻断（payload 变更或非 teammate 退出场景不应误杀）
[[ -z "$ROLE" ]] && exit 0

# role 不在执行层强制名单 → 不阻断
IN_LIST=0
for r in "${EXECUTION_ROLES[@]}"; do
  if [[ "$ROLE" == "$r" ]]; then
    IN_LIST=1
    break
  fi
done
[[ "$IN_LIST" -eq 0 ]] && exit 0

# 检查该 role 在 team task list 里是否有任务（pending / in_progress）；
# 没有就视为 standby，放行
if [[ -n "$TEAM" ]]; then
  TASK_DIR="$HOME/.claude/tasks/$TEAM"
  if [[ -d "$TASK_DIR" ]]; then
    # Escape regex metachars in $ROLE (latent defense — current allowlist has none,
    # but a future role like `miniapp.dev` would alter the match without escape).
    ROLE_ESC=$(printf '%s' "$ROLE" | sed 's/[][.*^$?+|(){}\\]/\\&/g')
    HAS_TASK=$(grep -lE "\"(assignee|teammate|to)\"[[:space:]]*:[[:space:]]*\"$ROLE_ESC\"" \
      "$TASK_DIR"/*.json 2>/dev/null | wc -l | tr -d ' ')
    if [[ "${HAS_TASK:-0}" -eq 0 ]]; then
      exit 0
    fi
  fi
fi

# 检查 progress/<role>.md 是否存在 + 含 ≥1 个 ## 二级标题
PROGRESS_FILE="progress/$ROLE.md"

if [[ ! -f "$PROGRESS_FILE" ]]; then
  cat >&2 <<EOF
❌ [check-progress-file] 退出/idle 被阻断 — Self-Reporting Pattern 未遵守

Role: $ROLE
缺失文件: $PROGRESS_FILE

按 .claude/standards/ac-lifecycle.md "Self-Reporting Pattern" 节要求：
  1. 完成任务后必须先 append 一条完整条目到 progress/$ROLE.md
  2. 条目须含: 状态 / 任务类型 / Skills used / 验证命令 / 验证输出 / AC 自验 / 涉及文件 / 下一步
  3. 写完 progress/ 后再 SendMessage 完成报告

如果你确实没领到任何任务（standby）— 通常 hook 已经豁免；若仍被拦截，
SendMessage 给 product-lead 说明并请求豁免授权。
EOF
  exit 2
fi

# 文件存在但无任何 ## 二级标题（空骨架不算数）
if ! grep -qE '^## ' "$PROGRESS_FILE" 2>/dev/null; then
  cat >&2 <<EOF
❌ [check-progress-file] 退出/idle 被阻断 — progress/$ROLE.md 存在但无任务条目

按 .claude/standards/ac-lifecycle.md "完整条目格式" append 至少一条 (## 标题开头)
后再尝试退出/idle。空文件 / 仅占位说明不算有效条目。
EOF
  exit 2
fi

exit 0
