X22a-3-arch-traits: P22-go — P31-cobra#

Scorecard#

ProjectT1T2T3T4T5T6T7T8Σ
P22-go3111223316
P23-gin2121222214
P24-echo3231122216
P25-fiber1221223215
P26-buffalo2221212214
P27-beego000121329
P28-gorm3232322320
P29-sqlc3231322319
P30-viper3122222317
P31-cobra3123233219

Evidence#

P22-go#

  • T1=3 1,201 interfaces; dominant 1-method form (io.Reader, io.Writer, io.Closer, fs.FS, Actor); ir.Node=22 methods is justified internal-only; textbook ISP across stdlib.
  • T2=1 init()-based command registration in cmd/go (build, test, vet, etc.); runtime package is inherently global state; pprof registers via init()/blank-import side effect.
  • T3=1 go tool wires subcommands via init() and package-level command registries; compiler and runtime have no single explicit composition root; wiring scattered across init() calls.
  • T4=1 os.Stdin/Stdout ambient throughout the go tool; time.Now() called directly in compiler; testing.TB provides injection in tests only, not in production code paths.
  • T5=2 compiler front-end (parsing, type-checking) are pure transformations with no I/O; math/, sort/, strings/ stdlib packages are pure; go tool and runtime are inherently I/O-bound.
  • T6=2 stdlib uses 1-method interfaces (io.Reader, io.Writer) at all external boundaries; ir.Node=22 is wide but internal-only; runtime internals use concrete types for hot paths.
  • T7=3 stdlib is the canonical public helper API: io, sync, testing, fmt, encoding, math; api/go1.N.txt formally tracks the public surface; 50+ stdlib packages with integrator affordances.
  • T8=3 strict internal/ layering enforced by go build tooling itself; no import cycles (test enforced by src/internal/dag); dual module split (std vs cmd); custom src/ layout with clear subsystem boundaries.

P23-gin#

  • T1=2 Binding=2, Render=2, HTMLRender=1 are narrow; IRoutes=15 and ResponseWriter=10+4-embedded are intentionally wide capability declarations that dominate the interface catalog.
  • T2=1 build-tag init() selects JSON backend (sonic/jsoniter/go_json/default); mutable package-level binding.Validator; global atomic ginMode; these exist in production paths.
  • T3=2 gin.New() constructor with OptionFunc pattern is a real composition root; but global ginMode and package-level binding.Validator are ambient state alongside it.
  • T4=1 no explicit time injection; context.Context has exactly 1 occurrence in tests; request/response I/O is ambient via net/http; no injection point for testing without real HTTP.
  • T5=2 binding/ and render/ have format-conversion logic that is partially pure; hot request path (context.go, gin.go) mixes I/O with routing and handler dispatch.
  • T6=2 Binding/Render/HTMLRender interfaces at extension boundaries (appropriate); radix tree router (tree.go) is deliberately concrete and non-pluggable for performance predictability.
  • T7=2 WrapF/WrapH adapters for stdlib http.HandlerFunc; H map alias; build-tag JSON backend selection; OptionFunc construction-time configuration.
  • T8=2 flat root with public sub-packages (binding/, render/, codec/json/); internal/ contains only bytesconv and fs (two packages); sub-packages have clear single concerns.

P24-echo#

  • T1=3 7 of 10 non-trivial interfaces have 1 method; Router=4; average ~1.6 methods/interface across the catalog; v5 removed the wide Context interface and made it concrete.
  • T2=2 NewWithConfig() is the primary constructor with explicit Config struct; no init() registrations observed; sync.Pool for Context is package-level but not a singleton or registry.
  • T3=3 Echo struct is the explicit composition root; Config holds all swappable slots (Binder, Renderer, Logger, IPExtractor, Router); NewWithConfig() is the discoverable wiring entry point.
  • T4=1 no explicit time or filesystem injection; signal.NotifyContext for graceful shutdown uses ambient OS signals; no IO injection point separate from net/http.
  • T5=1 framework is inherently I/O-bound (HTTP request/response pipeline); middleware/ handler logic is interleaved with I/O; no isolated pure-logic packages identified.
  • T6=2 Binder, Renderer, Logger, Router interfaces at replaceable boundaries; v5 concrete Context eliminates the former 100-method interface; middleware dependencies are concrete.
  • T7=2 echotest/ as first-class public testing utility in the library; Config struct for middleware construction; middleware/ as sibling package with clean handler signatures.
  • T8=2 flat structure with clear sibling packages (middleware/, echotest/); no internal/; root-package-as-framework is appropriate for the scope; no import cycles.

