feat: support Husky as equivalent pre-commit hook framework#467
feat: support Husky as equivalent pre-commit hook framework#467aviavraham wants to merge 1 commit into
Conversation
…code#433) Husky (.husky/) is the most popular pre-commit hook tool for JavaScript/TypeScript/Java projects. Previously it scored only 10 points (always failing). Now .husky/ with hook scripts scores 60 points — same as .pre-commit-config.yaml — so projects using either framework can pass the deterministic enforcement attribute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds Husky support to the deterministic enforcement assessment. The assessor now scans ChangesHusky Support for Deterministic Enforcement Assessment
Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/agentready/assessors/testing.py`:
- Around line 592-600: The current comprehension treats any regular file in
husky_dir as a hook (hook_scripts), so update the filter to only include real
hook scripts by checking executability and common hook signatures: use
f.is_file() and not f.name.startswith("_") AND (os.access(f, os.X_OK) OR the
file’s first line starts with "#!" OR f.name is in a small KNOWN_HOOKS set like
{"pre-commit","commit-msg","pre-push","post-commit","pre-commit.sample"}); keep
the rest of the logic (score += 60.0, hooks_list, evidence.append) but only
trigger it when this stricter hook_scripts list is non-empty, and import os and
read the first line safely when computing the filter.
- Around line 591-596: The assessor currently uses husky_dir.iterdir() (in
assess()) which can raise OSError/PermissionError and crash the run; wrap the
iteration in a try/except that catches OSError/PermissionError around the
husky_dir.iterdir() call (the block building hook_scripts from husky_dir) and
handle failures by returning or recording a skipped/error Finding instead of
letting the exception propagate; ensure you reference husky_dir and hook_scripts
in the handler and log the permission/error detail so assess() exits gracefully.
In `@tests/unit/test_assessors_testing.py`:
- Around line 594-606: The test test_husky_ignores_underscore_files currently
creates an underscore directory so it doesn't exercise the underscore-file
filtering; update the test to create an underscore-prefixed file (e.g., create a
file named ".husky/_local" with hook-like content) instead of or in addition to
the directory, using the same tmp_path and _make_repo setup, then run
DeterministicEnforcementAssessor().assess(repo) and assert the finding.score
remains >= 60.0 (or that hook detection count doesn't increase) to verify
underscore-prefixed files are ignored; reference the test function name
test_husky_ignores_underscore_files and the helper _make_repo to locate where to
change the fixture.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: c019d9a9-b680-4495-a76b-4c1e5f4c66ae
📒 Files selected for processing (3)
docs/attributes.mdsrc/agentready/assessors/testing.pytests/unit/test_assessors_testing.py
| if husky_dir.exists() and husky_dir.is_dir(): | ||
| hook_scripts = [ | ||
| f.name | ||
| for f in husky_dir.iterdir() | ||
| if f.is_file() and not f.name.startswith("_") | ||
| ] |
There was a problem hiding this comment.
Handle unreadable .husky/ safely instead of crashing the assessor
husky_dir.iterdir() can raise OSError/PermissionError; right now that bubbles out of assess() and breaks assessment execution.
As per coding guidelines, "Check for proper error handling (return skipped/error Finding, don't crash)."
Suggested fix
if husky_dir.exists() and husky_dir.is_dir():
- hook_scripts = [
- f.name
- for f in husky_dir.iterdir()
- if f.is_file() and not f.name.startswith("_")
- ]
+ try:
+ hook_scripts = [
+ f.name
+ for f in husky_dir.iterdir()
+ if f.is_file() and not f.name.startswith("_")
+ ]
+ except OSError:
+ hook_scripts = []
+ evidence.append(".husky directory exists but could not be read")
if hook_scripts:
score += 60.0
hooks_list = ", ".join(sorted(hook_scripts))
evidence.append(f".husky directory found with hooks: {hooks_list}")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/agentready/assessors/testing.py` around lines 591 - 596, The assessor
currently uses husky_dir.iterdir() (in assess()) which can raise
OSError/PermissionError and crash the run; wrap the iteration in a try/except
that catches OSError/PermissionError around the husky_dir.iterdir() call (the
block building hook_scripts from husky_dir) and handle failures by returning or
recording a skipped/error Finding instead of letting the exception propagate;
ensure you reference husky_dir and hook_scripts in the handler and log the
permission/error detail so assess() exits gracefully.
| hook_scripts = [ | ||
| f.name | ||
| for f in husky_dir.iterdir() | ||
| if f.is_file() and not f.name.startswith("_") | ||
| ] | ||
| if hook_scripts: | ||
| score += 60.0 | ||
| hooks_list = ", ".join(sorted(hook_scripts)) | ||
| evidence.append(f".husky directory found with hooks: {hooks_list}") |
There was a problem hiding this comment.
Don’t treat arbitrary files in .husky/ as hook scripts
Current detection counts any regular file (except underscore-prefixed), so files like .gitignore or README.md can incorrectly produce a 60-point pass. That violates the “with hook scripts” scoring intent.
Suggested fix
+ valid_hook_names = {
+ "applypatch-msg",
+ "commit-msg",
+ "post-applypatch",
+ "post-checkout",
+ "post-commit",
+ "post-merge",
+ "post-rewrite",
+ "pre-applypatch",
+ "pre-auto-gc",
+ "pre-commit",
+ "pre-merge-commit",
+ "pre-push",
+ "pre-rebase",
+ "prepare-commit-msg",
+ }
try:
hook_scripts = [
f.name
for f in husky_dir.iterdir()
- if f.is_file() and not f.name.startswith("_")
+ if f.is_file()
+ and not f.name.startswith("_")
+ and f.name in valid_hook_names
]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| hook_scripts = [ | |
| f.name | |
| for f in husky_dir.iterdir() | |
| if f.is_file() and not f.name.startswith("_") | |
| ] | |
| if hook_scripts: | |
| score += 60.0 | |
| hooks_list = ", ".join(sorted(hook_scripts)) | |
| evidence.append(f".husky directory found with hooks: {hooks_list}") | |
| valid_hook_names = { | |
| "applypatch-msg", | |
| "commit-msg", | |
| "post-applypatch", | |
| "post-checkout", | |
| "post-commit", | |
| "post-merge", | |
| "post-rewrite", | |
| "pre-applypatch", | |
| "pre-auto-gc", | |
| "pre-commit", | |
| "pre-merge-commit", | |
| "pre-push", | |
| "pre-rebase", | |
| "prepare-commit-msg", | |
| } | |
| hook_scripts = [ | |
| f.name | |
| for f in husky_dir.iterdir() | |
| if f.is_file() | |
| and not f.name.startswith("_") | |
| and f.name in valid_hook_names | |
| ] | |
| if hook_scripts: | |
| score += 60.0 | |
| hooks_list = ", ".join(sorted(hook_scripts)) | |
| evidence.append(f".husky directory found with hooks: {hooks_list}") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/agentready/assessors/testing.py` around lines 592 - 600, The current
comprehension treats any regular file in husky_dir as a hook (hook_scripts), so
update the filter to only include real hook scripts by checking executability
and common hook signatures: use f.is_file() and not f.name.startswith("_") AND
(os.access(f, os.X_OK) OR the file’s first line starts with "#!" OR f.name is in
a small KNOWN_HOOKS set like
{"pre-commit","commit-msg","pre-push","post-commit","pre-commit.sample"}); keep
the rest of the logic (score += 60.0, hooks_list, evidence.append) but only
trigger it when this stricter hook_scripts list is non-empty, and import os and
read the first line safely when computing the filter.
| def test_husky_ignores_underscore_files(self, tmp_path): | ||
| """Test that files starting with _ (like _/husky.sh) are ignored.""" | ||
| husky_dir = tmp_path / ".husky" | ||
| husky_dir.mkdir() | ||
| (husky_dir / "_").mkdir() | ||
| (husky_dir / "pre-commit").write_text("#!/bin/sh\nnpx lint-staged\n") | ||
|
|
||
| repo = _make_repo(tmp_path) | ||
| assessor = DeterministicEnforcementAssessor() | ||
| finding = assessor.assess(repo) | ||
|
|
||
| assert finding.score >= 60.0 | ||
|
|
There was a problem hiding this comment.
test_husky_ignores_underscore_files doesn’t verify the intended rule
This test uses an underscore directory (.husky/_), but production logic already ignores directories via f.is_file(). So it won’t catch regressions in underscore-file filtering. Add a case with an underscore-prefixed file (e.g., .husky/_local) and assert it does not contribute to hook detection/score.
As per coding guidelines, "tests/**: Verify test actually tests the intended behavior."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/unit/test_assessors_testing.py` around lines 594 - 606, The test
test_husky_ignores_underscore_files currently creates an underscore directory so
it doesn't exercise the underscore-file filtering; update the test to create an
underscore-prefixed file (e.g., create a file named ".husky/_local" with
hook-like content) instead of or in addition to the directory, using the same
tmp_path and _make_repo setup, then run
DeterministicEnforcementAssessor().assess(repo) and assert the finding.score
remains >= 60.0 (or that hook detection count doesn't increase) to verify
underscore-prefixed files are ignored; reference the test function name
test_husky_ignores_underscore_files and the helper _make_repo to locate where to
change the fixture.
Summary
Closes #433
.husky/with hook scripts now scores 60 pts (same as.pre-commit-config.yaml), so JS/TS/Java projects using Husky can pass the deterministic enforcement attribute.husky/directory (no hook scripts) stays at 10 pts_/husky.sh) are correctly ignorednpx husky init)docs/attributes.mdupdated with new scoring breakdownTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
Bug Fixes
Documentation