Skip to content

Project Structure

go-crap-tracker follows Hexagonal Architecture (Ports & Adapters) — it was scaffolded with HexaGo. Business logic in internal/core depends only on port interfaces; adapters in internal/adapters implement them, so infrastructure stays swappable.

go-crap-tracker/
├── main.go                  # entry point → cmd.Execute()
├── cmd/                     # Cobra CLI commands
│   ├── root.go              #   root command, config init
│   ├── serve.go             #   control plane startup
│   ├── worker.go            #   worker startup
│   ├── publish.go           #   Hugo report generation
│   ├── repo.go              #   repo cache + per-repo config management
│   ├── version.go           #   version
│   └── helpers.go           #   shared DB/migration helpers
├── internal/
│   ├── core/
│   │   ├── domain/          # pure business entities & value objects
│   │   │   ├── scantasks/   #   ScanTask + status state machine
│   │   │   ├── repoanalyses/#   RepoAnalysis result entity
│   │   │   ├── repos/       #   canonical repo registry / ID helpers
│   │   │   ├── repocfg/     #   per-repo overrides
│   │   │   ├── repospec/    #   repository spec (url + ref)
│   │   │   ├── jobspecs/    #   NATS job spec
│   │   │   ├── repoinfo/    #   cloned repo info
│   │   │   ├── repometadata/#   GitHub metadata
│   │   │   ├── crapreport/  #   go-crap report parser
│   │   │   ├── crapsummary/ #   CRAP score summary
│   │   │   └── crapentry/   #   per-function entry
│   │   ├── ports/           # port interfaces
│   │   │   ├── inbound/     #   Scheduler, ScanTaskService, WorkerService, Reconciler
│   │   │   └── outbound/    #   NATSClient, ScanTaskStore, ResultStore, RepoLock, RepoConfigStore, EventRelay, ...
│   │   └── usecases/        # application logic
│   │       ├── schedule/        # schedule a scan task
│   │       ├── scantaskservice/ # task CRUD, seen/hidden, result lookup
│   │       ├── reconciler/      # stale/retry/reschedule loop
│   │       ├── scancoordinator/ # lock → clone → test → scan → save
│   │       └── publishreport/   # build the Hugo report
│   ├── adapters/
│   │   ├── primary/         # inbound (drives the app)
│   │   │   └── http/        #   HTTP server (Chi)
│   │   │       ├── http.go  #     wiring: health + UI + API
│   │   │       ├── health/  #     /livez, /readyz, /health
│   │   │       ├── api/     #     REST + SSE handlers
│   │   │       └── ui/      #     embedded SPA (static/)
│   │   └── secondary/       # outbound (driven by the app)
│   │       ├── nats/        #     JetStream client (streams, consumers, events, heartbeats)
│   │       ├── postgres/    #     task/result/repo/repo-config stores, repo locks
│   │       ├── repocache/   #     shared clone + coverage cache, lease refresh
│   │       ├── coveragegen/ #     go test -coverprofile executor
│   │       ├── runner/      #     go-crap runner (local)
│   │       ├── githubmeta/  #     GitHub metadata fetcher
│   │       ├── eventrelay/  #     SSE event hub
│   │       ├── markdown/    #     Hugo/PaperMod report publisher
│   │       └── workerregistry/#  worker heartbeat registry
│   ├── config/              # Viper config loader + defaults
│   └── workers/             # scan worker loop (queue, heartbeat, commands)
├── pkg/
│   ├── httpserver/          # reusable HTTP server wrapper (timeouts, graceful stop)
│   └── logger/              # structured logger
├── migrations/              # golang-migrate SQL (embedded)
├── infra/                   # Kubernetes manifests
├── pages/                   # Hugo (PaperMod) blog the publish command writes into
├── main.go
├── Makefile
├── Dockerfile
└── go.mod

Dependency rule

adapters  ──▶  ports  ◀──  core (domain + usecases)
   └── implement the ports; core never imports adapters

internal/core has no external dependencies beyond the standard library and the domain. Adapters import the ports they implement. This is what lets you swap Postgres for something else, or add a gRPC inbound adapter, without touching the domain.