refactor(project): create src/project.py with ProjectContext + 5 sub + config IO (split from models.py)
Per the 4-criteria decision rule (C1=cross-system, C3=tests, C4=size);
ProjectContext is the typed return of project_manager.flat_config();
the 5 sub-dataclasses model the actual nested dict structure of
flat_config()'s return; load_config_from_disk / save_config_to_disk
are the canonical config I/O primitives (renamed from the private
_load_config_from_disk / _save_config_to_disk).
This commit:
1. Creates src/project.py with ProjectContext + 5 sub (ProjectMeta,
ProjectOutput, ProjectFiles, ProjectScreenshots, ProjectDiscussion)
+ EMPTY_PROJECT_CONTEXT + _clean_nones + load_config_from_disk +
save_config_to_disk + parse_history_entries.
2. Removes the original class + function definitions from src/models.py.
3. Adds backward-compat re-exports in src/models.py (the same pattern
used by Phase 3a mma.py and Phase 3g personas.py).
4. Updates src/app_controller.py to use the new public function names
(load_config_from_disk / save_config_to_disk).
5. Updates tests/test_models_no_top_level_tomli_w.py to use the new
public name (the test still asserts lazy-loading; the lazy load
happens in the new project.py module).
6. Updates scripts/audit_no_models_config_io.py FORBIDDEN_PATTERNS to
reference the new public names (models.load_config_from_disk /
models.save_config_to_disk) + the new src.project path.
Verification: VC6
uv run python -c 'from src.project import ProjectContext, ProjectMeta,
ProjectOutput, ProjectFiles, ProjectScreenshots, ProjectDiscussion,
_clean_nones, load_config_from_disk, save_config_to_disk,
parse_history_entries' # OK
uv run python -c 'from src.models import ProjectContext, ...' # OK
(re-exports work)
Pre-existing test regression (NOT caused by this commit):
tests/test_models_no_top_level_tomli_w.py::test_models_does_not_import_tomli_w_at_module_level
was already failing because the Phase 3g 'from src.personas import Persona'
re-export in src/models.py loads src.personas at module level, which
loads tomli_w. The Phase 5 reduce-models.py pass moves the persona
import into __getattr__ (lazy), which will make this test pass again.
Tests verified: tests/test_project_context_20260627.py (10/10 PASS),
tests/test_project_serialization.py (2/2 PASS), tests/test_thinking_persistence.py
(4/4 PASS), tests/test_presets.py (3/3 PASS), tests/test_persona_models.py
(2/2 PASS), tests/test_ticket_queue.py (PASS), tests/test_dag_engine.py
(PASS), tests/test_orchestration_logic.py (PASS).
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
"""Audit script: ensure no production code in src/ calls the models I/O primitives directly.
|
||||
|
||||
Architecture rule: AppController owns the config I/O. The
|
||||
models._load_config_from_disk and models._save_config_to_disk
|
||||
functions are private file I/O primitives. Direct callers in src/
|
||||
are an architectural smell (bypassing the controller state owner).
|
||||
models.load_config_from_disk and models.save_config_to_disk
|
||||
functions (formerly _load_config_from_disk and _save_config_to_disk)
|
||||
are private file I/O primitives. Direct callers in src/ are an
|
||||
architectural smell (bypassing the controller state owner). After
|
||||
module_taxonomy_refactor_20260627 Phase 3b, they live in src/project.py
|
||||
and are re-exported by src/models.py for backward compat. The same
|
||||
audit rule still applies: only AppController should call them.
|
||||
|
||||
The only allowed call sites are inside AppController itself.
|
||||
|
||||
@@ -22,13 +26,24 @@ from pathlib import Path
|
||||
|
||||
# Patterns that are architectural smells in production code.
|
||||
# These are the I/O primitives; only AppController should call them.
|
||||
# Post-Phase 3b the names are public (load_config_from_disk /
|
||||
# save_config_to_disk) but the architectural rule is unchanged.
|
||||
FORBIDDEN_PATTERNS = [
|
||||
(re.compile(r"\bmodels\.load_config_from_disk\s*\("), "models.load_config_from_disk"),
|
||||
(re.compile(r"\bmodels\.save_config_to_disk\s*\("), "models.save_config_to_disk"),
|
||||
(re.compile(r"\bsrc\.project\.load_config_from_disk\s*\("), "src.project.load_config_from_disk"),
|
||||
(re.compile(r"\bsrc\.project\.save_config_to_disk\s*\("), "src.project.save_config_to_disk"),
|
||||
]
|
||||
|
||||
# The OLD private names. After Phase 3b the private names are GONE;
|
||||
# these patterns are kept to detect any stale call site.
|
||||
LEGACY_PRIVATE_NAMES = [
|
||||
(re.compile(r"\bmodels\._load_config_from_disk\s*\("), "models._load_config_from_disk"),
|
||||
(re.compile(r"\bmodels\._save_config_to_disk\s*\("), "models._save_config_to_disk"),
|
||||
]
|
||||
|
||||
# The OLD public names. After the rename these should not exist anywhere.
|
||||
LEGACY_NAMES = [
|
||||
LEGACY_PUBLIC_NAMES = [
|
||||
(re.compile(r"\bmodels\.load_config\s*\("), "models.load_config"),
|
||||
(re.compile(r"\bmodels\.save_config\s*\("), "models.save_config"),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user