Skip to content

thoughtbot agents rules#783

Merged
laicuRoot merged 11 commits into
mainfrom
thoughtbot-agents-rules
Feb 27, 2026
Merged

thoughtbot agents rules#783
laicuRoot merged 11 commits into
mainfrom
thoughtbot-agents-rules

Conversation

@laicuRoot
Copy link
Copy Markdown
Contributor

@laicuRoot laicuRoot commented Feb 18, 2026

Summary

This proposed change for the rules is based on what I've been reading about the CLAUDE.md/agents.md file, rules, skills in their own documentation and my own testing in client and personal projects with good results.

I think these are good defaults that you can add at the start of any project, or even projects that have been running for a while. Obviously each of us has observed different behaviour from our interactions with the models because all our prompts are different. Some of you might feel these rules are a bit too detailed and that's totally fine, but they have worked well across different projects for me. I'd love to hear what has been working for you!

What does good results mean?

  • Reduce the need of prompting that we should follow TDD
  • Continuously applying the rules
  • Despite having other patterns or architecture, flag those as legacy and apply patterns described in rules

Files included

  • Project memory (CLAUDE.md or agents.md): Project architecture, link to rules. Source controlled.
  • Project rules: Language-specific guidelines, testing conventions, and other standards. Source controlled.

Why not skills?

Skills are supposed to be invoked on demand. CLAUDE.md and rules should be for every session. We should keep CLAUDE.md and rules under 500 lines.

Why this project-memory.md format?

The project-memory.md template is designed as a project context file that describe the stack, list common commands and links to the detail rules.

See also "give Codex a map, not a 1,000-page instruction manual."

From Claude documentation chatbot:

Based on the search results, the documentation states:

Keep CLAUDE.md under ~500 lines by including only essentials. This is the ideal length.

The documentation says: "Aim to keep CLAUDE.md under ~500 lines by including only essentials." This applies to CLAUDE.md specifically, and the guidance is to move specialized instructions into skills to keep your base context smaller.

