Compare commits
5 Commits
f72b081154
...
442d5d23b6
| Author | SHA1 | Date | |
|---|---|---|---|
| 442d5d23b6 | |||
| b41a8466f1 | |||
| 1e188fd3aa | |||
| 87902d82d8 | |||
| 34673ee32d |
@@ -30,7 +30,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
- **Tier 4 (QA):** Error analysis and diagnostics using `gemini-2.5-flash` or `deepseek-v3`. Operates statelessly with tool access.
|
- **Tier 4 (QA):** Error analysis and diagnostics using `gemini-2.5-flash` or `deepseek-v3`. Operates statelessly with tool access.
|
||||||
- **MMA Delegation Engine:** Route tasks, ensuring role-scoped context and detailed observability via timestamped sub-agent logs. Supports dynamic ticket creation and dependency resolution via an automated Dispatcher Loop.
|
- **MMA Delegation Engine:** Route tasks, ensuring role-scoped context and detailed observability via timestamped sub-agent logs. Supports dynamic ticket creation and dependency resolution via an automated Dispatcher Loop.
|
||||||
- **MMA Observability Dashboard:** A high-density control center within the GUI for monitoring and managing the 4-Tier architecture.
|
- **MMA Observability Dashboard:** A high-density control center within the GUI for monitoring and managing the 4-Tier architecture.
|
||||||
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars.
|
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times.
|
||||||
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
|
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
|
||||||
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
|
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
|
||||||
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files (e.g., `conductor/tracks/<track_id>/state.toml`). This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
|
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files (e.g., `conductor/tracks/<track_id>/state.toml`). This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
10. [x] **Track: Session Insights & Efficiency Scores**
|
10. [x] **Track: Session Insights & Efficiency Scores**
|
||||||
*Link: [./tracks/session_insights_20260306/](./tracks/session_insights_20260306/)*
|
*Link: [./tracks/session_insights_20260306/](./tracks/session_insights_20260306/)*
|
||||||
|
|
||||||
11. [ ] **Track: Track Progress Visualization**
|
11. [x] **Track: Track Progress Visualization**
|
||||||
*Link: [./tracks/track_progress_viz_20260306/](./tracks/track_progress_viz_20260306/)*
|
*Link: [./tracks/track_progress_viz_20260306/](./tracks/track_progress_viz_20260306/)*
|
||||||
|
|
||||||
12. [ ] **Track: Manual Skeleton Context Injection**
|
12. [ ] **Track: Manual Skeleton Context Injection**
|
||||||
@@ -117,5 +117,3 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
- [x] **Track: Simulation Hardening**
|
- [x] **Track: Simulation Hardening**
|
||||||
- [x] **Track: Deep Architectural Documentation Refresh**
|
- [x] **Track: Deep Architectural Documentation Refresh**
|
||||||
- [x] **Track: Robust Live Simulation Verification**
|
- [x] **Track: Robust Live Simulation Verification**
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
## Phase 1: Progress Calculation
|
## Phase 1: Progress Calculation
|
||||||
Focus: Calculate progress metrics from ticket states
|
Focus: Calculate progress metrics from ticket states
|
||||||
|
|
||||||
- [ ] Task 1.1: Initialize MMA Environment
|
- [x] Task 1.1: Initialize MMA Environment (34673ee)
|
||||||
- Run `activate_skill mma-orchestrator` before starting
|
- Run `activate_skill mma-orchestrator` before starting
|
||||||
|
|
||||||
- [ ] Task 1.2: Implement progress calculation function
|
- [x] Task 1.2: Implement progress calculation function (87902d8)
|
||||||
- WHERE: `src/gui_2.py` or helper in `src/project_manager.py`
|
- WHERE: `src/gui_2.py` or helper in `src/project_manager.py`
|
||||||
- WHAT: Calculate completion percentage from tickets
|
- WHAT: Calculate completion percentage from tickets
|
||||||
- HOW:
|
- HOW:
|
||||||
@@ -29,7 +29,7 @@ Focus: Calculate progress metrics from ticket states
|
|||||||
## Phase 2: Progress Bar Rendering
|
## Phase 2: Progress Bar Rendering
|
||||||
Focus: Display visual progress bar
|
Focus: Display visual progress bar
|
||||||
|
|
||||||
- [ ] Task 2.1: Add progress bar to MMA Dashboard
|
- [x] Task 2.1: Add progress bar to MMA Dashboard (1e188fd)
|
||||||
- WHERE: `src/gui_2.py` `_render_mma_dashboard()`
|
- WHERE: `src/gui_2.py` `_render_mma_dashboard()`
|
||||||
- WHAT: Visual progress bar with percentage
|
- WHAT: Visual progress bar with percentage
|
||||||
- HOW:
|
- HOW:
|
||||||
@@ -45,7 +45,7 @@ Focus: Display visual progress bar
|
|||||||
## Phase 3: Ticket Breakdown Display
|
## Phase 3: Ticket Breakdown Display
|
||||||
Focus: Show status breakdown
|
Focus: Show status breakdown
|
||||||
|
|
||||||
- [ ] Task 3.1: Add status breakdown text
|
- [x] Task 3.1: Add status breakdown text (1e188fd)
|
||||||
- WHERE: `src/gui_2.py` `_render_mma_dashboard()`
|
- WHERE: `src/gui_2.py` `_render_mma_dashboard()`
|
||||||
- WHAT: Show counts per status
|
- WHAT: Show counts per status
|
||||||
- HOW:
|
- HOW:
|
||||||
@@ -59,7 +59,7 @@ Focus: Show status breakdown
|
|||||||
## Phase 4: ETA Estimation
|
## Phase 4: ETA Estimation
|
||||||
Focus: Estimate time remaining
|
Focus: Estimate time remaining
|
||||||
|
|
||||||
- [ ] Task 4.1: Track ticket completion times
|
- [x] Task 4.1: Track ticket completion times (1e188fd)
|
||||||
- WHERE: `src/gui_2.py` or `src/app_controller.py`
|
- WHERE: `src/gui_2.py` or `src/app_controller.py`
|
||||||
- WHAT: Track average time per completed ticket
|
- WHAT: Track average time per completed ticket
|
||||||
- HOW:
|
- HOW:
|
||||||
@@ -71,7 +71,7 @@ Focus: Estimate time remaining
|
|||||||
# On ticket complete: elapsed = time.time() - start; update average
|
# On ticket complete: elapsed = time.time() - start; update average
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] Task 4.2: Calculate and display ETA
|
- [x] Task 4.2: Calculate and display ETA (1e188fd)
|
||||||
- WHERE: `src/gui_2.py`
|
- WHERE: `src/gui_2.py`
|
||||||
- WHAT: Show estimated time remaining
|
- WHAT: Show estimated time remaining
|
||||||
- HOW:
|
- HOW:
|
||||||
@@ -85,11 +85,11 @@ Focus: Estimate time remaining
|
|||||||
## Phase 5: Testing
|
## Phase 5: Testing
|
||||||
Focus: Verify all functionality
|
Focus: Verify all functionality
|
||||||
|
|
||||||
- [ ] Task 5.1: Write unit tests for progress calculation
|
- [x] Task 5.1: Write unit tests for progress calculation (1e188fd)
|
||||||
- WHERE: `tests/test_progress_viz.py` (new file)
|
- WHERE: `tests/test_progress_viz.py` (new file)
|
||||||
- WHAT: Test percentage calculation, edge cases
|
- WHAT: Test percentage calculation, edge cases
|
||||||
- HOW: Create mock tickets with various statuses
|
- HOW: Create mock tickets with various statuses
|
||||||
|
|
||||||
- [ ] Task 5.2: Conductor - Phase Verification
|
- [x] Task 5.2: Conductor - Phase Verification (1e188fd)
|
||||||
- Run: `uv run pytest tests/test_progress_viz.py -v`
|
- Run: `uv run pytest tests/test_progress_viz.py -v`
|
||||||
- Manual: Verify progress bar displays correctly
|
- Manual: Verify progress bar displays correctly
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ class AppController:
|
|||||||
self._cached_cache_stats: Dict[str, Any] = {} # Pre-computed cache stats for GUI
|
self._cached_cache_stats: Dict[str, Any] = {} # Pre-computed cache stats for GUI
|
||||||
self._token_history: List[Dict[str, Any]] = [] # Token usage over time [{"time": t, "input": n, "output": n, "model": s}, ...]
|
self._token_history: List[Dict[str, Any]] = [] # Token usage over time [{"time": t, "input": n, "output": n, "model": s}, ...]
|
||||||
self._session_start_time: float = time.time() # For calculating burn rate
|
self._session_start_time: float = time.time() # For calculating burn rate
|
||||||
|
self._ticket_start_times: dict[str, float] = {}
|
||||||
|
self._avg_ticket_time: float = 0.0
|
||||||
|
self._completed_ticket_count: int = 0
|
||||||
self._comms_log: List[Dict[str, Any]] = []
|
self._comms_log: List[Dict[str, Any]] = []
|
||||||
self.session_usage: Dict[str, Any] = {
|
self.session_usage: Dict[str, Any] = {
|
||||||
"input_tokens": 0,
|
"input_tokens": 0,
|
||||||
@@ -573,6 +576,21 @@ class AppController:
|
|||||||
self._mma_spawn_edit_mode = False
|
self._mma_spawn_edit_mode = False
|
||||||
if "dialog_container" in task:
|
if "dialog_container" in task:
|
||||||
task["dialog_container"][0] = spawn_dlg
|
task["dialog_container"][0] = spawn_dlg
|
||||||
|
elif action == "ticket_started":
|
||||||
|
payload = task.get("payload", {})
|
||||||
|
ticket_id = payload.get("ticket_id")
|
||||||
|
start_time = payload.get("timestamp")
|
||||||
|
if ticket_id and start_time:
|
||||||
|
self._ticket_start_times[ticket_id] = start_time
|
||||||
|
elif action == "ticket_completed":
|
||||||
|
payload = task.get("payload", {})
|
||||||
|
ticket_id = payload.get("ticket_id")
|
||||||
|
end_time = payload.get("timestamp")
|
||||||
|
if ticket_id and end_time and ticket_id in self._ticket_start_times:
|
||||||
|
start_time = self._ticket_start_times.pop(ticket_id)
|
||||||
|
elapsed = end_time - start_time
|
||||||
|
self._completed_ticket_count += 1
|
||||||
|
self._avg_ticket_time = ((self._avg_ticket_time * (self._completed_ticket_count - 1)) + elapsed) / self._completed_ticket_count
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
sys.stderr.write(f"[DEBUG] Error executing GUI task: {e}\n{traceback.format_exc()}\n")
|
sys.stderr.write(f"[DEBUG] Error executing GUI task: {e}\n{traceback.format_exc()}\n")
|
||||||
@@ -902,6 +920,18 @@ class AppController:
|
|||||||
if self.test_hooks_enabled:
|
if self.test_hooks_enabled:
|
||||||
with self._api_event_queue_lock:
|
with self._api_event_queue_lock:
|
||||||
self._api_event_queue.append({"type": "response", "payload": payload})
|
self._api_event_queue.append({"type": "response", "payload": payload})
|
||||||
|
elif event_name == "ticket_started":
|
||||||
|
with self._pending_gui_tasks_lock:
|
||||||
|
self._pending_gui_tasks.append({
|
||||||
|
"action": "ticket_started",
|
||||||
|
"payload": payload
|
||||||
|
})
|
||||||
|
elif event_name == "ticket_completed":
|
||||||
|
with self._pending_gui_tasks_lock:
|
||||||
|
self._pending_gui_tasks.append({
|
||||||
|
"action": "ticket_completed",
|
||||||
|
"payload": payload
|
||||||
|
})
|
||||||
|
|
||||||
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
|
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
|
||||||
"""Processes a UserRequestEvent by calling the AI client."""
|
"""Processes a UserRequestEvent by calling the AI client."""
|
||||||
|
|||||||
89
src/gui_2.py
89
src/gui_2.py
@@ -1,4 +1,4 @@
|
|||||||
# gui_2.py
|
# gui_2.py
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import tomli_w
|
import tomli_w
|
||||||
import time
|
import time
|
||||||
@@ -1752,26 +1752,21 @@ class App:
|
|||||||
def _render_mma_dashboard(self) -> None:
|
def _render_mma_dashboard(self) -> None:
|
||||||
# Task 5.3: Dense Summary Line
|
# Task 5.3: Dense Summary Line
|
||||||
track_name = self.active_track.description if self.active_track else "None"
|
track_name = self.active_track.description if self.active_track else "None"
|
||||||
total_tickets = len(self.active_tickets)
|
track_stats = {"percentage": 0.0, "completed": 0, "total": 0, "in_progress": 0, "blocked": 0, "todo": 0}
|
||||||
done_tickets = sum(1 for t in self.active_tickets if t.get('status') == 'complete')
|
if self.active_track:
|
||||||
|
track_stats = project_manager.calculate_track_progress(self.active_track.tickets)
|
||||||
|
|
||||||
total_cost = 0.0
|
total_cost = 0.0
|
||||||
for stats in self.mma_tier_usage.values():
|
for usage in self.mma_tier_usage.values():
|
||||||
model = stats.get('model', 'unknown')
|
model = usage.get('model', 'unknown')
|
||||||
in_t = stats.get('input', 0)
|
in_t = usage.get('input', 0)
|
||||||
out_t = stats.get('output', 0)
|
out_t = usage.get('output', 0)
|
||||||
total_cost += cost_tracker.estimate_cost(model, in_t, out_t)
|
total_cost += cost_tracker.estimate_cost(model, in_t, out_t)
|
||||||
|
|
||||||
imgui.text("Track:")
|
imgui.text("Track:")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
imgui.text_colored(C_VAL, track_name)
|
imgui.text_colored(C_VAL, track_name)
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
imgui.text(" | Tickets:")
|
|
||||||
imgui.same_line()
|
|
||||||
imgui.text_colored(C_VAL, f"{done_tickets}/{total_tickets}")
|
|
||||||
imgui.same_line()
|
|
||||||
imgui.text(" | Cost:")
|
|
||||||
imgui.same_line()
|
|
||||||
imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}")
|
|
||||||
imgui.same_line()
|
|
||||||
imgui.text(" | Status:")
|
imgui.text(" | Status:")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
status_col = imgui.ImVec4(1, 1, 1, 1)
|
status_col = imgui.ImVec4(1, 1, 1, 1)
|
||||||
@@ -1780,7 +1775,54 @@ class App:
|
|||||||
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
|
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
|
||||||
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
|
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
|
||||||
imgui.text_colored(status_col, self.mma_status.upper())
|
imgui.text_colored(status_col, self.mma_status.upper())
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text(" | Cost:")
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}")
|
||||||
|
|
||||||
|
# Progress Bar
|
||||||
|
perc = track_stats["percentage"] / 100.0
|
||||||
|
p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0)
|
||||||
|
if track_stats["percentage"] < 33:
|
||||||
|
p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0)
|
||||||
|
elif track_stats["percentage"] < 66:
|
||||||
|
p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
imgui.push_style_color(imgui.Col_.plot_histogram, p_color)
|
||||||
|
imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%")
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
|
# Detailed breakdown
|
||||||
|
if imgui.begin_table("ticket_stats_breakdown", 4):
|
||||||
|
imgui.table_next_column()
|
||||||
|
imgui.text_colored(C_LBL, "Completed:")
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_colored(C_VAL, str(track_stats["completed"]))
|
||||||
|
|
||||||
|
imgui.table_next_column()
|
||||||
|
imgui.text_colored(C_LBL, "In Progress:")
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_colored(C_VAL, str(track_stats["in_progress"]))
|
||||||
|
|
||||||
|
imgui.table_next_column()
|
||||||
|
imgui.text_colored(C_LBL, "Blocked:")
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_colored(C_VAL, str(track_stats["blocked"]))
|
||||||
|
|
||||||
|
imgui.table_next_column()
|
||||||
|
imgui.text_colored(C_LBL, "Todo:")
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_colored(C_VAL, str(track_stats["todo"]))
|
||||||
|
|
||||||
|
imgui.end_table()
|
||||||
|
|
||||||
|
if self.active_track:
|
||||||
|
remaining = track_stats["total"] - track_stats["completed"]
|
||||||
|
eta_mins = (self._avg_ticket_time * remaining) / 60.0
|
||||||
|
imgui.text_colored(C_LBL, "ETA:")
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_colored(C_VAL, f"~{int(eta_mins)}m ({remaining} tickets remaining)")
|
||||||
|
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)')
|
imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)')
|
||||||
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
|
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
|
||||||
@@ -1874,20 +1916,7 @@ class App:
|
|||||||
if imgui.button("Go to Approval"):
|
if imgui.button("Go to Approval"):
|
||||||
pass # scroll/focus handled by existing dialog rendering
|
pass # scroll/focus handled by existing dialog rendering
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
# 2. Active Track Info
|
# 3. Token Usage Table
|
||||||
if self.active_track:
|
|
||||||
imgui.text(f"Track: {self.active_track.description}")
|
|
||||||
# Progress bar
|
|
||||||
tickets = self.active_tickets
|
|
||||||
total = len(tickets)
|
|
||||||
if total > 0:
|
|
||||||
complete = sum(1 for t in tickets if t.get('status') == 'complete')
|
|
||||||
progress = complete / total
|
|
||||||
imgui.progress_bar(progress, imgui.ImVec2(-1, 0), f"{complete}/{total} Tickets")
|
|
||||||
else:
|
|
||||||
imgui.text_disabled("No active MMA track.")
|
|
||||||
# 3. Token Usage Table
|
|
||||||
imgui.separator()
|
|
||||||
imgui.text("Tier Usage (Tokens & Cost)")
|
imgui.text("Tier Usage (Tokens & Cost)")
|
||||||
if imgui.begin_table("mma_usage", 5, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
|
if imgui.begin_table("mma_usage", 5, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
|
||||||
imgui.table_setup_column("Tier")
|
imgui.table_setup_column("Tier")
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ class ConductorEngine:
|
|||||||
|
|
||||||
if spawned:
|
if spawned:
|
||||||
ticket.status = "in_progress"
|
ticket.status = "in_progress"
|
||||||
|
_queue_put(self.event_queue, "ticket_started", {"ticket_id": ticket.id, "timestamp": time.time()})
|
||||||
print(f"Executing ticket {ticket.id}: {ticket.description}")
|
print(f"Executing ticket {ticket.id}: {ticket.description}")
|
||||||
self._push_state(active_tier=f"Tier 3 (Worker): {ticket.id}")
|
self._push_state(active_tier=f"Tier 3 (Worker): {ticket.id}")
|
||||||
|
|
||||||
@@ -368,6 +369,8 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
|
|||||||
)
|
)
|
||||||
if not approved:
|
if not approved:
|
||||||
ticket.mark_blocked("Spawn rejected by user.")
|
ticket.mark_blocked("Spawn rejected by user.")
|
||||||
|
if event_queue:
|
||||||
|
_queue_put(event_queue, "ticket_completed", {"ticket_id": ticket.id, "timestamp": time.time()})
|
||||||
return "BLOCKED: Spawn rejected by user."
|
return "BLOCKED: Spawn rejected by user."
|
||||||
user_message = modified_prompt
|
user_message = modified_prompt
|
||||||
md_content = modified_context
|
md_content = modified_context
|
||||||
@@ -417,7 +420,7 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
|
|||||||
ai_client.comms_log_callback = old_comms_cb
|
ai_client.comms_log_callback = old_comms_cb
|
||||||
ai_client.set_current_tier(None)
|
ai_client.set_current_tier(None)
|
||||||
if event_queue:
|
if event_queue:
|
||||||
# Push via "response" event type — _process_event_queue wraps this
|
# Push via "response" event type — _process_event_queue wraps this
|
||||||
# as {"action": "handle_ai_response", "payload": ...} for the GUI.
|
# as {"action": "handle_ai_response", "payload": ...} for the GUI.
|
||||||
try:
|
try:
|
||||||
response_payload = {
|
response_payload = {
|
||||||
@@ -441,4 +444,7 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
|
|||||||
ticket.mark_blocked(response)
|
ticket.mark_blocked(response)
|
||||||
else:
|
else:
|
||||||
ticket.mark_complete()
|
ticket.mark_complete()
|
||||||
|
|
||||||
|
if event_queue:
|
||||||
|
_queue_put(event_queue, "ticket_completed", {"ticket_id": ticket.id, "timestamp": time.time()})
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -318,10 +318,10 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
track_info["id"] = state.metadata.id or track_id
|
track_info["id"] = state.metadata.id or track_id
|
||||||
track_info["title"] = state.metadata.name or track_id
|
track_info["title"] = state.metadata.name or track_id
|
||||||
track_info["status"] = state.metadata.status or "unknown"
|
track_info["status"] = state.metadata.status or "unknown"
|
||||||
track_info["complete"] = len([t for t in state.tasks if t.status == "completed"])
|
progress = calculate_track_progress(state.tasks)
|
||||||
track_info["total"] = len(state.tasks)
|
track_info["complete"] = progress["completed"]
|
||||||
if track_info["total"] > 0:
|
track_info["total"] = progress["total"]
|
||||||
track_info["progress"] = track_info["complete"] / track_info["total"]
|
track_info["progress"] = progress["percentage"] / 100.0
|
||||||
state_found = True
|
state_found = True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -352,3 +352,35 @@ def get_all_tracks(base_dir: Union[str, Path] = ".") -> list[dict[str, Any]]:
|
|||||||
pass
|
pass
|
||||||
results.append(track_info)
|
results.append(track_info)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def calculate_track_progress(tickets: list) -> dict:
|
||||||
|
"""
|
||||||
|
Calculates track progress based on ticket statuses.
|
||||||
|
percentage (float), completed (int), total (int), in_progress (int), blocked (int), todo (int)
|
||||||
|
"""
|
||||||
|
total = len(tickets)
|
||||||
|
if total == 0:
|
||||||
|
return {
|
||||||
|
"percentage": 0.0,
|
||||||
|
"completed": 0,
|
||||||
|
"total": 0,
|
||||||
|
"in_progress": 0,
|
||||||
|
"blocked": 0,
|
||||||
|
"todo": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
completed = sum(1 for t in tickets if t.status == "completed")
|
||||||
|
in_progress = sum(1 for t in tickets if t.status == "in_progress")
|
||||||
|
blocked = sum(1 for t in tickets if t.status == "blocked")
|
||||||
|
todo = sum(1 for t in tickets if t.status == "todo")
|
||||||
|
|
||||||
|
percentage = (completed / total) * 100.0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"percentage": float(percentage),
|
||||||
|
"completed": completed,
|
||||||
|
"total": total,
|
||||||
|
"in_progress": in_progress,
|
||||||
|
"blocked": blocked,
|
||||||
|
"todo": todo
|
||||||
|
}
|
||||||
|
|||||||
74
tests/test_gui_progress.py
Normal file
74
tests/test_gui_progress.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from src.gui_2 import App, C_LBL, C_VAL
|
||||||
|
from src.models import Ticket
|
||||||
|
|
||||||
|
def test_render_mma_dashboard_progress():
|
||||||
|
# Create a mock for the imgui module used in gui_2
|
||||||
|
mock_imgui = MagicMock()
|
||||||
|
|
||||||
|
# Configure ImVec4 and ImVec2
|
||||||
|
mock_imgui.ImVec2 = MagicMock(side_effect=lambda x, y: (float(x), float(y)))
|
||||||
|
mock_imgui.ImVec4 = MagicMock(side_effect=lambda r, g, b, a: (float(r), float(g), float(b), float(a)))
|
||||||
|
|
||||||
|
# Configure calls that return tuples
|
||||||
|
mock_imgui.input_text_multiline.return_value = (False, "")
|
||||||
|
mock_imgui.input_text.return_value = (False, "")
|
||||||
|
mock_imgui.checkbox.return_value = (False, False)
|
||||||
|
mock_imgui.begin_combo.return_value = (False, "")
|
||||||
|
mock_imgui.selectable.return_value = (False, False)
|
||||||
|
mock_imgui.begin_table.return_value = True
|
||||||
|
mock_imgui.collapsing_header.return_value = False
|
||||||
|
|
||||||
|
# Patch where it is actually used
|
||||||
|
with patch('src.gui_2.imgui', mock_imgui), \
|
||||||
|
patch('src.gui_2.cost_tracker.estimate_cost', return_value=0.0):
|
||||||
|
|
||||||
|
# Mock App instance - no spec because of delegation
|
||||||
|
app = MagicMock()
|
||||||
|
|
||||||
|
# Setup mock state
|
||||||
|
app.active_track = MagicMock()
|
||||||
|
app.active_track.description = "Test Track"
|
||||||
|
|
||||||
|
# Mock self.active_track.tickets as a list of src.models.Ticket objects
|
||||||
|
app.active_track.tickets = [
|
||||||
|
Ticket(id='T1', description='desc', status='completed'),
|
||||||
|
Ticket(id='T2', description='desc', status='in_progress'),
|
||||||
|
Ticket(id='T3', description='desc', status='blocked'),
|
||||||
|
Ticket(id='T4', description='desc', status='todo')
|
||||||
|
]
|
||||||
|
|
||||||
|
app.mma_tier_usage = {}
|
||||||
|
app.mma_status = "idle"
|
||||||
|
app.active_tier = None
|
||||||
|
app._pending_mma_spawn = None
|
||||||
|
app._pending_mma_approval = None
|
||||||
|
app._pending_ask_dialog = False
|
||||||
|
app.tracks = []
|
||||||
|
app.ui_epic_input = ""
|
||||||
|
app.ui_conductor_setup_summary = ""
|
||||||
|
app.ui_new_track_name = ""
|
||||||
|
app.ui_new_track_desc = ""
|
||||||
|
app.ui_new_track_type = "feature"
|
||||||
|
app.mma_step_mode = False
|
||||||
|
app.node_editor_ctx = None
|
||||||
|
app._avg_ticket_time = 60
|
||||||
|
|
||||||
|
# Call the method
|
||||||
|
App._render_mma_dashboard(app)
|
||||||
|
|
||||||
|
# Assertions
|
||||||
|
# 1 completed out of 4 tickets = 25.0% progress
|
||||||
|
# Update assertions: imgui.progress_bar is called with (0.25, (-1.0, 0.0), '25.0%')
|
||||||
|
mock_imgui.progress_bar.assert_any_call(0.25, (-1.0, 0.0), "25.0%")
|
||||||
|
|
||||||
|
# Verify status breakdown counts are asserted via imgui.text_colored calls
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_LBL, "Completed:")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_VAL, "1")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_LBL, "In Progress:")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_VAL, "1")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_LBL, "Blocked:")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_VAL, "1")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_LBL, "Todo:")
|
||||||
|
mock_imgui.text_colored.assert_any_call(C_VAL, "1")
|
||||||
49
tests/test_progress_viz.py
Normal file
49
tests/test_progress_viz.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import pytest
|
||||||
|
from src.project_manager import calculate_track_progress
|
||||||
|
from src.models import Ticket
|
||||||
|
|
||||||
|
def test_calculate_track_progress_empty():
|
||||||
|
results = calculate_track_progress([])
|
||||||
|
assert results["total"] == 0
|
||||||
|
assert results["percentage"] == 0.0
|
||||||
|
assert results["completed"] == 0
|
||||||
|
assert results["in_progress"] == 0
|
||||||
|
assert results["blocked"] == 0
|
||||||
|
assert results["todo"] == 0
|
||||||
|
|
||||||
|
def test_calculate_track_progress_all_todo():
|
||||||
|
tickets = [
|
||||||
|
Ticket(id="1", description="desc 1", status="todo"),
|
||||||
|
Ticket(id="2", description="desc 2", status="todo")
|
||||||
|
]
|
||||||
|
results = calculate_track_progress(tickets)
|
||||||
|
assert results["total"] == 2
|
||||||
|
assert results["percentage"] == 0.0
|
||||||
|
assert results["completed"] == 0
|
||||||
|
assert results["todo"] == 2
|
||||||
|
|
||||||
|
def test_calculate_track_progress_mixed():
|
||||||
|
tickets = [
|
||||||
|
Ticket(id="1", description="desc 1", status="completed"),
|
||||||
|
Ticket(id="2", description="desc 2", status="in_progress"),
|
||||||
|
Ticket(id="3", description="desc 3", status="blocked"),
|
||||||
|
Ticket(id="4", description="desc 4", status="todo")
|
||||||
|
]
|
||||||
|
results = calculate_track_progress(tickets)
|
||||||
|
assert results["total"] == 4
|
||||||
|
assert results["completed"] == 1
|
||||||
|
assert results["in_progress"] == 1
|
||||||
|
assert results["blocked"] == 1
|
||||||
|
assert results["todo"] == 1
|
||||||
|
assert results["percentage"] == 25.0
|
||||||
|
|
||||||
|
def test_calculate_track_progress_all_completed():
|
||||||
|
tickets = [
|
||||||
|
Ticket(id="1", description="desc 1", status="completed"),
|
||||||
|
Ticket(id="2", description="desc 2", status="completed")
|
||||||
|
]
|
||||||
|
results = calculate_track_progress(tickets)
|
||||||
|
assert results["total"] == 2
|
||||||
|
assert results["percentage"] == 100.0
|
||||||
|
assert results["completed"] == 2
|
||||||
|
|
||||||
Reference in New Issue
Block a user