diff --git a/openkb/skill/validator.py b/openkb/skill/validator.py index f40014d6..b70444ae 100644 --- a/openkb/skill/validator.py +++ b/openkb/skill/validator.py @@ -207,10 +207,11 @@ def validate_skill(skill_dir: Path, *, strict: bool = False) -> ValidationResult # references/ wikilink resolution wikilinks = WIKILINK_RE.findall(text) for link in wikilinks: - # link may or may not include .md suffix - target = refs_dir / link - if not target.suffix: - target = target.with_suffix(".md") + # The link may already include the .md suffix; append it otherwise. + # Test the literal ".md" rather than Path.suffix — a dotted stem like + # "api.v2" has a truthy suffix (".v2"), so Path.suffix would skip the + # ".md" and then look for a non-existent extension-less file. + target = refs_dir / (link if link.lower().endswith(".md") else f"{link}.md") if not target.exists(): result.errors.append( f"SKILL.md references [[references/{link}]] but " diff --git a/tests/test_skill_validator.py b/tests/test_skill_validator.py index 653cb64c..6321072b 100644 --- a/tests/test_skill_validator.py +++ b/tests/test_skill_validator.py @@ -259,6 +259,19 @@ def test_wikilink_without_md_suffix_resolves(tmp_path): assert result.passed, result.errors +def test_wikilink_dotted_stem_without_md_suffix_resolves(tmp_path): + # A reference name whose stem contains a dot (e.g. "api.v2") must still get + # the implicit ".md". Path.suffix would treat ".v2" as the suffix and skip + # appending ".md", falsely reporting the existing api.v2.md as missing. + sd = _write_skill( + tmp_path, "ref-dotted-stem", + body="See [[references/api.v2]] for details.\n", + refs={"api.v2.md": "# api v2\n"}, + ) + result = validate_skill(sd) + assert result.passed, result.errors + + # --------------------------------------------------------------------------- # scripts/ imports — strict mode only # ---------------------------------------------------------------------------