Code Quality¶
The agent keeps slipping print() into production modules instead of using your logger, and bare except: clauses show up under deadline pressure. You want layered feedback: a fast advisory on every edit, a structural lint that finds patterns regex cannot, a behavioral nudge when the agent's narration starts repeating the antipattern, and a final LLM gate for ambiguous cases.
from __future__ import annotations
import ast
import re
from collections.abc import Iterator
from captain_hook import (
Allow,
Content,
Event,
InlineTests,
Input,
Signal,
Signals,
SourceEdits,
TestFile,
Warn,
hook,
lint,
llm_gate,
nudge,
)
PRINT_TESTS: InlineTests = {
Input(tool="Edit", file="src/app.py", content='import sys\nprint("debug")\n'): Warn(pattern="logger"),
Input(tool="Edit", file="src/app.py", content="logger.info('ok')\n"): Allow(),
}
hook(
Event.PostToolUse,
only_if=[SourceEdits(lang="py"), Content(r"^\s*print\(")],
skip_if=[TestFile()],
message="Use the project logger instead of print(). See docs/logging.md.",
tests=PRINT_TESTS,
)
def bare_excepts(node: ast.AST) -> Iterator[str]:
if isinstance(node, ast.ExceptHandler) and node.type is None:
yield f"line {node.lineno}: bare except"
lint(
bare_excepts,
message="Bare except clauses silently swallow errors: {violations}",
trigger="except",
)
nudge(
"You keep adding print()s after edits. Switch to logger.debug() and tail the log.",
signals=Signals(
patterns=[
Signal(pattern=r"print\(", weight=1, flags=re.MULTILINE),
Signal(pattern=r"debug[\s_-]print", weight=2, flags=re.IGNORECASE),
],
threshold=3,
window=8,
),
)
llm_gate(
"Does this diff add a print() that should be a logger call, where the surrounding "
"module already imports a logger? Block only if the prod print is unambiguous.",
message=lambda r: f"Replace print() with logger: {r.reasoning}",
only_if=[SourceEdits(lang="py"), Content(r"^\s*print\(")],
skip_if=[TestFile()],
max_fires=2,
)
What to learn: The same problem expressed at four escalating tiers. hook(... Content(...)) is a fast regex check on the edit; lint(...) parses the file's AST so you can flag structures (bare except) that don't show up as plain text; nudge(... signals=...) watches transcript narration to catch the agent talking about the antipattern; llm_gate(...) lets a model adjudicate the ambiguous "is this really a prod print?" cases. Combine tiers when speed-vs-precision tradeoffs matter.