diff --git a/aai_coding/harness.py b/aai_coding/harness.py index ef76dc1..522b32a 100644 --- a/aai_coding/harness.py +++ b/aai_coding/harness.py @@ -7,7 +7,11 @@ NO_TRUNCATE = 'Output pipe truncates below 20 lines. Drop the pipe or keep >=20: truncation is decided before the output exists, so keep enough to diagnose surprises.' NO_STDERR_MERGE = 'Do not merge stderr into stdout with 2>&1. Run the command bare: the harness pushes large output to a file by itself, and stderr kept separate makes a crash unmissable.' -_TRUNC = re.compile(r'\|\s*(tail|head)\s+(-n\s*)?-?([1-9]|1[0-9])\b') +# Two ways a head/tail pipe truncates below 20: an explicit count under 20, or no count at all - +# bare `head`/`tail` default to 10, so a countless pipe cuts exactly as hard as the `-10` we reject. +_TRUNC = re.compile(r'\|\s*(?:tail|head)' + r'(?:\s+(?:-n\s*)?-?(?:[1-9]|1[0-9])\b' # explicit: -5, -n 5, -19 + r'|(?=\s*(?:$|[|;&\n])))') # bare: defaults to 10 _MERGE = re.compile(r'2>\s*&\s*1') diff --git a/tests/test_harness.py b/tests/test_harness.py index 3884200..7a3c8b4 100644 --- a/tests/test_harness.py +++ b/tests/test_harness.py @@ -3,7 +3,18 @@ import pytest from shutil import which -from aai_coding.harness import claude_air, claude_drop_sentinel, claude_slop, synthetic_resume +from aai_coding.harness import bash_guard_msg, claude_air, claude_drop_sentinel, claude_slop, synthetic_resume + + +def test_bash_guard_bare_head_tail(): + "A countless head/tail keeps 10 lines, the same cut as the -10 form the guard already rejects" + assert bash_guard_msg('tar tzf x.tgz | head') + assert bash_guard_msg('ls ~ | tail') + assert bash_guard_msg('cat big.log | head | wc -l') + assert bash_guard_msg('ls | head; echo done') + assert bash_guard_msg('ls | header') is None # merely starts with those four letters + assert bash_guard_msg('cat f | head -c 200') is None # bytes, not lines + assert bash_guard_msg('git status | head -30') is None def test_synthetic_resume(tmp_path):