diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index df24cf0..c2047e6 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -110,6 +110,7 @@ - 2026-07-03 起高可見 AI observability surfaces 必須有 route-level rendered HTML readback guard;`/observability/overview`、`/observability/ai_calls`、`/observability/quality_trend`、`/observability/budget`、`/observability/business_intel`、`/observability/host_health`、`/observability/rag_queries`、`/observability/agent_orchestration`、`/observability/promotion_review`、`/observability/ppt_audit_history` 必須在 Flask client render 後仍保留 compact density marker,且不得漏出 `PPT_VISION_ENABLED=`、`PPT_AUTO_GENERATION_ENABLED=`、`ppt_generation_runs`、DB write raw wording、manual/human gate primary wording、Artifact retention / Compact 回讀等產品面不該可見的 engineering copy。 - 2026-07-03 起高可見 AI surface HTML readback 必須納入 runtime AI smoke:`/api/ai-automation/surface-html-readback` 會 read-only 檢查 10 個 AI observability template contract,`/api/ai-automation/smoke` 需包含 `AI surface HTML readback` 檢查,`/api/ai-automation/scheduled-health-summary` 需輸出 `ai_surface_html_readback` family;此檢查不得寫 DB、不得送通知、不得要求 browser,失敗時下一個機器動作是 `repair_ai_surface_html_contract`。 - 2026-07-03 起整站 UI/UX 專業化必須由 Sitewide UI/UX Agent 管理:`/api/ai-automation/sitewide-ui-ux-agent` 會掃描真正頁面模板、排除 partial/components/base layouts,輸出高優先頁、compact guardrail、raw engineering issue 與 next machine action;`/api/ai-automation/sitewide-ui-ux-repair-package` 會產生 no-write controlled repair package。當前 Agent 判定為 55 個頁面模板、31 個高優先模板、29 個已具 compact guardrail、0 個待專業化、0 個 raw engineering leak,Sitewide Agent 狀態為 `ok`,repair package 為 `no_op`。四批 controlled UI/UX repair 已完成 AI observability、AI 建議、PPT/CI/CD/業績/比價/成長/供貨風險高優先 surface;下一個機器動作為 `keep_sitewide_ui_ux_agent_monitoring`,後續只接受 Agent 新增 issue 或更高階 visual/mobile QA 的證據驅動修復。 +- 2026-07-03 起 Sitewide UI/UX Agent 歸零狀態必須納入 AI automation runtime monitoring:`/api/ai-automation/smoke` 需包含 `Sitewide UI/UX Agent readback` 檢查,要求 `issue_surface_count=0`、`repair_package_status=no_op`、`repair_action_count=0`;`/api/ai-automation/scheduled-health-summary` 需輸出 `sitewide_ui_ux_agent` family;`/metrics` 需以 `momo_ai_automation_scheduled_health_family_status{family="sitewide_ui_ux_agent",...}` 暴露 scheduled family。此監控不得寫 DB、不得送通知、不得要求人工審核。 - 2026-07-02 起 `/ai_intelligence` 商品明細與單品作戰詳情的四格價格證據必須可測:PChome 價格、MOMO 參考價、差距、可信度需以 `data-evidence` 固定,並以 `aria-label="價格證據"` 對應可掃描區塊;候選待確認或缺資料只能顯示「候選待確認 / 待補」,不得捏造價格或讓使用者打開 raw payload 才知道判斷依據。 - 2026-07-02 起 `/ai_intelligence` 必須是密集 AI 工作台,不得退回大段文字說明頁:首屏與明細可見內容只保留短狀態、數字、四格證據與下一步按鈕;KPI note、benchmark detail、alert 副句、策略說明、decision copy、來源長句與單品 reason list 不得佔用第一層視覺。`tests/test_ai_intelligence_text_density_guardrails.py` 必須鎖住 `data-density-guardrail="compact-ai-workbench"`、短任務文案、detail meta 與 hidden explanatory copy。 - 2026-07-02 起 `/observability/overview` 也必須採密集 AI 觀測工作台:首屏以 `data-density-guardrail="compact-observability-workbench"`、`AI 觀測 / 風險優先 / 下一步` 與 golden signals 先呈現狀態、數字與操作入口;hero lede、signal note、route desc、host meta 與資料來源長句不得佔用第一層視覺。`tests/test_observability_text_density_guardrails.py` 必須鎖住 compact marker 與 hidden explanatory copy。 diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index c302b14..ad0fb4c 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -173,6 +173,7 @@ - 全站主要頁面需通過 desktop / mobile overflow guard。 - 表格與圖表只允許在局部容器橫向滾動,不可造成整頁無限延伸。 - 2026-07-03 起以 Sitewide UI/UX Agent 排序全站專業化工作,不再靠人工逐頁猜測;目前 55 個頁面模板 / 31 個高優先頁中,compact guardrail 已由 14 提升到 29,待專業化 surface 由 15 降到 0。第四批已完成 vendor stockout 剩餘五頁,repair package 進入 `no_op`,下一步轉為 production monitoring 與 desktop/mobile visual QA。 +- 2026-07-03 起 Sitewide UI/UX Agent 已接入 AI automation smoke、scheduled health summary 與 `/metrics` family;之後若整站 UI/UX 退化,主線入口是 `Sitewide UI/UX Agent readback` / `sitewide_ui_ux_agent` family,而不是人工逐頁盤點。 ## 8. 效能與可觀測性 diff --git a/services/ai_automation_smoke_service.py b/services/ai_automation_smoke_service.py index eca0f87..4cf314f 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -210,6 +210,8 @@ def build_scheduled_automation_health_summary( pchome_details = pchome_drift.get("details") or {} surface_readback = _find_check(source_result, "AI surface HTML readback") surface_details = surface_readback.get("details") or {} + sitewide_readback = _find_check(source_result, "Sitewide UI/UX Agent readback") + sitewide_details = sitewide_readback.get("details") or {} smoke_status = source_result.get("status") or ("warning" if latest_history else "warning") freshness_family = _history_freshness_family( latest_history, @@ -276,6 +278,28 @@ def build_scheduled_automation_health_summary( "writes_database": False, }, }, + { + "key": "sitewide_ui_ux_agent", + "label": "Sitewide UI/UX Agent", + "status": sitewide_readback.get("status") or "warning", + "summary": sitewide_readback.get("summary") or "Sitewide UI/UX Agent has no latest check.", + "next_machine_action": ( + "keep_sitewide_ui_ux_agent_monitoring" + if sitewide_readback.get("status") == "ok" + else "build_sitewide_ui_ux_repair_package" + ), + "details": { + "policy": sitewide_details.get("policy"), + "template_count": int(sitewide_details.get("template_count") or 0), + "high_priority_template_count": int(sitewide_details.get("high_priority_template_count") or 0), + "compact_guardrail_count": int(sitewide_details.get("compact_guardrail_count") or 0), + "issue_surface_count": int(sitewide_details.get("issue_surface_count") or 0), + "raw_engineering_issue_count": int(sitewide_details.get("raw_engineering_issue_count") or 0), + "repair_package_status": sitewide_details.get("repair_package_status"), + "repair_action_count": int(sitewide_details.get("repair_action_count") or 0), + "writes_database": False, + }, + }, freshness_family, { "key": "daily_summary_delivery", @@ -849,6 +873,71 @@ def _ai_surface_html_readback_check() -> Dict[str, Any]: ) +def _sitewide_ui_ux_agent_check() -> Dict[str, Any]: + """Read-only sitewide professional UI/UX regression sentinel.""" + try: + from services.ai_surface_html_readback_service import ( + build_sitewide_ui_ux_agent_inventory, + build_sitewide_ui_ux_repair_package, + ) + + inventory = build_sitewide_ui_ux_agent_inventory() + repair_package = build_sitewide_ui_ux_repair_package(source_inventory=inventory, limit=5) + summary = inventory.get("summary") or {} + package_summary = repair_package.get("summary") or {} + issue_count = int(summary.get("issue_surface_count") or 0) + raw_issue_count = int(summary.get("raw_engineering_issue_count") or 0) + write_count = int(summary.get("writes_database_count") or 0) + int( + package_summary.get("writes_database_count") or 0 + ) + repair_action_count = int(package_summary.get("controlled_action_count") or 0) + status = "ok" + if write_count: + status = "critical" + elif issue_count or raw_issue_count or repair_action_count: + status = "critical" + summary_text = ( + "Sitewide UI/UX Agent ok/no-op,整站專業化 surface 無退化" + if status == "ok" + else ( + f"Sitewide UI/UX Agent 偵測 {issue_count} 個待修 surface、" + f"{raw_issue_count} 個 raw wording、{repair_action_count} 個待套用 action" + ) + ) + return _check( + "Sitewide UI/UX Agent readback", + status, + summary_text, + { + "policy": inventory.get("policy"), + "inventory_status": inventory.get("status"), + "template_count": int(summary.get("template_count") or 0), + "high_priority_template_count": int(summary.get("high_priority_template_count") or 0), + "compact_guardrail_count": int(summary.get("compact_guardrail_count") or 0), + "issue_surface_count": issue_count, + "raw_engineering_issue_count": raw_issue_count, + "next_machine_action": inventory.get("next_machine_action"), + "repair_package_policy": repair_package.get("policy"), + "repair_package_status": repair_package.get("status"), + "repair_action_count": repair_action_count, + "repair_next_machine_action": repair_package.get("next_machine_action"), + "writes_database": False, + "writes_database_count": write_count, + "primary_human_gate_count": 0, + }, + ) + except Exception as exc: + return _check( + "Sitewide UI/UX Agent readback", + "critical", + f"Sitewide UI/UX Agent 無法執行:{exc}", + { + "writes_database": False, + "primary_human_gate_count": 0, + }, + ) + + def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: int = 20) -> Dict[str, Any]: checks: List[Dict[str, Any]] = [ _event_router_check(), @@ -859,6 +948,7 @@ def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: i _elephant_hitl_check(), _pchome_controlled_apply_drift_monitor_check(), _ai_surface_html_readback_check(), + _sitewide_ui_ux_agent_check(), ] worst = max(checks, key=lambda item: STATUS_RANK.get(item["status"], 2))["status"] result = { diff --git a/tests/test_ai_automation_metrics.py b/tests/test_ai_automation_metrics.py index 9ed06b9..0e943c7 100644 --- a/tests/test_ai_automation_metrics.py +++ b/tests/test_ai_automation_metrics.py @@ -117,6 +117,7 @@ def test_system_metrics_exports_scheduled_health_summary(): "families": [ {"key": "ai_automation_smoke", "status": "ok"}, {"key": "pchome_controlled_apply_drift_monitor", "status": "warning"}, + {"key": "sitewide_ui_ux_agent", "status": "ok"}, ], }, ) @@ -132,6 +133,10 @@ def test_system_metrics_exports_scheduled_health_summary(): 'momo_ai_automation_scheduled_health_family_status{family="pchome_controlled_apply_drift_monitor",status="warning"} 1.0' in output ) + assert ( + 'momo_ai_automation_scheduled_health_family_status{family="sitewide_ui_ux_agent",status="ok"} 1.0' + in output + ) assert "momo_ai_automation_scheduled_health_primary_human_gate_count 0.0" in output assert "momo_ai_automation_scheduled_health_writes_database_count 0.0" in output diff --git a/tests/test_ai_automation_smoke_service.py b/tests/test_ai_automation_smoke_service.py index ff349c7..422fc86 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -37,11 +37,12 @@ def test_collect_ai_automation_smoke_uses_worst_status(monkeypatch): monkeypatch.setattr(smoke, "_elephant_hitl_check", lambda: smoke._check("elephant", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_controlled_apply_drift_monitor_check", lambda: smoke._check("pchome", "ok", "ok")) monkeypatch.setattr(smoke, "_ai_surface_html_readback_check", lambda: smoke._check("surface", "ok", "ok")) + monkeypatch.setattr(smoke, "_sitewide_ui_ux_agent_check", lambda: smoke._check("sitewide", "ok", "ok")) result = smoke.collect_ai_automation_smoke(record_history=False) assert result["status"] == "critical" - assert result["summary"] == {"ok": 6, "warning": 1, "critical": 1, "total": 8} + assert result["summary"] == {"ok": 7, "warning": 1, "critical": 1, "total": 9} def test_pchome_controlled_apply_drift_monitor_reports_verified_zero_drift(monkeypatch): @@ -109,6 +110,7 @@ def test_collect_ai_automation_smoke_persists_recent_history(tmp_path, monkeypat monkeypatch.setattr(smoke, "_elephant_hitl_check", lambda: smoke._check("elephant", "ok", "ok")) monkeypatch.setattr(smoke, "_pchome_controlled_apply_drift_monitor_check", lambda: smoke._check("pchome", "ok", "ok")) monkeypatch.setattr(smoke, "_ai_surface_html_readback_check", lambda: smoke._check("surface", "ok", "ok")) + monkeypatch.setattr(smoke, "_sitewide_ui_ux_agent_check", lambda: smoke._check("sitewide", "ok", "ok")) first = smoke.collect_ai_automation_smoke(history_limit=5) second = smoke.collect_ai_automation_smoke(history_limit=5) @@ -164,7 +166,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( json.dumps({ "generated_at": datetime.now().isoformat(timespec="seconds"), "status": "ok", - "summary": {"ok": 8, "warning": 0, "critical": 0, "total": 8}, + "summary": {"ok": 9, "warning": 0, "critical": 0, "total": 9}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -189,6 +191,21 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( "failed_count": 0, "forbidden_leak_count": 0, }, + }, + { + "name": "Sitewide UI/UX Agent readback", + "status": "ok", + "summary": "Sitewide UI/UX Agent ok/no-op,整站專業化 surface 無退化", + "details": { + "policy": "read_only_sitewide_ui_ux_agent_inventory_v1", + "template_count": 55, + "high_priority_template_count": 31, + "compact_guardrail_count": 29, + "issue_surface_count": 0, + "raw_engineering_issue_count": 0, + "repair_package_status": "no_op", + "repair_action_count": 0, + }, } ], }, ensure_ascii=False) + "\n", @@ -207,7 +224,7 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( ) assert summary["policy"] == "read_only_ai_automation_scheduled_health_summary" assert summary["status"] == "ok" - assert summary["summary"]["total"] == 5 + assert summary["summary"]["total"] == 6 assert summary["summary"]["primary_human_gate_count"] == 0 assert summary["summary"]["writes_database_count"] == 0 assert pchome_family["status"] == "ok" @@ -218,6 +235,13 @@ def test_scheduled_automation_health_summary_reads_history_without_side_effects( ) assert surface_family["status"] == "ok" assert surface_family["details"]["checked_surface_count"] == 10 + sitewide_family = next( + item for item in summary["families"] + if item["key"] == "sitewide_ui_ux_agent" + ) + assert sitewide_family["status"] == "ok" + assert sitewide_family["details"]["issue_surface_count"] == 0 + assert sitewide_family["details"]["repair_package_status"] == "no_op" assert summary["scheduled_outputs"]["telegram_send_in_preview"] is False assert summary["scheduled_outputs"]["writes_database"] is False assert summary["automation_policy"]["primary_flow"] == "ai_controlled" @@ -232,7 +256,7 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor current_smoke = { "generated_at": "2026-07-02T12:00:00", "status": "critical", - "summary": {"ok": 7, "warning": 0, "critical": 1, "total": 8}, + "summary": {"ok": 8, "warning": 0, "critical": 1, "total": 9}, "checks": [ { "name": "PChome 受控落地 drift monitor", @@ -245,6 +269,18 @@ def test_scheduled_automation_health_summary_can_use_current_smoke_without_recor "status": "ok", "summary": "AI surface HTML readback 通過 10/10,無產品面 raw wording", "details": {"checked_surface_count": 10, "pass_count": 10, "failed_count": 0}, + }, + { + "name": "Sitewide UI/UX Agent readback", + "status": "ok", + "summary": "Sitewide UI/UX Agent ok/no-op,整站專業化 surface 無退化", + "details": { + "template_count": 55, + "compact_guardrail_count": 29, + "issue_surface_count": 0, + "repair_package_status": "no_op", + "repair_action_count": 0, + }, } ], } @@ -455,9 +491,16 @@ def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): item for item in result["checks"] if item["name"] == "AI surface HTML readback" ) - assert result["summary"]["total"] == 8 + sitewide_check = next( + item for item in result["checks"] + if item["name"] == "Sitewide UI/UX Agent readback" + ) + assert result["summary"]["total"] == 9 assert surface_check["status"] == "ok" assert surface_check["details"]["checked_surface_count"] == 10 + assert sitewide_check["status"] == "ok" + assert sitewide_check["details"]["issue_surface_count"] == 0 + assert sitewide_check["details"]["repair_package_status"] == "no_op" def test_surface_html_readback_route_returns_payload(monkeypatch):