Files
C.E.L_Slide_test2/.orchestrator/tmp/u4_comment.md
T

4.3 KiB

[Claude #7]

unit_executed: u4 — stale-detect short-circuit (compare index.html mtime+sha256 against last manifest entry; mark skipped_unchanged when both match)

files_changed:

  • MOD D:\ad-hoc\kei\design_agent\scripts\generate_frame_previews.py (88 → 110 lines per Measure-Object -Line; u4 net add = 22 lines, ≤ Stage 2 u4 estimate of 45)

diff_summary:

  • Added _sha256_file(path: Path) -> str (private, leading underscore) — buffered hashlib.sha256() digest of index.html content, 65 KiB chunked read. Stdlib only.
  • Added is_unchanged(row: FrameRow, last_entry: Optional[Dict[str, Any]]) -> bool — the Stage 2 u4 short-circuit predicate. Returns True iff:
    1. last_entry is not None, AND
    2. row.has_index and row.has_preview are both True, AND
    3. preview.png mtime >= index.html mtime (per Stage 2 guardrail: "skip if preview.png mtime >= index.html mtime AND content hash unchanged"), AND
    4. last_entry["index_sha256"] is non-empty, AND
    5. recomputed _sha256_file(index.html) equals last_entry["index_sha256"]. Otherwise returns False — the caller (u6) MUST re-render. OSError on stat() is caught and treated as "must re-render" (defensive against transient FS races).
  • No wiring into main() — predicate is provided in isolation per Stage 2 unit boundary. u6 will load the manifest, call is_unchanged() per row, and write back the skipped_unchanged / rendered status. This keeps u4 ≤ 45-line guardrail and lets u5 (orphan/missing categorization) and u6 (manifest writer + coverage report) layer on top without re-touching u4 surface.
  • Module-level imports: added hashlib (stdlib) and Dict, Optional to existing typing import. No new third-party deps. Selenium remains lazy (u2/u3 contract preserved).
  • Module docstring updated: (u1-u3; stale/orphan/manifest added in u4-u6)(u1-u4; orphan/manifest added in u5-u6).
  • FrameRow / discover / _build_driver / render_one / main / CLI untouched. u1/u2/u3 contracts preserved.

verification:

  • (Get-Content scripts/generate_frame_previews.py | Measure-Object -Line).Lines110 (u4 add = 22 lines, ≤ 45 guardrail).
  • python scripts/generate_frame_previews.py --dry-rundiscovered: total=33 with_index_html=20 with_preview_png=4 (unchanged from u1/u2/u3 baseline at commit 56619a0 — u4 does not alter discovery output and does not wire into main).
  • $env:PYTHONPATH = '...\scripts'; python -c "import generate_frame_previews as m; print('import_ok', hasattr(m,'is_unchanged'), hasattr(m,'_sha256_file'), hasattr(m,'render_one'), hasattr(m,'_build_driver'), hasattr(m,'discover'), hasattr(m,'main'))"import_ok True True True True True True (lazy-selenium import preserved — module imports without selenium installed).
  • Forbidden-pattern audit rg -n "^(import|from) |anthropic|openai|jinja|phase_z2|slide_measurer|1280|720|1400|900" → only stdlib imports (argparse, hashlib, sys, dataclasses, pathlib, typing). No AI/Jinja/phase_z2/slide_measurer substring; no 1280/720/1400/900 literal. RULE 7 (no hardcoding) holds.
  • Predicate behavioral smoke (.orchestrator/tmp/u4_smoke.py, gitignored temp): 6 cases — (1) None last_entry → False, (2) matching sha + png-newer-than-idx → True, (3) matching sha but stale png (older than idx) → False, (4) mismatched sha → False, (5) empty/missing index_sha256 key → False, (6) missing index.html (post-unlink rediscover) → False. All assertions pass; final line u4_predicate_ok printed.
  • Scope guard: git diff --name-only -- src/slide_measurer.py src/phase_z2_pipeline.py Front/vite.config.ts templates/phase_z2/frames tests figma_to_html_agent/blocks/_preview_manifest.jsonempty output (no tracked-file change outside scripts/generate_frame_previews.py).
  • Tracked status: scripts/generate_frame_previews.py is the only IMP-13 working-tree change; figma_to_html_agent/blocks/_preview_manifest.json is not created in u4 (correctly deferred to u6).

remaining_units: [u5, u6, u7]

follow_up_issue_candidates: none from this unit. Stage 2 already flagged IMP-04 (missing_index_html=13) and data/figma_previews/index.json parse failure as separate axes. u4 introduces no new out-of-scope dependency.

rewind_target: continue_same