Private
Public Access
0
0

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
This commit is contained in:
2026-06-05 21:44:41 -04:00
parent 1d89fcaf8a
commit 873edf42cf
53 changed files with 375 additions and 238 deletions
+4
View File
@@ -16,14 +16,18 @@ import glob
import os import os
import re import re
import tomllib import tomllib
from pathlib import Path, PureWindowsPath from pathlib import Path, PureWindowsPath
from typing import Any, cast from typing import Any, cast
from src import beads_client from src import beads_client
from src import project_manager from src import project_manager
from src import summarize from src import summarize
from src.file_cache import ASTParser from src.file_cache import ASTParser
from src.performance_monitor import get_monitor from src.performance_monitor import get_monitor
def find_next_increment(output_dir: Path, namespace: str) -> int: def find_next_increment(output_dir: Path, namespace: str) -> int:
pattern = re.compile(rf"^{re.escape(namespace)}_(\d+)\.md$") pattern = re.compile(rf"^{re.escape(namespace)}_(\d+)\.md$")
max_num = 0 max_num = 0
+14 -7
View File
@@ -16,34 +16,41 @@ import anthropic
from google import genai from google import genai
from google.genai import types from google.genai import types
from openai import OpenAI from openai import OpenAI
import asyncio import asyncio
import datetime import datetime
import difflib import difflib
import hashlib import hashlib
import json import json
import os import os
from pathlib import Path as _P
import requests # type: ignore[import-untyped] import requests # type: ignore[import-untyped]
import sys import sys
import threading import threading
import time import time
import tomllib import tomllib
# TODO(Ed): Eliminate These?
from collections import deque from collections import deque
from typing import Optional, Callable, Any, List, Union, cast, Iterable from pathlib import Path as _P
from pathlib import Path from pathlib import Path
from src.events import EventEmitter from typing import Optional, Callable, Any, List, Union, cast, Iterable
from src import project_manager from src import project_manager
from src import file_cache from src import file_cache
from src import mcp_client from src import mcp_client
from src import mma_prompts from src import mma_prompts
from src import performance_monitor from src import performance_monitor
from src import project_manager from src import project_manager
from src.paths import get_credentials_path
from src.tool_bias import ToolBiasEngine # TODO(Ed): Eliminate these?
from src.models import ToolPreset, BiasProfile, Tool from src.events import EventEmitter
from src.gemini_cli_adapter import GeminiCliAdapter 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 from src.tool_presets import ToolPresetManager
_provider: str = "gemini" _provider: str = "gemini"
_model: str = "gemini-2.5-flash-lite" _model: str = "gemini-2.5-flash-lite"
_temperature: float = 0.0 _temperature: float = 0.0
+3
View File
@@ -32,11 +32,14 @@ See Also:
- docs/guide_tools.md for Hook API documentation - docs/guide_tools.md for Hook API documentation
""" """
from __future__ import annotations from __future__ import annotations
import requests # type: ignore[import-untyped] import requests # type: ignore[import-untyped]
import sys import sys
import time import time
from typing import Any from typing import Any
class ApiHookClient: class ApiHookClient:
def __init__(self, base_url: str = "http://127.0.0.1:8999", api_key: str | None = None): def __init__(self, base_url: str = "http://127.0.0.1:8999", api_key: str | None = None):
""" """
+13 -7
View File
@@ -1,16 +1,22 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import json import json
import logging
import sys
import threading import threading
import uuid import uuid
import sys
import asyncio
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from typing import Any
import logging
import websockets import websockets
# TODO(Ed): Eliminate these?
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from typing import Any
from websockets.asyncio.server import serve from websockets.asyncio.server import serve
from src import session_logger
from src import cost_tracker from src import cost_tracker
from src import session_logger
""" """
API Hooks - REST API for external automation and state inspection. API Hooks - REST API for external automation and state inspection.
@@ -820,4 +826,4 @@ class WebSocketServer:
return return
message = json.dumps({"channel": channel, "payload": payload}) message = json.dumps({"channel": channel, "payload": payload})
for ws in list(self.clients[channel]): for ws in list(self.clients[channel]):
asyncio.run_coroutine_threadsafe(ws.send(message), self.loop) asyncio.run_coroutine_threadsafe(ws.send(message), self.loop)
+12 -6
View File
@@ -10,15 +10,18 @@ import time
import tomli_w import tomli_w
import traceback import traceback
import uuid import uuid
# TODO(Ed): Eliminate these?
from dataclasses import asdict from dataclasses import asdict
from datetime import datetime from datetime import datetime
from fastapi import FastAPI, Depends, HTTPException 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 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 aggregate
from src import models from src import models
from src.models import GenerateRequest, ConfirmRequest
from src import ai_client from src import ai_client
from src import conductor_tech_lead from src import conductor_tech_lead
from src import events from src import events
@@ -35,8 +38,11 @@ from src import shell_runner
from src import theme_2 as theme from src import theme_2 as theme
from src import thinking_parser from src import thinking_parser
from src import tool_presets from src import tool_presets
from src.context_presets import ContextPresetManager 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]: def parse_symbols(text: str) -> list[str]:
""" """
+5 -3
View File
@@ -1,8 +1,10 @@
from dataclasses import dataclass
from typing import List, Optional
from pathlib import Path
import json import json
from dataclasses import dataclass
from typing import List, Optional
from pathlib import Path
@dataclass @dataclass
class Bead: class Bead:
id: str id: str
+2 -1
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
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
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Callable from typing import TYPE_CHECKING, Callable
from src.command_palette import CommandRegistry from src.command_palette import CommandRegistry
if TYPE_CHECKING: if TYPE_CHECKING:
+5 -2
View File
@@ -34,10 +34,13 @@ See Also:
- src/dag_engine.py for TrackDAG - src/dag_engine.py for TrackDAG
""" """
import json import json
import re
from typing import Any
from src import ai_client from src import ai_client
from src import mma_prompts from src import mma_prompts
import re
from typing import Any
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]]:
""" """
+2
View File
@@ -1,6 +1,8 @@
from typing import Dict, Any from typing import Dict, Any
from src.models import ContextPreset from src.models import ContextPreset
class ContextPresetManager: class ContextPresetManager:
"""Manages context presets within the project dictionary (manual_slop.toml).""" """Manages context presets within the project dictionary (manual_slop.toml)."""
+1
View File
@@ -33,6 +33,7 @@ See Also:
""" """
import re import re
# Pricing per 1M tokens in USD # Pricing per 1M tokens in USD
MODEL_PRICING = [ MODEL_PRICING = [
(r"gemini-2\.5-flash-lite", {"input_per_mtok": 0.075, "output_per_mtok": 0.30}), (r"gemini-2\.5-flash-lite", {"input_per_mtok": 0.075, "output_per_mtok": 0.30}),
+2
View File
@@ -27,9 +27,11 @@ See Also:
- src/multi_agent_conductor.py for ConductorEngine integration - src/multi_agent_conductor.py for ConductorEngine integration
""" """
from typing import List from typing import List
from src.models import Ticket from src.models import Ticket
from src.performance_monitor import get_monitor from src.performance_monitor import get_monitor
class TrackDAG: class TrackDAG:
""" """
Manages a Directed Acyclic Graph of implementation tickets. Manages a Directed Acyclic Graph of implementation tickets.
+5 -3
View File
@@ -1,8 +1,10 @@
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
import shutil import shutil
import os import os
from pathlib import Path
from dataclasses import dataclass
from pathlib import Path
from typing import List, Dict, Optional, Tuple
@dataclass @dataclass
class DiffHunk: class DiffHunk:
+3 -1
View File
@@ -30,8 +30,10 @@ Thread Safety:
- UserRequestEvent: Immutable, safe for concurrent access - UserRequestEvent: Immutable, safe for concurrent access
""" """
import queue import queue
from typing import Callable, Any, Dict, List, Tuple, Optional
from pathlib import Path from pathlib import Path
from typing import Callable, Any, Dict, List, Tuple, Optional
class EventEmitter: class EventEmitter:
""" """
+3 -1
View File
@@ -4,8 +4,10 @@ from __future__ import annotations
import os import os
import subprocess import subprocess
import tempfile import tempfile
# TODO(Ed): Eliminate these?
from pathlib import Path from pathlib import Path
from typing import Optional, List from typing import Optional, List
from src.models import ExternalEditorConfig, TextEditorConfig from src.models import ExternalEditorConfig, TextEditorConfig
+6 -3
View File
@@ -34,13 +34,16 @@ See Also:
- docs/guide_tools.md for AST tool documentation - docs/guide_tools.md for AST tool documentation
- src/summarize.py for heuristic summaries - src/summarize.py for heuristic summaries
""" """
from pathlib import Path import re
from typing import Optional, Any, List, Tuple, Dict
import tree_sitter import tree_sitter
import tree_sitter_python import tree_sitter_python
import tree_sitter_cpp import tree_sitter_cpp
import tree_sitter_c 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]] = {} _ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {}
+2
View File
@@ -1,7 +1,9 @@
import hashlib import hashlib
import re import re
from typing import Optional, Tuple from typing import Optional, Tuple
class FuzzyAnchor: class FuzzyAnchor:
@staticmethod @staticmethod
def get_context(lines: list[str], index: int, count: int, direction: int) -> list[str]: def get_context(lines: list[str], index: int, count: int, direction: int) -> list[str]:
+5 -3
View File
@@ -33,14 +33,16 @@ See Also:
- docs/guide_architecture.md for CLI adapter integration - docs/guide_architecture.md for CLI adapter integration
- src/ai_client.py for provider dispatch - src/ai_client.py for provider dispatch
""" """
import subprocess
import json import json
import os import os
import time import subprocess
import sys import sys
from src import session_logger import time
from typing import Optional, Callable, Any from typing import Optional, Callable, Any
from src import session_logger
class GeminiCliAdapter: class GeminiCliAdapter:
""" """
+133 -134
View File
@@ -17,7 +17,6 @@ import threading
import time import time
import tomli_w import tomli_w
import typing import typing
from contextlib import ExitStack, nullcontext
# Ensure thirdparty is in sys.path for defer # Ensure thirdparty is in sys.path for defer
_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _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: if _thirdparty not in sys.path:
sys.path.insert(0, _thirdparty) sys.path.insert(0, _thirdparty)
from defer import defer from contextlib import ExitStack, nullcontext
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced # from defer import defer
from pathlib import Path from pathlib import Path
from tkinter import filedialog, Tk from tkinter import filedialog, Tk
from typing import Optional, Any from typing import Optional, Any
from defer import defer
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
from pathlib import Path from pathlib import Path
from tkinter import filedialog, Tk from tkinter import filedialog, Tk
from typing import Optional, Any from typing import Optional, Any
from src.diff_viewer import apply_patch_to_file from src.diff_viewer import apply_patch_to_file
from src import ai_client from src import ai_client
from src import aggregate 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 thinking_parser
from src import workspace_manager from src import workspace_manager
from src.hot_reloader import HotReloader from src.hot_reloader import HotReloader
if sys.platform == "win32": if sys.platform == "win32":
import win32gui import win32gui
import win32con import win32con
@@ -2784,8 +2784,6 @@ def render_files_and_media(app: App) -> None:
if p not in app.screenshots: app.screenshots.append(p) if p not in app.screenshots: app.screenshots.append(p)
return return
#endregion: Context Management
def render_context_batch_actions(app: App, total_lines: int, total_ast: int) -> None: def render_context_batch_actions(app: App, total_lines: int, total_ast: int) -> None:
imgui.text("Batch:") imgui.text("Batch:")
for mode in ["full", "summary", "skeleton", "outline", "masked", "none"]: 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): with imscope.child("last_sys_prompt", 0, 0, True):
markdown_helper.render(app.last_resolved_system_prompt, context_id="snapshot_sys") 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 #endregion: Context Management
#region: Discussions #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}") imgui.text(f"Session Cost: ${insights.get('session_cost', 0):.4f}")
completed = insights.get('completed_tickets', 0) completed = insights.get('completed_tickets', 0)
efficiency = insights.get('efficiency', 0) efficiency = insights.get('efficiency', 0)
def render_prior_session_view(app: App) -> None: def render_prior_session_view(app: App) -> None:
with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor")): 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 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(): with theme.ai_text_style():
markdown_helper.render(content, context_id=f'prior_disc_{idx}') markdown_helper.render(content, context_id=f'prior_disc_{idx}')
imgui.separator() imgui.separator()
def render_thinking_indicator(app: App) -> None: def render_thinking_indicator(app: App) -> None:
is_thinking = app.ai_status in ['sending...', 'streaming...', 'running powershell...'] is_thinking = app.ai_status in ['sending...', 'streaming...', 'running powershell...']
if is_thinking: 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 if app.ui_focus_agent and imgui.button("x##clear_focus"): app.ui_focus_agent = None
#endregion: MMA #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")
+3 -1
View File
@@ -1,7 +1,9 @@
import typing
import time import time
import typing
from dataclasses import dataclass, field from dataclasses import dataclass, field
@dataclass @dataclass
class UISnapshot: class UISnapshot:
"""Capture of restorable UI state.""" """Capture of restorable UI state."""
+5 -2
View File
@@ -1,9 +1,12 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
import copy import copy
import traceback import traceback
from dataclasses import dataclass, field
from typing import Any
@dataclass @dataclass
class HotModule: class HotModule:
name: str name: str
+3
View File
@@ -1,8 +1,11 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any
from imgui_bundle import imgui from imgui_bundle import imgui
from imgui_bundle import imgui_node_editor 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) 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: class _ScopeChild:
def __init__(self, id_str: str, size_x: float | imgui.ImVec2, size_y: float, flags: int): def __init__(self, id_str: str, size_x: float | imgui.ImVec2, size_y: float, flags: int):
+3
View File
@@ -2,9 +2,12 @@ import os
import shutil import shutil
import sys import sys
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from src.log_registry import LogRegistry from src.log_registry import LogRegistry
class LogPruner: class LogPruner:
""" """
+4 -2
View File
@@ -38,11 +38,13 @@ See Also:
- src/paths.py for registry path resolution - src/paths.py for registry path resolution
""" """
from __future__ import annotations from __future__ import annotations
import os
import tomli_w import tomli_w
import tomllib import tomllib
from datetime import datetime from datetime import datetime
import os from typing import Any
from typing import Any
class LogRegistry: class LogRegistry:
+7 -3
View File
@@ -1,11 +1,15 @@
# src/markdown_helper.py # src/markdown_helper.py
from __future__ import annotations from __future__ import annotations
from imgui_bundle import imgui_md, imgui, immapp, imgui_color_text_edit as ed
import webbrowser
import os import os
import re import re
import webbrowser
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
def _get_language_id(name: str): def _get_language_id(name: str):
"""Get a language identifier for ImGuiColorTextEdit. """Get a language identifier for ImGuiColorTextEdit.
+3 -1
View File
@@ -1,7 +1,9 @@
import re import re
from dataclasses import dataclass
from dataclasses import dataclass
from imgui_bundle import imgui, imgui_md from imgui_bundle import imgui, imgui_md
_TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$") _TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$")
def render_table(block: "TableBlock") -> None: def render_table(block: "TableBlock") -> None:
+9 -4
View File
@@ -52,22 +52,27 @@ See Also:
#MCP-style file context tools for manual_slop. #MCP-style file context tools for manual_slop.
from __future__ import annotations from __future__ import annotations
import ast import ast
import asyncio import asyncio
import json import json
import os import os
import re as _re import re as _re
import subprocess import subprocess
from html.parser import HTMLParser
from pathlib import Path
from typing import Optional, Callable, Any, cast
import urllib.request import urllib.request
import urllib.parse 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 beads_client
from src import models from src import models
from src import outline_tool from src import outline_tool
from src import summarize from src import summarize
from scripts import py_struct_tools
# ------------------------------------------------------------------ mutating tools sentinel # ------------------------------------------------------------------ mutating tools sentinel
+1
View File
@@ -5,6 +5,7 @@ Contains templates and static strings for hierarchical orchestration.
from typing import Dict from typing import Dict
# --- Tier 1 (Strategic/Orchestration: PM) --- # --- Tier 1 (Strategic/Orchestration: PM) ---
TIER1_BASE_SYSTEM: str = """ TIER1_BASE_SYSTEM: str = """
+9 -5
View File
@@ -37,16 +37,20 @@ See Also:
- src/project_manager.py for persistence layer - src/project_manager.py for persistence layer
""" """
from __future__ import annotations from __future__ import annotations
import datetime import datetime
import json import json
import os import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
import tomllib 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 from src.paths import get_config_path
#region: Constants #region: Constants
PROVIDERS: List[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"] PROVIDERS: List[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
@@ -1040,4 +1044,4 @@ def load_mcp_config(path: str) -> MCPConfiguration:
except Exception: except Exception:
return MCPConfiguration() return MCPConfiguration()
#endregion: MCP Config #endregion: MCP Config
+11 -8
View File
@@ -27,23 +27,26 @@ See Also:
- src/dag_engine.py for TrackDAG and ExecutionEngine - src/dag_engine.py for TrackDAG and ExecutionEngine
- src/models.py for Ticket, Track, WorkerContext - src/models.py for Ticket, Track, WorkerContext
""" """
from src import ai_client
from src import summarize
import json import json
import threading import threading
import time import time
import traceback import traceback
from typing import List, Optional, Tuple, Callable
from dataclasses import asdict 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 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 paths
from src import summarize
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
from src.models import Ticket, Track, WorkerContext
from src.personas import PersonaManager
class WorkerPool: class WorkerPool:
""" """
+6 -5
View File
@@ -1,13 +1,14 @@
import json 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 pathlib import Path
from typing import Any, Optional 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 paths
from src import summarize
def get_track_history_summary() -> str: def get_track_history_summary() -> str:
""" """
+1
View File
@@ -31,6 +31,7 @@ See Also:
- src/summarize.py for heuristic file summaries - src/summarize.py for heuristic file summaries
""" """
import ast import ast
from pathlib import Path from pathlib import Path
+2 -1
View File
@@ -1,5 +1,6 @@
from typing import Optional, Callable, List
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional, Callable, List
@dataclass @dataclass
class PendingPatch: class PendingPatch:
+6 -3
View File
@@ -40,10 +40,12 @@ See Also:
- src/session_logger.py for logging paths - src/session_logger.py for logging paths
- src/project_manager.py for project paths - src/project_manager.py for project paths
""" """
from pathlib import Path
import os import os
import tomllib import tomllib
from typing import Optional, Any
from pathlib import Path
from typing import Optional, Any
_RESOLVED: dict[str, Path] = {} _RESOLVED: dict[str, Path] = {}
@@ -237,4 +239,5 @@ def reset_resolved() -> None:
For testing only - clear cached resolutions. 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] [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() _RESOLVED.clear()
+5 -2
View File
@@ -53,12 +53,15 @@ Integration:
- Exposed via Hook API at /api/performance - Exposed via Hook API at /api/performance
""" """
from __future__ import annotations from __future__ import annotations
import psutil
import sys import sys
import time import time
import psutil
import threading import threading
from typing import Any, Optional, Callable, Dict, List
from collections import deque from collections import deque
from typing import Any, Optional, Callable, Dict, List
_instance: Optional[PerformanceMonitor] = None _instance: Optional[PerformanceMonitor] = None
+4 -2
View File
@@ -1,9 +1,11 @@
import tomllib import tomllib
import tomli_w import tomli_w
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from src.models import Persona from src.models import Persona
from src import paths from src import paths
class PersonaManager: class PersonaManager:
"""Manages Persona profiles across global and project-specific files.""" """Manages Persona profiles across global and project-specific files."""
+5 -2
View File
@@ -1,10 +1,13 @@
import sys import sys
import tomllib import tomllib
import tomli_w import tomli_w
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from src.models import Preset 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: class PresetManager:
"""Manages system prompt presets across global and project-specific files.""" """Manages system prompt presets across global and project-specific files."""
+9 -4
View File
@@ -5,19 +5,24 @@ Handles loading/saving of project .toml configurations.
Also handles serializing the discussion history into the TOML format using a special Also handles serializing the discussion history into the TOML format using a special
@timestamp prefix to preserve the exact sequence of events. @timestamp prefix to preserve the exact sequence of events.
""" """
import subprocess
import datetime import datetime
import json
import re
import subprocess
import tomllib import tomllib
import tomli_w import tomli_w
import re
import json
from typing import Any, Optional, TYPE_CHECKING, Union
from pathlib import Path from pathlib import Path
from typing import Any, Optional, TYPE_CHECKING, Union
from src import paths from src import paths
if TYPE_CHECKING: if TYPE_CHECKING:
from src.models import TrackState from src.models import TrackState
TS_FMT: str = "%Y-%m-%dT%H:%M:%S" TS_FMT: str = "%Y-%m-%dT%H:%M:%S"
def now_ts() -> str: def now_ts() -> str:
return datetime.datetime.now().strftime(TS_FMT) return datetime.datetime.now().strftime(TS_FMT)
+6 -3
View File
@@ -1,9 +1,11 @@
import asyncio
import copy
import json
import os import os
import sys import sys
import asyncio
import json
import copy
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from src import models from src import models
from src import mcp_client from src import mcp_client
@@ -11,6 +13,7 @@ _SENTENCE_TRANSFORMERS = None
_GOOGLE_GENAI = None _GOOGLE_GENAI = None
_CHROMADB = None _CHROMADB = None
def _get_sentence_transformers(): def _get_sentence_transformers():
global _SENTENCE_TRANSFORMERS global _SENTENCE_TRANSFORMERS
if _SENTENCE_TRANSFORMERS is None: if _SENTENCE_TRANSFORMERS is None:
+3 -1
View File
@@ -20,11 +20,13 @@ import atexit
import datetime import datetime
import json import json
import threading import threading
from typing import Any, Optional, TextIO
from pathlib import Path from pathlib import Path
from typing import Any, Optional, TextIO
from src import paths from src import paths
_ts: str = "" # session timestamp string e.g. "20260301_142233" _ts: str = "" # session timestamp string e.g. "20260301_142233"
_session_id: str = "" # YYYYMMDD_HHMMSS[_Label] _session_id: str = "" # YYYYMMDD_HHMMSS[_Label]
_session_dir: Optional[Path] = None # Path to the sub-directory for this session _session_dir: Optional[Path] = None # Path to the sub-directory for this session
+1
View File
@@ -1,5 +1,6 @@
from imgui_bundle import imgui 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: 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:
""" """
+4 -2
View File
@@ -7,10 +7,11 @@ configuring the environment via mcp_env.toml. It handles timeouts,
logging, and optional QA/patch callbacks for error recovery. logging, and optional QA/patch callbacks for error recovery.
""" """
import os import os
import subprocess
import shutil import shutil
import subprocess
from pathlib import Path from pathlib import Path
from typing import Callable, Optional from typing import Callable, Optional
try: try:
import tomllib import tomllib
@@ -20,6 +21,7 @@ except ImportError:
TIMEOUT_SECONDS: int = 60 TIMEOUT_SECONDS: int = 60
_ENV_CONFIG: dict = {} _ENV_CONFIG: dict = {}
def _load_env_config() -> dict: def _load_env_config() -> dict:
"""Load mcp_env.toml from project root or environment variable.""" """Load mcp_env.toml from project root or environment variable."""
env_path = os.environ.get("SLOP_MCP_ENV") env_path = os.environ.get("SLOP_MCP_ENV")
+5 -1
View File
@@ -24,12 +24,16 @@ context block that replaces full file contents in the initial <context> send.
""" """
import ast import ast
import re import re
from pathlib import Path from pathlib import Path
from typing import Callable, Any from typing import Callable, Any
from src.summary_cache import SummaryCache, get_file_hash from src.summary_cache import SummaryCache, get_file_hash
_summary_cache = SummaryCache() _summary_cache = SummaryCache()
# ------------------------------------------------------------------ per-type extractors # ------------------------------------------------------------------ per-type extractors
def _summarise_python(path: Path, content: str) -> str: def _summarise_python(path: Path, content: str) -> str:
+3 -1
View File
@@ -1,7 +1,9 @@
import hashlib import hashlib
import json import json
from pathlib import Path from pathlib import Path
from typing import Optional, Dict from typing import Optional, Dict
def get_file_hash(content: str) -> str: def get_file_hash(content: str) -> str:
""" """
+11 -7
View File
@@ -8,16 +8,20 @@ Font loading uses hello_imgui.load_font().
Scale uses imgui.get_style().font_scale_main. Scale uses imgui.get_style().font_scale_main.
""" """
from imgui_bundle import imgui, hello_imgui
from typing import Any, Optional
import typing 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 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.theme_nerv_fx import CRTFilter, AlertPulsing, StatusFlicker
from src.paths import get_global_themes_path from src.paths import get_global_themes_path
from src.theme_models import ThemeFile, load_themes_from_dir, load_themes_from_toml from src.theme_models import ThemeFile, load_themes_from_dir, load_themes_from_toml
# ------------------------------------------------------------------ palettes # ------------------------------------------------------------------ palettes
+4 -2
View File
@@ -1,9 +1,11 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
import tomllib import tomllib
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
VALID_SYNTAX_PALETTES: tuple[str, ...] = ("dark", "light", "mariana", "retro_blue") VALID_SYNTAX_PALETTES: tuple[str, ...] = ("dark", "light", "mariana", "retro_blue")
+1
View File
@@ -1,5 +1,6 @@
from imgui_bundle import imgui from imgui_bundle import imgui
def _c(r: int, g: int, b: int, a: int = 255) -> tuple[float, float, float, float]: 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.""" """Convert 0-255 RGBA to 0.0-1.0 floats."""
return (r / 255.0, g / 255.0, b / 255.0, a / 255.0) return (r / 255.0, g / 255.0, b / 255.0, a / 255.0)
+3 -1
View File
@@ -1,8 +1,10 @@
import time
import math import math
import random import random
import time
from imgui_bundle import imgui from imgui_bundle import imgui
class CRTFilter: class CRTFilter:
def __init__(self): def __init__(self):
self.enabled = True self.enabled = True
+3
View File
@@ -1,7 +1,10 @@
import re import re
from typing import List, Tuple from typing import List, Tuple
from src.models import ThinkingSegment from src.models import ThinkingSegment
def parse_thinking_trace(text: str) -> Tuple[List[ThinkingSegment], str]: def parse_thinking_trace(text: str) -> Tuple[List[ThinkingSegment], str]:
""" """
+2
View File
@@ -1,6 +1,8 @@
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from src.models import Tool, ToolPreset, BiasProfile from src.models import Tool, ToolPreset, BiasProfile
class ToolBiasEngine: class ToolBiasEngine:
def apply_semantic_nudges(self, tool_definitions: List[Dict[str, Any]], preset: ToolPreset) -> List[Dict[str, Any]]: def apply_semantic_nudges(self, tool_definitions: List[Dict[str, Any]], preset: ToolPreset) -> List[Dict[str, Any]]:
""" """
+5 -2
View File
@@ -1,10 +1,13 @@
import tomllib import tomllib
import tomli_w import tomli_w
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Union, Any from typing import Dict, List, Optional, Union, Any
from src import paths
from src import paths
from src.models import ToolPreset, BiasProfile from src.models import ToolPreset, BiasProfile
class ToolPresetManager: class ToolPresetManager:
def __init__(self, project_root: Optional[Union[str, Path]] = None): def __init__(self, project_root: Optional[Union[str, Path]] = None):
self.project_root = Path(project_root) if project_root else None self.project_root = Path(project_root) if project_root else None
+1
View File
@@ -1,5 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
@dataclass(frozen=True) @dataclass(frozen=True)
class VendorMetric: class VendorMetric:
"""Atomic vendor-state metric. """Atomic vendor-state metric.
+5 -2
View File
@@ -1,9 +1,12 @@
import tomllib import tomllib
import tomli_w import tomli_w
from pathlib import Path 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.models import WorkspaceProfile
from src import paths from src import paths
class WorkspaceManager: class WorkspaceManager:
"""Manages Workspace profiles across global and project-specific files.""" """Manages Workspace profiles across global and project-specific files."""