From 873edf42cfc9fdb4992845fd21fb94c7950faeae Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 5 Jun 2026 21:44:41 -0400 Subject: [PATCH] began to go through the files and organize imports and gui_2.py's new context defs still a bunch to sift through after the last ai passes --- src/aggregate.py | 4 + src/ai_client.py | 21 ++- src/api_hook_client.py | 3 + src/api_hooks.py | 20 ++- src/app_controller.py | 18 ++- src/beads_client.py | 8 +- src/command_palette.py | 3 +- src/commands.py | 2 + src/conductor_tech_lead.py | 7 +- src/context_presets.py | 2 + src/cost_tracker.py | 1 + src/dag_engine.py | 2 + src/diff_viewer.py | 8 +- src/events.py | 4 +- src/external_editor.py | 4 +- src/file_cache.py | 9 +- src/fuzzy_anchor.py | 2 + src/gemini_cli_adapter.py | 8 +- src/gui_2.py | 267 +++++++++++++++++------------------ src/history.py | 4 +- src/hot_reloader.py | 7 +- src/imgui_scopes.py | 3 + src/log_pruner.py | 3 + src/log_registry.py | 6 +- src/markdown_helper.py | 10 +- src/markdown_table.py | 4 +- src/mcp_client.py | 13 +- src/mma_prompts.py | 1 + src/models.py | 14 +- src/multi_agent_conductor.py | 19 +-- src/orchestrator_pm.py | 11 +- src/outline_tool.py | 1 + src/patch_modal.py | 3 +- src/paths.py | 9 +- src/performance_monitor.py | 7 +- src/personas.py | 6 +- src/presets.py | 7 +- src/project_manager.py | 13 +- src/rag_engine.py | 9 +- src/session_logger.py | 4 +- src/shaders.py | 1 + src/shell_runner.py | 6 +- src/summarize.py | 6 +- src/summary_cache.py | 4 +- src/theme_2.py | 18 ++- src/theme_models.py | 6 +- src/theme_nerv.py | 1 + src/theme_nerv_fx.py | 4 +- src/thinking_parser.py | 3 + src/tool_bias.py | 2 + src/tool_presets.py | 7 +- src/vendor_state.py | 1 + src/workspace_manager.py | 7 +- 53 files changed, 375 insertions(+), 238 deletions(-) diff --git a/src/aggregate.py b/src/aggregate.py index 982e0c63..dee13dbb 100644 --- a/src/aggregate.py +++ b/src/aggregate.py @@ -16,14 +16,18 @@ import glob import os import re import tomllib + from pathlib import Path, PureWindowsPath from typing import Any, cast + from src import beads_client from src import project_manager from src import summarize + from src.file_cache import ASTParser from src.performance_monitor import get_monitor + def find_next_increment(output_dir: Path, namespace: str) -> int: pattern = re.compile(rf"^{re.escape(namespace)}_(\d+)\.md$") max_num = 0 diff --git a/src/ai_client.py b/src/ai_client.py index 32f20200..b3baac2c 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -16,34 +16,41 @@ import anthropic from google import genai from google.genai import types from openai import OpenAI + import asyncio import datetime import difflib import hashlib import json import os -from pathlib import Path as _P import requests # type: ignore[import-untyped] import sys import threading import time import tomllib + +# TODO(Ed): Eliminate These? from collections import deque -from typing import Optional, Callable, Any, List, Union, cast, Iterable -from pathlib import Path -from src.events import EventEmitter +from pathlib import Path as _P +from pathlib import Path +from typing import Optional, Callable, Any, List, Union, cast, Iterable + from src import project_manager from src import file_cache from src import mcp_client from src import mma_prompts from src import performance_monitor from src import project_manager -from src.paths import get_credentials_path -from src.tool_bias import ToolBiasEngine -from src.models import ToolPreset, BiasProfile, Tool + +# TODO(Ed): Eliminate these? +from src.events import EventEmitter from src.gemini_cli_adapter import GeminiCliAdapter +from src.models import ToolPreset, BiasProfile, Tool +from src.paths import get_credentials_path +from src.tool_bias import ToolBiasEngine from src.tool_presets import ToolPresetManager + _provider: str = "gemini" _model: str = "gemini-2.5-flash-lite" _temperature: float = 0.0 diff --git a/src/api_hook_client.py b/src/api_hook_client.py index accc6d21..df4fd9c2 100644 --- a/src/api_hook_client.py +++ b/src/api_hook_client.py @@ -32,11 +32,14 @@ See Also: - docs/guide_tools.md for Hook API documentation """ from __future__ import annotations + import requests # type: ignore[import-untyped] import sys import time + from typing import Any + class ApiHookClient: def __init__(self, base_url: str = "http://127.0.0.1:8999", api_key: str | None = None): """ diff --git a/src/api_hooks.py b/src/api_hooks.py index 64d66545..c2b0108b 100644 --- a/src/api_hooks.py +++ b/src/api_hooks.py @@ -1,16 +1,22 @@ from __future__ import annotations + +import asyncio import json +import logging +import sys import threading import uuid -import sys -import asyncio -from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler -from typing import Any -import logging import websockets + +# TODO(Ed): Eliminate these? +from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler +from typing import Any from websockets.asyncio.server import serve -from src import session_logger + from src import cost_tracker +from src import session_logger + + """ API Hooks - REST API for external automation and state inspection. @@ -820,4 +826,4 @@ class WebSocketServer: return message = json.dumps({"channel": channel, "payload": payload}) for ws in list(self.clients[channel]): - asyncio.run_coroutine_threadsafe(ws.send(message), self.loop) \ No newline at end of file + asyncio.run_coroutine_threadsafe(ws.send(message), self.loop) diff --git a/src/app_controller.py b/src/app_controller.py index 97c07e7d..5c842204 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -10,15 +10,18 @@ import time import tomli_w import traceback import uuid + +# TODO(Ed): Eliminate these? from dataclasses import asdict -from datetime import datetime -from fastapi import FastAPI, Depends, HTTPException +from datetime import datetime +from fastapi import FastAPI, Depends, HTTPException +from pathlib import Path +from typing import Any, List, Dict, Optional, Callable + from fastapi.security.api_key import APIKeyHeader -from pathlib import Path -from typing import Any, List, Dict, Optional, Callable + from src import aggregate from src import models -from src.models import GenerateRequest, ConfirmRequest from src import ai_client from src import conductor_tech_lead from src import events @@ -35,8 +38,11 @@ from src import shell_runner from src import theme_2 as theme from src import thinking_parser from src import tool_presets + from src.context_presets import ContextPresetManager -from src.file_cache import ASTParser +from src.file_cache import ASTParser +from src.models import GenerateRequest, ConfirmRequest + def parse_symbols(text: str) -> list[str]: """ diff --git a/src/beads_client.py b/src/beads_client.py index ca07283d..73a87688 100644 --- a/src/beads_client.py +++ b/src/beads_client.py @@ -1,8 +1,10 @@ -from dataclasses import dataclass -from typing import List, Optional -from pathlib import Path import json +from dataclasses import dataclass +from typing import List, Optional +from pathlib import Path + + @dataclass class Bead: id: str diff --git a/src/command_palette.py b/src/command_palette.py index 1785e6c8..7da55d6c 100644 --- a/src/command_palette.py +++ b/src/command_palette.py @@ -1,6 +1,7 @@ from __future__ import annotations + from dataclasses import dataclass, field -from typing import Optional, Callable, List, Dict, Any +from typing import Optional, Callable, List, Dict, Any @dataclass diff --git a/src/commands.py b/src/commands.py index a2edfb2d..812a500b 100644 --- a/src/commands.py +++ b/src/commands.py @@ -1,5 +1,7 @@ from __future__ import annotations + from typing import TYPE_CHECKING, Callable + from src.command_palette import CommandRegistry if TYPE_CHECKING: diff --git a/src/conductor_tech_lead.py b/src/conductor_tech_lead.py index d4915672..2beb66db 100644 --- a/src/conductor_tech_lead.py +++ b/src/conductor_tech_lead.py @@ -34,10 +34,13 @@ See Also: - src/dag_engine.py for TrackDAG """ import json +import re + +from typing import Any + from src import ai_client from src import mma_prompts -import re -from typing import Any + def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str, Any]]: """ diff --git a/src/context_presets.py b/src/context_presets.py index 7bcd9740..aa7b08f9 100644 --- a/src/context_presets.py +++ b/src/context_presets.py @@ -1,6 +1,8 @@ from typing import Dict, Any + from src.models import ContextPreset + class ContextPresetManager: """Manages context presets within the project dictionary (manual_slop.toml).""" diff --git a/src/cost_tracker.py b/src/cost_tracker.py index eed81303..7c9d616d 100644 --- a/src/cost_tracker.py +++ b/src/cost_tracker.py @@ -33,6 +33,7 @@ See Also: """ import re + # Pricing per 1M tokens in USD MODEL_PRICING = [ (r"gemini-2\.5-flash-lite", {"input_per_mtok": 0.075, "output_per_mtok": 0.30}), diff --git a/src/dag_engine.py b/src/dag_engine.py index c289fded..09057a74 100644 --- a/src/dag_engine.py +++ b/src/dag_engine.py @@ -27,9 +27,11 @@ See Also: - src/multi_agent_conductor.py for ConductorEngine integration """ from typing import List + from src.models import Ticket from src.performance_monitor import get_monitor + class TrackDAG: """ Manages a Directed Acyclic Graph of implementation tickets. diff --git a/src/diff_viewer.py b/src/diff_viewer.py index b66e171c..d4c8f7c5 100644 --- a/src/diff_viewer.py +++ b/src/diff_viewer.py @@ -1,8 +1,10 @@ -from typing import List, Dict, Optional, Tuple -from dataclasses import dataclass import shutil import os -from pathlib import Path + +from dataclasses import dataclass +from pathlib import Path +from typing import List, Dict, Optional, Tuple + @dataclass class DiffHunk: diff --git a/src/events.py b/src/events.py index 8fe238f5..cbd64a91 100644 --- a/src/events.py +++ b/src/events.py @@ -30,8 +30,10 @@ Thread Safety: - UserRequestEvent: Immutable, safe for concurrent access """ import queue -from typing import Callable, Any, Dict, List, Tuple, Optional + from pathlib import Path +from typing import Callable, Any, Dict, List, Tuple, Optional + class EventEmitter: """ diff --git a/src/external_editor.py b/src/external_editor.py index 3496380f..817a6182 100644 --- a/src/external_editor.py +++ b/src/external_editor.py @@ -4,8 +4,10 @@ from __future__ import annotations import os import subprocess import tempfile + +# TODO(Ed): Eliminate these? from pathlib import Path -from typing import Optional, List +from typing import Optional, List from src.models import ExternalEditorConfig, TextEditorConfig diff --git a/src/file_cache.py b/src/file_cache.py index a7db940f..1489c80a 100644 --- a/src/file_cache.py +++ b/src/file_cache.py @@ -34,13 +34,16 @@ See Also: - docs/guide_tools.md for AST tool documentation - src/summarize.py for heuristic summaries """ -from pathlib import Path -from typing import Optional, Any, List, Tuple, Dict +import re import tree_sitter import tree_sitter_python import tree_sitter_cpp import tree_sitter_c -import re + +# TODO(Ed): Eliminate these? +from pathlib import Path +from typing import Optional, Any, List, Tuple, Dict + _ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {} diff --git a/src/fuzzy_anchor.py b/src/fuzzy_anchor.py index f5a1c07d..81af9ac0 100644 --- a/src/fuzzy_anchor.py +++ b/src/fuzzy_anchor.py @@ -1,7 +1,9 @@ import hashlib import re + from typing import Optional, Tuple + class FuzzyAnchor: @staticmethod def get_context(lines: list[str], index: int, count: int, direction: int) -> list[str]: diff --git a/src/gemini_cli_adapter.py b/src/gemini_cli_adapter.py index a2a41e56..b9bd13ea 100644 --- a/src/gemini_cli_adapter.py +++ b/src/gemini_cli_adapter.py @@ -33,14 +33,16 @@ See Also: - docs/guide_architecture.md for CLI adapter integration - src/ai_client.py for provider dispatch """ -import subprocess import json import os -import time +import subprocess import sys -from src import session_logger +import time + from typing import Optional, Callable, Any +from src import session_logger + class GeminiCliAdapter: """ diff --git a/src/gui_2.py b/src/gui_2.py index 6daa1a32..eb202301 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -17,7 +17,6 @@ import threading import time import tomli_w import typing -from contextlib import ExitStack, nullcontext # Ensure thirdparty is in sys.path for defer _project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -25,16 +24,16 @@ _thirdparty = os.path.join(_project_root, "thirdparty") if _thirdparty not in sys.path: sys.path.insert(0, _thirdparty) -from defer import defer -from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced -from pathlib import Path -from tkinter import filedialog, Tk -from typing import Optional, Any -from defer import defer +from contextlib import ExitStack, nullcontext +# from defer import defer +from pathlib import Path +from tkinter import filedialog, Tk +from typing import Optional, Any from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced from pathlib import Path from tkinter import filedialog, Tk from typing import Optional, Any + from src.diff_viewer import apply_patch_to_file from src import ai_client from src import aggregate @@ -61,6 +60,7 @@ from src import theme_nerv_fx as theme_fx from src import thinking_parser from src import workspace_manager from src.hot_reloader import HotReloader + if sys.platform == "win32": import win32gui import win32con @@ -2784,8 +2784,6 @@ def render_files_and_media(app: App) -> None: if p not in app.screenshots: app.screenshots.append(p) return -#endregion: Context Management - def render_context_batch_actions(app: App, total_lines: int, total_ast: int) -> None: imgui.text("Batch:") for mode in ["full", "summary", "skeleton", "outline", "masked", "none"]: @@ -3361,6 +3359,130 @@ def render_snapshot_tab(app: App) -> None: with imscope.child("last_sys_prompt", 0, 0, True): markdown_helper.render(app.last_resolved_system_prompt, context_id="snapshot_sys") +def render_empty_context_modal(app: App) -> None: + if app.show_empty_context_modal: + imgui.open_popup("Empty Context Warning") + app.show_empty_context_modal = False + + if imgui.begin_popup_modal("Empty Context Warning", True, imgui.WindowFlags_.always_auto_resize)[0]: + imgui.text_colored(theme.get_color("status_warning"), "WARNING: Empty Context Composition") + imgui.text("You are attempting to generate a response without any files selected.") + imgui.text("This may result in poor AI performance or loss of project context.") + imgui.separator() + if imgui.button("Proceed Anyway", imgui.ImVec2(150, 0)): + if app._pending_generation_action == 'generate': app.controller._handle_generate_send() + elif app._pending_generation_action == 'md_only': app.controller._handle_md_only() + app._pending_generation_action = None + imgui.close_current_popup() + imgui.same_line() + if imgui.button("Cancel", imgui.ImVec2(120, 0)): + imgui.close_current_popup() + imgui.end_popup() + +def render_context_modals(app: App) -> None: + render_empty_context_modal(app) + render_add_context_files_modal(app) + + if app.show_missing_files_modal: + imgui.open_popup("Missing Files Warning") + app.show_missing_files_modal = False + + if imgui.begin_popup_modal("Missing Files Warning", True, imgui.WindowFlags_.always_auto_resize)[0]: + imgui.text("The following files are missing from disk:") + imgui.separator() + imgui.begin_child("missing_files_list", imgui.ImVec2(0, 150), True) + for f in app.missing_context_files: + imgui.text_colored(theme.get_color("status_error"), f) + imgui.end_child() + imgui.separator() + + if imgui.button("Save Anyway") or getattr(app, "_pending_save_anyway_click", False): + app._pending_save_anyway_click = False + name = app.target_context_preset_name + preset_files = [] + for f in app.context_files: + import copy + from src import models + p = f.path if hasattr(f, 'path') else str(f) + vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' + slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else [] + msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {} + sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False + dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False + preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn)) + preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots)) + app.controller.save_context_preset(preset) + app.ui_new_context_preset_name = "" + imgui.close_current_popup() + + imgui.same_line() + if imgui.button("Cancel"): + imgui.close_current_popup() + + imgui.end_popup() + + render_ast_inspector_modal(app) + +def _get_context_composition_state(app: App) -> tuple: + files_state = [] + for f in app.context_files: + p = f.path if hasattr(f, 'path') else str(f) + vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' + agg = f.auto_aggregate if hasattr(f, 'auto_aggregate') else False + slc = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', [])) + mask = tuple(sorted(getattr(f, 'ast_mask', {}).items())) + files_state.append((p, vm, agg, slc, mask)) + screenshots_state = tuple(app.screenshots) + return (tuple(files_state), screenshots_state) + +def _check_auto_refresh_context_preview(app: App) -> None: + current_state = _get_context_composition_state(app) + if not hasattr(app, "_last_context_preview_state") or app._last_context_preview_state != current_state: + app._last_context_preview_state = current_state + if not any(getattr(f, 'auto_aggregate', False) for f in app.context_files) and not app.screenshots: + app.context_preview_text = "# Context Composition Empty\n\nNo files or screenshots have been selected for aggregation." + return + + if getattr(app, "_is_generating_preview", False): + app._pending_preview_refresh = True + return + + app._is_generating_preview = True + def worker(): + try: + app.controller.context_files = app.context_files + res = app.controller._do_generate() + app.context_preview_text = res[0] + except Exception: + app.context_preview_text = "Error generating context preview." + finally: + app._is_generating_preview = False + if getattr(app, "_pending_preview_refresh", False): + app._pending_preview_refresh = False + # This will trigger again on next GUI frame because _last_context_preview_state + # will be slightly behind if another change happened during the thread. + # Or we just clear the state so it re-triggers. + app._last_context_preview_state = None + + import threading + threading.Thread(target=worker, daemon=True).start() + +def render_context_preview_window(app: App) -> None: + _check_auto_refresh_context_preview(app) + with imscope.window("Context Preview", app.show_windows["Context Preview"]) as (exp, opened): + app.show_windows["Context Preview"] = bool(opened) + if not opened: + app.ui_separate_context_preview = False + if exp: + if imgui.button("Close"): + app.show_windows["Context Preview"] = False + app.ui_separate_context_preview = False + imgui.same_line() + if imgui.button("Copy to Clipboard"): + imgui.set_clipboard_text(app.context_preview_text) + with imscope.child("ctx_preview_scroll", 0, 0, True): + markdown_helper.render(app.context_preview_text, context_id="ctx_preview") + #endregion: Context Management #region: Discussions @@ -3587,6 +3709,7 @@ def render_session_insights_panel(app: App) -> None: imgui.text(f"Session Cost: ${insights.get('session_cost', 0):.4f}") completed = insights.get('completed_tickets', 0) efficiency = insights.get('efficiency', 0) + def render_prior_session_view(app: App) -> None: with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor")): if imgui.button("Exit Prior Session"): app.controller.cb_exit_prior_session(); app._comms_log_dirty = True @@ -3611,6 +3734,7 @@ def render_prior_session_view(app: App) -> None: with theme.ai_text_style(): markdown_helper.render(content, context_id=f'prior_disc_{idx}') imgui.separator() + def render_thinking_indicator(app: App) -> None: is_thinking = app.ai_status in ['sending...', 'streaming...', 'running powershell...'] if is_thinking: @@ -5406,128 +5530,3 @@ def render_mma_focus_selector(app: App) -> None: if app.ui_focus_agent and imgui.button("x##clear_focus"): app.ui_focus_agent = None #endregion: MMA - -def render_empty_context_modal(app: App) -> None: - if app.show_empty_context_modal: - imgui.open_popup("Empty Context Warning") - app.show_empty_context_modal = False - - if imgui.begin_popup_modal("Empty Context Warning", True, imgui.WindowFlags_.always_auto_resize)[0]: - imgui.text_colored(theme.get_color("status_warning"), "WARNING: Empty Context Composition") - imgui.text("You are attempting to generate a response without any files selected.") - imgui.text("This may result in poor AI performance or loss of project context.") - imgui.separator() - if imgui.button("Proceed Anyway", imgui.ImVec2(150, 0)): - if app._pending_generation_action == 'generate': app.controller._handle_generate_send() - elif app._pending_generation_action == 'md_only': app.controller._handle_md_only() - app._pending_generation_action = None - imgui.close_current_popup() - imgui.same_line() - if imgui.button("Cancel", imgui.ImVec2(120, 0)): - imgui.close_current_popup() - imgui.end_popup() - -def render_context_modals(app: App) -> None: - render_empty_context_modal(app) - render_add_context_files_modal(app) - - if app.show_missing_files_modal: - imgui.open_popup("Missing Files Warning") - app.show_missing_files_modal = False - - if imgui.begin_popup_modal("Missing Files Warning", True, imgui.WindowFlags_.always_auto_resize)[0]: - imgui.text("The following files are missing from disk:") - imgui.separator() - imgui.begin_child("missing_files_list", imgui.ImVec2(0, 150), True) - for f in app.missing_context_files: - imgui.text_colored(theme.get_color("status_error"), f) - imgui.end_child() - imgui.separator() - - if imgui.button("Save Anyway") or getattr(app, "_pending_save_anyway_click", False): - app._pending_save_anyway_click = False - name = app.target_context_preset_name - preset_files = [] - for f in app.context_files: - import copy - from src import models - p = f.path if hasattr(f, 'path') else str(f) - vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' - slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else [] - msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {} - sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False - dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False - preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn)) - preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots)) - app.controller.save_context_preset(preset) - app.ui_new_context_preset_name = "" - imgui.close_current_popup() - - imgui.same_line() - if imgui.button("Cancel"): - imgui.close_current_popup() - - imgui.end_popup() - - render_ast_inspector_modal(app) - -def _get_context_composition_state(app: App) -> tuple: - files_state = [] - for f in app.context_files: - p = f.path if hasattr(f, 'path') else str(f) - vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' - agg = f.auto_aggregate if hasattr(f, 'auto_aggregate') else False - slc = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', [])) - mask = tuple(sorted(getattr(f, 'ast_mask', {}).items())) - files_state.append((p, vm, agg, slc, mask)) - screenshots_state = tuple(app.screenshots) - return (tuple(files_state), screenshots_state) - -def _check_auto_refresh_context_preview(app: App) -> None: - current_state = _get_context_composition_state(app) - if not hasattr(app, "_last_context_preview_state") or app._last_context_preview_state != current_state: - app._last_context_preview_state = current_state - if not any(getattr(f, 'auto_aggregate', False) for f in app.context_files) and not app.screenshots: - app.context_preview_text = "# Context Composition Empty\n\nNo files or screenshots have been selected for aggregation." - return - - if getattr(app, "_is_generating_preview", False): - app._pending_preview_refresh = True - return - - app._is_generating_preview = True - def worker(): - try: - app.controller.context_files = app.context_files - res = app.controller._do_generate() - app.context_preview_text = res[0] - except Exception: - app.context_preview_text = "Error generating context preview." - finally: - app._is_generating_preview = False - if getattr(app, "_pending_preview_refresh", False): - app._pending_preview_refresh = False - # This will trigger again on next GUI frame because _last_context_preview_state - # will be slightly behind if another change happened during the thread. - # Or we just clear the state so it re-triggers. - app._last_context_preview_state = None - - import threading - threading.Thread(target=worker, daemon=True).start() - -def render_context_preview_window(app: App) -> None: - _check_auto_refresh_context_preview(app) - with imscope.window("Context Preview", app.show_windows["Context Preview"]) as (exp, opened): - app.show_windows["Context Preview"] = bool(opened) - if not opened: - app.ui_separate_context_preview = False - if exp: - if imgui.button("Close"): - app.show_windows["Context Preview"] = False - app.ui_separate_context_preview = False - imgui.same_line() - if imgui.button("Copy to Clipboard"): - imgui.set_clipboard_text(app.context_preview_text) - with imscope.child("ctx_preview_scroll", 0, 0, True): - markdown_helper.render(app.context_preview_text, context_id="ctx_preview") - diff --git a/src/history.py b/src/history.py index 3cf027ff..091f5516 100644 --- a/src/history.py +++ b/src/history.py @@ -1,7 +1,9 @@ -import typing import time +import typing + from dataclasses import dataclass, field + @dataclass class UISnapshot: """Capture of restorable UI state.""" diff --git a/src/hot_reloader.py b/src/hot_reloader.py index c761eeb0..0b3e492c 100644 --- a/src/hot_reloader.py +++ b/src/hot_reloader.py @@ -1,9 +1,12 @@ from __future__ import annotations -from dataclasses import dataclass, field -from typing import Any + import copy import traceback +from dataclasses import dataclass, field +from typing import Any + + @dataclass class HotModule: name: str diff --git a/src/imgui_scopes.py b/src/imgui_scopes.py index f935e5b5..e89dff93 100644 --- a/src/imgui_scopes.py +++ b/src/imgui_scopes.py @@ -1,8 +1,11 @@ from __future__ import annotations + from typing import Any + from imgui_bundle import imgui from imgui_bundle import imgui_node_editor + def child(id_str: str, size_x: float = 0, size_y: float = 0, flags: int = 0): return _ScopeChild(id_str, size_x, size_y, flags) class _ScopeChild: def __init__(self, id_str: str, size_x: float | imgui.ImVec2, size_y: float, flags: int): diff --git a/src/log_pruner.py b/src/log_pruner.py index 6d0f9be9..3d2eac5d 100644 --- a/src/log_pruner.py +++ b/src/log_pruner.py @@ -2,9 +2,12 @@ import os import shutil import sys import time + from datetime import datetime, timedelta + from src.log_registry import LogRegistry + class LogPruner: """ diff --git a/src/log_registry.py b/src/log_registry.py index 93b032a8..338bde55 100644 --- a/src/log_registry.py +++ b/src/log_registry.py @@ -38,11 +38,13 @@ See Also: - src/paths.py for registry path resolution """ from __future__ import annotations + +import os import tomli_w import tomllib + from datetime import datetime -import os -from typing import Any +from typing import Any class LogRegistry: diff --git a/src/markdown_helper.py b/src/markdown_helper.py index 62afb1b9..cbf62d6b 100644 --- a/src/markdown_helper.py +++ b/src/markdown_helper.py @@ -1,11 +1,15 @@ # src/markdown_helper.py from __future__ import annotations -from imgui_bundle import imgui_md, imgui, immapp, imgui_color_text_edit as ed -import webbrowser + import os import re +import webbrowser + +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 typing import Optional, Dict, Callable + def _get_language_id(name: str): """Get a language identifier for ImGuiColorTextEdit. diff --git a/src/markdown_table.py b/src/markdown_table.py index 180e604b..ab969f9c 100644 --- a/src/markdown_table.py +++ b/src/markdown_table.py @@ -1,7 +1,9 @@ import re -from dataclasses import dataclass + +from dataclasses import dataclass from imgui_bundle import imgui, imgui_md + _TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$") def render_table(block: "TableBlock") -> None: diff --git a/src/mcp_client.py b/src/mcp_client.py index 84b7bd9c..cf175030 100644 --- a/src/mcp_client.py +++ b/src/mcp_client.py @@ -52,22 +52,27 @@ See Also: #MCP-style file context tools for manual_slop. from __future__ import annotations + import ast import asyncio import json import os import re as _re import subprocess -from html.parser import HTMLParser -from pathlib import Path -from typing import Optional, Callable, Any, cast import urllib.request import urllib.parse + +from html.parser import HTMLParser +from pathlib import Path +from typing import Optional, Callable, Any, cast + +from scripts import py_struct_tools + from src import beads_client from src import models from src import outline_tool from src import summarize -from scripts import py_struct_tools + # ------------------------------------------------------------------ mutating tools sentinel diff --git a/src/mma_prompts.py b/src/mma_prompts.py index 4a6cb448..617a5a17 100644 --- a/src/mma_prompts.py +++ b/src/mma_prompts.py @@ -5,6 +5,7 @@ Contains templates and static strings for hierarchical orchestration. from typing import Dict + # --- Tier 1 (Strategic/Orchestration: PM) --- TIER1_BASE_SYSTEM: str = """ diff --git a/src/models.py b/src/models.py index eb0648a7..38a82d4b 100644 --- a/src/models.py +++ b/src/models.py @@ -37,16 +37,20 @@ See Also: - src/project_manager.py for persistence layer """ from __future__ import annotations + import datetime import json import os -from dataclasses import dataclass, field -from pathlib import Path -from typing import Any, Dict, List, Optional, Union import tomllib -from pydantic import BaseModel + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Dict, List, Optional, Union +from pydantic import BaseModel + from src.paths import get_config_path + #region: Constants PROVIDERS: List[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"] @@ -1040,4 +1044,4 @@ def load_mcp_config(path: str) -> MCPConfiguration: except Exception: return MCPConfiguration() -#endregion: MCP Config \ No newline at end of file +#endregion: MCP Config diff --git a/src/multi_agent_conductor.py b/src/multi_agent_conductor.py index 36453c5f..51a75e01 100644 --- a/src/multi_agent_conductor.py +++ b/src/multi_agent_conductor.py @@ -27,23 +27,26 @@ See Also: - src/dag_engine.py for TrackDAG and ExecutionEngine - src/models.py for Ticket, Track, WorkerContext """ -from src import ai_client -from src import summarize import json import threading import time import traceback -from typing import List, Optional, Tuple, Callable + from dataclasses import asdict -from src import events -from src import models -from src.models import Ticket, Track, WorkerContext -from src.file_cache import ASTParser from pathlib import Path -from src.personas import PersonaManager +from typing import List, Optional, Tuple, Callable + +from src import ai_client +from src import events +from src.file_cache import ASTParser +from src import models from src import paths +from src import summarize from src.dag_engine import TrackDAG, ExecutionEngine +from src.models import Ticket, Track, WorkerContext +from src.personas import PersonaManager + class WorkerPool: """ diff --git a/src/orchestrator_pm.py b/src/orchestrator_pm.py index 6c36e61e..9f378c86 100644 --- a/src/orchestrator_pm.py +++ b/src/orchestrator_pm.py @@ -1,13 +1,14 @@ - import json -from src import ai_client -from src import mma_prompts -from src import aggregate -from src import summarize + from pathlib import Path from typing import Any, Optional +from src import aggregate +from src import ai_client +from src import mma_prompts from src import paths +from src import summarize + def get_track_history_summary() -> str: """ diff --git a/src/outline_tool.py b/src/outline_tool.py index 5bba742e..61a7829f 100644 --- a/src/outline_tool.py +++ b/src/outline_tool.py @@ -31,6 +31,7 @@ See Also: - src/summarize.py for heuristic file summaries """ import ast + from pathlib import Path diff --git a/src/patch_modal.py b/src/patch_modal.py index 352a1a06..feb6daa0 100644 --- a/src/patch_modal.py +++ b/src/patch_modal.py @@ -1,5 +1,6 @@ -from typing import Optional, Callable, List from dataclasses import dataclass, field +from typing import Optional, Callable, List + @dataclass class PendingPatch: diff --git a/src/paths.py b/src/paths.py index f0b2632c..3e6190ca 100644 --- a/src/paths.py +++ b/src/paths.py @@ -40,10 +40,12 @@ See Also: - src/session_logger.py for logging paths - src/project_manager.py for project paths """ -from pathlib import Path import os import tomllib -from typing import Optional, Any + +from pathlib import Path +from typing import Optional, Any + _RESOLVED: dict[str, Path] = {} @@ -237,4 +239,5 @@ def reset_resolved() -> None: For testing only - clear cached resolutions. [C: tests/conftest.py:reset_paths, tests/test_app_controller_offloading.py:tmp_session_dir, tests/test_gui_phase3.py:test_conductor_setup_scan, tests/test_paths.py:reset_paths, tests/test_project_paths.py:test_get_all_tracks_project_specific, tests/test_project_paths.py:test_get_conductor_dir_default, tests/test_project_paths.py:test_get_conductor_dir_project_specific_with_toml] """ - _RESOLVED.clear() \ No newline at end of file + _RESOLVED.clear() + \ No newline at end of file diff --git a/src/performance_monitor.py b/src/performance_monitor.py index f578f886..40b0bdd3 100644 --- a/src/performance_monitor.py +++ b/src/performance_monitor.py @@ -53,12 +53,15 @@ Integration: - Exposed via Hook API at /api/performance """ from __future__ import annotations + +import psutil import sys import time -import psutil import threading -from typing import Any, Optional, Callable, Dict, List + from collections import deque +from typing import Any, Optional, Callable, Dict, List + _instance: Optional[PerformanceMonitor] = None diff --git a/src/personas.py b/src/personas.py index 6ece285e..0c1ff936 100644 --- a/src/personas.py +++ b/src/personas.py @@ -1,9 +1,11 @@ import tomllib import tomli_w + from pathlib import Path -from typing import Dict, Any, Optional +from typing import Dict, Any, Optional + from src.models import Persona -from src import paths +from src import paths class PersonaManager: """Manages Persona profiles across global and project-specific files.""" diff --git a/src/presets.py b/src/presets.py index a09b5df7..677bcd21 100644 --- a/src/presets.py +++ b/src/presets.py @@ -1,10 +1,13 @@ import sys import tomllib import tomli_w + from pathlib import Path -from typing import Dict, Any, Optional +from typing import Dict, Any, Optional + from src.models import Preset -from src.paths import get_global_presets_path, get_project_presets_path +from src.paths import get_global_presets_path, get_project_presets_path + class PresetManager: """Manages system prompt presets across global and project-specific files.""" diff --git a/src/project_manager.py b/src/project_manager.py index bdc46835..545e0af1 100644 --- a/src/project_manager.py +++ b/src/project_manager.py @@ -5,19 +5,24 @@ Handles loading/saving of project .toml configurations. Also handles serializing the discussion history into the TOML format using a special @timestamp prefix to preserve the exact sequence of events. """ -import subprocess import datetime +import json +import re +import subprocess import tomllib import tomli_w -import re -import json -from typing import Any, Optional, TYPE_CHECKING, Union + from pathlib import Path +from typing import Any, Optional, TYPE_CHECKING, Union + from src import paths + if TYPE_CHECKING: from src.models import TrackState + TS_FMT: str = "%Y-%m-%dT%H:%M:%S" + def now_ts() -> str: return datetime.datetime.now().strftime(TS_FMT) diff --git a/src/rag_engine.py b/src/rag_engine.py index e3412d9b..93bbf50d 100644 --- a/src/rag_engine.py +++ b/src/rag_engine.py @@ -1,9 +1,11 @@ +import asyncio +import copy +import json import os import sys -import asyncio -import json -import copy + from typing import List, Dict, Any, Optional + from src import models from src import mcp_client @@ -11,6 +13,7 @@ _SENTENCE_TRANSFORMERS = None _GOOGLE_GENAI = None _CHROMADB = None + def _get_sentence_transformers(): global _SENTENCE_TRANSFORMERS if _SENTENCE_TRANSFORMERS is None: diff --git a/src/session_logger.py b/src/session_logger.py index c42164b9..3728a605 100644 --- a/src/session_logger.py +++ b/src/session_logger.py @@ -20,11 +20,13 @@ import atexit import datetime import json import threading -from typing import Any, Optional, TextIO + from pathlib import Path +from typing import Any, Optional, TextIO from src import paths + _ts: str = "" # session timestamp string e.g. "20260301_142233" _session_id: str = "" # YYYYMMDD_HHMMSS[_Label] _session_dir: Optional[Path] = None # Path to the sub-directory for this session diff --git a/src/shaders.py b/src/shaders.py index 8bb81bda..faeab5eb 100644 --- a/src/shaders.py +++ b/src/shaders.py @@ -1,5 +1,6 @@ from imgui_bundle import imgui + def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None: """ diff --git a/src/shell_runner.py b/src/shell_runner.py index bbd2b1bd..5513993b 100644 --- a/src/shell_runner.py +++ b/src/shell_runner.py @@ -7,10 +7,11 @@ configuring the environment via mcp_env.toml. It handles timeouts, logging, and optional QA/patch callbacks for error recovery. """ import os -import subprocess import shutil +import subprocess + from pathlib import Path -from typing import Callable, Optional +from typing import Callable, Optional try: import tomllib @@ -20,6 +21,7 @@ except ImportError: TIMEOUT_SECONDS: int = 60 _ENV_CONFIG: dict = {} + def _load_env_config() -> dict: """Load mcp_env.toml from project root or environment variable.""" env_path = os.environ.get("SLOP_MCP_ENV") diff --git a/src/summarize.py b/src/summarize.py index 3c817c87..a2212fe3 100644 --- a/src/summarize.py +++ b/src/summarize.py @@ -24,12 +24,16 @@ context block that replaces full file contents in the initial send. """ import ast import re + from pathlib import Path -from typing import Callable, Any +from typing import Callable, Any + from src.summary_cache import SummaryCache, get_file_hash + _summary_cache = SummaryCache() + # ------------------------------------------------------------------ per-type extractors def _summarise_python(path: Path, content: str) -> str: diff --git a/src/summary_cache.py b/src/summary_cache.py index 8b326a6a..e95e9a05 100644 --- a/src/summary_cache.py +++ b/src/summary_cache.py @@ -1,7 +1,9 @@ import hashlib import json + from pathlib import Path -from typing import Optional, Dict +from typing import Optional, Dict + def get_file_hash(content: str) -> str: """ diff --git a/src/theme_2.py b/src/theme_2.py index 27de46e5..93d4247d 100644 --- a/src/theme_2.py +++ b/src/theme_2.py @@ -8,16 +8,20 @@ Font loading uses hello_imgui.load_font(). Scale uses imgui.get_style().font_scale_main. """ -from imgui_bundle import imgui, hello_imgui -from typing import Any, Optional import typing -from contextlib import nullcontext -from src import imgui_scopes as imscope + +from contextlib import nullcontext +from imgui_bundle import imgui, hello_imgui +from typing import Any, Optional + import src.theme_nerv -from src.theme_nerv import DATA_GREEN + +from src import imgui_scopes as imscope +from src.theme_nerv import DATA_GREEN from src.theme_nerv_fx import CRTFilter, AlertPulsing, StatusFlicker -from src.paths import get_global_themes_path -from src.theme_models import ThemeFile, load_themes_from_dir, load_themes_from_toml +from src.paths import get_global_themes_path +from src.theme_models import ThemeFile, load_themes_from_dir, load_themes_from_toml + # ------------------------------------------------------------------ palettes diff --git a/src/theme_models.py b/src/theme_models.py index e0c7b42e..1b4b5540 100644 --- a/src/theme_models.py +++ b/src/theme_models.py @@ -1,9 +1,11 @@ from __future__ import annotations + import sys import tomllib + from dataclasses import dataclass -from pathlib import Path -from typing import Any +from pathlib import Path +from typing import Any VALID_SYNTAX_PALETTES: tuple[str, ...] = ("dark", "light", "mariana", "retro_blue") diff --git a/src/theme_nerv.py b/src/theme_nerv.py index a082eb6c..6c2a8674 100644 --- a/src/theme_nerv.py +++ b/src/theme_nerv.py @@ -1,5 +1,6 @@ from imgui_bundle import imgui + def _c(r: int, g: int, b: int, a: int = 255) -> tuple[float, float, float, float]: """Convert 0-255 RGBA to 0.0-1.0 floats.""" return (r / 255.0, g / 255.0, b / 255.0, a / 255.0) diff --git a/src/theme_nerv_fx.py b/src/theme_nerv_fx.py index c952d917..b16d9c27 100644 --- a/src/theme_nerv_fx.py +++ b/src/theme_nerv_fx.py @@ -1,8 +1,10 @@ -import time import math import random +import time + from imgui_bundle import imgui + class CRTFilter: def __init__(self): self.enabled = True diff --git a/src/thinking_parser.py b/src/thinking_parser.py index 07a8fc07..53409e88 100644 --- a/src/thinking_parser.py +++ b/src/thinking_parser.py @@ -1,7 +1,10 @@ import re + from typing import List, Tuple + from src.models import ThinkingSegment + def parse_thinking_trace(text: str) -> Tuple[List[ThinkingSegment], str]: """ diff --git a/src/tool_bias.py b/src/tool_bias.py index 024377e9..ac7aef13 100644 --- a/src/tool_bias.py +++ b/src/tool_bias.py @@ -1,6 +1,8 @@ from typing import List, Dict, Any, Optional + from src.models import Tool, ToolPreset, BiasProfile + class ToolBiasEngine: def apply_semantic_nudges(self, tool_definitions: List[Dict[str, Any]], preset: ToolPreset) -> List[Dict[str, Any]]: """ diff --git a/src/tool_presets.py b/src/tool_presets.py index 67624ebe..fe56bc96 100644 --- a/src/tool_presets.py +++ b/src/tool_presets.py @@ -1,10 +1,13 @@ import tomllib import tomli_w + from pathlib import Path -from typing import Dict, List, Optional, Union, Any -from src import paths +from typing import Dict, List, Optional, Union, Any + +from src import paths from src.models import ToolPreset, BiasProfile + class ToolPresetManager: def __init__(self, project_root: Optional[Union[str, Path]] = None): self.project_root = Path(project_root) if project_root else None diff --git a/src/vendor_state.py b/src/vendor_state.py index 3ac94c80..2683e937 100644 --- a/src/vendor_state.py +++ b/src/vendor_state.py @@ -1,5 +1,6 @@ from dataclasses import dataclass + @dataclass(frozen=True) class VendorMetric: """Atomic vendor-state metric. diff --git a/src/workspace_manager.py b/src/workspace_manager.py index 7496681d..1bb6f64f 100644 --- a/src/workspace_manager.py +++ b/src/workspace_manager.py @@ -1,9 +1,12 @@ import tomllib import tomli_w + from pathlib import Path -from typing import Dict, Any, Optional, Union +from typing import Dict, Any, Optional, Union + from src.models import WorkspaceProfile -from src import paths +from src import paths + class WorkspaceManager: """Manages Workspace profiles across global and project-specific files."""