mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 05:50:44 +08:00
feat(tools): add grep and workspace profile, fix write path
- Add grep_tool: ripgrep with Python fallback; avoid --json with -l/-c. - Add workspace_profile_tool: scan, languages, tests, packages, git, CI hints; tests. - write_file: resolve relative paths via workspace root (no data/workspace prefix). - Update path guard and local public tool tests for new write behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
48190afbc1
commit
2b32d11f43
6 changed files with 1019 additions and 21 deletions
|
|
@ -123,7 +123,7 @@ def test_local_tool_integration_roundtrip(monkeypatch) -> None:
|
|||
tmpdir = Path(tempfile.mkdtemp(prefix="local_it_"))
|
||||
monkeypatch.setenv("OPS_WORKSPACE_ROOT", str(tmpdir))
|
||||
monkeypatch.setenv("AIA_ENABLE_RUN_COMMAND", "1")
|
||||
target_rel = "data/workspace/it_sample.txt"
|
||||
target_rel = "it_sample.txt"
|
||||
|
||||
out_write = write_spec.handler({"path": "it_sample.txt", "content": "line1\nline2\n", "mode": "overwrite"})
|
||||
assert out_write.get("ok") is True, out_write
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ class WorkspacePathGuardTests(unittest.TestCase):
|
|||
p = resolve_workspace_path(str(f))
|
||||
self.assertEqual(p, f.resolve())
|
||||
|
||||
def test_write_file_relative_path_defaults_to_data_workspace_subdir(self) -> None:
|
||||
def test_write_file_relative_path_resolves_under_workspace_root(self) -> None:
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{"OPS_WORKSPACE_ROOT": str(self.root), "OPS_WORKSPACE_EXTRA_ROOTS": "", "OPS_WORKSPACE_ALLOW_ANY_PATH": ""},
|
||||
|
|
@ -114,11 +114,11 @@ class WorkspacePathGuardTests(unittest.TestCase):
|
|||
with workspace_path_access_scope(None, None):
|
||||
r = spec.handler({"path": "generated.py", "content": "print('ok')\n", "mode": "overwrite"})
|
||||
self.assertTrue(r.get("ok"), r)
|
||||
expected = (self.root / "data" / "workspace" / "generated.py").resolve()
|
||||
expected = (self.root / "generated.py").resolve()
|
||||
self.assertEqual(str(expected), str(r.get("path")))
|
||||
self.assertTrue(expected.exists())
|
||||
|
||||
def test_write_file_relative_path_uses_workspace_namespace_scope(self) -> None:
|
||||
def test_write_file_relative_path_with_write_namespace_scope(self) -> None:
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{"OPS_WORKSPACE_ROOT": str(self.root), "OPS_WORKSPACE_EXTRA_ROOTS": "", "OPS_WORKSPACE_ALLOW_ANY_PATH": ""},
|
||||
|
|
@ -130,7 +130,7 @@ class WorkspacePathGuardTests(unittest.TestCase):
|
|||
self.assertEqual(current_workspace_write_namespace(), "ops")
|
||||
r = spec.handler({"path": "generated.py", "content": "print('ok')\n", "mode": "overwrite"})
|
||||
self.assertTrue(r.get("ok"), r)
|
||||
expected = (self.root / "data" / "workspace" / "generated.py").resolve()
|
||||
expected = (self.root / "generated.py").resolve()
|
||||
self.assertEqual(str(expected), str(r.get("path")))
|
||||
self.assertTrue(expected.exists())
|
||||
|
||||
|
|
|
|||
59
tests/test_workspace_profile_tool.py
Normal file
59
tests/test_workspace_profile_tool.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Unit tests for workspace_profile_tool helpers and handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from runtime.tools.path_guard import clear_workspace_path_access_for_tests, workspace_path_access_scope
|
||||
from runtime.tools.public import workspace_profile_tool as mod
|
||||
|
||||
|
||||
def test_walkable_dir_github_vs_vendor() -> None:
|
||||
assert mod._walkable_dir(".github") is True
|
||||
assert mod._walkable_dir(".gitlab") is True
|
||||
assert mod._walkable_dir("node_modules") is False
|
||||
assert mod._walkable_dir(".cache") is False
|
||||
|
||||
|
||||
def test_path_suggests_ci() -> None:
|
||||
assert mod._path_suggests_ci(".github/workflows/ci.yml")
|
||||
assert mod._path_suggests_ci("pkg/.circleci/config.yml")
|
||||
assert mod._path_suggests_ci("bitbucket-pipelines.yml")
|
||||
assert mod._path_suggests_ci("ci/Jenkinsfile")
|
||||
assert mod._path_suggests_ci(str(Path("x") / ".woodpecker" / "ci.yaml"))
|
||||
assert not mod._path_suggests_ci("src/main.py")
|
||||
|
||||
|
||||
def test_looks_like_test_file() -> None:
|
||||
assert mod._looks_like_test_file("tests/unit/test_x.py")
|
||||
assert mod._looks_like_test_file(str(Path("src") / "tests" / "a.py"))
|
||||
assert mod._looks_like_test_file("test_foo.py")
|
||||
assert not mod._looks_like_test_file("src/main.py")
|
||||
|
||||
|
||||
def test_detect_package_manager_nuget_csproj() -> None:
|
||||
files = [
|
||||
{"path": "src/App.csproj", "size": 12, "ext": ".csproj", "suffix": ".csproj"},
|
||||
]
|
||||
out = mod._detect_package_manager(files)
|
||||
assert out["detected"] is True
|
||||
assert "nuget" in out["managers"]
|
||||
assert "App.csproj" in out["config_files"]
|
||||
|
||||
|
||||
def test_workspace_profile_max_files_clamped(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("OPS_WORKSPACE_ROOT", str(tmp_path))
|
||||
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
|
||||
clear_workspace_path_access_for_tests()
|
||||
spec = mod.workspace_profile_tool()
|
||||
with workspace_path_access_scope(None, None):
|
||||
r = spec.handler({"root": ".", "max_files": 0})
|
||||
assert r.get("ok") is True
|
||||
assert (r.get("profile") or {}).get("total_files_scanned") == 1
|
||||
|
||||
with workspace_path_access_scope(None, None):
|
||||
r2 = spec.handler({"root": ".", "max_files": 999_999})
|
||||
assert r2.get("ok") is True
|
||||
assert (r2.get("profile") or {}).get("truncated") is False
|
||||
Loading…
Add table
Add a link
Reference in a new issue