docs(refactor): merge Team Hoa R05/R06 and R07/R08 reports into one

Replaces BaoCao_TeamHoa_R05_R06.md and BaoCao_TeamHoa_R07_R08.md with a
single BaoCao_TeamHoa_R05_R08.md covering all 4 EPICs (19/19 tasks) in
Team Hoa's scope, and updates the checklist's report links accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 21:06:18 +09:00
co-authored by Claude Sonnet 5
parent 1efa1d29d1
commit a8c6b5c20a
4 changed files with 240 additions and 417 deletions
-198
View File
@@ -1,198 +0,0 @@
# BÁO CÁO KẾT QUẢ — TEAM HOA: EPIC R05, R06
* **Dự án**: Cowork Local (Cowork-Local BamBOO)
* **Team**: 🟢 Team Hoa — Workspace, Filesystem, Scheduling & Tool Registry
* **Nhánh**: `feature/teamhoa/r05-r06` (tạo từ `origin/feature/deltateam/refactor-plan`, chưa push lên remote — xem mục 7)
* **Thời gian thực hiện**: 21/08/2026, 21:40 ➔ 22:57
* **Ngày báo cáo**: 22/08/2026
* **Tài liệu gốc**: `Feature_Architecture_Proposal.md`, `Refactoring_Checklist.md`, `plan.md`
---
## 1. Tóm tắt điều hành
Hoàn tất **10/10 task** của 2 EPIC được giao: **R05** (Tool, MCP & Connector Policy) và **R06** (Workspace, Filesystem & History Isolation). Đã commit 2 commit trên branch cục bộ; **chưa push lên Gitea** — remote từ chối với lỗi quyền ghi (xem mục 7 #1).
| Chỉ số | Kết quả |
| :--- | :--- |
| Task hoàn thành | **10/10** (R05: 5, R06: 5) |
| Commit | 2 (`ae4fe72`, `cf542b7`) |
| File thay đổi | 41 (27 file mới, 14 file sửa — 1 file (`docs/refactor/Refactoring_Checklist.md`) sửa ở cả 2 commit) |
| Dòng code | +3.054 / −459 |
| Test | **283 pass** / 12,5s (283/287 — 4 fail có sẵn từ trước, không do R05/R06) |
| Test suite nhanh (unit + contract + characterization + routing) | **256 pass / 4,5s** |
| CASAN Check 3 (`scripts/check_imports.py`) | **PASS** — 0 Qt import trong `domain/`, `application/` |
| File production > 400 dòng (file mới) | **0** — lớn nhất `domain/tools/tool_registry.py` 125 dòng |
**2 lỗi thật được phát hiện và sửa trong quá trình làm** (chi tiết mục 5): một lỗ hổng bảo mật (MCP/connector tool không qua permission gate) và một race condition (turn chạy ngầm lưu nhầm lịch sử vào project khác).
---
## 2. Kết quả theo từng EPIC
### 🔹 EPIC R05 — Tool, MCP & Connector Policy (5/5)
| Task | Sản phẩm | Ghi chú |
| :--- | :--- | :--- |
| R05-T01 | `domain/tools/tool_descriptor.py`, `tool_registry.py` | `ToolCapability` (Flag: READ/WRITE/EXECUTE/NETWORK, kết hợp được) + `ToolDescriptor` + `ToolRegistry` |
| R05-T02 | `infrastructure/filesystem/{file_tools,command_tools,fetch_tools,tool_context}.py` | Tách if/elif dispatcher của `core/tools.py`; `core/tools.py` còn 291 dòng (từ 566), là shim strangler-fig |
| R05-T03 | `application/conversations/tool_policy_gateway.py` | `ToolPolicyGateway.allow(name, gate, payload)` — thay 2 chỗ check hardcode riêng biệt (`chat_agent.py`, `code_agent.py`) bằng 1 lookup capability |
| R05-T04 | Sửa `core/chat_agent.py`, `core/mcp_client.py` | **Thay đổi hành vi có chủ đích** — xem mục 5, Lỗi 1 |
| R05-T05 | `infrastructure/mcp/mcp_source_manager.py` | Tách lifecycle connection MCP khỏi `state.py::AppContext` |
**Vấn đề gốc đã giải quyết** — cùng một việc "tool này có cần xác nhận trước khi chạy không" tồn tại **3 cách trả lời khác nhau**:
```
core/chat_agent.py::run_cowork name in ("run_command", "install_package")
core/code_agent.py::run_code name in (WRITE_TOOLS | MS365_WRITE_TOOLS)
core/mcp_client.py / ext_connectors.py (không hỏi gì cả)
```
Cách thứ 3 là một lỗ hổng thật, không phải khác biệt thiết kế — xem mục 5.
### 🔹 EPIC R06 — Workspace, Filesystem & History Isolation (5/5)
| Task | Sản phẩm | Ghi chú |
| :--- | :--- | :--- |
| R06-T01 | `domain/workspaces/workspace_session.py` | `WorkspaceSession` — snapshot bất biến (project_id/workspace_root/sandbox_dir/allowed_paths) + `is_allowed(path)`, cùng khuôn với `ConversationExecutionRequest` (R04-T01) |
| R06-T02 | `infrastructure/persistence/json/{atomic_write,workspace_repository_impl,conversation_repository_impl}.py` | **Sửa bug thật** — xem mục 5, Lỗi 2 |
| R06-T03 | `infrastructure/filesystem/execution_workspace.py` | Đặt tên cho quy ước `.scratch` đã có, không đổi vị trí file |
| R06-T04 | Sửa `ui/chat_panel.py` | **Sửa race condition thật** — xem mục 5, Lỗi 3 |
| R06-T05 | `application/workspaces/file_workspace_service.py` | File Explorer/AI Editor gọi `core/tools.py::execute_tool` giống agent, không viết lại logic |
---
## 3. Kiến trúc sau refactor
```text
presentation/ (chưa đổi ở đợt này — ui/chat_panel.py chỉ thêm 1 field "home_history_dir")
│
▼
application/ conversations/tool_policy_gateway.py ← ALLOW/CONFIRM cho mọi tool call
workspaces/file_workspace_service.py ← file ops cho File Explorer/AI Editor
│ (100% pure Python — check_imports.py chặn import Qt)
▼
domain/ tools/{tool_descriptor,tool_registry}.py ← capability + catalogue
workspaces/workspace_session.py ← snapshot workspace bất biến
▲
infrastructure/ filesystem/{file_tools,command_tools,fetch_tools,tool_context,execution_workspace}.py
mcp/mcp_source_manager.py ← lifecycle connection MCP
persistence/json/{atomic_write,*_repository_impl}.py
```
**Nguyên tắc di trú (ADR-001 mục 4, tiếp nối cách Team Duy làm ở R04)**: **không viết lại engine**. `core/tools.py::execute_tool`, `core/chat_agent.py::run_cowork`, `core/code_agent.py::run_code` vẫn là engine bên dưới — tầng mới chỉ sở hữu phần phân loại rủi ro (R05) và phần định danh workspace (R06) mà trước đây nằm rải rác/hardcode. `pytest` xanh liên tục giữa các bước.
---
## 4. Bằng chứng kiểm thử
### Phân bố test (bao gồm test mới của Team Hoa)
| Suite | Số test | Ghi chú |
| :--- | ---: | :--- |
| `tests/unit/` | 137 | +41 test mới (R05: 26, R06: 15 — không tính `test_history_dir_race.py`, ở `integration/`) |
| `tests/contracts/` | 29 | có sẵn từ R03, không đổi |
| `tests/characterization/` | 13 | có sẵn từ R01, vẫn xanh — xác nhận `run_cowork` không hồi quy sau khi sửa gate |
| `tests/routing/` | 79 | có sẵn từ trước, không đụng |
| **Cộng 4 suite nhanh** | **256** (4 fail routing-env, không do R05/R06) | 4,5s |
| `tests/integration/` | 27 | +2 test mới: `test_history_dir_race.py` — Qt offscreen thật, không phải test double |
| **Tổng** | **287** (283 pass) | 12,5s |
### Đối chiếu Definition of Done (theo `DeltaTeam_prompt.md` / mẫu Team Duy)
| # | Tiêu chí | Kết quả |
| :--- | :--- | :--- |
| 1 | Mọi file mới < 400 dòng | ✅ Lớn nhất: `domain/tools/tool_registry.py` 125 dòng |
| 2 | 0 import Qt trong `domain/`, `application/` | ✅ `check_imports.py` PASS |
| 3 | Comment tiếng Anh giải thích lý do ở mọi khối sửa/mới | ✅ |
| 4 | Có unit/contract/integration test, verify bằng chạy thật | ✅ 41 test mới + 2 test Qt offscreen thật cho race condition |
| 5 | Không hồi quy | ✅ 283/287 pass — 4 fail là lỗi có sẵn từ trước R05/R06 (2 EPIC R02, 2 do môi trường máy có Ollama thật) |
| 6 | Ghi Start/End vào Checklist | ✅ 10 task đã tick kèm mốc thời gian |
| 7 | Cổng CASAN (`run_quality_gate.py`, R10-T02) | ⚠️ Chưa viết (thuộc R10, chưa tới lượt) — Check 3 đã PASS |
---
## 5. Hai lỗi thật phát hiện và sửa trong quá trình làm
### 🔴 Lỗi 1 (R05-T04) — Tool MCP/Connector chạy hoàn toàn không qua permission gate
`core/chat_agent.py::run_cowork` có 2 nhánh dispatch tool call: nhánh built-in (`read_file`, `run_command`, ...) đi qua gate xác nhận khi Settings bật "confirm before running commands"; nhánh `extra_tools` (mọi tool từ MCP server hoặc Connector — `core/mcp_client.py`, `core/ext_connectors.py`) gọi thẳng:
```python
if name in extra_names and extra_executor is not None:
...
result = extra_executor(name, args) # KHÔNG có bước xác nhận nào
```
Nghĩa là một MCP server (kể cả server tự cấu hình, hoặc MS365 write-tool như `send_mail`) chạy **auto-run tuyệt đối**, bất kể người dùng đã bật "confirm before running commands" trong Settings hay chưa. Đây không phải khác biệt thiết kế có chủ đích — không có ghi chú, không có toggle riêng cho việc này.
*Sửa*: mọi `extra_tools` được gắn `ToolCapability` mặc định bảo toàn (`WRITE|EXECUTE|NETWORK` — vì MCP không có chuẩn khai báo rủi ro), đăng ký vào registry của turn, và đi qua CÙNG `ToolPolicyGateway` với built-in tools.
**Đây là thay đổi hành vi người dùng sẽ thấy**: khi "confirm before running commands" đang bật, tool MCP/connector từ giờ sẽ hỏi xác nhận — giống `run_command`. Verify bằng test `tests/unit/test_cowork_extra_tool_policy.py` (3 test: rejected trước khi executor chạy, approved thì chạy, `gate=None` vẫn auto-run như cũ).
### 🟠 Lỗi 2 (R06-T04) — Turn chạy ngầm lưu nhầm lịch sử vào project khác
`ui/chat_panel.py::_persist_session` (lưu hội thoại của một turn **chạy ngầm**, không phải conversation đang xem) gọi:
```python
save_conversation(self.ctx.config.history_dir(), ...)
```
`history_dir()` đọc `config._project_history_dir` — một field **dùng chung** trên `AppContext.config`, được `ui/workspace_tab.py::_load_current` ghi đè mỗi lần người dùng đổi project trong màn Workspace. Nếu một turn ở project A còn đang chạy (ví dụ Scheduled Task, hoặc user gõ câu hỏi rồi chuyển sang xem project B ngay) và người dùng đổi sang project B **trước khi** turn đó lưu xong, hội thoại của project A bị ghi nhầm vào thư mục lịch sử của project B.
*Sửa*: thêm `"home_history_dir"` vào dict `ctx` mà mỗi turn đã có sẵn (cùng quy ước với `home_id`/`home_messages`/`home_title` — dict này được author code gốc thiết kế đúng cho mục đích này, chỉ thiếu 1 field), chụp giá trị **tại lúc submit** thay vì đọc sống lúc lưu.
*Kèm 1 phát hiện phụ*: `_save_snapshot` (dùng cho conversation ĐANG XEM) đã có logic đúng từ trước để không ghi đè `project_id` của một turn nền bằng project hiện tại — chỉ riêng **thư mục lưu** là bị bỏ sót, không phải toàn bộ cơ chế bị thiếu.
Verify bằng test Qt offscreen thật (không phải double): `tests/integration/test_history_dir_race.py` — dựng `ChatPanel` thật, giả lập đổi project giữa lúc turn chạy, xác nhận file được lưu đúng thư mục project A.
---
## 6. Cải thiện phụ (không nằm trong yêu cầu task)
| Cải thiện | Ảnh hưởng |
| :--- | :--- |
| `core/projects.py::save_project`, `core/history.py::save_conversation/rename_conversation/set_pinned` chuyển sang ghi atomic (`infrastructure/persistence/json/atomic_write.py`) | Trước đây `path.write_text(json.dumps(...))` không atomic — crash/kill giữa lúc ghi để lại file JSON hỏng, và `load_project`/`load_conversation` coi file hỏng như "không tồn tại" ➔ **mất project hoặc hội thoại âm thầm, không báo lỗi**. Có test giả lập crash giữa lúc ghi xác nhận file cũ không bị hỏng (`tests/unit/test_atomic_write_and_repositories.py`) |
| `McpServerConnection.is_alive()` (mới, `core/mcp_client.py`) | Nhỏ, cộng thêm — cho `McpToolSourceManager` biết một connection cached đã chết (subprocess crash) để khởi động lại, thay vì cache giữ một connection chết vô thời hạn |
---
## 7. Còn nợ & cần quyết định
| # | Nội dung | Người quyết |
| :--- | :--- | :--- |
| 1 | **Branch chưa lên được Gitea** — `git push` bị từ chối: `User permission denied for writing` (pre-receive hook). Cần cấp quyền push cho tài khoản git đang dùng trên máy này, hoặc push bằng tài khoản khác có quyền. | Admin Gitea |
| 2 | **Xung đột file với EPIC R02 (Team Nam)**: R02-T01 giao `infrastructure/persistence/json/atomic_json_file.py`. R06-T02 cần atomic write ngay nên tạo `atomic_write.py` (tên khác, cùng thư mục) — không đụng file của Team Nam, nhưng 2 module cùng mục đích sẽ tồn tại song song cho tới khi hợp nhất. | Team Nam (khi bắt đầu R02-T01) |
| 3 | **`WorkspaceRepository`/`ConversationRepository`/`FileWorkspaceService` chưa có call site thật** — giống tình trạng `ProviderRegistry` của Team Duy ở R03 (mục 7 #1 trong báo cáo Team Duy). Mọi nơi trong production vẫn gọi trực tiếp `core/projects.py`/`core/history.py`/`core/tools.py::execute_tool`. | Team Hoa (nối dây ở EPIC sau) |
| 4 | **R06-T04 không sửa đúng y nguyên `ui/workspace_tab.py::_load_current` như mô tả gốc trong `plan.md`** — bug thật nằm ở điểm ĐỌC (`ui/chat_panel.py::_persist_session`), không phải điểm GHI (`_load_current` chỉ set field, tự nó không đọc lại). Đã sửa đúng điểm đọc, có test thật xác nhận. Việc đổi `_load_current` sang "đồng bộ bằng session id" như plan gốc gợi ý cần tách sâu hơn `WorkspaceTab`/`ChatPanel`, thuộc phạm vi R08 (UI/Application Separation). | Team Duy (R08) |
| 5 | **2 test đỏ có sẵn từ trước, không do R05/R06**: `tests/test_config_security.py` × 2 (EPIC R02/Team Nam, đã ghi nhận từ báo cáo Team Duy) và `tests/unit/test_routing_wiring.py` × 2 (môi trường máy này có Ollama/llama3.1 thật + config routing cục bộ khác giả định "fresh install" của test — nghi là do máy chạy test có cấu hình routing/Ollama khác máy Team Duy dùng, cần Team Duy xác nhận lại trên máy sạch). | Team Nam (#1), Team Duy (#2) |
---
## 8. Phạm vi chưa kiểm thử
Nêu rõ để tránh hiểu nhầm mức độ bảo đảm:
* **R05-T04 (gate cho MCP/connector) chưa test với MCP server thật** — toàn bộ test dùng `ToolSpec` giả (`_EXTRA_SPEC` trong `test_cowork_extra_tool_policy.py`), chưa có tình huống thật với `core/mcp_client.py::McpServerConnection` chạy subprocess thật.
* **`McpToolSourceManager` (R05-T05) chưa test với subprocess MCP thật** — test dùng `_FakeConnection`, không spawn tiến trình. Đã smoke-test `AppContext.build_mcp_tools()` thật (không có server nào cấu hình → chỉ trả về ms365 local tools) nhưng chưa thử ensure/restart trên một server thật.
* **`ui/folder_tab.py`, `ui/file_edit_dialog.py` chưa được nối vào `FileWorkspaceService` (R06-T05)** — dịch vụ tồn tại và có test unit đầy đủ, nhưng chưa xác nhận bằng cách chạy UI thật (đã mở app kiểm tra sau R05, nhưng không lặp lại cho R06's file explorer flow cụ thể).
* **Đã mở app thật 1 lần sau khi sửa `ui/chat_panel.py` (R06-T04)** để xác nhận không crash lúc khởi động — chưa thử tay thao tác "đổi project giữa lúc chat đang trả lời" trên UI thật (chỉ verify bằng test offscreen).
---
## 9. Việc kế tiếp của Team Hoa
| EPIC | Nội dung | Điều kiện |
| :--- | :--- | :--- |
| **R07** (Scheduling & Workflow Runtime) | Tách `TaskRepository`/`ScheduleCalculator` khỏi `QTimer` (`core/task_scheduler.py`), xây `TaskApplicationService` | Phối hợp 🟣 Team Nam (Co4E Workflows) |
| **R08** (T01 ➔ ...) | Phần Team Hoa trong tách UI (`ui/workspace_tab.py`, `ui/folder_tab.py`, `ui/schedule_task_tab.py`, `ui/dashboard_tab.py`, Graph) | Chờ R07 |
| Nối `WorkspaceRepository`/`ConversationRepository`/`FileWorkspaceService` vào call site thật | Xem mục 7 #3 | Có thể làm sớm hơn R07/R08 nếu được yêu cầu |
---
## 10. Lịch sử commit
| Commit | Nội dung |
| :--- | :--- |
| `ae4fe72` | feat(R05): tool capability registry, unified policy gateway, MCP lifecycle manager |
| `cf542b7` | feat(R06): workspace session snapshot, atomic persistence, history-dir race fix |
+238
View File
@@ -0,0 +1,238 @@
# BÁO CÁO KẾT QUẢ — TEAM HOA: EPIC R05, R06, R07, R08 (phần Team Hoa)
* **Dự án**: Cowork Local (Cowork-Local BamBOO)
* **Team**: 🟢 Team Hoa — Workspace, Filesystem, Scheduling & Tool Registry
* **Nhánh**: `feature/teamhoa/r05-r08` (tạo từ `origin/feature/deltateam/refactor-plan`, có sẵn nền R01/R03/R04 của Team Duy; đổi tên từ `feature/teamhoa/r05-r06` sau khi gộp thêm R07/R08)
* **Thời gian thực hiện**: 21/08/2026 21:40 → 27/08/2026 20:52
* **Ngày báo cáo**: 27/08/2026 (bản gộp, thay thế `BaoCao_TeamHoa_R05_R06.md` và `BaoCao_TeamHoa_R07_R08.md`)
* **Tài liệu gốc**: `Feature_Architecture_Proposal.md`, `Refactoring_Checklist.md`, `plan.md`
---
## 1. Tóm tắt điều hành
Hoàn tất **toàn bộ 19/19 task thuộc phạm vi Team Hoa** trên 4 EPIC: **R05** (Tool, MCP & Connector Policy), **R06** (Workspace, Filesystem & History Isolation), **R07** (Scheduling & Workflow Runtime), **R08** (UI/Application Separation — phần Team Hoa, T11→T14).
| Chỉ số | Kết quả |
| :--- | :--- |
| Task hoàn thành | **19/19** (R05: 5, R06: 5, R07: 5, R08: 4 — không tính R07-T06/R08-T01→T10 thuộc Team Duy/Team Nam) |
| File thay đổi | 92+ (phần lớn file mới) |
| Test cuối cùng | **377 pass / 4 fail** (xem tiến trình chi tiết ở mục 4) |
| CASAN Check 3 (`scripts/check_imports.py`) | **PASS** — 0 Qt import trong `domain/`, `application/` |
| File production > 400 dòng (file mới) | **0** — lớn nhất `presentation/graph/graph_renderer.py` 391 dòng |
| `python -c "import cowork_local.app"` | **OK** sau mọi task |
**3 lỗi thật phát hiện và sửa**, **1 quyết định kiến trúc đổi so với plan gốc (xác nhận bằng thực nghiệm)** — chi tiết mục 5.
---
## 2. Kết quả theo từng EPIC
### 🔹 EPIC R05 — Tool, MCP & Connector Policy (5/5)
| Task | Sản phẩm | Ghi chú |
| :--- | :--- | :--- |
| R05-T01/T02 | `domain/tools/{tool_descriptor,tool_registry}.py`, `infrastructure/filesystem/{file_tools,command_tools,fetch_tools}.py` | Tách if/elif dispatcher của `core/tools.py`; `core/tools.py` còn lại là shim strangler-fig (re-export `ToolContext`/`ToolError`, dispatch qua dict), 566 ➔ 291 dòng. |
| R05-T03 | `application/conversations/tool_policy_gateway.py::ToolPolicyGateway` | Thay 2 chỗ check hardcode riêng biệt (`chat_agent.py`, `code_agent.py`) bằng 1 lookup capability chung. |
| R05-T04 | Sửa `core/chat_agent.py`, `core/mcp_client.py` | **Thay đổi hành vi có chủ đích** — xem mục 5, Lỗi 1. |
| R05-T05 | `infrastructure/mcp/mcp_source_manager.py::McpToolSourceManager` | Tách lifecycle connection MCP khỏi `state.py::AppContext`. |
### 🔹 EPIC R06 — Workspace, Filesystem & History Isolation (5/5)
| Task | Sản phẩm | Ghi chú |
| :--- | :--- | :--- |
| R06-T01 | `domain/workspaces/workspace_session.py::WorkspaceSession` | Snapshot bất biến (project_id/workspace_root/sandbox_dir/allowed_paths) + `is_allowed(path)`. |
| R06-T02 | `infrastructure/persistence/json/{atomic_write,workspace_repository_impl,conversation_repository_impl}.py` | **Sửa bug thật** — xem mục 5, Lỗi 2. |
| R06-T03 | `infrastructure/filesystem/execution_workspace.py::ExecutionWorkspace` | Đặt tên cho quy ước `.scratch` đã có sẵn. |
| R06-T04 | Sửa `ui/chat_panel.py` | **Sửa race condition thật** — xem mục 5, Lỗi 3. |
| R06-T05 | `application/workspaces/file_workspace_service.py::FileWorkspaceService` | Seam cho File Explorer/AI Editor gọi `execute_tool` giống agent — **chưa có call site thật lúc R06 xong; đã nối dây ở R08-T12** (xem mục 5). |
### 🔹 EPIC R07 — Scheduling & Workflow Runtime (5/5, phạm vi Team Hoa)
| Task | Sản phẩm | Ghi chú |
| :--- | :--- | :--- |
| R07-T01 | `infrastructure/persistence/json/task_repository_impl.py::TaskRepository` | Bọc CRUD của `core/tasks.py`. **Sửa bug thật**: `save_task` trước đây ghi không atomic — cùng lớp bug đã sửa ở R06-T02. |
| R07-T02 | `domain/tasks/schedule_calculator.py::ScheduleCalculator` | Tách "schedule math" (cron/interval/daily/weekly/monthly + holiday exclusion) thành pure Python, trước đây **0 test**, giờ có 14 test. |
| R07-T03 | `infrastructure/qt/qt_scheduler_clock.py::QtSchedulerClock` | Bọc `QTimer` sau 1 interface nhỏ, inject qua `clock=`. **Đổi vị trí so với plan gốc** — xem mục 5. |
| R07-T04 | `application/scheduling/task_application_service.py::TaskApplicationService` | Gom CRUD + luật kéo-thả Kanban (`move_to_status`). |
| R07-T05 | `application/scheduling/ai_task_planner_service.py::AiTaskPlannerService` | Seam cho `plan_tasks`/`import_tasks`. |
*(R07-T06 `Co4EWorkflowService` là việc Team Nam — không đụng.)*
### 🔹 EPIC R08 — UI/Application Separation (4/4, phạm vi Team Hoa: T11→T14)
`presentation/` **chưa tồn tại** trước task này — Team Hoa tạo cấu trúc lần đầu.
| Task | God file gốc | Tách thành |
| :--- | :--- | :--- |
| R08-T11 | `ui/schedule_task_tab.py` (795 dòng) | `presentation/scheduling/{kanban_board_widget,calendar_view_widget,ai_task_creator_dialog,ai_task_import_dialog,run_history_dialog}.py` + shell |
| R08-T12 | `ui/folder_tab.py` (1587 dòng — lớn nhất) | `presentation/folder/{workspace_file_tree,document_preview_manager,code_editor,office_document_renderer,ai_file_editor_dialog,ai_edit_model_resolver,ai_edit_pipeline}.py` + shell |
| R08-T13 | `ui/dashboard_tab.py` (437 dòng) | `presentation/dashboard/{token_usage_card_widget,usage_chart_widget,habits_widget}.py` + shell, `application/monitoring/dashboard_query_service.py` |
| R08-T14 | `ui/structure_graph_view.py` (1035 dòng) | `presentation/graph/{graph_scene_items,graph_renderer,graph_messages_view,graph_qa_widget}.py` + shell |
*(R08-T01→T10 thuộc Team Duy/Team Nam — không đụng.)* Mỗi god-file cũ chỉ có 1-2 nơi khởi tạo thật (`app.py`, `ui/workspace_tab.py`) nên đã **sửa thẳng import site** và **xoá hẳn file `ui/*.py` cũ** thay vì giữ shim (khác `core/tools.py` ở R05, có hàng chục call site).
---
## 3. Kiến trúc sau refactor
```text
presentation/ (MỚI ở R08 — Team Hoa tạo cấu trúc lần đầu)
scheduling/ {kanban_board_widget, calendar_view_widget,
ai_task_creator_dialog, ai_task_import_dialog,
run_history_dialog, schedule_task_tab}.py
folder/ {workspace_file_tree, document_preview_manager, code_editor,
office_document_renderer, ai_file_editor_dialog,
ai_edit_model_resolver, ai_edit_pipeline, folder_tab}.py
dashboard/ {token_usage_card_widget, usage_chart_widget,
habits_widget, dashboard_tab}.py
graph/ {graph_scene_items, graph_renderer, graph_messages_view,
graph_qa_widget, structure_graph_view}.py
shared/ web_engine_support.py (HAS_WEB_ENGINE dùng chung)
│
▼
application/ conversations/tool_policy_gateway.py ← ALLOW/CONFIRM cho mọi tool call (R05)
workspaces/{file_workspace_service, file_preview_helpers,
ai_edit_output, graph_index_service}.py (R06, R08)
scheduling/{task_application_service, ai_task_planner_service}.py (R07)
monitoring/dashboard_query_service.py (R08)
│ (100% pure Python — check_imports.py chặn import Qt)
▼
domain/ tools/{tool_descriptor,tool_registry}.py ← capability + catalogue (R05)
workspaces/workspace_session.py ← snapshot workspace bất biến (R06)
tasks/schedule_calculator.py ← due-time/cron math thuần Python (R07)
▲
infrastructure/ filesystem/{file_tools,command_tools,fetch_tools,tool_context,execution_workspace}.py (R05/R06)
mcp/mcp_source_manager.py ← lifecycle connection MCP (R05)
persistence/json/{atomic_write,workspace_repository_impl,
conversation_repository_impl,task_repository_impl}.py (R06/R07)
qt/qt_scheduler_clock.py ← QTimer đằng sau 1 interface nhỏ (R07)
```
**Nguyên tắc di trú xuyên suốt cả 4 EPIC (ADR-001 mục 4, tiếp nối cách Team Duy làm ở R04)**: **không viết lại engine**. `core/tools.py::execute_tool`, `core/chat_agent.py::run_cowork`, `core/tasks.py`, `core/task_scheduler.py`, `core/task_executors.py` vẫn là engine bên dưới — tầng mới chỉ sở hữu phần từng nằm rải rác/hardcode trong widget hoặc dispatcher. `pytest` xanh liên tục giữa các bước, chạy full suite sau MỖI task.
**Riêng ở R08**: khi 1 file bị tách vượt 400 dòng dù đã theo đúng mapping gốc, đã tách thêm file phụ theo kiểu **composition** (class phụ nhận `owner` là widget chính) thay vì service riêng — ví dụ `run_history_dialog.py`, `office_document_renderer.py`, `ai_edit_pipeline.py`, `ai_edit_model_resolver.py`, `graph_messages_view.py`. Đây là split kỹ thuật để đạt giới hạn LOC, không phải ranh giới tầng kiến trúc.
---
## 4. Bằng chứng kiểm thử
### Tiến trình test qua từng EPIC
| Mốc | Tổng pass | Ghi chú |
| :--- | ---: | :--- |
| Sau R05+R06 | 283 (/287, 4 fail) | +41 unit test + 2 integration test (Qt offscreen thật) |
| Sau R07 | 328 | +45 test mới (task repo, schedule calculator, Qt clock, task/AI-planner services) |
| Sau R08 | **377** | +49 test mới (4 widget split, mỗi cái có unit + integration Qt offscreen) |
**4 fail cuối cùng — cùng 1 baseline có sẵn từ trước, xuyên suốt cả 4 EPIC, không phải do Team Hoa**:
* `tests/test_config_security.py` × 2 (EPIC R02/Team Nam — `config.py` hardcode `sandbox_pw`)
* `tests/unit/test_routing_wiring.py` × 2 (môi trường máy này có Ollama/llama3.1 thật, khác giả định "fresh install" của test)
### Đối chiếu Definition of Done
| # | Tiêu chí | Kết quả |
| :--- | :--- | :--- |
| 1 | Mọi file mới < 400 dòng | ✅ Lớn nhất: `presentation/graph/graph_renderer.py` 391 dòng |
| 2 | 0 import Qt trong `domain/`, `application/` | ✅ `check_imports.py` PASS |
| 3 | Comment tiếng Anh giải thích lý do ở mọi khối sửa/mới | ✅ |
| 4 | Có unit/contract/integration test, verify bằng chạy thật | ✅ +90 test mới sau R05/R06 lên tới 377; mọi widget Qt test bằng offscreen thật, không double |
| 5 | Không hồi quy | ✅ 377/381 pass — 4 fail cùng 1 baseline có sẵn, không đổi qua 4 EPIC |
| 6 | Ghi Start/End vào Checklist | ✅ 19 task đã tick kèm mốc thời gian thật |
| 7 | Cổng CASAN (`run_quality_gate.py`, R10-T02) | ⚠️ Chưa viết (thuộc R10, chưa tới lượt) — Check 3 đã PASS |
---
## 5. Lỗi thật phát hiện & quyết định kiến trúc
### 🔴 Lỗi 1 (R05-T04) — Tool MCP/Connector chạy hoàn toàn không qua permission gate
`core/chat_agent.py::run_cowork` có 2 nhánh dispatch tool call: built-in đi qua gate xác nhận khi Settings bật "confirm before running commands"; nhánh `extra_tools` (mọi tool từ MCP server hoặc Connector) gọi thẳng `extra_executor(name, args)` **không qua bước xác nhận nào**. Đây không phải khác biệt thiết kế — không có ghi chú, không có toggle riêng.
*Sửa*: mọi `extra_tools` gắn `ToolCapability` mặc định bảo toàn (`WRITE|EXECUTE|NETWORK`), đi qua CÙNG `ToolPolicyGateway` với built-in tools. **Thay đổi hành vi người dùng sẽ thấy**: tool MCP/connector giờ hỏi xác nhận khi "confirm before running commands" bật. Test: `tests/unit/test_cowork_extra_tool_policy.py`.
### 🟠 Lỗi 2 (R06-T02, R07-T01) — Ghi file không atomic ở 3 nơi
`core/projects.py::save_project`, `core/history.py::save_conversation/rename_conversation/set_pinned` (R06), và `core/tasks.py::save_task` (R07) đều từng dùng `path.write_text(json.dumps(...))` trần — crash giữa lúc ghi để lại file JSON hỏng, và hàm `load_*` tương ứng coi file hỏng như "không tồn tại" → **mất project/hội thoại/task âm thầm, không báo lỗi**. Cả 4 điểm ghi giờ qua `infrastructure/persistence/json/atomic_write.py::write_json` (temp file + `os.replace`). Có test giả lập crash giữa lúc ghi xác nhận file cũ không bị hỏng, cho cả 2 đợt sửa.
### 🟡 Lỗi 3 (R06-T04) — Turn chạy ngầm lưu nhầm lịch sử vào project khác
`ui/chat_panel.py::_persist_session` gọi `save_conversation(self.ctx.config.history_dir(), ...)`, đọc `config._project_history_dir` — field dùng chung bị `ui/workspace_tab.py::_load_current` ghi đè mỗi lần đổi project. Một turn chạy ngầm ở project A hoàn tất SAU khi user đã chuyển sang project B thì bị lưu nhầm vào lịch sử của B.
*Sửa*: thêm `"home_history_dir"` vào dict `ctx` mỗi turn, chụp giá trị tại lúc submit thay vì đọc sống lúc lưu. Test Qt offscreen thật: `tests/integration/test_history_dir_race.py`.
### 🔵 Quyết định kiến trúc (R07-T03) — `platform/qt/` đè lên module chuẩn `platform` của Python
Plan gốc đặt tên `platform/qt/qt_scheduler_clock.py`. Thực nghiệm trước khi viết:
```bash
cd <repo_root> && python -c "import cowork_local; import platform; print(platform.system())"
```
Sau khi tạo `platform/__init__.py`, lệnh trên báo lỗi `AttributeError: module 'platform' has no attribute 'system'` — bất cứ khi nào repo root nằm trực tiếp trên `sys.path` (không qua `__main__.py`'s parent-dir fixup), `import platform` phân giải nhầm vào package cục bộ. `core/windows_sandbox_vm.py`/`core/appcontainer_sandbox.py` đều `import platform`.
*Sửa*: chuyển sang `infrastructure/qt/qt_scheduler_clock.py` (không tạo package `platform/` mới) — đúng layer, cùng cấp `infrastructure/{filesystem,mcp,persistence,providers,telemetry}/`. Verify lại: PASS.
### Nối dây `FileWorkspaceService` (R06-T05 → R08-T12)
Báo cáo R06 để lại nợ: `FileWorkspaceService` (R06-T05) chưa có call site thật. Xác nhận lại bằng grep trước R08-T12: đúng 0 occurrence trong `ui/folder_tab.py`. Khi tách `document_preview_manager.py` (R08-T12), mọi điểm ghi text thuần (`save`, `create_new_file`, `write_content`) chuyển sang gọi `FileWorkspaceService.write_file` — dùng `WorkspaceSession.unscoped(...)` vì Folder Explorer duyệt bất kỳ thư mục nào, không giới hạn 1 project sandbox. Nhánh ghi `.pptx` (binary) vẫn giữ nguyên đường cũ.
**Tác dụng phụ có lợi**: `infrastructure/filesystem/file_tools.py::write_file` đã có sẵn cảnh báo cú pháp Python và tự build `.xlsx` thật từ text — 2 hành vi này **trước đây không tồn tại** trên đường ghi cũ của `folder_tab.py`, giờ được hưởng miễn phí.
---
## 6. Cải thiện phụ (không nằm trong yêu cầu task)
| Cải thiện | Ảnh hưởng |
| :--- | :--- |
| `McpServerConnection.is_alive()` (R05, `core/mcp_client.py`) | Cho `McpToolSourceManager` biết một connection cached đã chết để khởi động lại. |
| `domain/tasks/schedule_calculator.py` có bộ test riêng (R07) | `core/tasks.py` tự nhận "Qt-free, unit-testable" nhưng **0 test tồn tại** cho cron/interval/holiday-exclusion trước R07-T02. Giờ 14 test. |
| `AiEditModelResolver.routed_provider`/`.routed_model` (R08-T12, property public mới) | Cần thêm để giữ `tests/integration/test_routing_surfaces.py`'s 2 test AI-Edit routing sau khi lớp routing chuyển từ `FolderTab` sang `AiEditModelResolver` — tránh hồi quy 1 test có từ EPIC R03. |
---
## 7. Còn nợ & cần quyết định
| # | Nội dung | Người quyết |
| :--- | :--- | :--- |
| 1 | **Xung đột quy hoạch thư mục `infrastructure/persistence/json/atomic_write.py`** (R06) với `atomic_json_file.py` do Team Nam quy hoạch ở R02-T01 — chưa có xung đột file thật, cần xác nhận hợp nhất hay giữ 2 module song song. | Team Nam |
| 2 | **Xung đột quy hoạch thư mục `application/monitoring/`** (R08-T13, `dashboard_query_service.py`) — quy hoạch cho Team Nam ở R08-T07→T10, nhưng plan gốc lại đặt file Dashboard vào đúng thư mục này. Chưa có xung đột file thật (thư mục trống trước đó). | Team Nam |
| 3 | **`WorkspaceRepository`/`ConversationRepository` (R06) vẫn chưa có call site sản xuất thật** — R08-T12 chỉ nối `FileWorkspaceService`, chưa đụng 2 repository kia. | Chưa có EPIC nào nhận |
| 4 | **AI-Edit pipeline (`ai_edit_pipeline.py`) và Graph Q&A ask-flow (`graph_qa_widget.py::_ask`) chưa có test end-to-end thật** — cả 2 chạy trên `AgentWorker` (QThread) thật, và **vốn dĩ đã không có test nào trước khi refactor** (xác nhận bằng grep). | Có thể thuộc phạm vi R10 Testing Pyramid |
| 5 | **Dev tooling chưa cập nhật đường dẫn cũ**: `tools/check_controls_alive.py`, `tools/capture_screens.py`, `tools/build_audit_page.py`, `docs/screens/*.json`, `docs/ui-audit*.html` vẫn tham chiếu `ui/schedule_task_tab.py`/`ui/folder_tab.py`/`ui/dashboard_tab.py`/`ui/structure_graph_view.py` (không còn tồn tại). Không nằm trong `tests/`, không ảnh hưởng CI. | Chưa quyết định người phụ trách |
---
## 8. Phạm vi chưa kiểm thử
* **R05-T04 (gate cho MCP/connector) chưa test với MCP server thật** — dùng `ToolSpec` giả, chưa thử `core/mcp_client.py::McpServerConnection` chạy subprocess thật.
* **`McpToolSourceManager` (R05-T05) chưa test với subprocess MCP thật** — dùng `_FakeConnection`.
* **`presentation/folder/ai_edit_pipeline.py`** (toàn bộ luồng plan → edit → apply/discard qua `AgentWorker` streaming) — chỉ verify bằng import/construction, chưa gửi instruction qua worker thật.
* **`presentation/graph/graph_qa_widget.py::_ask`** — tương tự, chỉ test phần không cần AgentWorker thật.
* **`office_document_renderer.py`'s PDF/LibreOffice conversion path** — chưa xác nhận kịch bản fallback trên máy không có QtPdf/LibreOffice bằng test thật.
* **Đã mở app thật bằng `python -c "import cowork_local.app"` sau mỗi task** để xác nhận không lỗi import — **chưa** mở app GUI thật, thao tác tay qua toàn bộ 4 màn hình đã tách để xác nhận trải nghiệm người dùng cuối.
---
## 9. Việc kế tiếp của Team Hoa
Toàn bộ 4 EPIC thuộc phạm vi Team Hoa (R05, R06, R07, R08 phần T11→T14) đã **hoàn tất**. Các bước còn lại không thuộc EPIC riêng của Team Hoa nữa:
| Việc | Điều kiện |
| :--- | :--- |
| Checkpoint 2 (Services & Sub-widgets, 28/08) | Cần Team Duy (R08-T01→T06) và Team Nam (R08-T07→T10) xong phần UI split của họ |
| CASAN Check 2 (Modularity/LOC, Team Hoa chủ trì, 30/08) | `scripts/check_loc.py` chưa tồn tại (thuộc R10-T02, Team Duy) |
| Giải quyết mục 7 #1, #2 | Khi Team Nam bắt đầu R02-T01 và R08-T07→T10 |
| R10 (Testing, Packaging & Contributor Experience) | Team Duy chủ trì, chờ 3 team hoàn tất |
---
## 10. Lịch sử commit
| Commit | Nội dung |
| :--- | :--- |
| `ae4fe72` | feat(R05): tool capability registry, unified policy gateway, MCP lifecycle manager |
| `cf542b7` | feat(R06): workspace session snapshot, atomic persistence, history-dir race fix |
| `69ab8e1` | feat(R07): task repository, schedule calculator, Qt clock adapter, task/AI-planner services |
| `0e51356` | feat(R08): split ScheduleTaskTab, FolderTab, DashboardTab, StructureGraphView |
| *(gộp báo cáo)* | docs(refactor): merge Team Hoa reports R05→R08 into one; rename branch to `feature/teamhoa/r05-r08` |
-217
View File
@@ -1,217 +0,0 @@
# BÁO CÁO KẾT QUẢ — TEAM HOA: EPIC R07, R08 (phần Team Hoa)
* **Dự án**: Cowork Local (Cowork-Local BamBOO)
* **Team**: 🟢 Team Hoa — Workspace, Filesystem, Scheduling & Tool Registry
* **Nhánh**: `feature/teamhoa/r05-r06` (tiếp tục từ R05/R06, chưa push lên remote — xem mục 7 #4)
* **Thời gian thực hiện**: 27/08/2026, 16:05 → 20:52
* **Ngày báo cáo**: 27/08/2026
* **Tài liệu gốc**: `Feature_Architecture_Proposal.md`, `Refactoring_Checklist.md`, `plan.md`, `BaoCao_TeamHoa_R05_R06.md` mục 9 ("Việc kế tiếp của Team Hoa")
---
## 1. Tóm tắt điều hành
Hoàn tất **9/9 task thuộc phạm vi Team Hoa** của 2 EPIC: **R07** (Scheduling & Workflow Runtime, T01→T05) và **R08** (UI/Application Separation, T11→T14). Đã commit 2 commit trên branch cục bộ; **chưa push lên Gitea** (cùng lý do đã ghi nhận ở báo cáo R05/R06).
| Chỉ số | Kết quả |
| :--- | :--- |
| Task hoàn thành | **9/9** (R07: 5, R08: 4 — không tính R07-T06/R08-T01→T10 của team khác) |
| Commit | 2 (`69ab8e1` R07, `0e51356` R08) |
| File thay đổi (R08 riêng) | 51 (44 file mới, 1 file đổi tên+sửa, 6 file sửa) |
| Test | **377 pass** / 4 fail (283 sau R05/R06 → 328 sau R07 → 377 sau R08; +94 test mới) |
| CASAN Check 3 (`scripts/check_imports.py`) | **PASS** — 0 Qt import trong `domain/`, `application/` |
| File production > 400 dòng (file mới) | **0** — lớn nhất `presentation/graph/graph_renderer.py` 391 dòng |
| `python -c "import cowork_local.app"` | **OK** sau mỗi task |
**1 quyết định kiến trúc thay đổi so với plan gốc, xác nhận bằng thực nghiệm** (chi tiết mục 5): `platform/qt/qt_scheduler_clock.py` (R07-T03) đổi thành `infrastructure/qt/qt_scheduler_clock.py` vì package `platform/` ở top-level đè lên module chuẩn `platform` của Python trong một số ngữ cảnh chạy.
---
## 2. Kết quả theo từng EPIC
### 🔹 EPIC R07 — Scheduling & Workflow Runtime (5/5, phạm vi Team Hoa)
| Task | Sản phẩm | Ghi chú |
| :--- | :--- | :--- |
| R07-T01 | `infrastructure/persistence/json/task_repository_impl.py::TaskRepository` | Bọc CRUD của `core/tasks.py`. **Sửa bug thật**: `save_task` trước đây `path.write_text()` không atomic — cùng lớp bug đã sửa cho `projects.py`/`history.py` ở R06-T02. Giờ ghi qua `atomic_write.py::write_json`. |
| R07-T02 | `domain/tasks/schedule_calculator.py::ScheduleCalculator` | Tách phần "schedule math" (cron/interval/daily/weekly/monthly + working-days/holiday exclusion) khỏi `core/tasks.py` thành pure Python. `is_holiday`/`make_cron` inject qua constructor để domain/ không import core/ (ADR-001 I2). `core/tasks.py` giữ nguyên tên hàm cũ, chuyển thành wrapper mỏng — không phá call site nào. **Trước đây 0 test**, giờ có 14 test riêng. |
| R07-T03 | `infrastructure/qt/qt_scheduler_clock.py::QtSchedulerClock` | Bọc `QTimer` mà `TaskScheduler` trước đây tự tạo trực tiếp, inject qua tham số `clock=` mới (mặc định vẫn dùng clock thật). `tests/fakes/fake_clock.py::FakeClock` cho phép test dispatch tick-by-tick không cần Qt event loop. **Đổi vị trí so với plan gốc** — xem mục 5. |
| R07-T04 | `application/scheduling/task_application_service.py::TaskApplicationService` | Gom `run_now`/`duplicate`/`pause`/`delete`/`bulk_delete` và luật nghiệp vụ kéo-thả Kanban (`move_to_status`) — trước đây chỉ kiểm chứng được bằng cách thao tác trực tiếp trên widget thật. |
| R07-T05 | `application/scheduling/ai_task_planner_service.py::AiTaskPlannerService` | Bọc `core/ai_task_planner.py::plan_tasks` và `core/task_import.py::import_tasks` làm seam, cộng thêm bước "gắn file/link đính kèm vào mọi task vừa tạo" (trước đây chỉ tồn tại trong closure của AI worker). |
**Ghi chú phạm vi**: R07-T06 (`Co4EWorkflowService`, `core/co4e_run_manager.py`) là việc Team Nam — không đụng.
### 🔹 EPIC R08 — UI/Application Separation (4/4, phạm vi Team Hoa: T11→T14)
`presentation/` **chưa tồn tại** trong repo trước task này — Team Hoa là người tạo cấu trúc `presentation/` đầu tiên.
| Task | God file gốc | Tách thành | Ghi chú |
| :--- | :--- | :--- | :--- |
| R08-T11 | `ui/schedule_task_tab.py` (795 dòng) | `presentation/scheduling/{kanban_board_widget,calendar_view_widget,ai_task_creator_dialog,ai_task_import_dialog,run_history_dialog}.py` + shell `schedule_task_tab.py` | Kéo-thả Kanban giờ gọi `TaskApplicationService.move_to_status` (R07-T04) thay vì ~30 dòng if/elif nội tuyến. |
| R08-T12 | `ui/folder_tab.py` (1587 dòng — lớn nhất trong 4 file) | `presentation/folder/{workspace_file_tree,document_preview_manager,code_editor,office_document_renderer,ai_file_editor_dialog,ai_edit_model_resolver,ai_edit_pipeline}.py` + shell `folder_tab.py` | **Nối xong `FileWorkspaceService`** (nợ từ R06-T05) — xem mục 5. |
| R08-T13 | `ui/dashboard_tab.py` (437 dòng) | `presentation/dashboard/{token_usage_card_widget,usage_chart_widget,habits_widget}.py` + shell `dashboard_tab.py` | `application/monitoring/dashboard_query_service.py` mới — 3 widget dùng chung 1 nơi merge pricing/period thay vì mỗi widget tự tính lại. **Ghi chú xung đột thư mục** — xem mục 5. |
| R08-T14 | `ui/structure_graph_view.py` (1035 dòng) | `presentation/graph/{graph_scene_items,graph_renderer,graph_messages_view,graph_qa_widget}.py` + shell `structure_graph_view.py` | Renderer và Q&A panel chỉ giao tiếp qua signal (`node_selected`/`graph_rendered`/`raw_json_ready`/`project_changed`) — không bên nào import bên kia. |
**Quy ước áp dụng cho cả 4 task**: mỗi god-file cũ chỉ có 1-2 nơi khởi tạo thật (`app.py`, `ui/workspace_tab.py`) — khác `core/tools.py` ở R05 (hàng chục call site nên phải giữ shim). Nên đã **sửa thẳng import site** và **xoá hẳn file `ui/*.py` cũ**, không giữ shim vô thời hạn.
---
## 3. Kiến trúc sau refactor
```text
presentation/ (MỚI — Team Hoa tạo cấu trúc lần đầu)
scheduling/ {kanban_board_widget, calendar_view_widget,
ai_task_creator_dialog, ai_task_import_dialog,
run_history_dialog, schedule_task_tab}.py
folder/ {workspace_file_tree, document_preview_manager, code_editor,
office_document_renderer, ai_file_editor_dialog,
ai_edit_model_resolver, ai_edit_pipeline, folder_tab}.py
dashboard/ {token_usage_card_widget, usage_chart_widget,
habits_widget, dashboard_tab}.py
graph/ {graph_scene_items, graph_renderer, graph_messages_view,
graph_qa_widget, structure_graph_view}.py
shared/ web_engine_support.py (HAS_WEB_ENGINE — 1 flag dùng chung
thay vì folder/ import module của graph/)
│
▼
application/ scheduling/{task_application_service, ai_task_planner_service}.py
monitoring/dashboard_query_service.py
workspaces/{file_preview_helpers, ai_edit_output, graph_index_service}.py
│ (100% pure Python — check_imports.py chặn import Qt)
▼
domain/ tasks/schedule_calculator.py ← due-time/cron math thuần Python
▲
infrastructure/ qt/qt_scheduler_clock.py ← QTimer đằng sau 1 interface nhỏ
persistence/json/task_repository_impl.py
```
**Nguyên tắc di trú (tiếp nối R04/R05/R06)**: **không viết lại engine**. `core/tasks.py`, `core/task_scheduler.py`, `core/task_executors.py`, `core/ai_task_planner.py`, `core/task_import.py` vẫn là logic gốc bên dưới; các module mới chỉ sở hữu phần đã từng nằm rải rác trong widget (business rule kéo-thả, model routing, extraction pipeline). `pytest` xanh liên tục giữa các bước — chạy full suite sau MỖI task, không dồn tới cuối.
**Điểm khác với R05/R06**: khi 1 file bị tách vượt quá 400 dòng dù đã theo đúng mapping trong `Feature_Architecture_Proposal.md`, đã tách thêm 1-2 file phụ theo kiểu **composition** (một class phụ nhận `owner` là widget chính, thao tác trực tiếp lên state của owner) thay vì service riêng biệt — ví dụ `run_history_dialog.py`, `office_document_renderer.py`, `ai_edit_pipeline.py`, `ai_edit_model_resolver.py`, `graph_messages_view.py`. Đây là split thuần kỹ thuật để đạt giới hạn LOC, không phải ranh giới kiến trúc tầng (cả object chính và object phụ đều ở `presentation/`).
---
## 4. Bằng chứng kiểm thử
### Phân bố test (bao gồm test mới của Team Hoa)
| Mốc | Tổng pass | Test mới thêm |
| :--- | ---: | ---: |
| Sau R05/R06 (baseline) | 283 | — |
| Sau R07 (T01→T05) | 328 | +45 |
| Sau R08 (T11→T14) | 377 | +49 |
| **Tổng test mới R07+R08** | | **+94** |
4 fail còn lại **giống hệt baseline đã ghi nhận ở báo cáo R05/R06** — không liên quan R07/R08:
* `tests/test_config_security.py` × 2 (EPIC R02/Team Nam)
* `tests/unit/test_routing_wiring.py` × 2 (môi trường máy này có Ollama/llama3.1 thật)
### Loại test theo task
* **Pure Python, không Qt** (R07-T01/T02/T04/T05, `application/workspaces/*`, `application/monitoring/*`): `tests/unit/test_{task_repository,schedule_calculator,task_application_service,ai_task_planner_service,dashboard_query_service,file_preview_helpers_and_ai_edit_output,graph_index_service}.py`.
* **Qt thật, offscreen** (R07-T03, R08-T11→T14): `tests/integration/test_{qt_scheduler_clock,task_scheduler_clock_wiring* (unit, dùng FakeClock),schedule_task_tab,folder_tab,dashboard_tab,structure_graph_view}.py` — dựng widget thật, không phải double, theo đúng phong cách `test_history_dir_race.py` đã lập ở R06-T04.
* **Test đã sửa (không phải mới)**: `tests/integration/test_routing_surfaces.py` — 2 test `test_ai_edit_*` trỏ thẳng vào `ui.folder_tab.FolderTab._ai_apply_routing`/`_ai_routed_provider` (thuộc code CŨ); đã cập nhật để trỏ vào `presentation.folder.folder_tab.FolderTab.ai_panel.resolver.apply_routing`/`.routed_provider` (thêm 2 property public mới trên `AiEditModelResolver` để giữ khả năng test).
### Đối chiếu Definition of Done
| # | Tiêu chí | Kết quả |
| :--- | :--- | :--- |
| 1 | Mọi file mới < 400 dòng | ✅ Lớn nhất: `presentation/graph/graph_renderer.py` 391 dòng |
| 2 | 0 import Qt trong `domain/`, `application/` | ✅ `check_imports.py` PASS |
| 3 | Comment tiếng Anh giải thích lý do ở mọi khối sửa/mới | ✅ |
| 4 | Có unit/contract/integration test, verify bằng chạy thật | ✅ +94 test mới, mọi widget Qt test bằng offscreen thật |
| 5 | Không hồi quy | ✅ 377/381 pass — 4 fail là lỗi có sẵn từ trước, cùng baseline R05/R06 |
| 6 | Ghi Start/End vào Checklist | ✅ 9 task đã tick kèm mốc thời gian thật |
| 7 | Cổng CASAN (`run_quality_gate.py`, R10-T02) | ⚠️ Chưa viết (thuộc R10, chưa tới lượt) — Check 3 đã PASS |
---
## 5. Quyết định kiến trúc & phát hiện trong quá trình làm
### 🔴 R07-T03 — `platform/qt/` đè lên module chuẩn `platform` của Python
Plan gốc (`Feature_Architecture_Proposal.md`) đặt tên `platform/qt/qt_scheduler_clock.py` — một package top-level mới tên `platform`. Trước khi viết, đã thử:
```bash
cd <repo_root> && python -c "import cowork_local; import platform; print(platform.system())"
```
Sau khi tạo `platform/__init__.py`, lệnh trên báo lỗi:
```
AttributeError: module 'platform' has no attribute 'system' (consider renaming
'...\platform\__init__.py' since it has the same name as the standard library
module named 'platform' and prevents importing that standard library module)
```
Nguyên nhân: package repo root chính là `cowork_local`, nhưng bất cứ khi nào chính thư mục repo root nằm trực tiếp trên `sys.path` (ví dụ chạy `python -c "..."` hoặc bất kỳ script nào với cwd = repo root — không đi qua `__main__.py`'s parent-dir fixup), `import platform` sẽ phân giải nhầm vào package cục bộ thay vì thư viện chuẩn. `core/windows_sandbox_vm.py` và `core/appcontainer_sandbox.py` đều `import platform` — nếu 2 file này chạy trong ngữ cảnh cwd=repo root, chức năng phát hiện hệ điều hành sẽ vỡ hoàn toàn.
**Sửa**: chuyển sang `infrastructure/qt/qt_scheduler_clock.py` (không tạo package `platform/` mới) — `infrastructure/` đã có sẵn các thư mục cùng cấp (`filesystem/`, `mcp/`, `persistence/`, `providers/`, `telemetry/`), đúng layer cho một adapter cụ thể-toolkit. Verify lại: `python -c "import cowork_local; import platform; print(platform.system())"` → `Windows` (đúng).
### 🟠 R08-T12 — Nối `FileWorkspaceService` (nợ từ R06-T05)
Báo cáo R05/R06 mục 7-#3 đã ghi: *"`FileWorkspaceService` chưa có nơi gọi thật... `ui/folder_tab.py` vẫn dùng trực tiếp `core/tools.py::execute_tool`"*. Xác nhận lại bằng grep trước khi bắt đầu R08-T12: đúng 0 occurrence.
Khi tách `document_preview_manager.py`, mọi điểm ghi text thuần (`save()`, `create_new_file()`, `write_content()`) đã chuyển sang gọi `FileWorkspaceService.write_file(rel, content)` — dùng `WorkspaceSession.unscoped(Path(root))` vì Folder Explorer duyệt bất kỳ thư mục nào người dùng chọn (không giới hạn vào 1 project sandbox). Nhánh ghi `.pptx` (build file nhị phân từ `pptx_edit`) vẫn giữ nguyên đường cũ — không phải văn bản thuần, `FileWorkspaceService` không có ý nghĩa ở đó.
**Tác dụng phụ có lợi, không chủ đích tìm kiếm nhưng xác nhận đúng**: `infrastructure/filesystem/file_tools.py::write_file` đã có sẵn cảnh báo cú pháp Python (`_check_python_syntax`) và tự tạo `.xlsx` thật từ text khi đích là `.xlsx` — cả 2 hành vi này **trước đây KHÔNG tồn tại** trên đường ghi cũ của `folder_tab.py` (chỉ `Path.write_text()` trần). Từ giờ Folder Explorer/AI Editor được hưởng cả 2 miễn phí, cùng đường với agent's `write_file` tool.
### 🟡 R08-T13 — Xung đột thư mục `application/monitoring/` với Team Nam
Giống cách R06-T02 xử lý xung đột `atomic_write.py` vs `atomic_json_file.py`: `Feature_Architecture_Proposal.md`'s bảng "Ranh giới phân hệ" giao `application/monitoring/` cho **Team Nam** (R08-T07→T10), nhưng cùng tài liệu đó lại đặt `dashboard_query_service.py` vào ĐÚNG thư mục này như một phần việc Dashboard của Team Hoa (R08-T13). Vì thư mục chưa tồn tại (chưa ai tạo trước), không có xung đột FILE thật — chỉ tạo file mới. Đã ghi chú trong `application/monitoring/__init__.py`'s docstring và trong Checklist để Team Nam xác nhận khi họ bắt đầu R08-T07→T10.
---
## 6. Cải thiện phụ (không nằm trong yêu cầu task)
| Cải thiện | Ảnh hưởng |
| :--- | :--- |
| `core/tasks.py::save_task` chuyển sang ghi atomic (R07-T01) | Cùng lớp bug đã sửa cho `projects.py`/`history.py` ở R06-T02 — `path.write_text()` trần không atomic, crash giữa lúc ghi để lại file task hỏng, `load_task` coi như "không tồn tại" → mất task âm thầm. Có test giả lập crash xác nhận file cũ không hỏng. |
| `domain/tasks/schedule_calculator.py` có bộ test riêng (R07-T02) | `core/tasks.py`'s docstring tự nhận "Qt-free so it can be unit-tested headlessly" nhưng **0 test tồn tại** cho phần cron/interval/holiday-exclusion trước task này. Giờ có 14 test bao phủ daily/weekly/monthly/cron + working-days/holiday. |
| `AiEditModelResolver.routed_provider`/`.routed_model` (public property mới, R08-T12) | Cần thêm để giữ được `tests/integration/test_routing_surfaces.py`'s 2 test AI-Edit routing sau khi lớp routing chuyển từ `FolderTab` sang `AiEditModelResolver` — không có trong yêu cầu gốc nhưng bắt buộc để không hồi quy 1 test đã có từ EPIC R03. |
---
## 7. Còn nợ & cần quyết định
| # | Nội dung | Người quyết |
| :--- | :--- | :--- |
| 1 | **`application/monitoring/` xung đột quy hoạch với Team Nam** (mục 5) — cần xác nhận hợp nhất hay giữ nguyên khi Team Nam bắt đầu R08-T07→T10. | Team Nam |
| 2 | **AI-Edit pipeline (`ai_edit_pipeline.py`) và Graph Q&A ask-flow (`graph_qa_widget.py::_ask`) chưa có test end-to-end thật** — cả 2 chạy trên `AgentWorker` (QThread) thật, và **vốn dĩ đã không có test nào trước khi refactor** (xác nhận bằng grep trước khi bắt đầu R08-T12/T14). Phạm vi test hiện tại: wiring giữa các widget, containment ghi file, render pipeline gọi trực tiếp (`_render()`), KHÔNG phải luồng gửi câu hỏi/instruction → chờ AgentWorker → nhận kết quả qua QThread thật. | Team Hoa (nếu cần, thuộc phạm vi R10 Testing Pyramid) |
| 3 | **Dev tooling chưa cập nhật đường dẫn cũ**: `tools/check_controls_alive.py`, `tools/capture_screens.py`, `tools/build_audit_page.py`, `docs/screens/*.json`, `docs/ui-audit*.html` vẫn tham chiếu `ui/schedule_task_tab.py`/`ui/folder_tab.py`/`ui/dashboard_tab.py`/`ui/structure_graph_view.py` (đường dẫn cũ, giờ không còn tồn tại) — các script/tài liệu này không nằm trong `tests/`, không ảnh hưởng CI, nhưng sẽ lỗi nếu chạy tay. | Chưa quyết định người phụ trách |
| 4 | Chưa `git push` — cùng tình trạng đã ghi nhận ở báo cáo R05/R06 mục 7-#1. | Admin Gitea |
| 5 | `WorkspaceRepository`/`ConversationRepository` (R06) **vẫn chưa có call site sản xuất thật** — R08-T12 chỉ nối `FileWorkspaceService`, không đụng 2 repository kia (nằm ngoài phạm vi Folder Explorer). | Còn treo từ R06, chưa có EPIC nào nhận |
---
## 8. Phạm vi chưa kiểm thử
Nêu rõ để tránh hiểu nhầm mức độ bảo đảm, cùng tinh thần minh bạch đã dùng ở báo cáo R05/R06:
* **`presentation/folder/ai_edit_pipeline.py`** (toàn bộ luồng plan → edit → apply/discard, streaming qua `AgentWorker`) — chỉ verify được bằng cách đọc code + đảm bảo import/construction không lỗi (`test_folder_tab.py` dựng `FolderTab` thật, mở AI panel, nhưng không gửi instruction qua worker thật). Đây là khoảng trống **có sẵn từ code gốc**, không phải hồi quy do refactor.
* **`presentation/graph/graph_qa_widget.py::_ask`** (câu hỏi → provider thật → trích xuất file → câu trả lời) — tương tự, chỉ test được phần không cần AgentWorker thật (wiring `node_selected`/`project_changed`, `_candidate_file_paths`, dedup).
* **`presentation/folder/ai_edit_model_resolver.py`'s image-model scan/suggest** (`_scan_all_image_models`, `_suggest_cross_provider_image`) — chưa test với provider thật có model ảnh; unit test chỉ phủ `routing`/`routed_provider`/`routed_model` qua `test_routing_surfaces.py`.
* **`office_document_renderer.py`'s PDF/LibreOffice conversion path** (`show_document`, `_ensure_pdf_view`) — cần `QtPdf`/LibreOffice cài thật trên máy chạy test; chưa xác nhận trên máy không có 2 phụ thuộc này (tự động fallback sang text, đã giữ nguyên logic, nhưng chưa lặp lại kịch bản fallback bằng test thật).
* **Đã mở app thật bằng `python -c "import cowork_local.app"` sau mỗi task** để xác nhận không lỗi import — **chưa** mở app GUI thật, thao tác tay qua Schedule Task/Folder Explorer/Dashboard/GraphRAG để xác nhận trải nghiệm người dùng cuối (chỉ verify bằng test Qt offscreen).
---
## 9. Việc kế tiếp của Team Hoa
Theo `Refactoring_Checklist.md`, phạm vi 4 EPIC của Team Hoa (R05, R06, R07, R08 phần T11→T14) đã **hoàn tất toàn bộ**. Các bước còn lại không thuộc EPIC riêng của Team Hoa nữa:
| Việc | Điều kiện |
| :--- | :--- |
| Checkpoint 2 (Services & Sub-widgets, 28/08) | Cần cả 3 team xong phần UI split của mình (Team Duy R08-T01→T06, Team Nam R08-T07→T10 vẫn đang làm) |
| CASAN Check 2 (Modularity/LOC, Team Hoa chủ trì, 30/08) | `scripts/check_loc.py` **chưa tồn tại** (thuộc R10-T02, Team Duy) — khi có script, chạy trên toàn repo để xác nhận 0 file >400 dòng |
| Giải quyết mục 7 #1 (xung đột `application/monitoring/`) | Khi Team Nam bắt đầu R08-T07→T10 |
| R10 (Testing, Packaging & Contributor Experience) | Team Duy chủ trì, chờ 3 team hoàn tất |
---
## 10. Lịch sử commit
| Commit | Nội dung |
| :--- | :--- |
| `69ab8e1` | feat(R07): task repository, schedule calculator, Qt clock adapter, task/AI-planner services |
| `0e51356` | feat(R08): split ScheduleTaskTab, FolderTab, DashboardTab, StructureGraphView |
+2 -2
View File
@@ -74,7 +74,7 @@
> * Mọi file mới **< 400 dòng** (lớn nhất: `domain/tools/tool_registry.py` 125 dòng). `core/tools.py` giảm từ 566 ➔ 291 dòng. > * Mọi file mới **< 400 dòng** (lớn nhất: `domain/tools/tool_registry.py` 125 dòng). `core/tools.py` giảm từ 566 ➔ 291 dòng.
> >
> ### 📄 BÁO CÁO CHI TIẾT > ### 📄 BÁO CÁO CHI TIẾT
> Xem `docs/refactor/BaoCao_TeamHoa_R05_R06.md` — kết quả từng EPIC, bằng chứng kiểm thử, 2 lỗi thật đã phát hiện (permission gate bị bỏ qua cho MCP tools, race condition lưu nhầm lịch sử), và phạm vi **chưa** kiểm thử. > Xem `docs/refactor/BaoCao_TeamHoa_R05_R08.md` (báo cáo gộp R05→R08) — kết quả từng EPIC, bằng chứng kiểm thử, 2 lỗi thật đã phát hiện (permission gate bị bỏ qua cho MCP tools, race condition lưu nhầm lịch sử), và phạm vi **chưa** kiểm thử.
> >
> ### 🔧 TÓM TẮT R06 > ### 🔧 TÓM TẮT R06
> * **R06-T01**: `domain/workspaces/workspace_session.py::WorkspaceSession` — snapshot bất biến (project_id, workspace_root, sandbox_dir, allowed_paths) + `is_allowed(path)`. > * **R06-T01**: `domain/workspaces/workspace_session.py::WorkspaceSession` — snapshot bất biến (project_id, workspace_root, sandbox_dir, allowed_paths) + `is_allowed(path)`.
@@ -115,7 +115,7 @@
> * `python -c "import cowork_local.app"` ➔ OK sau mỗi task (app khởi động được với toàn bộ import mới) > * `python -c "import cowork_local.app"` ➔ OK sau mỗi task (app khởi động được với toàn bộ import mới)
> >
> ### 📄 BÁO CÁO CHI TIẾT > ### 📄 BÁO CÁO CHI TIẾT
> Xem `docs/refactor/BaoCao_TeamHoa_R07_R08.md` — kết quả từng task, 1 quyết định kiến trúc đổi so với plan gốc (đã thực nghiệm xác nhận), việc "nối dây" `FileWorkspaceService` (nợ từ R06-T05), và phạm vi **chưa** kiểm thử. > Xem `docs/refactor/BaoCao_TeamHoa_R05_R08.md` (báo cáo gộp R05→R08) — kết quả từng task, 1 quyết định kiến trúc đổi so với plan gốc (đã thực nghiệm xác nhận), việc "nối dây" `FileWorkspaceService` (nợ từ R06-T05), và phạm vi **chưa** kiểm thử.
> >
> ### 📌 CÒN NỢ / CẦN QUYẾT ĐỊNH > ### 📌 CÒN NỢ / CẦN QUYẾT ĐỊNH
> 1. **`application/monitoring/` mới tạo ở R08-T13** (`dashboard_query_service.py`) nhưng thư mục này được quy hoạch cho Team Nam (R08-T07→T10). Chưa có xung đột file thật (thư mục trống trước đó) nhưng **cần Team Nam xác nhận** khi bắt đầu phần Monitoring của họ — xem chi tiết trong báo cáo. > 1. **`application/monitoring/` mới tạo ở R08-T13** (`dashboard_query_service.py`) nhưng thư mục này được quy hoạch cho Team Nam (R08-T07→T10). Chưa có xung đột file thật (thư mục trống trước đó) nhưng **cần Team Nam xác nhận** khi bắt đầu phần Monitoring của họ — xem chi tiết trong báo cáo.