P25-fiber#

  • T1=1 Ctx interface generated by ifacemaker has 100+ methods — the primary user-facing abstraction is extremely wide; Storage=9, Views=2, StructValidator=1 are narrow but secondary.
  • T2=2 sync.Pool in 15+ locations (package-level performance pattern); no significant init() registration or process-wide singletons; code generation is build-time only.
  • T3=2 New() constructor with Config struct; but three distinct execution paths (regular, custom Ctx, custom pool) mean no single unambiguous composition root.
  • T4=1 fasthttp-based with no explicit time or filesystem injection identified in architecture or patterns reports.
  • T5=2 30 middleware packages are self-contained with no cross-dependencies; internal/memory and internal/storage hold pure data structures; core Ctx handling is I/O-bound.
  • T6=2 Storage, Views, StructValidator=1 interfaces at external/replaceable boundaries; fasthttp types used concretely in internal hot paths; Adaptor package bridges stdlib handlers.
  • T7=3 30 middleware packages as self-contained integrator affordances (each a separate importable package); generic pool/state helpers; rich fiber.App configuration API; Adaptor for stdlib compat.
  • T8=2 root as core; 30 middleware as self-contained packages; internal/ for memory/storage/tlstest; no import cycles identified; middleware isolation is a structural strength.

P26-buffalo#

  • T1=2 render.Renderer=2 (excellent ISP), servers.Server=3, worker.Worker=6 are bounded; but Context=17 is wide and permeates the framework core.
  • T2=2 sync.OnceValue for plugin singleton; no init() registration observed; Options struct approach for all major configuration decisions.
  • T3=2 Options struct passed to New() makes dependencies explicit; adapters (render, servers, worker, binding, plugins) are named and swappable; no single wiring function unifying all of them.
  • T4=1 reflection-based middleware identity detection is fragile; no explicit time, filesystem, or clock injection identified in the architecture.
  • T5=2 render/ is a standalone importable package with pure format-conversion logic; ports-and-adapters structure isolates some concerns; app struct core mixes I/O with dispatch.
  • T6=1 Context=17 is a massive interface used throughout internal routing; reflection-based middleware skip is fragile with closures; render.Renderer=2 is good at its boundary but is the exception.
  • T7=2 render/ as a standalone importable library; Resource interface for RESTful scaffolding; compile-time assertions for all server adapters.
  • T8=2 root-package library with clear ports-and-adapters separation; internal/ for private utilities; render/ standalone; no import cycles.

P27-beego#

  • T1=0 ControllerInterface=16, QuerySeter=40+, Configer=20+, Cache=9 — all wide; no narrow 1-3 method consumer-side interfaces found; all interface surfaces are framework-internal.
  • T2=0 Global BeeApp initialized in init(); global BConfig; init()-based driver registration for all backends (ORM, cache, session, log) via blank-import pattern.
  • T3=0 beego.Run() operates on the global BeeApp singleton; no explicit composition root; all wiring is through init() registrations and global state mutation.
  • T4=1 Configer interface is injectable as a slot; but global BeeApp and BConfig are ambient; no explicit time/clock or I/O injection at construction.
  • T5=2 async logging via channel-based pipeline is isolated; ORM decorator cache uses pure caching logic; but reflection-based controller routing tightly couples I/O with dispatch.
  • T6=1 ControllerInterface=16 is a wide interface used in core dispatch; reflection-based routing is not interface-driven; backend interfaces (Cache, Configer) are appropriate at those boundaries.
  • T7=3 massive feature set across four domains (ORM, cache, task scheduling, session, admin HTTP, beego.Run()); rich integrator affordance is the project’s dominant characteristic.
  • T8=2 four-domain split (core/client/server/task); one subdir per backend universally; mock packages co-located; only client/orm/internal/ uses internal/ mechanism; no import cycles.

