Convert the plugins that do not use Zeitwerk to Zeitwerk autoloading, with os-sdlc as the template #800

Closed
opened 2026-09-28 16:30:17 +00:00 by jared · 2 comments
Owner

Problem

Eight of the nine os-* plugins load their code by hand-written require_relative chains instead of Zeitwerk. A missing require in that scheme surfaces only when a file happens to run inside a preload that already requires the missing constant, not when the file runs alone. plugins/os/tests/all.rb hid exactly this gap: plugins/os/tests/worktree/start_command_test.rb raised NameError: SerializedChdir when run by itself, and 3 other files failed the same way, until #533 gave every test file its own require_relative "test_helper". plugins/os-sdlc does not have this class of failure. It loads production code through Zeitwerk::Loader in plugins/os-sdlc/lib/os_sdlc.rb (ADR-0194) and bootstraps its tests through plugins/os-sdlc/tests/test_helper.rb, which also loads its tests/support/ fixtures through a second Zeitwerk::Loader. Grep confirms the split: plugins/os-sdlc is the only plugin under plugins/* whose Gemfile or lib/ references zeitwerk; plugins/cc-architect, plugins/os, plugins/os-adr, plugins/os-aidd-lint, plugins/os-backlog, plugins/os-context, plugins/os-doc-hygiene, and plugins/os-vault all still hand-wire requires.

Approach

Required work:

  1. For each of the eight plugins (cc-architect, os, os-adr, os-aidd-lint, os-backlog, os-context, os-doc-hygiene, os-vault), give it a Zeitwerk::Loader entry point shaped like plugins/os-sdlc/lib/os_sdlc.rb, and bootstrap its tests through a tests/test_helper.rb that also loads tests/support/ via Zeitwerk::Loader, shaped like plugins/os-sdlc/tests/test_helper.rb — check: for f in $(find plugins/<name>/tests -name '*_test.rb'); do ruby "$f" || echo FAIL "$f"; done
  2. One ticket per plugin, or one batch ticket covering all eight — the implementing agent's choice.

Rejected: converting all eight plugins in this one ticket — too large a diff for one reviewable increment; left to the implementing agent to slice.

