Private
Public Access
0
0
Files
manual_slop/src/conductor_tech_lead.py
T
ed 8f11340b38 refactor(consumers): migrate 85 'from src.models import' sites to direct subsystem imports
Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7). Each
'from src.models import X' for a moved class is rewritten to
'from src.<destination> import X':

  Ticket, Track, WorkerContext, TrackState, TrackMetadata,
    ThinkingSegment, EMPTY_TRACK_STATE            -> src.mma
  ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
    ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT -> src.project
  FileItem, Preset, ContextPreset, ContextFileEntry,
    NamedViewPreset                                -> src.project_files
  Tool, ToolPreset                                 -> src.tool_presets
  BiasProfile                                      -> src.tool_bias
  TextEditorConfig, ExternalEditorConfig,
    EMPTY_TEXT_EDITOR_CONFIG                       -> src.external_editor
  Persona                                          -> src.personas
  WorkspaceProfile                                -> src.workspace_manager
  MCPServerConfig, MCPConfiguration, VectorStoreConfig,
    RAGConfig, load_mcp_config                      -> src.mcp_client

NOT touched (kept on src.models; Phase 3 or Phase 4 will move them):
  GenerateRequest, ConfirmRequest, DEFAULT_TOOL_CATEGORIES, Metadata, PROVIDERS

Migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_imports.py
which uses a class-to-module map and re.sub() to rewrite each
'from src.models import X' line.

Total: 85 import lines rewritten across 71 files.

Note: this commit depends on the v2 SHIPPED work
(origin/tier2/module_taxonomy_refactor_20260627) being merged into
this branch NEXT. On master (without the v2 SHIPPED commits), the
destination modules do not exist and these imports would fail.
2026-06-26 13:34:03 -04:00

128 lines
5.7 KiB
Python

"""
Conductor Tech Lead - Tier 2 ticket generation for MMA orchestration.
This module implements the Tier 2 (Tech Lead) function for generating implementation tickets from track briefs.
It uses the LLM to analyze the track requirements and produce structured ticket definitions.
Architecture:
- Uses ai_client.send() for LLM communication
- Uses mma_prompts.PROMPTS["tier2_sprint_planning"] for system prompt
- Returns JSON array of ticket definitions
Ticket Format:
Each ticket is a dict with:
- id: Unique identifier
- description: Task description
- depends_on: List of dependency ticket IDs
- step_mode: Whether to pause for approval between steps
Dependencies:
- Uses TrackDAG from dag_engine.py for topological sorting
- Uses Ticket from models.py for validation
Error Handling:
- Retries JSON parsing errors up to 3 times
- Raises RuntimeError if all retries fail
Thread Safety:
- NOT thread-safe. Should only be called from the main GUI thread.
- Modifies ai_client state (custom_system_prompt, current_tier)
See Also:
- docs/guide_mma.md for MMA orchestration documentation
- src/mma_prompts.py for Tier-specific prompts
- src/dag_engine.py for TrackDAG
"""
import json
import re
from typing import Any
from src import ai_client
from src import mma_prompts
def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str, Any]]:
"""
Tier 2 (Tech Lead) call.
Breaks down a Track Brief and module skeletons into discrete Tier 3 Tickets.
[C: tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_failure, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_success, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_success, tests/test_orchestration_logic.py:test_generate_tickets]
"""
# 1. Set Tier 2 Model (Tech Lead - Flash)
# 2. Construct Prompt
system_prompt = mma_prompts.PROMPTS.get("tier2_sprint_planning")
user_message = (
f"### TRACK BRIEF:\n{track_brief}\n\n"
f"### MODULE SKELETONS:\n{module_skeletons}\n\n"
"Please generate the implementation tickets for this track."
)
# Set custom system prompt for this call
old_system_prompt = ai_client._custom_system_prompt
ai_client.set_custom_system_prompt(system_prompt or "")
ai_client.set_current_tier("Tier 2")
last_error = None
try:
for _ in range(3):
try:
# 3. Call Tier 2 Model
result = ai_client.send(
md_content = "",
user_message = user_message
)
if not result.ok:
_err = result.errors[0] if result.errors else None
_msg = _err.ui_message() if _err else "unknown error"
print(f"[conductor_tech_lead] send failed: {_msg}")
return None
response = result.data
# 4. Parse JSON Output
# Extract JSON array from markdown code blocks if present
json_match = response.strip()
if "```json" in json_match:
json_match = json_match.split("```json")[1].split("```")[0].strip()
elif "```" in json_match:
json_match = json_match.split("```")[1].split("```")[0].strip()
# If it's still not valid JSON, try to find a [ ... ] block
if not (json_match.startswith('[') and json_match.endswith(']')):
match = re.search(r'\[\s*\{.*\}\s*\]', json_match, re.DOTALL)
if match:
json_match = match.group(0)
tickets: list[dict[str, Any]] = json.loads(json_match)
return tickets
except json.JSONDecodeError as e:
last_error = e
correction = f"\n\nYour previous output failed to parse as JSON: {e}. Here was your raw output:\n{json_match[:500]}\n\nPlease fix the formatting and output ONLY valid JSON array."
user_message += correction
print(f"JSON parsing error, retrying... ({_ + 1}/3)")
raise RuntimeError(f"Failed to generate valid JSON tickets after 3 attempts. Last error: {last_error}")
finally:
# Restore old system prompt and clear tier tag
ai_client.set_custom_system_prompt(old_system_prompt or "")
ai_client.set_current_tier(None)
from src.dag_engine import TrackDAG
from src.mma import Ticket
from src.result_types import ErrorInfo, ErrorKind, Result
def topological_sort(tickets: list[Ticket]) -> list[Ticket]:
"""
Sorts a list of Ticket objects based on their depends_on field.
Raises ValueError if a circular dependency or missing internal dependency is detected.
[C: tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_complex, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_cycle, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_empty, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_linear, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_missing_dependency, tests/test_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_dag_engine.py:test_topological_sort, tests/test_dag_engine.py:test_topological_sort_cycle, tests/test_orchestration_logic.py:test_topological_sort, tests/test_orchestration_logic.py:test_topological_sort_circular, tests/test_perf_dag.py:test_dag_edge_cases, tests/test_perf_dag.py:test_dag_performance]
"""
dag = TrackDAG(tickets)
try:
sorted_ids = dag.topological_sort()
except ValueError as e:
_dag_err = Result(data=None, errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"DAG Validation Error: {e}", source="conductor_tech_lead.topological_sort", original=e)])
raise ValueError(f"DAG Validation Error: {e}")
ticket_map = {t.id: t for t in tickets}
return [ticket_map[tid] for tid in sorted_ids]
if __name__ == "__main__":
# Quick test if run directly
test_brief = "Implement a new feature."
test_skeletons = "class NewFeature: pass"
tickets = generate_tickets(test_brief, test_skeletons)
print(json.dumps(tickets, indent=2))