fix(aipass): bare 'aipass <command>' runs the command, not an introspection banner
aipass is a user-facing binary — 'aipass doctor' must run the health check, not describe itself. All 7 modules (doctor, doctor_fix, doctor_wire, handoff, help_chat, init_flow, profile) hit a no-args→introspection gate (a standard meant for 'drone @branch <module>' discovery). Bare invocation now runs the command or shows usage; introspection moved to --info. seedgo introspection standard bypassed for these binary-invoked modules (documented). 424 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ed58eb7aa6
commit
f7d7f78c63
@@ -8,6 +8,27 @@ and this project uses [Calendar Versioning](https://calver.org/) in the format
|
||||
|
||||
---
|
||||
|
||||
## [2026.W23] - 2026-06-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`aipass <command>` runs instead of printing an introspection banner** —
|
||||
`aipass` is a user-facing binary, so `aipass doctor` (and every other command)
|
||||
must execute, not describe itself. All 7 modules (`doctor`, `doctor_fix`,
|
||||
`doctor_wire`, `handoff`, `help_chat`, `init_flow`, `profile`) previously hit a
|
||||
no-args→introspection gate (a standard meant for `drone @branch <module>`
|
||||
discovery) and showed a banner on bare invocation. Now bare invocation runs the
|
||||
command or shows usage; the introspection banner moved to `--info`. The seedgo
|
||||
introspection standard is bypassed for these binary-invoked modules (documented).
|
||||
- **Dashboard plan counts no longer zeroed on refresh** — a branch's
|
||||
`active_plans` was reset to `0` by every `drone @prax dashboard refresh`, because
|
||||
`PLANS.central.json` only held Flow's own plans (`location==FLOW_ROOT` filter).
|
||||
The central file is now comprehensive: all plans grouped per-branch, so refresh
|
||||
reports each branch's real count (e.g. devpulse now shows its 12 open plans
|
||||
instead of 0).
|
||||
|
||||
---
|
||||
|
||||
## [2026.W22] - 2026-05-30
|
||||
|
||||
### Added
|
||||
|
||||
@@ -260,6 +260,41 @@
|
||||
"file": "apps/handlers/init/scaffold_content.py",
|
||||
"standard": "json_structure",
|
||||
"reason": "scaffold_content.py is Pure Python only (no module/prax/cli imports) by design — pure string-returning template generators extracted from bootstrap.py. Same constraint as bootstrap.py."
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/doctor.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: aipass doctor runs the command; introspection via --info"
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/doctor_fix.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: bare invocation shows usage; introspection via --info"
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/doctor_wire.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: bare invocation shows usage; introspection via --info"
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/handoff.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: aipass handoff shows usage; introspection via --info"
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/help_chat.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: aipass help shows usage; introspection via --info"
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/init_flow.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: aipass init shows usage; introspection via --info"
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/profile.py",
|
||||
"standard": "introspection",
|
||||
"reason": "aipass is binary-invoked: aipass profile runs the command; introspection via --info"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -664,15 +664,11 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
if command != "doctor":
|
||||
return False
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
if args and args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] == "--info":
|
||||
if args and args[0] == "--info":
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
|
||||
@@ -304,12 +304,12 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
return False
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
json_handler.log_operation("doctor_fix_info", {"command": command})
|
||||
console.print("[dim]Helper module — use: aipass doctor --fix [--json][/dim]")
|
||||
json_handler.log_operation("doctor_fix_usage", {"command": command})
|
||||
return True
|
||||
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
print_introspection()
|
||||
console.print("[dim]Helper module — use: aipass doctor --fix [--json][/dim]")
|
||||
json_handler.log_operation("doctor_fix_help", {"command": command})
|
||||
return True
|
||||
|
||||
|
||||
@@ -283,13 +283,13 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
return False
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
json_handler.log_operation("doctor_wire_info", {"command": command})
|
||||
console.print("[dim]Helper module — use: aipass doctor (auto-wire runs when needed)[/dim]")
|
||||
json_handler.log_operation("doctor_wire_usage", {"command": command})
|
||||
return True
|
||||
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
print_introspection()
|
||||
json_handler.log_operation("doctor_wire_info", {"command": command})
|
||||
console.print("[dim]Helper module — use: aipass doctor (auto-wire runs when needed)[/dim]")
|
||||
json_handler.log_operation("doctor_wire_help", {"command": command})
|
||||
return True
|
||||
|
||||
if args[0] in ("--info", "info"):
|
||||
|
||||
@@ -134,13 +134,17 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
return False
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] == "--info":
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
if args[0] == "launch":
|
||||
cli, cwd, flag_variant = _parse_launch_args(args[1:])
|
||||
if cli not in CLI_CHOICES:
|
||||
|
||||
@@ -54,6 +54,20 @@ def print_introspection() -> None:
|
||||
console.print(f"[bold cyan]Version:[/bold cyan] {_VERSION}")
|
||||
|
||||
|
||||
def print_help() -> None:
|
||||
"""Print usage help for the help command."""
|
||||
console.print()
|
||||
console.print("[bold cyan]aipass help[/bold cyan] — README-backed Q&A")
|
||||
console.print()
|
||||
console.print("[yellow]USAGE:[/yellow]")
|
||||
console.print(" [green]aipass help <question>[/green] [dim]# Search branch READMEs[/dim]")
|
||||
console.print()
|
||||
console.print("[yellow]EXAMPLES:[/yellow]")
|
||||
console.print(" [green]aipass help what does drone do[/green]")
|
||||
console.print(" [green]aipass help how does ai_mail work[/green]")
|
||||
console.print()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# KEYWORD EXTRACTION
|
||||
# =============================================================================
|
||||
@@ -219,10 +233,14 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
json_handler.ensure_module_jsons(_MODULE_NAME)
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] == "--info":
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
|
||||
@@ -645,13 +645,18 @@ def _write_init_report(agent_path: str, accumulated: Dict[str, Any], dry_run: bo
|
||||
"cli_choice": accumulated.get("cli", "claude"),
|
||||
"total_agents": 1,
|
||||
"system": system_data,
|
||||
"note": "You are the first agent created in this project. You are the orchestrator. After dispatching work to other agents, monitor them with: drone @devpulse watchdog agent @target",
|
||||
"note": (
|
||||
"You are the first agent created in this project. You are the orchestrator."
|
||||
" After dispatching work to other agents, monitor them with:"
|
||||
" drone @devpulse watchdog agent @target"
|
||||
),
|
||||
}
|
||||
provider_gaps = accumulated.get("provider_gaps", {})
|
||||
if provider_gaps:
|
||||
report["provider_gaps"] = provider_gaps
|
||||
report["provider_action"] = (
|
||||
"Provider settings need configuring. Tell the user what is missing and point them to provider_manifest.json for details."
|
||||
"Provider settings need configuring. Tell the user what is missing"
|
||||
" and point them to provider_manifest.json for details."
|
||||
)
|
||||
report_path = dropbox / "init_report.json"
|
||||
report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
||||
@@ -917,13 +922,17 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
return False
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] == "--info":
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
if args[0] == "agent":
|
||||
sys.exit(_handle_init_agent(args[1:]))
|
||||
return True
|
||||
|
||||
@@ -141,6 +141,10 @@ def handle_command(command: str, args: list[str]) -> bool:
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if args[0] == "--info":
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
if args[0] == "set":
|
||||
if len(args) < 3:
|
||||
error("Usage: aipass profile set <field> <value>")
|
||||
|
||||
@@ -364,14 +364,15 @@ class TestDoctorFixHandleCommand:
|
||||
assert handle_command("doctor", []) is False
|
||||
assert handle_command("help", []) is False
|
||||
|
||||
def test_no_args_calls_introspection(self) -> None:
|
||||
"""No args triggers print_introspection."""
|
||||
def test_no_args_shows_usage(self) -> None:
|
||||
"""No args shows usage message (not introspection banner)."""
|
||||
from aipass.aipass.apps.modules.doctor_fix import handle_command
|
||||
|
||||
with patch("aipass.aipass.apps.modules.doctor_fix.print_introspection") as mock:
|
||||
with patch("aipass.aipass.apps.modules.doctor_fix.console") as mock_console:
|
||||
result = handle_command("doctor_fix", [])
|
||||
assert result is True
|
||||
mock.assert_called_once()
|
||||
printed = " ".join(str(c) for c in mock_console.print.call_args_list)
|
||||
assert "aipass doctor --fix" in printed
|
||||
|
||||
def test_info_flag(self) -> None:
|
||||
"""--info triggers print_introspection."""
|
||||
|
||||
@@ -52,20 +52,6 @@ _ENCODING = "utf-8"
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def _call_handle_command_no_args():
|
||||
"""Call handle_command('help', []) with json_handler and console mocked."""
|
||||
mock_console = MagicMock()
|
||||
patches = [
|
||||
patch("aipass.aipass.apps.modules.help_chat.json_handler"),
|
||||
patch("aipass.aipass.apps.modules.help_chat.console", mock_console),
|
||||
]
|
||||
with ExitStack() as stack:
|
||||
for p in patches:
|
||||
stack.enter_context(p)
|
||||
result = handle_command("help", [])
|
||||
return result, mock_console
|
||||
|
||||
|
||||
def _call_handle_command_drone_question(readme_content: str, readme_path: Path):
|
||||
"""Call handle_command for 'what does drone do' with file I/O mocked."""
|
||||
patches = [
|
||||
@@ -367,11 +353,21 @@ class TestHandleCommand:
|
||||
"""COMMAND module constant must equal the string 'help'."""
|
||||
assert COMMAND == "help"
|
||||
|
||||
def test_no_args_returns_true_and_calls_console(self):
|
||||
"""handle_command('help', []) must return True and print usage via console."""
|
||||
result, mock_console = _call_handle_command_no_args()
|
||||
def test_no_args_returns_true_and_shows_help(self):
|
||||
"""handle_command('help', []) must return True and print usage help."""
|
||||
with patch("aipass.aipass.apps.modules.help_chat.print_help") as mock_help:
|
||||
with patch("aipass.aipass.apps.modules.help_chat.json_handler"):
|
||||
result = handle_command("help", [])
|
||||
assert result is True
|
||||
mock_console.print.assert_called()
|
||||
mock_help.assert_called_once()
|
||||
|
||||
def test_info_flag_calls_introspection(self):
|
||||
"""--info flag triggers print_introspection."""
|
||||
with patch("aipass.aipass.apps.modules.help_chat.print_introspection") as mock_intro:
|
||||
with patch("aipass.aipass.apps.modules.help_chat.json_handler"):
|
||||
result = handle_command("help", ["--info"])
|
||||
assert result is True
|
||||
mock_intro.assert_called_once()
|
||||
|
||||
def test_valid_drone_question_returns_true(self):
|
||||
"""A well-formed question about drone must return True."""
|
||||
|
||||
@@ -198,11 +198,18 @@ class TestHandleCommand:
|
||||
assert handle_command("doctor", []) is False
|
||||
assert handle_command("profile", ["set", "name", "X"]) is False
|
||||
|
||||
def test_no_args_shows_introspection(self, tmp_local_json) -> None:
|
||||
"""'init' with no args calls print_introspection."""
|
||||
with patch("aipass.aipass.apps.modules.init_flow.print_introspection") as mock_intro:
|
||||
def test_no_args_shows_help(self, tmp_local_json) -> None:
|
||||
"""'init' with no args calls print_help (not introspection banner)."""
|
||||
with patch("aipass.aipass.apps.modules.init_flow.print_help") as mock_help:
|
||||
result = handle_command("init", [])
|
||||
assert result is True
|
||||
mock_help.assert_called_once()
|
||||
|
||||
def test_info_flag_calls_introspection(self, tmp_local_json) -> None:
|
||||
"""--info flag calls print_introspection."""
|
||||
with patch("aipass.aipass.apps.modules.init_flow.print_introspection") as mock_intro:
|
||||
result = handle_command("init", ["--info"])
|
||||
assert result is True
|
||||
mock_intro.assert_called_once()
|
||||
|
||||
def test_help_flag(self) -> None:
|
||||
|
||||
@@ -290,7 +290,9 @@ class TestReturnTypeContracts:
|
||||
"""Doctor handle_command returns True for match, False otherwise."""
|
||||
from aipass.aipass.apps.modules.doctor import handle_command as doctor_cmd
|
||||
|
||||
assert doctor_cmd("doctor", []) is True
|
||||
with patch("aipass.aipass.apps.modules.doctor.run_doctor", return_value=0):
|
||||
with patch("aipass.aipass.apps.modules.doctor.json_handler"):
|
||||
assert doctor_cmd("doctor", []) is True
|
||||
assert doctor_cmd("not_doctor", []) is False
|
||||
|
||||
def test_help_chat_handle_command_returns_bool(self):
|
||||
|
||||
@@ -170,12 +170,19 @@ class TestHandleCommand:
|
||||
assert handle_command("init", ["run"]) is False
|
||||
|
||||
def test_no_args_calls_introspection(self, tmp_local_json) -> None:
|
||||
"""'profile' with no args calls print_introspection."""
|
||||
"""'profile' with no args shows the profile (runs the command)."""
|
||||
with patch("aipass.aipass.apps.modules.profile.print_introspection") as mock_pi:
|
||||
result = handle_command("profile", [])
|
||||
assert result is True
|
||||
mock_pi.assert_called_once()
|
||||
|
||||
def test_info_flag_calls_introspection(self, tmp_local_json) -> None:
|
||||
"""--info flag calls print_introspection."""
|
||||
with patch("aipass.aipass.apps.modules.profile.print_introspection") as mock_pi:
|
||||
result = handle_command("profile", ["--info"])
|
||||
assert result is True
|
||||
mock_pi.assert_called_once()
|
||||
|
||||
def test_help_flag_returns_true(self) -> None:
|
||||
"""--help flag is handled."""
|
||||
with patch("aipass.aipass.apps.modules.profile.print_help"):
|
||||
|
||||
Reference in New Issue
Block a user