From e74a3dffd2460344bff3e552f17612a851ed2e1b Mon Sep 17 00:00:00 2001 From: Daniel Frey Date: Thu, 25 Dec 2025 19:53:55 +0100 Subject: [PATCH] fix: accept bug_fix workflow_type alias during planning (#240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(planning): accept bug_fix workflow_type alias * style(planning): ruff format * fix: refatored common logic * fix: remove ruff errors * fix: remove duplicate _normalize_workflow_type method Remove the incorrectly placed duplicate method inside ContextLoader class. The module-level function is the correct implementation being used. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: danielfrey63 Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com> Co-authored-by: AndyMik90 Co-authored-by: Claude Opus 4.5 --- apps/backend/planner_lib/context.py | 57 +++++++++++++---------- apps/backend/spec/validate_pkg/schemas.py | 10 +++- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/apps/backend/planner_lib/context.py b/apps/backend/planner_lib/context.py index c10392b4..ef2cdb28 100644 --- a/apps/backend/planner_lib/context.py +++ b/apps/backend/planner_lib/context.py @@ -11,6 +11,26 @@ from implementation_plan import WorkflowType from .models import PlannerContext +def _normalize_workflow_type(value: str) -> str: + """Normalize workflow type strings for consistent mapping. + + Strips whitespace, lowercases the value and removes underscores so variants + like 'bug_fix' or 'BugFix' map to the same key. + """ + normalized = (value or "").strip().lower() + return normalized.replace("_", "") + + +_WORKFLOW_TYPE_MAPPING: dict[str, WorkflowType] = { + "feature": WorkflowType.FEATURE, + "refactor": WorkflowType.REFACTOR, + "investigation": WorkflowType.INVESTIGATION, + "migration": WorkflowType.MIGRATION, + "simple": WorkflowType.SIMPLE, + "bugfix": WorkflowType.INVESTIGATION, +} + + class ContextLoader: """Loads context files and determines workflow type.""" @@ -64,13 +84,6 @@ class ContextLoader: 3. spec.md explicit declaration - Spec writer's declaration 4. Keyword-based detection - Last resort fallback """ - type_mapping = { - "feature": WorkflowType.FEATURE, - "refactor": WorkflowType.REFACTOR, - "investigation": WorkflowType.INVESTIGATION, - "migration": WorkflowType.MIGRATION, - "simple": WorkflowType.SIMPLE, - } # 1. Check requirements.json (user's explicit intent) requirements_file = self.spec_dir / "requirements.json" @@ -78,9 +91,11 @@ class ContextLoader: try: with open(requirements_file) as f: requirements = json.load(f) - declared_type = requirements.get("workflow_type", "").lower() - if declared_type in type_mapping: - return type_mapping[declared_type] + declared_type = _normalize_workflow_type( + requirements.get("workflow_type", "") + ) + if declared_type in _WORKFLOW_TYPE_MAPPING: + return _WORKFLOW_TYPE_MAPPING[declared_type] except (json.JSONDecodeError, KeyError): pass @@ -90,9 +105,11 @@ class ContextLoader: try: with open(assessment_file) as f: assessment = json.load(f) - declared_type = assessment.get("workflow_type", "").lower() - if declared_type in type_mapping: - return type_mapping[declared_type] + declared_type = _normalize_workflow_type( + assessment.get("workflow_type", "") + ) + if declared_type in _WORKFLOW_TYPE_MAPPING: + return _WORKFLOW_TYPE_MAPPING[declared_type] except (json.JSONDecodeError, KeyError): pass @@ -108,14 +125,6 @@ class ContextLoader: """ content_lower = spec_content.lower() - type_mapping = { - "feature": WorkflowType.FEATURE, - "refactor": WorkflowType.REFACTOR, - "investigation": WorkflowType.INVESTIGATION, - "migration": WorkflowType.MIGRATION, - "simple": WorkflowType.SIMPLE, - } - # Check for explicit workflow type declaration in spec # Look for patterns like "**Type**: feature" or "Type: refactor" explicit_type_patterns = [ @@ -127,9 +136,9 @@ class ContextLoader: for pattern in explicit_type_patterns: match = re.search(pattern, content_lower) if match: - declared_type = match.group(1).strip() - if declared_type in type_mapping: - return type_mapping[declared_type] + declared_type = _normalize_workflow_type(match.group(1)) + if declared_type in _WORKFLOW_TYPE_MAPPING: + return _WORKFLOW_TYPE_MAPPING[declared_type] # FALLBACK: Keyword-based detection (only if no explicit type found) # Investigation indicators diff --git a/apps/backend/spec/validate_pkg/schemas.py b/apps/backend/spec/validate_pkg/schemas.py index 1b8a07c0..f64db3d1 100644 --- a/apps/backend/spec/validate_pkg/schemas.py +++ b/apps/backend/spec/validate_pkg/schemas.py @@ -21,7 +21,15 @@ IMPLEMENTATION_PLAN_SCHEMA = { "workflow_rationale", "status", ], - "workflow_types": ["feature", "refactor", "investigation", "migration", "simple"], + "workflow_types": [ + "feature", + "refactor", + "investigation", + "migration", + "simple", + "bugfix", + "bug_fix", + ], "phase_schema": { # Support both old format ("phase" number) and new format ("id" string) "required_fields_either": [["phase", "id"]], # At least one of these