From ed93bcd2e4f926f0e48244dcf8ba995da7eb59da Mon Sep 17 00:00:00 2001 From: Piyush Makhija Date: Fri, 7 Aug 2026 19:49:07 +0530 Subject: [PATCH] chore: delete the orphaned helper_nodes package, keeping its two corrections langgraph_engine/helper_nodes/ held five modules and 538 lines that nothing imported. CLAUDE.md has described it as "scheduled for removal in a follow-up cleanup" since the subpackage migration; this is that cleanup. Closes item 3 of issue #296. "No importers" was not the whole justification, and checking further changed what this commit does. All nine of the package's public node functions are also defined in orchestrator.py -- the module create_flow_graph actually wires -- so this was an extraction that was begun and never finished, not a component that lost its callers. Both copies were last touched by the same commit (06b0463, the Level/Step rename), which had been maintaining them in lockstep. Comparing the two copies function by function found five character-identical and four divergent. The divergences are mostly the adaptations a deeper package needs -- parent.parent.parent instead of parent.parent, renamed module constants, different log tags -- but two of them are documentation corrections that exist ONLY in the orphaned copy, with the stale text in the live one: save_workflow_memory's comment named ~/.claude/memory/sessions/ while the code beneath it writes to memory/logs/sessions/. The comment contradicted the line it described. optimize_context_after_level1 said "Level 3" in a docstring line, a summary line and an inline comment. Level 3 stopped existing in 06b0463, which renamed it to Level 2. The orphan's docstring was updated by that rename; the live one's was not. Both are ported into orchestrator.py here, before the deletion rather than alongside it, so nothing is discarded that the live module lacked. Deleting the package first and noticing afterwards is how a fix disappears without anyone recording that it ever existed. The Quick Info counts move 253 -> 248 and 493 -> 488. That was not spotted by hand: tests/test_doc_counts_match_the_tree.py, merged one commit ago in #297, failed on the deletion and named both figures. It is the first thing that enforcement caught, and it caught it immediately. Verified: orchestrator imports and create_flow_graph(hook_mode=True) builds a graph, every test that touches the orchestrator passes, all six CI gates pass, test collection is clean, and no .py file anywhere still names helper_nodes. CHANGELOG.md, docs/orchestration_prompt.md and docs/architecture/ still refer to the package. The CHANGELOG entry and the architecture draft are point-in-time records; orchestration_prompt.md explicitly sequenced this removal as separate work, which is exactly what happened. Rewriting any of them would falsify the history rather than correct it. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 + CLAUDE.md | 5 +- langgraph_engine/helper_nodes/__init__.py | 20 - .../helper_nodes/context_helpers.py | 94 ----- .../helper_nodes/output_helpers.py | 382 ------------------ .../helper_nodes/standards_helpers.py | 26 -- langgraph_engine/helper_nodes/step_helpers.py | 16 - langgraph_engine/orchestrator.py | 8 +- 8 files changed, 8 insertions(+), 545 deletions(-) delete mode 100644 langgraph_engine/helper_nodes/__init__.py delete mode 100644 langgraph_engine/helper_nodes/context_helpers.py delete mode 100644 langgraph_engine/helper_nodes/output_helpers.py delete mode 100644 langgraph_engine/helper_nodes/standards_helpers.py delete mode 100644 langgraph_engine/helper_nodes/step_helpers.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4077ee6f..4d20112f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ### Removed +- **`langgraph_engine/helper_nodes/` is deleted** (5 modules, 538 lines). All nine of its public node functions were also defined in `orchestrator.py`, which is the module the graph actually wires, and nothing imported the package -- it was an extraction that was started and never completed. Two documentation corrections existed **only** in the orphaned copy and were ported to `orchestrator.py` before the deletion rather than discarded with it: `save_workflow_memory`'s comment named `~/.claude/memory/sessions/` while its code writes to `memory/logs/sessions/`, and `optimize_context_after_level1` still said "Level 3" in three places, a level that stopped existing in the `06b0463` rename. The live module had the stale text in both cases. + - **Step 1 no longer decomposes or executes the prompt it emits** (fb7bbaa). Phase 2 ran `todo_decomposer` to split the orchestration prompt into a TODO list and `orchestrator_agent_caller` once per TODO, each a nested `claude -p` subprocess. Both re-derived work that belongs elsewhere: the master template's STEP 13 already produces the MULTI-AGENT PROMPT BUNDLE, and the node runs inside a hook subprocess, so it cannot execute in the session it is serving -- it can only hand that session something to run. `orchestrator_result` now records what was emitted (`mode`, `prompt_chars`, `template_source`, `library_version`) rather than what was executed. An empty dict would have satisfied the contracts equally well, but `flow_trace_converter` and `decision_explainer` both read the field to build reports, and handing them a hollow value to keep a postcondition green is how a trace starts lying about what happened. - **The three Step 1 Phase 2 scripts are deleted** (c1d1d47): `todo_decomposer.py`, `todo_executor.py`, `orchestrator_agent_caller.py`, together with `tests/test_todo_decomposition.py` (27 tests, which imported them directly and covered nothing else). They had no caller after fb7bbaa. Dead code that still looks live is worse than absent code -- the next reader would have found three plausible scripts describing an execution model the pipeline no longer has. - **Four FlowState fields dropped** (e59a271): `todo_list`, `todo_results`, `completed_todos`, `current_todo_index`. With the scripts gone they had neither a writer nor a reader anywhere in the engine. `completed_todos` additionally declared an `_merge_lists` parallel-write reducer for a value no node produces. diff --git a/CLAUDE.md b/CLAUDE.md index 23b5a429..37a1b00b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ Claude Workflow Engine is a 3-level LangGraph-based orchestration pipeline for a | **Status** | Active Development | | **Primary Location** | langgraph_engine/ | | **MCP Servers** | 13 servers -- all in separate repos under [techdeveloper-org](https://github.com/orgs/techdeveloper-org/repositories); 1 also keeps an in-engine copy in `src/mcp/` | -| **Total Python Files** | 253 (langgraph_engine/); 493 repo-wide | +| **Total Python Files** | 248 (langgraph_engine/); 488 repo-wide | | **Test Files** | 100 (91 unit, 5 integration, 3 e2e, 1 load) | | **Call Graph** | 578 classes, 3,985 methods, 4 languages (Python/Java/TS/Kotlin) | @@ -141,7 +141,7 @@ Level 2: SDLC Execution Core (9 active steps: Steps 0-8) | +-- project_session.py # Session utilities (imported by hook packages) | +-- policy_tracking_helper.py # Policy tracking (imported by hook packages) +-- langgraph_engine/ # Core orchestration engine (REPO ROOT -- sibling of scripts/, NOT nested under it) -| +-- core/ state/ routing/ helper_nodes/ # LazyLoader/ErrorHandler, FlowState, routing, helper nodes +| +-- core/ state/ routing/ # LazyLoader/ErrorHandler, FlowState, routing | +-- diagrams/ (+drawio/) parsers/ integrations/ # Strategy/Factory: UML gens, 4 lang parsers, GitHub/Jira/Figma/Jenkins | +-- analysis/ context/ engine_logging/ github/ metrics/ quality/ security/ skills/ standards/ # domain subpackages (v1.20 migration) | +-- build_dependency_resolver/ runtime_verification/ # build-dep parsers; node contracts + verifier @@ -177,7 +177,6 @@ Level 2: SDLC Execution Core (9 active steps: Steps 0-8) | State Package | langgraph_engine/state/ | FlowState, StepKeys, reducers, WorkflowContextOptimizer | | Core Package | langgraph_engine/core/ | LazyLoader, get_logger, node_error_handler, NodeResult, create_step_node | | Routing Package | langgraph_engine/routing/ | All routing functions split by level | -| Helper Nodes | langgraph_engine/helper_nodes/ | Orphaned package -- no live importers anywhere in the repo (confirmed by repo-wide grep); kept internally consistent but scheduled for removal in a follow-up cleanup | | Graph Factory | langgraph_engine/orchestrator.py | create_flow_graph(hook_mode): single canonical StateGraph factory (verify_node runtime-verification wrapping applied here) | | Diagrams Package | langgraph_engine/diagrams/ | Strategy Pattern: DiagramFactory + 13 generators | | Parsers Package | langgraph_engine/parsers/ | Abstract Factory: ParserRegistry + 4 language parsers | diff --git a/langgraph_engine/helper_nodes/__init__.py b/langgraph_engine/helper_nodes/__init__.py deleted file mode 100644 index 3f7e6916..00000000 --- a/langgraph_engine/helper_nodes/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Helper nodes module - All non-subgraph node functions for the pipeline graph. - -Extracted from orchestrator.py to separate helper logic from graph construction. -Organized by concern: preflight_guard_helpers, context_helpers, output_helpers, -step_helpers, standards_helpers. -""" - -from .context_helpers import optimize_context_after_level1, optimize_context_for_level3_step, save_workflow_memory -from .output_helpers import output_node, synthesize_prompt_with_flow_data, verify_prompt_integrity -from .step_helpers import step5_retry_increment_node - -__all__ = [ - "optimize_context_after_level1", - "optimize_context_for_level3_step", - "save_workflow_memory", - "verify_prompt_integrity", - "synthesize_prompt_with_flow_data", - "output_node", - "step5_retry_increment_node", -] diff --git a/langgraph_engine/helper_nodes/context_helpers.py b/langgraph_engine/helper_nodes/context_helpers.py deleted file mode 100644 index caef55a2..00000000 --- a/langgraph_engine/helper_nodes/context_helpers.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Context helper nodes - Context optimization and workflow memory persistence. - -Extracted from orchestrator.py. Handles context compression between levels -and saving workflow memory for session persistence. -""" - -from pathlib import Path - -try: - import sys as _sys - - _sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "src")) - from utils.path_resolver import get_claude_home - - _CONTEXT_HELPERS_CLAUDE_HOME = get_claude_home() -except ImportError: - _CONTEXT_HELPERS_CLAUDE_HOME = Path.home() / ".claude" - -from ..core.logger_factory import get_logger -from ..flow_state import FlowState, StepKeys, WorkflowContextOptimizer - -logger = get_logger(__name__) - - -def optimize_context_after_level1(state: FlowState) -> dict: - """Optimize context after Level 1 completes before passing to Level 2. - - Stores full Level 1 output in workflow_memory, passes only summary to Level 2. - This keeps context clean while preserving full data for fallback/debugging. - """ - # Store full level 1 output - level1_output = { - StepKeys.CONTEXT_LOADED: state.get(StepKeys.CONTEXT_LOADED), - StepKeys.CONTEXT_PERCENTAGE: state.get(StepKeys.CONTEXT_PERCENTAGE), - StepKeys.SESSION_CHAIN_LOADED: state.get(StepKeys.SESSION_CHAIN_LOADED), - StepKeys.PATTERNS_DETECTED: state.get(StepKeys.PATTERNS_DETECTED, []), - StepKeys.PREFERENCES_DATA: state.get(StepKeys.PREFERENCES_DATA, {}), - } - - state = WorkflowContextOptimizer.store_step_output(state, "level1_output", level1_output) - - # Build optimized context for Level 2 - optimized = WorkflowContextOptimizer.build_optimized_context(state) - state["workflow_context_optimized"] = optimized - - return state - - -def optimize_context_for_level3_step(state: FlowState, step_name: str) -> dict: - """Optimize context before each Level 3 step. - - For Level 3, only pass data specific to current step. - All previous step outputs stay in workflow_memory. - """ - # Store current step's inputs/outputs - current_data = {k: v for k, v in state.items() if k.startswith("step") and isinstance(v, dict)} - - state = WorkflowContextOptimizer.store_step_output(state, step_name, current_data) - - return state - - -def save_workflow_memory(state: FlowState) -> dict: - """Save workflow memory to disk for session persistence.""" - try: - import json - - session_id = state.get(StepKeys.SESSION_ID, "unknown") - memory = state.get(StepKeys.WORKFLOW_MEMORY, {}) - - if memory and session_id != "unknown": - # Save to ~/.claude/memory/logs/sessions/{session_id}/workflow-memory.json - memory_dir = _CONTEXT_HELPERS_CLAUDE_HOME / "memory" / "logs" / "sessions" / session_id - memory_dir.mkdir(parents=True, exist_ok=True) - - memory_file = memory_dir / "workflow-memory.json" - with open(memory_file, "w", encoding="utf-8") as f: - json.dump( - { - "session_id": session_id, - "timestamp": state.get(StepKeys.TIMESTAMP), - "workflow_memory": memory, - "context_optimization_stats": state.get(StepKeys.STEP_OPTIMIZATION_STATS, {}), - "memory_size_kb": state.get(StepKeys.WORKFLOW_MEMORY_SIZE_KB, 0), - }, - f, - indent=2, - ) - - return {"workflow_memory_file": str(memory_file)} - except (OSError, TypeError) as exc: - logger.debug(f"[context_helpers] workflow memory save skipped: {exc}") - - return {} diff --git a/langgraph_engine/helper_nodes/output_helpers.py b/langgraph_engine/helper_nodes/output_helpers.py deleted file mode 100644 index a45f78ef..00000000 --- a/langgraph_engine/helper_nodes/output_helpers.py +++ /dev/null @@ -1,382 +0,0 @@ -"""Output helper nodes - Final output synthesis and pipeline execution logging. - -Extracted from orchestrator.py. Handles the final output_node which synthesizes -the prompt, saves the execution log, and accumulates session info. - -CHANGE LOG (v1.15.0): - Removed TOON log line from _save_pipeline_execution_log() Level 1 section - (TOON compression node removed from pipeline). - -CHANGE LOG (v1.15.2): - Removed step_info rows for steps 4-7 from _save_pipeline_execution_log() - (TOON Refinement, Skill & Agent Selection, Skill Validation, Final Prompt - Generation were removed from the pipeline in v1.13.0). -""" - -import sys -from pathlib import Path - -from ..core.logger_factory import get_logger - -try: - import sys as _sys - - _sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "src")) - from utils.path_resolver import get_claude_home - - _OUTPUT_HELPERS_CLAUDE_HOME = get_claude_home() -except ImportError: - _OUTPUT_HELPERS_CLAUDE_HOME = Path.home() / ".claude" - -from ..flow_state import FlowState, StepKeys -from .context_helpers import save_workflow_memory - -logger = get_logger(__name__) - - -def verify_prompt_integrity(state: FlowState) -> bool: - """Verify that original user prompt was never modified during flow. - - CRITICAL: user_message must equal user_message_original - If modified, that's a bug in the flow design. - """ - original = state.get(StepKeys.USER_MESSAGE_ORIGINAL, "") - current = state.get(StepKeys.USER_MESSAGE, "") - - if original != current: - print("[WARNING] PROMPT INTEGRITY VIOLATION!", file=sys.stderr) - print(f" Original: {original[:50]}...", file=sys.stderr) - print(f" Current: {current[:50]}...", file=sys.stderr) - return False - - return True - - -def synthesize_prompt_with_flow_data(state: FlowState) -> dict: - """Synthesize comprehensive prompt using all collected 3-level flow data. - - SYNTHESIS PROCESS: - 1. Collect data from all levels - 2. Call PromptGenerator.synthesize_with_flow_data() - 3. Create comprehensive prompt - 4. Return synthesized prompt for actual work - """ - try: - import importlib.util - - # Import PromptGenerator from prompt-generation-policy.py - scripts_path = ( - Path(__file__).parent.parent.parent - / "scripts" - / "architecture" - / "03-execution-system" - / "00-prompt-generation" - ) - spec = importlib.util.spec_from_file_location( - "prompt_generation_policy", scripts_path / "prompt-generation-policy.py" - ) - pg_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(pg_module) - - generator = pg_module.PromptGenerator() - - # Prepare flow data for synthesis - use ACTUAL pipeline results - # Collect validated tasks for TodoList - validated_tasks = state.get(StepKeys.TASKS_VALIDATED, []) - raw_tasks = state.get(StepKeys.TASKS, {}).get("tasks", []) - all_tasks = validated_tasks if validated_tasks else raw_tasks - - # Collect plan phases - plan_exec = state.get(StepKeys.PLAN_EXECUTION, {}) - plan_phases = plan_exec.get("phases", []) if isinstance(plan_exec, dict) else [] - - flow_data = { - "level0_preflight": { - "unicode_check": state.get(StepKeys.UNICODE_CHECK, False), - "encoding_check": state.get(StepKeys.ENCODING_CHECK, False), - "windows_path_check": state.get(StepKeys.WINDOWS_PATH_CHECK, False), - }, - "level1_context_sync": { - "context_percentage": state.get(StepKeys.CONTEXT_PERCENTAGE, 0), - "session_chain_loaded": state.get(StepKeys.SESSION_CHAIN_LOADED, False), - "patterns_detected": state.get(StepKeys.PATTERNS_DETECTED, []), - "project_type": state.get(StepKeys.DETECTED_FRAMEWORK, "Unknown"), - }, - "standards": { - "standards_count": state.get(StepKeys.STANDARDS_COUNT, 0), - "is_java_project": state.get(StepKeys.IS_JAVA_PROJECT, False), - "java_standards_loaded": state.get(StepKeys.JAVA_STANDARDS_LOADED, False), - }, - "level2_sdlc_pipeline": { - "task_type": state.get(StepKeys.TASK_TYPE, "General"), - "complexity": state.get(StepKeys.COMPLEXITY, 5), - "suggested_model": state.get(StepKeys.SELECTED_MODEL, "complex_reasoning"), - "plan_mode_suggested": state.get(StepKeys.PLAN_REQUIRED, False), - "reasoning": state.get(StepKeys.REASONING, ""), - "tasks": all_tasks, - "task_count": len(all_tasks), - "plan_phases": plan_phases, - "selected_skill": state.get(StepKeys.SKILL, ""), - "selected_agent": state.get(StepKeys.AGENT, ""), - "selected_skills": state.get(StepKeys.SKILLS, []), - "selected_agents": state.get(StepKeys.AGENTS, []), - "skill_definition": state.get(StepKeys.SKILL_DEFINITION, ""), - "agent_definition": state.get(StepKeys.AGENT_DEFINITION, ""), - }, - } - - # SYNTHESIS: Create comprehensive prompt - # Priority: user_message > user_message_original > env var CURRENT_USER_MESSAGE - import os - - user_msg = ( - state.get(StepKeys.USER_MESSAGE) - or state.get(StepKeys.USER_MESSAGE_ORIGINAL) - or os.environ.get("CURRENT_USER_MESSAGE", "") - ) - synthesis_result = generator.synthesize_with_flow_data(user_msg, flow_data) - - return { - "synthesized_prompt": synthesis_result.get("synthesized_prompt", ""), - "synthesis_metadata": { - "original_message": synthesis_result.get("original_message"), - "context_level": synthesis_result.get("context_level"), - "data_used": synthesis_result.get("data_used"), - }, - } - - except Exception as e: - print(f"[WARNING] Prompt synthesis failed: {e}", file=sys.stderr) - # Fallback: return original message - return { - "synthesized_prompt": state.get(StepKeys.USER_MESSAGE, ""), - "synthesis_metadata": {"status": "fallback"}, - } - - -def output_node(state: FlowState) -> dict: - """Final output node - determines completion status and synthesizes final prompt.""" - # Verify prompt integrity before finishing - if not verify_prompt_integrity(state): - # Log but don't block - prompt should never be modified - pass - - # Write session-start voice flag so Stop hook speaks a greeting - # Use LEGACY flag (no PID) because pipeline runs in a separate process - # from Claude Code. Stop-notifier's _resolve_flag checks PID first, then legacy. - try: - flag_dir = _OUTPUT_HELPERS_CLAUDE_HOME - start_flag = flag_dir / ".session-start-voice" - if not start_flag.exists(): - task_type = state.get(StepKeys.TASK_TYPE, "task") - complexity = state.get(StepKeys.COMPLEXITY, 5) - skill = state.get(StepKeys.SKILL, "") - msg = f"Starting {task_type} task, complexity {complexity} out of 10." - if skill: - msg += f" Using {skill} skill." - start_flag.write_text(msg, encoding="utf-8") - except OSError as exc: - logger.debug(f"[output] voice start-flag write skipped: {exc}") - - # SYNTHESIS: Create comprehensive prompt from all flow data - synthesis = synthesize_prompt_with_flow_data(state) - - # Save workflow memory before finishing - save_workflow_memory(state) - - # Determine final status based on actual step results (not just errors list) - if state.get(StepKeys.PREFLIGHT_STATUS) == "BLOCKED": - final_status = "BLOCKED" - else: - step_failures = [] - for key, val in state.items(): - if key.endswith("_status") and isinstance(val, str) and val == "ERROR": - step_failures.append(key) - if step_failures: - final_status = "FAILED" - elif state.get(StepKeys.WARNINGS): - final_status = "PARTIAL" - else: - final_status = "OK" - - # Save detailed pipeline execution log to session folder - _save_pipeline_execution_log(state, final_status) - - # In hook_mode, Step 8 (Final Telemetry & Summary Report) doesn't run. Save a quick summary anyway. - if not state.get(StepKeys.SUMMARY_SAVED): - session_dir = state.get(StepKeys.SESSION_DIR) or state.get(StepKeys.SESSION_PATH, "") - if session_dir: - try: - from datetime import datetime - - quick_summary = ( - f"Pipeline: {final_status} | " - f"Task: {state.get(StepKeys.TASK_TYPE, '?')} | " - f"Complexity: {state.get(StepKeys.COMPLEXITY, '?')}/10 | " - f"Skill: {state.get(StepKeys.SKILL, 'none')} | " - f"Agent: {state.get(StepKeys.AGENT, 'none')} | " - f"Framework: {state.get(StepKeys.DETECTED_FRAMEWORK, '?')} | " - f"Time: {datetime.now().strftime('%H:%M:%S')}" - ) - summary_file = Path(session_dir) / "execution-summary.txt" - summary_file.write_text(quick_summary, encoding="utf-8") - except OSError as exc: - logger.debug(f"[output] execution-summary write skipped: {exc}") - - # SESSION ACCUMULATE - Record this request's data via MCP session tools - try: - _src_mcp_dir = Path(__file__).resolve().parent.parent.parent / "src" / "mcp" - if str(_src_mcp_dir) not in sys.path: - sys.path.insert(0, str(_src_mcp_dir)) - from session_hooks import accumulate_request - - accumulate_request( - session_id=state.get(StepKeys.SESSION_ID, ""), - prompt=state.get(StepKeys.USER_MESSAGE, "")[:300], - task_type=state.get(StepKeys.TASK_TYPE, ""), - skill=state.get(StepKeys.SKILL, ""), - complexity=int(state.get(StepKeys.COMPLEXITY, 0)), - model=state.get(StepKeys.SELECTED_MODEL, ""), - cwd=state.get(StepKeys.PROJECT_ROOT, ""), - plan_mode=bool(state.get(StepKeys.PLAN_REQUIRED)), - context_pct=int(state.get("context_pct", 0)), - supplementary_skills=",".join(state.get(StepKeys.SKILLS, []) or []), - ) - except Exception as exc: - logger.debug(f"[output] session accumulate skipped: {exc}") - - # Return synthesis result with proper status - return { - "final_status": final_status, - "synthesized_prompt": synthesis.get("synthesized_prompt", ""), - "synthesis_metadata": synthesis.get("synthesis_metadata", {}), - } - - -def _save_pipeline_execution_log(state: FlowState, final_status: str) -> None: - """Save a detailed pipeline execution log to session folder. - - Creates a human-readable execution-log.md in the session directory showing: - - Each step's status, duration, decisions - - What came in, what went out - - Total pipeline timing - - Skills/agents selected - """ - from datetime import datetime - - session_dir = state.get(StepKeys.SESSION_DIR) or state.get(StepKeys.SESSION_PATH, "") - if not session_dir: - return - - try: - log_lines = [] - log_lines.append("# Pipeline Execution Log") - log_lines.append(f"\n**Session**: {state.get(StepKeys.SESSION_ID, 'unknown')}") - log_lines.append(f"**Date**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - log_lines.append(f"**Project**: {state.get(StepKeys.PROJECT_ROOT, '.')}") - log_lines.append(f"**Final Status**: {final_status}") - log_lines.append(f"**Framework**: {state.get(StepKeys.DETECTED_FRAMEWORK, 'unknown')}") - log_lines.append("") - - # Level 0: Pre-Flight Sanity Guard - log_lines.append("## Level 0: Pre-Flight Sanity Guard") - log_lines.append(f"- Unicode: {'PASS' if state.get(StepKeys.UNICODE_CHECK) else 'FAIL'}") - log_lines.append(f"- Encoding: {'PASS' if state.get(StepKeys.ENCODING_CHECK) else 'FAIL'}") - log_lines.append(f"- Paths: {'PASS' if state.get(StepKeys.WINDOWS_PATH_CHECK) else 'FAIL'}") - log_lines.append(f"- Status: {state.get(StepKeys.PREFLIGHT_STATUS, 'unknown')}") - log_lines.append("") - - # Level 1: Session & Context Synchronization - log_lines.append("## Level 1: Session & Context Synchronization") - log_lines.append(f"- Session: {state.get(StepKeys.SESSION_ID, 'none')}") - log_lines.append(f"- Complexity: {state.get(StepKeys.COMPLEXITY_SCORE, '?')}/10") - graph_score = state.get(StepKeys.GRAPH_COMPLEXITY_SCORE) - if graph_score: - log_lines.append(f"- Graph Complexity: {graph_score}/25") - combined = state.get(StepKeys.COMBINED_COMPLEXITY_SCORE) - if combined: - log_lines.append(f"- Combined Score: {combined}/25") - log_lines.append(f"- Context Files: {state.get(StepKeys.FILES_LOADED_COUNT, 0)}") - log_lines.append(f"- Cache Hit: {state.get(StepKeys.CONTEXT_CACHE_HIT, False)}") - log_lines.append("") - - # Standards (always-on, loaded from disk -- not a numbered pipeline level) - log_lines.append("## Standards (loaded from disk)") - log_lines.append(f"- Standards Loaded: {state.get(StepKeys.STANDARDS_COUNT, 0)}") - log_lines.append(f"- Framework Detected: {state.get(StepKeys.DETECTED_FRAMEWORK, 'unknown')}") - log_lines.append(f"- MCP Discovered: {state.get(StepKeys.MCP_DISCOVERED_COUNT, 0)}") - log_lines.append(f"- Tool Rules: {'Loaded' if state.get(StepKeys.TOOL_OPTIMIZATION_LOADED) else 'Missing'}") - log_lines.append("") - - # Level 2: SDLC Execution Core (Steps 0-8) - log_lines.append("## Level 2: SDLC Execution Core") - log_lines.append("") - log_lines.append("| Step | Name | Status | Duration | Details |") - log_lines.append("|------|------|--------|----------|---------|") - - step_info = [ - (0, "Pre-Analysis & CallGraph Scan", "step1_callgraph_available", "step1_files_read"), - (1, "Task Orchestration & Planning", "step1_task_type", "step1_complexity"), - (2, "Issue Tracking", "step1_status", "step1_issue_url"), - (3, "Branch & Workspace Setup", "step1_status", "step1_branch_name"), - (4, "Implementation & Code Generation", "step1_implementation_status", "step1_llm_invoked"), - (5, "Pull Request & Automated Review", "step1_status", "step1_pr_url"), - (6, "Issue & Ticket Closure", "step1_status", "step1_issue_closed"), - (7, "Documentation & UML Generation", "step1_documentation_status", "step1_update_count"), - (8, "Final Telemetry & Summary Report", "step2_status", "step2_voice_sent"), - ] - - for step_num, name, status_key, detail_key in step_info: - time_key = f"step{step_num}_execution_time_ms" - duration_ms = state.get(time_key, 0) - duration_str = f"{duration_ms:.0f}ms" if duration_ms else "-" - - status_val = state.get(status_key, "") - detail_val = state.get(detail_key, "") - - # Format detail value - if isinstance(detail_val, (list, dict)): - detail_str = f"{len(detail_val)} items" if isinstance(detail_val, list) else f"{len(detail_val)} keys" - elif isinstance(detail_val, bool): - detail_str = "Yes" if detail_val else "No" - elif detail_val is not None: - detail_str = str(detail_val)[:60] - else: - detail_str = "-" - - status_str = str(status_val)[:30] if status_val else "-" - log_lines.append(f"| {step_num:2d} | {name} | {status_str} | {duration_str} | {detail_str} |") - - log_lines.append("") - - # Selected resources - log_lines.append("## Selected Resources") - skills = state.get(StepKeys.SKILLS) or [] - skill = state.get(StepKeys.SKILL, "") - if skill and skill not in skills: - skills = [skill] + skills - agents = state.get(StepKeys.AGENTS) or [] - agent = state.get(StepKeys.AGENT, "") - if agent and agent not in agents: - agents = [agent] + agents - - if skills: - log_lines.append(f"- **Skills**: {', '.join(skills)}") - if agents: - log_lines.append(f"- **Agents**: {', '.join(agents)}") - log_lines.append("") - - # Errors - errors = state.get(StepKeys.ERRORS) or [] - if errors: - log_lines.append("## Errors") - for err in errors[:10]: - log_lines.append(f"- {err}") - log_lines.append("") - - # Write to session folder - log_path = Path(session_dir) / "execution-log.md" - log_path.write_text("\n".join(log_lines), encoding="utf-8") - - except OSError as exc: - logger.debug(f"[output] execution-log write skipped: {exc}") diff --git a/langgraph_engine/helper_nodes/standards_helpers.py b/langgraph_engine/helper_nodes/standards_helpers.py deleted file mode 100644 index 64b8659d..00000000 --- a/langgraph_engine/helper_nodes/standards_helpers.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Standards helper nodes - Level 2 standards selection and integration hooks. - -Extracted from orchestrator.py. Handles auto-detection of project type/framework -and loading all applicable standards with priority-based conflict resolution. -Also contains standards integration hook functions for Level 3 steps. - -CHANGE LOG (v1.15.2): - Removed apply_integration_step1 through apply_integration_step7 - (Steps 1-7 removed from the pipeline in v1.13.0; these hooks had zero - live callers in the graph). -""" - -from ..flow_state import FlowState -from ..standards_integration import apply_standards_at_step - - -def apply_integration_step10(state: FlowState) -> dict: - """Apply standards integration point at Step 10 (code review).""" - updated = apply_standards_at_step(10, dict(state)) - return {k: updated[k] for k in updated if k not in state or updated[k] != state.get(k)} - - -def apply_integration_step13(state: FlowState) -> dict: - """Apply standards integration point at Step 13 (documentation).""" - updated = apply_standards_at_step(13, dict(state)) - return {k: updated[k] for k in updated if k not in state or updated[k] != state.get(k)} diff --git a/langgraph_engine/helper_nodes/step_helpers.py b/langgraph_engine/helper_nodes/step_helpers.py deleted file mode 100644 index d067d7e5..00000000 --- a/langgraph_engine/helper_nodes/step_helpers.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Step helper nodes - Per-step utility nodes for Level 3 execution. - -Extracted from orchestrator.py. Contains nodes that manage step-level -state transitions (e.g., retry counter increments). -""" - -from ..flow_state import FlowState, StepKeys - - -def step5_retry_increment_node(state: FlowState) -> dict: - """Increment retry count before re-routing to Step 10. - - State mutations must happen in nodes, not routing functions (LangGraph anti-pattern). - """ - retry_count = state.get(StepKeys.RETRY_COUNT, 0) - return {StepKeys.RETRY_COUNT: retry_count + 1} diff --git a/langgraph_engine/orchestrator.py b/langgraph_engine/orchestrator.py index 43cd10a2..3abcce05 100644 --- a/langgraph_engine/orchestrator.py +++ b/langgraph_engine/orchestrator.py @@ -145,9 +145,9 @@ def step5_retry_increment_node(state: FlowState) -> dict: def optimize_context_after_level1(state: FlowState) -> dict: - """Optimize context after Level 1 completes before passing to Level 3. + """Optimize context after Level 1 completes before passing to Level 2. - Stores full Level 1 output in workflow_memory, passes only summary to Level 3. + Stores full Level 1 output in workflow_memory, passes only summary to Level 2. This keeps context clean while preserving full data for fallback/debugging. """ # Store full level 1 output @@ -161,7 +161,7 @@ def optimize_context_after_level1(state: FlowState) -> dict: state = WorkflowContextOptimizer.store_step_output(state, "level1_output", level1_output) - # Build optimized context for Level 3 + # Build optimized context for Level 2 optimized = WorkflowContextOptimizer.build_optimized_context(state) state["workflow_context_optimized"] = optimized @@ -191,7 +191,7 @@ def save_workflow_memory(state: FlowState) -> dict: memory = state.get(StepKeys.WORKFLOW_MEMORY, {}) if memory and session_id != "unknown": - # Save to ~/.claude/memory/sessions/{session_id}/workflow-memory.json + # Save to ~/.claude/memory/logs/sessions/{session_id}/workflow-memory.json memory_dir = _ORCHESTRATOR_CLAUDE_HOME / "memory" / "logs" / "sessions" / session_id memory_dir.mkdir(parents=True, exist_ok=True)