Update AI development principles from SSOT (20260512)
Summary
This MR updates AI development principles based on recent changes to the development documentation (SSOT).
Updated principles and their source-doc changes
database-migrations
Source files (4)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/database-migrations.md b/.ai/principles/baselines/database-migrations.md
new file mode 100644
index 000000000..0a2ccdbc0
--- /dev/null
+++ b/.ai/principles/baselines/database-migrations.md
@@ -0,0 +1,16 @@
+### Schema Migrations
+
+- Files in `db/schema_migrations/` are auto-generated and do not require a newline at the end -- do not flag missing newlines
+
+## BBM doc YAML required fields
+
+When creating a `db/docs/batched_background_migrations/<name>.yml`, the YAML MUST include:
+
+- `migration_job_name: <BBM class name in CamelCase>`
+- `description: <one-line description>`
+- `feature_category: <category symbol>`
+- `introduced_by_url: <MR URL>` (placeholder OK for unreleased)
+- `milestone: '<X.Y>'`
+- `queued_migration_version: <version timestamp>`
+- `gitlab_schema: <gitlab_main | gitlab_ci | gitlab_main_user | gitlab_main_org>` — match the schema of the BBM's primary table
+- (optional, post-finalize) `finalized_by: <version>`database-schema
Source files (12)
doc/development/database/adding_database_indexes.mddoc/development/database/foreign_keys.mddoc/development/database/not_null_constraints.mddoc/development/database/ordering_table_columns.mddoc/development/database/strings_and_the_text_data_type.mddoc/development/database/creating_enums.mddoc/development/database/constraint_naming_convention.mddoc/development/database/check_constraints.mddoc/development/database/polymorphic_associations.mddoc/development/database/serializing_data.mddoc/development/database/single_table_inheritance.mddoc/development/database/hash_indexes.md
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/database-schema.md b/.ai/principles/baselines/database-schema.md
new file mode 100644
index 000000000..4474173d2
--- /dev/null
+++ b/.ai/principles/baselines/database-schema.md
@@ -0,0 +1,5 @@
+### Index Removal
+
+- Before removing an index, verify queries can use other existing indexes efficiently
+- Confirm the index is unused on GitLab.com and Self-Managed
+- For investigating index usage, check Grafana dashboards for index usage datadatabase-queries
Source files (8)
doc/development/database/query_performance.mddoc/development/database/transaction_guidelines.mddoc/development/database/large_tables_limitations.mddoc/development/sql.mddoc/development/database/batching_best_practices.mddoc/development/database/iterating_tables_in_batches.mddoc/development/database/insert_into_tables_in_batches.mddoc/development/database/pagination_performance_guidelines.md
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/database-queries.md b/.ai/principles/baselines/database-queries.md
new file mode 100644
index 000000000..36f3c299c
--- /dev/null
+++ b/.ai/principles/baselines/database-queries.md
@@ -0,0 +1,21 @@
+### ActiveRecord Scopes
+
+- New or modified scopes should have appropriate indexes for filtered columns
+- Scopes should avoid expensive operations like subqueries on large tables
+- Default scopes are avoided unless absolutely necessary (they can cause unexpected query behavior)
+
+### Partitioned Tables
+
+- Leverage partition pruning wherever possible when querying partitioned tables to minimize LWLock contention
+
+### Query Plan Analysis
+
+- Analyze query plans in the MR description for:
+ - Sequential scans on large tables
+ - Nested loops with large datasets
+ - Missing or inefficient index usage
+ - High-cost operations
+ - Unexpected sort operations
+ - Verify the query returns expected records (not zero rows)
+ - Check that maximum query execution time is under 100ms
+ - Ensure the query plan reflects the complete query as executed (including all chained scopes, pagination, ordering)clickhouse
Source files (1)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/clickhouse.md b/.ai/principles/baselines/clickhouse.md
new file mode 100644
index 000000000..55741f3d2
--- /dev/null
+++ b/.ai/principles/baselines/clickhouse.md
@@ -0,0 +1,16 @@
+### Verify Before Flagging
+
+When a diff modifies or replaces an existing structure, always verify the current state from an
+authoritative source before flagging a discrepancy. Never infer the pre-change state solely from
+diff context — check the actual source of truth. For example:
+
+- **Migration `down` methods**: verify the `down` schema against the actual pre-migration schema by
+ querying the local ClickHouse database (`SHOW CREATE TABLE tablename`) or, if unavailable, reading
+ the schema from the base branch (`git show master:db/click_house/main.sql`). Compare
+ column-by-column: names, types, defaults, engine, primary key, ORDER BY, and SETTINGS.
+- **Table recreation** (`DROP TABLE IF EXISTS` + `CREATE TABLE`): verify the old table definition
+ the same way before claiming columns or settings are missing.
+
+### Schema Migration Files
+
+- Files in `db/click_house/schema_migrations/` are auto-generated and do not require a newline at the end — do not flag missing newlinessecurity
Source files (6)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/doc/development/secure_coding_guidelines/_index.md b/doc/development/secure_coding_guidelines/_index.md
index a03f7d6a2..20ffd3dbc 100644
--- a/doc/development/secure_coding_guidelines/_index.md
+++ b/doc/development/secure_coding_guidelines/_index.md
@@ -890,7 +890,7 @@ This sensitive data must be handled carefully to avoid leaks which could lead to
- The [Gitleaks Git hook](https://gitlab.com/gitlab-com/gl-security/security-research/gitleaks-endpoint-installer) is recommended for preventing credentials from being committed.
- Never log credentials under any circumstance. Issue [#353857](https://gitlab.com/gitlab-org/gitlab/-/issues/353857) is an example of credential leaks through log file.
- When credentials are required in a CI/CD job, use [masked variables](../../ci/variables/_index.md#mask-a-cicd-variable) to help prevent accidental exposure in the job logs. Be aware that when [debug logging](../../ci/variables/variables_troubleshooting.md#enable-debug-logging) is enabled, all masked CI/CD variables are visible in job logs. Also consider using [protected variables](../../ci/variables/_index.md#protect-a-cicd-variable) when possible so that sensitive CI/CD variables are only available to pipelines running on protected branches or protected tags.
-- Proper scanners must be enabled depending on what data those credentials are protecting. See the [Application Security Inventory Policy](https://handbook.gitlab.com/handbook/security/product-security/application-security/inventory/#policies) and our [Data Classification Standards](https://handbook.gitlab.com/handbook/security/data-classification-standard/#standard).
+- Proper scanners must be enabled depending on what data those credentials are protecting. See the [Application Security Inventory Policy](https://handbook.gitlab.com/handbook/security/product-security/security-platforms-architecture/application-security/inventory/#policies) and our [Data Classification Standards](https://handbook.gitlab.com/handbook/security/data-classification-standard/#standard).
- To store and/or share credentials between teams, refer to [1Password for Teams](https://handbook.gitlab.com/handbook/security/password-guidelines/#1password-for-teams) and follow [the 1Password Guidelines](https://handbook.gitlab.com/handbook/security/password-guidelines/#1password-guidelines).
- If you need to share a secret with a team member, use 1Password. Do not share a secret over email, Slack, or other service on the Internet.
@@ -1183,4 +1183,4 @@ vulnerability would be a critical (severity 1) incident.
## Who to contact if you have questions
For general guidance, contact the
-[Application Security](https://handbook.gitlab.com/handbook/security/product-security/application-security/) team.
+[Application Security](https://handbook.gitlab.com/handbook/security/product-security/security-platforms-architecture/application-security/) team.authentication
Source files (2)
code-review
Source files (1)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 4fb13a249..a534a2374 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -282,8 +282,8 @@ For further quality guidelines, see [testing](https://handbook.gitlab.com/handbo
##### Security
-1. You have confirmed that if this MR contains changes to processing or storing of credentials or tokens, authorization, and authentication methods, or other items described in [the security review guidelines](https://handbook.gitlab.com/handbook/security/product-security/application-security/appsec-reviews/#what-should-be-reviewed), you have added the `~security` label and you have `@`-mentioned `@gitlab-com/gl-security/appsec`.
-1. You have reviewed the documentation regarding [internal application security reviews](https://handbook.gitlab.com/handbook/security/product-security/application-security/appsec-reviews/#internal-application-security-reviews) for **when** and **how** to request a security review and requested a security review if this is warranted for this change.
+1. You have confirmed that if this MR contains changes to processing or storing of credentials or tokens, authorization, and authentication methods, or other items described in [the security review guidelines](https://handbook.gitlab.com/handbook/security/product-security/security-platforms-architecture/application-security/appsec-reviews/#what-should-be-reviewed), you have added the `~security` label and you have `@`-mentioned `@gitlab-com/gl-security/appsec`.
+1. You have reviewed the documentation regarding [internal application security reviews](https://handbook.gitlab.com/handbook/security/product-security/security-platforms-architecture/application-security/appsec-reviews/#internal-application-security-reviews) for **when** and **how** to request a security review and requested a security review if this is warranted for this change.
1. If there are security scan results that are blocking the MR (due to the [merge request approval policies](https://gitlab.com/gitlab-com/gl-security/security-policies)):
- For true positive findings, they should be corrected before the merge request is merged. This will remove the AppSec approval required by the merge request approval policy.
- For false positive findings, something that should be discussed for risk acceptance, or anything questionable, ping `@gitlab-com/gl-security/appsec`.backend-ruby
Source files (11)
doc/development/backend/ruby_style_guide.mddoc/development/gotchas.mddoc/development/logging.mddoc/development/json.mddoc/development/i18n/externalization.mddoc/development/redis.mddoc/development/polling.mddoc/development/routing.mddoc/development/rails_initializers.mddoc/development/code_comments.mddoc/development/rubocop_development_guide.md
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/backend-ruby.md b/.ai/principles/baselines/backend-ruby.md
new file mode 100644
index 000000000..3f27e9381
--- /dev/null
+++ b/.ai/principles/baselines/backend-ruby.md
@@ -0,0 +1,6 @@
+### Log Field Standards
+
+- Log fields should be defined within the LabKit Ruby Fields module
+- Common logging fields imported from `labkit-ruby` `lib/labkit/fields.rb`
+- New fields added to log messages must not be dynamically generated
+- Follow Field Standardisation Guidelines for observability
diff --git a/doc/development/backend/ruby_style_guide.md b/doc/development/backend/ruby_style_guide.md
index 217b0315a..db409f06e 100644
--- a/doc/development/backend/ruby_style_guide.md
+++ b/doc/development/backend/ruby_style_guide.md
@@ -411,18 +411,20 @@ Timelogs.for_project(project)
#### `with_`
-For scopes which `joins`, `includes`, or filters `where(has_one: record)` or `where(has_many: record)` or `where(boolean condition)`
+For scopes that use `joins`, or filters `where(has_one: record)` or `where(has_many: record)`
+or `where(boolean condition)` where the result set changes.
+
For example:
```ruby
-scope :with_labels, -> { includes(:labels) }
-AbuseReport.with_labels
-
scope :with_status, ->(status) { where(status: status) }
Clusters::AgentToken.with_status(:active)
scope :with_due_date, -> { where.not(due_date: nil) }
Issue.with_due_date
+
+scope :with_runner_type, ->(type) { joins(:runner).where(runner: { runner_type: type }) }
+Ci::Build.with_runner_type(:instance_type)
```
It is also fine to use custom scope names, for example:
@@ -432,6 +434,38 @@ scope :undeleted, -> { where('policy_index >= 0') }
Security::Policy.undeleted
```
+#### `including_`
+
+For scopes that eager load associations using `includes`. The result set does not change.
+Use `including_` to avoid N+1 queries when you do not need to control the SQL loading strategy.
+ActiveRecord decides whether to use a JOIN or a subquery.
+
+For example:
+
+```ruby
+scope :including_tags, -> { includes(:tags) }
+Package.including_tags
+
+scope :including_project, -> { includes(:project) }
+Issue.including_project
+```
+
+#### `preload_`
+
+For scopes that eager load associations using `preload`. The result set does not change.
+Use instead of `including_` when loading multiple `has_many` associations, or when a
+separate subquery is explicitly required.
+
+For example:
+
+```ruby
+scope :preload_author, -> { preload(:author) }
+MergeRequest.preload_author
+
+scope :preload_access_levels, -> { preload(:push_access_levels, :merge_access_levels, :unprotect_access_levels) }
+ProtectedBranch.preload_access_levels
+```
+
#### `order_by_`
For scopes which `order`.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 52b11cc38..fe186e943 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -410,137 +410,8 @@ use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
### Plurals
-- In Ruby/HAML:
-
- ```ruby
- n_('Apple', 'Apples', 3)
- # => 'Apples'
- ```
-
- Using interpolation:
-
- ```ruby
- n_("There is a mouse.", "There are %d mice.", size) % size
- # => When size == 1: 'There is a mouse.'
- # => When size == 2: 'There are 2 mice.'
- ```
-
- Avoid using `%d` or count variables in singular strings. This allows more natural translation in
- some languages.
-- In JavaScript:
-
- ```javascript
- n__('Apple', 'Apples', 3)
- // => 'Apples'
- ```
-
- Using interpolation:
-
- ```javascript
- n__('Last day', 'Last %d days', x)
- // => When x == 1: 'Last day'
- // => When x == 2: 'Last 2 days'
- ```
-
-- In Vue:
-
- One of [the recommended ways to organize translated strings for Vue files](#vue-files) is to extract them into a `constants.js` file.
- That can be difficult to do when there are pluralized strings because the `count` variable won't be known inside the constants file.
- To overcome this, we recommend creating a function which takes a `count` argument:
-
- ```javascript
- // .../feature/constants.js
- import { n__ } from '~/locale';
-
- export const I18N = {
- // Strings that are only singular don't need to be a function
- someDaysRemain: __('Some days remain'),
- daysRemaining(count) { return n__('%d day remaining', '%d days remaining', count); },
- };
- ```
-
- Then within a Vue component the function can be used to retrieve the correct pluralization form of the string:
-
- ```javascript
- // .../feature/components/days_remaining.vue
- import { sprintf } from '~/locale';
- import { I18N } from '../constants';
-
- <script>
- export default {
- props: {
- days: {
- type: Number,
- required: true,
- },
- },
- i18n: I18N,
- };
- </script>
-
- <template>
- <div>
- <span>
- A singular string:
- {{ $options.i18n.someDaysRemain }}
- </span>
- <span>
- A plural string:
- {{ $options.i18n.daysRemaining(days) }}
- </span>
- </div>
- </template>
- ```
-
-The `n_` and `n__` methods should only be used to fetch pluralized translations of the same
-string, not to control the logic of showing different strings for different
-quantities. For similar strings, pluralize the entire sentence to provide the most context
-when translating. Some languages have different quantities of target plural forms.
-For example, Chinese (simplified) has only one target plural form in our
-translation tool. This means the translator has to choose to translate only one
-of the strings, and the translation doesn't behave as intended in the other case.
-
-Below are some examples:
-
-Example 1: For different strings
-
-Use this:
-
-```ruby
-if selected_projects.one?
- selected_projects.first.name
-else
- n_("Project selected", "%d projects selected", selected_projects.count)
-end
-```
-
-Instead of this:
-
-```ruby
-# incorrect usage example
-format(n_("%{project_name}", "%d projects selected", count), project_name: 'GitLab')
-```
-
-Example 2: For similar strings
... (diff truncated at 200 lines / 8 KB; use the per-file commit-history links above for the full picture)backend-architecture
Source files (6)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/backend-architecture.md b/.ai/principles/baselines/backend-architecture.md
new file mode 100644
index 000000000..4685866ff
--- /dev/null
+++ b/.ai/principles/baselines/backend-architecture.md
@@ -0,0 +1,18 @@
+### CE/EE Code Separation
+
+- CE code (outside `ee/`) must not directly reference `EE::` namespaced classes
+- EE extensions use `prepend_mod` pattern in CE files
+- If CE code needs EE-aware behavior, use `prepend_mod` hooks or `Gitlab.ee?` guards
+- Flag direct references to `EE::` namespaced classes in CE code (prevents FOSS build failures)
+
+### ActiveRecord Callbacks
+
+- Callbacks should only modify data on the current model, not associated records
+- Question if callback logic should be in a service layer instead
+- Flag callbacks with side effects (external API calls, updating other records, complex business logic)
+- Flag bulk operations on associated records in callbacks (performance concern as associations grow)
+- Acceptable uses: data normalization on current model only (trimming whitespace, setting defaults)
+
+### Authorization
+
+- Before changing authorization logic, read the existing `authorize!` / `authorize_admin!` call and verify what permission it currently enforces; the required fix may be documentation- or test-only with no code change neededbackend-ee
Source files (1)
rest-api
Source files (1)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/rest-api.md b/.ai/principles/baselines/rest-api.md
new file mode 100644
index 000000000..0b9a2bc68
--- /dev/null
+++ b/.ai/principles/baselines/rest-api.md
@@ -0,0 +1,9 @@
+### API Documentation Format
+
+- Every method must include the REST API request with HTTP method (GET, PUT, DELETE) followed by the request path starting with `/`
+- Every method must have a detailed description of attributes in a table format, with required attributes listed first, then sorted alphabetically
+- Every method must include a cURL example using `https://gitlab.example.com/api/v4/` as the endpoint and `<your_access_token>` as the token placeholder
+- Every method must have a detailed description of the response body and a JSON response example
+- If endpoint attributes are available only to higher subscription tiers or specific offerings, include this information in the attribute description
+- For complex object types, represent sub-attributes with dots, like `project.name` or `projects[].name` for arrays
+- For cURL commands: use long option names (`--header` instead of `-H`), declare URLs with the `--url` parameter in double quotes, and use line breaks with `\` for readabilitygraphql
Source files (4)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index af43c1d8f..289cfb5bc 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -30,22 +30,6 @@ This does not mitigate the problem on GitLab.com. New GraphQL fields still need
You can use the `@gl_introduced` directive on any field, for example:
-<table>
-<thead>
- <tr>
- <td>
- Query
- </td>
- <td>
- Response
- </td>
- </tr>
-</thead>
-
-<tbody>
-<tr>
-<td>
-
```graphql
fragment otherFieldsWithFuture on Namespace {
webUrl
@@ -62,8 +46,7 @@ query namespaceWithFutureFields {
}
```
-</td>
-<td>
+Response:
```json
{
@@ -79,32 +62,11 @@ query namespaceWithFutureFields {
}
```
-</td>
-</tr>
-</tbody>
-</table>
-
You shouldn't use the directive with:
- Arguments: Executable directives don't support arguments.
- Fragments: Instead, use the directive in the fragment nodes.
-- Single future fields, in the query or in objects:
-
-<table>
-<thead>
- <tr>
- <td>
- Query
- </td>
- <td>
- Response
- </td>
- </tr>
-</thead>
-
-<tbody>
-<tr>
-<td>
+- Single future fields, in the query or in objects, for example:
```graphql
query fetchData {
@@ -112,8 +74,7 @@ You shouldn't use the directive with:
}
```
-</td>
-<td>
+ Response:
```json
{
@@ -147,11 +108,7 @@ You shouldn't use the directive with:
}
```
-</td>
-</tr>
-
-<tr>
-<td>
+ Query:
```graphql
query fetchData {
@@ -161,8 +118,7 @@ You shouldn't use the directive with:
}
```
-</td>
-<td>
+ Response:
```json
{
@@ -195,12 +151,8 @@ You shouldn't use the directive with:
]
}
```
-
-</td>
-</tr>
-
-<tr>
-<td>
+
+ Query:
```graphql
query fetchData {
@@ -210,8 +162,7 @@ You shouldn't use the directive with:
}
```
-</td>
-<td>
+ Response:
```json
{
@@ -246,12 +197,6 @@ You shouldn't use the directive with:
}
```
-</td>
-</tr>
-
-</tbody>
-</table>
-
##### Non-nullable fields
Future fields fallback to `null` when they don't exist in the backend. This means that non-nullableworkers
Source files (6)
frontend-vue
Source files (9)
doc/development/fe_guide/_index.mddoc/development/fe_guide/style/vue.mddoc/development/fe_guide/style/javascript.mddoc/development/fe_guide/style/typescript.mddoc/development/fe_guide/design_patterns.mddoc/development/fe_guide/state_management.mddoc/development/fe_guide/pinia.mddoc/development/fe_guide/axios.mddoc/development/i18n/externalization.md
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/frontend-vue.md b/.ai/principles/baselines/frontend-vue.md
new file mode 100644
index 000000000..4a84b8fb8
--- /dev/null
+++ b/.ai/principles/baselines/frontend-vue.md
@@ -0,0 +1,21 @@
+### Pajamas Component Usage
+
+- Flag component usage that appears inconsistent with Pajamas "when to use" and "when not to use" guidelines
+- Flag usage of container components purely for simple visual separation without using the component's structural features (header, footer, etc.)
+- For simple visual separation without structured content, prefer utility classes (e.g., `gl-border gl-rounded-lg gl-p-5`) over container components
+- When both control and variants are toggled in Vue components layer, prefer the `<gitlab-experiment>` component
+
+### Experiments
+
+- Experiment uses an `experiment` type feature flag (not `development` or `ops`)
+- Context is appropriate and consistent (e.g., `actor:`, `project:`, `group:`)
+- Variants are clearly defined (control, candidate, or named variants)
+- Tracking calls use the same context as experiment runs
+- Frontend or feature tests exist to prevent premature code removal
+- Tests cover experiment variants and tracking behavior
+- Temporary assets (icons/illustrations) are in `/ee/app/assets/images` or `/app/assets/images`, not Pajamas library
+
+### Internationalization (i18n)
+
+- DO NOT split a translatable sentence across multiple `GlSprintf` instances; keep the full sentence (e.g., `"Created %{date} by %{author}"`) in a single `GlSprintf :message` so translators can reorder words across languages
+- Extract translation strings to a static `i18n` object on the Vue component (e.g., `$options.i18n.myString`) instead of inlining `s__()` / `__()` calls directly in `<template>`
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 52b11cc38..fe186e943 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -410,137 +410,8 @@ use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
### Plurals
-- In Ruby/HAML:
-
- ```ruby
- n_('Apple', 'Apples', 3)
- # => 'Apples'
- ```
-
- Using interpolation:
-
- ```ruby
- n_("There is a mouse.", "There are %d mice.", size) % size
- # => When size == 1: 'There is a mouse.'
- # => When size == 2: 'There are 2 mice.'
- ```
-
- Avoid using `%d` or count variables in singular strings. This allows more natural translation in
- some languages.
-- In JavaScript:
-
- ```javascript
- n__('Apple', 'Apples', 3)
- // => 'Apples'
- ```
-
- Using interpolation:
-
- ```javascript
- n__('Last day', 'Last %d days', x)
- // => When x == 1: 'Last day'
- // => When x == 2: 'Last 2 days'
- ```
-
-- In Vue:
-
- One of [the recommended ways to organize translated strings for Vue files](#vue-files) is to extract them into a `constants.js` file.
- That can be difficult to do when there are pluralized strings because the `count` variable won't be known inside the constants file.
- To overcome this, we recommend creating a function which takes a `count` argument:
-
- ```javascript
- // .../feature/constants.js
- import { n__ } from '~/locale';
-
- export const I18N = {
- // Strings that are only singular don't need to be a function
- someDaysRemain: __('Some days remain'),
- daysRemaining(count) { return n__('%d day remaining', '%d days remaining', count); },
- };
- ```
-
- Then within a Vue component the function can be used to retrieve the correct pluralization form of the string:
-
- ```javascript
- // .../feature/components/days_remaining.vue
- import { sprintf } from '~/locale';
- import { I18N } from '../constants';
-
- <script>
- export default {
- props: {
- days: {
- type: Number,
- required: true,
- },
- },
- i18n: I18N,
- };
- </script>
-
- <template>
- <div>
- <span>
- A singular string:
- {{ $options.i18n.someDaysRemain }}
- </span>
- <span>
- A plural string:
- {{ $options.i18n.daysRemaining(days) }}
- </span>
- </div>
- </template>
- ```
-
-The `n_` and `n__` methods should only be used to fetch pluralized translations of the same
-string, not to control the logic of showing different strings for different
-quantities. For similar strings, pluralize the entire sentence to provide the most context
-when translating. Some languages have different quantities of target plural forms.
-For example, Chinese (simplified) has only one target plural form in our
-translation tool. This means the translator has to choose to translate only one
-of the strings, and the translation doesn't behave as intended in the other case.
-
-Below are some examples:
-
-Example 1: For different strings
-
-Use this:
-
-```ruby
-if selected_projects.one?
- selected_projects.first.name
-else
- n_("Project selected", "%d projects selected", selected_projects.count)
-end
-```
-
-Instead of this:
-
-```ruby
-# incorrect usage example
-format(n_("%{project_name}", "%d projects selected", count), project_name: 'GitLab')
-```
-
-Example 2: For similar strings
-
-Use this:
-
-```ruby
-n__('Last day', 'Last %d days', days.length)
-```
-
-Instead of this:
-
-```ruby
-# incorrect usage example
-const pluralize = n__('day', 'days', days.length)
-
-if (days.length === 1 ) {
- return sprintf(s__('Last %{pluralize}', pluralize)
-}
-
-return sprintf(s__('Last %{dayNumber} %{pluralize}'), { dayNumber: days.length, pluralize })
-```
+GitLab uses GNU gettext for pluralization. For guidance on `n_()` and `n__()` syntax,
+[CLDR](https://cldr.unicode.org/) plural categories, and common anti-patterns, see [pluralization](pluralization.md).
### Namespaces
frontend-style
Source files (3)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/frontend-style.md b/.ai/principles/baselines/frontend-style.md
new file mode 100644
index 000000000..bb283f7fc
--- /dev/null
+++ b/.ai/principles/baselines/frontend-style.md
@@ -0,0 +1,5 @@
+### Pajamas CSS Overrides
+
+- Flag excessive CSS overrides on Pajamas components (multiple class overrides changing borders, backgrounds, padding, or other default styling)
+- Flag hardcoded color values, spacing values, or typography that should use design tokens
+- Flag fixed type scales (e.g., `gl-text-700-fixed`) used outside of Markdown contextsfrontend-haml
Source files (2)
frontend-a11y
Source files (2)
qa-rspec
Source files (5)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/qa-rspec.md b/.ai/principles/baselines/qa-rspec.md
new file mode 100644
index 000000000..273214bc8
--- /dev/null
+++ b/.ai/principles/baselines/qa-rspec.md
@@ -0,0 +1,24 @@
+### Branch Coverage
+
+- For conditional logic (`||`, `&&`, `if/else`, `case`), verify each branch has test coverage
+- For helper methods, ensure unit specs exist (not just integration coverage)
+- For ActiveRecord callbacks (`before_validation`, `before_save`), ensure unit specs test the callback behavior specifically
+
+### Edge Case Coverage
+
+- Flag missing edge case coverage for:
+ - Nil/empty values
+ - Boundary conditions
+ - Fallback logic (`||` operators)
+ - Error states
+
+### Feature Flags in Tests
+
+- DO NOT use `stub_feature_flags(flag: true)` — feature flags are enabled by default in the test environment, so stubbing to `true` is redundant and misleading
+- Only use `stub_feature_flags(flag: false)` to test the disabled code path
+- For the enabled case, write tests without any feature flag stub — the default state is already enabled
+
+### Spec File Paths
+
+- DO NOT create a new spec file in a subdirectory when a spec already exists at the canonical path (e.g., modify `spec/requests/api/pages_spec.rb`, DO NOT create `spec/requests/api/pages/pages_spec.rb`)
+- DO NOT remove existing shared examples when adding or fixing coverage; extend them or add new examples alongsideqa-jest
Source files (1)
performance
Source files (1)
documentation
Source files (2)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/documentation.md b/.ai/principles/baselines/documentation.md
new file mode 100644
index 000000000..9c93695e3
--- /dev/null
+++ b/.ai/principles/baselines/documentation.md
@@ -0,0 +1,34 @@
+### Common Mistakes - Repetition
+
+- Do not restate information already covered earlier in the same page or in a linked topic
+- Each section should add new information. Do not summarize what was just explained
+- Avoid restating the title or introduction in the first paragraph
+
+### Common Mistakes - Scope
+
+- Do not create a new page for a single concept, term, or procedure step
+
+### Common Mistakes - Accuracy
+
+- Only include information you can ground in the existing codebase, linked documentation, or content already on the page
+- Do not speculate or infer how a feature works
+- Do not invent command syntax, API parameters, or UI element names
+
+### Screenshot Guidelines
+
+- Resize wide or tall screenshots
+- Compress size on disk to 100 KB or less
+- Use descriptive lowercase filenames with underscores instead of hyphens
+- Filenames should include the major and minor version of GitLab in the format `_v18_6`
+
+### Content Guidelines
+
+- Avoid writing about the document itself; get straight to the point instead of using phrases like "This page shows"
+- Do not promise work in future milestones; instead say work is being proposed
+- Avoid blockquotes
+- When linking to GitLab issues, include the GitLab issue number in the link text
+- Start optional steps with "Optional."
+
+### Word List
+
+- Follow the GitLab Documentation recommended word list for consistent terminologyfeature-flags
Source files (1)
SSOT diff since previous distillation (9ab16c75 → 52964caf)
diff --git a/.ai/principles/baselines/feature-flags.md b/.ai/principles/baselines/feature-flags.md
new file mode 100644
index 000000000..c9b53327e
--- /dev/null
+++ b/.ai/principles/baselines/feature-flags.md
@@ -0,0 +1,8 @@
+### YAML Definition
+
+- DO NOT guess or hard-code the `milestone` value — read it from the `VERSION` file in the repo root and use the `MAJOR.MINOR` portion only (e.g., if `VERSION` contains `19.0.0-pre`, set `milestone: "19.0"`)
+
+### Experiments
+
+- If experiment uses only `experiment(:name, actor: current_user)` as context but the corresponding issue mentions tracking of namespace-based activation events, assignment should happen based on namespace or actor + namespace
+- If experiment is first assigned during registration, there should be another assignment tracking event with namespace context: `experiment(:name, actor: current_user).track(:assignment, namespace: group)`analytics
Source files (1)
How this works
A scheduled pipeline detects changes to doc/development/ files
listed in .ai/principles/manifest.yml, then uses the
GitLab Duo Agent Platform Workflow API to distill updated
principles for the affected domains.
Please review the checklist changes to ensure they accurately reflect the documentation updates.