Private
Public Access
more organization
This commit is contained in:
+3
-1
@@ -1,10 +1,12 @@
|
|||||||
# src/bg_shader.py
|
# src/bg_shader.py
|
||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
from typing import Optional
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
from imgui_bundle import imgui, nanovg as nvg, hello_imgui
|
from imgui_bundle import imgui, nanovg as nvg, hello_imgui
|
||||||
|
|
||||||
|
|
||||||
class BackgroundShader:
|
class BackgroundShader:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
+12
-21
@@ -1,9 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from imgui_bundle import imgui
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional, Callable, List, Dict, Any
|
from typing import Optional, Callable, List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Command:
|
class Command:
|
||||||
id: str
|
id: str
|
||||||
@@ -14,7 +17,6 @@ class Command:
|
|||||||
enabled_when: Optional[str] = None
|
enabled_when: Optional[str] = None
|
||||||
action: Optional[Callable] = None
|
action: Optional[Callable] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ScoredCommand:
|
class ScoredCommand:
|
||||||
command: Command
|
command: Command
|
||||||
@@ -70,12 +72,9 @@ def _is_subsequence(query: str, target: str) -> bool:
|
|||||||
|
|
||||||
def _compute_score(query: str, target: str) -> float:
|
def _compute_score(query: str, target: str) -> float:
|
||||||
score = 0.0
|
score = 0.0
|
||||||
if target.startswith(query):
|
if target.startswith(query): score += 1.0
|
||||||
score += 1.0
|
elif _starts_at_word_boundary(query, target): score += 0.5
|
||||||
elif _starts_at_word_boundary(query, target):
|
if _is_contiguous(query, target): score += 0.3
|
||||||
score += 0.5
|
|
||||||
if _is_contiguous(query, target):
|
|
||||||
score += 0.3
|
|
||||||
gaps = _count_gaps(query, target)
|
gaps = _count_gaps(query, target)
|
||||||
score -= 0.1 * gaps
|
score -= 0.1 * gaps
|
||||||
return score
|
return score
|
||||||
@@ -97,8 +96,7 @@ def _count_gaps(query: str, target: str) -> int:
|
|||||||
last_match = -1
|
last_match = -1
|
||||||
for ti, ch in enumerate(target):
|
for ti, ch in enumerate(target):
|
||||||
if qi < len(query) and ch == query[qi]:
|
if qi < len(query) and ch == query[qi]:
|
||||||
if last_match >= 0 and ti - last_match > 1:
|
if last_match >= 0 and ti - last_match > 1: gaps += ti - last_match - 1
|
||||||
gaps += ti - last_match - 1
|
|
||||||
last_match = ti
|
last_match = ti
|
||||||
qi += 1
|
qi += 1
|
||||||
return gaps
|
return gaps
|
||||||
@@ -128,19 +126,14 @@ def render_palette_modal(app: Any, commands: List[Command]) -> None:
|
|||||||
if not getattr(app, "show_command_palette", False):
|
if not getattr(app, "show_command_palette", False):
|
||||||
return
|
return
|
||||||
|
|
||||||
from imgui_bundle import imgui
|
|
||||||
|
|
||||||
viewport = imgui.get_main_viewport()
|
viewport = imgui.get_main_viewport()
|
||||||
center = viewport.get_center()
|
center = viewport.get_center()
|
||||||
imgui.set_next_window_pos((center.x - 300, center.y - 200), imgui.Cond_.always)
|
imgui.set_next_window_pos((center.x - 300, center.y - 200), imgui.Cond_.always)
|
||||||
imgui.set_next_window_size((600, 400), imgui.Cond_.always)
|
imgui.set_next_window_size((600, 400), imgui.Cond_.always)
|
||||||
|
|
||||||
if not hasattr(app, "_command_palette_query"):
|
if not hasattr(app, "_command_palette_query"): 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_selected"):
|
if not hasattr(app, "_command_palette_focused"): app._command_palette_focused = False
|
||||||
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.
|
# Set focus on the window + input field ONCE per open.
|
||||||
if not app._command_palette_focused:
|
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
|
# Process Up/Down/Enter BEFORE input_text so we see the keys before the
|
||||||
# input field consumes them for cursor movement / text editing.
|
# input field consumes them for cursor movement / text editing.
|
||||||
results = fuzzy_match(app._command_palette_query, commands, top_n=20)
|
results = fuzzy_match(app._command_palette_query, commands, top_n=20)
|
||||||
if results:
|
if results: app._command_palette_selected = max(0, min(app._command_palette_selected, len(results) - 1))
|
||||||
app._command_palette_selected = max(0, min(app._command_palette_selected, len(results) - 1))
|
else: app._command_palette_selected = 0
|
||||||
else:
|
|
||||||
app._command_palette_selected = 0
|
|
||||||
|
|
||||||
if imgui.is_key_pressed(imgui.Key.down_arrow):
|
if imgui.is_key_pressed(imgui.Key.down_arrow):
|
||||||
if results:
|
if results:
|
||||||
|
|||||||
+13
-21
@@ -1,13 +1,19 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
|
from src import models
|
||||||
|
from src import theme_2
|
||||||
|
|
||||||
from src.command_palette import CommandRegistry
|
from src.command_palette import CommandRegistry
|
||||||
|
from src.hot_reloader import HotReloader
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from src.gui_2 import App
|
from src.gui_2 import App
|
||||||
|
|
||||||
|
|
||||||
registry = CommandRegistry()
|
registry = CommandRegistry()
|
||||||
|
|
||||||
|
|
||||||
@@ -38,14 +44,10 @@ def reset_session(app: "App") -> None:
|
|||||||
"""Reset Session — Reset the AI session, clear comms and tool logs."""
|
"""Reset Session — Reset the AI session, clear comms and tool logs."""
|
||||||
from src import ai_client
|
from src import ai_client
|
||||||
ai_client.reset_session()
|
ai_client.reset_session()
|
||||||
if hasattr(app, "_handle_reset_session"):
|
if hasattr(app, "_handle_reset_session"): app._handle_reset_session()
|
||||||
app._handle_reset_session()
|
if hasattr(app, "_comms_log"): app._comms_log.clear()
|
||||||
if hasattr(app, "_comms_log"):
|
if hasattr(app, "_tool_log"): app._tool_log.clear()
|
||||||
app._comms_log.clear()
|
if hasattr(app, "ai_response"): app.ai_response = ""
|
||||||
if hasattr(app, "_tool_log"):
|
|
||||||
app._tool_log.clear()
|
|
||||||
if hasattr(app, "ai_response"):
|
|
||||||
app.ai_response = ""
|
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
@@ -98,11 +100,8 @@ def save_project(app: "App") -> None:
|
|||||||
@registry.register
|
@registry.register
|
||||||
def save_all(app: "App") -> None:
|
def save_all(app: "App") -> None:
|
||||||
"""Save All — Flush to project, flush to config, save global config."""
|
"""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_project"):
|
if hasattr(app, "_flush_to_config"): app._flush_to_config()
|
||||||
app._flush_to_project()
|
|
||||||
if hasattr(app, "_flush_to_config"):
|
|
||||||
app._flush_to_config()
|
|
||||||
if hasattr(app, "config"):
|
if hasattr(app, "config"):
|
||||||
try:
|
try:
|
||||||
models.save_config(app.config)
|
models.save_config(app.config)
|
||||||
@@ -229,7 +228,6 @@ def show_workspace_manager(app: "App") -> None:
|
|||||||
@registry.register
|
@registry.register
|
||||||
def trigger_hot_reload(app: "App") -> None:
|
def trigger_hot_reload(app: "App") -> None:
|
||||||
"""Hot Reload — Reload the GUI module to pick up code changes."""
|
"""Hot Reload — Reload the GUI module to pick up code changes."""
|
||||||
from src.hot_reloader import HotReloader
|
|
||||||
HotReloader.reload("src.gui_2", app)
|
HotReloader.reload("src.gui_2", app)
|
||||||
|
|
||||||
|
|
||||||
@@ -254,28 +252,24 @@ def redo(app: "App") -> None:
|
|||||||
@registry.register
|
@registry.register
|
||||||
def switch_to_dark_theme(app: "App") -> None:
|
def switch_to_dark_theme(app: "App") -> None:
|
||||||
"""Switch to Dark Theme (10x Dark palette)."""
|
"""Switch to Dark Theme (10x Dark palette)."""
|
||||||
from src import theme_2
|
|
||||||
theme_2.apply("10x Dark")
|
theme_2.apply("10x Dark")
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
def switch_to_light_theme(app: "App") -> None:
|
def switch_to_light_theme(app: "App") -> None:
|
||||||
"""Switch to Light Theme (ImGui Light palette)."""
|
"""Switch to Light Theme (ImGui Light palette)."""
|
||||||
from src import theme_2
|
|
||||||
theme_2.apply("ImGui Light")
|
theme_2.apply("ImGui Light")
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
def switch_to_nerv_theme(app: "App") -> None:
|
def switch_to_nerv_theme(app: "App") -> None:
|
||||||
"""Switch to NERV Theme (Tactical Console aesthetic)."""
|
"""Switch to NERV Theme (Tactical Console aesthetic)."""
|
||||||
from src import theme_2
|
|
||||||
theme_2.apply("NERV")
|
theme_2.apply("NERV")
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
def cycle_theme(app: "App") -> None:
|
def cycle_theme(app: "App") -> None:
|
||||||
"""Cycle Theme — Switch to the next theme in the cycle (Dark → Light → NERV → Dark)."""
|
"""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"]
|
order = ["10x Dark", "ImGui Light", "NERV"]
|
||||||
current = theme_2.get_current_palette()
|
current = theme_2.get_current_palette()
|
||||||
if current in order:
|
if current in order:
|
||||||
@@ -292,14 +286,12 @@ def cycle_theme(app: "App") -> None:
|
|||||||
@registry.register
|
@registry.register
|
||||||
def show_documentation(app: "App") -> None:
|
def show_documentation(app: "App") -> None:
|
||||||
"""Show Documentation — Open the project URL in the browser."""
|
"""Show Documentation — Open the project URL in the browser."""
|
||||||
import webbrowser
|
|
||||||
webbrowser.open("https://git.cozyair.dev/ed/manual_slop/")
|
webbrowser.open("https://git.cozyair.dev/ed/manual_slop/")
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
def show_command_palette_help(app: "App") -> None:
|
def show_command_palette_help(app: "App") -> None:
|
||||||
"""Show Command Palette Help — Open the docs/Readme.md in the Text Viewer."""
|
"""Show Command Palette Help — Open the docs/Readme.md in the Text Viewer."""
|
||||||
from pathlib import Path
|
|
||||||
if hasattr(app, "readme_text"):
|
if hasattr(app, "readme_text"):
|
||||||
docs_readme = Path("docs/Readme.md")
|
docs_readme = Path("docs/Readme.md")
|
||||||
if docs_readme.exists():
|
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]]:
|
def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Tier 2 (Tech Lead) call.
|
Tier 2 (Tech Lead) call.
|
||||||
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
|
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]
|
[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]
|
||||||
@@ -101,8 +99,6 @@ from src.models import Ticket
|
|||||||
|
|
||||||
def topological_sort(tickets: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
def topological_sort(tickets: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Sorts a list of tickets based on their 'depends_on' field.
|
Sorts a list of tickets based on their 'depends_on' field.
|
||||||
Raises ValueError if a circular dependency or missing internal dependency is detected.
|
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]
|
[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:
|
if is_backtracking:
|
||||||
path.remove(node_id)
|
path.remove(node_id)
|
||||||
continue
|
continue
|
||||||
if node_id in path:
|
if node_id in path: return True
|
||||||
return True
|
if node_id in visited: continue
|
||||||
if node_id in visited:
|
|
||||||
continue
|
|
||||||
visited.add(node_id)
|
visited.add(node_id)
|
||||||
path.add(node_id)
|
path.add(node_id)
|
||||||
stack.append((node_id, True))
|
stack.append((node_id, True))
|
||||||
@@ -216,3 +214,4 @@ class ExecutionEngine:
|
|||||||
ticket = self.dag.ticket_map.get(task_id)
|
ticket = self.dag.ticket_map.get(task_id)
|
||||||
if ticket:
|
if ticket:
|
||||||
ticket.status = status
|
ticket.status = status
|
||||||
|
|
||||||
@@ -27,18 +27,14 @@ class ExternalEditorLauncher:
|
|||||||
return self.config.editors.get(editor_name)
|
return self.config.editors.get(editor_name)
|
||||||
return self.config.get_default()
|
return self.config.get_default()
|
||||||
|
|
||||||
def build_diff_command(
|
def build_diff_command(self, editor: TextEditorConfig, original_path: str, modified_path: str) -> List[str]:
|
||||||
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]
|
[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]
|
cmd = [editor.path] + editor.diff_args + [original_path, modified_path]
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def launch_diff(
|
def launch_diff(self, editor_name: Optional[str], original_path: str, modified_path: str) -> Optional[subprocess.Popen]:
|
||||||
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]
|
[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:
|
class ASTParser:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Parser for extracting AST-based views of source code.
|
Parser for extracting AST-based views of source code.
|
||||||
Currently supports Python.
|
Currently supports Python.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#region: Core Operations
|
#region: Core Operations
|
||||||
|
|
||||||
def __init__(self, language: str) -> None:
|
def __init__(self, language: str) -> None:
|
||||||
"""
|
"""
|
||||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
[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.")
|
raise ValueError(f"Language '{language}' not supported yet.")
|
||||||
self.language_name = language
|
self.language_name = language
|
||||||
# Load the tree-sitter language grammar
|
# Load the tree-sitter language grammar
|
||||||
if language == "python":
|
if language == "python": self.language = tree_sitter.Language(tree_sitter_python.language())
|
||||||
self.language = tree_sitter.Language(tree_sitter_python.language())
|
elif language == "cpp": self.language = tree_sitter.Language(tree_sitter_cpp.language())
|
||||||
elif language == "cpp":
|
elif language == "c": self.language = tree_sitter.Language(tree_sitter_c.language())
|
||||||
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)
|
self.parser = tree_sitter.Parser(self.language)
|
||||||
|
|
||||||
def parse(self, code: str) -> tree_sitter.Tree:
|
def parse(self, code: str) -> tree_sitter.Tree:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Parse the given code and return the 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]
|
[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"):
|
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 code_bytes[child.start_byte:child.end_byte].decode("utf8", errors="replace")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
#endregion: Core Operations
|
#endregion: Core Operations
|
||||||
|
|
||||||
#region: Skeleton & Curated Views
|
#region: Skeleton & Curated Views
|
||||||
|
|
||||||
def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
|
def get_skeleton(self, code: str, path: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Returns a skeleton of a Python file (preserving docstrings, stripping function bodies).
|
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]
|
[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")
|
return code_bytearray.decode("utf8")
|
||||||
def get_curated_view(self, code: str, path: Optional[str] = None) -> str:
|
def get_curated_view(self, code: str, path: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Returns a curated skeleton of a Python file.
|
Returns a curated skeleton of a Python file.
|
||||||
Preserves function bodies if they have @core_logic decorator or # [HOT] comment.
|
Preserves function bodies if they have @core_logic decorator or # [HOT] comment.
|
||||||
Otherwise strips bodies but preserves docstrings.
|
Otherwise strips bodies but preserves docstrings.
|
||||||
@@ -350,13 +345,13 @@ class ASTParser:
|
|||||||
for start, end, replacement in edits:
|
for start, end, replacement in edits:
|
||||||
code_bytearray[start:end] = bytes(replacement, "utf8")
|
code_bytearray[start:end] = bytes(replacement, "utf8")
|
||||||
return code_bytearray.decode("utf8")
|
return code_bytearray.decode("utf8")
|
||||||
|
|
||||||
#endregion: Skeleton & Curated Views
|
#endregion: Skeleton & Curated Views
|
||||||
|
|
||||||
#region: Targeted Views
|
#region: Targeted Views
|
||||||
|
|
||||||
def get_targeted_view(self, code: str, function_names: List[str], path: Optional[str] = None) -> str:
|
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
|
Returns a targeted view of the code including only the specified functions
|
||||||
and their dependencies up to depth 2.
|
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]
|
[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 = code_bytearray.decode("utf8")
|
||||||
result = re.sub(r'\n\s*\n\s*\n+', '\n\n', result)
|
result = re.sub(r'\n\s*\n\s*\n+', '\n\n', result)
|
||||||
return result.strip() + "\n"
|
return result.strip() + "\n"
|
||||||
|
|
||||||
#endregion: Targeted Views
|
#endregion: Targeted Views
|
||||||
|
|
||||||
#region: Symbol Extraction
|
#region: Symbol Extraction
|
||||||
|
|
||||||
def get_definition(self, code: str, name: str, path: Optional[str] = None) -> str:
|
def get_definition(self, code: str, name: str, path: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Returns the full source code for a specific definition by name.
|
Returns the full source code for a specific definition by name.
|
||||||
Supports 'ClassName::method' or 'method' for C++.
|
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]
|
[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:
|
def get_signature(self, code: str, name: str, path: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Returns only the signature part of a function or method.
|
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 '{'.
|
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]
|
[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")
|
code_bytes = code.encode("utf8")
|
||||||
tree = self.get_cached_tree(path, code)
|
tree = self.get_cached_tree(path, code)
|
||||||
|
|
||||||
parts = re.split(r'::|\.', name)
|
parts = re.split(r'::|\.', name)
|
||||||
|
|
||||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
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 code_bytes[found_node.start_byte:found_node.end_byte].decode("utf8", errors="replace").strip()
|
||||||
|
|
||||||
return f"ERROR: signature for '{name}' not found"
|
return f"ERROR: signature for '{name}' not found"
|
||||||
|
|
||||||
#endregion: Symbol Extraction
|
#endregion: Symbol Extraction
|
||||||
|
|
||||||
#region: Analysis & Updates
|
#region: Analysis & Updates
|
||||||
|
|
||||||
def get_code_outline(self, code: str, path: Optional[str] = None) -> str:
|
def get_code_outline(self, code: str, path: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Returns a hierarchical outline of the code (classes, structs, functions, methods).
|
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]
|
[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:
|
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.
|
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]
|
[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")
|
code_bytes = code.encode("utf8")
|
||||||
tree = self.get_cached_tree(path, code)
|
tree = self.get_cached_tree(path, code)
|
||||||
|
|
||||||
parts = re.split(r'::|\.', name)
|
parts = re.split(r'::|\.', name)
|
||||||
|
|
||||||
def walk(node: tree_sitter.Node, target_parts: List[str]) -> Optional[tree_sitter.Node]:
|
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")
|
code_bytearray[found_node.start_byte:found_node.end_byte] = bytes(new_content, "utf8")
|
||||||
return code_bytearray.decode("utf8")
|
return code_bytearray.decode("utf8")
|
||||||
return f"ERROR: definition '{name}' not found"
|
return f"ERROR: definition '{name}' not found"
|
||||||
|
|
||||||
#endregion: Analysis & Updates
|
#endregion: Analysis & Updates
|
||||||
|
|
||||||
#region: Module Level Utilities
|
#region: Module Level Utilities
|
||||||
|
|
||||||
def reset_client() -> None:
|
def reset_client() -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_file_id(path: Path) -> Optional[str]:
|
def get_file_id(path: Path) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
#endregion: Module Level Utilities
|
#endregion: Module Level Utilities
|
||||||
|
|||||||
@@ -46,13 +46,10 @@ from src import session_logger
|
|||||||
|
|
||||||
class GeminiCliAdapter:
|
class GeminiCliAdapter:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Adapter for the Gemini CLI that parses streaming JSON output.
|
Adapter for the Gemini CLI that parses streaming JSON output.
|
||||||
"""
|
"""
|
||||||
def __init__(self, binary_path: str = "gemini"):
|
def __init__(self, binary_path: str = "gemini"):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Initializes the adapter with the path to the gemini CLI executable.
|
Initializes the adapter with the path to the gemini CLI executable.
|
||||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
[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_usage: Optional[dict[str, Any]] = None
|
||||||
self.last_latency: float = 0.0
|
self.last_latency: float = 0.0
|
||||||
|
|
||||||
def send(self, message: str, safety_settings: list[Any] | None = None, system_instruction: str | None = None,
|
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]:
|
||||||
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.
|
Sends a message to the Gemini CLI and processes the streaming JSON output.
|
||||||
Uses non-blocking line-by-line reading to allow stream_callback.
|
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]
|
[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]
|
||||||
|
|||||||
+5
-25
@@ -79,8 +79,6 @@ class HistoryManager:
|
|||||||
|
|
||||||
def push(self, state: typing.Any, description: str) -> None:
|
def push(self, state: typing.Any, description: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Pushes a new state to the undo stack and clears the redo stack.
|
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.
|
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]
|
[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]:
|
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
|
Undoes the last action by moving the current_state to the redo stack
|
||||||
and returning the top of the undo 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]
|
[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:
|
if not self._undo_stack: return None
|
||||||
return None
|
|
||||||
|
|
||||||
redo_entry = HistoryEntry(state=current_state, description=current_description)
|
redo_entry = HistoryEntry(state=current_state, description=current_description)
|
||||||
self._redo_stack.append(redo_entry)
|
self._redo_stack.append(redo_entry)
|
||||||
return self._undo_stack.pop()
|
return self._undo_stack.pop()
|
||||||
|
|
||||||
def redo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]:
|
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
|
Redoes the last undone action by moving the current_state to the undo stack
|
||||||
and returning the top of the redo 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]
|
[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:
|
if not self._redo_stack: return None
|
||||||
return None
|
|
||||||
|
|
||||||
undo_entry = HistoryEntry(state=current_state, description=current_description)
|
undo_entry = HistoryEntry(state=current_state, description=current_description)
|
||||||
self._undo_stack.append(undo_entry)
|
self._undo_stack.append(undo_entry)
|
||||||
return self._redo_stack.pop()
|
return self._redo_stack.pop()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_undo(self) -> bool:
|
def can_undo(self) -> bool: return len(self._undo_stack) > 0
|
||||||
return len(self._undo_stack) > 0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_redo(self) -> bool:
|
def can_redo(self) -> bool: return len(self._redo_stack) > 0
|
||||||
return len(self._redo_stack) > 0
|
|
||||||
|
|
||||||
def get_history(self) -> typing.List[typing.Dict[str, typing.Any]]:
|
def get_history(self) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Returns a list of descriptions and timestamps for the undo stack.
|
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]
|
[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]:
|
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
|
Jumps to a specific state in the undo stack by moving subsequent states
|
||||||
and the current_state to the redo stack.
|
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]
|
[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):
|
if index < 0 or index >= len(self._undo_stack): return None
|
||||||
return None
|
|
||||||
|
|
||||||
# Move current state to redo
|
# Move current state to redo
|
||||||
self._redo_stack.append(HistoryEntry(state=current_state, description=current_description))
|
self._redo_stack.append(HistoryEntry(state=current_state, description=current_description))
|
||||||
|
|
||||||
# Move states between index and top of undo to redo
|
# Move states between index and top of undo to redo
|
||||||
while len(self._undo_stack) > index + 1:
|
while len(self._undo_stack) > index + 1:
|
||||||
self._redo_stack.append(self._undo_stack.pop())
|
self._redo_stack.append(self._undo_stack.pop())
|
||||||
|
|
||||||
return self._undo_stack.pop()
|
return self._undo_stack.pop()
|
||||||
+3
-4
@@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import importlib
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -45,8 +47,6 @@ class HotReloader:
|
|||||||
state = cls.capture_state(app, hm.state_keys)
|
state = cls.capture_state(app, hm.state_keys)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import importlib
|
|
||||||
import sys
|
|
||||||
if module_name in sys.modules:
|
if module_name in sys.modules:
|
||||||
old_module = sys.modules[module_name]
|
old_module = sys.modules[module_name]
|
||||||
importlib.reload(old_module)
|
importlib.reload(old_module)
|
||||||
@@ -65,6 +65,5 @@ class HotReloader:
|
|||||||
def reload_all(cls, app: Any) -> bool:
|
def reload_all(cls, app: Any) -> bool:
|
||||||
success = True
|
success = True
|
||||||
for name in cls.HOT_MODULES:
|
for name in cls.HOT_MODULES:
|
||||||
if not cls.reload(name, app):
|
if not cls.reload(name, app): success = False
|
||||||
success = False
|
|
||||||
return success
|
return success
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ from src.log_registry import LogRegistry
|
|||||||
|
|
||||||
class LogPruner:
|
class LogPruner:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Handles the automated deletion of old and insignificant session logs.
|
Handles the automated deletion of old and insignificant session logs.
|
||||||
Ensures that only whitelisted or significant sessions (based on size/content)
|
Ensures that only whitelisted or significant sessions (based on size/content)
|
||||||
are preserved long-term.
|
are preserved long-term.
|
||||||
@@ -19,8 +17,6 @@ class LogPruner:
|
|||||||
|
|
||||||
def __init__(self, log_registry: LogRegistry, logs_dir: str) -> None:
|
def __init__(self, log_registry: LogRegistry, logs_dir: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Initializes the LogPruner.
|
Initializes the LogPruner.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -33,8 +29,6 @@ class LogPruner:
|
|||||||
|
|
||||||
def prune(self, max_age_days: int = 1, min_size_kb: int = 2) -> None:
|
def prune(self, max_age_days: int = 1, min_size_kb: int = 2) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Prunes old and small session directories from the logs directory.
|
Prunes old and small session directories from the logs directory.
|
||||||
|
|
||||||
Deletes session directories that meet the following criteria:
|
Deletes session directories that meet the following criteria:
|
||||||
|
|||||||
@@ -49,16 +49,12 @@ from typing import Any
|
|||||||
|
|
||||||
class LogRegistry:
|
class LogRegistry:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Manages a persistent registry of session logs using a TOML file.
|
Manages a persistent registry of session logs using a TOML file.
|
||||||
Tracks session paths, start times, whitelisting status, and metadata.
|
Tracks session paths, start times, whitelisting status, and metadata.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, registry_path: str) -> None:
|
def __init__(self, registry_path: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Initializes the LogRegistry with a path to the registry file.
|
Initializes the LogRegistry with a path to the registry file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -76,8 +72,6 @@ class LogRegistry:
|
|||||||
|
|
||||||
def load_registry(self) -> None:
|
def load_registry(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Loads the registry data from the TOML file into memory.
|
Loads the registry data from the TOML file into memory.
|
||||||
Handles date/time conversions from TOML-native formats to strings for consistency.
|
Handles date/time conversions from TOML-native formats to strings for consistency.
|
||||||
"""
|
"""
|
||||||
@@ -106,8 +100,6 @@ class LogRegistry:
|
|||||||
|
|
||||||
def save_registry(self) -> None:
|
def save_registry(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Serializes and saves the current registry data to the TOML file.
|
Serializes and saves the current registry data to the TOML file.
|
||||||
Converts internal datetime objects to ISO format strings for compatibility.
|
Converts internal datetime objects to ISO format strings for compatibility.
|
||||||
[C: tests/test_logging_e2e.py:test_logging_e2e]
|
[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:
|
def register_session(self, session_id: str, path: str, start_time: datetime | str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Registers a new session in the registry.
|
Registers a new session in the registry.
|
||||||
|
|
||||||
Args:
|
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:
|
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.
|
Updates metadata fields for an existing session.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -204,8 +192,6 @@ class LogRegistry:
|
|||||||
|
|
||||||
def is_session_whitelisted(self, session_id: str) -> bool:
|
def is_session_whitelisted(self, session_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Checks if a specific session is marked as whitelisted.
|
Checks if a specific session is marked as whitelisted.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -223,8 +209,6 @@ class LogRegistry:
|
|||||||
|
|
||||||
def update_auto_whitelist_status(self, session_id: str) -> None:
|
def update_auto_whitelist_status(self, session_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Analyzes session logs and updates whitelisting status based on heuristics.
|
Analyzes session logs and updates whitelisting status based on heuristics.
|
||||||
Sessions are automatically whitelisted if they contain error keywords,
|
Sessions are automatically whitelisted if they contain error keywords,
|
||||||
have a high message count, or exceed a size threshold.
|
have a high message count, or exceed a size threshold.
|
||||||
@@ -284,8 +268,6 @@ class LogRegistry:
|
|||||||
|
|
||||||
def get_old_non_whitelisted_sessions(self, cutoff_datetime: datetime) -> list[dict[str, Any]]:
|
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
|
Retrieves a list of sessions that are older than a specific cutoff time
|
||||||
and are not marked as whitelisted.
|
and are not marked as whitelisted.
|
||||||
Also includes non-whitelisted sessions that are empty (message_count=0 or size_kb=0).
|
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 pathlib import Path
|
||||||
from typing import Optional, Dict, Callable
|
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):
|
def _get_language_id(name: str):
|
||||||
"""Get a language identifier for ImGuiColorTextEdit.
|
"""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).
|
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).
|
No-op when lang_obj is None (used to skip the call for unknown languages).
|
||||||
"""
|
"""
|
||||||
if lang_obj is None:
|
if lang_obj is None: return
|
||||||
return
|
|
||||||
if hasattr(editor, "set_language"):
|
if hasattr(editor, "set_language"): editor.set_language(lang_obj)
|
||||||
editor.set_language(lang_obj)
|
elif hasattr(editor, "set_language_definition"): editor.set_language_definition(lang_obj)
|
||||||
elif hasattr(editor, "set_language_definition"):
|
|
||||||
editor.set_language_definition(lang_obj)
|
|
||||||
|
|
||||||
class MarkdownRenderer:
|
class MarkdownRenderer:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Hybrid Markdown renderer that uses imgui_md for text/headers
|
Hybrid Markdown renderer that uses imgui_md for text/headers
|
||||||
and ImGuiColorTextEdit for syntax-highlighted code blocks.
|
and ImGuiColorTextEdit for syntax-highlighted code blocks.
|
||||||
"""
|
"""
|
||||||
@@ -80,7 +80,6 @@ class MarkdownRenderer:
|
|||||||
# Apply the current theme's syntax palette on construction so new
|
# Apply the current theme's syntax palette on construction so new
|
||||||
# editors we create pick up the right colors. The renderer is re-created
|
# editors we create pick up the right colors. The renderer is re-created
|
||||||
# when the theme changes (see theme_2 module-load behavior).
|
# 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())
|
palette_id = theme_2.get_syntax_palette_for_theme(theme_2.get_current_palette())
|
||||||
theme_2.apply_syntax_palette(palette_id)
|
theme_2.apply_syntax_palette(palette_id)
|
||||||
|
|
||||||
@@ -119,20 +118,16 @@ class MarkdownRenderer:
|
|||||||
|
|
||||||
def render(self, text: str, context_id: str = "default") -> None:
|
def render(self, text: str, context_id: str = "default") -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Render Markdown text with code block interception and GFM table substitution.
|
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]
|
[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:
|
if not text: return
|
||||||
return
|
|
||||||
from src.markdown_table import parse_tables, render_table
|
|
||||||
text = self._normalize_bullet_delimiters(text)
|
text = self._normalize_bullet_delimiters(text)
|
||||||
text = self._normalize_nested_list_endings(text)
|
text = self._normalize_nested_list_endings(text)
|
||||||
text = self._normalize_list_continuations(text)
|
text = self._normalize_list_continuations(text)
|
||||||
blocks = parse_tables(text)
|
blocks = parse_tables(text)
|
||||||
lines = text.splitlines(keepends=True)
|
lines = text.splitlines(keepends=True)
|
||||||
if not lines:
|
if not lines: return
|
||||||
return
|
|
||||||
|
|
||||||
table_at_line: dict[int, int] = {b.span[0]: i for i, b in enumerate(blocks)}
|
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)}
|
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).
|
(we cannot subclass the C++ imgui_md class).
|
||||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
return re.sub(r"(?m)^([ \t]*)\*[ \t]+", r"\1- ", text)
|
return re.sub(r"(?m)^([ \t]*)\*[ \t]+", r"\1- ", text)
|
||||||
|
|
||||||
def _normalize_nested_list_endings(self, text: str) -> str:
|
def _normalize_nested_list_endings(self, text: str) -> str:
|
||||||
@@ -226,7 +220,6 @@ class MarkdownRenderer:
|
|||||||
paragraph break. Cannot fix the upstream C++ from Python.
|
paragraph break. Cannot fix the upstream C++ from Python.
|
||||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
out: list[str] = []
|
out: list[str] = []
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
@@ -261,7 +254,6 @@ class MarkdownRenderer:
|
|||||||
a single list item. Acceptable for our use case.
|
a single list item. Acceptable for our use case.
|
||||||
[C: src.markdown_helper:MarkdownRenderer.render]
|
[C: src.markdown_helper:MarkdownRenderer.render]
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
out: list[str] = []
|
out: list[str] = []
|
||||||
prev_was_list = False
|
prev_was_list = False
|
||||||
@@ -288,8 +280,7 @@ class MarkdownRenderer:
|
|||||||
next_line = lines[j]
|
next_line = lines[j]
|
||||||
curr_indent = len(next_line) - len(next_line.lstrip())
|
curr_indent = len(next_line) - len(next_line.lstrip())
|
||||||
is_next_list = bool(re.match(r"^\s*[-*+\d]", next_line))
|
is_next_list = bool(re.match(r"^\s*[-*+\d]", next_line))
|
||||||
if curr_indent > prev_indent and not is_next_list:
|
if curr_indent > prev_indent and not is_next_list: continue
|
||||||
continue
|
|
||||||
out.append(line)
|
out.append(line)
|
||||||
prev_was_list = False
|
prev_was_list = False
|
||||||
return "\n".join(out)
|
return "\n".join(out)
|
||||||
@@ -386,8 +377,7 @@ _renderer: Optional[MarkdownRenderer] = None
|
|||||||
|
|
||||||
def get_renderer() -> MarkdownRenderer:
|
def get_renderer() -> MarkdownRenderer:
|
||||||
global _renderer
|
global _renderer
|
||||||
if _renderer is None:
|
if _renderer is None: _renderer = MarkdownRenderer()
|
||||||
_renderer = MarkdownRenderer()
|
|
||||||
return _renderer
|
return _renderer
|
||||||
|
|
||||||
def render(text: str, context_id: str = "default") -> None:
|
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:
|
def configure(file_items: list[dict[str, Any]], extra_base_dirs: list[str] | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Build the allowlist from aggregate file_items.
|
Build the allowlist from aggregate file_items.
|
||||||
Called by ai_client before each send so the list reflects the current project.
|
Called by ai_client before each send so the list reflects the current project.
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -41,6 +41,7 @@ from __future__ import annotations
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import tomllib
|
import tomllib
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -48,6 +49,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.dag_engine import TrackDAG
|
||||||
from src.paths import get_config_path
|
from src.paths import get_config_path
|
||||||
|
|
||||||
|
|
||||||
@@ -164,8 +166,6 @@ def load_config() -> dict[str, Any]:
|
|||||||
return tomllib.load(f)
|
return tomllib.load(f)
|
||||||
|
|
||||||
def save_config(config: dict[str, Any]) -> None:
|
def save_config(config: dict[str, Any]) -> None:
|
||||||
import tomli_w
|
|
||||||
import sys
|
|
||||||
config = _clean_nones(config)
|
config = _clean_nones(config)
|
||||||
sys.stderr.write(f"[DEBUG] Saving config. Theme: {config.get('theme')}\n")
|
sys.stderr.write(f"[DEBUG] Saving config. Theme: {config.get('theme')}\n")
|
||||||
sys.stderr.flush()
|
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]
|
[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)
|
dag = TrackDAG(self.tickets)
|
||||||
return dag.get_ready_tasks()
|
return dag.get_ready_tasks()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user