diff --git a/src/plyngent/cli/display.py b/src/plyngent/cli/display.py index a9ce0a5..03f292a 100644 --- a/src/plyngent/cli/display.py +++ b/src/plyngent/cli/display.py @@ -332,14 +332,14 @@ def _glob_detail(_args_json: str, result: str) -> str: return _pretty_segments((f"({count} {unit})", None)) -def _grep_prefix(args_json: str) -> str: +def _regex_prefix(args_json: str) -> str: pattern = _json_str_arg(args_json, "pattern") or "?" path = _json_str_arg(args_json, "path") or "." return _pretty_prefix(f"* Grep '{pattern}' in '{path}' ") -def _grep_detail(_args_json: str, result: str) -> str: - """``grep_files`` outcome: matches and file count, or none/error.""" +def _regex_detail(_args_json: str, result: str) -> str: + """``regex_files`` outcome: matches and file count, or none/error.""" if result.startswith("error:"): return _pretty_segments((f"({result})", "red")) if result == "(no matches)": @@ -802,7 +802,7 @@ _PRETTY_LINES: dict[str, PrettyLine] = { "todo_clear": _todo_line("todo_clear"), "listdir": PrettyLine(prefix=_listdir_prefix, detail=_listdir_detail), "glob_paths": PrettyLine(prefix=_glob_prefix, detail=_glob_detail), - "grep_files": PrettyLine(prefix=_grep_prefix, detail=_grep_detail), + "regex_files": PrettyLine(prefix=_regex_prefix, detail=_regex_detail), "get_truncated": PrettyLine(prefix=_resume_prefix, detail=_resume_detail), "run_argv": PrettyLine(prefix=_run_argv_prefix, detail=_run_argv_detail), "run_argv_batch": PrettyLine(prefix=_run_argv_batch_prefix, detail=_run_argv_batch_detail), diff --git a/src/plyngent/tools/__init__.py b/src/plyngent/tools/__init__.py index 81b986d..9ab71dc 100644 --- a/src/plyngent/tools/__init__.py +++ b/src/plyngent/tools/__init__.py @@ -37,10 +37,10 @@ from .file import delete_path as delete_path from .file import edit_lineno as edit_lineno from .file import edit_replace as edit_replace from .file import glob_paths as glob_paths -from .file import grep_files as grep_files from .file import listdir as listdir from .file import move_path as move_path from .file import read_file as read_file +from .file import regex_files as regex_files from .file import tree as tree from .file import write_file as write_file from .net import NET_TOOLS as NET_TOOLS diff --git a/src/plyngent/tools/file/__init__.py b/src/plyngent/tools/file/__init__.py index 5753146..21868ea 100644 --- a/src/plyngent/tools/file/__init__.py +++ b/src/plyngent/tools/file/__init__.py @@ -7,9 +7,9 @@ from .fs_ops import delete_path as delete_path from .fs_ops import move_path as move_path from .get_truncated import get_truncated as get_truncated from .glob_paths import glob_paths as glob_paths -from .grep_files import grep_files as grep_files from .listdir import listdir as listdir from .read import read_file as read_file +from .regex_files import regex_files as regex_files from .tree import tree as tree from .write import write_file as write_file @@ -19,7 +19,7 @@ FILE_TOOLS = [ listdir, tree, glob_paths, - grep_files, + regex_files, edit_replace, edit_lineno, copy_path, diff --git a/src/plyngent/tools/file/grep_files.py b/src/plyngent/tools/file/regex_files.py similarity index 99% rename from src/plyngent/tools/file/grep_files.py rename to src/plyngent/tools/file/regex_files.py index 9ab9342..cb0cf03 100644 --- a/src/plyngent/tools/file/grep_files.py +++ b/src/plyngent/tools/file/regex_files.py @@ -130,7 +130,7 @@ def _compile_pattern(pattern: str, *, case_insensitive: bool) -> re.Pattern[str] @tool(tags=ToolTag.LOCAL | ToolTag.INSTANCE_STATE | ToolTag.READ_ONLY) -async def grep_files( +async def regex_files( pattern: str, path: str = ".", *, diff --git a/src/plyngent/tools/skills.py b/src/plyngent/tools/skills.py index 21cb40d..39eb470 100644 --- a/src/plyngent/tools/skills.py +++ b/src/plyngent/tools/skills.py @@ -5,7 +5,7 @@ optional bundled files — scripts, references, templates. Discovery, parsing, a path rules live in :mod:`plyngent.skills`; this module is the model-facing shell. Reading needs no grant: the host registers the roots as static read-only roots, -so ``read_file`` / ``grep_files`` see them too. Writing goes through the skill +so ``read_file`` / ``regex_files`` see them too. Writing goes through the skill tools' own grant (a per-call confirm, or ``[skills].allow_write``) and is always confined to a skill root — that is why these tools never inherit the directory-access path of ``request_directory_access``. @@ -30,9 +30,9 @@ from plyngent.skills import ( resolve_skill_file, validate_skill_name, ) -from plyngent.tools.file.grep_files import DEFAULT_MAX_MATCHES -from plyngent.tools.file.grep_files import grep_files as _grep_files from plyngent.tools.file.read import read_file as _read_file +from plyngent.tools.file.regex_files import DEFAULT_MAX_MATCHES +from plyngent.tools.file.regex_files import regex_files as _regex_files from plyngent.tools.workspace import ( DEFAULT_POLICY_CONFIRM_TIMEOUT_SECONDS, WorkspaceError, @@ -47,7 +47,7 @@ if TYPE_CHECKING: # Keeps a listing readable when a third-party root holds a pile of skills. _MAX_LISTED_ISSUES = 8 _SKILL_WRITE_POLICY_KEY = "skill_write_policy" -# ``path:line: content`` as ``grep_files`` formats it (the path is non-greedy so +# ``path:line: content`` as ``regex_files`` formats it (the path is non-greedy so # a Windows drive letter or a colon in a file name cannot split it wrongly). _HIT_RE = re.compile(r"^(?P.+?):(?P\d+): ?(?P.*)$") @@ -159,9 +159,9 @@ async def skill_read( def _hit_relative(skill: Skill, path: str) -> str: - """Rebase one ``grep_files`` hit path onto its skill directory. + """Rebase one ``regex_files`` hit path onto its skill directory. - Grep reports paths relative to the workspace root for files inside it and + The search reports paths relative to the workspace root for files inside it and absolute paths for files outside, so a skill directory can arrive either way (a project-scope skill root lives inside the workspace). Normalizing to an absolute path first covers both without guessing from the string alone. @@ -179,7 +179,7 @@ def _hit_relative(skill: Skill, path: str) -> str: def _skill_hits(skill: Skill, result: str) -> list[str]: - """Attribute one skill's grep lines to that skill.""" + """Attribute one skill's search lines to that skill.""" hits: list[str] = [] for line in result.splitlines(): match = _HIT_RE.match(line) @@ -229,7 +229,7 @@ async def skill_search( remaining = max_matches - len(hits) if remaining <= 0: break - result = await _grep_files.handler( + result = await _regex_files.handler( pattern, str(target.path), case_insensitive=case_insensitive, diff --git a/tests/test_agent/test_responses_bridge.py b/tests/test_agent/test_responses_bridge.py index d37827c..9bde384 100644 --- a/tests/test_agent/test_responses_bridge.py +++ b/tests/test_agent/test_responses_bridge.py @@ -79,7 +79,7 @@ def test_parallel_tool_outputs_interleaved_after_calls() -> None: ), AssistantFunctionToolCall( id="call_02_ABCD", - function=AssistantFunctionTool(name="grep_files", arguments='{"pattern":"todo"}'), + function=AssistantFunctionTool(name="regex_files", arguments='{"pattern":"todo"}'), ), ], ), diff --git a/tests/test_cli/test_display.py b/tests/test_cli/test_display.py index 10dfcfb..b5b409b 100644 --- a/tests/test_cli/test_display.py +++ b/tests/test_cli/test_display.py @@ -352,13 +352,13 @@ async def test_pretty_glob_truncated(capsys: pytest.CaptureFixture[str]) -> None async def test_pretty_grep(capsys: pytest.CaptureFixture[str]) -> None: result = "src/a.py:3: x = 1\nsrc/a.py:9: x = 2\nsrc/b.py:1: y = 3\n" - await render_events(_aiter([_pretty_call("grep_files", '{"pattern": "x"}'), _result(result)])) + await render_events(_aiter([_pretty_call("regex_files", '{"pattern": "x"}'), _result(result)])) out = capsys.readouterr().out assert "* Grep 'x' in '.' (3 matches in 2 files)" in out async def test_pretty_grep_no_matches(capsys: pytest.CaptureFixture[str]) -> None: - await render_events(_aiter([_pretty_call("grep_files", '{"pattern": "zzz"}'), _result("(no matches)")])) + await render_events(_aiter([_pretty_call("regex_files", '{"pattern": "zzz"}'), _result("(no matches)")])) out = capsys.readouterr().out assert "* Grep 'zzz' in '.' (no matches)" in out diff --git a/tests/test_skills/test_frontmatter.py b/tests/test_skills/test_frontmatter.py index ed3f0db..ad72807 100644 --- a/tests/test_skills/test_frontmatter.py +++ b/tests/test_skills/test_frontmatter.py @@ -61,7 +61,7 @@ def test_yaml_subset_types_lists_and_booleans() -> None: "read_only: true", "confirm: no", "retries: 3", - "allowed-tools: [read_file, grep_files]", + "allowed-tools: [read_file, regex_files]", "tags:", " - alpha", " - beta", @@ -72,7 +72,7 @@ def test_yaml_subset_types_lists_and_booleans() -> None: "read_only": True, "confirm": False, "retries": 3, - "allowed-tools": ["read_file", "grep_files"], + "allowed-tools": ["read_file", "regex_files"], "tags": ["alpha", "beta"], } diff --git a/tests/test_tools/test_catalog.py b/tests/test_tools/test_catalog.py index 61b8ad4..06579cd 100644 --- a/tests/test_tools/test_catalog.py +++ b/tests/test_tools/test_catalog.py @@ -61,7 +61,7 @@ def test_read_only_classification() -> None: "read_file", "get_truncated", "glob_paths", - "grep_files", + "regex_files", "listdir", "tree", "todo_list", diff --git a/tests/test_tools/test_grep_files.py b/tests/test_tools/test_regex_files.py similarity index 60% rename from tests/test_tools/test_grep_files.py rename to tests/test_tools/test_regex_files.py index 394e91a..daf1ece 100644 --- a/tests/test_tools/test_grep_files.py +++ b/tests/test_tools/test_regex_files.py @@ -2,46 +2,46 @@ from __future__ import annotations from pathlib import Path -from plyngent.tools.file.grep_files import grep_files +from plyngent.tools.file.regex_files import regex_files from tests.test_tools.helpers import call_sync -def test_grep_files_basic(workspace: object) -> None: +def test_regex_files_basic(workspace: object) -> None: assert isinstance(workspace, Path) _ = (workspace / "a.py").write_text("hello world\nfoo\n", encoding="utf-8") _ = (workspace / "b.py").write_text("nope\n", encoding="utf-8") - out = call_sync(grep_files, "hello") + out = call_sync(regex_files, "hello") assert "a.py:1:" in out assert "hello world" in out assert "b.py" not in out -def test_grep_files_case_insensitive(workspace: object) -> None: +def test_regex_files_case_insensitive(workspace: object) -> None: assert isinstance(workspace, Path) _ = (workspace / "t.txt").write_text("Hello\n", encoding="utf-8") - assert "no matches" in call_sync(grep_files, "hello") - out = call_sync(grep_files, "hello", case_insensitive=True) + assert "no matches" in call_sync(regex_files, "hello") + out = call_sync(regex_files, "hello", case_insensitive=True) assert "t.txt:1:" in out -def test_grep_files_invalid_regex(workspace: object) -> None: +def test_regex_files_invalid_regex(workspace: object) -> None: del workspace - assert "invalid regex" in call_sync(grep_files, "[") + assert "invalid regex" in call_sync(regex_files, "[") -def test_grep_files_skip_git(workspace: object) -> None: +def test_regex_files_skip_git(workspace: object) -> None: assert isinstance(workspace, Path) (workspace / ".git").mkdir() _ = (workspace / ".git" / "x").write_text("secret\n", encoding="utf-8") _ = (workspace / "ok.txt").write_text("secret\n", encoding="utf-8") - out = call_sync(grep_files, "secret") + out = call_sync(regex_files, "secret") assert "ok.txt" in out assert ".git" not in out -def test_grep_files_max_matches(workspace: object) -> None: +def test_regex_files_max_matches(workspace: object) -> None: assert isinstance(workspace, Path) lines = "\n".join(f"match {i}" for i in range(10)) _ = (workspace / "m.txt").write_text(lines + "\n", encoding="utf-8") - out = call_sync(grep_files, "match", max_matches=3) + out = call_sync(regex_files, "match", max_matches=3) assert "truncated" in out diff --git a/tests/test_tools/test_skills.py b/tests/test_tools/test_skills.py index f54ba5b..c8e16b5 100644 --- a/tests/test_tools/test_skills.py +++ b/tests/test_tools/test_skills.py @@ -198,7 +198,7 @@ async def test_skill_search_single_skill_and_errors(tmp_path: Path) -> None: async def test_skill_search_inside_the_workspace_rebases_the_hit(tmp_path: Path) -> None: """A project-scope skill root lives inside the workspace: one path, once. - Grep reports in-workspace files relative to the workspace root and outside + The search reports in-workspace files relative to the workspace root and outside ones absolutely, so the skill name must never be glued onto a path that already carries the root. """