syntaxai/tdd.md · commit eab00a3

Blog: pointing SAMA v2 at the Go project wagoodman/dive

Companion to yesterday's WP plugin audit. Third datapoint after the
TS dogfood (this site) and the PHP plugin. Picked dive — 53k-star
Docker image explorer, 8,498 LOC across 92 source files, real product
not a library, mature single-binary CLI.

The result is much more interesting than 0/7. Go's standard layout
(cmd/ + internal/ + package-per-concern) plus the language's internal/
semantics already enforce:
- §1.2 Law (compiler refuses upward imports)
- §4.5 Atomic (largest file 496 LOC, well under the 700 cap)
- Architecture-by-package (no god-class, organized by concern)
- Boundary-localization (json/yaml/http/exec.Command mostly under
  dive/image/docker, dive/image/podman)

5 of 7 checks pass naturally. The two that don't:
- #1 Sorted — Go organizes by package directory, not filename prefix.
  Incompatible with v2.0's lex-sort-the-prefixes rule. This is a real
  spec-evolution finding: v2 needs a directory-based dialect for Go.
- #3 Modeled-tests — 18 test files for 92 source files, gaps in the
  image adapter packages.

The 30%-remaining sketch is small: add profile under the new dialect,
write ~30 sibling tests for image-adapter files, split filetree/ into
pure + loader. Days of work, not months. Compare to the WP plugin
rebuild which needed splitting a 1,554-line god-class into eleven
files and writing 20+ tests from scratch.

Three datapoints now on the §5 axes — tdd.md (TS, 7/7, fit 80%),
dive (Go, ~5/7, fit ~80%), WP plugin (PHP, 0/7, fit ~47%). Pattern
is suggestive but n=3 with two hand-estimates is not yet a
worth-following claim per the spec's own §6 evolution policy.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
author
syntaxai <[email protected]>
date
2026-05-23 15:04:50 +01:00
parent
1d2d5f4
commit
eab00a378eb2bb8866ddcd30e2c097435415ffce

2 files changed · +209 −0

