From c2e38be43db9014a867f9bd3e5ca8a6ca3de882d Mon Sep 17 00:00:00 2001 From: OoO Date: Thu, 30 Apr 2026 14:07:10 +0800 Subject: [PATCH] =?UTF-8?q?docs(modularization):=20=E5=BB=BA=E7=AB=8B?= =?UTF-8?q?=E6=A8=A1=E7=B5=84=E5=8C=96=E6=B2=BB=E7=90=86=E5=AE=88=E9=96=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 4 +- CONSTITUTION.md | 14 +++++- TODO_NEXT_STEPS.txt | 2 + app.py | 4 +- config.py | 2 +- .../ADR-017-modularization-cleanup-roadmap.md | 12 +++++ docs/guides/modularization_governance.md | 49 +++++++++++++++++++ docs/memory/README.md | 1 + .../code_modularization_inventory_20260430.md | 44 +++++++++++++++++ docs/memory/history_logs.md | 1 + .../memory/project_phase3f_cleanup_roadmap.md | 2 + routes/README.md | 2 + tests/test_modularization_governance.py | 44 +++++++++++++++++ 13 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 docs/guides/modularization_governance.md create mode 100644 docs/memory/code_modularization_inventory_20260430.md create mode 100644 tests/test_modularization_governance.py diff --git a/AGENTS.md b/AGENTS.md index 02f4fe9..8ed6a40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # EwoooC (MOMO Pro System) — Codex 專案工作規則 -> 版本: V13.3 +> 版本: V13.4 > 目標: 把專案知識整理成 Codex 可低成本讀取、可持續維護、可安全落地的單一工作入口。 ## 1. 入口原則 @@ -131,12 +131,14 @@ - 部署 SOP: `docs/guides/deployment_sop.md` - DevOps 手冊: `docs/guides/devops_handbook.md` +- 模組化治理: `docs/guides/modularization_governance.md` - AI 自動化 Session SOP: `docs/guides/ai_automation_session_sop.md` - AI 競價情報 SOT: `docs/AI_INTELLIGENCE_MODULE_SOT.md` - Agent 角色矩陣: `docs/guides/codex_agent_roles.md` - ADR 索引: `docs/adr/README.md` - Memory 索引: `docs/memory/README.md` - 歷史紀錄: `docs/memory/history_logs.md` +- 程式碼模組化盤點: `docs/memory/code_modularization_inventory_20260430.md` - AI 自動化閉環記憶: `docs/memory/ai_automation_closure_20260429.md` - 憑證手冊: `docs/memory/credentials_passbook.md` diff --git a/CONSTITUTION.md b/CONSTITUTION.md index 25a5e33..d5d329b 100644 --- a/CONSTITUTION.md +++ b/CONSTITUTION.md @@ -2,7 +2,7 @@ > 本文件定義專案開發的核心準則與不可違反的規範 > **建立日期**: 2026-01-12 -> **當前版本**: V10.20 (ElephantAlpha transient fallback 版) +> **當前版本**: V10.21 (模組化治理守門版) > **最後更新**: 2026-04-30 --- @@ -178,6 +178,18 @@ --- +## 第六之一章:模組化治理規範 + +### 第 21.1 條:模組分層與大檔治理(強制要求) +- ✅ **正確**:新功能必須先放入對應層級:`routes/` 只處理 HTTP/Blueprint glue,`services/` 放商業邏輯與外部 API client,`database/` 放 ORM/DB access,`utils/` 放無狀態共用工具,`templates/` 放 UI。 +- ✅ **正確**:`app.py` 只保留 Flask 初始化、Blueprint 註冊、啟動自檢、版本與全域設定;禁止新增 route、長函式或商業邏輯。 +- ✅ **正確**:現有超過 800 行的 Python 檔案視為拆分技術債,只能做 bugfix / 安全修補 / 搭橋式抽離;新增功能前應優先抽 shared service 或子模組。 +- ✅ **正確**:新增或修改導致 Python 檔案超過 600 行時,必須先評估拆模組;超過 800 行必須更新 `docs/memory/code_modularization_inventory_20260430.md` 與對應測試,並寫出拆分計畫。 +- ❌ **禁止**:把可共用邏輯藏在 route、template helper 或單一巨檔內,造成其他模組只能複製貼上。 +- **依據**:ADR-017、`docs/guides/modularization_governance.md` + +--- + ## 第七章:程式碼品質規範 ### 第 22 條:命名規範(強制要求) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 6c65f2e..2d708cc 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -29,8 +29,10 @@ - Scheduler 例外記錄強化:清除 `scheduler.py` 靜默 `except/pass`,資源清理、EDM 可選欄位、備份 insight/通知失敗全改為可診斷 log。 - AI metrics baseline 觀測:`/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免重啟後 Grafana/Prometheus 看不到 metric names。 - ElephantAlpha transient fallback:NVIDIA NIM timeout、connection error、429 與 5xx 會嘗試下一個 fallback model;400 等非暫時性請求錯誤不重試。 + - 模組化治理守門:新增 `docs/guides/modularization_governance.md`、`docs/memory/code_modularization_inventory_20260430.md` 與 `tests/test_modularization_governance.py`,盤點並鎖住 15 個 >800 行 Python 大檔。 【下次待辦】 + - 依 inventory 優先拆 `routes/openclaw_bot_routes.py`、`routes/sales_routes.py`、`scheduler.py`。 - 觀察 Prometheus scrape 後 `momo_ai_*` baseline 與非 baseline 事件序列是否持續穩定。 - Superset panel 設定與 Smoke 摘要成效觀察。 diff --git a/app.py b/app.py index f90af88..1f6f803 100644 --- a/app.py +++ b/app.py @@ -95,8 +95,8 @@ except Exception as e: sys_log.error(f"無法檢測磁碟空間: {e}") # 🚩 系統版本定義 (備份與顯示用) -# 🚩 2026-04-30 V10.20: ElephantAlpha transient NIM fallback -SYSTEM_VERSION = "V10.20" +# 🚩 2026-04-30 V10.21: Modularization governance guardrail +SYSTEM_VERSION = "V10.21" # ========================================== # 🔒 SQL Injection 防護函數 diff --git a/config.py b/config.py index 8cbc8f0..b729674 100644 --- a/config.py +++ b/config.py @@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.20" +SYSTEM_VERSION = "V10.21" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/adr/ADR-017-modularization-cleanup-roadmap.md b/docs/adr/ADR-017-modularization-cleanup-roadmap.md index 035d98d..76511e7 100644 --- a/docs/adr/ADR-017-modularization-cleanup-roadmap.md +++ b/docs/adr/ADR-017-modularization-cleanup-roadmap.md @@ -112,3 +112,15 @@ Phase 3e(4/28-29)完成 app.py 7,386→6,590 行(-10.8%),但**僅完 - 模板僅存 `templates/` + `web/templates/vendor_stockout/` - gunicorn `--preload` 啟用、cache 跨 worker 一致 - scheduler 7 處 except 全改、docker-compose mount 全清 + +## 2026-04-30 補充決策:模組化治理守門 + +Phase 3f 已陸續完成 DB metadata、路由註冊、cache、scheduler、模板與 orphan cleanup 的多個收斂項目,但 line-count 盤點仍顯示 15 個 Python 檔案超過 800 行。為避免後續功能開發再次把 route / service 寫成巨檔,本 ADR 補充以下守門規則: + +1. `app.py` 只保留 Flask app bootstrap、Blueprint registration、啟動自檢、版本與全域設定;不得新增 route 或商業邏輯。 +2. `routes/` 必須保持 thin controller,重複邏輯需抽到 `services/` 或 `utils/`。 +3. 超過 800 行的 Python 檔案列入 `docs/memory/code_modularization_inventory_20260430.md`,只能做安全修補、bugfix、或往外抽模組;新增功能應先拆分。 +4. 新增 Python 檔案超過 600 行需提出拆分理由;超過 800 行需同步更新 inventory 與測試。 +5. `tests/test_modularization_governance.py` 是守門測試:新巨檔沒有被盤點會失敗。 + +詳細操作規則以 `docs/guides/modularization_governance.md` 為準。 diff --git a/docs/guides/modularization_governance.md b/docs/guides/modularization_governance.md new file mode 100644 index 0000000..98fca2c --- /dev/null +++ b/docs/guides/modularization_governance.md @@ -0,0 +1,49 @@ +# 模組化治理指南 + +> 目的:讓 EwoooC 後續功能不再回到「單一巨檔、分類不清、共用邏輯複製貼上」的狀態。 + +## 分層規則 + +| 層級 | 放什麼 | 不放什麼 | +|---|---|---| +| `app.py` | Flask 初始化、Blueprint 註冊、啟動自檢、版本 | route 實作、DB 查詢、商業邏輯、排程任務 | +| `routes/` | HTTP request/response、表單參數解析、權限 decorator、呼叫 service | 大量資料轉換、外部 API client、可重用計算 | +| `services/` | 商業邏輯、AI pipeline、外部 API client、報表生成、可重用流程 | Flask request 物件、template rendering、route-only glue | +| `database/` | ORM models、DB manager、schema helpers、migration baseline | HTTP/Telegram/AI side effect | +| `utils/` | 無狀態純工具、字串/時間/安全 helper | 需要 DB/session/env-heavy 的流程 | +| `templates/` | Jinja UI | Python business rule | + +## 檔案尺寸規則 + +- 300 行以下:健康範圍。 +- 300-600 行:可接受,但新增功能前要確認是否能抽 service/helper。 +- 600-800 行:警戒範圍;PR/commit 需說明為何不拆。 +- 800 行以上:技術債範圍;只能做 bugfix、安全修補、或「往外抽」的過渡改動。新增功能應先拆模組。 +- 新增超過 800 行的 Python 檔案必須更新 `docs/memory/code_modularization_inventory_20260430.md`,否則 `tests/test_modularization_governance.py` 會失敗。 + +## 新功能落點決策 + +1. 先問:這是 HTTP endpoint、商業流程、DB access、外部 API、還是純工具? +2. endpoint 只新增到對應 Blueprint,不新增到 `app.py`。 +3. route 內超過 40 行的資料處理,優先抽到 `services/`。 +4. 兩個以上 route 需要同一段邏輯時,第一次就抽 shared service,不複製貼上。 +5. service 需要 DB 時,透過既有 `DatabaseManager` / repository-style helper,不在 route 裡反覆建立 engine。 +6. AI 自動化、告警、自癒相關變更需同步檢查 `docs/AI_INTELLIGENCE_MODULE_SOT.md`。 + +## 拆大檔順序 + +優先處理「又大又承擔多種責任」的檔案: + +1. `routes/openclaw_bot_routes.py`:拆成 route、bot command service、learning/report service、scheduler hook。 +2. `routes/sales_routes.py`:拆 dashboard/page routes、API routes、chart/query service。 +3. `scheduler.py`:拆 task registry、crawler jobs、report jobs、notification jobs。 +4. `routes/ai_routes.py` / `routes/vendor_routes.py`:把資料處理與整合邏輯移到 services。 +5. `services/ppt_generator.py`:拆 deck orchestration、chart builders、slide templates。 + +## Review 檢查表 + +- 是否新增 `@app.route`?若是,退回改 Blueprint。 +- route 是否直接包含大量 SQL、DataFrame、AI prompt 組裝或外部 API 呼叫?若是,抽 service。 +- 是否讓既有 >800 行檔案淨增加?若是,先抽模組或更新 inventory 與拆分計畫。 +- 是否新增重複 cache / DB manager / Telegram / AI client 初始化?若是,改用既有 service。 +- 是否需要更新 ADR、memory、SOT 或 guide?若是,本次 commit 一起更新。 diff --git a/docs/memory/README.md b/docs/memory/README.md index f0a92b5..1801268 100644 --- a/docs/memory/README.md +++ b/docs/memory/README.md @@ -18,6 +18,7 @@ | `feedback_db_metadata_import.md` | SQLAlchemy metadata / `create_all()` 漏表鐵律 | 新增 model、修 schema、排查 fresh env 漏表時 | | `db_connection_pool_singleton_20260430.md` | PostgreSQL `too many clients` 連線池放大事故與 DatabaseManager singleton 修正 | 排查 DB 連線數暴增、route 內反覆初始化 DatabaseManager、SQLAlchemy engine/pool 行為時 | | `project_phase3f_cleanup_roadmap.md` | ADR-017 執行矩陣與階段紅線 | 正在做 3f 模組化收尾時 | +| `code_modularization_inventory_20260430.md` | Python 大檔盤點、分層規範與拆分工作項目 | 新增功能、拆大檔、審查是否違反模組化治理時 | | `schema_inventory_baseline.md` | DB 表分類與 drift 基線 | 要收斂 migration / ORM / raw SQL 真相時 | ## 關聯 Guide diff --git a/docs/memory/code_modularization_inventory_20260430.md b/docs/memory/code_modularization_inventory_20260430.md new file mode 100644 index 0000000..d30a65c --- /dev/null +++ b/docs/memory/code_modularization_inventory_20260430.md @@ -0,0 +1,44 @@ +# 程式碼模組化盤點(2026-04-30) + +> 用途:接續 ADR-017 Phase 3f 時,快速知道哪些 Python 檔案仍是大檔技術債,以及新增功能應該放在哪個模組層。 + +## 盤點結論 + +- Python 總量:約 64,748 行。 +- 最大壓力區:`routes/` 約 21,020 行、`services/` 約 24,533 行。 +- `app.py` 已降到 1,206 行,功能定位應固定為 bootstrap / Blueprint registration / startup guard,不再承接新 route。 +- 目前仍有 15 個 Python 檔案超過 800 行;這些不是禁止修 bug,而是禁止繼續塞新功能。 + +## 超過 800 行檔案清單 + +| 行數 | 檔案 | 分類 | 拆分方向 | +|---:|---|---|---| +| 5543 | `routes/openclaw_bot_routes.py` | P0 巨型 Blueprint | route / bot command service / report service / scheduler hook | +| 2653 | `routes/sales_routes.py` | P0 巨型 Blueprint | page routes / API routes / chart query service / calendar service | +| 2644 | `scheduler.py` | P0 排程總管 | task registry / crawler jobs / report jobs / notification jobs | +| 1662 | `routes/ai_routes.py` | P1 AI Blueprint | route glue / AI orchestration service / prompt builders | +| 1661 | `routes/vendor_routes.py` | P1 Vendor Blueprint | route glue / vendor query service / stockout service | +| 1345 | `services/ppt_generator.py` | P1 報表生成 service | deck orchestration / slide builders / chart builders | +| 1339 | `services/nemoton_dispatcher_service.py` | P1 NemoTron service | NIM client / tool-call parser / action dispatcher | +| 1300 | `services/openclaw_strategist_service.py` | P1 OpenClaw service | prompt builders / report composer / strategy rules | +| 1206 | `app.py` | P1 bootstrap | 保持只做 app setup;繼續往 app_factory / extension setup 抽 | +| 1079 | `routes/cicd_routes.py` | P2 CI/CD Blueprint | route glue / CI query service / deployment action service | +| 986 | `services/telegram_bot_service.py` | P2 Telegram service | command handlers / message formatters / bot client | +| 966 | `services/trend_crawler.py` | P2 crawler service | source adapters / parser / persistence | +| 868 | `services/elephant_alpha_autonomous_engine.py` | P2 ElephantAlpha engine | HITL / executor / planning policy | +| 818 | `services/import_service.py` | P2 import service | validators / import writers / report builders | +| 805 | `routes/bot_api_routes.py` | P2 Bot API Blueprint | route glue / bot action service | + +## 工作項目 + +1. P0:拆 `routes/openclaw_bot_routes.py`,先把非 HTTP 邏輯搬到 `services/openclaw_bot/` 子模組。 +2. P0:拆 `routes/sales_routes.py`,先把 chart/query/calendar 計算搬到 `services/sales/`。 +3. P0:拆 `scheduler.py`,建立 `jobs/` 或 `services/scheduler/` task registry。 +4. P1:把 `routes/ai_routes.py` 與 `routes/vendor_routes.py` 的資料處理移出 route。 +5. P1:把 PPT / NemoTron / OpenClaw 大 service 拆成 client、parser、composer、policy。 +6. P2:對 800-1100 行檔案採「碰到就順手抽」策略,但不可讓淨行數繼續增加。 + +## 守門 + +- `tests/test_modularization_governance.py` 會掃描所有 Python 檔案;任何新的 >800 行檔案沒有列在本 inventory 就會失敗。 +- 若檔案拆小後低於 800 行,可從本清單移除並同步更新測試。 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index c04a34c..8ca7ba0 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -42,6 +42,7 @@ - **Scheduler 例外記錄強化**: 清除 `scheduler.py` 靜默 `except/pass`,Chrome 清理、EDM optional 欄位、備份 insight/Telegram 失敗均保留 log。 - **AI metrics baseline 觀測**: `/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免 app 重啟後 Grafana/Prometheus 看不到 metric names。 - **ElephantAlpha transient fallback**: NVIDIA NIM primary model timeout、connection error、429 與 5xx 會嘗試下一個 fallback model,400 等非暫時性請求錯誤不重試。 +- **模組化治理守門**: 盤點 15 個超過 800 行 Python 大檔,新增 `docs/guides/modularization_governance.md` 與 `tests/test_modularization_governance.py`,防止未分類巨檔再長出來。 ### 2026-04-28~29:Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除 - **app.py 縮減 -10.8%**: 7,386 → 6,590 行,11 commits 全綠零 502。 diff --git a/docs/memory/project_phase3f_cleanup_roadmap.md b/docs/memory/project_phase3f_cleanup_roadmap.md index 110e84c..da98f96 100644 --- a/docs/memory/project_phase3f_cleanup_roadmap.md +++ b/docs/memory/project_phase3f_cleanup_roadmap.md @@ -18,6 +18,7 @@ | 3f-3 | scheduler 裸 `except`、EventRouter 同步告警、compose mount、vendor template_folder | 中 | 1-2h | | 3f-4 | 模板統一至 `templates/` 與 `web/templates/vendor_stockout/` | 低 | 2-3h | | 3f-5 | 孤兒 service 與 `.env.example` 收尾 | 極低 | 30m | +| 3f-6 | 模組化治理守門:大檔 inventory、分層 guide、測試防新增巨檔 | 低 | 30m | ## 已校正事項 @@ -26,3 +27,4 @@ - 3f-2 的 `--preload` 只降低 copy-on-write 記憶體成本,一致性仍以 DB fingerprint 為準。 - 3f-3 必須先補 `EventRouter` 同步 facade,再把 scheduler P1/P2 失敗導入告警。 - 3f-4 移除 ChoiceLoader fallback 前,需先清 docker-compose 舊 mount 與根目錄模板。 +- 3f-6 已將 >800 行 Python 檔案列入 `docs/memory/code_modularization_inventory_20260430.md`;未來新增巨檔需更新 inventory,否則 `tests/test_modularization_governance.py` 會失敗。 diff --git a/routes/README.md b/routes/README.md index 3aa185a..ed7fd46 100644 --- a/routes/README.md +++ b/routes/README.md @@ -24,3 +24,5 @@ | `import_routes.py` | 匯入功能 | `/api/import_excel`, `/api/import/monthly_summary` | 新增 route 時請優先放入對應 Blueprint,並用本機 `app.url_map` duplicate check 驗證。 +若 route 內開始出現大量資料處理、SQL、AI prompt 或外部 API 呼叫,請依 +`docs/guides/modularization_governance.md` 抽到 `services/` 或 `utils/`,不要讓 Blueprint 變成新巨檔。 diff --git a/tests/test_modularization_governance.py b/tests/test_modularization_governance.py new file mode 100644 index 0000000..83103af --- /dev/null +++ b/tests/test_modularization_governance.py @@ -0,0 +1,44 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +INVENTORY = ROOT / "docs/memory/code_modularization_inventory_20260430.md" +GUIDE = ROOT / "docs/guides/modularization_governance.md" + + +def _python_line_counts(): + ignored_parts = {".git", "venv", "__pycache__", ".pytest_cache"} + for path in ROOT.rglob("*.py"): + if any(part in ignored_parts for part in path.relative_to(ROOT).parts): + continue + with path.open(encoding="utf-8", errors="ignore") as handle: + yield path.relative_to(ROOT).as_posix(), sum(1 for _ in handle) + + +def test_large_python_modules_are_tracked_in_modularization_inventory(): + inventory = INVENTORY.read_text(encoding="utf-8") + large_modules = { + path: lines + for path, lines in _python_line_counts() + if lines >= 800 + } + + assert large_modules, "盤點應至少包含目前既有大檔" + missing = [ + f"{path} ({lines} lines)" + for path, lines in sorted(large_modules.items()) + if f"`{path}`" not in inventory + ] + assert missing == [] + + +def test_modularization_governance_is_indexed_for_codex_sessions(): + agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8") + constitution = (ROOT / "CONSTITUTION.md").read_text(encoding="utf-8") + memory_index = (ROOT / "docs/memory/README.md").read_text(encoding="utf-8") + + assert "docs/guides/modularization_governance.md" in agents + assert "docs/guides/modularization_governance.md" in constitution + assert "code_modularization_inventory_20260430.md" in agents + assert "code_modularization_inventory_20260430.md" in memory_index + assert GUIDE.exists()