From 40ddb9caa413e50fe57e606faae5d195e238409f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 24 Jul 2026 16:43:09 +0200 Subject: [PATCH 1/5] Simplify file header updates (#836) --- actions/__init__.py | 2 +- actions/update_file_headers.py | 16 +- tests/test_file_headers.py | 259 --------------------------------- 3 files changed, 6 insertions(+), 271 deletions(-) delete mode 100644 tests/test_file_headers.py diff --git a/actions/__init__.py b/actions/__init__.py index 0ade891..22d88b2 100644 --- a/actions/__init__.py +++ b/actions/__init__.py @@ -28,4 +28,4 @@ # ├── test_summarize_pr.py # └── ... -__version__ = "0.2.37" +__version__ = "0.2.38" diff --git a/actions/update_file_headers.py b/actions/update_file_headers.py index fec0f9a..d6e1743 100644 --- a/actions/update_file_headers.py +++ b/actions/update_file_headers.py @@ -120,13 +120,9 @@ def update_file(file_path, prefix, block_start, block_end, base_header): # Check for special first line special_line_index = -1 - first_line = lines[0].lstrip("\ufeff") if lines else "" + first_line = lines[0].lstrip("\ufeff").lower() if lines else "" encoding_cookie = r"^[ \t\f]*#.*?coding[:=][ \t]*[-_.a-zA-Z0-9]+" - if lines and ( - first_line.startswith(("#!", " 1 and re.match(encoding_cookie, lines[1]): @@ -136,14 +132,12 @@ def update_file(file_path, prefix, block_start, block_end, base_header): start_idx = special_line_index + 1 if special_line_index >= 0 else 0 end_idx = min(start_idx + 5, len(lines)) # Look in first few lines - # An existing header must be the first non-empty line and use the file's comment syntax. candidate_index = next((i for i in range(start_idx, end_idx) if lines[i].strip()), -1) candidate = lines[candidate_index].strip() if candidate_index >= 0 else "" comment_start = (prefix or block_start or "# ").strip() - known_header = candidate.startswith(comment_start) and re.search( - r"AGPL-3\.0 License|CONFIDENTIAL: Unauthorized use|©\s*2014[–-]\d{4}\s+Ultralytics Inc\.|" - r"Ultralytics Inc\..*Copyright ©\s*2014[–-]\d{4}\s*-\s*CONFIDENTIAL\s*-", - candidate, + header = candidate[len(comment_start) :].strip() if candidate.startswith(comment_start) else "" + known_header = header.startswith(("Ultralytics 🚀 AGPL-3.0 License", "Ultralytics Inc. 🚀 Copyright")) or ( + header.startswith("© 2014-") and " Ultralytics Inc." in header ) header_index = candidate_index if candidate == formatted_header.strip() or known_header else -1 diff --git a/tests/test_file_headers.py b/tests/test_file_headers.py deleted file mode 100644 index f306386..0000000 --- a/tests/test_file_headers.py +++ /dev/null @@ -1,259 +0,0 @@ -# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license -"""Tests for the file headers update functionality.""" - -from pathlib import Path -from tempfile import TemporaryDirectory -from unittest.mock import patch - -from actions.update_file_headers import COMMENT_MAP, IGNORE_PATHS, main, update_file - - -def test_update_file_python(): - """Test updating Python file headers.""" - with TemporaryDirectory() as tmp_dir: - # Create a test Python file - test_file = Path(tmp_dir) / "test.py" - test_file.write_text("print('Hello World')\n") - - # Update file - result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") - - # Check results - assert result is True - content = test_file.read_text() - assert content.startswith("# Ultralytics 🚀 Test Header\n\n") - assert "print('Hello World')" in content - - -def test_update_file_cpp(): - """Test updating C++ file headers.""" - with TemporaryDirectory() as tmp_dir: - # Create a test C++ file - test_file = Path(tmp_dir) / "test.cpp" - test_file.write_text("#include \n\nint main() {\n return 0;\n}\n") - - # Update file - result = update_file(test_file, "// ", "/* ", " */", "Ultralytics 🚀 Test Header") - - # Check results - assert result is True - content = test_file.read_text() - assert content.startswith("// Ultralytics 🚀 Test Header\n\n") - assert "#include " in content - - -def test_update_file_with_existing_header(): - """Test updating file with existing header.""" - with TemporaryDirectory() as tmp_dir: - # Create a test file with existing header - test_file = Path(tmp_dir) / "test.py" - test_file.write_text("# Ultralytics 🚀 AGPL-3.0 License\n\ndef main():\n pass\n") - - # Update file - result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") - - # Check results - assert result is True - content = test_file.read_text() - assert content.startswith("# Ultralytics 🚀 Test Header\n\n") - assert "def main():" in content - - -def test_update_file_with_shebang(): - """Test updating file with shebang.""" - with TemporaryDirectory() as tmp_dir: - # Create a test file with shebang - test_file = Path(tmp_dir) / "test.py" - test_file.write_text("#!/usr/bin/env python3\n\nprint('Hello World')\n") - - # Update file - result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") - - # Check results - assert result is True - content = test_file.read_text() - assert content.startswith("#!/usr/bin/env python3\n# Ultralytics 🚀 Test Header\n\n") - assert "print('Hello World')" in content - - -def test_update_file_preserves_early_ultralytics_source(): - """Test that Ultralytics references in source code are not mistaken for headers.""" - with TemporaryDirectory() as tmp_dir: - tmp_path = Path(tmp_dir) - docstring_file = tmp_path / "api.py" - docstring_file.write_text('"""Ultralytics Platform API client."""\n\nVALUE = 1\n') - import_file = tmp_path / "__init__.py" - import_file.write_text("from ultralytics import YOLO\n") - - for test_file, spacing in ((docstring_file, ""), (import_file, "\n")): - original = test_file.read_text() - assert update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") is True - assert test_file.read_text() == f"# Ultralytics 🚀 Test Header\n{spacing}{original}" - - -def test_update_file_with_lowercase_doctype(): - """Test that Prettier's lowercase HTML doctype remains first.""" - with TemporaryDirectory() as tmp_dir: - test_file = Path(tmp_dir) / "index.html" - test_file.write_text("\n\n") - - assert update_file(test_file, None, "", "Ultralytics 🚀 Test Header") is True - assert test_file.read_text().startswith("\n\n\n") - - -def test_update_file_with_bom_xml_declaration(): - """Test that a BOM-prefixed XML declaration remains first.""" - with TemporaryDirectory() as tmp_dir: - test_file = Path(tmp_dir) / "layout.xml" - test_file.write_text('\ufeff\n\n') - - assert update_file(test_file, None, "", "Ultralytics 🚀 Test Header") is True - assert test_file.read_text().startswith( - '\ufeff\n\n\n' - ) - - -def test_update_file_with_shebang_and_encoding_cookie(): - """Test that a Python encoding cookie remains on line two and the header stays idempotent.""" - with TemporaryDirectory() as tmp_dir: - test_file = Path(tmp_dir) / "script.py" - test_file.write_text( - "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Ultralytics 🚀 AGPL-3.0 License\n\nprint('hello')\n" - ) - - assert update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") is True - assert test_file.read_text().startswith( - "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Ultralytics 🚀 Test Header\n\n" - ) - assert update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") is False - - -def test_update_file_replaces_legacy_private_header(): - """Test that the previously emitted private header is replaced rather than duplicated.""" - with TemporaryDirectory() as tmp_dir: - test_file = Path(tmp_dir) / "app.py" - test_file.write_text( - "# Ultralytics Inc. 🚀 Copyright © 2014-2025 - CONFIDENTIAL - " - "https://ultralytics.com - All Rights Reserved\n\n" - "print('hello')\n" - ) - - assert update_file(test_file, "# ", None, None, "© 2014-2026 Ultralytics Inc. CONFIDENTIAL") is True - assert test_file.read_text().startswith("# © 2014-2026 Ultralytics Inc. CONFIDENTIAL\n\n") - assert "Copyright" not in test_file.read_text() - - -def test_update_file_no_changes(): - """Test updating file with no changes needed.""" - with TemporaryDirectory() as tmp_dir: - # Create a test file with correct header - test_file = Path(tmp_dir) / "test.py" - test_file.write_text("# Ultralytics 🚀 Test Header\n\nprint('Hello World')\n") - - # Update file - result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") - - # Check results - assert result is False # No changes made - - -def test_update_file_edge_cases(): - """Test header handling for empty, unreadable, and block-comment files.""" - with TemporaryDirectory() as tmp_dir: - tmp_path = Path(tmp_dir) - empty_file = tmp_path / "empty.py" - empty_file.write_text("") - assert update_file(empty_file, "# ", None, None, "Ultralytics 🚀 Test Header") is False - assert update_file(tmp_path, "# ", None, None, "Ultralytics 🚀 Test Header") is False - - css_file = tmp_path / "style.css" - css_file.write_text("body { color: black; }\n") - assert update_file(css_file, None, "/* ", " */", "Ultralytics 🚀 Test Header") is True - assert css_file.read_text().startswith("/* Ultralytics 🚀 Test Header */\n\n") - - -def test_comment_map_coverage(): - """Test that all supported file extensions have defined comment styles.""" - # Check for a few key extensions - assert ".py" in COMMENT_MAP - assert ".cpp" in COMMENT_MAP - assert ".js" in COMMENT_MAP - assert ".html" in COMMENT_MAP - - # Check comment style format - for ext, (prefix, block_start, block_end) in COMMENT_MAP.items(): - assert isinstance(ext, str) - assert prefix is None or isinstance(prefix, str) - assert block_start is None or isinstance(block_start, str) - assert block_end is None or isinstance(block_end, str) - - -def test_ignore_paths(): - """Test that the ignore paths list exists and contains expected entries.""" - assert isinstance(IGNORE_PATHS, set) - assert ".git" in IGNORE_PATHS - assert "__pycache__" in IGNORE_PATHS - - -def test_main_real_files(): - """Test main function on actual repository files.""" - with patch("actions.update_file_headers.update_file", return_value=False) as mock_update, patch( - "actions.update_file_headers.Action" - ) as mock_action: - mock_action.return_value.repository = "ultralytics/actions" - mock_action.return_value.is_repo_private.return_value = False - main() - assert mock_update.call_count > 0 - - -def test_main_with_custom_header(): - """Test main function with custom header environment variable.""" - with patch("actions.update_file_headers.update_file", return_value=False), patch( - "actions.update_file_headers.HEADER", "Custom Test Header" - ), patch("actions.update_file_headers.Action") as mock_action: - mock_event = mock_action.return_value - mock_event.repository = "test/repo" - main() - mock_action.assert_called_once() - - -def test_main_private_and_skipped_repos(): - """Test main selects private headers and skips repos without a header source.""" - with TemporaryDirectory() as tmp_dir: - tmp_path = Path(tmp_dir) - (tmp_path / "app.py").write_text("print('hello')\n") - - with patch("actions.update_file_headers.Path.cwd", return_value=tmp_path), patch( - "actions.update_file_headers.update_file", return_value=False - ) as mock_update, patch("actions.update_file_headers.Action") as mock_action: - mock_event = mock_action.return_value - mock_event.repository = "ultralytics/private" - mock_event.is_repo_private.return_value = True - main() - assert any("CONFIDENTIAL" in call.args[4] for call in mock_update.call_args_list) - - with patch("actions.update_file_headers.update_file", return_value=False) as mock_update, patch( - "actions.update_file_headers.HEADER", None - ), patch("actions.update_file_headers.Action") as mock_action: - mock_action.return_value.repository = "other/repo" - main() - mock_action.return_value.is_repo_private.assert_not_called() - mock_update.assert_not_called() - - -def test_main_updates_files_in_current_directory(): - """Test main updates supported files under the current directory.""" - with TemporaryDirectory() as tmp_dir: - tmp_path = Path(tmp_dir) - test_file = tmp_path / "app.py" - test_file.write_text("print('hello')\n") - - with patch("actions.update_file_headers.Path.cwd", return_value=tmp_path), patch( - "actions.update_file_headers.Action" - ) as mock_action: - mock_event = mock_action.return_value - mock_event.repository = "ultralytics/actions" - mock_event.is_repo_private.return_value = False - main() - - assert test_file.read_text().startswith("# Ultralytics 🚀 AGPL-3.0 License") From 9a0454d9c137c09b1acf81c906f6b3a4870fd28b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 24 Jul 2026 16:52:46 +0200 Subject: [PATCH 2/5] Free Docker and swap disk space (#838) --- cleanup-disk/README.md | 8 ++++++-- cleanup-disk/action.yml | 11 +++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cleanup-disk/README.md b/cleanup-disk/README.md index 723e2bf..70e630a 100644 --- a/cleanup-disk/README.md +++ b/cleanup-disk/README.md @@ -2,7 +2,7 @@ # 🧹 Disk Space Cleanup Action -Cleans up disk space on Ubuntu GitHub Actions runners by removing unnecessary tool caches and swap space. Frees up ~19GB total space. +Cleans up disk space on Ubuntu GitHub Actions runners by removing unnecessary tool caches, Docker images, and swap space. ## 🚀 Usage @@ -10,6 +10,9 @@ Cleans up disk space on Ubuntu GitHub Actions runners by removing unnecessary to Add as early step in jobs requiring disk space: +> [!IMPORTANT] +> Run cleanup before pulling or building Docker images because it removes all images unused by containers. + ```yaml steps: - uses: ultralytics/actions/cleanup-disk@main @@ -57,7 +60,8 @@ steps: ## 🗑️ What Gets Cleaned - `/opt/hostedtoolcache` - Tool cache (~15GB) -- `/swapfile` - Swap space (~4GB) +- Unused Docker images +- `/swapfile` or `/mnt/swapfile` - Swap space (~4GB) ## 💡 When to Use diff --git a/cleanup-disk/action.yml b/cleanup-disk/action.yml index e14592b..72c3012 100644 --- a/cleanup-disk/action.yml +++ b/cleanup-disk/action.yml @@ -2,7 +2,7 @@ name: "Disk Space Cleanup" author: "Ultralytics" -description: "Cleans up disk space by removing unnecessary tool caches and swap space" +description: "Cleans up disk space by removing unnecessary tool caches, Docker images, and swap space" runs: using: "composite" steps: @@ -13,21 +13,24 @@ runs: df -h / echo "::endgroup::" - echo "::group::Remove Tool Cache and Swap" + echo "::group::Remove Tool Cache, Docker Images, and Swap" toolcache_pid="" if [ -e /opt/hostedtoolcache ]; then # Remove tool cache to free up ~15GB of space per https://github.com/ultralytics/ultralytics/pull/15848 rm -rf /opt/hostedtoolcache & toolcache_pid=$! fi + docker image prune --all --force & + docker_pid=$! # Remove swap space to free up ~4GB - if [ -f /swapfile ]; then + if [ -f /swapfile ] || [ -f /mnt/swapfile ]; then sudo swapoff -a - sudo rm -f /swapfile + sudo rm -f /swapfile /mnt/swapfile fi if [ -n "$toolcache_pid" ]; then wait "$toolcache_pid" || true fi + wait "$docker_pid" || true echo "::endgroup::" echo "::group::Disk Space After Cleanup" From 1304ff00258a8183acda52db658af8976300fa26 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 24 Jul 2026 22:01:16 +0200 Subject: [PATCH 3/5] Keep unresolved broken links instead of certifying them clean (#840) Co-authored-by: UltralyticsAssistant --- actions/__init__.py | 2 +- actions/utils/common_utils.py | 64 ++++++++++++++++++++++------------- tests/test_urls.py | 22 ++++++++++++ 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/actions/__init__.py b/actions/__init__.py index 22d88b2..c3fee67 100644 --- a/actions/__init__.py +++ b/actions/__init__.py @@ -28,4 +28,4 @@ # ├── test_summarize_pr.py # └── ... -__version__ = "0.2.38" +__version__ = "0.2.39" diff --git a/actions/utils/common_utils.py b/actions/utils/common_utils.py index 321d6d5..da57ecc 100644 --- a/actions/utils/common_utils.py +++ b/actions/utils/common_utils.py @@ -182,9 +182,9 @@ REDIRECT_END_IGNORE_LIST = frozenset( } ) URL_PATTERN = re.compile( - r"\[([^]]+)]\(([^)]+)\)" # Matches Markdown links [text](url) + r"\[(?P[^]]+)]\((?P[^)]+)\)" # Matches Markdown links [text](url) r"|" - r"(" # Start capturing group for plaintext URLs + r"(?P" # Start capturing group for plaintext URLs r"(?:https?://)?" # Optional http:// or https:// r"(?:www\.)?" # Optional www. r"(?:[\w.-]+)?" # Optional domain name and subdomains @@ -292,8 +292,14 @@ def brave_search(query, api_key, count=5): if len(query) > 400: print(f"WARNING ⚠️ Brave search query length {len(query)} exceed limit of 400 characters, truncating.") url = f"https://api.search.brave.com/res/v1/web/search?q={parse.quote(query.strip()[:400])}&count={count}" - response = requests.get(url, headers={"X-Subscription-Token": api_key, "Accept": "application/json"}) - data = response.json() if response.status_code == 200 else {} + try: + response = requests.get( + url, headers={"X-Subscription-Token": api_key, "Accept": "application/json"}, timeout=10 + ) + data = response.json() if response.status_code == 200 else {} + except Exception as e: # a search outage must never block the caller from returning its text + print(f"WARNING ⚠️ Brave search failed: {e}") + return [] results = data.get("web", {}).get("results", []) if data else [] return [result.get("url") for result in results if result.get("url")] @@ -351,10 +357,10 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=3, return_url= def check_links_in_string(text, verbose=True, return_bad=False, replace=False): """Process text, find URLs, check for 404s, and handle replacements with redirects or Brave search.""" urls = [] - for md_text, md_url, plain_url in URL_PATTERN.findall(text): - url = md_url or plain_url + for match in URL_PATTERN.finditer(text): + url = match["md_url"] or match["plain_url"] if url and parse.urlparse(url).scheme: - urls.append((md_text, clean_url(url))) + urls.append((match["md_text"] or "", clean_url(url))) with requests.Session() as session, ThreadPoolExecutor(max_workers=64) as executor: session.headers.update(REQUESTS_HEADERS) @@ -363,35 +369,47 @@ def check_links_in_string(text, verbose=True, return_bad=False, replace=False): bad_urls = [url for (title, url), (valid, redirect) in zip(urls, results) if not valid] if replace: - replacements = {} - modified_text = text + replacements, searched = {}, set() # Process all URLs for replacements brave_api_key = os.getenv("BRAVE_API_KEY") for (title, url), (valid, redirect) in zip(urls, results): - # Handle invalid URLs with Brave search - if not valid and brave_api_key: - query = f"{(redirect or url)[:200]} {title[:199]}" - if search_urls := brave_search(query, brave_api_key, count=3): - best_url = next( - (alt_url for alt_url in search_urls if is_url(alt_url, session)), - search_urls[0], - ) - if url != best_url: + # Handle invalid URLs with Brave search. Two queries, not two attempts: the dead URL biases the + # first toward the site root, so the second drops it and searches the link text on its domain. + if not valid: + if url in searched: # search once per URL, however many times it occurs + continue + searched.add(url) + for query in ( + f"{(redirect or url)[:200]} {title[:199]}", + f"{title[:199]} {parse.urlparse(url).netloc}", + ): + search_urls = brave_search(query, brave_api_key, count=3) or [] + if best_url := next((u for u in search_urls if u != url and is_url(u, session)), None): replacements[url] = best_url - modified_text = modified_text.replace(url, best_url) + break # Handle redirects for valid URLs - elif valid and redirect and redirect != url: + elif redirect and redirect != url: replacements[url] = redirect - modified_text = modified_text.replace(url, redirect) if verbose and replacements: print( f"WARNING ⚠️ replaced {len(replacements)} links:\n" + "\n".join(f" {k}: {v}" for k, v in replacements.items()) ) - if replacements: - return (True, bad_urls, modified_text) if return_bad else modified_text + + def replace_link(match): + """Swap a matched URL for its replacement, leaving the surrounding link syntax untouched.""" + group = "md_url" if match["md_url"] else "plain_url" + raw_url = match[group] + if not (new_url := replacements.get(clean_url(raw_url))): + return match[0] + start, end = (i - match.start() for i in match.span(group)) + suffix = raw_url[len(raw_url.rstrip(".,:;!?`\\")) :] # trailing punctuation clean_url() dropped + return f"{match[0][:start]}{new_url}{suffix}{match[0][end:]}" + + text = URL_PATTERN.sub(replace_link, text) + bad_urls = [url for url in bad_urls if url not in replacements] # unfixable links stay reported passing = not bad_urls if verbose and not passing: diff --git a/tests/test_urls.py b/tests/test_urls.py index 72f6174..30af8d1 100644 --- a/tests/test_urls.py +++ b/tests/test_urls.py @@ -134,6 +134,28 @@ def test_urls_with_different_tlds(verbose): assert mock_is_url.call_count == 5 +def test_replace_keeps_unresolved_links(monkeypatch): + """Replace links the search can fix, keep and report the ones it cannot, and never touch a longer neighbor.""" + text = "[Broken](https://site.test/bad) and [Fixed](https://site.test/gone) and https://site.test/gone/deeper" + + def fake_is_url(url, session=None, check=True, max_attempts=3, timeout=3, return_url=False, redirect=False): + valid = url in {"https://new.test", "https://site.test/gone/deeper"} + return (valid, url) if return_url else valid + + monkeypatch.setenv("BRAVE_API_KEY", "test-key") + with patch("actions.utils.common_utils.is_url", side_effect=fake_is_url), patch( + "actions.utils.common_utils.brave_search", + side_effect=lambda query, *_, **__: [] if "Broken" in query else ["https://new.test"], + ): + result = check_links_in_string(text, verbose=False, return_bad=True, replace=True) + + assert result == ( + False, + ["https://site.test/bad"], + "[Broken](https://site.test/bad) and [Fixed](https://new.test) and https://site.test/gone/deeper", + ) + + def test_case_sensitivity(verbose): """Tests URL case sensitivity by verifying that URLs with different cases are correctly identified and handled.""" text = "Case test: HTTPS://err.com and https://err.com" From c11c71a91c4890e51b721550f20f5bc2e4a8f8e0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 25 Jul 2026 01:30:17 +0200 Subject: [PATCH 4/5] Consolidate AGENTS.md Core Principles from eight to five (#841) --- AGENTS.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b5b2053..98b2d08 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,22 +4,17 @@ This file provides guidance to AI coding agents (Claude Code, etc.) when working ## Core Principles (CRITICAL) -Respecting these principles is critical for every PR. +**Less is more. The simplest solution is the best solution.** The action hierarchy for every change: **Delete > Replace > Add**. -**Less is more. The simplest solution is the best solution.** +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 anything new — a module, helper, utility, composite action, or workflow. 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 commented-out code; 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. -The action hierarchy for every change: **Delete > Replace > Add**. The best code change is a deletion. The second best is modifying what exists. Adding new code is the last resort. +**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. -1. **Minimal**: The simplest solution that works. Do not over-engineer, over-abstract, or add code just in case. Three similar lines beat a premature abstraction. Avoid error handling for impossible states, feature flags, compatibility shims, or policy scaffolding unless they are truly required. -2. **Solve at the source**: Do not hack fixes. Solve problems at their root. If something is broken, fix or remove the broken thing. Never patch over a broken abstraction, add workarounds, or add synchronization code for state that should not be duplicated. -3. **Delete ruthlessly**: When replacing code, delete what it replaced. Remove unused imports, functions, types, files, and commented-out code. Git preserves history. Run the repo's relevant dead-code or cleanup check when available. -4. **Replace > Add**: Modify existing code over adding new code. Edit existing files, extend existing components or functions with minimal parameters, and reuse existing utilities. If creating a new file, first prove it cannot fit cleanly in an existing file. -5. **Check existing**: Search the entire repo before creating anything new. If a feature, component, helper, responder, workflow, or utility already solves a similar problem, reuse or adapt it and delete the duplicate path. -6. **Deduplicate**: Do not duplicate existing code when updating the repo. Consolidate or refactor duplicates you find when it is in scope and low risk. -7. **Zero Regression**: Do not break existing features or workflows unless the PR intentionally removes them with evidence. -8. **Production ready**: All changes must be thoroughly debugged, validated, and production ready. - -**When fixing bugs, ask: "What can I delete?" before "What can I replace?" before "What should I add?"** +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 @@ -27,7 +22,7 @@ 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. Launch an independent adversarial review agent with cold context (just the PR diff and this file) to hunt for bugs, regressions, and Core Principles violations. Fix, push, and repeat with a fresh agent until one reports LGTM. -3. 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 force-push, reset, or revert commits you did not author. +3. 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. 4. After the PR merges, clean up: remove local worktrees and branches for it, then `git checkout main && git pull`. ## Commands From 023864fc8842ad0c65a0a5120c3b4468ace52fc4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 25 Jul 2026 19:16:06 +0200 Subject: [PATCH 5/5] Speed up action setup with tarball install and parallel tool installs (#842) Co-authored-by: UltralyticsAssistant --- action.yml | 22 +++++++++++----------- actions/__init__.py | 2 +- actions/format_code.py | 9 +++++---- actions/update_markdown_code_blocks.py | 17 ++++++++--------- dependabot/action.yml | 4 ++-- github-report/action.yml | 4 ++-- tests/test_update_markdown_codeblocks.py | 6 +++--- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/action.yml b/action.yml index 009a760..4a149a7 100644 --- a/action.yml +++ b/action.yml @@ -113,17 +113,17 @@ runs: env: INPUTS_SPELLING: ${{ inputs.spelling }} GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_REF: ${{ github.ref }} + GITHUB_SHA: ${{ github.sha }} run: | echo "::group::Install Dependencies" trap 'echo "::endgroup::"' EXIT - # Install from current git branch if testing in ultralytics/actions repo, otherwise from GitHub main branch + # Source tarballs install ~2x faster than git+ URLs, which clone the repository first. Test the current commit + # in the ultralytics/actions repo, otherwise install main. if [ "$GITHUB_REPOSITORY" = "ultralytics/actions" ]; then - echo "Installing from git branch: $GITHUB_REF" - packages="git+https://github.com/ultralytics/actions@${GITHUB_REF#refs/heads/}" + echo "Installing from commit: $GITHUB_SHA" + packages="https://github.com/ultralytics/actions/archive/${GITHUB_SHA}.tar.gz" else - # packages="ultralytics-actions" - packages="git+https://github.com/ultralytics/actions@main" + packages="https://github.com/ultralytics/actions/archive/refs/heads/main.tar.gz" fi if [ "$INPUTS_SPELLING" = "true" ]; then @@ -216,18 +216,18 @@ runs: run: | echo "::group::Run Prettier" trap 'echo "::endgroup::"' EXIT - npm install -g prettier@3.6.2 prettier-plugin-sh + npm install -g prettier@3.6.2 + curl -fsSL "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')" -o "$(npm prefix -g)/bin/shfmt" + chmod +x "$(npm prefix -g)/bin/shfmt" ultralytics-actions-update-markdown-code-blocks npx prettier --write --list-different --print-width 120 "**/*.{js,jsx,ts,tsx,css,less,scss,json,yml,yaml,html,vue,svelte}" '!**/*lock.{json,yaml,yml}' '!**/*.lock' '!**/model.json' '!**/*.min.js' '!**/*.min.css' - # Bash file format - if find . -name "*.sh" -type f | grep -q .; then - npx prettier --write --list-different --print-width 120 --plugin=$(npm root -g)/prettier-plugin-sh/lib/index.cjs "**/*.sh" - fi # Handle Markdown separately find . -name "*.md" -type f ! -path "*/docs/*" -exec npx prettier --write --list-different --print-width 120 {} + if [ -d "./docs" ]; then find ./docs -name "*.md" -type f ! -path "*/reference/*" -exec npx prettier --tab-width 4 --print-width 120 --write --list-different {} + fi + # Bash last: shfmt exits non-zero when a script fails to parse, which would otherwise skip the formatting above + find . -name "*.sh" -type f -not -path "*/node_modules/*" -not -path "*/.git/*" -exec shfmt -i 2 -sr -bn -ci -l -w {} + shell: bash continue-on-error: true diff --git a/actions/__init__.py b/actions/__init__.py index c3fee67..e301976 100644 --- a/actions/__init__.py +++ b/actions/__init__.py @@ -28,4 +28,4 @@ # ├── test_summarize_pr.py # └── ... -__version__ = "0.2.39" +__version__ = "0.2.40" diff --git a/actions/format_code.py b/actions/format_code.py index ca42770..810780e 100644 --- a/actions/format_code.py +++ b/actions/format_code.py @@ -19,17 +19,18 @@ RUFF_CHECK = [ RUFF_FORMAT = ["ruff", "format", "--line-length=120", "."] DOCSTRINGS = ["ultralytics-actions-format-python-docstrings", "."] PRETTIER = """ -npm install -g prettier@3.6.2 prettier-plugin-sh +npm install -g prettier@3.6.2 +curl -fsSL "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')" -o "$(npm prefix -g)/bin/shfmt" +chmod +x "$(npm prefix -g)/bin/shfmt" ultralytics-actions-update-markdown-code-blocks npx prettier --write --list-different --print-width 120 "**/*.{js,jsx,ts,tsx,css,less,scss,json,yml,yaml,html,vue,svelte}" '!**/*lock.{json,yaml,yml}' '!**/*.lock' '!**/model.json' '!**/*.min.js' '!**/*.min.css' -if find . -name "*.sh" -type f | grep -q .; then - npx prettier --write --list-different --print-width 120 --plugin=$(npm root -g)/prettier-plugin-sh/lib/index.cjs "**/*.sh" -fi # Handle Markdown separately find . -name "*.md" -type f ! -path "*/docs/*" -exec npx prettier --write --list-different --print-width 120 {} + if [ -d "./docs" ]; then find ./docs -name "*.md" -type f ! -path "*/reference/*" -exec npx prettier --tab-width 4 --print-width 120 --write --list-different {} + fi +# Bash last: shfmt exits non-zero when a script fails to parse, which would otherwise skip the formatting above +find . -name "*.sh" -type f -not -path "*/node_modules/*" -not -path "*/.git/*" -exec shfmt -i 2 -sr -bn -ci -l -w {} + """ CODESPELL = [ "codespell", diff --git a/actions/update_markdown_code_blocks.py b/actions/update_markdown_code_blocks.py index 622abad..a1396c3 100644 --- a/actions/update_markdown_code_blocks.py +++ b/actions/update_markdown_code_blocks.py @@ -96,26 +96,25 @@ def format_code_with_ruff(temp_dir): print(f"ERROR running Python docstring formatter ❌ {e}") -def format_bash_with_prettier(temp_dir): - """Formats bash script files in the specified directory using prettier.""" +def format_bash_with_shfmt(temp_dir): + """Formats bash script files in the specified directory using shfmt.""" if not next(Path(temp_dir).rglob("*.sh"), None): return try: - # Run prettier with explicit config path + # Flags reproduce prettier-plugin-sh output: 2-space indent, spaced redirects, binary ops and cases indented result = subprocess.run( - "npx prettier --write --print-width=120 --plugin=$(npm root -g)/prettier-plugin-sh/lib/index.cjs ./**/*.sh", - shell=True, # must use shell=True to expand internal $(cmd) + ["shfmt", "-i", "2", "-sr", "-bn", "-ci", "-w", str(temp_dir)], capture_output=True, text=True, check=False, ) if result.returncode != 0: - print(f"ERROR running prettier-plugin-sh ❌ {result.stderr}") + print(f"ERROR running shfmt ❌ {result.stderr}") else: print("Completed bash formatting ✅") except Exception as e: - print(f"ERROR running prettier-plugin-sh ❌ {e}") + print(f"ERROR running shfmt ❌ {e}") def process_markdown_string( @@ -141,7 +140,7 @@ def process_markdown_string( if python_files_exist: format_code_with_ruff(temp_dir) if bash_files_exist: - format_bash_with_prettier(temp_dir) + format_bash_with_shfmt(temp_dir) update_markdown_file(temp_md, markdown_snapshot, temp_files) formatted_markdown = temp_md.read_text(encoding="utf-8") @@ -244,7 +243,7 @@ def main(root_dir=None, process_python=True, process_bash=True, verbose=False): if process_python: format_code_with_ruff(temp_dir) # Format Python files if process_bash: - format_bash_with_prettier(temp_dir) # Format Bash files + format_bash_with_shfmt(temp_dir) # Format Bash files # Update Markdown files with formatted code blocks for markdown_file, markdown_content, temp_files in all_temp_files: diff --git a/dependabot/action.yml b/dependabot/action.yml index 65c1eea..2c7eeed 100644 --- a/dependabot/action.yml +++ b/dependabot/action.yml @@ -39,9 +39,9 @@ runs: echo "::group::Install ultralytics-actions" if [ "$GITHUB_REPOSITORY" = "ultralytics/actions" ]; then echo "Installing from commit: $GITHUB_SHA" - packages="git+https://github.com/ultralytics/actions@${GITHUB_SHA}" + packages="https://github.com/ultralytics/actions/archive/${GITHUB_SHA}.tar.gz" else - packages="git+https://github.com/ultralytics/actions@main" + packages="https://github.com/ultralytics/actions/archive/refs/heads/main.tar.gz" fi if [ "$(uname)" = "Darwin" ]; then uv pip install --system --break-system-packages $packages diff --git a/github-report/action.yml b/github-report/action.yml index 23fcd65..fed3a59 100644 --- a/github-report/action.yml +++ b/github-report/action.yml @@ -55,9 +55,9 @@ runs: echo "::group::Install ultralytics-actions" if [ "$GITHUB_REPOSITORY" = "ultralytics/actions" ]; then echo "Installing from commit: $GITHUB_SHA" - packages="git+https://github.com/ultralytics/actions@${GITHUB_SHA}" + packages="https://github.com/ultralytics/actions/archive/${GITHUB_SHA}.tar.gz" else - packages="git+https://github.com/ultralytics/actions@main" + packages="https://github.com/ultralytics/actions/archive/refs/heads/main.tar.gz" fi if [ "$(uname)" = "Darwin" ]; then uv pip install --system --break-system-packages $packages diff --git a/tests/test_update_markdown_codeblocks.py b/tests/test_update_markdown_codeblocks.py index 6319462..1358cd0 100644 --- a/tests/test_update_markdown_codeblocks.py +++ b/tests/test_update_markdown_codeblocks.py @@ -6,7 +6,7 @@ from unittest.mock import mock_open, patch from actions.update_markdown_code_blocks import ( add_indentation, extract_code_blocks, - format_bash_with_prettier, + format_bash_with_shfmt, generate_temp_filename, main, process_markdown_file, @@ -110,11 +110,11 @@ def test(): def test_format_bash_skips_when_no_shell_files(tmp_path): - """Test bash formatter skips Prettier when no shell snippets were extracted.""" + """Test bash formatter skips shfmt when no shell snippets were extracted.""" (tmp_path / "snippet.py").write_text("print('ok')", encoding="utf-8") with patch("subprocess.run") as mock_run: - format_bash_with_prettier(tmp_path) + format_bash_with_shfmt(tmp_path) mock_run.assert_not_called()