X22a-4-arch-traits: P32-fzf … P41-frp#
Scorecard rows#
| Project | T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | Composite |
|---|---|---|---|---|---|---|---|---|---|
| P32-fzf | 0 | 2 | 3 | 1 | 2 | 3 | 1 | 1 | 13 |
| P33-gh | 2 | 2 | 3 | 2 | 2 | 1 | 2 | 3 | 17 |
| P34-gitea | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 6 |
| P35-gogs | 2 | 0 | 1 | 0 | 1 | 2 | 1 | 2 | 9 |
| P36-drone | 2 | 2 | 3 | 1 | 2 | 0 | 2 | 1 | 13 |
| P37-buildkite-agent | 2 | 2 | 3 | 1 | 2 | 2 | 1 | 2 | 15 |
| P38-restic | 3 | 2 | 2 | 1 | 3 | 2 | 0 | 3 | 16 |
| P39-syncthing | 2 | 2 | 2 | 1 | 2 | 1 | 3 | 2 | 15 |
| P40-rclone | 3 | 0 | 1 | 2 | 2 | 0 | 3 | 2 | 13 |
| P41-frp | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 2 | 13 |
Evidence#
P32-fzf / T1 (score 0): report:P32-fzf–interfaces.md: only 2 interfaces exist in the entire codebase — Renderer (22 methods) and Window (24 methods), both in the tui package for backend swap; all other internal components use concrete structs; no consumer-defined narrow interfaces anywhere.
P32-fzf / T2 (score 2): report:P32-fzf–patterns.md: manual DI in Run(); no DI framework; EventBox is constructed and passed explicitly; no init() side effects for core wiring.
P32-fzf / T3 (score 3): report:P32-fzf–architecture.md: Run() in src/core.go is a textbook composition root — explicitly wires EventBox, ChunkList, Reader, Matcher, Terminal, and HTTP Server into a single goroutine-coordinator; single function, all deps assembled.
P32-fzf / T4 (score 1): report:P32-fzf–patterns.md: custom EventBox for inter-goroutine signaling (no context.Context used); Algo function type as a value parameter isolates variant logic; IO not injected via interface seams.
P32-fzf / T5 (score 2): report:P32-fzf–patterns.md: algo sub-package contains pure search algorithms (FuzzyMatchV1/V2, ExactMatch); Algo type as first-class function value cleanly isolates algorithm variants from the event-driven coordinator.
P32-fzf / T6 (score 3): report:P32-fzf–architecture.md: ALL internal collaborators are concrete structs (Reader, Matcher, Terminal, Server, ChunkList, EventBox, Slab); Run() wires concrete types directly; interfaces appear only at the TUI backend boundary.
P32-fzf / T7 (score 1): report:P32-fzf–api-surface.md: exported constructors exist but with no stability guarantee; CLI-only primary interface (custom 177-flag parser); optional minimalist HTTP REST server; no designed embedding surface.
P32-fzf / T8 (score 1): report:P32-fzf–structure.md: entire application lives in flat src/ (package fzf); no internal/ directory; only 4 sub-packages (algo, tui, util, protector); no meaningful package boundary enforcement.
P33-gh / T1 (score 2): report:P33-gh–interfaces.md: bimodal — per-command consumer-defined iprompter interfaces are 2–4 methods (narrow); service-boundary aggregates Config (17 methods), AuthConfig (13 methods), Prompter (10 methods) are present but justified at stable system seams.
P33-gh / T2 (score 2): report:P33-gh–patterns.md: manual DI via cmdutil.Factory struct; universal runF injection pattern across 35+ commands; no DI framework; no package-level globals for core logic.
P33-gh / T3 (score 3): report:P33-gh–architecture.md: factory.New() via lazy closures forms the explicit composition root in internal/ghcmd; all 35+ commands receive every dependency through the Factory; bootstrap sequence is traceable and single-origin.
P33-gh / T4 (score 2): report:P33-gh–patterns.md: IOStreams (stdin/stdout/stderr) explicitly injected through cmdutil.Factory (IO as parameter); 554 context.Context usages propagate cancellation and deadlines throughout the command pipeline.
P33-gh / T5 (score 2): report:P33-gh–interfaces.md: per-command local iprompter interfaces narrow each command’s dependency footprint; pkg/cmd/ isolates commands as self-contained units with no cross-command coupling.
P33-gh / T6 (score 1): report:P33-gh–patterns.md: cmdutil.Factory is a concrete carrier struct; however moq-generated mocks, per-command consumer interfaces, and AuthConfig internal test methods indicate interface-heavy internal collaboration.
P33-gh / T7 (score 2): report:P33-gh–structure.md: pkg/httpmock and pkg/iostreams are public packages explicitly provided for extension authors writing tests; formal extension system via ExtensionManager.Dispatch() with a public Extension interface.
P33-gh / T8 (score 3): report:P33-gh–structure.md: standard cmd/internal/pkg layout with internal/ enforcing private packages; pkg/cmd/ is unconventional but deliberate and documented; module boundaries clearly enforced by Go compiler.
P34-gitea / T1 (score 1): report:P34-gitea–interfaces.md: auth.Method (2 methods, good ISP), indexer interfaces 3–4 methods; notify.Notifier (40+ methods, explicit ISP violation); optional auth source capabilities via type assertion; wide interface at notification seam.
P34-gitea / T2 (score 0): report:P34-gitea–patterns.md: 196 init() functions; GetManager() singletons for graceful.Manager, process.Manager, queue.Manager; global setting.* config vars accessed throughout codebase; pervasive package-level mutable state.
P34-gitea / T3 (score 1): report:P34-gitea–architecture.md: 24-step explicit InitWebInstalled() sequence shows awareness of ordering; but the sequence wires into global singletons rather than explicit constructor parameters, making it a bootstrap chain not a composition root.
P34-gitea / T4 (score 0): report:P34-gitea–architecture.md: all managers accessed via GetManager() singletons; global setting.* vars for all configuration; no injection mechanism for time, IO, or randomness; effects accessed globally throughout.
P34-gitea / T5 (score 1): report:P34-gitea–architecture.md: 4-tier strict layering (modules→models→services→routers) separates concerns by convention; global coupling via setting.* and singletons means layers communicate through ambient state rather than pure function parameters.
P34-gitea / T6 (score 1): report:P34-gitea–structure.md: strict 4-tier layering passes concrete model types across tiers; but singleton managers and global config dominate; concrete models are present but global state prevents clean concrete-first collaboration.
P34-gitea / T7 (score 1): report:P34-gitea–structure.md: no pkg/ or explicitly designed public API; single binary with urfave/cli; no embedding surface; no designed integrator helpers.
P34-gitea / T8 (score 1): report:P34-gitea–structure.md: no internal/ directory at all (by design); 4-tier convention (modules/, models/, services/, routers/) maintained by project convention alone; no mechanical boundary enforcement.
P35-gogs / T1 (score 2): report:P35-gogs–interfaces.md: consumer-defined narrow interfaces present: context.Store (6 methods), lfsx.Storager (3 methods with compile-time assertion), auth.Provider (5 methods); database.Engine (12 methods) is a legacy wrapping artifact.
P35-gogs / T2 (score 0): report:P35-gogs–patterns.md: global conf.* vars accessed throughout; database.Handle global struct as the sole DB entry point; no functional options or injection mechanism anywhere in production code.
P35-gogs / T3 (score 1): report:P35-gogs–architecture.md: route registration centralized in cmd/gogs/web.go (~700 lines); partially explicit wiring point but global state is accessed directly from handlers without injection.
P35-gogs / T4 (score 0): report:P35-gogs–patterns.md: conf.* package-level vars accessed directly by all components; database.Handle global struct for all DB access; no explicit time, IO, or randomness injection.
P35-gogs / T5 (score 1): report:P35-gogs–structure.md: x-suffix utility packages (iox, strx, osx, urlx) provide some separation of pure utilities from I/O code; thin service layer exists but mixes logic with global state access.
P35-gogs / T6 (score 2): report:P35-gogs–patterns.md: database.Handle is a concrete struct used as the sole DB collaborator; internal collaboration via concrete model types throughout; no mock-friendly interface abstraction for the data layer.
P35-gogs / T7 (score 1): report:P35-gogs–structure.md: no designed public library surface; x-suffix packages (iox, strx) serve internal utilities only; no pkg/ or embedding affordances.
P35-gogs / T8 (score 2): report:P35-gogs–structure.md: cmd/internal layout; x-suffix naming convention provides clear scope signaling; internal/database dominates (61 source files); clear package naming discipline despite no internal/ enforcement.
P36-drone / T1 (score 2): report:P36-drone–interfaces.md: authn.Authenticator (1 method), authz.Authorizer (2 methods), router.Interface (3 methods) are textbook minimal; git.Interface (~50 methods) is a deliberate facade; store interfaces ~20–30 methods each (domain-appropriate width).
P36-drone / T2 (score 2): report:P36-drone–architecture.md: Google Wire generates explicit constructors in wire_gen.go with no global state; no package-level mutable state; init() for proxy factory self-registration is contained to a reflect.Type registry.
P36-drone / T3 (score 3): report:P36-drone–architecture.md: wire.Build() with ~120 WireSets in a single call; wire_gen.go is a compile-time verified composition root that encodes the full dependency graph; any wiring error is a compile error.
P36-drone / T4 (score 1): report:P36-drone–architecture.md: Wire-injected dependencies provide DI for services and stores; context threading via Wire; no explicit clock abstraction or IO injection beyond constructor parameters.
P36-drone / T5 (score 2): report:P36-drone–architecture.md: store interfaces separate domain from persistence; service layer over store layer; Redis as event bus cleanly separates event I/O from business logic.
P36-drone / T6 (score 0): report:P36-drone–interfaces.md: Wire and interfaces throughout; every collaboration point is abstracted — 15+ store interfaces, all service dependencies are interfaces; contradicts concrete-first internal collaboration.
P36-drone / T7 (score 2): report:P36-drone–structure.md: registry/ as a separate Go module via replace directive; events system with Go generics (cache.Cache[K,V], ReaderFactory[R]); shared pkg/ packages for auth, nathole, transport.
P36-drone / T8 (score 1): report:P36-drone–structure.md: no internal/ directory; convention-based layering in app/ (handler→controller→service→store); separation maintained by convention, not compiler enforcement.
P37-buildkite-agent / T1 (score 2): report:P37-buildkite-agent–interfaces.md: all narrow and consumer-defined — core.APIClient (7 methods, exactly what core needs), jobProcess (6 methods), logger.Logger (9 methods), tracetools.Span (3 methods), logger.Printer (1 method); no wide interfaces observed.
P37-buildkite-agent / T2 (score 2): report:P37-buildkite-agent–architecture.md: manual DI in agent_start.go; explicit AgentPool→AgentWorker→JobRunner constructor chain; no DI framework; no problematic init() side effects or package-level globals for core logic.
P37-buildkite-agent / T3 (score 3): report:P37-buildkite-agent–architecture.md: agent_start.go is the explicit composition root; sequential manual constructor chain (AgentPool→AgentWorker→JobRunner); all dependencies passed explicitly; no framework.
P37-buildkite-agent / T4 (score 1): report:P37-buildkite-agent–interfaces.md: consumer-defined interfaces facilitate injection of all major collaborators; two-process design (agent + bootstrap subprocess) separates concerns at process boundary; no explicit clock injection observed.
P37-buildkite-agent / T5 (score 2): report:P37-buildkite-agent–architecture.md: two-process design cleanly separates agent lifecycle management from job execution; internal/job.Executor handles bootstrap phases as a distinct concern; clean process-level separation.
P37-buildkite-agent / T6 (score 2): report:P37-buildkite-agent–architecture.md: AgentPool, AgentWorker, JobRunner are concrete structs used as internal collaborators; Kubernetes variant achieved via jobProcess interface swap at the external seam — concrete-first internally, interface at extension points.
P37-buildkite-agent / T7 (score 1): report:P37-buildkite-agent–structure.md: core/ package explicitly marked as an unstable public library; otherwise minimal designed public API surface.
P37-buildkite-agent / T8 (score 2): report:P37-buildkite-agent–structure.md: internal/ enforces private packages; clicommand/ separate from agent/; core/ explicitly scoped as public-but-unstable; clear package boundary discipline.
P38-restic / T1 (score 3): report:P38-restic–interfaces.md: BlobSaver/BlobSaverAsync (1–2 methods); Unwrapper (1 method) with generic AsBackend[B] for type-safe introspection; Layout (5 methods); compile-time assertions throughout; generics for FileType constraints; textbook ISP application.
P38-restic / T2 (score 2): report:P38-restic–architecture.md: backend registry uses init() blank imports for self-registration only — write-once immutable registry that does not affect test isolation; manual DI throughout; internal/restic imports nothing internal.
P38-restic / T3 (score 2): report:P38-restic–architecture.md: global.OpenRepository / global.wrapBackend assembles the full backend decorator stack, repository, cache, and index explicitly; clear single wiring function but named “global” rather than a canonical composition root file.
P38-restic / T4 (score 1): report:P38-restic–architecture.md: Backend interface injection provides a complete IO abstraction (no raw filesystem or network calls above repository layer); context threading present; no explicit clock injection observed.
P38-restic / T5 (score 3): report:P38-restic–architecture.md: internal/restic imports NO other internal packages — pure domain types and interfaces with zero I/O; internal/crypto is stdlib-only; textbook functional core / IO shell separation enforced by the package dependency graph.
P38-restic / T6 (score 2): report:P38-restic–architecture.md: backend decorator stack is a concrete struct chain — sema.Backend wraps logger.Backend wraps retry.Backend wraps storage driver; cross-cutting concerns implemented as concrete wrapping types, not additional interfaces.
P38-restic / T7 (score 0): report:P38-restic–structure.md: no public library surface; all packages under internal/; cmd/restic is the only binary entry point; deliberately no embedding affordances (backup tool, not a library).
P38-restic / T8 (score 3): report:P38-restic–architecture.md: strict enforced layering cmd→global/ui→archiver/restorer→repository→backend→restic; all under internal/; internal/restic at foundation with zero internal imports makes cycles impossible; build-tag-gated optional features (FUSE, debug, self-update).
P39-syncthing / T1 (score 2): report:P39-syncthing–interfaces.md: wide central hubs (model.Model ~30 methods, config.Wrapper ~25 methods, fs.Filesystem ~22 methods); narrow at component seams (config.Committer 2 methods, events.Logger 2+suture, discover.Finder 4 methods, events.Subscription 4 methods).
P39-syncthing / T2 (score 2): report:P39-syncthing–architecture.md: all major components behind interfaces with concrete structs unexported; manual DI in lib/syncthing.App.startup(); no DI framework; no package-level globals for core logic.
P39-syncthing / T3 (score 2): report:P39-syncthing–architecture.md: lib/syncthing.App.startup() is the explicit assembly point; lateAddressLister resolves circular dependency via deferred injection; lib/syncthing as a dedicated assembly package is an explicit architectural concept.
P39-syncthing / T4 (score 1): report:P39-syncthing–architecture.md: strong context threading via suture supervisor library; config.Wrapper injected at all component seams; no explicit clock injection identified.
P39-syncthing / T5 (score 2): report:P39-syncthing–structure.md: lib/ holds ~40 public packages with clear domain separation; suture supervisor tree cleanly separates lifecycle management from business logic; 5-tier layering enforced by structure.
P39-syncthing / T6 (score 1): report:P39-syncthing–architecture.md: all major components’ concrete structs are unexported; ALL cross-component communication is through interfaces; contradicts concrete-first internal collaboration.
P39-syncthing / T7 (score 3): report:P39-syncthing–structure.md: lib/ (not pkg/) explicitly designed for embedding; lib/syncthing is a dedicated embeddable assembly package; ~40 public packages with stable interfaces; syncthing is intentionally consumable as a library.
P39-syncthing / T8 (score 2): report:P39-syncthing–structure.md: lib/ + internal/ layout; 5-tier layering (primitives→domain utilities→protocol/network→application core→assembly); clear domain separation across tiers.
P40-rclone / T1 (score 3): report:P40-rclone–interfaces.md: fs.Fs (5 methods for all 70+ backends); ~25 single-method optional capability interfaces in fs/features.go; march.Marcher (3 methods); compile-time var _ fs.Fs assertions widespread; textbook ISP with optional-capability pattern.
P40-rclone / T2 (score 0): report:P40-rclone–architecture.md: “rclone uses manual global state everywhere” — fs.Registry []*RegInfo global populated by init(); accounting.GlobalStats() singleton; init() in every backend for self-registration; pervasive package-level mutable state.
P40-rclone / T3 (score 1): report:P40-rclone–architecture.md: no explicit composition root; rclone.go is 15 lines that triggers init() chains via blank imports; fs instances created lazily via cache.Get(ctx, remote) on demand; composition is via side effects, not an explicit wiring function.
P40-rclone / T4 (score 2): report:P40-rclone–architecture.md: fs.Fs interface is the IO injection mechanism — all 70+ backends implement it and are injected via the registry; fs.AddConfig(ctx, ci) / fs.GetConfig(ctx) for per-operation config DI; 3,557 context.Context usages throughout.
P40-rclone / T5 (score 2): report:P40-rclone–architecture.md: fs/march is pure two-tree walking logic; sync pipeline (checkers→copiers) has clean stage separation via typed channel pipes; Features struct separates capability declaration from implementation.
P40-rclone / T6 (score 0): report:P40-rclone–architecture.md: entirely interface-driven — fs.Fs, fs.Object, fs.DirEntry as primary abstractions; all 70+ backends implement fs.Fs; vfs.VFS wraps fs.Fs; type assertions for capability discovery; contradicts concrete-first.
P40-rclone / T7 (score 3): report:P40-rclone–structure.md: lib/ (rest, oauthutil, pacer, dircache, http, encoder — all shared utilities for backends); librclone/ C shared library; fstest/fstests/ generic backend conformance test suite reusable by all backends; textbook helper surface.
P40-rclone / T8 (score 2): report:P40-rclone–structure.md: clear unidirectional layers (cmd→fs→lib, backend→fs→lib, vfs→fs→lib); no internal/ directory but strict layering maintained by convention; no cross-direction imports observed.
P41-frp / T1 (score 2): report:P41-frp–interfaces.md: Connector (3 methods), MessageSender (1 method), client/proxy.Proxy (4 methods), Visitor (3 methods), client/plugin.Plugin (3 methods); server/proxy.Proxy at 11 methods is the outlier (“fat interface syndrome driven by embedding-based pseudo-inheritance” — per report).
P41-frp / T2 (score 1): report:P41-frp–patterns.md: init() self-registration for proxy factories (global reflect.Type→factory map) and client plugins (global string→creator map); primary DI via constructor parameters and ServiceOptions is clean; init() registries are present but bounded.
P41-frp / T3 (score 2): report:P41-frp–architecture.md: server.NewService(*v1.ServerConfig) and client.NewService(ServiceOptions{…}) are explicit wiring functions; ResourceController acts as a dependency bundle; two symmetric named composition roots (one per binary).
P41-frp / T4 (score 1): report:P41-frp–patterns.md: 114 context.Context occurrences with exemplary propagation; rate.Limiter explicitly injected via BaseProxy for bandwidth; lastPong heartbeat timestamp uses atomic.Value (not an injected clock).
P41-frp / T5 (score 2): report:P41-frp–architecture.md: pkg/msg is pure wire protocol types (stdlib only, no I/O); pkg/config/v1 is pure typed config structs; client/control.go and server/service.go mix I/O and logic as expected for network coordination code.
P41-frp / T6 (score 1): report:P41-frp–patterns.md: BaseProxy concrete struct embedded for shared behavior (work-conn wrapping, encryption, rate limiting); SessionContext and ResourceController are concrete dependency bundles; but Connector, MessageTransporter, Proxy, Visitor are all interfaces.
P41-frp / T7 (score 2): report:P41-frp–structure.md: pkg/sdk/client (Go SDK for frps admin REST API); pkg/virtual (VirtualClient for in-process frp tunnels); pkg/vnet (WireGuard L3 overlay); pkg/util/* (extensive network, limit, vhost, wait, log utilities).
P41-frp / T8 (score 2): report:P41-frp–structure.md: clear functional decomposition — client/ and server/ have no cross-imports; cmd/→client/|server/→pkg/ dependency direction; pkg/msg as explicit shared protocol boundary; no internal/ directory.
Confidence note (chunk-local)#
Hardest-to-assign scores:
P32-fzf T1 (0): The only two interfaces in the codebase are Renderer and Window, both with 22–24 methods. The absence of any narrow interfaces is striking for a mature tool — fzf’s flat src/ package simply passes concrete structs everywhere. The 0 is correct but may surprise readers who expect any interface to earn a 1.
P33-gh T4 (2): IOStreams (stdin/stdout/stderr wrappers) is explicitly injected through cmdutil.Factory — this is IO-as-parameter by definition. Combined with 554 context.Context usages, the 2 is defensible even without explicit clock injection. A reader requiring clock injection would score 1.
P36-drone T2 (2): Wire-generated code in wire_gen.go contains no global state — every dependency is a constructor parameter. The init() proxy factory registration is a bounded, immutable registry. A reader counting any init() as T2-negative would score 1. Wire’s explicit composition root is the deciding factor for 2.
P38-restic T3 (2): global.OpenRepository and global.wrapBackend constitute a single, explicit wiring sequence — but it lives in a package named “global,” which is architecturally a composition root in function if not in name. T3=3 would require a clearer single-file wiring entry point (like wire_gen.go or a New() function by convention).
P38-restic T7 (0): Restic is a backup tool with an explicit design decision to have no public library surface. The 0 is correct; reduce step should note this is a principled choice, not architectural debt.
P40-rclone T3 (1): The blank-import aggregators (backend/all/all.go, cmd/all/all.go) are the single point where all plugins are coupled together — a weak claim to T3. However, init()-driven composition with no explicit constructor wiring function means this is structural side-effectful registration, not a composition root. Scores 1 rather than 0 only because the aggregators are a recognizable architectural pattern.
P40-rclone T2 (0) with mitigating pattern: fs.AddConfig(ctx, ci) / fs.GetConfig(ctx) is an elegant per-operation config DI mechanism that avoids global mutation. A reader focusing on this pattern might score 1. However, the global fs.Registry, accounting.GlobalStats() singleton, and pervasive init() usage clearly dominate.
P41-frp T2 (1): The proxy type factory registration uses a global reflect.Type→factory map populated by init(). This is similar to rclone’s backend registry (scored 0 for T2) but more bounded — only proxy types register, not the entire program. Scored 1 rather than 0 because the init() usage is more limited in scope and the primary DI mechanism (constructor parameters, ServiceOptions) is genuinely clean.