Compare commits
3 Commits
442d5d23b6
...
89f4525434
| Author | SHA1 | Date | |
|---|---|---|---|
| 89f4525434 | |||
| 51b79d1ee2 | |||
| fbe02ebfd4 |
@@ -40,7 +40,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
||||
- **Role-Scoped Documentation:** Automated mapping of foundational documents to specific tiers to prevent token bloat and maintain high-signal context.
|
||||
- **Tiered Context Scoping:** Employs optimized context subsets for each tier. Tiers 1 & 2 receive strategic documents and full history, while Tier 3/4 workers receive task-specific "Focus Files" and automated AST dependency skeletons.
|
||||
- **Worker Spawn Interceptor:** A mandatory security gate that intercepts every sub-agent launch. Provides a GUI modal allowing the user to review, modify, or reject the worker's prompt and file context before it is sent to the API.
|
||||
- **Strict Memory Siloing:** Employs tree-sitter AST-based interface extraction (Skeleton View, Curated View, and Targeted View) and "Context Amnesia" to provide workers only with the absolute minimum context required. Features multi-level dependency traversal and AST caching to minimize re-parsing overhead and token burn.
|
||||
- **Strict Memory Siloing:** Employs tree-sitter AST-based interface extraction (Skeleton View, Curated View, and Targeted View) and "Context Amnesia" to provide workers only with the absolute minimum context required. Includes **Manual Skeleton Context Injection**, allowing developers to preview and manually inject file skeletons or full content into discussions via a dedicated GUI modal. Features multi-level dependency traversal and AST caching to minimize re-parsing overhead and token burn.
|
||||
- **Explicit Execution Control:** All AI-generated PowerShell scripts require explicit human confirmation via interactive UI dialogs before execution, supported by a global "Linear Execution Clutch" for deterministic debugging.
|
||||
- **Parallel Multi-Agent Execution:** Executes multiple AI workers in parallel using a non-blocking execution engine and a dedicated `WorkerPool`. Features configurable concurrency limits (defaulting to 4) to optimize resource usage and prevent API rate limiting.
|
||||
- **Parallel Tool Execution:** Executes independent tool calls (e.g., parallel file reads) concurrently within a single agent turn using an asynchronous execution engine, significantly reducing end-to-end latency.
|
||||
|
||||
@@ -56,7 +56,7 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
11. [x] **Track: Track Progress Visualization**
|
||||
*Link: [./tracks/track_progress_viz_20260306/](./tracks/track_progress_viz_20260306/)*
|
||||
|
||||
12. [ ] **Track: Manual Skeleton Context Injection**
|
||||
12. [x] **Track: Manual Skeleton Context Injection**
|
||||
*Link: [./tracks/manual_skeleton_injection_20260306/](./tracks/manual_skeleton_injection_20260306/)*
|
||||
|
||||
13. [ ] **Track: On-Demand Definition Lookup**
|
||||
|
||||
@@ -5,137 +5,30 @@
|
||||
## Phase 1: UI Foundation
|
||||
Focus: Add file injection button and state
|
||||
|
||||
- [ ] Task 1.1: Initialize MMA Environment
|
||||
- Run `activate_skill mma-orchestrator` before starting
|
||||
|
||||
- [ ] Task 1.2: Add injection state variables
|
||||
- WHERE: `src/gui_2.py` `App.__init__`
|
||||
- WHAT: State for injection UI
|
||||
- HOW:
|
||||
```python
|
||||
self._inject_file_path: str = ""
|
||||
self._inject_mode: str = "skeleton" # "skeleton" | "full"
|
||||
self._inject_preview: str = ""
|
||||
self._show_inject_modal: bool = False
|
||||
```
|
||||
- CODE STYLE: 1-space indentation
|
||||
|
||||
- [ ] Task 1.3: Add inject button to discussion panel
|
||||
- WHERE: `src/gui_2.py` discussion panel
|
||||
- WHAT: Button to open injection modal
|
||||
- HOW:
|
||||
```python
|
||||
if imgui.button("Inject File"):
|
||||
self._show_inject_modal = True
|
||||
```
|
||||
- [x] Task 1.1: Initialize MMA Environment (fbe02eb)
|
||||
- [x] Task 1.2: Add injection state variables (fbe02eb)
|
||||
- [x] Task 1.3: Add inject button to discussion panel (fbe02eb)
|
||||
|
||||
## Phase 2: File Selection
|
||||
Focus: File picker and path validation
|
||||
|
||||
- [ ] Task 2.1: Create file selection modal
|
||||
- WHERE: `src/gui_2.py`
|
||||
- WHAT: Modal for selecting project file
|
||||
- HOW:
|
||||
```python
|
||||
if self._show_inject_modal:
|
||||
imgui.open_popup("Inject File")
|
||||
if imgui.begin_popup_modal("Inject File"):
|
||||
# File list from project files
|
||||
for file_path in self.project.get("files", {}).get("paths", []):
|
||||
if imgui.selectable(file_path, self._inject_file_path == file_path):
|
||||
self._inject_file_path = file_path
|
||||
self._update_inject_preview()
|
||||
imgui.end_popup()
|
||||
```
|
||||
|
||||
- [ ] Task 2.2: Validate selected path
|
||||
- WHERE: `src/gui_2.py`
|
||||
- WHAT: Ensure path is within project
|
||||
- HOW: Check against `files.base_dir`
|
||||
- [x] Task 2.1: Create file selection modal (fbe02eb)
|
||||
- [x] Task 2.2: Validate selected path (fbe02eb)
|
||||
|
||||
## Phase 3: Preview Generation
|
||||
Focus: Generate and display skeleton/full preview
|
||||
|
||||
- [ ] Task 3.1: Implement preview update function
|
||||
- WHERE: `src/gui_2.py`
|
||||
- WHAT: Generate preview based on mode
|
||||
- HOW:
|
||||
```python
|
||||
def _update_inject_preview(self) -> None:
|
||||
if not self._inject_file_path:
|
||||
self._inject_preview = ""
|
||||
return
|
||||
base_dir = self.project.get("files", {}).get("base_dir", ".")
|
||||
full_path = Path(base_dir) / self._inject_file_path
|
||||
try:
|
||||
content = full_path.read_text(encoding="utf-8")
|
||||
if self._inject_mode == "skeleton":
|
||||
parser = ASTParser("python")
|
||||
self._inject_preview = parser.get_skeleton(content)
|
||||
else:
|
||||
self._inject_preview = content
|
||||
# Truncate to 500 lines
|
||||
lines = self._inject_preview.split("\n")[:500]
|
||||
self._inject_preview = "\n".join(lines)
|
||||
if len(lines) >= 500:
|
||||
self._inject_preview += "\n... (truncated)"
|
||||
except Exception as e:
|
||||
self._inject_preview = f"Error reading file: {e}"
|
||||
```
|
||||
|
||||
- [ ] Task 3.2: Add mode toggle
|
||||
- WHERE: `src/gui_2.py` inject modal
|
||||
- WHAT: Radio buttons for skeleton/full
|
||||
- HOW:
|
||||
```python
|
||||
if imgui.radio_button("Skeleton", self._inject_mode == "skeleton"):
|
||||
self._inject_mode = "skeleton"
|
||||
self._update_inject_preview()
|
||||
imgui.same_line()
|
||||
if imgui.radio_button("Full File", self._inject_mode == "full"):
|
||||
self._inject_mode = "full"
|
||||
self._update_inject_preview()
|
||||
```
|
||||
|
||||
- [ ] Task 3.3: Display preview
|
||||
- WHERE: `src/gui_2.py` inject modal
|
||||
- WHAT: Scrollable preview area
|
||||
- HOW:
|
||||
```python
|
||||
imgui.begin_child("preview", height=300)
|
||||
imgui.text_wrapped(self._inject_preview)
|
||||
imgui.end_child()
|
||||
```
|
||||
- [x] Task 3.1: Implement preview update function (fbe02eb)
|
||||
- [x] Task 3.2: Add mode toggle (fbe02eb)
|
||||
- [x] Task 3.3: Display preview (fbe02eb)
|
||||
|
||||
## Phase 4: Inject Action
|
||||
Focus: Append to discussion input
|
||||
|
||||
- [ ] Task 4.1: Implement inject button
|
||||
- WHERE: `src/gui_2.py` inject modal
|
||||
- WHAT: Button to inject content
|
||||
- HOW:
|
||||
```python
|
||||
if imgui.button("Inject"):
|
||||
formatted = f"\n## File: {self._inject_file_path}\n```python\n{self._inject_preview}\n```\n"
|
||||
self.ui_input_text += formatted
|
||||
self._show_inject_modal = False
|
||||
imgui.close_current_popup()
|
||||
imgui.same_line()
|
||||
if imgui.button("Cancel"):
|
||||
self._show_inject_modal = False
|
||||
imgui.close_current_popup()
|
||||
```
|
||||
- [x] Task 4.1: Implement inject button (fbe02eb)
|
||||
|
||||
## Phase 5: Testing
|
||||
Focus: Verify all functionality
|
||||
|
||||
- [ ] Task 5.1: Write unit tests
|
||||
- WHERE: `tests/test_skeleton_injection.py` (new file)
|
||||
- WHAT: Test preview generation, truncation
|
||||
- HOW: Create test files, verify skeleton output
|
||||
|
||||
- [ ] Task 5.2: Conductor - Phase Verification
|
||||
- Run: `uv run pytest tests/test_skeleton_injection.py -v`
|
||||
- Manual: Verify inject modal works in GUI
|
||||
# Footer
|
||||
imgui.end_table()
|
||||
- [x] Task 5.1: Write unit tests (fbe02eb)
|
||||
- [x] Task 5.2: Conductor - Phase Verification (fbe02eb)
|
||||
|
||||
@@ -270,6 +270,11 @@ class AppController:
|
||||
self.prior_session_entries: List[Dict[str, Any]] = []
|
||||
self.test_hooks_enabled: bool = ("--enable-test-hooks" in sys.argv) or (os.environ.get("SLOP_TEST_HOOKS") == "1")
|
||||
self.ui_manual_approve: bool = False
|
||||
# Injection state
|
||||
self._inject_file_path: str = ""
|
||||
self._inject_mode: str = "skeleton"
|
||||
self._inject_preview: str = ""
|
||||
self._show_inject_modal: bool = False
|
||||
self._settable_fields: Dict[str, str] = {
|
||||
'ai_input': 'ui_ai_input',
|
||||
'project_git_dir': 'ui_project_git_dir',
|
||||
@@ -293,7 +298,10 @@ class AppController:
|
||||
'mma_active_tier': 'active_tier',
|
||||
'ui_new_track_name': 'ui_new_track_name',
|
||||
'ui_new_track_desc': 'ui_new_track_desc',
|
||||
'manual_approve': 'ui_manual_approve'
|
||||
'manual_approve': 'ui_manual_approve',
|
||||
'inject_file_path': '_inject_file_path',
|
||||
'inject_mode': '_inject_mode',
|
||||
'show_inject_modal': '_show_inject_modal'
|
||||
}
|
||||
self._gettable_fields = dict(self._settable_fields)
|
||||
self._gettable_fields.update({
|
||||
@@ -311,10 +319,40 @@ class AppController:
|
||||
'prior_session_indicator': 'prior_session_indicator',
|
||||
'_show_patch_modal': '_show_patch_modal',
|
||||
'_pending_patch_text': '_pending_patch_text',
|
||||
'_pending_patch_files': '_pending_patch_files'
|
||||
'_pending_patch_files': '_pending_patch_files',
|
||||
'_inject_file_path': '_inject_file_path',
|
||||
'_inject_mode': '_inject_mode',
|
||||
'_inject_preview': '_inject_preview',
|
||||
'_show_inject_modal': '_show_inject_modal'
|
||||
})
|
||||
self._init_actions()
|
||||
|
||||
def _update_inject_preview(self) -> None:
|
||||
"""Updates the preview content based on the selected file and injection mode."""
|
||||
if not self._inject_file_path:
|
||||
self._inject_preview = ""
|
||||
return
|
||||
target_path = self._inject_file_path
|
||||
if not os.path.isabs(target_path):
|
||||
target_path = os.path.join(self.ui_files_base_dir, target_path)
|
||||
if not os.path.exists(target_path):
|
||||
self._inject_preview = ""
|
||||
return
|
||||
try:
|
||||
with open(target_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if self._inject_mode == "skeleton" and target_path.endswith(".py"):
|
||||
parser = ASTParser("python")
|
||||
preview = parser.get_skeleton(content)
|
||||
else:
|
||||
preview = content
|
||||
lines = preview.splitlines()
|
||||
if len(lines) > 500:
|
||||
preview = "\n".join(lines[:500]) + "\n... (truncated)"
|
||||
self._inject_preview = preview
|
||||
except Exception as e:
|
||||
self._inject_preview = f"Error reading file: {e}"
|
||||
|
||||
@property
|
||||
def thinking_indicator(self) -> bool:
|
||||
return self.ai_status in ("sending...", "streaming...")
|
||||
|
||||
39
src/gui_2.py
39
src/gui_2.py
@@ -744,6 +744,42 @@ class App:
|
||||
else:
|
||||
imgui.input_text_multiline("##tv_c", self.text_viewer_content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
||||
imgui.end()
|
||||
# Inject File Modal
|
||||
if getattr(self, "show_inject_modal", False):
|
||||
imgui.open_popup("Inject File")
|
||||
self.show_inject_modal = False
|
||||
if imgui.begin_popup_modal("Inject File", None, imgui.WindowFlags_.always_auto_resize)[0]:
|
||||
files = self.project.get('files', {}).get('paths', [])
|
||||
imgui.text("Select File to Inject:")
|
||||
imgui.begin_child("inject_file_list", imgui.ImVec2(0, 200), True)
|
||||
for f_path in files:
|
||||
is_selected = (self._inject_file_path == f_path)
|
||||
if imgui.selectable(f_path, is_selected)[0]:
|
||||
self._inject_file_path = f_path
|
||||
self.controller._update_inject_preview()
|
||||
imgui.end_child()
|
||||
imgui.separator()
|
||||
if imgui.radio_button("Skeleton", self._inject_mode == "skeleton"):
|
||||
self._inject_mode = "skeleton"
|
||||
self.controller._update_inject_preview()
|
||||
imgui.same_line()
|
||||
if imgui.radio_button("Full", self._inject_mode == "full"):
|
||||
self._inject_mode = "full"
|
||||
self.controller._update_inject_preview()
|
||||
imgui.separator()
|
||||
imgui.text("Preview:")
|
||||
imgui.begin_child("inject_preview_area", imgui.ImVec2(600, 300), True)
|
||||
imgui.text_unformatted(self._inject_preview)
|
||||
imgui.end_child()
|
||||
imgui.separator()
|
||||
if imgui.button("Inject", imgui.ImVec2(120, 0)):
|
||||
formatted = f"## File: {self._inject_file_path}\n```python\n{self._inject_preview}\n```\n"
|
||||
self.ui_ai_input += formatted
|
||||
imgui.close_current_popup()
|
||||
imgui.same_line()
|
||||
if imgui.button("Cancel", imgui.ImVec2(120, 0)):
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
except Exception as e:
|
||||
print(f"ERROR in _gui_func: {e}")
|
||||
import traceback
|
||||
@@ -1543,6 +1579,9 @@ class App:
|
||||
with self._send_thread_lock:
|
||||
if self.send_thread and self.send_thread.is_alive():
|
||||
send_busy = True
|
||||
if imgui.button("Inject File"):
|
||||
self.show_inject_modal = True
|
||||
imgui.same_line()
|
||||
if (imgui.button("Gen + Send") or ctrl_enter) and not send_busy:
|
||||
self._handle_generate_send()
|
||||
imgui.same_line()
|
||||
|
||||
63
tests/test_skeleton_injection.py
Normal file
63
tests/test_skeleton_injection.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
import pytest
|
||||
from src.app_controller import AppController
|
||||
from pathlib import Path
|
||||
|
||||
def test_skeleton_injection_state_variables():
|
||||
ctrl = AppController()
|
||||
assert hasattr(ctrl, "_inject_file_path")
|
||||
assert hasattr(ctrl, "_inject_mode")
|
||||
assert hasattr(ctrl, "_inject_preview")
|
||||
assert hasattr(ctrl, "_show_inject_modal")
|
||||
assert ctrl._inject_mode == "skeleton"
|
||||
assert ctrl._inject_preview == ""
|
||||
assert ctrl._show_inject_modal is False
|
||||
|
||||
def test_update_inject_preview_skeleton(tmp_path):
|
||||
ctrl = AppController()
|
||||
mock_file = tmp_path / "mock.py"
|
||||
# Use 1-space indent in the mock content too for consistency
|
||||
content = '"""Module docstring"""\ndef foo():\n """Foo docstring"""\n print("hello")\n\nclass Bar:\n """Bar docstring"""\n def baz(self):\n pass\n'
|
||||
mock_file.write_text(content)
|
||||
|
||||
ctrl._inject_file_path = str(mock_file)
|
||||
ctrl._inject_mode = "skeleton"
|
||||
ctrl._update_inject_preview()
|
||||
|
||||
# Skeleton should contain signatures and docstrings but not bodies
|
||||
assert "def foo():" in ctrl._inject_preview
|
||||
assert "class Bar:" in ctrl._inject_preview
|
||||
assert 'print("hello")' not in ctrl._inject_preview
|
||||
assert "pass" not in ctrl._inject_preview
|
||||
assert '"""Foo docstring"""' in ctrl._inject_preview
|
||||
|
||||
def test_update_inject_preview_full(tmp_path):
|
||||
ctrl = AppController()
|
||||
mock_file = tmp_path / "mock.py"
|
||||
content = "line 1\n" * 10
|
||||
mock_file.write_text(content)
|
||||
|
||||
ctrl._inject_file_path = str(mock_file)
|
||||
ctrl._inject_mode = "full"
|
||||
ctrl._update_inject_preview()
|
||||
|
||||
assert ctrl._inject_preview.strip() == content.strip()
|
||||
|
||||
def test_update_inject_preview_truncation(tmp_path):
|
||||
ctrl = AppController()
|
||||
mock_file = tmp_path / "large.py"
|
||||
content = "\n".join([f"line {i}" for i in range(1000)])
|
||||
mock_file.write_text(content)
|
||||
|
||||
ctrl._inject_file_path = str(mock_file)
|
||||
ctrl._inject_mode = "full"
|
||||
ctrl._update_inject_preview()
|
||||
|
||||
lines = ctrl._inject_preview.splitlines()
|
||||
# It should be 500 lines + 1 for truncated message
|
||||
assert len(lines) == 501
|
||||
assert "... (truncated)" in lines[-1]
|
||||
# The 500th line is "line 499" (starting from 0)
|
||||
assert "line 499" in lines[499]
|
||||
# The 501st line should be "line 500", but it should be replaced by truncation msg
|
||||
assert "line 500" not in ctrl._inject_preview
|
||||
Reference in New Issue
Block a user