Add missing `feature_category` metadata to RSpec files clearing RSpec/FeatureCategory todos
<!--IssueSummary start--> <details> <summary> Everyone can contribute. [Help move this issue forward](https://handbook.gitlab.com/handbook/marketing/developer-relations/contributor-success/community-contributors-workflows/#contributor-links) while earning points, leveling up and collecting rewards. </summary> - [Close this issue](https://contributors.gitlab.com/manage-issue?action=close&projectId=278964&issueIid=600670) </details> <!--IssueSummary end--> ## Summary **3,295 spec files** are excluded from the `RSpec/FeatureCategory` cop via [`.rubocop_todo/rspec/feature_category.yml`](../blob/master/.rubocop_todo/rspec/feature_category.yml) due to a missing `feature_category:` tag on their top-level `RSpec.describe`. To kick off, give an AI agent the following prompt: > Work on https://gitlab.com/gitlab-org/gitlab/-/work_items/600670 Relates to: https://gitlab.com/groups/gitlab-org/quality/-/work_items/275 --- ## Agent Guidelines - **Batch size:** 25 files per MR (max 30). - **Only add missing values.** Skip files that already have any `feature_category:` (even `:shared`); leave them in the rubocop_todo. - **Valid categories:** `config/feature_categories.yml` + `tooling`, `test_platform`, `rails_platform`. `:shared` is **not** valid. - **Top-level only.** Only modify the `RSpec.describe` line; never nested `context`/`describe`. **Determining the category** (in order): 1. Source file — `feature_category :xxx` in the corresponding class 2. Sibling specs in the same directory that already have the tag 3. Base (non-EE) equivalent spec 4. CODEOWNERS group → team's primary category 5. Module/class name --- ## Learned patterns ### RuboCop cop specs (`spec/rubocop/`) Match category to what the cop enforces, not just the directory: | Cop type | Category | |---|---| | `cop/database/`, ActiveRecord/ORM performance cops | `:database` | | `cop/api/`, `cop/graphql/` | `:api` | | `cop/scalability/`, Sidekiq worker cops | `:scalability` | | `cop/migration/` | `:database` | | `cop/gitlab/feature_flag*`, `cop/feature_flag*` | `:scalability` | | `cop/gettext/` | `:internationalization` | | Security/file-safety cops | `:vulnerability_management` | | General code quality / tooling helpers | `:tooling` | | `cop/qa/`, QA helpers | `:test_platform` | | `cop/usage_data/` | `:service_ping` | | `cop/user_admin` | `:system_access` | | `cop/static_translation_definition` | `:internationalization` | | Sidekiq/Redis cops (`sidekiq_*`, `redis_*`) | `:scalability` | `rails_platform` is for Rails core framework gems only (`rails`, `zeitwerk`) — not for cops enforcing Rails patterns. **Important:** Many `spec/rubocop/cop/` files already have `feature_category: :shared` (set by the shared copconfig). Always check `grep "^RSpec.describe"` before editing — skip files that already have any `feature_category:` value. ### `spec/lib/gitlab/database/` All files in this directory tree → `:database`. ### Running RuboCop locally `bundle exec rubocop` may fail with `Could not find gitlab_query_language` (native gem missing in GDK setup). Use the system rubocop directly instead: ```shell REVEAL_RUBOCOP_TODO=0 rubocop --only RSpec/FeatureCategory <files> ``` For commits/pushes, exclude the failing hooks: ```shell LEFTHOOK_EXCLUDE=rubocop git commit ... LEFTHOOK_EXCLUDE=openapi_docs,rubocop,danger,commit-message-linting git push origin <branch> ``` --- ## Instructions ### Setup (once) Resolve and reuse across all batches: - **Current user ID:** `get_user` (no args) → `id` - **Current milestone ID:** search `gitlab-org` group milestones for the lowest-version active milestone (e.g. `19.1`) → `id` ### Pre-flight (REQUIRED before every batch) **Step 0 — Pull master.** ```shell git checkout master && git pull origin master ``` **Step 1 — Build an exclusion set of already-claimed files.** Use the GitLab MCP tool `search` (scope: `merge_requests`, project: `gitlab-org/gitlab`) to find all open MRs whose title contains `"Add feature_category metadata"`. For each result, call `list_merge_request_diffs` and collect every `new_path` ending in `_spec.rb`. Also call `list_merge_requests_related_to_issue` (issue IID 600670) and repeat for any open ones. Union all paths into a single exclusion set. **Step 2 — Check for branch name collisions.** ```shell git ls-remote --heads origin '*add-feature-category-batch-*' ``` The naming convention is `add-feature-category-batch-NNN`. Pick the next unused number (e.g. if `add-feature-category-batch-001` exists, use `add-feature-category-batch-002`). **Step 3 — Select files (spread across the YAML to avoid conflicts).** Read `.rubocop_todo/rspec/feature_category.yml`. Extract **all** entries (including those already excluded or tagged) to get the full list in YAML order. Call this list `all_entries` (length `N_total`). **Conflict-avoidance — this takes priority over everything else.** The sole source of merge conflicts is `.rubocop_todo/rspec/feature_category.yml`. When multiple MRs are open simultaneously, their diff hunks must not overlap in that file. The critical insight: stride must be computed over **YAML positions** (the full entry list), not over the filtered candidate list. Striding over candidates fails when many consecutive YAML entries are all eligible — they map to adjacent YAML lines regardless of candidate-list distance. 1. Let `N_total` = total entries in the YAML file (all entries, not just candidates), `B` = batch size (25). 2. Compute stride `S = N_total / B` (round to nearest integer). With ~3100 entries this gives `S ≈ 125`. 3. For `i` in `0..B-1`: start at YAML index `i * S`. Advance forward until finding an entry that is (a) not in the exclusion set and (b) does not already have `feature_category:` on its `RSpec.describe` line. Select that entry. This guarantees each selected deletion is ~`S` YAML lines from the next — well beyond git's 3-line context window (which needs only 7 lines of separation to avoid conflicts). **Secondary goal — minimize distinct CODEOWNERS groups.** After the stride selection, check whether the chosen files cluster into one or two CODEOWNERS groups. If swapping a few stride-selected files for nearby unclaimed ones (within ±S/4 positions **in the YAML**) would reduce the reviewer count without closing the gap between hunks below 20 lines, make that swap. Do not override the spread to achieve grouping — a few extra reviewers is cheaper than a conflict rebase. ### Per batch 1. **Edit:** add `, feature_category: :symbol` before `do` on the `RSpec.describe` line. 2. **Remove** the file from `.rubocop_todo/rspec/feature_category.yml`. 3. **Verify:** ```shell REVEAL_RUBOCOP_TODO=0 rubocop --only RSpec/FeatureCategory <files> ``` 4. **Commit:** ``` Add feature_category metadata to batch-NNN specs Adds missing `feature_category:` metadata to N spec files and removes them from .rubocop_todo/rspec/feature_category.yml. Contributes to https://gitlab.com/gitlab-org/gitlab/-/issues/600670 ``` Use the full URL — the commit-msg linter rejects short references. 5. **Push:** ```shell LEFTHOOK_EXCLUDE=openapi_docs,rubocop,danger,commit-message-linting git push origin <branch> ``` (`openapi_docs` requires a dev database absent in standard GDK setups; `rubocop`/`danger`/`commit-message-linting` may fail if `gitlab_query_language` native gem is not installed.) 6. **Create MR** via GitLab MCP: - `title`: `Add feature_category metadata to batch-NNN specs` - `labels`: `pipeline::tier-1` - `squash`: `true`, `remove_source_branch`: `true` - `assignee_ids`: current user ID, `milestone_id`: current milestone ID - `description`: ```markdown ## Summary Adds missing `feature_category:` metadata to N spec files and removes them from `.rubocop_todo/rspec/feature_category.yml`. | File | `feature_category` | Rationale | |---|---|---| | `path/to/spec.rb` | `:category` | rationale | Contributes to https://gitlab.com/gitlab-org/gitlab/-/issues/600670 ``` 7. **Update this issue** if new knowledge was gained during the batch — e.g. category assignment patterns, edge cases, or gotchas discovered — by editing the Agent Guidelines or adding to the Learned patterns section. This keeps future batches consistent. --- ## Definition of Done - [ ] All files missing `feature_category:` have a valid tag - [ ] `.rubocop_todo/rspec/feature_category.yml` contains only pre-existing non-standard values or is empty - [ ] CI passes - [ ] Every assigned category exists in `config/feature_categories.yml`
issue