Skip to content

refactor: architecture ports and split god modules - #15

Draft
jeremiepas wants to merge 4 commits into
mainfrom
refactor/architecture-ports-and-split-god-modules
Draft

refactor: architecture ports and split god modules#15
jeremiepas wants to merge 4 commits into
mainfrom
refactor/architecture-ports-and-split-god-modules

Conversation

@jeremiepas

Copy link
Copy Markdown
Owner

Refactor: Architecture Ports and Split God Modules

Why

31 UseCase→Infrastructure import violations break clean architecture. Three god modules (UseCase.Extract 657 lines, UseCase.Pipeline 588 lines, Domain.Config 677 lines) hurt comprehension, testability, and evolution. Main.hs (718 lines) directly wires Infrastructure to UseCase without abstraction.

What Changes

  1. Port interfacesUseCase.Port.* record-of-functions decoupling UseCase from Infrastructure
  2. God module splits — Domain.Config ✅ (done), UseCase.Extract, UseCase.Pipeline split into focused sub-modules with backward-compatible re-exports
  3. IO pushed to edge — UseCase functions use AppEnv ports, not bare IO
  4. Thinned Main.hs — Infrastructure.Wiring handles all port wiring

Progress

  • Task 1: Domain.Config split — 5 focused sub-modules + backward-compatible re-export
  • Task 2-5: Port interfaces + AppEnv
  • Task 6-8: Refactor UseCase to use ports
  • Task 9-10: Split UseCase.Extract and Pipeline god modules
  • Task 11: Thin Main.hs
  • Task 12: Final verification

Verification

  • cabal build passes
  • cabal test passes (199/200 — 1 pre-existing flaky SDKSpec test)
  • openspec validate --changes runs in CI (advisory, non-blocking)

OpenSpec Artifacts

  • openspec/changes/refactor-architecture-ports-and-split-god-modules/ — proposal, design, specs, tasks

Jeremie added 4 commits July 28, 2026 14:29
Split Domain.Config (677 lines) into:
- Domain.Config.Core (97 lines) — GraphosConfig, defaults, merge
- Domain.Config.Extraction (284 lines) — ExtractorMode, Granularity, LSPServerConfig, defaults
- Domain.Config.Export (89 lines) — Neo4jConfig, MemgraphConfig, defaults
- Domain.Config.Observability (108 lines) — ObservabilityConfig, OtelConfig, defaults
- Domain.Config.Vision (135 lines) — VisionConfig, EmbeddingConfig, LabelingConfig, defaults

Original Domain.Config becomes a backward-compatible re-export module.
All existing imports continue to compile without changes.

Part of refactor-architecture-ports-and-split-god-modules (Task 1/12).
Add UseCase.Port.* record-of-functions interfaces:
- ExtractionPort: extractAll, extractGroup, extractViaLSP, extractFromFile,
  extractImageFile, extractImageBytes, extractChanged
- ExportPort: exportAll + ExportResult type
- FileSystemPort: load/save/clear checkpoint, load ignore patterns
- LoggingPort: logTrace/Debug/Info/Warn/Error + LogLevel enum
- ObservabilityPort: init, shutdown, incCounter, setGauge, traceEvent
- LLMPort: callLLM, parseLabels, generateEmbedding, analyzeImage,
  validateUrl + ImageAnalysis/ImageKind types

Add UseCase.AppEnv: aggregates all 6 ports as single DI mechanism.

All signatures reference only Domain types — no Infrastructure imports
in port signatures (FileSystemPort imports AnnotatedPattern as transitional).
Build passes, 199/200 tests pass (1 pre-existing flaky test).
…complete)

- Infrastructure.Wiring: production composition root that creates AppEnv
  with concrete implementations for all 6 ports
- ExtractionPort: refined with fine-grained LSP/TreeSitter primitives,
  LSPHandle uses Dynamic for opaque type safety
- LLMPort: fixed ImageKind (added OtherKind), Entity type, CommunityId
- FileSystemPort: imports PipelineCheckpoint from Domain.Types.Pipeline
- ExportPort: imports from Domain.Types (not Domain.Config)
- ObservabilityPort: fixed field names (otelMetrics/otelDebugTrace)
- All 6 ports compile, Wiring compiles, 199/200 tests pass

Note: ExportPort.epExportAll is TODO (will wire once UseCase.Export
is refactored to use ports). ExtractionPort uses unsafeCoerce for
LSPHandle -> LSPClient conversion (safe: only Wiring creates handles).
…ture (Task 6 partial)

- UseCase.Extract: replaced ALL Infrastructure imports with port calls
  - extractAll, extractChangedFiles, extractFromFile now take AppEnv
  - LSP calls go through extractionPort (epConnectLSP, epExtractViaLSP, etc.)
  - Logging goes through loggingPort (lpLogInfo, lpLogDebug, etc.)
  - Neo4j streaming goes through epPushExtractionStreaming
  - TreeSitter goes through epParseWithGrammar
  - Office/image extraction goes through epExtractDocFile, etc.
  - Zero Infrastructure imports in UseCase.Extract!

- UseCase.Pipeline: added appEnv creation via productionAppEnv
  - extractAll, extractChangedFiles now receive AppEnv
  - ingestFile now receives AppEnv

- UseCase.Ingest: added AppEnv parameter to ingestFile
  - Calls Extract.extractAll with AppEnv

Build passes, 199/200 tests pass (pre-existing flaky test).

Note: UseCase.Pipeline, Ingest, Label, Detect, Export, and Extract
sub-modules still have Infrastructure imports. Full migration is Tasks 7-8.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant