Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
80 changes: 80 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: haskell-actions/setup@v2
id: setup-haskell
with:
ghc-version: "9.10"
cabal-version: "3.14"

- name: Configure
run: cabal configure --enable-tests --flag dev

- name: Cache
uses: actions/cache@v4
env:
cache-name: cache-cabal
with:
path: |
${{ steps.setup-haskell.outputs.cabal-store }}
dist-newstyle
key: ${{ runner.os }}-release-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }}
restore-keys: |
${{ runner.os }}-release-${{ env.cache-name }}-
${{ runner.os }}-release-
${{ runner.os }}-

- name: Install dependencies
run: cabal build --only-dependencies all

- name: Build
run: cabal build all

- name: Run tests
run: cabal test all

- name: Generate SHA256 checksums
run: |
mkdir -p dist/release
cp "$(cabal list-bin graphos)" dist/release/graphos-linux-x86_64
cd dist/release
sha256sum graphos-linux-x86_64 > graphos-linux-x86_64.sha256

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Graphos ${{ github.ref_name }}
body: |
## Graphos ${{ github.ref_name }}

**Changes**: See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md)

### Assets
| File | Description |
|------|-------------|
| `graphos-linux-x86_64` | Static Linux binary (x86_64) |
| `graphos-linux-x86_64.sha256` | SHA256 checksum |
files: |
dist/release/graphos-linux-x86_64
dist/release/graphos-linux-x86_64.sha256
draft: false
prerelease: false
107 changes: 107 additions & 0 deletions .tmp/sessions/2026-04-20-graphos-refactor/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Task Context: Graphos Modular Refactoring

Session ID: 2026-04-20-graphos-refactor
Created: 2026-04-20T00:00:00Z
Status: in_progress

## Current Request

Refactor the 6 identified coupling hotspots in the Graphos Haskell codebase. All findings came from Graphos graph analysis (god_nodes, get_neighbors, get_community). The goal is to split oversized modules into focused sub-modules following clean architecture principles while maintaining backward compatibility via re-export hubs.

## Context Files (Standards to Follow)

- .opencode/context/core/standards/code-quality.md — Haskell patterns, clean architecture, module structure, anti-patterns
- .opencode/context/core/standards/test-coverage.md — Hspec/QuickCheck testing patterns

## Reference Files (Source Material)

These are the 6 modules identified as refactoring targets, ordered by priority:

### P0: Infrastructure.Export.HTML (degree 436 — #1 hotspot)
- src/Graphos/Infrastructure/Export/HTML.hs — Contains ~200+ inline JS strings, ~100+ inline CSS strings, HTML templates, VisNode/VisEdge JSON types, search UI, community sidebar, all embedded as Haskell string literals
- src/Graphos/Infrastructure/Export/CommunityGraph.hs — Hub that knows all export formats (cross-coupling)

### P1: Domain.Types (degree 342 — #2 hotspot, community 2621 with 258 members)
- src/Graphos/Domain/Types.hs — God Module containing: Node, Edge, Relation, Confidence, FileType, PipelineConfig, Detection, Analysis, GraphDiff, Hyperedge, LabeledGraph, ToJSON/FromJSON instances, relationToText/textToRelation converters, defaultConfig

### P1: Infrastructure.LSP.Client (degree 292 — #3 hotspot, community 2040 with 211 members)
- src/Graphos/Infrastructure/LSP/Client.hs — Single module handling: 30+ hardcoded language→server mappings (languageServerCommands), process management (connectToLSP, disconnectLSP), JSON-RPC protocol (readLSPMessage, sendLSPMessage), capability parsing (parseServerCapabilities, lookupBoolCaps), symbol extraction (extractDocumentSymbols, extractCallHierarchy, extractWorkspaceSymbols), data conversion (symbolToNodes, symbolTreeToEdges, workspaceSymbolsToDocumentSymbols)
- src/Graphos/Infrastructure/LSP/Capabilities.hs — Partial capability handling already exists

