Repository navigation
Architecture
This page is for someone changing the code. A user does not need it; the command reference is for them.
cmd/ driver adapter β cobra parses and delegates
β
internal/session/ composition root β paths, loaded state, persistence
β
internal/application/ use cases β the flow, one package per verb
β
internal/domain/ the model β the catalog aggregate, the operation enum
β
internal/adapters/ technology β storage, output, (shell, prompt)
The arrow points inward. A package in a ring may import the rings inside it, never the ones outside.
| Ring | May import | Must not import |
|---|---|---|
internal/domain |
its own subtree, the standard library | anything else |
internal/application |
internal/domain |
internal/adapters, internal/session, cmd
|
internal/adapters |
internal/domain, internal/application, the standard library, third-party code |
internal/session, cmd
|
internal/session |
internal/adapters, internal/application, internal/domain, the standard library |
cmd, internal/tui, internal/desktop
|
cmd |
everything | β |
internal/session is the composition root: it owns the configuration paths,
the loaded command and history state, and the save/commit/revert calls. cmd
is its only production importer; internal/testutil constructs one for tests.
The pre-refactor internal/app, internal/data, internal/helper and
internal/interfaces are gone (#215).
architecture_test.go parses the imports of every non-test file and applies one
rule per ring. It is not a convention in a document β a violation fails
make check:
internal/application/β¦ imports internal/adapters/β¦: the dependency arrow points the other way
It also reports which rings it examined, and fails a rule that is marked required but matched no package β a gate that checks nothing must not read green.
| Path | Holds |
|---|---|
internal/domain/catalog |
Command, Catalog: order, alias index, warnings, Resolve, Update
|
internal/domain/operation |
the operation enum β a file format, not a label |
internal/application/<verb>commands |
one use case: the flow, with the ports it declares |
internal/adapters/storage |
the TOML command map, the JSON history index, the snapshots |
internal/adapters/output |
the streams, the result format, the JSON encoding |
internal/shell |
the platform's shell invocation and quoting |
internal/testutil |
the fixture harness every test starts from |
pkg/ |
generic helpers: logging, filesystem, codecs |
-
Model. If the operation changes a rule the aggregate owns (what a name may be, which alias wins), put it in
internal/domain/catalogwith tests. It takes no I/O and prints nothing. -
Use case.
internal/application/<verb>commands/β a struct with aRun, and the interfaces it needs declared in the same package:type State interface { Commands() catalog.Catalog Commit(catalog.Catalog, operation.Operation, string) error } type Output interface{ Info(message string) } type Prompter interface{ Confirm(question string) (bool, error) }
The consumer owns the interface, so the port set can only describe what this use case does.
-
Adapter. Implement those interfaces. If it wraps the legacy core, wire it in
cmd/adapters.go; if it is real technology, it belongs ininternal/adapters/. -
Command.
cmd/<verb>.goparses flags and delegates β it stays around 20 lines. -
Tests. Fakes for the use case, an end-to-end run through the cobra tree, and a mutation proof that the test fails when the behaviour is removed.
-
Docs. A
### crower <verb>section in Command-Reference; the guard test fails without it.
| File | Format | Contents |
|---|---|---|
crower.yaml |
TOML | the command map; the name is historical |
historyhelper.json |
JSON | one entry per operation: version, file, timestamp, operation, command, note |
history/NNNNN.yaml |
TOML | the command map as it was after that operation |
crower.log |
text | a debug copy of every result and diagnostic |
The field names in these files are a contract with every existing
installation. internal/adapters/storage is the only package that knows them,
and internal/session/persist_test.go pins them.
-
cmdis not behind a layer. The release injects the version at build time with-X github.com/fiwon123/crower/cmd.Version; moving the package would breakMakefile,release.ymlandinternal/tools/archiveverifytogether. -
Ports are declared by the consumer, not in a shared
portspackage. Go connects them structurally, so a use case can only describe what it actually needs β and a test can pass a fake with three methods.
go test -run TestLayerDependencies ./ # the rings, and which were checked
go test ./... # behaviour
make check # gofmt, vet, tests, three platformsA page like this can go stale. The gate is the part that cannot: if a rule here
stops being true, make check fails.
Β© 2025 Felipe Inoue Β· crower is MIT licensed Β· third-party notices