Skip to content

Commit fa7da62

Browse files
greynewellclaude
andcommitted
chore: vertical slice architecture with Supermodel enforcement
Architecture (docs/architecture.md): - Strict vertical slices — slices must not import each other - Shared kernel: internal/api, config, cache, ui, build - Slices: analyze, deadcode, blastradius, graph, auth - cmd/ is pure wiring (cobra → slice handler), may import any internal/ Scaffold: - internal/{api,config,cache,ui}/doc.go — shared kernel stubs - internal/{analyze,deadcode,blastradius,graph,auth}/doc.go — slice stubs Each doc comment states the rule and lists permitted kernel imports. Enforcement (scripts/check-architecture/main.go): - Zips repo with git archive, uploads to Supermodel API (/v1/supermodel) - Parses dependency graph (nodes + IMPORTS/WILDCARD_IMPORTS relationships) - Resolves both file-path and import-path node formats to "internal/X" - Fails with a clear violation report if any slice imports another slice CI (.github/workflows/architecture.yml): - Triggers on PRs touching internal/, cmd/, or main.go - Skips gracefully when SUPERMODEL_API_KEY is absent (fork PRs) - `make arch-check` runs it locally Secret required: SUPERMODEL_API_KEY — API key from api.supermodeltools.com Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 16b68fc commit fa7da62

File tree

13 files changed

+488
-0
lines changed

13 files changed

+488
-0
lines changed

.github/workflows/architecture.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Architecture
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'internal/**'
7+
- 'cmd/**'
8+
- 'main.go'
9+
- 'scripts/check-architecture/**'
10+
push:
11+
branches: [main]
12+
paths:
13+
- 'internal/**'
14+
- 'cmd/**'
15+
- 'main.go'
16+
17+
jobs:
18+
vertical-slice:
19+
name: vertical slice check
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
26+
- uses: actions/setup-go@v5
27+
with:
28+
go-version-file: go.mod
29+
cache: true
30+
31+
- name: Check vertical slice architecture
32+
env:
33+
SUPERMODEL_API_KEY: ${{ secrets.SUPERMODEL_API_KEY }}
34+
run: |
35+
if [ -z "$SUPERMODEL_API_KEY" ]; then
36+
echo "::warning::SUPERMODEL_API_KEY not set — skipping architecture check (fork PR?)"
37+
exit 0
38+
fi
39+
go run ./scripts/check-architecture

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ tidy:
5050
clean:
5151
rm -rf $(OUTDIR) coverage.out
5252

53+
## arch-check: validate vertical slice architecture via Supermodel API
54+
arch-check:
55+
@[ -n "$$SUPERMODEL_API_KEY" ] || (echo "error: SUPERMODEL_API_KEY is not set" && exit 1)
56+
go run ./scripts/check-architecture
57+
5358
## release-dry: dry-run GoReleaser (builds all platform binaries locally)
5459
release-dry:
5560
goreleaser release --snapshot --clean

