Skip to content

⚙️ [Maintenance]: Cross-repository links are now verified - #145

Merged
Marius Storhaug (MariusStorhaug) merged 13 commits into
mainfrom
check-cross-repo-links
Aug 9, 2026
Merged

⚙️ [Maintenance]: Cross-repository links are now verified#145
Marius Storhaug (MariusStorhaug) merged 13 commits into
mainfrom
check-cross-repo-links

Conversation

@MariusStorhaug

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented Aug 2, 2026

Copy link
Copy Markdown
Member

Documentation that points to MSX-controlled GitHub repositories now stays trustworthy after a target changes. Each link's file and referenced heading are verified in pull requests and weekly, with results that distinguish a broken link from a target that cannot be reached.

New: Cross-repository link verification

Links to files and headings in MSX-controlled repositories are now checked before documentation is published and again each week. When a target has moved, its heading no longer exists, or readers cannot access it, the check identifies the link and the reason instead of leaving a dead reference for readers to find.

The check accepts only targets that an anonymous reader can open. No author workflow changes are needed: continue using the canonical cross-repository URLs prescribed by the Markdown standard.


Technical details
  • Added .github/scripts/Test-CrossRepositoryLink.ps1, its 31-case Pester suite, and a separate scheduled Cross-repository links workflow.
  • The check validates github.com and raw.githubusercontent.com links into MSXOrg, PSModule, and Storhaug-ting, including repository paths, named references, files, and GitHub-rendered heading slugs.
  • Links into this repository resolve against the checkout, preventing a pull request that moves a file from passing against the default branch's pre-merge content.
  • Repository visibility is probed before a 404 is classified; network failures, rate-limit exhaustion, inaccessible repositories, and broken links report separately. GITHUB_TOKEN provides rate-limit headroom only and does not make private targets valid public references.
  • The check fails when no in-scope link is found and stops further requests after the API quota is exhausted.
  • Implementation plan progress: all eight steps in Check the cross-repository links the Markdown standard tells authors to write #142 are complete. Published-site URLs remain outside this delivery and are tracked by Check the published-URL links the Markdown standard also asks authors to write #150.