### P2: UseCase.Extract (degree 254 — #4 hotspot)
- src/Graphos/UseCase/Extract.hs — Combines: Haskell stub parsing (parseHaskellImports, parseHaskellDecls, extractHaskellStub, isTopLevelDecl), doc extraction (parseHeader, parseTags, parseWikiLinks, extractDocFile), LSP orchestration (doExtractWithSharedLSP, groupByLSPServer), thread pool management (QSemN concurrency)

### P2: Domain.Graph (degree 208 — #6 hotspot)
- src/Graphos/Domain/Graph.hs — 15+ exported functions: buildGraph, shortestPath, articulationPoints, biconnectedComponents, godNodes, mergeGraphs, graphDiff, subgraph, neighbors, degree, breadthFirstSearch, depthFirstSearch, dominators, edgeBetweenness, isFileNode, isConceptNode + Graph record with 7+ fields + FGL bridge
- src/Graphos/Domain/Graph/FGL.hs — Already a sub-module, extend this pattern

### P2: Export cross-coupling
- src/Graphos/Infrastructure/Export/CommunityGraph.hs — conceptually_related_to edges to every export format AND FileSystem modules. Mediator anti-pattern.

## Components

### P0: Export.HTML Refactor
- Extract JS/CSS/HTML into template files (templates/graph.html, templates/graph.js, templates/graph.css)
- Extract VisNode/VisEdge JSON types into Domain.Export.Visualization (domain-level types, not HTML-specific)
- Split buildHTML into: renderNodesJSON, renderEdgesJSON, renderPageShell, renderSearchUI, renderCommunitySidebar

### P1: Domain.Types Split
- Domain.Types.Node — Node, NodeId, file types (Code, Doc, Image, Paper, Video)
- Domain.Types.Edge — Edge, EdgeId, Relation, Confidence, relationToText/textToRelation converters
- Domain.Types.Graph — LabeledGraph, GraphDiff, Extraction, Hyperedge types
- Domain.Types.Pipeline — PipelineConfig, defaultConfig, Detection types
- Domain.Types.Analysis — Analysis, GodNode, SurprisingConnection, SuggestedQuestion
- Keep Domain.Types as re-export hub for backward compatibility

### P1: LSP.Client Split
- Infrastructure.LSP.ServerMap — 30+ languageServerCommands mapping
- Infrastructure.LSP.Transport — JSON-RPC read/write, connectToLSP, disconnectLSP
- Infrastructure.LSP.CapabilityParse — Server capability parsing (merge with existing Capabilities.hs)
- Infrastructure.LSP.Extraction — symbolToNodes, symbolTreeToEdges, workspaceSymbolsToDocumentSymbols conversions
- Keep LSP.Client as thin orchestrator

### P2: UseCase.Extract Split
- UseCase.Extract.Haskell — parseHaskellImports, parseHaskellDecls, extractHaskellStub, isTopLevelDecl
- UseCase.Extract.Markdown — parseHeader, parseTags, parseWikiLinks, extractDocFile
- UseCase.Extract.LSPOrchestrator — doExtractWithSharedLSP, groupByLSPServer
- Keep UseCase.Extract as composition orchestrator

### P2: Domain.Graph Split
- Domain.Graph.Core — Graph type, buildGraph, mergeGraphs, mergeExtractions, field accessors
- Domain.Graph.Query — shortestPath, neighbors, degree, BFS, DFS, subgraph
- Domain.Graph.Analysis — articulationPoints, biconnectedComponents, godNodes, edgeBetweenness, dominators
- Domain.Graph.Diff — graphDiff, LabeledGraph
- Keep Domain.Graph as re-export hub
- Domain.Graph.FGL already exists — extend this pattern

### P2: Export Type Class
- Introduce Domain.Export.Format type class: class ExportFormat a where render :: LabeledGraph -> a
- Reduces CommunityGraph hub coupling

## Constraints

- Haskell project: cabal build, nix-shell, Hspec/QuickCheck tests
- Clean architecture: Domain has NO IO, UseCase has NO IO implementation, Infrastructure handles all side effects
- Module naming: Graphos.{Domain|UseCase|Infrastructure}.{SubModule}
- PascalCase types, camelCase functions, explicit exports, type signatures on all top-level definitions
- Backward compatibility: original modules become re-export hubs where possible
- Tests must pass after each subtask (cabal test)
- Each refactoring step must be independently compilable and testable
- Graph has 0 bridge nodes = safe to refactor (no single point of failure)

