#!/usr/bin/env bash set -euo pipefail PROJECT_DIR="/home/long/project/蚊子" REPORT_DIR="$PROJECT_DIR/logs/prd-review" RUN_LOG="$PROJECT_DIR/logs/prd_review_cycle.log" LOCK_FILE="/tmp/mosquito_prd_review_cycle.lock" PID_FILE="$PROJECT_DIR/logs/prd-review/cycle.pid" FORCE_RERUN="${FORCE_RERUN:-0}" STALE_SECONDS="${STALE_SECONDS:-10800}" CLAUDE_BIN="$HOME/.cursor/extensions/anthropic.claude-code-2.1.15-linux-x64/resources/native-binary/claude" EVIDENCE_CHECK_SCRIPT="$PROJECT_DIR/scripts/verify_review_evidence.sh" # cron环境下PATH通常不完整,显式补齐nvm/codex export HOME="${HOME:-/home/long}" export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" export PATH="$HOME/.local/bin:$HOME/bin:/usr/local/bin:/usr/bin:/bin:$PATH" if ! command -v codex >/dev/null 2>&1; then if [ -s "$NVM_DIR/nvm.sh" ]; then # shellcheck disable=SC1090 . "$NVM_DIR/nvm.sh" >/dev/null 2>&1 || true nvm use --silent 20 >/dev/null 2>&1 || true nvm use --silent default >/dev/null 2>&1 || true fi fi CODEX_BIN="$(command -v codex || true)" if [ -z "$CODEX_BIN" ] && [ -x "$HOME/.nvm/versions/node/v20.19.0/bin/codex" ]; then CODEX_BIN="$HOME/.nvm/versions/node/v20.19.0/bin/codex" fi mkdir -p "$REPORT_DIR" "$PROJECT_DIR/logs" if [ -f "$PID_FILE" ]; then prev_pid="$(cat "$PID_FILE" 2>/dev/null || true)" if [ -n "${prev_pid:-}" ] && kill -0 "$prev_pid" 2>/dev/null; then now_ts=$(date +%s) pid_age=$((now_ts - $(stat -c %Y "$PID_FILE" 2>/dev/null || echo "$now_ts"))) if [ "$FORCE_RERUN" = "1" ] || [ "$pid_age" -gt "$STALE_SECONDS" ]; then echo "[$(date '+%F %T')] force/stale rerun: killing previous pid=$prev_pid age=${pid_age}s" >> "$RUN_LOG" kill "$prev_pid" 2>/dev/null || true sleep 2 fi fi fi exec 9>"$LOCK_FILE" if ! flock -n 9; then echo "[$(date '+%F %T')] skip: previous cycle still running" >> "$RUN_LOG" exit 0 fi echo $$ > "$PID_FILE" trap 'rm -f "$PID_FILE"' EXIT cd "$PROJECT_DIR" TIMESTAMP="$(date '+%Y%m%d_%H%M%S')" REPORT_FILE="$REPORT_DIR/review_${TIMESTAMP}.md" LATEST_REPORT="$REPORT_DIR/latest_review.md" CLAUDE_SUMMARY="$REPORT_DIR/claude_apply_${TIMESTAMP}.md" PRD_FILES=() for f in "$PROJECT_DIR/docs/PRD.md" "$PROJECT_DIR/docs/prd"/*.md; do if [ -f "$f" ]; then PRD_FILES+=("$f") fi done if [ ${#PRD_FILES[@]} -eq 0 ]; then echo "[$(date '+%F %T')] error: no PRD files found" >> "$RUN_LOG" exit 1 fi PRD_LIST=$(printf '%s\n' "${PRD_FILES[@]}") CODEX_PROMPT=$(cat <> "$RUN_LOG" exit 1 fi echo "[$(date '+%F %T')] cycle start: generating codex report with $CODEX_BIN" >> "$RUN_LOG" "$CODEX_BIN" exec \ --cd "$PROJECT_DIR" \ --dangerously-bypass-approvals-and-sandbox \ --output-last-message "$REPORT_FILE" \ "$CODEX_PROMPT" >> "$RUN_LOG" 2>&1 if [ ! -s "$REPORT_FILE" ]; then echo "[$(date '+%F %T')] error: codex report is empty: $REPORT_FILE" >> "$RUN_LOG" exit 1 fi cp -f "$REPORT_FILE" "$LATEST_REPORT" echo "[$(date '+%F %T')] codex report ready: $REPORT_FILE" >> "$RUN_LOG" CLAUDE_PROMPT=$(cat <> "$RUN_LOG" "$CLAUDE_BIN" -p \ --permission-mode dontAsk \ --dangerously-skip-permissions \ "$CLAUDE_PROMPT" > "$CLAUDE_SUMMARY" 2>> "$RUN_LOG" if [ ! -s "$CLAUDE_SUMMARY" ]; then echo "[$(date '+%F %T')] error: claude summary is empty: $CLAUDE_SUMMARY" >> "$RUN_LOG" exit 1 fi if [ -x "$EVIDENCE_CHECK_SCRIPT" ]; then if "$EVIDENCE_CHECK_SCRIPT" "$CLAUDE_SUMMARY" "$PROJECT_DIR" >> "$RUN_LOG" 2>&1; then echo "[$(date '+%F %T')] evidence gate pass" >> "$RUN_LOG" else echo "[$(date '+%F %T')] error: evidence gate failed, mark cycle invalid" >> "$RUN_LOG" exit 1 fi else echo "[$(date '+%F %T')] warn: evidence checker missing: $EVIDENCE_CHECK_SCRIPT" >> "$RUN_LOG" exit 1 fi echo "[$(date '+%F %T')] cycle done: $CLAUDE_SUMMARY" >> "$RUN_LOG"