From c2762dd5691a33aaa7f84a0a4901a5bab7980fc8 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Wed, 13 May 2026 06:16:40 -0400 Subject: [PATCH] feat: add Ruby and Rails rules --- manifests/install-components.json | 18 ++++++++++ rules/README.md | 3 ++ rules/ruby/coding-style.md | 46 ++++++++++++++++++++++++++ rules/ruby/hooks.md | 37 +++++++++++++++++++++ rules/ruby/patterns.md | 44 +++++++++++++++++++++++++ rules/ruby/security.md | 51 +++++++++++++++++++++++++++++ rules/ruby/testing.md | 51 +++++++++++++++++++++++++++++ scripts/lib/install-manifests.js | 3 ++ tests/lib/install-executor.test.js | 2 ++ tests/lib/install-manifests.test.js | 18 ++++++++++ tests/lib/selective-install.test.js | 2 ++ 11 files changed, 275 insertions(+) create mode 100644 rules/ruby/coding-style.md create mode 100644 rules/ruby/hooks.md create mode 100644 rules/ruby/patterns.md create mode 100644 rules/ruby/security.md create mode 100644 rules/ruby/testing.md diff --git a/manifests/install-components.json b/manifests/install-components.json index 9076882b..585e7f61 100644 --- a/manifests/install-components.json +++ b/manifests/install-components.json @@ -243,6 +243,24 @@ "security" ] }, + { + "id": "lang:ruby", + "family": "language", + "description": "Ruby and Rails coding, testing, and security guidance. Resolves through framework-language and security modules.", + "modules": [ + "framework-language", + "security" + ] + }, + { + "id": "framework:rails", + "family": "framework", + "description": "Rails 8 application guidance for MVC, Hotwire, Solid Queue/Cache/Cable, authentication, testing, and security.", + "modules": [ + "framework-language", + "security" + ] + }, { "id": "lang:rust", "family": "language", diff --git a/rules/README.md b/rules/README.md index efac416d..203fe90f 100644 --- a/rules/README.md +++ b/rules/README.md @@ -21,6 +21,7 @@ rules/ ├── web/ # Web and frontend specific ├── swift/ # Swift specific ├── php/ # PHP specific +├── ruby/ # Ruby / Rails specific └── arkts/ # HarmonyOS / ArkTS specific ``` @@ -40,6 +41,7 @@ rules/ ./install.sh web ./install.sh swift ./install.sh php +./install.sh ruby ./install.sh arkts # Install multiple languages at once @@ -66,6 +68,7 @@ cp -r rules/golang ~/.claude/rules/golang cp -r rules/web ~/.claude/rules/web cp -r rules/swift ~/.claude/rules/swift cp -r rules/php ~/.claude/rules/php +cp -r rules/ruby ~/.claude/rules/ruby cp -r rules/arkts ~/.claude/rules/arkts # Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only. diff --git a/rules/ruby/coding-style.md b/rules/ruby/coding-style.md new file mode 100644 index 00000000..39506bbc --- /dev/null +++ b/rules/ruby/coding-style.md @@ -0,0 +1,46 @@ +--- +paths: + - "**/*.rb" + - "**/*.rake" + - "**/Gemfile" + - "**/*.gemspec" + - "**/config.ru" +--- +# Ruby Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Ruby and Rails specific content. + +## Standards + +- Target **Ruby 3.3+** for new Rails work unless the project already pins an older supported runtime. +- Enable **YJIT** in production only after measuring boot time, memory, and request/job throughput. +- Add `# frozen_string_literal: true` to new Ruby files when the project uses that convention. +- Prefer clear Ruby over clever metaprogramming; isolate DSL-heavy code behind narrow, tested boundaries. + +## Formatting And Linting + +- Use the project's checked-in RuboCop config. For Rails 8+ apps, start from `rubocop-rails-omakase` and customize only where the codebase has a real convention. +- Keep formatter/linter commands behind binstubs or scripts so CI and local runs match: + +```bash +bundle exec rubocop +bundle exec rubocop -A +``` + +- Do not silence cops inline unless the exception is narrow, documented, and harder to express cleanly in code. + +## Rails Style + +- Follow Rails naming and directory conventions before adding custom structure. +- Keep controllers transport-focused: authentication, authorization, parameter handling, response shape. +- Put reusable domain behavior in models, concerns, service objects, query objects, or form objects based on actual complexity, not as default ceremony. +- Prefer `bin/rails`, `bin/rake`, and checked-in binstubs over globally installed commands. + +## Error Handling + +- Rescue specific exceptions. Avoid broad `rescue StandardError` blocks unless they re-raise or preserve enough context for operators. +- Use `ActiveSupport::Notifications` or the app's logger for operational events; do not leave `puts`, `pp`, or `debugger` in committed application code. + +## Reference + +See skill: `backend-patterns` for broader service/repository layering guidance. diff --git a/rules/ruby/hooks.md b/rules/ruby/hooks.md new file mode 100644 index 00000000..0415fe61 --- /dev/null +++ b/rules/ruby/hooks.md @@ -0,0 +1,37 @@ +--- +paths: + - "**/*.rb" + - "**/*.rake" + - "**/Gemfile" + - "**/Gemfile.lock" + - "**/config/routes.rb" +--- +# Ruby Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Ruby and Rails specific content. + +## PostToolUse Hooks + +Configure project-local hooks to prefer binstubs and checked-in tooling: + +- **RuboCop**: run `bundle exec rubocop -A ` or the project's safer formatter command after Ruby edits. +- **Brakeman**: run `bundle exec brakeman --no-pager` after security-sensitive Rails changes. +- **Tests**: run the narrowest matching `bin/rails test ...` or `bundle exec rspec ...` command for touched files. +- **Bundler audit**: run `bundle exec bundle-audit check --update` when `Gemfile` or `Gemfile.lock` changes and the project has bundler-audit installed. + +## Warnings + +- Warn on committed `debugger`, `binding.irb`, `binding.pry`, `puts`, `pp`, or `p` calls in application code. +- Warn when an edit disables CSRF protection, expands mass-assignment, or adds raw SQL without parameterization. +- Warn when a migration changes data destructively without a reversible path or documented rollout plan. + +## CI Gate Suggestions + +```bash +bundle exec rubocop +bundle exec brakeman --no-pager +bin/rails test +bundle exec rspec +``` + +Use only the commands that are present in the project; do not install new hook dependencies without maintainer approval. diff --git a/rules/ruby/patterns.md b/rules/ruby/patterns.md new file mode 100644 index 00000000..e4053847 --- /dev/null +++ b/rules/ruby/patterns.md @@ -0,0 +1,44 @@ +--- +paths: + - "**/*.rb" + - "**/*.rake" + - "**/Gemfile" + - "**/app/**/*.erb" + - "**/config/routes.rb" +--- +# Ruby Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Ruby and Rails specific content. + +## Rails Way First + +- Start with plain Rails MVC and Active Record conventions for small and medium features. +- Introduce service objects, query objects, form objects, decorators, or presenters when the model/controller boundary is carrying multiple responsibilities. +- Name extracted objects after the business operation they perform, not after generic layers like `Manager` or `Processor`. + +## Persistence + +- Prefer PostgreSQL for multi-host production Rails apps unless the existing platform has a clear reason for MySQL or SQLite. +- Treat Rails 8 SQLite-backed defaults as viable for single-host or modest deployments, not as an automatic fit for shared multi-service systems. +- Keep raw SQL behind query objects or model scopes and parameterize every dynamic value. + +## Background Jobs And Runtime Services + +- Use **Solid Queue** for greenfield Rails 8 apps with modest throughput and simple deployment needs. +- Use **Sidekiq** when the app needs mature observability, high throughput, existing Redis infrastructure, or Pro/Enterprise features. +- Use **Solid Cache** and **Solid Cable** when their deployment model matches the app; use Redis when shared cross-service behavior, high fanout, or advanced data structures matter. + +## Frontend + +- Prefer **Hotwire** with Turbo, Stimulus, Importmap, and Propshaft for server-rendered Rails apps. +- Use React, Vue, Inertia.js, or a separate SPA when interaction complexity, existing product architecture, or team ownership justifies the extra client surface. +- Keep view components, partials, and presenters focused on rendering decisions; keep persistence and authorization out of templates. + +## Authentication + +- Use the Rails 8 authentication generator for straightforward session auth and password reset needs. +- Use Devise or another established auth system when requirements include OAuth, MFA, confirmable/lockable flows, multi-model auth, or a large existing Devise footprint. + +## Reference + +See skill: `backend-patterns` for service boundaries and adapter patterns. diff --git a/rules/ruby/security.md b/rules/ruby/security.md new file mode 100644 index 00000000..1ecf0645 --- /dev/null +++ b/rules/ruby/security.md @@ -0,0 +1,51 @@ +--- +paths: + - "**/*.rb" + - "**/*.rake" + - "**/Gemfile" + - "**/Gemfile.lock" + - "**/config/routes.rb" + - "**/config/credentials*.yml.enc" +--- +# Ruby Security + +> This file extends [common/security.md](../common/security.md) with Ruby and Rails specific content. + +## Rails Defaults + +- Keep CSRF protection enabled for state-changing browser requests. +- Use strong parameters or typed boundary objects before mass assignment. +- Store secrets in Rails credentials, environment variables, or a secret manager. Never commit plaintext keys, tokens, private credentials, or copied `.env` values. + +## SQL And Active Record + +- Prefer Active Record query APIs and parameterized SQL. +- Never interpolate request, cookie, header, job, or webhook values into SQL strings. +- Scope model callbacks carefully; security-sensitive side effects should be explicit and covered by tests. + +## Authentication And Sessions + +- Use the Rails 8 authentication generator for simple session auth, or Devise when OAuth, MFA, confirmable, lockable, multi-model auth, or existing Devise conventions are required. +- Rotate sessions after sign-in and privilege changes. +- Protect account recovery flows with expiry, single-use tokens, rate limiting, and audit logging. + +## Dependencies + +- Run dependency checks when the lockfile changes: + +```bash +bundle audit check --update +bundle exec brakeman --no-pager +``` + +- Review new gems for maintainer activity, native extension risk, transitive dependencies, and whether the same behavior can be implemented with Rails core. + +## Web Safety + +- Escape template output by default. Treat `html_safe`, `raw`, and custom sanitizers as security-sensitive code. +- Validate file uploads by content type, extension, size, and storage destination. +- Treat background jobs, webhooks, Action Cable messages, and Turbo Stream inputs as untrusted boundaries. + +## Reference + +See skill: `security-review` for secure-by-default review patterns. diff --git a/rules/ruby/testing.md b/rules/ruby/testing.md new file mode 100644 index 00000000..e96a1c9b --- /dev/null +++ b/rules/ruby/testing.md @@ -0,0 +1,51 @@ +--- +paths: + - "**/*.rb" + - "**/*.rake" + - "**/Gemfile" + - "**/test/**/*.rb" + - "**/spec/**/*.rb" + - "**/config/routes.rb" +--- +# Ruby Testing + +> This file extends [common/testing.md](../common/testing.md) with Ruby and Rails specific content. + +## Framework + +- Use **Minitest** when the Rails app follows the default Rails test stack. +- Use **RSpec** when it is already established in the project or the team has explicit production conventions around it. +- Do not mix Minitest and RSpec inside the same feature area without a migration reason. + +## Test Pyramid + +- Put fast domain behavior in model, service, query, policy, and job tests. +- Use request/controller tests for HTTP contracts, auth behavior, redirects, status codes, and response shapes. +- Use system tests with Capybara for browser-critical flows only; keep them focused and stable. +- Cover background jobs with unit tests for behavior and integration tests for queue/enqueue contracts. + +## Fixtures And Factories + +- Use Rails fixtures when they are the project default and the data graph is small. +- Use `factory_bot` when scenarios need explicit object construction or complex traits. +- Keep test data close to the behavior being asserted; avoid global fixtures that hide setup cost. + +## Commands + +Prefer project-local commands: + +```bash +bin/rails test +bin/rails test test/models/user_test.rb +bundle exec rspec +bundle exec rspec spec/models/user_spec.rb +``` + +## Coverage + +- Use SimpleCov when coverage is enforced; keep thresholds in CI and avoid gaming branch coverage with low-value tests. +- Add regression tests for bug fixes before changing production code. + +## Reference + +See skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop. diff --git a/scripts/lib/install-manifests.js b/scripts/lib/install-manifests.js index 2fba6677..c9958b9f 100644 --- a/scripts/lib/install-manifests.js +++ b/scripts/lib/install-manifests.js @@ -51,6 +51,8 @@ const LEGACY_LANGUAGE_ALIAS_TO_CANONICAL = Object.freeze({ perl: 'perl', php: 'php', python: 'python', + rails: 'ruby', + ruby: 'ruby', rust: 'rust', swift: 'swift', typescript: 'typescript', @@ -66,6 +68,7 @@ const LEGACY_LANGUAGE_EXTRA_MODULE_IDS = Object.freeze({ perl: [], php: [], python: ['framework-language'], + ruby: ['framework-language', 'security'], rust: ['framework-language'], swift: [], typescript: ['framework-language'], diff --git a/tests/lib/install-executor.test.js b/tests/lib/install-executor.test.js index e9f5ee75..8b3cdcd6 100644 --- a/tests/lib/install-executor.test.js +++ b/tests/lib/install-executor.test.js @@ -143,6 +143,8 @@ function runTests() { const languages = listAvailableLanguages(sourceRoot); assert.ok(languages.includes('typescript')); + assert.ok(languages.includes('ruby')); + assert.ok(languages.includes('rails')); assert.ok(languages.includes('zig')); assert.ok(!languages.includes('common')); assert.deepStrictEqual([...languages].sort(), languages); diff --git a/tests/lib/install-manifests.test.js b/tests/lib/install-manifests.test.js index 8ba3006e..0db0a312 100644 --- a/tests/lib/install-manifests.test.js +++ b/tests/lib/install-manifests.test.js @@ -176,6 +176,8 @@ function runTests() { assert.ok(languages.includes('golang')); assert.ok(languages.includes('kotlin')); assert.ok(languages.includes('rust')); + assert.ok(languages.includes('ruby')); + assert.ok(languages.includes('rails')); assert.ok(languages.includes('cpp')); assert.ok(languages.includes('c')); assert.ok(languages.includes('csharp')); @@ -432,6 +434,22 @@ function runTests() { 'fsharp should resolve to framework-language module'); })) passed++; else failed++; + if (test('resolves ruby and rails legacy compatibility into framework-language and security modules', () => { + const selection = resolveLegacyCompatibilitySelection({ + target: 'cursor', + legacyLanguages: ['ruby', 'rails'], + }); + + assert.deepStrictEqual(selection.canonicalLegacyLanguages, ['ruby', 'ruby']); + assert.ok(selection.moduleIds.includes('rules-core')); + assert.strictEqual(selection.moduleIds.filter(moduleId => moduleId === 'framework-language').length, 1); + assert.strictEqual(selection.moduleIds.filter(moduleId => moduleId === 'security').length, 1); + assert.ok(selection.moduleIds.includes('framework-language'), + 'ruby should resolve to framework-language module'); + assert.ok(selection.moduleIds.includes('security'), + 'rails alias should add security guidance for Rails apps'); + })) passed++; else failed++; + if (test('keeps antigravity legacy compatibility selections target-safe', () => { const selection = resolveLegacyCompatibilitySelection({ target: 'antigravity', diff --git a/tests/lib/selective-install.test.js b/tests/lib/selective-install.test.js index 226d2af2..97914ced 100644 --- a/tests/lib/selective-install.test.js +++ b/tests/lib/selective-install.test.js @@ -236,6 +236,7 @@ function runTests() { assert.ok(components.some(c => c.id === 'lang:python'), 'Should have lang:python'); assert.ok(components.some(c => c.id === 'lang:go'), 'Should have lang:go'); assert.ok(components.some(c => c.id === 'lang:java'), 'Should have lang:java'); + assert.ok(components.some(c => c.id === 'lang:ruby'), 'Should have lang:ruby'); })) passed++; else failed++; if (test('component catalog includes framework: family entries', () => { @@ -244,6 +245,7 @@ function runTests() { assert.ok(components.some(c => c.id === 'framework:nextjs'), 'Should have framework:nextjs'); assert.ok(components.some(c => c.id === 'framework:django'), 'Should have framework:django'); assert.ok(components.some(c => c.id === 'framework:springboot'), 'Should have framework:springboot'); + assert.ok(components.some(c => c.id === 'framework:rails'), 'Should have framework:rails'); })) passed++; else failed++; if (test('component catalog includes capability: family entries', () => {