P28-gorm#

  • T1=3 clause.Expression=1, all callback hooks are 1-method; ConnPool=4, logger.Interface=5 are narrow; Dialector=8 is wide but justified (external DB-engine boundary with 8 distinct responsibilities).
  • T2=2 sync.Map for schema cache is package-level but not init()-registered; copy-on-write DB avoids shared mutable per-request state; no init() registrations identified.
  • T3=3 Open(dialector, config) is the single composition root; dialector, callbacks, logger, and ConnPool all wired there; DB copy-on-write propagates the wired config to all derived sessions.
  • T4=2 Dialector and ConnPool injected as interfaces at Open(); logger.Interface injectable via gorm.Config; time.Now() usage not explicitly parameterized.
  • T5=3 clause/ is a pure SQL AST package with no I/O; schema/ is a pure reflection cache; callbacks/ are pure transformation pipeline steps; error accumulation on DB.Error is logic-only.
  • T6=2 Dialector=8 and ConnPool=4 interfaces at external DB boundaries (appropriate); clause.Expression=1 at AST boundary; schema/ and internal callback logic use concrete types.
  • T7=2 G[T] generic API for type-safe query results; functional options for gorm.Config; callback pipeline is expressive but requires domain knowledge to extend correctly.
  • T8=3 flat library root with single-concern sub-packages (callbacks/, clause/, schema/, logger/, migrator/); internal/ for LRU cache and stmt_store; no import cycles.

P29-sqlc#

  • T1=3 ast.Node=1, Parser=3, Analyzer=4 — all consumer-defined and narrow; CachedAnalyzer is a 1-method decorator; ext.Handler is the plugin boundary over grpc.ClientConnInterface.
  • T2=2 no init() registration; standard Go layout with no ambient singletons; all plugin invocation via protobuf RPC; errgroup concurrency is explicit in code.
  • T3=3 parse → catalog → type-resolve → IR → codegen pipeline stages are explicit; pkg/cli is the sole public surface; errgroup fan-out with GOMAXPROCS bound is discoverable in internal/.
  • T4=1 compiler reads SQL files directly from disk; no explicit filesystem abstraction or time injection; GOMAXPROCS-bounded fan-out is explicit but concurrency is not parameterized.
  • T5=3 parse → catalog → IR pipeline stages are pure transformations (input SQL → output IR); protobuf plugin boundary cleanly separates codegen I/O; each stage maps input to output without side effects.
  • T6=2 Parser and Analyzer interfaces at replaceable boundaries (PostgreSQL vs MySQL backends); concrete types for internal catalog, IR nodes, and AST; only external plugin boundary uses interfaces.
  • T7=2 protobuf plugin boundary enables external code generators; pkg/cli as the sole public surface; but depth of compiler internals limits casual integrator affordance.
  • T8=3 textbook cmd/internal/pkg layout; all logic in internal/; pkg/cli as sole public surface; clean compiler-style layering with no import cycles.

