rails-ai-context 5.8.1 → 5.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +156 -20
  3. data/README.md +14 -14
  4. data/Rakefile +66 -0
  5. data/demo/demo-trace.tape +2 -2
  6. data/demo/demo.tape +1 -1
  7. data/docs/ARCHITECTURE.md +1 -1
  8. data/docs/CLI.md +2 -2
  9. data/docs/FAQ.md +2 -2
  10. data/docs/GUIDE.md +25 -25
  11. data/docs/QUICKSTART.md +1 -1
  12. data/docs/RECIPES.md +16 -16
  13. data/docs/SETUP.md +5 -4
  14. data/docs/TOOLS.md +1 -1
  15. data/exe/rails-ai-context +137 -2
  16. data/lib/generators/rails_ai_context/install/install_generator.rb +85 -2
  17. data/lib/rails_ai_context/doctor.rb +5 -2
  18. data/lib/rails_ai_context/introspectors/job_introspector.rb +2 -1
  19. data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +3 -3
  20. data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +1 -1
  21. data/lib/rails_ai_context/serializers/claude_serializer.rb +1 -77
  22. data/lib/rails_ai_context/serializers/compact_serializer_helper.rb +81 -0
  23. data/lib/rails_ai_context/serializers/context_file_serializer.rb +15 -34
  24. data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +45 -8
  25. data/lib/rails_ai_context/serializers/section_marker_writer.rb +63 -0
  26. data/lib/rails_ai_context/serializers/stack_overview_helper.rb +3 -3
  27. data/lib/rails_ai_context/serializers/tool_guide_helper.rb +16 -16
  28. data/lib/rails_ai_context/tasks/rails_ai_context.rake +136 -3
  29. data/lib/rails_ai_context/tools/analyze_feature.rb +28 -18
  30. data/lib/rails_ai_context/tools/base_tool.rb +47 -1
  31. data/lib/rails_ai_context/tools/diagnose.rb +7 -7
  32. data/lib/rails_ai_context/tools/generate_test.rb +7 -6
  33. data/lib/rails_ai_context/tools/get_concern.rb +74 -13
  34. data/lib/rails_ai_context/tools/get_context.rb +5 -5
  35. data/lib/rails_ai_context/tools/get_controllers.rb +2 -2
  36. data/lib/rails_ai_context/tools/get_conventions.rb +15 -11
  37. data/lib/rails_ai_context/tools/get_edit_context.rb +2 -2
  38. data/lib/rails_ai_context/tools/get_env.rb +7 -5
  39. data/lib/rails_ai_context/tools/get_helper_methods.rb +21 -12
  40. data/lib/rails_ai_context/tools/get_job_pattern.rb +21 -13
  41. data/lib/rails_ai_context/tools/get_partial_interface.rb +8 -0
  42. data/lib/rails_ai_context/tools/get_routes.rb +1 -1
  43. data/lib/rails_ai_context/tools/get_schema.rb +1 -1
  44. data/lib/rails_ai_context/tools/get_service_pattern.rb +17 -9
  45. data/lib/rails_ai_context/tools/get_stimulus.rb +7 -4
  46. data/lib/rails_ai_context/tools/get_test_info.rb +13 -11
  47. data/lib/rails_ai_context/tools/get_turbo_map.rb +19 -15
  48. data/lib/rails_ai_context/tools/get_view.rb +61 -9
  49. data/lib/rails_ai_context/tools/migration_advisor.rb +1 -1
  50. data/lib/rails_ai_context/tools/onboard.rb +2 -1
  51. data/lib/rails_ai_context/tools/query.rb +29 -1
  52. data/lib/rails_ai_context/tools/read_logs.rb +18 -4
  53. data/lib/rails_ai_context/tools/review_changes.rb +4 -4
  54. data/lib/rails_ai_context/tools/search_code.rb +3 -3
  55. data/lib/rails_ai_context/tools/validate.rb +7 -7
  56. data/lib/rails_ai_context/version.rb +1 -1
  57. data/lib/rails_ai_context/vfs.rb +9 -0
  58. metadata +16 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b8c8f1c7133f9f97b35975be1ecf1485e6fd663867c7fb6ae776af131f972ec
4
- data.tar.gz: 2a4e374b3509f1eca37ddeefa730eb8fc181a7710df616c7a0ab389babce7280
3
+ metadata.gz: 63fa43e2446217a8b226ba3fd59b0345c7e4fec6300acd0ab877354339187dac
4
+ data.tar.gz: 0d12192067351b3e28e1ae8ba16792be31a8e340e356b4b261249345a2295fa3
5
5
  SHA512:
