mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
fix(skills): strip trailing sentence punctuation from extracted resource refs (#5739)
* fix(skills): strip trailing sentence punctuation from extracted resource refs
The resource-graph extractor's _PATH_TOKEN_RE includes '.' in its path
character class, so a bare package-relative path at the end of a sentence
(e.g. "Read references/setup.md.") is extracted with the trailing period
still attached. build_resource_graph then emits a false-positive
resource.missing finding for "references/setup.md." and, because the
real file is never matched, reports the actual references/setup.md as an
unreferenced orphan.
Other sentence punctuation (",;:!?()") already falls outside the regex
character class and is correctly dropped; only '.' is ambiguous because it
is both a valid path character and prose punctuation.
Strip trailing '.', '?' and '!' from every extracted reference in
_extract_references. Real dotted filenames are unaffected: rstrip only
removes punctuation at the very end, so references/config.yaml and a link
target such as references/v1.0.md keep their dots.
Adds two regression tests covering prose-terminated bare paths and real
dotted filenames.
* style(tests): format skill review tests
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
bcaf6db36a
commit
83c42a61e7
@ -86,16 +86,24 @@ def build_resource_graph(snapshot: dict[str, Any]) -> tuple[dict[str, Any], list
|
||||
return graph, findings
|
||||
|
||||
|
||||
# Trailing sentence punctuation a resource path can legitimately be followed
|
||||
# by in prose (".", "?", "!") but that is never part of a package-relative
|
||||
# path. "." in particular is also a valid path character, so it must only be
|
||||
# stripped from the very end — e.g. "references/setup.md." -> "references/setup.md"
|
||||
# while "references/config.yaml" keeps its extension.
|
||||
_TRAILING_SENTENCE_PUNCTUATION = ".?!"
|
||||
|
||||
|
||||
def _extract_references(content: str) -> set[str]:
|
||||
refs: set[str] = set()
|
||||
for match in _MARKDOWN_LINK_RE.finditer(content):
|
||||
refs.add(match.group(1).split("#", 1)[0])
|
||||
refs.add(match.group(1).split("#", 1)[0].rstrip(_TRAILING_SENTENCE_PUNCTUATION))
|
||||
for match in _CODE_SPAN_RE.finditer(content):
|
||||
token = match.group(1).strip()
|
||||
if "/" in token:
|
||||
refs.add(token)
|
||||
refs.add(token.rstrip(_TRAILING_SENTENCE_PUNCTUATION))
|
||||
for match in _PATH_TOKEN_RE.finditer(content):
|
||||
refs.add(match.group(0))
|
||||
refs.add(match.group(0).rstrip(_TRAILING_SENTENCE_PUNCTUATION))
|
||||
return refs
|
||||
|
||||
|
||||
|
||||
@ -115,6 +115,43 @@ def test_resource_graph_tracks_referenced_resource(tmp_path):
|
||||
assert "references/guide.md" not in facts["resources"]["orphans"]
|
||||
|
||||
|
||||
def test_resource_graph_strips_trailing_sentence_punctuation_from_prose_refs(tmp_path):
|
||||
# A bare path at the end of an English sentence is followed by "." or "!".
|
||||
# Those are prose punctuation, not part of the path: they must not turn a
|
||||
# valid reference into a resource.missing finding or orphan the real file.
|
||||
_write(
|
||||
tmp_path / "SKILL.md",
|
||||
_valid_skill() + "\nRead references/setup.md.\nAlso see references/usage.md!\n",
|
||||
)
|
||||
_write(tmp_path / "references" / "setup.md", "# Setup\n")
|
||||
_write(tmp_path / "references" / "usage.md", "# Usage\n")
|
||||
|
||||
facts = analyze_skill_package(LocalDirectoryReader(tmp_path).read())
|
||||
|
||||
for target in ("references/setup.md", "references/usage.md"):
|
||||
assert {"source": "SKILL.md", "target": target} in facts["resources"]["edges"]
|
||||
assert target not in facts["resources"]["orphans"]
|
||||
assert not any(f["rule_id"] == "resource.missing" and f["path"] == "SKILL.md" for f in facts["findings"])
|
||||
|
||||
|
||||
def test_resource_graph_keeps_real_dotted_filenames(tmp_path):
|
||||
# "." is also a legitimate path character: a real dotted filename in a
|
||||
# link target or a path token must keep its dots, not be stripped.
|
||||
_write(
|
||||
tmp_path / "SKILL.md",
|
||||
_valid_skill() + "\nSee [config](references/config.yaml) and references/v1.0.md.\n",
|
||||
)
|
||||
_write(tmp_path / "references" / "config.yaml", "a: 1\n")
|
||||
_write(tmp_path / "references" / "v1.0.md", "# v1.0\n")
|
||||
|
||||
facts = analyze_skill_package(LocalDirectoryReader(tmp_path).read())
|
||||
|
||||
for target in ("references/config.yaml", "references/v1.0.md"):
|
||||
assert {"source": "SKILL.md", "target": target} in facts["resources"]["edges"]
|
||||
assert target not in facts["resources"]["orphans"]
|
||||
assert not any(f["rule_id"] == "resource.missing" and f["path"] == "SKILL.md" for f in facts["findings"])
|
||||
|
||||
|
||||
def test_resource_graph_ignores_eval_fixture_references(tmp_path):
|
||||
_write(tmp_path / "SKILL.md", _valid_skill())
|
||||
_write(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user