feat(server): settings pack — 22 environment tools behind TOOL_PACKS, lean grows to 55, three groups, golden set +10
Task 24 (merge sanity) on main after the five track E groups merged.
- TOOL_PACK_NAMES = ("core", "pid", "settings"); PACK_TOOL_NAMES["settings"]
is exactly the 22 SECTION 20 tools. Styles and page setup stay in core.
- LEAN_TOOL_NAMES += page_setup_apply, batch_plot (53 -> 55).
- _GROUP_TAG_PRIORITY / _GROUP_TAG_LABELS: "style" moved up next to "pid",
"pagesetup" -> page_setup and "environment" -> environment added; the 28
SECTION 19/20 decorators gain their section tag, so _tool_groups() reports
styles 10 / page_setup 6 / environment 22 (23 groups, 204 tools) and
pack == group == section for the settings pack.
- SECTION headers, backends/base.py composition, contracts/__init__.py
exports and the layer_states feature entry were already correct after the
merge (read and confirmed, nothing edited).
- Pack text: config.py, .env.example, README config row; README <sub> line
204 tools in 23 groups; release snapshot 3259 collected tests (measured).
- docs/tool-inventory.json regenerated; registry snapshot re-derived.
- tests: test_tool_packs.py replaced (11 tests), test_capability_contract.py
+3 (composition, the agreed capability flags, layer_states honesty),
golden set +10 tuning cases (81 = 66 tuning + 15 holdout); all ten route
in the top 3 without alias edits.
Deviation from the plan text: TRACK_E_KEYS carries five keys, not six. The
plan's "mleaderstyle" (ezdxf True / com False) contradicts spec §8.1, which
retracted that key because the ActiveX route exists (§4.2); measured on the
tree: capability_keys() == {boundary_trace, chspace, interactive_prompt,
live_application, preferences, revcloud, wipeout}, and
ComBackend.mleaderstyle_create is a native implementation
(backends/com_backend.py:6752, is_capability_default -> False). The test now
also asserts the key is absent.
No new ActiveX call was written in this task, so no live probe was needed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
90c1194f7f
commit
70afd57831
+122
-14
@@ -1,4 +1,11 @@
|
||||
"""TOOL_PACKS: advertise only the vertical packs a client uses; core is always on."""
|
||||
# tests/test_tool_packs.py
|
||||
"""TOOL_PACKS: advertise only the vertical packs a client uses; core is always on.
|
||||
|
||||
v1.6 track E adds the `settings` pack: the 22 SECTION 20 environment tools.
|
||||
Styles (SECTION 18) and page setup / templates (SECTION 19) are drafting
|
||||
essentials and stay in `core`, so `TOOL_PACKS=core` hides the environment
|
||||
surface and nothing else.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -24,6 +31,63 @@ PID_TOOLS = {
|
||||
"pid_from_spec",
|
||||
}
|
||||
|
||||
STYLE_TOOLS = {
|
||||
"dimstyle_list",
|
||||
"dimstyle_create",
|
||||
"dimstyle_modify",
|
||||
"dimstyle_set_current",
|
||||
"textstyle_list",
|
||||
"textstyle_create",
|
||||
"textstyle_set_current",
|
||||
"mleaderstyle_list",
|
||||
"mleaderstyle_create",
|
||||
"drawing_apply_standard",
|
||||
}
|
||||
|
||||
PAGE_SETUP_TOOLS = {
|
||||
"page_setup_list",
|
||||
"page_setup_apply",
|
||||
"plot_style_list",
|
||||
"batch_plot",
|
||||
"drawing_template_list",
|
||||
"drawing_template_save",
|
||||
}
|
||||
|
||||
SETTINGS_TOOLS = {
|
||||
"document_list",
|
||||
"document_activate",
|
||||
"document_close",
|
||||
"layer_state_save",
|
||||
"layer_state_restore",
|
||||
"layer_state_list",
|
||||
"layer_state_delete",
|
||||
"view_named_save",
|
||||
"view_named_restore",
|
||||
"view_named_list",
|
||||
"ucs_list",
|
||||
"ucs_set",
|
||||
"ucs_restore",
|
||||
"system_launch",
|
||||
"system_preferences_get",
|
||||
"system_preferences_set",
|
||||
"drawing_properties_get",
|
||||
"drawing_properties_set",
|
||||
"system_variable_describe",
|
||||
"user_pick_point",
|
||||
"user_select",
|
||||
"system_prompt_message",
|
||||
}
|
||||
|
||||
LEAN_SETTINGS_ESSENTIALS = {
|
||||
"drawing_apply_standard",
|
||||
"page_setup_apply",
|
||||
"batch_plot",
|
||||
"dimstyle_set_current",
|
||||
"textstyle_set_current",
|
||||
}
|
||||
|
||||
ALL_PACKS = ["core", "pid", "settings"]
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(autouse=True)
|
||||
async def _restore(monkeypatch):
|
||||
@@ -32,29 +96,48 @@ async def _restore(monkeypatch):
|
||||
await server._apply_tool_profile("full")
|
||||
|
||||
|
||||
def test_pack_registry_names_real_tools_and_only_pid_ones():
|
||||
assert server.TOOL_PACK_NAMES == ("core", "pid")
|
||||
def test_pack_registry_names_real_tools_and_only_them():
|
||||
assert server.TOOL_PACK_NAMES == ("core", "pid", "settings")
|
||||
assert server.PACK_TOOL_NAMES["pid"] == frozenset(PID_TOOLS)
|
||||
assert server.PACK_TOOL_NAMES["settings"] == frozenset(SETTINGS_TOOLS)
|
||||
assert len(SETTINGS_TOOLS) == 22
|
||||
assert not (server.PACK_TOOL_NAMES["settings"] & server.PACK_TOOL_NAMES["pid"])
|
||||
|
||||
|
||||
async def test_core_only_hides_every_pid_tool_but_keeps_block_define(monkeypatch):
|
||||
async def test_every_pack_member_is_a_registered_tool():
|
||||
registered = {tool.name for tool in await server._registered_tools()}
|
||||
for pack, names in server.PACK_TOOL_NAMES.items():
|
||||
missing = names - registered
|
||||
assert not missing, f"pack {pack!r} names unregistered tools: {sorted(missing)}"
|
||||
|
||||
|
||||
async def test_core_only_hides_pid_and_settings_but_keeps_styles_and_page_setup(monkeypatch):
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "core")
|
||||
info = await server._apply_tool_profile("full")
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert PID_TOOLS <= disabled
|
||||
assert SETTINGS_TOOLS <= disabled
|
||||
assert not (STYLE_TOOLS & disabled), "styles are core drafting essentials"
|
||||
assert not (PAGE_SETUP_TOOLS & disabled), "page setup and templates are core"
|
||||
assert "block_define" not in disabled and "entity_set_xdata" not in disabled
|
||||
assert info["tool_packs"] == {
|
||||
"enabled": ["core"],
|
||||
"available": ["core", "pid"],
|
||||
"ignored": [],
|
||||
}
|
||||
assert info["tool_packs"] == {"enabled": ["core"], "available": ALL_PACKS, "ignored": []}
|
||||
|
||||
|
||||
async def test_all_is_the_default_and_enables_pid(monkeypatch):
|
||||
async def test_settings_pack_alone_keeps_pid_hidden(monkeypatch):
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "settings")
|
||||
info = await server._apply_tool_profile("full")
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert PID_TOOLS <= disabled
|
||||
assert not (SETTINGS_TOOLS & disabled)
|
||||
assert info["tool_packs"]["enabled"] == ["core", "settings"]
|
||||
|
||||
|
||||
async def test_all_is_the_default_and_enables_every_pack(monkeypatch):
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "all")
|
||||
info = await server._apply_tool_profile("full")
|
||||
assert not (PID_TOOLS & set(info["disabled_tools"]))
|
||||
assert info["tool_packs"]["enabled"] == ["core", "pid"]
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert not ((PID_TOOLS | SETTINGS_TOOLS) & disabled)
|
||||
assert info["tool_packs"]["enabled"] == ALL_PACKS
|
||||
|
||||
|
||||
async def test_unknown_pack_is_ignored_with_a_warning_and_core_cannot_be_dropped(
|
||||
@@ -65,7 +148,7 @@ async def test_unknown_pack_is_ignored_with_a_warning_and_core_cannot_be_dropped
|
||||
info = await server._apply_tool_profile("full")
|
||||
assert info["tool_packs"] == {
|
||||
"enabled": ["core", "pid"],
|
||||
"available": ["core", "pid"],
|
||||
"available": ALL_PACKS,
|
||||
"ignored": ["mech3000"],
|
||||
}
|
||||
assert "mech3000" in caplog.text
|
||||
@@ -79,9 +162,34 @@ async def test_lean_intersects_with_packs(monkeypatch):
|
||||
assert "entity_create_line" not in info["disabled_tools"]
|
||||
|
||||
|
||||
async def test_lean_carries_the_five_settings_essentials_and_nothing_from_the_settings_pack(
|
||||
monkeypatch,
|
||||
):
|
||||
"""Spec §8.2: lean = 55. The five are core-pack tools, so `TOOL_PACKS=core`
|
||||
leaves them on a lean surface; no environment tool is lean."""
|
||||
assert LEAN_SETTINGS_ESSENTIALS <= server.LEAN_TOOL_NAMES
|
||||
assert not (SETTINGS_TOOLS & server.LEAN_TOOL_NAMES)
|
||||
assert len(server.LEAN_TOOL_NAMES) == 55
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "core")
|
||||
info = await server._apply_tool_profile("lean")
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert not (LEAN_SETTINGS_ESSENTIALS & disabled)
|
||||
assert info["enabled_count"] == 55 - 3, "lean minus the three pid tools"
|
||||
|
||||
|
||||
async def test_the_three_track_e_sections_file_under_their_own_groups():
|
||||
"""Pack, group and SECTION are the same list, so `system_about` and
|
||||
`docs/tool-inventory.json` describe the surface the packs gate."""
|
||||
groups = await server._tool_groups()
|
||||
assert set(groups["styles"]) == STYLE_TOOLS
|
||||
assert set(groups["page_setup"]) == PAGE_SETUP_TOOLS
|
||||
assert set(groups["environment"]) == SETTINGS_TOOLS
|
||||
assert len(groups) == 23
|
||||
|
||||
|
||||
async def test_system_about_reports_packs():
|
||||
from fastmcp import Client
|
||||
|
||||
async with Client(server.mcp) as client:
|
||||
about = (await client.call_tool("system_about", {})).structured_content
|
||||
assert about["tool_packs"]["available"] == ["core", "pid"]
|
||||
assert about["tool_packs"]["available"] == ALL_PACKS
|
||||
|
||||
Reference in New Issue
Block a user