Gate O — module production phải có ít nhất một nơi import
---------------------------------------------------------
Ba cổng đang có đều không bắt được mã chết, đúng như 1.400 dòng ở commit
trước đã chứng minh. Gate O dựng đồ thị import bằng AST từ
`__init__`/`__main__`/`app`, theo cả import muộn trong thân hàm.
Hai ngoại lệ tự động để `ALLOWLIST` không phải chép lại cùng một lý do nhiều
lần: `__init__.py` của gói mà mọi thành viên đều dormant, và module chỉ được
chính mã dormant đã miễn trừ import.
Cổng cũng đếm tuổi 9 seam chưa nối dây (nhãn `SEAM · dựng <ngày>`) và nhắc
khi quá 30 ngày. Chỉ [WARN], không làm CI đỏ: để nó đỏ thì CI sẽ đỏ vào một
buổi sáng mà không ai sửa gì, và cách nhanh nhất để xanh lại là sửa ngày.
Cổng LOC — quét 366 file thay vì 191
------------------------------------
`DEFAULT_TARGET_DIRS` chỉ có 4 gói Clean Architecture, nên một file 944 dòng
trong `ui/` vẫn qua cổng. Nay quét cả `ui/`, `core/`, `providers/`,
`security/`, `mcp_servers/` và các module ở thư mục gốc.
18 file đã dài hơn 400 dòng từ trước nằm trong `LEGACY_ALLOWANCE` — bánh cóc
chỉ quay một chiều, và nó đo DÒNG MÃ chứ không đo dòng vật lý. Bánh cóc chỉ
hỏi một câu, "file này có đang để thêm việc vào không?", mà viết thêm một
docstring thì không. Đếm dòng vật lý ở đó biến cổng thành thứ phạt người viết
tài liệu, và cách dễ nhất để làm nó xanh lại sẽ là xoá bớt chú thích. Trần
400 vẫn đếm dòng vật lý — đó là hợp đồng đã chốt của cổng S.
CI
--
Ghim tên thư mục checkout là `cowork_local`: nhiều test characterization sinh
tiến trình con `python -c "from cowork_local... import ..."`, mà tiến trình
con chỉ import được khi trên sys.path có thư mục mang đúng tên gói. Checkout
vào thư mục tên khác làm 73 test đỏ vì lý do không liên quan tới mã.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hai bản tách song song của cùng một god-file cùng được giữ lại sau một lần
merge. Bản chết không ai import, và hai file trong đó còn không import nổi:
`graph_render.py` lấy `GraphQaMixin` không tồn tại, `task_actions.py` lấy
`ui.calendar_view` đã bị xoá.
Kèm theo 5 gói chỉ có `__init__.py` với docstring hứa những module chưa bao
giờ được tạo. Hai trong số đó (`adapters/qt/`, `infrastructure/platform/qt/`)
là vị trí đã bị bác bỏ có ghi lý do — `QtSchedulerClock` nằm ở
`infrastructure/qt/`, và lý do vì sao không đặt ở `platform/` vẫn còn nguyên
trong `infrastructure/qt/__init__.py`.
Không cổng nào bắt được đám này: file không ai import vẫn đúng chiều phụ
thuộc, vẫn sạch credential, vẫn dưới 400 dòng. Cổng O ở commit sau đi tìm
đúng khoảng trống đó.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R03 (Team Duy) — Model Providers & Routing. All six tasks done.
R03-T02 — Provider catalogue
domain/models/provider_descriptor.py ProviderDescriptor (frozen), WireProtocol, AuthKind
infrastructure/providers/provider_registry.py
thread-safe registry: id/alias lookup, dynamic
lookup by model id, adapter selection by protocol
providers/factory.py drops its own _REGISTRY table and delegates to the
registry, still raising ProviderError for callers
R03-T03 — RoutingApplicationService (pure Python, 4 modes)
application/model_routing/routing_models.py
RoutingMode (off/auto/manual/fallback),
RoutingRequest (immutable snapshot), RouteEvaluation,
RoutingOutcome
application/model_routing/routing_application_service.py
the single decision flow, reached through two narrow
ports plus a caller-supplied confirm callback, so no
Qt import is needed
application/model_routing/core_routing_adapter.py
binds the ports to core/routing and AppContext
Fallback is a new resilience mode: keep the selected model while it can serve the turn,
re-route only when it cannot. Wired end to end through config.py, state.py,
ui/routing_toggle.py and i18n.py (EN/JA/VI).
R03-T04 / T05 — Remove the duplicated routing flow
ui/chat_panel.py (#L638), ui/co4e_tab.py, ui/folder_tab.py each drop ~35 lines of copied
logic and call the shared service; the widgets now only build a RoutingRequest, host the
Manual-mode modal and render the outcome.
R03-T06 — Token usage as an event
infrastructure/telemetry/usage_sink.py UsageEvent + UsageEventSink protocol, with tracker,
in-memory and composite sinks
providers/openai_compat.py, providers/anthropic.py
publish a UsageEvent instead of writing to the
usage tracker themselves
core/usage_tracker.py adds current_context() so a sink can borrow and
restore a thread's attribution
R03-T01 — Contract tests
tests/contracts/test_providers.py parametrises over every provider in the registry: chat()
signature, canonical assistant message, normalised tool calls, response closed, tool schema
translation, ProviderError, list_models/test_connection, one UsageEvent per turn.
Test infrastructure fix (required to verify any of the above): tests/conftest.py used to put
the repository's PARENT directory on sys.path, so `import cowork_local.*` resolved against
whichever sibling folder happened to carry that name — on a dev machine, an unrelated older
checkout. The suite reported green while exercising different code. The conftest now binds
this checkout to the cowork_local name in sys.modules.
Verification
pytest tests/ 236 passed in ~1.8s (102 before this change)
scripts/check_imports.py PASS, 0 forbidden imports in domain/ and application/
new production files largest is 288 lines, all under the 400 LOC ceiling
new tests 134 (50 contract, 70 unit, 14 integration), all offline
scripts/run_quality_gate.py does not exist yet (R10-T02), so DoD item 7 was covered by
check_imports.py plus the full suite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/refactor/BaoCao_TeamDuy_R01_R03_R04.md records what was delivered against
each of the 16 tasks, the measured evidence (243 tests, 218 of them in 1.22s;
check_imports PASS; no production file over 400 LOC), the three real defects
found while working - the routing_application() deadlock, the swallowed
"notice" event, and the suite silently testing a different checkout - plus the
six open decisions and, explicitly, what was NOT tested (no manual app launch,
no real provider traffic, tools/check_*.py not run).
Refactoring_Checklist.md now links to it from the progress block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit recorded Team Duy as owning R01/R02/R04/R10. That is wrong.
Feature_Architecture_Proposal.md line 7 and DeltaTeam_prompt.md line 17 both
state R01, R03, R04, R08 (Chat UI) and R10; R02 belongs to Team Nam, which is
also who owns the two failing config-security tests.
The completed work itself (R01, R03, R04) was already correct and is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verification gap closed. The suite proved the new services correct in isolation,
but three paths I had modified had no test actually running them:
tests/integration/test_task_executor_flow.py (7 tests)
The Schedule Task path after R04-T05. Pins that History is still re-saved from
the LIVE message list mid-run (the reason begin_turn() exists - the pre-turn
copy would have frozen progress at the first user message), that update_plan
tracking still reports an unfinished checklist, and that a failed run still
raises so execute_task writes error.txt.
tests/integration/test_routing_surfaces.py (11 tests)
Real offscreen CoworkTab/Co4ETab/FolderTab calling the shared routing service:
correct surface key per screen, Auto switches, Off does not consult the engine,
Manual switches only on approval, a pinned Admin agent still wins, and AI-Edit
still pins TaskType.CODING. Also pins the field contract ui/routing_toggle.py
reads off RoutingDecision (from_model/to_model as provider/model keys) - a
rename there would only fail inside a modal dialog.
Also updates docs/refactor/Refactoring_Checklist.md: the 16 completed R01/R03/R04
tasks, the Team Duy daily rows, and a status block recording the measured
numbers, the scope correction (team owns R01/R02/R04/R10), and what is still
outstanding.
Suite: 243 passed, 2 pre-existing failures (EPIC R02). Fast suite (unit +
contracts + characterization + routing): 218 passed in 1.16s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R04 (Team Duy) - the turn lifecycle leaves the widget.
R04-T01 domain/agents/conversation_execution_request.py
Frozen snapshot of one turn, captured on the UI thread at submit time. The
job closure used to read widget/workspace state from inside the worker
thread, so a turn could run on a mix of submit-time and later state
depending on thread timing.
R04-T02 domain/agents/agent_event.py
13 frozen event types replacing untyped emit() dicts, with a two-way bridge
so existing widgets keep consuming the legacy shape until EPIC R08. Adds
TurnCompletedEvent - the end-of-turn signal the engine never had, which is
why a cancelled turn and a failed turn look identical to the UI today.
R04-T03 application/conversations/conversation_application_service.py
Runs a turn from a request and reports typed events. Never raises across the
worker boundary; TurnResult.raise_if_failed() preserves the existing
exception-based failure path. begin_turn()/execute_turn() expose the live
message list for callers that autosave history mid-run.
R04-T04 ui/cowork_tab.py::build_job -> snapshot + service.
R04-T05 core/task_executors.py::_run_agent -> same service (was a second,
slightly different assembly of the same call).
Caught while wiring the bridge: the first event vocabulary had no "notice"
event, so Agent Security warnings and auto-compaction notices would have been
silently swallowed. Added NoticeEvent plus a test that scans the engine sources
for emit() tags and fails when one has no typed counterpart.
New: tests/integration/ - real offscreen CoworkTab running a scripted turn end
to end (7 tests), including a characterisation of the extra provider call Agent
Security spends reviewing each request.
Suite: 225 passed, 2.74s. check_imports: PASS. All new files < 400 LOC.
2 pre-existing failures remain in test_config_security.py (EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R03 (Team Duy) - one provider catalogue, one routing flow, one usage seam.
R03-T01 tests/contracts/test_providers.py
29 contract tests every provider must satisfy: canonical assistant message,
streamed text == returned content, reasoning never joins the answer, parsed
tool arguments, ProviderError for every failure. Real adapters exercised
offline by stubbing Provider._request.
R03-T02 domain/models/provider_descriptor.py
infrastructure/providers/provider_registry.py
Provider facts declared once (was split across providers/factory.py,
DEFAULT_CONFIG and PROVIDER_LABELS). ProviderRegistry.build() also stamps the
descriptor id onto the instance, so ollama/github_copilot/codex usage is no
longer all attributed to "openai_compat", and never mutates the caller config.
R03-T03 application/model_routing/routing_application_service.py
Pure-Python routing policy with four modes: Off, Auto, Manual and the new
Fallback (switch only AFTER the current model fails). Depends on a RoutingPort
protocol; production wires the existing core.routing engine underneath.
R03-T04/T05 ui/chat_panel.py, ui/co4e_tab.py, ui/folder_tab.py
Three near-identical routing copies (~40 lines each) replaced by a call to
ctx.routing_application() plus a confirm callback. Mode vocabulary now lives
in one place (normalize_mode/is_valid_mode) instead of four literal tuples.
R03-T06 infrastructure/telemetry/usage_sink.py
Token usage extracted from both providers into UsageEvent + UsageEventSink.
Estimation pinned against core.usage_tracker so no recorded number changes.
Also fixes a deadlock introduced while wiring AppContext: routing_application()
held _routing_lock and called routing(), which takes the same non-reentrant lock.
Suite: 186 passed, 1.22s. check_imports: PASS. All new files < 400 LOC.
2 pre-existing failures remain in test_config_security.py (EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R01 (Team Duy) - safety net before the parallel refactor starts.
R01-T01 docs/architecture/ADR-001-layered-architecture.md
4-tier boundaries, allowed dependency directions, invariants I1-I6 and
the strangler-fig migration strategy.
R01-T02 tests/fakes/{fake_provider,fake_tool_executor}.py
Scripted, offline Provider and extra-tool executor doubles.
R01-T03 scripts/check_imports.py
AST-based Clean Architecture Guard (CASAN Check 3). Also covers relative
imports and function-local imports; ASCII-only output for cp932 consoles.
R01-T04 tests/characterization/test_run_cowork.py
13 snapshot tests pinning run_cowork's current observable contract before
EPIC R04 moves its orchestration into application/.
R01-T05 docs/architecture/dormant-code.md
Import-graph scan: 43 unimported modules verified down to 6 genuinely
dormant items (~1887 LOC); the rest run via subprocess/CLI entry points.
tests/conftest.py binds `cowork_local` to THIS checkout by absolute path -
previously sys.path discovery could import a sibling checkout and the suite
would silently test the wrong code.
Suite: 104 passed, 1.08s (2 pre-existing failures in test_config_security.py
remain - config.py still ships a hardcoded default password, EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy of sections VI-IX from Feature_Architecture_Proposal.md
(roadmap, team assignment/KPI, anti-patterns, function migration map).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>