Tasks: Docs SEO Metadata Audit and Enforcement

Mission: docs-seo-metadata-enforcement-01KZ9PJ2 Branch: planning base and merge target are both feat/docs-seo-metadata-enforcement Generated: 2026-08-05 Inputs: spec.md, plan.md, research.md, data-model.md, contracts/, quickstart.md


Overview

8 work packages, 41 subtasks. Six of the eight have no dependencies and can start immediately — the mission is unusually parallelizable because the bulk content work (3 packages, 147 descriptions) is fully decoupled from the pipeline code.

WPTitleSubtasksDepsEst. linesConcern
WP01Published-page-set resolver6~430IC-01
WP02ADR descriptions — 1.x and 2.x (51 pages)5~330IC-04
WP03ADR descriptions — 3.x early (48 pages)5~320IC-04
WP04ADR descriptions — 3.x late (48 pages)5~340IC-04
WP05Render emission and built-output verifier7~480IC-03
WP06Source metadata gate hardening6WP01–WP04~420IC-02
WP07Internal link equity for high-intent pages4~260IC-05
WP08CI trigger scoping3~210IC-06

Critical path: WP01 + (WP02 ∥ WP03 ∥ WP04) → WP06. Everything else is off the critical path.

The one hard ordering constraint: WP06 removes docs/adr/ from the description gate's exclusion list. If it lands before WP02–WP04 complete, CI goes red for 147 files. This is encoded as a real dependency, not a comment.


Subtask Index

Reference table only — not a tracking surface. Progress is tracked by the checkbox rows under each work package below.

IDDescriptionWPParallel
T001Establish and record the green baselineWP01
T002Define PublishedPageSet and Exclusion value objectsWP01
T003Implement resolve_published_pages reading docfx.jsonWP01
T004Translate DocFX glob semantics with membership validationWP01
T005Add fail-closed error paths, non-vacuity floor, enumerated exclusionsWP01
T006Write the resolver test suite including the regression proofWP01
T007Survey the 1.x/2.x batch and build an authoring worklistWP02[P]
T008Author descriptions for docs/adr/1.x (13 pages)WP02[P]
T009Author descriptions for docs/adr/2.x (38 pages)WP02[P]
T010Self-check length band and intra-batch uniquenessWP02[P]
T011Run terminology guard and confirm no lockfile driftWP02[P]
T012Survey the 3.x early batch (2026-03 … 2026-05)WP03[P]
T013Author descriptions for 2026-04 (33 pages)WP03[P]
T014Author descriptions for 2026-03 and 2026-05 (15 pages)WP03[P]
T015Self-check length band and intra-batch uniquenessWP03[P]
T016Run terminology guard and confirm no lockfile driftWP03[P]
T017Survey the 3.x late batch (2026-06 … 2026-08 + README)WP04[P]
T018Author descriptions for 2026-06 (22 pages)WP04[P]
T019Author descriptions for 2026-07 and 2026-08 (25 pages)WP04[P]
T020Add title and description frontmatter to 3.x/README.mdWP04[P]
T021Self-check, terminology guard, and lockfile drift checkWP04[P]
T022Emit <meta name="description"> from seo_postprocess.pyWP05[P]
T023Create seo_verify.py with classification reusing should_indexWP05[P]
T024Implement rendered-page rules V-06 … V-10WP05[P]
T025Enforce stub, sitemap, and read-only invariantsWP05[P]
T026Emit the audit record including the stale-URL findingWP05[P]
T027Wire the verifier into docs-pages.yml as the last stepWP05[P]
T028Write the verifier and post-processor test suiteWP05[P]
T029Consume the resolver in description_length_check.pyWP06
T030Retire the ADR exclusion and correct its stale rationaleWP06
T031Add boilerplate detection pinned to the render-side constantWP06
T032Add duplicate detection that names the colliding peerWP06
T033Add the non-vacuity coverage assertionWP06
T034Repoint test_docs_seo.py and extend the boundary proofsWP06
T035Add one-click links from the documentation homeWP07[P]
T036Add cross-links from topically relevant guidesWP07[P]
T037Satisfy the relative-link and related-edge validatorsWP07[P]
T038Verify click depth against the built siteWP07[P]
T039Add a paths: filter covering every gate inputWP08[P]
T040Verify the required-check safety precondition still holdsWP08[P]
T041Record the filter's input-set invariantWP08[P]

