diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 41fa69f..f4ab1b4 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -93,11 +93,10 @@ jobs: run: | CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "") echo "$CHANGED" - if echo "$CHANGED" | grep -qE '^(templates/admin/.*|templates/ewoooc_base\.html|templates/components/_ewoooc_shell\.html|static/css/observability-system\.css|web/static/css/observability-system\.css|routes/admin_observability_routes\.py|scripts/(check_observability_|check_observability_suite\.sh|observability_contract|quick_review\.sh|sync_observability_css)|docs/guides/observability_ui_governance\.md|docs/guides/deployment_sop\.md)'; then - echo "needed=true" >> $GITHUB_OUTPUT + printf '%s\n' "$CHANGED" | python3 scripts/check_observability_deploy_gate.py --stdin --github-output "$GITHUB_OUTPUT" + if grep -q '^needed=true$' "$GITHUB_OUTPUT"; then echo "🎛️ AI 觀測台 QA: required" else - echo "needed=false" >> $GITHUB_OUTPUT echo "ℹ️ AI 觀測台 QA: skipped" fi CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "") diff --git a/docs/guides/deployment_sop.md b/docs/guides/deployment_sop.md index 7feb782..f64015b 100644 --- a/docs/guides/deployment_sop.md +++ b/docs/guides/deployment_sop.md @@ -78,6 +78,7 @@ CD 也會自動判斷觀測台相關變更: - Deploy 後跑 `./scripts/quick_review.sh --observability-smoke --base-url https://mo.wooo.work --timeout 12`。 - 若變更與觀測台無關,CD 會跳過這組額外 QA,避免拖慢一般後端部署。 - 觸發範圍包含觀測台 templates、shell/topbar、觀測台 CSS、`routes/admin_observability_routes.py`、`quick_review.sh`、`check_observability_*`、`observability_contract.py`、`sync_observability_css.py`。 +- 觸發判斷集中在 `scripts/check_observability_deploy_gate.py`,不要在 workflow 內新增第二份長 regex。 ## 🔍 維運指令 - **查看日誌**: `docker logs -f momo-pro-system --tail 100` diff --git a/docs/memory/observability_ui_qa_guardrails_20260505.md b/docs/memory/observability_ui_qa_guardrails_20260505.md index b686e98..1b8ba8d 100644 --- a/docs/memory/observability_ui_qa_guardrails_20260505.md +++ b/docs/memory/observability_ui_qa_guardrails_20260505.md @@ -56,6 +56,7 @@ bash scripts/check_observability_suite.sh - 觀測台頁面清單、URL、`active_page`、內容 marker 不要分散維護,先改 `scripts/observability_contract.py`。 - `quick_review.sh --observability-qa` 預設打 production `https://mo.wooo.work`;測 staging/localhost 時要明確帶 `--base-url`。 - Gitea CD 會偵測觀測台 template/CSS/route/QA script/guide 變更:deploy 前跑 CSS mirror check + static QA,deploy 後跑 production smoke。QA script 範圍包含 `quick_review.sh`、`check_observability_*`、`observability_contract.py`、`sync_observability_css.py`。CD 不會偷偷修 mirror;若 check fail,先本地跑 sync 後提交。 +- CD 觸發判斷集中在 `scripts/check_observability_deploy_gate.py`;不要在 `.gitea/workflows/cd.yaml` 另維護一份長 regex。 ## 已鎖住的回歸 diff --git a/scripts/check_observability_deploy_gate.py b/scripts/check_observability_deploy_gate.py new file mode 100644 index 0000000..06e6de4 --- /dev/null +++ b/scripts/check_observability_deploy_gate.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Decide whether AI observability deploy QA should run. + +Input is a list of changed files via argv or stdin. The script prints matched +files and can write `needed=true|false` to a GitHub/Gitea Actions output file. +It exits 0 for both true and false so workflow control stays explicit. +""" + +from __future__ import annotations + +import argparse +import os +import re +from pathlib import Path + + +TRIGGER_PATTERNS = ( + r"^templates/admin/.*", + r"^templates/ewoooc_base\.html$", + r"^templates/components/_ewoooc_shell\.html$", + r"^static/css/observability-system\.css$", + r"^web/static/css/observability-system\.css$", + r"^routes/admin_observability_routes\.py$", + r"^scripts/check_observability_.*", + r"^scripts/check_observability_suite\.sh$", + r"^scripts/observability_contract\.py$", + r"^scripts/quick_review\.sh$", + r"^scripts/sync_observability_css\.py$", + r"^docs/guides/observability_ui_governance\.md$", + r"^docs/guides/deployment_sop\.md$", +) + + +def normalize_paths(raw_paths: list[str]) -> list[str]: + paths: list[str] = [] + for raw in raw_paths: + for line in raw.splitlines(): + path = line.strip() + if path: + paths.append(path) + return paths + + +def match_paths(paths: list[str]) -> list[str]: + regexes = [re.compile(pattern) for pattern in TRIGGER_PATTERNS] + return [path for path in paths if any(regex.search(path) for regex in regexes)] + + +def write_output(output_path: str | None, needed: bool) -> None: + if not output_path: + return + path = Path(output_path) + with path.open("a", encoding="utf-8") as handle: + handle.write(f"needed={'true' if needed else 'false'}\n") + + +def main() -> int: + parser = argparse.ArgumentParser(description="Check observability deploy QA trigger") + parser.add_argument("paths", nargs="*", help="Changed file paths") + parser.add_argument("--stdin", action="store_true", help="Read changed paths from stdin") + parser.add_argument( + "--github-output", + default=os.environ.get("GITHUB_OUTPUT"), + help="Actions output file to append needed=true|false.", + ) + args = parser.parse_args() + + raw_paths = list(args.paths) + if args.stdin: + import sys + + raw_paths.append(sys.stdin.read()) + + paths = normalize_paths(raw_paths) + matches = match_paths(paths) + needed = bool(matches) + + write_output(args.github_output, needed) + + print(f"observability_qa_needed={'true' if needed else 'false'}") + if matches: + print("matched_files:") + for path in matches: + print(f"- {path}") + else: + print("matched_files: none") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())