Thư mục gốc: 22 file .py -> 7
--------------------------------
13 file "thành phần" nằm rải rác ngay ngoài thư mục gốc, mỗi file chỉ có ĐÚNG
MỘT nơi import — chính cái hub của nó:
i18n.py + 10 file i18n_*.py -> i18n/__init__.py + i18n/*.py
theme.py + 3 file theme_*.py -> theme/__init__.py + theme/*.py
Đổi hub thành `__init__.py` nên 78 chỗ `from ..i18n import tr` và 24 chỗ
`from ..theme import current_palette` KHÔNG phải sửa một dòng nào. Git nhận ra
11/15 file là đổi tên thuần, 0 dòng thay đổi; 4 file còn lại chỉ sửa đúng dòng
import và mấy tham chiếu tên file trong docstring.
Đối chiếu với bản trước khi gom, cùng một phép băm:
số khoá i18n 1431 -> 1431 hash STRINGS a06cc34b... (trùng)
QSS dark hash 7bb230a4... (trùng)
QSS light hash 884f73ce... (trùng)
`check_loc.py` phải khai thêm "i18n", "theme" vào DEFAULT_TARGET_DIRS: chúng
từng được quét theo diện "module nằm ở thư mục gốc", gom vào gói rồi thì không
khai là lặng lẽ tuột khỏi tầm quét.
Bánh cóc `ui/widgets.py` siết 505 -> 466 sau khi tách SegmentedControl — nợ cũ
co lại thì con số phải co theo, không thì bánh cóc đứng yên mãi ở mức cũ.
Một file requirements
---------------------
Xoá `requirements-test.txt`. Nó chỉ có `pytest` + `pydantic`, nhưng 64/108 file
test dựng widget thật và 20 file trong đó import PySide6 thẳng ở đầu file không
có bảo vệ — nên CI cài mỗi file kia thì pytest chết ngay lúc thu thập test chứ
không phải "vài test bị bỏ qua". Hai file cho một danh sách gần trùng nhau chỉ
tạo thêm một chỗ để lệch phiên bản, và `pydantic` đã bị chép ở cả hai.
CI đổi sang cài `requirements.txt`. Người dùng cuối cài thừa pytest vài MB.
Kèm theo: `install.bat` bỏ cờ `--dev` (không còn gì để cài thêm). Khối `if`
rỗng còn sót lại làm cmd.exe báo "( was unexpected at this time" và script chết
ngay sau bước cài thư viện — đã gỡ hẳn.
859 test xanh · 4/4 cổng CASAN · check_design_parity 32/32 ·
check_layout_geometry trùng từng byte với bản trước refactor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
102 lines
3.7 KiB
Markdown
102 lines
3.7 KiB
Markdown
# Cowork Local
|
|
|
|
Cowork Local is the internal AI cowork desktop platform. It provides a local-first desktop runtime, multi-turn conversational agents, workspace isolation, task scheduling, MCP connectors, security guardrails, and model routing.
|
|
|
|
---
|
|
|
|
## 🏛️ 4-Tier Clean Architecture
|
|
|
|
The codebase strictly adheres to **Clean Architecture** with unidirectional inward dependencies:
|
|
|
|
```text
|
|
presentation/ (PySide6 UI, Shell, NavRail, Chat, Scheduling, Settings, Dashboard)
|
|
│
|
|
▼
|
|
application/ (Pure Python Orchestration: Conversations, Scheduling, Workspaces, Monitoring, Routing)
|
|
│
|
|
▼
|
|
domain/ (Pure Python: Entities, Immutable Execution Requests, Agent Events, Descriptors)
|
|
▲
|
|
│
|
|
infrastructure/ (Adapters, LLM Providers, Atomic Persistence, Keyring SecretStore, MCP)
|
|
```
|
|
|
|
- **Domain & Application Layers**: 100% Pure Python (zero Qt/UI imports).
|
|
- **Single Responsibility**: Every production module is strictly `<= 400 LOC`.
|
|
- **Security & Durability**: API keys stored in OS Keyring; atomic JSON disk persistence.
|
|
|
|
---
|
|
|
|
## 🚀 Quick Start
|
|
|
|
### 1. Windows — two double-clicks
|
|
|
|
```
|
|
install.bat once, to install the Python dependencies
|
|
run.bat every time, to start the app
|
|
```
|
|
|
|
`install.bat` builds an isolated virtualenv under `%LOCALAPPDATA%\CoworkLocal`
|
|
(deliberately **outside** the repo — the quality gates walk the whole directory
|
|
tree, so a `.venv` in here would turn every vendored module into a Gate O
|
|
violation). Add `--dev` to also install the test dependencies, or `--system` to
|
|
skip the virtualenv and install into the Python already on `PATH`.
|
|
|
|
Both scripts also make the source importable under its package name. That step
|
|
is not optional: `python -m cowork_local` only resolves when the checkout
|
|
directory is literally named `cowork_local`, and the MS365 MCP server is
|
|
launched as a subprocess with `python -m cowork_local.mcp_servers.ms365_server`,
|
|
so a differently-named checkout breaks the app *and* its subprocesses. The
|
|
scripts create a junction instead of forcing anyone to rename their folder.
|
|
|
|
### 2. Any platform — run from source
|
|
|
|
From the **parent** of a checkout directory named `cowork_local`:
|
|
|
|
```bash
|
|
python -m cowork_local
|
|
```
|
|
|
|
### 3. Run Automated Tests
|
|
```bash
|
|
python -m pip install -r requirements.txt
|
|
pytest -q
|
|
```
|
|
|
|
There is one requirements file, not a runtime/test pair. A separate test file
|
|
would hold only `pytest`: 64 of the 108 test modules build real widgets, and 20
|
|
of them import PySide6 unguarded at module scope, so it would have to pull in
|
|
almost the whole runtime list anyway — two files for one near-identical list is
|
|
just a second place for the pins to drift.
|
|
|
|
---
|
|
|
|
## 🛡️ CASAN Quality Gate & Verification
|
|
|
|
Before submitting any Pull Request, run the unified CASAN Quality Gate:
|
|
|
|
```bash
|
|
# Run all 4 quality gates (Clean Arch, Secrets, LOC, and Pytest Suite)
|
|
python scripts/run_quality_gate.py
|
|
|
|
# Run static and architectural guards only (fast check)
|
|
python scripts/run_quality_gate.py --skip-tests
|
|
```
|
|
|
|
Individual guard scripts:
|
|
- **Clean Architecture Import Guard**: `python scripts/check_imports.py`
|
|
- **Secrets & Plaintext Audit**: `python scripts/audit_security.py`
|
|
- **Single Responsibility LOC Guard**: `python scripts/check_loc.py --max-lines 400`
|
|
- **Release E2E Smoke Test**: `pytest tests/e2e/test_smoke.py -v`
|
|
|
|
---
|
|
|
|
## 🤝 Contributing & Recipes
|
|
|
|
- **Quick Start Guide**: See [START_CONTRIBUTING.md](START_CONTRIBUTING.md).
|
|
- **Contributor Recipes**: See [docs/governance/contributor-recipes.md](docs/governance/contributor-recipes.md) for step-by-step recipes to:
|
|
1. Add a new AI Model Provider.
|
|
2. Add a new Built-in Tool / MCP Server.
|
|
3. Add a new Screen / Tab / Widget.
|
|
- **Security Policy**: See [SECURITY.md](SECURITY.md).
|