Skip to content

Commit ae558f2

Browse files
fix: classify git_branch as a git capability
Expose git_branch consistently to CLI, Web, and MCP profiles, regenerate the runtime inventory, and document the policy in every README. Add capability discovery, denial, and execution coverage.\n\nFixes #76
1 parent ddd696c commit ae558f2

8 files changed

Lines changed: 54 additions & 2 deletions

File tree

‎README.ja.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ kyrozen
280280

281281
31 個すべてのランタイムツールは、JSON アクションブロック内でプレーン文字列の `args` フィールドを受け付けます。ツール名、能力ラベル、MCP 入力 schema、実際の HTTP ルートは[生成されたランタイムインベントリ](docs/tool-inventory.md)を正とします:
282282

283+
`git_branch` を含む組み込み Git ツールには `git` 能力が必要で、`dynamic` はユーザー定義ツール専用です。
284+
283285
```json
284286
{"action": "read_file", "args": "README.md"}
285287
```

‎README.ko.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ kyrozen
280280

281281
모든 31개 런타임 도구는 JSON 액션 블록에서 일반 문자열 `args` 필드를 허용합니다. 도구 이름, capability 라벨, MCP 입력 schema 및 실제 HTTP 경로는[생성된 런타임 인벤토리](docs/tool-inventory.md)를 기준으로 합니다:
282282

283+
`git_branch`를 포함한 기본 Git 도구에는 `git` capability가 필요하며, `dynamic`은 사용자 정의 도구 전용입니다.
284+
283285
```json
284286
{"action": "read_file", "args": "README.md"}
285287
```

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,9 @@ The [generated runtime inventory](docs/tool-inventory.md) is authoritative for
367367
tool names, capability labels, MCP input schemas, and the live HTTP endpoint
368368
list:
369369

370+
Built-in Git tools, including `git_branch`, require the `git` capability;
371+
`dynamic` is reserved for user-defined tools.
372+
370373
```json
371374
{"action": "read_file", "args": "README.md"}
372375
```

‎README.zh-CN.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ kyrozen
277277
所有 31 项运行时工具在 JSON 动作块中接受纯字符串 `args` 字段。完整的
278278
工具名称、能力标签、MCP 输入 schema 和实时 HTTP 路由以[生成的运行时清单](docs/tool-inventory.md)为准:
279279

280+
包括 `git_branch` 在内的内置 Git 工具需要 `git` 能力;`dynamic` 仅用于用户定义的工具。
281+
280282
```json
281283
{"action": "read_file", "args": "README.md"}
282284
```

‎docs/self-evolution.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Historical verification snapshot: `51be33361422e55e1f2f00c33a0e0f8c56132a91`
99
(the post-#54 `main` revision, captured before this #55 documentation-only
1010
update). Snapshot date: 2026-09-04.
1111

12-
Current repository test count at this snapshot: **161 unittest cases**.
12+
Current repository test count at this snapshot: **162 unittest cases**.
1313

1414
## Verified surface
1515

‎docs/tool-inventory.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Run `make docs-check` after changing a tool, endpoint, or MCP contract.
2424
| `execute_terminal_command` | `shell` | Execute a terminal command. This is an alias for run_cmd. | `command`: string; required: `command` |
2525
| `find_files` | `read` | Find files matching a pattern. Args format: "pattern" or "pattern\|directory". | `pattern`: string, `directory`: string; required: `pattern` |
2626
| `git_add` | `git` | Stage files for commit. Args format: "file1 file2" or "." (stage all). | `args`: string |
27-
| `git_branch` | `dynamic` | List or manage git branches. Args format: "" (list all), "branch_name" (create), | `args`: string |
27+
| `git_branch` | `git` | List or manage git branches. Args format: "" (list all), "branch_name" (create), | `args`: string |
2828
| `git_checkout` | `git` | Switch branches or restore files. Args format: "branch_name" (switch), | `args`: string |
2929
| `git_clone` | `git` | Clone a git repository. Args format: "url" or "url\|destination". | `url`: string, `destination`: string; required: `url` |
3030
| `git_commit` | `git` | Commit staged changes. Args format: '"commit message"' (quotes recommended). | `args`: string |

‎tests/test_server.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,48 @@ def test_server_profiles_keep_workspace_tools_and_gate_reset(self):
258258
with patch.dict(os.environ, {"KYROZEN_MCP_CAPABILITIES": "full"}):
259259
self.assertIn("git_reset", server._allowed_server_tools("mcp"))
260260

261+
def test_git_branch_uses_git_capability_across_surfaces(self):
262+
from tools import tool_capability
263+
264+
self.assertEqual(tool_capability("git_branch"), "git")
265+
with patch.dict(os.environ, {
266+
"KYROZEN_MCP_CAPABILITIES": "workspace",
267+
"KYROZEN_WEB_CAPABILITIES": "workspace",
268+
}):
269+
self.assertIn("git_branch", server._allowed_server_tools("mcp"))
270+
self.assertIn("git_branch", server._allowed_server_tools("web"))
271+
listing = TestClient(server.app).post("/mcp", json={
272+
"jsonrpc": "2.0", "id": 76, "method": "tools/list", "params": {},
273+
}).json()
274+
self.assertIn("git_branch", {item["name"] for item in listing["result"]["tools"]})
275+
276+
with patch.dict(os.environ, {
277+
"KYROZEN_MCP_CAPABILITIES": "readonly",
278+
"KYROZEN_WEB_CAPABILITIES": "readonly",
279+
}):
280+
self.assertNotIn("git_branch", server._allowed_server_tools("mcp"))
281+
self.assertNotIn("git_branch", server._allowed_server_tools("web"))
282+
denied = TestClient(server.app).post("/mcp", json={
283+
"jsonrpc": "2.0", "id": 77, "method": "tools/call",
284+
"params": {"name": "git_branch", "arguments": {"args": ""}},
285+
}).json()
286+
self.assertEqual(denied["error"]["code"], -32001)
287+
288+
with tempfile.TemporaryDirectory(prefix="openkyrozen-git-branch-") as directory:
289+
root = Path(directory).resolve()
290+
subprocess.run(["git", "init", "--quiet", str(root)], check=True)
291+
previous_root = server._agent._get_workspace_root()
292+
server._agent._set_workspace_root(root)
293+
try:
294+
with patch.dict(os.environ, {"KYROZEN_MCP_CAPABILITIES": "workspace"}):
295+
allowed = TestClient(server.app).post("/mcp", json={
296+
"jsonrpc": "2.0", "id": 78, "method": "tools/call",
297+
"params": {"name": "git_branch", "arguments": {"args": ""}},
298+
}).json()
299+
finally:
300+
server._agent._set_workspace_root(previous_root)
301+
self.assertFalse(allowed["result"]["isError"])
302+
261303
def test_cli_approval_denies_noninteractive_high_impact_action(self):
262304
with patch.object(server._agent, "_EXECUTION_SURFACE", "cli"):
263305
with patch.dict(os.environ, {"KYROZEN_APPROVAL_MODE": "dangerous"}):

‎tools.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,7 @@ def browser_close(args: str) -> str:
10481048
"search_web": "network",
10491049
"read_webpage": "network",
10501050
"git_clone": "git",
1051+
"git_branch": "git",
10511052
"analyze_remote_repo": "network",
10521053
"browser_open": "browser",
10531054
"browser_snapshot": "browser",

0 commit comments

Comments
 (0)