Private
Public Access
0
0

Antigravity is dog shit.

This commit is contained in:
2026-05-20 07:51:58 -04:00
parent 180dc167d2
commit e2305ff49a
15 changed files with 123 additions and 50 deletions
-4
View File
@@ -54,10 +54,6 @@ This file tracks all major tracks for the project. Each track has its own detail
*Link: [./tracks/context_preview_fixes_20260516/](./tracks/context_preview_fixes_20260516/)*
*Goal: Fix Preview button generating empty content, and Inspect/Slices buttons failing to open their respective editor panels.*
15. [ ] **Track: GUI Architecture Refinement & AI-Friendliness**
*Link: [./tracks/gui_architecture_refinement_20260512/](./tracks/gui_architecture_refiinement_20260512/)*
*Goal: Reduce nesting and compactness of ImGui code in `gui_2.py`, and formalize ImGui Defer patterns.*
13. [x] **Track: GUI Refactor & Stabilization**
*Link: [./tracks/gui_refactor_stabilization_20260512/](./tracks/gui_refactor_stabilization_20260512/)*
*Goal: Refactor gui_2.py to fix regressions and enforce better imgui scoping patterns.*
+6 -2
View File
@@ -229,8 +229,10 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
if ast_mask:
mask_sections = []
from src import mcp_client
for symbol, mode in ast_mask.items():
for symbol_raw, mode in ast_mask.items():
if mode == "hide": continue
import re
symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw)
res = ""
if suffix_lower == ".py":
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
@@ -429,9 +431,11 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
if ast_mask and not item.get("error"):
mask_sections = []
from src import mcp_client
for symbol, mode in ast_mask.items():
for symbol_raw, mode in ast_mask.items():
if mode == "hide":
continue
import re
symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw)
res = ""
if path.suffix == ".py":
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
+27 -6
View File
@@ -67,6 +67,7 @@ def _set_app_attr(app: Any, name: str, value: Any) -> None:
setattr(app, name, value)
class HookServerInstance(ThreadingHTTPServer):
allow_reuse_address = True
"""Custom HTTPServer that carries a reference to the main App instance."""
def __init__(self, server_address: tuple[str, int], RequestHandlerClass: type, app: Any) -> None:
"""
@@ -85,6 +86,9 @@ def _serialize_for_api(obj: Any) -> Any:
return [_serialize_for_api(x) for x in obj]
if isinstance(obj, dict):
return {k: _serialize_for_api(v) for k, v in obj.items()}
from pathlib import PurePath
if isinstance(obj, PurePath):
return str(obj)
return obj
class HookHandler(BaseHTTPRequestHandler):
@@ -272,6 +276,13 @@ class HookHandler(BaseHTTPRequestHandler):
files = _get_app_attr(app, "files", [])
screenshots = _get_app_attr(app, "screenshots", [])
self.wfile.write(json.dumps({"files": _serialize_for_api(files), "screenshots": _serialize_for_api(screenshots)}).encode("utf-8"))
elif self.path == "/api/v1/context":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
from src.app_controller import _api_get_context
ctx_data = _api_get_context(app.controller)
self.wfile.write(json.dumps(_serialize_for_api(ctx_data)).encode("utf-8"))
elif self.path == "/api/metrics/financial":
self.send_response(200)
self.send_header("Content-Type", "application/json")
@@ -765,12 +776,22 @@ class WebSocketServer:
asyncio.set_event_loop(self.loop)
self._stop_event = asyncio.Event()
async def main():
"""
[C: simulation/live_walkthrough.py:module, simulation/ping_pong.py:module, src/gui_2.py:module, tests/mock_concurrent_mma.py:module, tests/mock_gemini_cli.py:module, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_allow_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_deny_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_unreachable_hook_server, tests/test_cli_tool_bridge.py:module, tests/test_cli_tool_bridge_mapping.py:TestCliToolBridgeMapping.test_mapping_from_api_format, tests/test_cli_tool_bridge_mapping.py:module, tests/test_discussion_takes.py:module, tests/test_external_editor_gui.py:module, tests/test_headless_service.py:TestHeadlessStartup.test_headless_flag_triggers_run, tests/test_headless_service.py:TestHeadlessStartup.test_normal_startup_calls_app_run, tests/test_mma_skeleton.py:module, tests/test_orchestrator_pm.py:module, tests/test_orchestrator_pm_history.py:module, tests/test_presets.py:module, tests/test_project_serialization.py:module, tests/test_run_worker_lifecycle_abort.py:module, tests/test_symbol_lookup.py:module, tests/test_system_prompt_exposure.py:module, tests/test_theme_nerv_fx.py:module]
"""
async with serve(self._handler, "127.0.0.1", self.port) as server:
self.server = server
await self._stop_event.wait()
max_retries = 10
current_port = self.port
for attempt in range(max_retries):
try:
async with serve(self._handler, "127.0.0.1", current_port) as server:
self.port = current_port
self.server = server
logging.info(f"WebSocketServer successfully bound to port {self.port}")
await self._stop_event.wait()
break
except OSError as e:
if attempt == max_retries - 1:
logging.error(f"WebSocketServer failed to bind after {max_retries} attempts: {e}")
raise
logging.warning(f"WebSocketServer port {current_port} in use, retrying on {current_port + 1}...")
current_port += 1
self.loop.run_until_complete(main())
def start(self) -> None:
+10
View File
@@ -886,6 +886,8 @@ class AppController:
self.project_paths: List[str] = []
self.active_discussion: str = "main"
self.disc_entries: List[Dict[str, Any]] = []
self.discussion_sent_markdown: str = ""
self.discussion_sent_system_prompt: str = ""
self.disc_roles: List[str] = []
self.tracks: List[Dict[str, Any]] = []
self.active_track: Optional[models.Track] = None
@@ -2216,6 +2218,8 @@ class AppController:
ai_client.set_use_default_base_prompt(self.ui_use_default_base_prompt)
ai_client.set_project_context_marker(self.ui_project_context_marker)
self.last_resolved_system_prompt = ai_client.get_combined_system_prompt()
self.discussion_sent_markdown = event.stable_md
self.discussion_sent_system_prompt = self.last_resolved_system_prompt
ai_client.set_model_params(self.temperature, self.max_tokens, self.history_trunc_limit, self.top_p)
ai_client.set_agent_tools(self.ui_agent_tools) # Force update adapter path right before send to bypass potential duplication issues
self._update_gcli_adapter(self.ui_gemini_cli_path)
@@ -3012,6 +3016,8 @@ class AppController:
disc_data = discussions[name]
with self._disc_entries_lock:
self.disc_entries[:] = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
self.discussion_sent_markdown = disc_data.get("sent_markdown", "")
self.discussion_sent_system_prompt = disc_data.get("sent_system_prompt", "")
if "context_snapshot" in disc_data:
snapshot_data = disc_data["context_snapshot"]
self.context_files = [models.FileItem.from_dict(f) if isinstance(f, dict) else models.FileItem(path=str(f)) for f in snapshot_data]
@@ -3031,6 +3037,8 @@ class AppController:
disc_data["history"] = history_strings
disc_data["last_updated"] = project_manager.now_ts()
disc_data["context_snapshot"] = [f.to_dict() if hasattr(f, "to_dict") else {"path": str(f)} for f in self.context_files]
disc_data["sent_markdown"] = getattr(self, "discussion_sent_markdown", "")
disc_data["sent_system_prompt"] = getattr(self, "discussion_sent_system_prompt", "")
def _create_discussion(self, name: str) -> None:
"""
@@ -3182,6 +3190,8 @@ class AppController:
self._tool_stats.clear()
self._comms_log.clear()
self.disc_entries.clear()
self.discussion_sent_markdown = ""
self.discussion_sent_system_prompt = ""
self.files.clear()
self.context_files.clear()
self.tracks.clear()
+14 -19
View File
@@ -205,19 +205,11 @@ class ASTParser:
"""
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
"""
if node.type in ("function_definition", "method_definition", "template_declaration"):
# If template, look for inner function/method
target_node = node
if node.type == "template_declaration":
for child in node.children:
if child.type in ("function_definition", "method_definition"):
target_node = child
break
body = target_node.child_by_field_name("body")
if node.type in ("function_definition", "method_definition"):
body = node.child_by_field_name("body")
if not body:
# C++ fallback: sometimes the body is just a compound_statement without a field name in certain contexts
for child in target_node.children:
for child in node.children:
if child.type in ("compound_statement", "block"):
body = child
break
@@ -232,7 +224,7 @@ class ASTParser:
initializer = None
if self.language_name in ("cpp", "c"):
for child in target_node.children:
for child in node.children:
if child.type == "field_initializer_list":
initializer = child
break
@@ -248,18 +240,19 @@ class ASTParser:
else:
edits.append((start_byte, end_byte, f"\n{indent}..."))
else:
# If there's an initializer list (C++), we strip it too or start from it
start_byte = initializer.start_byte if initializer else body.start_byte
end_byte = body.end_byte
# Try to preserve braces for C-style languages
if body.type == "compound_statement" and len(body.children) >= 2:
if body.children[0].type == "{" and body.children[-1].type == "}":
if body.type == "compound_statement" and len(body.children) >= 2 and body.children[0].type == "{" and body.children[-1].type == "}":
if initializer:
start_byte = initializer.start_byte
end_byte = body.children[-1].start_byte
edits.append((start_byte, end_byte, "{ ... "))
else:
start_byte = body.children[0].end_byte
end_byte = body.children[-1].start_byte
edits.append((start_byte, end_byte, " ... "))
else:
edits.append((start_byte, end_byte, "..."))
else:
edits.append((start_byte, end_byte, "..."))
@@ -754,9 +747,11 @@ class ASTParser:
ntype = node.type
label = ""
if ntype in ("class_definition", "class_specifier"):
label = "[Class]"
has_body = node.child_by_field_name("body") is not None
label = "[Class]" if has_body else "[ClassDecl]"
elif ntype == "struct_specifier":
label = "[Struct]"
has_body = node.child_by_field_name("body") is not None
label = "[Struct]" if has_body else "[StructDecl]"
elif ntype == "function_definition":
label = "[Method]" if indent > 0 else "[Func]"
+49 -3
View File
@@ -190,6 +190,7 @@ class App:
self.show_windows.setdefault('Undo/Redo History', False)
# --- Preset & Profile Management State ---
self.context_preview_text = ""
self.ui_separate_context_preview = False
self.ui_active_context_preset = ""
self.ui_new_context_preset_name = ""
self._pending_save_ctx_click = False
@@ -302,6 +303,7 @@ class App:
def _set_context_files(self, paths: list[str]) -> None:
from src import models
self.context_files = [models.FileItem(path=p) for p in paths]
self.controller.context_files = self.context_files
def _simulate_save_preset(self, name: str) -> None:
from src import models
@@ -551,6 +553,7 @@ class App:
def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
ini = imgui.save_ini_settings_to_memory()
panel_states = {
"ui_separate_context_preview": getattr(self, "ui_separate_context_preview", False),
"ui_separate_message_panel": getattr(self, "ui_separate_message_panel", False),
"ui_separate_response_panel": getattr(self, "ui_separate_response_panel", False),
"ui_separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
@@ -3026,9 +3029,13 @@ def render_ast_inspector_modal(app: App) -> None:
if imgui.is_item_hovered():
app._hovered_ast_node = full_path
btn_width = 150 # Estimated width of the 3 radio buttons
btn_width = 150
avail_width = imgui.get_content_region_avail().x
if avail_width > btn_width:
try:
do_align = avail_width > btn_width
except TypeError:
do_align = False
if do_align:
imgui.same_line(imgui.get_window_width() - btn_width)
else:
imgui.same_line()
@@ -3391,11 +3398,24 @@ def render_snapshot_tab(app: App) -> None:
#region: Discussions
def render_discussion_hub(app: App) -> None:
_check_auto_refresh_context_preview(app)
ch, popped = imgui.checkbox("Pop Out Context Preview", getattr(app, "ui_separate_context_preview", False))
if ch:
app.ui_separate_context_preview = popped
app.show_windows["Context Preview"] = popped
with imscope.tab_bar("discussion_hub_tabs"):
with imscope.tab_item("Discussion") as (exp, opened):
if exp: render_discussion_tab(app)
with imscope.tab_item("Context Composition") as (exp, opened):
if exp: render_context_composition_panel(app)
if not getattr(app, "ui_separate_context_preview", False):
with imscope.tab_item("Context Preview") as (exp, opened):
if exp:
if imgui.button("Copy to Clipboard"):
imgui.set_clipboard_text(app.context_preview_text)
imgui.begin_child("ctx_preview_scroll_tab", imgui.ImVec2(0, 0), True)
markdown_helper.render(app.context_preview_text, context_id="ctx_preview_tab")
imgui.end_child()
with imscope.tab_item("Snapshot") as (exp, opened):
if exp: render_snapshot_tab(app)
with imscope.tab_item("Takes") as (exp, opened):
@@ -5446,16 +5466,42 @@ def render_context_modals(app: App) -> None:
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'
sel = f.selected if hasattr(f, 'selected') 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, sel, 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
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."
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)
imgui.begin_child("ctx_preview_scroll", imgui.ImVec2(0, 0), True)
markdown_helper.render(app.context_preview_text, context_id="ctx_preview")
imgui.end_child()
+2 -2
View File
@@ -73,9 +73,9 @@ void myTemplateFunc(T x) {
"""
skeleton = parser.get_skeleton(code)
assert 'class MyClass {' in skeleton
assert 'void myMethod() ...' in skeleton
assert 'void myMethod() { ... }' in skeleton
assert 'template <typename T>' in skeleton
assert 'void myTemplateFunc(T x) ...' in skeleton
assert 'void myTemplateFunc(T x) { ... }' in skeleton
assert 'int x = 1;' not in skeleton
assert 'x.doSomething();' not in skeleton
+2 -2
View File
@@ -90,12 +90,12 @@ def test_app_controller_save_load(tmp_path, monkeypatch):
controller._save_active_project.assert_called_once()
# Change state
controller.ui_file_paths = []
controller.context_files = []
controller.screenshots = []
# Load preset
loaded = controller.load_context_preset("saved_preset")
assert loaded.name == "saved_preset"
assert controller.ui_file_paths == ["app.py"]
assert [f.path for f in controller.context_files] == ["app.py"]
assert controller.screenshots == ["app.png"]
controller._save_active_project.assert_called()
+1 -1
View File
@@ -5,7 +5,7 @@ def test_context_file_entry_serialization():
p = ContextFileEntry(path="test.py", view_mode="skeleton")
d = p.to_dict()
# Check for default custom_slices
assert d == {"path": "test.py", "view_mode": "skeleton", "custom_slices": []}
assert d == {"path": "test.py", "view_mode": "skeleton", "custom_slices": [], "ast_definitions": False, "ast_mask": {}, "ast_signatures": False}
p2 = ContextFileEntry.from_dict(d)
assert p2.path == "test.py"
+3 -3
View File
@@ -12,9 +12,9 @@ def test_context_preview_and_ast_inspector(live_gui):
# Set context file
resp = requests.post("http://127.0.0.1:8999/api/gui", json={
"action": "set_value",
"item": "files",
"value": ["src/aggregate.py"]
"action": "custom_callback",
"callback": "set_context_files",
"args": [["src/aggregate.py"]]
})
assert resp.status_code == 200
time.sleep(1)
+1
View File
@@ -35,6 +35,7 @@ def test_phase4_final_verify(live_gui):
client.set_value('current_provider', 'gemini_cli')
client.set_value('gcli_path', os.path.abspath(os.path.join(os.path.dirname(__file__), "mock_gcli.bat")))
time.sleep(1.5)
# Wait for settings to apply and engine to sync
success = False
for _ in range(100):
+2 -2
View File
@@ -33,7 +33,7 @@ def test_rag_large_codebase_verification_sim(live_gui):
client.set_value('rag_source', 'chroma')
client.set_value('rag_emb_provider', 'local')
client.set_value('auto_add_history', True)
time.sleep(1.5)
# Wait for settings to apply and engine to sync (initial indexing happens automatically)
print("[SIM] Waiting for automatic initial indexing...")
start_initial = time.time()
@@ -93,7 +93,7 @@ def test_rag_large_codebase_verification_sim(live_gui):
break
time.sleep(0.2)
client.set_value('ai_input', "What is the modified content?")
client.set_value('ai_input', "Search for MODIFIED CONTENT FOR FILE 25")
client.click('btn_gen_send')
# Wait for completion
+2 -2
View File
@@ -34,8 +34,8 @@ struct Point {
try:
skeleton = ts_c_get_skeleton(str(c_file))
assert "void hello() ..." in skeleton
assert "int add(int a, int b) ..." in skeleton
assert "void hello() { ... }" in skeleton
assert "int add(int a, int b) { ... }" in skeleton
assert "struct Point" in skeleton
assert "printf" not in skeleton
finally:
+3 -3
View File
@@ -37,9 +37,9 @@ void globalFunc() {
try:
skeleton = ts_cpp_get_skeleton(str(cpp_file))
assert "class Box" in skeleton
assert "Box(T val) ..." in skeleton
assert "T getValue() ..." in skeleton
assert "void globalFunc() ..." in skeleton
assert "Box(T val) { ... }" in skeleton
assert "T getValue() { ... }" in skeleton
assert "void globalFunc() { ... }" in skeleton
assert "std::cout" not in skeleton
finally:
mcp_client._resolve_and_check = original_resolve
+1 -1
View File
@@ -18,7 +18,7 @@ async def test_websocket_subscription_and_broadcast():
await asyncio.sleep(0.5)
try:
uri = f"ws://127.0.0.1:{port}"
uri = f"ws://127.0.0.1:{server.port}"
async with websockets.connect(uri) as websocket:
# Subscribe to events channel
subscribe_msg = {"action": "subscribe", "channel": "events"}