Changed surface Standards checked Framework docs checked Result
.github/scripts/** (PowerShell) PowerShell/Scripts, PowerShell/Functions, Naming, Error Handling, Documentation None (no framework-specific docs) Aligned
tests/** (PowerShell) Testing, PowerShell/Testing, PowerShell/Functions None (no framework-specific docs) Aligned
.github/workflows/** GitHub Actions None (no framework-specific docs) Aligned
.github/linters/** Markdown, Natural Language None (no framework-specific docs) Aligned
src/docs/**, CONTRIBUTING.md Markdown, Documentation, Natural Language Documentation Model Aligned

Issue convergence sweep: scoped to open documentation-link validation work and the affected repositories. #142 is fully delivered; no other delivery issue is fully satisfied by this diff.

Relevant issues (or links)

Related work

The check does not exist yet, so every case is red. That is the point:
the negative proof comes before the implementation, per the Testing
standard's test-first rule.

The suite runs entirely offline. An in-process HttpListener stub answers
the two GitHub endpoints the check will call, so the cases for a missing
file, a missing anchor, an exhausted rate limit, a failing request, and a
target repository a reader cannot read are deterministic and locally
runnable.

The slug expectations are a recorded fixture from github-slugger 2.0.0 -
the library GitHub's own anchors come from - not a second derivation of
the rule under test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The check reads the target's content rather than sending a HEAD request:
a URL fragment never reaches the server, so an anchor into a file answers
200 whether or not the heading exists - which is exactly the case that
broke seven links from Storhaug-ting/S62 into Storhaug-ting/Kilden.

Anchors are slugged with GitHub's rules, taken from Kilden's
Test-MarkdownLink.ps1 rather than from this repository's ConvertTo-Slug.
That one mirrors python-markdown for the published site, and a
cross-repository link resolves against GitHub's rendering, not ours.

Three outcomes rather than two. A 404 alone cannot tell a deleted file
from a repository no reader can open, so the repository is probed before
a link is called broken, and both a network failure and an unreadable
target are reported under their own heading.

Proven negatively as well as positively: disabling the anchor comparison
turns exactly the two anchor cases red, and treating a 404 as success
turns exactly the missing-file and unreadable-repository cases red.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Every one of these was a live 404 in published documentation, found by
the check rather than by a reader - which is the whole point of it.

Process-PSModule moved its framework tests from 'scripts/tests' into
'.github/actions/Test-PSModule/src/tests', and renamed 'Publish-Docs.yml'
to 'Publish-Site.yml'. Test-PSModule's README no longer carries the
'SourceCode tests' and 'Module tests' sections the two anchors pointed
at, so those links now point at the test settings themselves, which is
where the behaviour they describe actually lives.

'DEPENDENCIES.md' no longer exists in Process-PSModule and has no
successor anywhere in the organization, so the sentence promising it now
points at the workflows and actions that make up the composition.

The Checkout-GitHubRepo link is left in place and marked private: the
file is there, the repository is not public, so a reader who gets a 404
now knows why. Personal accounts are outside the check's scope for the
same reason - no check can speak for a repository it cannot read.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Its own workflow rather than a job in Docs.yml for two reasons. The name
of a red check then says the network check failed rather than the
documentation being wrong. And Docs.yml's publish job needs build, lint,
links, and test, so a job there would let a GitHub outage block a Pages
deploy.

It also runs weekly. A target repository moves content on its own
schedule, long after a pull request here has merged, and nothing in the
pull-request trigger will ever notice.

GITHUB_TOKEN is passed for rate-limit headroom only - 60 requests an hour
anonymously against 1000 authenticated. It unlocks no private repository
elsewhere, so the check still measures what an anonymous reader can
reach.

Docs.yml's comment claiming to be the only workflow is corrected, and its
Links job now says which half of the link checking it owns.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Running the check locally without a token exhausted the anonymous limit
of 60 requests an hour part way through, and it then spent another two
dozen requests being told the same thing. The quota is a run-level fact,
not a per-request one: the first 403 with no remaining quota now stops
the rest.

The message also says when no token was set, so the reader is told the
one thing that would have changed the outcome rather than being left to
infer it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The standard asked authors to write a link form nothing verified. It now
says which links are checked, by what, and against which slug rules -
including that GitHub's anchors differ from the published site's, so an
author writing a cross-repository anchor knows which of the two to copy.

Two consequences are spelled out because they change what an author
writes: a public page must not link into a repository a reader cannot
open, and a run that resolved no cross-repository link fails rather than
passing on an empty set.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pushed to make the new check go red in CI on purpose, so the run linked
from the pull request shows it failing and shows what it says when it
does. Reverted in the next commit.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The GitHub slug rules are expressed as '\p{Nd}' and friends, and codespell
reads the category name as a misspelling of 'And'. An ignore-regex for the
whole '\p{...}' form fixes the class rather than the one word, and leaves
ordinary spell checking intact - a probe file with 'mispelled' and
'sentance' still produces two errors.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

Pull request overview

This PR adds CI validation for cross-repository links (into MSX-controlled GitHub orgs) so that moved files/anchors in other repositories are detected automatically, and it repairs several existing dead cross-repo links in the documentation.

Changes:

  • Introduces a new PowerShell validator (Test-CrossRepositoryLink.ps1) plus an offline Pester suite with an in-process stub GitHub API.
  • Adds a dedicated GitHub Actions workflow (Cross-Repository-Links.yml) to run the cross-repository link check on PRs/pushes and weekly.
  • Updates documentation/standards and repairs broken links into PSModule repositories.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Test-CrossRepositoryLink.Tests.ps1 Adds offline Pester coverage for cross-repository link parsing, resolution, anchoring, caching, and failure classification.
./.github/scripts/Test-CrossRepositoryLink.ps1 Implements the cross-repository link checker (GitHub API resolution + GitHub-style anchor validation).
.github/workflows/Cross-Repository-Links.yml New workflow to run the cross-repo link check (PR/push + weekly schedule).
.github/workflows/Docs.yml Clarifies that the existing Links job is intentionally network-free and that cross-repo checking lives elsewhere.
CONTRIBUTING.md Documents running the new cross-repository link check locally before opening a PR.
src/docs/Coding-Standards/Markdown.md Documents that links are checked and describes the new cross-repo check’s behavior/scope.
src/docs/Ways-of-Working/Git-Worktrees.md Adds context that a referenced helper script link points to a private repository.
src/docs/Capabilities/process-psmodule/pipeline-stages.md Repairs multiple PSModule cross-repo links to moved/renamed targets.
src/docs/Capabilities/process-psmodule/index.md Replaces an obsolete dependency reference with links to current workflow/action structure.
.github/linters/.codespellrc Avoids false positives from \p{...} Unicode-category escapes used in regexes.
Suppressed comments (2)

src/docs/Coding-Standards/Markdown.md:67

  • Test-CrossRepositoryLink.ps1 is described here as resolving links "as published URLs", but the very next bullet scopes it to github.com and raw.githubusercontent.com. Rewording this to explicitly say “GitHub URLs” avoids confusion with published-site (Pages) URLs.
**Into another repository** — `Test-CrossRepositoryLink.ps1` resolves the links this standard asks you to write as published URLs. It runs as its own job, so a red check says the network check failed rather than the documentation being wrong, and again weekly, because a target repository moves content long after a pull request here has merged.

src/docs/Capabilities/process-psmodule/pipeline-stages.md:244

  • Grammatically, “tests” is plural, so the verb should be “verify” (or rephrase to a singular subject like “test suite”).
The [PSModule - Module tests](https://github.com/PSModule/Process-PSModule/blob/main/.github/actions/Test-PSModule/src/tests/Module/PSModule/PSModule.Tests.ps1) verifies the following coding practices that the framework enforces:

Comment thread .github/scripts/Test-CrossRepositoryLink.ps1 Outdated
Comment thread src/docs/Coding-Standards/Markdown.md Outdated
- **Use sentence-style headings.**
- **Surround headings, lists, and fenced blocks with a blank line** for readability, even though the linter no longer enforces it.
- **Prefer relative links** within a repository; use the canonical published URL for cross-repository references.
- **Prefer relative links** within a repository; use the canonical published URL for cross-repository references. Both forms are checked in CI — see [Links are checked](#links-are-checked).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right, and that sentence was doing exactly what this pull request exists to stop — a standard claiming a gate it does not have.

Fixed in d00db90. The bullet now says relative links, and cross-repository links on \github.com, are checked in CI, the section says the check resolves \github.com\ links rather than 'the links this standard asks you to write as published URLs', and there is a paragraph naming what is not covered: a \msxorg.github.io\ URL, which is the canonical form for a repository publishing to Pages. That gap is #150, which also records why it was not simply added here — a pull request adding a page and linking its published URL would fail until the page is published.

Comment thread src/docs/Capabilities/process-psmodule/pipeline-stages.md Outdated
A link of the form 'github.com/OWNER/REPO/tree/REF' carries a reference
and no path, and the check only asked whether the repository existed. A
renamed or deleted branch passed as resolved - the one thing that link
form actually asserts went unverified.

It now asks the contents endpoint for the repository root at that
reference, so an unknown branch, tag, or commit answers 404 and is
reported by name. Proven the same way as the rest: reverting the routing
condition turns exactly the new case red.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The standard claimed both link forms it recommends are checked. Only one
is. The canonical published URL for a repository that publishes to Pages
is a 'msxorg.github.io' URL, and nothing verifies those - the gap is now
named in the standard and carried by #150 rather than being papered over
by the sentence that was supposed to close it.

The Process-PSModule tables are also introduced by 'tests ... verify'
rather than 'verifies', on the two lines whose links moved.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/docs/Capabilities/process-psmodule/pipeline-stages.md:66

  • Grammar: this sentence is missing “to” (“…is used by … evaluate…”). As written it’s ungrammatical and slightly harder to read in the rendered docs.
  - [PSModule framework settings for style and standards for source code](https://github.com/PSModule/Process-PSModule/tree/main/.github/actions/Test-PSModule/src/tests/SourceCode)
- This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) evaluate the results of the tests.

@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 🚀 [Feature]: Cross-repository links are checked, not just the ones inside this repository 📖 [Docs]: The Markdown standard's cross-repository links are now checked, not just described Aug 2, 2026
@MariusStorhaug
Marius Storhaug (MariusStorhaug) marked this pull request as ready for review August 2, 2026 17:28
Marius Storhaug (MariusStorhaug) added a commit to Storhaug-ting/Kilden that referenced this pull request Aug 4, 2026
…duces (#16)

`ConvertTo-GitHubSlug`'s example claimed the wrong answer. It said
`Bruksordning for veg - § 3-8` slugs to `bruksordning-for-veg---3-8`; it
slugs to `bruksordning-for-veg----3-8`. Four hyphens, not three.

The section sign is dropped and the space on each side of it survives,
so `veg`, space, hyphen, space, space, `3-8` becomes four hyphens in a
row. Anyone copying the example to work out an anchor by hand would have
got it wrong.

---
<details>
<summary>Technical details</summary>

Found while reusing `ConvertTo-GitHubSlug` and `Get-RenderedHeadingText`
in MSXOrg/docs#145, which adds a cross-repository link check and takes
GitHub's slug rules from here rather than writing a third dialect.

The function is correct; only the `.EXAMPLE` line was wrong. Verified
against `github-slugger` 2.0.0 — the library GitHub's own anchors come
from — over fourteen headings including this one:

```text
"Bruksordning for veg - § 3-8" => "bruksordning-for-veg----3-8"
```

The PowerShell implementation returns the same string for all fourteen,
so the character class here and github-slugger's generated table agree
on every case tested. That comparison is now a recorded fixture in
`tests/Test-CrossRepositoryLink.Tests.ps1` in MSXOrg/docs.

No behaviour change; comment-based help only.

</details>

<details>
<summary>Relevant issues (or links)</summary>

- MSXOrg/docs#145

</details>

Co-authored-by: Marius Storhaug <MariusStorhaug@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 📖 [Docs]: The Markdown standard's cross-repository links are now checked, not just described 📖 [Docs]: Cross-repository links are now verified Aug 9, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 📖 [Docs]: Cross-repository links are now verified ⚙️ [Maintenance]: Cross-repository links are now verified Aug 9, 2026
@MariusStorhaug Marius Storhaug (MariusStorhaug) added release:none No release and removed NoRelease No release required Docs labels Aug 9, 2026
@MariusStorhaug
Marius Storhaug (MariusStorhaug) merged commit ceb7200 into main Aug 9, 2026
21 checks passed
@MariusStorhaug
Marius Storhaug (MariusStorhaug) deleted the check-cross-repo-links branch August 9, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:none No release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Check the cross-repository links the Markdown standard tells authors to write

2 participants