docs/architecture.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# CLI Architecture — Vertical Slice
2+
3+
## Guiding principle
4+
5+
Each user-facing feature is a **vertical slice**: a self-contained package that
6+
owns its own command wiring, business logic, API calls, and output formatting.
7+
Slices are allowed to share infrastructure (the kernel) but are **forbidden from
8+
depending on each other**.
9+
10+
This keeps features independently changeable. Adding `blast-radius` cannot break
11+
`dead-code`. Refactoring `auth` cannot affect `analyze`.
12+
13+
## Package map
14+
15+
```
16+
main.go entry — calls cmd.Execute(), nothing else
17+
18+
cmd/ wiring layer — cobra commands that delegate to slice handlers
19+
root.go root command, global flags
20+
version.go version subcommand (trivial, no handler)
21+
analyze.go ─────────────────────────────────────────────┐
22+
deadcode.go ──────────────────────────────────────────┐ │
23+
blastradius.go ────────────────────────────────────┐ │ │
24+
graph.go ──────────────────────────────────┐ │ │ │
25+
auth.go ──────────────────────────────┐ │ │ │ │
26+
│ │ │ │ │
27+
internal/ │ │ │ │ │
28+
┌──────────────── SHARED KERNEL ───────┐ │ │ │ │ │
29+
│ api/ HTTP client primitives │ │ │ │ │ │
30+
│ config/ ~/.supermodel/config.yaml │◄──┘ │ │ │ │
31+
│ cache/ local graph cache │◄──────┘ │ │ │
32+
│ ui/ output, tables, spinners │◄──────────┘ │ │
33+
│ build/ version/commit/date vars │◄──────────────┘ │
34+
└──────────────────────────────────────┘ │
35+
36+
┌──────────────── VERTICAL SLICES ─────────────────────┐ │
37+
│ analyze/ upload & full analysis pipeline │◄─┘
38+
│ deadcode/ dead code detection │
39+
│ blastradius/ downstream impact analysis │
40+
│ graph/ graph display and export │
41+
│ auth/ login / logout / token storage │
42+
└──────────────────────────────────────────────────────┘
43+
```
44+
45+
## Rules
46+
47+
| From → To | Allowed? |
48+
|-------------------|----------|
49+
| `main.go``cmd/` ||
50+
| `cmd/` → any `internal/` | ✅ (wiring) |
51+
| `internal/<slice>``internal/kernel` ||
52+
| `internal/<slice>``internal/<slice>`|**FORBIDDEN** |
53+
| `internal/kernel``internal/<slice>` |**FORBIDDEN** |
54+
55+
**Shared kernel packages** (`internal/api`, `internal/build`, `internal/cache`,
56+
`internal/config`, `internal/ui`) must contain zero business logic. They are
57+
pure infrastructure — HTTP primitives, config loading, formatting utilities.
58+
59+
Any package under `internal/` that is NOT in the kernel list is treated as a
60+
slice and subject to the cross-slice import ban.
61+
62+
## Adding a new feature
63+
64+
1. Create `internal/<feature>/` with `command.go`, `handler.go`, `types.go`.
65+
2. Register the cobra command in `cmd/<feature>.go` by calling into the slice.
66+
3. Do not import any other slice from the new package.
67+
4. The architecture check in CI will reject the PR if the rule is violated.
68+
69+
## Adding a new kernel package
70+
71+
1. Add the package under `internal/<name>/`.
72+
2. Add `"internal/<name>": true` to `sharedKernel` in
73+
`scripts/check-architecture/main.go`.
74+
3. Keep it free of business logic.
75+
76+
## Enforcement
77+
78+
The `.github/workflows/architecture.yml` workflow runs on every PR that touches
79+
`internal/`, `cmd/`, or `main.go`. It zips the repository, sends it to the
80+
[Supermodel API](https://api.supermodeltools.com), parses the dependency graph,
81+
and fails the build if any cross-slice `IMPORTS` relationship is found.
82+
83+
Run locally:
84+
85+
```sh
86+
SUPERMODEL_API_KEY=<key> go run ./scripts/check-architecture
87+
```

internal/analyze/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Package analyze implements the `supermodel analyze` command.
2+
// It uploads a repository ZIP to the Supermodel API, runs the full analysis
3+
// pipeline, and caches the resulting graph locally.
4+
//
5+
// This is a vertical slice. It must not import any other slice package.
6+
// It may import from the shared kernel: internal/api, internal/cache,
7+
// internal/config, internal/ui, internal/build.
8+
package analyze

internal/api/doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Package api provides the HTTP client and base request primitives for the
2+
// Supermodel API. It handles authentication headers, idempotency keys, error
3+
// parsing, and response decoding.
4+
//
5+
// This is a shared kernel package. It must contain no business logic.
6+
// Slice packages under internal/ may import it freely.
7+
package api

internal/auth/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Package auth implements the `supermodel login` and `supermodel logout` commands.
2+
// It handles GitHub OAuth, API key retrieval, and secure token storage in
3+
// the user's config file.
4+
//
5+
// This is a vertical slice. It must not import any other slice package.
6+
// It may import from the shared kernel: internal/api, internal/cache,
7+
// internal/config, internal/ui, internal/build.
8+
package auth

internal/blastradius/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Package blastradius implements the `supermodel blast-radius` command.
2+
// Given a file or function, it queries the Supermodel API call graph to
3+
// show which other files and functions would be affected by a change.
4+
//
5+
// This is a vertical slice. It must not import any other slice package.
6+
// It may import from the shared kernel: internal/api, internal/cache,
7+
// internal/config, internal/ui, internal/build.
8+
package blastradius

internal/cache/doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Package cache manages the local graph cache stored under ~/.supermodel/cache/.
2+
// Graphs are keyed by the SHA-256 hash of the uploaded repository ZIP, matching
3+
// the content-addressed scheme used by the Supermodel API.
4+
//
5+
// This is a shared kernel package. It must contain no business logic.
6+
// Slice packages under internal/ may import it freely.
7+
package cache

internal/config/doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Package config manages the user's Supermodel configuration stored at
2+
// ~/.supermodel/config.yaml. It handles loading, saving, and validating
3+
// config values such as the API key, default output format, and API base URL.
4+
//
5+
// This is a shared kernel package. It must contain no business logic.
6+
// Slice packages under internal/ may import it freely.
7+
package config

internal/deadcode/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Package deadcode implements the `supermodel dead-code` command.
2+
// It calls the Supermodel API to identify functions and files with no callers,
3+
// then renders the results as a table or JSON.
4+
//
5+
// This is a vertical slice. It must not import any other slice package.
6+
// It may import from the shared kernel: internal/api, internal/cache,
7+
// internal/config, internal/ui, internal/build.
8+
package deadcode

0 commit comments

Comments
 (0)