2f277dfc66
Two real bugs flagged in the automated review of PR #400; both small. 1. render.py::_skill_version manifest with no "version" key `json.loads(manifest.read_text()).get("version", "?")` returned "?" immediately on a valid JSON manifest that lacked the "version" key, never falling through to the SKILL.md frontmatter fallback. Contradicted the docstring's "Returns '?' only if both sources are missing" contract. Same shape if version is present but empty string ("" produces the broken badge `🌐 last30days v · synced ...`). Fix: pull the version out of the parsed dict, then `continue` to the next ancestor if it's None or empty. Falls through to the SKILL.md walk only after exhausting every ancestor. 2. SKILL.md STEP 0 re-read target hardcoded to nested cache layout STEP 0 told the model to re-read from `$CLAUDE_CACHE_LATEST/skills/last30days/SKILL.md` — the new nested layout. But Step 1's resolver explicitly handles both shapes (nested `{cache}/{version}/skills/last30days/` and flat `{cache}/{version}/`), noting "Both shapes ship in the wild." On an install where the highest-versioned cache happens to be the older flat shape, STEP 0's re-read target wouldn't exist; the model would silently stay on the stale marketplaces/ copy STEP 0 was supposed to move it away from — the exact failure mode this guard was added to prevent. Fix: extend the STEP 0 bash to resolve $CLAUDE_CACHE_SKILL_MD by probing both layouts, then have the model hop to that resolved path instead of constructing the path from a hardcoded suffix. Two new tests in tests/test_skill_version.py cover the missing-key and empty-string cases for fix 1. Fix 2 is exercised via the bash probe at verify time (the STEP 0 prose-contract test isn't unit-testable from Python, but the dual-layout bash is verified to resolve to the correct SKILL.md on both shapes). Stale finding skipped: greptile also flagged a missing try/except on the SKILL.md read_text() call, but that was already addressed during the ce-code-review safe_auto pass earlier in this PR — current code wraps it in `try/except (OSError, UnicodeDecodeError)`, strictly more defensive than the suggested fix.