added content/blog/sama-v2-go-project-dive.md +203 −0
@@ -0,0 +1,203 @@
1+# Pointing SAMA v2 at `dive`: Go's conventions cover more than you'd think
2+
3+The [WordPress plugin audit](/blog/sama-v2-wordpress-plugin-audit) earlier today scored 0 of 7 §4 checks for a real-world plugin in the wild. That post argued — and I still believe — that the score isn't a failure; it's the expected baseline for code written under WordPress idioms with no external discipline. WP itself actively pushes devs toward hook-and-filter god-classes.
4+
5+But "WordPress is messy" isn't an interesting finding on its own. The harder question is what v2 sees when pointed at a language that has stronger architectural defaults built in. So: same exercise, same methodology, but against **`wagoodman/dive`** — a 53k-star Go project that explores Docker image layers. 8,498 lines of Go across 92 source files, downloaded straight from `git clone`, walked carefully.
6+
7+The result is much more interesting than 0/7.
8+
9+## What's in the box
10+
11+```
12+dive/
13+├── cmd/dive/ # CLI entry tree
14+│ ├── main.go # 12 lines: calls cli.Run()
15+│ └── cli/
16+│ ├── cli.go # 130 lines: root command setup
17+│ └── internal/
18+│ ├── options/ # 10 files, ~150 LOC each — YAML config types
19+│ ├── command/ # cobra command files (root, build, ci, export, adapter/)
20+│ └── ui/v1/ # the TUI: app/, view/, viewmodel/, layout/, key/
21+├── dive/ # Domain tree
22+│ ├── filetree/ # 14 files: pure tree-diff logic. file_tree.go (390 LOC),
23+│ │ # file_node.go (354 LOC). Has 4 test files.
24+│ └── image/ # image archive parsing + Docker/Podman resolvers
25+│ ├── docker/ # 13 files: archive parsing, daemon API, CLI shelling
26+│ └── podman/ # 4 files: podman daemon + CLI
27+├── internal/ # Shared helpers
28+│ ├── utils/ ── 2 files
29+│ ├── log/ ── 1 file
30+│ └── bus/ ── 2 files + event/payload/
31+└── (Dockerfile, Makefile, go.mod, etc.)
32+```
33+
34+Some numbers worth flagging next to yesterday's WordPress plugin:
35+
36+| metric | `dive` (Go) | WP plugin (PHP) |
37+|---|---|---|
38+| Source files | 92 | 17 (non-vendor) |
39+| Total LOC | 8,498 | 6,445 |
40+| Largest single file | 496 LOC | **1,554 LOC** |
41+| Files over 700-LOC cap | **0** | 3 |
42+| Test files | 18 | 0 |
43+| Test LOC | 3,017 | 0 |
44+| Top-level layers as directories | `cmd/`, `internal/`, `dive/` | — none — |
45+
46+The two codebases are roughly the same size. They look completely different.
47+
48+## What `dive` already gets for free
49+
50+Go's standard project layout enforces several things that SAMA v2 has to write down as rules:
51+
52+- **`cmd/dive/main.go` is unambiguously the entry point.** Nothing imports back into it. Whatever lives in `cmd/`'s tree depends on `dive/` and `internal/`, not the other way around. That's the §1.2 Law direction enforced by Go's import resolver: `internal/` packages can only be imported by paths under their parent. The Law check has nothing left to do because the language already won't compile the violation.
53+- **Package-per-concern is a hard convention in Go.** `dive/filetree/` is the tree-diff package. `dive/image/docker/` is the Docker resolver. `dive/image/podman/` is Podman. There's no `Webdados_FB`-style god-class because the Go community would reject the PR at code review. Architecture as a property is half-enforced by the language ecosystem.
54+- **All files are under the 700-LOC cap.** The largest is `cmd/dive/cli/internal/ui/v1/view/filetree.go` at 496 lines; the next two are 472 and 390. Atomic check **passes by construction**.
55+- **Boundary parsing is mostly localized.** `json.Unmarshal`, `yaml.Unmarshal`, `os.Open`, `http.Client`, `exec.Command("docker", ...)` — almost every call appears under `dive/image/docker/` or `dive/image/podman/`. There's exactly one smell: `cmd/dive/cli/internal/options/ci.go` parses the user's `.dive-ci.yaml` config inside the `cmd/` tree.
56+- **Tests exist.** 18 test files, ~20% file ratio. Coverage is patchy (more on this below), but the WP plugin had zero. Modeled-tests goes from "vacuous fail" to "partial pass with named gaps."
57+
58+That covers maybe 60% of what v2 asks for, without anyone ever having looked at the spec.
59+
60+## What `dive` would still fail
61+
62+Walking the seven §4 checks honestly:
63+
64+### #1 Sorted — would fail
65+
66+> *Every file carries a profile-recognised prefix; lexicographic prefix order equals layer order.*
67+
68+This is the check Go projects cannot pass without changing language idioms. Go organizes by **directory**, not by **filename prefix**. Filenames inside `dive/filetree/` are `comparer.go`, `diff.go`, `efficiency.go`, `file_info.go`, `file_node.go`, `file_tree.go` — they're descriptive, not layer-marking. Lex-sorting them in alphabetical order has no relationship to architectural layer.
69+
70+This isn't `dive` doing something wrong. It's the SAMA v2 spec being written with a language model (TypeScript modules, PHP files) where prefix-sortable filenames are idiomatic. Go's idiom is the opposite. The spec needs a directory-based dialect for Go — *"the package directory's lex position equals the layer's order"* — to be honestly applicable here. I'll come back to this in the conclusion.
71+
72+### #2 Architecture — would partly pass
73+
74+> *Every file maps to exactly one canonical layer.*
75+
76+Without a written `sama.profile.toml` mapping packages to layers, every file is technically "unprefixed." But the **natural** mapping is obvious enough to write down right now:
77+
78+```toml
79+sama_version = "2.0"
80+profile = "dive"
81+layout = "directory" # ← hypothetical extension; see conclusion
82+
83+[layers.0]
84+packages = ["internal/utils", "internal/log", "internal/bus", "internal/bus/event/payload"]
85+
86+[layers.1]
87+sublayers = [
88+ { name = "core", packages = ["dive/filetree", "dive/image"] },
89+ { name = "viewmodel", packages = ["cmd/dive/cli/internal/ui/v1/viewmodel"] },
90+ { name = "view", packages = ["cmd/dive/cli/internal/ui/v1/view", "cmd/dive/cli/internal/ui/v1/layout", "cmd/dive/cli/internal/ui/v1/format", "cmd/dive/cli/internal/ui/v1/key"] },
91+]
92+
93+[layers.2]
94+sublayers = [
95+ { name = "resolver", packages = ["dive/image/docker", "dive/image/podman"] },
96+ { name = "config", packages = ["cmd/dive/cli/internal/options"] },
97+]
98+
99+[layers.3]
100+packages = ["cmd/dive", "cmd/dive/cli", "cmd/dive/cli/internal/command", "cmd/dive/cli/internal/command/ci", "cmd/dive/cli/internal/command/export", "cmd/dive/cli/internal/command/adapter", "cmd/dive/cli/internal/ui", "cmd/dive/cli/internal/ui/v1", "cmd/dive/cli/internal/ui/v1/app"]
101+```
102+
103+That maps every package without ambiguity. Under the directory-based dialect, this passes.
104+
105+### #3 Modeled (tests) — would fail
106+
107+18 test files for 92 non-test source files. The packages with tests:
108+
109+- `dive/filetree/` — 4 tests (efficiency, file_node, file_tree, node_data) ✓
110+- `cmd/dive/cli/internal/ui/v1/viewmodel/` — has tests ✓
111+- `cmd/dive/cli/internal/command/ci/evaluator_test.go` ✓
112+- `cmd/dive/cli/cli_test.go` + `cli_load_test.go` ✓ (integration-level)
113+
114+The packages without sibling tests include nearly all of `dive/image/`, `dive/image/docker/`, `dive/image/podman/` (the Layer 2 adapters), most of the UI view layer, and the entire `cmd/dive/cli/internal/command/` tree. That's ~30 source files in Layer 1 and Layer 2 territory that lack sibling tests. Modeled-tests would fail.
115+
116+### #4 Modeled (boundary) — would mostly pass
117+
118+Boundary parsing call sites:
119+
120+- `json.Unmarshal` / `json.NewDecoder` → 4 files in `dive/image/docker/`, all Layer 2. ✓
121+- `yaml.Unmarshal` → `cmd/dive/cli/internal/options/ci.go` (Layer 2 config sublayer in the proposed profile) ✓
122+- `os.Open` / `os.ReadFile` → `dive/filetree/file_info.go` (filesystem inspection, Layer 2 territory if you classify it that way) and `dive/image/docker/image_archive.go` (Layer 2) ✓
123+- `exec.Command("docker", ...)` / `exec.Command("podman", ...)` → `dive/image/docker/cli.go` + `dive/image/podman/cli.go`, both Layer 2 ✓
124+- `http.Client` → in the Docker daemon resolvers, Layer 2 ✓
125+
126+One borderline case: `dive/filetree/file_info.go` calls `os.Lstat` and traverses the filesystem. Under v2's strict reading, anything filesystem-touching is Layer 2 (Adapter). But `filetree` is otherwise pure tree-diff math. Either split it (`filetree/` → Layer 1 + a new `dive/filetree_adapter/` → Layer 2), or accept that this one file's `Lstat` calls are tightly scoped. The check would still pass under the proposed profile because `file_info.go` is in the L1 `filetree` package — but it's a soft tension worth naming.
127+
128+Score: passes with one named tension.
129+
130+### #5 Atomic — passes outright
131+
132+All 92 source files under 700 LOC. No barrel files. Done.
133+
134+### #6 Law (§1.2) — would pass
135+
136+Go's `internal/` semantics + the natural cmd→domain→helpers direction mean upward imports are mostly impossible to write without the compiler refusing. The only same-layer-but-reversed-sublayer concern: does `dive/image/docker` import `dive/image/podman` or vice versa? A quick grep — neither imports the other. They're siblings under L2 resolver, both downstream of `dive/image/image.go` (which is L1 core).
137+
138+### #7 Consistency (§3) — would pass
139+
140+Derives from Law. No file's declared layer is contradicted by what it imports.
141+
142+**Estimated tally: 5 of 7 pass under the directory-based dialect, with 2 named failures (Sorted, Modeled-tests).** That's a real result, not "0/7 because no one tried."
143+
144+## The §5 metrics — estimated for `dive`
145+
146+| metric | `dive` (Go, estimated) | WP plugin (PHP, estimated) | tdd.md (TS, measured) |
147+|---|---|---|---|
148+| §4 checks passing | ~5 / 7 | 0 / 7 | 7 / 7 |
149+| graphDepth | ~5 (cmd → command → ui → dive → filetree → internal/utils) | ~3 | 7 |
150+| boundaryRatio | ~85% (one borderline case in `options/ci.go`) | <10% | 100% |
151+| workingSetFit (50–500 LOC) | ~80% | ~47% | 80% |
152+| violationCounts (sum) | ~30 (mostly Modeled-tests gaps) | 17+ | 0 |
153+
154+The `workingSetFit` is essentially **identical** between `dive` and this site (80%). Two unrelated projects, two different languages, two different scopes, written by different teams under different conventions — landing at the same fit ratio is a useful data point: 80% might just be what "reasonably engineered" looks like on this axis.
155+
156+## What `dive` would look like at 7/7 — the last 30%
157+
158+Far less work than the WordPress refactor sketch from earlier. Three concrete changes get from ~5/7 to 7/7:
159+
160+**1. Add `sama.profile.toml` (under a directory-based dialect).** The proposed profile in §2 above maps every package to a canonical layer. The dialect requires a spec extension (v2.1 maybe) — see conclusion. No code changes, just declaration.
161+
162+**2. Add sibling tests for the ~30 untested Layer-1/2 files.** This is the only real work. The candidates that most need them:
163+ - `dive/image/docker/image_archive.go` (378 LOC, archive parsing — needs fixture-based tests)
164+ - `dive/image/docker/engine_resolver.go` (197 LOC — needs a fake daemon)
165+ - `dive/image/podman/build.go` (build coordination — needs a fake podman client)
166+ - The `cmd/dive/cli/internal/command/ci/rules.go` rule-evaluation logic (196 LOC)
167+ - The TUI `view/` files (496 + 377 + others) — testing renderers is annoying but feasible with text-fixture comparison
168+
169+A week of focused work would close this gap. Not a refactor — just tests being written that aren't there yet.
170+
171+**3. Resolve the `file_info.go` filesystem-vs-pure tension.** Split `dive/filetree/` so the pure tree math sits separately from the file-walking adapter. Concretely: move `file_info.go`'s `os.Lstat` calls into a new `dive/filetree_loader/` package, keep the tree algebra in `dive/filetree/`. ~half a day's work.
172+
173+The codebase is already so close to v2 that the lift is small. **Compare to the WordPress plugin where the same goal requires splitting a 1,554-line god-class into eleven files, writing 20+ test files from scratch, and introducing a typed Settings replacement for an untyped option array.** The "30% remaining" for `dive` is not a comparable amount of work.
174+
175+## Where this leaves the spec
176+
177+The audit surfaces one real finding about SAMA v2 itself: **the §4.1 Sorted check is written with TypeScript/PHP filename conventions in mind, and doesn't translate cleanly to Go**.
178+
179+Go's idiom is to organize by package directory, not by filename prefix. A `sama.profile.toml` that says *"these prefixes lex-sort in layer order"* (the v2.0 format) has nothing meaningful to assert about `dive`. A *directory-based* dialect — *"these package directories lex-sort in layer order"* — does.
180+
181+That's one of the §6 evolution-policy moves the spec was designed to accommodate: a new profile dialect, falsifiable against the §5 metrics, admitted only if measurements hold across multiple Go repos. Today's `dive` audit is the first datapoint for that hypothesis. To validate the directory-based dialect properly, the same exercise would need to be done against 3-5 more Go projects. That's a future post.
182+
183+## Three real-world datapoints, on the same axes
184+
185+After today, the §5 baseline graph has three points on it:
186+
187+| project | language | §4 score | workingSetFit | boundaryRatio | graphDepth |
188+|---|---|---|---|---|---|
189+| **tdd.md** (this site) | TypeScript | 7/7 (measured) | 80% (measured) | 100% (measured) | 7 (measured) |
190+| **wagoodman/dive** | Go | ~5/7 (estimated) | ~80% (estimated) | ~85% (estimated) | ~5 (estimated) |
191+| **Open Graph plugin** | PHP / WordPress | 0/7 (estimated) | ~47% (estimated) | <10% (estimated) | ~3 (estimated) |
192+
193+That's still n=3 and two of them are hand-estimated, so nobody should be drawing conclusions about empirical v2 worth yet. But the pattern is suggestive: real-world Go code, written under no v2 discipline, scores closer to the v2-disciplined dogfood than to the WP-idiom code. The differential is exactly the kind of thing §5 was designed to surface. Whether that differential causes better outcomes for agents working in the code — fewer harness loops, faster onboarding, less drift — is the next experiment, not this post's claim.
194+
195+---
196+
197+**See for yourself:**
198+
199+- The project: <https://github.com/wagoodman/dive>
200+- Yesterday's WP audit (companion piece): [Pointing SAMA v2 at a real WordPress plugin in the wild](/blog/sama-v2-wordpress-plugin-audit)
201+- The hypothetical WP rebuild: [The Open Graph plugin, rebuilt under SAMA v2](/blog/sama-v2-wordpress-plugin-rebuilt)
202+- The §5 metric definitions: [/sama/v2#5-operational--core-metrics-definitions](/sama/v2#5-operational--core-metrics-definitions)
203+- The spec being audited against: [/sama/v2](/sama/v2)
modified src/a31_blog.ts +6 −0
@@ -12,6 +12,12 @@ export interface BlogEntry {
1212 }
1313
1414 export const ALL_POSTS: BlogEntry[] = [
15+ {
16+ slug: "sama-v2-go-project-dive",
17+ title: "Pointing SAMA v2 at `dive`: Go's conventions cover more than you'd think",
18+ description: "After auditing a WordPress plugin (0/7) and sketching its v2-rebuild, the natural follow-up was a Go project for n=3. Picked wagoodman/dive — 53k-star Docker image explorer, 8,498 LOC across 92 source files, mature codebase. Cloned, walked the source, scored honestly. The result is much more interesting than 0/7: Go's standard layout (cmd/, internal/, package-per-concern) plus the language's internal/ semantics already enforce the §1.2 Law, the Atomic 700-LOC cap (largest file 496), and Architecture-by-package — five of the seven §4 checks pass naturally. The two that don't: #1 Sorted (Go organizes by package directory, not filename prefix — incompatible with v2.0's lex-sort-the-prefixes rule) and #3 Modeled-tests (18 test files for 92 source files, gaps in the image adapters). The audit surfaces a real spec-evolution finding: v2 needs a directory-based dialect for Go, where 'package directory lex-position = layer order' replaces 'filename prefix lex-position = layer order'. The 30% remaining work to push dive to 7/7 is far smaller than the WP plugin's: add a profile under the new dialect, write ~30 sibling tests for image-adapter files, split filetree/ into pure + loader. Three datapoints now on the §5 axes: tdd.md (TS, 7/7, fit 80%), dive (Go, ~5/7, fit ~80%), WP plugin (PHP, 0/7, fit ~47%). The pattern is suggestive — real Go scores closer to v2-disciplined code than to WP-idiom code — but n=3 with two hand-estimates is not yet a worth-following claim.",
19+ date: "2026-05-23",
20+ },
1521 {
1622 slug: "sama-v2-wordpress-plugin-rebuilt",
1723 title: "The Open Graph plugin, rebuilt under SAMA v2 — a thought experiment",