# =============================================================================
# AWOOOI Agent Market Watch (Gitea Actions)
# =============================================================================
# Weekly read-only AI Agent market scan. This workflow detects primary-source
# changes only; it does not install SDKs, call LLM APIs, commit reports, approve
# shadow/canary, or change production routing.

name: Agent Market Watch

on:
  workflow_dispatch:
  schedule:
    - cron: '0 1 * * 1'  # 每週一 09:00 台北 (UTC+8)

env:
  GITEA_ACTIONS_URL: http://192.168.0.110:3001/wooo/awoooi/actions
  SRE_GROUP_CHAT_ID: "-1003711974679"

jobs:
  market-watch:
    runs-on: awoooi-non110-ubuntu
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Run read-only market watch
        id: watch
        run: |
          set -euo pipefail
          REPORT="/tmp/agent_market_watch_report.json"
          PREVIOUS_REPORT="$(find docs/evaluations -maxdepth 1 -type f -name 'agent_market_watch_report_*.json' | sort | tail -n 1 || true)"
          PREVIOUS_ARGS=()
          if [ -n "$PREVIOUS_REPORT" ]; then
            PREVIOUS_ARGS=(--previous-report "$PREVIOUS_REPORT")
            echo "Using previous committed market watch baseline: $PREVIOUS_REPORT"
          else
            echo "No previous committed market watch baseline found; running first live baseline."
          fi

          python3 scripts/agents/agent-market-watch.py \
            --registry docs/ai/agent-market-watch-sources.v1.json \
            --output "$REPORT" \
            --mode live \
            --timeout-seconds 12 \
            "${PREVIOUS_ARGS[@]}"

          python3 -m json.tool "$REPORT" >/dev/null
          python3 - "$REPORT" <<'PY'
          import json
          import os
          import sys

          report_path = sys.argv[1]
          with open(report_path, encoding="utf-8") as handle:
              data = json.load(handle)

          if data.get("schema_version") != "agent_market_watch_report_v1":
              raise SystemExit("unexpected market watch schema_version")
          if data.get("mode") != "live":
              raise SystemExit("market watch workflow must run in live mode")

          summary = data.get("summary")
          if not isinstance(summary, dict):
              raise SystemExit("missing market watch summary")

          required = [
              "candidate_count",
              "source_count",
              "changed_candidates",
              "watch_only_candidates",
              "integration_queue_count",
              "failure_count",
          ]
          missing = [key for key in required if key not in summary]
          if missing:
              raise SystemExit(f"missing market watch summary keys: {missing}")

          integration_queue = data.get("integration_queue")
          if not isinstance(integration_queue, list):
              raise SystemExit("integration_queue must be a list")

          output_path = os.environ.get("GITHUB_OUTPUT")
          if output_path:
              with open(output_path, "a", encoding="utf-8") as handle:
                  for key in required:
                      handle.write(f"{key}={summary.get(key, 0)}\n")

          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write("## Agent Market Watch\n\n")
                  handle.write(f"- Candidates: {summary['candidate_count']}\n")
                  handle.write(f"- Sources: {summary['source_count']}\n")
                  handle.write(f"- Changed candidates: {summary['changed_candidates']}\n")
                  handle.write(f"- Integration queue: {summary['integration_queue_count']}\n")
                  handle.write(f"- Source failures: {summary['failure_count']}\n")
                  handle.write("\nPolicy: read-only watch; no SDK/API/prod change is approved by this workflow.\n")

          print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
          PY

      - name: Run read-only integration review
        id: review
        run: |
          set -euo pipefail
          REVIEW="/tmp/agent_market_integration_review.json"
          python3 scripts/agents/agent-market-integration-review.py \
            --watch-report /tmp/agent_market_watch_report.json \
            --candidates docs/ai/agent-replacement-candidates.v1.json \
            --scorecard docs/evaluations/agent_market_capability_scorecard_2026-06-01.json \
            --review-scope all \
            --output "$REVIEW"

          python3 -m json.tool "$REVIEW" >/dev/null
          python3 - "$REVIEW" <<'PY'
          import json
          import os
          import sys

          review_path = sys.argv[1]
          with open(review_path, encoding="utf-8") as handle:
              data = json.load(handle)

          if data.get("schema_version") != "agent_market_integration_review_v1":
              raise SystemExit("unexpected integration review schema_version")
          policy = data.get("policy") or {}
          forbidden = [
              "production_changes_approved",
              "replacement_decision_allowed",
              "sdk_installation_approved",
              "paid_api_calls_approved",
              "shadow_or_canary_approved",
          ]
          unsafe = [key for key in forbidden if policy.get(key) is not False]
          if unsafe:
              raise SystemExit(f"integration review policy must stay false: {unsafe}")

          summary = data.get("summary")
          if not isinstance(summary, dict):
              raise SystemExit("missing integration review summary")
          required = [
              "reviewed_candidates",
              "blocked_from_integration",
              "requires_cost_approval",
              "requires_dependency_approval",
              "source_failures",
              "production_changes_approved",
              "shadow_or_canary_approved",
          ]
          missing = [key for key in required if key not in summary]
          if missing:
              raise SystemExit(f"missing integration review summary keys: {missing}")

          output_path = os.environ.get("GITHUB_OUTPUT")
          if output_path:
              with open(output_path, "a", encoding="utf-8") as handle:
                  for key in required:
                      handle.write(f"{key}={summary.get(key, 0)}\n")

          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write("\n## Agent Integration Review\n\n")
                  handle.write("- Review scope: all candidates\n")
                  handle.write(f"- Reviewed candidates: {summary['reviewed_candidates']}\n")
                  handle.write(f"- Blocked from integration: {summary['blocked_from_integration']}\n")
                  handle.write(f"- Cost approvals required: {summary['requires_cost_approval']}\n")
                  handle.write(f"- Dependency approvals required: {summary['requires_dependency_approval']}\n")
                  handle.write(f"- Production changes approved: {summary['production_changes_approved']}\n")
                  handle.write(f"- Shadow/canary approved: {summary['shadow_or_canary_approved']}\n")

          print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
          PY

      - name: Run read-only discovery review
        id: discovery
        run: |
          set -euo pipefail
          DISCOVERY="/tmp/agent_market_discovery_review.json"
          PREVIOUS_DISCOVERY="$(find docs/evaluations -maxdepth 1 -type f -name 'agent_market_discovery_review_*.json' | sort | tail -n 1 || true)"
          PREVIOUS_ARGS=()
          if [ -n "$PREVIOUS_DISCOVERY" ]; then
            PREVIOUS_ARGS=(--previous-review "$PREVIOUS_DISCOVERY")
            echo "Using previous committed discovery review baseline: $PREVIOUS_DISCOVERY"
          else
            echo "No previous committed discovery review baseline found; running first discovery intake."
          fi

          python3 scripts/agents/agent-market-discovery-review.py \
            --watch-report /tmp/agent_market_watch_report.json \
            --candidates docs/ai/agent-replacement-candidates.v1.json \
            --source-registry docs/ai/agent-market-watch-sources.v1.json \
            --output "$DISCOVERY" \
            "${PREVIOUS_ARGS[@]}"

          python3 -m json.tool "$DISCOVERY" >/dev/null
          python3 - "$DISCOVERY" <<'PY'
          import json
          import os
          import sys

          discovery_path = sys.argv[1]
          with open(discovery_path, encoding="utf-8") as handle:
              data = json.load(handle)

          if data.get("schema_version") != "agent_market_discovery_review_v1":
              raise SystemExit("unexpected discovery review schema_version")
          policy = data.get("policy") or {}
          forbidden = [
              "auto_registry_addition_approved",
              "sdk_installation_approved",
              "paid_api_calls_approved",
              "production_changes_approved",
              "shadow_or_canary_approved",
              "replacement_decision_allowed",
          ]
          unsafe = [key for key in forbidden if policy.get(key) is not False]
          if unsafe:
              raise SystemExit(f"discovery review policy must stay false: {unsafe}")

          summary = data.get("summary")
          if not isinstance(summary, dict):
              raise SystemExit("missing discovery review summary")
          required = [
              "discovery_sources",
              "discovered_items",
              "unique_repositories",
              "already_watched_or_registered",
              "controlled_classification_queue",
              "new_controlled_classification_queue",
              "source_failures",
          ]
          missing = [key for key in required if key not in summary]
          if missing:
              raise SystemExit(f"missing discovery review summary keys: {missing}")

          output_path = os.environ.get("GITHUB_OUTPUT")
          if output_path:
              with open(output_path, "a", encoding="utf-8") as handle:
                  for key in required:
                      handle.write(f"{key}={summary.get(key, 0)}\n")

          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write("\n## Agent Discovery Review\n\n")
                  handle.write(f"- Discovery sources: {summary['discovery_sources']}\n")
                  handle.write(f"- Unique repositories: {summary['unique_repositories']}\n")
                  handle.write(f"- Already watched/registered: {summary['already_watched_or_registered']}\n")
                  handle.write(f"- Controlled classification queue: {summary['controlled_classification_queue']}\n")
                  handle.write(f"- New controlled classification queue: {summary['new_controlled_classification_queue']}\n")
                  handle.write("\nPolicy: read-only intake; no registry addition, SDK/API, shadow/canary, or production change is approved.\n")

          print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
          PY

      - name: Run AI controlled discovery classification
        id: classify
        run: |
          set -euo pipefail
          CLASSIFICATION="/tmp/agent_market_discovery_classification.json"
          python3 scripts/agents/agent-market-discovery-classify.py \
            --discovery-review /tmp/agent_market_discovery_review.json \
            --output "$CLASSIFICATION" \
            --timeout-seconds 12

          python3 -m json.tool "$CLASSIFICATION" >/dev/null
          python3 - "$CLASSIFICATION" <<'PY'
          import json
          import os
          import sys

          classification_path = sys.argv[1]
          with open(classification_path, encoding="utf-8") as handle:
              data = json.load(handle)

          if data.get("schema_version") != "agent_market_discovery_classification_v1":
              raise SystemExit("unexpected discovery classification schema_version")
          policy = data.get("policy") or {}
          forbidden = [
              "auto_watch_registry_addition_approved",
              "sdk_installation_approved",
              "paid_api_calls_approved",
              "production_changes_approved",
              "shadow_or_canary_approved",
              "replacement_decision_allowed",
          ]
          unsafe = [key for key in forbidden if policy.get(key) is not False]
          if unsafe:
              raise SystemExit(f"discovery classification policy must stay false: {unsafe}")

          summary = data.get("summary")
          if not isinstance(summary, dict):
              raise SystemExit("missing discovery classification summary")
          required = [
              "classified_repositories",
              "recommended_watch_additions",
              "watch_only_or_defer",
              "production_changes_approved",
              "shadow_or_canary_approved",
          ]
          missing = [key for key in required if key not in summary]
          if missing:
              raise SystemExit(f"missing discovery classification summary keys: {missing}")

          output_path = os.environ.get("GITHUB_OUTPUT")
          if output_path:
              with open(output_path, "a", encoding="utf-8") as handle:
                  for key in required:
                      handle.write(f"{key}={summary.get(key, 0)}\n")

          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write("\n## Agent Discovery Classification\n\n")
                  handle.write(f"- Classified repositories: {summary['classified_repositories']}\n")
                  handle.write(f"- Recommended watch additions: {summary['recommended_watch_additions']}\n")
                  handle.write(f"- Watch-only/defer: {summary['watch_only_or_defer']}\n")
                  handle.write("\nPolicy: read-only classification; no watch registry addition, SDK/API, replay, shadow/canary, or production change is approved.\n")

          print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
          PY

      - name: Run read-only watch promotion review
        id: promote
        run: |
          set -euo pipefail
          PROMOTION="/tmp/agent_market_watch_promotion_review.json"
          CLASSIFICATION="/tmp/agent_market_discovery_classification.json"
          if [ ! -f "$CLASSIFICATION" ]; then
            echo "Classification artifact missing after controlled classification step."
            exit 1
          fi

          python3 scripts/agents/agent-market-watch-promotion-review.py \
            --watch-report /tmp/agent_market_watch_report.json \
            --integration-review /tmp/agent_market_integration_review.json \
            --discovery-classification "$CLASSIFICATION" \
            --candidates docs/ai/agent-replacement-candidates.v1.json \
            --output "$PROMOTION"

          python3 -m json.tool "$PROMOTION" >/dev/null
          python3 - "$PROMOTION" <<'PY'
          import json
          import os
          import sys

          promotion_path = sys.argv[1]
          with open(promotion_path, encoding="utf-8") as handle:
              data = json.load(handle)

          if data.get("schema_version") != "agent_market_watch_promotion_review_v1":
              raise SystemExit("unexpected watch promotion review schema_version")
          policy = data.get("policy") or {}
          forbidden = [
              "priority_upgrade_approved",
              "market_scorecard_update_approved",
              "replay_candidate_approved",
              "sdk_installation_approved",
              "paid_api_calls_approved",
              "production_changes_approved",
              "shadow_or_canary_approved",
              "replacement_decision_allowed",
          ]
          unsafe = [key for key in forbidden if policy.get(key) is not False]
          if unsafe:
              raise SystemExit(f"watch promotion policy must stay false: {unsafe}")

          summary = data.get("summary")
          if not isinstance(summary, dict):
              raise SystemExit("missing watch promotion summary")
          required = [
              "watch_only_candidates_reviewed",
              "eligible_for_market_scorecard_prescreen",
              "remain_watch_only",
              "priority_upgrades_approved",
              "market_scorecard_updates_approved",
              "replay_candidates_approved",
          ]
          missing = [key for key in required if key not in summary]
          if missing:
              raise SystemExit(f"missing watch promotion summary keys: {missing}")

          output_path = os.environ.get("GITHUB_OUTPUT")
          if output_path:
              with open(output_path, "a", encoding="utf-8") as handle:
                  for key in required:
                      handle.write(f"{key}={summary.get(key, 0)}\n")

          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write("\n## Agent Watch Promotion Review\n\n")
                  handle.write(f"- Watch-only candidates reviewed: {summary['watch_only_candidates_reviewed']}\n")
                  handle.write(f"- Eligible for scorecard prescreen: {summary['eligible_for_market_scorecard_prescreen']}\n")
                  handle.write(f"- Remain watch-only: {summary['remain_watch_only']}\n")
                  handle.write(f"- Priority upgrades approved: {summary['priority_upgrades_approved']}\n")
                  handle.write(f"- Replay candidates approved: {summary['replay_candidates_approved']}\n")
                  handle.write("\nPolicy: read-only promotion readiness; no priority upgrade, scorecard update, replay, SDK/API, shadow/canary, or production change is approved.\n")

          print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
          PY

      - name: Build read-only governance snapshot
        id: snapshot
        run: |
          set -euo pipefail
          SNAPSHOT="/tmp/agent_market_governance_snapshot.json"
          CLASSIFICATION="/tmp/agent_market_discovery_classification.json"
          if [ ! -f "$CLASSIFICATION" ]; then
            echo "Classification artifact missing after controlled classification step."
            exit 1
          fi
          PROMOTION="/tmp/agent_market_watch_promotion_review.json"
          if [ ! -f "$PROMOTION" ]; then
            echo "Promotion review missing after controlled promotion step."
            exit 1
          fi

          python3 scripts/agents/agent-market-governance-snapshot.py \
            --watch-report /tmp/agent_market_watch_report.json \
            --integration-review /tmp/agent_market_integration_review.json \
            --discovery-classification "$CLASSIFICATION" \
            --promotion-review "$PROMOTION" \
            --candidates docs/ai/agent-replacement-candidates.v1.json \
            --output "$SNAPSHOT"

          python3 -m json.tool "$SNAPSHOT" >/dev/null
          python3 - "$SNAPSHOT" <<'PY'
          import json
          import os
          import sys

          snapshot_path = sys.argv[1]
          with open(snapshot_path, encoding="utf-8") as handle:
              data = json.load(handle)

          if data.get("schema_version") != "agent_market_governance_snapshot_v1":
              raise SystemExit("unexpected governance snapshot schema_version")
          policy = data.get("policy") or {}
          forbidden = [
              "priority_upgrade_approved",
              "market_scorecard_update_approved",
              "replay_candidate_approved",
              "sdk_installation_approved",
              "paid_api_calls_approved",
              "production_changes_approved",
              "shadow_or_canary_approved",
              "replacement_decision_allowed",
          ]
          unsafe = [key for key in forbidden if policy.get(key) is not False]
          if unsafe:
              raise SystemExit(f"governance snapshot policy must stay false: {unsafe}")

          summary = data.get("summary")
          if not isinstance(summary, dict):
              raise SystemExit("missing governance snapshot summary")
          required = [
              "candidate_count",
              "source_count",
              "blocked_from_integration",
              "eligible_for_market_scorecard_prescreen",
              "replacement_decisions_approved",
              "replay_candidates_approved",
              "production_changes_approved",
          ]
          missing = [key for key in required if key not in summary]
          if missing:
              raise SystemExit(f"missing governance snapshot summary keys: {missing}")

          output_path = os.environ.get("GITHUB_OUTPUT")
          if output_path:
              with open(output_path, "a", encoding="utf-8") as handle:
                  for key in required:
                      handle.write(f"{key}={summary.get(key, 0)}\n")

          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write("\n## Agent Market Governance Snapshot\n\n")
                  handle.write(f"- Current decision: {data['current_decision']}\n")
                  handle.write(f"- Candidates: {summary['candidate_count']}\n")
                  handle.write(f"- Sources: {summary['source_count']}\n")
                  handle.write(f"- Blocked from integration: {summary['blocked_from_integration']}\n")
                  handle.write(f"- Scorecard prescreen eligible: {summary['eligible_for_market_scorecard_prescreen']}\n")
                  handle.write(f"- Replacement approvals: {summary['replacement_decisions_approved']}\n")
                  handle.write(f"- Replay approvals: {summary['replay_candidates_approved']}\n")
                  handle.write(f"- Production approvals: {summary['production_changes_approved']}\n")

          print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
          PY

      - name: Summarize actionable change or failure
        if: always()
        env:
          TG_CHAT_ID: ${{ env.SRE_GROUP_CHAT_ID }}
          JOB_STATUS: ${{ job.status }}
          CANDIDATE_COUNT: ${{ steps.watch.outputs.candidate_count }}
          SOURCE_COUNT: ${{ steps.watch.outputs.source_count }}
          CHANGED_CANDIDATES: ${{ steps.watch.outputs.changed_candidates }}
          INTEGRATION_QUEUE_COUNT: ${{ steps.watch.outputs.integration_queue_count }}
          FAILURE_COUNT: ${{ steps.watch.outputs.failure_count }}
          REVIEWED_CANDIDATES: ${{ steps.review.outputs.reviewed_candidates }}
          BLOCKED_FROM_INTEGRATION: ${{ steps.review.outputs.blocked_from_integration }}
          REVIEW_COST_APPROVALS: ${{ steps.review.outputs.requires_cost_approval }}
          REVIEW_DEPENDENCY_APPROVALS: ${{ steps.review.outputs.requires_dependency_approval }}
          DISCOVERY_CONTROLLED_QUEUE: ${{ steps.discovery.outputs.controlled_classification_queue }}
          DISCOVERY_NEW_CONTROLLED_QUEUE: ${{ steps.discovery.outputs.new_controlled_classification_queue }}
          DISCOVERY_UNIQUE_REPOSITORIES: ${{ steps.discovery.outputs.unique_repositories }}
          CLASSIFIED_REPOSITORIES: ${{ steps.classify.outputs.classified_repositories }}
          RECOMMENDED_WATCH_ADDITIONS: ${{ steps.classify.outputs.recommended_watch_additions }}
          WATCH_PROMOTION_ELIGIBLE: ${{ steps.promote.outputs.eligible_for_market_scorecard_prescreen }}
          WATCH_PROMOTION_APPROVED: ${{ steps.promote.outputs.priority_upgrades_approved }}
          REPLAY_CANDIDATES_APPROVED: ${{ steps.promote.outputs.replay_candidates_approved }}
          GITEA_ACTIONS_URL: ${{ env.GITEA_ACTIONS_URL }}
        run: |
          set -euo pipefail
          CHANGED="${CHANGED_CANDIDATES:-0}"
          QUEUE="${INTEGRATION_QUEUE_COUNT:-0}"
          FAILURES="${FAILURE_COUNT:-0}"
          NEW_DISCOVERY="${DISCOVERY_NEW_CONTROLLED_QUEUE:-0}"

          if [ "$JOB_STATUS" = "success" ] && [ "$CHANGED" = "0" ] && [ "$QUEUE" = "0" ] && [ "$FAILURES" = "0" ] && [ "$NEW_DISCOVERY" = "0" ]; then
            echo "No actionable market changes; keep Telegram quiet."
            exit 0
          fi

          python3 - <<'PY'
          import os
          from datetime import datetime
          from zoneinfo import ZoneInfo

          status = os.environ.get("JOB_STATUS", "unknown")
          changed = os.environ.get("CHANGED_CANDIDATES") or "0"
          queue = os.environ.get("INTEGRATION_QUEUE_COUNT") or "0"
          failures = os.environ.get("FAILURE_COUNT") or "0"
          reviewed = os.environ.get("REVIEWED_CANDIDATES") or "0"
          blocked = os.environ.get("BLOCKED_FROM_INTEGRATION") or "0"
          cost_approvals = os.environ.get("REVIEW_COST_APPROVALS") or "0"
          dependency_approvals = os.environ.get("REVIEW_DEPENDENCY_APPROVALS") or "0"
          discovery_controlled_queue = os.environ.get("DISCOVERY_CONTROLLED_QUEUE") or "0"
          discovery_new = os.environ.get("DISCOVERY_NEW_CONTROLLED_QUEUE") or "0"
          discovery_repos = os.environ.get("DISCOVERY_UNIQUE_REPOSITORIES") or "0"
          classified_repos = os.environ.get("CLASSIFIED_REPOSITORIES") or "0"
          recommended_watch_additions = os.environ.get("RECOMMENDED_WATCH_ADDITIONS") or "0"
          watch_promotion_eligible = os.environ.get("WATCH_PROMOTION_ELIGIBLE") or "0"
          watch_promotion_approved = os.environ.get("WATCH_PROMOTION_APPROVED") or "0"
          replay_candidates_approved = os.environ.get("REPLAY_CANDIDATES_APPROVED") or "0"
          candidates = os.environ.get("CANDIDATE_COUNT") or "0"
          sources = os.environ.get("SOURCE_COUNT") or "0"
          actions_url = os.environ.get("GITEA_ACTIONS_URL", "")
          generated = datetime.now(ZoneInfo("Asia/Taipei")).strftime("%Y-%m-%d %H:%M")

          title = "Agent Market Watch 受控隊列更新" if status == "success" else "Agent Market Watch 執行失敗"
          lines = [
              f"## {title}",
              "",
              f"- 時間：`{generated}`",
              f"- 狀態：`{status}`",
              f"- 候選 / 來源：`{candidates}` / `{sources}`",
              f"- 變動候選 / 整合佇列 / 來源失敗：`{changed}` / `{queue}` / `{failures}`",
              f"- Review：已審 `{reviewed}`；擋下整合 `{blocked}`；成本批准需求 `{cost_approvals}`；依賴批准需求 `{dependency_approvals}`",
              f"- Discovery：unique repo `{discovery_repos}`；AI 受控分類隊列 `{discovery_controlled_queue}`；新受控分類 `{discovery_new}`；已分類 `{classified_repos}`；建議 watch `{recommended_watch_additions}`",
              f"- Promotion：scorecard prescreen eligible `{watch_promotion_eligible}`；priority upgrade approved `{watch_promotion_approved}`；replay approved `{replay_candidates_approved}`",
              "",
              "政策：此 workflow 只建立市場觀察、整合審查、discovery intake/classification 訊號，不批准 SDK 安裝、付費 API、replay、shadow/canary 或 OpenClaw 取代。",
              f"Log：{actions_url}",
          ]
          summary = "\n".join(lines) + "\n"
          print(summary)
          step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
          if step_summary_path:
              with open(step_summary_path, "a", encoding="utf-8") as handle:
                  handle.write(summary)
          PY
