Titles containing ' | ' (e.g. "Neo, Now in the Terminal | Pulumi Blog")
broke the Markdown table renderer because the unescaped pipe was parsed
as a column separator.
Root cause: v2_optimizer.py escaped pipes in the 'why' column but not
in the link title — one-line fix adds `.replace("|", r"\|")` to `t`.
Also corrects the 4 already-rendered broken entries in tech-digest.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The rebase loop never attempted a push after self-healing; it looped
back to `git pull --rebase` and hit a fresh conflict every iteration
(5x), then the final bare push failed non-fast-forward.
Root cause: in a rebase `--ours` = remote side (not our bot commit),
so the README was discarded and rebuilt each round while the remote
kept advancing.
Fix:
- Pull latest *before* committing (shrinks conflict window)
- Retry loop now tries push first, then merge-pulls on rejection
- Uses `-X ours` merge (correct semantics: keep bot's README)
- Re-runs updater after each merge so metrics stay consistent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reconstructed the missing 2.6.0-2.9.1 entries from GitHub Release notes,
closing the gap where CHANGELOG.md had stalled at 2.5.8 while releases
reached 2.9.x. Changelog is now complete and continuous through 2.9.9.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The in-repo CHANGELOG had drifted (last entry 2.5.8) while GitHub Releases
went to 2.9.7. Backfill the 2.9.2-2.9.8 entries I have full detail on and
flag the 2.6.0-2.9.1 gap. 2.9.8 also covers the existing gh_pushed
'N/A'->null data cleanup (PR #385).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the v2.9.4 source fix by cleaning the existing data. last_checked
is already INTEGER (applied by a pipeline run after v2.9.6), so this diff is
limited to the 281 gh_pushed values. Deterministic / CI-reproducible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The four logo hero cards use a 100px <img>; the Intelligence Digest card
uses an emoji <div class=hero-badge-icon> that was only ~32px tall, so it
rendered shorter. Give .hero-badge-icon a 100px flex-centered box (and
matching 12px bottom margin) so all five cards are the same height.
Cache-bust bumped to ?v=2.9.7.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
last_checked stored epoch seconds as REAL; SQLite's version-specific
REAL->text float printing rewrote ~every row on dump, so a locally
regenerated inventory.sql diverged from CI's. Normalize last_checked to
int on the entry before both the SQL record and the YAML dump, and make
the column INTEGER. All readers do coarse day-level staleness checks, so
sub-second precision is unused.
Verified: load->save idempotent for inventory.sql AND inventory.yaml, and
local (SQLite 3.40.1) output byte-identical to CI-equivalent python:3.11
(SQLite 3.46.1). The deterministic data regenerates on the next pipeline
save; committing only the code avoids conflicting with pipeline data churn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
load_inventory_channels() selected entries via 'youtube_mosaic' in entry
(key presence). save_inventory() always writes a youtube_mosaic column
that the SQL round-trip materializes as an empty dict {} on EVERY entry,
so the filter matched all 18,647 inventory entries and emitted ~18.6k
inline <img> links on a single line. That produced a 4MB v2-docs/index.md
(and 2.9MB docs/index.md), hanging both V1 and V2 index pages in the
browser. A prior revert masked it; the next SQL-backed sync re-broke it.
Fix: only treat an entry as a mosaic channel when youtube_mosaic is a
non-empty dict with a logo image (136 curated channels). Regenerated both
index files via reorganize_mosaic — docs/index.md is now byte-identical
to the last known-good state (52KB); v2-docs/index.md back to 41KB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The date field gh_pushed was written as the literal string 'N/A' when
GitHub data was unavailable (fast_enrich, gemini_utils). That non-date
string then reached datetime.fromisoformat() downstream. Now writes None
(which all readers already handle); gh_license keeps its 'N/A' string
sentinel since it is a displayed value, not a date.
Also hardens the twin fromisoformat() call in agentic_curator's MVQ
penalty check (guards N/A/None and wraps parsing) so a bad value can't
crash a curation batch.
Adds scripts/normalize_gh_pushed.py to clean the 281 existing inventory
entries (run in CI to avoid SQLite float-serialization churn — see
script docstring).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>