For rules (.claude/rules/*.md), there's no specific line limit mentioned, but since they're also loaded automatically at session start, the same principle applies: keep them concise and focused.*

Test plan

As mentioned above I've been testing this in client and personal projects and I'm happy with the behaviour (with modifications to meet the application differences).

  • Copy template to a Rails project's .claude/CLAUDE.md and make the necessary modifications based on your project
  • Copy rules/ folder to .claude/rules/
  • Verify Claude Code follows thoughtbot conventions when generating code

@laicuRoot laicuRoot self-assigned this Feb 18, 2026
@laicuRoot laicuRoot requested a review from gokcelb February 18, 2026 09:59
@laicuRoot laicuRoot force-pushed the thoughtbot-agents-rules branch 3 times, most recently from 3ee17cb to c2619eb Compare February 18, 2026 10:19
@laicuRoot
Copy link
Copy Markdown
Contributor Author

Example:
image

@laicuRoot
Copy link
Copy Markdown
Contributor Author

Another example from plan mode:
image

Replace single rails-rules.md with a project memory template and
separate rule files. This aligns with Claude Code's .claude/rules/
convention for domain-specific instructions.

The project-memory.md serves as a CLAUDE.md template that developers
copy into their projects, providing:
 - Project context (stack, commands)
 - Architecture principles with links to detailed rules

Rule files are loaded on-demand, reducing context window usage
while keeping detailed guidance available when needed.

See more here: https://code.claude.com/docs/en/memory#determine-memory-type
@laicuRoot laicuRoot force-pushed the thoughtbot-agents-rules branch from c2619eb to dcb5e3c Compare February 18, 2026 16:08
Copy link
Copy Markdown
Contributor

@jaredlt jaredlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOVE this! 🔥

Left a few comments but I think this looks great and something we can start testing and iterating on over time.

Comment thread rails/ai-rules/rules/controllers.md Outdated
Comment thread rails/ai-rules/rules/controllers.md Outdated
Comment thread rails/ai-rules/rules/controllers.md Outdated
Comment thread rails/ai-rules/rules/database.md Outdated
Comment thread rails/ai-rules/rules/database.md Outdated
Comment thread rails/ai-rules/rules/security.md Outdated
Comment thread rails/ai-rules/rules/testing.md Outdated
Comment thread rails/ai-rules/rules/testing.md
- Use presenters to display logic. Instantiate in controller, use in view.
- Extract repeated markup into partials. Pass data via `locals:`, not instance variables.
- Helpers for simple formatting only (dates, currencies). If longer than 5 lines, use a presenter.
- Turbo: return `status: :unprocessable_entity` on failed forms. Keep Stimulus controllers small.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's probably a Hotwire / Stimulus rule file to come in the future :)

Comment thread rails/ai-rules/CLAUDE.md
bin/rails server # Start dev server
bin/rails spec # Full test suite (Suspenders rake task)
bundle exec rspec spec/models # Model specs only
bundle exec rspec spec/requests # Request specs only
Copy link
Copy Markdown
Contributor

@jaredlt jaredlt Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you found it knows how to run tests just for one file or how to run just one test? This can be useful for speed on a big project eg.

bundle exec rspec spec/path/to/file_spec.rb

You can run a specific test by appending the line number (it can be any line number starting from the "it" block of the test) eg. bundle exec rspec spec/path/to/file_spec.rb:72

^ it's a bit wordy...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh! I haven't tried that. It tends to run the whole file for the affected areas.
We can definitely try and add it if we see that it works!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm testing with

bundle exec rspec spec/path/to/file_spec.rb  # Run all tests in file
bundle exec rspec spec/path/to/file_spec.rb:72  # Run just the test at line 72

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude running in cursor tends to run examples or groups with -e "example or context name" by default

https://rspec.info/features/3-12/rspec-core/command-line/example-matches-name-option/

Comment thread rails/ai-rules/rules/database.md
Comment thread rails/ai-rules/rules/testing.md
Co-authored-by: Jared Turner <jared.turner@thoughtbot.com>
@laicuRoot laicuRoot force-pushed the thoughtbot-agents-rules branch from c121640 to bd35016 Compare February 19, 2026 09:07
Co-authored-by: Jared Turner <jared.turner@thoughtbot.com>
@laicuRoot laicuRoot force-pushed the thoughtbot-agents-rules branch from 207a409 to 54e43e0 Compare February 20, 2026 10:48
Copy link
Copy Markdown
Contributor

@gokcelb gokcelb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I really like this setup of the rules files 💯 I found that my client has a similar approach as well.

One thing I'm not sure about is mentioning 'suspenders' or not, could that be confusing for the agent and something extra to think about. I feel like in a mature project, it doesn't make sense that the agent is searching for 'suspenders' and trying to understand what it is.

We don't have to answer this quesiton now but I was also wondering how should we flag the lines in specific rules files mentioning technologies that may or may not be used in client projects, e.g. Turbo or Sidekiq?

Comment thread rails/ai-rules/rules/database.md Outdated
@@ -0,0 +1,11 @@
# Database & Migrations

- Always use the rails generate migration command to create migration files.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still follow strong_migrations guidelines? Should we warn against using safety_assured?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We included now strong_migrations as default in Suspenders.
I haven't seen any output from Claude Code that needed correction related to safety_assured 🤔

@@ -0,0 +1,15 @@
# Testing

- Must use TDD. Write tests first and follow red, green, refactor
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your experience with this red, green, refactor process? How does your iterations look like if you have this process?

I mentioned my TDD workflow in the Hub message as well.

I think my recent use of TDD, even before coding agents, has been to focus on behavior by writing the test cases but I hadn't been doing the red-green process at all.

Maybe this is more a general TDD question about how folks are doing it, so it might be out of scope for this PR.

I do wonder how the agents work with this 3-step TDD process though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my own experience of having this line in my CLAUDE.md, certainly the red > green process works well. I don't know if I've observed or thought too much about the refactor step

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, does the agent stop its editing process twice to go from red -> green -> refactor? Then you review it? Or it just uses this as a coding process for itself, without you reviewing it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does it automatically. You normally step in after the refactor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found that forcing red -> green causes agents to write higher quality tests. It's much harder to accidentally introduce a false positive.

Comment thread rails/ai-rules/rules/views.md Outdated
Comment thread rails/ai-rules/CLAUDE.md
Comment thread rails/ai-rules/rules/database.md Outdated
@laicuRoot
Copy link
Copy Markdown
Contributor Author

Generally I found that making the LLM to enforce TDD without explicitly saying so in the prompt was difficult but it seems that with the rules in place is much better. Leaving another example in plan mode from some testing I did today.

My prompt was just: let's start with the recipe model

Screenshot 2026-02-20 at 11 47 20 image image

laicuRoot and others added 3 commits February 20, 2026 12:19
Co-authored-by: Chad Pytel <chad@thoughtbot.com>
Co-authored-by: Laçin <lacin@thoughtbot.com>
Co-authored-by: Laçin <lacin@thoughtbot.com>
@laicuRoot laicuRoot changed the base branch from ai-rails-rules to main February 24, 2026 12:38
@laicuRoot
Copy link
Copy Markdown
Contributor Author

I'm going to merge this today unless I hear any objection from anyone 👍

Copy link
Copy Markdown
Contributor

@JoelQ JoelQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go ahead and merge! I think this is a strong base we can iterate from

- No service objects. All domain classes live in `app/models/` with namespaces, never `app/services/`.
- Name classes after domain nouns, not actions. No `*Service`, `*Manager`, `*Handler` suffixes.
- Use `ActiveModel::Model` for POROs that need validation or form integration.
- Replace `.call` / `.perform` with domain verbs: `#save`, `#complete`, `#submit`, `#deliver`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just service objects by another name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it can be, if you're not thoughtful about it. Perhaps these rules oversimplify that what really matters is whether the class represents a genuine concept in the domain. That said, they've been useful for me when I'm working with Claude. With these rules Claude stop suggesting yet another service object when there are already plenty in the codebase.

@laicuRoot laicuRoot merged commit 11e9e5b into main Feb 27, 2026
2 checks passed
@laicuRoot laicuRoot deleted the thoughtbot-agents-rules branch February 27, 2026 15:08
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 27, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 27, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 27, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 27, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 27, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.

Finally, we update the Ruby matrix to account for other supported
versions, and by avoiding point releases.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 27, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.

Finally, we update the Ruby matrix to account for other supported
versions, and by avoiding point releases.
- Use presenters to display logic. Instantiate in controller, use in view.
- Extract repeated markup into partials. Pass data via `locals:`, not instance variables.
- Helpers for simple formatting only (dates, currencies). If longer than 5 lines, use a presenter.
- Turbo: return `status: :unprocessable_entity` on failed forms. Keep Stimulus controllers small.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubocop's Rails/HttpStatusNameConsistency recommends usage of :unprocessable_content:

Suggested change
- Turbo: return `status: :unprocessable_entity` on failed forms. Keep Stimulus controllers small.
- Turbo: return `status: :unprocessable_content` on failed forms. Keep Stimulus controllers small.

stevepolitodesign added a commit that referenced this pull request Apr 29, 2026
Follow-up to #783.

First, move the usage section into a dedicated README.

Next, update commands to reflect what's actually supported with
Suspenders and a new Rails application.
stevepolitodesign added a commit that referenced this pull request Apr 29, 2026
Follow-up to #783.

First, move the usage section into a dedicated README.

Next, update commands to reflect what's actually supported with
Suspenders and a new Rails application.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 29, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.
stevepolitodesign added a commit to thoughtbot/suspenders that referenced this pull request Apr 29, 2026
Relates to #1350 and
#1351.

We introduced [thoughtbot agents
rules](thoughtbot/guides#783) to our guides a
few months ago. This commit simply pulls those files into newly
generated Suspenders applications. There's a case to be made that these
should be kept separate, but I like having a single source of truth.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants