msos/tools
popovskik 8ff968739a feat(cms): U3 derive article dates from content/ front-matter
Replace the hand-maintained ARTICLE_DATES registry as the source of
truth with dates read from content/ front-matter, so publishing a post
via the CMS needs no manual registry edit (plan R4).

- tools/content_index.py: load_dates() reads content/ front-matter into
  {source_folder/slug: {published|modified: iso}}. Standalone (os+yaml),
  no import cycle with seo_inject.
- tools/seo_inject.py: ARTICLE_DATES is now {**static_fallback,
  **content_dates}; content wins. The static table is kept only as a
  fallback for entries not present in content/.
- migration now preserves the published-vs-modified distinction
  (date_type), so the switch is byte-identical: content-derived dates
  equal the old static table exactly (17/17, incl. the one 'modified').

Verified: full `seo_inject --all --sitemap` + build_listings +
build_botlinks run produces ZERO changes to any site page. build_content
--check still reproduces all 51 pages.

build_botlinks.py needs no change (it scrapes built pages, which are a
pure function of content/). Listings get content-driven dates for free
via seo_inject.ARTICLE_DATES.

tools/tests/test_content_index.py: 5 tests (dates == legacy registry,
merged registry unchanged, published/modified distinction, events key on
projects/<slug>, one ISO date per entry).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-08 16:47:03 +02:00
..
__pycache__ GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00
git-hooks GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00
tests feat(cms): U3 derive article dates from content/ front-matter 2026-08-08 16:47:03 +02:00
README.md feat(cms): U3 derive article dates from content/ front-matter 2026-08-08 16:47:03 +02:00
build_blog.py Blog: add personal essay "How I coped with leaving home" (trilingual, prose) 2026-08-05 17:37:14 +02:00
build_botlinks.py Bot pages: use root-relative links so they work on any host 2026-08-05 18:18:32 +02:00
build_content.py feat(cms): U2 render content/ into detail pages (characterization-verified) 2026-08-08 16:36:11 +02:00
build_listings.py GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00
build_search_index.py feat(assistant): v2 — query-language detection, analytics, chips, auto-index 2026-08-04 01:48:39 +02:00
content_index.py feat(cms): U3 derive article dates from content/ front-matter 2026-08-08 16:47:03 +02:00
migrate_pages_to_content.py feat(cms): U3 derive article dates from content/ front-matter 2026-08-08 16:47:03 +02:00
seo_inject.py feat(cms): U3 derive article dates from content/ front-matter 2026-08-08 16:47:03 +02:00

README.md

tools/

content_index.py

Reads the content/ source tree (front-matter) as structured data. Its load_dates() builds the article date registry consumed by seo_inject.py, so adding a post no longer needs a manual ARTICLE_DATES edit — the date lives in the post's front-matter (date / date_type). See docs/content-model.md.

build_content.py

Renders the content/ source files into the blog/news/event detail pages (--check verifies the output matches the current site; --write emits pages). See docs/content-model.md and the no-code CMS plan in docs/plans/.

seo_inject.py

Injects a technical-SEO block into every language page (en|mk|si/**/index.html) and regenerates sitemap.xml + robots.txt. Safe to re-run — the block lives between <!-- SEO:START --> and <!-- SEO:END --> and is replaced in place.

The block adds: meta description (auto-extracted from the page's lead/subtitle where available), canonical, hreflang alternates (en / mk / sl / x-default), Open Graph, Twitter Card, and an Organization JSON-LD.

All absolute URLs point at the final domain https://msosorg.com (not the temporary host), so nothing needs rewriting at launch. Note: the Slovene folder is /si/ but the ISO code is sl — the script maps si → sl for hreflang/locale.

Usage

python tools/seo_inject.py            # dry run: print the block for pilot pages
python tools/seo_inject.py --all      # write the block into every page
python tools/seo_inject.py --all --sitemap   # also rebuild sitemap.xml + robots.txt

Run --all --sitemap after adding, renaming, or deleting any page so canonical tags, hreflang alternates, and the sitemap stay in sync.