feat(context): Decouple context composition from files and media
This commit is contained in:
@@ -177,7 +177,8 @@ class AppController:
|
||||
self.disc_entries: List[Dict[str, Any]] = []
|
||||
self.ui_active_persona: str = ""
|
||||
self.disc_roles: List[str] = []
|
||||
self.files: List[str] = []
|
||||
self.files: List[models.FileItem] = []
|
||||
self.context_files: List[models.FileItem] = []
|
||||
self.screenshots: List[str] = []
|
||||
self.event_queue: events.AsyncEventQueue = events.AsyncEventQueue()
|
||||
self._loop_thread: Optional[threading.Thread] = None
|
||||
@@ -3286,6 +3287,7 @@ class AppController:
|
||||
# Use current full markdown context for the track execution
|
||||
track_id_param = track.id
|
||||
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=track_id_param)
|
||||
flat.setdefault("files", {})["paths"] = self.context_files
|
||||
full_md, _, _ = aggregate.run(flat)
|
||||
# Start the engine in a separate thread
|
||||
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
|
||||
@@ -3506,4 +3508,4 @@ class AppController:
|
||||
if self.active_track:
|
||||
self.active_tickets = [asdict(t) if not isinstance(t, dict) else t for t in self.active_track.tickets]
|
||||
else:
|
||||
self.active_tickets = []
|
||||
self.active_tickets = []]
|
||||
+23
-10
@@ -244,6 +244,7 @@ class App:
|
||||
self._cached_ast_nodes = []
|
||||
self._cached_ast_file_path = ''
|
||||
self.ui_editing_slices_file = None
|
||||
self.context_files = []
|
||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||
self._handle_approve_ask()
|
||||
|
||||
@@ -814,6 +815,7 @@ class App:
|
||||
self._render_base_prompt_diff_modal()
|
||||
self._render_save_preset_modal()
|
||||
self._render_save_workspace_profile_modal()
|
||||
self._render_add_context_files_modal()
|
||||
self._render_preset_manager_window()
|
||||
self._render_tool_preset_manager_window()
|
||||
self._render_persona_editor_window()
|
||||
@@ -2803,7 +2805,7 @@ class App:
|
||||
imgui.text("Batch:")
|
||||
imgui.same_line()
|
||||
if imgui.button("Full##batch"):
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path in self.ui_selected_context_files:
|
||||
f.force_full = True
|
||||
@@ -2813,7 +2815,7 @@ class App:
|
||||
f.ast_definitions = False
|
||||
imgui.same_line()
|
||||
if imgui.button("Agg##batch"):
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path in self.ui_selected_context_files:
|
||||
f.auto_aggregate = True
|
||||
@@ -2823,7 +2825,7 @@ class App:
|
||||
f.ast_definitions = False
|
||||
imgui.same_line()
|
||||
if imgui.button("Sig##batch"):
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path in self.ui_selected_context_files:
|
||||
if hasattr(f, "ast_signatures"):
|
||||
@@ -2833,7 +2835,7 @@ class App:
|
||||
f.ast_definitions = False
|
||||
imgui.same_line()
|
||||
if imgui.button("Def##batch"):
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path in self.ui_selected_context_files:
|
||||
if hasattr(f, "ast_definitions"):
|
||||
@@ -2843,7 +2845,7 @@ class App:
|
||||
f.ast_signatures = False
|
||||
imgui.same_line()
|
||||
if imgui.button("None##batch"):
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path in self.ui_selected_context_files:
|
||||
f.auto_aggregate = False
|
||||
@@ -2853,20 +2855,31 @@ class App:
|
||||
f.ast_definitions = False
|
||||
imgui.same_line()
|
||||
if imgui.button("Sel All##selall"):
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
self.ui_selected_context_files.add(f_path)
|
||||
imgui.same_line()
|
||||
if imgui.button("Unsel All##unselall"):
|
||||
self.ui_selected_context_files.clear()
|
||||
imgui.same_line()
|
||||
if imgui.button("Add Files"):
|
||||
imgui.open_popup("Select Context Files")
|
||||
imgui.same_line()
|
||||
if imgui.button("Add All##addall"):
|
||||
import copy
|
||||
context_paths = {f.path if hasattr(f, "path") else str(f) for f in self.context_files}
|
||||
for f in self.files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path not in context_paths:
|
||||
self.context_files.append(copy.deepcopy(f))
|
||||
imgui.same_line()
|
||||
if imgui.button("Del##batch"):
|
||||
new_files = []
|
||||
for f in self.files:
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
if f_path not in self.ui_selected_context_files:
|
||||
new_files.append(f)
|
||||
self.files = new_files
|
||||
self.context_files = new_files
|
||||
self.ui_selected_context_files.clear()
|
||||
#endregion: Batch Action Bar
|
||||
|
||||
@@ -2876,7 +2889,7 @@ class App:
|
||||
imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
|
||||
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200)
|
||||
imgui.table_headers_row()
|
||||
for i, f_item in enumerate(self.files):
|
||||
for i, f_item in enumerate(self.context_files):
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
|
||||
@@ -2889,7 +2902,7 @@ class App:
|
||||
start = min(self._last_selected_context_index, i)
|
||||
end = max(self._last_selected_context_index, i)
|
||||
for idx in range(start, end + 1):
|
||||
item = self.files[idx]
|
||||
item = self.context_files[idx]
|
||||
item_path = item.path if hasattr(item, "path") else str(item)
|
||||
if is_sel:
|
||||
self.ui_selected_context_files.add(item_path)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
from src.app_controller import AppController
|
||||
from src.models import FileItem
|
||||
|
||||
def test_context_files_is_decoupled():
|
||||
controller = AppController()
|
||||
|
||||
# Verify both lists exist and are distinct
|
||||
assert hasattr(controller, 'files')
|
||||
assert hasattr(controller, 'context_files')
|
||||
assert controller.files is not controller.context_files
|
||||
|
||||
# Modifying one should not affect the other
|
||||
controller.files.append(FileItem(path="whitelist.txt"))
|
||||
controller.context_files.append(FileItem(path="context.txt"))
|
||||
|
||||
assert len(controller.files) == 1
|
||||
assert controller.files[0].path == "whitelist.txt"
|
||||
|
||||
assert len(controller.context_files) == 1
|
||||
assert controller.context_files[0].path == "context.txt"
|
||||
|
||||
def test_do_generate_uses_context_files(monkeypatch):
|
||||
controller = AppController()
|
||||
controller.init_state()
|
||||
controller.context_files = [FileItem(path="context.txt")]
|
||||
controller.files = [FileItem(path="whitelist.txt")]
|
||||
|
||||
# Mock project_manager.flat_config and aggregate.run to verify passed data
|
||||
import src.project_manager as pm
|
||||
import src.aggregate as agg
|
||||
|
||||
def mock_flat_config(*args, **kwargs):
|
||||
return {"files": {}}
|
||||
|
||||
def mock_aggregate_run(flat, **kwargs):
|
||||
assert flat["files"]["paths"] == controller.context_files
|
||||
return ("md", "path", [], "stable_md", "disc_text")
|
||||
|
||||
monkeypatch.setattr(pm, "flat_config", mock_flat_config)
|
||||
monkeypatch.setattr(pm, "save_project", lambda *args: None)
|
||||
monkeypatch.setattr(agg, "run", mock_aggregate_run)
|
||||
monkeypatch.setattr(agg, "build_markdown_no_history", lambda *args, **kwargs: "stable")
|
||||
monkeypatch.setattr(agg, "build_discussion_text", lambda *args, **kwargs: "disc")
|
||||
|
||||
# Should not raise assertion error
|
||||
controller._do_generate()
|
||||
Reference in New Issue
Block a user