6
- metadata.gz: a6c3bed1bd469fbee71f6395ad2f7ac0fc90347fdf2bf8066f36f8396a99a915f5954aa207207cabf9b073c435e2fbe7849692e28ba2c0ab752ef516cc8b32e0
7
- data.tar.gz: 16791ed1aa9293f5d87d61bd1a63c3fca990cdf25da6f06639ba514912e097f69a177f9e829b14684fbe2242b8ab53f4828f5b2ad7c15f485a1e5c2493152c4b
6
+ metadata.gz: 6d3f4378d84b890464cdda8c550195430987c05970b892184bda610fb727f7614d943b46141e4b3f7b39642503deacf71b76fca933923aada849e1c39f887fef
7
+ data.tar.gz: 4632cbf34995fb74922c19f89988d7bd4a5dfb3cdac98ab78e8ab3d08bac11f84c3cab0df68364b46740c133fdda22bd7311400dd5381d857b611cb4de2fe6e7
data/CHANGELOG.md CHANGED
@@ -5,6 +5,142 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.9.1] — 2026-04-20
9
+
10
+ ### Fixed — `GetConcern` missed plural concern names (#78)
11
+
12
+ Thanks to [@johan--](https://github.com/johan--) for the report and fix.
13
+
14
+ `rails_get_concern`'s includer search built its include-pattern regex via `String#classify`, which singularizes its input. Concerns with intentionally plural module names — `WorksheetImports`, `PaperTrailEvents`, `SoftDeletables`, etc. — got demodulized to `WorksheetImport` and the `include WorksheetImports` line in the model never matched. The tool reported no includers even when the concern was in use.
15
+
16
+ Switched to `String#camelize`, which normalizes case (so lowercase input like `plan_limitable` → `PlanLimitable` still works) **without** singularizing. This also restores consistency with the three other `.camelize` calls already used in `get_concern.rb` for the same "file basename / module name → class name" conversion. Covered by a new spec in `get_concern_spec.rb` that exercises the plural-name case end-to-end.
17
+
18
+ ### Fixed — internal invariant compliance
19
+
20
+ - **`validate.rb` now routes all Prism parses through `AstCache`.** The Ruby syntax validator was calling `Prism.parse_file` directly and the ERB + semantic-visitor paths `Prism.parse` on string input, bypassing the cache entirely for the first and violating the "all Prism parses must flow through `RailsAiContext::AstCache`" invariant (`.results/3-identify-architecture.json:33`) for all three. Now uses `AstCache.parse(path)` for on-disk sources (picking up the existing size-cap + content-hash caching) and `AstCache.parse_string(source)` for synthetic strings. Note: `AstCache.parse` enforces a 5 MB `MAX_PARSE_SIZE` cap; Ruby files above that size fall through the existing `rescue` to the `ruby -c` subprocess validator, which returns errors but not Prism warnings — a graceful degradation that affects only pathologically large source files.
21
+ - **`Listeners::BaseListener` uses `Confidence::INFERRED` constant.** Replaced three hardcoded `"[INFERRED]"` literals in `extract_first_symbol`, `extract_key`, and `extract_value` with `RailsAiContext::Confidence::INFERRED`. Value is identical; constant reference prevents drift if the marker string is ever versioned.
22
+ - **Diagnostic `$stderr.puts` in `rescue` blocks now `ENV["DEBUG"]`-gated.** 12 previously-unconditional stderr writes across `tools/diagnose.rb` (5), `tools/review_changes.rb` (4), and `serializers/stack_overview_helper.rb` (3) were logging under normal operation whenever an optional context-enrichment step failed. These were never visible to most users but polluted stderr in MCP/CLI logs. Now silent unless `DEBUG=1`, matching the convention used everywhere else in the gem.
23
+
24
+ ### Added
25
+
26
+ - **Prism-discipline regression spec** (`spec/lib/rails_ai_context/ast_cache_discipline_spec.rb`). Scans every `lib/**/*.rb` file (excluding `ast_cache.rb`) for direct `Prism.parse` / `Prism.parse_file` / `Prism.parse_string` calls and fails if any are found. Prevents re-introduction of the bypass that `validate.rb` had.
27
+
28
+ ## [5.9.0] — 2026-04-16
29
+
30
+ ### Fixed — Cursor chat agent didn't detect rules
31
+
32
+ Real user report during release QA: the Cursor IDE's chat agent didn't pick up rules written only as `.cursor/rules/*.mdc`, even when the rule declared `alwaysApply: true`. Cursor has **two** rule systems and the chat-agent composition path still consults the legacy `.cursorrules` file in many current builds.
33
+
34
+ `CursorRulesSerializer` now writes **both**: `.cursor/rules/*.mdc` (newer format with frontmatter / glob scoping / agent-requested triggers) AND a plain-text `.cursorrules` at the project root (legacy fallback, parsed verbatim by every Cursor build). Newer clients read the mdc files; older / chat-mode clients read `.cursorrules`. No behavior change for users who already relied on the mdc format.
35
+
36
+ The `.cursorrules` content goes through the **same** `CompactSerializerHelper#render_compact_rules` pipeline as `CLAUDE.md`, so both files convey identical project context — header, stack overview, key models, gems, architecture, commands, rules, and the MCP tool guide. Drift between the two files is no longer possible short of a manual divergence (regression spec enforces parity).
37
+
38
+ `.cursorrules` is **wrapped in `<!-- BEGIN/END rails-ai-context -->` markers** via the new `SectionMarkerWriter` module — same convention as `CLAUDE.md`, `AGENTS.md`, and `.github/copilot-instructions.md`. Pre-existing user content above or below the marker block survives every `rails ai:context` regeneration. Three regression specs cover the three branches: no-file → write markers; existing-without-markers → prepend gem block; existing-with-markers → replace only the gem block.
39
+
40
+ `FORMAT_PATHS[:cursor]` in the install generator now includes `.cursorrules` so re-install cleanup covers both files when a user removes Cursor from their selection. Regression specs added in `cursor_rules_serializer_spec.rb` and `in_gemfile_install_spec.rb` (e2e) verify both files are produced and the legacy file is plain text without frontmatter.
41
+
42
+ ### Fixed — Round 3 follow-ups (post-quad-agent review)
43
+
44
+ - **`safe_glob_realpath` rescue widened.** Previously rescued only `Errno::ENOENT` and `Errno::EACCES`. Circular symlink chains (think `node_modules/@scope/*` cycles or developer-crafted loops) raise `Errno::ELOOP`; path components exceeding `NAME_MAX` raise `Errno::ENAMETOOLONG`. Both now rescued — return `nil` to skip the entry — preserving the CLAUDE.md invariant that every introspector wraps errors.
45
+
46
+ - **Install generator `CONFIG_SECTIONS` gained 3 sections.** Several user-facing config options existed in `Configuration::YAML_KEYS` but had no commented-out template line in the generated `config/initializers/rails_ai_context.rb`. Added "Database Query Tool" (`query_timeout`, `query_row_limit`, `query_redacted_columns`, `allow_query_in_production`), "Log Reading" (`log_lines`), and "Hydration" (`hydration_enabled`, `hydration_max_hints`) sections so in-Gemfile installs surface every supported knob.
47
+
48
+ ### Added
49
+
50
+ - **`preset` command** — composite multi-tool workflows from CLI and rake. `rails ai:preset[architecture]` runs `analyze_feature` + `dependency_graph` + `performance_check` in one call. Also: `debugging` (logs + review + validate) and `migration` (schema + migration_advisor + validate). Available via both `rails-ai-context preset architecture` and `rails 'ai:preset[architecture]'`.
51
+
52
+ - **`facts` command** — concise schema facts summary. `rails ai:facts` / `rails-ai-context facts` prints tables with column/index/FK counts, model associations, key dependencies, and architecture patterns. Single command replaces 3+ MCP tool calls for quick context loading.
53
+
54
+ - **Validation pre-commit hook** — optional during `rails generate rails_ai_context:install`. Prompts to install a `.git/hooks/pre-commit` hook that runs `rails ai:tool[validate]` on staged `.rb` and `.erb` files. Catches hallucinated columns and schema drift before commit. Respects existing hooks and `--no-verify`.
55
+
56
+ ### Added — E2E harness (`spec/e2e/`)
57
+
58
+ Real `rails new` → install → exercise → teardown against a fresh Rails application in a tmpdir. Covers the three install paths documented in CLAUDE.md #36, every CLI tool, the install generator, all 5 AI-client config files, and the MCP JSON-RPC protocol over both stdio and HTTP transports. Excluded from the default `rspec` run — opt-in via `E2E=1` or the new rake tasks.
59
+
60
+ - **`spec/e2e/in_gemfile_install_spec.rb`** — Path A (Gemfile entry + `rails generate rails_ai_context:install`). Verifies generator idempotency, per-AI-client config file validity, every built-in tool callable via both `bin/rails ai:tool[name]` and `bundle exec rails-ai-context tool name`, plus the `version`/`doctor`/`inspect` subcommands.
61
+
62
+ - **`spec/e2e/standalone_install_spec.rb`** — Path B (`gem install rails-ai-context` into an isolated GEM_HOME, no Gemfile entry, then `rails-ai-context init`). Verifies the Bundler-stripped `$LOAD_PATH` restoration logic described in CLAUDE.md #33 actually works on a real app.
63
+
64
+ - **`spec/e2e/zero_config_install_spec.rb`** — Path C (gem install, no init, no generator). Verifies the CLI works from pure defaults against any Rails app without any project-side setup.
65
+
66
+ - **`spec/e2e/mcp_stdio_protocol_spec.rb`** — spawns `rails-ai-context serve` as a subprocess and walks the full JSON-RPC 2.0 handshake: `initialize` → `notifications/initialized` → `tools/list` → `tools/call`. Verifies every registered built-in tool is advertised in `tools/list` with a rails_-prefixed name, description, and inputSchema.
67
+
68
+ - **`spec/e2e/mcp_http_protocol_spec.rb`** — spawns `rails-ai-context serve --transport http` on a random free port and sends `Net::HTTP` POST requests with JSON-RPC payloads. Verifies the HTTP transport returns the same tool registry and tool-call responses as stdio. Handles the Streamable HTTP requirements: `Accept: application/json, text/event-stream` header + `Mcp-Session-Id` round-trip from initialize.
69
+
70
+ - **`spec/e2e/empty_app_spec.rb`** — every built-in tool must handle a Rails app with no scaffolds, no models, no custom routes. Catches "tool crashes when introspecting an empty greenfield app" — the moment a developer is most likely to install rails-ai-context.
71
+
72
+ - **`spec/e2e/tool_edge_cases_spec.rb`** — malformed CLI inputs: unknown tool name, unknown parameter, missing required parameter, oversized string (10 KB), invalid enum value, fuzzy-match recovery, nonexistent target. Each case must produce structured user-facing errors, never an unhandled exception or signal.
73
+
74
+ - **`spec/e2e/concurrent_mcp_spec.rb`** — two parallel `rails-ai-context serve` subprocesses against the same Rails app. Verifies independent initialize responses, identical tool registries, and that simultaneous `tools/call` invocations don't cross-talk (response id matches request id per client).
75
+
76
+ - **`spec/e2e/postgres_install_spec.rb`** — Postgres adapter coverage for the `rails_query` tool's adapter-specific code paths: `SET TRANSACTION READ ONLY`, `BLOCKED_FUNCTIONS` regex against `pg_read_file`, `dblink`, `COPY ... PROGRAM`, and DDL rejection. Skipped locally unless `TEST_POSTGRES=1`; runs unconditionally in CI which spins up a Postgres 16 service container.
77
+
78
+ - **`spec/e2e/massive_app_spec.rb`** — 1500-model stress test. Programmatically generates a single migration with 1500 `create_table` statements and 1500 corresponding `ApplicationRecord` subclass files (rails-g-scaffold × 1500 would take 30+ min; direct file writes take seconds). Runs representative tools (`schema`, `model_details`, `routes`, `context`, `onboard`, `analyze_feature`, `get_turbo_map`, `get_env`) against the massive fixture and asserts: no signal, exit < 2, stdout non-empty, response size < 2 MB (tools must truncate — uncapped output overwhelms AI client context). Also verifies `rails_get_schema --table thing_0750s` finds a table in the middle of the range, proving schema introspection walks beyond the first page.
79
+
80
+ Rake tasks: `bundle exec rake e2e` (full), `rake e2e:in_gemfile`, `rake e2e:standalone`, `rake e2e:zero_config`, `rake e2e:mcp`.
81
+
82
+ CI: `.github/workflows/e2e.yml` runs on push to main + workflow_dispatch (separate from `ci.yml` so the 30-min job doesn't fail-stop the per-commit matrix). Matrix covers Ruby 3.3 + 3.4 across Rails 7.1, 7.2, 8.0, 8.1, and includes a Postgres 16 service container so the SQL-query and adapter-specific code paths are exercised on every push.
83
+
84
+ ### Fixed — Security Hardening (Round 3)
85
+
86
+ Pre-release audit of **every** `Dir.glob` call site across the 38 tools. The 5-rule file-read pattern documented in CLAUDE.md was enforced on caller-supplied paths, but glob-sourced paths were reading file content without the same hardening. A symlink pre-planted inside `app/services/`, `app/jobs/`, `app/helpers/`, `app/models/`, `app/controllers/`, `app/views/`, or `app/` (pointing at `config/master.key`) would have leaked secret contents through tool output.
87
+
88
+ - **`BaseTool.safe_glob_realpath` + `BaseTool.safe_glob`** added as shared helpers. Every glob-sourced file read now passes through this filter: realpath + separator-aware containment + `sensitive_file?` recheck on the realpath. Broken symlinks, sibling-directory bypasses, and sensitive-pattern matches return `nil` and are skipped.
89
+
90
+ - **`get_service_pattern`, `get_job_pattern`, `get_helper_methods`** — glob+read on `app/services/`, `app/jobs/`, `app/helpers/` plus nested `find_callers` / `find_enqueuers` / `find_view_references` / `detect_framework_helpers`. All hardened.
91
+
92
+ - **`analyze_feature`** — 10 glob sites across `discover_services`, `discover_jobs`, `discover_views`, `discover_tests`, `discover_test_gaps`, `discover_channels`, `discover_mailers`, `discover_env_dependencies`. All hardened.
93
+
94
+ - **`get_conventions`** — glob+read on controllers (convention detection), services (listing), locales, controllers (UI-language detection), tests (pattern detection). All hardened.
95
+
96
+ - **`get_turbo_map`** — glob+read on models, controllers/services/jobs/channels, and two view scans. All hardened.
97
+
98
+ - **`get_env`** — glob+read on `app/config/lib` for ENV scans, `app/` for HTTP-client detection, and `app/config/lib` for prefix-matched ENV vars. All hardened. Removed redundant pre-realpath `sensitive_file?` now that `safe_glob` checks post-realpath.
99
+
100
+ - **`get_test_info`** — glob+read on `test/**/*_test.rb` for Devise detection, `test/fixtures` and `spec/fixtures` for fixture parsing. Hardened.
101
+
102
+ - **`generate_test`** — glob+read on `spec/**/*_spec.rb` or `test/**/*_test.rb` for pattern detection. Hardened.
103
+
104
+ - **`get_stimulus`** — glob+read on `app/views/**/*.{erb,html.erb}` for `data-controller` usage. Hardened.
105
+
106
+ - **`onboard`** — glob of `app/services/` for service name extraction (basename only). Hardened for consistency even though no content is read.
107
+
108
+ - **`search_code`** (ruby-fallback path) — the pre-realpath `sensitive_file?` check did not catch a symlink `app/models/innocent.rb → config/master.key` because the relative path looked safe. Now goes through `safe_glob` which rechecks on the realpath.
109
+
110
+ - **`job_introspector.rb:205`** — bare `rescue` (catching `Exception`, including `Interrupt`/`SystemExit`) replaced with project-standard `rescue => e` + DEBUG logging guard.
111
+
112
+ - **14 new regression specs** covering every newly-hardened tool with a symlink-to-master.key PoC + 5 edge cases for the `safe_glob_realpath` helper (sibling bypass, broken symlinks, sensitive realpath, separator awareness, in-tree passthrough).
113
+
114
+ ### Fixed — Security Hardening (Round 2)
115
+
116
+ Eleven additional vulnerabilities and defense-in-depth gaps found by multi-round adversarial code review. All discovered post-v5.8.1 — **users on 5.8.x should upgrade**.
117
+
118
+ - **MySQL executable-comment bypass of `BLOCKED_FUNCTIONS`.** `strip_sql_comments` stripped `/*! ... */` (MySQL version-conditional comments) along with regular block comments. MySQL *executes* content inside `/*! ... */`, so `SELECT /*!50000 LOAD_FILE('/etc/passwd') */ AS x` passed all validation. **Fix:** unwraps executable comments (preserves inner content for checker visibility) before the block-comment strip. Belt-and-suspenders: also runs `BLOCKED_FUNCTIONS` against the raw SQL before any stripping.
119
+
120
+ - **`execute_explain` bypassed READ ONLY transaction and statement timeout.** The EXPLAIN path called `conn.select_all(explain_sql)` directly instead of routing through `execute_postgresql`/`execute_mysql`/`execute_sqlite`. PostgreSQL `EXPLAIN (FORMAT JSON, ANALYZE)` actually executes the query plan — an attacker could hold a DB connection indefinitely and bypass the read-only guard. **Fix:** routes through adapter-specific safety wrappers.
121
+
122
+ - **`read_logs` C1 sibling-directory bypass.** Bare `real.start_with?(File.realpath(root))` matched `/var/app/myapp_evil` against `/var/app/myapp`. **Fix:** separator-aware containment (`real == base || real.start_with?(base + File::SEPARATOR)`).
123
+
124
+ - **`read_logs` TOCTOU window.** Resolved the realpath for the containment check, then opened the original `path` for reading. Symlink swap between check and open leaked arbitrary files. **Fix:** returns and reads from the realpath.
125
+
126
+ - **`read_logs` missing post-realpath sensitive recheck.** A symlink `log/credentials.log -> ../config/master.key` resolves to a path still under Rails.root, passing containment. Without `sensitive_file?` on the realpath, `tail_file` read the secret. **Fix:** added post-realpath `sensitive_file?` recheck.
127
+
128
+ - **VFS `resolve_view` existence-oracle side channel.** `File.exist?` ran before `sensitive_file?`, so two distinct error messages ("View not found" vs "sensitive file") revealed whether `.env` / `master.key` existed inside `app/views/`. **Fix:** early `sensitive_file?` check before any filesystem stat.
129
+
130
+ - **`get_partial_interface` existence-oracle side channel.** `candidates.find { |c| File.exist?(c) }` stat'd candidates before any sensitive check on the caller-supplied `partial` string. Same oracle as the VFS fix. **Fix:** early `sensitive_file?` check in `call`.
131
+
132
+ - **`get_view` `list_layouts` missing all security rules.** Iterated `Dir.glob` results with no realpath containment, no sensitive recheck, and no size gate. A symlink `layouts/leak.key -> ../../config/master.key` leaked secrets in the `full` detail branch. **Fix:** full 5-rule file-reading pattern per file.
133
+
134
+ - **`get_view` `read_view_content` missing all security rules.** Called `SafeFile.read` after a bare `File.exist?` with no containment, no sensitive recheck, and no size cap. **Fix:** full 5-rule file-reading pattern with `max_file_size` gate.
135
+
136
+ - **`get_concern` `show_concern` path traversal.** `name.underscore` does not sanitize `../`, so `name: "../../config/initializers/devise"` read arbitrary `.rb` files under Rails.root. The proposed fix in the IDOR variant would have been a security downgrade. **Fix:** early traversal/null-byte/absolute-path rejection, early `sensitive_file?`, per-candidate realpath + separator containment, post-realpath sensitive recheck.
137
+
138
+ - **`get_concern` `list_concerns` symlink escape.** `Dir.glob` results passed to `SafeFile.read` with no realpath containment or sensitive recheck. **Fix:** per-file 5-rule pattern.
139
+
140
+ ### Changed
141
+
142
+ - All documentation examples, tool descriptions, code comments, and test fixtures now use generic Rails terminology (`PostsController`, `publishable?`, `posts/index.html.erb`) instead of app-specific references. Affects README, GUIDE, CLI, RECIPES docs, tool_guide_helper serializer, 6 MCP tool description strings, CHANGELOG, demo scripts, and 3 spec files.
143
+
8
144
  ## [5.8.1] — 2026-04-15
9
145
 
10
146
  ### Fixed — Security Hardening
@@ -628,11 +764,11 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
628
764
  - `analyze_feature` with nonexistent feature — returns clean "no match" instead of scaffolded empty sections
629
765
  - `migration_advisor` crash on empty/invalid action — now validates with "Did you mean?" suggestions
630
766
  - `migration_advisor` generates broken SQL with empty table/column — now validates required params
631
- - `migration_advisor` doesn't normalize table names — "Cook" now auto-resolves to "cooks"
767
+ - `migration_advisor` doesn't normalize table names — "Post" now auto-resolves to "posts"
632
768
  - `migration_advisor` no duplicate column/index detection — now warns on existing columns, indexes, and FKs
633
769
  - `migration_advisor` no nonexistent column detection — now warns on remove/rename/change_type/add_index for missing columns
634
770
  - `edit_context` "File not found" with no hint — now suggests full path with "Did you mean?"
635
- - `performance_check` model filter fails for multi-word models — "BrandProfile" now resolves to "brand_profiles"
771
+ - `performance_check` model filter fails for multi-word models — "UserProfile" now resolves to "user_profiles"
636
772
  - `performance_check` unknown model silently ignored — now returns "not found" with suggestions
637
773
  - `turbo_map` stream filter misses dynamic broadcasts — multi-line call handling + snippet fallback + fuzzy prefix matching
638
774
  - `turbo_map` controller filter misses job broadcasts — now includes broadcasts matching filtered subscriptions' streams
@@ -640,7 +776,7 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
640
776
  - `search_code` unknown match_type silently ignored — now returns error with valid values
641
777
  - `validate` unknown level silently ignored — now returns error with valid values
642
778
  - `get_view` no "Did you mean?" on wrong controller — now uses `find_closest_match`
643
- - `get_context` plural model name ("Cooks") produces mixed output — now normalizes via singularize/classify, fails fast when not found
779
+ - `get_context` plural model name ("Posts") produces mixed output — now normalizes via singularize/classify, fails fast when not found
644
780
  - `component_catalog` specific component returns generic "no components" — now acknowledges the input
645
781
  - `stimulus` doesn't strip `_controller` suffix — now auto-strips for lookup
646
782
  - `controller_introspector_spec` rate_limit test crashes on Rails 7.1 — split into source-parsing test (no class loading)
@@ -694,15 +830,15 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
694
830
  ### Fixed
695
831
 
696
832
  - **Consistent input normalization across all tools** — AI agents and humans can now use any casing or format and tools resolve correctly:
697
- - `model=brand_profile` (snake_case) now resolves to `BrandProfile` via `.underscore` comparison in `get_model_details`.
698
- - `table=Cook` (model name) now resolves to `cooks` table via `.underscore.pluralize` normalization in `get_schema`.
699
- - `controller=CooksController` now works in `get_view` and `get_routes` — both strip `Controller`/`_controller` suffix consistently, matching `get_controllers` behavior.
700
- - `controller=cooks_controller` no longer leaves a trailing underscore in route matching.
701
- - `stimulus=CookStatus` (PascalCase) now resolves to `cook_status` via `.underscore` conversion in `get_stimulus`.
833
+ - `model=user_profile` (snake_case) now resolves to `UserProfile` via `.underscore` comparison in `get_model_details`.
834
+ - `table=Post` (model name) now resolves to `posts` table via `.underscore.pluralize` normalization in `get_schema`.
835
+ - `controller=PostsController` now works in `get_view` and `get_routes` — both strip `Controller`/`_controller` suffix consistently, matching `get_controllers` behavior.
836
+ - `controller=posts_controller` no longer leaves a trailing underscore in route matching.
837
+ - `stimulus=PostStatus` (PascalCase) now resolves to `post_status` via `.underscore` conversion in `get_stimulus`.
702
838
  - `partial=_status_badge` (underscore-prefixed, no directory) now searches recursively across all view directories in `get_partial_interface`.
703
- - `model=cooks` (plural) now tries `.singularize` for test file lookup in `get_test_info`.
704
- - **Smarter fuzzy matching** — `BaseTool.find_closest_match` now prefers shortest substring match (so `Cook` suggests `cooks`, not `cook_comments`) and supports underscore/classify variant matching.
705
- - **File path suggestions in validate** — `files=["cook.rb"]` now suggests `app/models/cook.rb` when the file isn't found at the given path.
839
+ - `model=posts` (plural) now tries `.singularize` for test file lookup in `get_test_info`.
840
+ - **Smarter fuzzy matching** — `BaseTool.find_closest_match` now prefers shortest substring match (so `Post` suggests `posts`, not `post_comments`) and supports underscore/classify variant matching.
841
+ - **File path suggestions in validate** — `files=["post.rb"]` now suggests `app/models/post.rb` when the file isn't found at the given path.
706
842
  - **Empty parameter validation** — `edit_context` now returns friendly messages for empty `file` or `near` parameters instead of hard errors.
707
843
 
708
844
  ## [3.0.1] — 2026-03-26
@@ -748,10 +884,10 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
748
884
 
749
885
  ### Added
750
886
 
751
- - **Orphaned table detection** — `get_schema` standard mode flags tables with no ActiveRecord model: "⚠ Orphaned tables: content_calendars, cook_comments"
887
+ - **Orphaned table detection** — `get_schema` standard mode flags tables with no ActiveRecord model: "⚠ Orphaned tables: content_calendars, post_comments"
752
888
  - **Concern method source code** — `get_concern(name:"X", detail:"full")` shows method bodies inline, same pattern as callbacks tool.
753
889
  - **analyze_feature: inherited filters** — shows `authenticate_user! (from ApplicationController)` in controller section.
754
- - **analyze_feature: code-ready route helpers** — `cook_path(@record)`, `cooks_path` inline with routes.
890
+ - **analyze_feature: code-ready route helpers** — `post_path(@record)`, `posts_path` inline with routes.
755
891
  - **analyze_feature: service test gaps** — checks services for missing test files, not just models/controllers/jobs.
756
892
  - **All 6 serializers updated** — Claude, Cursor, Copilot, Windsurf, OpenCode all document trace mode, concern source, orphaned tables, inherited filters.
757
893
 
@@ -875,9 +1011,9 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
875
1011
  - **Empty string defaults shown as `""`** — schema tool now renders `""` instead of a blank cell for empty string defaults. AI can distinguish "no default" from "empty string default".
876
1012
  - **Implicit belongs_to validations labeled** — `presence on user` from `belongs_to :user` now shows `_(implicit from belongs_to)_` and filters phantom `(message: required)` options.
877
1013
  - **Array columns shown as `type[]`** in generated rules — `string` columns with `array: true` now render as `string[]` in schema rules.
878
- - **External ID columns no longer hidden** — columns like `paymongo_checkout_id` and `stripe_payment_id` are now shown in schema rules. Only conventional Rails FK columns (matching a table name) are filtered.
1014
+ - **External ID columns no longer hidden** — columns like `stripe_checkout_id` and `stripe_payment_id` are now shown in schema rules. Only conventional Rails FK columns (matching a table name) are filtered.
879
1015
  - **Column defaults shown in generated rules** — columns with non-nil defaults now show `(=value)` inline.
880
- - **`analyze_feature` matches models by table name and underscore form** — `feature:"share"` now finds `CookShare` (via `cook_shares` table and `cook_share` underscore form), not just exact model name substring.
1016
+ - **`analyze_feature` matches models by table name and underscore form** — `feature:"share"` now finds `PostShare` (via `post_shares` table and `post_share` underscore form), not just exact model name substring.
881
1017
 
882
1018
  ## [1.2.0] — 2026-03-23
883
1019
 
@@ -966,7 +1102,7 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
966
1102
 
967
1103
  - **Schema defaults always visible** — Null and Default columns always shown (NOT NULL marked bold). Previous token-saving logic accidentally hid critical migration data.
968
1104
  - **Optional associations** — `belongs_to` with `optional: true` now shows `[optional]` flag.
969
- - **Concern methods inline** — shows public methods from concern source files (e.g. `PlanLimitablecan_cook?, increment_cook_count!`).
1105
+ - **Concern methods inline** — shows public methods from concern source files (e.g. `Publishablepublishable?, publish!`).
970
1106
  - **MCP tool error messages** — all tools now show available values on error/not-found for AI self-correction.
971
1107
 
972
1108
  ## [0.15.5] — 2026-03-22
@@ -986,8 +1122,8 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
986
1122
 
987
1123
  ### Fixed
988
1124
 
989
- - **View subfolder paths** — listings now show full relative paths (`bonus/brand_profiles/index.html.erb`) instead of just basenames.
990
- - **Controller flexible matching** — `"cooks"`, `"CooksController"`, `"cookscontroller"` all resolve (matches other tools' forgiving lookup).
1125
+ - **View subfolder paths** — listings now show full relative paths (`admin/comments/index.html.erb`) instead of just basenames.
1126
+ - **Controller flexible matching** — `"posts"`, `"PostsController"`, `"postscontroller"` all resolve (matches other tools' forgiving lookup).
991
1127
  - **View path traversal** — explicit `..` and absolute path rejection before any filesystem operation.
992
1128
  - **Schema case-insensitive** — table lookup now case-insensitive (matches models/routes/etc.).
993
1129
  - **limit:0 silent empty** — uses default instead of returning empty results.
@@ -1080,7 +1216,7 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
1080
1216
  - **`app_only` filter for routes** — `rails_get_routes(app_only: true)` (default) hides internal Rails routes (Active Storage, Action Mailbox, Conductor).
1081
1217
  - **Search context lines** — `rails_search_code(context_lines: 2)` adds surrounding lines to matches (passes `-C` to ripgrep).
1082
1218
  - **Stimulus dash/underscore normalization** — Both `weekly-chart` and `weekly_chart` work for controller lookup. Output shows HTML `data-controller` attribute.
1083
- - **Model public method signatures** — `rails_get_model_details(model: "Cook")` shows method names with params from source, stopping at private boundary.
1219
+ - **Model public method signatures** — `rails_get_model_details(model: "Post")` shows method names with params from source, stopping at private boundary.
1084
1220
 
1085
1221
  ## [0.13.1] — 2026-03-20
1086
1222
 
@@ -1118,7 +1254,7 @@ AI assistants that consume pre-digested summaries produce worse output than AI t
1118
1254
  - **Design Token Introspector** — auto-detects CSS framework and extracts tokens from Tailwind v3/v4, Bootstrap/Sass, plain CSS custom properties, Webpacker-era stylesheets, and ViewComponent sidecar CSS. Tested across 8 CSS setups. Added to standard preset.
1119
1255
  - **`rails_get_edit_context` MCP tool** — purpose-built for surgical edits. Returns code around a match point with line numbers. Replaces the Read + Edit workflow with a single call.
1120
1256
  - **Line numbers in action source** — `rails_get_controllers(action: "index")` now returns start/end line numbers for targeted editing.
1121
- - **Model file structure** — `rails_get_model_details(model: "Cook")` now returns line ranges for each section (associations, validations, scopes, etc.).
1257
+ - **Model file structure** — `rails_get_model_details(model: "Post")` now returns line ranges for each section (associations, validations, scopes, etc.).
1122
1258
 
1123
1259
  ### Changed
1124
1260
 
data/README.md CHANGED
@@ -21,7 +21,7 @@
21
21
  <br>
22
22
  [![Ruby](https://img.shields.io/badge/Ruby-3.2%20%7C%203.3%20%7C%203.4-CC342D)](https://github.com/crisnahine/rails-ai-context)
23
23
  [![Rails](https://img.shields.io/badge/Rails-7.1%20%7C%207.2%20%7C%208.0-CC0000)](https://github.com/crisnahine/rails-ai-context)
24
- [![Tests](https://img.shields.io/badge/Tests-1989%20passing-brightgreen)](https://github.com/crisnahine/rails-ai-context/actions)
24
+ [![Tests](https://img.shields.io/badge/Tests-2078%20passing-brightgreen)](https://github.com/crisnahine/rails-ai-context/actions)
25
25
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
26
26
 
27
27
  </div>
@@ -95,7 +95,7 @@ Real scenarios where AI goes sideways — and what it does instead with ground t
95
95
  | Write tests for a new model | Scaffolds with FactoryBot | Detects your fixture-based suite and matches it |
96
96
  | Fix a failing create action | Misses inherited `before_action :authenticate_user!` | Returns parent-controller filters inline with the action source |
97
97
  | Build a dashboard page | Invents Tailwind classes from memory | Returns your actual button/card/alert patterns, copy-paste ready |
98
- | Trace where `can_cook?` is used | Reads 6 files sequentially, still misses callers | Single call: definition + source + every caller + tests |
98
+ | Trace where `publishable?` is used | Reads 6 files sequentially, still misses callers | Single call: definition + source + every caller + tests |
99
99
 
100
100
  <details>
101
101
  <summary><strong>Verify it on your own app</strong></summary>
@@ -139,7 +139,7 @@ rails ai:serve
139
139
  ```
140
140
 
141
141
  ```
142
- → rails_search_code(pattern: "can_cook?", match_type: "trace")
142
+ → rails_search_code(pattern: "publishable?", match_type: "trace")
143
143
  → rails_get_schema(table: "users")
144
144
  → rails_analyze_feature(feature: "billing")
145
145
  ```
@@ -166,7 +166,7 @@ Native Rails controller transport. No separate process needed.
166
166
  Same 38 tools, no server needed. Works in any terminal, any AI tool.
167
167
 
168
168
  ```bash
169
- rails 'ai:tool[search_code]' pattern="can_cook?" match_type=trace
169
+ rails 'ai:tool[search_code]' pattern="publishable?" match_type=trace
170
170
  rails 'ai:tool[schema]' table=users
171
171
  rails 'ai:tool[analyze_feature]' feature=billing
172
172
  ```
@@ -203,19 +203,19 @@ AI sees `subscription_status` already exists. Checks the model, then generates a
203
203
  </details>
204
204
 
205
205
  <details>
206
- <summary><strong>"Fix the broken cook creation flow"</strong></summary>
206
+ <summary><strong>"Fix the broken post creation flow"</strong></summary>
207
207
 
208
208
  <br>
209
209
 
210
210
  ```bash
211
- rails 'ai:tool[controllers]' controller=CooksController action=create
211
+ rails 'ai:tool[controllers]' controller=PostsController action=create
212
212
  ```
213
213
  ```
214
- # CooksController#create
214
+ # PostsController#create
215
215
 
216
- Filters: before_action :authenticate_user!, before_action :set_cook (only: show, edit)
217
- Strong params: cook_paramsname, specialty, bio
218
- Renders: redirect_to @cook | render :new
216
+ Filters: before_action :authenticate_user!, before_action :set_post (only: show, edit)
217
+ Strong params: post_paramstitle, body, published_at
218
+ Renders: redirect_to @post | render :new
219
219
  ```
220
220
 
221
221
  AI sees the inherited `authenticate_user!` filter, the actual strong params, and the render paths. No guessing.
@@ -288,7 +288,7 @@ Every tool is **read-only** and returns data verified against your actual app
288
288
  | Tool | What it does |
289
289
  |:-----|:------------|
290
290
  | `get_controllers` | Actions + inherited filters + render map + strong params |
291
- | `get_routes` | Code-ready helpers (`cook_path(@record)`) + required params |
291
+ | `get_routes` | Code-ready helpers (`post_path(@record)`) + required params |
292
292
 
293
293
  </details>
294
294
 
@@ -376,7 +376,7 @@ Plus 9 static resources (schema, routes, conventions, gems, controllers, config,
376
376
  Every generated context file ships with **6 rules that force AI verification** before writing code. The protocol targets the exact cognitive failures that produce confident-wrong code: statistical priors overriding observed facts, pattern completion beating verification, stale context lies.
377
377
 
378
378
  <details>
379
- <summary><strong>The 6 rules (shown to AI in every CLAUDE.md / .cursor/rules / .github/instructions)</strong></summary>
379
+ <summary><strong>The 6 rules (shown to AI in every CLAUDE.md / .cursor/rules / .cursorrules / .github/instructions)</strong></summary>
380
380
 
381
381
  <br>
382
382
 
@@ -403,7 +403,7 @@ graph TD
403
403
 
404
404
  B --> C["MCP Server\nstdio / HTTP\n38 tools · 5 templates"]
405
405
  B --> D["CLI Tools\nRake / Thor\nSame 38 tools"]
406
- B --> E["Static Files\nCLAUDE.md · .cursor/rules/\n.github/instructions/"]
406
+ B --> E["Static Files\nCLAUDE.md · .cursor/rules/ · .cursorrules\n.github/instructions/"]
407
407
 
408
408
  style A fill:#4a9eff,stroke:#2d7ad4,color:#fff
409
409
  style B fill:#2d2d2d,stroke:#555,color:#fff
@@ -543,7 +543,7 @@ end
543
543
  ## About
544
544
 
545
545
  Built by a Rails developer with 10+ years of production experience.<br>
546
- 1989 tests. 38 tools. 5 resource templates. 31 introspectors. Standalone or in-Gemfile.<br>
546
+ 2078 tests + 100-example e2e harness. 38 tools. 5 resource templates. 31 introspectors. Standalone or in-Gemfile.<br>
547
547
  MIT licensed. [Contributions welcome.](CONTRIBUTING.md)
548
548
 
549
549
  <br>
data/Rakefile CHANGED
@@ -5,4 +5,70 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
+ # E2E harness — runs specs under spec/e2e/ with E2E=1 so they're not
9
+ # excluded by spec/spec_helper.rb. Each install-path spec spawns a
10
+ # fresh Rails app in a tmpdir and can take minutes.
11
+ #
12
+ # Usage:
13
+ # bundle exec rake e2e # full suite
14
+ # bundle exec rake e2e:in_gemfile # just Path A
15
+ # bundle exec rake e2e:standalone # just Path B
16
+ # bundle exec rake e2e:zero_config # just Path C
17
+ # bundle exec rake e2e:mcp # stdio + HTTP protocol specs
18
+ namespace :e2e do
19
+ RSpec::Core::RakeTask.new(:in_gemfile) do |t|
20
+ t.pattern = "spec/e2e/in_gemfile_install_spec.rb"
21
+ ENV["E2E"] = "1"
22
+ end
23
+
24
+ RSpec::Core::RakeTask.new(:standalone) do |t|
25
+ t.pattern = "spec/e2e/standalone_install_spec.rb"
26
+ ENV["E2E"] = "1"
27
+ end
28
+
29
+ RSpec::Core::RakeTask.new(:zero_config) do |t|
30
+ t.pattern = "spec/e2e/zero_config_install_spec.rb"
31
+ ENV["E2E"] = "1"
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:mcp) do |t|
35
+ t.pattern = "spec/e2e/mcp_{stdio,http}_protocol_spec.rb"
36
+ ENV["E2E"] = "1"
37
+ end
38
+ end
39
+
40
+ desc "Run the full E2E harness (fresh Rails app per install path)"
41
+ RSpec::Core::RakeTask.new(:e2e) do |t|
42
+ t.pattern = "spec/e2e/**/*_spec.rb"
43
+ ENV["E2E"] = "1"
44
+ end
45
+
46
+ namespace :e2e do
47
+ desc "Run e2e specs in parallel via parallel_tests (opt-in; uses all CPUs)"
48
+ task :parallel do
49
+ # Opt-in parallelization. Each parallel_tests worker is a separate
50
+ # process — it will rebuild its own shared-fixture memoization and
51
+ # its own built .gem artefact. Still a net win because rspec-level
52
+ # wall-clock scales with the slowest worker, not the sum.
53
+ #
54
+ # Caveats:
55
+ # - postgres_install_spec is excluded here because its DB name is
56
+ # derived from E2E_DB_SUFFIX and workers would collide unless you
57
+ # pre-create per-worker databases. Run `rake e2e` separately if
58
+ # you need postgres coverage.
59
+ # - Shared BUNDLE_PATH is NOT used in parallel mode — two workers
60
+ # writing to the same bundle path can corrupt native extension
61
+ # builds. Each worker gets its own implicit bundle directory.
62
+ require "shellwords"
63
+ spec_files = Dir["spec/e2e/**/*_spec.rb"].reject { |p| p.include?("postgres_install_spec") }
64
+ cmd = [
65
+ "bundle", "exec", "parallel_rspec",
66
+ "--serialize-stdout",
67
+ "--"
68
+ ] + spec_files
69
+ env = { "E2E" => "1", "BUNDLE_PATH" => nil }
70
+ sh(env, *cmd) { |ok, _res| abort("parallel_rspec failed") unless ok }
71
+ end
72
+ end
73
+
8
74
  task default: :spec
data/demo/demo-trace.tape CHANGED
@@ -9,11 +9,11 @@ Set Padding 12
9
9
  Set Theme "Catppuccin Mocha"
10
10
  Set TypingSpeed 30ms
11
11
 
12
- Type "cd /Users/crisjosephnahine/Documents/Projects/daily-content-chef"
12
+ Type "cd ~/my-rails-app"
13
13
  Enter
14
14
  Sleep 800ms
15
15
 
16
- Type "rails 'ai:tool[search_code]' pattern=can_cook match_type=trace"
16
+ Type "rails 'ai:tool[search_code]' pattern=publishable match_type=trace"
17
17
  Sleep 400ms
18
18
  Enter
19
19
  Sleep 5s
data/demo/demo.tape CHANGED
@@ -9,7 +9,7 @@ Set Padding 12
9
9
  Set Theme "Catppuccin Mocha"
10
10
  Set TypingSpeed 35ms
11
11
 
12
- Type "cd /Users/crisjosephnahine/Documents/Projects/daily-content-chef"
12
+ Type "cd ~/my-rails-app"
13
13
  Enter
14
14
  Sleep 1s
15
15
 
data/docs/ARCHITECTURE.md CHANGED
@@ -173,7 +173,7 @@ Result: controller and view tools automatically include relevant schema informat
173
173
  |:-----------|:-------|
174
174
  | `ClaudeSerializer` | `CLAUDE.md` |
175
175
  | `ClaudeRulesSerializer` | `.claude/rules/*.md` |
176
- | `CursorRulesSerializer` | `.cursor/rules/*.mdc` |
176
+ | `CursorRulesSerializer` | `.cursor/rules/*.mdc` AND `.cursorrules` (legacy chat-agent fallback) |
177
177
  | `CopilotSerializer` | `.github/copilot-instructions.md` |
178
178
  | `CopilotInstructionsSerializer` | `.github/instructions/*.instructions.md` |
179
179
  | `OpencodeSerializer` | `AGENTS.md` (root) |
data/docs/CLI.md CHANGED
@@ -45,12 +45,12 @@ Run any of the 38 MCP tools from the terminal.
45
45
  ```bash
46
46
  # Rake syntax
47
47
  rails 'ai:tool[schema]' table=users detail=full
48
- rails 'ai:tool[search_code]' pattern="can_cook?" match_type=trace
48
+ rails 'ai:tool[search_code]' pattern="publishable?" match_type=trace
49
49
  rails 'ai:tool[model_details]' model=User
50
50
 
51
51
  # Thor syntax
52
52
  rails-ai-context tool schema --table users --detail full
53
- rails-ai-context tool search_code --pattern "can_cook?" --match-type trace
53
+ rails-ai-context tool search_code --pattern "publishable?" --match-type trace
54
54
  rails-ai-context tool model_details --model User
55
55
  ```
56
56
 
data/docs/FAQ.md CHANGED
@@ -53,7 +53,7 @@ Yes, freely. Both generate identical context files and provide the same 38 tools
53
53
 
54
54
  **Yes, commit these:**
55
55
  - `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `opencode.json`, `.codex/config.toml` — so teammates get MCP auto-discovery
56
- - `CLAUDE.md`, `.cursor/rules/`, `.github/instructions/`, `AGENTS.md` — so AI has context
56
+ - `CLAUDE.md`, `.cursor/rules/`, `.cursorrules`, `.github/instructions/`, `AGENTS.md` — so AI has context
57
57
  - `config/initializers/rails_ai_context.rb`, `.rails-ai-context.yml` — so config is shared
58
58
 
59
59
  **Don't commit:**
@@ -126,7 +126,7 @@ Depends on your `ai_tools` config. For all tools:
126
126
  | AI Tool | Files |
127
127
  |:--------|:------|
128
128
  | Claude | CLAUDE.md, .claude/rules/*.md |
129
- | Cursor | .cursor/rules/*.mdc |
129
+ | Cursor | .cursor/rules/*.mdc AND .cursorrules (legacy fallback for chat agent) |
130
130
  | Copilot | .github/copilot-instructions.md, .github/instructions/*.instructions.md |
131
131
  | OpenCode | AGENTS.md, app/*/AGENTS.md |
132
132
  | Codex | Shares AGENTS.md and OpenCode rules |