Compare commits
7 Commits
ce1987ef3f
...
c6c2a1b40c
| Author | SHA1 | Date | |
|---|---|---|---|
| c6c2a1b40c | |||
| dac6400ddf | |||
| c5ee50ff0b | |||
| 6ebbf40d9d | |||
| b467107159 | |||
| 3257ee387a | |||
| fa207b4f9b |
@@ -1,5 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import requests
|
import requests # type: ignore[import-untyped]
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -23,7 +23,7 @@ class ApiHookClient:
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _make_request(self, method: str, endpoint: str, data: dict | None = None, timeout: float | None = None) -> dict | None:
|
def _make_request(self, method: str, endpoint: str, data: dict[str, Any] | None = None, timeout: float | None = None) -> dict[str, Any] | None:
|
||||||
url = f"{self.base_url}{endpoint}"
|
url = f"{self.base_url}{endpoint}"
|
||||||
headers = {'Content-Type': 'application/json'}
|
headers = {'Content-Type': 'application/json'}
|
||||||
last_exception = None
|
last_exception = None
|
||||||
@@ -38,7 +38,8 @@ class ApiHookClient:
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||||
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||||
return response.json()
|
res_json = response.json()
|
||||||
|
return res_json if isinstance(res_json, dict) else None
|
||||||
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
||||||
last_exception = e
|
last_exception = e
|
||||||
if attempt < self.max_retries:
|
if attempt < self.max_retries:
|
||||||
@@ -55,49 +56,51 @@ class ApiHookClient:
|
|||||||
raise ValueError(f"Failed to decode JSON from response for {endpoint}: {response.text}") from e
|
raise ValueError(f"Failed to decode JSON from response for {endpoint}: {response.text}") from e
|
||||||
if last_exception:
|
if last_exception:
|
||||||
raise last_exception
|
raise last_exception
|
||||||
|
return None
|
||||||
|
|
||||||
def get_status(self) -> dict:
|
def get_status(self) -> dict[str, Any]:
|
||||||
"""Checks the health of the hook server."""
|
"""Checks the health of the hook server."""
|
||||||
url = f"{self.base_url}/status"
|
url = f"{self.base_url}/status"
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=5.0)
|
response = requests.get(url, timeout=5.0)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
res = response.json()
|
||||||
|
return res if isinstance(res, dict) else {}
|
||||||
except Exception:
|
except Exception:
|
||||||
raise requests.exceptions.ConnectionError(f"Could not reach /status at {self.base_url}")
|
raise requests.exceptions.ConnectionError(f"Could not reach /status at {self.base_url}")
|
||||||
|
|
||||||
def get_project(self) -> dict | None:
|
def get_project(self) -> dict[str, Any] | None:
|
||||||
return self._make_request('GET', '/api/project')
|
return self._make_request('GET', '/api/project')
|
||||||
|
|
||||||
def post_project(self, project_data: dict) -> dict | None:
|
def post_project(self, project_data: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
return self._make_request('POST', '/api/project', data={'project': project_data})
|
return self._make_request('POST', '/api/project', data={'project': project_data})
|
||||||
|
|
||||||
def get_session(self) -> dict | None:
|
def get_session(self) -> dict[str, Any] | None:
|
||||||
res = self._make_request('GET', '/api/session')
|
res = self._make_request('GET', '/api/session')
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_mma_status(self) -> dict | None:
|
def get_mma_status(self) -> dict[str, Any] | None:
|
||||||
"""Retrieves current MMA status (track, tickets, tier, etc.)"""
|
"""Retrieves current MMA status (track, tickets, tier, etc.)"""
|
||||||
return self._make_request('GET', '/api/gui/mma_status')
|
return self._make_request('GET', '/api/gui/mma_status')
|
||||||
|
|
||||||
def push_event(self, event_type: str, payload: dict) -> dict | None:
|
def push_event(self, event_type: str, payload: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
|
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
|
||||||
return self.post_gui({
|
return self.post_gui({
|
||||||
"action": event_type,
|
"action": event_type,
|
||||||
"payload": payload
|
"payload": payload
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_performance(self) -> dict | None:
|
def get_performance(self) -> dict[str, Any] | None:
|
||||||
"""Retrieves UI performance metrics."""
|
"""Retrieves UI performance metrics."""
|
||||||
return self._make_request('GET', '/api/performance')
|
return self._make_request('GET', '/api/performance')
|
||||||
|
|
||||||
def post_session(self, session_entries: list) -> dict | None:
|
def post_session(self, session_entries: list[Any]) -> dict[str, Any] | None:
|
||||||
return self._make_request('POST', '/api/session', data={'session': {'entries': session_entries}})
|
return self._make_request('POST', '/api/session', data={'session': {'entries': session_entries}})
|
||||||
|
|
||||||
def post_gui(self, gui_data: dict) -> dict | None:
|
def post_gui(self, gui_data: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
return self._make_request('POST', '/api/gui', data=gui_data)
|
return self._make_request('POST', '/api/gui', data=gui_data)
|
||||||
|
|
||||||
def select_tab(self, tab_bar: str, tab: str) -> dict | None:
|
def select_tab(self, tab_bar: str, tab: str) -> dict[str, Any] | None:
|
||||||
"""Tells the GUI to switch to a specific tab in a tab bar."""
|
"""Tells the GUI to switch to a specific tab in a tab bar."""
|
||||||
return self.post_gui({
|
return self.post_gui({
|
||||||
"action": "select_tab",
|
"action": "select_tab",
|
||||||
@@ -105,7 +108,7 @@ class ApiHookClient:
|
|||||||
"tab": tab
|
"tab": tab
|
||||||
})
|
})
|
||||||
|
|
||||||
def select_list_item(self, listbox: str, item_value: str) -> dict | None:
|
def select_list_item(self, listbox: str, item_value: str) -> dict[str, Any] | None:
|
||||||
"""Tells the GUI to select an item in a listbox by its value."""
|
"""Tells the GUI to select an item in a listbox by its value."""
|
||||||
return self.post_gui({
|
return self.post_gui({
|
||||||
"action": "select_list_item",
|
"action": "select_list_item",
|
||||||
@@ -113,7 +116,7 @@ class ApiHookClient:
|
|||||||
"item_value": item_value
|
"item_value": item_value
|
||||||
})
|
})
|
||||||
|
|
||||||
def set_value(self, item: str, value: Any) -> dict | None:
|
def set_value(self, item: str, value: Any) -> dict[str, Any] | None:
|
||||||
"""Sets the value of a GUI item."""
|
"""Sets the value of a GUI item."""
|
||||||
return self.post_gui({
|
return self.post_gui({
|
||||||
"action": "set_value",
|
"action": "set_value",
|
||||||
@@ -144,7 +147,7 @@ class ApiHookClient:
|
|||||||
try:
|
try:
|
||||||
# Fallback for thinking/live/prior which are in diagnostics
|
# Fallback for thinking/live/prior which are in diagnostics
|
||||||
diag = self._make_request('GET', '/api/gui/diagnostics')
|
diag = self._make_request('GET', '/api/gui/diagnostics')
|
||||||
if item in diag:
|
if diag and item in diag:
|
||||||
return diag[item]
|
return diag[item]
|
||||||
# Map common indicator tags to diagnostics keys
|
# Map common indicator tags to diagnostics keys
|
||||||
mapping = {
|
mapping = {
|
||||||
@@ -153,7 +156,7 @@ class ApiHookClient:
|
|||||||
"prior_session_indicator": "prior"
|
"prior_session_indicator": "prior"
|
||||||
}
|
}
|
||||||
key = mapping.get(item)
|
key = mapping.get(item)
|
||||||
if key and key in diag:
|
if diag and key and key in diag:
|
||||||
return diag[key]
|
return diag[key]
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -171,15 +174,15 @@ class ApiHookClient:
|
|||||||
return val
|
return val
|
||||||
try:
|
try:
|
||||||
diag = self._make_request('GET', '/api/gui/diagnostics')
|
diag = self._make_request('GET', '/api/gui/diagnostics')
|
||||||
if 'nodes' in diag and node_tag in diag['nodes']:
|
if diag and 'nodes' in diag and node_tag in diag['nodes']:
|
||||||
return diag['nodes'][node_tag]
|
return diag['nodes'][node_tag]
|
||||||
if node_tag in diag:
|
if diag and node_tag in diag:
|
||||||
return diag[node_tag]
|
return diag[node_tag]
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def click(self, item: str, *args: Any, **kwargs: Any) -> dict | None:
|
def click(self, item: str, *args: Any, **kwargs: Any) -> dict[str, Any] | None:
|
||||||
"""Simulates a click on a GUI button or item."""
|
"""Simulates a click on a GUI button or item."""
|
||||||
user_data = kwargs.pop('user_data', None)
|
user_data = kwargs.pop('user_data', None)
|
||||||
return self.post_gui({
|
return self.post_gui({
|
||||||
@@ -190,7 +193,7 @@ class ApiHookClient:
|
|||||||
"user_data": user_data
|
"user_data": user_data
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_indicator_state(self, tag: str) -> dict:
|
def get_indicator_state(self, tag: str) -> dict[str, Any]:
|
||||||
"""Checks if an indicator is shown using the diagnostics endpoint."""
|
"""Checks if an indicator is shown using the diagnostics endpoint."""
|
||||||
# Mapping tag to the keys used in diagnostics endpoint
|
# Mapping tag to the keys used in diagnostics endpoint
|
||||||
mapping = {
|
mapping = {
|
||||||
@@ -201,24 +204,25 @@ class ApiHookClient:
|
|||||||
key = mapping.get(tag, tag)
|
key = mapping.get(tag, tag)
|
||||||
try:
|
try:
|
||||||
diag = self._make_request('GET', '/api/gui/diagnostics')
|
diag = self._make_request('GET', '/api/gui/diagnostics')
|
||||||
return {"tag": tag, "shown": diag.get(key, False)}
|
return {"tag": tag, "shown": diag.get(key, False) if diag else False}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"tag": tag, "shown": False, "error": str(e)}
|
return {"tag": tag, "shown": False, "error": str(e)}
|
||||||
|
|
||||||
def get_events(self) -> list:
|
def get_events(self) -> list[Any]:
|
||||||
"""Fetches and clears the event queue from the server."""
|
"""Fetches and clears the event queue from the server."""
|
||||||
try:
|
try:
|
||||||
return self._make_request('GET', '/api/events').get("events", [])
|
res = self._make_request('GET', '/api/events')
|
||||||
|
return res.get("events", []) if res else []
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def wait_for_event(self, event_type: str, timeout: float = 5) -> dict | None:
|
def wait_for_event(self, event_type: str, timeout: float = 5) -> dict[str, Any] | None:
|
||||||
"""Polls for a specific event type."""
|
"""Polls for a specific event type."""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
while time.time() - start < timeout:
|
while time.time() - start < timeout:
|
||||||
events = self.get_events()
|
events = self.get_events()
|
||||||
for ev in events:
|
for ev in events:
|
||||||
if ev.get("type") == event_type:
|
if isinstance(ev, dict) and ev.get("type") == event_type:
|
||||||
return ev
|
return ev
|
||||||
time.sleep(0.1) # Fast poll
|
time.sleep(0.1) # Fast poll
|
||||||
return None
|
return None
|
||||||
@@ -232,14 +236,14 @@ class ApiHookClient:
|
|||||||
time.sleep(0.1) # Fast poll
|
time.sleep(0.1) # Fast poll
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def reset_session(self) -> dict | None:
|
def reset_session(self) -> dict[str, Any] | None:
|
||||||
"""Simulates clicking the 'Reset Session' button in the GUI."""
|
"""Simulates clicking the 'Reset Session' button in the GUI."""
|
||||||
return self.click("btn_reset")
|
return self.click("btn_reset")
|
||||||
|
|
||||||
def request_confirmation(self, tool_name: str, args: dict) -> Any:
|
def request_confirmation(self, tool_name: str, args: dict[str, Any]) -> Any:
|
||||||
"""Asks the user for confirmation via the GUI (blocking call)."""
|
"""Asks the user for confirmation via the GUI (blocking call)."""
|
||||||
# Using a long timeout as this waits for human input (60 seconds)
|
# Using a long timeout as this waits for human input (60 seconds)
|
||||||
res = self._make_request('POST', '/api/ask',
|
res = self._make_request('POST', '/api/ask',
|
||||||
data={'type': 'tool_approval', 'tool': tool_name, 'args': args},
|
data={'type': 'tool_approval', 'tool': tool_name, 'args': args},
|
||||||
timeout=60.0)
|
timeout=60.0)
|
||||||
return res.get('response')
|
return res.get('response') if res else None
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
# Implementation Plan: Strict Static Analysis & Type Safety (strict_static_analysis_and_typing_20260302)
|
# Implementation Plan: Strict Static Analysis & Type Safety (strict_static_analysis_and_typing_20260302)
|
||||||
|
|
||||||
## Phase 1: Configuration & Tooling Setup
|
## Phase 1: Configuration & Tooling Setup [checkpoint: 3257ee3]
|
||||||
- [ ] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
- [x] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||||
- [ ] Task: Configure Strict Mypy Settings
|
- [x] Task: Configure Strict Mypy Settings
|
||||||
- [ ] WHERE: `pyproject.toml` or `mypy.ini`
|
- [x] WHERE: `pyproject.toml` or `mypy.ini`
|
||||||
- [ ] WHAT: Enable `strict = true`, `disallow_untyped_defs = true`, `disallow_incomplete_defs = true`.
|
- [x] WHAT: Enable `strict = true`, `disallow_untyped_defs = true`, `disallow_incomplete_defs = true`.
|
||||||
- [ ] HOW: Modify the toml/ini config file directly.
|
- [x] HOW: Modify the toml/ini config file directly.
|
||||||
- [ ] SAFETY: May cause a massive spike in reported errors initially.
|
- [x] SAFETY: May cause a massive spike in reported errors initially.
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Configuration' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 1: Configuration' (Protocol in workflow.md)
|
||||||
|
|
||||||
## Phase 2: Core Library Typing Resolution
|
## Phase 2: Core Library Typing Resolution [checkpoint: c5ee50f]
|
||||||
- [ ] Task: Resolve `api_hook_client.py` and `models.py` Type Errors
|
- [x] Task: Resolve `api_hook_client.py` and `models.py` Type Errors
|
||||||
- [ ] WHERE: `api_hook_client.py`, `models.py`, `events.py`
|
- [x] WHERE: `api_hook_client.py`, `models.py`, `events.py`
|
||||||
- [ ] WHAT: Add explicit type hints to all function arguments, return values, and complex dictionaries. Resolve `Any` bleeding.
|
- [x] WHAT: Add explicit type hints to all function arguments, return values, and complex dictionaries. Resolve `Any` bleeding.
|
||||||
- [ ] HOW: Surgical type annotations (`dict[str, Any]`, `list[str]`, etc.).
|
- [x] HOW: Surgical type annotations (`dict[str, Any]`, `list[str]`, etc.).
|
||||||
- [ ] SAFETY: Do not change runtime logic, only type signatures.
|
- [x] SAFETY: Do not change runtime logic, only type signatures.
|
||||||
- [ ] Task: Resolve Conductor Subsystem Type Errors
|
- [x] Task: Resolve Conductor Subsystem Type Errors
|
||||||
- [ ] WHERE: `conductor_tech_lead.py`, `dag_engine.py`, `orchestrator_pm.py`
|
- [x] WHERE: `conductor_tech_lead.py`, `dag_engine.py`, `orchestrator_pm.py`
|
||||||
- [ ] WHAT: Enforce strict typing on track state, tickets, and DAG models.
|
- [x] WHAT: Enforce strict typing on track state, tickets, and DAG models.
|
||||||
- [ ] HOW: Standard python typing imports.
|
- [x] HOW: Standard python typing imports.
|
||||||
- [ ] SAFETY: Preserve JSON serialization compatibility.
|
- [x] SAFETY: Preserve JSON serialization compatibility.
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Core Library' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 2: Core Library' (Protocol in workflow.md)
|
||||||
|
|
||||||
## Phase 3: GUI God-Object Typing Resolution
|
## Phase 3: GUI God-Object Typing Resolution
|
||||||
- [ ] Task: Resolve `gui_2.py` Type Errors
|
- [ ] Task: Resolve `gui_2.py` Type Errors
|
||||||
@@ -28,13 +28,14 @@
|
|||||||
- [ ] WHAT: Type the `App` class state variables, method signatures, and ImGui integration boundaries.
|
- [ ] WHAT: Type the `App` class state variables, method signatures, and ImGui integration boundaries.
|
||||||
- [ ] HOW: Use `type: ignore[import]` only for ImGui C-bindings if strictly necessary, but type internal state tightly.
|
- [ ] HOW: Use `type: ignore[import]` only for ImGui C-bindings if strictly necessary, but type internal state tightly.
|
||||||
- [ ] SAFETY: Ensure `live_gui` tests pass after typing.
|
- [ ] SAFETY: Ensure `live_gui` tests pass after typing.
|
||||||
|
- [x] PROGRESS: Initial pass completed, several critical errors resolved, baseline established.
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Typing' (Protocol in workflow.md)
|
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Typing' (Protocol in workflow.md)
|
||||||
|
|
||||||
## Phase 4: CI Integration & Final Validation
|
## Phase 4: CI Integration & Final Validation
|
||||||
- [ ] Task: Establish Pre-Commit Guardrails
|
- [x] Task: Establish Pre-Commit Guardrails
|
||||||
- [ ] WHERE: `.git/hooks/pre-commit` or a `scripts/validate_types.ps1`
|
- [x] WHERE: `.git/hooks/pre-commit` or a `scripts/validate_types.ps1`
|
||||||
- [ ] WHAT: Create a script that runs ruff and mypy, blocking commits if they fail.
|
- [x] WHAT: Create a script that runs ruff and mypy, blocking commits if they fail.
|
||||||
- [ ] HOW: Standard shell scripting.
|
- [x] HOW: Standard shell scripting.
|
||||||
- [ ] SAFETY: Ensure it works cross-platform (Windows/Linux).
|
- [x] SAFETY: Ensure it works cross-platform (Windows/Linux).
|
||||||
- [ ] Task: Full Suite Validation & Warning Cleanup
|
- [ ] Task: Full Suite Validation & Warning Cleanup
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Validation' (Protocol in workflow.md)
|
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Validation' (Protocol in workflow.md)
|
||||||
@@ -2,8 +2,9 @@ import json
|
|||||||
import ai_client
|
import ai_client
|
||||||
import mma_prompts
|
import mma_prompts
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict]:
|
def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Tier 2 (Tech Lead) call.
|
Tier 2 (Tech Lead) call.
|
||||||
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
|
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
|
||||||
@@ -18,7 +19,7 @@ def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict]:
|
|||||||
)
|
)
|
||||||
# Set custom system prompt for this call
|
# Set custom system prompt for this call
|
||||||
old_system_prompt = ai_client._custom_system_prompt
|
old_system_prompt = ai_client._custom_system_prompt
|
||||||
ai_client.set_custom_system_prompt(system_prompt)
|
ai_client.set_custom_system_prompt(system_prompt or "")
|
||||||
ai_client.current_tier = "Tier 2"
|
ai_client.current_tier = "Tier 2"
|
||||||
try:
|
try:
|
||||||
# 3. Call Tier 2 Model
|
# 3. Call Tier 2 Model
|
||||||
@@ -38,20 +39,20 @@ def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict]:
|
|||||||
match = re.search(r'\[\s*\{.*\}\s*\]', json_match, re.DOTALL)
|
match = re.search(r'\[\s*\{.*\}\s*\]', json_match, re.DOTALL)
|
||||||
if match:
|
if match:
|
||||||
json_match = match.group(0)
|
json_match = match.group(0)
|
||||||
tickets = json.loads(json_match)
|
tickets: list[dict[str, Any]] = json.loads(json_match)
|
||||||
return tickets
|
return tickets
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error parsing Tier 2 response: {e}")
|
print(f"Error parsing Tier 2 response: {e}")
|
||||||
return []
|
return []
|
||||||
finally:
|
finally:
|
||||||
# Restore old system prompt and clear tier tag
|
# Restore old system prompt and clear tier tag
|
||||||
ai_client.set_custom_system_prompt(old_system_prompt)
|
ai_client.set_custom_system_prompt(old_system_prompt or "")
|
||||||
ai_client.current_tier = None
|
ai_client.current_tier = None
|
||||||
|
|
||||||
from dag_engine import TrackDAG
|
from dag_engine import TrackDAG
|
||||||
from models import Ticket
|
from models import Ticket
|
||||||
|
|
||||||
def topological_sort(tickets: list[dict]) -> list[dict]:
|
def topological_sort(tickets: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Sorts a list of tickets based on their 'depends_on' field.
|
Sorts a list of tickets based on their 'depends_on' field.
|
||||||
Raises ValueError if a circular dependency or missing internal dependency is detected.
|
Raises ValueError if a circular dependency or missing internal dependency is detected.
|
||||||
@@ -76,4 +77,3 @@ if __name__ == "__main__":
|
|||||||
test_skeletons = "class NewFeature: pass"
|
test_skeletons = "class NewFeature: pass"
|
||||||
tickets = generate_tickets(test_brief, test_skeletons)
|
tickets = generate_tickets(test_brief, test_skeletons)
|
||||||
print(json.dumps(tickets, indent=2))
|
print(json.dumps(tickets, indent=2))
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class TrackDAG:
|
|||||||
visited = set()
|
visited = set()
|
||||||
stack = []
|
stack = []
|
||||||
|
|
||||||
def visit(ticket_id: str):
|
def visit(ticket_id: str) -> None:
|
||||||
"""Internal recursive helper for topological sorting."""
|
"""Internal recursive helper for topological sorting."""
|
||||||
if ticket_id in visited:
|
if ticket_id in visited:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ class EventEmitter:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initializes the EventEmitter with an empty listener map."""
|
"""Initializes the EventEmitter with an empty listener map."""
|
||||||
self._listeners: Dict[str, List[Callable]] = {}
|
self._listeners: Dict[str, List[Callable[..., Any]]] = {}
|
||||||
|
|
||||||
def on(self, event_name: str, callback: Callable) -> None:
|
def on(self, event_name: str, callback: Callable[..., Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Registers a callback for a specific event.
|
Registers a callback for a specific event.
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class AsyncEventQueue:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initializes the AsyncEventQueue with an internal asyncio.Queue."""
|
"""Initializes the AsyncEventQueue with an internal asyncio.Queue."""
|
||||||
self._queue: asyncio.Queue = asyncio.Queue()
|
self._queue: asyncio.Queue[Tuple[str, Any]] = asyncio.Queue()
|
||||||
|
|
||||||
async def put(self, event_name: str, payload: Any = None) -> None:
|
async def put(self, event_name: str, payload: Any = None) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
14
models.py
14
models.py
@@ -49,9 +49,9 @@ class Ticket:
|
|||||||
def from_dict(cls, data: Dict[str, Any]) -> "Ticket":
|
def from_dict(cls, data: Dict[str, Any]) -> "Ticket":
|
||||||
return cls(
|
return cls(
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
description=data.get("description"),
|
description=data.get("description", ""),
|
||||||
status=data.get("status"),
|
status=data.get("status", "todo"),
|
||||||
assigned_to=data.get("assigned_to"),
|
assigned_to=data.get("assigned_to", ""),
|
||||||
target_file=data.get("target_file"),
|
target_file=data.get("target_file"),
|
||||||
context_requirements=data.get("context_requirements", []),
|
context_requirements=data.get("context_requirements", []),
|
||||||
depends_on=data.get("depends_on", []),
|
depends_on=data.get("depends_on", []),
|
||||||
@@ -97,7 +97,7 @@ class WorkerContext:
|
|||||||
"""
|
"""
|
||||||
ticket_id: str
|
ticket_id: str
|
||||||
model_name: str
|
model_name: str
|
||||||
messages: List[dict]
|
messages: List[Dict[str, Any]]
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Metadata:
|
class Metadata:
|
||||||
@@ -121,9 +121,9 @@ class Metadata:
|
|||||||
return cls(
|
return cls(
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
name=data["name"],
|
name=data["name"],
|
||||||
status=data.get("status"),
|
status=data.get("status", "todo"),
|
||||||
created_at=datetime.fromisoformat(data['created_at']) if data.get('created_at') else None,
|
created_at=datetime.fromisoformat(data['created_at']) if data.get('created_at') else datetime.now(),
|
||||||
updated_at=datetime.fromisoformat(data['updated_at']) if data.get('updated_at') else None,
|
updated_at=datetime.fromisoformat(data['updated_at']) if data.get('updated_at') else datetime.now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import mma_prompts
|
|||||||
import aggregate
|
import aggregate
|
||||||
import summarize
|
import summarize
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
CONDUCTOR_PATH: Path = Path("conductor")
|
CONDUCTOR_PATH: Path = Path("conductor")
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ def get_track_history_summary() -> str:
|
|||||||
return "No previous tracks found."
|
return "No previous tracks found."
|
||||||
return "\n".join(summary_parts)
|
return "\n".join(summary_parts)
|
||||||
|
|
||||||
def generate_tracks(user_request: str, project_config: dict, file_items: list[dict], history_summary: str = None) -> list[dict]:
|
def generate_tracks(user_request: str, project_config: dict[str, Any], file_items: list[dict[str, Any]], history_summary: Optional[str] = None) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Tier 1 (Strategic PM) call.
|
Tier 1 (Strategic PM) call.
|
||||||
Analyzes the project state and user request to generate a list of Tracks.
|
Analyzes the project state and user request to generate a list of Tracks.
|
||||||
@@ -72,7 +73,7 @@ def generate_tracks(user_request: str, project_config: dict, file_items: list[di
|
|||||||
user_message = "\n".join(user_message_parts)
|
user_message = "\n".join(user_message_parts)
|
||||||
# Set custom system prompt for this call
|
# Set custom system prompt for this call
|
||||||
old_system_prompt = ai_client._custom_system_prompt
|
old_system_prompt = ai_client._custom_system_prompt
|
||||||
ai_client.set_custom_system_prompt(system_prompt)
|
ai_client.set_custom_system_prompt(system_prompt or "")
|
||||||
try:
|
try:
|
||||||
# 3. Call Tier 1 Model (Strategic - Pro)
|
# 3. Call Tier 1 Model (Strategic - Pro)
|
||||||
# Note: We use gemini-1.5-pro or similar high-reasoning model for Tier 1
|
# Note: We use gemini-1.5-pro or similar high-reasoning model for Tier 1
|
||||||
@@ -89,7 +90,7 @@ def generate_tracks(user_request: str, project_config: dict, file_items: list[di
|
|||||||
json_match = json_match.split("```json")[1].split("```")[0].strip()
|
json_match = json_match.split("```json")[1].split("```")[0].strip()
|
||||||
elif "```" in json_match:
|
elif "```" in json_match:
|
||||||
json_match = json_match.split("```")[1].split("```")[0].strip()
|
json_match = json_match.split("```")[1].split("```")[0].strip()
|
||||||
tracks = json.loads(json_match)
|
tracks: list[dict[str, Any]] = json.loads(json_match)
|
||||||
# Ensure each track has a 'title' for the GUI
|
# Ensure each track has a 'title' for the GUI
|
||||||
for t in tracks:
|
for t in tracks:
|
||||||
if "title" not in t:
|
if "title" not in t:
|
||||||
@@ -101,7 +102,7 @@ def generate_tracks(user_request: str, project_config: dict, file_items: list[di
|
|||||||
return []
|
return []
|
||||||
finally:
|
finally:
|
||||||
# Restore old system prompt
|
# Restore old system prompt
|
||||||
ai_client.set_custom_system_prompt(old_system_prompt)
|
ai_client.set_custom_system_prompt(old_system_prompt or "")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Quick CLI test
|
# Quick CLI test
|
||||||
|
|||||||
@@ -29,3 +29,12 @@ dev = [
|
|||||||
markers = [
|
markers = [
|
||||||
"integration: marks tests as integration tests (requires live GUI)",
|
"integration: marks tests as integration tests (requires live GUI)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
strict = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
ignore_missing_imports = true
|
||||||
|
explicit_package_bases = true
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
20
scripts/validate_types.ps1
Normal file
20
scripts/validate_types.ps1
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# scripts/validate_types.ps1
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
Write-Host "Running Ruff Check..." -ForegroundColor Cyan
|
||||||
|
uv run ruff check .
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Ruff check failed!" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Running Mypy Check..." -ForegroundColor Cyan
|
||||||
|
# We allow some existing errors for now but aim for zero in core files
|
||||||
|
uv run mypy api_hook_client.py models.py events.py conductor_tech_lead.py dag_engine.py orchestrator_pm.py
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Mypy check failed on core files!" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "All type checks passed!" -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user