This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/timmy/test_tool_safety.py
Alexander Whitestone 9d78eb31d1 ruff (#169)
* polish: streamline nav, extract inline styles, improve tablet UX

- Restructure desktop nav from 8+ flat links + overflow dropdown into
  5 grouped dropdowns (Core, Agents, Intel, System, More) matching
  the mobile menu structure to reduce decision fatigue
- Extract all inline styles from mission_control.html and base.html
  notification elements into mission-control.css with semantic classes
- Replace JS-built innerHTML with secure DOM construction in
  notification loader and chat history
- Add CONNECTING state to connection indicator (amber) instead of
  showing OFFLINE before WebSocket connects
- Add tablet breakpoint (1024px) with larger touch targets for
  Apple Pencil / stylus use and safe-area padding for iPad toolbar
- Add active-link highlighting in desktop dropdown menus
- Rename "Mission Control" page title to "System Overview" to
  disambiguate from the chat home page
- Add "Home — Timmy Time" page title to index.html

https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h

* fix(security): move auth-gate credentials to environment variables

Hardcoded username, password, and HMAC secret in auth-gate.py replaced
with os.environ lookups. Startup now refuses to run if any variable is
unset. Added AUTH_GATE_SECRET/USER/PASS to .env.example.

https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h

* refactor(tooling): migrate from black+isort+bandit to ruff

Replace three separate linting/formatting tools with a single ruff
invocation. Updates tox.ini (lint, format, pre-push, pre-commit envs),
.pre-commit-config.yaml, and CI workflow. Fixes all ruff errors
including unused imports, missing raise-from, and undefined names.
Ruff config maps existing bandit skips to equivalent S-rules.

https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-11 12:23:35 -04:00

114 lines
3.9 KiB
Python

"""Tests for timmy.tool_safety — classification, extraction, and formatting."""
from timmy.tool_safety import (
extract_tool_calls,
format_action_description,
get_impact_level,
requires_confirmation,
)
# ---------------------------------------------------------------------------
# requires_confirmation
# ---------------------------------------------------------------------------
class TestRequiresConfirmation:
def test_dangerous_tools(self):
for tool in ("shell", "python", "write_file", "aider", "plan_and_execute"):
assert requires_confirmation(tool) is True
def test_safe_tools(self):
for tool in ("web_search", "calculator", "read_file", "list_files"):
assert requires_confirmation(tool) is False
def test_unknown_defaults_to_dangerous(self):
assert requires_confirmation("totally_unknown") is True
# ---------------------------------------------------------------------------
# extract_tool_calls
# ---------------------------------------------------------------------------
class TestExtractToolCalls:
def test_arguments_format(self):
text = (
'Creating dir. {"name": "shell", "arguments": {"args": ["mkdir", "-p", "/tmp/test"]}}'
)
calls = extract_tool_calls(text)
assert len(calls) == 1
assert calls[0][0] == "shell"
assert calls[0][1]["args"] == ["mkdir", "-p", "/tmp/test"]
def test_parameters_format(self):
text = 'Result: {"name": "python", "parameters": {"code": "print(1+1)"}}'
calls = extract_tool_calls(text)
assert len(calls) == 1
assert calls[0][0] == "python"
def test_multiple_calls(self):
text = (
'Step 1: {"name": "shell", "arguments": {"args": ["mkdir", "/tmp/a"]}} '
'Step 2: {"name": "write_file", "arguments": {"file_name": "/tmp/a/f.md", "contents": "hi"}}'
)
calls = extract_tool_calls(text)
assert len(calls) == 2
def test_empty_and_none(self):
assert extract_tool_calls("") == []
assert extract_tool_calls(None) == []
assert extract_tool_calls("Just normal text.") == []
def test_malformed_json(self):
text = '{"name": "shell", "arguments": {not valid json}}'
assert extract_tool_calls(text) == []
# ---------------------------------------------------------------------------
# format_action_description
# ---------------------------------------------------------------------------
class TestFormatActionDescription:
def test_shell_command(self):
desc = format_action_description("shell", {"command": "ls -la /tmp"})
assert "ls -la /tmp" in desc
def test_shell_args_list(self):
desc = format_action_description("shell", {"args": ["mkdir", "-p", "/tmp/t"]})
assert "mkdir -p /tmp/t" in desc
def test_write_file(self):
desc = format_action_description(
"write_file", {"file_name": "/tmp/f.md", "contents": "hello world"}
)
assert "/tmp/f.md" in desc
assert "11 chars" in desc
def test_python(self):
desc = format_action_description("python", {"code": "print(42)"})
assert "print(42)" in desc
def test_unknown_tool(self):
desc = format_action_description("custom_tool", {"key": "value"})
assert "custom_tool" in desc
# ---------------------------------------------------------------------------
# get_impact_level
# ---------------------------------------------------------------------------
class TestGetImpactLevel:
def test_high(self):
assert get_impact_level("shell") == "high"
assert get_impact_level("python") == "high"
def test_medium(self):
assert get_impact_level("write_file") == "medium"
assert get_impact_level("aider") == "medium"
def test_low(self):
assert get_impact_level("web_search") == "low"
assert get_impact_level("unknown") == "low"