WP01 — Published-page-set resolver

Prompt: tasks/WP01-published-page-set-resolver.md

Goal: Make "which pages are published" answerable from exactly one authority — docs/docfx.json — so a directory move can never again leave a gate guarding an empty tree.

Priority: P0. This is the mission's root-cause fix and the foundation WP06 consumes.

Independent test: A page set built from the retired pre-move glob list fails the floor assertion; a page set built from docfx.json resolves ~674 pages including three named canary pages.

Included subtasks

  • ✅ T001 Establish and record the green baseline (WP01)
  • ✅ T002 Define PublishedPageSet and Exclusion value objects (WP01)
  • ✅ T003 Implement resolve_published_pages reading docfx.json (WP01)
  • ✅ T004 Translate DocFX glob semantics with membership validation (WP01)
  • ✅ T005 Add fail-closed error paths, non-vacuity floor, enumerated exclusions (WP01)
  • ✅ T006 Write the resolver test suite including the regression proof (WP01)

Implementation sketch: Baseline first (T001 — non-negotiable, planning never confirmed it). Then value objects, then the reader, then the glob translation, then the guards, then tests. Tests last only because the guards define what they assert; the regression proof in T006 is the acceptance gate.

Parallel opportunities: None internally — the subtasks are a straight dependency chain. WP02–WP05, WP07, WP08 all run alongside it.

Dependencies: None.

Risks: DocFX glob semantics differ from pathlib semantics. Getting this wrong silently under-collects — the exact bug being repaired, in a new location. Mitigation is empirical membership assertions, never reasoning about glob translation.


WP02 — ADR descriptions: 1.x and 2.x (51 pages)

Prompt: tasks/WP02-adr-descriptions-1x-2x.md

Goal: Give each of the 51 pages under docs/adr/1.x/ and docs/adr/2.x/ a unique, hand-authored description in the 50–180 character band.

Priority: P1. Off the critical path only in the sense that it does not block WP01 — it does block WP06.

Independent test: Every file in the two era directories has a description in band, no two descriptions are identical, and the terminology guard passes.

Included subtasks

  • ✅ T007 Survey the 1.x/2.x batch and build an authoring worklist (WP02)
  • ✅ T008 Author descriptions for docs/adr/1.x (13 pages) (WP02)
  • ✅ T009 Author descriptions for docs/adr/2.x (38 pages) (WP02)
  • ✅ T010 Self-check length band and intra-batch uniqueness (WP02)
  • ✅ T011 Run terminology guard and confirm no lockfile drift (WP02)

Implementation sketch: Read each ADR's Context and Decision sections, write a description that says what the decision was rather than what the document is. Verify band and uniqueness mechanically before finishing.

Parallel opportunities: Fully parallel with WP03 and WP04 — the three own disjoint file sets by construction.

Dependencies: None.

Risks: The 50–180 band is narrow and uniqueness is enforced; near-identical ADRs need genuinely distinguishing text. docs/ is in the terminology guard's scan roots, unlike kitty-specs/.


WP03 — ADR descriptions: 3.x early (48 pages)

Prompt: tasks/WP03-adr-descriptions-3x-early.md

Goal: Author descriptions for the 48 docs/adr/3.x/ pages dated 2026-03 through 2026-05.

Priority: P1.

Independent test: All 48 dated files in range carry an in-band description; no duplicates within the batch.

Included subtasks

  • ✅ T012 Survey the 3.x early batch (2026-03 … 2026-05) (WP03)
  • ✅ T013 Author descriptions for 2026-04 (33 pages) (WP03)
  • ✅ T014 Author descriptions for 2026-03 and 2026-05 (15 pages) (WP03)
  • ✅ T015 Self-check length band and intra-batch uniqueness (WP03)
  • ✅ T016 Run terminology guard and confirm no lockfile drift (WP03)

