perf: run only affected modules in pre-push hooks#992
Conversation
Replace full mypy + full pytest unit suite in pre-push hooks with git-diff-based module mappers that only check changed modules. New scripts: - scripts/run_affected_tests.py: maps src/synthorg/<module>/ changes to tests/unit/<module>/ and runs only those tests - scripts/run_affected_mypy.py: runs mypy on changed module dirs only Blast radius rules: changes to core/, config/, observability/, or conftest.py trigger a full run (these are imported everywhere). Expected improvement: ~3-4 min -> ~20-30s for typical 1-2 module changes. Full suite continues to run in CI as the correctness backstop.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughUpdated pre-push hooks in 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Code Review
This pull request introduces pre-push hooks to run mypy and pytest only on modules affected by changes, improving CI efficiency. The review identified several issues where the use of .is_dir() instead of .exists() prevents the scripts from correctly identifying and testing individual files, and highlighted that the mypy script should be updated to include integration tests and ensure better coverage for top-level test files.
scripts/run_affected_mypy.py
Outdated
| if src_dir.is_dir(): | ||
| paths.append(f"src/synthorg/{mod}") | ||
| test_dir = _REPO_ROOT / "tests" / "unit" / mod | ||
| if test_dir.is_dir(): | ||
| paths.append(f"tests/unit/{mod}") | ||
|
|
||
| # Also include directly-changed test dirs not covered by src_modules. | ||
| for tp in sorted(test_paths): | ||
| if tp not in paths and (_REPO_ROOT / tp).is_dir(): | ||
| paths.append(tp) |
There was a problem hiding this comment.
The use of .is_dir() prevents mypy from running on individual Python files that are located directly under src/synthorg/ or tests/unit/ (and are not part of the blast radius). Additionally, when a source module changes, the script should check for corresponding tests in all configured _TEST_KINDS (e.g., integration tests), not just unit tests.
| if src_dir.is_dir(): | |
| paths.append(f"src/synthorg/{mod}") | |
| test_dir = _REPO_ROOT / "tests" / "unit" / mod | |
| if test_dir.is_dir(): | |
| paths.append(f"tests/unit/{mod}") | |
| # Also include directly-changed test dirs not covered by src_modules. | |
| for tp in sorted(test_paths): | |
| if tp not in paths and (_REPO_ROOT / tp).is_dir(): | |
| paths.append(tp) | |
| if src_dir.exists(): | |
| paths.append(f"src/synthorg/{mod}") | |
| for kind in _TEST_KINDS: | |
| t_path = f"tests/{kind}/{mod}" | |
| if (_REPO_ROOT / t_path).exists(): | |
| paths.append(t_path) | |
| # Also include directly-changed test paths not covered by src_modules. | |
| for tp in sorted(test_paths): | |
| if tp not in paths and (_REPO_ROOT / tp).exists(): | |
| paths.append(tp) |
| test_dirs.append(str(smoke.relative_to(_REPO_ROOT))) | ||
| else: | ||
| test_dir = _REPO_ROOT / "tests" / "unit" / mod | ||
| if test_dir.is_dir(): |
There was a problem hiding this comment.
scripts/run_affected_mypy.py
Outdated
| and parts[2] not in _TOP_LEVEL_SRC | ||
| and parts[2] != "test_smoke.py" | ||
| ): |
There was a problem hiding this comment.
Pull request overview
Updates pre-push hooks to run targeted mypy/pytest based on git diff against origin/main, reducing local push time while keeping CI as the full-suite backstop.
Changes:
- Add
scripts/run_affected_tests.pyto run only affectedtests/unit/<module>(or full unit suite on “blast radius” changes). - Add
scripts/run_affected_mypy.pyto run mypy only on affected module dirs (or full run on “blast radius” changes). - Wire both scripts into pre-push hooks and add ruff per-file ignores for expected subprocess usage in
scripts/.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/run_affected_tests.py | Computes changed modules and runs affected unit tests only |
| scripts/run_affected_mypy.py | Computes changed modules and runs mypy on affected dirs only |
| .pre-commit-config.yaml | Swaps pre-push mypy/pytest hooks to call the new scripts |
| pyproject.toml | Adds S603/S607 ignores for scripts/**/*.py |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _git(*args: str) -> str: | ||
| """Run a git command and return stripped stdout.""" | ||
| result = subprocess.run( | ||
| ["git", *args], | ||
| capture_output=True, | ||
| text=True, | ||
| cwd=_REPO_ROOT, | ||
| check=False, | ||
| ) | ||
| if result.returncode != 0: | ||
| print(f"git {' '.join(args)} failed: {result.stderr}", file=sys.stderr) | ||
| return "" | ||
| return result.stdout.strip() |
There was a problem hiding this comment.
The script documents exit code 2 for "script error", but _git() currently just prints an error and returns an empty string. That can lead to false-success behavior (e.g., if git diff fails, py_changed becomes empty and the hook returns 0, skipping tests). Consider treating any git command failure as a hard error: propagate a non-zero status (return 2 from main()), or raise and catch an exception so the hook fails closed rather than skipping checks.
| def _git(*args: str) -> str: | ||
| """Run a git command and return stripped stdout.""" | ||
| result = subprocess.run( | ||
| ["git", *args], | ||
| capture_output=True, | ||
| text=True, | ||
| cwd=_REPO_ROOT, | ||
| check=False, | ||
| ) | ||
| if result.returncode != 0: | ||
| print(f"git {' '.join(args)} failed: {result.stderr}", file=sys.stderr) | ||
| return "" | ||
| return result.stdout.strip() |
There was a problem hiding this comment.
Same issue as the test runner script: _git() swallows failures by returning "", but the script advertises exit code 2 for errors and may incorrectly skip mypy if git commands fail (empty changed -> "No Python files changed" -> exit 0). Suggest failing closed by returning 2 from main() when any required git command fails (or raising an exception) so pre-push doesn't silently bypass type checks.
scripts/run_affected_mypy.py
Outdated
| if ( | ||
| len(parts) >= _MIN_MODULE_DEPTH | ||
| and parts[0] == "tests" | ||
| and parts[1] in _TEST_KINDS | ||
| and parts[2] not in _TOP_LEVEL_SRC | ||
| and parts[2] != "test_smoke.py" | ||
| ): | ||
| return "test_module", None, f"tests/{parts[1]}/{parts[2]}" | ||
|
|
||
| return "other", None, None |
There was a problem hiding this comment.
_classify_path() explicitly excludes tests/unit/test_smoke.py (and other top-level tests/<kind>/*.py files), which means changing tests/unit/test_smoke.py will make the pre-push mypy hook print "Changed files don't map to any mypy targets -- skipping." and exit 0. Consider mapping these top-level test files to a concrete mypy target (e.g., include the file itself, or treat it as run-all) so edits to smoke tests are still type-checked.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/run_affected_mypy.py`:
- Around line 128-130: The current logic in scripts/run_affected_mypy.py only
adds unit test directories for a changed source module (it builds test_dir =
_REPO_ROOT / "tests" / "unit" / mod and appends "tests/unit/{mod}"); update that
routine to also check for an integration test directory (_REPO_ROOT / "tests" /
"integration" / mod) and, if present, append "tests/integration/{mod}" to paths
so integration tests for the changed module are included in the mypy run; adjust
any variables or loops that build paths/test_paths accordingly (referencing
test_dir, mod, and paths).
In `@scripts/run_affected_tests.py`:
- Around line 34-69: The helper functions _git, _merge_base, _changed_files and
the constants _BLAST_RADIUS_MODULES, _TOP_LEVEL_SRC, _MIN_MODULE_DEPTH are
duplicated across two scripts; extract them into a single shared module (e.g.,
scripts._git_utils) and import that module from both scripts so logic is
centralized. Move the implementations of _git, _merge_base, _changed_files and
the three constants into the new module, update both scripts to import and call
the functions/constants from the shared module, and ensure the new module uses
the same _REPO_ROOT or accepts it as an argument to preserve behavior.
- Line 156: The current calls that return _run_pytest(["tests/"]) run full
pytest discovery across all test folders; change those call sites to return
_run_pytest(["tests/unit/"]) so pytest only discovers unit tests and avoids
scanning integration/e2e directories—locate the return statements that call
_run_pytest(["tests/"]) in scripts/run_affected_tests.py (the two occurrences
currently returning _run_pytest(["tests/"])) and update the argument to
["tests/unit/"].
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1ccb7bdc-82d3-4ed5-8ef2-e0e629d53184
📒 Files selected for processing (4)
.pre-commit-config.yamlpyproject.tomlscripts/run_affected_mypy.pyscripts/run_affected_tests.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Deploy Preview
- GitHub Check: Agent
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Build Web
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do not usefrom __future__ import annotationsin Python files; Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling in Python 3.14 as enforced by ruff
Use line length of 88 characters for Python code (enforced by ruff)
Files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
scripts/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
scripts/directory for CI/build utilities and development-time validation with relaxed ruff rules allowingprint()and deferred imports
Files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
All Python versions must be pinned using
==inpyproject.toml; useuv syncto install dependencies
Files:
pyproject.toml
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Run pre-commit hooks including: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI, conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings, conditional)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to scripts/**/*.py : Use `scripts/` directory for CI/build utilities and development-time validation with relaxed ruff rules allowing `print()` and deferred imports
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to scripts/**/*.py : Scripts in `scripts/` (CI/build utilities and development-time validation hooks) — relaxed ruff rules: `print` and deferred imports allowed
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Type-check Python code with `uv run mypy src/ tests/` (strict mode)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to src/**/*.py : Provide type hints for all public functions and use mypy strict mode
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run all pre-commit hooks with `uv run pre-commit run --all-files`
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).
Applied to files:
.pre-commit-config.yamlscripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci
Applied to files:
.pre-commit-config.yamlscripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to .pre-commit-config.yaml : Pre-commit.ci: autoupdate disabled (`autoupdate_schedule: never`) — Dependabot owns hook version bumps via `pre-commit` ecosystem
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting).
Applied to files:
.pre-commit-config.yamlscripts/run_affected_mypy.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Run pre-commit hooks including: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI, conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings, conditional)
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run all pre-commit hooks with `uv run pre-commit run --all-files`
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run unit tests with `uv run python -m pytest tests/ -m unit -n auto`; integration tests with `-m integration -n auto`; e2e tests with `-m e2e -n auto`
Applied to files:
.pre-commit-config.yamlscripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Type-check Python code with `uv run mypy src/ tests/` (strict mode)
Applied to files:
.pre-commit-config.yamlscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Lint Python code with `uv run ruff check src/ tests/`; auto-fix with `--fix`; format with `uv run ruff format src/ tests/`
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run full pytest suite with coverage: `uv run python -m pytest tests/ -n auto --cov=synthorg --cov-fail-under=80`
Applied to files:
.pre-commit-config.yamlscripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to scripts/**/*.py : Scripts in `scripts/` (CI/build utilities and development-time validation hooks) — relaxed ruff rules: `print` and deferred imports allowed
Applied to files:
.pre-commit-config.yamlscripts/run_affected_tests.pypyproject.tomlscripts/run_affected_mypy.py
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Docker workflow (`docker.yml`): build + Trivy/Grype scan + push to GHCR + cosign sign + SLSA L3 provenance. CVE triage: `.github/.trivyignore.yaml`, `.github/.grype.yaml`
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to src/synthorg/**/*.py : Maintain 80% minimum test coverage (enforced in CI)
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to pyproject.toml : Docs group in `pyproject.toml` includes: `zensical`, `mkdocstrings[python]`, `griffe-pydantic`
Applied to files:
pyproject.toml
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to scripts/**/*.py : Scripts in scripts/ have relaxed ruff rules: print and deferred imports allowed.
Applied to files:
pyproject.toml
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to scripts/**/*.py : Use `scripts/` directory for CI/build utilities and development-time validation with relaxed ruff rules allowing `print()` and deferred imports
Applied to files:
pyproject.tomlscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to pyproject.toml : Dev group in `pyproject.toml` includes: test group + ruff, mypy, pre-commit, commitizen, pip-audit
Applied to files:
pyproject.toml
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to src/**/*.py : Provide type hints for all public functions and use mypy strict mode
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Include type hints for all public functions; enforce with mypy strict mode
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-15T22:16:31.632Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:16:31.632Z
Learning: Applies to **/*.py : Type hints: all public functions and classes require type hints, enforced with mypy strict mode.
Applied to files:
scripts/run_affected_mypy.py
🔇 Additional comments (11)
scripts/run_affected_mypy.py (6)
37-49: LGTM!The
_githelper properly handles subprocess calls withcheck=False, captures output, and logs failures to stderr. Usingcwd=_REPO_ROOTensures consistent behavior regardless of where the hook is invoked.
52-57: LGTM!The fallback to
HEAD~1whenorigin/mainis unavailable (shallow clones, detached HEAD) is a sensible approach. The empty string case is handled gracefully inmain()by triggering a full mypy run.
60-68: LGTM!The combination of
base...HEAD(committed changes since merge-base) andHEAD(uncommitted working tree changes) correctly captures everything that will land on the remote after push.
71-99: LGTM!The path classification logic correctly handles the various cases:
conftest.pyanywhere triggers full run, blast-radius modules (core,config,observability) trigger full run, and regular modules are mapped to their corresponding directories.
140-144: LGTM!Using
sys.executable -m mypyensures the correct Python environment is used. The return code is properly propagated.
147-180: LGTM!The main function orchestrates the workflow cleanly with appropriate logging for each decision path. The fallback behaviors are sensible: empty merge base runs full mypy, no Python changes skips, and foundational changes trigger full runs.
pyproject.toml (1)
151-152: LGTM!Adding
S603andS607ignores forscripts/**/*.pyis appropriate. These Bandit rules flag subprocess calls and partial executable paths, which are expected and necessary in pre-push hook scripts that invokegit,mypy, andpytest..pre-commit-config.yaml (1)
66-80: LGTM!The hook configurations are correctly updated to use the new affected-modules scripts. Keeping
files: ^(src/|tests/).*\.py$ensures the hooks only trigger when Python files are involved, whilepass_filenames: falseallows the scripts to determine their own targets via git diff analysis.scripts/run_affected_tests.py (3)
99-128: LGTM!The mapping logic correctly handles the module-to-test-directory relationship. The special case for
"."mapping totest_smoke.pymakes sense for isolated smoke test changes.
131-145: LGTM!The pytest invocation correctly includes
-m unitmarker filter,-n autofor parallel execution, and-qfor quieter output suitable for hook context. Based on learnings, this matches the expected command pattern.
148-177: LGTM!The main function handles all edge cases appropriately with clear logging. The decision flow is sound: empty merge-base → full run, no Python changes → skip, foundational changes → full run, no mapped tests → skip, otherwise → targeted run.
| def _git(*args: str) -> str: | ||
| """Run a git command and return stripped stdout.""" | ||
| result = subprocess.run( | ||
| ["git", *args], | ||
| capture_output=True, | ||
| text=True, | ||
| cwd=_REPO_ROOT, | ||
| check=False, | ||
| ) | ||
| if result.returncode != 0: | ||
| print(f"git {' '.join(args)} failed: {result.stderr}", file=sys.stderr) | ||
| return "" | ||
| return result.stdout.strip() | ||
|
|
||
|
|
||
| def _merge_base() -> str: | ||
| """Find the merge base between HEAD and origin/main.""" | ||
| base = _git("merge-base", "HEAD", "origin/main") | ||
| if not base: | ||
| # Fallback: if origin/main doesn't exist (shallow clone, detached), | ||
| # diff against HEAD~1 so we at least check *something*. | ||
| base = _git("rev-parse", "HEAD~1") | ||
| return base | ||
|
|
||
|
|
||
| def _changed_files(base: str) -> list[str]: | ||
| """Return files changed between *base* and HEAD.""" | ||
| # Include both committed and staged/unstaged changes so the hook catches | ||
| # everything that will land on the remote. | ||
| committed = _git("diff", "--name-only", f"{base}...HEAD") | ||
| uncommitted = _git("diff", "--name-only", "HEAD") | ||
| all_files: set[str] = set() | ||
| for block in (committed, uncommitted): | ||
| if block: | ||
| all_files.update(block.splitlines()) | ||
| return sorted(all_files) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider extracting shared utilities.
The _git, _merge_base, _changed_files functions and constants (_BLAST_RADIUS_MODULES, _TOP_LEVEL_SRC, _MIN_MODULE_DEPTH) are duplicated verbatim in run_affected_mypy.py. While self-contained scripts are easier to maintain independently, extracting these to a shared module (e.g., scripts/_git_utils.py) would reduce duplication.
This is a trade-off: keeping scripts self-contained avoids import complexity in hook execution, but increases maintenance burden when logic needs to change (e.g., adding a new blast-radius module).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/run_affected_tests.py` around lines 34 - 69, The helper functions
_git, _merge_base, _changed_files and the constants _BLAST_RADIUS_MODULES,
_TOP_LEVEL_SRC, _MIN_MODULE_DEPTH are duplicated across two scripts; extract
them into a single shared module (e.g., scripts._git_utils) and import that
module from both scripts so logic is centralized. Move the implementations of
_git, _merge_base, _changed_files and the three constants into the new module,
update both scripts to import and call the functions/constants from the shared
module, and ensure the new module uses the same _REPO_ROOT or accepts it as an
argument to preserve behavior.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #992 +/- ##
=======================================
Coverage 91.93% 91.93%
=======================================
Files 643 643
Lines 34897 34897
Branches 3423 3423
=======================================
Hits 32082 32082
Misses 2211 2211
Partials 604 604 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Fail-closed on git errors (raise _GitError instead of returning empty) - Add _SAFE_MODULE_NAME regex to prevent path traversal via crafted paths - Include integration test dirs in mypy for changed source modules - Use tests/unit/ instead of tests/ for full runs (avoid scanning e2e/integration) - Handle test_smoke.py in mypy script (was silently skipped) - Remove dead py.typed entry from _TOP_LEVEL_SRC - Fix docstrings: exit code 2, conftest wording, .py-only note, merge-base comment - Update CLAUDE.md pre-push hooks description (affected modules only) - Update contributing.md pre-push description - Update getting_started.md to list pre-push hooks
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/run_affected_mypy.py`:
- Around line 13-17: The module docstring claims exit code 2 for a git command
failure, but main() catches _GitError and falls back to running full mypy, so 2
is never returned; update the top-level docstring to reflect the actual
"fail-closed" behavior (i.e., only return mypy's exit codes 0 or 1) or
explicitly state that git failures cause a full mypy run and thus no distinct
exit code 2 is produced; look at main() and the _GitError handling to ensure the
docstring matches the implemented behavior.
In `@scripts/run_affected_tests.py`:
- Around line 13-17: The module-level docstring currently claims exit code 2 is
returned for git failures, but the script catches _GitError and falls back to
running the full test suite (returning pytest's exit code) so exit code 2 is
never produced; update the docstring in scripts/run_affected_tests.py to remove
or correct the line about exit code 2 (or reword it to state that git failures
fall back to running all tests and the script returns the pytest exit code), and
ensure the docstring references the actual behavior implemented around the
_GitError handling (the fallback-to-full-run logic) so documentation matches
code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8345be4d-8ea7-4995-8089-44a7414dd240
📒 Files selected for processing (5)
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.mdscripts/run_affected_mypy.pyscripts/run_affected_tests.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do not usefrom __future__ import annotationsin Python files; Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling in Python 3.14 as enforced by ruff
Use line length of 88 characters for Python code (enforced by ruff)
Files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
scripts/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
scripts/directory for CI/build utilities and development-time validation with relaxed ruff rules allowingprint()and deferred imports
Files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
🧠 Learnings (38)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Run pre-commit hooks including: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI, conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings, conditional)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to scripts/**/*.py : Scripts in `scripts/` (CI/build utilities and development-time validation hooks) — relaxed ruff rules: `print` and deferred imports allowed
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to scripts/**/*.py : Use `scripts/` directory for CI/build utilities and development-time validation with relaxed ruff rules allowing `print()` and deferred imports
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to web/src/**/*.{ts,tsx} : PostToolUse hook (`scripts/check_web_design_system.py`) enforces design system rules on every Edit/Write to `web/src/`
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).
Applied to files:
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.mdscripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci
Applied to files:
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.mdscripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI, conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings, conditional)
Applied to files:
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting).
Applied to files:
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.md
📚 Learning: 2026-03-15T12:00:18.113Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T12:00:18.113Z
Learning: Commits: <type>: <description> — types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook). Signed commits: required on main via branch protection — all commits must be GPG/SSH signed.
Applied to files:
CLAUDE.mddocs/guides/contributing.md
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Run pre-commit hooks including: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Applied to files:
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.md
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: All commits must be GPG/SSH signed on `main` branch via branch protection
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: GPG/SSH signed commits required on `main` via branch protection — all commits must be signed
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: CLI workflow (`.github/workflows/cli.yml`) runs Go lint (golangci-lint + go vet) + test (race, coverage) + build (cross-compile matrix) + vulnerability check (govulncheck) + fuzz testing. Cross-compiles for linux/darwin/windows × amd64/arm64. GoReleaser release on v* tags with cosign keyless signing and SLSA L3 attestations.
Applied to files:
CLAUDE.mddocs/getting_started.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to cli/**/*.go : golangci-lint + go vet enforced on CLI code via pre-commit (conditional on `cli/**/*.go`) and pre-push hooks
Applied to files:
CLAUDE.mddocs/getting_started.mddocs/guides/contributing.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run all pre-commit hooks with `uv run pre-commit run --all-files`
Applied to files:
docs/getting_started.mddocs/guides/contributing.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Install via `uv sync` (installs all deps including dev group by default)
Applied to files:
docs/getting_started.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Lint Python code with `uv run ruff check src/ tests/`; auto-fix with `--fix`; format with `uv run ruff format src/ tests/`
Applied to files:
docs/getting_started.mddocs/guides/contributing.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run unit tests with `uv run python -m pytest tests/ -m unit -n auto`; integration tests with `-m integration -n auto`; e2e tests with `-m e2e -n auto`
Applied to files:
docs/getting_started.mdscripts/run_affected_tests.py
📚 Learning: 2026-03-16T19:52:03.656Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:52:03.656Z
Learning: Applies to cli/**/*.go : Lint CLI Go code with golangci-lint and go vet; test with go test -race; check vulnerabilities with govulncheck
Applied to files:
docs/getting_started.md
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/cli.yml : CLI workflow: Go lint (golangci-lint + go vet) + test (-race -coverprofile) + build (cross-compile: linux/darwin/windows × amd64/arm64) + govulncheck + fuzz testing (main-only, 30s/target, continue-on-error, matrix over 4 packages). cli-pass gate includes fuzz as informational. GoReleaser release on v* tags. Cosign keyless signing of checksums.txt. SLSA L3 provenance attestations. Sigstore bundle (.sigstore.json) attached. Post-release appends checksums/verification/provenance to draft release notes.
Applied to files:
docs/getting_started.md
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: Applies to cli/**/*.go : Lint Go code with `golangci-lint` and `go vet`. Run tests with `-race` flag to detect race conditions.
Applied to files:
docs/getting_started.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Gitleaks scans for secrets via pre-commit hook and weekly CI job
Applied to files:
docs/guides/contributing.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to scripts/**/*.py : Scripts in `scripts/` (CI/build utilities and development-time validation hooks) — relaxed ruff rules: `print` and deferred imports allowed
Applied to files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run full pytest suite with coverage: `uv run python -m pytest tests/ -n auto --cov=synthorg --cov-fail-under=80`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to scripts/**/*.py : Use `scripts/` directory for CI/build utilities and development-time validation with relaxed ruff rules allowing `print()` and deferred imports
Applied to files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).
Applied to files:
scripts/run_affected_tests.pyscripts/run_affected_mypy.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to tests/**/*.py : Use pytest markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()`, `asyncio.sleep()`, or using `asyncio.Event().wait()` for indefinite blocking
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Vitest unit tests must use coverage scoped to files changed vs origin/main branch
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to src/**/*.py : Provide type hints for all public functions and use mypy strict mode
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Include type hints for all public functions; enforce with mypy strict mode
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-15T22:16:31.632Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:16:31.632Z
Learning: Applies to **/*.py : Type hints: all public functions and classes require type hints, enforced with mypy strict mode.
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Type-check Python code with `uv run mypy src/ tests/` (strict mode)
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to src/synthorg/**/*.py : Maintain 80% minimum test coverage (enforced in CI)
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Tests must use test-provider, test-small-001, etc. for vendor-agnostic test data.
Applied to files:
scripts/run_affected_mypy.py
🔇 Additional comments (14)
docs/guides/contributing.md (1)
161-161: LGTM!Documentation accurately reflects the new affected-modules behavior for pre-push hooks, with appropriate cross-reference to the full hook table.
CLAUDE.md (1)
180-180: LGTM!Pre-push hook documentation correctly describes the affected-modules behavior and the blast-radius triggers (core, config, observability, conftest). This matches the implementation in the new scripts.
docs/getting_started.md (2)
53-53: LGTM!Accurate documentation that
pre-commit installnow covers thepre-pushstage alongsidepre-commitandcommit-msg.
72-75: LGTM!The hook table clearly documents the new pre-push hooks with accurate descriptions of affected-modules behavior and appropriate conditional triggers for Go CLI and web dashboard.
scripts/run_affected_mypy.py (5)
42-61: LGTM!The
_GitErrorexception and_githelper function are well-implemented with proper error handling and fail-closed behavior.
64-85: LGTM!The merge-base computation with HEAD~1 fallback handles shallow clones gracefully. Including both committed and uncommitted changes provides a good safety net for pre-push validation.
88-117: LGTM!The path classification logic correctly identifies all relevant categories. The defensive choice to treat unsafe module names as "top_level_src" (triggering full runs) is appropriate for a pre-push safety gate.
120-164: LGTM!The path mapping logic correctly handles module-to-target resolution. The inclusion of both unit and integration test directories (addressing the past review feedback) ensures comprehensive type-checking coverage.
167-209: LGTM!The mypy invocation and main entry point are well-structured with clear user feedback and proper fail-closed behavior.
scripts/run_affected_tests.py (5)
39-82: LGTM!Git helper functions are correctly implemented. The code duplication with
run_affected_mypy.pywas previously noted as a nitpick trade-off for self-contained scripts.
85-115: LGTM!Path classification correctly identifies test categories and handles special cases like
test_smoke.pyfor root-level test changes.
118-147: LGTM!Test directory mapping correctly handles both module-specific tests and the special
test_smoke.pycase for root-level changes.
150-164: LGTM!Pytest invocation correctly uses the
unitmarker and parallel execution. The-n autois technically redundant withaddoptsinpyproject.tomlbut explicit specification ensures the intended behavior.
167-202: LGTM!The main entry point correctly implements fail-closed behavior with full unit suite fallback. The use of
tests/unit/for full runs (addressing past review feedback) avoids unnecessary discovery overhead in integration/e2e directories.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/run_affected_tests.py`:
- Around line 106-110: The is_root boolean expression redundantly checks
parts[2] == "test_smoke.py" and parts[2] in _TOP_LEVEL_SRC even though not
_SAFE_MODULE_NAME.match(parts[2]) already covers non-identifier names (the regex
^[a-zA-Z_][a-zA-Z0-9_]*$ cannot match strings with '.'), so either remove those
redundant disjuncts from the is_root assignment or, if they are kept for
readability, add a clarifying comment above the is_root calculation that
explains why test_smoke.py and entries in _TOP_LEVEL_SRC are explicitly listed
despite the regex check; update the expression in the is_root calculation or add
the comment accordingly and keep references to is_root, _SAFE_MODULE_NAME,
parts[2], "test_smoke.py", and _TOP_LEVEL_SRC so reviewers can verify the
change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 204b4509-770f-4194-96ea-08a9bd0894b6
📒 Files selected for processing (2)
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Socket Security: Pull Request Alerts
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do not usefrom __future__ import annotationsin Python files; Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling in Python 3.14 as enforced by ruff
Use line length of 88 characters for Python code (enforced by ruff)
Files:
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
scripts/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
scripts/directory for CI/build utilities and development-time validation with relaxed ruff rules allowingprint()and deferred imports
Files:
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
🧠 Learnings (24)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Run pre-commit hooks including: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI, conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings, conditional)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to scripts/**/*.py : Scripts in `scripts/` (CI/build utilities and development-time validation hooks) — relaxed ruff rules: `print` and deferred imports allowed
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to scripts/**/*.py : Use `scripts/` directory for CI/build utilities and development-time validation with relaxed ruff rules allowing `print()` and deferred imports
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to web/src/**/*.{ts,tsx} : PostToolUse hook (`scripts/check_web_design_system.py`) enforces design system rules on every Edit/Write to `web/src/`
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).
Applied to files:
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci
Applied to files:
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to src/**/*.py : Provide type hints for all public functions and use mypy strict mode
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to scripts/**/*.py : Scripts in `scripts/` (CI/build utilities and development-time validation hooks) — relaxed ruff rules: `print` and deferred imports allowed
Applied to files:
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Include type hints for all public functions; enforce with mypy strict mode
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-15T22:16:31.632Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:16:31.632Z
Learning: Applies to **/*.py : Type hints: all public functions and classes require type hints, enforced with mypy strict mode.
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Type-check Python code with `uv run mypy src/ tests/` (strict mode)
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to scripts/**/*.py : Use `scripts/` directory for CI/build utilities and development-time validation with relaxed ruff rules allowing `print()` and deferred imports
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Run pre-commit hooks including: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to src/synthorg/**/*.py : Maintain 80% minimum test coverage (enforced in CI)
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).
Applied to files:
scripts/run_affected_mypy.pyscripts/run_affected_tests.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Tests must use test-provider, test-small-001, etc. for vendor-agnostic test data.
Applied to files:
scripts/run_affected_mypy.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run unit tests with `uv run python -m pytest tests/ -m unit -n auto`; integration tests with `-m integration -n auto`; e2e tests with `-m e2e -n auto`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Run full pytest suite with coverage: `uv run python -m pytest tests/ -n auto --cov=synthorg --cov-fail-under=80`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to tests/**/*.py : Use pytest markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-04-02T08:03:40.390Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:03:40.390Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()`, `asyncio.sleep()`, or using `asyncio.Event().wait()` for indefinite blocking
Applied to files:
scripts/run_affected_tests.py
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Vitest unit tests must use coverage scoped to files changed vs origin/main branch
Applied to files:
scripts/run_affected_tests.py
🔇 Additional comments (13)
scripts/run_affected_mypy.py (7)
40-83: LGTM!The git helper functions correctly implement the fail-closed pattern with appropriate error handling.
Note: These utilities are duplicated with
run_affected_tests.py. A previous review suggested extracting shared code to a common module—this remains a valid optional improvement for reducing maintenance burden.
1-20: LGTM!The docstring accurately reflects the expanded scope (covering both
tests/unit/andtests/integration/directories) and the mypy-aligned exit codes.
22-37: LGTM!The
_TEST_KINDSconstant appropriately extends mypy coverage to both unit and integration test directories, ensuring type errors in integration tests are caught when their source modules change.
86-115: LGTM!The 3-value return design cleanly separates concerns:
modulecaptures source modules for bidirectional mapping, whiletest_pathcaptures directly-changed test locations. The distinction betweentest_fileandtest_modulecategories correctly handles both standalone test files and module directories.
118-128: LGTM!The helper cleanly encapsulates the module-to-paths mapping logic, including both source and test directories (unit + integration). Existence checks prevent mypy errors from missing optional directories.
131-162: LGTM!The two-phase path collection ensures comprehensive coverage: source module changes pull in their corresponding tests, while directly-changed test paths are also included. The deduplication and existence checks prevent duplicate or invalid mypy targets.
165-207: LGTM!The main function mirrors the test script's structure with appropriate adaptations for mypy. The full
["src/", "tests/"]fallback on errors ensures type safety even when change detection fails.scripts/run_affected_tests.py (6)
1-20: LGTM!The docstring accurately describes the script's behavior, exit codes, and fallback strategy. Imports are minimal and appropriate for the task.
22-34: LGTM!Constants are appropriately defined. The
_SAFE_MODULE_NAMEregex effectively validates Python package names and prevents path traversal attacks by rejecting paths containing dots or special characters.
37-56: LGTM!The
_githelper follows secure subprocess practices (no shell execution, explicit error handling). The error messages include both the command and stderr output for debugging.
59-80: LGTM!Good defensive implementation: the merge-base fallback handles shallow clones and detached HEADs, while combining committed and uncommitted changes ensures nothing slips through pre-push checks.
116-145: LGTM!The early-exit pattern for blast-radius changes is efficient, and existence checks ensure pytest receives only valid paths. The special handling for root-level test files (
test_smoke.py) aligns with the project's test layout.
148-200: LGTM!The main function has clear control flow with appropriate fallbacks. Using
sys.executableensures pytest runs with the same interpreter as the script. The informative print statements aid debugging when hooks run.
🤖 I have created a release *beep* *boop* --- ## [0.5.7](v0.5.6...v0.5.7) (2026-04-02) ### Features * comparison page -- SynthOrg vs agent orchestration frameworks ([#994](#994)) ([6f937ef](6f937ef)), closes [#981](#981) * event-driven and budget-driven ceremony scheduling strategies ([#995](#995)) ([f88e7b0](f88e7b0)), closes [#971](#971) [#972](#972) * template packs for post-setup additive team expansion ([#996](#996)) ([b45e14a](b45e14a)), closes [#727](#727) ### Performance * preload JetBrains Mono font, remove unused api.github.com preconnect ([#998](#998)) ([2a189c2](2a189c2)) * run only affected modules in pre-push hooks ([#992](#992)) ([7956e23](7956e23)) ### Maintenance * bump astro from 6.1.2 to 6.1.3 in /site in the all group ([#988](#988)) ([17b58db](17b58db)) * bump the all group across 1 directory with 2 updates ([#989](#989)) ([1ff462a](1ff462a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
Local
git pushtakes 3-4 minutes when Python files are touched because the pre-push hook runs full mypy (src/ tests/) and the full pytest unit suite (~5000+ tests) every time -- even for a one-file change. CI runs the same checks anyway.This PR replaces the full-suite pre-push hooks with git-diff-based module mappers that only check changed modules, cutting typical push time to ~20-30 seconds.
Changes
scripts/run_affected_tests.py(new): Mapssrc/synthorg/<module>/changes totests/unit/<module>/via git diff against origin/main and runs only affected testsscripts/run_affected_mypy.py(new): Runs mypy on changed module dirs only (both src and corresponding test dirs).pre-commit-config.yaml: Updatedmypyandpytest-unitpre-push hooks to use the new scriptspyproject.toml: AddedS603/S607ruff ignores forscripts/(subprocess calls expected in hook scripts)Blast radius rules
Changes to foundational modules trigger a full run (these are imported everywhere):
core/,config/,observability/conftest.pysrc/synthorg/__init__.pyorconstants.pyExpected performance
Test plan
tests/unit/budget-- 731 tests in 30s)src/synthorg/budget tests/unit/budget-- clean)