fix: PageCache::set() guards against overwriting valid content with empty not_found Prevents WAL-mode stale reads from causing batch-enhance to destroy existing valid emoji data when LLM calls fail. Before writing a not_found entry, check if valid (non-empty, status=found) content already exists — if so, skip the overwrite. Fixes the man -139 entry and info -21 entry data loss observed during the batch enhance monitoring loop. Co-Authored-By: Claude <noreply@taotoken.net>
feat: generate compressed sitemap during reindex
Docs: CSP nonce + strict-dynamic strategy (#158) + v4.9 PLANDOC entry - docs/01-PRODUCT.md: new §3.2 CSP Strategy — current state, target state, transition plan (3-phase), implementation note for nonce sharing - docs/05-PLAN.md: v4.9 CSP nonce + strict-dynamic entry Co-Authored-By: Claude Code deepseek-v4-pro <noreply@taotoken.net>
fix: --status query excludes NOT_FOUND entries from emoji counts Failed enhancements are now cached as status='not_found' to prevent retries. The --status count query must filter status='found' to avoid counting failures as completed enhancements. Co-Authored-By: Claude <noreply@taotoken.net>
fix: cache NOT_FOUND on LLM failure to prevent repeated retries Pages that fail enhancement (LLM returns empty) were not cached, causing them to be retried every batch run, wasting consecutive failure quota and killing processes prematurely. Now failed pages are cached as status='not_found', so the next run's annotation check correctly skips them. Use --rebuild to force retry cached failures. Co-Authored-By: Claude <noreply@taotoken.net>
fix: preg_match_all called before empty() check in splitHtmlIntoSections The heading regex was defined but preg_match_all was placed AFTER the first empty($matches) guard, so $matches was always undefined and the function always returned a single chunk. Also fixed stale variable references ($tag → $headingText, $positions → $matches). Co-Authored-By: Claude <noreply@taotoken.net>
fix: man page heading detection in splitHtmlIntoSections Man pages use <a id="section-NAME"><b>SECTION</b> anchors, not <h1> tags. Previous regex required only ALL_CAPS <b> content, producing 378 false positives. Fixed to require <a id="section-" or <a id="sub-" prefix. Also wired chunked path into batch-enhance.php HTML phase. Co-Authored-By: Claude <noreply@taotoken.net>
fix: wire chunked enhancement into batch-enhance.php HTML path batch-enhance.php called callLLM() directly with full raw HTML, bypassing enhanceManPageChunked(). Now pages > PHPMAN_ENHANCE_CHUNK_THRESHOLD (200KB) are automatically split and enhanced in chunks. Co-Authored-By: Claude <noreply@taotoken.net>
feat: chunked LLM enhancement for large man pages (>200KB) Pages exceeding PHPMAN_ENHANCE_CHUNK_THRESHOLD (default 200KB) are automatically split into sections at h1/h2 heading boundaries, each chunk independently enhanced via callLLM(), then reassembled. Split strategy (failsafe cascade): 1. h1 section boundaries (NAME, SYNOPSIS, DESCRIPTION, etc.) 2. h2 sub-section boundaries within oversized sections 3. Paragraph/block boundaries (</p>, <br>, </li>, </dd>, etc.) 4. Sentence boundaries ($. + ' ') — last resort, never mid-sentence Key guarantees: - Never splits inside <pre>, <table>, <dl> blocks - Parent heading preserved in every chunk for LLM context - Failed chunks fall back to original HTML (graceful degradation) - Each chunk capped at PHPMAN_ENHANCE_MAX_CHARS (32KB) Co-Authored-By: Claude <noreply@taotoken.net>
docs: add architectural paradox — data sources are already CLI tools The man/perldoc/info/ri/pydoc3 data sources already have CLI interfaces. phpMan's web layer (HTML rendering, format negotiation, emoji enhance) is redundant for LLM Agent use cases. The only irreplaceable value: TLDR cheatsheets + cross-mode FTS5 search. Co-Authored-By: Claude <noreply@taotoken.net>