Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .egg/phase-permissions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": "1.0",
"phases": {
"refine": {
"analyze": {
"allowed_operations": [
{
"type": "gh",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -135,15 +135,15 @@
}
},
"phase_file_restrictions": {
"refine": {
"analyze": {
"allowed_patterns": [
".egg-state/contracts/*",
".egg-state/drafts/*analysis*",
".egg-state/checkpoints/*",
".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": [
Expand Down
2 changes: 1 addition & 1 deletion .egg/schemas/checkpoint.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
8 changes: 4 additions & 4 deletions .egg/schemas/contract.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .egg/schemas/phase-permissions.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"$ref": "#/$defs/phasePermissions"
},
"properties": {
"refine": {
"analyze": {
"$ref": "#/$defs/phasePermissions"
},
"plan": {
Expand Down
4 changes: 2 additions & 2 deletions .egg/schemas/usage.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
"description": "Pipeline phases seen",
"items": {
"type": "string",
"enum": ["refine", "plan", "implement", "pr"]
"enum": ["analyze", "plan", "implement", "pr", "refine"]
},
"default": []
}
Expand Down Expand Up @@ -282,7 +282,7 @@
"description": "Pipeline phases seen",
"items": {
"type": "string",
"enum": ["refine", "plan", "implement", "pr"]
"enum": ["analyze", "plan", "implement", "pr", "refine"]
},
"default": []
}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ secrets.yaml
docker-compose.override.yml
.compose-build-hash

# Agent sandbox artifacts
CLAUDE.md

# OS
.DS_Store
Thumbs.db
Expand Down
8 changes: 4 additions & 4 deletions docs/hitl-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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 `<!-- egg-phase-approval -->` 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
Expand Down Expand Up @@ -265,7 +265,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
)
```

Expand Down
4 changes: 2 additions & 2 deletions gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,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 |
Expand All @@ -178,7 +178,7 @@ POST /api/v1/sessions/create
Description: Create a new session with optional SDLC phase tracking

PATCH /api/v1/sessions/<session_token>/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
```
Expand Down
6 changes: 3 additions & 3 deletions gateway/agent_restrictions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -444,7 +444,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,
Expand Down
14 changes: 11 additions & 3 deletions gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,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
#
Expand Down Expand Up @@ -3271,6 +3271,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(
Expand Down Expand Up @@ -3744,7 +3748,7 @@ def session_update(session_token: str) -> tuple[Response, int] | Response:


# Valid SDLC pipeline phases
VALID_PIPELINE_PHASES = frozenset({"refine", "plan", "implement", "pr"})
VALID_PIPELINE_PHASES = frozenset({"analyze", "plan", "implement", "pr"})


@app.route("/api/v1/sessions/<session_token>/phase", methods=["PATCH"])
Expand All @@ -3759,7 +3763,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:
Expand All @@ -3775,6 +3779,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))}"
Expand Down
2 changes: 1 addition & 1 deletion gateway/phase_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": "..."}}
Expand Down
16 changes: 8 additions & 8 deletions gateway/phase_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""

Expand Down Expand Up @@ -32,7 +32,7 @@ class PipelinePhase(StrEnum):
complexity in the gateway module. Values must be kept in sync.
"""

REFINE = "refine"
ANALYZE = "analyze"
PLAN = "plan"
IMPLEMENT = "implement"
PR = "pr"
Expand Down Expand Up @@ -409,7 +409,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"),
Expand All @@ -420,7 +420,7 @@ def _get_default_permissions(self) -> dict[PipelinePhase, PhasePermissions]:
Operation(OperationType.EGG_CONTRACT, "show *", "View contract state"),
],
blocked_operations=[
Operation(OperationType.GH, "pr create*", "Cannot create PRs during refine"),
Operation(OperationType.GH, "pr create*", "Cannot create PRs during analyze"),
],
exit_requires="human",
),
Expand Down Expand Up @@ -481,13 +481,13 @@ def _get_default_phase_file_restrictions(self) -> dict[PipelinePhase, PhaseFileR
"""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*",
Expand Down Expand Up @@ -602,7 +602,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

Expand Down Expand Up @@ -873,7 +873,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

Expand Down
2 changes: 1 addition & 1 deletion gateway/phase_transition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions gateway/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions gateway/tests/test_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -1406,10 +1406,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 (
Expand Down
Loading
Loading