Layering
gh-infra is organized around a small set of package layers. This page documents the intended dependency direction and the reasons behind it.
Overview
Section titled “Overview”At a high level, gh-infra is split into:
- Domain / use-case packages
internal/repositoryinternal/filesetinternal/importer
- Presentation
internal/ui
- Orchestration
internal/infracmd
- Infrastructure / utility
internal/ghinternal/manifestinternal/yamleditinternal/parallelinternal/logger
Dependency Direction
Section titled “Dependency Direction”The intended direction is:
flowchart TD
cmd[cmd] --> infra[internal/infra]
infra --> repository[internal/repository]
infra --> fileset[internal/fileset]
infra --> importer[internal/importer]
infra --> ui[internal/ui]
repository --> gh[internal/gh]
repository --> manifest[internal/manifest]
repository --> parallel[internal/parallel]
fileset --> gh
fileset --> manifest
fileset --> parallel
importer --> gh
importer --> manifest
importer --> repository
importer --> fileset
importer --> yamledit[internal/yamledit]
ui --> logger[internal/logger]
Or in words:
cmdandinfrawire the application togetherrepository,fileset, andimportercontain feature logicuihandles terminal rendering and interaction- lower-level helpers such as
gh,manifest, andyamleditsupport the feature packages
Key Rule
Section titled “Key Rule”Presentation should not leak into domain packages.
In particular:
repositoryshould not depend onuifilesetshould not depend onuiimportershould not depend onui
Instead, domain packages define the smallest interfaces they need, and infra passes ui implementations into them.
This keeps the dependency direction one-way:
infraknows both domain packages and UI- domain packages do not need to know how the terminal is rendered
Why This Matters
Section titled “Why This Matters”This structure avoids two common problems:
1. Import cycles
Section titled “1. Import cycles”If ui depends on a domain package while that domain package also depends on ui, Go package cycles appear quickly.
The import --into diff viewer is a good example:
uineeds a view model to renderimporterneeds to plan write-back modes
If UI-specific state is pushed down into importer, or if importer imports ui directly, cycles become much more likely.
2. Mixed responsibilities
Section titled “2. Mixed responsibilities”repository and fileset are sibling packages: both represent resource-specific logic.
They should focus on:
- fetching current state
- computing diffs
- applying changes
They should not own:
- terminal widgets
- confirmation flows
- viewer-specific interaction state
Those belong in ui or in infra, which coordinates the full command flow.
Practical Guidance
Section titled “Practical Guidance”When adding a new feature, prefer these boundaries:
- Put GitHub/resource logic in
repository,fileset, orimporter - Put terminal rendering and key handling in
ui - Put command/session coordination in
infra - Put shared low-level helpers in focused utility packages such as
yamleditorparallel
If a type seems to be needed by both ui and a domain package, first ask:
- Is this really domain state?
- Or is it session / interaction state that should stay in
infraorui?
In many cases, the right fix is not a new shared package, but moving the state back to the correct layer.