Skip to content

Validate documentation snapshots with mdast JSON#43

Closed
lodyai[bot] wants to merge 4 commits into
mainfrom
mdast-snapshot-assertions
Closed

Validate documentation snapshots with mdast JSON#43
lodyai[bot] wants to merge 4 commits into
mainfrom
mdast-snapshot-assertions

Conversation

@lodyai

@lodyai lodyai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Implement normalized mdast JSON documentation snapshots for both the parent Copier template tests and the generated Rust projects.

  • Add parent pytest coverage that parses rendered Markdown documentation with Python mdast, removes source-position metadata, normalizes soft text wrapping, and snapshots semantic JSON with syrupy.
  • Add generated Rust tests that parse documentation with markdown-rs, normalize through serde_json, and compare insta JSON snapshots.
  • Keep repository-layout snapshots flavour-specific so app-only paths such as .github/workflows/release.yml and src/main.rs remain part of the contract.
  • Document the parent and generated-project snapshot workflows.

Review Walkthrough

  • Start with docs/execplans/mdast-snapshot-assertions.md for scope, decisions, red/green evidence, validation logs, and outcomes.
  • Review tests/helpers/markdown_semantics.py and tests/test_template/test_documentation_snapshots.py for the parent semantic snapshot contract.
  • Review template/tests/documentation_snapshots.rs and template/tests/snapshots/*.snap.jinja for generated-project snapshot behaviour.

Validation

  • make test 2>&1 | tee /tmp/test-agent-template-rust-mdast-snapshot-assertions-full.out: 52 passed, 1 skipped.
  • coderabbit review --agent after the parent snapshot milestone: 0 findings.
  • coderabbit review --agent after the generated snapshot milestone: 0 findings.

References

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 1 minute

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 564b06b0-4241-4ae8-90ad-edd7da8228eb

📥 Commits

Reviewing files that changed from the base of the PR and between 7d7eb00 and 8910cf7.

📒 Files selected for processing (13)
  • Makefile
  • docs/developers-guide.md
  • docs/execplans/mdast-snapshot-assertions.md
  • template/Cargo.toml.jinja
  • template/docs/developers-guide.md.jinja
  • template/tests/documentation_snapshots.rs
  • template/tests/snapshots/documentation_snapshots__docs_contents.snap.jinja
  • template/tests/snapshots/documentation_snapshots__docs_repository_layout_app.snap.jinja
  • template/tests/snapshots/documentation_snapshots__docs_repository_layout_lib.snap.jinja
  • tests/helpers/markdown_semantics.py
  • tests/test_helpers.py
  • tests/test_template/__snapshots__/test_documentation_snapshots.ambr
  • tests/test_template/test_documentation_snapshots.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mdast-snapshot-assertions

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a detailed ExecPlan document outlining how to migrate documentation snapshot tests to use normalized mdast JSON semantics instead of raw Markdown formatting, including constraints, risks, implementation steps, and validation strategy for both the parent template and generated Rust projects.

Sequence diagram for semantic mdast JSON snapshot test flow

sequenceDiagram
  actor Developer
  participant Makefile
  participant Pytest as pytest
  participant MdastHelper as parse_markdown_semantics
  participant MarkdownParser as parse_markdown
  participant Syrupy as syrupy

  Developer->>Makefile: make test
  Makefile->>Pytest: pytest tests/test_template -k snapshot
  Pytest->>MdastHelper: parse_markdown_semantics(markdown)
  MdastHelper->>MarkdownParser: parse_markdown(markdown)
  MarkdownParser-->>MdastHelper: parser_ast
  MdastHelper-->>Pytest: normalized_mdast_json
  Pytest->>Syrupy: snapshot.compare(normalized_mdast_json)
  Syrupy-->>Pytest: assertion result
  Pytest-->>Makefile: test pass/fail
  Makefile-->>Developer: gate result
Loading

File-Level Changes

Change Details Files
Introduce a draft ExecPlan describing the planned migration of documentation snapshot assertions to normalized mdast JSON snapshots in the parent template and generated Rust projects.
  • Define purpose and motivation for semantic mdast-based documentation snapshots, separating them from existing Markdown formatting checks.
  • Specify constraints, tolerances, and risks around parser choice, runtime dependencies, snapshot determinism, and test gate behavior.
  • Document current progress, surprises, and decisions discovered during repository exploration related to existing snapshot tests and tooling.
  • Lay out a red-green-refactor implementation path for adding mdast normalization helpers, tests, and snapshot usage in both Python (parent) and Rust (generated projects).
  • Describe validation commands and expectations for focused and full test runs, and reserve an outcomes section for post-implementation retrospective.
docs/execplans/mdast-snapshot-assertions.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@lodyai lodyai Bot changed the title Plan: Validate documentation snapshots with mdast JSON Validate documentation snapshots with mdast JSON Jun 28, 2026
leynos added 4 commits July 6, 2026 17:05
Describe the approved path for replacing documentation snapshot checks
with normalized mdast JSON assertions in both the parent template tests
and generated Rust projects.
Parse generated Markdown documentation into normalized mdast JSON before snapshotting so wrapping and table alignment changes do not churn semantic snapshots.
Add generated Rust tests that parse documentation with markdown-rs and compare normalized mdast JSON with insta snapshots. Document the snapshot workflow for template and generated-project contributors.
Mark the execplan complete and capture the shipped parser choices, validation evidence, commits, and CodeRabbit review results.
@pandalump
pandalump force-pushed the mdast-snapshot-assertions branch from f9fe6d5 to 8910cf7 Compare July 6, 2026 16:05
@pandalump
pandalump marked this pull request as ready for review July 6, 2026 16:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/developers-guide.md`:
- Around line 25-30: Update the documentation wording in the affected markdown
sections to use en-GB spellings: replace “normalized” with “normalised” and
“normalization” with “normalisation” in the prose that describes the Markdown
snapshot process, including the reference to
tests/helpers/markdown_semantics.py. Keep the rest of the content unchanged and
ensure the terminology is consistent across both updated sections in
docs/developers-guide.md.

In `@docs/execplans/mdast-snapshot-assertions.md`:
- Line 137: The markdown snapshot text contains an inline code span with a
trailing space in the `Plan: ` fragment, which triggers the MD038 syntax check.
Update the affected entry in the mdast snapshot assertions so the inline code
uses the trimmed form `Plan:` or rephrase the sentence to avoid spacing inside
backticks; make sure the fix is applied in the snapshot content itself where the
`Removed the` note is defined.

In `@template/docs/developers-guide.md.jinja`:
- Around line 20-28: The documentation guidance incorrectly refers contributors
to the nonexistent Make target `make fmt`; update the text in the developers
guide to point to the actual formatting target exposed by the generated
Makefile, `check-fmt`, and keep the surrounding workflow references in
`tests/documentation_snapshots.rs` and `markdownlint` unchanged.

In `@tests/helpers/markdown_semantics.py`:
- Line 32: Refactor _normalise_node to use structural pattern matching instead
of the current isinstance-based decomposition for dict and list values. Update
the branching in _normalise_node to match on the value shape directly with
match/case, covering both the dict handling and the list handling referenced in
the comment, while preserving the existing normalization behavior.
- Around line 14-17: Add numpy-style docstrings in the helper module: expand
parse_markdown_semantics with full structured documentation including Parameters
and Returns, and add at least a one-line summary docstring to the private
helpers _gfm_parse_options, _normalise_node, and _normalise_soft_text. Keep the
wording aligned with their actual behavior so the public API and internal
utilities all satisfy the path docstring requirements.

In `@tests/test_template/test_documentation_snapshots.py`:
- Line 41: The tests in the markdown snapshot checks use bare asserts, which
should be updated to include failure messages. In the
`test_documentation_snapshots` test functions, replace each bare `assert` with
the same condition plus a clear message so failures are easier to diagnose,
including the semantic comparison in the `parse_markdown_semantics` checks and
the other asserts covered in the same block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: af1a7efb-e5ad-4d16-a410-d50b6f4f8b73

📥 Commits

Reviewing files that changed from the base of the PR and between 7d7eb00 and 8910cf7.

📒 Files selected for processing (13)
  • Makefile
  • docs/developers-guide.md
  • docs/execplans/mdast-snapshot-assertions.md
  • template/Cargo.toml.jinja
  • template/docs/developers-guide.md.jinja
  • template/tests/documentation_snapshots.rs
  • template/tests/snapshots/documentation_snapshots__docs_contents.snap.jinja
  • template/tests/snapshots/documentation_snapshots__docs_repository_layout_app.snap.jinja
  • template/tests/snapshots/documentation_snapshots__docs_repository_layout_lib.snap.jinja
  • tests/helpers/markdown_semantics.py
  • tests/test_helpers.py
  • tests/test_template/__snapshots__/test_documentation_snapshots.ambr
  • tests/test_template/test_documentation_snapshots.py

Comment thread docs/developers-guide.md
Comment on lines +25 to +30
Documentation snapshot tests parse Markdown files into normalized mdast JSON
before comparing snapshots. The normalization removes parser source positions
and treats soft line wrapping as formatting noise, while preserving document
structure such as headings, links, lists, tables, inline code, and code blocks.
Markdown formatting remains covered separately by `markdownlint-cli2` and
`mdtablefix`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use en-GB spellings here.

Replace normalized/normalization with normalised/normalisation in both updated sections.

Triage: [type:spelling]
As per path instructions, use en-GB-oxendict spelling in docs/**.

♻️ Proposed fix
-Documentation snapshot tests parse Markdown files into normalized mdast JSON
-before comparing snapshots. The normalization removes parser source positions
+Documentation snapshot tests parse Markdown files into normalised mdast JSON
+before comparing snapshots. The normalisation removes parser source positions
@@
-- `tests/helpers/markdown_semantics.py` parses Markdown as normalized mdast JSON
+- `tests/helpers/markdown_semantics.py` parses Markdown as normalised mdast JSON

Also applies to: 98-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/developers-guide.md` around lines 25 - 30, Update the documentation
wording in the affected markdown sections to use en-GB spellings: replace
“normalized” with “normalised” and “normalization” with “normalisation” in the
prose that describes the Markdown snapshot process, including the reference to
tests/helpers/markdown_semantics.py. Keep the rest of the content unchanged and
ensure the terminology is consistent across both updated sections in
docs/developers-guide.md.

Source: Path instructions

50 passed and 1 skipped.
- [x] 2026-06-28: Received explicit user approval to implement the planned
functionality and began execution.
- [x] 2026-06-28: Removed the `Plan: ` prefix from PR #43 and renamed the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Trailing space inside inline code span.

`Plan: ` includes a trailing space inside the backticks (MD038). Trim it to `Plan:` or reword.

Triage: [type:syntax/md]

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 137-137: Spaces inside code span elements

(MD038, no-space-in-code)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/execplans/mdast-snapshot-assertions.md` at line 137, The markdown
snapshot text contains an inline code span with a trailing space in the `Plan: `
fragment, which triggers the MD038 syntax check. Update the affected entry in
the mdast snapshot assertions so the inline code uses the trimmed form `Plan:`
or rephrase the sentence to avoid spacing inside backticks; make sure the fix is
applied in the snapshot content itself where the `Removed the` note is defined.

Source: Linters/SAST tools

Comment on lines +20 to +28
Documentation snapshot tests live in `tests/documentation_snapshots.rs`. They
parse selected Markdown files into normalized mdast JSON before comparing
`insta` snapshots, so line wrapping and table alignment changes do not churn
semantic snapshots. Markdown formatting is still checked by `make markdownlint`
and formatted by `make fmt`. When a documentation meaning or structure change
is intentional, update the affected `tests/snapshots/*.snap` files with
`INSTA_UPDATE=always cargo test --test documentation_snapshots`, then review
the JSON diff before committing it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace make fmt with make check-fmt.

The generated Makefile exposes check-fmt, not fmt, so this instruction points contributors at a dead target.

Proposed fix
- Markdown formatting is still checked by `make markdownlint` and formatted by `make fmt`.
+ Markdown formatting is still checked by `make markdownlint` and validated by `make check-fmt`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Documentation snapshot tests live in `tests/documentation_snapshots.rs`. They
parse selected Markdown files into normalized mdast JSON before comparing
`insta` snapshots, so line wrapping and table alignment changes do not churn
semantic snapshots. Markdown formatting is still checked by `make markdownlint`
and formatted by `make fmt`. When a documentation meaning or structure change
is intentional, update the affected `tests/snapshots/*.snap` files with
`INSTA_UPDATE=always cargo test --test documentation_snapshots`, then review
the JSON diff before committing it.
Documentation snapshot tests live in `tests/documentation_snapshots.rs`. They
parse selected Markdown files into normalized mdast JSON before comparing
`insta` snapshots, so line wrapping and table alignment changes do not churn
semantic snapshots. Markdown formatting is still checked by `make markdownlint`
and validated by `make check-fmt`. When a documentation meaning or structure change
is intentional, update the affected `tests/snapshots/*.snap` files with
`INSTA_UPDATE=always cargo test --test documentation_snapshots`, then review
the JSON diff before committing it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/docs/developers-guide.md.jinja` around lines 20 - 28, The
documentation guidance incorrectly refers contributors to the nonexistent Make
target `make fmt`; update the text in the developers guide to point to the
actual formatting target exposed by the generated Makefile, `check-fmt`, and
keep the surrounding workflow references in `tests/documentation_snapshots.rs`
and `markdownlint` unchanged.

Comment on lines +14 to +17
def parse_markdown_semantics(markdown: str) -> object:
"""Parse Markdown into normalized mdast JSON."""
tree = mdast.md_to_ast(markdown, config=_gfm_parse_options())
return _normalise_node(tree)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add numpy-style docstrings per path instructions.

parse_markdown_semantics is a public interface but only carries a one-line summary; as per path instructions, public interfaces need full structured (Parameters/Returns) numpy-style docs. _gfm_parse_options, _normalise_node, and _normalise_soft_text are private and currently have no docstring at all, but the guideline requires at least a single-line summary for private functions/methods too.

Also applies to: 20-28, 50-51

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/markdown_semantics.py` around lines 14 - 17, Add numpy-style
docstrings in the helper module: expand parse_markdown_semantics with full
structured documentation including Parameters and Returns, and add at least a
one-line summary docstring to the private helpers _gfm_parse_options,
_normalise_node, and _normalise_soft_text. Keep the wording aligned with their
actual behavior so the public API and internal utilities all satisfy the path
docstring requirements.

Source: Path instructions



def _normalise_node(value: Any) -> object:
if isinstance(value, dict):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Prefer structural pattern matching over isinstance() chains.

_normalise_node decomposes with isinstance(value, dict) / isinstance(value, list). As per path instructions, "Prefer structural pattern matching over isinstance() or imperative decomposition" for **/*.py.

