ci: add the gem's CI workflow + the missing generator specs#25
Conversation
## Summary
The gem repo had no CI: `bundle exec rake` (rspec + rubocop) was trust-based on
every PR, and CLAUDE.md's claim that "Generator specs cover docs_kit:install
output" was false — spec/generators/ did not exist. This wires real CI + real
generator specs and makes the docs true.
- `.github/workflows/ci.yml`: push to main + all PRs; matrix Ruby 3.2/3.3/3.4;
ruby/setup-ruby with bundler-cache; `bundle exec rake`; concurrency-cancel on
the same ref. One job — the generator specs run plain Thor against a tmp app
skeleton (no Rails boot), so no heavier harness is needed.
- `spec/generators/install_generator_spec.rb`: runs the install generator against
a throwaway destination root (a minimal fake app skeleton) and asserts the file
manifest + key contents (brand substitution, route injection, the
`include DocsKit::Controller` inject, package.json scripts, the docs_kit
Stimulus registration, chmod 0755 on bin/build-css, theme list ↔ CSS sync).
- SimpleCov in spec_helper with `minimum_coverage 80` — the 80% floor from
CLAUDE.md is now enforced (suite fails below it, locally and in CI). Line
coverage: 86.76%.
## Incidental fixes (CI-blocking, pre-existing)
- `bundle exec rubocop` crashed on clean main: it descended into the tracked
`docs/` dogfood site (inherits rubocop-rails-omakase, not in the gem's bundle)
and into agent scratch worktrees. Excluded `docs/`, `.claude/`, `coverage/`,
`tmp/` in .rubocop.yml so `rake` (and CI) runs.
- Guarded the `daisyui` path override in the Gemfile (`if File.directory?`) so a
fresh clone / CI without the sibling checkout resolves daisyui from rubygems.
- Ignored `.claude/worktrees/`.
## Test Coverage
- 14 generator examples asserting the install manifest + key file contents
against current behavior (largely idempotent; create_initializer re-renders).
- SimpleCov gate proven: flipping an assertion goes RED; coverage below 80 fails.
## Verification
- [x] bundle exec rake passes (75 examples, 0 failures; 86.76% coverage; rubocop
clean — 39 files, 0 offenses)
Refs #23
DocsUI::Shell and Sidebar `include DaisyUI` (the daisyUI Phlex kit). The gem name is `daisyui`; the require path is `daisy_ui` — a consuming site loads it via `gem "daisyui", require: "daisy_ui"`. docs_kit itself never requires it (the host app does). The suite requires only `docs_kit`, so nothing loaded the daisyUI kit. Locally it passed by accident — the sibling `../daisyui` checkout's loader happened to be active. On CI (published daisyui 1.2.0, no sibling) `include DaisyUI` raised `NameError: uninitialized constant DocsUI::Shell::DaisyUI` and every component spec errored on load. Fix at the earliest point: `spec_helper.rb` requires `daisy_ui` before `docs_kit`, exactly as a real consuming site does. Verified by hiding the sibling checkout so Bundler resolves daisyui 1.2.0 from rubygems (CI's exact state): 75 examples, 0 failures, 86.76% coverage, rubocop clean. Refs #23
RuboCop discovers and loads .rubocop.yml files across the whole tree, including docs/.rubocop.yml — the dogfood site's config, which `inherit_gem`s rubocop-rails-omakase (and, transitively, panolint). Those gems live in the docs/ bundle, not the gem's, so on CI the inheritance can't resolve and RuboCop crashes during config loading — before linting a single file. An AllCops Exclude of docs/ stops linting those files but NOT loading their config as directory inheritance. The fix is to pass RuboCop explicit paths so it never discovers docs/.rubocop.yml: scope the rake task to app/ lib/ spec/ + the root Ruby files. Verified with --debug that only the root .rubocop.yml is loaded, and that a full `rake` under CI conditions (published daisyui, no sibling checkout) is green: 75 examples 0 failures, 86.76% coverage, 37 files rubocop-clean. docs/ lints itself with its own bundle. The AllCops Exclude stays as defense for anyone running `rubocop` bare. Refs #23
CI is green on 3.2 / 3.3 / 3.4 ✅Two additional CI-blocking bugs surfaced only once the workflow ran against the published
Both were reproduced locally by hiding the sibling checkout so Bundler resolves |
Closes #23. Part of #8 (Phase 5) — the CI gate every other issue in the epic merges through.
What this does
The gem repo had no CI:
bundle exec rake(rspec + rubocop) was trust-based on every PR, andCLAUDE.md's claim that "Generator specs coverdocs_kit:installoutput" was false —spec/generators/did not exist. This wires real CI + real generator specs and makes the docs true.CI —
.github/workflows/ci.ymlmainand all PRs.3.2/3.3/3.4(matches the gemspec's>= 3.2).ruby/setup-rubywithbundler-cache: true, thenbundle exec rake.Generator specs —
spec/generators/install_generator_spec.rb14 examples. Each builds a minimal fake-app skeleton (
routes.rb,application_controller.rb,controllers/index.js,assets.rb) in a throwaway destination root, runs the generator, and asserts:c.brand/c.title_suffixsubstituted from the humanized app name;@pluginblock;include DocsKit::Controllerinjected intoApplicationController(and skipped when absent);buildsasset path appended toassets.rb;package.jsonstub scripts (and that an existingpackage.jsonwithbuild:cssis left untouched);docs_kit/controllersStimulus registration (and skipped when there's noindex.js);bin/build-csscreated executable (chmod0755).These assert current behavior. I verified the generator is largely idempotent (Thor's
route/inject_into_fileguard against duplicate lines; several stepssay_status(:skip)on an existing target) — the issue's assumption thatadd_routesblind-appends is stale, so there's no idempotency bug to file.create_initializerre-renders on every run, which is fine.Coverage gate
SimpleCov in
spec/spec_helper.rbwithminimum_coverage 80— the 80% floor documented inCLAUDE.mdis now enforced (the suite fails below it, locally and in CI). Current line coverage: 86.76%.Incidental fixes (CI-blocking, pre-existing)
bundle exec rubocopalready crashed on cleanmain(verified by stashing my changes) — it descended into the trackeddocs/dogfood site, whose.rubocop.ymlinheritsrubocop-rails-omakase(not in the gem's bundle), and into agent scratch worktrees. Without fixing this, CI'srakecan never go green. Fixes:docs/,.claude/,coverage/,tmp/in.rubocop.yml(thedocs/site lints itself with its own bundle).daisyuipath override in theGemfile(if File.directory?(...)) so CI / a fresh clone without the sibling checkout resolvesdaisyuifrom rubygems via the gemspec dependency..claude/worktrees/.Docs
CLAUDE.md+.claude/rules/testing.md: the generator-specs claim is now true; documented the SimpleCov enforcement and the CI matrix.Test plan
bundle exec rakegreen locally — 75 examples, 0 failures, 86.76% line coverage, rubocop clean (39 files, 0 offenses).mainin repo settings (Settings → Branches → branch protection). GitHub Actions can't self-configure branch protection.Out of scope