detached 16d10fa

chore: bootstrap vev — hexagonal skeleton, deps, import guard

commit
16d10fa43c0cac8d074304ae7c211e54cf101f3b
author
brice · · 2026-07-02 09:03 UTC
parent
none (root commit)
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
15 files · +158 -0
A .gitignore +3 -0
@@ -0,0 +1,3 @@
1+/vev
2+*.test
3+.superpowers/
@@ -0,0 +1,3 @@
1+/vev
2+*.test
3+.superpowers/
A .mockery.yml +30 -0
@@ -0,0 +1,30 @@
1+all: false
2+dir: '{{.InterfaceDir}}'
3+filename: mocks_test.go
4+force-file-write: true
5+formatter: goimports
6+formatter-options:
7+ goimports:
8+ all-errors: false
9+ comments: true
10+ format-only: true
11+ fragment: false
12+ local-prefix: ""
13+ tab-indent: true
14+ tab-width: 8
15+generate: true
16+include-auto-generated: false
17+log-level: info
18+structname: '{{.Mock}}{{.InterfaceName}}'
19+pkgname: '{{.SrcPackageName}}'
20+recursive: false
21+require-template-schema-exists: true
22+template: testify
23+template-schema: '{{.Template}}.schema.json'
24+packages:
25+ github.com/bnema/vev/internal/ports:
26+ config:
27+ all: true
28+ dir: internal/ports/mocks
29+ filename: mocks.go
30+ pkgname: portsmocks
@@ -0,0 +1,30 @@
1+all: false
2+dir: '{{.InterfaceDir}}'
3+filename: mocks_test.go
4+force-file-write: true
5+formatter: goimports
6+formatter-options:
7+ goimports:
8+ all-errors: false
9+ comments: true
10+ format-only: true
11+ fragment: false
12+ local-prefix: ""
13+ tab-indent: true
14+ tab-width: 8
15+generate: true
16+include-auto-generated: false
17+log-level: info
18+structname: '{{.Mock}}{{.InterfaceName}}'
19+pkgname: '{{.SrcPackageName}}'
20+recursive: false
21+require-template-schema-exists: true
22+template: testify
23+template-schema: '{{.Template}}.schema.json'
24+packages:
25+ github.com/bnema/vev/internal/ports:
26+ config:
27+ all: true
28+ dir: internal/ports/mocks
29+ filename: mocks.go
30+ pkgname: portsmocks
A boundary_test.go +74 -0
@@ -0,0 +1,74 @@
1+package main_test
2+
3+import (
4+ "go/parser"
5+ "go/token"
6+ "io/fs"
7+ "path/filepath"
8+ "strings"
9+ "testing"
10+)
11+
12+// boundaryRule expresses a single import-boundary constraint: no .go file
13+// under root may import a package whose path has disallowedPrefix.
14+type boundaryRule struct {
15+ name string
16+ root string
17+ disallowedPrefix string
18+}
19+
20+func TestImportBoundaries(t *testing.T) {
21+ rules := []boundaryRule{
22+ {
23+ name: "pkg must not import internal",
24+ root: "pkg",
25+ disallowedPrefix: "github.com/bnema/vev/internal",
26+ },
27+ }
28+
29+ for _, rule := range rules {
30+ t.Run(rule.name, func(t *testing.T) {
31+ violations := findImportViolations(t, rule.root, rule.disallowedPrefix)
32+ if len(violations) > 0 {
33+ t.Errorf("found disallowed imports of %q under %q:\n%s", rule.disallowedPrefix, rule.root, strings.Join(violations, "\n"))
34+ }
35+ })
36+ }
37+}
38+
39+// findImportViolations walks root, parses each .go file's imports, and
40+// returns a description for every import whose path starts with
41+// disallowedPrefix.
42+func findImportViolations(t *testing.T, root, disallowedPrefix string) []string {
43+ t.Helper()
44+
45+ var violations []string
46+ fset := token.NewFileSet()
47+
48+ err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
49+ if err != nil {
50+ return err
51+ }
52+ if d.IsDir() || !strings.HasSuffix(path, ".go") {
53+ return nil
54+ }
55+
56+ f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
57+ if err != nil {
58+ return err
59+ }
60+
61+ for _, imp := range f.Imports {
62+ importPath := strings.Trim(imp.Path.Value, `"`)
63+ if strings.HasPrefix(importPath, disallowedPrefix) {
64+ violations = append(violations, path+": imports "+importPath)
65+ }
66+ }
67+ return nil
68+ })
69+ if err != nil {
70+ t.Fatalf("walking %s: %v", root, err)
71+ }
72+
73+ return violations
74+}
@@ -0,0 +1,74 @@
1+package main_test
2+
3+import (
4+ "go/parser"
5+ "go/token"
6+ "io/fs"
7+ "path/filepath"
8+ "strings"
9+ "testing"
10+)
11+
12+// boundaryRule expresses a single import-boundary constraint: no .go file
13+// under root may import a package whose path has disallowedPrefix.
14+type boundaryRule struct {
15+ name string
16+ root string
17+ disallowedPrefix string
18+}
19+
20+func TestImportBoundaries(t *testing.T) {
21+ rules := []boundaryRule{
22+ {
23+ name: "pkg must not import internal",
24+ root: "pkg",
25+ disallowedPrefix: "github.com/bnema/vev/internal",
26+ },
27+ }
28+
29+ for _, rule := range rules {
30+ t.Run(rule.name, func(t *testing.T) {
31+ violations := findImportViolations(t, rule.root, rule.disallowedPrefix)
32+ if len(violations) > 0 {
33+ t.Errorf("found disallowed imports of %q under %q:\n%s", rule.disallowedPrefix, rule.root, strings.Join(violations, "\n"))
34+ }
35+ })
36+ }
37+}
38+
39+// findImportViolations walks root, parses each .go file's imports, and
40+// returns a description for every import whose path starts with
41+// disallowedPrefix.
42+func findImportViolations(t *testing.T, root, disallowedPrefix string) []string {
43+ t.Helper()
44+
45+ var violations []string
46+ fset := token.NewFileSet()
47+
48+ err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
49+ if err != nil {
50+ return err
51+ }
52+ if d.IsDir() || !strings.HasSuffix(path, ".go") {
53+ return nil
54+ }
55+
56+ f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
57+ if err != nil {
58+ return err
59+ }
60+
61+ for _, imp := range f.Imports {
62+ importPath := strings.Trim(imp.Path.Value, `"`)
63+ if strings.HasPrefix(importPath, disallowedPrefix) {
64+ violations = append(violations, path+": imports "+importPath)
65+ }
66+ }
67+ return nil
68+ })
69+ if err != nil {
70+ t.Fatalf("walking %s: %v", root, err)
71+ }
72+
73+ return violations
74+}
A go.mod +8 -0
@@ -0,0 +1,8 @@
1+module github.com/bnema/vev
2+
3+go 1.26
4+
5+require (
6+ golang.org/x/sys v0.46.0 // indirect
7+ golang.org/x/term v0.44.0 // indirect
8+)
@@ -0,0 +1,8 @@
1+module github.com/bnema/vev
2+
3+go 1.26
4+
5+require (
6+ golang.org/x/sys v0.46.0 // indirect
7+ golang.org/x/term v0.44.0 // indirect
8+)
A go.sum +4 -0 generated file: collapsed
A internal/adapters/ipc/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package ipc adapts vev's ports to inter-process communication implementations.
2+package ipc
@@ -0,0 +1,2 @@
1+// Package ipc adapts vev's ports to inter-process communication implementations.
2+package ipc
A internal/adapters/pty/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package pty adapts vev's ports to pseudo-terminal implementations.
2+package pty
@@ -0,0 +1,2 @@
1+// Package pty adapts vev's ports to pseudo-terminal implementations.
2+package pty
A internal/adapters/term/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package term adapts vev's ports to terminal I/O implementations.
2+package term
@@ -0,0 +1,2 @@
1+// Package term adapts vev's ports to terminal I/O implementations.
2+package term
A internal/app/run.go +8 -0
@@ -0,0 +1,8 @@
1+// Package app wires vev's runtime together via dependency injection.
2+package app
3+
4+// Run is the entry point invoked by main. It parses no flags yet and
5+// performs no work; later milestones will wire the application here.
6+func Run(args []string) error {
7+ return nil
8+}
@@ -0,0 +1,8 @@
1+// Package app wires vev's runtime together via dependency injection.
2+package app
3+
4+// Run is the entry point invoked by main. It parses no flags yet and
5+// performs no work; later milestones will wire the application here.
6+func Run(args []string) error {
7+ return nil
8+}
A internal/domain/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package domain contains vev's pure domain types.
2+package domain
@@ -0,0 +1,2 @@
1+// Package domain contains vev's pure domain types.
2+package domain
A internal/ports/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package ports defines the interfaces that decouple vev's core from its adapters.
2+package ports
@@ -0,0 +1,2 @@
1+// Package ports defines the interfaces that decouple vev's core from its adapters.
2+package ports
A internal/usecase/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package usecase implements vev's application use cases.
2+package usecase
@@ -0,0 +1,2 @@
1+// Package usecase implements vev's application use cases.
2+package usecase
A main.go +15 -0
@@ -0,0 +1,15 @@
1+package main
2+
3+import (
4+ "fmt"
5+ "os"
6+
7+ "github.com/bnema/vev/internal/app"
8+)
9+
10+func main() {
11+ if err := app.Run(os.Args[1:]); err != nil {
12+ fmt.Fprintln(os.Stderr, "vev:", err)
13+ os.Exit(1)
14+ }
15+}
@@ -0,0 +1,15 @@
1+package main
2+
3+import (
4+ "fmt"
5+ "os"
6+
7+ "github.com/bnema/vev/internal/app"
8+)
9+
10+func main() {
11+ if err := app.Run(os.Args[1:]); err != nil {
12+ fmt.Fprintln(os.Stderr, "vev:", err)
13+ os.Exit(1)
14+ }
15+}
A pkg/renderer/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package renderer draws terminal state to the screen.
2+package renderer
@@ -0,0 +1,2 @@
1+// Package renderer draws terminal state to the screen.
2+package renderer
A pkg/vt/doc.go +2 -0
@@ -0,0 +1,2 @@
1+// Package vt implements a terminal emulator core.
2+package vt
@@ -0,0 +1,2 @@
1+// Package vt implements a terminal emulator core.
2+package vt

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close