---
id: 12259
title: Make sitemap lastmod regeneration diff-clean
state: CLOSED
labels:
  - documentation
  - enhancement
  - ai
  - build
assignees:
  - neo-gpt
createdAt: '2026-05-31T14:12:20Z'
updatedAt: '2026-05-31T16:14:32Z'
githubUrl: 'https://github.com/neomjs/neo/issues/12259'
author: neo-gpt
commentsCount: 1
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-31T16:14:32Z'
---
# Make sitemap lastmod regeneration diff-clean

## 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"

## Timeline

- 2026-05-31T14:12:22Z @neo-gpt added the `documentation` label
- 2026-05-31T14:12:23Z @neo-gpt added the `enhancement` label
- 2026-05-31T14:12:23Z @neo-gpt added the `ai` label
- 2026-05-31T14:12:23Z @neo-gpt added the `build` label
- 2026-05-31T14:12:38Z @neo-gpt added parent issue #12225
### @neo-gpt - 2026-05-31T14:35:55Z

**Input from GPT-5.5 (Codex Desktop):**

> ● ## Epic-topology note
> 
> During #12225 closeout, this ticket was reclassified as **non-blocking identity-maintenance tooling**, not an identity-apex surface rollout item.
> 
> Evidence:
> - The sitemap priority/content update shipped via #12258 / #12255.
> - This ticket targets `lastmod` regeneration hygiene so future generated-output diffs stay clean.
> - Peer validation from @neo-opus-ada agreed this remains valid work, but not a blocker for the #12225 surface-rollout epic.
> 
> Action: decoupling this issue from #12225's native subissue list while leaving it open for independent scheduling.

- 2026-05-31T14:36:13Z @neo-gpt removed parent issue #12225
- 2026-05-31T14:48:55Z @neo-opus-ada cross-referenced by PR #12260
- 2026-05-31T14:58:27Z @neo-gpt cross-referenced by #12225
- 2026-05-31T15:02:20Z @neo-gpt assigned to @neo-gpt
- 2026-05-31T15:17:22Z @neo-gpt cross-referenced by PR #12261
- 2026-05-31T15:41:28Z @neo-gpt referenced in commit `5e7acd1` - "fix(seo): stabilize sitemap lastmod generation (#12259)"
- 2026-05-31T16:05:17Z @neo-gpt referenced in commit `0148e31` - "fix(seo): parse renamed sitemap sources (#12259)"
- 2026-05-31T16:14:32Z @tobiu closed this issue
- 2026-05-31T16:14:32Z @tobiu referenced in commit `aec7eaf` - "fix(seo): stabilize sitemap lastmod generation (#12259) (#12261)

* fix(seo): stabilize sitemap lastmod generation (#12259)

* fix(seo): parse renamed sitemap sources (#12259)

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"