Acceptance criteria

  1. Given a test file in a converted plugin, when it is run alone (not through that plugin's tests/all.rb), then it passes without a NameError for a missing constant.
  2. Given a converted plugin's lib/ directory, when its entry-point file loads, then it registers a Zeitwerk::Loader the way plugins/os-sdlc/lib/os_sdlc.rb does.

Owner sketch

# per plugin, e.g. plugins/os/lib/os.rb
loader = Zeitwerk::Loader.new
loader.push_dir(__dir__)
loader.setup

Non-goals

Converting plugins/os-sdlc itself — it already uses Zeitwerk. Changing the runtime behavior of any converted plugin's commands.

Origin

  • Trigger: cd plugins/os && ruby tests/worktree/start_command_test.rb raised NameError: uninitialized constant SerializedChdir, fixed in #533 by adding a per-file tests/test_helper.rb require.
  • Improvised this session: none — #533 fixed only plugins/os; this ticket generalizes the fix to the other seven affected plugins.
  • Chain: hand-written require_relative chains break when a file runs outside its preloading aggregator ← plugins other than os-sdlc do not use Zeitwerk ← DESIGN: plugins/os-sdlc adopted Zeitwerk under ADR-0194; the rest were never migrated.
  • Root candidate: this ticket
  • Where: plugins/cc-architect, plugins/os, plugins/os-adr, plugins/os-aidd-lint, plugins/os-backlog, plugins/os-context, plugins/os-doc-hygiene, plugins/os-vault
  • Session: 80ad7dfb-adda-49df-ac21-4afd6ead90ca
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/80ad7dfb-adda-49df-ac21-4afd6ead90ca.jsonl
## Problem Eight of the nine `os-*` plugins load their code by hand-written `require_relative` chains instead of Zeitwerk. A missing require in that scheme surfaces only when a file happens to run inside a preload that already requires the missing constant, not when the file runs alone. `plugins/os/tests/all.rb` hid exactly this gap: `plugins/os/tests/worktree/start_command_test.rb` raised `NameError: SerializedChdir` when run by itself, and 3 other files failed the same way, until #533 gave every test file its own `require_relative "test_helper"`. `plugins/os-sdlc` does not have this class of failure. It loads production code through `Zeitwerk::Loader` in `plugins/os-sdlc/lib/os_sdlc.rb` (ADR-0194) and bootstraps its tests through `plugins/os-sdlc/tests/test_helper.rb`, which also loads its `tests/support/` fixtures through a second `Zeitwerk::Loader`. Grep confirms the split: `plugins/os-sdlc` is the only plugin under `plugins/*` whose `Gemfile` or `lib/` references `zeitwerk`; `plugins/cc-architect`, `plugins/os`, `plugins/os-adr`, `plugins/os-aidd-lint`, `plugins/os-backlog`, `plugins/os-context`, `plugins/os-doc-hygiene`, and `plugins/os-vault` all still hand-wire requires. ## Approach Required work: 1. For each of the eight plugins (`cc-architect`, `os`, `os-adr`, `os-aidd-lint`, `os-backlog`, `os-context`, `os-doc-hygiene`, `os-vault`), give it a `Zeitwerk::Loader` entry point shaped like `plugins/os-sdlc/lib/os_sdlc.rb`, and bootstrap its tests through a `tests/test_helper.rb` that also loads `tests/support/` via `Zeitwerk::Loader`, shaped like `plugins/os-sdlc/tests/test_helper.rb` — check: `for f in $(find plugins/<name>/tests -name '*_test.rb'); do ruby "$f" || echo FAIL "$f"; done` 2. One ticket per plugin, or one batch ticket covering all eight — the implementing agent's choice. Rejected: converting all eight plugins in this one ticket — too large a diff for one reviewable increment; left to the implementing agent to slice. ## Acceptance criteria 1. Given a test file in a converted plugin, when it is run alone (not through that plugin's `tests/all.rb`), then it passes without a `NameError` for a missing constant. 2. Given a converted plugin's `lib/` directory, when its entry-point file loads, then it registers a `Zeitwerk::Loader` the way `plugins/os-sdlc/lib/os_sdlc.rb` does. ## Owner sketch ```ruby # per plugin, e.g. plugins/os/lib/os.rb loader = Zeitwerk::Loader.new loader.push_dir(__dir__) loader.setup ``` ## Non-goals Converting `plugins/os-sdlc` itself — it already uses Zeitwerk. Changing the runtime behavior of any converted plugin's commands. ## Origin - Trigger: `cd plugins/os && ruby tests/worktree/start_command_test.rb` raised `NameError: uninitialized constant SerializedChdir`, fixed in #533 by adding a per-file `tests/test_helper.rb` require. - Improvised this session: none — #533 fixed only `plugins/os`; this ticket generalizes the fix to the other seven affected plugins. - Chain: hand-written require_relative chains break when a file runs outside its preloading aggregator ← plugins other than os-sdlc do not use Zeitwerk ← DESIGN: `plugins/os-sdlc` adopted Zeitwerk under ADR-0194; the rest were never migrated. - Root candidate: this ticket - Where: plugins/cc-architect, plugins/os, plugins/os-adr, plugins/os-aidd-lint, plugins/os-backlog, plugins/os-context, plugins/os-doc-hygiene, plugins/os-vault - Session: 80ad7dfb-adda-49df-ac21-4afd6ead90ca - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/80ad7dfb-adda-49df-ac21-4afd6ead90ca.jsonl
Author
Owner

Work started on branch worktree-ticket-800 (session aa699ebb). Pilot on plugins/os first, then the other seven in parallel, one commit per plugin.

Work started on branch worktree-ticket-800 (session aa699ebb). Pilot on plugins/os first, then the other seven in parallel, one commit per plugin.
Author
Owner

Resolution

Done: Converted every Ruby plugin (os, os-adr, os-aidd-lint, os-backlog) from hand-written require_relative chains to Zeitwerk autoloading shaped like os-sdlc: one loader over lib/ with a cli=>CLI inflection, tests/test_helper.rb eager-loads a second loader over tests/support/, and a zeitwerk_eager_load_test.rb per plugin enforces file-to-constant naming. ADR-0241 records the convention. cc-architect, os-context, os-doc-hygiene, and os-vault have no Ruby lib/ and were not touched; the ticket's count of eight was wrong, four Ruby plugins existed.

Evidence: main e5716bb4 (fast-forward). Every _test.rb run alone across the four plugins: 27+7+53+53 files, 0 failures, run twice. Suites: os 32 runs, os-adr 51, os-aidd-lint 503, os-backlog 269, all 0 failures. Bins boot: worktree, adr-, aidd-lint, os-backlog. Conversion exposed two latent stdlib gaps (YAML, JSON, Time, Date used without require; order-dependent under lazy loading) fixed in test_helper. Plugin cache refreshed.

Follow-ups: none

## Resolution **Done:** Converted every Ruby plugin (os, os-adr, os-aidd-lint, os-backlog) from hand-written require_relative chains to Zeitwerk autoloading shaped like os-sdlc: one loader over lib/ with a cli=>CLI inflection, tests/test_helper.rb eager-loads a second loader over tests/support/, and a zeitwerk_eager_load_test.rb per plugin enforces file-to-constant naming. ADR-0241 records the convention. cc-architect, os-context, os-doc-hygiene, and os-vault have no Ruby lib/ and were not touched; the ticket's count of eight was wrong, four Ruby plugins existed. **Evidence:** main e5716bb4 (fast-forward). Every *_test.rb run alone across the four plugins: 27+7+53+53 files, 0 failures, run twice. Suites: os 32 runs, os-adr 51, os-aidd-lint 503, os-backlog 269, all 0 failures. Bins boot: worktree, adr-*, aidd-lint, os-backlog. Conversion exposed two latent stdlib gaps (YAML, JSON, Time, Date used without require; order-dependent under lazy loading) fixed in test_helper. Plugin cache refreshed. **Follow-ups:** none
jared closed this issue 2026-09-28 17:18:39 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#800
No description provided.