Architecture: How should the 2,075-line module-gen-phase.executor.ts be de...

Documentation for Sessions

🏗️ Architecture: How should the 2,075-line module-gen-phase.executor.ts be de...

Date: 2026-03-27
Time: 12:49:26 UTC
Session: gspace1-271.3
Agent: architecture
Confidence: 91%
Status: ✅ Documented


📋 Decision Context

Question:

How should the 2,075-line module-gen-phase.executor.ts be decomposed given it has a 970-line run() method, 26 constructor parameters, and an inline 447-line DEPLOY phase?

Task: S271: Design decomposition plan for module-gen executor god-class


🎯 Decision Made

Chosen:

Incremental 5-service extraction: (1) slug-utils.ts — unified slug function replacing 8 duplicates, (2) llm-response-utils.ts — runtime logic extracted from types file, (3) GenerationFilesystemService — all fs/path/spawnSync operations with async execFile replacement, (4) GenerationOutcomeService — mind/spaceIntel/mastery recording with Promise.allSettled isolation, (5) DeployPhaseService — 447-line inline DEPLOY block promoted to proper phase service with DeployContext interface. Target: executor < 600 lines, run() < 100 lines, constructor < 15 params.

Options Considered:

  1. Incremental 5-service extraction with DeployContext (chosen)
  2. Extract only DEPLOY and leave the rest (insufficient — leaves 8 slug duplicates, spawnSync, coupled outcome recording)
  3. Full rewrite from scratch (risky — 30+ sessions of accumulated behavior would need re-verification)
  4. Event-driven with domain events (architecturally ideal but breaks SSE contract — deferred to future session)

💭 Reasoning

Root cause: The DEPLOY phase was never extracted into its own service. Every other phase (ENHANCE through INTEGRATE, plus BACKEND_SETUP) has a dedicated *.phase.ts file, but DEPLOY — the most complex phase — remained as 447 lines of inline code inside run(). This single missing extraction caused cascading complexity: file system operations, GitLab commits, manifest creation, structure building, template generation, and app shell creation all accumulated inline because they were 'part of DEPLOY.' The 26 constructor parameters (15 @Optional) directly result from the executor importing services that should belong to DEPLOY, outcome recording, and filesystem operations. The incremental approach was chosen over a full rewrite because: (a) the existing phase services are well-structured and shouldn't be touched, (b) NestJS DI wiring changes are risky in bulk, (c) each extraction can be verified independently via TypeScript compilation. The DeployContext interface is the critical design choice — DEPLOY reads 7+ local variables from run() that cannot be accessed via this., so they must be packaged into a typed context object. Alternative: event-driven architecture with domain events would be architecturally superior but requires changing the SSE emission contract, which is a breaking change for the frontend listener service.


📁 Files Involved


🔗 Related


Auto-generated by MCP Smart Consolidation