1 line
6.5 KiB
JSON
1 line
6.5 KiB
JSON
{"body": "[Claude #5] Stage 3 code-edit Round #3 — IMP-27 K5\n\nExecuting unit: u3\n\n■ unit_executed\n- id: u3\n- summary: Delegate `block_selector.load_catalog` and `block_selector._get_block_by_id` to `src.catalog`, preserving the root-dict and catalog-injected contracts. Drop module-level `CATALOG_PATH`, `_catalog_cache`, `_catalog_mtime`, and the now-unused `yaml` / `pathlib.Path` imports.\n\n■ files_changed\n- M `D:\\ad-hoc\\kei\\design_agent\\src\\block_selector.py` (+7 / -32 net, `git diff --stat` confirms)\n- M `D:\\ad-hoc\\kei\\design_agent\\tests\\test_catalog_shared_loader.py` (+71, 4 new tests appended after the u2 block)\n\n■ diff_summary\n- `src/block_selector.py`:\n - Header import block:\n - REMOVED `from pathlib import Path` (only consumer was the deleted `CATALOG_PATH`).\n - REMOVED `import yaml` (only consumer was the deleted `load_catalog` body).\n - ADDED `from src import catalog as _catalog_mod` (single delegation entry point — mirrors the u2 wiring in `block_reference.py:30`).\n - `from typing import Any` retained (still used by `topic: dict[str, Any]` in `select_block_candidates`).\n - `from src.space_allocator import ContainerSpec, HEIGHT_COST_ORDER` retained.\n - Module-level state (formerly L22~L24):\n - REMOVED `CATALOG_PATH = Path(\"templates/catalog.yaml\")`.\n - REMOVED `_catalog_cache: dict | None = None`.\n - REMOVED `_catalog_mtime: float = 0.0`.\n - Catalog loading section (formerly L57~L83, now L51~L58):\n - `load_catalog() -> dict` shrunk to 1-line wrapper: `return _catalog_mod.load_root_catalog()`. Preserves the root-dict return contract consumed by `select_block_candidates` (L83 → L86 of the new file), `select_fallback_candidates` (post-edit L177 region), `scripts/test_phase_q.py:26` (external import `from src.block_selector import ... load_catalog`), and the `format_candidates_for_prompt` caller chain.\n - `_get_block_by_id(block_id: str, catalog: dict) -> dict | None` shrunk to 1-line wrapper: `return _catalog_mod.get_block_by_id(block_id, catalog)`. Preserves the catalog-injected signature (positional `catalog` required by the existing definition; the canonical `src.catalog.get_block_by_id` accepts a positional `catalog` so the wrapper is byte-equivalent at call sites).\n - Section header comment updated: `catalog.yaml 로딩 (mtime 캐시)` → `catalog.yaml 로딩 (IMP-27: src.catalog 공유 로더 위임)` so future readers locate the delegation, not a now-deleted local cache.\n- `tests/test_catalog_shared_loader.py` (4 new tests appended after the u2 block, lines 237~303):\n - `test_block_selector_load_catalog_returns_root_dict` — verifies the root-dict contract (`isinstance(root, dict)`, `\"blocks\" in root`, `isinstance(root[\"blocks\"], list)`) is preserved through the wrapper. This is the contract that `select_block_candidates(catalog=None)` and `scripts/test_phase_q.py:26` rely on.\n - `test_block_selector_get_block_by_id_catalog_injected_signature` — verifies the catalog-injected form still resolves an id from a caller-supplied dict and returns `None` for unknown ids (asserts `block_selector._get_block_by_id(block_id, catalog_dict)` still accepts positional catalog).\n - `test_block_selector_shares_cache_with_shared_loader` — uses `monkeypatch` on `catalog_mod.yaml.safe_load` to count file reads; asserts that `block_selector.load_catalog()` + `block_selector._get_block_by_id(...)` + `catalog.load_root_catalog()` together produce exactly 1 file read. This is the regression guard for \"no private `_catalog_cache` in `block_selector`\".\n - `test_block_selector_has_no_private_catalog_cache` — explicit `hasattr` guards against re-introduction of `_catalog_cache`, `_catalog_mtime`, or `CATALOG_PATH` on the `block_selector` module.\n\n■ scope-lock verification\n- u3 edits only `src/block_selector.py` + test file. No edits to `src/renderer.py` (u4) or any caller-side file (`src/kei_client.py:454`, `scripts/test_phase_q.py:26`).\n- `git diff --name-only -- src/ tests/` lists only `src/block_reference.py` (u2 carry-over from Round #2; unchanged this round) and `src/block_selector.py`. `tests/test_catalog_shared_loader.py` and `src/catalog.py` remain untracked (u1 carry-over). No spurious file in scope.\n- catalog.yaml schema/path frozen — `src.catalog.CATALOG_PATH` is the single source of truth and is unchanged.\n- No runtime AI path touched. Phase Z 22-step pipeline untouched. AI isolation contract preserved (no AI call in load path).\n- Public surface verification: `python -c \"from src.block_selector import load_catalog, _get_block_by_id; import inspect; print(inspect.signature(load_catalog), inspect.signature(_get_block_by_id))\"` → `() -> 'dict'` and `(block_id: 'str', catalog: 'dict') -> 'dict | None'` — byte-identical to the pre-IMP-27 signatures.\n- Caller-side audit: `rg -n \"block_selector\\\\.(CATALOG_PATH|_catalog_cache|_catalog_mtime)|from src\\\\.block_selector import.*(CATALOG_PATH|_catalog_cache|_catalog_mtime)\" --include=\"*.py\" .` → only the new u3 guard test lines (303). No other code in the repo depends on the removed symbols, so no caller-side update is required.\n\n■ verification\n- `python -m pytest tests/test_catalog_shared_loader.py -q` → **18 passed in 0.26s** (10 u1 tests + 4 u2 tests + 4 u3 tests).\n- `python -c \"from src.block_selector import load_catalog, _get_block_by_id, select_block_candidates, format_candidates_for_prompt\"` → resolves; full public surface importable.\n- `git diff --stat -- src/block_selector.py` → `1 file changed, 7 insertions(+), 32 deletions(-)`. Net delta consistent with \"delegate + drop local cache\" — no scope creep.\n- `git status --short -- src/block_selector.py tests/test_catalog_shared_loader.py src/catalog.py` → `M src/block_selector.py`, `?? tests/test_catalog_shared_loader.py`, `?? src/catalog.py`. Consistent with cumulative state across u1+u2+u3.\n\n■ remaining_units\n- u4 — Refactor `renderer._load_catalog_map` and `_load_catalog_map_with_variants` to consume the shared root catalog while keeping renderer-local projection caches keyed off `src.catalog.get_catalog_mtime()`.\n\n■ follow_up_issue_candidates\n- None new this round. F-1 from Round #1 (`templates/catalog.yaml` was deleted in cc2f434 during a legacy block library cleanup; the loader contract still exists for any remaining Phase Q call sites but no production catalog file is present) remains informational — outside IMP-27 K5 scope; surface separately if any Phase Q runtime path actually needs a populated catalog.\n\nscope-lock retained. Awaiting Codex u3 review before proceeding to u4.\n"} |