tools/skills: list, read, and search skills

skill_list shows what was discovered (name, source root, directory, description)
together with the copies a name collision shadowed and the notes from repaired
frontmatter, so a third-party skill never disappears quietly. skill_read
addresses a skill by name and defaults to its SKILL.md, reusing read_file for
ranges, line numbers, and the continuation token; a bundled file is resolved
inside the skill directory only, symlinks included. skill_search greps every
visible skill (or one) and rebases each hit on its skill, which makes a match
attributable without absolute paths.

Reads need no grant: the host registers the roots as static read-only roots on
the workspace policy, so read_file/grep_files see skill directories too while
write access stays denied — skill writes will go through their own confirm.
Grep used to drop every file outside the primary workspace root, which also hid
hits under a granted directory; those now come back as absolute paths.
This commit is contained in:
2026-09-22 22:42:45 +08:00
parent a9f58428bf
commit 1d05da62c2
12 changed files with 599 additions and 3 deletions
+14 -1
View File
@@ -12,6 +12,7 @@ from plyngent.tools.chat import CHAT_TOOLS
from plyngent.tools.file import FILE_TOOLS
from plyngent.tools.net import NET_TOOLS
from plyngent.tools.process import PROCESS_TOOLS
from plyngent.tools.skills import SKILL_TOOLS
from plyngent.tools.todo import TODO_TOOLS
from plyngent.tools.vcs import VCS_TOOLS
@@ -19,7 +20,16 @@ from plyngent.tools.vcs import VCS_TOOLS
def test_default_tool_names_match_group_lists() -> None:
register_builtin_tools()
selected = default_tool_definitions(surface="local")
groups = [*ACCESS_TOOLS, *FILE_TOOLS, *PROCESS_TOOLS, *VCS_TOOLS, *CHAT_TOOLS, *TODO_TOOLS, *NET_TOOLS]
groups = [
*ACCESS_TOOLS,
*FILE_TOOLS,
*PROCESS_TOOLS,
*VCS_TOOLS,
*CHAT_TOOLS,
*TODO_TOOLS,
*SKILL_TOOLS,
*NET_TOOLS,
]
assert sorted(t.name for t in selected) == sorted(t.name for t in groups)
assert len(selected) == len(groups)
@@ -65,6 +75,9 @@ def test_read_only_classification() -> None:
"vcs_log",
"vcs_branch",
"fetch",
"skill_list",
"skill_read",
"skill_search",
}