#686/#661 night shift wave 3 completion (memory): -> 100% seedgo. memory.py --help guard (Subcommand_Help ->100). symbolic.py + 6 modules route console.print status/error through cli error()/success()/warning() (Output_Routing ->100). 27 test assertions updated to match the routing (test_symbolic_cli.py, test_symbolic_module.py). Full suite 990 pass / 0 fail (devpulse re-ran the suite; the first agent report wrongly claimed no changes and skipped tests — caught by verify, re-dispatched, now green).
This commit is contained in:
+2
-1
@@ -21,7 +21,8 @@ PyPI version — not the changelog header.
|
||||
`@cli` `success()/error()/warning()` helpers instead of raw `console.print`
|
||||
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,
|
||||
drone, flow, daemon, prax, ai_mail, backup, seedgo. Remaining waves following.
|
||||
drone, flow, daemon, prax, ai_mail, backup, seedgo, memory. Remaining waves
|
||||
following.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -349,6 +349,14 @@ def main():
|
||||
command = args[0]
|
||||
remaining_args = args[1:] if len(args) > 1 else []
|
||||
|
||||
if remaining_args and remaining_args[0] in ["--help", "-h"]:
|
||||
remaining_args = ["--help"]
|
||||
for module in modules:
|
||||
if module.handle_command(command, remaining_args):
|
||||
return
|
||||
print_help()
|
||||
return
|
||||
|
||||
if route_command(command, remaining_args, modules):
|
||||
return # Module handled it successfully
|
||||
else:
|
||||
@@ -367,5 +375,5 @@ if __name__ == "__main__":
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(f"[memory] Entry point error: {e}", exc_info=True)
|
||||
console.print(f"\nError: {e}")
|
||||
error(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
@@ -33,7 +33,7 @@ if sys.platform == "win32":
|
||||
_reconfigure(encoding="utf-8", errors="replace")
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.cli.apps.modules import console, error, warning
|
||||
from aipass.cli.apps.modules import console, error, success, warning
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# Handler import (same package family — json handlers)
|
||||
@@ -161,12 +161,12 @@ def _display_results(result: dict[str, Any], branch_filter: str | None) -> None:
|
||||
|
||||
if not violations:
|
||||
scope = f"@{branch_filter}" if branch_filter else "all branches"
|
||||
console.print(f"[green]No violations found[/green] across {scope} ({scanned} scanned)")
|
||||
success(f"No violations found across {scope} ({scanned} scanned)")
|
||||
console.print()
|
||||
return
|
||||
|
||||
# Per-violation detail (sorted worst-first by handler)
|
||||
console.print(f"[bold red]{total} violation(s) found[/bold red]")
|
||||
warning(f"{total} violation(s) found")
|
||||
console.print()
|
||||
|
||||
current_branch: str | None = None
|
||||
|
||||
@@ -111,7 +111,7 @@ def _run_process_command() -> None:
|
||||
if pool.get("skipped"):
|
||||
console.print(f"[dim]Pool: skipped — {pool.get('reason', 'unknown')}[/dim]")
|
||||
elif pool.get("success") is False:
|
||||
console.print(f"[red]Pool: failed — {pool.get('error', 'unknown')}[/red]")
|
||||
error(f"Pool: failed — {pool.get('error', 'unknown')}")
|
||||
else:
|
||||
files = pool.get("files_processed", 0)
|
||||
chunks = pool.get("total_chunks", 0)
|
||||
@@ -125,7 +125,7 @@ def _run_process_command() -> None:
|
||||
if rollover.get("skipped"):
|
||||
console.print("[dim]Rollover: no triggers[/dim]")
|
||||
elif rollover.get("success") is False:
|
||||
console.print(f"[red]Rollover: failed — {rollover.get('error', 'unknown')}[/red]")
|
||||
error(f"Rollover: failed — {rollover.get('error', 'unknown')}")
|
||||
else:
|
||||
processed = rollover.get("processed", 0)
|
||||
total = rollover.get("triggers", 0)
|
||||
|
||||
@@ -171,7 +171,7 @@ def print_help() -> None:
|
||||
console.print(" [cyan]status[/cyan] Show rollover statistics for all branches")
|
||||
console.print(" [cyan]check[/cyan] Check which files need rollover (dry run)")
|
||||
console.print(" [cyan]sync-lines[/cyan] Update line count metadata for all branches")
|
||||
console.print(" [cyan]push[/cyan] ⚠ Reset ALL per_branch limits to defaults (system-wide)")
|
||||
console.print(" [cyan]push[/cyan] Reset ALL per_branch limits to defaults (system-wide, use with caution)")
|
||||
console.print(" [cyan]help[/cyan] Show this help message")
|
||||
console.print()
|
||||
console.print("[bold]LIMITS:[/bold]")
|
||||
@@ -559,6 +559,5 @@ if __name__ == "__main__":
|
||||
# Execute command via handle_command
|
||||
command = sys.argv[1]
|
||||
if not handle_command(command, sys.argv[2:]):
|
||||
console.print(f"[red]Unknown command:[/red] {command}")
|
||||
console.print("Run with [cyan]help[/cyan] for available commands")
|
||||
error(f"Unknown command: {command}", suggestion="Run 'drone @memory rollover --help' for available commands")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -35,7 +35,7 @@ from rich.panel import Panel
|
||||
from rich import box
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.cli.apps.modules import console, error, warning
|
||||
from aipass.cli.apps.modules import console, error, success, warning
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# =============================================================================
|
||||
@@ -212,7 +212,7 @@ def show_search_results(
|
||||
filtered_results = result.get("results", [])
|
||||
|
||||
# Display summary
|
||||
console.print(f"[green]>[/green] Found {total_results} results in {collections_searched} collections")
|
||||
success(f"Found {total_results} results in {collections_searched} collections")
|
||||
console.print()
|
||||
|
||||
if not filtered_results and total_results == 0:
|
||||
@@ -346,6 +346,5 @@ if __name__ == "__main__":
|
||||
# Execute command via handle_command
|
||||
command = sys.argv[1]
|
||||
if not handle_command(command, sys.argv[2:]):
|
||||
console.print(f"[red]Unknown command:[/red] {command}")
|
||||
console.print("Run with [cyan]help[/cyan] for available commands")
|
||||
error(f"Unknown command: {command}", suggestion="Run 'drone @memory search --help' for available commands")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -32,7 +32,7 @@ if sys.platform == "win32":
|
||||
|
||||
# Service imports
|
||||
from aipass.prax import logger
|
||||
from aipass.cli.apps.modules import console, error, header, warning
|
||||
from aipass.cli.apps.modules import console, error, header, success, warning
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# Handler imports (domain-organized)
|
||||
@@ -687,16 +687,14 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
|
||||
if sub == "analyze":
|
||||
if not remaining:
|
||||
console.print("[red]Error:[/red] File path required")
|
||||
console.print("Usage: symbolic analyze <conversation.json>")
|
||||
error("File path required", suggestion="Usage: symbolic analyze <conversation.json>")
|
||||
return True
|
||||
analyze_file(remaining[0])
|
||||
return True
|
||||
|
||||
if sub == "extract":
|
||||
if not remaining:
|
||||
console.print("[red]Error:[/red] File path required")
|
||||
console.print("Usage: symbolic extract <conversation.json>")
|
||||
error("File path required", suggestion="Usage: symbolic extract <conversation.json>")
|
||||
return True
|
||||
extract_file(remaining[0], source_branch=remaining[1] if len(remaining) > 1 else None)
|
||||
return True
|
||||
@@ -726,16 +724,14 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
|
||||
if command == "analyze":
|
||||
if not args:
|
||||
console.print("[red]Error:[/red] File path required")
|
||||
console.print("Usage: symbolic analyze <conversation.json>")
|
||||
error("File path required", suggestion="Usage: symbolic analyze <conversation.json>")
|
||||
return True
|
||||
analyze_file(args[0])
|
||||
return True
|
||||
|
||||
if command == "extract":
|
||||
if not args:
|
||||
console.print("[red]Error:[/red] File path required")
|
||||
console.print("Usage: symbolic extract <conversation.json>")
|
||||
error("File path required", suggestion="Usage: symbolic extract <conversation.json>")
|
||||
return True
|
||||
extract_file(args[0], source_branch=args[1] if len(args) > 1 else None)
|
||||
return True
|
||||
@@ -851,7 +847,7 @@ def run_demo() -> None:
|
||||
dims = result["dimensions"]
|
||||
meta = result["metadata"]
|
||||
|
||||
console.print("[green]✓[/green] Analysis complete")
|
||||
success("Analysis complete")
|
||||
console.print()
|
||||
|
||||
console.print("[bold cyan]Extracted Dimensions:[/bold cyan]")
|
||||
@@ -868,7 +864,7 @@ def run_demo() -> None:
|
||||
console.print(f" [dim]Depth:[/dim] {meta.get('depth', 'unknown')}")
|
||||
console.print()
|
||||
else:
|
||||
console.print(f"[red]✗[/red] Analysis failed: {result.get('error', 'Unknown error')}")
|
||||
error(f"Analysis failed: {result.get('error', 'Unknown error')}")
|
||||
|
||||
# v2 LLM Extraction mock preview
|
||||
console.print()
|
||||
@@ -918,7 +914,7 @@ def run_demo() -> None:
|
||||
},
|
||||
}
|
||||
recall = format_fragment_recall(mock_stored)
|
||||
console.print(f" [green]Recall:[/green] {recall}")
|
||||
console.print(f" [cyan]Recall:[/cyan] {recall}")
|
||||
console.print()
|
||||
|
||||
console.print("[dim]Note: Run 'symbolic extract <file>' to use the real LLM pipeline[/dim]")
|
||||
@@ -945,8 +941,7 @@ def search_fragments_cli(args: List[str]) -> None:
|
||||
key, value = dim_arg.split("=", 1)
|
||||
dimension_filters[key] = value
|
||||
else:
|
||||
console.print(f"[red]Error:[/red] Invalid dimension format: {dim_arg}")
|
||||
console.print("Expected: --dimension KEY=VALUE")
|
||||
error(f"Invalid dimension format: {dim_arg}", suggestion="Expected: --dimension KEY=VALUE")
|
||||
return
|
||||
i += 2
|
||||
elif args[i] == "--trigger" and i + 1 < len(args):
|
||||
@@ -957,7 +952,7 @@ def search_fragments_cli(args: List[str]) -> None:
|
||||
n_results = int(args[i + 1])
|
||||
except ValueError:
|
||||
logger.warning(f"[symbolic] Invalid --n argument: {args[i + 1]}")
|
||||
console.print(f"[red]Error:[/red] Invalid number: {args[i + 1]}")
|
||||
error(f"Invalid number: {args[i + 1]}")
|
||||
return
|
||||
i += 2
|
||||
else:
|
||||
@@ -967,8 +962,10 @@ def search_fragments_cli(args: List[str]) -> None:
|
||||
query = " ".join(query_parts) if query_parts else None
|
||||
|
||||
if not query and not dimension_filters and not trigger_keywords:
|
||||
console.print("[red]Error:[/red] Search query, dimension filter, or trigger required")
|
||||
console.print("Usage: symbolic fragments <query> [--dimension KEY=VALUE] [--trigger KEYWORD]")
|
||||
error(
|
||||
"Search query, dimension filter, or trigger required",
|
||||
suggestion="Usage: symbolic fragments <query> [--dimension KEY=VALUE] [--trigger KEYWORD]",
|
||||
)
|
||||
return
|
||||
|
||||
console.print()
|
||||
@@ -996,13 +993,13 @@ def search_fragments_cli(args: List[str]) -> None:
|
||||
if not result.get("success"):
|
||||
error_msg = result.get("error", "Unknown error")
|
||||
logger.error(f"[symbolic] Fragment search failed: {error_msg}")
|
||||
console.print(f"[red]Error:[/red] {error_msg}")
|
||||
error(error_msg)
|
||||
return
|
||||
|
||||
results = result.get("results", [])
|
||||
methods = result.get("search_methods", [])
|
||||
|
||||
console.print(f"[green]Found {len(results)} fragments[/green] (methods: {', '.join(methods)})")
|
||||
success(f"Found {len(results)} fragments (methods: {', '.join(methods)})")
|
||||
console.print()
|
||||
|
||||
if not results:
|
||||
@@ -1127,11 +1124,11 @@ def run_hook_test(args: List[str]) -> None:
|
||||
context = extract_conversation_context(messages)
|
||||
|
||||
if context.get("success"):
|
||||
console.print(f" [green]Keywords:[/green] {context.get('keywords', [])}")
|
||||
console.print(f" [green]Mood:[/green] {context.get('mood', 'neutral')}")
|
||||
console.print(f" [green]Themes:[/green] {context.get('themes', [])}")
|
||||
console.print(f" [cyan]Keywords:[/cyan] {context.get('keywords', [])}")
|
||||
console.print(f" [cyan]Mood:[/cyan] {context.get('mood', 'neutral')}")
|
||||
console.print(f" [cyan]Themes:[/cyan] {context.get('themes', [])}")
|
||||
else:
|
||||
console.print(f" [red]Failed:[/red] {context.get('error', 'Unknown')}")
|
||||
error(f"Failed: {context.get('error', 'Unknown')}")
|
||||
return
|
||||
|
||||
console.print()
|
||||
@@ -1142,9 +1139,9 @@ def run_hook_test(args: List[str]) -> None:
|
||||
|
||||
if frag_result.get("success"):
|
||||
fragments = frag_result.get("fragments", [])
|
||||
console.print(f" [green]Query used:[/green] {frag_result.get('query_used', '')}")
|
||||
console.print(f" [green]Threshold:[/green] {frag_result.get('threshold_applied', 0.3)}")
|
||||
console.print(f" [green]Fragments found:[/green] {len(fragments)}")
|
||||
console.print(f" [cyan]Query used:[/cyan] {frag_result.get('query_used', '')}")
|
||||
console.print(f" [cyan]Threshold:[/cyan] {frag_result.get('threshold_applied', 0.3)}")
|
||||
console.print(f" [cyan]Fragments found:[/cyan] {len(fragments)}")
|
||||
|
||||
if fragments:
|
||||
for i, frag in enumerate(fragments, 1):
|
||||
@@ -1152,7 +1149,7 @@ def run_hook_test(args: List[str]) -> None:
|
||||
content = frag.get("content", "")[:80]
|
||||
console.print(f" [{i}] Score: {score:.2%} - {content}...")
|
||||
else:
|
||||
console.print(f" [yellow]No fragments:[/yellow] {frag_result.get('message', frag_result.get('error', ''))}")
|
||||
warning(f"No fragments: {frag_result.get('message', frag_result.get('error', ''))}")
|
||||
|
||||
console.print()
|
||||
|
||||
@@ -1170,7 +1167,7 @@ def run_hook_test(args: List[str]) -> None:
|
||||
|
||||
if result.get("success"):
|
||||
if result.get("surfaced"):
|
||||
console.print("[green]Fragment surfaced![/green]")
|
||||
success("Fragment surfaced!")
|
||||
console.print()
|
||||
|
||||
recall = result.get("recall", "")
|
||||
@@ -1182,7 +1179,7 @@ def run_hook_test(args: List[str]) -> None:
|
||||
else:
|
||||
console.print(f"[yellow]Not surfaced:[/yellow] {result.get('reason', 'Unknown')}")
|
||||
else:
|
||||
console.print(f"[red]Hook failed:[/red] {result.get('error', 'Unknown')}")
|
||||
error(f"Hook failed: {result.get('error', 'Unknown')}")
|
||||
|
||||
console.print()
|
||||
|
||||
@@ -1193,7 +1190,7 @@ def run_hook_test(args: List[str]) -> None:
|
||||
frag_metadata = frag.get("metadata", {})
|
||||
if frag_metadata.get("schema_version") == "v2":
|
||||
recall_preview = format_fragment_recall(frag)
|
||||
console.print(f" [green]Fragment {i} (v2):[/green] {recall_preview}")
|
||||
console.print(f" [cyan]Fragment {i} (v2):[/cyan] {recall_preview}")
|
||||
else:
|
||||
# Show what it would look like if it were v2
|
||||
console.print(f" [dim]Fragment {i} (v1 - no v2 metadata)[/dim]")
|
||||
@@ -1220,18 +1217,18 @@ def analyze_file(file_path: str) -> None:
|
||||
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
console.print(f"[red]Error:[/red] File not found: {file_path}")
|
||||
error(f"File not found: {file_path}")
|
||||
return
|
||||
|
||||
read_result = memory_files.read_memory_file(path)
|
||||
if not read_result.get("success"):
|
||||
console.print(f"[red]Error:[/red] {read_result.get('error', 'Failed to read JSON')}")
|
||||
error(read_result.get("error", "Failed to read JSON"))
|
||||
return
|
||||
|
||||
chat_history = read_result.get("data")
|
||||
|
||||
if not isinstance(chat_history, list):
|
||||
console.print("[red]Error:[/red] Expected JSON array of messages")
|
||||
error("Expected JSON array of messages")
|
||||
return
|
||||
|
||||
console.print()
|
||||
@@ -1244,7 +1241,7 @@ def analyze_file(file_path: str) -> None:
|
||||
dims = result["dimensions"]
|
||||
meta = result["metadata"]
|
||||
|
||||
console.print("[green]✓[/green] Analysis complete")
|
||||
success("Analysis complete")
|
||||
console.print()
|
||||
|
||||
console.print("[bold cyan]Extracted Dimensions:[/bold cyan]")
|
||||
@@ -1262,7 +1259,7 @@ def analyze_file(file_path: str) -> None:
|
||||
console.print()
|
||||
json_handler.log_operation("symbolic_analyze", {"file": path.name, "messages": result["message_count"]})
|
||||
else:
|
||||
console.print(f"[red]✗[/red] Analysis failed: {result.get('error', 'Unknown error')}")
|
||||
error(f"Analysis failed: {result.get('error', 'Unknown error')}")
|
||||
|
||||
|
||||
def extract_file(file_path: str, source_branch: str | None = None) -> None:
|
||||
@@ -1280,18 +1277,18 @@ def extract_file(file_path: str, source_branch: str | None = None) -> None:
|
||||
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
console.print(f"[red]Error:[/red] File not found: {file_path}")
|
||||
error(f"File not found: {file_path}")
|
||||
return
|
||||
|
||||
read_result = memory_files.read_memory_file(path)
|
||||
if not read_result.get("success"):
|
||||
console.print(f"[red]Error:[/red] {read_result.get('error', 'Failed to read JSON')}")
|
||||
error(read_result.get("error", "Failed to read JSON"))
|
||||
return
|
||||
|
||||
chat_history = read_result.get("data")
|
||||
|
||||
if not isinstance(chat_history, list):
|
||||
console.print("[red]Error:[/red] Expected JSON array of messages")
|
||||
error("Expected JSON array of messages")
|
||||
return
|
||||
|
||||
console.print()
|
||||
@@ -1309,20 +1306,20 @@ def extract_file(file_path: str, source_branch: str | None = None) -> None:
|
||||
result = extract_and_store_llm(chat_history, source_branch=source_branch)
|
||||
|
||||
if result.get("success"):
|
||||
console.print("[green]Pipeline complete[/green]")
|
||||
success("Pipeline complete")
|
||||
console.print()
|
||||
console.print(f" [cyan]Processed:[/cyan] {result.get('processed', 0)}")
|
||||
console.print(f" [green]Added:[/green] {result.get('added', 0)}")
|
||||
console.print(f" [cyan]Added:[/cyan] {result.get('added', 0)}")
|
||||
console.print(f" [yellow]Updated:[/yellow] {result.get('updated', 0)}")
|
||||
console.print(f" [dim]Skipped:[/dim] {result.get('skipped', 0)}")
|
||||
|
||||
if result.get("errors"):
|
||||
console.print()
|
||||
console.print(f" [red]Errors ({len(result['errors'])}):[/red]")
|
||||
warning(f"Errors ({len(result['errors'])})")
|
||||
for err in result["errors"]:
|
||||
console.print(f" - {err}")
|
||||
else:
|
||||
console.print(f"[red]Pipeline failed:[/red] {result.get('errors', ['Unknown error'])}")
|
||||
error(f"Pipeline failed: {result.get('errors', ['Unknown error'])}")
|
||||
|
||||
console.print()
|
||||
logger.info(f"[symbolic] extract_file complete: {result}")
|
||||
@@ -1539,15 +1536,15 @@ def bootstrap_from_jsonl(max_sessions: int = 8) -> None:
|
||||
total_errors += e
|
||||
processed_count += 1
|
||||
console.print(
|
||||
f" [green]+{a} added[/green]"
|
||||
f" [cyan]+{a} added[/cyan]"
|
||||
f"{f', {u} updated' if u else ''}"
|
||||
f"{f', {s} skipped' if s else ''}"
|
||||
f"{f', [red]{e} errors[/red]' if e else ''}"
|
||||
f"{f', {e} errors' if e else ''}"
|
||||
)
|
||||
else:
|
||||
total_errors += 1
|
||||
err_msg = result.get("errors", ["Unknown"])
|
||||
console.print(f" [red]Failed: {err_msg}[/red]")
|
||||
error(f"Failed: {err_msg}")
|
||||
|
||||
# Brief pause between API calls to avoid rate limiting
|
||||
if i < len(sessions):
|
||||
@@ -1558,11 +1555,11 @@ def bootstrap_from_jsonl(max_sessions: int = 8) -> None:
|
||||
header("Bootstrap Summary")
|
||||
console.print()
|
||||
console.print(f" [cyan]Sessions processed:[/cyan] {processed_count}/{len(sessions)}")
|
||||
console.print(f" [green]Fragments added:[/green] {total_added}")
|
||||
console.print(f" [cyan]Fragments added:[/cyan] {total_added}")
|
||||
console.print(f" [yellow]Fragments updated:[/yellow] {total_updated}")
|
||||
console.print(f" [dim]Skipped (dedup):[/dim] {total_skipped}")
|
||||
if total_errors:
|
||||
console.print(f" [red]Errors:[/red] {total_errors}")
|
||||
warning(f"Errors: {total_errors}")
|
||||
console.print()
|
||||
|
||||
# Verify collection count
|
||||
@@ -1599,6 +1596,5 @@ if __name__ == "__main__":
|
||||
# Execute command via handle_command
|
||||
command = sys.argv[1]
|
||||
if not handle_command(command, sys.argv[2:]):
|
||||
console.print(f"[red]Unknown command:[/red] {command}")
|
||||
console.print("Run with [cyan]help[/cyan] for available commands")
|
||||
error(f"Unknown command: {command}", suggestion="Run 'drone @memory symbolic --help' for available commands")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -285,7 +285,7 @@ def _display_push_results(result: dict, dry_run: bool) -> None:
|
||||
# Errors
|
||||
errors = result.get("errors", [])
|
||||
if errors:
|
||||
console.print(f"[red]Errors ({len(errors)}):[/red]")
|
||||
warning(f"Errors ({len(errors)}):")
|
||||
for err in errors:
|
||||
error(err)
|
||||
console.print()
|
||||
@@ -436,7 +436,7 @@ def _display_diff_results(branch_name: str | None = None) -> None:
|
||||
warning(f"{total_diffs} branches have template differences")
|
||||
console.print("[dim]Run 'push-templates --dry-run' to preview changes[/dim]")
|
||||
if total_errors > 0:
|
||||
console.print(f"[red]{total_errors} errors encountered[/red]")
|
||||
warning(f"{total_errors} errors encountered")
|
||||
|
||||
logger.info(f"[templates] Diff complete: {total_diffs} branches with diffs, {total_errors} errors")
|
||||
json_handler.log_operation(
|
||||
@@ -454,7 +454,7 @@ def _display_file_diffs(file_diffs: list) -> None:
|
||||
console.print(f" [green]+ {a}[/green]")
|
||||
if entry.get("removals"):
|
||||
for r in entry["removals"]:
|
||||
console.print(f" [red]- {r}[/red]")
|
||||
console.print(f" [magenta]- {r}[/magenta]")
|
||||
if entry.get("modifications"):
|
||||
for m in entry["modifications"]:
|
||||
console.print(f" [yellow]~ {m}[/yellow]")
|
||||
@@ -619,6 +619,5 @@ if __name__ == "__main__":
|
||||
# Execute command via handle_command
|
||||
command = sys.argv[1]
|
||||
if not handle_command(command, sys.argv[2:]):
|
||||
console.print(f"[red]Unknown command:[/red] {command}")
|
||||
console.print("Run with [cyan]help[/cyan] for available commands")
|
||||
error(f"Unknown command: {command}", suggestion="Run 'drone @memory templates --help' for available commands")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -31,7 +31,7 @@ if sys.platform == "win32":
|
||||
_reconfigure(encoding="utf-8", errors="replace")
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.cli.apps.modules import console, error
|
||||
from aipass.cli.apps.modules import console, error, warning
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# =============================================================================
|
||||
@@ -181,7 +181,7 @@ def _verify_plan(plan_label: str) -> None:
|
||||
if found:
|
||||
console.print(f" Plan {plan_label}: [green]Vectorized[/green] ({count} chunks)")
|
||||
else:
|
||||
console.print(f" Plan {plan_label}: [red]NOT vectorized[/red]")
|
||||
warning(f"Plan {plan_label}: NOT vectorized")
|
||||
console.print()
|
||||
|
||||
json_handler.log_operation(
|
||||
@@ -292,6 +292,5 @@ if __name__ == "__main__":
|
||||
# Execute command via handle_command
|
||||
command = sys.argv[1]
|
||||
if not handle_command(command, sys.argv[2:]):
|
||||
console.print(f"[red]Unknown command:[/red] {command}")
|
||||
console.print("Run with [cyan]help[/cyan] for available commands")
|
||||
error(f"Unknown command: {command}", suggestion="Run 'drone @memory verify --help' for available commands")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -61,11 +61,13 @@ def _mock_symbolic_infrastructure(monkeypatch):
|
||||
mock_console = MagicMock()
|
||||
mock_header = MagicMock()
|
||||
mock_error = MagicMock()
|
||||
mock_success = MagicMock()
|
||||
mock_warning = MagicMock()
|
||||
cli_modules = MagicMock()
|
||||
cli_modules.console = mock_console
|
||||
cli_modules.header = mock_header
|
||||
cli_modules.error = mock_error
|
||||
cli_modules.success = mock_success
|
||||
cli_modules.warning = mock_warning
|
||||
monkeypatch.setitem(sys.modules, "aipass.cli", MagicMock())
|
||||
monkeypatch.setitem(sys.modules, "aipass.cli.apps", MagicMock())
|
||||
@@ -147,6 +149,7 @@ def _mock_symbolic_infrastructure(monkeypatch):
|
||||
_handler_mocks.console = mock_console
|
||||
_handler_mocks.header = mock_header
|
||||
_handler_mocks.error_fn = mock_error
|
||||
_handler_mocks.success_fn = mock_success
|
||||
_handler_mocks.warning_fn = mock_warning
|
||||
_handler_mocks.json_handler = mock_json_handler
|
||||
_handler_mocks.memory_files = mock_memory_files
|
||||
@@ -237,7 +240,7 @@ class TestHandleCommand:
|
||||
result = symbolic.handle_command("symbolic", ["analyze"])
|
||||
assert result is True
|
||||
# Should print error about missing file path
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("File path required" in c for c in calls)
|
||||
|
||||
def test_symbolic_analyze_with_file(self, tmp_path):
|
||||
@@ -256,7 +259,7 @@ class TestHandleCommand:
|
||||
symbolic = _import_symbolic()
|
||||
result = symbolic.handle_command("symbolic", ["extract"])
|
||||
assert result is True
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("File path required" in c for c in calls)
|
||||
|
||||
def test_symbolic_extract_with_branch(self, tmp_path):
|
||||
@@ -355,7 +358,7 @@ class TestHandleCommand:
|
||||
symbolic = _import_symbolic()
|
||||
result = symbolic.handle_command("analyze", [])
|
||||
assert result is True
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("File path required" in c for c in calls)
|
||||
|
||||
def test_backward_compat_extract(self, tmp_path):
|
||||
@@ -377,7 +380,7 @@ class TestHandleCommand:
|
||||
symbolic = _import_symbolic()
|
||||
result = symbolic.handle_command("extract", [])
|
||||
assert result is True
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("File path required" in c for c in calls)
|
||||
|
||||
def test_backward_compat_bootstrap(self, monkeypatch):
|
||||
@@ -485,7 +488,7 @@ class TestRunDemo:
|
||||
|
||||
_handler_mocks.extractor.analyze_conversation.assert_called_once()
|
||||
_handler_mocks.hook.format_fragment_recall.assert_called()
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.success_fn.call_args_list]
|
||||
assert any("Analysis complete" in c for c in calls)
|
||||
|
||||
def test_run_demo_analysis_failure(self):
|
||||
@@ -498,7 +501,7 @@ class TestRunDemo:
|
||||
|
||||
symbolic.run_demo()
|
||||
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Analysis failed" in c for c in calls)
|
||||
|
||||
|
||||
@@ -513,7 +516,7 @@ class TestSearchFragmentsCli:
|
||||
def test_search_no_args(self):
|
||||
symbolic = _import_symbolic()
|
||||
symbolic.search_fragments_cli([])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("query, dimension filter, or trigger required" in c for c in calls)
|
||||
|
||||
def test_search_query_only(self):
|
||||
@@ -566,13 +569,13 @@ class TestSearchFragmentsCli:
|
||||
def test_search_invalid_n(self):
|
||||
symbolic = _import_symbolic()
|
||||
symbolic.search_fragments_cli(["query", "--n", "abc"])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Invalid number" in c for c in calls)
|
||||
|
||||
def test_search_invalid_dimension(self):
|
||||
symbolic = _import_symbolic()
|
||||
symbolic.search_fragments_cli(["query", "--dimension", "bad_format_no_equals"])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Invalid dimension format" in c for c in calls)
|
||||
|
||||
def test_search_no_results(self):
|
||||
@@ -592,7 +595,7 @@ class TestSearchFragmentsCli:
|
||||
"error": "DB unavailable",
|
||||
}
|
||||
symbolic.search_fragments_cli(["test"])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("DB unavailable" in c for c in calls)
|
||||
|
||||
def test_search_v1_results(self):
|
||||
@@ -722,14 +725,14 @@ class TestRunHookTest:
|
||||
symbolic = _import_symbolic()
|
||||
self._setup_hook_mocks(context_success=False)
|
||||
symbolic.run_hook_test(["text"])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Failed" in c for c in calls)
|
||||
|
||||
def test_hook_test_surfaced(self):
|
||||
symbolic = _import_symbolic()
|
||||
self._setup_hook_mocks(surfaced=True)
|
||||
symbolic.run_hook_test(["text"])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.success_fn.call_args_list]
|
||||
assert any("Fragment surfaced" in c for c in calls)
|
||||
|
||||
def test_hook_test_not_surfaced(self):
|
||||
@@ -743,7 +746,7 @@ class TestRunHookTest:
|
||||
symbolic = _import_symbolic()
|
||||
self._setup_hook_mocks(hook_success=False)
|
||||
symbolic.run_hook_test(["text"])
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Hook failed" in c for c in calls)
|
||||
|
||||
def test_hook_test_v2_fragments(self):
|
||||
@@ -800,7 +803,7 @@ class TestAnalyzeFile:
|
||||
def test_analyze_file_not_found(self):
|
||||
symbolic = _import_symbolic()
|
||||
symbolic.analyze_file("/nonexistent/path/to/file.json")
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("File not found" in c for c in calls)
|
||||
|
||||
def test_analyze_file_read_fails(self, tmp_path):
|
||||
@@ -813,7 +816,7 @@ class TestAnalyzeFile:
|
||||
"error": "Read error",
|
||||
}
|
||||
symbolic.analyze_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Read error" in c for c in calls)
|
||||
|
||||
def test_analyze_file_not_list(self, tmp_path):
|
||||
@@ -826,7 +829,7 @@ class TestAnalyzeFile:
|
||||
"data": {"not": "a list"},
|
||||
}
|
||||
symbolic.analyze_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Expected JSON array" in c for c in calls)
|
||||
|
||||
def test_analyze_file_success(self, tmp_path):
|
||||
@@ -842,7 +845,7 @@ class TestAnalyzeFile:
|
||||
_handler_mocks.extractor.analyze_conversation.return_value = _default_analysis_result()
|
||||
|
||||
symbolic.analyze_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.success_fn.call_args_list]
|
||||
assert any("Analysis complete" in c for c in calls)
|
||||
|
||||
def test_analyze_file_failure(self, tmp_path):
|
||||
@@ -861,7 +864,7 @@ class TestAnalyzeFile:
|
||||
}
|
||||
|
||||
symbolic.analyze_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Analysis failed" in c for c in calls)
|
||||
|
||||
|
||||
@@ -876,7 +879,7 @@ class TestExtractFile:
|
||||
def test_extract_file_not_found(self):
|
||||
symbolic = _import_symbolic()
|
||||
symbolic.extract_file("/nonexistent/path/to/file.json")
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("File not found" in c for c in calls)
|
||||
|
||||
def test_extract_file_read_fails(self, tmp_path):
|
||||
@@ -889,7 +892,7 @@ class TestExtractFile:
|
||||
"error": "Read error",
|
||||
}
|
||||
symbolic.extract_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Read error" in c for c in calls)
|
||||
|
||||
def test_extract_file_not_list(self, tmp_path):
|
||||
@@ -902,7 +905,7 @@ class TestExtractFile:
|
||||
"data": {"not": "a list"},
|
||||
}
|
||||
symbolic.extract_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Expected JSON array" in c for c in calls)
|
||||
|
||||
def test_extract_file_success(self, tmp_path):
|
||||
@@ -937,7 +940,7 @@ class TestExtractFile:
|
||||
}
|
||||
|
||||
symbolic.extract_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.success_fn.call_args_list]
|
||||
assert any("Pipeline complete" in c for c in calls)
|
||||
|
||||
def test_extract_file_with_branch(self, tmp_path):
|
||||
@@ -976,7 +979,7 @@ class TestExtractFile:
|
||||
}
|
||||
|
||||
symbolic.extract_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Pipeline failed" in c for c in calls)
|
||||
|
||||
def test_extract_file_with_errors(self, tmp_path):
|
||||
@@ -1011,9 +1014,10 @@ class TestExtractFile:
|
||||
]
|
||||
|
||||
symbolic.extract_file(str(chat_file))
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
# Pipeline should still complete with errors shown
|
||||
calls = [str(c) for c in _handler_mocks.success_fn.call_args_list]
|
||||
assert any("Pipeline complete" in c for c in calls)
|
||||
calls = [str(c) for c in _handler_mocks.warning_fn.call_args_list]
|
||||
assert any("Errors" in c for c in calls)
|
||||
|
||||
# Reset side_effect
|
||||
@@ -1297,5 +1301,5 @@ class TestBootstrapFromJsonl:
|
||||
}
|
||||
|
||||
symbolic.bootstrap_from_jsonl()
|
||||
calls = [str(c) for c in _handler_mocks.console.print.call_args_list]
|
||||
calls = [str(c) for c in _handler_mocks.error_fn.call_args_list]
|
||||
assert any("Failed" in c for c in calls)
|
||||
|
||||
@@ -943,7 +943,7 @@ class TestSearchFragmentsCli:
|
||||
symbolic.search_fragments_cli([])
|
||||
|
||||
# Should print error about missing query
|
||||
assert _handler_mocks.console.print.called
|
||||
assert _handler_mocks.error_fn.called
|
||||
|
||||
def test_dimension_filter_parsing(self):
|
||||
symbolic = _import_symbolic()
|
||||
@@ -1091,7 +1091,7 @@ class TestAnalyzeFile:
|
||||
symbolic.analyze_file(nonexistent)
|
||||
|
||||
# Should print error about missing file
|
||||
assert _handler_mocks.console.print.called
|
||||
assert _handler_mocks.error_fn.called
|
||||
|
||||
def test_successful_analysis(self, tmp_path):
|
||||
symbolic = _import_symbolic()
|
||||
@@ -1138,7 +1138,7 @@ class TestAnalyzeFile:
|
||||
symbolic.analyze_file(str(chat_file))
|
||||
|
||||
# Should print error about expected array
|
||||
assert _handler_mocks.console.print.called
|
||||
assert _handler_mocks.error_fn.called
|
||||
|
||||
def test_read_failure(self, tmp_path):
|
||||
symbolic = _import_symbolic()
|
||||
@@ -1153,7 +1153,7 @@ class TestAnalyzeFile:
|
||||
|
||||
symbolic.analyze_file(str(chat_file))
|
||||
|
||||
assert _handler_mocks.console.print.called
|
||||
assert _handler_mocks.error_fn.called
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user