diff --git a/.agents/skills/docs-governance/scripts/markdown_links.py b/.agents/skills/docs-governance/scripts/markdown_links.py index 05ebb3423..c0da8da97 100644 --- a/.agents/skills/docs-governance/scripts/markdown_links.py +++ b/.agents/skills/docs-governance/scripts/markdown_links.py @@ -49,10 +49,36 @@ def without_fenced_code(text: str) -> str: return "".join(output) -def without_inline_code(text: str) -> str: +def _is_escaped(text: str, index: int) -> bool: + backslashes = 0 + while index - backslashes > 0 and text[index - backslashes - 1] == "\\": + backslashes += 1 + return backslashes % 2 == 1 + + +def without_inline_code_and_html_comments(text: str) -> str: + """Blank out inline code spans and HTML comments, whichever opens first. + + A comment runs from `` after it (`` and `` included). It + becomes an opaque run of NUL characters, so a link or a reference definition cannot continue + through it, as none can through raw HTML. An escaped `\\", cursor + 2) + if closing != -1: + output.extend( + "\n" if character == "\n" else "\0" + for character in text[cursor : closing + 3] + ) + cursor = closing + 3 + continue + # -- no closer here means none for any later opener either -- + comments_can_close = False if text[cursor] != "`": output.append(text[cursor]) cursor += 1 @@ -74,28 +100,6 @@ def without_inline_code(text: str) -> str: return "".join(output) -def without_html_comments(text: str) -> str: - """Blank out HTML comments, where link-shaped text is not a link. - - A comment runs from `` after it (`` and `` included). An - opener with no closer is left alone, as CommonMark leaves it literal text inline. - """ - output: list[str] = [] - cursor = 0 - while True: - start = text.find("", start + 2) - if end == -1: - break - output.append(text[cursor:start]) - output.append(re.sub(r"[^\n]", " ", text[start : end + 3])) - cursor = end + 3 - output.append(text[cursor:]) - return "".join(output) - - def inline_link_targets(text: str) -> list[str]: targets: list[str] = [] matches = list(INLINE_LINK_START_RE.finditer(text)) @@ -195,7 +199,7 @@ def reference_link_targets(text: str) -> list[str]: def markdown_link_targets(text: str) -> list[str]: - text = without_html_comments(without_inline_code(without_fenced_code(text))) + text = without_inline_code_and_html_comments(without_fenced_code(text)) return inline_link_targets(text) + reference_link_targets(text) diff --git a/manifests/context-packs/skill-triggers@1.json b/manifests/context-packs/skill-triggers@1.json index edcb97000..dbe73c73e 100644 --- a/manifests/context-packs/skill-triggers@1.json +++ b/manifests/context-packs/skill-triggers@1.json @@ -10,7 +10,7 @@ "id": "hand-seeded", "source": "manual-curation-pending-regeneration" }, - "registryDigest": "26e3471f7d97e474b311e1cc45c3d967f5be98fca09f264a8cfd8de33ac1158d", + "registryDigest": "f2a5daa835fd6ed2974e14b3f6118ac640115fd8caf10fd8568f1b8adc9a432b", "schemaVersion": 1, "triggers": { "skill:agent-runtime-gateway-smoke-test": [ diff --git a/pi/core/skills/docs-governance/scripts/markdown_links.py b/pi/core/skills/docs-governance/scripts/markdown_links.py index 05ebb3423..c0da8da97 100644 --- a/pi/core/skills/docs-governance/scripts/markdown_links.py +++ b/pi/core/skills/docs-governance/scripts/markdown_links.py @@ -49,10 +49,36 @@ def without_fenced_code(text: str) -> str: return "".join(output) -def without_inline_code(text: str) -> str: +def _is_escaped(text: str, index: int) -> bool: + backslashes = 0 + while index - backslashes > 0 and text[index - backslashes - 1] == "\\": + backslashes += 1 + return backslashes % 2 == 1 + + +def without_inline_code_and_html_comments(text: str) -> str: + """Blank out inline code spans and HTML comments, whichever opens first. + + A comment runs from `` after it (`` and `` included). It + becomes an opaque run of NUL characters, so a link or a reference definition cannot continue + through it, as none can through raw HTML. An escaped `\\", cursor + 2) + if closing != -1: + output.extend( + "\n" if character == "\n" else "\0" + for character in text[cursor : closing + 3] + ) + cursor = closing + 3 + continue + # -- no closer here means none for any later opener either -- + comments_can_close = False if text[cursor] != "`": output.append(text[cursor]) cursor += 1 @@ -74,28 +100,6 @@ def without_inline_code(text: str) -> str: return "".join(output) -def without_html_comments(text: str) -> str: - """Blank out HTML comments, where link-shaped text is not a link. - - A comment runs from `` after it (`` and `` included). An - opener with no closer is left alone, as CommonMark leaves it literal text inline. - """ - output: list[str] = [] - cursor = 0 - while True: - start = text.find("", start + 2) - if end == -1: - break - output.append(text[cursor:start]) - output.append(re.sub(r"[^\n]", " ", text[start : end + 3])) - cursor = end + 3 - output.append(text[cursor:]) - return "".join(output) - - def inline_link_targets(text: str) -> list[str]: targets: list[str] = [] matches = list(INLINE_LINK_START_RE.finditer(text)) @@ -195,7 +199,7 @@ def reference_link_targets(text: str) -> list[str]: def markdown_link_targets(text: str) -> list[str]: - text = without_html_comments(without_inline_code(without_fenced_code(text))) + text = without_inline_code_and_html_comments(without_fenced_code(text)) return inline_link_targets(text) + reference_link_targets(text) diff --git a/skills/docs-governance/scripts/markdown_links.py b/skills/docs-governance/scripts/markdown_links.py index 05ebb3423..c0da8da97 100644 --- a/skills/docs-governance/scripts/markdown_links.py +++ b/skills/docs-governance/scripts/markdown_links.py @@ -49,10 +49,36 @@ def without_fenced_code(text: str) -> str: return "".join(output) -def without_inline_code(text: str) -> str: +def _is_escaped(text: str, index: int) -> bool: + backslashes = 0 + while index - backslashes > 0 and text[index - backslashes - 1] == "\\": + backslashes += 1 + return backslashes % 2 == 1 + + +def without_inline_code_and_html_comments(text: str) -> str: + """Blank out inline code spans and HTML comments, whichever opens first. + + A comment runs from `` after it (`` and `` included). It + becomes an opaque run of NUL characters, so a link or a reference definition cannot continue + through it, as none can through raw HTML. An escaped `\\", cursor + 2) + if closing != -1: + output.extend( + "\n" if character == "\n" else "\0" + for character in text[cursor : closing + 3] + ) + cursor = closing + 3 + continue + # -- no closer here means none for any later opener either -- + comments_can_close = False if text[cursor] != "`": output.append(text[cursor]) cursor += 1 @@ -74,28 +100,6 @@ def without_inline_code(text: str) -> str: return "".join(output) -def without_html_comments(text: str) -> str: - """Blank out HTML comments, where link-shaped text is not a link. - - A comment runs from `` after it (`` and `` included). An - opener with no closer is left alone, as CommonMark leaves it literal text inline. - """ - output: list[str] = [] - cursor = 0 - while True: - start = text.find("", start + 2) - if end == -1: - break - output.append(text[cursor:start]) - output.append(re.sub(r"[^\n]", " ", text[start : end + 3])) - cursor = end + 3 - output.append(text[cursor:]) - return "".join(output) - - def inline_link_targets(text: str) -> list[str]: targets: list[str] = [] matches = list(INLINE_LINK_START_RE.finditer(text)) @@ -195,7 +199,7 @@ def reference_link_targets(text: str) -> list[str]: def markdown_link_targets(text: str) -> list[str]: - text = without_html_comments(without_inline_code(without_fenced_code(text))) + text = without_inline_code_and_html_comments(without_fenced_code(text)) return inline_link_targets(text) + reference_link_targets(text) diff --git a/tests/test_docs_governance_audit.py b/tests/test_docs_governance_audit.py index 073f4676b..1c6b1ad56 100644 --- a/tests/test_docs_governance_audit.py +++ b/tests/test_docs_governance_audit.py @@ -280,6 +280,23 @@ def test_artifact_scope_ignores_links_inside_html_comments(project: Path) -> Non assert "Broken Markdown link: 'guide.md' -> 'missing-after.md'" in result.stdout +def test_artifact_scope_reads_html_comments_as_commonmark_does(project: Path) -> None: + (project / "guide.md").write_text( + # -- an escaped opener is text, so the link after it is real -- + "Intro \\\n\n" + # -- a comment that opens before a code span ends at its own closer -- + " `sample` [after-code](missing-after-code.md)\n\n" + # -- a comment where a destination must begin does not make a definition -- + "[foo]: missing-definition.md\n\n[foo]\n", + encoding="utf-8", + ) + result = run_audit(project, "artifacts") + assert result.returncode == 1 + assert "Broken Markdown link: 'guide.md' -> 'missing-escaped.md'" in result.stdout + assert "Broken Markdown link: 'guide.md' -> 'missing-after-code.md'" in result.stdout + assert "missing-definition.md" not in result.stdout + + def test_artifact_scope_ignores_title_with_escaped_quotes(project: Path) -> None: (project / "guide.md").write_text("# Guide\n", encoding="utf-8") (project / "index.md").write_text(