Private
Public Access
0
0

Progress on context composition

This commit is contained in:
2026-05-17 06:43:19 -04:00
parent 4642a337ba
commit 22f3b9f33a
8 changed files with 225 additions and 13 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ This file tracks all major tracks for the project. Each track has its own detail
*Link: [./tracks/context_comp_slices_20260510/](./tracks/context_comp_slices_20260510/)*
*Goal: Enhance slice visualization with visual editor, annotation support (tags/comments), and view presets.*
14. [ ] **Track: Context Preview & Slice Editor Fixes**
14. [~] **Track: Context Preview & Slice Editor Fixes**
*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.*
+35
View File
@@ -0,0 +1,35 @@
import sys
import os
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.resolve()))
import asyncio
from simulation.sim_base import run_sim
from simulation.sim_tools import click_tab, click_button_by_label
from src.gui_2 import App
from src.models import FileItem
async def verify_preview(app: App):
# Let the GUI render once
await asyncio.sleep(0.1)
app.context_files = [FileItem(path='src/aggregate.py', auto_aggregate=True)]
app.controller.context_files = app.context_files
# Try rendering _render_files_and_media
app.show_windows["Context Preview"] = True
await asyncio.sleep(0.1)
click_tab(app, "Context Composition")
await asyncio.sleep(0.1)
click_button_by_label(app, "Preview##ctx")
await asyncio.sleep(0.2)
print("--- PREVIEW TEXT ---")
print(repr(app.context_preview_text[:200]))
print("--------------------")
if __name__ == "__main__":
os.environ['SLOP_TEST_HOOKS'] = '1'
run_sim(verify_preview)
+81
View File
@@ -0,0 +1,81 @@
import sys
import os
from pathlib import Path
# Fix sys.path
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir)
src_dir = os.path.join(project_root, "src")
for d in [project_root, src_dir]:
if d not in sys.path:
sys.path.insert(0, d)
# Ensure thirdparty is in sys.path
thirdparty = os.path.join(project_root, "thirdparty")
if thirdparty not in sys.path:
sys.path.insert(0, thirdparty)
import asyncio
from simulation.sim_base import BaseSimulation, run_sim
from src.gui_2 import App
from src.models import FileItem
class VerifyTrackFixesSim(BaseSimulation):
async def run(self):
app = self.app
print("[Sim] Starting verification of track fixes...")
# 1. Setup a test file in context
test_file = os.path.abspath("src/aggregate.py")
f_item = FileItem(path=test_file)
app.context_files = [f_item]
app.controller.context_files = app.context_files
await asyncio.sleep(0.5)
# 2. Verify AST Inspector
print("[Sim] Testing AST Inspector...")
app.ui_inspecting_ast_file = f_item
app._show_ast_inspector = True
await asyncio.sleep(0.5)
# Check if nodes were cached
if app._cached_ast_nodes:
print(f"[Sim] SUCCESS: AST nodes cached ({len(app._cached_ast_nodes)} nodes)")
else:
print("[Sim] FAILURE: No AST nodes cached")
# 3. Verify Slice Editor
print("[Sim] Testing Slice Editor...")
app.ui_editing_slices_file = f_item
app.text_viewer_title = f"Slices: {test_file}"
from src import mcp_client
app.text_viewer_content = mcp_client.read_file(test_file)
app.text_viewer_type = "python"
app.show_text_viewer = True
app.show_windows["Text Viewer"] = True
await asyncio.sleep(0.5)
# Test Auto-Populate button
print("[Sim] Testing Auto-Populate AST Slices...")
app._populate_auto_slices(f_item)
if f_item.custom_slices:
print(f"[Sim] SUCCESS: Auto-slices populated ({len(f_item.custom_slices)} slices)")
else:
print("[Sim] FAILURE: No auto-slices populated")
# 4. Verify Context Preview
print("[Sim] Testing Context Preview...")
app.context_preview_text = app.controller._do_generate()[0]
if "## Files" in app.context_preview_text and "aggregate.py" in app.context_preview_text:
print("[Sim] SUCCESS: Context preview generated with content")
else:
print(f"[Sim] FAILURE: Context preview text length: {len(app.context_preview_text)}")
print("[Sim] Verification finished.")
self.stop()
if __name__ == "__main__":
os.environ['SLOP_TEST_HOOKS'] = '1'
run_sim(VerifyTrackFixesSim)
+8
View File
@@ -205,12 +205,20 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
if path.suffix == ".py":
if not parser: parser = ASTParser("python")
content = parser.get_skeleton(content, path=str(path))
elif path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
from src import mcp_client
if path.suffix in ['.c', '.h']: content = mcp_client.ts_c_get_skeleton(str(path))
else: content = mcp_client.ts_cpp_get_skeleton(str(path))
else:
content = summarize.summarise_file(path, content)
elif view_mode == "outline":
if path.suffix == ".py":
if not parser: parser = ASTParser("python")
content = parser.get_code_outline(content, path=str(path))
elif path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
from src import mcp_client
if path.suffix in ['.c', '.h']: content = mcp_client.ts_c_get_code_outline(str(path))
else: content = mcp_client.ts_cpp_get_code_outline(str(path))
else:
content = summarize.summarise_file(path, content)
elif view_mode == "none":
+1
View File
@@ -955,6 +955,7 @@ class AppController:
self.ui_new_track_name: str = ""
self.ui_new_track_desc: str = ""
self.ui_new_track_type: str = "feature"
self.ui_project_conductor_dir: str = ""
self.ui_conductor_setup_summary: str = ""
self.ui_last_script_text: str = ""
self.ui_last_script_output: str = ""
+62 -12
View File
@@ -18,6 +18,13 @@ import time
import tomli_w
import typing
from contextlib import ExitStack, nullcontext
# Ensure thirdparty is in sys.path for defer
_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_thirdparty = os.path.join(_project_root, "thirdparty")
if _thirdparty not in sys.path:
sys.path.insert(0, _thirdparty)
from defer import defer
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
from pathlib import Path
@@ -923,8 +930,21 @@ class App:
"""
from src import mcp_client
import re
mcp_client.configure([{"path": f_item.path}])
outline = mcp_client.py_get_code_outline(f_item.path)
from pathlib import Path
import os
proj_dir = str(Path(self.controller.active_project_path).parent.resolve()) if getattr(self, 'controller', None) and self.controller.active_project_path else None
abs_path = f_item.path if os.path.isabs(f_item.path) else os.path.join(proj_dir or '.', f_item.path)
mcp_client.configure([{"path": abs_path}], [proj_dir] if proj_dir else None)
f_path_lower = f_item.path.lower()
try:
if f_path_lower.endswith('.py'): outline = mcp_client.py_get_code_outline(abs_path)
elif f_path_lower.endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(abs_path)
elif f_path_lower.endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(abs_path)
else: return
except Exception:
return
if outline.startswith("ERROR") or outline.startswith("ACCESS DENIED"):
return
pattern = re.compile(r'^\s*\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)', re.MULTILINE)
@@ -2841,8 +2861,13 @@ def render_context_batch_actions(app: App, total_lines: int, total_ast: int) ->
if not app.context_files:
app.context_preview_text = "# Context Composition Empty\n\nNo files have been added to the context composition yet."
else:
app.controller.context_files = app.context_files
app.context_preview_text = app.controller._do_generate()[0]
try:
app.controller.context_files = app.context_files
app.context_preview_text = app.controller._do_generate()[0]
except Exception as e:
import traceback
err = traceback.format_exc()
app.context_preview_text = f"# Error generating preview\n\n```python\n{err}\n```"
app.show_windows["Context Preview"] = True
imgui.same_line()
imgui.text(f" | Total: {len(app.context_files)} files, {total_lines} lines, {total_ast} AST elements")
@@ -2911,7 +2936,8 @@ def render_ast_inspector_modal(app: App) -> None:
app._show_ast_inspector = False
#region: AST Inspector
expanded, opened = imgui.begin_popup_modal('AST Inspector', True, imgui.WindowFlags_.always_auto_resize)
imgui.set_next_window_size(imgui.ImVec2(1200, 800), imgui.Cond_.first_use_ever)
expanded, opened = imgui.begin_popup_modal('AST Inspector', True, imgui.WindowFlags_.none)
if opened:
if expanded:
if app.ui_inspecting_ast_file is None:
@@ -2923,6 +2949,11 @@ def render_ast_inspector_modal(app: App) -> None:
if f_path != app._cached_ast_file_path:
outline = ""
try:
from src import mcp_client
from pathlib import Path
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)
else: outline = mcp_client.ts_cpp_get_code_outline(f_path)
@@ -2959,12 +2990,14 @@ def render_ast_inspector_modal(app: App) -> None:
imgui.text(f"Inspecting AST: {f_path}")
imgui.separator()
avail = imgui.get_content_region_avail()
table_height = max(100.0, avail.y - imgui.get_frame_height_with_spacing() - 10)
#region: ast_dual_pane
if imgui.begin_table('ast_dual_pane', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
if imgui.begin_table('ast_dual_pane', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v, imgui.ImVec2(0, table_height)):
imgui.table_next_column()
#region: LEFT COLUMN (Tree) ---
imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 600), True)
imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 0), True)
if True:
if not app._cached_ast_nodes: imgui.text("No AST nodes found or error fetching outline.")
else:
@@ -2977,7 +3010,14 @@ def render_ast_inspector_modal(app: App) -> None:
imgui.dummy(imgui.ImVec2(indent * 10, 0))
imgui.same_line()
imgui.text(f"[{kind}] {name}")
imgui.same_line(imgui.get_window_width() - 200)
# Calculate space left and align radio buttons to the right
btn_width = 150 # Estimated width of the 3 radio buttons
avail_width = imgui.get_content_region_avail().x
if avail_width > btn_width:
imgui.same_line(imgui.get_window_width() - btn_width)
else:
imgui.same_line()
current_mode = f_item.ast_mask.get(full_path, 'hide')
@@ -2994,7 +3034,7 @@ def render_ast_inspector_modal(app: App) -> None:
imgui.table_next_column()
#region: RIGHT COLUMN (Content) ---
imgui.begin_child("ast_content_scroll", imgui.ImVec2(0, 600), True)
imgui.begin_child("ast_content_scroll", imgui.ImVec2(0, 0), True)
if True:
if not hasattr(app, '_cached_ast_file_lines') or not app._cached_ast_file_lines:
imgui.text("No file content loaded.")
@@ -3117,8 +3157,10 @@ def render_context_files_table(app: App) -> None:
f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
is_sel = f_path in app.ui_selected_context_files
f_item.auto_aggregate = is_sel
changed_sel, is_sel = imgui.checkbox(f"##sel{i}", is_sel)
if changed_sel:
f_item.auto_aggregate = is_sel
if imgui.get_io().key_shift and app._last_selected_context_index != -1:
start = min(app._last_selected_context_index, i)
end = max(app._last_selected_context_index, i)
@@ -4104,6 +4146,9 @@ def render_text_viewer_window(app: App) -> None:
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 AST Slices"): app._populate_auto_slices(app.ui_editing_slices_file)
to_remove = -1
for idx, slc in enumerate(app.ui_editing_slices_file.custom_slices):
imgui.push_id(f"slc_row_{idx}"); imgui.text(f"Slice {idx+1}: {slc['start_line']}-{slc['end_line']}"); imgui.same_line()
@@ -4127,14 +4172,19 @@ def render_text_viewer_window(app: App) -> None:
lines = app.text_viewer_content.splitlines(); draw_list = imgui.get_window_draw_list()
for i, line_text in enumerate(lines):
line_num = i + 1; pos = imgui.get_cursor_screen_pos(); line_height = imgui.get_text_line_height()
is_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices)
if is_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(255, 165, 0, 0.2)))
is_auto_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') == 'auto-ast')
is_manual_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') != 'auto-ast')
if is_manual_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(255, 165, 0, 0.2)))
elif is_auto_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(0, 255, 0, 0.15)))
if app._slice_sel_start != -1 and app._slice_sel_end != -1:
s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end)
if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(100, 100, 255, 0.3)))
imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False)
if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num
if imgui.is_item_hovered() and imgui.is_mouse_down(0): app._slice_sel_end = line_num
if imgui.is_item_hovered(imgui.HoveredFlags_.allow_when_blocked_by_active_item) and imgui.is_mouse_down(0): app._slice_sel_end = line_num
elif tv_type in renderer._lang_map:
if app._text_viewer_editor is None:
app._text_viewer_editor = ced.TextEditor(); app._text_viewer_editor.set_read_only_enabled(True); app._text_viewer_editor.set_show_line_numbers_enabled(True)
+2
View File
@@ -26,6 +26,8 @@ def test_ast_inspector_line_range_parsing():
mock_imgui.begin_child.return_value = True
# radio_button returns (changed, active)
mock_imgui.radio_button.return_value = (False, False)
mock_imgui.get_content_region_avail.return_value.y = 800.0
mock_imgui.get_frame_height_with_spacing.return_value = 24.0
# Setup imscope mocks
mock_imscope.window.return_value.__enter__.return_value = (True, True)
+35
View File
@@ -0,0 +1,35 @@
import pytest
import time
import requests
import json
import os
def test_context_preview_and_ast_inspector(live_gui):
process, script_path = live_gui
# Give it a second to stabilize
time.sleep(1)
# Set context file
resp = requests.post("http://127.0.0.1:8999/api/gui", json={
"action": "set_value",
"item": "files",
"value": ["src/aggregate.py"]
})
assert resp.status_code == 200
time.sleep(1)
# Trigger Context Preview
resp = requests.post("http://127.0.0.1:8999/api/gui", json={
"action": "click",
"item": "btn_preview_ctx" # wait, there is no btn_preview_ctx registered in _clickable_actions
})
# Wait, the best way to verify if _do_generate works without crashing is
# checking /api/v1/context
resp = requests.get("http://127.0.0.1:8999/api/v1/context")
assert resp.status_code == 200
data = resp.json()
assert "## Files" in data["markdown"]
assert "aggregate.py" in data["markdown"]
print("SUCCESS: Context Generation works.")