P30-viper#

  • T1=3 Encoder=1, Decoder=1, FlagValueSet=1 (VisitAll), Finder=1, StringReplacer=1 — all 1-method; Codec embeds Encoder+Decoder; FlagValue=4 is the widest and still bounded; ISP textbook.
  • T2=1 package-level singleton initialized in init(); every global function (Get, Set, ReadInConfig) delegates to singleton; remote/ self-registers via init() on blank import.
  • T3=2 NewWithOptions() with Option interface is a clean, dependency-explicit composition root; but the global singleton (GetViper()) coexists and is the primary documented usage path.
  • T4=2 afero.Fs injected via WithFinder/SetFs (not ambient os.FS); io.Reader/io.Writer accepted for ReadConfig/WriteConfigTo; slog.Logger injected via WithLogger option.
  • T5=2 internal/encoding/* codecs are pure format-conversion packages (no I/O); internal/features holds pure bool constants; viper.go mixes waterfall key-lookup logic with filesystem I/O.
  • T6=2 afero.Fs interface at filesystem boundary (replaceable for testing); codec interfaces at format boundary; waterfall lookup algorithm (deep() function) uses concrete Go map traversal.
  • T7=2 Sub() for scoped instances; FlagValue/FlagValueSet decouple from pflag; io.Reader/Writer for ReadConfig/WriteConfigTo; DefaultCodecRegistry.RegisterCodec for format extension.
  • T8=3 internal/encoding/* for codec implementations; internal/features for build-tag flags; internal/testutil for shared test helpers; remote/ as a separate Go module; no import cycles.

P31-cobra#

  • T1=3 one named interface (SliceValue=1 method, consumer-defined for pflag detection); all other contracts are function types (PositionalArgs, CompletionFunc) or stdlib io.Reader/io.Writer — minimal by design.
  • T2=1 package-level globals: EnablePrefixMatching, EnableCommandSorting, EnableCaseInsensitive, EnableTraverseRunHooks; flagCompletionFunctions map (RWMutex-guarded); initializers/finalizers slices.
  • T3=2 consumers wire the command tree via &cobra.Command{...} struct literal + AddCommand() — explicit; Execute() is the single entry point; but lazy injection of help/__complete/completion subcommands in ExecuteC() is not visible at construction.
  • T4=3 SetIn/SetOut/SetErr explicitly inject io.Reader/io.Writer; parent-chain fallback propagates streams to all descendants automatically; ExecuteContext threads context.Context; no ambient I/O in production code.
  • T5=2 args.go validators are pure functions (no I/O); flag_groups.go validation is pure; doc/ is a pure tree-traversal package; completion generation and command execution are I/O-bound.
  • T6=3 one named interface (SliceValue=1) used only for type-detection in completion; pflag.FlagSet is concrete; lifecycle hooks are function fields (Run, PreRun, PostRun) not interface methods; io.Writer only at I/O boundary.
  • T7=3 MatchAll validator combinator; completion generators (GenBashCompletionV2, GenFishCompletion, GenZshCompletion); SetIn/SetOut/SetErr for test injection; doc/ for docgen; AddTemplateFunc for help customization.
  • T8=2 no internal/ by deliberate choice (stable library; all symbols intentionally public); doc/ as a clean one-way dependency subpackage; platform files via filename build constraints; no import cycles.

Confidence note#

Overall confidence: medium-high.

Scores are based on architecture, interfaces, patterns, structure, and api-surface reports only (testing reports not read per reading constraint).

Hardest scores:

  • P22-go T3=1 and T4=1: Scoring a monorepo compiler+runtime toolchain against “composition root” and “injected I/O” traits is inherently difficult. The Go toolchain is the builder of all Go programs; it does not itself apply the patterns it enables. Scores reflect actual observable wiring style (init()-registration, ambient os calls) not capability.

  • P27-beego T5=2: Beego’s four-domain split suggests more isolation than a flat monolith, but without reading the testing report it’s hard to verify whether the subsystems are truly testable in isolation. The score reflects what is structurally present (async log pipeline, ORM cache layer) but hedges on actual purity.

  • P25-fiber T1=1: The generated Ctx interface (100+ methods via ifacemaker) is the dominant user-facing abstraction and drives the score down despite narrow Storage/Views interfaces. If future analysis shows Ctx is primarily used by the framework itself and not composed by consumers, this score could rise to 2.

  • P31-cobra T3=2 vs T3=3: The struct-literal construction pattern is arguably “explicit” enough for a 3, but the package-level initializers/finalizers and lazy ExecuteC() injection introduce ambient wiring that justifies 2. Borderline call.