Implementation sketch: 2026-04 is the dense month (33 of 48) and is its own subtask for that reason. Same authoring standard as WP02.

Parallel opportunities: Fully parallel with WP02 and WP04.

Dependencies: None.

Risks: 2026-04 contains several closely-related ADRs (skill installation, shim generation, charter handoff) whose descriptions will collide unless deliberately differentiated.


WP04 — ADR descriptions: 3.x late (48 pages)

Prompt: tasks/WP04-adr-descriptions-3x-late.md

Goal: Author descriptions for the 47 docs/adr/3.x/ pages dated 2026-06 through 2026-08, plus frontmatter for docs/adr/3.x/README.md.

Priority: P1.

Independent test: All dated files in range carry an in-band description; the README carries title and description and no other frontmatter keys.

Included subtasks

  • ✅ T017 Survey the 3.x late batch (2026-06 … 2026-08 + README) (WP04)
  • ✅ T018 Author descriptions for 2026-06 (22 pages) (WP04)
  • ✅ T019 Author descriptions for 2026-07 and 2026-08 (25 pages) (WP04)
  • ✅ T020 Add title and description frontmatter to 3.x/README.md (WP04)
  • ✅ T021 Self-check, terminology guard, and lockfile drift check (WP04)

Implementation sketch: As WP02/WP03, plus the README special case in T020.

Parallel opportunities: Fully parallel with WP02 and WP03.

Dependencies: None.

Risks: T020 is a live trap. 3.x/README.md currently has no frontmatter. Adding tag, divio_type, or owning_workstream will drift the page-inventory lockfile and trip INVENTORY-LOCKFILE-DRIFT. Add title and description only.


WP05 — Render emission and built-output verifier

Prompt: tasks/WP05-render-emission-and-verifier.md

Goal: Make the render actually emit a description tag, and add a verifier that proves what ships carries correct metadata — the only layer that can catch a render-path defect.

Priority: P0. This closes the defect class that source-level checks structurally cannot see.

Independent test: A synthetic _site fixture with a missing description tag fails seo_verify.py --strict; a compliant fixture passes; the post-processor is idempotent across two runs.

Included subtasks

  • □ T022 Emit <meta name="description"> from seo_postprocess.py (WP05)
  • □ T023 Create seo_verify.py with classification reusing should_index (WP05)
  • □ T024 Implement rendered-page rules V-06 … V-10 (WP05)
  • □ T025 Enforce stub, sitemap, and read-only invariants (WP05)
  • □ T026 Emit the audit record including the stale-URL finding (WP05)
  • □ T027 Wire the verifier into docs-pages.yml as the last step (WP05)
  • □ T028 Write the verifier and post-processor test suite (WP05)

Implementation sketch: Fix the emission first so the verifier has something to pass against, then build the verifier bottom-up, then wire it in. All tests use synthetic _site fixtures under tmp_path, so none of this needs a real DocFX build.

Parallel opportunities: The whole WP is parallel with everything else.

Dependencies: None.

> Correction to plan.md: plan.md lists IC-03 as depending on IC-01 "for the indexable-page definition". On closer reading of the contract that is wrong — contracts/built-output-verifier.md C-B4 requires reuse of seo_postprocess.should_index(), which is render-side and already exists. The resolver is source-side and is not needed here. WP05 is therefore dependency-free, which improves parallelism.

Risks: Step ordering in docs-pages.yml is load-bearing and already subtle. The verifier must run after stub generation so it can confirm stubs are correctly excluded, but must never treat a stub as indexable.


WP06 — Source metadata gate hardening

Prompt: tasks/WP06-source-metadata-gate.md

Goal: Make a missing, boilerplate, or duplicated description fail at PR time across the whole published tree instead of 2.4% of it.

Priority: P0. This is the gate that stops the defect recurring.

Independent test: The gate goes red on a missing description, a 49-character description, a 181-character description, the boilerplate string, a duplicate pair, and an empty page set — six distinct proofs that it can fail.

Included subtasks

  • □ T029 Consume the resolver in description_length_check.py (WP06)
  • □ T030 Retire the ADR exclusion and correct its stale rationale (WP06)
  • □ T031 Add boilerplate detection pinned to the render-side constant (WP06)
  • □ T032 Add duplicate detection that names the colliding peer (WP06)
  • □ T033 Add the non-vacuity coverage assertion (WP06)
  • □ T034 Repoint test_docs_seo.py and extend the boundary proofs (WP06)

Implementation sketch: Swap the page source first, then the new rules, then flip the exclusion last — the flip is the change that turns CI red if WP02–WP04 have not landed, so it wants to be the final, obvious step.

Parallel opportunities: None internally.

Dependencies: WP01 (needs the resolver), WP02, WP03, WP04 (T030 turns CI red for 147 files without them).

Risks: T030 is the sequencing hazard of the whole mission. Also: switching from rglob(".md") to the resolver may reduce* the checked count, because unpublished trees leave scope. That reduction is legitimate and must not be mistaken for the under-collection the floor assertion guards.


Prompt: tasks/WP07-internal-link-equity.md

Goal: Put the install guide and the slash-command reference one click from where readers actually land, without disturbing the navigation shape a prior mission deliberately chose.

Priority: P2.

Independent test: Both pages are reachable by a single link from docs/index.md; docs/toc.yml is unchanged; the relative-link and related-edge validators pass.

Included subtasks

  • ✅ T035 Add one-click links from the documentation home (WP07)
  • ✅ T036 Add cross-links from topically relevant guides (WP07)
  • ✅ T037 Satisfy the relative-link and related-edge validators (WP07)
  • ✅ T038 Verify click depth against the built site (WP07)

Implementation sketch: Edit docs/index.md to link both pages directly, add reciprocal cross-links from related guides, then run the two link validators.

Parallel opportunities: Fully parallel with everything.

Dependencies: None.

Risks: docs/toc.yml documents a "2 zones, ≤6 top-level entries" shape and zone 1 is already at exactly 6. Per decision 01KZ9Q2E397AB1WJYZMAP0A0VB that file is not to be modified. Note the constraint is prose, not an automated gate — nothing will stop a careless edit.


WP08 — CI trigger scoping

Prompt: tasks/WP08-ci-trigger-scoping.md

Goal: Stop docs-freshness running on pull requests that touch no documentation surface.

Priority: P2.

Independent test: The workflow declares a paths: filter that includes every path the four gates read; a PR touching only unrelated source does not trigger it.

Included subtasks

  • ✅ T039 Add a paths: filter covering every gate input (WP08)
  • ✅ T040 Verify the required-check safety precondition still holds (WP08)
  • ✅ T041 Record the filter's input-set invariant (WP08)

Implementation sketch: Add the filter, re-verify branch protection, and leave a comment explaining why the input set is what it is.

Parallel opportunities: Fully parallel with everything.

Dependencies: None.

Risks: A filter narrower than the gates' true input set silently stops guarding real changes — the same failure shape this mission exists to fix. Must include scripts/docs/ and the two packs/built-in/ paths, not just docs/.


MVP scope

WP01 alone is the meaningful minimum. It converts the silent-coverage-collapse failure from possible to structurally unrepresentable. Even without any description backfill, landing WP01 plus WP06's coverage assertion means the next directory move fails loudly instead of quietly.

Recommended first slice if the mission must be cut: WP01 + WP05. Together they fix both root causes (two authorities for the page set; a render that drops metadata) without requiring the 147-page authoring effort.


Parallelization

Six of eight packages have no dependencies. A realistic lane allocation:

  • Lane A: WP01 → WP06 (the critical path)
  • Lane B: WP02 → WP07
  • Lane C: WP03 → WP08
  • Lane D: WP04
  • Lane E: WP05

Wall-clock is bounded by Lane A, which cannot finish until all of WP02–WP04 land. If the ADR authoring is the long pole, adding lanes to split WP02–WP04 further is the correct lever.