docs(tools): apply review round-1 findings (cwd notes)

- docs/tools.md: sync the tool-JSON metadata-keys table to _META_KEYS —
  it had drifted to 3 of 8 keys (coordinator, interactive, kind_variants
  were already missing; cwd_note/workspace_note are new).
- tests: cover the third note-rebuild trigger (_drop_mcp_surface) with a
  count==1 assertion on both lanes, and pin the deliberately uniform
  workspace_note wording across the fs tools so a one-file reword cannot
  drift the copies apart.
This commit is contained in:
Patrick Buckley
2026-07-19 04:09:22 -07:00
parent 460308241d
commit 8d1190d17a
2 changed files with 33 additions and 6 deletions
+12 -6
View File
@@ -28,13 +28,19 @@ schema plus turnstone-specific metadata keys:
}
```
**Metadata keys** (stripped before sending the schema to the model):
**Metadata keys** (stripped before sending the schema to the model; the full
set lives in `_META_KEYS` in `turnstone/core/tools.py`):
| Key | Type | Meaning |
|----------------|------|---------|
| `task_agent` | bool | Tool is available to task sub-agents. |
| `auto_approve` | bool | Tool runs without user confirmation (read-only, safe operations). |
| `primary_key` | str | When the model sends a bare string instead of JSON args, map it to this parameter name. |
| Key | Type | Meaning |
|------------------|------|---------|
| `task_agent` | bool | Tool is available to task sub-agents. |
| `coordinator` | bool | Tool is available to coordinator sessions. Without `interactive: true` alongside it, this reads as coord-only and the tool is stripped from interactive sessions. |
| `interactive` | bool | Opt a `coordinator: true` tool back into interactive sessions (dual-kind tools like `memory`). |
| `auto_approve` | bool | Tool runs without user confirmation (read-only, safe operations). |
| `primary_key` | str | When the model sends a bare string instead of JSON args, map it to this parameter name. |
| `kind_variants` | dict | Per-kind description / parameter-schema overlays so each session kind sees only the surface it can use (see `memory.json`). |
| `cwd_note` | str | Sentence appended to the description at session build time with `{working_dir}` substituted — declare on tools whose semantics depend on the process working directory (see `bash.json`, `apply_cwd_context`). |
| `workspace_note` | str | Companion sentence naming the operator-configured workspace directory, `{workspace_dir}` substituted; dropped when no workspace is configured. |
---
+21
View File
@@ -123,6 +123,14 @@ class TestNoteMetadataInvariants:
assert "cwd_note" not in t["function"]
assert "workspace_note" not in t["function"]
def test_workspace_note_wording_uniform(self):
# The workspace fact is one node-level value, so its sentence is
# deliberately identical across the fs tools (unlike cwd_note, whose
# prose is per-tool). Guards a one-file reword from drifting the
# copies apart, independent of the exact wording.
notes = {_META[name]["workspace_note"] for name in _FS_TOOLS}
assert len(notes) == 1, notes
# ---------------------------------------------------------------------------
# ChatSession build sites
@@ -208,3 +216,16 @@ class TestSessionCwdNotes:
session._on_mcp_tools_changed()
assert _desc(session._tools, "bash").count(f"Commands run in {os.getcwd()}") == 1
assert _desc(session._task_tools, "bash").count(f"Commands run in {os.getcwd()}") == 1
def test_mcp_drop_surface_keeps_single_note(self, tmp_db):
# The MCP-disconnect rebuild (_drop_mcp_surface, reached via resume()
# adopting an MCP-off persona) is the third rebuild trigger — the note
# must survive it, exactly once, on both lanes.
mock_mcp = MagicMock()
mock_mcp.get_tools.return_value = []
with patch("turnstone.core.session.get_workspace_dir", return_value=None):
session = _make_session(mcp_client=mock_mcp)
session._drop_mcp_surface()
assert session._mcp_client is None
assert _desc(session._tools, "bash").count(f"Commands run in {os.getcwd()}") == 1
assert _desc(session._task_tools, "bash").count(f"Commands run in {os.getcwd()}") == 1