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.disc_entries: List[Dict[str, Any]] = []
|
||||||
self.ui_active_persona: str = ""
|
self.ui_active_persona: str = ""
|
||||||
self.disc_roles: List[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.screenshots: List[str] = []
|
||||||
self.event_queue: events.AsyncEventQueue = events.AsyncEventQueue()
|
self.event_queue: events.AsyncEventQueue = events.AsyncEventQueue()
|
||||||
self._loop_thread: Optional[threading.Thread] = None
|
self._loop_thread: Optional[threading.Thread] = None
|
||||||
@@ -3286,6 +3287,7 @@ class AppController:
|
|||||||
# Use current full markdown context for the track execution
|
# Use current full markdown context for the track execution
|
||||||
track_id_param = track.id
|
track_id_param = track.id
|
||||||
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=track_id_param)
|
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)
|
full_md, _, _ = aggregate.run(flat)
|
||||||
# Start the engine in a separate thread
|
# Start the engine in a separate thread
|
||||||
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
|
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
|
||||||
@@ -3506,4 +3508,4 @@ class AppController:
|
|||||||
if self.active_track:
|
if self.active_track:
|
||||||
self.active_tickets = [asdict(t) if not isinstance(t, dict) else t for t in self.active_track.tickets]
|
self.active_tickets = [asdict(t) if not isinstance(t, dict) else t for t in self.active_track.tickets]
|
||||||
else:
|
else:
|
||||||
self.active_tickets = []
|
self.active_tickets = []]
|
||||||
+23
-10
@@ -244,6 +244,7 @@ class App:
|
|||||||
self._cached_ast_nodes = []
|
self._cached_ast_nodes = []
|
||||||
self._cached_ast_file_path = ''
|
self._cached_ast_file_path = ''
|
||||||
self.ui_editing_slices_file = None
|
self.ui_editing_slices_file = None
|
||||||
|
self.context_files = []
|
||||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||||
self._handle_approve_ask()
|
self._handle_approve_ask()
|
||||||
|
|
||||||
@@ -814,6 +815,7 @@ class App:
|
|||||||
self._render_base_prompt_diff_modal()
|
self._render_base_prompt_diff_modal()
|
||||||
self._render_save_preset_modal()
|
self._render_save_preset_modal()
|
||||||
self._render_save_workspace_profile_modal()
|
self._render_save_workspace_profile_modal()
|
||||||
|
self._render_add_context_files_modal()
|
||||||
self._render_preset_manager_window()
|
self._render_preset_manager_window()
|
||||||
self._render_tool_preset_manager_window()
|
self._render_tool_preset_manager_window()
|
||||||
self._render_persona_editor_window()
|
self._render_persona_editor_window()
|
||||||
@@ -2803,7 +2805,7 @@ class App:
|
|||||||
imgui.text("Batch:")
|
imgui.text("Batch:")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Full##batch"):
|
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)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
if f_path in self.ui_selected_context_files:
|
if f_path in self.ui_selected_context_files:
|
||||||
f.force_full = True
|
f.force_full = True
|
||||||
@@ -2813,7 +2815,7 @@ class App:
|
|||||||
f.ast_definitions = False
|
f.ast_definitions = False
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Agg##batch"):
|
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)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
if f_path in self.ui_selected_context_files:
|
if f_path in self.ui_selected_context_files:
|
||||||
f.auto_aggregate = True
|
f.auto_aggregate = True
|
||||||
@@ -2823,7 +2825,7 @@ class App:
|
|||||||
f.ast_definitions = False
|
f.ast_definitions = False
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Sig##batch"):
|
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)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
if f_path in self.ui_selected_context_files:
|
if f_path in self.ui_selected_context_files:
|
||||||
if hasattr(f, "ast_signatures"):
|
if hasattr(f, "ast_signatures"):
|
||||||
@@ -2833,7 +2835,7 @@ class App:
|
|||||||
f.ast_definitions = False
|
f.ast_definitions = False
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Def##batch"):
|
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)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
if f_path in self.ui_selected_context_files:
|
if f_path in self.ui_selected_context_files:
|
||||||
if hasattr(f, "ast_definitions"):
|
if hasattr(f, "ast_definitions"):
|
||||||
@@ -2843,7 +2845,7 @@ class App:
|
|||||||
f.ast_signatures = False
|
f.ast_signatures = False
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("None##batch"):
|
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)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
if f_path in self.ui_selected_context_files:
|
if f_path in self.ui_selected_context_files:
|
||||||
f.auto_aggregate = False
|
f.auto_aggregate = False
|
||||||
@@ -2853,20 +2855,31 @@ class App:
|
|||||||
f.ast_definitions = False
|
f.ast_definitions = False
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Sel All##selall"):
|
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)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
self.ui_selected_context_files.add(f_path)
|
self.ui_selected_context_files.add(f_path)
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Unsel All##unselall"):
|
if imgui.button("Unsel All##unselall"):
|
||||||
self.ui_selected_context_files.clear()
|
self.ui_selected_context_files.clear()
|
||||||
imgui.same_line()
|
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"):
|
if imgui.button("Del##batch"):
|
||||||
new_files = []
|
new_files = []
|
||||||
for f in self.files:
|
for f in self.context_files:
|
||||||
f_path = f.path if hasattr(f, "path") else str(f)
|
f_path = f.path if hasattr(f, "path") else str(f)
|
||||||
if f_path not in self.ui_selected_context_files:
|
if f_path not in self.ui_selected_context_files:
|
||||||
new_files.append(f)
|
new_files.append(f)
|
||||||
self.files = new_files
|
self.context_files = new_files
|
||||||
self.ui_selected_context_files.clear()
|
self.ui_selected_context_files.clear()
|
||||||
#endregion: Batch Action Bar
|
#endregion: Batch Action Bar
|
||||||
|
|
||||||
@@ -2876,7 +2889,7 @@ class App:
|
|||||||
imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
|
imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
|
||||||
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200)
|
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200)
|
||||||
imgui.table_headers_row()
|
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_next_row()
|
||||||
imgui.table_set_column_index(0)
|
imgui.table_set_column_index(0)
|
||||||
|
|
||||||
@@ -2889,7 +2902,7 @@ class App:
|
|||||||
start = min(self._last_selected_context_index, i)
|
start = min(self._last_selected_context_index, i)
|
||||||
end = max(self._last_selected_context_index, i)
|
end = max(self._last_selected_context_index, i)
|
||||||
for idx in range(start, end + 1):
|
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)
|
item_path = item.path if hasattr(item, "path") else str(item)
|
||||||
if is_sel:
|
if is_sel:
|
||||||
self.ui_selected_context_files.add(item_path)
|
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