#685 seedgo: subcommand_help standard — enforces every entry point intercepts <cmd> --help before dispatch (explicit guard or argparse parse_known_args), else <cmd> --help executes the command. 21 tests, cwd-portable (_AIPASS_ROOT anchor). Checker verified independently: 7/17 comply, 10 offenders.

This commit is contained in:
AIOSAI
2026-07-10 17:58:41 -07:00
parent f4d067a3cc
commit 948d2ed535
3 changed files with 751 additions and 0 deletions
@@ -0,0 +1,279 @@
# =================== AIPass ====================
# Name: subcommand_help_check.py
# Description: Subcommand Help Standards Checker Handler
# Version: 1.0.0
# Created: 2026-07-10
# Modified: 2026-07-10
# =============================================
"""Subcommand Help Standards Checker Handler.
Validates that branch entry points handle <cmd> --help by showing
subcommand-specific help — never executing the command, never silently
falling back to top-level help.
THE CONTRACT:
Every entry point that routes subcommands must intercept --help in the
remaining args (after command extraction) BEFORE dispatching to handlers.
WHAT PASSES (any one of):
a) Explicit subcommand --help guard on remaining args
b) argparse with parse_known_args (absorbs --help from any position)
Only entry point files are checked (apps/{branch}.py).
"""
import ast
from pathlib import Path
from typing import Dict
from aipass.prax import logger
from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed
from aipass.seedgo.apps.handlers.json import json_handler
AUDIT_SCOPE = "entry_point"
_TOPLEVEL_ARG_NAMES = frozenset({"args", "argv"})
_HELP_STRINGS = frozenset({"--help", "-h"})
_ENTRY_NAMES = {"main", "handle_command"}
def _called_names(func_node: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]:
"""Return names of functions called directly from func_node's body."""
names: set[str] = set()
for node in ast.walk(func_node):
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
names.add(node.func.id)
return names
def _find_entry_functions(tree: ast.Module) -> list[ast.FunctionDef | ast.AsyncFunctionDef]:
"""Return entry functions and their direct delegates from the module top level."""
all_funcs: dict[str, ast.FunctionDef | ast.AsyncFunctionDef] = {}
for node in ast.iter_child_nodes(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
all_funcs[node.name] = node
targets: list[ast.FunctionDef | ast.AsyncFunctionDef] = []
for name in _ENTRY_NAMES:
if name not in all_funcs:
continue
func = all_funcs[name]
targets.append(func)
for called in _called_names(func):
if called in all_funcs and called.startswith("_") and called not in _ENTRY_NAMES:
targets.append(all_funcs[called])
return targets
def _has_argparse_known_args(func_node: ast.AST) -> bool:
"""Return True if the function uses argparse parse_known_args."""
for node in ast.walk(func_node):
if not isinstance(node, ast.Call):
continue
if isinstance(node.func, ast.Attribute) and node.func.attr == "parse_known_args":
return True
return False
def _compare_has_help_string(node: ast.Compare) -> bool:
"""Return True if a Compare node involves a --help string constant."""
for part in [node.left, *node.comparators]:
if isinstance(part, ast.Constant) and part.value in _HELP_STRINGS:
return True
if isinstance(part, (ast.List, ast.Tuple, ast.Set)):
for elt in part.elts:
if isinstance(elt, ast.Constant) and elt.value in _HELP_STRINGS:
return True
return False
def _is_subscript_on_name(node: ast.expr, names: frozenset[str]) -> bool:
"""Return True if node is name[N] where name is in the given set."""
if isinstance(node, ast.Subscript) and isinstance(node.value, ast.Name):
return node.value.id in names
return False
def _is_toplevel_help_check(node: ast.Compare) -> bool:
"""Return True if this is a top-level args[0] --help check."""
return _is_subscript_on_name(node.left, _TOPLEVEL_ARG_NAMES)
def _is_help_in_nonargs_var(node: ast.Compare) -> bool:
"""Check for '"--help" in some_var' where some_var is not args/argv."""
if not isinstance(node.left, ast.Constant) or node.left.value not in _HELP_STRINGS:
return False
if not any(isinstance(op, ast.In) for op in node.ops):
return False
return any(isinstance(c, ast.Name) and c.id not in _TOPLEVEL_ARG_NAMES for c in node.comparators)
def _is_nonargs_subscript_help(node: ast.Compare) -> bool:
"""Check for 'remaining[0] in ["--help", ...]' where remaining != args."""
left = node.left
if not isinstance(left, ast.Subscript) or not isinstance(left.value, ast.Name):
return False
return left.value.id not in _TOPLEVEL_ARG_NAMES
def _has_subcommand_help_guard(func_node: ast.AST, func_name: str = "") -> bool:
"""Return True if the function has a subcommand-level --help check.
Detects patterns like:
remaining_args[0] in ["--help", "-h"]
"--help" in remaining_args
rest[0] == "--help"
where the variable is NOT the raw args/argv.
In handle_command(), args IS the subcommand args (not full argv),
so args[0] checks there count as subcommand guards.
"""
args_are_subcommand = func_name == "handle_command"
for node in ast.walk(func_node):
if not isinstance(node, ast.Compare):
continue
if not _compare_has_help_string(node):
continue
if _is_toplevel_help_check(node):
if args_are_subcommand:
return True
continue
if _is_subscript_on_name(node.left, _TOPLEVEL_ARG_NAMES):
if args_are_subcommand:
return True
continue
if _is_help_in_nonargs_var(node):
return True
if _is_nonargs_subscript_help(node):
return True
if isinstance(node.left, ast.Name) and node.left.id not in _TOPLEVEL_ARG_NAMES:
return True
return False
def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
"""Check if entry point handles subcommand --help."""
path = Path(module_path)
if is_bypassed(module_path, "subcommand_help", bypass_rules=bypass_rules):
return {
"passed": True,
"checks": [{"name": "Bypassed", "passed": True, "message": "Standard bypassed via .seedgo/bypass.json"}],
"score": 100,
"standard": "SUBCOMMAND_HELP",
}
if not path.exists():
return {
"passed": False,
"checks": [{"name": "File exists", "passed": False, "message": f"File not found: {module_path}"}],
"score": 0,
"standard": "SUBCOMMAND_HELP",
}
if path.parent.name != "apps":
return {
"passed": True,
"checks": [{"name": "Subcommand help", "passed": True, "message": "Not an entry point (skipped)"}],
"score": 100,
"standard": "SUBCOMMAND_HELP",
}
try:
source = path.read_text(encoding="utf-8")
except Exception as e:
logger.info("Cannot read %s: %s", path, e)
return {
"passed": False,
"checks": [{"name": "File readable", "passed": False, "message": f"Error reading file: {e}"}],
"score": 0,
"standard": "SUBCOMMAND_HELP",
}
try:
tree = ast.parse(source, filename=str(path))
except SyntaxError as e:
logger.info("Skipped %s: SyntaxError during parse", path)
return {
"passed": False,
"checks": [{"name": "File parseable", "passed": False, "message": f"Syntax error: {e}"}],
"score": 0,
"standard": "SUBCOMMAND_HELP",
}
entry_funcs = _find_entry_functions(tree)
if not entry_funcs:
return {
"passed": True,
"checks": [
{
"name": "Subcommand help",
"passed": True,
"message": "No main/handle_command entry function found (skipped)",
}
],
"score": 100,
"standard": "SUBCOMMAND_HELP",
}
for func in entry_funcs:
if _has_argparse_known_args(func):
json_handler.log_operation(
"check_completed",
{"file": str(module_path), "score": 100, "standard": "subcommand_help"},
)
return {
"passed": True,
"checks": [
{
"name": "Subcommand help",
"passed": True,
"message": f"argparse parse_known_args in {func.name}() absorbs --help",
}
],
"score": 100,
"standard": "SUBCOMMAND_HELP",
}
if _has_subcommand_help_guard(func, func.name):
json_handler.log_operation(
"check_completed",
{"file": str(module_path), "score": 100, "standard": "subcommand_help"},
)
return {
"passed": True,
"checks": [
{
"name": "Subcommand help",
"passed": True,
"message": f"Subcommand --help guard found in {func.name}()",
}
],
"score": 100,
"standard": "SUBCOMMAND_HELP",
}
func_names = ", ".join(f.name for f in entry_funcs)
json_handler.log_operation(
"check_completed",
{"file": str(module_path), "score": 0, "standard": "subcommand_help"},
)
return {
"passed": False,
"checks": [
{
"name": "Subcommand help",
"passed": False,
"message": (
f"No subcommand --help guard in {func_names}() — "
"<cmd> --help will execute the command or fall to top-level help"
),
}
],
"score": 0,
"standard": "SUBCOMMAND_HELP",
}
@@ -0,0 +1,75 @@
# =================== AIPass ====================
# Name: subcommand_help_content.py
# Description: Subcommand Help Standards Content
# Version: 1.0.0
# Created: 2026-07-10
# Modified: 2026-07-10
# =============================================
"""Subcommand Help Standards Content.
Provides Rich-formatted reference text for the subcommand help standard.
"""
from aipass.seedgo.apps.handlers.json import json_handler
def get_subcommand_help_standards() -> str:
"""Return Rich-formatted subcommand help standards text."""
json_handler.log_operation("standard_content_queried", {"standard": "subcommand_help"})
return """[bold white]SUBCOMMAND HELP STANDARD[/bold white]
[yellow]PURPOSE:[/yellow]
Every branch entry point must handle [bold]<cmd> --help[/bold] by showing
that subcommand's help — never execute the command, never silently
fall back to top-level help.
[yellow]THE CONTRACT:[/yellow]
[dim]drone @branch cmd --help[/dim] → shows cmd-specific help
[dim]drone @branch cmd --help[/dim] ✗ executes cmd (side-effect risk)
[dim]drone @branch cmd --help[/dim] ✗ shows top-level help (unhelpful)
[yellow]CANONICAL PATTERN (module-discovery branches):[/yellow]
[dim]command = args[0][/dim]
[dim]remaining = args[1:][/dim]
[bold cyan]# Subcommand --help guard (REQUIRED)[/bold cyan]
[dim]if remaining and remaining[0] in ["--help", "-h"]:[/dim]
[dim] for module in modules:[/dim]
[dim] if module.handle_command(command, ["--help"]):[/dim]
[dim] return 0[/dim]
[dim] print_help() # fallback to top-level[/dim]
[dim] return 0[/dim]
[dim]# Normal dispatch (only reached if NOT --help)[/dim]
[dim]if route_command(command, remaining, modules):[/dim]
[dim] return 0[/dim]
[yellow]ALTERNATIVE PATTERNS (also accepted):[/yellow]
[bold cyan]argparse with parse_known_args:[/bold cyan]
[dim]parser.add_argument("--help", action="store_true", dest="show_help")[/dim]
[dim]parsed, remaining = parser.parse_known_args()[/dim]
[dim]if parsed.show_help:[/dim]
[dim] all_args = ["--help"] + all_args # pass to handler[/dim]
[bold cyan]Post-dispatch fallback:[/bold cyan]
[dim]if not route_command(command, remaining, modules):[/dim]
[dim] if remaining and remaining[0] in ["--help", "-h"]:[/dim]
[dim] print_module_help(command, modules)[/dim]
[yellow]WHAT THE CHECKER DETECTS:[/yellow]
[green]✓[/green] A [bold]--help[/bold] comparison on remaining/subcommand args (not args[0])
[green]✓[/green] argparse [bold]parse_known_args()[/bold] call (absorbs --help)
[red]✗[/red] Only top-level --help check (args[0] in ["--help", ...])
[red]✗[/red] No --help handling at all
[yellow]KEY RULES:[/yellow]
[bold white]Intercept before dispatch[/bold white] — don't let --help reach the handler
[bold white]Show subcommand help[/bold white] — not top-level help
[bold white]Never execute[/bold white] — --help must never trigger side effects
[bold white]Scope: entry points only[/bold white] — apps/{branch}.py files"""
@@ -0,0 +1,397 @@
# =================== AIPass ====================
# Name: test_subcommand_help.py
# Description: Tests for subcommand_help_check.py
# Version: 1.0.0
# Created: 2026-07-10
# Modified: 2026-07-10
# =============================================
"""Tests for subcommand_help_check — subcommand --help guard detection."""
from pathlib import Path
import pytest
from unittest.mock import MagicMock
@pytest.fixture(autouse=True)
def _mock_infrastructure(monkeypatch):
import sys
mock_logger = MagicMock()
mock_json_handler = MagicMock()
mock_json_handler.log_operation = MagicMock(return_value=True)
prax_mod = MagicMock()
prax_mod.logger = mock_logger
monkeypatch.setitem(sys.modules, "aipass.prax", prax_mod)
json_pkg = MagicMock()
json_pkg.json_handler = mock_json_handler
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.json", json_pkg)
json_mod = MagicMock()
json_mod.log_operation = mock_json_handler.log_operation
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.json.json_handler", json_mod)
bypass_pkg = MagicMock()
bypass_ignore = MagicMock()
bypass_ignore.get_template_ignore_patterns = MagicMock(return_value=[])
from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed as real_is_bypassed
bypass_utils = MagicMock()
bypass_utils.is_bypassed = real_is_bypassed
bypass_pkg.utils = bypass_utils
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass", bypass_pkg)
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass.ignore_handler", bypass_ignore)
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass.utils", bypass_utils)
for mod_name in ["aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check"]:
monkeypatch.delitem(sys.modules, mod_name, raising=False)
def _entry_file(tmp_path, source):
"""Create a file under an apps/ directory to pass entry-point check."""
apps_dir = tmp_path / "apps"
apps_dir.mkdir()
f = apps_dir / "branch.py"
f.write_text(source)
return str(f)
# ============================================================
# Non-entry-point files — skipped
# ============================================================
def test_non_entry_point_skipped(tmp_path):
f = tmp_path / "handler.py"
f.write_text("def main(): pass\n")
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(str(f))
assert result["passed"] is True
assert result["score"] == 100
def test_missing_file():
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module("/nonexistent/apps/branch.py")
assert result["passed"] is False
assert result["score"] == 0
def test_no_entry_function(tmp_path):
src = "def helper(): pass\n"
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
assert "skipped" in result["checks"][0]["message"]
# ============================================================
# MUST FAIL — top-level --help only, no subcommand guard
# ============================================================
def test_toplevel_only_fails(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
if args[0] in ["--help", "-h"]:
print_help()
return 0
command = args[0]
remaining = args[1:]
route_command(command, remaining, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is False
assert result["score"] == 0
assert "No subcommand --help guard" in result["checks"][0]["message"]
def test_no_help_at_all_fails(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
command = args[0]
remaining = args[1:]
route_command(command, remaining, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is False
# ============================================================
# MUST PASS — explicit subcommand --help guard
# ============================================================
def test_remaining_subscript_guard_passes(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
if args[0] in ["--help", "-h"]:
print_help()
return 0
command = args[0]
remaining = args[1:]
if remaining and remaining[0] in ["--help", "-h"]:
show_subcommand_help(command)
return 0
route_command(command, remaining, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
assert result["score"] == 100
def test_remaining_args_variable_passes(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
if args[0] in ["--help", "-h"]:
print_help()
return 0
command = args[0]
remaining_args = args[1:]
if remaining_args and remaining_args[0] in ["--help", "-h"]:
show_subcommand_help(command)
return 0
route_command(command, remaining_args, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
def test_help_in_remaining_passes(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
command = args[0]
remaining = args[1:]
if "--help" in remaining:
show_help(command)
return 0
route_command(command, remaining, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
def test_post_dispatch_fallback_passes(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
if args[0] in ["--help", "-h"]:
print_help()
return 0
command = args[0]
remaining_args = args[1:]
if route_command(command, remaining_args, modules):
return 0
if remaining_args and remaining_args[0] in ["--help", "-h"]:
print_module_help(command, modules)
return 0
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
# ============================================================
# MUST PASS — argparse pattern
# ============================================================
def test_argparse_parse_known_args_passes(tmp_path):
src = """\
import argparse
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("command", nargs="?")
parser.add_argument("--help", "-h", action="store_true", dest="show_help")
parsed_args, remaining = parser.parse_known_args()
if parsed_args.show_help:
all_args = ["--help"] + remaining
route_command(parsed_args.command, all_args, handlers)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
assert "parse_known_args" in result["checks"][0]["message"]
# ============================================================
# MUST PASS — handle_command function
# ============================================================
def test_handle_command_function_detected(tmp_path):
src = """\
def handle_command(command, args):
if args and args[0] in ["--help", "-h"]:
return False
route_command(command, args, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
# ============================================================
# MUST PASS — bypass
# ============================================================
def test_bypassed_file_passes(tmp_path):
src = "def main(): pass\n"
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
bypass_rules = [{"file": f, "standard": "subcommand_help"}]
result = check_module(f, bypass_rules=bypass_rules)
assert result["passed"] is True
assert result["score"] == 100
# ============================================================
# Edge cases
# ============================================================
def test_syntax_error_file(tmp_path):
src = "def main(\n"
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is False
assert "Syntax error" in result["checks"][0]["message"]
def test_rest_variable_passes(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
command = args[0]
rest = args[1:]
if rest and rest[0] in ["--help", "-h"]:
show_help(command)
return 0
route_command(command, rest, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
def test_cmd_args_variable_passes(tmp_path):
src = """\
import sys
def main():
args = sys.argv[1:]
command = args[0]
cmd_args = args[1:]
if cmd_args and cmd_args[0] in ["--help", "-h"]:
show_help(command)
return 0
route_command(command, cmd_args, modules)
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
def test_name_eq_help_passes(tmp_path):
src = """\
def main():
command = args[0]
remaining = args[1:]
flag = remaining[0]
if flag == "--help":
show_help(command)
return 0
"""
f = _entry_file(tmp_path, src)
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(f)
assert result["passed"] is True
# ============================================================
# Fleet fixtures — real entry points
# ============================================================
_AIPASS_ROOT = Path(__file__).resolve().parents[2]
def test_commons_entry_passes():
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(str(_AIPASS_ROOT / "commons" / "apps" / "commons.py"))
assert result["passed"] is True, f"commons should pass: {result['checks']}"
def test_prax_entry_passes():
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(str(_AIPASS_ROOT / "prax" / "apps" / "prax.py"))
assert result["passed"] is True, f"prax should pass: {result['checks']}"
def test_flow_entry_passes():
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(str(_AIPASS_ROOT / "flow" / "apps" / "flow.py"))
assert result["passed"] is True, f"flow should pass: {result['checks']}"
def test_seedgo_entry_fails():
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(str(_AIPASS_ROOT / "seedgo" / "apps" / "seedgo.py"))
assert result["passed"] is False, f"seedgo should fail (no subcommand guard): {result['checks']}"
def test_ai_mail_entry_fails():
from aipass.seedgo.apps.handlers.aipass_standards.subcommand_help_check import check_module
result = check_module(str(_AIPASS_ROOT / "ai_mail" / "apps" / "ai_mail.py"))
assert result["passed"] is False, f"ai_mail should fail: {result['checks']}"