Skip to content
Open
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
18 changes: 9 additions & 9 deletions src/coder_eval/criteria/skill_triggered.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@
_NO = "no"

# Extracts the skill name between ``skills/<name>/`` path segments (the Codex
# file-read signal). The leading class keeps a bare ``skills//`` from matching.
_SKILL_PATH_RE = re.compile(r"skills/([A-Za-z0-9][A-Za-z0-9_-]*)/")
# file-read signal). Accept both POSIX and Windows separators because telemetry
# records the command exactly as the agent emitted it. One-or-more separators
# also handles JSON-escaped commands that retain doubled backslashes. The
# lookahead permits overlapping matches such as ``.../skills/skills/<name>/...``.
_SKILL_PATH_RE = re.compile(r"(?=skills[\\/]+([A-Za-z0-9][A-Za-z0-9_-]*)[\\/]+)")


def _engaged_skill_names(cmd: CommandTelemetry) -> set[str]:
Expand All @@ -46,9 +49,9 @@ def _engaged_skill_names(cmd: CommandTelemetry) -> set[str]:

- the Claude ``Skill`` tool call's ``skill`` parameter, namespace-stripped
via ``.split(":")[-1]`` (the SDK reports ``plugin:skill-name``); and
- every ``skills/<name>/`` path segment appearing in any string parameter
(the Codex / file-read signal — repo layout or the ``.agents/skills``
sandbox symlink).
- every ``skills/<name>/`` or ``skills\\<name>\\`` path segment appearing
in any string parameter (the Codex / file-read signal — repo layout or
the ``.agents/skills`` sandbox symlink).

Returns the (possibly empty) set of engaged skill names for this command.
"""
Expand Down Expand Up @@ -77,10 +80,7 @@ def _engaged_skill(cmd: CommandTelemetry, skill_name: str) -> bool:
(Bash ``parameters['command']``) or a file-path parameter. The trailing
slash prevents prefix collisions (``uipath-agents`` vs ``uipath-agents-foo``).
"""
if cmd.tool_name == "Skill" and cmd.parameters.get("skill", "").split(":")[-1] == skill_name:
return True
needle = f"skills/{skill_name}/"
return any(isinstance(v, str) and needle in v for v in cmd.parameters.values())
return skill_name in _engaged_skill_names(cmd)


@register_criterion
Expand Down
8 changes: 8 additions & 0 deletions tests/test_early_stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ def test_file_read_path_segment(self) -> None:
got = _engaged_skill_names(_cmd("Read", {"file_path": "/repo/skills/date-teller/SKILL.md"}))
assert got == {"date-teller"}

def test_windows_file_read_path_segment(self) -> None:
got = _engaged_skill_names(_cmd("Read", {"file_path": r"C:\repo\skills\date-teller\SKILL.md"}))
assert got == {"date-teller"}

def test_json_escaped_windows_path_segment(self) -> None:
got = _engaged_skill_names(_cmd("Bash", {"command": r"type C:\\repo\\skills\\date-teller\\SKILL.md"}))
assert got == {"date-teller"}

def test_bash_command_path_segment(self) -> None:
assert _engaged_skill_names(_cmd("Bash", {"command": "cat skills/foo/refs.md"})) == {"foo"}

Expand Down
16 changes: 16 additions & 0 deletions tests/test_skill_triggered.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ def test_codex_reads_skill_md_agents_symlink_tp(self) -> None:
)
assert result.score == 1.0 and result.observed_label == "yes"

def test_codex_reads_skill_md_windows_path_tp(self) -> None:
result = _check(
expected_skill="uipath-agents",
skill_name="uipath-agents",
commands=[self._sed(r"C:\sandbox\.agents\skills\uipath-agents\SKILL.md")],
)
assert result.score == 1.0 and result.observed_label == "yes"

def test_codex_reads_skill_md_json_escaped_windows_command_tp(self) -> None:
result = _check(
expected_skill="uipath-agents",
skill_name="uipath-agents",
commands=[self._sed(r"C:\\sandbox\\.agents\\skills\\uipath-agents\\SKILL.md")],
)
assert result.score == 1.0 and result.observed_label == "yes"

def test_codex_reads_wrong_skill_not_counted(self) -> None:
# Read uipath-rpa's SKILL.md, but criterion filters on uipath-agents -> observed="no".
result = _check(
Expand Down
Loading