From 86cd40eb4ebd4d6c5366c2b3bb3516afadf679c3 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Thu, 20 Aug 2026 15:40:29 -0400 Subject: [PATCH] chore: drop expired release-notes banner; complete autohands registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two hygiene leftovers from the 2026-08-19 release-board census (#239). The time-boxed ANNOUNCEMENT banner in generate_release_notes.py expired 2026-07-24, so announcement_banner() had returned "" on every call since. Delete the mechanism rather than document it; the datetime.date import goes with it. The banner markdown also appears as a literal fixture string in test_slack_release_notes.py, which imports nothing from the deleted code — kept as the file's only blockquote -> mrkdwn case, with its comment corrected. `bin/autohands help` claimed to be "the registry of what is a CLI verb", but eleven modules in autohands/ had grown __main__ blocks without ever appearing in it. Nine become real verbs (generate_markdown, regenerate_navigator, the four workspace guards, slack_release_notes, tombstone, clone_seed), each with help text stating its actual input contract — they differ: cwd, --root, or a positional path. The rest are declared in a new INTERNAL_MODULES array with a one-line reason each, and printed by `autohands help`, so the registry describes the whole package rather than a subset. tests/test_autohands_registry.py makes that an enforced invariant: every executable module is a verb or allowlisted, the allowlist has no ghosts, every verb carries its SHORT_DESC + cmd_* + help_* trio, and the two lists are disjoint. Each assertion was negative-tested to confirm it fires. Refs #249 Co-Authored-By: Claude Opus 5 --- AGENTS.md | 17 +- autohands/generate_release_notes.py | 36 ---- bin/autohands | 247 ++++++++++++++++++++++++++++ tests/test_autohands_registry.py | 114 +++++++++++++ tests/test_slack_release_notes.py | 6 +- 5 files changed, 374 insertions(+), 46 deletions(-) create mode 100644 tests/test_autohands_registry.py diff --git a/AGENTS.md b/AGENTS.md index bcf0071b..30d588b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,14 +24,15 @@ deep `verify_install` suite, and URL hygiene all live in PyAutoHeart now; `autohands verify_install` / `autohands url_check` / `autohands watch|status| tick|fix` are thin shims that delegate to `pyauto-heart`. Build keeps only the executor primitives: the build/notebook pipeline (`pre_build`, `generate*`, -`run_all` / `run*`), the navigator catalogue (the `navigator.py` / -`check_navigator.py` / `regenerate_navigator.py` modules — workflow-invoked, -not CLI verbs), tagging + release (`tag_and_merge`, `bump_colab_urls`, -`release.yml`), the release-notes and Slack tooling -(`generate_release_notes`, `slack_release_notes`), the release board -(`board`, published by `release_board.yml`), assistant seeding -(`clone_seed`), and `repro_command`. `bin/autohands help` is the registry of -what is a CLI verb; see `docs/internals.md` for the pipeline detail. +`run_all` / `run*`), the navigator catalogue and the workspace guards, tagging + +release (`tag_and_merge`, `bump_colab_urls`, `release.yml`), the release-notes +and Slack tooling (`generate_release_notes`, `slack_release_notes`), the release +board (`board`, published by `release_board.yml`), assistant seeding +(`clone_seed`), and `repro_command`. `bin/autohands help` is the **complete** +registry of `autohands/` — every module there is a CLI verb or an +`INTERNAL_MODULES` entry, enforced by `tests/test_autohands_registry.py`, so +read `help` rather than listing verbs here. See `docs/internals.md` for the +pipeline detail. See [`docs/internals.md`](docs/internals.md) for the build pipeline, workspace folder structure, config files, and `release.yml` details. Read it when diff --git a/autohands/generate_release_notes.py b/autohands/generate_release_notes.py index 76988f44..206463b6 100644 --- a/autohands/generate_release_notes.py +++ b/autohands/generate_release_notes.py @@ -14,39 +14,9 @@ import subprocess import sys from argparse import ArgumentParser -from datetime import date from pathlib import Path -# Time-boxed announcement banner prepended to generated release notes. -# The banner is included only while today <= EXPIRY, then drops off -# automatically — no follow-up edit needed. Set EXPIRY to None (or move the -# date into the past) to disable. `repos` limits which repos show it; None -# means all repos. -ANNOUNCEMENT = { - "expiry": date(2026, 7, 24), - "repos": {"PyAutoLabs/PyAutoLens"}, - "markdown": ( - "> 📣 **Major Milestones Announcement** — PyAutoLens now ships an AI assistant " - "(conversational + agentic), full JAX GPU support, and agentic-AI development via " - "PyAutoScientist. " - "[Read the announcement →](https://github.com/PyAutoLabs/PyAutoLens/discussions/603)" - ), -} - - -def announcement_banner(repo, today=None): - """Return the announcement markdown for `repo`, or "" if none applies today.""" - today = today or date.today() - expiry = ANNOUNCEMENT.get("expiry") - if not expiry or today > expiry: - return "" - repos = ANNOUNCEMENT.get("repos") - if repos and repo not in repos: - return "" - return ANNOUNCEMENT.get("markdown", "") - - # Dependency chain: downstream repos include upstream changes UPSTREAM_DEPS = { "PyAutoLabs/PyAutoFit": [], @@ -197,12 +167,6 @@ def generate_notes(repo, version, prs, upstream_prs_by_repo): name = REPO_NAMES.get(repo, repo.split("/")[-1]) lines = [f"# {name} v{version}", ""] - # Time-boxed announcement banner (self-expiring; see ANNOUNCEMENT above). - banner = announcement_banner(repo) - if banner: - lines.append(banner) - lines.append("") - # Classify own PRs categories = {"breaking": [], "feature": [], "fix": [], "internal": []} for pr in prs: diff --git a/bin/autohands b/bin/autohands index e87221bc..635dd23c 100755 --- a/bin/autohands +++ b/bin/autohands @@ -21,6 +21,10 @@ ADMIN_JAMMY="$PYAUTOBASE/admin_jammy" # with "# " are rendered as section headers; everything else is a real # subcommand and must have a matching SHORT_DESC entry plus cmd_ and # help_ functions. +# +# Together with INTERNAL_MODULES below, this is the complete registry of +# autohands/: every module there is either a subcommand here or an internal +# module listed there. tests/test_autohands_registry.py enforces it. SUBCOMMAND_ORDER=( "# Build and release pipeline" @@ -31,17 +35,28 @@ SUBCOMMAND_ORDER=( bump_colab_urls "# Workspace operations (run from a workspace root)" generate + generate_markdown + regenerate_navigator run run_python run_all + "# Workspace guards (CI-invoked; runnable locally)" + check_navigator + check_search_memory + check_dataset_allowlist + validate_env_profiles "# Release support" board script_matrix aggregate_results slow_skip_check generate_release_notes + slack_release_notes create_analysis_issue tag_and_merge + tombstone + "# Assistant seeding" + clone_seed "# Triage support" repro_command "# Monitoring daemon (delegates to PyAutoHeart)" @@ -59,6 +74,12 @@ declare -A SHORT_DESC=( [url_check]="Shim → pyauto-heart url_check (forbidden Binder/Colab URL guard)" [bump_colab_urls]="Rewrite Colab URLs in cwd from old date-tag to new date-tag" [generate]="Convert workspace scripts/ to notebooks/ (run from a workspace root)" + [generate_markdown]="Render curated workspace scripts to executed markdown pages with figures" + [regenerate_navigator]="Rebuild a workspace's llms-full.txt + workspace_index.json catalogue" + [check_navigator]="Guard: navigator/instruction path references all resolve on disk" + [check_search_memory]="Guard: every MultiStart* search in a workspace sets batch_size explicitly" + [check_dataset_allowlist]="Guard: tracked dataset/ files are covered by the .gitignore allowlist" + [validate_env_profiles]="Guard: a workspace's smoke/release env profiles parse and resolve" [run]="Execute notebooks in a workspace folder" [run_python]="Execute Python scripts in a workspace folder" [run_all]="Run scripts across one or more workspaces and produce summary reports" @@ -67,8 +88,11 @@ declare -A SHORT_DESC=( [aggregate_results]="Aggregate per-job JSON results into a release-readiness report" [slow_skip_check]="Surface SLOW / NEEDS_FIX entries in workspace no_run.yaml files" [generate_release_notes]="Generate release notes from merged PRs and create GitHub Releases" + [slack_release_notes]="Convert a release body into the #pipreleases Slack payload" [create_analysis_issue]="Open a GitHub issue with the release report and assign Copilot" [tag_and_merge]="Commit and tag every library repo for a release" + [tombstone]="Build the sub-floor tombstone sdists that redirect users to a supported release" + [clone_seed]="Build (and optionally push) an assistant seed repo from a clone plan" [repro_command]="Emit the shell command autohands uses to run one script (for triage handoffs)" [watch]="Start the PyAutoHeart monitoring daemon (Ctrl-C to stop)" [status]="Print the latest PyAutoHeart state cache with green/yellow/red" @@ -77,6 +101,19 @@ declare -A SHORT_DESC=( [help]="List subcommands or show details for one" ) +# Modules in autohands/ that are deliberately NOT CLI verbs. Every *.py there +# must be a registered subcommand above or listed here — the completeness rule +# tests/test_autohands_registry.py enforces. Keep the reason on the line. +INTERNAL_MODULES=( + add_notebook_quotes # docstring->cell transform stage; its __main__ is a debug aid + run_notebook # per-notebook worker subprocess spawned by run.py + navigator # catalogue library behind regenerate_navigator / check_navigator + build_util # shared py_to_notebook machinery + env_config # env-profile resolution library + result_collector # per-run JSON result library + generate_autofit # generate.py's autofit-specific branch +) + # _python_in_autohands