diff --git a/skills/scripts/skills/lib/workflow/ast/dispatch_renderer.py b/skills/scripts/skills/lib/workflow/ast/dispatch_renderer.py
index 9f249c8..ef42907 100644
--- a/skills/scripts/skills/lib/workflow/ast/dispatch_renderer.py
+++ b/skills/scripts/skills/lib/workflow/ast/dispatch_renderer.py
@@ -143,7 +143,7 @@ def render_subagent_dispatch(node: SubagentDispatchNode) -> str:
# Wrap invoke in directive to signal immediate execution
lines.append(' ')
- lines.append(f' ')
+ lines.append(f' ')
lines.append(' ')
lines.append("")
@@ -198,7 +198,7 @@ def render_template_dispatch(node: TemplateDispatchNode) -> str:
lines.append(f" {prompt_line}" if prompt_line else "")
lines.append(" ")
- lines.append(f' ')
+ lines.append(f' ')
lines.append(" ")
lines.append(" ")
@@ -257,7 +257,7 @@ def render_roster_dispatch(node: RosterDispatchNode) -> str:
for task_line in agent_prompt.split("\n"):
lines.append(f" {task_line}" if task_line else "")
lines.append(" ")
- lines.append(f' ')
+ lines.append(f' ')
lines.append(" ")
lines.append(" ")
diff --git a/skills/scripts/skills/lib/workflow/ast/renderer.py b/skills/scripts/skills/lib/workflow/ast/renderer.py
index 69909b9..00bd9ed 100644
--- a/skills/scripts/skills/lib/workflow/ast/renderer.py
+++ b/skills/scripts/skills/lib/workflow/ast/renderer.py
@@ -85,11 +85,11 @@ def render_invoke_after(self, node: InvokeAfterNode) -> str:
Renderer assumes valid node, focuses solely on XML generation.
"""
if node.cmd is not None:
- invoke = f''
+ invoke = f''
return f"\n{invoke}\n"
else:
- if_pass_invoke = f''
- if_fail_invoke = f''
+ if_pass_invoke = f''
+ if_fail_invoke = f''
return f"\n \n {if_pass_invoke}\n \n \n {if_fail_invoke}\n \n"
def _render_node(self, node: Node) -> str:
diff --git a/skills/scripts/skills/lib/workflow/prompts/step.py b/skills/scripts/skills/lib/workflow/prompts/step.py
index f149395..d39e420 100644
--- a/skills/scripts/skills/lib/workflow/prompts/step.py
+++ b/skills/scripts/skills/lib/workflow/prompts/step.py
@@ -35,8 +35,8 @@ def format_step(body: str, next_cmd: str = "", title: str = "",
invoke = (
f"NEXT STEP (MANDATORY -- execute exactly one):\n"
f" Working directory: {SKILLS_DIR}\n"
- f" ALL agents returned PASS -> {if_pass}\n"
- f" ANY agent returned FAIL -> {if_fail}\n\n"
+ f" ALL agents returned PASS -> cd {SKILLS_DIR} && {if_pass}\n"
+ f" ANY agent returned FAIL -> cd {SKILLS_DIR} && {if_fail}\n\n"
f"This is a mechanical routing decision. Do not interpret, summarize, "
f"or assess the results.\n"
f"Count PASS vs FAIL, then execute the matching command."
@@ -45,11 +45,11 @@ def format_step(body: str, next_cmd: str = "", title: str = "",
elif next_cmd:
# Working directory is explicit because CLI execution context varies.
- # Command is literal shell invocation for next step.
+ # Command is self-contained with cd prefix to avoid working directory issues.
invoke = (
f"NEXT STEP:\n"
f" Working directory: {SKILLS_DIR}\n"
- f" Command: {next_cmd}\n\n"
+ f" Command: cd {SKILLS_DIR} && {next_cmd}\n\n"
f"Execute this command now."
)
return f"{body}\n\n{invoke}"
diff --git a/skills/scripts/skills/lib/workflow/prompts/subagent.py b/skills/scripts/skills/lib/workflow/prompts/subagent.py
index 0567313..3afd1ee 100644
--- a/skills/scripts/skills/lib/workflow/prompts/subagent.py
+++ b/skills/scripts/skills/lib/workflow/prompts/subagent.py
@@ -43,8 +43,7 @@
FIRST ACTION REQUIRED: Run this command via Bash tool before any other response.
Do NOT skip. Do NOT produce analysis first. Execute this command IMMEDIATELY.
-Working directory: {working_dir}
-Command: {cmd}
+Command: cd {working_dir} && {cmd}
================================================================================
CRITICAL: Copy the block above EXACTLY. Do not paraphrase or summarize.
The subagent needs "FIRST ACTION REQUIRED" to know it must run the command."""
diff --git a/skills/scripts/skills/planner/developer/exec_implement_qr_fix.py b/skills/scripts/skills/planner/developer/exec_implement_qr_fix.py
index 551be16..61c4598 100644
--- a/skills/scripts/skills/planner/developer/exec_implement_qr_fix.py
+++ b/skills/scripts/skills/planner/developer/exec_implement_qr_fix.py
@@ -28,7 +28,7 @@
def get_step_guidance(
- step: int) -> dict:
+ step: int, module_path: str = None, **kwargs) -> dict:
"""Return guidance for the given step."""
MODULE_PATH = module_path or "skills.planner.developer.exec_implement_qr_fix"
state_dir = kwargs.get("state_dir", "")
diff --git a/skills/scripts/skills/planner/orchestrator/executor.py b/skills/scripts/skills/planner/orchestrator/executor.py
index 1577eb3..1fe08aa 100644
--- a/skills/scripts/skills/planner/orchestrator/executor.py
+++ b/skills/scripts/skills/planner/orchestrator/executor.py
@@ -2,32 +2,50 @@
"""
Plan Executor - Execute approved plans through delegation.
-Nine-step workflow:
- 1. Execution Planning - analyze plan, build wave list
- 2. Reconciliation - validate existing code (conditional)
- 3. Implementation - dispatch developers (wave-aware parallel)
- 4. Code QR - verify code quality (RULE 0/1/2)
+Ten-step workflow with parallel QR verification:
+ 1. Execution Planning - analyze plan, build wave list, create state_dir
+ 2. Implementation - dispatch developers (wave-aware parallel)
+ 3. Code QR Decompose - generate verification items
+ 4. Code QR Verify - parallel verification of items
5. Code QR Gate - route pass/fail
6. Documentation - TW pass
- 7. Doc QR - verify documentation quality
- 8. Doc QR Gate - route pass/fail
- 9. Retrospective - present summary
+ 7. Doc QR Decompose - generate verification items
+ 8. Doc QR Verify - parallel verification of items
+ 9. Doc QR Gate - route pass/fail
+ 10. Retrospective - present summary
+
+QR Block Pattern (matching planner's 4-step pattern per phase):
+ N work developer/TW agents Implementation or documentation
+ N+1 decompose 1 QR agent qr-{phase}.json
+ N+2 verify N QR agents (parallel) Each: PASS or FAIL
+ N+3 route 0 agents (orchestrator) Loop to N or proceed to N+4
"""
import argparse
+import json
import sys
+import tempfile
+from pathlib import Path
from skills.lib.workflow.types import AgentRole
-from skills.lib.workflow.prompts import subagent_dispatch
+from skills.lib.workflow.prompts import subagent_dispatch, template_dispatch
from skills.lib.workflow.prompts.step import format_step
from skills.planner.shared.qr.cli import add_qr_args
-from skills.planner.shared.qr.types import QRState, QRStatus, GateConfig, LoopState
+from skills.planner.shared.qr.types import QRState, QRStatus, LoopState
+from skills.planner.shared.qr.utils import (
+ qr_file_exists,
+ increment_qr_iteration,
+ load_qr_state,
+ query_items,
+ by_status,
+ by_blocking_severity,
+)
+from skills.planner.shared.qr.phases import get_phase_config
+from skills.planner.shared.gates import build_gate_output
from skills.planner.shared.resources import get_mode_script_path
from skills.planner.shared.builders import (
THINKING_EFFICIENCY,
format_forbidden,
- format_gate_result,
- PEDANTIC_ENFORCEMENT,
)
from skills.planner.shared.constraints import (
ORCHESTRATOR_CONSTRAINT,
@@ -39,239 +57,100 @@
MODULE_PATH = "skills.planner.orchestrator.executor"
-def detect_reconciliation_signals(user_request: str) -> bool:
- """Detect if user request requires reconciliation phase."""
- signals = ["already implemented", "resume", "partially complete",
- "existing code", "continue from"]
- return any(s in user_request.lower() for s in signals)
+def _format_qr_item_flags(item_ids: list[str]) -> str:
+ """Format item IDs as repeated --qr-item flags."""
+ return " ".join(f"--qr-item {id}" for id in item_ids)
-STEPS = {
- 1: {
- "title": "Execution Planning",
- "actions": [
- "Plan file: $PLAN_FILE (substitute from context)",
- "",
- "ANALYZE plan:",
- " - Count milestones and parse dependency diagram",
- " - Group milestones into WAVES for execution",
- " - Set up TodoWrite tracking",
- "",
- "WAVE ANALYSIS:",
- " Parse the plan's 'Milestone Dependencies' diagram.",
- " Group into waves: milestones at same depth = one wave.",
- "",
- " Example diagram:",
- " M0 (foundation)",
- " |",
- " +---> M1 (auth) \\",
- " | } Wave 2 (parallel)",
- " +---> M2 (users) /",
- " |",
- " +---> M3 (posts) ----> M4 (feed)",
- " Wave 3 Wave 4",
- "",
- " Output format:",
- " Wave 1: [0] (foundation, sequential)",
- " Wave 2: [1, 2] (parallel)",
- " Wave 3: [3] (sequential)",
- " Wave 4: [4] (sequential)",
- "",
- "WORKFLOW:",
- " This step is ANALYSIS ONLY. Do NOT delegate yet.",
- " Record wave groupings for step 3 (Implementation).",
- ],
- },
- 2: {
- "title": "Reconciliation",
- "is_dispatch": True,
- "dispatch_agent": "quality-reviewer",
- "mode_script": "quality_reviewer/exec-reconcile.py",
- "invoke_suffix": " --milestone N",
- "pre_dispatch": [
- "Validate existing code against plan requirements BEFORE executing.",
- "",
- "For EACH milestone, launch quality-reviewer agent:",
- ],
- "post_dispatch": [
- "The sub-agent will invoke the script and follow its guidance.",
- "",
- "Expected output: SATISFIED | NOT_SATISFIED | PARTIALLY_SATISFIED",
- ],
- "routing": {
- "SATISFIED": "Mark milestone complete, skip execution",
- "NOT_SATISFIED": "Execute milestone normally",
- "PARTIALLY_SATISFIED": "Execute only missing parts",
- },
- "extra_instructions": [
- "",
- "Parallel execution: May run reconciliation for multiple milestones",
- "in parallel (multiple Task calls in single response) when milestones",
- "are independent.",
- ],
- },
- 3: {
- "title": "Implementation",
- },
- 4: {
- "title": "Code QR",
- "is_qr": True,
- "qr_name": "CODE QR",
- "is_dispatch": True,
- "dispatch_agent": "quality-reviewer",
- "mode_script": "quality_reviewer/impl-code-qr.py",
- "pre_dispatch": [
- "",
- "Before QR code review, run post-implementation QA.",
- "",
- "1. DISPATCH QA decomposition:",
- " python3 -m skills.planner.qa.decompose --step 1",
- " Context: PLAN_FILE, MODIFIED_FILES, STATE_DIR (if available)",
- " Phase: post-implementation",
- "",
- "2. Once decomposition complete, read qa.yaml from STATE_DIR.",
- "",
- "3. For each item where scope != '*': dispatch parallel verifier.",
- " For each item where scope == '*': dispatch sequential verifier.",
- "",
- "4. Aggregate results into qa.yaml (update status/finding fields).",
- "",
- "5. Then proceed with Code QR review below.",
- "",
- "",
- ],
- "post_dispatch": [
- "The sub-agent will invoke the script and follow its guidance.",
- "",
- "Expected output: PASS or ISSUES (XML grouped by milestone).",
- ],
- "post_qr_routing": {"self_fix": False, "fix_target": "developer"},
- },
- # Step 5 is the Code QR gate - handled separately
- 6: {
- "title": "Documentation",
- },
- 7: {
- "title": "Doc QR",
- "is_qr": True,
- "qr_name": "DOC QR",
- "is_dispatch": True,
- "dispatch_agent": "quality-reviewer",
- "mode_script": "quality_reviewer/impl-docs-qr.py",
- "post_dispatch": [
- "The sub-agent will invoke the script and follow its guidance.",
- "",
- "Expected output: PASS or ISSUES.",
- ],
- "post_qr_routing": {"self_fix": False, "fix_target": "technical-writer"},
- },
- # Step 8 is the Doc QR gate - handled separately
- 9: {
- "title": "Retrospective",
- "actions": [
- "PRESENT retrospective to user (do not write to file):",
- "",
- "EXECUTION RETROSPECTIVE",
- "=======================",
- "Plan: [path]",
- "Status: COMPLETED | BLOCKED | ABORTED",
- "",
- "Milestone Outcomes: | Milestone | Status | Notes |",
- "Reconciliation Summary: [if run]",
- "Plan Accuracy Issues: [if any]",
- "Deviations from Plan: [if any]",
- "Quality Review Summary: [counts by category]",
- "Feedback for Future Plans: [actionable suggestions]",
- ],
- },
-}
-
-
-# Gate configuration for step 5 (Code QR Gate)
-CODE_QR_GATE = GateConfig(
- qr_name="Code QR",
- work_step=3,
- pass_step=6,
- pass_message="Code quality verified. Proceed to documentation.",
- fix_target=AgentRole.DEVELOPER,
-)
+# =============================================================================
+# Step 1: Execution Planning
+# =============================================================================
-# Gate configuration for step 8 (Doc QR Gate)
-DOC_QR_GATE = GateConfig(
- qr_name="Doc QR",
- work_step=6,
- pass_step=9,
- pass_message="Documentation verified. Proceed to retrospective.",
- fix_target=AgentRole.TECHNICAL_WRITER,
-)
-
-
-def format_gate(step: int, gate: GateConfig, qr: QRState, total_steps: int) -> str:
- """Format gate step output."""
- parts = []
-
- # Gate result banner
- parts.append(format_gate_result(qr.passed))
- parts.append("")
+def format_step_1(state_dir: str, reconciliation_check: bool) -> str:
+ """Create state_dir, analyze plan, build wave list."""
+ actions = [
+ THINKING_EFFICIENCY,
+ "",
+ "Plan file: $PLAN_FILE (substitute from context)",
+ "",
+ "ANALYZE plan:",
+ " - Count milestones and parse dependency diagram",
+ " - Group milestones into WAVES for execution",
+ " - Set up TodoWrite tracking",
+ "",
+ "WAVE ANALYSIS:",
+ " Parse the plan's 'Milestone Dependencies' diagram.",
+ " Group into waves: milestones at same depth = one wave.",
+ "",
+ " Example diagram:",
+ " M0 (foundation)",
+ " |",
+ " +---> M1 (auth) \\",
+ " | } Wave 2 (parallel)",
+ " +---> M2 (users) /",
+ " |",
+ " +---> M3 (posts) ----> M4 (feed)",
+ " Wave 3 Wave 4",
+ "",
+ " Output format:",
+ " Wave 1: [0] (foundation, sequential)",
+ " Wave 2: [1, 2] (parallel)",
+ " Wave 3: [3] (sequential)",
+ " Wave 4: [4] (sequential)",
+ "",
+ "STATE SETUP:",
+ f" State directory created: {state_dir}",
+ f" Write plan context to: {state_dir}/plan.json",
+ "",
+ " After analyzing the plan, use the Write tool to create plan.json:",
+ " {",
+ ' "schema_version": 2,',
+ ' "plan_file": "",',
+ ' "milestones": [',
+ ' {"id": "M-001", "name": "...", "acceptance_criteria": ["..."], "files": ["..."]}',
+ " ],",
+ ' "waves": [[0], [1, 2], [3]]',
+ " }",
+ "",
+ "WORKFLOW:",
+ " This step is ANALYSIS + STATE SETUP. Do NOT delegate yet.",
+ " Record wave groupings for step 2 (Implementation).",
+ ]
- if qr.passed:
- parts.append(gate.pass_message)
- parts.append("")
- parts.append(format_forbidden(
- "Asking the user whether to proceed - the workflow is deterministic",
- "Offering alternatives to the next step - all steps are mandatory",
- "Interpreting 'proceed' as optional - EXECUTE immediately",
- ))
- else:
- parts.append(PEDANTIC_ENFORCEMENT)
- parts.append("")
-
- fix_target = gate.fix_target.value if gate.fix_target else "developer"
- parts.append("NEXT ACTION:")
- parts.append(" Invoke the next step command.")
- parts.append(f" The next step will dispatch {fix_target} with fix guidance.")
- parts.append("")
-
- parts.append(format_forbidden(
- "Fixing issues directly from this gate step",
- "Spawning agents directly from this gate step",
- "Using Edit/Write tools yourself",
- "Proceeding without invoking the next step",
- "Interpreting 'minor issues' as skippable",
- "Claiming 'diminishing returns' or 'comprehensive enough'",
- "Proceeding to next phase without QR PASS",
- ))
+ if reconciliation_check:
+ actions.extend([
+ "",
+ "RECONCILIATION CHECK REQUESTED:",
+ " Before implementing, verify which milestones are already satisfied.",
+ " For each milestone, check if acceptance criteria are met in current code.",
+ " Mark satisfied milestones as complete; execute only remaining ones.",
+ ])
- body = "\n".join(parts)
+ body = "\n".join(actions)
+ next_cmd = f"python3 -m {MODULE_PATH} --step 2 --state-dir {state_dir}"
- # Determine next command
- if qr.passed and gate.pass_step is not None:
- next_cmd = f"python3 -m {MODULE_PATH} --step {gate.pass_step}"
- else:
- next_iteration = qr.iteration + 1
- next_cmd = f"python3 -m {MODULE_PATH} --step {gate.work_step} --qr-fail --qr-iteration {next_iteration}"
+ return format_step(body, next_cmd, title="Execution Planning")
- return format_step(body, next_cmd, title=f"{gate.qr_name} Gate")
+# =============================================================================
+# Step 2: Implementation
+# =============================================================================
-def format_step_3_implementation(qr: QRState, total_steps: int, milestone_count: int) -> str:
- """Format step 3 implementation output."""
+def format_step_2(qr: QRState, state_dir: str) -> str:
+ """Wave-aware implementation dispatch."""
if qr.state == LoopState.RETRY:
title = "Implementation - Fix Mode"
- else:
- title = "Implementation"
-
- actions = []
- if qr.state == LoopState.RETRY:
- actions.append(format_state_banner("IMPLEMENTATION FIX", qr.iteration, "fix"))
- actions.append("")
- actions.append("FIX MODE: Code QR found issues.")
- actions.append("")
- actions.append(ORCHESTRATOR_CONSTRAINT)
- actions.append("")
+ actions = [
+ format_state_banner("IMPLEMENTATION FIX", qr.iteration, "fix"),
+ "",
+ "FIX MODE: Code QR found issues.",
+ "",
+ ORCHESTRATOR_CONSTRAINT,
+ "",
+ ]
- mode_script = get_mode_script_path("dev/fix-code.py")
- invoke_cmd = f"python3 -m {mode_script} --step 1 --qr-fail --qr-iteration {qr.iteration}"
+ mode_script = get_mode_script_path("developer/exec_implement.py")
+ invoke_cmd = f"python3 -m {mode_script} --step 1 --state-dir {state_dir}"
actions.append(subagent_dispatch(
agent_type="developer",
@@ -281,7 +160,8 @@ def format_step_3_implementation(qr: QRState, total_steps: int, milestone_count:
actions.append("Developer reads QR report and fixes issues in blocks.")
actions.append("After developer completes, re-run Code QR for fresh verification.")
else:
- actions.extend([
+ title = "Implementation"
+ actions = [
"Execute ALL milestones using wave-aware parallel dispatch.",
"",
"WAVE-AWARE EXECUTION:",
@@ -318,291 +198,356 @@ def format_step_3_implementation(qr: QRState, total_steps: int, milestone_count:
" Clear problem + solution: Task(developer) immediately",
" Difficult/unclear problem: Task(debugger) to diagnose first",
" Uncertain how to proceed: AskUserQuestion with options",
- ])
+ ]
body = "\n".join(actions)
- next_cmd = f"python3 -m {MODULE_PATH} --step 4"
+ next_cmd = f"python3 -m {MODULE_PATH} --step 3 --state-dir {state_dir}"
return format_step(body, next_cmd, title=title)
-def format_step_6_documentation(qr: QRState, total_steps: int) -> str:
- """Format step 6 documentation output."""
- mode_script = get_mode_script_path("technical_writer/exec-docs.py")
-
- if qr.state == LoopState.RETRY:
- title = "Documentation - Fix Mode"
- else:
- title = "Documentation"
-
- actions = []
-
- if qr.state == LoopState.RETRY:
- actions.append(format_state_banner("DOCUMENTATION FIX", qr.iteration, "fix"))
- actions.append("")
- actions.append("FIX MODE: Doc QR found issues.")
- actions.append("")
- actions.append(ORCHESTRATOR_CONSTRAINT)
- actions.append("")
-
- invoke_cmd = f"python3 -m {mode_script} --step 1 --qr-fail --qr-iteration {qr.iteration}"
- actions.append(subagent_dispatch(
- agent_type="technical-writer",
- command=invoke_cmd,
- ))
- else:
- actions.append(ORCHESTRATOR_CONSTRAINT)
- actions.append("")
-
- invoke_cmd = f"python3 -m {mode_script} --step 1"
- actions.append(subagent_dispatch(
- agent_type="technical-writer",
- command=invoke_cmd,
- ))
-
- body = "\n".join(actions)
- next_cmd = f"python3 -m {MODULE_PATH} --step 7"
-
- return format_step(body, next_cmd, title=title)
+# =============================================================================
+# Steps 3, 7: QR Decompose
+# =============================================================================
+def format_qr_decompose(step: int, phase: str, state_dir: str, qr: QRState) -> str:
+ """Dispatch QR decomposition agent for a phase."""
+ config = get_phase_config(phase)
+ decompose_script = config["decompose_script"]
-def format_step_1_planning(qr: QRState, total_steps: int, reconciliation_check: bool, **kw) -> str:
- """Format step 1 planning output."""
- info = STEPS[1]
+ title_map = {3: "Code QR Decompose", 7: "Doc QR Decompose"}
+ title = title_map.get(step, f"QR Decompose ({phase})")
- actions = list(info["actions"])
- actions.extend([
- "",
- "=" * 70,
- "MANDATORY NEXT ACTION",
- "=" * 70,
- ])
- if reconciliation_check:
- next_cmd = f"python3 -m {MODULE_PATH} --step 2 --reconciliation-check"
- else:
- actions.extend([
- "Proceed to Implementation step.",
- "Use the wave groupings from your analysis.",
- "=" * 70,
+ # Skip if already decomposed
+ if qr_file_exists(state_dir, phase):
+ next_step = step + 1
+ body = "\n".join([
+ f"QR items for {phase} already defined.",
+ "Proceeding to verification of existing items.",
])
- next_cmd = f"python3 -m {MODULE_PATH} --step 3"
-
- body_parts = []
- body_parts.append(THINKING_EFFICIENCY)
- body_parts.append("")
- body_parts.extend(actions)
-
- body = "\n".join(body_parts)
- return format_step(body, next_cmd, title=info["title"])
-
-
-def format_step_4_code_qr(qr: QRState, total_steps: int, **kw) -> str:
- """Format step 4 code QR output with branching."""
- info = STEPS[4]
-
- actions = []
- actions.append(format_state_banner(info["qr_name"], qr.iteration, "fresh_review"))
- actions.append("")
+ next_cmd = f"python3 -m {MODULE_PATH} --step {next_step} --state-dir {state_dir}"
+ return format_step(body, next_cmd, title=f"{title} - Skipped")
- pre_dispatch = info.get("pre_dispatch", [])
- actions.extend(pre_dispatch)
-
- actions.append(ORCHESTRATOR_CONSTRAINT)
- actions.append("")
-
- mode_script = get_mode_script_path(info["mode_script"])
- dispatch_agent = info.get("dispatch_agent", "agent")
- invoke_suffix = info.get("invoke_suffix", "")
- invoke_cmd = f"python3 -m {mode_script} --step 1{invoke_suffix}"
+ actions = [
+ format_state_banner(f"QR-{phase.upper()}-DECOMPOSE", qr.iteration, "decompose"),
+ "",
+ ORCHESTRATOR_CONSTRAINT,
+ "",
+ ]
+ invoke_cmd = f"python3 -m {decompose_script} --step 1 --state-dir {state_dir}"
actions.append(subagent_dispatch(
- agent_type=dispatch_agent,
+ agent_type="quality-reviewer",
command=invoke_cmd,
))
actions.append("")
-
- post_dispatch = info.get("post_dispatch", [])
- actions.extend(post_dispatch)
+ actions.append(f"Expected output: qr-{phase}.json written to STATE_DIR")
+ actions.append("Orchestrator generates verification dispatch from this file.")
body = "\n".join(actions)
- if_pass = f"python3 -m {MODULE_PATH} --step 5 --qr-status pass"
- if_fail = f"python3 -m {MODULE_PATH} --step 5 --qr-status fail"
+ next_step = step + 1
+ next_cmd = f"python3 -m {MODULE_PATH} --step {next_step} --state-dir {state_dir}"
- return format_step(body, title=info["title"], if_pass=if_pass, if_fail=if_fail)
+ return format_step(body, next_cmd, title=title)
-def format_step_7_doc_qr(qr: QRState, total_steps: int, **kw) -> str:
- """Format step 7 doc QR output with branching."""
- info = STEPS[7]
+# =============================================================================
+# Steps 4, 8: QR Verify (parallel)
+# =============================================================================
- actions = []
- actions.append(format_state_banner(info["qr_name"], qr.iteration, "fresh_review"))
- actions.append("")
+def format_qr_verify(step: int, phase: str, state_dir: str, qr: QRState) -> str:
+ """Dispatch parallel QR verification agents."""
+ config = get_phase_config(phase)
+ verify_script = config["verify_script"]
- pre_dispatch = info.get("pre_dispatch", [])
- actions.extend(pre_dispatch)
-
- actions.append(ORCHESTRATOR_CONSTRAINT)
- actions.append("")
+ title_map = {4: "Code QR Verify", 8: "Doc QR Verify"}
+ title = title_map.get(step, f"QR Verify ({phase})")
- mode_script = get_mode_script_path(info["mode_script"])
- dispatch_agent = info.get("dispatch_agent", "agent")
- invoke_suffix = info.get("invoke_suffix", "")
- invoke_cmd = f"python3 -m {mode_script} --step 1{invoke_suffix}"
+ qr_state = load_qr_state(state_dir, phase)
+ if not qr_state or "items" not in qr_state:
+ decompose_step = step - 1
+ body = f"Error: qr-{phase}.json not found or malformed. Routing back to decompose step."
+ retry_cmd = f"python3 -m {MODULE_PATH} --step {decompose_step} --state-dir {state_dir}"
+ return format_step(body, retry_cmd, title=title)
- actions.append(subagent_dispatch(
- agent_type=dispatch_agent,
- command=invoke_cmd,
- ))
- actions.append("")
-
- post_dispatch = info.get("post_dispatch", [])
- actions.extend(post_dispatch)
+ iteration = qr_state.get("iteration", 1)
+ if qr.state == LoopState.RETRY:
+ new_iter = increment_qr_iteration(state_dir, phase)
+ if new_iter is not None:
+ iteration = new_iter
+
+ # Dispatch only items at blocking severity for current iteration
+ items = query_items(qr_state, by_status("TODO", "FAIL"), by_blocking_severity(iteration))
+ if not items:
+ next_step = step + 1
+ body = "All items already verified. Proceeding with pass."
+ if_pass = f"python3 -m {MODULE_PATH} --step {next_step} --state-dir {state_dir} --qr-status pass"
+ return format_step(body, title=title, if_pass=if_pass, if_fail=if_pass)
+
+ # Group items by group_id for batch verification
+ groups = {}
+ for item in items:
+ gid = item.get("group_id") or item["id"]
+ groups.setdefault(gid, []).append(item)
+
+ targets = [
+ {
+ "group_id": gid,
+ "item_ids": ",".join(i["id"] for i in group_items),
+ "qr_item_flags": _format_qr_item_flags([i["id"] for i in group_items]),
+ "item_count": str(len(group_items)),
+ "checks_summary": "; ".join(i.get("check", "")[:40] for i in group_items[:3]),
+ }
+ for gid, group_items in groups.items()
+ ]
+
+ tmpl = f"""Verify QR group: $group_id ($item_count items)
+Items: $item_ids
+Checks: $checks_summary
+
+Start: python3 -m {verify_script} --step 1 --state-dir {state_dir} $qr_item_flags"""
+
+ command = f"python3 -m {verify_script} --step 1 --state-dir {state_dir} $qr_item_flags"
+
+ dispatch_text = template_dispatch(
+ agent_type="quality-reviewer",
+ template=tmpl,
+ targets=targets,
+ command=command,
+ instruction=f"Verify {len(groups)} groups ({len(items)} items) in parallel.",
+ )
- extra_instructions = info.get("extra_instructions", [])
- actions.extend(extra_instructions)
+ actions = [
+ ORCHESTRATOR_CONSTRAINT,
+ "",
+ "=== PHASE 1: DISPATCH (delegate to sub-agents) ===",
+ "",
+ f"VERIFY: {len(items)} items",
+ "",
+ dispatch_text,
+ "",
+ "=== PHASE 2: AGGREGATE (your action after all agents return) ===",
+ "",
+ f"After ALL {len(groups)} agents return, tally results mechanically:",
+ f" ALL agents returned PASS -> invoke next step with --qr-status pass",
+ f" ANY agent returned FAIL -> invoke next step with --qr-status fail",
+ "",
+ format_forbidden(
+ "Interpreting results beyond PASS/FAIL tallying",
+ "Claiming 'diminishing returns' or 'comprehensive enough'",
+ "Skipping the next step command",
+ "Proceeding to a later step without QR PASS",
+ ),
+ ]
body = "\n".join(actions)
- if_pass = f"python3 -m {MODULE_PATH} --step 8 --qr-status pass"
- if_fail = f"python3 -m {MODULE_PATH} --step 8 --qr-status fail"
-
- return format_step(body, title=info["title"], if_pass=if_pass, if_fail=if_fail)
+ next_step = step + 1
+ base_cmd = f"python3 -m {MODULE_PATH} --step {next_step} --state-dir {state_dir}"
+
+ return format_step(body, title=title,
+ if_pass=f"{base_cmd} --qr-status pass",
+ if_fail=f"{base_cmd} --qr-status fail")
+
+
+# =============================================================================
+# Steps 5, 9: QR Gate (route pass/fail)
+# =============================================================================
+
+def format_qr_gate(step: int, phase: str, state_dir: str, qr: QRState) -> str:
+ """Route based on QR results: pass → next phase, fail → fix loop."""
+ gate_config = {
+ 5: ("Code QR", 2, 6, "Code quality verified. Proceed to documentation.", AgentRole.DEVELOPER),
+ 9: ("Doc QR", 6, 10, "Documentation verified. Proceed to retrospective.", AgentRole.TECHNICAL_WRITER),
+ }
+
+ qr_name, work_step, pass_step, pass_message, fix_target = gate_config[step]
+
+ result = build_gate_output(
+ module_path=MODULE_PATH,
+ script_name="executor",
+ qr_name=qr_name,
+ qr=qr,
+ step=step,
+ work_step=work_step,
+ pass_step=pass_step,
+ pass_message=pass_message,
+ fix_target=fix_target,
+ state_dir=state_dir,
+ )
+ return result.output
-STEP_HANDLERS = {
- 1: format_step_1_planning,
- 3: lambda qr, total_steps, milestone_count, **kw: format_step_3_implementation(qr, total_steps, milestone_count),
- 4: format_step_4_code_qr,
- 5: lambda qr, total_steps, qr_status, **kw: format_gate(5, CODE_QR_GATE, qr, total_steps) if qr_status else "Error: --qr-status required for step 5",
- 6: lambda qr, total_steps, **kw: format_step_6_documentation(qr, total_steps),
- 7: format_step_7_doc_qr,
- 8: lambda qr, total_steps, qr_status, **kw: format_gate(8, DOC_QR_GATE, qr, total_steps) if qr_status else "Error: --qr-status required for step 8",
-}
+# =============================================================================
+# Step 6: Documentation
+# =============================================================================
-def format_output(step: int,
- qr_iteration: int, qr_fail: bool, qr_status: str,
- reconciliation_check: bool, milestone_count: int) -> str:
- """Format output for display."""
- from skills.planner.shared.constants import EXECUTOR_TOTAL_STEPS
+def format_step_6(qr: QRState, state_dir: str) -> str:
+ """Dispatch technical writer for documentation."""
+ mode_script = get_mode_script_path("technical_writer/exec_docs.py")
- total_steps = EXECUTOR_TOTAL_STEPS
+ if qr.state == LoopState.RETRY:
+ title = "Documentation - Fix Mode"
+ actions = [
+ format_state_banner("DOCUMENTATION FIX", qr.iteration, "fix"),
+ "",
+ "FIX MODE: Doc QR found issues.",
+ "",
+ ORCHESTRATOR_CONSTRAINT,
+ "",
+ ]
- # Construct QRState from legacy parameters
- status = QRStatus(qr_status) if qr_status else None
- state = LoopState.RETRY if qr_fail else LoopState.INITIAL
- qr = QRState(iteration=qr_iteration, state=state, status=status)
+ invoke_cmd = f"python3 -m {mode_script} --step 1 --state-dir {state_dir}"
+ actions.append(subagent_dispatch(
+ agent_type="technical-writer",
+ command=invoke_cmd,
+ ))
+ else:
+ title = "Documentation"
+ actions = [
+ ORCHESTRATOR_CONSTRAINT,
+ "",
+ ]
- # Dispatch to step-specific handlers
- handler = STEP_HANDLERS.get(step)
- if handler:
- return handler(qr=qr, total_steps=total_steps, qr_status=qr_status,
- milestone_count=milestone_count, reconciliation_check=reconciliation_check)
-
- # Generic step handling
- info = STEPS.get(step, STEPS[9])
-
- # Handle QR step in fix mode (developer/TW fixes, not QR re-run)
- if info.get("is_qr") and qr.state == LoopState.RETRY:
- post_qr_config = info.get("post_qr_routing", {})
- fix_target = post_qr_config.get("fix_target", "developer")
- qr_name = info.get("qr_name", "QR")
-
- fix_actions = []
- fix_actions.append(format_state_banner(qr_name, qr.iteration, "fix"))
- fix_actions.append("")
- fix_actions.append(f"FIX MODE: {qr_name} found issues.")
- fix_actions.append("")
- fix_actions.append(ORCHESTRATOR_CONSTRAINT)
- fix_actions.append("")
-
- mode_script = get_mode_script_path(f"{fix_target}/fix.py")
- invoke_cmd = f"python3 -m {mode_script} --step 1 --qr-fail --qr-iteration {qr.iteration}"
-
- fix_actions.append(subagent_dispatch(
- agent_type=fix_target,
+ invoke_cmd = f"python3 -m {mode_script} --step 1 --state-dir {state_dir}"
+ actions.append(subagent_dispatch(
+ agent_type="technical-writer",
command=invoke_cmd,
))
- body = "\n".join(fix_actions)
- next_cmd = f"python3 -m {MODULE_PATH} --step {step}"
+ body = "\n".join(actions)
+ next_cmd = f"python3 -m {MODULE_PATH} --step 7 --state-dir {state_dir}"
- return format_step(body, next_cmd, title=f"{info['title']} - Fix Mode")
+ return format_step(body, next_cmd, title=title)
- is_complete = step >= total_steps
- # Build actions
- actions = []
+# =============================================================================
+# Step 10: Retrospective
+# =============================================================================
- # Add QR banner for QR steps
- if info.get("is_qr"):
- qr_name = info.get("qr_name", "QR")
- actions.append(format_state_banner(qr_name, qr.iteration, "fresh_review"))
- actions.append("")
+def format_step_10() -> str:
+ """Present execution retrospective."""
+ actions = [
+ "PRESENT retrospective to user (do not write to file):",
+ "",
+ "EXECUTION RETROSPECTIVE",
+ "=======================",
+ "Plan: [path]",
+ "Status: COMPLETED | BLOCKED | ABORTED",
+ "",
+ "Milestone Outcomes: | Milestone | Status | Notes |",
+ "Reconciliation Summary: [if run]",
+ "Plan Accuracy Issues: [if any]",
+ "Deviations from Plan: [if any]",
+ "Quality Review Summary: [counts by category]",
+ "Feedback for Future Plans: [actionable suggestions]",
+ ]
- # Handle dispatch steps
- if info.get("is_dispatch"):
- pre_dispatch = info.get("pre_dispatch", [])
- actions.extend(pre_dispatch)
+ body = "\n".join(actions)
+ return format_step(body, title="Retrospective")
- actions.append(ORCHESTRATOR_CONSTRAINT)
- actions.append("")
- mode_script = get_mode_script_path(info["mode_script"])
- dispatch_agent = info.get("dispatch_agent", "agent")
- invoke_suffix = info.get("invoke_suffix", "")
- invoke_cmd = f"python3 -m {mode_script} --step 1{invoke_suffix}"
+# =============================================================================
+# Step Dispatch
+# =============================================================================
- actions.append(subagent_dispatch(
- agent_type=dispatch_agent,
- command=invoke_cmd,
- ))
- actions.append("")
+def format_output(step: int, state_dir: str,
+ qr_iteration: int, qr_fail: bool, qr_status: str,
+ reconciliation_check: bool) -> str:
+ """Format output for display."""
+ from skills.planner.shared.constants import EXECUTOR_TOTAL_STEPS
- post_dispatch = info.get("post_dispatch", [])
- actions.extend(post_dispatch)
- elif "actions" in info:
- actions.extend(info["actions"])
+ # Construct QRState
+ status = QRStatus(qr_status) if qr_status else None
+ state = LoopState.RETRY if qr_fail else LoopState.INITIAL
+ qr = QRState(iteration=qr_iteration, state=state, status=status)
- # Build next command
- if is_complete:
- actions.append("")
- actions.append("EXECUTION COMPLETE - Present retrospective to user.")
- next_cmd = ""
+ # Derive fix mode from QR state files (like planner does)
+ phase_for_step = {2: "impl-code", 6: "impl-docs"}
+ if step in phase_for_step and state_dir and not qr_fail:
+ from skills.planner.shared.qr.utils import has_qr_failures
+ phase = phase_for_step[step]
+ if has_qr_failures(state_dir, phase):
+ qr = QRState(iteration=qr_iteration, state=LoopState.RETRY, status=status)
+
+ if step == 1:
+ return format_step_1(state_dir, reconciliation_check)
+ elif step == 2:
+ return format_step_2(qr, state_dir)
+ elif step == 3:
+ return format_qr_decompose(3, "impl-code", state_dir, qr)
+ elif step == 4:
+ return format_qr_verify(4, "impl-code", state_dir, qr)
+ elif step == 5:
+ if not qr_status:
+ return "Error: --qr-status required for step 5 (Code QR Gate)"
+ return format_qr_gate(5, "impl-code", state_dir, qr)
+ elif step == 6:
+ return format_step_6(qr, state_dir)
+ elif step == 7:
+ return format_qr_decompose(7, "impl-docs", state_dir, qr)
+ elif step == 8:
+ return format_qr_verify(8, "impl-docs", state_dir, qr)
+ elif step == 9:
+ if not qr_status:
+ return "Error: --qr-status required for step 9 (Doc QR Gate)"
+ return format_qr_gate(9, "impl-docs", state_dir, qr)
+ elif step == 10:
+ return format_step_10()
else:
- next_cmd = f"python3 -m {MODULE_PATH} --step {step + 1}"
-
- body = "\n".join(actions)
- return format_step(body, next_cmd, title=info["title"])
+ return f"Error: invalid step {step} (valid: 1-10)"
def main():
parser = argparse.ArgumentParser(
- description="Plan Executor - Execute approved plans",
- epilog="Steps: plan -> reconcile -> implement -> code QR -> gate -> docs -> doc QR -> gate -> retrospective",
+ description="Plan Executor - Execute approved plans (10-step workflow)",
+ epilog="Steps: plan -> implement -> code QR (decompose/verify/gate) -> docs -> doc QR (decompose/verify/gate) -> retrospective",
)
parser.add_argument("--step", type=int, required=True)
+ parser.add_argument("--state-dir", type=str, default=None,
+ help="State directory path (created in step 1)")
add_qr_args(parser)
- parser.add_argument("--qr-iteration", type=int, default=0)
+ parser.add_argument("--qr-iteration", type=int, default=1)
parser.add_argument("--qr-fail", action="store_true")
parser.add_argument("--reconciliation-check", action="store_true")
- parser.add_argument("--milestone-count", type=int, default=0)
args = parser.parse_args()
- if args.step < 1 or args.step > 9:
- sys.exit("Error: step must be 1-9")
-
- if args.step == 5 and not args.qr_status:
- sys.exit("Error: --qr-status required for step 5")
-
- if args.step == 8 and not args.qr_status:
- sys.exit("Error: --qr-status required for step 8")
-
- print(format_output(args.step,
+ if args.step < 1 or args.step > 10:
+ sys.exit("Error: step must be 1-10")
+
+ # Create state_dir for step 1 if not provided
+ state_dir = args.state_dir
+ if args.step == 1 and not state_dir:
+ state_dir = tempfile.mkdtemp(prefix="executor-")
+ # Write minimal plan skeleton
+ plan_path = Path(state_dir) / "plan.json"
+ plan_path.write_text(json.dumps({
+ "schema_version": 2,
+ "overview": {"problem": "", "approach": ""},
+ "planning_context": {
+ "decisions": [],
+ "rejected_alternatives": [],
+ "constraints": [],
+ "risks": [],
+ },
+ "invisible_knowledge": {
+ "system": "",
+ "invariants": [],
+ "tradeoffs": [],
+ },
+ "milestones": [],
+ "waves": [],
+ }, indent=2))
+
+ # Validate state_dir for steps 2+
+ if args.step > 1 and not state_dir:
+ sys.exit(f"Error: --state-dir required for step {args.step}")
+
+ print(format_output(args.step, state_dir,
args.qr_iteration, args.qr_fail, args.qr_status,
- args.reconciliation_check, args.milestone_count))
+ args.reconciliation_check))
if __name__ == "__main__":