#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:
AIOSAI
2026-07-11 02:40:26 -07:00
parent 90296b80f0
commit 80badb784a
11 changed files with 106 additions and 101 deletions
+2 -1
View File
@@ -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
+9 -1
View File
@@ -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)
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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)
+2 -3
View File
@@ -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)
+3 -4
View File
@@ -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)
+45 -49
View File
@@ -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)
+4 -5
View File
@@ -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)
+3 -4
View File
@@ -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)
+29 -25
View File
@@ -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
# ===========================================================================