refactor: standardize --agent option usage, improve YAML registry fallback, and fix layout falsy-zero trap (J-1)
- In stop_session.sh and update_yaml_resumed.sh: standardize explicit --agent option and use resolve_agent_type_from_registry to read agent type from YAML/DB state rather than brittle suffix-only regex inference. - Update multi-agent-mux-stop/SKILL.md, multi-agent-mux-resume/SKILL.md, multi-agent-mux-create/SKILL.md, and deploy/INSTALL.md to standardize passing --agent explicitly. - Fix J-1 in layout.py: refactor _env_int(*names, default=None) to take an explicit default parameter, eliminating the falsy-zero trap so MAM_MIN_PANE_COLS=0 is respected. - Add regression and contract tests: test_j1_env_zero_min_cols_matches_flag_zero, test_j1_env_zero_min_rows_matches_flag_zero, test_comp_stop_agent_fallback_*, test_comp_docs_stop_examples_pass_agent. - Verified 100% UNANIMOUS PASS from Planner claude and Reviewers claude and cline.
This commit is contained in:
@@ -403,7 +403,79 @@ def test_headless_max_columns_growth_guard():
|
||||
d3 = compute_2xk_layout(headless(3), max_columns=2)
|
||||
assert d3.direction == "down" and not d3.is_overflow
|
||||
|
||||
# n=5 is the first odd n that can discriminate: n//2 == 2 == max_columns, so an
|
||||
# over-correction that also checked the cap on the odd branch would return
|
||||
# overflow here. n=3 has n//2 == 1 and cannot reach the check at all.
|
||||
d5 = compute_2xk_layout(headless(5), max_columns=2)
|
||||
assert d5.direction == "down" and not d5.is_overflow
|
||||
assert d5.reason == "headless_odd_down"
|
||||
|
||||
# When max_columns is not set, existing alternation is preserved (behavior neutrality)
|
||||
assert compute_2xk_layout(headless(4)).direction == "right"
|
||||
|
||||
|
||||
_LAYOUT_ENV_VARS = ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", "MAM_MIN_ROWS",
|
||||
"MAM_MIN_PANE_ROWS", "MAM_MAX_COLS", "MAM_MAX_PANE_COLS")
|
||||
|
||||
|
||||
def _run_layout(payload, args=(), env_extra=None):
|
||||
env = {**os.environ, "PYTHONPATH": os.path.abspath(".agents/skills")}
|
||||
for k in _LAYOUT_ENV_VARS:
|
||||
env.pop(k, None) # 호출자 셸의 오염 차단
|
||||
env.update(env_extra or {})
|
||||
res = subprocess.run([sys.executable, "-m", "lib_py.layout", "--json", *args],
|
||||
input=json.dumps(payload), capture_output=True, text=True, env=env)
|
||||
assert res.returncode == 0, res.stderr
|
||||
return json.loads(res.stdout)
|
||||
|
||||
|
||||
# height//2 = 15 < min_rows(20) 로 제약 분기 진입, width//2 = 25 가 min_cols 와 비교됨.
|
||||
_ZERO_TRAP = {"result": {"panes": [
|
||||
{"pane_id": "p1", "rect": {"x": 0, "y": 0, "width": 50, "height": 30}}]}}
|
||||
|
||||
|
||||
def test_j1_env_zero_min_cols_matches_flag_zero():
|
||||
"""J-1: MAM_MIN_PANE_COLS=0 must mean 0, not fall through to the 60 default."""
|
||||
flag = _run_layout(_ZERO_TRAP, ("--min-cols", "0"))
|
||||
assert flag["direction"] == "right" and flag["reason"] == "single_pane_height_constrained"
|
||||
for var in ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS"):
|
||||
assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var
|
||||
|
||||
|
||||
def test_j1_env_zero_min_rows_matches_flag_zero():
|
||||
flag = _run_layout(_ZERO_TRAP, ("--min-rows", "0"))
|
||||
assert flag["direction"] == "down" and flag["reason"] == "single_pane_split_down"
|
||||
for var in ("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS"):
|
||||
assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var
|
||||
|
||||
|
||||
def test_j1_nonzero_and_malformed_env_behaviour_unchanged():
|
||||
"""Behaviour neutrality: non-zero env still applies, and a lone typo still
|
||||
lands on the documented default instead of crashing on a None comparison."""
|
||||
assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) == \
|
||||
_run_layout(_ZERO_TRAP, ("--min-cols", "25"))
|
||||
assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "abc"}) == _run_layout(_ZERO_TRAP)
|
||||
|
||||
|
||||
def test_j1b_invalid_alias_does_not_shadow_the_documented_var():
|
||||
"""C-2: MAM_MIN_COLS is a legacy alias checked first; MAM_MIN_PANE_COLS is the
|
||||
name .mam.env.example documents. An unparsable value in the alias must be
|
||||
skipped, not abort the search and discard the documented setting.
|
||||
|
||||
Empty values already fell through (`if raw:`); this makes invalid values
|
||||
behave the same way. When every candidate is unusable, `default` still wins.
|
||||
"""
|
||||
good = _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"})
|
||||
assert good["direction"] == "right"
|
||||
# 별칭이 깨져 있어도 문서화된 변수가 적용된다
|
||||
assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo",
|
||||
"MAM_MIN_PANE_COLS": "25"}) == good
|
||||
# 0 도 마찬가지 (J-1 과의 상호작용)
|
||||
assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo",
|
||||
"MAM_MIN_PANE_COLS": "0"}) == \
|
||||
_run_layout(_ZERO_TRAP, ("--min-cols", "0"))
|
||||
# 모든 후보가 무효면 문서화된 기본값으로 흡수 (Rev.1 불변식 보존)
|
||||
assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo",
|
||||
"MAM_MIN_PANE_COLS": "bar"}) == _run_layout(_ZERO_TRAP)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user