Skip to content

fix(hygiene): use CommonMark for dead-link checks - #109

Merged
chrisknvidia merged 10 commits into
NVIDIA:mainfrom
rioyu123:codex/fix-commonmark-dead-links
Aug 30, 2026
Merged

chrisknvidia merged 10 commits into
NVIDIA:mainfrom
rioyu123:codex/fix-commonmark-dead-links

Conversation

@rioyu123

@rioyu123 rioyu123 commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #108.

Use the existing shared CommonMark parser for Hygiene dead-link checks. This covers reference-style, collapsed, shortcut, HTML-anchor, and Markdown-image destinations without treating code, comments, or non-navigation HTML as links. Local destination normalization is shared with quality scoring, and repeated equivalent destinations produce one finding per document.

The review follow-up keeps scheme classification before percent decoding, preserves invalid UTF-8 bytes without replacement-character aliasing, rejects Windows anchors after dot-segment normalization, contains frontmatter and per-target lookup failures, escapes and bounds diagnostics, and avoids repeated suffix scans for unclosed inline comments, processing instructions, CDATA, and declarations.

Behavior changes

  • Root-absolute and protocol-relative URLs are skipped rather than interpreted as host paths.
  • Diagnostics use the parser-normalized href with bounded, single-line escaping; duplicate normalized targets collapse to the first finding.
  • Markdown images remain checked. Raw HTML <img src> remains outside scope, matching the old regex.
  • Relative ../ targets retain their previous lookup behavior; containment is not changed by this PR.
  • Invalid or inaccessible local destinations remain per-link errors instead of aborting the complete validation run.
  • Relative URLs that gain an absolute or drive-relative anchor during decoding/normalization produce an Invalid local link finding without filesystem lookup; they are not silently skipped. Repeated identical invalid hrefs are reported once per document. Quality scoring retains the shared helper's default behavior.

Verification

  • I am familiar with the Contributing Guidelines
  • Added or updated focused tests
  • Updated CHANGELOG.md
  • Ran lint
  • Ran the full test suite; environment failures are detailed below
  • Built the sdist and wheel
  • Did not add credentials, private datasets, or proprietary benchmark content

Results on the current candidate:

  • Focused Markdown, Hygiene, and quality-score tests on Windows: 366 passed, 1 skipped (symlink privileges), rerun on fec0b96. The earlier combined run including upstream fix: scan Markdown headings for PII instead of skipping them #96's Security tests was 566 passed, 1 skipped.
  • Expanded verification from the original validator-only run to the full Linux suite with all extras: 5343 passed, 51 skipped, 2 failed on merge head fec0b96. Both failures were also reproduced on unchanged upstream 5387261: the runtime-discovery test sees the host's /usr/bin/codex, and the legacy bridge-port test cannot bind the already occupied port 18080. No host services or installed tools were changed to work around these tests.
  • Ruff check: passed.
  • Python package build: passed (sdist and wheel).

The regressions exercise the run_validation boundary and CLI/Markdown reporting. Five new unsafe-anchor cases failed on the previous published behavior and now produce a finding while preserving the subsequent link check. A dedup regression checks repeated invalid hrefs across three link syntaxes in two documents. A 2,704-case comparison found no changes to the helper's default normalization results. Malformed-frontmatter tests assert that body links are still checked; unexpected lookup RuntimeErrors propagate. Truncation tests cover control characters at the display boundary, distinguishing the CLI's intentional SGR colors from injected control sequences. Their fixed-width console separates the message-escaping check from intentional terminal reflow.

Scaling checks cover all four unclosed HTML forms at 8/16/32/64 KiB using the cumulative suffix length passed to the HTML regex rule, not elapsed-time thresholds. The inline-source cache tests also fail under a deliberately stale-cache mutation: all four expose a hidden anchor that must remain suppressed.

Same-host timing

Linux / Intel Core i9-13900K / Python 3.13.13 / markdown-it-py 4.2.0 / PyYAML 6.0.3. Median of five samples after one warm-up per size; upstream samples batch 100 calls to reduce clock noise. The timed boundary is _check_dead_links, including file enumeration/read, parsing, lookup, and findings. No full test suite ran concurrently.

Input: '<a href="before.md">before</a>' + '<!--' * (kib * 256) in one guide.md. The repeated-openers payload is 8/16/32/64 KiB, plus the 30-byte anchor prefix.

Payload Upstream c15ac0f Original PR 5e73a27 Revised aa019aa
8 KiB 0.0304 ms 108.77 ms 9.032 ms
16 KiB 0.0475 ms 380.58 ms 19.092 ms
32 KiB 0.0816 ms 1,385.33 ms 42.113 ms
64 KiB 0.1507 ms 5,292.28 ms 100.700 ms

Both PR versions preserve the preceding anchor. The upstream regex does not recognize HTML anchors, so its lower timing is not equivalent functionality. All measured calls stayed within a 15-second budget. Revised sample ranges were 9.017–9.192 / 18.928–19.415 / 41.902–42.350 / 100.643–100.906 ms.

The later merges of upstream #96 in 9f3e223 and #78 in fec0b96 resolved changelog-only conflicts by retaining both entries. Neither changed this PR's reviewed source/tests, or the Hygiene/Markdown implementation used for the timing comparison above.

