Private
Public Access
more organization
This commit is contained in:
+3
-1
@@ -1,10 +1,12 @@
|
||||
# src/bg_shader.py
|
||||
import time
|
||||
import math
|
||||
from typing import Optional
|
||||
import numpy as np
|
||||
|
||||
from typing import Optional
|
||||
from imgui_bundle import imgui, nanovg as nvg, hello_imgui
|
||||
|
||||
|
||||
class BackgroundShader:
|
||||
def __init__(self):
|
||||
"""
|
||||
|
||||
+12
-21
@@ -1,9 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from imgui_bundle import imgui
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Callable, List, Dict, Any
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class Command:
|
||||
id: str
|
||||
@@ -14,7 +17,6 @@ class Command:
|
||||
enabled_when: Optional[str] = None
|
||||
action: Optional[Callable] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScoredCommand:
|
||||
command: Command
|
||||
@@ -70,12 +72,9 @@ def _is_subsequence(query: str, target: str) -> bool:
|
||||
|
||||
def _compute_score(query: str, target: str) -> float:
|
||||
score = 0.0
|
||||
if target.startswith(query):
|
||||
score += 1.0
|
||||
elif _starts_at_word_boundary(query, target):
|
||||
score += 0.5
|
||||
if _is_contiguous(query, target):
|
||||
score += 0.3
|
||||
if target.startswith(query): score += 1.0
|
||||
elif _starts_at_word_boundary(query, target): score += 0.5
|
||||
if _is_contiguous(query, target): score += 0.3
|
||||
gaps = _count_gaps(query, target)
|
||||
score -= 0.1 * gaps
|
||||
return score
|
||||
@@ -97,8 +96,7 @@ def _count_gaps(query: str, target: str) -> int:
|
||||
last_match = -1
|
||||
for ti, ch in enumerate(target):
|
||||
if qi < len(query) and ch == query[qi]:
|
||||
if last_match >= 0 and ti - last_match > 1:
|
||||
gaps += ti - last_match - 1
|
||||
if last_match >= 0 and ti - last_match > 1: gaps += ti - last_match - 1
|
||||
last_match = ti
|
||||
qi += 1
|
||||
return gaps
|
||||
@@ -128,19 +126,14 @@ def render_palette_modal(app: Any, commands: List[Command]) -> None:
|
||||
if not getattr(app, "show_command_palette", False):
|
||||
return
|
||||
|
||||
from imgui_bundle import imgui
|
||||
|
||||
viewport = imgui.get_main_viewport()
|
||||
center = viewport.get_center()
|
||||
imgui.set_next_window_pos((center.x - 300, center.y - 200), imgui.Cond_.always)
|
||||
imgui.set_next_window_size((600, 400), imgui.Cond_.always)
|
||||
|
||||
if not hasattr(app, "_command_palette_query"):
|
||||
app._command_palette_query = ""
|
||||
if not hasattr(app, "_command_palette_selected"):
|
||||
app._command_palette_selected = 0
|
||||
if not hasattr(app, "_command_palette_focused"):
|
||||
app._command_palette_focused = False
|
||||
if not hasattr(app, "_command_palette_query"): app._command_palette_query = ""
|
||||
if not hasattr(app, "_command_palette_selected"): app._command_palette_selected = 0
|
||||
if not hasattr(app, "_command_palette_focused"): app._command_palette_focused = False
|
||||
|
||||
# Set focus on the window + input field ONCE per open.
|
||||
if not app._command_palette_focused:
|
||||
@@ -167,10 +160,8 @@ def render_palette_modal(app: Any, commands: List[Command]) -> None:
|
||||
# Process Up/Down/Enter BEFORE input_text so we see the keys before the
|
||||
# input field consumes them for cursor movement / text editing.
|
||||
results = fuzzy_match(app._command_palette_query, commands, top_n=20)
|
||||
if results:
|
||||
app._command_palette_selected = max(0, min(app._command_palette_selected, len(results) - 1))
|
||||
else:
|
||||
app._command_palette_selected = 0
|
||||
if results: app._command_palette_selected = max(0, min(app._command_palette_selected, len(results) - 1))
|
||||
else: app._command_palette_selected = 0
|
||||
|
||||
if imgui.is_key_pressed(imgui.Key.down_arrow):
|
||||
if results:
|
||||
|
||||
+13
-21
@@ -1,13 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import webbrowser
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
from src import models
|
||||
from src import theme_2
|
||||
|
||||
from src.command_palette import CommandRegistry
|
||||
from src.hot_reloader import HotReloader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.gui_2 import App
|
||||
|
||||
|
||||
registry = CommandRegistry()
|
||||
|
||||
|
||||
@@ -38,14 +44,10 @@ def reset_session(app: "App") -> None:
|
||||
"""Reset Session — Reset the AI session, clear comms and tool logs."""
|
||||
from src import ai_client
|
||||
ai_client.reset_session()
|
||||
if hasattr(app, "_handle_reset_session"):
|
||||
app._handle_reset_session()
|
||||
if hasattr(app, "_comms_log"):
|
||||
app._comms_log.clear()
|
||||
if hasattr(app, "_tool_log"):
|
||||
app._tool_log.clear()
|
||||
if hasattr(app, "ai_response"):
|
||||
app.ai_response = ""
|
||||
if hasattr(app, "_handle_reset_session"): app._handle_reset_session()
|
||||
if hasattr(app, "_comms_log"): app._comms_log.clear()
|
||||
if hasattr(app, "_tool_log"): app._tool_log.clear()
|
||||
if hasattr(app, "ai_response"): app.ai_response = ""
|
||||
|
||||
|
||||
@registry.register
|
||||
@@ -98,11 +100,8 @@ def save_project(app: "App") -> None:
|
||||
@registry.register
|
||||
def save_all(app: "App") -> None:
|
||||
"""Save All — Flush to project, flush to config, save global config."""
|
||||
from src import models
|
||||
if hasattr(app, "_flush_to_project"):
|
||||
app._flush_to_project()
|
||||
if hasattr(app, "_flush_to_config"):
|
||||
app._flush_to_config()
|
||||
if hasattr(app, "_flush_to_project"): app._flush_to_project()
|
||||
if hasattr(app, "_flush_to_config"): app._flush_to_config()
|
||||
if hasattr(app, "config"):
|
||||
try:
|
||||
models.save_config(app.config)
|
||||
@@ -229,7 +228,6 @@ def show_workspace_manager(app: "App") -> None:
|
||||
@registry.register
|
||||
def trigger_hot_reload(app: "App") -> None:
|
||||
"""Hot Reload — Reload the GUI module to pick up code changes."""
|
||||
from src.hot_reloader import HotReloader
|
||||
HotReloader.reload("src.gui_2", app)
|
||||
|
||||
|
||||
@@ -254,28 +252,24 @@ def redo(app: "App") -> None:
|
||||
@registry.register
|
||||
def switch_to_dark_theme(app: "App") -> None:
|
||||
"""Switch to Dark Theme (10x Dark palette)."""
|
||||
from src import theme_2
|
||||
theme_2.apply("10x Dark")
|
||||
|
||||
|
||||
@registry.register
|
||||
def switch_to_light_theme(app: "App") -> None:
|
||||
"""Switch to Light Theme (ImGui Light palette)."""
|
||||
from src import theme_2
|
||||
theme_2.apply("ImGui Light")
|
||||
|
||||
|
||||
@registry.register
|
||||
def switch_to_nerv_theme(app: "App") -> None:
|
||||
"""Switch to NERV Theme (Tactical Console aesthetic)."""
|
||||
from src import theme_2
|
||||
theme_2.apply("NERV")
|
||||
|
||||
|
||||
@registry.register
|
||||
def cycle_theme(app: "App") -> None:
|
||||
"""Cycle Theme — Switch to the next theme in the cycle (Dark → Light → NERV → Dark)."""
|
||||
from src import theme_2
|
||||
order = ["10x Dark", "ImGui Light", "NERV"]
|
||||
current = theme_2.get_current_palette()
|
||||
if current in order:
|
||||
@@ -292,14 +286,12 @@ def cycle_theme(app: "App") -> None:
|
||||
@registry.register
|
||||
def show_documentation(app: "App") -> None:
|
||||
"""Show Documentation — Open the project URL in the browser."""
|
||||
import webbrowser
|
||||
webbrowser.open("https://git.cozyair.dev/ed/manual_slop/")
|
||||
|
||||
|
||||
@registry.register
|
||||
def show_command_palette_help(app: "App") -> None:
|
||||
"""Show Command Palette Help — Open the docs/Readme.md in the Text Viewer."""
|
||||
from pathlib import Path
|
||||
if hasattr(app, "readme_text"):
|
||||
docs_readme = Path("docs/Readme.md")
|
||||
if docs_readme.exists():
|
||||
|
||||
@@ -44,8 +44,6 @@ from src import mma_prompts
|
||||
|
||||
def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str, Any]]:
|
||||
"""
|
||||
|
||||
|
||||
Tier 2 (Tech Lead) call.
|
||||
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
|
||||
[C: tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_failure, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_success, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_success, tests/test_orchestration_logic.py:test_generate_tickets]
|
||||
@@ -68,8 +66,8 @@ def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str,
|
||||
try:
|
||||
# 3. Call Tier 2 Model
|
||||
response = ai_client.send(
|
||||
md_content="",
|
||||
user_message=user_message
|
||||
md_content = "",
|
||||
user_message = user_message
|
||||
)
|
||||
# 4. Parse JSON Output
|
||||
# Extract JSON array from markdown code blocks if present
|
||||
@@ -101,8 +99,6 @@ from src.models import Ticket
|
||||
|
||||
def topological_sort(tickets: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""
|
||||
|
||||
|
||||
Sorts a list of tickets based on their 'depends_on' field.
|
||||
Raises ValueError if a circular dependency or missing internal dependency is detected.
|
||||
[C: tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_complex, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_cycle, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_empty, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_linear, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_missing_dependency, tests/test_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_dag_engine.py:test_topological_sort, tests/test_dag_engine.py:test_topological_sort_cycle, tests/test_orchestration_logic.py:test_topological_sort, tests/test_orchestration_logic.py:test_topological_sort_circular, tests/test_perf_dag.py:test_dag_edge_cases, tests/test_perf_dag.py:test_dag_performance]
|
||||
|
||||
+3
-4
@@ -116,10 +116,8 @@ class TrackDAG:
|
||||
if is_backtracking:
|
||||
path.remove(node_id)
|
||||
continue
|
||||
if node_id in path:
|
||||
return True
|
||||
if node_id in visited:
|
||||
continue
|
||||
if node_id in path: return True
|
||||
if node_id in visited: continue
|
||||
visited.add(node_id)
|
||||
path.add(node_id)
|
||||
stack.append((node_id, True))
|
||||
@@ -216,3 +214,4 @@ class ExecutionEngine:
|
||||
ticket = self.dag.ticket_map.get(task_id)
|
||||
if ticket:
|
||||
ticket.status = status
|
||||
|
||||
@@ -27,18 +27,14 @@ class ExternalEditorLauncher:
|
||||
return self.config.editors.get(editor_name)
|
||||
return self.config.get_default()
|
||||
|
||||
def build_diff_command(
|
||||
self, editor: TextEditorConfig, original_path: str, modified_path: str
|
||||
) -> List[str]:
|
||||
def build_diff_command(self, editor: TextEditorConfig, original_path: str, modified_path: str) -> List[str]:
|
||||
"""
|
||||
[C: tests/test_external_editor.py:TestExternalEditorLauncher.test_build_diff_command, tests/test_external_editor_gui.py:test_verify_command_format, tests/test_external_editor_gui.py:test_verify_vscode_command_format]
|
||||
"""
|
||||
cmd = [editor.path] + editor.diff_args + [original_path, modified_path]
|
||||
return cmd
|
||||
|
||||
def launch_diff(
|
||||
self, editor_name: Optional[str], original_path: str, modified_path: str
|
||||
) -> Optional[subprocess.Popen]:
|
||||
def launch_diff(self, editor_name: Optional[str], original_path: str, modified_path: str) -> Optional[subprocess.Popen]:
|
||||
"""
|
||||
[C: src/gui_2.py:App._open_patch_in_external_editor, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_file_not_found, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_missing_editor, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_success]
|
||||
"""
|
||||
|
||||
+17
-24
@@ -49,12 +49,12 @@ _ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {}
|
||||
|
||||
class ASTParser:
|
||||
"""
|
||||
|
||||
|
||||
Parser for extracting AST-based views of source code.
|
||||
Currently supports Python.
|
||||
"""
|
||||
|
||||
#region: Core Operations
|
||||
|
||||
def __init__(self, language: str) -> None:
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
@@ -63,17 +63,13 @@ class ASTParser:
|
||||
raise ValueError(f"Language '{language}' not supported yet.")
|
||||
self.language_name = language
|
||||
# Load the tree-sitter language grammar
|
||||
if language == "python":
|
||||
self.language = tree_sitter.Language(tree_sitter_python.language())
|
||||
elif language == "cpp":
|
||||
self.language = tree_sitter.Language(tree_sitter_cpp.language())
|
||||
elif language == "c":
|
||||
self.language = tree_sitter.Language(tree_sitter_c.language())
|
||||
if language == "python": self.language = tree_sitter.Language(tree_sitter_python.language())
|
||||
elif language == "cpp": self.language = tree_sitter.Language(tree_sitter_cpp.language())
|
||||
elif language == "c": self.language = tree_sitter.Language(tree_sitter_c.language())
|
||||
self.parser = tree_sitter.Parser(self.language)
|
||||
|
||||
def parse(self, code: str) -> tree_sitter.Tree:
|
||||
"""
|
||||
|
||||
Parse the given code and return the tree-sitter Tree.
|
||||
[C: src/mcp_client.py:_search_file, src/mcp_client.py:derive_code_path, src/mcp_client.py:py_check_syntax, src/mcp_client.py:py_get_class_summary, src/mcp_client.py:py_get_definition, src/mcp_client.py:py_get_docstring, src/mcp_client.py:py_get_imports, src/mcp_client.py:py_get_signature, src/mcp_client.py:py_get_symbol_info, src/mcp_client.py:py_get_var_declaration, src/mcp_client.py:py_set_signature, src/mcp_client.py:py_set_var_declaration, src/mcp_client.py:py_update_definition, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/rag_engine.py:RAGEngine._chunk_code, src/summarize.py:_summarise_python, tests/test_ast_parser.py:test_ast_parser_parse, tests/test_tree_sitter_setup.py:test_tree_sitter_python_setup]
|
||||
"""
|
||||
@@ -185,12 +181,13 @@ class ASTParser:
|
||||
if child.type in ("type_identifier", "identifier", "namespace_identifier", "qualified_identifier"):
|
||||
return code_bytes[child.start_byte:child.end_byte].decode("utf8", errors="replace")
|
||||
return ""
|
||||
|
||||
#endregion: Core Operations
|
||||
|
||||
#region: Skeleton & Curated Views
|
||||
|
||||
def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
|
||||
Returns a skeleton of a Python file (preserving docstrings, stripping function bodies).
|
||||
[C: src/mcp_client.py:py_get_skeleton, src/mcp_client.py:ts_c_get_skeleton, src/mcp_client.py:ts_cpp_get_skeleton, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ast_parser.py:test_ast_parser_get_skeleton_c, tests/test_ast_parser.py:test_ast_parser_get_skeleton_cpp, tests/test_ast_parser.py:test_ast_parser_get_skeleton_python, tests/test_context_pruner.py:test_ast_caching, tests/test_context_pruner.py:test_performance_large_file]
|
||||
"""
|
||||
@@ -275,8 +272,6 @@ class ASTParser:
|
||||
return code_bytearray.decode("utf8")
|
||||
def get_curated_view(self, code: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
|
||||
Returns a curated skeleton of a Python file.
|
||||
Preserves function bodies if they have @core_logic decorator or # [HOT] comment.
|
||||
Otherwise strips bodies but preserves docstrings.
|
||||
@@ -350,13 +345,13 @@ class ASTParser:
|
||||
for start, end, replacement in edits:
|
||||
code_bytearray[start:end] = bytes(replacement, "utf8")
|
||||
return code_bytearray.decode("utf8")
|
||||
|
||||
#endregion: Skeleton & Curated Views
|
||||
|
||||
#region: Targeted Views
|
||||
|
||||
def get_targeted_view(self, code: str, function_names: List[str], path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
|
||||
Returns a targeted view of the code including only the specified functions
|
||||
and their dependencies up to depth 2.
|
||||
[C: src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ast_parser.py:test_ast_parser_get_targeted_view, tests/test_context_pruner.py:test_class_targeted_extraction, tests/test_context_pruner.py:test_targeted_extraction]
|
||||
@@ -517,12 +512,13 @@ class ASTParser:
|
||||
result = code_bytearray.decode("utf8")
|
||||
result = re.sub(r'\n\s*\n\s*\n+', '\n\n', result)
|
||||
return result.strip() + "\n"
|
||||
|
||||
#endregion: Targeted Views
|
||||
|
||||
#region: Symbol Extraction
|
||||
|
||||
def get_definition(self, code: str, name: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
Returns the full source code for a specific definition by name.
|
||||
Supports 'ClassName::method' or 'method' for C++.
|
||||
[C: src/mcp_client.py:trace, src/mcp_client.py:ts_c_get_definition, src/mcp_client.py:ts_cpp_get_definition, tests/test_ast_parser.py:test_ast_parser_get_definition_c, tests/test_ast_parser.py:test_ast_parser_get_definition_cpp, tests/test_ast_parser.py:test_ast_parser_get_definition_cpp_template]
|
||||
@@ -621,15 +617,12 @@ class ASTParser:
|
||||
|
||||
def get_signature(self, code: str, name: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
|
||||
Returns only the signature part of a function or method.
|
||||
For C/C++, this is the code from the start of the definition until the block start '{'.
|
||||
[C: src/mcp_client.py:ts_c_get_signature, src/mcp_client.py:ts_cpp_get_signature, tests/test_ast_parser.py:test_ast_parser_get_signature_c, tests/test_ast_parser.py:test_ast_parser_get_signature_cpp]
|
||||
"""
|
||||
code_bytes = code.encode("utf8")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
|
||||
parts = re.split(r'::|\.', name)
|
||||
|
||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
||||
@@ -729,13 +722,13 @@ class ASTParser:
|
||||
return code_bytes[found_node.start_byte:found_node.end_byte].decode("utf8", errors="replace").strip()
|
||||
|
||||
return f"ERROR: signature for '{name}' not found"
|
||||
|
||||
#endregion: Symbol Extraction
|
||||
|
||||
#region: Analysis & Updates
|
||||
|
||||
def get_code_outline(self, code: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
|
||||
Returns a hierarchical outline of the code (classes, structs, functions, methods).
|
||||
[C: src/mcp_client.py:ts_c_get_code_outline, src/mcp_client.py:ts_cpp_get_code_outline, tests/test_ast_parser.py:test_ast_parser_get_code_outline_c, tests/test_ast_parser.py:test_ast_parser_get_code_outline_cpp]
|
||||
"""
|
||||
@@ -778,14 +771,11 @@ class ASTParser:
|
||||
|
||||
def update_definition(self, code: str, name: str, new_content: str, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
|
||||
|
||||
Surgically replace the definition of a class or function by name.
|
||||
[C: src/mcp_client.py:ts_c_update_definition, src/mcp_client.py:ts_cpp_update_definition, tests/test_ast_parser.py:test_ast_parser_update_definition_cpp]
|
||||
"""
|
||||
code_bytes = code.encode("utf8")
|
||||
tree = self.get_cached_tree(path, code)
|
||||
|
||||
parts = re.split(r'::|\.', name)
|
||||
|
||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
||||
@@ -876,12 +866,15 @@ class ASTParser:
|
||||
code_bytearray[found_node.start_byte:found_node.end_byte] = bytes(new_content, "utf8")
|
||||
return code_bytearray.decode("utf8")
|
||||
return f"ERROR: definition '{name}' not found"
|
||||
|
||||
#endregion: Analysis & Updates
|
||||
|
||||
#region: Module Level Utilities
|
||||
|
||||
def reset_client() -> None:
|
||||
pass
|
||||
|
||||
def get_file_id(path: Path) -> Optional[str]:
|
||||
return None
|
||||
|
||||
#endregion: Module Level Utilities
|
||||
|
||||
+13
-19
@@ -46,13 +46,10 @@ from src import session_logger
|
||||
|
||||
class GeminiCliAdapter:
|
||||
"""
|
||||
|
||||
|
||||
Adapter for the Gemini CLI that parses streaming JSON output.
|
||||
"""
|
||||
def __init__(self, binary_path: str = "gemini"):
|
||||
"""
|
||||
|
||||
Initializes the adapter with the path to the gemini CLI executable.
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
@@ -61,11 +58,8 @@ class GeminiCliAdapter:
|
||||
self.last_usage: Optional[dict[str, Any]] = None
|
||||
self.last_latency: float = 0.0
|
||||
|
||||
def send(self, message: str, safety_settings: list[Any] | None = None, system_instruction: str | None = None,
|
||||
model: str | None = None, stream_callback: Optional[Callable[[str], None]] = None) -> dict[str, Any]:
|
||||
def send(self, message: str, safety_settings: list[Any] | None = None, system_instruction: str | None = None, model: str | None = None, stream_callback: Optional[Callable[[str], None]] = None) -> dict[str, Any]:
|
||||
"""
|
||||
|
||||
|
||||
Sends a message to the Gemini CLI and processes the streaming JSON output.
|
||||
Uses non-blocking line-by-line reading to allow stream_callback.
|
||||
[C: simulation/user_agent.py:UserSimAgent.generate_response, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_completion_logic, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoning_logic, tests/test_deepseek_provider.py:test_deepseek_streaming, tests/test_deepseek_provider.py:test_deepseek_tool_calling, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_full_flow_integration, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_captures_usage_metadata, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_handles_tool_use_events, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_parses_jsonl_output, tests/test_gemini_cli_adapter.py:TestGeminiCliAdapter.test_send_starts_subprocess_with_correct_args, tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_send_parses_tool_calls_from_streaming_json, tests/test_gemini_cli_adapter_parity.py:TestGeminiCliAdapterParity.test_send_starts_subprocess_with_model, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_context_bleed_prevention, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_loop_termination, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_cli_parity_regression.py:test_send_invokes_adapter_send, tests/test_gui2_mcp.py:test_mcp_tool_call_is_dispatched, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_token_usage.py:test_token_usage_tracking, tests/test_websocket_server.py:test_websocket_subscription_and_broadcast]
|
||||
@@ -115,13 +109,13 @@ class GeminiCliAdapter:
|
||||
|
||||
process = subprocess.Popen(
|
||||
cmd_list,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
shell=False,
|
||||
env=env
|
||||
stdin = subprocess.PIPE,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE,
|
||||
text = True,
|
||||
encoding = "utf-8",
|
||||
shell = False,
|
||||
env = env
|
||||
)
|
||||
|
||||
# Use communicate to avoid pipe deadlocks with large input/output.
|
||||
@@ -180,11 +174,11 @@ class GeminiCliAdapter:
|
||||
raise Exception(f"Gemini CLI failed with exit {process.returncode}\nStderr: {stderr_final}")
|
||||
session_logger.open_session()
|
||||
session_logger.log_cli_call(
|
||||
command=command,
|
||||
stdin_content=prompt_text,
|
||||
stdout_content="\n".join(stdout_content),
|
||||
stderr_content=stderr_final,
|
||||
latency=current_latency
|
||||
command = command,
|
||||
stdin_content = prompt_text,
|
||||
stdout_content = "\n".join(stdout_content),
|
||||
stderr_content = stderr_final,
|
||||
latency = current_latency
|
||||
)
|
||||
self.last_latency = current_latency
|
||||
|
||||
|
||||
+18
-38
@@ -47,19 +47,19 @@ class UISnapshot:
|
||||
[C: src/models.py:ContextPreset.from_dict, src/models.py:ExternalEditorConfig.from_dict, src/models.py:MCPConfiguration.from_dict, src/models.py:RAGConfig.from_dict, src/models.py:ToolPreset.from_dict, src/models.py:Track.from_dict, src/models.py:TrackState.from_dict, src/models.py:load_mcp_config, src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
||||
"""
|
||||
return cls(
|
||||
ai_input=data.get("ai_input", ""),
|
||||
project_system_prompt=data.get("project_system_prompt", ""),
|
||||
global_system_prompt=data.get("global_system_prompt", ""),
|
||||
base_system_prompt=data.get("base_system_prompt", ""),
|
||||
use_default_base_prompt=data.get("use_default_base_prompt", True),
|
||||
temperature=data.get("temperature", 0.0),
|
||||
top_p=data.get("top_p", 1.0),
|
||||
max_tokens=data.get("max_tokens", 4096),
|
||||
auto_add_history=data.get("auto_add_history", False),
|
||||
disc_entries=data.get("disc_entries", []),
|
||||
files=data.get("files", []),
|
||||
context_files=data.get("context_files", []),
|
||||
screenshots=data.get("screenshots", [])
|
||||
ai_input = data.get("ai_input", ""),
|
||||
project_system_prompt = data.get("project_system_prompt", ""),
|
||||
global_system_prompt = data.get("global_system_prompt", ""),
|
||||
base_system_prompt = data.get("base_system_prompt", ""),
|
||||
use_default_base_prompt = data.get("use_default_base_prompt", True),
|
||||
temperature = data.get("temperature", 0.0),
|
||||
top_p = data.get("top_p", 1.0),
|
||||
max_tokens = data.get("max_tokens", 4096),
|
||||
auto_add_history = data.get("auto_add_history", False),
|
||||
disc_entries = data.get("disc_entries", []),
|
||||
files = data.get("files", []),
|
||||
context_files = data.get("context_files", []),
|
||||
screenshots = data.get("screenshots", [])
|
||||
)
|
||||
|
||||
@dataclass
|
||||
@@ -79,8 +79,6 @@ class HistoryManager:
|
||||
|
||||
def push(self, state: typing.Any, description: str) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Pushes a new state to the undo stack and clears the redo stack.
|
||||
If the undo stack exceeds max_capacity, the oldest state is removed.
|
||||
[C: tests/test_history.py:test_jump_to_undo, tests/test_history.py:test_max_capacity, tests/test_history.py:test_push_state, tests/test_history.py:test_redo_cleared_on_push, tests/test_history.py:test_undo_redo, tests/test_history_manager.py:TestHistoryManager.test_get_history_returns_descriptions, tests/test_history_manager.py:TestHistoryManager.test_jump_to_undo, tests/test_history_manager.py:TestHistoryManager.test_push_and_undo, tests/test_history_manager.py:TestHistoryManager.test_push_clears_redo_stack, tests/test_history_manager.py:TestHistoryManager.test_undo_and_redo]
|
||||
@@ -93,45 +91,33 @@ class HistoryManager:
|
||||
|
||||
def undo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]:
|
||||
"""
|
||||
|
||||
|
||||
Undoes the last action by moving the current_state to the redo stack
|
||||
and returning the top of the undo stack.
|
||||
[C: tests/test_history.py:test_redo_cleared_on_push, tests/test_history.py:test_undo_redo, tests/test_history_manager.py:TestHistoryManager.test_push_and_undo, tests/test_history_manager.py:TestHistoryManager.test_push_clears_redo_stack, tests/test_history_manager.py:TestHistoryManager.test_undo_and_redo, tests/test_history_manager.py:TestHistoryManager.test_undo_no_history_returns_none]
|
||||
"""
|
||||
if not self._undo_stack:
|
||||
return None
|
||||
|
||||
if not self._undo_stack: return None
|
||||
redo_entry = HistoryEntry(state=current_state, description=current_description)
|
||||
self._redo_stack.append(redo_entry)
|
||||
return self._undo_stack.pop()
|
||||
|
||||
def redo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]:
|
||||
"""
|
||||
|
||||
|
||||
Redoes the last undone action by moving the current_state to the undo stack
|
||||
and returning the top of the redo stack.
|
||||
[C: tests/test_history.py:test_undo_redo, tests/test_history_manager.py:TestHistoryManager.test_redo_no_history_returns_none, tests/test_history_manager.py:TestHistoryManager.test_undo_and_redo]
|
||||
"""
|
||||
if not self._redo_stack:
|
||||
return None
|
||||
|
||||
if not self._redo_stack: return None
|
||||
undo_entry = HistoryEntry(state=current_state, description=current_description)
|
||||
self._undo_stack.append(undo_entry)
|
||||
return self._redo_stack.pop()
|
||||
|
||||
@property
|
||||
def can_undo(self) -> bool:
|
||||
return len(self._undo_stack) > 0
|
||||
|
||||
def can_undo(self) -> bool: return len(self._undo_stack) > 0
|
||||
@property
|
||||
def can_redo(self) -> bool:
|
||||
return len(self._redo_stack) > 0
|
||||
def can_redo(self) -> bool: return len(self._redo_stack) > 0
|
||||
|
||||
def get_history(self) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
"""
|
||||
|
||||
Returns a list of descriptions and timestamps for the undo stack.
|
||||
[C: tests/test_history.py:test_initial_state, tests/test_history.py:test_push_state, tests/test_history_manager.py:TestHistoryManager.test_get_history_returns_descriptions]
|
||||
"""
|
||||
@@ -142,20 +128,14 @@ class HistoryManager:
|
||||
|
||||
def jump_to_undo(self, index: int, current_state: typing.Any, current_description: str = "Before Jump") -> typing.Optional[HistoryEntry]:
|
||||
"""
|
||||
|
||||
|
||||
Jumps to a specific state in the undo stack by moving subsequent states
|
||||
and the current_state to the redo stack.
|
||||
[C: tests/test_history.py:test_jump_to_undo, tests/test_history_manager.py:TestHistoryManager.test_jump_to_undo]
|
||||
"""
|
||||
if index < 0 or index >= len(self._undo_stack):
|
||||
return None
|
||||
|
||||
if index < 0 or index >= len(self._undo_stack): return None
|
||||
# Move current state to redo
|
||||
self._redo_stack.append(HistoryEntry(state=current_state, description=current_description))
|
||||
|
||||
# Move states between index and top of undo to redo
|
||||
while len(self._undo_stack) > index + 1:
|
||||
self._redo_stack.append(self._undo_stack.pop())
|
||||
|
||||
return self._undo_stack.pop()
|
||||
+3
-4
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import importlib
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
@@ -45,8 +47,6 @@ class HotReloader:
|
||||
state = cls.capture_state(app, hm.state_keys)
|
||||
|
||||
try:
|
||||
import importlib
|
||||
import sys
|
||||
if module_name in sys.modules:
|
||||
old_module = sys.modules[module_name]
|
||||
importlib.reload(old_module)
|
||||
@@ -65,6 +65,5 @@ class HotReloader:
|
||||
def reload_all(cls, app: Any) -> bool:
|
||||
success = True
|
||||
for name in cls.HOT_MODULES:
|
||||
if not cls.reload(name, app):
|
||||
success = False
|
||||
if not cls.reload(name, app): success = False
|
||||
return success
|
||||
|
||||
@@ -10,8 +10,6 @@ from src.log_registry import LogRegistry
|
||||
|
||||
class LogPruner:
|
||||
"""
|
||||
|
||||
|
||||
Handles the automated deletion of old and insignificant session logs.
|
||||
Ensures that only whitelisted or significant sessions (based on size/content)
|
||||
are preserved long-term.
|
||||
@@ -19,8 +17,6 @@ class LogPruner:
|
||||
|
||||
def __init__(self, log_registry: LogRegistry, logs_dir: str) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Initializes the LogPruner.
|
||||
|
||||
Args:
|
||||
@@ -33,8 +29,6 @@ class LogPruner:
|
||||
|
||||
def prune(self, max_age_days: int = 1, min_size_kb: int = 2) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Prunes old and small session directories from the logs directory.
|
||||
|
||||
Deletes session directories that meet the following criteria:
|
||||
|
||||
+5
-23
@@ -49,16 +49,12 @@ from typing import Any
|
||||
|
||||
class LogRegistry:
|
||||
"""
|
||||
|
||||
|
||||
Manages a persistent registry of session logs using a TOML file.
|
||||
Tracks session paths, start times, whitelisting status, and metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, registry_path: str) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Initializes the LogRegistry with a path to the registry file.
|
||||
|
||||
Args:
|
||||
@@ -76,8 +72,6 @@ class LogRegistry:
|
||||
|
||||
def load_registry(self) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Loads the registry data from the TOML file into memory.
|
||||
Handles date/time conversions from TOML-native formats to strings for consistency.
|
||||
"""
|
||||
@@ -106,8 +100,6 @@ class LogRegistry:
|
||||
|
||||
def save_registry(self) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Serializes and saves the current registry data to the TOML file.
|
||||
Converts internal datetime objects to ISO format strings for compatibility.
|
||||
[C: tests/test_logging_e2e.py:test_logging_e2e]
|
||||
@@ -142,8 +134,6 @@ class LogRegistry:
|
||||
|
||||
def register_session(self, session_id: str, path: str, start_time: datetime | str) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Registers a new session in the registry.
|
||||
|
||||
Args:
|
||||
@@ -169,8 +159,6 @@ class LogRegistry:
|
||||
|
||||
def update_session_metadata(self, session_id: str, message_count: int, errors: int, size_kb: int, whitelisted: bool, reason: str) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Updates metadata fields for an existing session.
|
||||
|
||||
Args:
|
||||
@@ -204,8 +192,6 @@ class LogRegistry:
|
||||
|
||||
def is_session_whitelisted(self, session_id: str) -> bool:
|
||||
"""
|
||||
|
||||
|
||||
Checks if a specific session is marked as whitelisted.
|
||||
|
||||
Args:
|
||||
@@ -223,8 +209,6 @@ class LogRegistry:
|
||||
|
||||
def update_auto_whitelist_status(self, session_id: str) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Analyzes session logs and updates whitelisting status based on heuristics.
|
||||
Sessions are automatically whitelisted if they contain error keywords,
|
||||
have a high message count, or exceed a size threshold.
|
||||
@@ -275,17 +259,15 @@ class LogRegistry:
|
||||
reason = f"Large session size: {size_kb:.1f} KB"
|
||||
self.update_session_metadata(
|
||||
session_id,
|
||||
message_count=message_count,
|
||||
errors=len(found_keywords),
|
||||
size_kb=int(size_kb),
|
||||
whitelisted=whitelisted,
|
||||
reason=reason
|
||||
message_count = message_count,
|
||||
errors = len(found_keywords),
|
||||
size_kb = int(size_kb),
|
||||
whitelisted = whitelisted,
|
||||
reason = reason
|
||||
)
|
||||
|
||||
def get_old_non_whitelisted_sessions(self, cutoff_datetime: datetime) -> list[dict[str, Any]]:
|
||||
"""
|
||||
|
||||
|
||||
Retrieves a list of sessions that are older than a specific cutoff time
|
||||
and are not marked as whitelisted.
|
||||
Also includes non-whitelisted sessions that are empty (message_count=0 or size_kb=0).
|
||||
|
||||
+12
-22
@@ -10,6 +10,10 @@ from imgui_bundle import imgui_md, imgui, immapp, imgui_color_text_edit as ed
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Callable
|
||||
|
||||
from src import theme_2
|
||||
|
||||
from src.markdown_table import parse_tables, render_table
|
||||
|
||||
|
||||
def _get_language_id(name: str):
|
||||
"""Get a language identifier for ImGuiColorTextEdit.
|
||||
@@ -41,17 +45,13 @@ def _set_editor_language(editor, lang_obj) -> None:
|
||||
1.92.801+: editor.set_language(obj). 1.92.5: editor.set_language_definition(obj).
|
||||
No-op when lang_obj is None (used to skip the call for unknown languages).
|
||||
"""
|
||||
if lang_obj is None:
|
||||
return
|
||||
if hasattr(editor, "set_language"):
|
||||
editor.set_language(lang_obj)
|
||||
elif hasattr(editor, "set_language_definition"):
|
||||
editor.set_language_definition(lang_obj)
|
||||
if lang_obj is None: return
|
||||
|
||||
if hasattr(editor, "set_language"): editor.set_language(lang_obj)
|
||||
elif hasattr(editor, "set_language_definition"): editor.set_language_definition(lang_obj)
|
||||
|
||||
class MarkdownRenderer:
|
||||
"""
|
||||
|
||||
|
||||
Hybrid Markdown renderer that uses imgui_md for text/headers
|
||||
and ImGuiColorTextEdit for syntax-highlighted code blocks.
|
||||
"""
|
||||
@@ -80,7 +80,6 @@ class MarkdownRenderer:
|
||||
# Apply the current theme's syntax palette on construction so new
|
||||
# editors we create pick up the right colors. The renderer is re-created
|
||||
# when the theme changes (see theme_2 module-load behavior).
|
||||
from src import theme_2
|
||||
palette_id = theme_2.get_syntax_palette_for_theme(theme_2.get_current_palette())
|
||||
theme_2.apply_syntax_palette(palette_id)
|
||||
|
||||
@@ -119,20 +118,16 @@ class MarkdownRenderer:
|
||||
|
||||
def render(self, text: str, context_id: str = "default") -> None:
|
||||
"""
|
||||
|
||||
Render Markdown text with code block interception and GFM table substitution.
|
||||
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
|
||||
"""
|
||||
if not text:
|
||||
return
|
||||
from src.markdown_table import parse_tables, render_table
|
||||
if not text: return
|
||||
text = self._normalize_bullet_delimiters(text)
|
||||
text = self._normalize_nested_list_endings(text)
|
||||
text = self._normalize_list_continuations(text)
|
||||
blocks = parse_tables(text)
|
||||
lines = text.splitlines(keepends=True)
|
||||
if not lines:
|
||||
return
|
||||
if not lines: return
|
||||
|
||||
table_at_line: dict[int, int] = {b.span[0]: i for i, b in enumerate(blocks)}
|
||||
table_end: dict[int, int] = {b.span[0]: b.span[1] for i, b in enumerate(blocks)}
|
||||
@@ -213,7 +208,6 @@ class MarkdownRenderer:
|
||||
(we cannot subclass the C++ imgui_md class).
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
import re
|
||||
return re.sub(r"(?m)^([ \t]*)\*[ \t]+", r"\1- ", text)
|
||||
|
||||
def _normalize_nested_list_endings(self, text: str) -> str:
|
||||
@@ -226,7 +220,6 @@ class MarkdownRenderer:
|
||||
paragraph break. Cannot fix the upstream C++ from Python.
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
import re
|
||||
lines = text.split("\n")
|
||||
out: list[str] = []
|
||||
for i, line in enumerate(lines):
|
||||
@@ -261,7 +254,6 @@ class MarkdownRenderer:
|
||||
a single list item. Acceptable for our use case.
|
||||
[C: src.markdown_helper:MarkdownRenderer.render]
|
||||
"""
|
||||
import re
|
||||
lines = text.split("\n")
|
||||
out: list[str] = []
|
||||
prev_was_list = False
|
||||
@@ -288,8 +280,7 @@ class MarkdownRenderer:
|
||||
next_line = lines[j]
|
||||
curr_indent = len(next_line) - len(next_line.lstrip())
|
||||
is_next_list = bool(re.match(r"^\s*[-*+\d]", next_line))
|
||||
if curr_indent > prev_indent and not is_next_list:
|
||||
continue
|
||||
if curr_indent > prev_indent and not is_next_list: continue
|
||||
out.append(line)
|
||||
prev_was_list = False
|
||||
return "\n".join(out)
|
||||
@@ -386,8 +377,7 @@ _renderer: Optional[MarkdownRenderer] = None
|
||||
|
||||
def get_renderer() -> MarkdownRenderer:
|
||||
global _renderer
|
||||
if _renderer is None:
|
||||
_renderer = MarkdownRenderer()
|
||||
if _renderer is None: _renderer = MarkdownRenderer()
|
||||
return _renderer
|
||||
|
||||
def render(text: str, context_id: str = "default") -> None:
|
||||
|
||||
@@ -106,8 +106,6 @@ perf_monitor_callback: Optional[Callable[[], dict[str, Any]]] = None
|
||||
|
||||
def configure(file_items: list[dict[str, Any]], extra_base_dirs: list[str] | None = None) -> None:
|
||||
"""
|
||||
|
||||
|
||||
Build the allowlist from aggregate file_items.
|
||||
Called by ai_client before each send so the list reflects the current project.
|
||||
|
||||
|
||||
+18
-19
@@ -41,6 +41,7 @@ from __future__ import annotations
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
@@ -48,6 +49,7 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src.dag_engine import TrackDAG
|
||||
from src.paths import get_config_path
|
||||
|
||||
|
||||
@@ -164,8 +166,6 @@ def load_config() -> dict[str, Any]:
|
||||
return tomllib.load(f)
|
||||
|
||||
def save_config(config: dict[str, Any]) -> None:
|
||||
import tomli_w
|
||||
import sys
|
||||
config = _clean_nones(config)
|
||||
sys.stderr.write(f"[DEBUG] Saving config. Theme: {config.get('theme')}\n")
|
||||
sys.stderr.flush()
|
||||
@@ -345,7 +345,6 @@ class Track:
|
||||
"""
|
||||
[C: tests/test_mma_models.py:test_track_get_executable_tickets, tests/test_mma_models.py:test_track_get_executable_tickets_complex]
|
||||
"""
|
||||
from src.dag_engine import TrackDAG
|
||||
dag = TrackDAG(self.tickets)
|
||||
return dag.get_ready_tasks()
|
||||
|
||||
@@ -936,11 +935,11 @@ class MCPServerConfig:
|
||||
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
||||
"""
|
||||
return cls(
|
||||
name=name,
|
||||
command=data.get('command'),
|
||||
args=data.get('args', []),
|
||||
url=data.get('url'),
|
||||
auto_start=data.get('auto_start', False),
|
||||
name = name,
|
||||
command = data.get('command'),
|
||||
args = data.get('args', []),
|
||||
url = data.get('url'),
|
||||
auto_start = data.get('auto_start', False),
|
||||
)
|
||||
|
||||
@dataclass
|
||||
@@ -990,12 +989,12 @@ class VectorStoreConfig:
|
||||
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
||||
"""
|
||||
return cls(
|
||||
provider=data["provider"],
|
||||
url=data.get("url"),
|
||||
api_key=data.get("api_key"),
|
||||
collection_name=data.get("collection_name", "manual_slop"),
|
||||
mcp_server=data.get("mcp_server"),
|
||||
mcp_tool=data.get("mcp_tool"),
|
||||
provider = data["provider"],
|
||||
url = data.get("url"),
|
||||
api_key = data.get("api_key"),
|
||||
collection_name = data.get("collection_name", "manual_slop"),
|
||||
mcp_server = data.get("mcp_server"),
|
||||
mcp_tool = data.get("mcp_tool"),
|
||||
)
|
||||
|
||||
@dataclass
|
||||
@@ -1024,11 +1023,11 @@ class RAGConfig:
|
||||
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
||||
"""
|
||||
return cls(
|
||||
enabled=data.get("enabled", False),
|
||||
vector_store=VectorStoreConfig.from_dict(data.get("vector_store", {"provider": "mock"})),
|
||||
embedding_provider=data.get("embedding_provider", "gemini"),
|
||||
chunk_size=data.get("chunk_size", 1000),
|
||||
chunk_overlap=data.get("chunk_overlap", 200),
|
||||
enabled = data.get("enabled", False),
|
||||
vector_store = VectorStoreConfig.from_dict(data.get("vector_store", {"provider": "mock"})),
|
||||
embedding_provider = data.get("embedding_provider", "gemini"),
|
||||
chunk_size = data.get("chunk_size", 1000),
|
||||
chunk_overlap = data.get("chunk_overlap", 200),
|
||||
)
|
||||
|
||||
def load_mcp_config(path: str) -> MCPConfiguration:
|
||||
|
||||
Reference in New Issue
Block a user