Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions test/test_doc_citations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Repo-root citation checks must not reinterpret nested upstream paths."""
from __future__ import annotations

import importlib.util
from pathlib import Path

import pytest


_SPEC = importlib.util.spec_from_file_location(
"check_doc_citations", Path(__file__).resolve().parents[1] / "tools/check_doc_citations.py")
citations = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(citations)


@pytest.fixture
def source(tmp_path, monkeypatch):
monkeypatch.setattr(citations, "REPO", tmp_path)
root = tmp_path / "src"
root.mkdir()
return root / "sample.py"


@pytest.mark.parametrize("prefix", ["upstream-", "upstream/", "upstream_", ".", "../", "\\"])
def test_embedded_paths_are_not_local_citations(source, capsys, prefix):
source.write_text(prefix + "doc/missing.py") # doc-citation-exempt
assert citations.main() == 0
assert "0 citations resolve" in capsys.readouterr().out


@pytest.mark.parametrize("path", [
"jiuwenswarm/resources/skills/skill-gen-4-enterprise-doc/scripts/skill_gen/validator.py", # doc-citation-exempt
"https://example.test/doc/missing.md", # doc-citation-exempt
"https://example.test/component-doc/scripts/validator.py", # doc-citation-exempt
])
def test_upstream_and_url_paths_are_not_local_citations(source, capsys, path):
source.write_text(path)
assert citations.main() == 0
assert "0 citations resolve" in capsys.readouterr().out


@pytest.mark.parametrize("text", [
"doc/missing.md", # doc-citation-exempt
"See `doc/missing.md`.", # doc-citation-exempt
"(doc/missing.md)", # doc-citation-exempt
"upstream-doc/remote.md and doc/missing.md", # doc-citation-exempt
])
def test_missing_root_citations_still_fail(source, capsys, text):
source.write_text(text)
assert citations.main() == 1
assert "1 dangling citation(s) of 1 checked" in capsys.readouterr().out


def test_valid_local_citation_and_root_segments_resolve(source, capsys):
root = citations.REPO / "doc"
root.mkdir()
(root / "guide.md").write_text("guide")
source.write_text('See `doc/guide.md`.\nROOT / "doc" / "guide.md"') # doc-citation-exempt
assert citations.main() == 0
assert "2 citations resolve" in capsys.readouterr().out


def test_missing_root_segments_still_fail(source, capsys):
source.write_text('REPO_ROOT / "doc" / "missing.md"') # doc-citation-exempt
assert citations.main() == 1
output = capsys.readouterr().out
assert "built from path segments" in output
assert "1 dangling citation(s) of 1 checked" in output
4 changes: 3 additions & 1 deletion tools/check_doc_citations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@
# A repo-root-relative doc path under `doc/` (singular), ending in a real
# extension. Trailing punctuation is excluded from the match so
# a trailing sentence period does not become part of the filename.
# A path or word prefix means this is an embedded upstream path, not a
# repo-root citation (for example, an upstream component ending in `-doc/`).
#
# Deliberately NOT `docs?/`: plural `docs/` is ambiguous — it is the TARGET
# repo's documentation tree, which knowledge pages, adapters and MCP tests
# legitimately reference by upstream path (`docs/design/module/index.md`), and
# which `test/` also fabricates as tmp_path fixtures. Matching it produced only
# false positives. This repo's own `docs/` directory was dissolved by the doc
# refactor, after which `doc/` is the sole owned prefix.
CITATION = re.compile(r"\b(doc/[A-Za-z0-9_][A-Za-z0-9_./-]*\.(?:md|toml|json|yaml|yml|tsv|txt|py))")
CITATION = re.compile(r"(?<![\w./\\-])(doc/[A-Za-z0-9_][A-Za-z0-9_./-]*\.(?:md|toml|json|yaml|yml|tsv|txt|py))")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep ./doc citations in the scan

When a repository-root citation is written in the common shell-style form ./doc/missing.md, the new negative lookbehind rejects it because doc/ is preceded by a dot. The scanner then reports zero citations and allows a dangling in-repository reference to pass, contrary to its stated repo-root citation contract. Exclude dots only when they belong to a preceding path component, while continuing to recognize an explicit ./doc/... prefix.

Useful? React with 👍 / 👎.


# Paths built from pathlib SEGMENTS rather than written as one string:
# (ROOT / "docs" / "codex" / "README.md").read_text() # doc-citation-exempt
Expand Down
Loading