## Exit Criteria

- [ ] All 6 hotspot modules split into focused sub-modules
- [ ] Each new sub-module has explicit exports and < 100 lines ideally
- [ ] Original modules serve as re-export hubs where applicable
- [ ] Clean architecture boundaries respected (no IO in Domain/UseCase)
- [ ] cabal build succeeds after each and all subtasks
- [ ] cabal test passes after each and all subtasks
- [ ] Graph degree for former hotspot modules significantly reduced

## Progress

- [ ] Session initialized
- [ ] Tasks created by TaskManager
- [ ] Implementation complete
- [ ] All tests pass
26 changes: 26 additions & 0 deletions .tmp/tasks/graphos-refactor/subtask_01.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"id": "graphos-refactor-01",
"seq": "01",
"title": "Create Domain.Types.Node sub-module",
"status": "pending",
"depends_on": [],
"parallel": true,
"suggested_agent": "coder-agent",
"context_files": [".opencode/context/core/standards/code-quality.md"],
"reference_files": ["src/Graphos/Domain/Types.hs", "Graphos.cabal"],
"acceptance_criteria": [
"src/Graphos/Domain/Types/Node.hs created with Node, NodeId, FileType (Code/Doc/Image/Paper/Video) and related smart constructors",
"Explicit export list with type signatures on all top-level definitions",
"No IO in this module (pure domain)",
"cabal build succeeds with new module in exposed-modules",
"Domain.Types re-exports all names from Domain.Types.Node"
],
"deliverables": [
"src/Graphos/Domain/Types/Node.hs",
"Updated src/Graphos/Domain/Types.hs (re-export + removed definitions)",
"Updated Graphos.cabal"
],
"started_at": null,
"completed_at": null,
"completion_summary": null
}
26 changes: 26 additions & 0 deletions .tmp/tasks/graphos-refactor/subtask_02.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"id": "graphos-refactor-02",
"seq": "02",
"title": "Create Domain.Types.Edge sub-module",
"status": "pending",
"depends_on": [],
"parallel": true,
"suggested_agent": "coder-agent",
"context_files": [".opencode/context/core/standards/code-quality.md"],
"reference_files": ["src/Graphos/Domain/Types.hs", "Graphos.cabal"],
"acceptance_criteria": [
"src/Graphos/Domain/Types/Edge.hs created with Edge, EdgeId, Relation, Confidence, relationToText, textToRelation",
"Explicit export list with type signatures",
"No IO (pure domain)",
"cabal build succeeds",
"Domain.Types re-exports all names from Domain.Types.Edge"
],
"deliverables": [
"src/Graphos/Domain/Types/Edge.hs",
"Updated src/Graphos/Domain/Types.hs (re-export)",
"Updated Graphos.cabal"
],
"started_at": null,
"completed_at": null,
"completion_summary": null
}
26 changes: 26 additions & 0 deletions .tmp/tasks/graphos-refactor/subtask_03.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"id": "graphos-refactor-03",
"seq": "03",
"title": "Create Domain.Types.Graph sub-module",
"status": "pending",
"depends_on": [],
"parallel": true,
"suggested_agent": "coder-agent",
"context_files": [".opencode/context/core/standards/code-quality.md"],
"reference_files": ["src/Graphos/Domain/Types.hs", "Graphos.cabal"],
"acceptance_criteria": [
"src/Graphos/Domain/Types/Graph.hs created with LabeledGraph, GraphDiff, Extraction, Hyperedge types",
"Explicit export list with type signatures",
"No IO (pure domain)",
"cabal build succeeds",
"Domain.Types re-exports all names from Domain.Types.Graph"
],
"deliverables": [
"src/Graphos/Domain/Types/Graph.hs",
"Updated src/Graphos/Domain/Types.hs (re-export)",
"Updated Graphos.cabal"
],
"started_at": null,
"completed_at": null,
"completion_summary": null
}
28 changes: 28 additions & 0 deletions .tmp/tasks/graphos-refactor/subtask_04.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"id": "graphos-refactor-04",
"seq": "04",
"title": "Create Domain.Types.Pipeline + Domain.Types.Analysis sub-modules",
"status": "pending",
"depends_on": [],
"parallel": true,
"suggested_agent": "coder-agent",
"context_files": [".opencode/context/core/standards/code-quality.md"],
"reference_files": ["src/Graphos/Domain/Types.hs", "src/Graphos/Domain/Analysis.hs", "Graphos.cabal"],
"acceptance_criteria": [
"src/Graphos/Domain/Types/Pipeline.hs created with PipelineConfig, defaultConfig, Detection types",
"src/Graphos/Domain/Types/Analysis.hs created with Analysis, GodNode, SurprisingConnection, SuggestedQuestion",
"Explicit export lists with type signatures",
"No IO (pure domain)",
"cabal build succeeds",
"Domain.Types re-exports all names from both"
],
"deliverables": [
"src/Graphos/Domain/Types/Pipeline.hs",
"src/Graphos/Domain/Types/Analysis.hs",
"Updated src/Graphos/Domain/Types.hs (re-export hub, all original definitions removed)",
"Updated Graphos.cabal"
],
"started_at": null,
"completed_at": null,
"completion_summary": null
}
37 changes: 37 additions & 0 deletions .tmp/tasks/graphos-refactor/subtask_05.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"id": "graphos-refactor-05",
"seq": "05",
"title": "Update all imports after Domain.Types split + cabal test",
"status": "pending",
"depends_on": ["01", "02", "03", "04"],
"parallel": false,
"suggested_agent": "coder-agent",
"context_files": [".opencode/context/core/standards/code-quality.md"],
"reference_files": [
"src/Graphos/Domain/Types.hs",
"src/Graphos/UseCase/Extract.hs",
"src/Graphos/UseCase/Build.hs",
"src/Graphos/UseCase/Cluster.hs",
"src/Graphos/UseCase/Infer.hs",
"src/Graphos/UseCase/Analyze.hs",
"src/Graphos/UseCase/Query.hs",
"src/Graphos/UseCase/Export.hs",
"src/Graphos/UseCase/Report.hs",
"src/Graphos/UseCase/Pipeline.hs",
"src/Graphos/Infrastructure/Export/HTML.hs",
"src/Graphos/Infrastructure/LSP/Client.hs"
],
"acceptance_criteria": [
"All modules that imported Graphos.Domain.Types still compile (re-export hub ensures compatibility)",
"Modules that only need specific types update to import new sub-modules directly",
"cabal build succeeds",
"cabal test passes"
],
"deliverables": [
"Updated import statements across all consuming modules",
"All tests passing"
],
"started_at": null,
"completed_at": null,
"completion_summary": null
}
27 changes: 27 additions & 0 deletions .tmp/tasks/graphos-refactor/subtask_06.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"id": "graphos-refactor-06",
"seq": "06",
"title": "Create Domain.Graph.Core sub-module",
"status": "pending",
"depends_on": ["05"],
"parallel": false,
"suggested_agent": "coder-agent",
"context_files": [".opencode/context/core/standards/code-quality.md"],
"reference_files": ["src/Graphos/Domain/Graph.hs", "src/Graphos/Domain/Graph/FGL.hs", "src/Graphos/Domain/Types.hs", "Graphos.cabal"],
"acceptance_criteria": [
"src/Graphos/Domain/Graph/Core.hs created with: Graph record type, buildGraph, mergeGraphs, mergeExtractions, field accessors (gNodes, gEdges, gAdjFwd, gAdjBack, gDirected)",
"isFileNode, isConceptNode helper predicates",
"Explicit export list, type signatures on all top-level definitions",
"No IO (pure domain)",
"cabal build succeeds",
"Domain.Graph re-exports from Domain.Graph.Core"
],
"deliverables": [
"src/Graphos/Domain/Graph/Core.hs",
"Updated src/Graphos/Domain/Graph.hs (re-export)",
"Updated Graphos.cabal"
],
"started_at": null,
"completed_at": null,
"completion_summary": null
}
Loading
Loading