LearnNewsExamplesServices
Frontmatter
id12259
titleMake sitemap lastmod regeneration diff-clean
stateClosed
labels
documentationenhancementaibuild
assigneesneo-gpt
createdAtMay 31, 2026, 4:12 PM
updatedAtMay 31, 2026, 6:14 PM
githubUrlhttps://github.com/neomjs/neo/issues/12259
authorneo-gpt
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 31, 2026, 6:14 PM

Make sitemap lastmod regeneration diff-clean

Closed v13.0.0/archive-v13-0-0-chunk-15 documentationenhancementaibuild
neo-gpt
neo-gpt commented on May 31, 2026, 4:12 PM

Context

Follow-up from PR #12258's cross-family review. While implementing #12255, the source-side SEO priority fix was straightforward: buildScripts/docs/seo/generate.mjs#PRIORITIES now maps the current identity / Benefits / Agent OS / cloud-deployment routes, and apps/portal/sitemap.xml carries the matching priority lines.

The friction came from the generated output step. Running the full sitemap generator from the #12255 branch completed successfully, but it produced unrelated <lastmod> churn across the entire sitemap. To keep #12258 reviewable and scoped, the PR preserved existing lastmod values and applied only the priority-line deltas. Claude's review approved that as non-blocking, but correctly surfaced the root generator behavior as follow-up debt.

Creation V-B-A:

  • buildScripts/docs/seo/generate.mjs#getSitemapXml() calls getGitLastModifiedBatch() and emits <lastmod> for every route with a file path.
  • Full generator execution during #12255 changed thousands of sitemap lines unrelated to the priority-map edit, mostly <lastmod> values.
  • The committed #12258 diff had to normalize apps/portal/sitemap.xml back to priority-only changes to avoid hiding a small SEO-priority fix in all-route timestamp churn.
  • Duplicate sweep: Knowledge Base unavailable (Failed to access collections); exact rg found closed #7742 (initial git-history <lastmod> support) and closed #7770 (example-directory lastmod accuracy), but no active ticket for diff-clean regeneration / no unrelated lastmod churn. Live gh issue list --search "sitemap lastmod getSitemapXml regeneration churn priority-only" returned [].

The Problem

Any future SEO-generator PR can hit the same trap: a small intended sitemap change becomes a noisy all-route <lastmod> diff, or the author must manually normalize generated output after the generator runs. That undermines the generated-output discipline ADR 0018 relies on: agents should be able to update the source generator, regenerate output, and review the resulting diff without a post-generation workaround.

The exact root cause still needs implementation-time V-B-A. It may be one of these, or a combination:

  • the generator derives different git timestamps than the currently committed sitemap carries;
  • the data-sync pipeline stamps or preserves sitemap lastmod values differently than local generation;
  • top-level / issue / release routes need a different stable timestamp source than learn-file routes;
  • the release-time generation path and local CLI generation path disagree.

The ticket is not to pick a solution prematurely. It is to make the generator's regeneration behavior stable and reviewable.

The Architectural Reality

buildScripts/docs/seo/generate.mjs owns sitemap generation:

  • getSitemapXml() formats routes into XML and emits <lastmod> from getGitLastModifiedBatch().
  • buildScripts/release/prepare.mjs imports the same generator and writes apps/portal/sitemap.xml for release preparation.
  • apps/portal/sitemap.xml is generated output; durable fixes belong in the generator or the generation contract, not ad-hoc sitemap edits.
  • Closed #7742 and #7770 established the original accuracy goal for <lastmod>, so this follow-up must preserve accurate timestamps where possible rather than deleting <lastmod> wholesale.

The Fix

Investigate and fix the sitemap generation path so a no-op or narrow sitemap regeneration does not produce unrelated all-route <lastmod> churn.

Expected implementation shape:

  1. Reproduce the churn from a clean branch using the current generator and committed apps/portal/sitemap.xml.
  2. Identify whether the divergence is caused by git-history lookup, top-level route handling, issue/release route handling, release/data-sync behavior, or local CLI defaults.
  3. Update buildScripts/docs/seo/generate.mjs and, if needed, buildScripts/release/prepare.mjs so generated output is stable and reviewable.
  4. Preserve the SEO purpose of <lastmod>: accurate content modified dates where available, explicit stable fallback where not.
  5. Add focused validation so future priority-only or route-only generator changes can prove they do not rewrite unrelated lastmod lines.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
buildScripts/docs/seo/generate.mjs#getSitemapXml() Current generator + closed #7742/#7770 accuracy intent Emits stable, accurate <lastmod> values without unrelated regeneration churn If a route has no trustworthy source, use a documented stable fallback rather than build-time churn PR body / inline comments only if needed Reproduction before/after + focused generator check
apps/portal/sitemap.xml Generated from getSitemapXml() No-op or priority-only regeneration does not rewrite unrelated lastmod values Any unavoidable churn must be explicitly justified in PR body PR body Diff proves only intended sitemap lines changed
Release preparation path buildScripts/release/prepare.mjs Release generation uses the same stable lastmod semantics as local CLI generation Document if release intentionally differs and why PR body Command evidence or source audit

Decision Record Impact

Aligned-with ADR 0018 generated-output-source discipline. No ADR update expected unless implementation discovers the release/data-sync pipeline intentionally owns sitemap timestamps differently from generate.mjs.

Acceptance Criteria

  • Reproducer documented: from a clean branch, current sitemap generation shows the unrelated <lastmod> churn that forced the #12258 priority-only normalization.
  • getSitemapXml() produces stable <lastmod> values for unchanged routes across repeated local generation runs.
  • The fix preserves accurate git-history-derived dates for content routes where that source is valid.
  • Routes without a reliable git-history source use an explicit stable fallback, not build-time restamping across the whole sitemap.
  • apps/portal/sitemap.xml can be regenerated without manual post-processing for a narrow priority/route change.
  • Focused validation proves a no-op generation, or a narrow fixture/change, does not rewrite unrelated lastmod lines.
  • Closed #7742/#7770 behavior is not regressed: examples still use directory-level modified dates where applicable.

Out of Scope

  • Changing sitemap priority policy; #12255 / PR #12258 owns that.
  • Removing <lastmod> from the sitemap wholesale.
  • Rewriting README / identity prose.
  • Changing llms.txt unless the same timestamp contract is discovered there.
  • Release-pipeline redesign beyond the sitemap generation semantics needed here.

Avoided Traps

  • Treating #12258's manual priority-only normalization as the permanent workflow.
  • Solving review noise by deleting useful SEO metadata.
  • Reopening the closed #7742/#7770 accuracy decisions without evidence.
  • Assuming the root cause is build-time stamping before reproducing the exact generator/data-sync path.

Related

  • Follow-up from: #12258
  • Root issue: #12255
  • Parent epic: #12225
  • Historical context: #7742, #7770
  • ADR 0018: Neo Identity Source-of-Truth Model / generated-output-source discipline

Origin Session ID: 87203e0b-c9ff-4a82-a67b-9a0dda760c32 Retrieval Hint: "sitemap lastmod regeneration churn getSitemapXml priority-only normalization PR 12258"

tobiu closed this issue on May 31, 2026, 6:14 PM
tobiu referenced in commit aec7eaf - "fix(seo): stabilize sitemap lastmod generation (#12259) (#12261) on May 31, 2026, 6:14 PM