From 124eed9ba1206819915a4ac35979a2a0d7b7373e Mon Sep 17 00:00:00 2001 From: glenn-jocher Date: Sun, 2 Aug 2026 20:04:58 +0200 Subject: [PATCH] Fix capitalize-comments hook entry point and add AGENTS.md --- .pre-commit-hooks.yaml | 2 +- AGENTS.md | 63 ++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + README.md | 7 +++-- capitalize_comments.py | 10 +++++-- pyproject.toml | 3 ++ 6 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index e2b4c0b..58e0d5a 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -5,6 +5,6 @@ - id: capitalize-comments name: Capitalize standalone inline comments description: This hook capitalizes the first letter in standalone inline comments. - entry: capitalize_comments.py + entry: capitalize-comments language: python types: [python] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5d95c8d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,63 @@ +# AGENTS.md + +This file provides guidance to AI coding agents (Claude Code, etc.) when working with code in this repository. CLAUDE.md is a symlink to this file. + +Ultralytics Pre-commit (`ultralytics-pre-commit-hooks`, AGPL-3.0) is a small collection of [pre-commit](https://pre-commit.com/) hooks that enforce Ultralytics code-quality standards. Consumers reference this repository from their own `.pre-commit-config.yaml`; it is not published to PyPI. + +## Core Principles (CRITICAL) + +**Less is more. The simplest solution is the best solution.** The action hierarchy for every change: **Delete > Replace > Add**. + +1. **Solve at the owner**: Put behavior in the code path that owns or observes it. For fixes, never guard a symptom with a staleness check, initialization flag, skip-first-call branch, or `try/except` around broken logic; relocate the trigger and delete the wrong path. For features, extend the existing owner rather than creating a parallel abstraction. +2. **Search and reuse first**: Search the whole repository before creating a feature, component, helper, workflow, or utility. Reuse or adapt what exists, consolidate in-scope duplication in the shared owner, and delete duplicate paths. Three similar lines beat a helper nobody else calls. +3. **Delete and modify existing code before creating new code**: Bugfixes are net-negative by default unless deletion and relocation are demonstrably impossible. A new file must first prove it cannot fit cleanly in an existing owner. +4. **Keep scope minimal**: Implement only the simplest complete solution. Avoid impossible-state handling, speculative flags, compatibility shims, policy scaffolding, and unrelated cleanup. Tests are out of scope by default — rely on existing coverage and focused validation; only an uncovered, high-risk regression path justifies minimal new test code. +5. **Ship zero-regression, production-ready changes**: Understand what you remove instead of retaining broken code as insurance. Remove unused imports, functions, types, files, and comments; run relevant cleanup checks; and thoroughly debug and validate the changed owner. Do not break existing features or workflows unless the PR intentionally removes them with evidence. + +**Review gate:** for every addition, the reviewer decides whether deleting or changing existing code would have fixed the problem instead — if it would, that is a blocking finding. A missing or thin PR description is never itself a finding. + +NEVER push to `main`. NEVER force push. Always start work in a new git worktree (`git worktree add`) on a feature branch and open a PR — never edit the primary checkout directly, it may hold in-flight work. + +## PR Workflow + +After opening a PR: + +1. Wait for the automated PR review and auto-format commit from Ultralytics Actions (`format.yml`), then pull and address every finding. +2. Review the full diff in-session against the Core Principles, performance, and the review gate above, then batch the fixes into one commit and push. After each round of bot or human commits, pull and resume the same reviewer on `..HEAD` plus anything that delta could have invalidated. Repeat until the local head matches the live head. +3. Hand off or merge only on a clean final pass: one cold full-diff review returning LGTM with no findings, on a head that is still live at merge time. +4. Never fight other commits: Ultralytics Actions pushes auto-format and header commits, and multiple users may work on the same PR. `git pull --rebase` before pushing; never reset or revert commits you did not author. +5. After the PR merges, clean up: remove local worktrees and branches for it, then `git checkout main && git pull`. + +## Commands + +```bash +# Run a hook end-to-end exactly as a consumer would, from a scratch git repo +uvx pre-commit try-repo /path/to/this/clone capitalize-comments --all-files + +# Run a hook script directly against files +python capitalize_comments.py path/to/file.py +``` + +- There is no test suite. CI is only `format.yml` (Ultralytics Actions autoformat, labels, PR summaries) and `cla.yml`; neither exercises the hooks. +- Always verify a hook change with `pre-commit try-repo` against a scratch repository — a hook that fails to resolve its entry point still reports as a normal hook failure, so nothing else in this repo will catch it. + +## Architecture + +Each hook is a standalone Python script at the repository root exposing a `main()` that processes the file paths pre-commit passes in `sys.argv[1:]`: + +- `capitalize_comments.py` — capitalizes the first letter of standalone inline comments. It skips any line containing an `EXCLUDE_TERMS` token and any comment directly preceded by another comment, so only single-line comments are rewritten. + +Three files must stay in sync for a hook to run: + +- `capitalize_comments.py` (or any new hook script) defines `main()`. +- `pyproject.toml` `[project.scripts]` maps a console-script name to `module:main`. +- `.pre-commit-hooks.yaml` declares the hook's `id`, `name`, `description`, `entry`, `language`, and `types`. + +`language: python` hooks resolve `entry` on `PATH` inside the isolated environment pre-commit builds from `pyproject.toml`, so `entry` must be a console-script name, never a filename — a filename fails at run time with ``Executable `...` not found``. The hook `id` is the consumer-facing contract: renaming one breaks every `.pre-commit-config.yaml` that references it. + +## Conventions + +- Every file starts with `# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license` — Ultralytics Actions adds headers automatically; don't add or revert them manually. +- Google-style docstrings and Ruff formatting at line length 120, applied automatically by `format.yml` on PRs. +- Hooks rewrite files in place and let pre-commit report the diff, so they must be idempotent: a second run over the same files must change nothing. +- `version` in `pyproject.toml` is pinned at `0.0.0` because consumers pin a git `rev` rather than a release — leave it alone. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md index 5d6bec2..df49751 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository is organized for clarity and ease of use: - **Hook scripts:** Individual [Python](https://www.python.org/) scripts live at the repository root, each representing a distinct pre-commit hook designed for specific checks or code formatting tasks. - **`.pre-commit-hooks.yaml`:** The root configuration file defining the available hooks, making them discoverable and easy to integrate into your projects. +- **`pyproject.toml`:** Packages the repository and exposes each hook script as a console script under `[project.scripts]`, which is what `.pre-commit-hooks.yaml` invokes. ### Available Hook Scripts 📂 @@ -29,7 +30,7 @@ The `.pre-commit-hooks.yaml` file lists all installable hooks. Here's an example ```yaml - id: hook-id # Unique identifier for the hook name: "A descriptive name for the hook" # User-friendly name - entry: your_hook_script.py # Path to the hook script + entry: your-hook-command # Console script declared in [project.scripts] language: python # Specifies the language the script is written in types: [python] # File types the hook should run on ``` @@ -38,8 +39,8 @@ The `.pre-commit-hooks.yaml` file lists all installable hooks. Here's an example Interested in contributing a new hook? Follow these steps: -1. **Create Your Hook Script:** Develop a Python script implementing your desired check or formatting logic. Place it at the repository root, or use a subdirectory and reference that path from `.pre-commit-hooks.yaml`. Ensure your script includes clear error messages and handles potential edge cases. -2. **Declare the Hook:** Add a new entry for your hook in the `.pre-commit-hooks.yaml` file, following the structure shown above. +1. **Create Your Hook Script:** Develop a Python script implementing your desired check or formatting logic, exposing a `main()` that reads file paths from `sys.argv[1:]`. Place it at the repository root. Ensure your script includes clear error messages and handles potential edge cases. +2. **Declare the Hook:** Register the script as a console script in `pyproject.toml` under `[project.scripts]`, then add a new entry for your hook in the `.pre-commit-hooks.yaml` file whose `entry` is that console script name, following the structure shown above. 3. **Test Locally:** Thoroughly test your hook in a local project environment using `pre-commit run --all-files` to confirm it works as expected before submitting. 4. **Submit Your Contribution:** Commit your changes and create a Pull Request to the repository. Adhering to our [contribution guidelines](https://docs.ultralytics.com/help/contributing/) is appreciated. diff --git a/capitalize_comments.py b/capitalize_comments.py index 4802b7b..0965d10 100755 --- a/capitalize_comments.py +++ b/capitalize_comments.py @@ -1,6 +1,7 @@ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license import re +import sys from pathlib import Path EXCLUDE_TERMS = ["import", "return", "def ", "class ", ".", "=", ":", "(", ",", "["] @@ -39,8 +40,11 @@ def process_file(file_path: Path): f.writelines(processed_lines) -if __name__ == "__main__": - import sys - +def main(): + """Capitalizes standalone inline comments in each file passed on the command line.""" for file in sys.argv[1:]: process_file(Path(file)) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 73df4a7..c30a9d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,9 @@ classifiers = [ "Topic :: Utilities", ] +[project.scripts] +capitalize-comments = "capitalize_comments:main" + [tool.black] line-length = 120