#686/#661 night shift wave 4 completion (aipass): -> 100% seedgo. aipass.py main() intercepts <cmd> --help before dispatch (Subcommand_Help ->100). Output_Routing ->100: 31 status prints across doctor/init_flow/handoff/profile routed via cli success()/error()/warning(). Modules ->100: auto_wire_provider extracted to NEW handler provider_wire.py, prompt_auto_wire made private (doctor_wire.py). Tests updated (test_doctor patch targets, test_init_flow success routing). Full suite re-run by devpulse: 657 pass / 0 fail. Audit 100% incl Modules.
This commit is contained in:
+2
-2
@@ -21,8 +21,8 @@ PyPI version — not the changelog header.
|
|||||||
`@cli` `success()/error()/warning()` helpers instead of raw `console.print`
|
`@cli` `success()/error()/warning()` helpers instead of raw `console.print`
|
||||||
markup. Owners self-audited and self-fixed their own branches; devpulse verified
|
markup. Owners self-audited and self-fixed their own branches; devpulse verified
|
||||||
each diff + re-ran each audit and committed per wave. Landed so far: spawn,
|
each diff + re-ran each audit and committed per wave. Landed so far: spawn,
|
||||||
drone, flow, daemon, prax, ai_mail, backup, seedgo, memory, trigger, api, cli.
|
drone, flow, daemon, prax, ai_mail, backup, seedgo, memory, trigger, api, cli,
|
||||||
Remaining waves following.
|
aipass. Remaining: commons.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,14 @@ def main():
|
|||||||
command = args[0]
|
command = args[0]
|
||||||
remaining = args[1:] if len(args) > 1 else []
|
remaining = args[1:] if len(args) > 1 else []
|
||||||
|
|
||||||
|
# Subcommand --help guard: intercept before dispatch
|
||||||
|
if remaining and remaining[0] in ("--help", "-h"):
|
||||||
|
for module in modules:
|
||||||
|
if module.handle_command(command, ["--help"]):
|
||||||
|
return 0
|
||||||
|
print(f"Unknown command: {command}")
|
||||||
|
return 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if route_command(command, remaining, modules):
|
if route_command(command, remaining, modules):
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# =================== AIPass ====================
|
||||||
|
# Name: provider_wire.py
|
||||||
|
# Description: Auto-wire provider settings from manifest into user config
|
||||||
|
# Version: 1.0.0
|
||||||
|
# Created: 2026-07-11
|
||||||
|
# Modified: 2026-07-11
|
||||||
|
# =============================================
|
||||||
|
|
||||||
|
"""provider_wire — auto-wire provider settings.
|
||||||
|
|
||||||
|
Implements the additive merge of manifest into ~/.claude/settings.json.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from aipass.aipass.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# HOOK & ENV DESCRIPTIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
HOOK_DESCRIPTIONS: Dict[str, str] = {
|
||||||
|
"pre_edit_gate.py": "blocks edits outside agent's branch",
|
||||||
|
"subagent_stop_gate.py": "validates agent output on exit",
|
||||||
|
"auto_fix_diagnostics.py": "auto-fixes lint issues after edits",
|
||||||
|
"global_prompt_loader.py": "injects branch context on each turn",
|
||||||
|
"identity_injector.py": "injects agent identity on each turn",
|
||||||
|
"email_notification.py": "notifies on incoming agent mail",
|
||||||
|
"branch_prompt_loader.py": "loads branch-specific prompts",
|
||||||
|
"pre_compact.py": "saves state before context compaction",
|
||||||
|
}
|
||||||
|
|
||||||
|
ENV_DESCRIPTIONS: Dict[str, str] = {
|
||||||
|
"AIPASS_HOME": "tells agents where AIPass lives",
|
||||||
|
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "prevents conflict with .trinity/ memory system",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# AUTO-WIRE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
def auto_wire_provider(manifest_path: Path, interactive: bool = True) -> List[str]:
|
||||||
|
"""Auto-wire provider settings from manifest into ~/.claude/settings.json.
|
||||||
|
|
||||||
|
Additive merge only — never removes or overwrites existing keys/values.
|
||||||
|
Returns list of action descriptions (for logging/display).
|
||||||
|
"""
|
||||||
|
actions: List[str] = []
|
||||||
|
|
||||||
|
manifest = json_handler.load_path(manifest_path)
|
||||||
|
if manifest is None:
|
||||||
|
return actions
|
||||||
|
claude_section = manifest.get("cli", {}).get("claude", {})
|
||||||
|
if not claude_section:
|
||||||
|
return actions
|
||||||
|
|
||||||
|
settings_path = Path.home() / ".claude" / "settings.json"
|
||||||
|
if settings_path.exists():
|
||||||
|
settings = json_handler.load_path(settings_path) or {}
|
||||||
|
else:
|
||||||
|
settings = {}
|
||||||
|
|
||||||
|
if settings_path.exists():
|
||||||
|
date_stamp = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
|
||||||
|
backup_path = settings_path.with_suffix(f".json.bak.{date_stamp}")
|
||||||
|
shutil.copy2(settings_path, backup_path)
|
||||||
|
actions.append(f"Backed up settings to {backup_path.name}")
|
||||||
|
|
||||||
|
manifest_hooks = claude_section.get("hooks", [])
|
||||||
|
|
||||||
|
for hook in manifest_hooks:
|
||||||
|
command = hook.get("command", "")
|
||||||
|
event = hook.get("event", "")
|
||||||
|
if not command or not event:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "hooks" not in settings:
|
||||||
|
settings["hooks"] = {}
|
||||||
|
if event not in settings["hooks"]:
|
||||||
|
settings["hooks"][event] = []
|
||||||
|
event_hooks = settings["hooks"][event]
|
||||||
|
if not isinstance(event_hooks, list):
|
||||||
|
event_hooks = [event_hooks]
|
||||||
|
settings["hooks"][event] = event_hooks
|
||||||
|
|
||||||
|
hook_matcher = hook.get("matcher", "")
|
||||||
|
already_wired = any(
|
||||||
|
isinstance(h, dict) and command in json.dumps(h) and h.get("matcher", "") == hook_matcher
|
||||||
|
for h in event_hooks
|
||||||
|
)
|
||||||
|
if not already_wired:
|
||||||
|
cmd_entry: Dict[str, object] = {
|
||||||
|
"type": "command",
|
||||||
|
"command": command,
|
||||||
|
}
|
||||||
|
if hook.get("timeout"):
|
||||||
|
cmd_entry["timeout"] = hook["timeout"]
|
||||||
|
wrapper: Dict[str, object] = {}
|
||||||
|
if hook.get("matcher"):
|
||||||
|
wrapper["matcher"] = hook["matcher"]
|
||||||
|
wrapper["hooks"] = [cmd_entry]
|
||||||
|
event_hooks.append(wrapper)
|
||||||
|
label = command.rsplit(" ", 1)[-1] if " " in command else command
|
||||||
|
actions.append(f"Wired hook {label} -> {event}")
|
||||||
|
|
||||||
|
manifest_env = claude_section.get("env", {})
|
||||||
|
if manifest_env:
|
||||||
|
if "env" not in settings:
|
||||||
|
settings["env"] = {}
|
||||||
|
repo_root = str(manifest_path.parent.parent)
|
||||||
|
project_root = str(Path.cwd())
|
||||||
|
for key, value in manifest_env.items():
|
||||||
|
if key not in settings["env"]:
|
||||||
|
resolved = value.replace("{{REPO_ROOT}}", repo_root)
|
||||||
|
resolved = resolved.replace("{{PROJECT_ROOT}}", project_root)
|
||||||
|
settings["env"][key] = resolved
|
||||||
|
actions.append(f"Set env {key}={resolved}")
|
||||||
|
|
||||||
|
manifest_perms = claude_section.get("permissions", {})
|
||||||
|
manifest_deny = manifest_perms.get("deny", [])
|
||||||
|
manifest_ask = manifest_perms.get("ask", [])
|
||||||
|
|
||||||
|
if manifest_deny or manifest_ask:
|
||||||
|
if "permissions" not in settings:
|
||||||
|
settings["permissions"] = {}
|
||||||
|
if "deny" not in settings["permissions"]:
|
||||||
|
settings["permissions"]["deny"] = []
|
||||||
|
if "ask" not in settings["permissions"]:
|
||||||
|
settings["permissions"]["ask"] = []
|
||||||
|
|
||||||
|
existing_deny = set(settings["permissions"]["deny"])
|
||||||
|
for rule in manifest_deny:
|
||||||
|
if rule not in existing_deny:
|
||||||
|
settings["permissions"]["deny"].append(rule)
|
||||||
|
actions.append(f"Added deny rule: {rule}")
|
||||||
|
|
||||||
|
existing_ask = set(settings["permissions"]["ask"])
|
||||||
|
for rule in manifest_ask:
|
||||||
|
if rule not in existing_ask:
|
||||||
|
settings["permissions"]["ask"].append(rule)
|
||||||
|
actions.append(f"Added ask rule: {rule}")
|
||||||
|
|
||||||
|
json_handler.save_path(settings_path, settings)
|
||||||
|
actions.append("Updated ~/.claude/settings.json")
|
||||||
|
|
||||||
|
json_handler.log_operation("auto_wire_provider", {"actions": len(actions)})
|
||||||
|
return actions
|
||||||
@@ -17,7 +17,7 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, NamedTuple
|
from typing import Dict, List, NamedTuple
|
||||||
|
|
||||||
from aipass.cli.apps.modules import console
|
from aipass.cli.apps.modules import console, error as cli_error, success
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
|
||||||
from aipass.aipass.shared.registry_discovery import find_registry as _discover_registry
|
from aipass.aipass.shared.registry_discovery import find_registry as _discover_registry
|
||||||
@@ -60,8 +60,8 @@ from aipass.aipass.apps.modules.doctor_fix import (
|
|||||||
)
|
)
|
||||||
from aipass.aipass.apps.modules.doctor_wire import (
|
from aipass.aipass.apps.modules.doctor_wire import (
|
||||||
_auto_wire_provider,
|
_auto_wire_provider,
|
||||||
|
_prompt_auto_wire as prompt_auto_wire,
|
||||||
check_wire_verify,
|
check_wire_verify,
|
||||||
prompt_auto_wire,
|
|
||||||
reconcile_stale_deny,
|
reconcile_stale_deny,
|
||||||
)
|
)
|
||||||
from aipass.aipass.apps.handlers.system_detect.system_detector import (
|
from aipass.aipass.apps.handlers.system_detect.system_detector import (
|
||||||
@@ -424,7 +424,7 @@ def _check_provider_manifest(interactive: bool = False, fix: bool = False) -> Li
|
|||||||
if fix:
|
if fix:
|
||||||
actions = _auto_wire_provider(manifest_path, interactive=False)
|
actions = _auto_wire_provider(manifest_path, interactive=False)
|
||||||
for action in actions:
|
for action in actions:
|
||||||
console.print(f"[green]✓[/green] {action}")
|
success(action)
|
||||||
wired = bool(actions)
|
wired = bool(actions)
|
||||||
else:
|
else:
|
||||||
wired = prompt_auto_wire(manifest_path, missing_hooks, missing_env, missing_deny, missing_ask)
|
wired = prompt_auto_wire(manifest_path, missing_hooks, missing_env, missing_deny, missing_ask)
|
||||||
@@ -899,11 +899,11 @@ def run_cross_os_record(path: str | None = None, run_e2e: bool = False) -> int:
|
|||||||
try:
|
try:
|
||||||
written = generate_run_record(path, run_heavy_e2e=run_e2e)
|
written = generate_run_record(path, run_heavy_e2e=run_e2e)
|
||||||
except RunRecordError as exc:
|
except RunRecordError as exc:
|
||||||
console.print(f"[red]✗[/red] {exc}")
|
cli_error(str(exc))
|
||||||
logger.error("[doctor] cross-os run record failed: %s", exc)
|
logger.error("[doctor] cross-os run record failed: %s", exc)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
console.print(f"[green]✓[/green] Run Record written: [bold]{written}[/bold]")
|
success(f"Run Record written: {written}")
|
||||||
console.print("[dim]Complete the '— human' rows and run the real Layer-3 acceptance pass before it counts.[/dim]")
|
console.print("[dim]Complete the '— human' rows and run the real Layer-3 acceptance pass before it counts.[/dim]")
|
||||||
console.print()
|
console.print()
|
||||||
logger.info("[doctor] cross-os run record written to %s", written)
|
logger.info("[doctor] cross-os run record written to %s", written)
|
||||||
@@ -1012,7 +1012,7 @@ def print_help() -> None:
|
|||||||
"[dim]# write a machine pre-flight Run Record draft (human completes it)[/dim]"
|
"[dim]# write a machine pre-flight Run Record draft (human completes it)[/dim]"
|
||||||
)
|
)
|
||||||
console.print()
|
console.print()
|
||||||
console.print("[yellow]OUTPUT:[/yellow] [green]✓[/green] pass [yellow]![/yellow] warn [red]✗[/red] error")
|
console.print("[yellow]OUTPUT:[/yellow] pass / warn / error (color-coded)")
|
||||||
console.print("[yellow]EXIT:[/yellow] 0 = pass/warn | 1 = errors found")
|
console.print("[yellow]EXIT:[/yellow] 0 = pass/warn | 1 = errors found")
|
||||||
console.print()
|
console.print()
|
||||||
|
|
||||||
|
|||||||
@@ -18,38 +18,20 @@ Provides:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, NamedTuple
|
from typing import List, NamedTuple
|
||||||
|
|
||||||
from aipass.cli.apps.modules import console, success
|
from aipass.cli.apps.modules import console, success
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
|
||||||
from aipass.aipass.apps.handlers.json import json_handler
|
from aipass.aipass.apps.handlers.json import json_handler
|
||||||
|
from aipass.aipass.apps.handlers.provider_wire import ( # noqa: F401
|
||||||
# =============================================================================
|
HOOK_DESCRIPTIONS,
|
||||||
# HOOK & ENV DESCRIPTIONS (for interactive "no" warning)
|
ENV_DESCRIPTIONS,
|
||||||
# =============================================================================
|
auto_wire_provider as _auto_wire_provider,
|
||||||
|
)
|
||||||
HOOK_DESCRIPTIONS: Dict[str, str] = {
|
|
||||||
"pre_edit_gate.py": "blocks edits outside agent's branch",
|
|
||||||
"subagent_stop_gate.py": "validates agent output on exit",
|
|
||||||
"auto_fix_diagnostics.py": "auto-fixes lint issues after edits",
|
|
||||||
"global_prompt_loader.py": "injects branch context on each turn",
|
|
||||||
"identity_injector.py": "injects agent identity on each turn",
|
|
||||||
"email_notification.py": "notifies on incoming agent mail",
|
|
||||||
"branch_prompt_loader.py": "loads branch-specific prompts",
|
|
||||||
"pre_compact.py": "saves state before context compaction",
|
|
||||||
}
|
|
||||||
|
|
||||||
ENV_DESCRIPTIONS: Dict[str, str] = {
|
|
||||||
"AIPASS_HOME": "tells agents where AIPass lives",
|
|
||||||
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "prevents conflict with .trinity/ memory system",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -59,130 +41,12 @@ ENV_DESCRIPTIONS: Dict[str, str] = {
|
|||||||
from aipass.aipass.apps.handlers.provider_reconcile import reconcile_stale_deny # noqa: E402, F401
|
from aipass.aipass.apps.handlers.provider_reconcile import reconcile_stale_deny # noqa: E402, F401
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# AUTO-WIRE
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def _auto_wire_provider(manifest_path: Path, interactive: bool = True) -> List[str]:
|
|
||||||
"""Auto-wire provider settings from manifest into ~/.claude/settings.json.
|
|
||||||
|
|
||||||
Additive merge only — never removes or overwrites existing keys/values.
|
|
||||||
Returns list of action descriptions (for logging/display).
|
|
||||||
"""
|
|
||||||
actions: List[str] = []
|
|
||||||
|
|
||||||
manifest = json_handler.load_path(manifest_path)
|
|
||||||
if manifest is None:
|
|
||||||
return actions
|
|
||||||
claude_section = manifest.get("cli", {}).get("claude", {})
|
|
||||||
if not claude_section:
|
|
||||||
return actions
|
|
||||||
|
|
||||||
# Read existing settings
|
|
||||||
settings_path = Path.home() / ".claude" / "settings.json"
|
|
||||||
if settings_path.exists():
|
|
||||||
settings = json_handler.load_path(settings_path) or {}
|
|
||||||
else:
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
# Backup
|
|
||||||
if settings_path.exists():
|
|
||||||
date_stamp = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
|
|
||||||
backup_path = settings_path.with_suffix(f".json.bak.{date_stamp}")
|
|
||||||
shutil.copy2(settings_path, backup_path)
|
|
||||||
actions.append(f"Backed up settings to {backup_path.name}")
|
|
||||||
|
|
||||||
# Hooks — add bridge entries to provider settings
|
|
||||||
manifest_hooks = claude_section.get("hooks", [])
|
|
||||||
|
|
||||||
for hook in manifest_hooks:
|
|
||||||
command = hook.get("command", "")
|
|
||||||
event = hook.get("event", "")
|
|
||||||
if not command or not event:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if "hooks" not in settings:
|
|
||||||
settings["hooks"] = {}
|
|
||||||
if event not in settings["hooks"]:
|
|
||||||
settings["hooks"][event] = []
|
|
||||||
event_hooks = settings["hooks"][event]
|
|
||||||
if not isinstance(event_hooks, list):
|
|
||||||
event_hooks = [event_hooks]
|
|
||||||
settings["hooks"][event] = event_hooks
|
|
||||||
|
|
||||||
hook_matcher = hook.get("matcher", "")
|
|
||||||
already_wired = any(
|
|
||||||
isinstance(h, dict) and command in json.dumps(h) and h.get("matcher", "") == hook_matcher
|
|
||||||
for h in event_hooks
|
|
||||||
)
|
|
||||||
if not already_wired:
|
|
||||||
cmd_entry: Dict[str, object] = {
|
|
||||||
"type": "command",
|
|
||||||
"command": command,
|
|
||||||
}
|
|
||||||
if hook.get("timeout"):
|
|
||||||
cmd_entry["timeout"] = hook["timeout"]
|
|
||||||
wrapper: Dict[str, object] = {}
|
|
||||||
if hook.get("matcher"):
|
|
||||||
wrapper["matcher"] = hook["matcher"]
|
|
||||||
wrapper["hooks"] = [cmd_entry]
|
|
||||||
event_hooks.append(wrapper)
|
|
||||||
label = command.rsplit(" ", 1)[-1] if " " in command else command
|
|
||||||
actions.append(f"Wired hook {label} -> {event}")
|
|
||||||
|
|
||||||
# Env vars
|
|
||||||
manifest_env = claude_section.get("env", {})
|
|
||||||
if manifest_env:
|
|
||||||
if "env" not in settings:
|
|
||||||
settings["env"] = {}
|
|
||||||
repo_root = str(manifest_path.parent.parent)
|
|
||||||
project_root = str(Path.cwd())
|
|
||||||
for key, value in manifest_env.items():
|
|
||||||
if key not in settings["env"]:
|
|
||||||
resolved = value.replace("{{REPO_ROOT}}", repo_root)
|
|
||||||
resolved = resolved.replace("{{PROJECT_ROOT}}", project_root)
|
|
||||||
settings["env"][key] = resolved
|
|
||||||
actions.append(f"Set env {key}={resolved}")
|
|
||||||
|
|
||||||
# Permissions
|
|
||||||
manifest_perms = claude_section.get("permissions", {})
|
|
||||||
manifest_deny = manifest_perms.get("deny", [])
|
|
||||||
manifest_ask = manifest_perms.get("ask", [])
|
|
||||||
|
|
||||||
if manifest_deny or manifest_ask:
|
|
||||||
if "permissions" not in settings:
|
|
||||||
settings["permissions"] = {}
|
|
||||||
if "deny" not in settings["permissions"]:
|
|
||||||
settings["permissions"]["deny"] = []
|
|
||||||
if "ask" not in settings["permissions"]:
|
|
||||||
settings["permissions"]["ask"] = []
|
|
||||||
|
|
||||||
existing_deny = set(settings["permissions"]["deny"])
|
|
||||||
for rule in manifest_deny:
|
|
||||||
if rule not in existing_deny:
|
|
||||||
settings["permissions"]["deny"].append(rule)
|
|
||||||
actions.append(f"Added deny rule: {rule}")
|
|
||||||
|
|
||||||
existing_ask = set(settings["permissions"]["ask"])
|
|
||||||
for rule in manifest_ask:
|
|
||||||
if rule not in existing_ask:
|
|
||||||
settings["permissions"]["ask"].append(rule)
|
|
||||||
actions.append(f"Added ask rule: {rule}")
|
|
||||||
|
|
||||||
# Write settings back
|
|
||||||
json_handler.save_path(settings_path, settings)
|
|
||||||
actions.append("Updated ~/.claude/settings.json")
|
|
||||||
|
|
||||||
return actions
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# INTERACTIVE WIRE PROMPTS
|
# INTERACTIVE WIRE PROMPTS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
def prompt_auto_wire(
|
def _prompt_auto_wire(
|
||||||
manifest_path: Path,
|
manifest_path: Path,
|
||||||
missing_hooks: List[str],
|
missing_hooks: List[str],
|
||||||
missing_env: List[str],
|
missing_env: List[str],
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Usage:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aipass.cli.apps.modules import console, warning
|
from aipass.cli.apps.modules import console, success, warning
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
|
||||||
from aipass.aipass.apps.handlers.json import json_handler
|
from aipass.aipass.apps.handlers.json import json_handler
|
||||||
@@ -64,7 +64,7 @@ def do_handoff(
|
|||||||
|
|
||||||
if launched:
|
if launched:
|
||||||
console.print()
|
console.print()
|
||||||
console.print(f"[green]✓[/green] Session started via tmux/wt — CLI: [cyan]{cli}[/cyan]")
|
success(f"Session started via tmux/wt — CLI: {cli}")
|
||||||
console.print(f"[dim]Session name: aipass-handoff | cwd: {cwd}[/dim]")
|
console.print(f"[dim]Session name: aipass-handoff | cwd: {cwd}[/dim]")
|
||||||
console.print()
|
console.print()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from datetime import datetime, timezone
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from aipass.cli.apps.modules import console, warning
|
from aipass.cli.apps.modules import console, error as cli_error, success, warning
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
|
||||||
from aipass.aipass.apps.handlers.json import json_handler
|
from aipass.aipass.apps.handlers.json import json_handler
|
||||||
@@ -210,7 +210,7 @@ def _choose(msg: str, choices: List[str], default: str | None = None) -> str:
|
|||||||
return choices[idx]
|
return choices[idx]
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
logger.info("[init_flow] invalid menu input %r: %s", raw, exc)
|
logger.info("[init_flow] invalid menu input %r: %s", raw, exc)
|
||||||
console.print("[red]Invalid choice.[/red]")
|
cli_error("Invalid choice.")
|
||||||
|
|
||||||
|
|
||||||
# --- STAGE FUNCTIONS ---
|
# --- STAGE FUNCTIONS ---
|
||||||
@@ -358,7 +358,7 @@ def stage_3_user_profile(
|
|||||||
else:
|
else:
|
||||||
profile_mod.save_profile(existing)
|
profile_mod.save_profile(existing)
|
||||||
|
|
||||||
console.print(f"[green]✓[/green] Hello, {name}!")
|
success(f"Hello, {name}!")
|
||||||
_save_stage(3, {"name": name}, dry_run=dry_run)
|
_save_stage(3, {"name": name}, dry_run=dry_run)
|
||||||
return {"name": name}
|
return {"name": name}
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ def stage_4_style_questions(
|
|||||||
else:
|
else:
|
||||||
style = _choose("What are you looking to do?", STYLE_CHOICES, default=STYLE_CHOICES[0])
|
style = _choose("What are you looking to do?", STYLE_CHOICES, default=STYLE_CHOICES[0])
|
||||||
|
|
||||||
console.print(f"[green]✓[/green] Got it: {style}")
|
success(f"Got it: {style}")
|
||||||
_save_stage(4, {"style": style}, dry_run=dry_run)
|
_save_stage(4, {"style": style}, dry_run=dry_run)
|
||||||
return {"style": style}
|
return {"style": style}
|
||||||
|
|
||||||
@@ -424,7 +424,7 @@ def _handle_missing_claude(non_interactive: bool) -> None:
|
|||||||
if raw.lower() in ("y", "yes", ""):
|
if raw.lower() in ("y", "yes", ""):
|
||||||
console.print("[cyan]Installing Claude Code[/cyan] [dim](this can take a minute)…[/dim]")
|
console.print("[cyan]Installing Claude Code[/cyan] [dim](this can take a minute)…[/dim]")
|
||||||
if _install_claude_code():
|
if _install_claude_code():
|
||||||
console.print("[green]✓[/green] Claude Code installed successfully.")
|
success("Claude Code installed successfully.")
|
||||||
else:
|
else:
|
||||||
warning("[bold yellow]Installation failed.[/bold yellow]")
|
warning("[bold yellow]Installation failed.[/bold yellow]")
|
||||||
console.print(" Install manually: https://claude.ai/download")
|
console.print(" Install manually: https://claude.ai/download")
|
||||||
@@ -460,7 +460,7 @@ def stage_5_tool_choice(
|
|||||||
default="default",
|
default="default",
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(f"[green]✓[/green] {cli_choice} ({flag_variant})")
|
success(f"{cli_choice} ({flag_variant})")
|
||||||
_save_stage(5, {"cli": cli_choice, "flag_variant": flag_variant}, dry_run=dry_run)
|
_save_stage(5, {"cli": cli_choice, "flag_variant": flag_variant}, dry_run=dry_run)
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
@@ -496,14 +496,14 @@ def stage_6_first_agent(non_interactive: bool = False, dry_run: bool = False) ->
|
|||||||
agent_path = f"src/{agent_name}"
|
agent_path = f"src/{agent_name}"
|
||||||
console.print(f"[cyan]Creating your first agent[/cyan] [dim](drone @spawn create {agent_path})…[/dim]")
|
console.print(f"[cyan]Creating your first agent[/cyan] [dim](drone @spawn create {agent_path})…[/dim]")
|
||||||
|
|
||||||
success = False
|
spawned = False
|
||||||
if dry_run:
|
if dry_run:
|
||||||
console.print(f"[yellow]\\[dry-run][/yellow] would run: drone @spawn create {agent_path}")
|
console.print(f"[yellow]\\[dry-run][/yellow] would run: drone @spawn create {agent_path}")
|
||||||
success = True
|
spawned = True
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(["drone", "@spawn", "create", agent_path], timeout=60)
|
proc = subprocess.run(["drone", "@spawn", "create", agent_path], timeout=60)
|
||||||
success = proc.returncode == 0
|
spawned = proc.returncode == 0
|
||||||
except FileNotFoundError as exc:
|
except FileNotFoundError as exc:
|
||||||
logger.warning("[init_flow] drone not found in stage 6: %s", exc)
|
logger.warning("[init_flow] drone not found in stage 6: %s", exc)
|
||||||
warning("drone not found — skipping agent creation.")
|
warning("drone not found — skipping agent creation.")
|
||||||
@@ -511,10 +511,10 @@ def stage_6_first_agent(non_interactive: bool = False, dry_run: bool = False) ->
|
|||||||
logger.warning("[init_flow] spawn timed out in stage 6: %s", exc)
|
logger.warning("[init_flow] spawn timed out in stage 6: %s", exc)
|
||||||
warning("spawn timed out — agent may still be created.")
|
warning("spawn timed out — agent may still be created.")
|
||||||
|
|
||||||
if success:
|
if spawned:
|
||||||
console.print(f"[green]✓[/green] Agent created at {agent_path}")
|
success(f"Agent created at {agent_path}")
|
||||||
|
|
||||||
_save_stage(6, {"agent_name": agent_name, "agent_path": agent_path, "success": success}, dry_run=dry_run)
|
_save_stage(6, {"agent_name": agent_name, "agent_path": agent_path, "success": spawned}, dry_run=dry_run)
|
||||||
return {"agent_name": agent_name, "agent_path": agent_path}
|
return {"agent_name": agent_name, "agent_path": agent_path}
|
||||||
|
|
||||||
|
|
||||||
@@ -576,12 +576,12 @@ def stage_8_smoke_test(non_interactive: bool = False, dry_run: bool = False) ->
|
|||||||
aipass_bin = shutil.which("aipass")
|
aipass_bin = shutil.which("aipass")
|
||||||
|
|
||||||
if drone_bin:
|
if drone_bin:
|
||||||
console.print(f"[green]✓[/green] drone: {drone_bin}")
|
success(f"drone: {drone_bin}")
|
||||||
else:
|
else:
|
||||||
warning("drone not on PATH — clone the repo and run setup.sh")
|
warning("drone not on PATH — clone the repo and run setup.sh")
|
||||||
|
|
||||||
if aipass_bin:
|
if aipass_bin:
|
||||||
console.print(f"[green]✓[/green] aipass: {aipass_bin}")
|
success(f"aipass: {aipass_bin}")
|
||||||
else:
|
else:
|
||||||
warning("aipass not on PATH — clone the repo and run setup.sh")
|
warning("aipass not on PATH — clone the repo and run setup.sh")
|
||||||
|
|
||||||
@@ -640,7 +640,7 @@ def stage_9_handoff(
|
|||||||
if accumulated:
|
if accumulated:
|
||||||
_write_init_report(accumulated.get("agent_path", agent_path), accumulated, dry_run=dry_run)
|
_write_init_report(accumulated.get("agent_path", agent_path), accumulated, dry_run=dry_run)
|
||||||
console.print()
|
console.print()
|
||||||
console.print("[bold green]✓ Setup complete![/bold green]")
|
success("Setup complete!")
|
||||||
console.print()
|
console.print()
|
||||||
from aipass.aipass.apps.handlers.handoff_platform import launch_inline
|
from aipass.aipass.apps.handlers.handoff_platform import launch_inline
|
||||||
|
|
||||||
@@ -716,7 +716,7 @@ def stage_10_done(accumulated: Dict[str, Any] | None = None, dry_run: bool = Fal
|
|||||||
console.print()
|
console.print()
|
||||||
console.print(render_step_header(10, TOTAL_STAGES, "Done!"))
|
console.print(render_step_header(10, TOTAL_STAGES, "Done!"))
|
||||||
console.print()
|
console.print()
|
||||||
console.print("[bold green]✓ Setup complete![/bold green]")
|
success("Setup complete!")
|
||||||
console.print()
|
console.print()
|
||||||
console.print(" [cyan]aipass help[/cyan] [dim]# Ask any question[/dim]")
|
console.print(" [cyan]aipass help[/cyan] [dim]# Ask any question[/dim]")
|
||||||
console.print(" [cyan]aipass doctor[/cyan] [dim]# Check system health[/dim]")
|
console.print(" [cyan]aipass doctor[/cyan] [dim]# Check system health[/dim]")
|
||||||
@@ -777,7 +777,7 @@ def run_init(
|
|||||||
# Pre-flight: refuse to run inside existing projects or agent dirs
|
# Pre-flight: refuse to run inside existing projects or agent dirs
|
||||||
err = _preflight_check()
|
err = _preflight_check()
|
||||||
if err:
|
if err:
|
||||||
console.print(f"[red]✗[/red] {err}")
|
cli_error(str(err))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Template selection — before scaffold
|
# Template selection — before scaffold
|
||||||
@@ -799,7 +799,7 @@ def run_init(
|
|||||||
if not dry_run:
|
if not dry_run:
|
||||||
with activity_spinner("Building project scaffold…"):
|
with activity_spinner("Building project scaffold…"):
|
||||||
init_project(cwd)
|
init_project(cwd)
|
||||||
console.print("[green]✓[/green] Project scaffold ready")
|
success("Project scaffold ready")
|
||||||
else:
|
else:
|
||||||
console.print("[yellow]\\[dry-run][/yellow] would create project scaffold")
|
console.print("[yellow]\\[dry-run][/yellow] would create project scaffold")
|
||||||
|
|
||||||
@@ -807,7 +807,7 @@ def run_init(
|
|||||||
last_done = 0 if dry_run else _get_last_completed_stage()
|
last_done = 0 if dry_run else _get_last_completed_stage()
|
||||||
|
|
||||||
if last_done >= TOTAL_STAGES:
|
if last_done >= TOTAL_STAGES:
|
||||||
console.print("[green]✓[/green] Setup already complete.")
|
success("Setup already complete.")
|
||||||
console.print("[dim]Run 'aipass doctor' to check status.[/dim]")
|
console.print("[dim]Run 'aipass doctor' to check status.[/dim]")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -859,7 +859,7 @@ def run_init(
|
|||||||
|
|
||||||
if template != TEMPLATE_AIPASS:
|
if template != TEMPLATE_AIPASS:
|
||||||
console.print()
|
console.print()
|
||||||
console.print("[green]✓[/green] Project initialized.")
|
success("Project initialized.")
|
||||||
console.print("[dim]Run 'aipass init agent <name>' to add an agent.[/dim]")
|
console.print("[dim]Run 'aipass init agent <name>' to add an agent.[/dim]")
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -877,7 +877,7 @@ def print_introspection() -> None:
|
|||||||
if last == 0:
|
if last == 0:
|
||||||
console.print("[dim]Setup not started. Run: aipass init run[/dim]")
|
console.print("[dim]Setup not started. Run: aipass init run[/dim]")
|
||||||
elif last >= TOTAL_STAGES:
|
elif last >= TOTAL_STAGES:
|
||||||
console.print("[green]✓[/green] Setup complete.")
|
success("Setup complete.")
|
||||||
else:
|
else:
|
||||||
console.print(f"[yellow]In progress:[/yellow] stage {last}/{TOTAL_STAGES} completed.")
|
console.print(f"[yellow]In progress:[/yellow] stage {last}/{TOTAL_STAGES} completed.")
|
||||||
console.print(f"[dim]Run 'aipass init run' to resume from stage {last + 1}.[/dim]")
|
console.print(f"[dim]Run 'aipass init run' to resume from stage {last + 1}.[/dim]")
|
||||||
@@ -911,7 +911,7 @@ def _handle_init_scaffold(args: list[str]) -> int:
|
|||||||
project_name = args[1] if len(args) > 1 else None
|
project_name = args[1] if len(args) > 1 else None
|
||||||
try:
|
try:
|
||||||
result = init_project(target, project_name)
|
result = init_project(target, project_name)
|
||||||
console.print(f"\n[green]✓[/green] Project initialized at [bold]{target}[/bold]")
|
success(f"Project initialized at {target}")
|
||||||
console.print()
|
console.print()
|
||||||
|
|
||||||
# Find the package directory to show in guidance
|
# Find the package directory to show in guidance
|
||||||
@@ -938,7 +938,7 @@ def _handle_init_scaffold(args: list[str]) -> int:
|
|||||||
return 0
|
return 0
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("[init_flow] scaffold failed: %s", exc)
|
logger.warning("[init_flow] scaffold failed: %s", exc)
|
||||||
console.print(f"[red]✗[/red] Init failed: {exc}")
|
cli_error(f"Init failed: {exc}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@@ -952,11 +952,11 @@ def _handle_init_update(args: list[str]) -> int:
|
|||||||
updated = result.get("updated_files", [])
|
updated = result.get("updated_files", [])
|
||||||
current = result.get("already_current", [])
|
current = result.get("already_current", [])
|
||||||
if updated:
|
if updated:
|
||||||
console.print(f"[green]✓[/green] Updated {len(updated)} file(s):")
|
success(f"Updated {len(updated)} file(s):")
|
||||||
for f in updated:
|
for f in updated:
|
||||||
console.print(f" [green]+[/green] {f}")
|
console.print(f" + {f}")
|
||||||
else:
|
else:
|
||||||
console.print("[green]✓[/green] All files already current.")
|
success("All files already current.")
|
||||||
if current:
|
if current:
|
||||||
console.print(f" ({len(current)} already up to date)")
|
console.print(f" ({len(current)} already up to date)")
|
||||||
# Heal registry: prune stale entries (e.g. cross-project ../paths)
|
# Heal registry: prune stale entries (e.g. cross-project ../paths)
|
||||||
@@ -968,7 +968,7 @@ def _handle_init_update(args: list[str]) -> int:
|
|||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
if sync_proc.returncode == 0:
|
if sync_proc.returncode == 0:
|
||||||
console.print(" [green]Registry synced.[/green]")
|
success("Registry synced.")
|
||||||
except Exception as sync_exc:
|
except Exception as sync_exc:
|
||||||
logger.warning("[init_flow] registry sync during update skipped: %s", sync_exc)
|
logger.warning("[init_flow] registry sync during update skipped: %s", sync_exc)
|
||||||
|
|
||||||
@@ -976,14 +976,14 @@ def _handle_init_update(args: list[str]) -> int:
|
|||||||
return 0
|
return 0
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("[init_flow] update failed: %s", exc)
|
logger.warning("[init_flow] update failed: %s", exc)
|
||||||
console.print(f"[red]✗[/red] Update failed: {exc}")
|
cli_error(f"Update failed: {exc}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def _handle_init_agent(args: list[str]) -> int:
|
def _handle_init_agent(args: list[str]) -> int:
|
||||||
"""Handle `aipass init agent <name>` — create a new agent via spawn."""
|
"""Handle `aipass init agent <name>` — create a new agent via spawn."""
|
||||||
if not args:
|
if not args:
|
||||||
console.print("[red]✗[/red] Usage: aipass init agent <name>")
|
cli_error("Usage: aipass init agent <name>")
|
||||||
return 1
|
return 1
|
||||||
agent_name = args[0]
|
agent_name = args[0]
|
||||||
import subprocess as _sp
|
import subprocess as _sp
|
||||||
@@ -1075,7 +1075,7 @@ def handle_command(command: str, args: list[str]) -> bool:
|
|||||||
# Positional args = target path and/or project name for scaffold
|
# Positional args = target path and/or project name for scaffold
|
||||||
err = _preflight_check()
|
err = _preflight_check()
|
||||||
if err:
|
if err:
|
||||||
console.print(f"[red]✗[/red] {err}")
|
cli_error(str(err))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
sys.exit(_handle_init_scaffold(args))
|
sys.exit(_handle_init_scaffold(args))
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import os
|
|||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aipass.cli.apps.modules import console, error, warning
|
from aipass.cli.apps.modules import console, error, success, warning
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
|
||||||
from aipass.aipass.apps.handlers.json import json_handler
|
from aipass.aipass.apps.handlers.json import json_handler
|
||||||
@@ -151,13 +151,13 @@ def handle_command(command: str, args: list[str]) -> bool:
|
|||||||
return True
|
return True
|
||||||
field, value = args[1], args[2]
|
field, value = args[1], args[2]
|
||||||
if field not in USER_FIELDS:
|
if field not in USER_FIELDS:
|
||||||
console.print(f"[red]Unknown field: {field}[/red]")
|
error(f"Unknown field: {field}")
|
||||||
console.print("[dim]Valid fields: " + ", ".join(USER_FIELDS) + "[/dim]")
|
console.print("[dim]Valid fields: " + ", ".join(USER_FIELDS) + "[/dim]")
|
||||||
return True
|
return True
|
||||||
profile = get_user_profile()
|
profile = get_user_profile()
|
||||||
profile[field] = value
|
profile[field] = value
|
||||||
save_profile(profile)
|
save_profile(profile)
|
||||||
console.print(f"[green]✓[/green] {field} = {value}")
|
success(f"{field} = {value}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if args[0] == "clear":
|
if args[0] == "clear":
|
||||||
@@ -166,7 +166,7 @@ def handle_command(command: str, args: list[str]) -> bool:
|
|||||||
skip_confirm = any(a in ("--yes", "-y") for a in args[1:])
|
skip_confirm = any(a in ("--yes", "-y") for a in args[1:])
|
||||||
if skip_confirm:
|
if skip_confirm:
|
||||||
save_profile({f: None for f in USER_FIELDS})
|
save_profile({f: None for f in USER_FIELDS})
|
||||||
console.print("[green]✓[/green] Profile cleared.")
|
success("Profile cleared.")
|
||||||
return True
|
return True
|
||||||
warning("Type 'aipass' to confirm clearing your profile (ctrl-C to cancel):")
|
warning("Type 'aipass' to confirm clearing your profile (ctrl-C to cancel):")
|
||||||
try:
|
try:
|
||||||
@@ -177,7 +177,7 @@ def handle_command(command: str, args: list[str]) -> bool:
|
|||||||
return True
|
return True
|
||||||
if confirm == "aipass":
|
if confirm == "aipass":
|
||||||
save_profile({f: None for f in USER_FIELDS})
|
save_profile({f: None for f in USER_FIELDS})
|
||||||
console.print("[green]✓[/green] Profile cleared.")
|
success("Profile cleared.")
|
||||||
else:
|
else:
|
||||||
console.print("[yellow]Cancelled.[/yellow]")
|
console.print("[yellow]Cancelled.[/yellow]")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -658,7 +658,7 @@ class TestReconcileStaleDeny:
|
|||||||
"""Missing settings.json returns no results."""
|
"""Missing settings.json returns no results."""
|
||||||
from aipass.aipass.apps.modules.doctor_wire import reconcile_stale_deny
|
from aipass.aipass.apps.modules.doctor_wire import reconcile_stale_deny
|
||||||
|
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=False)
|
results = reconcile_stale_deny(fix=False)
|
||||||
assert results == []
|
assert results == []
|
||||||
|
|
||||||
@@ -672,7 +672,7 @@ class TestReconcileStaleDeny:
|
|||||||
json.dumps({"permissions": {"deny": ["Bash(git push --force*)", "Bash(git reset --hard*)"]}}),
|
json.dumps({"permissions": {"deny": ["Bash(git push --force*)", "Bash(git reset --hard*)"]}}),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=False)
|
results = reconcile_stale_deny(fix=False)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0][1] == GLYPH_PASS
|
assert results[0][1] == GLYPH_PASS
|
||||||
@@ -688,7 +688,7 @@ class TestReconcileStaleDeny:
|
|||||||
json.dumps({"permissions": {"deny": ["Bash(rm -rf*)", "Bash(git push --force*)", "Bash(rm -r *)"]}}),
|
json.dumps({"permissions": {"deny": ["Bash(rm -rf*)", "Bash(git push --force*)", "Bash(rm -r *)"]}}),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=False)
|
results = reconcile_stale_deny(fix=False)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0][1] == GLYPH_WARN
|
assert results[0][1] == GLYPH_WARN
|
||||||
@@ -706,7 +706,7 @@ class TestReconcileStaleDeny:
|
|||||||
"env": {"AIPASS_HOME": "/test"},
|
"env": {"AIPASS_HOME": "/test"},
|
||||||
}
|
}
|
||||||
settings.write_text(json.dumps(original), encoding="utf-8")
|
settings.write_text(json.dumps(original), encoding="utf-8")
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=True)
|
results = reconcile_stale_deny(fix=True)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0][1] == GLYPH_PASS
|
assert results[0][1] == GLYPH_PASS
|
||||||
@@ -727,7 +727,7 @@ class TestReconcileStaleDeny:
|
|||||||
json.dumps({"permissions": {"deny": ["Bash(rm -rf*)", "Bash(git reset --hard*)"]}}),
|
json.dumps({"permissions": {"deny": ["Bash(rm -rf*)", "Bash(git reset --hard*)"]}}),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=True)
|
results = reconcile_stale_deny(fix=True)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0][1] == GLYPH_PASS
|
assert results[0][1] == GLYPH_PASS
|
||||||
@@ -744,7 +744,7 @@ class TestReconcileStaleDeny:
|
|||||||
json.dumps({"permissions": {"deny": ["Bash(rm -rf*)", "Bash(rm -r *)"]}}),
|
json.dumps({"permissions": {"deny": ["Bash(rm -rf*)", "Bash(rm -r *)"]}}),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
reconcile_stale_deny(fix=True)
|
reconcile_stale_deny(fix=True)
|
||||||
results = reconcile_stale_deny(fix=True)
|
results = reconcile_stale_deny(fix=True)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
@@ -758,7 +758,7 @@ class TestReconcileStaleDeny:
|
|||||||
settings = tmp_path / ".claude" / "settings.json"
|
settings = tmp_path / ".claude" / "settings.json"
|
||||||
settings.parent.mkdir(parents=True)
|
settings.parent.mkdir(parents=True)
|
||||||
settings.write_text(json.dumps({"permissions": {"deny": []}}), encoding="utf-8")
|
settings.write_text(json.dumps({"permissions": {"deny": []}}), encoding="utf-8")
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=False)
|
results = reconcile_stale_deny(fix=False)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0][1] == GLYPH_PASS
|
assert results[0][1] == GLYPH_PASS
|
||||||
@@ -770,7 +770,7 @@ class TestReconcileStaleDeny:
|
|||||||
settings = tmp_path / ".claude" / "settings.json"
|
settings = tmp_path / ".claude" / "settings.json"
|
||||||
settings.parent.mkdir(parents=True)
|
settings.parent.mkdir(parents=True)
|
||||||
settings.write_text(json.dumps({"env": {"FOO": "bar"}}), encoding="utf-8")
|
settings.write_text(json.dumps({"env": {"FOO": "bar"}}), encoding="utf-8")
|
||||||
with patch("aipass.aipass.apps.modules.doctor_wire.Path.home", return_value=tmp_path):
|
with patch("aipass.aipass.apps.handlers.provider_reconcile.Path.home", return_value=tmp_path):
|
||||||
results = reconcile_stale_deny(fix=False)
|
results = reconcile_stale_deny(fix=False)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0][1] == GLYPH_PASS
|
assert results[0][1] == GLYPH_PASS
|
||||||
@@ -857,7 +857,7 @@ class TestPromptAutoWireIsatty:
|
|||||||
patch.object(doctor_wire, "_print_manual_wire_warning") as mock_warn,
|
patch.object(doctor_wire, "_print_manual_wire_warning") as mock_warn,
|
||||||
):
|
):
|
||||||
mock_stdin.isatty.return_value = False
|
mock_stdin.isatty.return_value = False
|
||||||
result = doctor_wire.prompt_auto_wire(**self._args())
|
result = doctor_wire._prompt_auto_wire(**self._args())
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
mock_input.assert_not_called()
|
mock_input.assert_not_called()
|
||||||
@@ -873,7 +873,7 @@ class TestPromptAutoWireIsatty:
|
|||||||
patch.object(doctor_wire, "_print_manual_wire_warning"),
|
patch.object(doctor_wire, "_print_manual_wire_warning"),
|
||||||
):
|
):
|
||||||
mock_stdin.isatty.return_value = True
|
mock_stdin.isatty.return_value = True
|
||||||
result = doctor_wire.prompt_auto_wire(**self._args())
|
result = doctor_wire._prompt_auto_wire(**self._args())
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
mock_input.assert_called_once()
|
mock_input.assert_called_once()
|
||||||
@@ -888,7 +888,7 @@ class TestPromptAutoWireIsatty:
|
|||||||
patch.object(doctor_wire, "_auto_wire_provider", return_value=["wired hook"]) as mock_wire,
|
patch.object(doctor_wire, "_auto_wire_provider", return_value=["wired hook"]) as mock_wire,
|
||||||
):
|
):
|
||||||
mock_stdin.isatty.return_value = True
|
mock_stdin.isatty.return_value = True
|
||||||
result = doctor_wire.prompt_auto_wire(**self._args())
|
result = doctor_wire._prompt_auto_wire(**self._args())
|
||||||
|
|
||||||
assert result is True
|
assert result is True
|
||||||
mock_wire.assert_called_once()
|
mock_wire.assert_called_once()
|
||||||
|
|||||||
@@ -671,7 +671,8 @@ class TestInitUpdateRegistrySync:
|
|||||||
return_value={"updated_files": [], "already_current": []},
|
return_value={"updated_files": [], "already_current": []},
|
||||||
),
|
),
|
||||||
patch(f"{_MOD_UPDATE}.subprocess.run", return_value=mock_result) as mock_run,
|
patch(f"{_MOD_UPDATE}.subprocess.run", return_value=mock_result) as mock_run,
|
||||||
patch(f"{_MOD_UPDATE}.console") as mock_console,
|
patch(f"{_MOD_UPDATE}.console"),
|
||||||
|
patch(f"{_MOD_UPDATE}.success") as mock_success,
|
||||||
patch(f"{_MOD_UPDATE}.json_handler"),
|
patch(f"{_MOD_UPDATE}.json_handler"),
|
||||||
):
|
):
|
||||||
rc = _handle_init_update([str(tmp_path)])
|
rc = _handle_init_update([str(tmp_path)])
|
||||||
@@ -682,7 +683,7 @@ class TestInitUpdateRegistrySync:
|
|||||||
text=True,
|
text=True,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
sync_calls = [c for c in mock_console.print.call_args_list if "Registry synced" in str(c)]
|
sync_calls = [c for c in mock_success.call_args_list if "Registry synced" in str(c)]
|
||||||
assert len(sync_calls) == 1
|
assert len(sync_calls) == 1
|
||||||
|
|
||||||
def test_sync_failure_degrades_silently(self, tmp_path: Path) -> None:
|
def test_sync_failure_degrades_silently(self, tmp_path: Path) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user