diff --git a/.egg/phase-permissions.json b/.egg/phase-permissions.json index dc2086eb96..74c68acfb5 100644 --- a/.egg/phase-permissions.json +++ b/.egg/phase-permissions.json @@ -1,7 +1,7 @@ { "schemaVersion": "1.0", "phases": { - "refine": { + "analyze": { "allowed_operations": [ { "type": "gh", @@ -33,7 +33,7 @@ { "type": "gh", "pattern": "pr create*", - "description": "Cannot create PRs during refine phase" + "description": "Cannot create PRs during analyze phase" } ], "exit_requires": "human" @@ -135,7 +135,7 @@ } }, "phase_file_restrictions": { - "refine": { + "analyze": { "allowed_patterns": [ ".egg-state/contracts/*", ".egg-state/drafts/*analysis*", @@ -143,7 +143,7 @@ ".egg-state/agent-outputs/*", ".egg-state/reviews/*" ], - "description": "Refine phase can only push contracts, analysis drafts, checkpoints, agent outputs, and reviews" + "description": "Analyze phase can only push contracts, analysis drafts, checkpoints, agent outputs, and reviews" }, "plan": { "allowed_patterns": [ diff --git a/.egg/schemas/checkpoint.schema.json b/.egg/schemas/checkpoint.schema.json index 5b05104b99..852cdf8133 100644 --- a/.egg/schemas/checkpoint.schema.json +++ b/.egg/schemas/checkpoint.schema.json @@ -70,7 +70,7 @@ "pipeline_phase": { "type": ["string", "null"], "description": "SDLC pipeline phase when checkpoint was created", - "enum": ["refine", "plan", "implement", "pr", null], + "enum": ["analyze", "plan", "implement", "pr", "refine", null], "default": null }, "pipeline_id": { diff --git a/.egg/schemas/contract.schema.json b/.egg/schemas/contract.schema.json index f6a1ab2ff4..80dec90e6b 100644 --- a/.egg/schemas/contract.schema.json +++ b/.egg/schemas/contract.schema.json @@ -54,8 +54,8 @@ "current_phase": { "type": "string", "description": "Current pipeline phase", - "enum": ["refine", "plan", "implement", "pr"], - "default": "refine" + "enum": ["analyze", "plan", "implement", "pr", "refine"], + "default": "analyze" }, "acceptance_criteria": { "type": "array", @@ -138,7 +138,7 @@ "type": "object", "description": "Phase-specific configurations", "propertyNames": { - "enum": ["refine", "plan", "implement", "pr"] + "enum": ["analyze", "plan", "implement", "pr", "refine"] }, "additionalProperties": { "$ref": "#/$defs/phaseConfig" @@ -500,7 +500,7 @@ "phase": { "type": "string", "description": "Pipeline phase this feedback belongs to", - "enum": ["refine", "plan", "implement", "pr"] + "enum": ["analyze", "plan", "implement", "pr", "refine"] }, "questions": { "type": "array", diff --git a/.egg/schemas/phase-permissions.schema.json b/.egg/schemas/phase-permissions.schema.json index adcc545470..968fcd4da3 100644 --- a/.egg/schemas/phase-permissions.schema.json +++ b/.egg/schemas/phase-permissions.schema.json @@ -19,7 +19,7 @@ "$ref": "#/$defs/phasePermissions" }, "properties": { - "refine": { + "analyze": { "$ref": "#/$defs/phasePermissions" }, "plan": { diff --git a/.egg/schemas/usage.schema.json b/.egg/schemas/usage.schema.json index 89be25df80..e82ea7fcb6 100644 --- a/.egg/schemas/usage.schema.json +++ b/.egg/schemas/usage.schema.json @@ -187,7 +187,7 @@ "description": "Pipeline phases seen", "items": { "type": "string", - "enum": ["refine", "plan", "implement", "pr"] + "enum": ["analyze", "plan", "implement", "pr", "refine"] }, "default": [] } @@ -282,7 +282,7 @@ "description": "Pipeline phases seen", "items": { "type": "string", - "enum": ["refine", "plan", "implement", "pr"] + "enum": ["analyze", "plan", "implement", "pr", "refine"] }, "default": [] } diff --git a/.gitignore b/.gitignore index ea75d036e5..d43949f800 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ secrets.yaml docker-compose.override.yml .compose-build-hash +# Agent sandbox artifacts +CLAUDE.md + # OS .DS_Store Thumbs.db diff --git a/README.md b/README.md index 2c59f0a5d1..6f543ee9aa 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Point egg at a GitHub issue and it runs the full lifecycle. Here's what a comple ``` ╔═════════════════════════════════════════════╗ - │ ✓ Refine │ + │ ✓ Analyze │ │ complete │ │ ✓ refiner │ │ ✓ reviewer_refine ✓ reviewer_agent_design│ @@ -55,24 +55,24 @@ Point egg at a GitHub issue and it runs the full lifecycle. Here's what a comple ╚════════════╝ ``` -Each box is a pipeline phase. Within each phase, specialized agents run in dependency-ordered waves. Some sequentially, some in parallel. The orchestrator manages the entire DAG. Humans approve at the refine and plan gates; then agents implement, test, review, and the orchestrator auto-creates the PR. +Each box is a pipeline phase. Within each phase, specialized agents run in dependency-ordered waves. Some sequentially, some in parallel. The orchestrator manages the entire DAG. Humans approve at the analyze and plan gates; then agents implement, test, review, and the orchestrator auto-creates the PR. ## How It Works ``` ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌───────────┐ - │ REFINE │─────▶│ PLAN │─────▶│ IMPLEMENT │─────▶│ PR │ + │ ANALYZE │─────▶│ PLAN │─────▶│ IMPLEMENT │─────▶│ PR │ └────┬─────┘ └────┬─────┘ └──────────────┘ └─────┬─────┘ │ │ │ Human gate Human gate Human merge ``` -1. **Refine** — Agents analyze the task, research the codebase, and produce a requirements document. Reviewers validate the analysis. Human approves before planning begins. +1. **Analyze** — Agents analyze the task, research the codebase, and produce a requirements document. Reviewers validate the analysis. Human approves before planning begins. 2. **Plan** — An architect recommends an approach, a task planner breaks it into discrete tasks with acceptance criteria, and a risk analyst flags concerns. Human approves before any code is written. 3. **Implement** — A coder writes code, a tester finds gaps and writes tests, a documenter updates docs, and an integrator runs the full test suite. Code and contract reviewers provide line-level feedback. Re-implementation cycles continue until all checks pass. 4. **PR** — The orchestrator auto-creates the PR using metadata from the plan, commit log, and diff stats. No agent is spawned. Only a human can merge via GitHub UI. -**Short-circuit mode**: Simple tasks (typos, config changes) skip the plan phase entirely — the refine phase signals `short_circuit: true` and jumps straight to implementation. +**Short-circuit mode**: Simple tasks (typos, config changes) skip the plan phase entirely — the analyze phase signals `short_circuit: true` and jumps straight to implementation. ### Tiered Dispatch @@ -80,7 +80,7 @@ The pipeline adapts its execution strategy to task complexity: | Tier | Complexity | Strategy | |------|-----------|----------| -| **Tier 1** | Low (typos, config) | Short-circuit: refine → implement (skip plan) | +| **Tier 1** | Low (typos, config) | Short-circuit: analyze → implement (skip plan) | | **Tier 2** | Medium (single features) | Full pipeline, single coder in waves | | **Tier 3** | High (multi-phase features) | Parallel implement cycles per plan phase | diff --git a/docs/adr/implemented/ADR-SDLC-Pipeline.md b/docs/adr/implemented/ADR-SDLC-Pipeline.md index 778dea7f49..f95ffff0ea 100644 --- a/docs/adr/implemented/ADR-SDLC-Pipeline.md +++ b/docs/adr/implemented/ADR-SDLC-Pipeline.md @@ -16,7 +16,7 @@ For operational details, CLI commands, and triggering instructions, see the [SDL **The core guarantee**: An agent cannot bypass verification gates or self-approve its own work. All state transitions are enforced structurally through role-based mutations and gateway policy enforcement. **Key properties:** -- **Phased execution**: Work progresses through defined phases (refine → plan → implement → pr) +- **Phased execution**: Work progresses through defined phases (analyze → plan → implement → pr) - **Role-based control**: Implementer, Reviewer, and Human roles have distinct permissions - **Human-in-the-loop**: Critical transitions pause for human approval - **Audit trail**: All mutations are logged for accountability @@ -113,7 +113,7 @@ This architecture does **not** protect against: | Phase | Purpose | Exit Requires | |-------|---------|---------------| -| **refine** | Problem analysis and requirements gathering | Human approval | +| **analyze** | Problem analysis and requirements gathering | Human approval | | **plan** | Implementation planning with task breakdown | Human approval | | **implement** | Task execution and code changes | All checks pass (CI + PR review) | | **pr** | Pull request creation and merge | Human merge | diff --git a/docs/agentic-feedback-loop.md b/docs/agentic-feedback-loop.md index a6ac9eaa30..bb91bf9532 100644 --- a/docs/agentic-feedback-loop.md +++ b/docs/agentic-feedback-loop.md @@ -71,7 +71,7 @@ Egg's SDLC pipeline combines both loop types in a structured workflow: ├────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ REFINE PHASE │ │ +│ │ ANALYZE PHASE │ │ │ │ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ Agentic Feedback Loop │ │ │ @@ -121,14 +121,14 @@ Egg's SDLC pipeline combines both loop types in a structured workflow: | Phase | Agentic Loop | Human Loop | Purpose | |-------|--------------|------------|---------| -| **Refine** | Agent analyzes problem, reviewer validates | Human approves analysis | Ensure shared understanding of the problem | +| **Analyze** | Agent analyzes problem, reviewer validates | Human approves analysis | Ensure shared understanding of the problem | | **Plan** | Agent creates plan, reviewer validates | Human approves approach | Ensure alignment on implementation strategy | | **Implement** | Agent codes, tester finds gaps, PR review provides feedback | CI/tests validate | Produce working, tested code | | **Merge** | — | Human reviews and merges | Final human accountability gate | ### Why Each Human Gate Matters -**After Refine**: Misunderstanding the problem leads to wasted implementation effort. Human approval ensures the agent understood the requirement correctly before any code is written. +**After Analyze**: Misunderstanding the problem leads to wasted implementation effort. Human approval ensures the agent understood the requirement correctly before any code is written. **After Plan**: A flawed plan produces flawed code. Human review of the plan catches architectural issues, missing requirements, and scope creep before implementation begins. @@ -148,11 +148,11 @@ Issues missed by one reviewer are caught by another. ### 2. Early Feedback Is Cheap -Feedback in the refine phase costs minutes. Feedback after implementation costs hours. The pipeline front-loads review to when changes are cheapest. +Feedback in the analyze phase costs minutes. Feedback after implementation costs hours. The pipeline front-loads review to when changes are cheapest. ``` Cost of change: - Refine phase: ~5 minutes to revise analysis + Analyze phase: ~5 minutes to revise analysis Plan phase: ~15 minutes to update plan Implement phase: ~1 hour to refactor code After merge: ~days to debug in production @@ -202,7 +202,7 @@ You don't need the full SDLC pipeline to benefit from the feedback loop model. T ### Structured Version -1. **Refine**: "Analyze this problem. What are the constraints? What are the options?" +1. **Analyze**: "Analyze this problem. What are the constraints? What are the options?" 2. **Review**: "Review this analysis. What's missing? What's wrong?" 3. **Plan**: "Create an implementation plan for the chosen approach." 4. **Review**: "Review this plan. Is it complete? Are the tasks clear?" diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 6f5c6f0dc5..b4d5b713b5 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -16,7 +16,7 @@ See the [main README](../../README.md) for the architecture diagram. ## Key Design Principles **Structurally Enforced SDLC:** -- Work progresses through defined phases (refine → plan → implement → merge) +- Work progresses through defined phases (analyze → plan → implement → merge) - Each phase has specific permitted operations enforced by the gateway - Phase transitions require human approval - Role-based contract mutations prevent agents from self-approving work @@ -122,7 +122,7 @@ Browse and query checkpoints via the `egg-checkpoint` CLI: - `--trigger ` — Filter by trigger type - `--status ` — Filter by session status - `--agent-type ` — Filter by agent type -- `--phase ` — Filter by pipeline phase +- `--phase ` — Filter by pipeline phase Checkpoints enable post-hoc analysis of agent behavior, debugging failed sessions, auditing agent decisions, and tracking token usage across issues and PRs. @@ -146,7 +146,7 @@ Each SDLC phase can have configurable automated checks that run before completio - `lint`: Runs project linter (via `make lint`) - `test`: Runs project tests (via `make test` or pytest) - `merge-conflict`: Detects merge conflicts with base branch -- `draft-validation`: Validates refine phase draft documents +- `draft-validation`: Validates analyze phase draft documents - `plan-yaml`: Validates plan phase YAML appendix - `deployment`: Validates changes against locally running devserver (DinD) - `fixer`: Auto-fixes certain check failures when possible diff --git a/docs/architecture/orchestrator.md b/docs/architecture/orchestrator.md index 4e552a0cc5..bca28f7562 100644 --- a/docs/architecture/orchestrator.md +++ b/docs/architecture/orchestrator.md @@ -126,7 +126,7 @@ The orchestrator reads pipeline artifacts (verdict files, draft documents, check **Key artifact files in worktrees:** - `.egg-state/contracts/{identifier}.json` — Contract state (issue number for issue-mode, pipeline ID for local-mode) -- `.egg-state/drafts/{identifier}-analysis.md` — Draft for `refine` phase (special-cased to `analysis`) +- `.egg-state/drafts/{identifier}-analysis.md` — Draft for `analyze` phase (special-cased to `analysis`) - `.egg-state/drafts/{identifier}-{phase}.md` — Draft for other phases (e.g., `plan`). No draft for `implement` phase. - `.egg-state/reviews/{identifier}-{phase}-{reviewer_type}-review.json` — Review verdict files - `.egg-state/agent-outputs/{identifier}-{role}-output.json` — Agent handoff data (e.g., `871-coder-output.json`). Falls back to `{role}-output.json` for backward compatibility. @@ -140,7 +140,7 @@ The orchestrator reads pipeline artifacts (verdict files, draft documents, check During the `implement` phase, certain `.egg-state/` subdirectories are mounted readonly into agent containers to prevent direct filesystem modifications to plan/contract artifacts: -| Directory | Implement phase | Refine/Plan phases | +| Directory | Implement phase | Analyze/Plan phases | |-----------|----------------|-------------------| | `.egg-state/contracts/` | Readonly | Writable | | `.egg-state/drafts/` | Readonly | Writable | @@ -196,7 +196,7 @@ When `coordinator_enabled: true` is set on a pipeline, a single **coordinator** See the [Coordinator Guide](../guides/coordinator.md) for operational details. -### Refine Phase Roles +### Analyze Phase Roles | Role | Responsibility | |------|----------------| @@ -245,7 +245,7 @@ Agent prompts are scoped to role-relevant context. Analysis roles (architect, ta ### Reviewer Execution -Reviewers always run as a separate step after all workers (and checkers, if applicable) complete. They spawn in parallel with a configurable concurrency limit (`max_parallel_agents`). In implement phase, reviewers run after the integrator completes. In plan phase, reviewers run after the task planner and risk analyst complete. In refine phase, reviewers run after the refiner completes. +Reviewers always run as a separate step after all workers (and checkers, if applicable) complete. They spawn in parallel with a configurable concurrency limit (`max_parallel_agents`). In implement phase, reviewers run after the integrator completes. In plan phase, reviewers run after the task planner and risk analyst complete. In analyze phase, reviewers run after the refiner completes. ### Wave Cycle Safety diff --git a/docs/guides/coordinator.md b/docs/guides/coordinator.md index d43a9caf8c..ef2f7c78eb 100644 --- a/docs/guides/coordinator.md +++ b/docs/guides/coordinator.md @@ -182,7 +182,7 @@ POST /api/v1/pipelines/{id}/coordinator/phase } ``` -Omit `target_phase` to advance to the next phase in sequence. When `target_phase` is provided, the action is recorded as `"skip"`; otherwise it is recorded as `"advance"`. Valid phases: `refine`, `plan`, `implement`, `pr`. +Omit `target_phase` to advance to the next phase in sequence. When `target_phase` is provided, the action is recorded as `"skip"`; otherwise it is recorded as `"advance"`. Valid phases: `analyze`, `plan`, `implement`, `pr`. ### Escalate to Human @@ -223,7 +223,7 @@ The coordinator analyzes each task and selects an appropriate workflow: ### Bug Fix (simple, clear reproduction) -Skip refine and plan phases, go straight to implementation: +Skip analyze and plan phases, go straight to implementation: ```bash egg-orch coordinator phase $PID --reason "Simple bug fix, skipping to implement" --target implement @@ -237,12 +237,12 @@ egg-orch coordinator spawn $PID --role tester --context "Test the auth fix" Run the full workflow with all phases: ``` -refine → plan → implement (coder + tester + documenter) → integrate → complete +analyze → plan → implement (coder + tester + documenter) → integrate → complete ``` ### Refactor (code restructuring) -Skip refine, go through plan: +Skip analyze, go through plan: ``` plan → implement (coder + tester) → integrate → complete @@ -253,7 +253,7 @@ plan → implement (coder + tester) → integrate → complete Start with a refiner to understand the problem: ``` -refine → assess output → decide next steps +analyze → assess output → decide next steps ``` ## Guardrails @@ -361,7 +361,7 @@ The coordinator container receives these additional environment variables: | `EGG_ISSUE_NUMBER` | issue number | Set when the pipeline is tied to a GitHub issue | | `EGG_PIPELINE_ID` | pipeline ID | Standard pipeline identifier | -The coordinator runs with `phase="coordinator"` — a special phase value distinct from the standard SDLC phases (`refine`, `plan`, `implement`, `pr`). +The coordinator runs with `phase="coordinator"` — a special phase value distinct from the standard SDLC phases (`analyze`, `plan`, `implement`, `pr`). ## Troubleshooting diff --git a/docs/guides/local-quickstart.md b/docs/guides/local-quickstart.md index 2ae396d375..1ed6298bdf 100644 --- a/docs/guides/local-quickstart.md +++ b/docs/guides/local-quickstart.md @@ -89,9 +89,9 @@ With no arguments, this starts a **local pipeline**. The agent will: 1. Ask what you want to build 2. Ask 1-2 clarifying questions 3. Create a local pipeline in the orchestrator -4. Run through refine → plan → implement → PR phases entirely locally +4. Run through analyze → plan → implement → PR phases entirely locally -During refine and plan phases, the gateway restricts pushes to state files and blocks PR operations. During the PR phase, the orchestrator auto-creates the PR using metadata from the plan, commit log, and diff stats — no agent is spawned. +During analyze and plan phases, the gateway restricts pushes to state files and blocks PR operations. During the PR phase, the orchestrator auto-creates the PR using metadata from the plan, commit log, and diff stats — no agent is spawned. **Local pipeline phases:** @@ -149,7 +149,7 @@ Here's what the issue pipeline creates and when: | Artifact | When | Details | |----------|------|---------| | **Issue comments** | Each phase | Analysis documents, plans, approval checkboxes, decision prompts | -| **Issue labels** | Phase transitions | `sdlc:refine`, `sdlc:plan`, `sdlc:implement`, `sdlc:pr` | +| **Issue labels** | Phase transitions | `sdlc:analyze`, `sdlc:plan`, `sdlc:implement`, `sdlc:pr` | | **Feature branch** | Implement phase | `egg/issue-` — pushed to your repo | | **Draft PR** | Implement phase | Links to the issue, contains all implementation commits | | **PR review comments** | Implement phase | Automated code review feedback (line-level) | diff --git a/docs/guides/sdlc-pipeline.md b/docs/guides/sdlc-pipeline.md index 424a801e22..0a8ce1fbc9 100644 --- a/docs/guides/sdlc-pipeline.md +++ b/docs/guides/sdlc-pipeline.md @@ -48,7 +48,7 @@ Code reviews are performed by the existing PR review workflow (`reusable-review. ### 4. Human-in-the-Loop at Critical Points -The pipeline pauses for human approval at phase transitions (refine and plan). The orchestrator's decision queue handles approval in both issue and local modes, and supports requesting changes with a circuit breaker (`max_review_cycles`, default 3) to prevent unbounded revision loops. +The pipeline pauses for human approval at phase transitions (analyze and plan). The orchestrator's decision queue handles approval in both issue and local modes, and supports requesting changes with a circuit breaker (`max_review_cycles`, default 3) to prevent unbounded revision loops. ## Pipeline Architecture @@ -58,7 +58,7 @@ The pipeline pauses for human approval at phase transitions (refine and plan). T ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ -│ │ REFINE │───▶│ PLAN │───▶│ IMPLEMENT │───▶│ CREATE │ │ +│ │ ANALYZE │───▶│ PLAN │───▶│ IMPLEMENT │───▶│ CREATE │ │ │ │ (cycles) │ │ (cycles) │ │ (cycles) │ │ PR │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └──────────┘ │ │ │ ╎ │ │ │ │ @@ -74,7 +74,7 @@ The pipeline pauses for human approval at phase transitions (refine and plan). T │ │ Approve │ ╎ │ Approve │ │ │ └─────────┘ ╎ └─────────┘ │ │ ╎ │ -│ ╎ short-circuit: REFINE ·····▶ IMPLEMENT (skips PLAN when approved) │ +│ ╎ short-circuit: ANALYZE ····▶ IMPLEMENT (skips PLAN when approved) │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` @@ -191,11 +191,11 @@ Returns a Server-Sent Events (SSE) stream for real-time updates across all activ "current_phase": "implement", "visualization": { "dag": ">>> ╔══════════════════════╗\n │ ▶ Implement │\n │ running (2 cycles completed) │\n │ ✓ coder ▶ reviewer│\n │ [last cycle: 5m0s | total: 15m0s]│\n ╚══════════════════════╝", - "compact": "✓Refine → ✓Plan → [▶Implement] → ○PR", + "compact": "✓Analyze → ✓Plan → [▶Implement] → ○PR", "progress": "[███████████░░░░░░░░░] 60%" }, "phases": { - "refine": {"status": "complete", "review_cycles": 2, "containers": 1, "agents": [{"role": "coder", "status": "complete"}]}, + "analyze": {"status": "complete", "review_cycles": 2, "containers": 1, "agents": [{"role": "coder", "status": "complete"}]}, "plan": {"status": "complete", "review_cycles": 1, "containers": 1, "agents": [{"role": "coder", "status": "complete"}]}, "implement": {"status": "running", "review_cycles": 2, "containers": 2, "agents": [{"role": "coder", "status": "complete"}, {"role": "reviewer", "status": "running"}]}, "pr": {"status": "pending", "review_cycles": 0, "containers": 0, "agents": []} @@ -270,14 +270,14 @@ Timing starts when actual work begins (`work_started_at`), excluding setup and H | Phase | Purpose | Allowed Operations | Exit Requires | |-------|---------|-------------------|---------------| -| **Refine** | Analyze issue, produce analysis document | `gh issue comment/edit` | Auto-review pass + Human approval | +| **Analyze** | Analyze issue, produce analysis document | `gh issue comment/edit` | Auto-review pass + Human approval | | **Plan** | Create implementation plan with tasks | `gh issue comment/edit`, `egg-contract add-decision` | Auto-review pass + Human approval | | **Implement** | Execute tasks on draft PR with CI and review feedback | `git push`, `egg-contract add-commit/update-notes` | All checks pass (CI + PR review) | | **PR** | Finalize PR for human review and merge | `gh pr edit`, `git push` | Human merge (closes issue automatically) | ### Complexity Tiers -The refine agent assesses task complexity and signals a tier in its analysis metadata block. The pipeline adapts its dispatch strategy accordingly: +The analyze agent assesses task complexity and signals a tier in its analysis metadata block. The pipeline adapts its dispatch strategy accordingly: | Tier | Signal | Dispatch Strategy | Use Case | |------|--------|-------------------|----------| @@ -285,13 +285,13 @@ The refine agent assesses task complexity and signals a tier in its analysis met | **Tier 2 (mid)** | `complexity_tier: mid` (default) | Full plan + single coder, wave-based multi-agent dispatch | Standard features, bug fixes, multi-file changes | | **Tier 3 (high)** | `complexity_tier: high` | Per-phase implement cycles with optional parallel execution | Large features, cross-cutting changes, multiple independent work items | -**Tier 1 (short-circuit)**: The refine agent includes `short_circuit: true` and `complexity_tier: low` in its analysis. After refine completes and internal review approves, the pipeline advances directly from refine to implement, using the analysis as guidance instead of a formal plan. If reviewers request revision, the signal is rechecked after the next cycle. This optimization is enabled by default and can be disabled via `allow_short_circuit`. +**Tier 1 (short-circuit)**: The analyze agent includes `short_circuit: true` and `complexity_tier: low` in its analysis. After analyze completes and internal review approves, the pipeline advances directly from analyze to implement, using the analysis as guidance instead of a formal plan. If reviewers request revision, the signal is rechecked after the next cycle. This optimization is enabled by default and can be disabled via `allow_short_circuit`. **Tier 2 (standard)**: The default. A full plan phase produces tasks, then a single implement phase dispatches agents in waves: Coder -> Tester/Documenter -> Integrator. **Tier 3 (phase-level dispatch)**: For high-complexity tasks, the plan decomposes work into phases with a dependency graph. Each plan phase becomes its own implement cycle (Coder -> Tester -> Agentic Review), with independent phases optionally running in parallel. After all phase cycles complete, an Integrator with write access merges results and fixes integration issues. See [Tier 3 Phase-Level Dispatch](#tier-3-phase-level-dispatch) for details. -The refine agent's metadata block format: +The analyze agent's metadata block format: ```yaml # metadata complexity_tier: high @@ -306,7 +306,7 @@ The orchestrator runs multiple specialized reviewers in parallel, with phase-spe | Phase | Reviewers | Focus | |-------|-----------|-------| -| **Refine** | Refine, Agent-Design | Analysis quality, agent-mode alignment | +| **Analyze** | Refine, Agent-Design | Analysis quality, agent-mode alignment | | **Plan** | Plan | Plan quality, task breakdown, alignment with analysis | | **Implement** | Contract, Code | Contract fulfillment, security, correctness | @@ -399,7 +399,7 @@ After all phases complete: **Phase-scoped prompts**: Each coder in a phase cycle receives a prompt scoped to that phase's tasks only (`_build_phase_scoped_prompt()`), preventing cross-phase context leakage. The prompt embeds the plan overview (goals, approach, constraints) rather than the full plan, with other phases shown as one-line summaries for orientation. A pointer to the full plan file is included for on-demand access. -**Parallel execution**: When `parallel_phases: true` is signaled by the refine agent and `PipelineConfig.enable_parallel_phases` is set, independent phases within the same wave run concurrently using `ThreadPoolExecutor`. The `PipelineConfig.max_parallel_agents` controls concurrency. +**Parallel execution**: When `parallel_phases: true` is signaled by the analyze agent and `PipelineConfig.enable_parallel_phases` is set, independent phases within the same wave run concurrently using `ThreadPoolExecutor`. The `PipelineConfig.max_parallel_agents` controls concurrency. **Pipeline model changes**: The `Pipeline` model tracks `complexity_tier` (a `ComplexityTier` enum: `low`, `mid`, `high`), and `PipelineConfig` has an `enable_parallel_phases` flag. For DAG visualization, the model also stores `plan_phase_waves` (dependency-ordered list of wave groups) and `plan_phase_names` (phase ID to human-readable name mapping), populated at Tier 3 implement start by `_run_tier3_implement()`. @@ -419,11 +419,11 @@ After all phases complete: | `gateway/agent_restrictions.py` | `INTEGRATOR_TIER3_PATTERNS`, tier-aware `get_agent_pattern()` | | `gateway/worktree_manager.py` | `create_phase_worktree()`, `cleanup_phase_worktrees()` | -### Refine and Plan Phase Review Cycles +### Analyze and Plan Phase Review Cycles -The refine and plan phases include an automated internal review step before human approval. All reviews happen internally without posting to the issue until approval: +The analyze and plan phases include an automated internal review step before human approval. All reviews happen internally without posting to the issue until approval: -1. **Producer agent runs** — The refine/plan agent writes its output to `.egg-state/drafts/{identifier}-{analysis|plan}.md` +1. **Producer agent runs** — The analyze/plan agent writes its output to `.egg-state/drafts/{identifier}-{analysis|plan}.md` 2. **Reviewer agents run in parallel** — Each reviewer reads the draft and writes verdict to its own file 3. **Verdicts aggregated** — If any reviewer needs revision, the aggregate verdict is `needs_revision` 4. **If approved** — The final draft is posted to the issue with an approval checkbox for human review @@ -436,11 +436,11 @@ The refine and plan phases include an automated internal review step before huma .egg-state/ ├── contracts/{identifier}.json # Contract state ├── drafts/ -│ ├── {identifier}-analysis.md # Refine phase draft +│ ├── {identifier}-analysis.md # Analyze phase draft │ └── {identifier}-plan.md # Plan phase draft └── reviews/ - ├── {identifier}-refine-refine-review.json # Refine review verdict - ├── {identifier}-refine-agent-design-review.json # Agent-design review verdict + ├── {identifier}-analyze-refine-review.json # Refine review verdict + ├── {identifier}-analyze-agent-design-review.json # Agent-design review verdict ├── {identifier}-plan-plan-review.json # Plan review verdict ├── {identifier}-implement-code-review.json # Code review verdict └── {identifier}-implement-contract-review.json # Contract review verdict @@ -460,7 +460,7 @@ The refine and plan phases include an automated internal review step before huma ``` **Field guidelines:** -- **analysis**: Always populated regardless of verdict. Describes what was reviewed, what was found, and reasoning. Code reviewers provide file-by-file analysis; contract reviewers provide criterion-by-criterion verification; plan/refine reviewers provide section-by-section evaluation. +- **analysis**: Always populated regardless of verdict. Describes what was reviewed, what was found, and reasoning. Code reviewers provide file-by-file analysis; contract reviewers provide criterion-by-criterion verification; plan/analyze reviewers provide section-by-section evaluation. - **suggestions**: Non-blocking observations and improvement ideas, included even when approving. These are surfaced as advisory content for observability. - **feedback**: Reserved for blocking issues only — problems that must be fixed before approval. Empty when the verdict is `approved`. @@ -474,9 +474,9 @@ All SDLC reviewers follow quality standards aligned with the PR reviewer workflo 4. **Suggest fixes** — When identifying a problem, include a concrete suggestion for resolution 5. **Provide context** — Explain *why* something is an issue (impact, risk, or principle violated) -Code reviewers additionally receive "last line of defense" framing and must provide file-by-file analysis of all changed files. Draft-based reviewers (refine, plan) follow expanded procedural steps that require cross-referencing each section against criteria and citing specific evidence. +Code reviewers additionally receive "last line of defense" framing and must provide file-by-file analysis of all changed files. Draft-based reviewers (analyze, plan) follow expanded procedural steps that require cross-referencing each section against criteria and citing specific evidence. -**Review Criteria for Refine:** +**Review Criteria for Analyze:** - Does the analysis address the issue description? - Are options meaningfully different and well-reasoned? - Are constraints and dependencies identified? @@ -504,7 +504,7 @@ Each phase has a defined set of permitted operations. The gateway blocks all oth 5. If the operation is not allowed for that phase (per `.egg/phase-permissions.json`), the gateway returns HTTP 403 **Phase restrictions:** -- **Refine/Plan phases**: Cannot `git push` or `gh pr create`—prevents code changes before plan approval +- **Analyze/Plan phases**: Cannot `git push` or `gh pr create`—prevents code changes before plan approval - **Implement phase**: Can `git push` to the branch; draft PR is created automatically by the pipeline (not by agent) - **PR phase**: PR is auto-created by the orchestrator from contract metadata and git log (no agent spawned). Human must merge. @@ -807,7 +807,7 @@ For detailed documentation on HITL workflows, see [HITL Decisions](../hitl-decis ### Decision Sync to Contract -Pipeline decisions made during refine and plan phases are automatically synced to the contract after each phase completes. This ensures implement-phase agents have visibility into substantive choices (e.g., database selection, API style, config handling) made earlier in the pipeline. +Pipeline decisions made during analyze and plan phases are automatically synced to the contract after each phase completes. This ensures implement-phase agents have visibility into substantive choices (e.g., database selection, API style, config handling) made earlier in the pipeline. **How it works:** @@ -928,7 +928,7 @@ The implement job monitors remaining time: ## Document Standards -### Analysis Document (Refine Phase Output) +### Analysis Document (Analyze Phase Output) Path: `docs/issues/{number}-analysis.md` @@ -1015,7 +1015,7 @@ If not configured, the checker+autofixer agent falls back to auto-discovery (sca | Check | ID | Purpose | Fixable | |-------|-----|---------|---------| -| **Draft Validation** | `check-draft-validation` | Validates refine phase analysis document | No | +| **Draft Validation** | `check-draft-validation` | Validates analyze phase analysis document | No | | **Plan YAML** | `check-plan-yaml` | Validates plan phase YAML appendix | No | | **Merge Conflict** | `check-merge-conflict` | Detects conflicts with base branch | No | | **Lint** | `check-lint` | Runs `make lint` if available | Yes | @@ -1027,7 +1027,7 @@ If not configured, the checker+autofixer agent falls back to auto-discovery (sca Default checks for each phase are defined in `shared/egg_contracts/phase_defaults.py`: -**Refine phase:** +**Analyze phase:** - Draft validation (required) **Plan phase:** diff --git a/docs/hitl-decisions.md b/docs/hitl-decisions.md index 6d183fb838..20a55d138f 100644 --- a/docs/hitl-decisions.md +++ b/docs/hitl-decisions.md @@ -5,7 +5,7 @@ This document explains how human decisions are captured and processed in the SDL ## Overview The SDLC pipeline includes phases where human input is required before proceeding: -- **Refine phase**: Human approves the analysis before planning +- **Analyze phase**: Human approves the analysis before planning - **Plan phase**: Human approves the implementation plan before coding Three mechanisms exist for gathering human input: @@ -15,7 +15,7 @@ Three mechanisms exist for gathering human input: In local mode, decisions carry a `decision_type` field (`phase_gate`, `choice`, or `feedback`) that drives type-specific terminal rendering. The orchestrator's decision queue supports a "request changes" option at phase gates, with a circuit breaker (`max_hitl_review_cycles`, default 3) to prevent unbounded revision loops. See [Local Mode: Type-Aware Terminal Rendering](#local-mode-type-aware-terminal-rendering) for details. -**Decision sync to contract**: Resolved decisions made during refine and plan phases are automatically synced to the contract (`.egg-state/contracts/{identifier}.json`) after each phase completes, so implement-phase agents can see substantive choices (database selection, API style, config handling, etc.) made earlier. Phase gate decisions (approve/reject) are excluded from sync as they are process control, not implementation-relevant context. See [SDLC Pipeline Guide § Decision Sync to Contract](sdlc-pipeline.md#decision-sync-to-contract) for details. +**Decision sync to contract**: Resolved decisions made during analyze and plan phases are automatically synced to the contract (`.egg-state/contracts/{identifier}.json`) after each phase completes, so implement-phase agents can see substantive choices (database selection, API style, config handling, etc.) made earlier. Phase gate decisions (approve/reject) are excluded from sync as they are process control, not implementation-relevant context. See [SDLC Pipeline Guide § Decision Sync to Contract](sdlc-pipeline.md#decision-sync-to-contract) for details. ## Formal HITL Decisions @@ -147,7 +147,7 @@ Phase approval is a simpler mechanism for advancing the pipeline at HITL gates. ### How It Works -1. The agent includes this at the end of phase completion comments (refine and plan phases) +1. The agent includes this at the end of phase completion comments (analyze and plan phases) 2. The `` marker identifies the approval section 3. When the human checks the `[x] Approve` checkbox, the orchestrator detects the change 4. The contract phase is updated and the next pipeline phase is triggered @@ -275,7 +275,7 @@ client.create_decision( {"id": "q1", "question": "What is the expected traffic volume?"}, {"id": "q2", "question": "Any specific performance requirements?"}, ], - phase="refine", # Optional: helps sandbox locate correct draft paths + phase="analyze", # Optional: helps sandbox locate correct draft paths ) ``` diff --git a/docs/index.md b/docs/index.md index 0f56a2054e..924d638def 100644 --- a/docs/index.md +++ b/docs/index.md @@ -70,7 +70,7 @@ This index helps both humans and LLMs navigate the documentation efficiently. | Document | Description | |----------|-------------| -| [Analysis Template](templates/analysis.md) | Problem analysis template for the refine phase | +| [Analysis Template](templates/analysis.md) | Problem analysis template for the analyze phase | | [Plan Template](templates/plan.md) | Implementation plan template with task ID format for the plan phase | | [Phase Completion Template](templates/phase-completion.md) | Phase completion comment format with approval checkbox | | [Feedback Template](templates/feedback.md) | Feedback comment template for open-ended questions | diff --git a/docs/templates/analysis.md b/docs/templates/analysis.md index cd8129c7f9..b823ef4373 100644 --- a/docs/templates/analysis.md +++ b/docs/templates/analysis.md @@ -1,6 +1,6 @@ # Analysis: [Issue Title] -> Issue: #[number] | Phase: refine +> Issue: #[number] | Phase: analyze ## Problem Statement diff --git a/docs/templates/phase-completion.md b/docs/templates/phase-completion.md index 74f12197f2..0763e5ac9b 100644 --- a/docs/templates/phase-completion.md +++ b/docs/templates/phase-completion.md @@ -31,12 +31,12 @@ which the orchestrator's decision queue detects when edited. - The `` marker must appear on the line immediately before the approval checkbox - When the human checks the `[x] Approve` checkbox, the workflow detects the edit and advances to the next phase - Keep the approval checkbox as a single option to avoid confusion -- The phase name in the heading should match the current SDLC phase (refine, plan, implement) +- The phase name in the heading should match the current SDLC phase (analyze, plan, implement) -## Example: Refine Phase Complete +## Example: Analyze Phase Complete ```markdown -## Refine Phase Complete +## Analyze Phase Complete Analysis of issue #123 is complete. The recommended approach is to implement Option B (Redis caching) based on the constraints identified. diff --git a/gateway/README.md b/gateway/README.md index 7ff573aa3f..15c592c562 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -165,7 +165,7 @@ When a pipeline agent container exits (normally or via timeout), the gateway aut | Phase | Allowed Operations | Blocked Operations | Exit Requires | |-------|-------------------|-------------------|---------------| -| **refine** | `gh issue comment/edit`, `egg-contract add-decision` | `git push`, `gh pr create` | Human approval | +| **analyze** | `gh issue comment/edit`, `egg-contract add-decision` | `git push`, `gh pr create` | Human approval | | **plan** | `gh issue comment/edit`, `egg-contract add-decision` | `git push`, `gh pr create` | Human approval | | **implement** | `git push`, `egg-contract add-commit/update-notes` | `gh pr create` | All checks pass | | **pr** | `gh pr create/edit`, `git push` | — | Human merge | @@ -187,7 +187,7 @@ POST /api/v1/sessions/create but optional for local mode (orchestrator-internal temp sessions for git auth only) PATCH /api/v1/sessions//phase - Request: {phase: "refine"|"plan"|"implement"|"pr"} + Request: {phase: "analyze"|"plan"|"implement"|"pr"} Auth: Bearer {launcher_secret} Description: Update the SDLC pipeline phase for a session ``` diff --git a/gateway/agent_restrictions.py b/gateway/agent_restrictions.py index 4eba7121ac..ead7b1f1a5 100644 --- a/gateway/agent_restrictions.py +++ b/gateway/agent_restrictions.py @@ -12,7 +12,7 @@ - Tester: Can write test files only - Documenter: Can write docs and markdown only - Integrator: Can only write handoff output (read-only otherwise) -- Refiner: Can write drafts and agent-outputs only, blocked from source code and contracts +- Refiner: Can write drafts and agent-outputs only, blocked from source code and contracts (analyze phase) - Reviewers: Can write reviews and agent-outputs only The gateway uses these restrictions during git push to validate that @@ -45,7 +45,7 @@ class AgentRole: ARCHITECT = "architect" TASK_PLANNER = "task_planner" RISK_ANALYST = "risk_analyst" - # Refine-phase roles + # Analyze-phase roles REFINER = "refiner" # Reviewer roles REVIEWER_CODE = "reviewer_code" @@ -443,7 +443,7 @@ def _matches_pattern(file_path: str, pattern: str) -> bool: blocked_patterns=_REVIEWER_BLOCKED, ) -# Refine-phase agent patterns +# Analyze-phase agent patterns REFINER_PATTERNS = AgentFilePattern( role=AgentRole.REFINER, diff --git a/gateway/gateway.py b/gateway/gateway.py index f89d9ca9b4..441eafeba9 100644 --- a/gateway/gateway.py +++ b/gateway/gateway.py @@ -833,7 +833,7 @@ def git_push() -> tuple[Response, int] | Response: # SECURITY: Check phase-based file restrictions for local mode sessions. # This replaces the blanket local-mode push block with granular phase-based # restrictions. Each phase has specific allowed/blocked file patterns: - # - refine/plan: Can only push .egg-state/ files (contracts, drafts, checkpoints) + # - analyze/plan: Can only push .egg-state/ files (contracts, drafts, checkpoints) # - implement: Can push code but not .egg-state/ (except checkpoints) # - pr: Can push everything # @@ -3478,6 +3478,10 @@ def session_create() -> tuple[Response, int] | Response: if gid is not None and (not isinstance(gid, int) or gid < 0): return make_error("Invalid gid: must be a non-negative integer") + # Backwards compat: normalize legacy "refine" → "analyze" + if phase == "refine": + phase = "analyze" + # Validate phase if provided if phase is not None and phase not in VALID_PIPELINE_PHASES: return make_error( @@ -4032,7 +4036,7 @@ def session_update_phase(session_token: str) -> tuple[Response, int] | Response: Request body: { - "phase": "refine"|"plan"|"implement"|"pr" + "phase": "analyze"|"plan"|"implement"|"pr" } Args: @@ -4048,6 +4052,10 @@ def session_update_phase(session_token: str) -> tuple[Response, int] | Response: if not phase: return make_error("Missing phase") + # Backwards compat: normalize legacy "refine" → "analyze" + if phase == "refine": + phase = "analyze" + if phase not in VALID_PIPELINE_PHASES: return make_error( f"Invalid phase: {phase}. Must be one of: {', '.join(sorted(VALID_PIPELINE_PHASES))}" diff --git a/gateway/phase_api.py b/gateway/phase_api.py index 5e6badd3b1..ea6aabf4c0 100644 --- a/gateway/phase_api.py +++ b/gateway/phase_api.py @@ -472,7 +472,7 @@ def get_phase_permissions(phase: str) -> tuple[Response, int]: Get the permissions for a specific phase. URL params: - phase: Phase name (refine, plan, implement, pr) + phase: Phase name (analyze, plan, implement, pr) Returns: {"success": true, "data": {"allowed": [...], "blocked": [...], "exit_requires": "..."}} diff --git a/gateway/phase_filter.py b/gateway/phase_filter.py index 3c20331940..62aa254f08 100644 --- a/gateway/phase_filter.py +++ b/gateway/phase_filter.py @@ -2,7 +2,7 @@ Phase-based operation filtering for the SDLC pipeline. This module enforces phase-specific operation restrictions. Each pipeline phase -(refine, plan, implement, pr) has a defined set of permitted and blocked operations. +(analyze, plan, implement, pr) has a defined set of permitted and blocked operations. The gateway uses this module to filter operations against the current phase. """ @@ -408,7 +408,7 @@ def _load_permissions(self) -> None: def _get_default_permissions(self) -> dict[PipelinePhase, PhasePermissions]: """Get default permissions when no file is available.""" return { - PipelinePhase.REFINE: PhasePermissions( + PipelinePhase.ANALYZE: PhasePermissions( allowed_operations=[ Operation(OperationType.GH, "issue comment *", "Comment on issues"), Operation(OperationType.GH, "issue edit *", "Edit issues"), @@ -424,7 +424,7 @@ def _get_default_permissions(self) -> dict[PipelinePhase, PhasePermissions]: Operation( OperationType.GH, "pr create*", - "Cannot create PRs during refine", + "Cannot create PRs during analyze", ), ], exit_requires="human", @@ -515,13 +515,13 @@ def _get_default_phase_file_restrictions( """Get default phase-based file restrictions. These defaults define which files can be pushed during each phase: - - refine: Only .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) + - analyze: Only .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) - plan: Only .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) - implement: Code only, not .egg-state/ (except checkpoints and agent-outputs) - pr: Everything """ return { - PipelinePhase.REFINE: PhaseFileRestriction( + PipelinePhase.ANALYZE: PhaseFileRestriction( allowed_patterns=[ ".egg-state/contracts/*", ".egg-state/drafts/*analysis*", @@ -529,7 +529,7 @@ def _get_default_phase_file_restrictions( ".egg-state/agent-outputs/*", ".egg-state/reviews/*", ], - description="Refine phase can only push contracts, analysis drafts, checkpoints, agent outputs, and reviews", + description="Analyze phase can only push contracts, analysis drafts, checkpoints, agent outputs, and reviews", ), PipelinePhase.PLAN: PhaseFileRestriction( allowed_patterns=[ @@ -648,7 +648,7 @@ def check_phase_file_restrictions( proper separation of concerns in the SDLC pipeline. SECURITY: This is a critical control for pipeline integrity: - - refine/plan phases can only modify .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) + - analyze/plan phases can only modify .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) - implement phase can modify code but not .egg-state/ (except checkpoints and agent-outputs) - pr phase has full access @@ -919,7 +919,7 @@ def check_phase_file_restrictions( separation of concerns in the SDLC pipeline. SECURITY: Enforces phase-based file access control: - - refine/plan: Can only push .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) + - analyze/plan: Can only push .egg-state/ files (contracts, drafts, checkpoints, agent-outputs, reviews) - implement: Can push code but not .egg-state/ (except checkpoints and agent-outputs) - pr: Can push everything diff --git a/gateway/phase_transition.py b/gateway/phase_transition.py index 862170b0ec..3f0b095983 100644 --- a/gateway/phase_transition.py +++ b/gateway/phase_transition.py @@ -39,7 +39,7 @@ class TransitionRole(StrEnum): # Phase transition graph: defines which phases can transition to which VALID_TRANSITIONS: dict[PipelinePhase, list[PipelinePhase]] = { - PipelinePhase.REFINE: [PipelinePhase.PLAN], + PipelinePhase.ANALYZE: [PipelinePhase.PLAN], PipelinePhase.PLAN: [PipelinePhase.IMPLEMENT], PipelinePhase.IMPLEMENT: [PipelinePhase.PR], PipelinePhase.PR: [], # Terminal state - no automatic transitions diff --git a/gateway/session_manager.py b/gateway/session_manager.py index 209e8a9bc9..772b87e819 100644 --- a/gateway/session_manager.py +++ b/gateway/session_manager.py @@ -271,7 +271,7 @@ class Session: last_seen: Last request timestamp (for heartbeat) expires_at: Session expiry timestamp agent_role: Role set by workflow context for contract operations - phase: SDLC pipeline phase (refine, plan, implement, pr) for operation filtering + phase: SDLC pipeline phase (analyze, plan, implement, pr) for operation filtering """ session_token: str | None # Raw token, only in memory @@ -522,7 +522,7 @@ def register_session( container_id: Docker container ID container_ip: Container's IP address on the Docker network mode: Repository visibility mode (private or public) - phase: SDLC pipeline phase (e.g., "refine", "plan", "implement", "pr") + phase: SDLC pipeline phase (e.g., "analyze", "plan", "implement", "pr") issue_number: Optional GitHub issue number for checkpoint linkage pr_number: Optional GitHub PR number for checkpoint linkage pipeline_id: Optional pipeline run ID for multi-agent correlation @@ -787,7 +787,7 @@ def update_phase(self, token: str, phase: str) -> bool: Args: token: The session token - phase: The new phase value (e.g., "refine", "plan", "implement", "pr") + phase: The new phase value (e.g., "analyze", "plan", "implement", "pr") Returns: True if phase was updated, False if session not found diff --git a/gateway/tests/test_gateway.py b/gateway/tests/test_gateway.py index 8c88392b58..2b6768324d 100644 --- a/gateway/tests/test_gateway.py +++ b/gateway/tests/test_gateway.py @@ -1439,10 +1439,10 @@ def test_pr_create_allowed_without_phase_backward_compat(self, client, auth_head data = json.loads(response.data) assert data["success"] is True - def test_pr_create_blocked_during_refine_phase(self, client, auth_headers_with_phase): - """PR create is blocked when session phase is 'refine'.""" + def test_pr_create_blocked_during_analyze_phase(self, client, auth_headers_with_phase): + """PR create is blocked when session phase is 'analyze'.""" headers, mock_result, mock_policy_result, current_session_manager = auth_headers_with_phase( - "refine" + "analyze" ) with ( diff --git a/gateway/tests/test_phase_api.py b/gateway/tests/test_phase_api.py index 156418c579..a0b5de7d03 100644 --- a/gateway/tests/test_phase_api.py +++ b/gateway/tests/test_phase_api.py @@ -80,7 +80,7 @@ def mock_contract(): title="Test Issue", url="https://github.com/test/repo/issues/123", ), - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, ) @@ -119,7 +119,7 @@ def test_get_current_phase_success(self, client, auth_headers, mock_contract): assert response.status_code == 200 data = response.get_json() assert data["success"] is True - assert data["data"]["phase"] == "refine" + assert data["data"]["phase"] == "analyze" assert data["data"]["exit_requires"] == "human" assert data["data"]["next_phase"] == "plan" @@ -149,17 +149,17 @@ def test_get_current_phase_not_found(self, client, auth_headers): class TestGetPhasePermissions: """Tests for GET /api/v1/phase/permissions/.""" - def test_get_permissions_refine(self, client, auth_headers): - """Get permissions for refine phase.""" + def test_get_permissions_analyze(self, client, auth_headers): + """Get permissions for analyze phase.""" response = client.get( - "/api/v1/phase/permissions/refine", + "/api/v1/phase/permissions/analyze", headers=auth_headers, ) assert response.status_code == 200 data = response.get_json() assert data["success"] is True - assert data["data"]["phase"] == "refine" + assert data["data"]["phase"] == "analyze" assert data["data"]["exit_requires"] == "human" assert len(data["data"]["blocked_operations"]) > 0 @@ -282,7 +282,7 @@ def test_advance_phase_success(self, client, auth_headers, mock_contract): assert response.status_code == 200 data = response.get_json() assert data["success"] is True - assert data["data"]["from_phase"] == "refine" + assert data["data"]["from_phase"] == "analyze" assert data["data"]["to_phase"] == "plan" def test_advance_phase_unauthorized(self, client, mock_contract): diff --git a/gateway/tests/test_phase_filter.py b/gateway/tests/test_phase_filter.py index 212fa2cee2..204dcb9983 100644 --- a/gateway/tests/test_phase_filter.py +++ b/gateway/tests/test_phase_filter.py @@ -97,7 +97,7 @@ def custom_permissions_file(self) -> Path: permissions = { "schemaVersion": "1.0", "phases": { - "refine": { + "analyze": { "allowed_operations": [ {"type": "gh", "pattern": "issue comment *", "description": "Comment"}, ], @@ -124,7 +124,7 @@ def custom_permissions_file(self) -> Path: def test_load_from_file(self, custom_permissions_file: Path): """Load permissions from a file.""" pf = PhaseFilter(permissions_path=custom_permissions_file) - permissions = pf.get_permissions(PipelinePhase.REFINE) + permissions = pf.get_permissions(PipelinePhase.ANALYZE) assert permissions is not None assert permissions.exit_requires == "human" @@ -142,13 +142,13 @@ def test_filter_blocked_operation(self, custom_permissions_file: Path): """Blocked operations are correctly identified.""" pf = PhaseFilter(permissions_path=custom_permissions_file) result = pf.filter_operation( - PipelinePhase.REFINE, + PipelinePhase.ANALYZE, OperationType.GIT, "push origin main", ) assert result.allowed is False - assert result.phase == PipelinePhase.REFINE + assert result.phase == PipelinePhase.ANALYZE assert result.operation_type == OperationType.GIT assert "push" in result.message.lower() @@ -156,7 +156,7 @@ def test_filter_allowed_operation(self, custom_permissions_file: Path): """Allowed operations are correctly identified.""" pf = PhaseFilter(permissions_path=custom_permissions_file) result = pf.filter_operation( - PipelinePhase.REFINE, + PipelinePhase.ANALYZE, OperationType.GH, "issue comment 123", ) @@ -167,7 +167,7 @@ def test_filter_not_explicitly_blocked(self, custom_permissions_file: Path): """Operations not explicitly blocked are allowed.""" pf = PhaseFilter(permissions_path=custom_permissions_file) result = pf.filter_operation( - PipelinePhase.REFINE, + PipelinePhase.ANALYZE, OperationType.EGG_CONTRACT, "show", ) @@ -180,7 +180,7 @@ def test_is_operation_blocked_helper(self, custom_permissions_file: Path): pf = PhaseFilter(permissions_path=custom_permissions_file) assert ( - pf.is_operation_blocked(PipelinePhase.REFINE, OperationType.GIT, "push origin main") + pf.is_operation_blocked(PipelinePhase.ANALYZE, OperationType.GIT, "push origin main") is True ) assert ( @@ -192,7 +192,7 @@ def test_get_exit_requirement(self, custom_permissions_file: Path): """Get exit requirement for a phase.""" pf = PhaseFilter(permissions_path=custom_permissions_file) - assert pf.get_exit_requirement(PipelinePhase.REFINE) == "human" + assert pf.get_exit_requirement(PipelinePhase.ANALYZE) == "human" assert pf.get_exit_requirement(PipelinePhase.IMPLEMENT) == "reviewer" @@ -225,12 +225,12 @@ def test_filter_with_enums(self): class TestIsOperationBlockedFunction: """Tests for the convenience is_operation_blocked function.""" - def test_push_allowed_during_refine(self): - """Git push is allowed during refine phase (issue #543 change).""" + def test_push_allowed_during_analyze(self): + """Git push is allowed during analyze phase (issue #543 change).""" # Previously blocked, now allowed with file restrictions phase_filter._filter = None - assert is_operation_blocked("refine", "git", "push origin main") is False + assert is_operation_blocked("analyze", "git", "push origin main") is False def test_allowed_during_implement(self): """Git push is allowed during implement phase.""" @@ -242,7 +242,7 @@ def test_pr_create_blocked_until_pr_phase(self): """PR create is blocked until PR phase.""" phase_filter._filter = None - assert is_operation_blocked("refine", "gh", "pr create") is True + assert is_operation_blocked("analyze", "gh", "pr create") is True assert is_operation_blocked("plan", "gh", "pr create") is True assert is_operation_blocked("implement", "gh", "pr create") is True assert is_operation_blocked("pr", "gh", "pr create") is False @@ -258,16 +258,16 @@ def reset_filter(self): yield phase_filter._filter = None - def test_refine_phase_allows_push(self): - """Refine phase allows git push (with file restrictions applied separately).""" + def test_analyze_phase_allows_push(self): + """Analyze phase allows git push (with file restrictions applied separately).""" pf = PhaseFilter(permissions_path=Path("/nonexistent")) - result = pf.filter_operation(PipelinePhase.REFINE, OperationType.GIT, "push origin main") + result = pf.filter_operation(PipelinePhase.ANALYZE, OperationType.GIT, "push origin main") assert result.allowed is True - def test_refine_phase_blocks_pr_create(self): - """Refine phase blocks PR creation.""" + def test_analyze_phase_blocks_pr_create(self): + """Analyze phase blocks PR creation.""" pf = PhaseFilter(permissions_path=Path("/nonexistent")) - result = pf.filter_operation(PipelinePhase.REFINE, OperationType.GH, "pr create") + result = pf.filter_operation(PipelinePhase.ANALYZE, OperationType.GH, "pr create") assert result.allowed is False def test_plan_phase_allows_push(self): @@ -340,8 +340,8 @@ def reset_filter(self): def test_pr_create_without_args_matches_pattern(self): """'pr create' without arguments matches 'pr create*' pattern.""" pf = PhaseFilter(permissions_path=Path("/nonexistent")) - # In refine phase, pr create should be blocked - result = pf.filter_operation(PipelinePhase.REFINE, OperationType.GH, "pr create") + # In analyze phase, pr create should be blocked + result = pf.filter_operation(PipelinePhase.ANALYZE, OperationType.GH, "pr create") assert result.allowed is False # In pr phase, pr create should be allowed @@ -360,7 +360,7 @@ def test_partial_command_does_not_match_blocked_pattern(self): """Commands that partially match blocked patterns should not be blocked.""" pf = PhaseFilter(permissions_path=Path("/nonexistent")) # 'push-status' should not match 'push *' pattern - result = pf.filter_operation(PipelinePhase.REFINE, OperationType.GIT, "push-status") + result = pf.filter_operation(PipelinePhase.ANALYZE, OperationType.GIT, "push-status") # push-status doesn't match "push *" because there's no space after push assert result.allowed is True @@ -376,8 +376,8 @@ def test_pr_creates_typo_not_blocked(self): """'pr creates' (typo) should not be blocked by 'pr create*' pattern.""" pf = PhaseFilter(permissions_path=Path("/nonexistent")) # 'pr creates' matches 'pr create*' because * matches 's' - result = pf.filter_operation(PipelinePhase.REFINE, OperationType.GH, "pr creates") - assert result.allowed is False # It does match, so it's blocked in refine + result = pf.filter_operation(PipelinePhase.ANALYZE, OperationType.GH, "pr creates") + assert result.allowed is False # It does match, so it's blocked in analyze class TestFileRestriction: @@ -743,45 +743,45 @@ def teardown_method(self): phase_filter._filter = None - def test_refine_phase_allows_contracts(self): - """Refine phase should allow .egg-state/contracts/ files.""" + def test_analyze_phase_allows_contracts(self): + """Analyze phase should allow .egg-state/contracts/ files.""" from phase_filter import check_phase_file_restrictions result = check_phase_file_restrictions( - "refine", + "analyze", [".egg-state/contracts/123.json"], ) assert result.allowed is True - def test_refine_phase_allows_analysis_drafts(self): - """Refine phase should allow analysis draft files.""" + def test_analyze_phase_allows_analysis_drafts(self): + """Analyze phase should allow analysis draft files.""" from phase_filter import check_phase_file_restrictions result = check_phase_file_restrictions( - "refine", + "analyze", [".egg-state/drafts/local-abc-analysis.md"], ) assert result.allowed is True - def test_refine_phase_allows_checkpoints(self): - """Refine phase should allow checkpoint files.""" + def test_analyze_phase_allows_checkpoints(self): + """Analyze phase should allow checkpoint files.""" from phase_filter import check_phase_file_restrictions result = check_phase_file_restrictions( - "refine", + "analyze", [".egg-state/checkpoints/commit-abc123.json"], ) assert result.allowed is True - def test_refine_phase_blocks_code_files(self): - """Refine phase should block code files.""" + def test_analyze_phase_blocks_code_files(self): + """Analyze phase should block code files.""" from phase_filter import check_phase_file_restrictions result = check_phase_file_restrictions( - "refine", + "analyze", ["src/main.py", "README.md"], ) @@ -1047,23 +1047,23 @@ def test_plan_phase_allows_reviews(self): assert result.allowed is True - def test_refine_phase_allows_agent_outputs(self): - """Refine phase should allow .egg-state/agent-outputs/ files.""" + def test_analyze_phase_allows_agent_outputs(self): + """Analyze phase should allow .egg-state/agent-outputs/ files.""" from phase_filter import check_phase_file_restrictions result = check_phase_file_restrictions( - "refine", + "analyze", [".egg-state/agent-outputs/risk-analyst-output.json"], ) assert result.allowed is True - def test_refine_phase_allows_reviews(self): - """Refine phase should allow .egg-state/reviews/ files.""" + def test_analyze_phase_allows_reviews(self): + """Analyze phase should allow .egg-state/reviews/ files.""" from phase_filter import check_phase_file_restrictions result = check_phase_file_restrictions( - "refine", + "analyze", [".egg-state/reviews/risk-review.md"], ) diff --git a/gateway/tests/test_phase_filter_restrictions.py b/gateway/tests/test_phase_filter_restrictions.py index 565f6bff7c..6d5da227a3 100644 --- a/gateway/tests/test_phase_filter_restrictions.py +++ b/gateway/tests/test_phase_filter_restrictions.py @@ -202,34 +202,34 @@ def reset_filter(self): yield reset_phase_filter() - def test_refine_allows_only_egg_state(self): + def test_analyze_allows_only_egg_state(self): pf = PhaseFilter() - result = pf.check_phase_file_restrictions("refine", ["src/app.py"]) + result = pf.check_phase_file_restrictions("analyze", ["src/app.py"]) assert result.allowed is False - def test_refine_allows_contracts(self): + def test_analyze_allows_contracts(self): pf = PhaseFilter() - result = pf.check_phase_file_restrictions("refine", [".egg-state/contracts/123.json"]) + result = pf.check_phase_file_restrictions("analyze", [".egg-state/contracts/123.json"]) assert result.allowed is True - def test_refine_allows_analysis_drafts(self): + def test_analyze_allows_analysis_drafts(self): pf = PhaseFilter() - result = pf.check_phase_file_restrictions("refine", [".egg-state/drafts/644-analysis.md"]) + result = pf.check_phase_file_restrictions("analyze", [".egg-state/drafts/644-analysis.md"]) assert result.allowed is True - def test_refine_allows_checkpoints(self): + def test_analyze_allows_checkpoints(self): pf = PhaseFilter() - result = pf.check_phase_file_restrictions("refine", [".egg-state/checkpoints/ckpt.json"]) + result = pf.check_phase_file_restrictions("analyze", [".egg-state/checkpoints/ckpt.json"]) assert result.allowed is True - def test_refine_allows_agent_outputs(self): + def test_analyze_allows_agent_outputs(self): pf = PhaseFilter() - result = pf.check_phase_file_restrictions("refine", [".egg-state/agent-outputs/out.json"]) + result = pf.check_phase_file_restrictions("analyze", [".egg-state/agent-outputs/out.json"]) assert result.allowed is True - def test_refine_allows_reviews(self): + def test_analyze_allows_reviews(self): pf = PhaseFilter() - result = pf.check_phase_file_restrictions("refine", [".egg-state/reviews/review.json"]) + result = pf.check_phase_file_restrictions("analyze", [".egg-state/reviews/review.json"]) assert result.allowed is True def test_plan_allows_plan_drafts(self): @@ -336,8 +336,8 @@ def test_is_operation_blocked_convenience(self): assert is_operation_blocked("implement", "gh", "pr create --title x") is True assert is_operation_blocked("implement", "git", "push origin branch") is False - def test_pr_create_blocked_during_refine(self): - result = filter_operation("refine", "gh", "pr create --title foo") + def test_pr_create_blocked_during_analyze(self): + result = filter_operation("analyze", "gh", "pr create --title foo") assert result.allowed is False def test_pr_create_blocked_during_plan(self): @@ -483,7 +483,7 @@ def test_unknown_phase_returns_none(self): pf = PhaseFilter() # Use a valid PipelinePhase that might not have permissions # get_exit_requirement returns None if no permissions found - result = pf.get_exit_requirement(PipelinePhase.REFINE) + result = pf.get_exit_requirement(PipelinePhase.ANALYZE) assert result == "human" diff --git a/gateway/tests/test_phase_transition.py b/gateway/tests/test_phase_transition.py index f664696d99..772e046993 100644 --- a/gateway/tests/test_phase_transition.py +++ b/gateway/tests/test_phase_transition.py @@ -27,13 +27,13 @@ class TestTransitionResult: def test_allowed_result(self): """Create an allowed transition result.""" result = TransitionResult.allowed( - from_phase=PipelinePhase.REFINE, + from_phase=PipelinePhase.ANALYZE, to_phase=PipelinePhase.PLAN, transitioned_by="james-in-a-box", ) assert result.success is True - assert result.from_phase == PipelinePhase.REFINE + assert result.from_phase == PipelinePhase.ANALYZE assert result.to_phase == PipelinePhase.PLAN assert result.transitioned_by == "james-in-a-box" assert result.transitioned_at is not None @@ -41,14 +41,14 @@ def test_allowed_result(self): def test_denied_result(self): """Create a denied transition result.""" result = TransitionResult.denied( - message="Role 'implementer' cannot exit phase 'refine'", - from_phase=PipelinePhase.REFINE, + message="Role 'implementer' cannot exit phase 'analyze'", + from_phase=PipelinePhase.ANALYZE, to_phase=PipelinePhase.PLAN, ) assert result.success is False assert "implementer" in result.message - assert result.from_phase == PipelinePhase.REFINE + assert result.from_phase == PipelinePhase.ANALYZE assert result.to_phase == PipelinePhase.PLAN @@ -58,7 +58,7 @@ class TestTransitionRequest: def test_from_dict(self): """Create TransitionRequest from dictionary.""" data = { - "from_phase": "refine", + "from_phase": "analyze", "to_phase": "plan", "role": "human", "actor": "test-user", @@ -66,7 +66,7 @@ def test_from_dict(self): } request = TransitionRequest.from_dict(data) - assert request.from_phase == PipelinePhase.REFINE + assert request.from_phase == PipelinePhase.ANALYZE assert request.to_phase == PipelinePhase.PLAN assert request.role == TransitionRole.HUMAN assert request.actor == "test-user" @@ -90,10 +90,10 @@ def test_from_dict_minimal(self): class TestValidTransitions: """Tests for the valid transitions graph.""" - def test_refine_to_plan(self): - """Refine can only transition to plan.""" - assert PipelinePhase.PLAN in VALID_TRANSITIONS[PipelinePhase.REFINE] - assert len(VALID_TRANSITIONS[PipelinePhase.REFINE]) == 1 + def test_analyze_to_plan(self): + """Analyze can only transition to plan.""" + assert PipelinePhase.PLAN in VALID_TRANSITIONS[PipelinePhase.ANALYZE] + assert len(VALID_TRANSITIONS[PipelinePhase.ANALYZE]) == 1 def test_plan_to_implement(self): """Plan can only transition to implement.""" @@ -116,7 +116,7 @@ class TestValidateTransition: def test_valid_transition_with_human(self): """Human can transition between any valid phases.""" request = TransitionRequest( - from_phase=PipelinePhase.REFINE, + from_phase=PipelinePhase.ANALYZE, to_phase=PipelinePhase.PLAN, role=TransitionRole.HUMAN, actor="test-human", @@ -124,13 +124,13 @@ def test_valid_transition_with_human(self): result = validate_transition(request) assert result.success is True - assert result.from_phase == PipelinePhase.REFINE + assert result.from_phase == PipelinePhase.ANALYZE assert result.to_phase == PipelinePhase.PLAN def test_invalid_transition_path(self): """Cannot skip phases in the pipeline.""" request = TransitionRequest( - from_phase=PipelinePhase.REFINE, + from_phase=PipelinePhase.ANALYZE, to_phase=PipelinePhase.IMPLEMENT, # Invalid - must go through plan role=TransitionRole.HUMAN, actor="test-human", @@ -153,10 +153,10 @@ def test_backwards_transition_blocked(self): assert result.success is False assert "Invalid transition" in result.message - def test_implementer_cannot_exit_refine(self): - """Implementer cannot exit refine phase (requires human).""" + def test_implementer_cannot_exit_analyze(self): + """Implementer cannot exit analyze phase (requires human).""" request = TransitionRequest( - from_phase=PipelinePhase.REFINE, + from_phase=PipelinePhase.ANALYZE, to_phase=PipelinePhase.PLAN, role=TransitionRole.IMPLEMENTER, actor="james-in-a-box", @@ -220,8 +220,8 @@ class TestRoleHierarchy: def test_human_can_satisfy_any_requirement(self): """Human role can satisfy any exit requirement.""" - # Human can exit refine (requires human) - result = can_transition_to(PipelinePhase.REFINE, PipelinePhase.PLAN, TransitionRole.HUMAN) + # Human can exit analyze (requires human) + result = can_transition_to(PipelinePhase.ANALYZE, PipelinePhase.PLAN, TransitionRole.HUMAN) assert result.success is True # Human can exit implement (requires reviewer) @@ -239,7 +239,7 @@ def test_reviewer_can_satisfy_reviewer_and_lower(self): def test_reviewer_cannot_satisfy_human_requirement(self): """Reviewer cannot satisfy human requirement.""" result = can_transition_to( - PipelinePhase.REFINE, PipelinePhase.PLAN, TransitionRole.REVIEWER + PipelinePhase.ANALYZE, PipelinePhase.PLAN, TransitionRole.REVIEWER ) assert result.success is False @@ -256,9 +256,9 @@ def test_implementer_limited_permissions(self): class TestGetNextPhase: """Tests for get_next_phase function.""" - def test_refine_next_is_plan(self): - """Next phase after refine is plan.""" - assert get_next_phase(PipelinePhase.REFINE) == PipelinePhase.PLAN + def test_analyze_next_is_plan(self): + """Next phase after analyze is plan.""" + assert get_next_phase(PipelinePhase.ANALYZE) == PipelinePhase.PLAN def test_plan_next_is_implement(self): """Next phase after plan is implement.""" @@ -278,10 +278,10 @@ class TestCanTransitionTo: def test_with_strings(self): """Function accepts string arguments.""" - result = can_transition_to("refine", "plan", "human", "test-actor") + result = can_transition_to("analyze", "plan", "human", "test-actor") assert result.success is True - assert result.from_phase == PipelinePhase.REFINE + assert result.from_phase == PipelinePhase.ANALYZE assert result.to_phase == PipelinePhase.PLAN def test_with_enums(self): @@ -302,7 +302,7 @@ class TestCreateAuditEntry: def test_creates_valid_entry(self): """Create a valid audit entry from transition result.""" result = TransitionResult.allowed( - from_phase=PipelinePhase.REFINE, + from_phase=PipelinePhase.ANALYZE, to_phase=PipelinePhase.PLAN, transitioned_by="james-in-a-box", ) @@ -310,7 +310,7 @@ def test_creates_valid_entry(self): assert entry["action"] == "transition" assert entry["field_path"] == "current_phase" - assert entry["old_value"] == "refine" + assert entry["old_value"] == "analyze" assert entry["new_value"] == "plan" assert entry["role"] == "human" assert entry["actor"] == "james-in-a-box" diff --git a/integration_tests/local_pipeline/test_error_recovery.py b/integration_tests/local_pipeline/test_error_recovery.py index f02470f2b2..bfbbeb2035 100644 --- a/integration_tests/local_pipeline/test_error_recovery.py +++ b/integration_tests/local_pipeline/test_error_recovery.py @@ -60,7 +60,7 @@ def test_partial_failure_preserves_draft(self, local_pipeline_stack) -> None: try: start_pipeline(orchestrator_url, pipeline_id) - # Should fail on refine phase (first phase where PARTIAL_FAILURE triggers) + # Should fail on analyze phase (first phase where PARTIAL_FAILURE triggers) final = wait_for_pipeline_terminal(orchestrator_url, pipeline_id, timeout=300) assert final["data"]["status"] == "failed", ( f"Pipeline should have failed but got: {final}" @@ -84,8 +84,8 @@ def test_partial_failure_preserves_draft(self, local_pipeline_stack) -> None: class TestPhaseFailureMidExecution: """Test phase failure mid-execution with FAIL_ON_PHASE.""" - def test_fail_on_plan_phase_leaves_refine_complete(self, orchestrator_url: str) -> None: - """Pipeline fails on plan phase; refine phase remains complete.""" + def test_fail_on_plan_phase_leaves_analyze_complete(self, orchestrator_url: str) -> None: + """Pipeline fails on plan phase; analyze phase remains complete.""" data, status = create_pipeline( orchestrator_url, prompt="FAIL_ON_PHASE=plan - fail only on plan", @@ -102,12 +102,12 @@ def test_fail_on_plan_phase_leaves_refine_complete(self, orchestrator_url: str) assert final["data"]["status"] == "failed" assert final["data"]["current_phase"] == "plan" - # Verify refine phase completed before the failure + # Verify analyze phase completed before the failure get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - assert "refine" in phases - assert phases["refine"]["status"] == "complete", ( - "Refine should have completed before plan failed" + assert "analyze" in phases + assert phases["analyze"]["status"] == "complete", ( + "Analyze should have completed before plan failed" ) assert "plan" in phases assert phases["plan"]["status"] == "failed" @@ -116,7 +116,7 @@ def test_fail_on_plan_phase_leaves_refine_complete(self, orchestrator_url: str) delete_pipeline(orchestrator_url, pipeline_id) def test_fail_on_implement_phase(self, orchestrator_url: str) -> None: - """Pipeline fails on implement phase; refine and plan remain complete.""" + """Pipeline fails on implement phase; analyze and plan remain complete.""" data, status = create_pipeline( orchestrator_url, prompt="FAIL_ON_PHASE=implement - fail only on implement", @@ -136,7 +136,7 @@ def test_fail_on_implement_phase(self, orchestrator_url: str) -> None: # Verify earlier phases completed get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - assert phases.get("refine", {}).get("status") == "complete" + assert phases.get("analyze", {}).get("status") == "complete" assert phases.get("plan", {}).get("status") == "complete" assert phases.get("implement", {}).get("status") == "failed" diff --git a/integration_tests/local_pipeline/test_local_pipeline.py b/integration_tests/local_pipeline/test_local_pipeline.py index 46929a749b..67c54c6c08 100644 --- a/integration_tests/local_pipeline/test_local_pipeline.py +++ b/integration_tests/local_pipeline/test_local_pipeline.py @@ -71,7 +71,7 @@ def test_create_local_pipeline(self, orchestrator_url: str) -> None: assert pipeline["prompt"] == "Add a logout button to the navbar" assert pipeline["status"] == "pending" assert pipeline["id"].startswith("local-") - assert pipeline["current_phase"] == "refine" + assert pipeline["current_phase"] == "analyze" # Cleanup delete_pipeline(orchestrator_url, pipeline["id"]) @@ -97,7 +97,7 @@ def test_start_local_pipeline_completes(self, orchestrator_url: str) -> None: assert start_status == 200, f"Start failed: {start_data}" assert start_data["data"]["status"] == "running" - # Wait for terminal state (4 phases: refine, plan, implement, pr) + # Wait for terminal state (4 phases: analyze, plan, implement, pr) final = wait_for_pipeline_terminal(orchestrator_url, pipeline_id, timeout=360) assert final["data"]["status"] == "complete", f"Pipeline did not complete: {final}" @@ -116,7 +116,7 @@ def test_start_local_pipeline_completes(self, orchestrator_url: str) -> None: class TestLocalPipelineIncludesPrPhase: - """Local pipelines run all 4 phases (refine, plan, implement, pr).""" + """Local pipelines run all 4 phases (analyze, plan, implement, pr).""" def test_local_pipeline_includes_pr_phase(self, orchestrator_url: str) -> None: """Verify the pipeline runs through all phases including PR.""" @@ -138,7 +138,7 @@ def test_local_pipeline_includes_pr_phase(self, orchestrator_url: str) -> None: # Verify all 4 phase executions are present and complete get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - for phase_name in ["refine", "plan", "implement", "pr"]: + for phase_name in ["analyze", "plan", "implement", "pr"]: assert phase_name in phases, f"Missing phase: {phase_name}" assert phases[phase_name]["status"] == "complete" @@ -168,14 +168,14 @@ def test_local_pipeline_container_failure(self, orchestrator_url: str) -> None: start_data, start_status = start_pipeline(orchestrator_url, pipeline_id) assert start_status == 200 - # Wait for terminal state — should fail on the refine phase + # Wait for terminal state — should fail on the analyze phase final = wait_for_pipeline_terminal(orchestrator_url, pipeline_id, timeout=300) assert final["data"]["status"] == "failed", ( f"Pipeline should have failed but got: {final}" ) - # Pipeline should have failed in the refine phase (first phase) - assert final["data"]["current_phase"] == "refine" + # Pipeline should have failed in the analyze phase (first phase) + assert final["data"]["current_phase"] == "analyze" # Verify error message on the pipeline get_data, _ = get_pipeline(orchestrator_url, pipeline_id) @@ -183,10 +183,10 @@ def test_local_pipeline_container_failure(self, orchestrator_url: str) -> None: assert pipeline["status"] == "failed" assert "exit" in pipeline["error"].lower() or "code 1" in pipeline["error"] - # Verify the refine phase execution is marked as failed + # Verify the analyze phase execution is marked as failed phases = pipeline.get("phases", {}) - if "refine" in phases: - assert phases["refine"]["status"] == "failed" + if "analyze" in phases: + assert phases["analyze"]["status"] == "failed" finally: delete_pipeline(orchestrator_url, pipeline_id) @@ -260,7 +260,7 @@ def test_sandbox_receives_pipeline_env(self, orchestrator_url: str) -> None: get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - for phase_name in ["refine", "plan", "implement", "pr"]: + for phase_name in ["analyze", "plan", "implement", "pr"]: assert phase_name in phases, f"Phase {phase_name} not found" assert phases[phase_name]["status"] == "complete" @@ -362,7 +362,7 @@ def test_start_awaiting_human_pipeline_returns_409(self, orchestrator_url: str) pipeline_id = data["data"]["pipeline"]["id"] try: - # Start pipeline — it will pause at the refine HITL gate + # Start pipeline — it will pause at the analyze HITL gate start_data, start_status = start_pipeline(orchestrator_url, pipeline_id) assert start_status == 200 @@ -452,7 +452,7 @@ def test_issue_pipeline_includes_pr_phase(self, orchestrator_url: str) -> None: # Verify all 4 phases were executed get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - for phase_name in ["refine", "plan", "implement", "pr"]: + for phase_name in ["analyze", "plan", "implement", "pr"]: assert phase_name in phases, f"Missing phase: {phase_name}" assert phases[phase_name]["status"] == "complete" @@ -600,7 +600,7 @@ class TestReviewCycleApproved: The mock sandbox writes an 'approved' verdict by default, so the pipeline should complete normally — with multiple typed reviewer - containers spawned after each reviewed phase (refine, plan, implement). + containers spawned after each reviewed phase (analyze, plan, implement). """ def test_pipeline_completes_with_multi_review(self, orchestrator_url: str) -> None: @@ -615,7 +615,7 @@ def test_pipeline_completes_with_multi_review(self, orchestrator_url: str) -> No try: start_pipeline(orchestrator_url, pipeline_id) - # Longer timeout: 2 reviewers for refine+plan, 4 for implement + checker + # Longer timeout: 2 reviewers for analyze+plan, 4 for implement + checker final = wait_for_pipeline_terminal(orchestrator_url, pipeline_id, timeout=360) assert final["data"]["status"] == "complete", ( f"Pipeline should complete with approved reviews: {final}" @@ -625,12 +625,12 @@ def test_pipeline_completes_with_multi_review(self, orchestrator_url: str) -> No # Verify all phases completed get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - for phase_name in ["refine", "plan", "implement", "pr"]: + for phase_name in ["analyze", "plan", "implement", "pr"]: assert phase_name in phases, f"Missing phase: {phase_name}" assert phases[phase_name]["status"] == "complete" # Review cycles should be 0 (approved on first review, no revisions) - assert phases["refine"]["review_cycles"] == 0 + assert phases["analyze"]["review_cycles"] == 0 assert phases["plan"]["review_cycles"] == 0 assert phases["implement"]["review_cycles"] == 0 @@ -712,7 +712,7 @@ class TestImplementPhaseReviewed: the implement phase now runs: 1. Worker (CODER) 2. Checker (CHECKER) — runs tests/lint - 3. Multi-reviewer loop (code, contract for implement; refine, agent-design for refine) + 3. Multi-reviewer loop (code, contract for implement; refine, agent-design for analyze) """ def test_implement_phase_gets_reviewed(self, orchestrator_url: str) -> None: @@ -844,7 +844,7 @@ def test_contract_file_exists_after_create(self, local_pipeline_stack) -> None: # Verify contract content contract_data = json.loads(contract_path.read_text()) assert contract_data.get("pipeline_id") == pipeline_id - assert contract_data.get("current_phase") == "refine" + assert contract_data.get("current_phase") == "analyze" finally: delete_pipeline(orchestrator_url, pipeline_id) @@ -876,14 +876,14 @@ def resolve_pending_decision( class TestHITLGate: - """Pipeline pauses for human approval after refine and plan phases.""" + """Pipeline pauses for human approval after analyze and plan phases.""" def test_hitl_gate_full_flow(self, orchestrator_url: str) -> None: - """Pipeline pauses after refine and plan, resumes on approval. + """Pipeline pauses after analyze and plan, resumes on approval. 1. Create pipeline with hitl_gates=True (default) 2. Start pipeline - 3. Wait for awaiting_human after refine + 3. Wait for awaiting_human after analyze 4. Verify pending_decision in status response 5. Resolve decision 6. Wait for awaiting_human after plan @@ -902,7 +902,7 @@ def test_hitl_gate_full_flow(self, orchestrator_url: str) -> None: start_data, start_status = start_pipeline(orchestrator_url, pipeline_id) assert start_status == 200 - # --- Gate 1: refine phase --- + # --- Gate 1: analyze phase --- status_data = wait_for_awaiting_human(orchestrator_url, pipeline_id, timeout=180) assert status_data["data"]["status"] == "awaiting_human" assert status_data["data"]["pending_decisions"] >= 1 @@ -912,12 +912,12 @@ def test_hitl_gate_full_flow(self, orchestrator_url: str) -> None: assert pending is not None, "Status should include pending_decision details" assert "id" in pending assert "question" in pending - assert "refine" in pending["question"] + assert "analyze" in pending["question"] # Context should contain the mock draft content assert "context" in pending assert len(pending["context"]) > 0 - # Resolve the refine gate + # Resolve the analyze gate resolve_data = resolve_pending_decision(orchestrator_url, pipeline_id, pending["id"]) assert resolve_data["success"] is True @@ -984,7 +984,7 @@ def test_reviewer_mixed_verdict_handled(self, orchestrator_url: str) -> None: phases = get_data["data"]["pipeline"].get("phases", {}) # At least one reviewed phase should have processed the mixed verdict - reviewed_phases = ["refine", "plan", "implement"] + reviewed_phases = ["analyze", "plan", "implement"] any_reviewed = any( phases.get(p, {}).get("status") == "complete" for p in reviewed_phases ) @@ -1032,7 +1032,7 @@ def test_max_review_cycles_zero_triggers_immediate_circuit_breaker( phases = get_data["data"]["pipeline"].get("phases", {}) # With max_review_cycles=0, revision attempts should be blocked - for phase_name in ["refine", "plan", "implement"]: + for phase_name in ["analyze", "plan", "implement"]: if phase_name in phases: # review_cycles should not exceed 0 (circuit breaker fired immediately) cycles = phases[phase_name].get("review_cycles", 0) @@ -1076,7 +1076,7 @@ def test_review_cycle_counter_accuracy(self, orchestrator_url: str) -> None: # With REVIEW_NEEDS_REVISION, we expect some revision cycles # unless the orchestrator uses a different counting mechanism - assert "refine" in phases, "Should have refine phase data" + assert "analyze" in phases, "Should have analyze phase data" finally: delete_pipeline(orchestrator_url, pipeline_id) @@ -1176,7 +1176,7 @@ def test_pipeline_completes_despite_all_reviewers_reject(self, orchestrator_url: # Verify all phases eventually completed get_data, _ = get_pipeline(orchestrator_url, pipeline_id) phases = get_data["data"]["pipeline"].get("phases", {}) - for phase_name in ["refine", "plan", "implement", "pr"]: + for phase_name in ["analyze", "plan", "implement", "pr"]: assert phases.get(phase_name, {}).get("status") == "complete", ( f"Phase {phase_name} should be complete" ) diff --git a/integration_tests/local_pipeline/test_unified_pipeline_behavior.py b/integration_tests/local_pipeline/test_unified_pipeline_behavior.py index 8c1fc06862..bb58456f79 100644 --- a/integration_tests/local_pipeline/test_unified_pipeline_behavior.py +++ b/integration_tests/local_pipeline/test_unified_pipeline_behavior.py @@ -126,8 +126,8 @@ def test_contract_synced_set_after_creation(self, local_pipeline_stack) -> None: class TestPhaseBasedPushRestrictions: """Push restrictions are per-phase, not per-mode.""" - def test_gateway_session_in_local_refine_allows_state_push(self, local_pipeline_stack) -> None: - """Local session in refine phase can push .egg-state/ files.""" + def test_gateway_session_in_local_analyze_allows_state_push(self, local_pipeline_stack) -> None: + """Local session in analyze phase can push .egg-state/ files.""" gateway_url = local_pipeline_stack.gateway_url launcher_secret = local_pipeline_stack.launcher_secret @@ -135,7 +135,7 @@ def test_gateway_session_in_local_refine_allows_state_push(self, local_pipeline_ health_resp = requests.get(f"{gateway_url}/api/v1/health", timeout=10) source_ip = health_resp.json().get("client_ip", "") - # Create a local-mode session in refine phase + # Create a local-mode session in analyze phase session_resp = requests.post( f"{gateway_url}/api/v1/sessions/create", headers={"Authorization": f"Bearer {launcher_secret}"}, @@ -143,7 +143,7 @@ def test_gateway_session_in_local_refine_allows_state_push(self, local_pipeline_ "container_id": f"test-push-{int(time.time())}", "container_ip": source_ip, "mode": "local", - "phase": "refine", # Key: setting phase enables per-phase restrictions + "phase": "analyze", # Key: setting phase enables per-phase restrictions "repos": ["test-owner/test-repo"], "uid": 1000, "gid": 1000, diff --git a/integration_tests/local_pipeline/test_worktree_integration.py b/integration_tests/local_pipeline/test_worktree_integration.py index d1185f1e29..9ab3d88c8a 100644 --- a/integration_tests/local_pipeline/test_worktree_integration.py +++ b/integration_tests/local_pipeline/test_worktree_integration.py @@ -509,7 +509,7 @@ class TestPipelineWorktreeSharing: def test_files_created_in_pipeline_persist_across_phases( self, local_pipeline_stack: LocalPipelineStack ) -> None: - """Files created in refine phase exist in plan phase (via mock sandbox drafts).""" + """Files created in analyze phase exist in plan phase (via mock sandbox drafts).""" orchestrator_url = local_pipeline_stack.orchestrator_url repos_dir = local_pipeline_stack.repos_dir @@ -526,14 +526,14 @@ def test_files_created_in_pipeline_persist_across_phases( final = wait_for_pipeline_terminal(orchestrator_url, pipeline_id, timeout=360) assert final["data"]["status"] == "complete", f"Pipeline did not complete: {final}" - # The mock sandbox creates draft files during refine and plan phases + # The mock sandbox creates draft files during analyze and plan phases # These files should persist in the repo directory (mounted as worktree) drafts_dir = Path(repos_dir) / ".egg-state" / "drafts" - # Check that refine phase created analysis.md + # Check that analyze phase created analysis.md analysis_file = drafts_dir / "analysis.md" assert analysis_file.exists(), ( - f"analysis.md should exist after refine phase: {drafts_dir}" + f"analysis.md should exist after analyze phase: {drafts_dir}" ) # Check that plan phase created plan.md diff --git a/integration_tests/sdlc/__init__.py b/integration_tests/sdlc/__init__.py index 46923b07ca..2ab2502587 100644 --- a/integration_tests/sdlc/__init__.py +++ b/integration_tests/sdlc/__init__.py @@ -2,7 +2,7 @@ These tests verify the structurally-enforced agent checkpoint system, including: -- Happy path: Full pipeline success from refine to PR +- Happy path: Full pipeline success from analyze to PR - Review rejection: Reviewer rejects tasks, implementer fixes them - HITL flow: Human decision pauses and resumes the pipeline - Role enforcement: Gateway blocks unauthorized mutations diff --git a/integration_tests/sdlc/test_happy_path.py b/integration_tests/sdlc/test_happy_path.py index ef384755c8..201355e388 100644 --- a/integration_tests/sdlc/test_happy_path.py +++ b/integration_tests/sdlc/test_happy_path.py @@ -2,7 +2,7 @@ Tests the full pipeline success scenario where: 1. Contract is created from issue -2. Phases progress from refine → plan → implement → pr +2. Phases progress from analyze → plan → implement → pr 3. Tasks are completed by implementer 4. Tasks are approved by reviewer 5. Pipeline completes successfully @@ -107,7 +107,7 @@ def test_contract_creation_and_persistence(self, temp_repo, sample_issue_info): repo_root=temp_repo, ) assert contract.issue.number == 133 - assert contract.current_phase == PipelinePhase.REFINE + assert contract.current_phase == PipelinePhase.ANALYZE # Save to disk save_contract(contract, temp_repo) @@ -117,9 +117,9 @@ def test_contract_creation_and_persistence(self, temp_repo, sample_issue_info): assert loaded.issue.number == 133 assert loaded.issue.title == sample_issue_info.title - def test_phase_progression_refine_to_plan(self, temp_repo, sample_contract): - """Phase can progress from refine to plan with human approval.""" - sample_contract.current_phase = PipelinePhase.REFINE + def test_phase_progression_analyze_to_plan(self, temp_repo, sample_contract): + """Phase can progress from analyze to plan with human approval.""" + sample_contract.current_phase = PipelinePhase.ANALYZE save_contract(sample_contract, temp_repo) # Human approves transition to plan phase diff --git a/integration_tests/sdlc/test_multi_agent_orchestration.py b/integration_tests/sdlc/test_multi_agent_orchestration.py index ac8e5e2f30..531324a1e4 100644 --- a/integration_tests/sdlc/test_multi_agent_orchestration.py +++ b/integration_tests/sdlc/test_multi_agent_orchestration.py @@ -568,32 +568,32 @@ def test_reviewer_plan_role_definition(self): assert not role_def.file_access.can_write("src/main.py") -class TestRefinePhaseRoles: - """Tests for refine-phase agent roles.""" +class TestAnalyzePhaseRoles: + """Tests for analyze-phase agent roles.""" - def test_get_roles_for_refine_phase(self): - """Get roles for refine phase returns refiner.""" + def test_get_roles_for_analyze_phase(self): + """Get roles for analyze phase returns refiner.""" from egg_contracts.agent_roles import get_roles_for_phase - roles = get_roles_for_phase("refine") + roles = get_roles_for_phase("analyze") assert len(roles) == 1 assert AgentRole.REFINER in roles - def test_get_refine_roles_with_reviewers(self): - """Get refine roles with reviewers included.""" + def test_get_analyze_roles_with_reviewers(self): + """Get analyze roles with reviewers included.""" from egg_contracts.agent_roles import get_roles_for_phase - roles = get_roles_for_phase("refine", include_reviewers=True) + roles = get_roles_for_phase("analyze", include_reviewers=True) assert len(roles) == 3 # 1 refiner + 2 reviewers assert AgentRole.REFINER in roles assert AgentRole.REVIEWER_REFINE in roles assert AgentRole.REVIEWER_AGENT_DESIGN in roles - def test_refine_phase_dependency_graph(self): - """Refine-phase has simple single-agent dependency structure.""" + def test_analyze_phase_dependency_graph(self): + """Analyze-phase has simple single-agent dependency structure.""" from egg_contracts.agent_roles import get_roles_for_phase - roles = get_roles_for_phase("refine") + roles = get_roles_for_phase("analyze") graph = build_dependency_graph(roles) assert len(graph.nodes) == 1 diff --git a/integration_tests/sdlc/test_refine_plan_review_cycles.py b/integration_tests/sdlc/test_refine_plan_review_cycles.py index 0fadc3911e..5f955c08d9 100644 --- a/integration_tests/sdlc/test_refine_plan_review_cycles.py +++ b/integration_tests/sdlc/test_refine_plan_review_cycles.py @@ -1,7 +1,7 @@ -"""Integration tests for refine and plan phase review cycles. +"""Integration tests for analyze and plan phase review cycles. -Tests the review cycle mechanism for refine and plan phases: -1. Refine phase review cycle tracking +Tests the review cycle mechanism for analyze and plan phases: +1. Analyze phase review cycle tracking 2. Plan phase review cycle tracking 3. Feedback injection into producer prompts 4. Re-dispatch logic on review failure @@ -38,7 +38,7 @@ def base_contract(): "title": "Test issue for review cycles", "url": "https://github.com/test-owner/test-repo/issues/400", }, - "current_phase": "refine", + "current_phase": "analyze", "acceptance_criteria": [], "phases": [], "decisions": [], @@ -47,8 +47,8 @@ def base_contract(): } -class TestRefineReviewCycle: - """Tests for refine phase review cycle.""" +class TestAnalyzeReviewCycle: + """Tests for analyze phase review cycle.""" def test_initial_refine_review_cycle_is_zero(self, temp_repo, base_contract): """New contract has zero refine review cycles.""" @@ -62,7 +62,7 @@ def test_initial_refine_review_cycle_is_zero(self, temp_repo, base_contract): assert contract.get("refine_review_feedback", "") == "" def test_refine_review_cycle_increments(self, temp_repo, base_contract): - """Refine review cycle increments after each review.""" + """Analyze phase review cycle increments after each review.""" contract_path = temp_repo / ".egg-state" / "contracts" / "400.json" # Simulate first review cycle @@ -82,7 +82,7 @@ def test_refine_review_cycle_increments(self, temp_repo, base_contract): assert contract["refine_review_cycles"] == 2 def test_refine_review_feedback_stored_in_contract(self, temp_repo, base_contract): - """Review feedback is stored in contract for re-run.""" + """Review feedback is stored in contract for re-run (analyze phase).""" contract_path = temp_repo / ".egg-state" / "contracts" / "400.json" feedback = """### Issues Found @@ -166,8 +166,8 @@ def test_plan_review_feedback_stored_in_contract(self, temp_repo, base_contract) class TestFeedbackInjection: """Tests for feedback injection into producer prompts.""" - def test_build_refine_prompt_includes_feedback(self, temp_repo, base_contract): - """Refine prompt includes feedback when present.""" + def test_build_analyze_prompt_includes_feedback(self, temp_repo, base_contract): + """Analyze prompt includes feedback when present.""" # Create contract with feedback contract_path = temp_repo / ".egg-state" / "contracts" / "400.json" base_contract["refine_review_cycles"] = 1 @@ -208,7 +208,7 @@ class TestReviewVerdictFileParsing: def test_approved_verdict_from_file(self, temp_repo): """Approved verdict is correctly read from JSON file.""" - review_file = temp_repo / ".egg-state" / "reviews" / "400-refine-review.json" + review_file = temp_repo / ".egg-state" / "reviews" / "400-analyze-review.json" review_data = { "verdict": "approved", "summary": "The analysis meets quality standards.", @@ -223,7 +223,7 @@ def test_approved_verdict_from_file(self, temp_repo): def test_needs_revision_verdict_from_file(self, temp_repo): """Needs revision verdict is correctly read from JSON file.""" - review_file = temp_repo / ".egg-state" / "reviews" / "400-refine-review.json" + review_file = temp_repo / ".egg-state" / "reviews" / "400-analyze-review.json" feedback = "### Issues Found\\n\\n1. **Problem Understanding**: Missing root cause" review_data = { "verdict": "needs_revision", @@ -346,7 +346,7 @@ def test_review_cycle_creates_audit_entry(self, temp_repo, base_contract): "field_path": "refine_review_cycles", "old_value": 0, "new_value": 1, - "reason": "Refine review cycle 1: needs_revision", + "reason": "Analyze review cycle 1: needs_revision", } ) contract_path.write_text(json.dumps(base_contract)) diff --git a/integration_tests/sdlc/test_role_enforcement.py b/integration_tests/sdlc/test_role_enforcement.py index 76a9db6d72..9a9ec5d450 100644 --- a/integration_tests/sdlc/test_role_enforcement.py +++ b/integration_tests/sdlc/test_role_enforcement.py @@ -335,7 +335,7 @@ def test_reviewer_can_transition_to_pr(self, temp_repo, contract_with_tasks): def test_human_can_transition_any_phase(self, temp_repo, contract_with_tasks): """Human can transition to any phase.""" - contract_with_tasks.current_phase = PipelinePhase.REFINE + contract_with_tasks.current_phase = PipelinePhase.ANALYZE save_contract(contract_with_tasks, temp_repo) contract = load_contract(500, temp_repo) diff --git a/orchestrator/README.md b/orchestrator/README.md index 1646102478..b157a845ba 100644 --- a/orchestrator/README.md +++ b/orchestrator/README.md @@ -46,7 +46,7 @@ Pipelines progress through four SDLC phases: | Phase | Purpose | Agents | |-------|---------|--------| -| **refine** | Analyze task, evaluate options | Refiner, reviewers | +| **analyze** | Analyze task, evaluate options | Refiner, reviewers | | **plan** | Break work into tasks, assess risks | Architect, Task Planner, Risk Analyst, reviewers | | **implement** | Write code, tests, docs | Coder, Tester, Documenter, Checker, Integrator, reviewers | | **pr** | Create pull request | Single agent | diff --git a/orchestrator/dag_visualizer.py b/orchestrator/dag_visualizer.py index 4040484e0a..24bc1781d9 100644 --- a/orchestrator/dag_visualizer.py +++ b/orchestrator/dag_visualizer.py @@ -52,7 +52,7 @@ # Phase order for linear DAG PHASE_ORDER = [ - PipelinePhase.REFINE, + PipelinePhase.ANALYZE, PipelinePhase.PLAN, PipelinePhase.IMPLEMENT, PipelinePhase.PR, @@ -60,7 +60,7 @@ # Phase display names PHASE_NAMES = { - PipelinePhase.REFINE: "Refine", + PipelinePhase.ANALYZE: "Analyze", PipelinePhase.PLAN: "Plan", PipelinePhase.IMPLEMENT: "Implement", PipelinePhase.PR: "PR", @@ -162,7 +162,7 @@ def _compute_wave_order( (same wave) and which run sequentially (different waves). Falls back to a single group (flat list) for phases without - defined agent roles (e.g. refine, pr). + defined agent roles (e.g. analyze, pr). """ try: from egg_contracts.agent_roles import get_roles_for_phase diff --git a/orchestrator/health_checks/tier1/phase_output.py b/orchestrator/health_checks/tier1/phase_output.py index 82f0e4cb69..83a5117429 100644 --- a/orchestrator/health_checks/tier1/phase_output.py +++ b/orchestrator/health_checks/tier1/phase_output.py @@ -8,7 +8,7 @@ For each phase type, verifies: - implement: new commits on the remote branch beyond origin/main - plan: {identifier}-architect-output.json (or plan draft) exists -- refine: refine output exists +- analyze: analyze output exists Returns DEGRADED (not FAILED) when agents succeeded but artifacts are missing — this is a semantic problem, not an infrastructure one. @@ -91,7 +91,7 @@ def run(self, context: PipelineHealthContext) -> HealthResult: elif phase == PipelinePhase.PLAN: return self._check_plan_outputs(context) else: - # REFINE and PR phases: no strict artifact requirements yet + # ANALYZE and PR phases: no strict artifact requirements yet return HealthResult( status=HealthStatus.HEALTHY, check_name=self.name, diff --git a/orchestrator/models.py b/orchestrator/models.py index a552f54fb1..7c6e975f1d 100644 --- a/orchestrator/models.py +++ b/orchestrator/models.py @@ -10,7 +10,7 @@ from typing import Any, Literal, NamedTuple from egg_contracts.models import PipelinePhase -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator class PipelineStatus(StrEnum): @@ -333,11 +333,11 @@ class PipelineConfig(BaseModel): ) hitl_gates: bool = Field( default=True, - description="Pause for human approval after refine and plan phases", + description="Pause for human approval after analyze and plan phases", ) allow_short_circuit: bool = Field( default=True, - description="Allow refine agent to skip plan phase for low-complexity tasks", + description="Allow analyze agent to skip plan phase for low-complexity tasks", ) enable_parallel_phases: bool = Field( default=True, @@ -392,7 +392,7 @@ class Pipeline(BaseModel): status: PipelineStatus = Field( default=PipelineStatus.PENDING, description="Overall pipeline status" ) - current_phase: PipelinePhase = Field(default=PipelinePhase.REFINE, description="Current phase") + current_phase: PipelinePhase = Field(default=PipelinePhase.ANALYZE, description="Current phase") config: PipelineConfig = Field( default_factory=PipelineConfig, description="Pipeline configuration" ) @@ -409,9 +409,18 @@ class Pipeline(BaseModel): default=None, description="Network mode for spawned containers: 'public', 'private', or None (auto from pipeline mode)", ) + + @field_validator("current_phase", mode="before") + @classmethod + def _normalize_legacy_phase(cls, v: Any) -> Any: + """Accept legacy ``"refine"`` phase value from historical pipelines.""" + if v == "refine": + return "analyze" + return v + short_circuit: bool = Field( default=False, - description="Skip plan phase (refine → implement) for low-complexity tasks", + description="Skip plan phase (analyze → implement) for low-complexity tasks", ) complexity_tier: ComplexityTier = Field( default=ComplexityTier.MID, diff --git a/orchestrator/routes/phases.py b/orchestrator/routes/phases.py index 153573c177..6a7fd042e4 100644 --- a/orchestrator/routes/phases.py +++ b/orchestrator/routes/phases.py @@ -48,7 +48,7 @@ def get_logger(name: str, **kwargs) -> logging.Logger: # type: ignore[misc] # Valid phase transitions for issue-driven pipelines PHASE_TRANSITIONS = { - PipelinePhase.REFINE: [PipelinePhase.PLAN, PipelinePhase.IMPLEMENT], + PipelinePhase.ANALYZE: [PipelinePhase.PLAN, PipelinePhase.IMPLEMENT], PipelinePhase.PLAN: [PipelinePhase.IMPLEMENT], PipelinePhase.IMPLEMENT: [PipelinePhase.PR], PipelinePhase.PR: [], # Terminal phase @@ -56,7 +56,7 @@ def get_logger(name: str, **kwargs) -> logging.Logger: # type: ignore[misc] # Valid phase transitions for local pipelines LOCAL_PHASE_TRANSITIONS = { - PipelinePhase.REFINE: [PipelinePhase.PLAN, PipelinePhase.IMPLEMENT], + PipelinePhase.ANALYZE: [PipelinePhase.PLAN, PipelinePhase.IMPLEMENT], PipelinePhase.PLAN: [PipelinePhase.IMPLEMENT], PipelinePhase.IMPLEMENT: [PipelinePhase.PR], PipelinePhase.PR: [], # Terminal phase @@ -225,7 +225,7 @@ def advance_phase(pipeline_id: str) -> tuple[Response, int]: "success": true, "message": "Phase advanced to plan", "data": { - "previous_phase": "refine", + "previous_phase": "analyze", "current_phase": "plan" } } diff --git a/orchestrator/routes/pipelines.py b/orchestrator/routes/pipelines.py index 76b8e5b338..79d8ec8d21 100644 --- a/orchestrator/routes/pipelines.py +++ b/orchestrator/routes/pipelines.py @@ -1112,8 +1112,8 @@ def _get_contract_review_criteria(repo_path: str | None = None) -> str: ) -def _get_refine_review_criteria() -> str: - """Return review criteria for the dedicated refine reviewer.""" +def _get_analyze_review_criteria() -> str: + """Return review criteria for the dedicated analyze reviewer.""" return ( "### 1. Problem Understanding\n" "- Does the analysis correctly identify the core problem or feature request?\n" @@ -1199,7 +1199,7 @@ def _get_review_criteria_for_type( elif reviewer_type == "contract": return _get_contract_review_criteria(repo_path=repo_path) elif reviewer_type == "refine": - return _get_refine_review_criteria() + return _get_analyze_review_criteria() elif reviewer_type == "plan": return _get_plan_review_criteria() else: @@ -1239,11 +1239,11 @@ def _get_reviewer_scope_preamble(reviewer_type: str, phase: str) -> str: ) elif reviewer_type == "refine": return ( - "This is a **refine phase review**. Focus on the quality and completeness " - "of the analysis produced during the refine phase. Evaluate problem " + "This is an **analyze phase review**. Focus on the quality and completeness " + "of the analysis produced during the analyze phase. Evaluate problem " "understanding, codebase research, options analysis, and the recommended " "approach. Agent-mode design alignment is handled by another reviewer.\n\n" - "**Analysis format:** Provide section-by-section evaluation of the refine " + "**Analysis format:** Provide section-by-section evaluation of the analyze " "output — assess each major section for depth, accuracy, and completeness." ) elif reviewer_type == "plan": @@ -1271,7 +1271,7 @@ def _verdict_path_for_type( """Return the relative verdict file path for a given reviewer type. For issue mode, uses issue number as prefix (e.g., 123-implement-code-review.json). - For local mode, uses pipeline_id as prefix (e.g., local-abc12345-refine-refine-review.json). + For local mode, uses pipeline_id as prefix (e.g., local-abc12345-analyze-refine-review.json). When plan_phase_id is provided (Tier 3 phase-level dispatch), it is included in the path to avoid race conditions between parallel phase reviewers: @@ -1299,14 +1299,14 @@ def _get_draft_path( is_local = pipeline_mode == "local" if is_local: prefix = pipeline_id if pipeline_id else "local" - if phase == "refine": + if phase == "analyze": return f".egg-state/drafts/{prefix}-analysis.md" elif phase == "implement": return None else: return f".egg-state/drafts/{prefix}-{phase}.md" else: - if phase == "refine": + if phase == "analyze": return f".egg-state/drafts/{issue_number}-analysis.md" elif phase == "implement": return None @@ -1578,12 +1578,12 @@ def _check_short_circuit_signal( issue_number: int | None = None, pipeline_id: str | None = None, ) -> bool: - """Check the refine analysis draft for a short-circuit signal. + """Check the analyze analysis draft for a short-circuit signal. Looks for the *last* fenced YAML block containing ``short_circuit: true`` in the analysis. Returns True if found. """ - draft_rel = _get_draft_path("refine", pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path("analyze", pipeline_mode, issue_number, pipeline_id) if not draft_rel: return False draft_path = repo_path / draft_rel @@ -1595,7 +1595,7 @@ def _check_short_circuit_signal( # Look for a fenced YAML block containing short_circuit: true. # Only the *last* YAML block is checked to avoid false positives from - # example/quoted YAML earlier in the document. The refine prompt + # example/quoted YAML earlier in the document. The analyze prompt # instructs the LLM to place the metadata block at the very end. yaml_block_pattern = re.compile(r"```ya?ml\s*\n(.*?)```", re.DOTALL) matches = list(yaml_block_pattern.finditer(content)) @@ -1613,7 +1613,7 @@ def _check_high_complexity_signal( issue_number: int | None = None, pipeline_id: str | None = None, ) -> tuple[str, bool]: - """Check the refine analysis draft for a complexity tier signal. + """Check the analyze analysis draft for a complexity tier signal. Looks for the *last* fenced YAML block containing ``complexity_tier`` in the analysis. Returns a tuple of (tier, parallel_phases). @@ -1623,7 +1623,7 @@ def _check_high_complexity_signal( complexity_tier is one of "low", "mid", "high". Defaults to ("mid", False) if no signal is found. """ - draft_rel = _get_draft_path("refine", pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path("analyze", pipeline_mode, issue_number, pipeline_id) if not draft_rel: return "mid", False draft_path = repo_path / draft_rel @@ -2529,10 +2529,10 @@ def _build_phase_prompt( lines.append("## Your Task\n") # Get the correct draft path based on mode - analysis_path = _get_draft_path("refine", pipeline_mode, issue_number, pipeline_id) + analysis_path = _get_draft_path("analyze", pipeline_mode, issue_number, pipeline_id) plan_path = _get_draft_path("plan", pipeline_mode, issue_number, pipeline_id) - if phase == "refine": + if phase == "analyze": lines.extend( [ "Analyze this issue and produce a structured analysis document. Your goal is to:\n", @@ -2553,7 +2553,7 @@ def _build_phase_prompt( "Create an analysis document following this template:\n", "````markdown", "# Analysis: [Issue Title]\n", - "> Issue: #[number] | Phase: refine\n", + "> Issue: #[number] | Phase: analyze\n", "## Problem Statement\n", "[Describe the problem or feature request. " "What is the current state? What is the desired outcome?]\n", @@ -2726,7 +2726,7 @@ def _build_phase_prompt( # (avoids file-I/O turns inside the sandbox). draft_embedded = False if repo_path and review_cycle == 0: - draft_phase = "refine" if short_circuit else "plan" + draft_phase = "analyze" if short_circuit else "plan" draft_text = _read_phase_draft( Path(repo_path), draft_phase, @@ -2844,7 +2844,7 @@ def _build_phase_prompt( # --- Phase restrictions --- lines.append("## Phase Restrictions\n") - if is_local and phase in ("refine", "plan"): + if is_local and phase in ("analyze", "plan"): lines.extend( [ "In this phase:", @@ -2874,7 +2874,7 @@ def _build_phase_prompt( ] ) else: - if phase in ("refine", "plan"): + if phase in ("analyze", "plan"): lines.extend( [ "- You CAN write drafts to `.egg-state/drafts/`", @@ -2898,7 +2898,7 @@ def _build_phase_prompt( ) # --- Completion --- lines.append("## Phase Completion\n") - if phase in ("refine", "plan"): + if phase in ("analyze", "plan"): lines.append( "When your draft is complete, commit and push it. " "The pipeline will have an internal reviewer evaluate your work. " @@ -2971,7 +2971,7 @@ def _build_agent_prompt( Complete prompt string for the agent """ # CODER and REFINER use the existing phase prompt (phase-specific - # instructions are already tailored for refine vs implement etc.) + # instructions are already tailored for analyze vs implement etc.) if role_value in ("coder", "refiner"): return _build_phase_prompt( phase=phase, @@ -3107,7 +3107,7 @@ def _build_agent_prompt( f"You MUST only write to `.egg-state/agent-outputs/{_identifier}-architect-output.json`.", "Do NOT create or modify any other files. Specifically:", "- Do NOT modify analysis drafts (`.egg-state/drafts/*-analysis.md`) — " - "these are finalized in the refine phase and are read-only", + "these are finalized in the analyze phase and are read-only", "- Do NOT create or modify contracts (`.egg-state/contracts/`)", "- Do NOT create or modify reviews (`.egg-state/reviews/`)", "- Do NOT create or modify plan drafts (`.egg-state/drafts/*-plan.md`)", @@ -3247,7 +3247,7 @@ def _build_agent_prompt( "", ] ) - elif phase in ("refine", "plan"): + elif phase in ("analyze", "plan"): lines.extend( [ "- You CAN write to `.egg-state/drafts/` and `.egg-state/agent-outputs/`", @@ -5044,7 +5044,7 @@ def _spawn_and_wait( # Phases that pause for human approval before advancing (HITL gates) -_HITL_GATE_PHASES = {"refine", "plan"} +_HITL_GATE_PHASES = {"analyze", "plan"} # Keywords that indicate human approval at HITL gates _APPROVE_KEYWORDS = {"approved", "approve", "lgtm", "yes", ""} @@ -5626,7 +5626,7 @@ def _sync_pipeline_decisions_to_contract( Converts HITLDecision objects from pipeline state into contract Decision objects so that implement-phase agents can see what was decided during - refine/plan phases. + analyze/plan phases. Only syncs decisions with decision_type != "phase_gate" (substantive choices, not process-control gates). Skips decisions already present @@ -5740,7 +5740,7 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: This runs in a background thread. For each phase it: 1. Spawns a worker (CODER) container — or multi-agent wave execution for implement and plan phases when multi_agent is enabled - 2. For reviewed phases (refine, implement, plan): spawns reviewers + 2. For reviewed phases (analyze, implement, plan): spawns reviewers as a separate step after all workers (and checkers) complete, then reads reviewer verdicts and loops back with feedback if revision is needed. @@ -5903,7 +5903,7 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: prior_phase_succeeded = True current_phase = pipeline.current_phase phase_order = [ - PipelinePhase.REFINE, + PipelinePhase.ANALYZE, PipelinePhase.PLAN, PipelinePhase.IMPLEMENT, PipelinePhase.PR, @@ -6539,11 +6539,11 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: phase_prompt, ] - # Use the REFINER role for the refine phase, + # Use the REFINER role for the analyze phase, # CODER for all other single-agent phases. single_agent_role = ( AgentRole.REFINER - if current_phase.value == "refine" + if current_phase.value == "analyze" else AgentRole.CODER ) @@ -6953,11 +6953,11 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] phase_execution.status = PipelineStatus.COMPLETE phase_execution.completed_at = datetime.utcnow() - # Check for short-circuit signal after refine phase. + # Check for short-circuit signal after analyze phase. # Reset first so a HITL revision that removes the signal # correctly clears a previously-detected short-circuit. - if current_phase.value == "refine": - # Detect complexity tier from refine analysis. + if current_phase.value == "analyze": + # Detect complexity tier from analyze analysis. # Reset parallel flag first so a HITL revision that # downgrades complexity correctly clears a previously- # detected parallel_phases signal. @@ -7005,7 +7005,7 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] # NOTE: In short-circuit mode the plan phase is skipped, so the # contract will have no task structure. This is intentional — # low-complexity tasks go straight to implement with only the - # refine analysis as guidance. The implement agent does not + # analyze analysis as guidance. The implement agent does not # require a populated contract to function. # NOTE: worktree_repo_path is used for both draft reads and # contract load/save inside _populate_contract_from_plan. @@ -7019,10 +7019,10 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] worktree_repo_path, pipeline_id, pipeline_mode, pipeline.issue_number ) - # After refine and plan phases: sync substantive HITL decisions + # After analyze and plan phases: sync substantive HITL decisions # (non-phase-gate) to the contract so implement-phase agents - # can see what was decided. Called for both refine and plan - # phases — refine decisions inform the plan, plan decisions + # can see what was decided. Called for both analyze and plan + # phases — analyze decisions inform the plan, plan decisions # inform the implementation. if current_phase.value in _HITL_GATE_PHASES: _sync_pipeline_decisions_to_contract( @@ -7071,7 +7071,9 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] pipeline.issue_number, pipeline_id, ) - phase_label = "analysis" if current_phase.value == "refine" else current_phase.value + phase_label = ( + "analysis" if current_phase.value == "analyze" else current_phase.value + ) # Warn if draft is missing — the agent may not have written # it to the expected path. See #1016. @@ -7087,7 +7089,6 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] f"work branch. The agent may not have written the output " f"to the expected path." ) - question = ( f"The {current_phase.value} phase has completed. " f"Please review the {phase_label} and approve to continue, " @@ -7302,10 +7303,10 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] next_phases = transitions.get(current_phase, []) # Short-circuit: skip PLAN phase, advance directly to IMPLEMENT. - # The transition table in phases.py allows REFINE → IMPLEMENT for + # The transition table in phases.py allows ANALYZE → IMPLEMENT for # the external validation API, but the internal runner uses this # manual override to select the next phase. Both must stay in sync. - skip_plan = pipeline.short_circuit and current_phase.value == "refine" + skip_plan = pipeline.short_circuit and current_phase.value == "analyze" if skip_plan: next_phases = [PipelinePhase.IMPLEMENT] logger.info("Skipping plan phase (short-circuit)", pipeline_id=pipeline_id) @@ -7548,8 +7549,8 @@ def start_pipeline(pipeline_id: str) -> tuple[Response, int]: current_phase = pipeline.current_phase next_phases = transitions.get(current_phase, []) - # Handle short-circuit: refine → implement (skip plan) - if pipeline.short_circuit and current_phase.value == "refine": + # Handle short-circuit: analyze → implement (skip plan) + if pipeline.short_circuit and current_phase.value == "analyze": next_phases = [PipelinePhase.IMPLEMENT] if not next_phases: @@ -7574,7 +7575,7 @@ def start_pipeline(pipeline_id: str) -> tuple[Response, int]: pipeline.current_phase = next_phase # Mark plan phase as skipped if short-circuit - if pipeline.short_circuit and current_phase.value == "refine": + if pipeline.short_circuit and current_phase.value == "analyze": plan_execution = pipeline.get_phase_execution(PipelinePhase.PLAN) plan_execution.status = PipelineStatus.COMPLETE plan_execution.completed_at = datetime.utcnow() diff --git a/orchestrator/tests/test_container_spawner.py b/orchestrator/tests/test_container_spawner.py index bd343462ef..7042057f85 100644 --- a/orchestrator/tests/test_container_spawner.py +++ b/orchestrator/tests/test_container_spawner.py @@ -556,14 +556,14 @@ def test_spawn_passes_repos_and_phase_to_register_session( agent_role=AgentRole.CODER, issue_number=123, repos=["test-owner/test-repo"], - phase="refine", + phase="analyze", ) # Verify register_session was called with repos and phase mock_gateway_client.register_session.assert_called_once() register_call = mock_gateway_client.register_session.call_args assert register_call.kwargs.get("repos") == ["test-owner/test-repo"] - assert register_call.kwargs.get("phase") == "refine" + assert register_call.kwargs.get("phase") == "analyze" def test_spawn_includes_extra_hosts_for_gateway( self, spawner, mock_docker_client, mock_gateway_client diff --git a/orchestrator/tests/test_coordinator_events.py b/orchestrator/tests/test_coordinator_events.py index 58ec632c34..754935d4df 100644 --- a/orchestrator/tests/test_coordinator_events.py +++ b/orchestrator/tests/test_coordinator_events.py @@ -48,7 +48,7 @@ def test_emit_coordinator_decision(self): "issue-1028", data={ "action": "skip_phase", - "phase": "refine", + "phase": "analyze", "reason": "Simple bug fix", }, ) diff --git a/orchestrator/tests/test_coordinator_gaps.py b/orchestrator/tests/test_coordinator_gaps.py index d12b435015..b6e386f3d6 100644 --- a/orchestrator/tests/test_coordinator_gaps.py +++ b/orchestrator/tests/test_coordinator_gaps.py @@ -150,7 +150,7 @@ class TestPhaseTransitionEdgeCases: def test_skip_backward_to_earlier_phase( self, mock_repo, mock_lock, mock_store_fn, mock_emit, client ): - """Skip from implement back to refine — the API allows this.""" + """Skip from implement back to analyze — the API allows this.""" mock_repo.return_value = Path("/tmp/repo") mock_lock.return_value.__enter__ = MagicMock() mock_lock.return_value.__exit__ = MagicMock(return_value=False) @@ -162,12 +162,12 @@ def test_skip_backward_to_earlier_phase( resp = client.post( "/api/v1/pipelines/test-pipeline/coordinator/phase", - json={"target_phase": "refine", "reason": "Need to re-analyze"}, + json={"target_phase": "analyze", "reason": "Need to re-analyze"}, ) assert resp.status_code == 200 data = resp.get_json() assert data["data"]["action"] == "loopback" - assert data["data"]["current_phase"] == "refine" + assert data["data"]["current_phase"] == "analyze" assert data["data"]["previous_phase"] == "implement" @patch("routes.coordinator.emit_event") diff --git a/orchestrator/tests/test_coordinator_integration.py b/orchestrator/tests/test_coordinator_integration.py index 93b4b58859..12cc67e3c8 100644 --- a/orchestrator/tests/test_coordinator_integration.py +++ b/orchestrator/tests/test_coordinator_integration.py @@ -147,7 +147,7 @@ class TestCoordinatorWorkflowScenarios: """Integration tests for coordinator workflow decision scenarios.""" def test_simple_bug_fix_workflow_state(self): - """Simple bug fix: coordinator should skip refine/plan phases. + """Simple bug fix: coordinator should skip analyze/plan phases. Validates the state model can represent a skip-to-implement workflow. """ @@ -155,9 +155,9 @@ def test_simple_bug_fix_workflow_state(self): workflow_type="bug_fix", phase_decisions=[ PhaseDecision( - phase="refine", + phase="analyze", action="skip", - reason="Simple bug fix, no refinement needed", + reason="Simple bug fix, no analysis needed", ), PhaseDecision( phase="plan", @@ -191,7 +191,7 @@ def test_complex_feature_workflow_state(self): state = CoordinatorState( workflow_type="feature", phase_decisions=[ - PhaseDecision(phase="refine", action="advance"), + PhaseDecision(phase="analyze", action="advance"), PhaseDecision(phase="plan", action="advance"), PhaseDecision(phase="implement", action="advance"), ], diff --git a/orchestrator/tests/test_coordinator_models.py b/orchestrator/tests/test_coordinator_models.py index ce8276e0e2..625c0a5bb3 100644 --- a/orchestrator/tests/test_coordinator_models.py +++ b/orchestrator/tests/test_coordinator_models.py @@ -238,7 +238,7 @@ def test_with_phase_decisions(self): """State tracks phase decisions.""" state = CoordinatorState( phase_decisions=[ - PhaseDecision(phase="refine", action="skip", reason="Simple fix"), + PhaseDecision(phase="analyze", action="skip", reason="Simple fix"), PhaseDecision(phase="implement", action="advance", reason="Done"), ] ) @@ -322,7 +322,7 @@ def test_backward_compat_no_coordinator_fields(self): "branch": "egg/issue-300", "mode": "issue", "status": "pending", - "current_phase": "refine", + "current_phase": "analyze", } ) pipeline = Pipeline.model_validate_json(raw) @@ -461,7 +461,7 @@ def test_pipeline_backward_compat_no_coordinator_state(self): "branch": "egg/issue-300", "mode": "issue", "status": "pending", - "current_phase": "refine", + "current_phase": "analyze", } ) pipeline = Pipeline.model_validate_json(raw) diff --git a/orchestrator/tests/test_coordinator_routes_functional.py b/orchestrator/tests/test_coordinator_routes_functional.py index 79874c6baf..34e2d3e86a 100644 --- a/orchestrator/tests/test_coordinator_routes_functional.py +++ b/orchestrator/tests/test_coordinator_routes_functional.py @@ -707,7 +707,7 @@ def test_skip_to_target_phase(self, mock_repo, mock_lock, mock_store_fn, mock_em mock_lock.return_value.__enter__ = MagicMock() mock_lock.return_value.__exit__ = MagicMock(return_value=False) - pipeline = _make_pipeline(phase=PipelinePhase.REFINE) + pipeline = _make_pipeline(phase=PipelinePhase.ANALYZE) store = MagicMock() store.load_pipeline.return_value = pipeline mock_store_fn.return_value = store @@ -719,7 +719,7 @@ def test_skip_to_target_phase(self, mock_repo, mock_lock, mock_store_fn, mock_em assert response.status_code == 200 data = response.get_json()["data"] - assert data["previous_phase"] == "refine" + assert data["previous_phase"] == "analyze" assert data["current_phase"] == "implement" assert data["action"] == "skip" @@ -761,7 +761,7 @@ def test_skip_to_invalid_phase(self, mock_repo, mock_lock, mock_store_fn, client mock_lock.return_value.__enter__ = MagicMock() mock_lock.return_value.__exit__ = MagicMock(return_value=False) - pipeline = _make_pipeline(phase=PipelinePhase.REFINE) + pipeline = _make_pipeline(phase=PipelinePhase.ANALYZE) store = MagicMock() store.load_pipeline.return_value = pipeline mock_store_fn.return_value = store diff --git a/orchestrator/tests/test_coordinator_state_store.py b/orchestrator/tests/test_coordinator_state_store.py index fb8fe6b7cc..f5ffd0e88a 100644 --- a/orchestrator/tests/test_coordinator_state_store.py +++ b/orchestrator/tests/test_coordinator_state_store.py @@ -134,9 +134,9 @@ def test_coordinator_state_serialization_full_cycle(self): ], phase_decisions=[ PhaseDecision( - phase="refine", + phase="analyze", action="skip", - reason="Simple fix, no refinement needed", + reason="Simple fix, no analysis needed", ), PhaseDecision( phase="implement", diff --git a/orchestrator/tests/test_dag_visualizer.py b/orchestrator/tests/test_dag_visualizer.py index e7f901e759..fe8c3a5d4a 100644 --- a/orchestrator/tests/test_dag_visualizer.py +++ b/orchestrator/tests/test_dag_visualizer.py @@ -145,7 +145,7 @@ def test_basic_dag_structure(self): result = render_pipeline_dag(pipeline) # Check all phases are present - assert "Refine" in result + assert "Analyze" in result assert "Plan" in result assert "Implement" in result assert "PR" in result @@ -187,8 +187,8 @@ def test_ascii_mode(self): def test_phase_with_review_cycles(self): """Test phase showing review cycles.""" phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, review_cycles=2, ) @@ -293,14 +293,14 @@ def test_phase_agents_ascii_mode(self): def test_hitl_gate_phase_shows_awaiting_approval(self): """Test that a phase in AWAITING_HUMAN status shows 'awaiting approval'.""" phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.AWAITING_HUMAN, ), } pipeline = create_test_pipeline( phases=phases, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, status=PipelineStatus.AWAITING_HUMAN, ) result = render_pipeline_dag(pipeline) @@ -314,7 +314,7 @@ def test_hitl_gate_header_shows_awaiting_approval(self): """Test that pipeline header shows 'awaiting approval' instead of 'awaiting_human'.""" pipeline = create_test_pipeline( status=PipelineStatus.AWAITING_HUMAN, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, ) result = render_pipeline_dag(pipeline, include_header=True) @@ -330,7 +330,7 @@ def test_all_phases_shown(self): pipeline = create_test_pipeline() result = render_compact_status(pipeline) - assert "Refine" in result + assert "Analyze" in result assert "Plan" in result assert "Implement" in result assert "PR" in result @@ -365,7 +365,7 @@ class TestRenderProgressBar: def test_empty_progress(self): """Test progress bar with no completed phases.""" pipeline = create_test_pipeline( - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, status=PipelineStatus.PENDING, ) result = render_progress_bar(pipeline, width=20) @@ -376,8 +376,8 @@ def test_empty_progress(self): def test_partial_progress(self): """Test progress bar with some completed phases.""" phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, ), "plan": PhaseExecution( @@ -470,14 +470,14 @@ def test_phase_with_all_details(self): def test_failed_phase_shows_error(self): """Test detail view shows error for failed phase.""" phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.FAILED, error="Container failed to start", ) } pipeline = create_test_pipeline(phases=phases) - result = render_phase_detail(pipeline, PipelinePhase.REFINE) + result = render_phase_detail(pipeline, PipelinePhase.ANALYZE) assert "failed" in result assert "Error: Container failed to start" in result @@ -532,8 +532,8 @@ def test_dag_duration_uses_work_started_at(self): """Test that DAG overview duration prefers work_started_at.""" now = datetime.utcnow() phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, started_at=now - timedelta(minutes=15), work_started_at=now - timedelta(minutes=5), @@ -557,13 +557,13 @@ def test_dag_duration_uses_work_started_at(self): def test_awaiting_human_phase_shows_awaiting_approval(self): """Test detail view shows 'awaiting approval' for AWAITING_HUMAN status.""" phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.AWAITING_HUMAN, ), } pipeline = create_test_pipeline(phases=phases) - result = render_phase_detail(pipeline, PipelinePhase.REFINE) + result = render_phase_detail(pipeline, PipelinePhase.ANALYZE) assert "⏸" in result assert "awaiting approval" in result @@ -637,7 +637,7 @@ def test_phases_contains_all_phases(self): assert impl_agents[1]["status"] == "running" # Phases without agents should have empty list - assert report["phases"]["refine"]["agents"] == [] + assert report["phases"]["analyze"]["agents"] == [] def test_ascii_mode_propagates(self): """Test that ASCII mode affects visualizations.""" @@ -780,11 +780,11 @@ def test_unrecognized_agents_appended(self): assert waves[0][0].role == AgentRole.CODER assert waves[-1][0].role == AgentRole.REVIEWER - def test_refine_phase_wave_order(self): - """Refiner is wave 1, reviewers are wave 2 in refine phase.""" + def test_analyze_phase_wave_order(self): + """Refiner is wave 1, reviewers are wave 2 in analyze phase.""" phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.RUNNING, agents=[ AgentExecution(role=AgentRole.REFINER, status=AgentExecutionStatus.COMPLETE), @@ -797,7 +797,7 @@ def test_refine_phase_wave_order(self): ], ) } - pipeline = create_test_pipeline(phases=phases, current_phase=PipelinePhase.REFINE) + pipeline = create_test_pipeline(phases=phases, current_phase=PipelinePhase.ANALYZE) result = render_pipeline_dag(pipeline, include_header=False) lines = result.split("\n") @@ -810,8 +810,8 @@ def test_refine_phase_wave_order(self): "reviewer_agent_design" in line and "reviewer_refine" in line for line in agent_lines ) - def test_compute_wave_order_refine(self): - """_compute_wave_order returns correct wave groups for refine phase.""" + def test_compute_wave_order_analyze(self): + """_compute_wave_order returns correct wave groups for analyze phase.""" agents = [ AgentExecution(role=AgentRole.REFINER, status=AgentExecutionStatus.COMPLETE), AgentExecution( @@ -819,7 +819,7 @@ def test_compute_wave_order_refine(self): ), AgentExecution(role=AgentRole.REVIEWER_REFINE, status=AgentExecutionStatus.RUNNING), ] - waves = _compute_wave_order(PipelinePhase.REFINE, agents) + waves = _compute_wave_order(PipelinePhase.ANALYZE, agents) assert len(waves) == 2 # refiner, reviewers assert len(waves[0]) == 1 # refiner @@ -904,8 +904,8 @@ def test_single_cycle_dag_shows_simple_duration(self): """DAG box shows [Xm] with no cycle/total split for single cycle.""" now = datetime.utcnow() phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, started_at=now - timedelta(minutes=10), work_started_at=now - timedelta(minutes=5), @@ -1738,8 +1738,8 @@ def _make_tier3_pipeline( "phase-4": "Integration", } phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, ), "plan": PhaseExecution( @@ -1767,7 +1767,7 @@ def test_full_dag_has_all_phases(self): pipeline = self._make_tier3_pipeline() result = render_pipeline_dag(pipeline, include_header=False) - assert "Refine" in result + assert "Analyze" in result assert "Plan" in result assert "Implement (Tier 3)" in result assert "PR" in result diff --git a/orchestrator/tests/test_dag_visualizer_extended.py b/orchestrator/tests/test_dag_visualizer_extended.py index 0dd3794f81..f62c081176 100644 --- a/orchestrator/tests/test_dag_visualizer_extended.py +++ b/orchestrator/tests/test_dag_visualizer_extended.py @@ -196,13 +196,13 @@ class TestRenderPhaseBox: def test_pending_phase_minimal(self): """Pending phase with no agents or duration.""" lines = _render_phase_box( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, status=PipelineStatus.PENDING, review_cycles=0, is_current=False, ) text = "\n".join(lines) - assert "Refine" in text + assert "Analyze" in text assert "pending" in text assert ">>>" not in text @@ -217,7 +217,7 @@ def test_current_phase_marker(self): def test_single_review_cycle_singular(self): lines = _render_phase_box( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, review_cycles=1, is_current=False, @@ -227,7 +227,7 @@ def test_single_review_cycle_singular(self): def test_multiple_review_cycles_plural(self): lines = _render_phase_box( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, review_cycles=3, is_current=False, @@ -238,7 +238,7 @@ def test_multiple_review_cycles_plural(self): def test_duration_only(self): """Duration line shows [Xm] when only cycle duration is provided.""" lines = _render_phase_box( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, review_cycles=0, is_current=False, @@ -251,7 +251,7 @@ def test_duration_only(self): def test_duration_and_total_duration(self): """Duration line shows [last cycle: X | total: Y].""" lines = _render_phase_box( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, review_cycles=1, is_current=False, @@ -265,7 +265,7 @@ def test_duration_and_total_duration(self): def test_duration_and_total_duration_same_value(self): """When duration equals total_duration, shows simple format.""" lines = _render_phase_box( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, review_cycles=0, is_current=False, @@ -347,7 +347,7 @@ def test_pipeline_plan_phase_waves_default_none(self): p = Pipeline( id="test", status=PipelineStatus.PENDING, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, ) assert p.plan_phase_waves is None assert p.plan_phase_names is None @@ -356,7 +356,7 @@ def test_pipeline_plan_phase_waves_set(self): p = Pipeline( id="test", status=PipelineStatus.PENDING, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, plan_phase_waves=[["p1", "p2"], ["p3"]], plan_phase_names={"p1": "Auth", "p2": "API", "p3": "UI"}, ) @@ -368,7 +368,7 @@ def test_pipeline_roundtrip_json(self): p = Pipeline( id="test", status=PipelineStatus.PENDING, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, plan_phase_waves=[["p1"], ["p2", "p3"]], plan_phase_names={"p1": "Auth", "p2": "API", "p3": "UI"}, ) @@ -491,8 +491,8 @@ def test_progress_bar_tier3(self): plan_phase_waves=[["p1"], ["p2"]], plan_phase_names={"p1": "Auth", "p2": "API"}, phases={ - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, ), "plan": PhaseExecution( @@ -997,7 +997,7 @@ def test_all_phases_complete(self): status=PipelineStatus.COMPLETE, ) for phase in [ - PipelinePhase.REFINE, + PipelinePhase.ANALYZE, PipelinePhase.PLAN, PipelinePhase.IMPLEMENT, PipelinePhase.PR, @@ -1017,8 +1017,8 @@ def test_all_phases_complete(self): def test_failed_pipeline(self): phases = { - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, ), "plan": PhaseExecution( diff --git a/orchestrator/tests/test_dag_visualizer_review_fixes.py b/orchestrator/tests/test_dag_visualizer_review_fixes.py index 2a317c89ae..c4de951732 100644 --- a/orchestrator/tests/test_dag_visualizer_review_fixes.py +++ b/orchestrator/tests/test_dag_visualizer_review_fixes.py @@ -539,8 +539,8 @@ def test_orphaned_agents_visible_in_full_dag(self): plan_phase_waves=[["p1", "p2"]], plan_phase_names={"p1": "Auth", "p2": "API"}, phases={ - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.COMPLETE, ), "plan": PhaseExecution( diff --git a/orchestrator/tests/test_decisions_routes.py b/orchestrator/tests/test_decisions_routes.py index 805ffff812..8c4f2b38eb 100644 --- a/orchestrator/tests/test_decisions_routes.py +++ b/orchestrator/tests/test_decisions_routes.py @@ -399,7 +399,7 @@ def test_valid_phases_accepted(self, mock_get_queue, mock_repo, client, tmp_path mock_queue = MagicMock() mock_get_queue.return_value = mock_queue - for phase in ("refine", "plan", "implement", "pr"): + for phase in ("analyze", "plan", "implement", "pr"): mock_queue.queue_decision.return_value = _make_decision() response = client.post( "/api/v1/pipelines/test-pipeline/decisions", diff --git a/orchestrator/tests/test_health_check_tester_coverage.py b/orchestrator/tests/test_health_check_tester_coverage.py index 1740c3413c..bbdad4a0bd 100644 --- a/orchestrator/tests/test_health_check_tester_coverage.py +++ b/orchestrator/tests/test_health_check_tester_coverage.py @@ -317,11 +317,11 @@ def test_pr_phase_always_healthy(self): result = check.run(ctx) assert result.status == HealthStatus.HEALTHY - def test_refine_phase_always_healthy(self): - """Refine phase has no artifact requirements.""" + def test_analyze_phase_always_healthy(self): + """Analyze phase has no artifact requirements.""" check = PhaseOutputPresenceCheck() - pipeline = _make_pipeline(phase=PipelinePhase.REFINE) - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + pipeline = _make_pipeline(phase=PipelinePhase.ANALYZE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) phase_exec.status = PipelineStatus.RUNNING phase_exec.agents.append( AgentExecution( diff --git a/orchestrator/tests/test_health_check_tier1.py b/orchestrator/tests/test_health_check_tier1.py index 112f37fe47..2fba29e0e1 100644 --- a/orchestrator/tests/test_health_check_tier1.py +++ b/orchestrator/tests/test_health_check_tier1.py @@ -302,11 +302,11 @@ def test_plan_phase_degraded_when_no_plan(self): result = PhaseOutputPresenceCheck().run(ctx) assert result.status == HealthStatus.DEGRADED - def test_refine_phase_always_healthy(self): - """REFINE phase has no artifact requirements.""" + def test_analyze_phase_always_healthy(self): + """ANALYZE phase has no artifact requirements.""" pipeline = _make_pipeline_with_completed_agent( commit=None, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, ) ctx = _make_context(pipeline) result = PhaseOutputPresenceCheck().run(ctx) diff --git a/orchestrator/tests/test_hitl_revision.py b/orchestrator/tests/test_hitl_revision.py index c8558faa12..a662b3c5b1 100644 --- a/orchestrator/tests/test_hitl_revision.py +++ b/orchestrator/tests/test_hitl_revision.py @@ -794,7 +794,7 @@ def test_phase_gate_decisions_are_excluded(self): phase_gate = self._make_pipeline_decision( decision_type="phase_gate", - question="Approve refine phase?", + question="Approve analyze phase?", options=["approve", "request changes"], resolution="approve", ) @@ -965,7 +965,7 @@ def test_sync_function_is_callable(self): assert callable(_sync_pipeline_decisions_to_contract) def test_sync_called_for_hitl_gate_phases(self): - """Verify the pipeline source calls sync for both refine and plan phases.""" + """Verify the pipeline source calls sync for both analyze and plan phases.""" import inspect from routes import pipelines @@ -1009,7 +1009,7 @@ def test_sync_end_to_end_filters_and_maps_decisions(self): # Should be excluded: phase_gate self._make_pipeline_decision( decision_id="d-2", - question="Approve refine phase?", + question="Approve analyze phase?", options=["approve", "request changes"], decision_type="phase_gate", status="resolved", diff --git a/orchestrator/tests/test_models.py b/orchestrator/tests/test_models.py index e2bcecd4b3..878f826341 100644 --- a/orchestrator/tests/test_models.py +++ b/orchestrator/tests/test_models.py @@ -136,8 +136,8 @@ class TestPhaseExecution: def test_create_phase(self): """Test creating phase execution.""" - phase = PhaseExecution(phase=PipelinePhase.REFINE) - assert phase.phase == PipelinePhase.REFINE + phase = PhaseExecution(phase=PipelinePhase.ANALYZE) + assert phase.phase == PipelinePhase.ANALYZE assert phase.status == PipelineStatus.PENDING assert phase.work_started_at is None assert phase.containers == [] @@ -238,7 +238,7 @@ def test_create_pipeline(self): assert pipeline.id == "issue-496" assert pipeline.issue_number == 496 assert pipeline.status == PipelineStatus.PENDING - assert pipeline.current_phase == PipelinePhase.REFINE + assert pipeline.current_phase == PipelinePhase.ANALYZE assert pipeline.phases == {} def test_get_phase_execution_creates(self): @@ -249,9 +249,9 @@ def test_get_phase_execution_creates(self): repo="owner/repo", branch="egg/issue-496", ) - phase = pipeline.get_phase_execution(PipelinePhase.REFINE) - assert phase.phase == PipelinePhase.REFINE - assert "refine" in pipeline.phases + phase = pipeline.get_phase_execution(PipelinePhase.ANALYZE) + assert phase.phase == PipelinePhase.ANALYZE + assert "analyze" in pipeline.phases def test_get_phase_execution_returns_existing(self): """Test get_phase_execution returns existing phase.""" @@ -261,13 +261,13 @@ def test_get_phase_execution_returns_existing(self): repo="owner/repo", branch="egg/issue-496", phases={ - "refine": PhaseExecution( - phase=PipelinePhase.REFINE, + "analyze": PhaseExecution( + phase=PipelinePhase.ANALYZE, status=PipelineStatus.RUNNING, ) }, ) - phase = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase.status == PipelineStatus.RUNNING def test_add_decision(self): @@ -399,7 +399,7 @@ def test_network_mode_backward_compat(self): "branch": "egg/issue-496", "mode": "issue", "status": "pending", - "current_phase": "refine", + "current_phase": "analyze", } ) restored = Pipeline.model_validate_json(raw) @@ -461,7 +461,7 @@ def test_backward_compat_old_format_dict(self): "branch": "egg/issue-496", "mode": "issue", "status": "pending", - "current_phase": "refine", + "current_phase": "analyze", "decisions": [ { "id": "decision-1", @@ -553,7 +553,7 @@ class TestPipelinePhase: def test_phase_order(self): """Test phases are defined in SDLC order.""" phases = list(PipelinePhase) - assert phases[0] == PipelinePhase.REFINE + assert phases[0] == PipelinePhase.ANALYZE assert phases[1] == PipelinePhase.PLAN assert phases[2] == PipelinePhase.IMPLEMENT assert phases[3] == PipelinePhase.PR diff --git a/orchestrator/tests/test_pipeline_failure_path.py b/orchestrator/tests/test_pipeline_failure_path.py index aee8431947..ace48f095f 100644 --- a/orchestrator/tests/test_pipeline_failure_path.py +++ b/orchestrator/tests/test_pipeline_failure_path.py @@ -37,10 +37,10 @@ def _make_running_pipeline(branch="egg/issue-42"): branch=branch, mode="issue", status=PipelineStatus.RUNNING, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, ) pipeline.contract_synced = True # Skip contract creation - execution = pipeline.get_phase_execution(PipelinePhase.REFINE) + execution = pipeline.get_phase_execution(PipelinePhase.ANALYZE) execution.status = PipelineStatus.RUNNING execution.started_at = datetime.utcnow() return pipeline diff --git a/orchestrator/tests/test_pipeline_prompts.py b/orchestrator/tests/test_pipeline_prompts.py index e80d03b37e..572f3c4610 100644 --- a/orchestrator/tests/test_pipeline_prompts.py +++ b/orchestrator/tests/test_pipeline_prompts.py @@ -2445,7 +2445,7 @@ def test_contract_preamble_includes_criterion_verification(self): def test_refine_preamble_includes_section_evaluation(self): """Refine reviewer preamble includes section-by-section evaluation.""" - preamble = _get_reviewer_scope_preamble("refine", "refine") + preamble = _get_reviewer_scope_preamble("refine", "analyze") assert "section-by-section" in preamble def test_plan_preamble_includes_section_evaluation(self): @@ -2467,7 +2467,7 @@ def test_analysis_field_guidelines(self): def test_draft_reviewer_has_expanded_steps(self): """Draft-based reviewers get expanded procedural steps (6+).""" prompt = _build_review_prompt( - phase="refine", + phase="analyze", pipeline_id="test-pipe", pipeline_mode="issue", reviewer_type="refine", diff --git a/orchestrator/tests/test_read_phase_draft.py b/orchestrator/tests/test_read_phase_draft.py index 8488e0a580..9cbb785992 100644 --- a/orchestrator/tests/test_read_phase_draft.py +++ b/orchestrator/tests/test_read_phase_draft.py @@ -22,7 +22,7 @@ def test_returns_full_content_within_limit(self, tmp_path: Path): drafts.mkdir(parents=True) (drafts / "42-analysis.md").write_text("short content", encoding="utf-8") - result = _read_phase_draft(tmp_path, "refine", "issue", issue_number=42) + result = _read_phase_draft(tmp_path, "analyze", "issue", issue_number=42) assert result == "short content" def test_truncates_content_exceeding_limit(self, tmp_path: Path): @@ -32,7 +32,7 @@ def test_truncates_content_exceeding_limit(self, tmp_path: Path): content = "x" * 200 (drafts / "42-analysis.md").write_text(content, encoding="utf-8") - result = _read_phase_draft(tmp_path, "refine", "issue", issue_number=42, max_chars=100) + result = _read_phase_draft(tmp_path, "analyze", "issue", issue_number=42, max_chars=100) assert result.startswith("x" * 100) assert "... (truncated, 200 chars total)" in result @@ -43,7 +43,7 @@ def test_no_draft_for_implement_phase(self, tmp_path: Path): def test_missing_draft_file(self, tmp_path: Path): """Returns None when draft file does not exist on disk.""" - result = _read_phase_draft(tmp_path, "refine", "issue", issue_number=42) + result = _read_phase_draft(tmp_path, "analyze", "issue", issue_number=42) assert result is None def test_local_mode_uses_pipeline_id(self, tmp_path: Path): @@ -61,7 +61,7 @@ def test_local_mode_fallback_prefix(self, tmp_path: Path): drafts.mkdir(parents=True) (drafts / "local-analysis.md").write_text("fallback", encoding="utf-8") - result = _read_phase_draft(tmp_path, "refine", "local") + result = _read_phase_draft(tmp_path, "analyze", "local") assert result == "fallback" def test_truncation_suffix_format(self, tmp_path: Path): diff --git a/orchestrator/tests/test_short_circuit.py b/orchestrator/tests/test_short_circuit.py index ada95cfbeb..fc6890db32 100644 --- a/orchestrator/tests/test_short_circuit.py +++ b/orchestrator/tests/test_short_circuit.py @@ -166,40 +166,40 @@ def test_plan_phase_marked_complete_on_short_circuit(self): plan_exec.status = PipelineStatus.COMPLETE assert plan_exec.status == PipelineStatus.COMPLETE - def test_phase_can_advance_refine_to_implement(self): - """Pipeline current_phase can be set from REFINE to IMPLEMENT directly.""" - p = Pipeline(id="test-1", short_circuit=True, current_phase=PipelinePhase.REFINE) + def test_phase_can_advance_analyze_to_implement(self): + """Pipeline current_phase can be set from ANALYZE to IMPLEMENT directly.""" + p = Pipeline(id="test-1", short_circuit=True, current_phase=PipelinePhase.ANALYZE) p.current_phase = PipelinePhase.IMPLEMENT assert p.current_phase == PipelinePhase.IMPLEMENT class TestPhaseTransitions: - """Tests for REFINE → IMPLEMENT being a valid transition.""" + """Tests for ANALYZE → IMPLEMENT being a valid transition.""" - def test_issue_mode_refine_to_implement_valid(self): - """REFINE → IMPLEMENT is valid in issue mode transitions.""" - assert PipelinePhase.IMPLEMENT in PHASE_TRANSITIONS[PipelinePhase.REFINE] + def test_issue_mode_analyze_to_implement_valid(self): + """ANALYZE → IMPLEMENT is valid in issue mode transitions.""" + assert PipelinePhase.IMPLEMENT in PHASE_TRANSITIONS[PipelinePhase.ANALYZE] - def test_local_mode_refine_to_implement_valid(self): - """REFINE → IMPLEMENT is valid in local mode transitions.""" - assert PipelinePhase.IMPLEMENT in LOCAL_PHASE_TRANSITIONS[PipelinePhase.REFINE] + def test_local_mode_analyze_to_implement_valid(self): + """ANALYZE → IMPLEMENT is valid in local mode transitions.""" + assert PipelinePhase.IMPLEMENT in LOCAL_PHASE_TRANSITIONS[PipelinePhase.ANALYZE] - def test_refine_to_plan_still_valid(self): - """REFINE → PLAN remains valid (default path).""" - assert PipelinePhase.PLAN in PHASE_TRANSITIONS[PipelinePhase.REFINE] + def test_analyze_to_plan_still_valid(self): + """ANALYZE → PLAN remains valid (default path).""" + assert PipelinePhase.PLAN in PHASE_TRANSITIONS[PipelinePhase.ANALYZE] - def test_validate_refine_to_implement(self): - """validate_phase_transition accepts REFINE → IMPLEMENT.""" + def test_validate_analyze_to_implement(self): + """validate_phase_transition accepts ANALYZE → IMPLEMENT.""" is_valid, error = validate_phase_transition( - PipelinePhase.REFINE, PipelinePhase.IMPLEMENT, pipeline_mode="issue" + PipelinePhase.ANALYZE, PipelinePhase.IMPLEMENT, pipeline_mode="issue" ) assert is_valid is True assert error == "" - def test_validate_refine_to_implement_local(self): - """validate_phase_transition accepts REFINE → IMPLEMENT in local mode.""" + def test_validate_analyze_to_implement_local(self): + """validate_phase_transition accepts ANALYZE → IMPLEMENT in local mode.""" is_valid, error = validate_phase_transition( - PipelinePhase.REFINE, PipelinePhase.IMPLEMENT, pipeline_mode="local" + PipelinePhase.ANALYZE, PipelinePhase.IMPLEMENT, pipeline_mode="local" ) assert is_valid is True assert error == "" @@ -231,10 +231,10 @@ def test_implement_prompt_references_plan_normally(self): ) assert "Review the plan" in result - def test_refine_prompt_includes_complexity_assessment(self): - """Refine prompt includes complexity assessment instructions.""" + def test_analyze_prompt_includes_complexity_assessment(self): + """Analyze prompt includes complexity assessment instructions.""" result = _build_phase_prompt( - phase="refine", + phase="analyze", pipeline_id="test-1", pipeline_mode="issue", issue_number=42, diff --git a/orchestrator/tests/test_start_pipeline.py b/orchestrator/tests/test_start_pipeline.py index 6d898dd7ed..b32269c2f0 100644 --- a/orchestrator/tests/test_start_pipeline.py +++ b/orchestrator/tests/test_start_pipeline.py @@ -55,7 +55,7 @@ def client(app): return app.test_client() -def _make_pipeline(status, phase=PipelinePhase.REFINE, phase_status=None): +def _make_pipeline(status, phase=PipelinePhase.ANALYZE, phase_status=None): """Create a Pipeline with the given status and phase state.""" pipeline = Pipeline( id="issue-42", @@ -105,7 +105,7 @@ def test_restart_failed_pipeline_returns_200( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -125,7 +125,7 @@ def test_restart_resets_failed_phase_to_pending( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -133,7 +133,7 @@ def test_restart_resets_failed_phase_to_pending( client.post("/api/v1/pipelines/issue-42/start") # The failed phase should be reset - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.status == PipelineStatus.PENDING assert phase_exec.started_at is None assert phase_exec.work_started_at is None @@ -151,7 +151,7 @@ def test_restart_clears_pipeline_error( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -170,7 +170,7 @@ def test_restart_saves_pipeline_state( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) mock_store = _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -188,14 +188,14 @@ def test_restart_clears_agents_and_artifacts( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) client.post("/api/v1/pipelines/issue-42/start") - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.agents == [] assert phase_exec.artifacts == {} assert phase_exec.containers == [] @@ -209,7 +209,7 @@ def test_restart_bumps_created_at( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) original_created_at = pipeline.created_at @@ -228,7 +228,7 @@ def test_restart_calls_run_pipeline( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.FAILED, ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -256,7 +256,7 @@ def test_restart_resets_running_phase_to_pending( ): pipeline = _make_pipeline( PipelineStatus.FAILED, - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, phase_status=PipelineStatus.RUNNING, ) # Simulate pipeline-level failure (pipeline.error set, but phase not FAILED) @@ -266,7 +266,7 @@ def test_restart_resets_running_phase_to_pending( resp = client.post("/api/v1/pipelines/issue-42/start") assert resp.status_code == 200 - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.status == PipelineStatus.PENDING assert phase_exec.started_at is None assert phase_exec.agents == [] @@ -315,7 +315,7 @@ def test_cancelled_pipeline_returns_409(self, mock_get_repo, mock_resolve, clien def _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, pending_decisions=0, resolution='{"action": "approve"}', decision_type="phase_gate", @@ -389,7 +389,7 @@ def test_recovery_approved_advances_phase( ): """Approved resolution advances to the next phase and starts runner.""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution='{"action": "approve"}', ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -412,7 +412,7 @@ def test_recovery_request_changes_resets_phase( ): """request_changes resolution resets the phase for re-run.""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution='{"action": "request_changes", "feedback": "Fix tests"}', ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -421,13 +421,13 @@ def test_recovery_request_changes_resets_phase( assert resp.status_code == 200 # Phase should be reset to PENDING - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.status == PipelineStatus.PENDING assert phase_exec.started_at is None assert phase_exec.agents == [] assert phase_exec.artifacts == {} # Pipeline should still be on REFINE (not advanced) - assert pipeline.current_phase == PipelinePhase.REFINE + assert pipeline.current_phase == PipelinePhase.ANALYZE assert pipeline.status == PipelineStatus.RUNNING @patch("routes.pipelines.get_pipeline_state_lock", side_effect=_noop_lock) @@ -496,7 +496,7 @@ def test_short_circuit_skips_plan( ): """Short-circuit pipeline skips plan phase (refine → implement).""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution='{"action": "approve"}', short_circuit=True, ) @@ -521,7 +521,7 @@ def test_recovery_change_approach_resets_phase( ): """change_approach resolution resets the phase for re-run (not approval).""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution='{"action": "change_approach", "feedback": "Try a different strategy"}', ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -530,10 +530,10 @@ def test_recovery_change_approach_resets_phase( assert resp.status_code == 200 # Phase should be reset to PENDING (not advanced) - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.status == PipelineStatus.PENDING assert phase_exec.started_at is None - assert pipeline.current_phase == PipelinePhase.REFINE + assert pipeline.current_phase == PipelinePhase.ANALYZE assert pipeline.status == PipelineStatus.RUNNING # Feedback should be preserved for the re-running agent assert phase_exec.hitl_feedback == "Try a different strategy" @@ -547,7 +547,7 @@ def test_recovery_bare_string_approved( ): """Bare-string 'approved' resolution advances the phase.""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution="approved", ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -568,7 +568,7 @@ def test_recovery_bare_string_request_changes( ): """Bare-string 'request changes' resolution resets the phase.""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution="request changes", ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -576,9 +576,9 @@ def test_recovery_bare_string_request_changes( resp = client.post("/api/v1/pipelines/issue-42/start") assert resp.status_code == 200 - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.status == PipelineStatus.PENDING - assert pipeline.current_phase == PipelinePhase.REFINE + assert pipeline.current_phase == PipelinePhase.ANALYZE assert pipeline.status == PipelineStatus.RUNNING @patch("routes.pipelines.get_pipeline_state_lock", side_effect=_noop_lock) @@ -590,7 +590,7 @@ def test_recovery_preserves_feedback( ): """request_changes feedback is stored in phase_execution.hitl_feedback.""" pipeline = _make_awaiting_pipeline( - phase=PipelinePhase.REFINE, + phase=PipelinePhase.ANALYZE, resolution='{"action": "request_changes", "feedback": "Fix the tests"}', ) _setup_mocks(mock_get_repo, mock_resolve, pipeline) @@ -598,5 +598,5 @@ def test_recovery_preserves_feedback( resp = client.post("/api/v1/pipelines/issue-42/start") assert resp.status_code == 200 - phase_exec = pipeline.get_phase_execution(PipelinePhase.REFINE) + phase_exec = pipeline.get_phase_execution(PipelinePhase.ANALYZE) assert phase_exec.hitl_feedback == "Fix the tests" diff --git a/orchestrator/tests/test_startup_reconciliation.py b/orchestrator/tests/test_startup_reconciliation.py index a4be4df233..a322f8f821 100644 --- a/orchestrator/tests/test_startup_reconciliation.py +++ b/orchestrator/tests/test_startup_reconciliation.py @@ -332,7 +332,7 @@ def _make_awaiting_human_pipeline( branch="egg/issue-77", mode="issue", status=PipelineStatus.AWAITING_HUMAN, - current_phase=PipelinePhase.REFINE, + current_phase=PipelinePhase.ANALYZE, ) # Add resolved decisions for i in range(resolved_decisions): diff --git a/orchestrator/tests/test_state_store.py b/orchestrator/tests/test_state_store.py index d63d8e94fd..03165bfe9a 100644 --- a/orchestrator/tests/test_state_store.py +++ b/orchestrator/tests/test_state_store.py @@ -653,7 +653,7 @@ def test_stale_pipeline_save_overwrites_decision(self, state_store): with patch("decision_queue.get_state_store", return_value=state_store): dq = DecisionQueue("issue-530", state_store.repo_path) decision = dq.queue_decision( - question="Approve refine phase?", + question="Approve analyze phase?", options=["approve"], ) assert decision.id == "decision-1" @@ -691,7 +691,7 @@ def test_reload_after_queue_decision_preserves_decision(self, state_store): with patch("decision_queue.get_state_store", return_value=state_store): dq = DecisionQueue("issue-531", state_store.repo_path) dq.queue_decision( - question="Approve refine phase?", + question="Approve analyze phase?", options=["approve"], ) diff --git a/sandbox/.claude/rules/coordinator.md b/sandbox/.claude/rules/coordinator.md index 624d6572a1..17d66867aa 100644 --- a/sandbox/.claude/rules/coordinator.md +++ b/sandbox/.claude/rules/coordinator.md @@ -33,14 +33,14 @@ Also use standard commands: Analyze the task and choose an appropriate workflow: **Bug fix** (simple, clear reproduction): -- Skip refine/plan → spawn coder directly → spawn tester → complete +- Skip analyze/plan → spawn coder directly → spawn tester → complete - `egg-orch coordinator phase --reason "Simple bug fix, skipping to implement" --target implement` **Feature** (new functionality, clear requirements): -- Full workflow: refine → plan → implement (coder, tester, documenter) → integrate → complete +- Full workflow: analyze → plan → implement (coder, tester, documenter) → integrate → complete **Refactor** (code restructuring, no behavior change): -- Skip refine → plan → implement (coder, tester) → integrate → complete +- Skip analyze → plan → implement (coder, tester) → integrate → complete **Investigation** (unclear issue, needs analysis): - Spawn refiner first → assess output → decide next steps diff --git a/sandbox/egg_lib/contract_cli.py b/sandbox/egg_lib/contract_cli.py index af184dc245..a615c349d1 100755 --- a/sandbox/egg_lib/contract_cli.py +++ b/sandbox/egg_lib/contract_cli.py @@ -1117,7 +1117,7 @@ def create_parser() -> argparse.ArgumentParser: ) decision_parser.add_argument( "--phase", - choices=["refine", "plan", "implement", "pr"], + choices=["analyze", "plan", "implement", "pr"], help="Pipeline phase (defaults to contract's current_phase)", ) decision_parser.add_argument( diff --git a/sandbox/egg_lib/data/hitl_editing_rules.md b/sandbox/egg_lib/data/hitl_editing_rules.md index ca426e9da4..83c213e713 100644 --- a/sandbox/egg_lib/data/hitl_editing_rules.md +++ b/sandbox/egg_lib/data/hitl_editing_rules.md @@ -11,7 +11,7 @@ You are helping a human review and edit an SDLC pipeline draft document. ## Phase Context -- **refine** phase: The draft is an analysis document (problem statement, constraints, options considered, recommendation) +- **analyze** phase: The draft is an analysis document (problem statement, constraints, options considered, recommendation) - **plan** phase: The draft is an implementation plan (summary, phased tasks with acceptance criteria, risk assessment) ## Available Tools diff --git a/sandbox/egg_lib/gateway.py b/sandbox/egg_lib/gateway.py index 3b22e0263f..01271cd553 100644 --- a/sandbox/egg_lib/gateway.py +++ b/sandbox/egg_lib/gateway.py @@ -482,7 +482,7 @@ def create_session( local_only_repos: Repo names with no GitHub remote; mounted in private mode only uid: User ID to set worktree ownership to gid: Group ID to set worktree ownership to - phase: SDLC pipeline phase (e.g., "refine", "plan", "implement", "pr") + phase: SDLC pipeline phase (e.g., "analyze", "plan", "implement", "pr") issue_number: GitHub issue number for checkpoint metadata pr_number: GitHub PR number for checkpoint metadata pipeline_id: Pipeline run ID for multi-agent correlation diff --git a/sandbox/egg_lib/orch_cli.py b/sandbox/egg_lib/orch_cli.py index 38641766f2..60e9e0c794 100755 --- a/sandbox/egg_lib/orch_cli.py +++ b/sandbox/egg_lib/orch_cli.py @@ -1469,7 +1469,7 @@ def add_signal_args(p: argparse.ArgumentParser) -> None: ph_advance.add_argument( "--target-phase", required=True, - choices=["refine", "plan", "implement", "pr"], + choices=["analyze", "plan", "implement", "pr"], help="Target phase to advance to", ) ph_advance.add_argument("--reason", help="Reason for advancement") @@ -1507,7 +1507,7 @@ def add_signal_args(p: argparse.ArgumentParser) -> None: dec_create.add_argument("--options", nargs="*", help="Decision options") dec_create.add_argument( "--phase", - choices=["refine", "plan", "implement", "pr"], + choices=["analyze", "plan", "implement", "pr"], help="Pipeline phase (auto-inferred from pipeline state if omitted)", ) dec_create.add_argument( @@ -1595,7 +1595,7 @@ def add_signal_args(p: argparse.ArgumentParser) -> None: gw_perms = gw_sub.add_parser("permissions", help="Get allowed operations for a phase") gw_perms.add_argument( "phase", - choices=["refine", "plan", "implement", "pr", "coordinator"], + choices=["analyze", "plan", "implement", "pr", "coordinator"], help="SDLC phase", ) _add_json_flag(gw_perms) diff --git a/sandbox/egg_lib/orch_client.py b/sandbox/egg_lib/orch_client.py index df628599d0..e50782d75b 100644 --- a/sandbox/egg_lib/orch_client.py +++ b/sandbox/egg_lib/orch_client.py @@ -214,7 +214,7 @@ def create_decision( decision_type: Type of decision ('phase_gate', 'choice', or 'feedback'). questions: Structured feedback questions (list of dicts with id, question, answer). context: Additional context for the decision. - phase: Pipeline phase that created the decision (e.g., 'plan', 'refine'). + phase: Pipeline phase that created the decision (e.g., 'plan', 'analyze'). Returns: Created decision dict from the orchestrator API. diff --git a/sandbox/egg_lib/runtime.py b/sandbox/egg_lib/runtime.py index 381bb92c25..359a71bc04 100644 --- a/sandbox/egg_lib/runtime.py +++ b/sandbox/egg_lib/runtime.py @@ -318,7 +318,7 @@ def _setup_session_repos( mode: Repository visibility mode ("private" or "public") mount_args: List to append mount arguments to quiet: Suppress output - phase: SDLC pipeline phase (e.g., "refine", "plan", "implement", "pr") + phase: SDLC pipeline phase (e.g., "analyze", "plan", "implement", "pr") issue_number: GitHub issue number for checkpoint metadata pr_number: GitHub PR number for checkpoint metadata pipeline_id: Pipeline run ID for multi-agent correlation diff --git a/sandbox/egg_lib/sdlc_hitl.py b/sandbox/egg_lib/sdlc_hitl.py index bc4a70545f..484ca5a493 100644 --- a/sandbox/egg_lib/sdlc_hitl.py +++ b/sandbox/egg_lib/sdlc_hitl.py @@ -31,6 +31,13 @@ CYAN = "\033[36m" +def _phase_display_label(phase: str) -> str: + """Return human-readable label for a pipeline phase.""" + if phase == "analyze": + return "analysis" + return phase + + def _get_draft_path( phase: str, pipeline_mode: str, @@ -47,7 +54,7 @@ def _get_draft_path( else: prefix = str(issue_number) if issue_number else "unknown" - if phase == "refine": + if phase == "analyze": return f".egg-state/drafts/{prefix}-analysis.md" elif phase == "implement": return None @@ -569,7 +576,7 @@ def _handle_phase_gate( """Handle a phase_gate decision with draft review options.""" decision_id = decision.get("id", "unknown") draft_path = repo_path / draft_rel if draft_rel else None - phase_label = "analysis" if phase == "refine" else phase + phase_label = _phase_display_label(phase) while True: # Load pending contract decisions each iteration (may change after answering). @@ -1061,7 +1068,7 @@ def handle_hitl_checkpoint( print(f"{BOLD}{YELLOW} HUMAN DECISION REQUIRED{RESET}") print(f"{BOLD}{YELLOW}{'=' * 60}{RESET}") print(f"\n {BOLD}Pipeline:{RESET} {pipeline_id}") - print(f" {BOLD}Phase:{RESET} {phase}") + print(f" {BOLD}Phase:{RESET} {_phase_display_label(phase)}") print(f" {BOLD}Question:{RESET} {question}") # Show full document in pager for phase gates @@ -1118,8 +1125,8 @@ def _detect_phase(question: str, context: str | None = None) -> str: orchestrator always passes a string). """ q = question.lower() - if re.search(r"\brefine\b", q) or re.search(r"\banalysis\b", q): - return "refine" + if re.search(r"\banalyze\b", q) or re.search(r"\banalysis\b", q): + return "analyze" elif re.search(r"\bplan\b", q): return "plan" elif re.search(r"\bimplement\b", q): diff --git a/shared/egg_container/__init__.py b/shared/egg_container/__init__.py index ede09e7bb3..e5bbac74e1 100644 --- a/shared/egg_container/__init__.py +++ b/shared/egg_container/__init__.py @@ -183,7 +183,7 @@ def ensure_egg_state_dirs( f"Reason: Plan and contract artifacts must not be modified " f"by code agents during implementation.\n" f"To modify these files, use the appropriate SDLC phase " - f"(refine or plan).\n" + f"(analyze or plan).\n" ) if uid is not None and gid is not None: os.chown(str(marker), uid, gid) diff --git a/shared/egg_contracts/agent_roles.py b/shared/egg_contracts/agent_roles.py index e64cf7479e..2eb022f37c 100644 --- a/shared/egg_contracts/agent_roles.py +++ b/shared/egg_contracts/agent_roles.py @@ -10,7 +10,7 @@ - TESTER: Writes tests for the implemented changes - DOCUMENTER: Updates documentation for the changes - INTEGRATOR: Runs full test suite and validates integration -- REFINER: Analyzes tasks and produces structured analysis in the refine phase +- REFINER: Analyzes tasks and produces structured analysis in the analyze phase The orchestrator uses these definitions to: 1. Determine execution order based on dependencies @@ -31,7 +31,7 @@ class AgentRole(StrEnum): Implement-phase roles: CODER, TESTER, DOCUMENTER, INTEGRATOR Plan-phase roles: ARCHITECT, TASK_PLANNER, RISK_ANALYST - Refine-phase roles: REFINER + Analyze-phase roles: REFINER Reviewer roles: REVIEWER_CODE, REVIEWER_CONTRACT, REVIEWER_AGENT_DESIGN, REVIEWER_REFINE, REVIEWER_PLAN """ @@ -44,7 +44,7 @@ class AgentRole(StrEnum): ARCHITECT = "architect" TASK_PLANNER = "task_planner" RISK_ANALYST = "risk_analyst" - # Refine-phase roles + # Analyze-phase roles REFINER = "refiner" # Reviewer roles REVIEWER_CODE = "reviewer_code" @@ -420,11 +420,11 @@ def depends_on(self, other: AgentRole) -> bool: ) -# Refine-phase agent role definitions +# Analyze-phase agent role definitions REFINER_ROLE = AgentRoleDefinition( role=AgentRole.REFINER, - description="Analyzes the task and produces a structured analysis in the refine phase", + description="Analyzes the task and produces a structured analysis in the analyze phase", responsibilities=[ "Understand the problem or feature request", "Research the current codebase to understand existing patterns", @@ -548,7 +548,7 @@ def depends_on(self, other: AgentRole) -> bool: REVIEWER_REFINE_ROLE = AgentRoleDefinition( role=AgentRole.REVIEWER_REFINE, - description="Reviews refine phase analysis quality and completeness", + description="Reviews analyze phase analysis quality and completeness", responsibilities=[ "Verify the analysis correctly identifies the core problem", "Assess research quality and codebase exploration", @@ -644,7 +644,7 @@ def depends_on(self, other: AgentRole) -> bool: AgentRole.ARCHITECT: ARCHITECT_ROLE, AgentRole.TASK_PLANNER: TASK_PLANNER_ROLE, AgentRole.RISK_ANALYST: RISK_ANALYST_ROLE, - # Refine-phase roles + # Analyze-phase roles AgentRole.REFINER: REFINER_ROLE, # Coordinator role AgentRole.COORDINATOR: COORDINATOR_ROLE, @@ -807,7 +807,7 @@ def can_retry(self, max_retries: int = 2) -> bool: _PHASE_ROLES: dict[str, list[AgentRole]] = { "implement": [AgentRole.CODER, AgentRole.TESTER, AgentRole.DOCUMENTER, AgentRole.INTEGRATOR], "plan": [AgentRole.ARCHITECT, AgentRole.TASK_PLANNER, AgentRole.RISK_ANALYST], - "refine": [AgentRole.REFINER], + "analyze": [AgentRole.REFINER], } _PHASE_REVIEWERS: dict[str, list[AgentRole]] = { @@ -818,7 +818,7 @@ def can_retry(self, max_retries: int = 2) -> bool: "plan": [ AgentRole.REVIEWER_PLAN, ], - "refine": [ + "analyze": [ AgentRole.REVIEWER_REFINE, AgentRole.REVIEWER_AGENT_DESIGN, ], diff --git a/shared/egg_contracts/checkpoint_cli.py b/shared/egg_contracts/checkpoint_cli.py index b642407ab4..dade8207d8 100644 --- a/shared/egg_contracts/checkpoint_cli.py +++ b/shared/egg_contracts/checkpoint_cli.py @@ -1626,7 +1626,7 @@ def create_parser() -> argparse.ArgumentParser: ) list_parser.add_argument( "--phase", - choices=["refine", "plan", "implement", "pr"], + choices=["analyze", "plan", "implement", "pr"], help="Filter by pipeline phase", ) list_parser.add_argument("--pipeline", help="Filter by pipeline run ID") @@ -1662,7 +1662,7 @@ def create_parser() -> argparse.ArgumentParser: ) context_parser.add_argument( "--phase", - choices=["refine", "plan", "implement", "pr"], + choices=["analyze", "plan", "implement", "pr"], help="Filter by pipeline phase", ) context_parser.add_argument( @@ -1714,7 +1714,7 @@ def create_parser() -> argparse.ArgumentParser: ) search_parser.add_argument( "--phase", - choices=["refine", "plan", "implement", "pr"], + choices=["analyze", "plan", "implement", "pr"], help="Filter by pipeline phase", ) search_parser.add_argument("--pipeline", help="Filter by pipeline run ID") diff --git a/shared/egg_contracts/checkpoints.py b/shared/egg_contracts/checkpoints.py index b655513620..a2bdd9649c 100644 --- a/shared/egg_contracts/checkpoints.py +++ b/shared/egg_contracts/checkpoints.py @@ -12,7 +12,7 @@ from enum import StrEnum from typing import Any -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field, field_validator, model_validator class FileOperationType(StrEnum): @@ -271,12 +271,15 @@ class CheckpointV2(BaseModel): session_started_at: datetime = Field(..., description="When session started") session_ended_at: datetime | None = Field(default=None, description="When session ended") - @field_validator("pipeline_phase") + @field_validator("pipeline_phase", mode="before") @classmethod def validate_pipeline_phase(cls, v: str | None) -> str | None: if v is None: return None - valid_phases = {"refine", "plan", "implement", "pr"} + # Normalize legacy "refine" phase from historical checkpoints + if v == "refine": + return "analyze" + valid_phases = {"analyze", "plan", "implement", "pr"} if v not in valid_phases: msg = f"pipeline_phase must be one of {valid_phases}" raise ValueError(msg) @@ -325,6 +328,20 @@ class CheckpointSummaryV2(BaseModel): total_tokens: int = Field(default=0, ge=0, description="Total tokens used") files_touched_count: int = Field(default=0, ge=0, description="Number of files touched") + @field_validator("pipeline_phase", mode="before") + @classmethod + def validate_pipeline_phase(cls, v: str | None) -> str | None: + if v is None: + return None + # Normalize legacy "refine" phase from historical checkpoints + if v == "refine": + return "analyze" + valid_phases = {"analyze", "plan", "implement", "pr"} + if v not in valid_phases: + msg = f"pipeline_phase must be one of {valid_phases}" + raise ValueError(msg) + return v + @classmethod def from_checkpoint(cls, checkpoint: "CheckpointV2") -> "CheckpointSummaryV2": """Create a summary from a full v2 checkpoint.""" @@ -406,6 +423,18 @@ class CheckpointIndexV2(BaseModel): default_factory=dict, description="repo (owner/repo) -> [checkpoint_ids]" ) + @model_validator(mode="after") + def _normalize_legacy_phase_index(self) -> "CheckpointIndexV2": + """Merge by_phase['refine'] entries into by_phase['analyze'] for backward compat.""" + if "refine" in self.by_phase: + refine_ids = self.by_phase.pop("refine") + existing = self.by_phase.get("analyze", []) + # Merge without duplicates, preserving order + existing_set = set(existing) + merged = existing + [cid for cid in refine_ids if cid not in existing_set] + self.by_phase["analyze"] = merged + return self + def get_by_session(self, session_id: str) -> list[str]: """Get checkpoint IDs for a session.""" return self.by_session.get(session_id, []) diff --git a/shared/egg_contracts/loader.py b/shared/egg_contracts/loader.py index 9459355d98..7e134abb9e 100644 --- a/shared/egg_contracts/loader.py +++ b/shared/egg_contracts/loader.py @@ -147,7 +147,7 @@ def create_contract( title: str, url: str, repo_root: Path | None = None, - initial_phase: PipelinePhase = PipelinePhase.REFINE, + initial_phase: PipelinePhase = PipelinePhase.ANALYZE, ) -> Contract: """ Create a new contract for an issue. @@ -179,7 +179,7 @@ def create_local_contract( pipeline_id: str, title: str, repo_root: Path | None = None, - initial_phase: PipelinePhase = PipelinePhase.REFINE, + initial_phase: PipelinePhase = PipelinePhase.ANALYZE, ) -> Contract: """ Create a new contract for a local-mode pipeline. diff --git a/shared/egg_contracts/models.py b/shared/egg_contracts/models.py index 2776197bfc..562d722eae 100644 --- a/shared/egg_contracts/models.py +++ b/shared/egg_contracts/models.py @@ -34,7 +34,7 @@ class PhaseStatus(StrEnum): class PipelinePhase(StrEnum): """Current pipeline phase.""" - REFINE = "refine" + ANALYZE = "analyze" PLAN = "plan" IMPLEMENT = "implement" PR = "pr" @@ -419,7 +419,7 @@ class Contract(BaseModel): default=None, description="Pipeline ID for local-mode pipelines" ) current_phase: PipelinePhase = Field( - default=PipelinePhase.REFINE, description="Current pipeline phase" + default=PipelinePhase.ANALYZE, description="Current pipeline phase" ) acceptance_criteria: list[AcceptanceCriterion] = Field( default_factory=list, description="Top-level acceptance criteria" @@ -432,9 +432,9 @@ class Contract(BaseModel): ) audit_log: list[AuditEntry] = Field(default_factory=list, description="Audit trail") refine_review_cycles: int = Field( - default=0, ge=0, description="Number of refine phase review cycles" + default=0, ge=0, description="Number of analyze phase review cycles" ) - refine_review_feedback: str = Field(default="", description="Feedback from last refine review") + refine_review_feedback: str = Field(default="", description="Feedback from last analyze review") plan_review_cycles: int = Field( default=0, ge=0, description="Number of plan phase review cycles" ) @@ -459,6 +459,14 @@ class Contract(BaseModel): description="Configuration for multi-agent orchestration", ) + @field_validator("current_phase", mode="before") + @classmethod + def _normalize_legacy_phase(cls, v: Any) -> Any: + """Accept legacy ``"refine"`` phase value from historical contracts.""" + if v == "refine": + return "analyze" + return v + @model_validator(mode="after") def _require_issue_or_pipeline_id(self) -> "Contract": """At least one of issue or pipeline_id must be set.""" diff --git a/shared/egg_contracts/phase_defaults.py b/shared/egg_contracts/phase_defaults.py index c16d5c2257..9bd26a34d9 100644 --- a/shared/egg_contracts/phase_defaults.py +++ b/shared/egg_contracts/phase_defaults.py @@ -13,8 +13,8 @@ PipelinePhase, ) -# Default checks for the refine phase -_REFINE_CHECKS: list[CheckDefinition] = [ +# Default checks for the analyze phase +_ANALYZE_CHECKS: list[CheckDefinition] = [ CheckDefinition( id="check-draft-validation", name="Draft Validation", @@ -78,8 +78,8 @@ # Default phase configurations _DEFAULT_PHASE_CONFIGS: dict[PipelinePhase, PhaseConfig] = { - PipelinePhase.REFINE: PhaseConfig( - checks=_REFINE_CHECKS, + PipelinePhase.ANALYZE: PhaseConfig( + checks=_ANALYZE_CHECKS, max_review_cycles=3, human_review_mechanism=HumanReviewMechanism.ISSUE_CHECKBOX, ), diff --git a/tests/sandbox/test_contract_cli.py b/tests/sandbox/test_contract_cli.py index 134f281cd6..2c032d1cb9 100644 --- a/tests/sandbox/test_contract_cli.py +++ b/tests/sandbox/test_contract_cli.py @@ -657,7 +657,7 @@ def test_add_decision_auto_appends_other_option(self, mock_gateway_factory, caps "success": True, "data": { "issue": {"number": 123, "title": "Test"}, - "current_phase": "refine", + "current_phase": "analyze", "phases": [], "decisions": [], }, @@ -693,7 +693,7 @@ def test_add_decision_markdown_format(self, mock_gateway_factory, capsys): "success": True, "data": { "issue": {"number": 123, "title": "Test"}, - "current_phase": "refine", + "current_phase": "analyze", "phases": [], "decisions": [], }, @@ -737,7 +737,7 @@ def test_add_decision_no_options_no_other(self, mock_gateway_factory, capsys): "success": True, "data": { "issue": {"number": 123, "title": "Test"}, - "current_phase": "refine", + "current_phase": "analyze", "phases": [], "decisions": [], }, @@ -775,7 +775,7 @@ def test_explicit_phase_overrides_contract(self, mock_gateway_factory): "success": True, "data": { "issue": {"number": 123, "title": "Test"}, - "current_phase": "refine", + "current_phase": "analyze", "phases": [], "decisions": [], }, @@ -807,7 +807,7 @@ def capturing_make_request(*args, **kwargs): assert result == 0 assert len(captured_payloads) == 1 decision = captured_payloads[0]["new_value"] - # Explicit --phase "implement" overrides contract's "refine" + # Explicit --phase "implement" overrides contract's "analyze" assert decision["phase"] == "implement" def test_phase_falls_back_to_contract_current_phase(self, mock_gateway_factory): diff --git a/tests/sandbox/test_orch_client.py b/tests/sandbox/test_orch_client.py index 2c1d0cf48f..d6178a0f32 100644 --- a/tests/sandbox/test_orch_client.py +++ b/tests/sandbox/test_orch_client.py @@ -235,7 +235,7 @@ def test_success(self, stub_server): { "/api/v1/pipelines/p1/status": ( 200, - {"data": {"status": "running", "phase": "refine"}}, + {"data": {"status": "running", "phase": "analyze"}}, ) } ) diff --git a/tests/sandbox/test_sdlc_cli.py b/tests/sandbox/test_sdlc_cli.py index a6d439bab7..ca8658ba24 100644 --- a/tests/sandbox/test_sdlc_cli.py +++ b/tests/sandbox/test_sdlc_cli.py @@ -177,7 +177,7 @@ def test_running_status(self): assert "12:34:56" in info def test_awaiting_human_display(self): - data = {"status": "awaiting_human", "current_phase": "refine", "pending_decisions": 1} + data = {"status": "awaiting_human", "current_phase": "analyze", "pending_decisions": 1} info = render_event_info(data) assert "awaiting approval" in info assert "1 pending decision" in info @@ -299,7 +299,7 @@ def parse_side_effect(resp): mock_parse.side_effect = parse_side_effect client.list_decisions.side_effect = [[decision], [decision2]] - mock_resolve.return_value = (".egg-state/drafts/42-analysis.md", "# Analysis", "refine") + mock_resolve.return_value = (".egg-state/drafts/42-analysis.md", "# Analysis", "analyze") mock_hitl.return_value = "resolved" result = watch_pipeline(client, "issue-42", pipeline_mode="issue", issue_number=42) diff --git a/tests/sandbox/test_sdlc_hitl.py b/tests/sandbox/test_sdlc_hitl.py index 2262699875..f450c34dc6 100644 --- a/tests/sandbox/test_sdlc_hitl.py +++ b/tests/sandbox/test_sdlc_hitl.py @@ -40,11 +40,11 @@ def test_string_context_ignored(self): def test_context_none(self): assert _detect_phase("review the pr", None) == "pr" - def test_refine_keyword(self): - assert _detect_phase("Please refine the draft") == "refine" + def test_analyze_keyword(self): + assert _detect_phase("Please analyze the draft") == "analyze" def test_analysis_keyword(self): - assert _detect_phase("Review the analysis document") == "refine" + assert _detect_phase("Review the analysis document") == "analyze" def test_plan_keyword(self): assert _detect_phase("Approve the plan") == "plan" @@ -71,7 +71,7 @@ def test_unknown_question(self): assert _detect_phase("Something else entirely") == "unknown" def test_case_insensitive(self): - assert _detect_phase("REFINE the document") == "refine" + assert _detect_phase("ANALYZE the document") == "analyze" assert _detect_phase("PLAN approval needed") == "plan" @@ -81,12 +81,12 @@ def test_case_insensitive(self): class TestGetDraftPath: - def test_refine_issue_mode(self): - path = _get_draft_path("refine", "issue", issue_number=42) + def test_analyze_issue_mode(self): + path = _get_draft_path("analyze", "issue", issue_number=42) assert path == ".egg-state/drafts/42-analysis.md" - def test_refine_local_mode(self): - path = _get_draft_path("refine", "local", pipeline_id="local-abc") + def test_analyze_local_mode(self): + path = _get_draft_path("analyze", "local", pipeline_id="local-abc") assert path == ".egg-state/drafts/local-abc-analysis.md" def test_implement_returns_none(self): @@ -103,7 +103,7 @@ def test_pr_phase(self): def test_local_mode_fallback_prefix(self): """When no pipeline_id given in local mode, prefix is 'local'.""" - path = _get_draft_path("refine", "local") + path = _get_draft_path("analyze", "local") assert path == ".egg-state/drafts/local-analysis.md" @@ -224,8 +224,8 @@ class TestHandleHitlCheckpoint: def _make_decision(self, **overrides): base = { "id": "d1", - "question": "Approve the refine analysis?", - "context": "Draft content for refine phase", + "question": "Approve the analysis?", + "context": "Draft content for analyze phase", } base.update(overrides) return base @@ -415,7 +415,7 @@ def test_edit_creates_missing_draft(self, mock_input, mock_editor, mock_repo, tm mock_input.side_effect = ["1", "3"] client = self._make_client() - decision = self._make_decision() # "refine" phase, has context + decision = self._make_decision() # "analyze" phase, has context result = handle_hitl_checkpoint( client, @@ -429,7 +429,7 @@ def test_edit_creates_missing_draft(self, mock_input, mock_editor, mock_repo, tm draft_file = tmp_path / ".egg-state" / "drafts" / "42-analysis.md" assert draft_file.exists() # File should contain the decision context (fallback from worktree) - assert draft_file.read_text() == "Draft content for refine phase" + assert draft_file.read_text() == "Draft content for analyze phase" mock_editor.assert_called_once_with(draft_file) @patch("egg_lib.sdlc_hitl._find_repo_path") @@ -457,7 +457,7 @@ def test_edit_creates_stub_when_no_context( assert result == "resolved" draft_file = tmp_path / ".egg-state" / "drafts" / "42-analysis.md" assert draft_file.exists() - assert draft_file.read_text() == "# Draft: refine\n\n" + assert draft_file.read_text() == "# Draft: analyze\n\n" mock_editor.assert_called_once_with(draft_file) @patch("egg_lib.sdlc_hitl._find_repo_path") @@ -556,7 +556,7 @@ def test_launch_claude(self, mock_input, mock_claude, mock_repo, tmp_path, capsy mock_claude.assert_called_once_with( tmp_path, ".egg-state/drafts/42-analysis.md", - "refine", + "analyze", 42, ) @@ -586,17 +586,17 @@ def test_explicit_phase_overrides_detect( mock_input.return_value = "3" # approve client = self._make_client() - # Question text says "refine" but explicit phase is "plan" + # Question text says "analysis" but explicit phase is "plan" decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", "phase": "plan", } handle_hitl_checkpoint(client, "issue-1", decision, pipeline_mode="issue", issue_number=1) - # _get_draft_path should have been called with "plan", not "refine" + # _get_draft_path should have been called with "plan", not "analyze" mock_draft_path.assert_called_once_with("plan", "issue", 1, "issue-1") @patch("egg_lib.sdlc_hitl._find_repo_path") @@ -613,14 +613,14 @@ def test_missing_phase_falls_back_to_detect( client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", } handle_hitl_checkpoint(client, "issue-1", decision, pipeline_mode="issue", issue_number=1) - # _detect_phase should resolve "refine" from the question text - mock_draft_path.assert_called_once_with("refine", "issue", 1, "issue-1") + # _detect_phase should resolve "analyze" from the question text + mock_draft_path.assert_called_once_with("analyze", "issue", 1, "issue-1") @patch("egg_lib.sdlc_hitl._find_repo_path") @patch("egg_lib.sdlc_hitl._get_draft_path") @@ -713,7 +713,9 @@ class TestLaunchClaude: @patch("llm.runner.shutil.which", return_value="/usr/bin/claude") def test_with_draft_context(self, mock_which, mock_run, tmp_path): """When draft_rel is provided, --append-system-prompt includes rules and context.""" - _launch_claude(tmp_path, draft_rel="drafts/42-analysis.md", phase="refine", issue_number=42) + _launch_claude( + tmp_path, draft_rel="drafts/42-analysis.md", phase="analyze", issue_number=42 + ) mock_run.assert_called_once() cmd = mock_run.call_args[0][0] assert cmd[0] == "/usr/bin/claude" @@ -724,7 +726,7 @@ def test_with_draft_context(self, mock_which, mock_run, tmp_path): # Rules text should be included assert "HITL Draft Editing Session" in prompt_text # Context-specific parts - assert "refine" in prompt_text + assert "analyze" in prompt_text assert "#42" in prompt_text assert "drafts/42-analysis.md" in prompt_text @@ -785,7 +787,7 @@ class TestHandlePhaseGate: def _make_decision(self, **overrides): base = { "id": "d1", - "question": "The refine phase has completed. Please review the analysis.", + "question": "The analyze phase has completed. Please review the analysis.", "context": "Draft content here", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -1186,7 +1188,7 @@ def test_unknown_type_falls_back(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "Some context", "decision_type": "unknown_type", } @@ -1212,7 +1214,7 @@ def test_choice_without_options_falls_back(self, mock_input, mock_repo, tmp_path client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "Draft content here", "decision_type": "choice", "options": [], @@ -1238,7 +1240,7 @@ def test_generic_cancel(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", "decision_type": "unknown_type", } @@ -1268,7 +1270,7 @@ def test_generic_eof_returns_cancelled(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", "decision_type": "unknown_type", } @@ -1293,7 +1295,7 @@ def test_generic_keyboard_interrupt_returns_cancelled(self, mock_input, mock_rep client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", "decision_type": "unknown_type", } @@ -1318,7 +1320,7 @@ def test_generic_c_option_cancels(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", "decision_type": "unknown_type", } @@ -1343,7 +1345,7 @@ def test_generic_feedback_then_approve(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "", "decision_type": "unknown_type", } @@ -1544,7 +1546,7 @@ def test_request_changes_empty_feedback_loops(self, mock_input, mock_repo, tmp_p client = self._make_client() decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "Draft here", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -1577,7 +1579,7 @@ def test_resolve_failure_retries_in_phase_gate(self, mock_input, mock_repo, tmp_ ] decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -1602,7 +1604,7 @@ def test_no_decision_id_uses_unknown(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -1629,7 +1631,7 @@ def test_phase_gate_multiline_feedback(self, mock_input, mock_repo, tmp_path): client = self._make_client() decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -2012,7 +2014,7 @@ def test_phase_gate_shows_header(self, mock_input, mock_repo, tmp_path, capsys): client = self._make_client() decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "Draft", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -2166,7 +2168,7 @@ def test_no_decision_type_no_options_falls_to_generic(self, mock_input, mock_rep client = self._make_client() decision = { "id": "d1", - "question": "Approve the refine analysis?", + "question": "Approve the analysis?", "context": "Some context", } result = handle_hitl_checkpoint( @@ -2378,7 +2380,7 @@ def test_phase_gate_shows_q_option(self, mock_input, mock_repo, tmp_path, capsys client.resolve_decision.return_value = {"status": "resolved"} decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", } @@ -2399,7 +2401,7 @@ def test_phase_gate_no_q_without_decisions(self, mock_input, mock_repo, tmp_path client.resolve_decision.return_value = {"status": "resolved"} decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", } @@ -2435,7 +2437,7 @@ def test_approve_with_pending_warns(self, mock_input, mock_repo, tmp_path, capsy client.resolve_decision.return_value = {"status": "resolved"} decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", } @@ -2772,7 +2774,7 @@ def test_q_answer_then_approve_flow(self, mock_input, mock_repo, tmp_path, capsy client.resolve_decision.return_value = {"status": "resolved"} decision = { "id": "d1", - "question": "The refine phase has completed.", + "question": "The analyze phase has completed.", "context": "", "decision_type": "phase_gate", } @@ -3024,7 +3026,7 @@ def test_view_option_opens_pager(self, mock_input, mock_pager, mock_repo, tmp_pa client = self._make_client() decision = { "id": "d1", - "question": "The refine phase has completed. Please review the analysis.", + "question": "The analyze phase has completed. Please review the analysis.", "context": "Full document content here", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -3090,7 +3092,7 @@ def test_initial_pager_called_for_phase_gate(self, mock_input, mock_pager, mock_ client = self._make_client() decision = { "id": "d1", - "question": "The refine phase has completed. Please review the analysis.", + "question": "The analyze phase has completed. Please review the analysis.", "context": "Analysis document content", "decision_type": "phase_gate", "options": ["approve", "request changes"], @@ -3134,7 +3136,7 @@ def test_returns_draft_for_refine(self, mock_repo, tmp_path): assert draft_rel == ".egg-state/drafts/42-analysis.md" assert draft_content == "# Analysis\nContent" - assert phase == "refine" + assert phase == "analyze" @patch("egg_lib.sdlc_hitl._find_repo_path") def test_returns_draft_for_plan(self, mock_repo, tmp_path): @@ -3173,7 +3175,7 @@ def test_falls_back_to_context(self, mock_repo, tmp_path): assert draft_rel == ".egg-state/drafts/42-analysis.md" assert draft_content == "Fallback context content" - assert phase == "refine" + assert phase == "analyze" @patch("egg_lib.sdlc_hitl._find_repo_path") def test_returns_none_for_implement(self, mock_repo, tmp_path): diff --git a/tests/shared/egg_container/test_marker_files.py b/tests/shared/egg_container/test_marker_files.py index 9467dd77ff..8d19e16efb 100644 --- a/tests/shared/egg_container/test_marker_files.py +++ b/tests/shared/egg_container/test_marker_files.py @@ -120,10 +120,10 @@ def test_no_markers_for_plan_phase(self, tmp_path): marker = tmp_path / ".egg-state" / dirname / ".egg-readonly" assert not marker.exists() - def test_no_markers_for_refine_phase(self, tmp_path): - """Refine phase should not create marker files.""" + def test_no_markers_for_analyze_phase(self, tmp_path): + """Analyze phase should not create marker files.""" repo_volumes = {"repo": str(tmp_path)} - ensure_egg_state_dirs(repo_volumes, phase="refine") + ensure_egg_state_dirs(repo_volumes, phase="analyze") for dirname in _IMPLEMENT_READONLY_DIRS: marker = tmp_path / ".egg-state" / dirname / ".egg-readonly" diff --git a/tests/shared/egg_container/test_phase_mounts.py b/tests/shared/egg_container/test_phase_mounts.py index ebfde41c23..e8cf32c269 100644 --- a/tests/shared/egg_container/test_phase_mounts.py +++ b/tests/shared/egg_container/test_phase_mounts.py @@ -241,7 +241,7 @@ def test_custom_container_base(self, tmp_path): for mount in mounts: assert mount.destination.startswith("/custom/path/myrepo/") - @pytest.mark.parametrize("phase", ["plan", "refine", "pr", "review", None, ""]) + @pytest.mark.parametrize("phase", ["plan", "analyze", "pr", "review", None, ""]) def test_non_implement_phases_return_empty(self, phase, tmp_path): """Non-implement phases return no mounts.""" for dirname in _IMPLEMENT_READONLY_DIRS: diff --git a/tests/shared/egg_contracts/test_audit.py b/tests/shared/egg_contracts/test_audit.py index eb72b8768c..2f845e476e 100644 --- a/tests/shared/egg_contracts/test_audit.py +++ b/tests/shared/egg_contracts/test_audit.py @@ -168,7 +168,7 @@ def test_creates_transition_action(self): entry = create_transition_entry( actor="system", role=AuditRole.SYSTEM, - from_phase="refine", + from_phase="analyze", to_phase="implement", ) @@ -190,11 +190,11 @@ def test_phases_stored_as_old_and_new_values(self): entry = create_transition_entry( actor="system", role=AuditRole.SYSTEM, - from_phase="refine", + from_phase="analyze", to_phase="plan", ) - assert entry.old_value == "refine" + assert entry.old_value == "analyze" assert entry.new_value == "plan" def test_with_reason(self): @@ -214,7 +214,7 @@ def test_without_reason(self): entry = create_transition_entry( actor="system", role=AuditRole.SYSTEM, - from_phase="refine", + from_phase="analyze", to_phase="implement", ) @@ -406,7 +406,7 @@ def test_transition_entry_format(self): role=AuditRole.SYSTEM, action=AuditAction.TRANSITION, field_path="current_phase", - old_value="refine", + old_value="analyze", new_value="implement", reason="Refinement complete", ) @@ -416,5 +416,5 @@ def test_transition_entry_format(self): assert "transition" in lines[1] assert "current_phase" in lines[1] - assert "(refine -> implement)" in lines[1] + assert "(analyze -> implement)" in lines[1] assert "- Refinement complete" in lines[1] diff --git a/tests/shared/egg_contracts/test_checkpoints.py b/tests/shared/egg_contracts/test_checkpoints.py index d41d55e523..3c5f64b6ad 100644 --- a/tests/shared/egg_contracts/test_checkpoints.py +++ b/tests/shared/egg_contracts/test_checkpoints.py @@ -414,7 +414,7 @@ def test_pipeline_phase_validation(self): session = self._make_session_metadata(now) # Valid phases - for phase in ("refine", "plan", "implement", "pr"): + for phase in ("analyze", "plan", "implement", "pr"): cp = CheckpointV2( id="ckpt-abc123def456", trigger_type=TriggerType.COMMIT, @@ -821,3 +821,64 @@ def test_get_by_repo(self): assert index.get_by_repo("jwbron/egg") == ["ckpt-aa00000001", "ckpt-bb00000002"] assert index.get_by_repo("entireio/cli") == ["ckpt-cc00000003"] assert index.get_by_repo("nonexistent/repo") == [] + + def test_legacy_refine_phase_normalized_in_by_phase(self): + """Test that by_phase 'refine' entries are merged into 'analyze' at load time.""" + now = datetime.now(UTC) + index = CheckpointIndexV2( + last_updated=now, + by_phase={"refine": ["ckpt-aa00000001", "ckpt-bb00000002"]}, + ) + # "refine" key should be gone, entries merged into "analyze" + assert "refine" not in index.by_phase + assert index.get_by_phase("analyze") == ["ckpt-aa00000001", "ckpt-bb00000002"] + + def test_legacy_refine_phase_merged_with_existing_analyze(self): + """Test that by_phase 'refine' entries merge with existing 'analyze' without duplicates.""" + now = datetime.now(UTC) + index = CheckpointIndexV2( + last_updated=now, + by_phase={ + "refine": ["ckpt-aa00000001", "ckpt-bb00000002"], + "analyze": ["ckpt-bb00000002", "ckpt-cc00000003"], + }, + ) + assert "refine" not in index.by_phase + # ckpt-bb00000002 appears in both but should be deduplicated + assert index.get_by_phase("analyze") == [ + "ckpt-bb00000002", + "ckpt-cc00000003", + "ckpt-aa00000001", + ] + + +class TestCheckpointV2BackwardCompat: + """Tests for backward-compat normalization of legacy 'refine' phase.""" + + def test_checkpoint_v2_refine_normalizes_to_analyze(self): + """Test that CheckpointV2 with pipeline_phase='refine' normalizes to 'analyze'.""" + now = datetime.now(UTC) + session = SessionMetadata(session_id="test-session", started_at=now) + checkpoint = CheckpointV2( + id="ckpt-abc123def456", + trigger_type=TriggerType.COMMIT, + commit_sha="abc1234567890", + session_id="container-123", + session=session, + created_at=now, + session_started_at=now, + pipeline_phase="refine", + ) + assert checkpoint.pipeline_phase == "analyze" + + def test_summary_v2_refine_normalizes_to_analyze(self): + """Test that CheckpointSummaryV2 with pipeline_phase='refine' normalizes to 'analyze'.""" + now = datetime.now(UTC) + summary = CheckpointSummaryV2( + id="ckpt-abc123def456", + trigger_type=TriggerType.COMMIT, + session_id="container-123", + created_at=now, + pipeline_phase="refine", + ) + assert summary.pipeline_phase == "analyze" diff --git a/tests/shared/egg_contracts/test_loader.py b/tests/shared/egg_contracts/test_loader.py index d88a377c12..ec8f21fe7e 100644 --- a/tests/shared/egg_contracts/test_loader.py +++ b/tests/shared/egg_contracts/test_loader.py @@ -30,7 +30,7 @@ def _make_contract( issue_number: int = 42, title: str = "Test issue", url: str = "https://github.com/owner/repo/issues/42", - phase: PipelinePhase = PipelinePhase.REFINE, + phase: PipelinePhase = PipelinePhase.ANALYZE, ) -> Contract: """Helper to create a minimal contract for testing.""" return Contract( @@ -125,7 +125,7 @@ def test_load_valid_contract(self, tmp_path): loaded = load_contract(42, repo_root=tmp_path) assert loaded.issue.number == 42 assert loaded.issue.title == "Test issue" - assert loaded.current_phase == PipelinePhase.REFINE + assert loaded.current_phase == PipelinePhase.ANALYZE def test_load_not_found_raises(self, tmp_path): """Test that loading a missing contract raises ContractNotFoundError.""" @@ -251,7 +251,7 @@ def test_creates_and_saves_contract(self, tmp_path): assert contract.issue.number == 55 assert contract.issue.title == "New feature" - assert contract.current_phase == PipelinePhase.REFINE + assert contract.current_phase == PipelinePhase.ANALYZE # Verify it was saved to disk assert contract_exists(55, repo_root=tmp_path) @@ -269,15 +269,15 @@ def test_create_with_custom_phase(self, tmp_path): ) assert contract.current_phase == PipelinePhase.IMPLEMENT - def test_create_default_phase_is_refine(self, tmp_path): - """Test that the default initial phase is REFINE.""" + def test_create_default_phase_is_analyze(self, tmp_path): + """Test that the default initial phase is ANALYZE.""" contract = create_contract( issue_number=57, title="Test", url="https://github.com/owner/repo/issues/57", repo_root=tmp_path, ) - assert contract.current_phase == PipelinePhase.REFINE + assert contract.current_phase == PipelinePhase.ANALYZE class TestLoadContractFromBranch: diff --git a/tests/shared/egg_contracts/test_models.py b/tests/shared/egg_contracts/test_models.py index c2c0d9e428..78d148c72a 100644 --- a/tests/shared/egg_contracts/test_models.py +++ b/tests/shared/egg_contracts/test_models.py @@ -207,7 +207,7 @@ def test_minimal_contract(self): ) assert contract.schemaVersion == "1.0" assert contract.issue.number == 133 - assert contract.current_phase == PipelinePhase.REFINE + assert contract.current_phase == PipelinePhase.ANALYZE assert contract.phases == [] assert contract.decisions == [] assert contract.workflow_owner is None @@ -647,7 +647,7 @@ def test_contract_phase_configs_json_roundtrip(self): url="https://example.com", ), phase_configs={ - PipelinePhase.REFINE: PhaseConfig( + PipelinePhase.ANALYZE: PhaseConfig( checks=[ CheckDefinition( id="check-draft", @@ -661,13 +661,13 @@ def test_contract_phase_configs_json_roundtrip(self): data = original.model_dump(mode="json") assert "phase_configs" in data - assert "refine" in data["phase_configs"] - assert len(data["phase_configs"]["refine"]["checks"]) == 1 + assert "analyze" in data["phase_configs"] + assert len(data["phase_configs"]["analyze"]["checks"]) == 1 restored = Contract.model_validate(data) assert restored.phase_configs is not None - assert PipelinePhase.REFINE in restored.phase_configs - assert len(restored.phase_configs[PipelinePhase.REFINE].checks) == 1 + assert PipelinePhase.ANALYZE in restored.phase_configs + assert len(restored.phase_configs[PipelinePhase.ANALYZE].checks) == 1 def test_contract_backward_compatibility_no_phase_configs(self): """Test that existing contracts without phase_configs still work.""" @@ -679,10 +679,41 @@ def test_contract_backward_compatibility_no_phase_configs(self): "title": "Test", "url": "https://example.com", }, - "current_phase": "refine", + "current_phase": "analyze", "phases": [], "decisions": [], "audit_log": [], } contract = Contract.model_validate(data) assert contract.phase_configs is None + + def test_contract_backward_compatibility_refine_normalizes_to_analyze(self): + """Test that legacy 'refine' phase value normalizes to ANALYZE.""" + data = { + "schemaVersion": "1.0", + "issue": { + "number": 123, + "title": "Test", + "url": "https://example.com", + }, + "current_phase": "refine", + "phases": [], + "decisions": [], + "audit_log": [], + } + contract = Contract.model_validate(data) + assert contract.current_phase == PipelinePhase.ANALYZE + + +class TestPipelineBackwardCompat: + """Tests for Pipeline backward-compat normalization of legacy 'refine' phase.""" + + def test_pipeline_refine_normalizes_to_analyze(self): + """Test that Pipeline with current_phase='refine' normalizes to ANALYZE.""" + from orchestrator.models import Pipeline + + pipeline = Pipeline( + id="issue-123", + current_phase="refine", + ) + assert pipeline.current_phase.value == "analyze" diff --git a/tests/shared/egg_contracts/test_phase_defaults.py b/tests/shared/egg_contracts/test_phase_defaults.py index 524b31dbd3..0ba5cad968 100644 --- a/tests/shared/egg_contracts/test_phase_defaults.py +++ b/tests/shared/egg_contracts/test_phase_defaults.py @@ -17,9 +17,9 @@ class TestGetDefaultPhaseConfig: """Tests for get_default_phase_config function.""" - def test_refine_phase_defaults(self): - """Test default config for refine phase.""" - config = get_default_phase_config(PipelinePhase.REFINE) + def test_analyze_phase_defaults(self): + """Test default config for analyze phase.""" + config = get_default_phase_config(PipelinePhase.ANALYZE) assert isinstance(config, PhaseConfig) assert config.max_review_cycles == 3 assert config.human_review_mechanism == HumanReviewMechanism.ISSUE_CHECKBOX @@ -118,7 +118,7 @@ def test_returns_defaults_when_phase_not_in_overrides(self): url="https://example.com", ), phase_configs={ - PipelinePhase.REFINE: PhaseConfig(max_review_cycles=5), + PipelinePhase.ANALYZE: PhaseConfig(max_review_cycles=5), }, ) # Request IMPLEMENT phase which is not overridden @@ -151,12 +151,12 @@ def test_contract_overrides_human_review_mechanism(self): url="https://example.com", ), phase_configs={ - PipelinePhase.REFINE: PhaseConfig( + PipelinePhase.ANALYZE: PhaseConfig( human_review_mechanism=HumanReviewMechanism.PR_REVIEW, ), }, ) - config = get_effective_phase_config(contract, PipelinePhase.REFINE) + config = get_effective_phase_config(contract, PipelinePhase.ANALYZE) assert config.human_review_mechanism == HumanReviewMechanism.PR_REVIEW def test_contract_overrides_checks_completely(self): diff --git a/tests/shared/egg_contracts/test_usage.py b/tests/shared/egg_contracts/test_usage.py index 16686322e9..f67220f1b0 100644 --- a/tests/shared/egg_contracts/test_usage.py +++ b/tests/shared/egg_contracts/test_usage.py @@ -268,7 +268,7 @@ def test_full_issue_usage(self): pr_number=522, session_ids=["session-1", "session-2"], branch="egg/issue-519", - pipeline_phases=["refine", "plan", "implement"], + pipeline_phases=["analyze", "plan", "implement"], tokens=TokenCounts(input_tokens=5000, output_tokens=2500), estimated_cost_usd=0.05, checkpoint_count=5, diff --git a/tests/shared/egg_contracts/test_usage_loader.py b/tests/shared/egg_contracts/test_usage_loader.py index f7028db87f..ee142a043d 100644 --- a/tests/shared/egg_contracts/test_usage_loader.py +++ b/tests/shared/egg_contracts/test_usage_loader.py @@ -135,7 +135,7 @@ def test_save_and_load_issue_usage(self): pr_number=522, session_ids=["s1", "s2"], branch="egg/issue-519", - pipeline_phases=["refine", "plan"], + pipeline_phases=["analyze", "plan"], tokens=TokenCounts(input_tokens=5000, output_tokens=2500), checkpoint_count=5, last_updated=now, diff --git a/tests/workflows/test_hitl_integration.py b/tests/workflows/test_hitl_integration.py index 3cca219be7..0b5f1b5837 100644 --- a/tests/workflows/test_hitl_integration.py +++ b/tests/workflows/test_hitl_integration.py @@ -78,7 +78,7 @@ def test_decision_markdown_checkbox_format(self): def test_phase_approval_format_detection(self): """Test that phase approval format is detectable by workflow conditions.""" # Simulate a phase completion comment - comment = """## Refine Phase Complete + comment = """## Analyze Phase Complete Analysis complete. diff --git a/tests/workflows/test_multi_reviewer.py b/tests/workflows/test_multi_reviewer.py index 93a02ee260..0c43050381 100644 --- a/tests/workflows/test_multi_reviewer.py +++ b/tests/workflows/test_multi_reviewer.py @@ -128,9 +128,9 @@ def test_all_empty_returns_empty(self): class TestPhaseBasedReviewerDefaults: """Tests for phase-specific reviewer configurations.""" - def test_refine_phase_reviewers(self): - """Refine phase should use refine + agent-design reviewers.""" - reviewers = get_default_reviewers("refine") + def test_analyze_phase_reviewers(self): + """Analyze phase should use refine + agent-design reviewers.""" + reviewers = get_default_reviewers("analyze") names = [r["name"] for r in reviewers] assert "refine" in names @@ -158,7 +158,7 @@ def test_implement_phase_reviewers(self): def test_reviewers_have_valid_names(self): """Each reviewer should have a valid name.""" - for phase in ["refine", "plan", "implement"]: + for phase in ["analyze", "plan", "implement"]: reviewers = get_default_reviewers(phase) for reviewer in reviewers: @@ -287,7 +287,7 @@ def get_default_reviewers(phase: str) -> list: Note: Review prompts are now built by the local orchestrator (orchestrator/routes/pipelines.py), not by shell scripts. """ - if phase == "refine": + if phase == "analyze": return [ {"name": "refine"}, {"name": "agent-design"},