|
1 | 1 | # AI Agent Playbook |
2 | 2 |
|
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. |
0 commit comments