Skip to content

Commit 9fda0f8

Browse files
authored
fix: preserve release changelog history (#9)
Release preparation was using overwrite-style changelog generation. That kept this gem vulnerable to losing previous release notes when preparing the next version. Route changelog generation through git-cliff --prepend, add local and remote tag validation, link future PR references, and document upgrade plus commit-message expectations for public contract changes. Verification: ruby -c Rakefile; ruby -c test/release_task_test.rb; git-cliff -c cliff.toml -o /tmp/git-markdown-changelog-smoke.md; git diff --check. Bundle-backed tests and Standard Ruby were blocked because required gems are not installed locally.
1 parent f469658 commit 9fda0f8

6 files changed

Lines changed: 246 additions & 264 deletions

File tree

AGENTS.md

Lines changed: 36 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -1,248 +1,38 @@
11
# AI Agent Playbook
22

3-
Rules any code-generation agent must follow when editing this repository.
4-
Keep changes minimal, validated, and consistent with existing patterns.
5-
6-
## How to work in this repo
7-
8-
- Prefer surgical edits. Don’t reformat unrelated code. Preserve public APIs unless required.
9-
- Source of truth is the code: read actual files, signatures, and call sites before changing anything.
10-
- Think before acting: understand root causes; don’t treat symptoms.
11-
12-
### Stack constraints (do not drift)
13-
14-
- Ruby 3.4 + Rails 8.1
15-
- No-build frontend: Importmap + tailwindcss-rails (Tailwind v4)
16-
- Use `bin/dev` for local dev (Rails + Tailwind watcher + GoodJob)
17-
- Do not introduce Node/Yarn or JS bundlers
18-
19-
### Commands (run before finishing)
20-
21-
- Tests: `bundle exec rails test` (all) or targeted files
22-
- System tests: `bundle exec rails test:system`
23-
- Coverage: `COVERAGE=1 bundle exec rails test`
24-
- Lint: `bin/rubocop`
25-
- ERB lint: `bundle exec erb_lint --lint-all`
26-
- Assets: `RAILS_ENV=production bin/rails assets:precompile`
27-
- Data: `bin/rails data:plans:seed`
28-
29-
## Boundaries
30-
31-
### Always
32-
33-
- Keep diffs surgical; don’t shuffle files or reformat unrelated code.
34-
- Add/update minimal tests when behavior changes.
35-
- Use i18n keys for user-facing copy (no hardcoded UI strings).
36-
- Keep WebMock enabled in tests; do not allow live HTTP.
37-
38-
### Ask first (high-risk)
39-
40-
- New dependencies (gems / JS packages / tooling).
41-
- Database migrations or schema changes.
42-
- Changes to provider contracts, auth/authorization, billing, or security boundaries.
43-
- Editing Rails credentials / secrets handling.
44-
45-
### Never
46-
47-
- Commit secrets/credentials, master keys, or decrypted values.
48-
- Add Node/Yarn/bundlers to the stack.
49-
- Add debug prints/breakpoints in app/runtime code (`puts`, `pp`, `binding.pry`).
50-
- Exception: intentional CLI output in Rake tasks is acceptable.
51-
52-
## Git commits
53-
54-
- Use Conventional Commits.
55-
- Subject line ≤ 80 chars, imperative, no trailing period.
56-
- Body (when non-trivial): explain **why** and **how** (not what).
57-
- Types: `feat`, `fix`, `refactor`, `chore`, `test`, `docs`.
58-
59-
## Project map (what goes where)
60-
61-
- `app/`: Rails MVC, jobs, services (legacy), views.
62-
- `lib/`: External providers and infra (`lib/providers`, `lib/faraday`, `lib/rack`).
63-
- Data in `lib/data/` (e.g., `plans.yml`). Rake tasks in `lib/tasks/`.
64-
- `lib/providers`: Provider interfaces/results + implementations. Use `Providers::Page`/`Providers::Review` at boundaries.
65-
- Webhook resources via `Providers::Resources` (`:page`, `:review`).
66-
- `test/`: Minitest (`integration/`, `system/`, etc.)
67-
- VCR cassettes: `test/support/cassettes/`
68-
- Webhook captures: `test/support/webhook_captures/`
69-
- Controllers: root under `app/controllers/`; namespaced in matching folders.
70-
- Base classes: `PublicBaseController`, `DashboardBaseController`, `Admin::BaseController`.
71-
72-
## Decision trees (use these defaults)
73-
74-
### 1) You’re touching an Actor/service object
75-
76-
- Is the change trivial (typo, small conditional, tiny refactor)?
77-
- Yes → keep the existing Actor/service shape.
78-
- No → convert the touched service to a PORO as part of the change.
79-
- When converting:
80-
- Prefer explicit constructors + methods (`ThingCreator.new(...).create`)
81-
- Or move behavior onto the relevant model when it naturally belongs there.
82-
83-
### 2) You’re touching a ViewComponent / adding shared UI
84-
85-
- Are you adding new UI/shared UI?
86-
- Yes → use Rails partials under `app/views/shared/` (layout-aware; see below).
87-
- Are you touching an existing ViewComponent?
88-
- Small change → keep it as-is.
89-
- Meaningful change → prefer migrating it to a partial as part of the change.
90-
91-
## Rails conventions
92-
93-
- Routing: keep routes simple and close to Rails defaults.
94-
- Avoid redundant `defaults:` / `controller:` overrides when convention can solve it.
95-
- Avoid `:as` overrides unless strictly necessary.
96-
- Prefer `namespace` + `resources`.
97-
- Prefer REST/CRUD: introduce a resource instead of custom actions.
98-
99-
## Code style (scoped, Fizzy-inspired)
100-
101-
Directional rules. Apply them to **new code** and **methods/files you already touch**.
102-
Do not do churn-only refactors solely to “match style”.
103-
104-
- Prefer expanded conditionals over guard clauses.
105-
- Exception: early return at the very start of a method when the main body is non-trivial.
106-
- Method ordering in classes:
107-
1) class methods
108-
2) public methods (with `initialize` first)
109-
3) private methods
110-
- Order methods vertically by invocation order (top-down flow).
111-
- Only use `!` when there is a corresponding non-`!` method.
112-
- No blank line after visibility modifiers; indent methods under them.
113-
- Thin controllers invoking rich domain APIs; avoid introducing “service artifacts” as glue.
114-
- Jobs should be shallow: enqueue via `*_later`, execute logic in `*_now` / domain methods.
115-
116-
## Fail fast (avoid defensive programming)
117-
118-
We prefer code to break loudly when something unexpected happens.
119-
120-
- Avoid “defensive” checks that mask errors:
121-
- `respond_to?`, `try`, dynamic `send` to avoid using the real API
122-
- broad `rescue` that swallows exceptions
123-
- returning nil/false as a fallback for unexpected states
124-
- Prefer explicit contracts:
125-
- `find_by!` instead of `find_by` when it must exist
126-
- `Hash#fetch` when a key must exist
127-
- Rescue only specific exceptions you truly handle, and keep handling intentional.
128-
129-
## Architecture direction
130-
131-
### Services (Actor gem) → POROs
132-
133-
- Current state: there are service objects using the Actor gem.
134-
- Direction: migrate toward POROs inline with Rails/DHH style.
135-
- Do not add new Actor-based services.
136-
- Use the decision tree above to decide when to migrate.
137-
138-
### Views: ViewComponents → Partials
139-
140-
- Do not add new ViewComponents.
141-
- Prefer Rails partials under `app/views/shared/`.
142-
- Because we have 3 layouts (`public`, `application`, `admin`), prefer layout-aware shared folders:
143-
- `app/views/shared/public/…`
144-
- `app/views/shared/application/…`
145-
- `app/views/shared/admin/…`
146-
- Pass data via `locals`; avoid relying on instance vars in shared partials.
147-
148-
## Coding standards & optimization
149-
150-
- Less is more: prefer less code without sacrificing readability.
151-
- Use clear, intention-revealing names; avoid vague names like `Manager`, `Handler`.
152-
- Performance:
153-
- Avoid N+1 (`includes`, `preload`).
154-
- Use DB constraints/indexes for new query patterns.
155-
- Batch processing for large datasets (`find_each`, `insert_all`).
156-
- Dependencies:
157-
- Do not add new dependencies for trivial tasks.
158-
- Prefer stdlib / Rails built-ins already in the stack.
159-
160-
## UI & Frontend
161-
162-
- Tailwind v4 tokens live in `app/assets/tailwind/application.css`; use theme variables and brand classes.
163-
- Forms baseline: “Labels on left” layout.
164-
- Stimulus-first:
165-
- Prefer controllers from `tailwindcss-stimulus-components` and `@stimulus-components/*`.
166-
- New controllers must be generic, reusable, under `app/javascript/controllers/`, and registered in `controllers/index.js`.
167-
- Copy must follow `docs/brand.md`. Marketing pages must follow `docs/marketing/style-guide.md`.
168-
- No inline styles (`style="..."`). Use Tailwind utilities.
169-
- Prefer semantic HTML and accessibility basics (labels, autocomplete where appropriate).
170-
171-
## Mailers
172-
173-
- Mailer views live under `app/views/mailers/`.
174-
- Shared layout: `app/views/layouts/mailer.html.erb`
175-
- Prefer partials under `app/views/mailers/` for shared sections.
176-
- Use `premailer-rails` to inline styles.
177-
- Set `deliver_later_queue_name` to `:latency_5m` in mailers.
178-
179-
## Testing (must follow)
180-
181-
### What tests should do
182-
183-
- Tests must cover application behavior, not framework behavior.
184-
- Tests must validate observable outcomes (rendered content, redirects, DB changes, enqueued jobs, outbound payloads).
185-
- Avoid:
186-
- tautological tests (re-implementing method logic in the test)
187-
- “Rails works” tests
188-
- status-only assertions with no behavioral check
189-
- heavy stubbing that can’t catch regressions
190-
191-
### Practical rules
192-
193-
- Framework: Minitest. Fixtures only (factories have been removed). Do not add factories.
194-
- Anatomy: setup (optional); exercise; assertions; cleanup (optional). Leave an empty line between sections.
195-
- Controller/system tests must assert status/redirect AND content (selectors/text) or side effects.
196-
- System tests: use `data-test-id` selectors; add `data-test-id` attributes in views when needed.
197-
- Keep review pages small in tests (~10) for speed and deterministic assertions.
198-
199-
### HTTP / VCR / webhooks
200-
201-
- WebMock enabled.
202-
- Outgoing API calls: recorded with VCR (see Project map for cassette location).
203-
- VCR structure mirrors provider/API path segments.
204-
- Filename: `{platform}_{resource}[optional_suffix].yml`.
205-
- Incoming webhooks: do not VCR. Use JSON captures (see Project map).
206-
207-
Canonical pattern (webhook fixture ingestion):
208-
209-
```ruby
210-
test "DataForSEO resolve with async mode" do
211-
VCR.use_cassette("dataforseo/serp_google_maps_task_post/google_maps_page_resolve") do
212-
result = Providers::Dataforseo::GoogleMaps::Page.new(
213-
mode: :async,
214-
recording: VCR.current_cassette.recording?,
215-
platform: :google_maps
216-
).submit(
217-
place_eid: @page.place_eid,
218-
business_name: @location.name,
219-
country: "GB"
220-
)
221-
222-
assert_equal :scheduled, result.status
223-
224-
ExternalTask.create!(
225-
record_uuid: @page.uuid,
226-
record_type: @page.class.name,
227-
provider: Providers::External::DATAFORSEO,
228-
resource: Providers::Resources::PAGE,
229-
eid: result.eid
230-
)
231-
232-
payload = WebhookCapture.read(
233-
provider: Providers::External::DATAFORSEO,
234-
platform: :google_maps,
235-
resource: Providers::Resources::PAGE,
236-
task_eid: result.eid
237-
)
238-
assert_not_nil payload
239-
240-
response = Providers::Dataforseo::GoogleMaps::Page.new(
241-
mode: :async,
242-
platform: :google_maps
243-
).ingest(payload)
244-
245-
assert_equal :ok, response.status
246-
assert_equal "ChIJrVtdwkDzdkgRHgNW25ELRtQ", response.data.place_eid
247-
end
248-
end
3+
Repository-specific rules for code-generation agents. Keep changes minimal,
4+
validated, and aligned with this gem's public API.
5+
6+
## Core Workflow
7+
8+
- Prefer surgical edits. Do not reformat unrelated code or shuffle files.
9+
- Read actual files, signatures, call sites, and tests before changing code.
10+
- Preserve public APIs unless the requested change requires a contract update.
11+
- Keep the gem reusable; do not add host-app-specific routes, models, or copy.
12+
- Add or update focused tests when behavior changes.
13+
- Do not commit secrets, credentials, tokens, or decrypted values.
14+
15+
## Release And Upgrade
16+
17+
- Release changes must preserve existing `CHANGELOG.md` history.
18+
- Use the release task instead of hand-editing version files and tags.
19+
- Changelog pull request references must link to PRs, not issues.
20+
- When a change breaks or changes a public contract, update `UPGRADE.md` in
21+
the same change with explicit host-app migration steps.
22+
- Before finishing release-harness changes, run the focused release tests and
23+
a `git-cliff` smoke check.
24+
25+
## Commit Messages
26+
27+
- Use Conventional Commits: `feat`, `fix`, `docs`, `test`, `refactor`, or
28+
`chore`.
29+
- Keep the subject imperative, specific, and under 72 characters.
30+
- Leave a blank line between the subject and body.
31+
- Write one coherent reason per commit; split unrelated work first.
32+
- Use the body when the reasoning matters. Explain why the change exists,
33+
what approach was taken, and what constraints or side effects matter.
34+
- Wrap body lines at 72 characters so commit hooks and terminal tools stay
35+
readable.
36+
- Avoid vague subjects such as `misc fixes`, `updates`, or `cleanup` unless
37+
the cleanup is the actual scoped purpose.
38+
- Mention verification in the body when it materially helps future readers.

README.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,12 @@ Note: `Gemfile.lock` is intentionally not tracked to avoid conflicts across Ruby
130130

131131
### Git hooks
132132

133-
We use [lefthook](https://lefthook.dev/) with the Ruby [commitlint](https://github.com/arandilopez/commitlint) gem to enforce Conventional Commits on every commit. We also use [Standard Ruby](https://standardrb.com/) to keep code style consistent. CI validates commit messages, Standard Ruby, tests, and git-cliff changelog generation on pull requests and pushes to main/master.
133+
We use [lefthook](https://lefthook.dev/) with the Ruby
134+
[commitlint](https://github.com/arandilopez/commitlint) gem to enforce
135+
Conventional Commits on every commit. We also use
136+
[Standard Ruby](https://standardrb.com/) to keep code style consistent. CI
137+
validates commit messages, Standard Ruby, tests, and git-cliff changelog
138+
generation on pull requests and pushes to main/master.
134139

135140
Run the hook installer once per clone:
136141

@@ -146,11 +151,16 @@ rake install
146151

147152
## Release
148153

149-
Releases are tag-driven and published by GitHub Actions to RubyGems. Local release commands never publish directly.
154+
Releases are tag-driven and published by GitHub Actions to RubyGems.
155+
Local release commands never publish directly.
150156

151-
Install [git-cliff](https://git-cliff.org/) locally before preparing a release. The release task regenerates `CHANGELOG.md` from Conventional Commits.
157+
Install [git-cliff](https://git-cliff.org/) locally before preparing a
158+
release. The release task prepends the next `CHANGELOG.md` section from
159+
Conventional Commits.
152160

153-
Before preparing a release, make sure you are on `main` or `master` with a clean worktree.
161+
Before preparing a release, make sure you are on `main` or `master` with a
162+
clean worktree. If the release contains a breaking public-contract change,
163+
update `UPGRADE.md` with the host-app migration steps first.
154164

155165
Then run one of:
156166

@@ -163,12 +173,13 @@ bundle exec rake 'release:prepare[0.1.0]'
163173

164174
The task will:
165175

166-
1. Regenerate `CHANGELOG.md` with `git-cliff`.
176+
1. Prepend the next `CHANGELOG.md` section with `git-cliff`.
167177
1. Update `lib/git/markdown/version.rb`.
168178
1. Commit the release changes.
169179
1. Create and push the `vX.Y.Z` tag.
170180

171-
The `Release` workflow then runs tests, publishes the gem to RubyGems, and creates the GitHub release from the changelog entry.
181+
The `Release` workflow then runs tests, publishes the gem to RubyGems,
182+
and creates the GitHub release from the changelog entry.
172183

173184
## Contributing
174185

0 commit comments

Comments
 (0)