diff --git a/src/app_controller.py b/src/app_controller.py index 5a454304..070eed66 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -46,17 +46,12 @@ from src.warmup import WarmupManager def parse_symbols(text: str) -> list[str]: - """ - Finds all occurrences of '@SymbolName' in text and returns SymbolName. + """Finds all occurrences of '@SymbolName' in text and returns SymbolName. SymbolName can be a function, class, or method (e.g. @MyClass, @my_func, @MyClass.my_method). - [C: tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_basic, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_edge_cases, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_methods, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_mixed, tests/test_symbol_lookup.py:TestSymbolLookup.test_parse_symbols_no_symbols] """ return re.findall(r"@([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)", text) def get_symbol_definition(symbol: str, files: list[str]) -> tuple[str, str, int] | None: - """ - [C: tests/test_symbol_lookup.py:TestSymbolLookup.test_get_symbol_definition_found, tests/test_symbol_lookup.py:TestSymbolLookup.test_get_symbol_definition_not_found] - """ for file_path in files: result = mcp_client.py_get_symbol_info(file_path, symbol) if isinstance(result, tuple): @@ -66,9 +61,6 @@ def get_symbol_definition(symbol: str, files: list[str]) -> tuple[str, str, int] class ConfirmDialog: def __init__(self, script: str, base_dir: str) -> None: - """ - [C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__] - """ self._uid = str(uuid.uuid4()) self._script = str(script) if script is not None else "" self._base_dir = str(base_dir) if base_dir is not None else "" @@ -1657,10 +1649,7 @@ class AppController: self._pending_gui_tasks.append({'action': 'set_tool_log_dirty'}) def _process_pending_gui_tasks(self) -> None: - """ - Processes pending GUI tasks from the queue on the main render thread. - [C: src/gui_2.py:App._render_main_interface, tests/test_api_hook_extensions.py:test_app_processes_new_actions, tests/test_gui_updates.py:test_gui_updates_on_event, tests/test_live_gui_integration_v2.py:test_user_request_error_handling, tests/test_live_gui_integration_v2.py:test_user_request_integration_flow, tests/test_mma_orchestration_gui.py:test_handle_ai_response_fallback, tests/test_mma_orchestration_gui.py:test_handle_ai_response_with_stream_id, tests/test_mma_orchestration_gui.py:test_process_pending_gui_tasks_mma_spawn_approval, tests/test_mma_orchestration_gui.py:test_process_pending_gui_tasks_show_track_proposal, tests/test_process_pending_gui_tasks.py:test_gcli_path_updates_adapter, tests/test_process_pending_gui_tasks.py:test_process_pending_gui_tasks_drag, tests/test_process_pending_gui_tasks.py:test_process_pending_gui_tasks_right_click, tests/test_process_pending_gui_tasks.py:test_redundant_calls_in_process_pending_gui_tasks] - """ + """Processes pending GUI tasks from the queue on the main render thread.""" now = time.time() if hasattr(self, 'event_queue') and hasattr(self.event_queue, 'websocket_server') and self.event_queue.websocket_server: if now - self._last_telemetry_time >= 1.0: @@ -1809,52 +1798,48 @@ class AppController: with self._disc_entries_lock: self.disc_entries[:] = models.parse_history_entries(disc_data.get("history", []), self.disc_roles) # UI state - self.ui_output_dir = self.project.get("output", {}).get("output_dir", "./md_gen") - self.ui_files_base_dir = self.project.get("files", {}).get("base_dir", ".") - self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".") + self.ui_output_dir = self.project.get("output", {}).get("output_dir", "./md_gen") + self.ui_files_base_dir = self.project.get("files", {}).get("base_dir", ".") + self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".") proj_meta = self.project.get("project", {}) - self.ui_project_git_dir = proj_meta.get("git_dir", "") - self.ui_project_conductor_dir = self.project.get('conductor', {}).get('dir', 'conductor') - self.ui_project_system_prompt = proj_meta.get("system_prompt", "") - self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini") + self.ui_project_git_dir = proj_meta.get("git_dir", "") + self.ui_project_conductor_dir = self.project.get('conductor', {}).get('dir', 'conductor') + self.ui_project_system_prompt = proj_meta.get("system_prompt", "") + self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini") self._update_gcli_adapter(self.ui_gemini_cli_path) - self.ui_word_wrap = proj_meta.get("word_wrap", True) - self.ui_auto_add_history = disc_sec.get("auto_add", False) - self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "") - self.ui_base_system_prompt = self.config.get("ai", {}).get("base_system_prompt", "") + self.ui_word_wrap = proj_meta.get("word_wrap", True) + self.ui_auto_add_history = disc_sec.get("auto_add", False) + self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "") + self.ui_base_system_prompt = self.config.get("ai", {}).get("base_system_prompt", "") self.ui_use_default_base_prompt = self.config.get("ai", {}).get("use_default_base_prompt", True) - self.ui_project_context_marker = proj_meta.get("context_marker", "") + self.ui_project_context_marker = proj_meta.get("context_marker", "") - self.preset_manager = presets.PresetManager(Path(self.active_project_path).parent if self.active_project_path else None) - self.presets = self.preset_manager.load_all() + self.preset_manager = presets.PresetManager(Path(self.active_project_path).parent if self.active_project_path else None) + self.presets = self.preset_manager.load_all() self.tool_preset_manager = tool_presets.ToolPresetManager(Path(self.active_project_path).parent if self.active_project_path else None) - self.tool_presets = self.tool_preset_manager.load_all_presets() - self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() + self.tool_presets = self.tool_preset_manager.load_all_presets() + self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() mcp_path = self.project.get('project', {}).get('mcp_config_path') or self.config.get('ai', {}).get('mcp_config_path') if mcp_path: mcp_p = Path(mcp_path) if not mcp_p.is_absolute() and self.active_project_path: mcp_p = Path(self.active_project_path).parent / mcp_path - if mcp_p.exists(): - self.mcp_config = models.load_mcp_config(str(mcp_p)) - else: - self.mcp_config = models.MCPConfiguration() + if mcp_p.exists(): self.mcp_config = models.load_mcp_config(str(mcp_p)) + else: self.mcp_config = models.MCPConfiguration() else: self.mcp_config = models.MCPConfiguration() rag_data = self.config.get('rag') - if rag_data: - self.rag_config = models.RAGConfig.from_dict(rag_data) - else: - self.rag_config = models.RAGConfig() + if rag_data: self.rag_config = models.RAGConfig.from_dict(rag_data) + else: self.rag_config = models.RAGConfig() self.rag_engine = None - if self.rag_config.enabled: - self._sync_rag_engine() + if self.rag_config.enabled: self._sync_rag_engine() from src.personas import PersonaManager self.persona_manager = PersonaManager(Path(self.active_project_path).parent if self.active_project_path else None) + from src.vendor_capabilities import get_capabilities try: caps = get_capabilities(self.current_provider, self.current_model) diff --git a/src/gui_2.py b/src/gui_2.py index c852a24b..e99a3626 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -147,9 +147,9 @@ def C_NUM() -> imgui.ImVec4: return theme.get_color("status_success") def C_TRM() -> imgui.ImVec4: return theme.get_color("text_disabled") def C_SUB() -> imgui.ImVec4: return theme.get_color("text_disabled") -DIR_COLORS: dict[str, typing.Callable[[], imgui.ImVec4]] = {"OUT": C_OUT, "IN": C_IN} +DIR_COLORS: dict[str, typing.Callable[[], imgui.ImVec4]] = {"OUT": C_OUT, "IN": C_IN} KIND_COLORS: dict[str, typing.Callable[[], imgui.ImVec4]] = {"request": C_REQ, "response": C_RES, "tool_call": C_TC, "tool_result": C_TR, "tool_result_send": C_TRS} -HEAVY_KEYS: set[str] = {"message", "text", "script", "output", "content"} +HEAVY_KEYS: set[str] = {"message", "text", "script", "output", "content"} def render_text_viewer(app: App, label: str, content: str, text_type: str = 'text', force_open: bool = False, id_suffix: str = "") -> None: if imgui.button(f"[+]##{id_suffix or str(id(content))}") or force_open: @@ -3579,13 +3579,11 @@ def render_context_composition_panel(app: App) -> None: render_context_screenshots(app) def render_ast_inspector_modal(app: App) -> None: - """ - Renders the 'Structural File Editor' modal dialog. + """Renders the 'Structural File Editor' modal dialog. Provides an interactive tree-sitter AST inspector to mask classes, methods, or functions and extract custom annotated slices (Definition, Signature, Hide). - SSDL Shape: - `[B:open_modal?] -> [Q:outline] -> [I:render_ast_tree]` + SSDL: `[B:open_modal?] -> [Q:outline] -> [I:render_ast_tree]` ASCII Layout Map: +-------------------------------------------------------------+ @@ -3622,14 +3620,14 @@ def render_ast_inspector_modal(app: App) -> None: proj_dir = str(Path(app.controller.active_project_path).parent.resolve()) if getattr(app, 'controller', None) and app.controller.active_project_path else None mcp_client.configure([{"path": f_path}], [proj_dir] if proj_dir else None) - if f_path.lower().endswith('.py'): outline = mcp_client.py_get_code_outline(f_path) - elif f_path.lower().endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(f_path) + if f_path.lower().endswith('.py'): outline = mcp_client.py_get_code_outline(f_path) + elif f_path.lower().endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(f_path) elif f_path.lower().endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(f_path) except Exception as e: outline = f"Error fetching outline: {e}" app._cached_ast_nodes = [] - import re + import re #TODO(Ed): Review local import pattern = re.compile(r'^(\s*)\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)') stack = [] # (indent, name) for line in outline.splitlines(): @@ -3671,74 +3669,83 @@ def render_ast_inspector_modal(app: App) -> None: # --- LEFT COLUMN: AST Tree & Slice Management --- imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 0), True) - if True: - if imgui.collapsing_header("AST Tree", imgui.TreeNodeFlags_.default_open): - if not getattr(app, '_cached_ast_nodes', None): imgui.text("No AST nodes found.") - else: + # if True: + if imgui.collapsing_header("AST Tree", imgui.TreeNodeFlags_.default_open): + if not getattr(app, '_cached_ast_nodes', None): imgui.text("No AST nodes found.") + else: + with imscope.table('ast tree members', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.sizing_stretch_prop) + imgui.table_setup_column("symbol", imgui.TableColumnFlags_.width_fixed, 400) + imgui.table_setup_column("option", imgui.TableColumnFlags_.width_stretch) + imgui.table_next_row() + imgui.table_next_column() for node in app._cached_ast_nodes: + imgui.table_next_row() + imgui.table_next_column() indent = node['indent'] kind = node['kind'] name = node['name'] full_path = node['full_path'] - + imgui.dummy(imgui.ImVec2(indent * 10, 0)) imgui.same_line() imgui.text(f"[{kind}] {name}") - + if imgui.is_item_hovered(): app._hovered_ast_node = full_path - - btn_width = 150 - avail_width = imgui.get_content_region_avail().x - do_align = avail_width > btn_width if isinstance(avail_width, (int, float)) else False - if do_align: imgui.same_line(imgui.get_window_width() - btn_width) - else: imgui.same_line() + imgui.table_next_column() + + # btn_width = 150 + # avail_width = imgui.get_content_region_avail().x + # do_align = avail_width > btn_width if isinstance(avail_width, (int, float)) else False + # if do_align: imgui.same_line((avail_width - btn_width)) + # else: imgui.same_line() + # imgui.same_line((avail_width - btn_width) * 0.925) if not hasattr(f_item, 'ast_mask'): f_item.ast_mask = {} current_mode = f_item.ast_mask.get(full_path, 'hide') - + imgui.push_id(full_path) - if imgui.radio_button("Def", current_mode == 'def'): f_item.ast_mask[full_path] = 'def' + if imgui.radio_button("Def", current_mode == 'def'): f_item.ast_mask[full_path] = 'def' imgui.same_line() - if imgui.radio_button("Sig", current_mode == 'sig'): f_item.ast_mask[full_path] = 'sig' + if imgui.radio_button("Sig", current_mode == 'sig'): f_item.ast_mask[full_path] = 'sig' imgui.same_line() if imgui.radio_button("Hide", current_mode == 'hide'): f_item.ast_mask[full_path] = 'hide' imgui.pop_id() - - imgui.separator() - if imgui.collapsing_header("Custom Slices", imgui.TreeNodeFlags_.default_open): - if not hasattr(f_item, 'custom_slices'): f_item.custom_slices = [] - imgui.text_colored(C_IN(), "Highlight lines in right pane to add slices.") - if imgui.button("Add Selection as Slice"): - if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1: - s_line = min(app._slice_sel_start, app._slice_sel_end) - e_line = max(app._slice_sel_start, app._slice_sel_end) - from src.fuzzy_anchor import FuzzyAnchor - slice_data = FuzzyAnchor.create_slice(app.text_viewer_content, s_line, e_line) - slice_data['tag'] = ""; slice_data['comment'] = "" - f_item.custom_slices.append(slice_data) - app._slice_sel_start = -1; app._slice_sel_end = -1 + + imgui.separator() + if imgui.collapsing_header("Custom Slices", imgui.TreeNodeFlags_.default_open): + if not hasattr(f_item, 'custom_slices'): f_item.custom_slices = [] + imgui.text_colored(C_IN(), "Highlight lines in right pane to add slices.") + if imgui.button("Add Selection as Slice"): + if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1: + s_line = min(app._slice_sel_start, app._slice_sel_end) + e_line = max(app._slice_sel_start, app._slice_sel_end) + from src.fuzzy_anchor import FuzzyAnchor + slice_data = FuzzyAnchor.create_slice(app.text_viewer_content, s_line, e_line) + slice_data['tag'] = ""; slice_data['comment'] = "" + f_item.custom_slices.append(slice_data) + app._slice_sel_start = -1; app._slice_sel_end = -1 + imgui.same_line() + if imgui.button("Clear Selection"): app._slice_sel_start = -1; app._slice_sel_end = -1 + imgui.same_line() + if imgui.button("Auto-Populate"): app._populate_auto_slices(f_item) + + to_remove = -1 + tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"]) + for idx, slc in enumerate(f_item.custom_slices): + imgui.push_id(f"slc_row_{idx}"); imgui.text(f"#{idx+1}: L{slc['start_line']}-{slc['end_line']}"); imgui.same_line() + current_tag = slc.get('tag', '') + if current_tag not in tags and current_tag: tags.append(current_tag) + tag_idx = tags.index(current_tag) if current_tag in tags else 0 + imgui.set_next_item_width(100) + ch_tag, new_tag_idx = imgui.combo("##Tag", tag_idx, tags) + if ch_tag: slc['tag'] = tags[new_tag_idx] + imgui.same_line(); imgui.set_next_item_width(-30); changed_comm, new_comm = imgui.input_text("##Note", slc.get('comment', '')) + if changed_comm: slc['comment'] = new_comm imgui.same_line() - if imgui.button("Clear Selection"): app._slice_sel_start = -1; app._slice_sel_end = -1 - imgui.same_line() - if imgui.button("Auto-Populate"): app._populate_auto_slices(f_item) - - to_remove = -1 - tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"]) - for idx, slc in enumerate(f_item.custom_slices): - imgui.push_id(f"slc_row_{idx}"); imgui.text(f"#{idx+1}: L{slc['start_line']}-{slc['end_line']}"); imgui.same_line() - current_tag = slc.get('tag', '') - if current_tag not in tags and current_tag: tags.append(current_tag) - tag_idx = tags.index(current_tag) if current_tag in tags else 0 - imgui.set_next_item_width(100) - ch_tag, new_tag_idx = imgui.combo("##Tag", tag_idx, tags) - if ch_tag: slc['tag'] = tags[new_tag_idx] - imgui.same_line(); imgui.set_next_item_width(-30); changed_comm, new_comm = imgui.input_text("##Note", slc.get('comment', '')) - if changed_comm: slc['comment'] = new_comm - imgui.same_line() - if imgui.button("X"): to_remove = idx - imgui.pop_id() - if to_remove != -1: f_item.custom_slices.pop(to_remove) + if imgui.button("X"): to_remove = idx + imgui.pop_id() + if to_remove != -1: f_item.custom_slices.pop(to_remove) imgui.end_child() imgui.table_next_column() @@ -6275,11 +6282,9 @@ def render_heavy_text(app: App, label: str, content: str, id_suffix: str = "") - #region: MMA def render_mma_dashboard(app: App) -> None: - """ - Main MMA dashboard interface. + """Main MMA dashboard interface. - SSDL Shape: - `[I:focus_selector] -> [I:track_summary] -> [B:epic_planner] -> [I:track_browser] -> [B:global_controls] -> [I:usage_analytics] -> [I:ticket_queue] -> [I:task_dag] => [I:agent_streams]` + SSDL: `[I:focus_selector] -> [I:track_summary] -> [B:epic_planner] -> [I:track_browser] -> [B:global_controls] -> [I:usage_analytics] -> [I:ticket_queue] -> [I:task_dag] => [I:agent_streams]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6325,13 +6330,11 @@ def render_mma_dashboard(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_mma_dashboard") def render_mma_modals(app: App) -> None: - """ - Renders all MMA-specific approval and info modals: Tool Execution Approval, + """Renders all MMA-specific approval and info modals: Tool Execution Approval, MMA Step Approval (with optional payload edit), MMA Spawn Approval (with prompt/context editing), and the Cycle Detected error modal. - SSDL Shape: - `[I:pending_queues] => [I:tool_approval_modal] | [I:step_approval_modal] | [I:spawn_approval_modal] | [I:cycle_modal]` + SSDL: `[I:pending_queues] => [I:tool_approval_modal] | [I:step_approval_modal] | [I:spawn_approval_modal] | [I:cycle_modal]` ASCII Layout Map: Conditionally opens (at most one at a time): @@ -6426,13 +6429,11 @@ def render_mma_modals(app: App) -> None: imgui.end_popup() def render_mma_track_summary(app: App) -> None: - """ - Renders the active-track summary row: track name, MMA pipeline status, and running + """Renders the active-track summary row: track name, MMA pipeline status, and running cost estimate, followed by a colour-coded progress bar and a 4-column breakdown table (Completed / In Progress / Blocked / Todo) and ETA estimation. - SSDL Shape: - `[I:track_name] -> [I:status_badge] -> [I:cost] -> [I:progress_bar] -> [I:stats_table] -> [I:eta]` + SSDL: `[I:track_name] -> [I:status_badge] -> [I:cost] -> [I:progress_bar] -> [I:stats_table] -> [I:eta]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6470,11 +6471,9 @@ def render_mma_track_summary(app: App) -> None: imgui.text_colored(C_LBL(), "ETA:"); imgui.same_line(); imgui.text_colored(C_VAL(), f"~{int(eta_mins)}m ({remaining} tickets remaining)") def render_mma_epic_planner(app: App) -> None: - """ - Renders the Epic Planning panel for Tier 1 strategy input and planning button. + """Renders the Epic Planning panel for Tier 1 strategy input and planning button. - SSDL Shape: - `[I:input_textbox] => [B:plan_button]` + SSDL: `[I:input_textbox] => [B:plan_button]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6482,7 +6481,7 @@ def render_mma_epic_planner(app: App) -> None: | +-----------------------------------------------------+ | | | Describe the epic goal... | | | +-----------------------------------------------------+ | - | [Plan Epic (Tier 1) ] | + | [Plan Epic (Tier 1) ] | +---------------------------------------------------------+ """ imgui.text_colored(C_LBL(), 'Epic Planning (Tier 1)') @@ -6490,12 +6489,10 @@ def render_mma_epic_planner(app: App) -> None: if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)): app._cb_plan_epic() def render_mma_conductor_setup(app: App) -> None: - """ - Renders the Conductor Setup panel. Runs a one-shot project-structure scan and + """Renders the Conductor Setup panel. Runs a one-shot project-structure scan and shows the summarised output in a read-only text area. - SSDL Shape: - `[B:run_scan] => [I:summary_text]` + SSDL: `[B:run_scan] => [I:summary_text]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6511,22 +6508,20 @@ def render_mma_conductor_setup(app: App) -> None: if app.ui_conductor_setup_summary: imgui.input_text_multiline("##setup_summary", app.ui_conductor_setup_summary, imgui.ImVec2(-1, 120), imgui.InputTextFlags_.read_only) def render_mma_track_browser(app: App) -> None: - """ - Renders the Track Browser: a table listing all known tracks with status, progress bar, + """Renders the Track Browser: a table listing all known tracks with status, progress bar, and a [Load] button per row. Below the table is a form for creating a new track. - SSDL Shape: - `[I:tracks_table] -> [B:load_button] -> [I:create_track_form] => [B:create_track]` + SSDL: `[I:tracks_table] -> [B:load_button] -> [I:create_track_form] => [B:create_track]` ASCII Layout Map: +---------------------------------------------------------+ | Track Browser | - | +------------------+--------+----------+---------+ | - | | Title | Status | Progress | Actions | | - | +------------------+--------+----------+---------+ | - | | my-feature-0612 | ACTIVE | [===== ] [Load] | | + | +------------------+--------+----------+---------+ | + | | Title | Status | Progress | Actions | | + | +------------------+--------+----------+---------+ | + | | my-feature-0612 | ACTIVE | [===== ] [Load] | | | | chore-cleanup | NEW | [ ] [Load] | | - | +------------------+--------+----------+---------+ | + | +------------------+--------+----------+---------+ | | Create New Track | | Name: [___________] | | Description: [_____________________________] | @@ -6560,13 +6555,11 @@ def render_mma_track_browser(app: App) -> None: app.ui_new_track_name = ""; app.ui_new_track_desc = "" def render_mma_global_controls(app: App) -> None: - """ - Renders the MMA global controls bar: Step Mode (HITL) toggle, pipeline status label, + """Renders the MMA global controls bar: Step Mode (HITL) toggle, pipeline status label, Pause/Resume engine button, active tier badge, approval-pending blink indicator, and the Hot Reload shortcut. - SSDL Shape: - `[B:step_mode] -> [I:status] -> [B:pause_resume] -> [I:active_tier] -> [I:approval_blink] -> [B:hot_reload]` + SSDL: `[B:step_mode] -> [I:status] -> [B:pause_resume] -> [I:active_tier] -> [I:approval_blink] -> [B:hot_reload]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6604,25 +6597,23 @@ def render_mma_global_controls(app: App) -> None: imgui.same_line(); imgui.text_disabled("(Ctrl+Alt+R)") def render_mma_usage_section(app: App) -> None: - """ - Renders the Tier Usage table (tokens and estimated cost per tier) and, when expanded, + """Renders the Tier Usage table (tokens and estimated cost per tier) and, when expanded, a collapsible Tier Model Config section with per-tier provider, model, tool-preset, and persona overrides. - SSDL Shape: - `[I:usage_table] -> [B:tier_model_config_header] => [I:combos_per_tier]` + SSDL: `[I:usage_table] -> [B:tier_model_config_header] => [I:combos_per_tier]` ASCII Layout Map: +---------------------------------------------------------+ | Tier Usage (Tokens & Cost) | - | +--------+------------------+-------+-------+--------+ | - | | Tier | Model | Input | Output | Cost | | - | +--------+------------------+-------+-------+--------+ | - | | Tier 1 | gemini-2.5-flash | 1,200 | 400 | $0.001 | | - | | TOTAL | | | | $0.004 | | - | +--------+------------------+-------+-------+--------+ | + | +--------+------------------+-------+-------+--------+ | + | | Tier | Model | Input | Output | Cost | | + | +--------+------------------+-------+-------+--------+ | + | | Tier 1 | gemini-2.5-flash | 1,200 | 400 | $0.001 | | + | | TOTAL | | | | $0.004 | | + | +--------+------------------+-------+-------+--------+ | | > Tier Model Config (collapsible) | - | Tier 1: [gemini v] [gemini-2.5-flash v] [None v] | + | Tier 1: [gemini v] [gemini-2.5-flash v] [None v] | +---------------------------------------------------------+ """ imgui.text("Tier Usage (Tokens & Cost)") @@ -6680,7 +6671,7 @@ def render_mma_ticket_editor(app: App) -> None: | Editing: ticket-042 | | Status: in_progress | | Priority: [medium v] | - | Target: src/gui_2.py | + | Target: src/gui_2.py | | Depends on: ticket-041, ticket-040 | | Persona Override: [None v] | | [Mark Complete] [Delete] | @@ -6738,13 +6729,11 @@ def render_mma_agent_streams(app: App) -> None: imgui.end_tab_bar() def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> None: - """ - Renders a single tier's live streaming text panel. For Tier 1, 2, and 4 a single scrollable + """Renders a single tier's live streaming text panel. For Tier 1, 2, and 4 a single scrollable selectable text area is shown; for Tier 3 (stream_key=None), each worker ticket gets its own labelled, colour-coded sub-child with an auto-scroll behaviour. - SSDL Shape: - `[I:stream_text] => [I:single_scroll] | [I:worker_sub_children]` + SSDL: `[I:stream_text] => [I:single_scroll] | [I:worker_sub_children]` ASCII Layout Map (single-stream, e.g. Tier 1): +---------------------------------------------------------+ @@ -6813,13 +6802,11 @@ def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tier_stream_panel") def render_track_proposal_modal(app: App) -> None: - """ - Renders the Track Proposal modal. Shows Tier 1's generated implementation tracks + """Renders the Track Proposal modal. Shows Tier 1's generated implementation tracks with editable title and goal fields, and buttons to remove, start individual tracks, accept all, or cancel. - SSDL Shape: - `[I:proposed_tracks] -> [B:edit_title/goal] -> [B:remove/start] => [B:accept_all | B:cancel]` + SSDL: `[I:proposed_tracks] -> [B:edit_title/goal] -> [B:remove/start] => [B:accept_all | B:cancel]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6880,13 +6867,11 @@ def render_track_proposal_modal(app: App) -> None: imgui.end_popup() def render_ticket_queue(app: App) -> None: - """ - Renders the Ticket Queue Management panel. Provides select-all/none controls, + """Renders the Ticket Queue Management panel. Provides select-all/none controls, bulk action buttons, a 7-column scrollable table of tickets with drag-to-reorder, priority/model combos, status labels, and per-row Kill/Block/Unblock actions. - SSDL Shape: - `[B:select_all/none] -> [B:bulk_actions] -> [I:ticket_table] => [I:ticket_editor?]` + SSDL: `[B:select_all/none] -> [B:bulk_actions] -> [I:ticket_table] => [I:ticket_editor?]` ASCII Layout Map: +---------------------------------------------------------+ @@ -7013,12 +6998,10 @@ def render_ticket_queue(app: App) -> None: imgui.end_table() def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer - """ - Renders the interactive node editor DAG visualizer, mapping ticket relationships + """Renders the interactive node editor DAG visualizer, mapping ticket relationships and allowing creation or deletion of links. - SSDL Shape: - `[I:task_nodes] -> [B:node_editor_canvas] => [I:link_connections]` + SSDL: `[I:task_nodes] -> [B:node_editor_canvas] => [I:link_connections]` ASCII Layout Map: +---------------------------------------------------------+ @@ -7089,8 +7072,8 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer tid = str(t.get('id', '')) if abs(hash(tid + "_out")) == s_id: source_tid = tid if abs(hash(tid + "_out")) == e_id: source_tid = tid - if abs(hash(tid + "_in")) == s_id: target_tid = tid - if abs(hash(tid + "_in")) == e_id: target_tid = tid + if abs(hash(tid + "_in")) == s_id: target_tid = tid + if abs(hash(tid + "_in")) == e_id: target_tid = tid if source_tid and target_tid and source_tid != target_tid: for t in app.active_tickets: if str(t.get('id', '')) == target_tid: @@ -7136,17 +7119,17 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer if tid.startswith('T-'): try: max_id = max(max_id, int(tid[2:])) except: pass - app.ui_new_ticket_id = f"T-{max_id + 1:03d}" - app.ui_new_ticket_desc = "" + app.ui_new_ticket_id = f"T-{max_id + 1:03d}" + app.ui_new_ticket_desc = "" app.ui_new_ticket_target = "" - app.ui_new_ticket_deps = "" + app.ui_new_ticket_deps = "" if app._show_add_ticket_form: imgui.begin_child("add_ticket_form", imgui.ImVec2(-1, 220), True) imgui.text_colored(C_VAL(), "New Ticket Details") - _, app.ui_new_ticket_id = imgui.input_text("ID##new_ticket", app.ui_new_ticket_id) - _, app.ui_new_ticket_desc = imgui.input_text_multiline("Description##new_ticket", app.ui_new_ticket_desc, imgui.ImVec2(-1, 60)) + _, app.ui_new_ticket_id = imgui.input_text("ID##new_ticket", app.ui_new_ticket_id) + _, app.ui_new_ticket_desc = imgui.input_text_multiline("Description##new_ticket", app.ui_new_ticket_desc, imgui.ImVec2(-1, 60)) _, app.ui_new_ticket_target = imgui.input_text("Target File##new_ticket", app.ui_new_ticket_target) - _, app.ui_new_ticket_deps = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", app.ui_new_ticket_deps) + _, app.ui_new_ticket_deps = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", app.ui_new_ticket_deps) imgui.text("Priority:") imgui.same_line() if imgui.begin_combo("##new_prio", app.ui_new_ticket_priority): diff --git a/src/hot_reloader.py b/src/hot_reloader.py index 9e5a2aa5..c1f235b1 100644 --- a/src/hot_reloader.py +++ b/src/hot_reloader.py @@ -43,7 +43,7 @@ class HotReloader: cls.is_error_state = True return False - hm = cls.HOT_MODULES[module_name] + hm = cls.HOT_MODULES[module_name] state = cls.capture_state(app, hm.state_keys) try: