X22a-4-arch-traits: P32-fzf … P41-frp#

Scorecard rows#

ProjectT1T2T3T4T5T6T7T8Composite
P32-fzf0231231113
P33-gh2232212317
P34-gitea101011116
P35-gogs201012129
P36-drone2231202113
P37-buildkite-agent2231221215
P38-restic3221320316
P39-syncthing2221213215
P40-rclone3012203213
P41-frp2121212213

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.