hermes-agent/tests/agent/test_system_prompt.py
Brooklyn Nicholson 76858f5c3e refactor(agent): model coding-context as a frozen RuntimeMode seam
Evolve coding-context from a pair of loose functions into one resolved-once
posture object so the coding/general distinction is decided in a single place
and read by every domain, rather than re-probed ad hoc.

- ContextProfile registry (coding/general): declarative posture data — toolset
  to collapse to, operating brief, plus model_hint/memory_policy seams for
  future routing/memory consumers.
- RuntimeMode: immutable, resolved once via resolve_runtime_mode(); exposes
  toolset_selection() and system_blocks(). Detection is not memoized so a
  long-lived gateway/TUI process can't pin a stale posture.
- Detection now fires on a git repo OR a recognised project root (pyproject,
  package.json, Cargo.toml, go.mod, AGENTS.md, …), walking up to the workspace
  root — additive, so a non-git project dir also counts.
- Enriched operating brief with Cursor-grade patterns: trace symbols before
  editing, never substitute chat code blocks for tool edits, re-read a file
  before retrying a failed patch, cap linter-fix loops (~3) before asking,
  verify deps against the manifest.
- system_prompt routes through coding_system_blocks(); cli/tui keep calling
  coding_selection() (now thin wrappers). Subagents inherit the posture for
  free via existing toolset inheritance + the shared prompt builder.

Back-compat: is_coding_context / coding_selection / build_coding_workspace_block
/ CODING_AGENT_GUIDANCE all preserved.
2026-06-10 00:44:31 -05:00

98 lines
3.5 KiB
Python

"""Tests for agent/system_prompt.py — context-file cwd wiring."""
from types import SimpleNamespace
from unittest.mock import patch
from agent.system_prompt import build_system_prompt_parts
def _make_agent(**overrides):
base = dict(
load_soul_identity=False,
skip_context_files=False,
valid_tool_names=[],
_task_completion_guidance=False,
_tool_use_enforcement=False,
_environment_probe=False,
_kanban_worker_guidance="",
_memory_store=None,
_memory_manager=None,
model="",
provider="",
platform="",
pass_session_id=False,
session_id="",
)
base.update(overrides)
return SimpleNamespace(**base)
def _captured_context_cwd(agent):
"""The cwd build_system_prompt_parts hands to build_context_files_prompt."""
captured = {}
def fake_context_files(cwd=None, skip_soul=False):
captured["cwd"] = cwd
return ""
with (
patch("run_agent.load_soul_md", return_value=""),
patch("run_agent.build_nous_subscription_prompt", return_value=""),
patch("run_agent.build_environment_hints", return_value=""),
patch("run_agent.build_context_files_prompt", side_effect=fake_context_files),
):
build_system_prompt_parts(agent)
return captured["cwd"]
class TestContextFileCwd:
def test_none_when_terminal_cwd_unset(self, monkeypatch):
# Unset → None, so discovery falls back to the launch dir inside
# build_context_files_prompt (the local-CLI #19242 contract).
monkeypatch.delenv("TERMINAL_CWD", raising=False)
assert _captured_context_cwd(_make_agent()) is None
def test_configured_dir_when_terminal_cwd_set(self, monkeypatch, tmp_path):
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
assert _captured_context_cwd(_make_agent()) == tmp_path
def _stable_prompt(agent):
with (
patch("run_agent.load_soul_md", return_value=""),
patch("run_agent.build_nous_subscription_prompt", return_value=""),
patch("run_agent.build_environment_hints", return_value=""),
patch("run_agent.build_context_files_prompt", return_value=""),
):
return build_system_prompt_parts(agent)["stable"]
class TestCodingContextBlock:
def test_injected_when_active(self, monkeypatch, tmp_path):
import subprocess
subprocess.run(["git", "-C", str(tmp_path), "init", "-q"], check=True)
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
agent = _make_agent(valid_tool_names=["read_file"], platform="cli")
stable = _stable_prompt(agent)
assert "coding agent" in stable
assert "Workspace" in stable
def test_absent_when_off(self, monkeypatch, tmp_path):
import subprocess
subprocess.run(["git", "-C", str(tmp_path), "init", "-q"], check=True)
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
agent = _make_agent(valid_tool_names=["read_file"], platform="cli")
# Drive the real path: force the resolved mode to "off" via config.
with patch("agent.coding_context._coding_mode", return_value="off"):
stable = _stable_prompt(agent)
assert "coding agent" not in stable
def test_absent_without_tools(self, monkeypatch, tmp_path):
import subprocess
subprocess.run(["git", "-C", str(tmp_path), "init", "-q"], check=True)
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
agent = _make_agent(valid_tool_names=[], platform="cli")
assert "coding agent" not in _stable_prompt(agent)