diff --git a/.coderabbit.yaml b/.coderabbit.yaml
new file mode 100644
index 000000000..77f7a526a
--- /dev/null
+++ b/.coderabbit.yaml
@@ -0,0 +1,277 @@
+# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
+language: "en-US"
+early_access: false
+
+reviews:
+ profile: "chill"
+ request_changes_workflow: false
+ high_level_summary: true
+ high_level_summary_instructions: >
+ Evaluate this pull request against the legacy Expertiza Danger policy as well as normal Rails review quality.
+ Call out violations in pull request size, file scope, work-in-progress titles, TODO/FIXME markers,
+ debug statements, missing tests, schema-without-migration changes, config-file churn, vendor/factory changes,
+ and shallow RSpec patterns.
+ review_status: true
+ review_details: true
+ commit_status: true
+ fail_commit_status: false
+ collapse_walkthrough: false
+ changed_files_summary: true
+ sequence_diagrams: false
+ estimate_code_review_effort: true
+ assess_linked_issues: true
+ related_issues: true
+ related_prs: true
+ suggested_labels: true
+ suggested_reviewers: false
+ auto_apply_labels: false
+ auto_assign_reviewers: false
+ in_progress_fortune: false
+ poem: false
+ enable_prompt_for_ai_agents: false
+
+ labeling_instructions:
+ - label: "api"
+ instructions: "Apply when controllers, routes, request specs, serializers, or Swagger docs change."
+ - label: "database"
+ instructions: "Apply when models, migrations, schema, associations, or data integrity rules change."
+ - label: "workflow"
+ instructions: "Apply when GitHub Actions, CI, bot behavior, or automation files change."
+ - label: "security"
+ instructions: "Apply when authentication, authorization, secrets handling, or workflow permissions change."
+ - label: "tests"
+ instructions: "Apply when RSpec coverage or test helpers are added or modified."
+
+ path_filters:
+ - "!coverage/**"
+ - "!tmp/**"
+ - "!log/**"
+ - "!storage/**"
+ - "!public/assets/**"
+ - "!vendor/bundle/**"
+ - "!*.log"
+ - "!*.ibd"
+
+ path_instructions:
+ - path: "app/controllers/**/*.rb"
+ instructions: |
+ Review authorization, strong parameters, HTTP status codes, and response shape consistency.
+ Flag N+1 queries, missing request-spec coverage, and behavior changes not reflected in Swagger docs.
+
+ - path: "app/models/**/*.rb"
+ instructions: |
+ Focus on associations, validations, callbacks, transactions, STI and polymorphic behavior,
+ foreign keys, data integrity, and query efficiency.
+
+ - path: "app/serializers/**/*.rb"
+ instructions: |
+ Check backward compatibility of response payloads and accidental exposure of internal or sensitive fields.
+
+ - path: "app/mailers/**/*.rb"
+ instructions: |
+ Check mailer/template consistency, recipient safety, and avoid debug output or sensitive data leaks.
+
+ - path: "config/routes.rb"
+ instructions: |
+ Flag duplicate or shadowed routes, surprising non-RESTful patterns, and route changes without matching request specs.
+
+ - path: "db/migrate/**/*.rb"
+ instructions: |
+ Review for reversibility, null constraints, indexes, foreign keys, destructive data changes,
+ and alignment with db/schema.rb and affected model behavior.
+
+ - path: "db/schema.rb"
+ instructions: |
+ If schema changes, verify there is a corresponding migration and that indexes, constraints,
+ and associated tests stay aligned.
+
+ - path: "spec/models/**/*.rb"
+ instructions: |
+ Check that model specs cover validations, associations, callbacks, and important edge cases
+ with meaningful assertions instead of shallow examples.
+ Also apply the legacy Danger policy: discourage `create(` in unit tests when `build` or doubles are enough,
+ reject `.should`, skipped/focused specs, wildcard matcher overuse, missing expectations, matcher-less expectations,
+ and expectations that only prove values are not nil, empty, or zero.
+
+ - path: "spec/controllers/**/*.rb"
+ instructions: |
+ Apply the same legacy RSpec policy used in Expertiza Danger checks:
+ avoid `create(` when lighter test setup would work, avoid `.should`,
+ avoid skipped/focused specs, and flag shallow controller expectations.
+
+ - path: "spec/requests/**/*.rb"
+ instructions: |
+ Check authentication, authorization, invalid input, response payload assertions, and failure cases.
+ When endpoints change, expect Swagger docs to stay aligned.
+ Apply the legacy shallow-test rules: each new test should have a meaningful expectation,
+ avoid commented-out expectations, and do not rely only on page content or non-nil checks.
+
+ - path: "swagger/**/*.yml"
+ instructions: |
+ Check for drift between documented endpoints and controller or request-spec behavior.
+
+ - path: ".github/workflows/**/*.{yml,yaml}"
+ instructions: |
+ Review GitHub Actions for unsafe pull_request_target usage, overly broad permissions, secrets exposure,
+ skipped-job logic, artifact passing, and brittle PR-comment behavior.
+
+ - path: "Dangerfile"
+ instructions: |
+ Preserve the legacy Expertiza Danger policy. If replacing or changing a Danger rule,
+ verify the equivalent behavior exists in CodeRabbit, GitHub Actions, or another documented enforcement path.
+
+ - path: "Gemfile"
+ instructions: |
+ Non-maintainer Gemfile changes should be scrutinized closely. Prefer existing gems when possible
+ and call out dependency additions or lockfile churn without strong justification.
+
+ - path: "Gemfile.lock"
+ instructions: |
+ Flag lockfile-only churn, IDE-generated noise, or dependency graph changes that are not explained in the PR.
+
+ - path: "*.md"
+ instructions: |
+ Non-maintainer documentation changes should be questioned unless they are clearly part of the intended work.
+
+ - path: "*.yml"
+ instructions: |
+ Treat YAML changes as sensitive, especially workflow, environment, and configuration files.
+ Call out changes that look unrelated to the stated task.
+
+ - path: ".rspec"
+ instructions: |
+ Changing `.rspec` is sensitive. Flag it unless the PR clearly justifies altering the global test runner behavior.
+
+ - path: "config.ru"
+ instructions: |
+ Treat Rack entrypoint changes as sensitive and require a clear reason in the PR description.
+
+ - path: "setup.sh"
+ instructions: |
+ Treat setup script changes as sensitive and require a clear reason in the PR description.
+
+ - path: "vendor/**"
+ instructions: |
+ Flag vendor directory changes unless the PR clearly explains why vendored files must change.
+
+ - path: "spec/factories/**"
+ instructions: |
+ Non-maintainer changes to factories should be scrutinized. Ensure they are necessary and do not hide weak tests.
+
+ - path: "Dockerfile"
+ instructions: |
+ Review base image choices, layer ordering, cleanup, and security-sensitive package installation.
+
+ - path: "bin/*"
+ instructions: |
+ Check shell safety, idempotence, and environment assumptions for developer setup scripts.
+
+ auto_review:
+ enabled: true
+ drafts: false
+ auto_incremental_review: true
+ auto_pause_after_reviewed_commits: 3
+ ignore_title_keywords:
+ - "WIP"
+ - "DRAFT"
+ - "[WIP]"
+
+ pre_merge_checks:
+ override_requested_reviewers_only: false
+ title:
+ mode: "warning"
+ requirements: "Use a concise, imperative title that names the subsystem changed."
+ custom_checks:
+ - name: "schema-without-migration"
+ mode: "warning"
+ instructions: |
+ Warn if db/schema.rb changes but no file under db/migrate/ changes in the same pull request.
+
+ - name: "behavior-change-needs-tests"
+ mode: "warning"
+ instructions: |
+ Warn when files under app/controllers/, app/models/, config/routes.rb, or db/migrate/ change
+ without meaningful updates under spec/models/ or spec/requests/.
+
+ - name: "workflow-security"
+ mode: "warning"
+ instructions: |
+ Warn on GitHub Actions changes that use pull_request_target with PR-head checkout,
+ overbroad write permissions, or token and secrets exposure patterns.
+
+ - name: "config-and-setup-scrutiny"
+ mode: "warning"
+ instructions: |
+ Warn when config/database.yml, config/storage.yml, config/credentials.yml.enc, config.ru, setup.sh,
+ or workflow files change without a clear explanation in the pull request summary.
+
+ - name: "todo-temp-debug-artifacts"
+ mode: "warning"
+ instructions: |
+ Warn when the pull request introduces TODO or FIXME markers, debug print statements,
+ temp or cache artifacts, or generated local-state files such as results.txt or safe.log.
+
+ - name: "legacy-pr-scope-and-title"
+ mode: "warning"
+ instructions: |
+ Warn on pull requests larger than roughly 500 LoC, touching more than 30 files,
+ course-project PRs under roughly 50 LoC, duplicated commit messages, or WIP titles.
+
+ - name: "legacy-config-file-guardrails"
+ mode: "warning"
+ instructions: |
+ For non-maintainers, warn on changes to Gemfile, Gemfile.lock, .gitignore, .rspec, Dangerfile,
+ Rakefile, config.ru, setup.sh, YAML files, Markdown files, vendor/**, and spec/factories/** unless clearly justified.
+
+ - name: "legacy-rspec-hygiene"
+ mode: "warning"
+ instructions: |
+ Warn on skipped, pending, or focused specs; `.should`; unnecessary helper requires;
+ text fixtures under spec; and weak/shallow expectations such as wildcard matcher overuse,
+ no expectations, commented-out expectations, matcher-less expectations, or non-real-value assertions.
+
+ - name: "legacy-global-debug-code"
+ mode: "warning"
+ instructions: |
+ Warn on newly introduced global variables, class variables, or debugging statements such as puts, print,
+ binding.pry, debugger, or console.log.
+
+ tools:
+ github-checks:
+ enabled: true
+ timeout_ms: 300000
+ rubocop:
+ enabled: true
+ brakeman:
+ enabled: true
+ actionlint:
+ enabled: true
+ gitleaks:
+ enabled: true
+ hadolint:
+ enabled: true
+ shellcheck:
+ enabled: true
+ markdownlint:
+ enabled: true
+ yamllint:
+ enabled: true
+
+chat:
+ auto_reply: true
+ allow_non_org_members: true
+ art: false
+
+knowledge_base:
+ web_search:
+ enabled: false
+ code_guidelines:
+ enabled: true
+ filePatterns:
+ - "CODERABBIT_GUIDELINES.md"
+ learnings:
+ scope: "local"
+ issues:
+ scope: "local"
+ pull_requests:
+ scope: "local"
diff --git a/.github/workflows/CommentPR.yml b/.github/workflows/CommentPR.yml
index 8f378c7f1..3e56f3ae2 100644
--- a/.github/workflows/CommentPR.yml
+++ b/.github/workflows/CommentPR.yml
@@ -40,5 +40,5 @@ jobs:
```
${{ steps.read_files.outputs.test_output }}
```
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b5c6344ed..df26ba359 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -155,12 +155,11 @@ jobs:
name: code-coverage-artifacts
path: coverage/
- - name: Upload code-coverage report to code-climate
- run: |
- export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}"
- gem install codeclimate-test-reporter
- cc-test-reporter sum-coverage coverage/codeclimate.*.json
- cc-test-reporter after-build -t simplecov -r ${{ secrets.CC_TEST_REPORTER_ID }}
+ - name: Upload code-coverage report to qlty
+ uses: qltysh/qlty-action/coverage@v2
+ with:
+ token: ${{ secrets.CC_TEST_REPORTER_ID }}
+ files: coverage/coverage.json
docker:
needs: test
diff --git a/CODERABBIT_GUIDELINES.md b/CODERABBIT_GUIDELINES.md
new file mode 100644
index 000000000..d45b80846
--- /dev/null
+++ b/CODERABBIT_GUIDELINES.md
@@ -0,0 +1,252 @@
+# CodeRabbit Setup For This Repository
+
+This repository uses CodeRabbit as an AI pull request reviewer for the Expertiza backend reimplementation. The goal is to improve review quality and preserve project-specific policy without replacing the existing CI runners.
+
+## What CodeRabbit Should Do Here
+
+- Review pull requests automatically and re-review on new commits.
+- Leave useful inline comments on Rails, API, database, workflow, and test changes.
+- Summarize the impact of a pull request clearly for human reviewers.
+- Surface policy issues that used to live in Danger-like rules, but start in warning mode instead of blocking merges.
+- Read GitHub check results and use them as context when tests or workflows fail.
+
+## What CodeRabbit Should Not Replace Here
+
+- GitHub Actions still runs the real work.
+- `main.yml` and `TestPR.yml` still execute RSpec, database setup, coverage generation, and Docker-related jobs.
+- `lint.yml` still remains the source of truth for runnable spellcheck and lint workflows unless those checks are intentionally retired.
+- CodeRabbit is a reviewer and explainer, not the test runner or deployment engine.
+
+## Repository Context
+
+- Language and framework: Ruby 3.4.5 with Rails 8.
+- Test stack: RSpec, mainly under `spec/models` and `spec/requests`.
+- API surface: controllers plus request specs, with Swagger documentation checked into `swagger/v1/swagger.yaml`.
+- Database: MySQL-backed Rails app with a large migration history and `db/schema.rb` committed.
+- Automation: GitHub Actions workflows in `.github/workflows`, including CI, PR test reporting, linting, and Danger.
+
+## Review Standards For This Codebase
+
+### Controllers
+
+- Verify authorization behavior and strong parameters.
+- Check HTTP status codes and response payload consistency.
+- Flag N+1 queries or heavy database work in request paths.
+- Expect request-spec coverage for behavior changes.
+- If an endpoint contract changes, expect Swagger documentation to stay in sync.
+
+### Models
+
+- Check associations, validations, callbacks, and transactions.
+- Pay extra attention to STI, polymorphic associations, foreign keys, and null constraints.
+- Prefer data integrity enforced in both application code and schema where appropriate.
+- Flag query inefficiencies and fragile callback chains.
+
+### Serializers And Mailers
+
+- Watch for accidental exposure of internal fields or sensitive data.
+- Keep serialized response shape stable unless the PR explicitly changes the API contract.
+- For mailers, ensure template data and recipients are safe and consistent.
+
+### Routes And API Docs
+
+- Flag duplicate, shadowed, or surprising route definitions.
+- Require controller, request-spec, and Swagger alignment when routes or endpoints change.
+
+### Migrations And Schema
+
+- Prefer reversible migrations.
+- Check for missing indexes, foreign keys, null constraints, and destructive data changes.
+- If `db/schema.rb` changes, expect a matching migration.
+- Schema changes should usually have corresponding model and test updates.
+
+### Tests
+
+- Prefer meaningful request and model specs over shallow examples.
+- Check authentication, authorization, invalid input, and failure paths.
+- Flag behavior changes in `app/` or `config/routes.rb` that are not reflected in `spec/models` or `spec/requests`.
+- Apply the legacy Expertiza Danger RSpec rules:
+ - avoid skipped, pending, or focused tests
+ - avoid `.should`
+ - avoid redundant helper `require` lines
+ - avoid overusing `create(` in unit-style specs when lighter setup would work
+ - flag shallow expectations such as wildcard matcher overuse, missing expectations, commented-out expectations,
+ matcher-less expectations, and assertions that only prove values are not nil, empty, or zero
+
+### Workflows And Bot Files
+
+- Treat workflow changes as security-sensitive.
+- Flag `pull_request_target` combined with checkout of PR-head code, broad write permissions, unsafe token handling, brittle artifact passing, and comment spam patterns.
+- If a Danger rule is removed, the replacement enforcement path must be explicit.
+
+## Legacy Danger Policy To Preserve
+
+The repository now carries a larger Expertiza-style Danger policy. CodeRabbit should mirror that policy in spirit even when the rule is heuristic rather than executable.
+
+### Pull Request Hygiene
+
+- Welcome first-time or non-maintainer contributors politely.
+- Flag PRs over roughly 500 LoC.
+- Flag course-project PRs under roughly 50 LoC.
+- Flag PRs touching more than 30 files.
+- Flag many duplicated commit messages.
+- Flag WIP titles.
+
+### Change Content Rules
+
+- Flag newly added TODO or FIXME markers.
+- Fail or strongly warn on temp, tmp, or cache artifacts committed into the PR.
+- Flag newly introduced global variables, class variables, and obvious debug code.
+- Flag application changes without corresponding test changes.
+
+### Spec Rules
+
+- Flag skipped, pending, or focused tests.
+- Discourage `create(` inside model and controller specs when lighter setup would work.
+- Reject `.should`.
+- Reject redundant `require 'rspec'`, `spec_helper`, `rails_helper`, `test_helper`, or factory helper lines in specs.
+- Warn on committed `.txt` or `.csv` files under spec paths unless clearly justified.
+
+### Sensitive Files
+
+- Non-maintainer changes to Markdown, YAML, helper files, Gemfile, Gemfile.lock, `.gitignore`, `.rspec`,
+ `Dangerfile`, `Rakefile`, `config.ru`, `setup.sh`, `vendor/**`, and `spec/factories/**` should be scrutinized closely.
+- Schema changes should normally come with migrations.
+
+### Shallow Test Detection
+
+- Flag excessive wildcard argument matchers.
+- Flag tests with no expectations.
+- Flag commented-out expectations.
+- Flag expectations without matchers.
+- Flag expectations that only verify non-nil, non-empty, or non-zero conditions instead of real values.
+- In page-oriented tests, encourage assertions beyond simple page content when deeper validation is possible.
+
+### Developer Scripts And Docker
+
+- `bin/*` and `setup.sh` should be idempotent and safe for repeated local use.
+- `Dockerfile` changes should minimize layers, avoid unnecessary packages, and avoid leaving unsafe defaults behind.
+
+## How The `.coderabbit.yaml` Is Structured
+
+### Review Behavior
+
+- The profile is set to `chill` to reduce noise.
+- `request_changes_workflow` is disabled so CodeRabbit does not block PRs on day one.
+- Review details are enabled during rollout so reviewers can see ignored files and review context.
+- Walkthroughs stay expanded to make the review easier to scan.
+- Poems and fortune messages are disabled because this repository benefits more from direct signal than personality in review comments.
+
+### Auto Review
+
+- CodeRabbit reviews PRs automatically.
+- It skips drafts.
+- It re-reviews new commits incrementally.
+- It pauses after a few reviewed commits so a very active PR does not get spammy.
+
+### Labels
+
+- The config narrows label suggestions to a repository-specific set:
+ - `api`
+ - `database`
+ - `workflow`
+ - `security`
+ - `tests`
+
+### Path Instructions
+
+The config teaches CodeRabbit that different parts of the repository need different review criteria. This is more useful than a single broad Rails instruction because this codebase has clear subsystems:
+
+- `app/controllers/**/*.rb`
+- `app/models/**/*.rb`
+- `app/serializers/**/*.rb`
+- `app/mailers/**/*.rb`
+- `config/routes.rb`
+- `db/migrate/**/*.rb`
+- `db/schema.rb`
+- `spec/models/**/*.rb`
+- `spec/requests/**/*.rb`
+- `swagger/**/*.yml`
+- `.github/workflows/**/*.{yml,yaml}`
+- `Dangerfile`
+- `Dockerfile`
+- `bin/*`
+
+### Pre-Merge Checks
+
+These checks are configured as warnings first. They are meant to preserve policy signal from the current Danger setup without creating accidental merge blockers.
+
+- `schema-without-migration`
+- `behavior-change-needs-tests`
+- `workflow-security`
+- `config-and-setup-scrutiny`
+- `todo-temp-debug-artifacts`
+- `legacy-pr-scope-and-title`
+- `legacy-config-file-guardrails`
+- `legacy-rspec-hygiene`
+- `legacy-global-debug-code`
+
+These checks should only move from `warning` to `error` after the team is happy with the signal quality.
+
+### Tool Integrations
+
+The config enables tooling that fits this repository:
+
+- `github-checks` for reading CI outcomes and logs
+- `rubocop` for Ruby style and common issues
+- `brakeman` for Rails security review
+- `actionlint` for workflow validation
+- `gitleaks` for secrets scanning
+- `hadolint` for Dockerfile review
+- `shellcheck` for shell script review
+- `markdownlint` for Markdown consistency
+- `yamllint` for YAML sanity
+
+If the current CodeRabbit plan does not include tool integrations, the YAML can stay in place and those tools can be treated as future-ready configuration.
+
+### Knowledge Base
+
+- Web search is disabled so reviews stay grounded in repository context.
+- Learnings, issues, and past PR context are scoped to `local` so this repository does not inherit unrelated preferences from other repositories.
+- This file is explicitly included as a CodeRabbit guideline document through `knowledge_base.code_guidelines.filePatterns`.
+
+## Relationship To The Current Workflows
+
+### Keep
+
+- `.github/workflows/main.yml`
+- `.github/workflows/TestPR.yml`
+- `.github/workflows/lint.yml`
+
+These are execution workflows and should remain the source of truth for running tests, linting, coverage, and build logic.
+
+### Keep For Comparison During Rollout
+
+- `Dangerfile`
+- `.github/workflows/danger.yml`
+- `.github/workflows/danger_target.yml`
+
+Keep them temporarily while comparing CodeRabbit output against current customized review behavior.
+
+### Possible Future Cleanup
+
+Once CodeRabbit proves it is consistently covering the intended review policy, the repository can decide whether parts of the Danger setup are redundant. That should happen only after side-by-side comparison on real PRs.
+
+## Rollout Plan
+
+1. Commit `.coderabbit.yaml` and this document on a feature branch.
+2. Open a small PR that touches one Rails file and, ideally, one spec or workflow file.
+3. Confirm CodeRabbit posts an automatic review and walkthrough.
+4. Compare its comments with Danger and CI output.
+5. Tune noisy path instructions or checks before expanding enforcement.
+6. Only after a few successful PRs, decide whether any Danger logic should be retired.
+
+## Practical Commands
+
+Common PR comment commands reviewers can use:
+
+- `@coderabbitai review`
+- `@coderabbitai summary`
+- `@coderabbitai resolve`
+
+Use comment chat to teach preferences gradually. If CodeRabbit learns something that should become a permanent repository rule, move it into this document or `.coderabbit.yaml`.
diff --git a/Dangerfile b/Dangerfile
index 6fd640064..334847f7f 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,59 +1,543 @@
-# Helper to safely read files in UTF-8 and avoid "invalid byte sequence" errors
-def safe_read(path)
- File.read(path, encoding: "UTF-8", invalid: :replace, undef: :replace)
+# frozen_string_literal: true
+
+CURRENT_MAINTAINERS = %w[
+ efg
+ Winbobob
+ TheAkbar
+ johnbumgardner
+ nnhimes
+].freeze
+
+ADDED_FILES = git.added_files
+DELETED_FILES = git.deleted_files
+MODIFIED_FILES = git.modified_files
+RENAMED_FILES = git.renamed_files
+TOUCHED_FILES = ADDED_FILES + DELETED_FILES + MODIFIED_FILES + RENAMED_FILES
+LOC = git.lines_of_code
+COMMITS = git.commits
+
+PR_AUTHOR = github.respond_to?(:pr_author) ? github.pr_author.to_s : ''
+PR_TITLE = github.respond_to?(:pr_title) ? github.pr_title.to_s : ''
+PR_DIFF = github.respond_to?(:pr_diff) ? github.pr_diff.to_s : ''
+PR_ADDED = PR_DIFF
+ .split("\n")
+ .select { |loc| loc.start_with?('+') && !loc.include?('+++ b') }
+ .join("\n")
+
+def added_lines_for(file)
+ diff = git.diff_for_file(file)
+ patch = diff&.patch.to_s
+ patch
+ .split("\n")
+ .select { |loc| loc.start_with?('+') && !loc.include?('+++ b') }
end
-# --- PR Size Checks ---
-warn("Pull request is too big (more than 500 LoC).") if git.lines_of_code > 500
-warn("Pull request is too small (less than 50 LoC).") if git.lines_of_code < 50
+def warning_message_of_config_file_change(filename, regex)
+ return if CURRENT_MAINTAINERS.include?(PR_AUTHOR)
+ return unless TOUCHED_FILES.grep(regex).any?
+
+ fail("You changed #{filename}; please double-check whether this is necessary.", sticky: true)
+end
+
+# ------------------------------------------------------------------------------
+# 0. Welcome message
+# ------------------------------------------------------------------------------
+unless CURRENT_MAINTAINERS.include?(PR_AUTHOR)
+ if PR_TITLE =~ /E[0-9]{4}/
+ message(
+ markdown(
+ <<~MARKDOWN
+ Thanks for the pull request, and welcome! The Expertiza team is excited to review your changes, and you should hear from us soon.
+
+ Please make sure the PR passes all checks and you have run `rubocop -a` to autocorrect issues before requesting a review.
-# --- File Change Checks ---
-warn("Pull request touches too many files (more than 30 files).") if git.modified_files.count > 30
+ This repository is being automatically checked for code-quality issues using CodeRabbit, Danger, and CI workflows. Please address newly introduced issues before marking the PR ready for review.
-# --- Commit Message Checks ---
-commit_messages = git.commits.map(&:message)
-duplicated_commits = commit_messages.group_by(&:itself).select { |_, v| v.size > 1 }
-warn("Pull request has duplicated commit messages.") if duplicated_commits.any?
+ Also, please spend some time looking at the instructions at the top of your course project writeup.
+ If you have any questions, please send email to expertiza-support@lists.ncsu.edu.
+ MARKDOWN
+ )
+ )
+ else
+ message(
+ markdown(
+ <<~MARKDOWN
+ Thanks for the pull request, and welcome! The Expertiza team is excited to review your changes, and you should hear from us soon.
+
+ Please make sure the PR passes all checks and you have run `rubocop -a` to autocorrect issues before requesting a review.
+
+ This repository is being automatically checked for code-quality issues using CodeRabbit, Danger, and CI workflows. Please address newly introduced issues before marking the PR ready for review.
+
+ If you have any questions, please send email to expertiza-support@lists.ncsu.edu.
+ MARKDOWN
+ )
+ )
+ end
+end
+
+# ------------------------------------------------------------------------------
+# 1. Your pull request should not be too big (more than 500 LoC).
+# ------------------------------------------------------------------------------
+if LOC > 500
+ warn(
+ markdown(
+ <<~MARKDOWN
+ Your pull request is more than 500 LoC.
+ Please make sure you did not commit unnecessary changes, such as `schema.rb`, `node_modules`, or changelog noise.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 2. Your pull request should not be too small (less than 50 LoC).
+# ------------------------------------------------------------------------------
+if PR_TITLE =~ /E[0-9]{4}/ && LOC < 50
+ warn(
+ markdown(
+ <<~MARKDOWN
+ Your pull request is less than 50 LoC.
+ If you are finished refactoring the code, please consider writing corresponding tests.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
-# --- TODO/FIXME Checks ---
-todo_fixme = (git.modified_files + git.added_files).any? do |file|
- File.exist?(file) && safe_read(file).match?(/\b(TODO|FIXME)\b/i)
+# ------------------------------------------------------------------------------
+# 3. Your pull request should not touch too many files (more than 30 files).
+# ------------------------------------------------------------------------------
+if TOUCHED_FILES.size > 30
+ warn(
+ markdown(
+ <<~MARKDOWN
+ Your pull request touches more than 30 files.
+ Please make sure you did not commit unnecessary changes, such as `node_modules`, `vendor`, or workflow churn.
+ MARKDOWN
+ ),
+ sticky: true
+ )
end
-warn("Pull request contains TODO or FIXME comments.") if todo_fixme
-# --- Temp File Checks ---
-temp_files = git.added_files.any? { |file| file.match?(/(tmp|temp|cache)/i) }
-warn("Pull request includes temp, tmp, or cache files.") if temp_files
+# ------------------------------------------------------------------------------
+# 4. Your pull request should not have too many duplicated commit messages.
+# ------------------------------------------------------------------------------
+messages = COMMITS.map(&:message)
+has_many_dup_commit_messages = messages.uniq.any? { |msg| messages.count(msg) >= 5 }
-# --- Missing Test Checks ---
-warn("There are no test changes in this PR.") if (git.modified_files + git.added_files).none? { |f| f.include?('spec/') || f.include?('test/') }
+if has_many_dup_commit_messages
+ warn(
+ markdown(
+ <<~MARKDOWN
+ Your pull request has many duplicated commit messages. Please try to squash similar commits
+ and use meaningful commit messages later.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 5. Your pull request is "work in progress" and it will not be merged.
+# ------------------------------------------------------------------------------
+if PR_TITLE.match?(/\bWIP\b/i)
+ warn(
+ markdown(
+ <<~MARKDOWN
+ This pull request is classed as `Work in Progress`. It cannot be merged right now.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 6. Your pull request should not contain "Todo" or "Fixme" keyword.
+# ------------------------------------------------------------------------------
+if PR_ADDED.match?(/\b(TODO|FIXME)\b/i)
+ warn(
+ markdown(
+ <<~MARKDOWN
+ This pull request contains `TODO` or `FIXME` task(s); please fix them.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 7. Your pull request should not include temp, tmp, cache file.
+# ------------------------------------------------------------------------------
+if ADDED_FILES.grep(/temp|tmp|cache/i).any?
+ fail(
+ markdown(
+ <<~MARKDOWN
+ You committed `temp`, `tmp` or `cache` files. Please remove them.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 8. Your pull request should avoid using global variables and/or class variables.
+# ------------------------------------------------------------------------------
+(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file|
+ next unless file.end_with?('.rb')
+
+ added_lines = added_lines_for(file).join("\n")
+ next unless added_lines.match?(/\$[A-Za-z0-9_]+/) || added_lines.match?(/@@[A-Za-z0-9_]+/)
+
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You are using global variables (`$`) or class variables (`@@`); please double-check whether this is necessary.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+end
+
+# ------------------------------------------------------------------------------
+# 9. Your pull request should avoid keeping debugging code.
+# ------------------------------------------------------------------------------
+if PR_ADDED.include?('puts ') ||
+ PR_ADDED.include?('print ') ||
+ PR_ADDED.include?('binding.pry') ||
+ PR_ADDED.include?('debugger;') ||
+ PR_ADDED.include?('console.log')
+ warn('You are including debug code in your pull request, please remove it.', sticky: true)
+end
+
+# ------------------------------------------------------------------------------
+# 10. You should write tests after making changes to the application.
+# ------------------------------------------------------------------------------
+if TOUCHED_FILES.grep(%r{^app/}).any? && TOUCHED_FILES.grep(%r{^spec/}).empty?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ There are code changes, but no corresponding tests.
+ Please include tests if this PR introduces any modifications in behavior.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
-# --- .md File Changes ---
-md_changes = git.modified_files.any? { |file| file.end_with?('.md') }
-warn("Pull request modifies markdown files (*.md). Make sure you have a good reason.") if md_changes
+# ------------------------------------------------------------------------------
+# 11. Your pull request should not include skipped/pending/focused test cases.
+# ------------------------------------------------------------------------------
+(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file|
+ next unless file.end_with?('_spec.rb')
-# --- DB Schema Changes ---
-schema_changes = git.modified_files.any? { |file| file.match?(/db\/schema\.rb$/) }
-unless git.modified_files.any? { |file| file.match?(/db\/migrate\//) }
- warn("Schema changes detected without a corresponding DB migration.") if schema_changes
+ added_lines = added_lines_for(file).join("\n")
+ if added_lines.include?('xdescribe') ||
+ added_lines.include?('xspecify') ||
+ added_lines.include?('xexample') ||
+ added_lines.include?('xit') ||
+ added_lines.include?('skip(') ||
+ added_lines.include?('skip ') ||
+ added_lines.include?('pending(') ||
+ added_lines.include?('fdescribe') ||
+ added_lines.include?('fit')
+ warn(
+ markdown(
+ <<~MARKDOWN
+ There are one or more skipped, pending, or focused test cases in your pull request. Please fix them.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+ end
end
-# --- Config/Setup File Changes (selected ones not excluded) ---
-config_files = %w[
- config/database.yml
- config/secrets.yml
- config/secrets.yml.example
- config/settings.yml
- config/settings.yml.example
- setup.sh
- config.ru
-]
-changed_config_files = git.modified_files.select { |file| config_files.include?(file) }
-warn("Pull request modifies config or setup files: #{changed_config_files.join(', ')}.") if changed_config_files.any?
+# ------------------------------------------------------------------------------
+# 12. Unit tests and integration tests should avoid using "create" keyword.
+# ------------------------------------------------------------------------------
+(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file|
+ next unless file.match?(%r{spec/models}) || file.match?(%r{spec/controllers})
+
+ added_lines = added_lines_for(file).join("\n")
+ next unless added_lines.match?(/create\(/)
+
+ warn(
+ markdown(
+ <<~MARKDOWN
+ Using `create` in unit tests or integration tests may be overkill. Try to use `build` or `double` instead.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+end
+
+# ------------------------------------------------------------------------------
+# 13. RSpec tests should avoid using "should" keyword.
+# ------------------------------------------------------------------------------
+(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file|
+ next unless file.end_with?('_spec.rb')
+
+ added_lines = added_lines_for(file).join("\n")
+ next unless added_lines.include?('.should')
+
+ warn(
+ markdown(
+ <<~MARKDOWN
+ The `should` syntax is deprecated in RSpec 3. Please use `expect` syntax instead.
+ Even in test descriptions, please avoid using `should`.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+end
+
+# ------------------------------------------------------------------------------
+# 14. Your RSpec testing files do not need to require helper files.
+# ------------------------------------------------------------------------------
+if PR_ADDED.include?("require 'rspec'") ||
+ PR_ADDED.include?('require "rspec"') ||
+ PR_ADDED.include?("require 'spec_helper'") ||
+ PR_ADDED.include?('require "spec_helper"') ||
+ PR_ADDED.include?("require 'rails_helper'") ||
+ PR_ADDED.include?('require "rails_helper"') ||
+ PR_ADDED.include?("require 'test_helper'") ||
+ PR_ADDED.include?('require "test_helper"') ||
+ PR_ADDED.include?("require 'factory_girl_rails'") ||
+ PR_ADDED.include?('require "factory_girl_rails"') ||
+ PR_ADDED.include?("require 'factory_bot_rails'") ||
+ PR_ADDED.include?('require "factory_bot_rails"')
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You are requiring `rspec`, fixture-related gems, or helper methods in RSpec tests.
+ These have already been included, so you do not need to require them again. Please remove them.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 15. You should avoid committing text files for RSpec tests.
+# ------------------------------------------------------------------------------
+if TOUCHED_FILES.grep(/spec.*\.(txt|csv)$/).any?
+ warn('You committed text files (`*.txt` or `*.csv`) for RSpec tests; please double-check whether this is necessary.', sticky: true)
+end
+
+# ------------------------------------------------------------------------------
+# 16. Your pull request should not change or add *.md files unless you have a good reason.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(/\.md$/).any?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You changed MARKDOWN (`*.md`) documents; please double-check whether it is necessary to do so.
+ Alternatively, you can insert project-related content in the description field of the pull request.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 17. Your pull request should not change DB schema unless there is new DB migration.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) &&
+ TOUCHED_FILES.grep(%r{db/migrate}).empty? &&
+ (MODIFIED_FILES.grep(/schema\.rb$/).any? || TOUCHED_FILES.grep(/schema\.json$/).any?)
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You should commit changes to the DB schema (`db/schema.rb`) only if you have created new DB migrations.
+ Please double check your code. If you did not aim to change the DB, please revert the DB schema changes.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 18. Your pull request should not modify *.yml or *.yml.example file.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(/\.ya?ml(\.example)?$/).any?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You changed YAML (`*.yml`, `*.yaml`) or example config files; please double-check whether this is necessary.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 19. Your pull request should not modify test-related helper files.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) &&
+ (MODIFIED_FILES.grep(/rails_helper\.rb$/).any? || MODIFIED_FILES.grep(/spec_helper\.rb$/).any?)
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You should not change `rails_helper.rb` or `spec_helper.rb` without a strong reason; please double-check these changes.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 20. Your pull request should not modify Gemfile, Gemfile.lock.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) &&
+ (MODIFIED_FILES.include?('Gemfile') || MODIFIED_FILES.include?('Gemfile.lock'))
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You are modifying `Gemfile` or `Gemfile.lock`, please double check whether it is necessary.
+ Add a new gem only if you have a very good reason, and please revert lockfile noise made by the IDE.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 21-33. Configuration files should not change casually.
+# ------------------------------------------------------------------------------
+warning_message_of_config_file_change('.bowerrc', /\.bowerrc$/)
+warning_message_of_config_file_change('.gitignore', /\.gitignore$/)
+warning_message_of_config_file_change('.mention-bot', /\.mention-bot$/)
+warning_message_of_config_file_change('.rspec', /\.rspec$/)
+warning_message_of_config_file_change('Capfile', /(^|\/)Capfile$/)
+warning_message_of_config_file_change('Dangerfile', /(^|\/)Dangerfile$/)
+warning_message_of_config_file_change('Guardfile', /(^|\/)Guardfile$/)
+warning_message_of_config_file_change('LICENSE', /(^|\/)LICENSE$/)
+warning_message_of_config_file_change('Procfile', /(^|\/)Procfile$/)
+warning_message_of_config_file_change('Rakefile', /(^|\/)Rakefile$/)
+warning_message_of_config_file_change('bower.json', /(^|\/)bower\.json$/)
+warning_message_of_config_file_change('config.ru', /(^|\/)config\.ru$/)
+warning_message_of_config_file_change('setup.sh', /(^|\/)setup\.sh$/)
+
+# ------------------------------------------------------------------------------
+# 34. The PR should not modify vendor folder.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(%r{^vendor/}).any?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You modified the `vendor` folder; please double-check whether it is necessary.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 35. You should not modify /spec/factories/ folder.
+# ------------------------------------------------------------------------------
+if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(%r{^spec/factories/}).any?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You modified `spec/factories/`; please double-check whether it is necessary.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 36. You should not commit .vscode folder to your pull request.
+# ------------------------------------------------------------------------------
+if ADDED_FILES.grep(/\.vscode/).any?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ You committed `.vscode` folder; please remove it.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+end
+
+# ------------------------------------------------------------------------------
+# 37-41. RSpec tests should avoid shallow tests.
+# ------------------------------------------------------------------------------
+(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file|
+ next unless file.end_with?('_spec.rb')
+
+ added_lines_arr = added_lines_for(file)
+ added_lines = added_lines_arr.join("\n")
+ num_of_tests = added_lines.scan(/\+\s*it\s*['"]/).count
+ num_of_expect_key_words = added_lines.scan(/\+\s*expect\s*(\(|\{|do)/).count
+ num_of_commented_out_expect_key_words = added_lines.scan(/\+\s*#\s*expect\s*(\(|\{|do)/).count
+ num_of_expectation_without_matchers = added_lines_arr.count do |loc|
+ loc.match?(/^\+\s*expect\s*[\(\{]/) && !loc.match?(/\.(to|not_to|to_not)/)
+ end
+ num_of_expectation_not_focus_on_real_value = added_lines_arr.count do |loc|
+ loc.match?(/^\+\s*expect\s*[\(\{]/) && loc.match?(/\.(not_to|to_not)\s*(be_nil|be_empty|eq 0|eql 0|equal 0)/)
+ end
+ num_of_wildcard_argument_matchers = added_lines.scan(/\((anything|any_args)\)/).count
+ num_of_expectations_on_page = added_lines.scan(/\+\s*expect\s*\(page\)/).count
-# --- Shallow Tests (RSpec) ---
-shallow_test_files = git.modified_files.select { |file| file.include?('spec/') }
-shallow_test_warning = shallow_test_files.any? do |file|
- File.exist?(file) && safe_read(file).match?(/\bit\b|\bspecify\b/)
+ if num_of_wildcard_argument_matchers >= 5
+ warn(
+ markdown(
+ <<~MARKDOWN
+ There are many wildcard argument matchers (e.g., `anything`, `any_args`) in your tests.
+ To avoid shallow tests, please avoid wildcard matchers.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+ elsif num_of_expect_key_words < num_of_tests || num_of_commented_out_expect_key_words.positive?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ One or more of your tests do not have expectations or you commented out some expectations.
+ To avoid shallow tests, please write at least one expectation for each test and do not comment out expectations.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+ elsif num_of_expectation_without_matchers.positive?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ One or more of your test expectations do not have matchers.
+ To avoid shallow tests, please include matchers such as comparisons, object state changes, or explicit error handling.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+ elsif num_of_expectation_not_focus_on_real_value.positive?
+ warn(
+ markdown(
+ <<~MARKDOWN
+ One or more of your test expectations only focus on the return value not being `nil`, `empty`, or `0` without testing the real value.
+ To avoid shallow tests, please write expectations that verify the real value.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+ elsif num_of_expect_key_words - num_of_expectations_on_page < num_of_tests
+ warn(
+ markdown(
+ <<~MARKDOWN
+ In your tests, there are many expectations of elements on pages, which is good.
+ To avoid shallow tests, please write more expectations to validate other things, such as database records or dynamically generated contents.
+ MARKDOWN
+ ),
+ sticky: true
+ )
+ break
+ end
end
-warn("RSpec tests seem shallow (single `it` blocks or no context). Consider improving test structure.") if shallow_test_warning