Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion scripts/make-detection-fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ def canonical_tail(content: str, limit: int = DETECTOR_TAIL_LIMIT) -> str:
return "\n".join(lines[start:])


# The runtime names `DetectedAgent` carries, which are also the `<runtime>` path
# component of a fixture. Two of them are not the case name (`cursor-agent`,
# `qodercli`), so an unconstrained flag is easy to get wrong — and every wrong
# value would take the bounded-tail branch below without saying so.
# `test_agent_vocabulary_matches_the_swift_enum` fails when this list drifts.
DETECTED_AGENTS = (
"pi",
"omp",
"claude",
"codex",
"gemini",
"cursor-agent",
"cline",
"opencode",
"copilot",
"kimi",
"droid",
"amp",
"qodercli",
"qwen",
"grok",
)


def detection_screen_text(text: str, agent: str) -> str:
"""Port of `DetectedAgent.detectionScreenText(from:)`.

Expand Down Expand Up @@ -234,10 +258,11 @@ def main() -> int:
parser.add_argument(
"--agent",
required=True,
choices=DETECTED_AGENTS,
metavar="AGENT",
help="detector the fixture targets (the <runtime> path component, e.g. claude "
"or codex); claude keeps the full screen, every other agent takes the "
"bounded 24-line tail",
"bounded 24-line tail. One of: " + ", ".join(DETECTED_AGENTS),
)
parser.add_argument(
"--redact",
Expand Down
23 changes: 23 additions & 0 deletions scripts/test_make_detection_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
import importlib.util
import json
import pathlib
import re
import subprocess
import sys
import tempfile
import unittest

SCRIPT = pathlib.Path(__file__).resolve().parent / "make-detection-fixture.py"
DETECTED_AGENT_SWIFT = (
SCRIPT.parent.parent / "supacode/Domain/AgentDetection/DetectedAgent.swift"
)

_spec = importlib.util.spec_from_file_location("make_detection_fixture", SCRIPT)
fixture = importlib.util.module_from_spec(_spec)
Expand Down Expand Up @@ -116,6 +120,18 @@ def test_several_amounts_on_one_row(self):
self.assertEqual(len(applied), 2)



class AgentVocabulary(unittest.TestCase):
def test_agent_vocabulary_matches_the_swift_enum(self):
# The generator mirrors the detector's dispatch, so its agent names have
# to be the detector's. A hand-copied list drifts silently; this fails.
source = DETECTED_AGENT_SWIFT.read_text(encoding="utf-8")
body = source.split("enum DetectedAgent", 1)[1].split("var id:", 1)[0]
cases = re.findall(r'^\s*case (\w+)(?:\s*=\s*"([^"]+)")?\s*$', body, re.M)
self.assertTrue(cases, "no cases parsed from DetectedAgent.swift")
self.assertEqual(list(fixture.DETECTED_AGENTS), [raw or name for name, raw in cases])


class Reduction(unittest.TestCase):
def test_claude_keeps_the_full_screen(self):
# Mirrors `DetectedAgent.detectionScreenText(from:)`: trimming a Claude
Expand Down Expand Up @@ -175,6 +191,13 @@ def test_summary_never_names_what_it_replaced(self):
self.assertIn("/Users/usr", result.stderr)
self.assertIn("1 line", result.stderr)

def test_unknown_agent_is_rejected(self):
# Before the flag was constrained, "Claude" was not "claude" and so took
# the bounded-tail branch, silently dropping rows from a Claude capture.
result = self.run_script("row", agent="Claude")
self.assertEqual(result.returncode, 2)
self.assertIn("invalid choice", result.stderr)

def test_undecidable_replacement_fails_the_run(self):
result = self.run_script("│ owner name │", "--redact", "owner=❤️")
self.assertEqual(result.returncode, 1)
Expand Down
Loading