♻️ Proposed refactor using match/case
 def _normalise_node(value: Any) -> object:
-    if isinstance(value, dict):
+    match value:
+     case dict():
         normalised = {}
         node_type = value.get("type")
         for key, child in value.items():
             if key in _POSITION_KEYS:
                 continue
             if key == "value" and node_type == "text" and isinstance(child, str):
                 normalised[key] = _normalise_soft_text(child)
                 continue
             normalised[key] = _normalise_node(child)
         return normalised
-
-    if isinstance(value, list):
+     case list():
         return [_normalise_node(child) for child in value]
-
-    return value
+     case _:
+        return value

Also applies to: 44-46

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/markdown_semantics.py` at line 32, Refactor _normalise_node to
use structural pattern matching instead of the current isinstance-based
decomposition for dict and list values. Update the branching in _normalise_node
to match on the value shape directly with match/case, covering both the dict
handling and the list handling referenced in the comment, while preserving the
existing normalization behavior.

Source: Path instructions

- [x] Keep snapshots semantic
"""

assert parse_markdown_semantics(compact) == parse_markdown_semantics(reflowed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add failure messages to asserts.

Both asserts are bare. As per path instructions, "Use assert …, message over bare asserts" for **/*.py.

✅ Proposed fix
-    assert parse_markdown_semantics(compact) == parse_markdown_semantics(reflowed)
+    assert parse_markdown_semantics(compact) == parse_markdown_semantics(reflowed), (
+        "expected formatting-only Markdown differences to normalise identically"
+    )
-    assert {
+    assert (
+        {
         "docs/contents.md": parse_markdown_semantics(
             (project / "docs/contents.md").read_text(encoding="utf-8")
         ),
         "docs/repository-layout.md": parse_markdown_semantics(
             (project / "docs/repository-layout.md").read_text(encoding="utf-8")
         ),
-    } == snapshot
+        }
+        == snapshot
+    ), "expected generated documentation to match the reviewed semantic snapshot"

Also applies to: 56-63

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_template/test_documentation_snapshots.py` at line 41, The tests in
the markdown snapshot checks use bare asserts, which should be updated to
include failure messages. In the `test_documentation_snapshots` test functions,
replace each bare `assert` with the same condition plus a clear message so
failures are easier to diagnose, including the semantic comparison in the
`parse_markdown_semantics` checks and the other asserts covered in the same
block.

Source: Path instructions

@leynos leynos closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant