From 2fb011470e8ae9de910d0140b320df8ada501eb3 Mon Sep 17 00:00:00 2001 From: OG T Date: Wed, 25 Mar 2026 23:47:01 +0800 Subject: [PATCH] =?UTF-8?q?refactor(api):=20Phase=2016=20R3.4=20=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=20Repository=20=E5=B1=A4=E6=95=B4=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - incident_repository: 新增 get_status(), update_status() 方法 - incidents.py: feedback + debug 端點全面改用 Repository - 消除所有 Router 層直接 DB 存取 (符合積木化鐵律) - trust_engine.py: 修復 import 順序 lint 警告 - pre-commit hook: 修正誤判問題 (排除刪除行+註解行) - LOGBOOK: 更新 Phase 16 完成狀態 驗證結果: 31/31 測試通過 Co-Authored-By: Claude Opus 4.5 --- apps/api/src/api/v1/incidents.py | 92 +++-- .../src/repositories/incident_repository.py | 95 +++++ apps/api/src/services/trust_engine.py | 13 +- docs/LOGBOOK.md | 42 ++- scripts/hooks/pre-commit | 331 ++++++++++++++++++ 5 files changed, 511 insertions(+), 62 deletions(-) create mode 100755 scripts/hooks/pre-commit diff --git a/apps/api/src/api/v1/incidents.py b/apps/api/src/api/v1/incidents.py index 9f51f4a5..3c82650e 100644 --- a/apps/api/src/api/v1/incidents.py +++ b/apps/api/src/api/v1/incidents.py @@ -29,6 +29,8 @@ from src.models.incident import Incident, IncidentStatus, Severity from src.services.decision_manager import get_decision_manager from src.services.proposal_service import get_proposal_service from src.utils.timezone import now_taipei +# Phase 16 R3.3b (2026-03-25 台北時區): Repository 層整合 +from src.repositories.incident_repository import get_incident_repository router = APIRouter(prefix="/incidents", tags=["Incidents"]) logger = get_logger("awoooi.incidents") @@ -417,10 +419,12 @@ async def submit_feedback( HTTPException: 404 事件不存在 """ - from sqlalchemy import select - - from src.db.base import get_db_context - from src.db.models import IncidentRecord + # Phase 16 R3.3b (2026-03-25): 移除直接 DB import,改用 Repository + # --- 以下為舊 import,已由 Repository 取代 --- + # from sqlalchemy import select + # from src.db.base import get_db_context + # from src.db.models import IncidentRecord + # --- 封存結束 --- from src.models.incident import IncidentOutcome redis_client = get_redis() @@ -477,28 +481,27 @@ async def submit_feedback( ) from e # 4. 同步到 PostgreSQL (Episodic Memory) + # Phase 16 R3.3b (2026-03-25 台北時區): 改用 Repository 層 + # 執行者: Claude Code + # 原因: 消除 Router 層直接 DB 存取,統一透過 Repository try: - async with get_db_context() as db: - stmt = select(IncidentRecord).where( - IncidentRecord.incident_id == incident_id + repo = get_incident_repository() + updated = await repo.update_outcome( + incident_id=incident_id, + outcome=incident.outcome.model_dump(mode="json"), + updated_at=now_taipei(), + ) + if updated: + logger.info( + "feedback_db_updated", + incident_id=incident_id, + ) + else: + logger.warning( + "feedback_db_record_not_found", + incident_id=incident_id, + message="將在下次 memory 同步時建立", ) - result = await db.execute(stmt) - record = result.scalar_one_or_none() - - if record: - record.outcome = incident.outcome.model_dump(mode="json") - record.updated_at = now_taipei() - await db.commit() - logger.info( - "feedback_db_updated", - incident_id=incident_id, - ) - else: - logger.warning( - "feedback_db_record_not_found", - incident_id=incident_id, - message="將在下次 memory 同步時建立", - ) except Exception as e: logger.warning( "feedback_db_write_error", @@ -528,13 +531,10 @@ async def debug_resolve_incident(incident_id: str) -> dict[str, Any]: """ DEBUG: 直接更新 Incident 狀態為 RESOLVED 用於測試 resolve_incident_after_approval 邏輯 + + Phase 16 R3.4 (2026-03-26): 重構為使用 Repository 層 """ - - from sqlalchemy import select - - from src.db.base import get_db_context - from src.db.models import IncidentRecord - + repo = get_incident_repository() redis_client = get_redis() error_msg = None @@ -548,15 +548,10 @@ async def debug_resolve_incident(incident_id: str) -> dict[str, Any]: except Exception as e: error_msg = f"Redis read error: {e}" - # 2. 取得 DB 當前狀態 + # 2. 取得 DB 當前狀態 (透過 Repository) before_db = None try: - async with get_db_context() as db: - stmt = select(IncidentRecord).where(IncidentRecord.incident_id == incident_id) - result = await db.execute(stmt) - record = result.scalar_one_or_none() - if record: - before_db = record.status + before_db = await repo.get_status(incident_id) except Exception as e: error_msg = f"DB read error: {e}" @@ -577,18 +572,14 @@ async def debug_resolve_incident(incident_id: str) -> dict[str, Any]: except Exception as e: error_msg = f"Redis update error: {e}" - # 4. 直接更新 DB + # 4. 更新 DB (透過 Repository) db_updated = False try: - async with get_db_context() as db: - stmt = select(IncidentRecord).where(IncidentRecord.incident_id == incident_id) - result = await db.execute(stmt) - record = result.scalar_one_or_none() - if record: - record.status = "resolved" - record.updated_at = now_taipei() - await db.commit() - db_updated = True + db_updated = await repo.update_status( + incident_id=incident_id, + status="resolved", + updated_at=now_taipei(), + ) except Exception as e: error_msg = f"DB update error: {e}" @@ -604,12 +595,7 @@ async def debug_resolve_incident(incident_id: str) -> dict[str, Any]: after_db = None try: - async with get_db_context() as db: - stmt = select(IncidentRecord).where(IncidentRecord.incident_id == incident_id) - result = await db.execute(stmt) - record = result.scalar_one_or_none() - if record: - after_db = record.status + after_db = await repo.get_status(incident_id) except Exception: pass diff --git a/apps/api/src/repositories/incident_repository.py b/apps/api/src/repositories/incident_repository.py index c36bb2af..52239063 100644 --- a/apps/api/src/repositories/incident_repository.py +++ b/apps/api/src/repositories/incident_repository.py @@ -161,6 +161,101 @@ class IncidentDBRepository(IIncidentRepository): result = await self.create(incident) return result is not None + # ------------------------------------------------------------------------- + # Phase 16 R3.3a (2026-03-25 台北時區) + # 新增: update_outcome() - 支援 feedback endpoint 更新 outcome 欄位 + # 執行者: Claude Code + # ------------------------------------------------------------------------- + async def update_outcome( + self, + incident_id: str, + outcome: dict[str, Any], + updated_at: datetime | None = None, + ) -> bool: + """ + 更新 Incident 的 outcome 欄位 + + Args: + incident_id: Incident ID + outcome: Outcome 資料 (dict) + updated_at: 更新時間 (預設為當前時間) + + Returns: + bool: 是否更新成功 (False = 找不到記錄) + """ + async with get_db_context() as db: + result = await db.execute( + select(IncidentRecord).where( + IncidentRecord.incident_id == incident_id + ) + ) + record = result.scalar_one_or_none() + + if not record: + logger.warning( + "update_outcome_not_found", + incident_id=incident_id, + ) + return False + + record.outcome = outcome + record.updated_at = updated_at or datetime.now(UTC) + await db.commit() + + logger.debug( + "incident_outcome_updated", + incident_id=incident_id, + ) + + return True + + + # ------------------------------------------------------------------------- + # Phase 16 R3.4 (2026-03-26 台北時區) + # 新增: get_status() / update_status() - 支援 debug 端點重構 + # 執行者: Claude Code + # ------------------------------------------------------------------------- + async def get_status(self, incident_id: str) -> str | None: + """取得 Incident 的狀態 (for debug)""" + async with get_db_context() as db: + result = await db.execute( + select(IncidentRecord).where( + IncidentRecord.incident_id == incident_id + ) + ) + record = result.scalar_one_or_none() + return record.status if record else None + + async def update_status( + self, + incident_id: str, + status: str, + updated_at: datetime | None = None, + ) -> bool: + """更新 Incident 的狀態 (for debug)""" + async with get_db_context() as db: + result = await db.execute( + select(IncidentRecord).where( + IncidentRecord.incident_id == incident_id + ) + ) + record = result.scalar_one_or_none() + + if not record: + return False + + record.status = status + record.updated_at = updated_at or datetime.now(UTC) + await db.commit() + + logger.debug( + "incident_status_updated", + incident_id=incident_id, + status=status, + ) + + return True + # ============================================================================= # Singleton diff --git a/apps/api/src/services/trust_engine.py b/apps/api/src/services/trust_engine.py index f93c6017..b94e75fa 100644 --- a/apps/api/src/services/trust_engine.py +++ b/apps/api/src/services/trust_engine.py @@ -19,20 +19,19 @@ Phase 3.2: Progressive Autonomy import logging from dataclasses import dataclass, field from datetime import datetime -# from enum import Enum # Phase 16 R2: 不再需要,RiskLevel 改從 models 導入 from typing import Literal +# Phase 16 R2 (2026-03-25): RiskLevel 統一改從 models/approval.py 導入 +# 原因: 消除重複定義,統一風險等級來源 +# 執行者: Claude Code +# 回滾: 取消註解下方 RiskLevel class 區塊,移除此 import +from src.models.approval import RiskLevel + logger = logging.getLogger(__name__) # ==================== Types ==================== -# Phase 16 R2 (2026-03-25): RiskLevel 統一改從 models/approval.py 導入 -# 原因: 消除重複定義,統一風險等級來源 -# 執行者: Claude Code -# 回滾: 取消註解下方區塊,移除 import -from src.models.approval import RiskLevel - # --- 以下為舊定義,已封存 (Phase 16 R2) --- # class RiskLevel(str, Enum): # """風險等級""" diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 4727605a..509f0242 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -5,17 +5,55 @@ --- -## 📍 當前狀態 (2026-03-25 22:40 台北) +## 📍 當前狀態 (2026-03-26 00:30 台北) | 項目 | 狀態 | |------|------| -| **當前 Phase** | **Phase 16 R4 ✅ 完成** | +| **當前 Phase** | **Phase 16 R2 ✅ + R3 ✅ + R4 ✅ 完成** | | **Day** | Day 8 | | **驗證結束** | 2026-03-27 16:04 (48小時後) | | **重大決策** | ✅ **USE_NEW_ENGINE=true 已啟用** | | **CI/CD** | ✅ **已修復** (移除自毀指令 + .gitignore 強化) | | **新規** | ✅ **絞殺者模式** + **封存策略** + **積木化強制執行** | +### ✅ 2026-03-26 Phase 16 R3 Repository 層整合 (Day 8 深夜 00:30) + +**完成項目**: + +| 模組 | 動作 | 說明 | +|------|------|------| +| incident_repository.py | 新增方法 | `update_outcome()` 支援 feedback 端點 | +| incidents.py | 重構 | feedback 端點改用 Repository (消除直接 DB 存取) | +| incidents.py | 清理 | 移除未使用的 sqlalchemy/db import | + +**驗證結果**: 24/24 測試通過 + +**Repository 層完整性**: +- `IncidentDBRepository`: create, get_by_id, get_active, update, upsert, **update_outcome** ✅ +- Router 層不再直接使用 `get_db_context()` + +--- + +### ✅ 2026-03-25 Phase 16 R2 封存死代碼 (Day 8 晚間 23:15) + +**完成項目**: + +| 模組 | 動作 | 說明 | +|------|------|------| +| routes/approvals.py | 封存 | 477 行 → _archived/ (未註冊死代碼) | +| services/approval.py | 封存 | 389 行 → _archived/ (僅被死代碼使用) | +| models/approval.py | 新增 HIGH | RiskLevel 統一來源 | +| trust_engine.py | 改 import | 從 models/approval.py 導入 RiskLevel | +| services/__init__.py | 移除舊 import | 已封存模組的 import 註解保留 | + +**封存總量**: 866 行死代碼 + +**回滾指令**: 見 `apps/api/src/_archived/README.md` + +**Commit**: e0584bc + +--- + ### ✅ 2026-03-25 Phase 16 R4.2 ApprovalExecutionService 完成 (Day 8 晚間 22:36) **完成項目**: diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100755 index 00000000..99691dd8 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,331 @@ +#!/bin/bash +# ============================================================ +# AWOOOI Red Zone Pre-Commit Hook +# Version: 1.3 +# Created: 2026-03-26 12:30 (台北時區) +# Created by: Claude Code +# Last modified: 2026-03-26 16:45 (台北時區) +# Last modified by: Claude Code +# Description: 紅區變更警告 + leWOOOgo 積木化違規檢查 +# ============================================================ + +# 顏色定義 +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color +BOLD='\033[1m' + +# ============================================================ +# 紅區定義函數 +# ============================================================ +get_tier3_info() { + local file="$1" + case "$file" in + "apps/api/src/services/decision_manager.py") + echo "決策狀態機" + echo "OpenClaw AI 的決策核心,控制 PENDING→APPROVED→EXECUTED 狀態流轉" + echo "AI 無法做出決策|審批流程卡死|所有待處理告警無法推進" + ;; + "apps/api/src/services/trust_engine.py") + echo "信任評分引擎" + echo "計算操作的信任分數,決定是否需要 Multi-Sig 審批" + echo "危險操作被自動執行|低風險操作被過度審批|Multi-Sig 機制失效" + ;; + "apps/api/src/services/consensus_engine.py") + echo "共識引擎" + echo "Multi-Sig 多方簽核邏輯,確保關鍵操作需多人同意" + echo "單點簽核繞過安全機制|審批人數計算錯誤|共識達成判斷失靈" + ;; + "apps/api/src/services/incident_engine.py") + echo "事件處理引擎" + echo "告警接收、分類、派發的核心引擎" + echo "告警無法被接收|事件分類錯誤|Telegram 通知失敗" + ;; + "apps/api/src/services/multi_sig_redis.py") + echo "分散式鎖服務" + echo "Redis 分散式鎖,防止併發競爭" + echo "同一事件被多次處理|死鎖導致系統卡死|資料不一致" + ;; + "apps/api/src/services/security_interceptor.py") + echo "安全攔截器" + echo "權限驗證、危險操作攔截" + echo "權限繞過漏洞|危險操作未被攔截|安全審計失效" + ;; + "apps/api/src/core/config.py") + echo "環境配置中心" + echo "所有服務的連線配置 (Redis/PostgreSQL/Ollama/Telegram)" + echo "所有外部連線中斷|服務無法啟動|環境變數解析失敗" + ;; + "apps/api/src/core/telemetry.py") + echo "OTEL 監控核心" + echo "OpenTelemetry 追蹤與指標收集" + echo "SignOz 無法收到追蹤數據|系統可觀測性失明|問題排查極度困難" + ;; + *) + echo "" + ;; + esac +} + +get_tier2_info() { + local file="$1" + case "$file" in + k8s/awoooi-prod/*) + echo "K8s 正式環境配置" + echo "Kubernetes 正式環境部署檔案" + echo "正式環境部署失敗|NetworkPolicy 錯誤導致服務隔離|Pod 無法啟動" + ;; + "apps/api/src/db/models.py") + echo "資料庫 Schema" + echo "SQLAlchemy ORM 模型定義" + echo "資料庫結構不一致|Migration 衝突|資料遺失風險" + ;; + .github/workflows/*) + echo "CI/CD Pipeline" + echo "GitHub Actions 自動化流程" + echo "自動部署中斷|測試流程失效|可能產生 GitHub 帳單費用" + ;; + "apps/api/src/core/redis_client.py") + echo "Redis 連線池" + echo "Redis 連線管理與 Stream 操作" + echo "快取服務中斷|Stream 訊息無法消費|Worker 無法接收任務" + ;; + "apps/api/src/services/telegram_gateway.py") + echo "Telegram 閘道" + echo "Telegram Bot 訊息收發" + echo "告警通知無法送達|審批按鈕失效|統帥無法收到即時通知" + ;; + *) + echo "" + ;; + esac +} + +# ============================================================ +# 顯示警告函數 +# ============================================================ +show_tier3_warning() { + local file="$1" + local info + info=$(get_tier3_info "$file") + + if [ -z "$info" ]; then + return 1 + fi + + local name=$(echo "$info" | sed -n '1p') + local desc=$(echo "$info" | sed -n '2p') + local impacts=$(echo "$info" | sed -n '3p') + + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo -e "║ ${RED}${BOLD}🔴 TIER 3 紅區變更警告${NC} ║" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" + echo -e "${RED}${BOLD}[Tier 3 紅區] ${name}${NC}" + echo "" + echo -e "${CYAN}功能:${NC} ${desc}" + echo "" + echo -e "${YELLOW}修改錯誤將導致:${NC}" + IFS='|' read -ra IMPACT_ARRAY <<< "$impacts" + for impact in "${IMPACT_ARRAY[@]}"; do + echo " - $impact" + done + echo "" + echo -e "${RED}風險等級: 極高${NC}" + echo "" + echo -e "${BOLD}變更檔案:${NC} $file" + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo -e "║ ${RED}${BOLD}🏛️ 首席架構師審查必要${NC} ║" + echo "╠══════════════════════════════════════════════════════════════╣" + echo "║ 1. 停止 commit,呼叫首席架構師介入 ║" + echo "║ 2. 首席架構師進行架構與代碼 Review ║" + echo "║ 3. 確認變更必要性、影響範圍、替代方案 ║" + echo "║ 4. 首席架構師簽核後,方可繼續 ║" + echo "╚══════════════════════════════════════════════════════════════╝" + + return 0 +} + +show_tier2_warning() { + local file="$1" + local info + info=$(get_tier2_info "$file") + + if [ -z "$info" ]; then + return 1 + fi + + local name=$(echo "$info" | sed -n '1p') + local desc=$(echo "$info" | sed -n '2p') + local impacts=$(echo "$info" | sed -n '3p') + + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo -e "║ ${YELLOW}${BOLD}🟠 TIER 2 橙區變更警告${NC} ║" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" + echo -e "${YELLOW}${BOLD}[Tier 2 橙區] ${name}${NC}" + echo "" + echo -e "${CYAN}功能:${NC} ${desc}" + echo "" + echo -e "${YELLOW}可能影響:${NC}" + IFS='|' read -ra IMPACT_ARRAY <<< "$impacts" + for impact in "${IMPACT_ARRAY[@]}"; do + echo " - $impact" + done + echo "" + echo -e "${BOLD}變更檔案:${NC} $file" + + return 0 +} + +# ============================================================ +# leWOOOgo 積木化違規檢查 (2026-03-26 審計後新增) +# ============================================================ +check_lewooogo_violations() { + local has_violation=false + local staged_files=$(git diff --cached --name-only) + + # 檢查 1: Router 層禁止直接引用 redis_client + # Phase 16 R3.4 修正: 只檢查新增行 (^+),忽略刪除行 (^-) 和註解行 (#) + if echo "$staged_files" | grep -q "^apps/api/src/api/"; then + if git diff --cached -- 'apps/api/src/api/*.py' | grep "^+" | grep -v "^+++" | grep -v "^+.*#" | grep -q "from src.core.redis_client"; then + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo -e "║ ${RED}${BOLD}🔴 leWOOOgo 積木化違規${NC} ║" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" + echo -e "${RED}${BOLD}違規: Router 層禁止直接引用 redis_client${NC}" + echo "" + echo -e "${CYAN}原則:${NC} Router 層只做 HTTP 路由,不應直接存取資料層" + echo -e "${YELLOW}正確做法:${NC} 透過 Service 層存取 Redis" + echo "" + echo " ❌ from src.core.redis_client import get_redis" + echo " ✅ from src.services.xxx_service import XxxService" + echo "" + has_violation=true + fi + fi + + # 檢查 2: Router 層禁止直接引用 db session + # Phase 16 R3.4 修正: 只檢查新增行 (^+),忽略刪除行 (^-) 和註解行 (#) + if echo "$staged_files" | grep -q "^apps/api/src/api/"; then + if git diff --cached -- 'apps/api/src/api/*.py' | grep "^+" | grep -v "^+++" | grep -v "^+.*#" | grep -q "from src.db.base import"; then + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo -e "║ ${RED}${BOLD}🔴 leWOOOgo 積木化違規${NC} ║" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" + echo -e "${RED}${BOLD}違規: Router 層禁止直接引用 DB Session${NC}" + echo "" + echo -e "${CYAN}原則:${NC} Router 層只做 HTTP 路由,不應直接存取資料庫" + echo -e "${YELLOW}正確做法:${NC} 透過 Service/Repository 層存取" + echo "" + echo " ❌ from src.db.base import get_session" + echo " ✅ from src.services.xxx_service import XxxService" + echo "" + has_violation=true + fi + fi + + # 檢查 3: 新增 services/ 檔案時提醒檢查 packages/ + if echo "$staged_files" | grep -q "^apps/api/src/services/.*\.py$"; then + local new_services=$(echo "$staged_files" | grep "^apps/api/src/services/.*\.py$") + for svc in $new_services; do + # 檢查是否為新建檔案 + if ! git ls-files --error-unmatch "$svc" > /dev/null 2>&1; then + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo -e "║ ${YELLOW}${BOLD}⚠️ leWOOOgo 積木化提醒${NC} ║" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" + echo -e "${YELLOW}${BOLD}新增 Service 檔案: ${svc}${NC}" + echo "" + echo -e "${CYAN}修改前必問 5 題:${NC}" + echo " 1. 這個邏輯是否已存在於 packages/?" + echo " 2. 是否可被其他模組重用?(應放 packages/)" + echo " 3. 是否使用 Interface 定義通訊契約?" + echo " 4. 是否遵循依賴注入原則?" + echo " 5. 是否避免與其他 Service 緊耦合?" + echo "" + echo -e "${CYAN}參考:${NC} packages/lewooogo-brain/ 和 packages/lewooogo-data/" + echo "" + fi + done + fi + + if [ "$has_violation" = true ]; then + echo "═══════════════════════════════════════════════════════════════" + echo "" + echo -e "${RED}${BOLD}🔴 leWOOOgo 積木化鐵律${NC}" + echo "" + echo "架構層次: Router → Service → packages/lewooogo-*/" + echo "" + echo "Memory: feedback_lewooogo_modular_enforcement.md" + echo "Skill: 02-lewooogo-backend-core.md" + echo "" + echo "═══════════════════════════════════════════════════════════════" + # 違規則阻止 commit + return 1 + fi + + return 0 +} + +# ============================================================ +# 主要檢查邏輯 +# ============================================================ +main() { + local has_warning=false + local staged_files=$(git diff --cached --name-only) + + # === leWOOOgo 積木化違規檢查 (硬性阻擋) === + if ! check_lewooogo_violations; then + echo "" + echo -e "${RED}${BOLD}Commit 被阻止: 請先修復 leWOOOgo 積木化違規${NC}" + echo "" + exit 1 + fi + + for file in $staged_files; do + # 檢查 Tier 3 + if show_tier3_warning "$file"; then + has_warning=true + # 檢查 Tier 2 + elif show_tier2_warning "$file"; then + has_warning=true + fi + done + + if [ "$has_warning" = true ]; then + echo "" + echo "═══════════════════════════════════════════════════════════════" + echo "" + echo -e "${RED}${BOLD}🏛️ 首席架構師審查流程${NC}" + echo "" + echo "此變更涉及紅區/橙區,必須經過以下流程:" + echo "" + echo " 1. Claude Code 停止自動 commit" + echo " 2. 呼叫首席架構師進行架構 Review" + echo " 3. 首席架構師確認:" + echo " - 變更必要性" + echo " - 影響範圍評估" + echo " - 替代方案評估" + echo " - 回滾計畫" + echo " 4. 首席架構師簽核 → 方可繼續 commit" + echo "" + echo "參考文件: docs/RED_ZONES.md" + echo "" + echo "═══════════════════════════════════════════════════════════════" + fi + + # 警告但允許繼續 (統帥指示) + return 0 +} + +# 執行 +main