The deterministic check measures 16,418 / 32,802 / 65,570 / 131,106 cumulative regex-input characters for the comment fixtures, at most twice the input length. The guard does four fixed-terminator rfind scans once per inline source (linear input work), then constant-size opener checks. The same bound holds for the tested processing-instruction, CDATA, and declaration families. This bounds the guard/regex-suffix work for these fixtures, not end-to-end parser time or all Markdown inputs.

Release Impact

  • Updated CHANGELOG.md

@chrisknvidia chrisknvidia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requesting changes. The intended CommonMark behavior works and the focused suite passes, but the current implementation introduces five blocking issue groups: unsafe local-path normalization, uncontained frontmatter failures, uncontained filesystem lookup failures, multiline diagnostic injection, and approximately quadratic malformed-HTML parsing. Please address the inline findings and add the specified regressions before merge.

Comment thread src/skillevaluator/validators/markdown.py Outdated
Comment thread src/skillevaluator/validators/markdown.py
Comment thread src/skillevaluator/validators/hygiene.py Outdated
Comment thread src/skillevaluator/validators/hygiene.py Outdated
Comment thread src/skillevaluator/validators/hygiene.py
@rioyu123

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. I've addressed the five groups and added regressions at the public validation/reporting boundaries:

  • Classify schemes before percent decoding, preserve distinct invalid UTF-8 bytes, and reject Windows anchors after normalization. Existing quality-score tests still pass.
  • Contain oversized integer and recursive YAML errors from supporting documents without aborting run_validation.
  • Treat filesystem lookup failures as individual dead-link findings and continue with subsequent links.
  • Escape diagnostic text onto one line and bound its escaped length; both CLI and Markdown reporters are covered.
  • Skip provably unclosed HTML fragments before the repeated regex suffix scan. The guard covers comments, processing instructions, CDATA, and declarations, with 8/16/32/64 KiB work-scaling tests, preceding-link preservation, closed-fragment tests, and per-paragraph offsets. The offset regressions were checked against a deliberately stale-cache mutation; all four fail by exposing an anchor that should stay hidden.

I also merged upstream main at c15ac0f to clear the changelog conflict. Focused checks: 287 passed, 1 skipped on Windows. Full Linux suite: 5,178 passed, 51 skipped, with two host-dependent failures also reproduced on unchanged upstream (installed /usr/bin/codex and occupied port 18080). Ruff and the package build pass. The PR description has the complete verification details.

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified the five prior blocking findings are addressed by the follow-up commits. The focused hygiene, boundary, Markdown, and quality suites pass (288 tests). No new blocker found; the branch still needs its merge conflict resolved and checks rerun against current main.

@rioyu123

rioyu123 commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Adding the numbers for point 5: on the same Linux host, 8/16/32/64 KiB took 9.0 / 19.1 / 42.1 / 100.7 ms after the guard, versus 108.8 / 380.6 / 1,385.3 / 5,292.3 ms on the original PR. Upstream Hygiene was 0.030 / 0.047 / 0.082 / 0.151 ms, but does not recognize the HTML anchor. These are five-sample medians including file scanning/read and findings; the PR description has the environment and sample ranges.

The CI check counts cumulative regex-suffix input, not wall-clock time: 16,418 / 32,802 / 65,570 / 131,106 characters. The guard itself makes four linear rfind scans once per inline source. This removes the repeated suffix scans for the four tested forms; it is not a claim about all Markdown inputs.

The recheck also caught one silent skip, fixed in aa019aa: relative URLs that normalize to Windows/absolute anchors now emit an Invalid local link finding before lookup and continue scanning; identical rejected hrefs are deduplicated per document. The five regressions fail against the previous behavior. Malformed frontmatter still has its body links checked; lookup catches remain OSError/ValueError, with a RuntimeError propagation test. Truncated escapes remain printable text; the reporter test fixes console width and distinguishes intentional SGR styling. Lookup failures retain the allowed dead-link classification.

I also expanded the original validator-only verification to the full Linux suite: 5,272 passed, 51 skipped, with the same two host-dependent failures reproduced again on unchanged upstream. This latest run includes upstream #96 via 9f3e223; only the changelog conflicted, and this PR's source/tests are unchanged by that merge. Ruff and package builds pass.

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed current head after aa019aa and the merge from main. The follow-up now reports relative URLs that normalize into absolute or drive-relative paths instead of silently skipping them, without performing filesystem lookup; the merge resolution preserves both branches. Focused hygiene/Markdown/quality coverage passes (367 tests) and Ruff is clean. No new blocker found.

@rioyu123

Copy link
Copy Markdown
Contributor Author

Thanks for the re-review. Merged current main (5387261, #78) in fec0b96 and resolved the changelog-only conflict, keeping both entries. This PR's reviewed source and tests are unchanged, and the branch is mergeable again.

Rechecked the merged tree: 366 passed, 1 skipped on Windows; 5,343 passed, 51 skipped, 2 failed in the full Linux suite. Both failures reproduce on unchanged 5387261 with the same host conditions noted above. Ruff and the sdist/wheel build pass; the PR description is updated.

All 15 GitHub checks passed on 9f3e223. The fresh CI, Security, and DCO workflow runs for fec0b96 are awaiting maintainer approval.

@chrisknvidia chrisknvidia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed current head 3b13a83. The supported-Python compatibility and Quality Score compatibility issues are fixed, all review conversations are resolved, and all fresh hosted checks pass. Approving.

@chrisknvidia
chrisknvidia merged commit 79d6fd3 into NVIDIA:main Aug 30, 2026
17 checks passed
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.

hygiene: use shared CommonMark parsing for dead-link checks

3 participants