Skip to content

Load lib/agentic through Zeitwerk alone; drop 57 require_relative lines, add loader spec - #78

Open
minerva-sky wants to merge 1 commit into
codenamev:mainfrom
minerva-sky:fix/zeitwerk-single-loader
Open

minerva-sky wants to merge 1 commit into
codenamev:mainfrom
minerva-sky:fix/zeitwerk-single-loader

Conversation

@minerva-sky

Copy link
Copy Markdown
Collaborator

Closes #57.

lib/agentic.rb opens with a rule: Zeitwerk is the single loader, files must not require_relative their siblings. Then 26 files under lib/agentic/ break it, 57 times. The history is exact: 11c9407 took the count to zero in July and wrote the rule; #7 brought every line back in one merge in September. No build went red in between, because nothing tested the promise behind the rule. A rule the codebase breaks 57 times is a comment, and this PR makes it a rule again.

What changed

  • Every require_relative under lib/agentic/ is gone. I checked each target: all 57 resolve to loader-managed files, none to a gem or anything outside the tree. Stdlib and gem requires stay, as the header says they should.
  • The one inline require_relative "strategy_factory" inside VerificationHub#add_strategy_from_factory is gone too. It did a path lookup on every call for a constant the loader serves on first reference.
  • spec/zeitwerk_spec.rb, two examples. The first boots a fresh interpreter, require "agentic", finds the gem's loader and calls eager_load(force: true) so lib/agentic/cli (excluded from normal eager loading) is walked as well, then resolves Agentic::CLI. Fresh process on purpose: inside the suite, spec_helper has already loaded half the tree, and a file that only loads because its neighbour loaded first would hide. The second example greps lib/agentic/**/*.rb for require_relative and names file and line on any hit. The first tests the promise, the second tests the rule. Together they cost about 0.35s.
  • One CHANGELOG line under Unreleased.

No constant, method, signature, or file path changes. require "agentic" consumers see nothing. Zeitwerk already owned these files, so autoload timing is the same work the deleted lines did, once per constant, never per call.

Evidence

  • Full suite on the branch: 1393 examples, 1 failure, and the failure is event_dispatcher_integration_spec.rb:291, the wall-clock < 0.1 assertion fix: de-flake EventDispatcher integration timing spec #36 de-flakes. I ran that example in isolation on untouched upstream/main and it fails there too, so it is not this diff.
  • standardrb clean.
  • Eager load in a fresh process on Ruby 4.0.2: Agentic::CLI, Agentic::CLI::Streaming::EnhancedProgressTracker, Agentic::HumanIntervention::Portal all resolve after force: true.
  • Zeitwerk::Registry.loaders is semi-internal. I checked it exists in both 2.6.16 (locked) and 2.8.3 (where deps: refresh the dev lockfile within its constraints; cap parallel and simplecov for the 3.2 floor #75 lands), and the spec aborts with a clear message if the tag lookup ever returns nil.

Rebase note

#68 rewrites security/secure_error_mixin.rb from line 4 down; this PR deletes line 3 of the same file. Whichever lands second gets a one-line conflict on that file and nothing else. The other open PRs touching workspace.rb (#30, #67, #73) and file_generation_capability.rb (#50) work hundreds of lines below the deleted requires.

Intended labels: loop:drain.

Panel

Three seats per expert, majority rule per criterion.

  • DHH: cleared 3/3. Pure deletion plus one guard spec, nothing to mimic or abstract. Nits folded in: trimmed the spec header, and the grep example is "a lint in spec clothing" (kept: it is the only thing that enforces the rule, and bundle exec rake has no other lint slot for it).
  • Obie Fernandez: cleared 3/3. Zeitwerk rides on Module#autoload, which the VM serializes per constant, so no new race; every deleted target is loader-managed; no top-level side effects depended on load order (register_standard_capabilities and Schemas.register_all! are explicit calls).
  • Vladimir Dementyev: cleared 3/3. Subprocess never loads spec_helper, so SimpleCov is untouched; one interpreter boot per CI run, no sleeps or network; autoload is one-time per constant, not N+1. Nit folded in: nil guard on the loader lookup.
  • The gem consumer: cleared 3/3. No public surface moved; saved agents and plans are data and carry no load order. Two seats probed require "agentic/security" and require "agentic/human_intervention/portal" bare, both still load.

12/12 criteria cleared, 0 revisions.

🤖 Generated with Claude Code

lib/agentic.rb makes Zeitwerk the single code loader and says files must
not require_relative their siblings. 57 lines in 26 files did it anyway,
all reintroduced by the codenamev#7 merge two months after 11c9407 took the count
to zero, because nothing tested the promise behind the rule.

Delete all 57 (every target is a loader-managed file under lib/agentic/)
plus the inline require_relative in VerificationHub#add_strategy_from_factory,
which did a path lookup on every call. Add spec/zeitwerk_spec.rb: a fresh
process eager-loads the gem with force: true (so lib/agentic/cli is walked
too) and resolves Agentic::CLI, and a second example fails on any
require_relative that comes back under lib/agentic/.

Closes codenamev#57

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@minerva-sky minerva-sky added the loop:drain Implemented from the improvements ledger by the drain loop label Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

loop:drain Implemented from the improvements ledger by the drain loop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lib/agentic.rb forbids require_relative between siblings; 61 lines in 27 files do it anyway, all reintroduced by #7

1 participant