Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions complete/2026/07/minimum-library-version-adoption.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# minimum-library-version-adoption

- shipped: 2026-07-17 (autolens_workspace `21702119`; siblings adopted the same key)
- follows: PyAutoConf#119 (merged 2026-07-08) — `check_version` prefers `version.minimum_library_version` over the legacy records
- repos:
- autofit_workspace, autogalaxy_workspace, autolens_workspace
- HowToFit, HowToGalaxy, HowToLens
- euclid_strong_lens_modeling_pipeline

## Summary

Every one of the seven repos in scope carries
`version.minimum_library_version: 2026.7.9.1` in `config/general.yaml`, with the
bump-deliberately rule documented inline as a comment on the key itself:

> The compatibility FLOOR: the oldest library release whose API this workspace's
> scripts require. Preferred over workspace_version (autonerves/workspace.py).
> Bump DELIBERATELY — only when a script starts needing new API — never per
> release. Must always name an INSTALLABLE (non-yanked) release.

## Verified 2026-08-08, all three legs of the prompt's scope

1. **The key is adopted in all 7** — confirmed by reading each repo's
`config/general.yaml` on `main`, not inferred from one.
2. **The dead `workspace_version` key is gone.** The only surviving matches are
the explanatory comment above and `workspace_version_check`, which is a
*different* and still-live key (the documented bypass for `main`-branch
clones, where mismatches are expected because `main` moves faster than
releases).
3. **`version.txt` is gone** — `HTTP 404` on `main` for autolens_workspace,
autofit_workspace and HowToLens.

## Bookkeeping note

Reconstructed 2026-08-08. This prompt was sitting in
`draft/feature/workspaces/` — the backlog folder — with `Priority: high`, while
the work had been fully delivered for three weeks. `draft/` is graded by no
check at all, which is how it stayed there; that gap is what
`lifecycle.py issues --drafts` now partially addresses, though only for the
minority of drafts that cite an issue (this one cited none).

## Original prompt

# Adopt version.minimum_library_version in workspace configs

Type: feature
Target: workspaces
Difficulty: small
Autonomy: supervised
Priority: high
Status: formalised

## Context

Follow-up to PyAutoConf#119 (merged 2026-07-08): `check_version` now enforces
a compatibility floor, preferring `version.minimum_library_version` in
`config/general.yaml` over the legacy `workspace_version`/`version.txt`
records (which releases no longer write since PyAutoBuild#121).

## Scope

- Add `version.minimum_library_version` to `config/general.yaml` in
autofit_workspace, autogalaxy_workspace, autolens_workspace, HowToFit,
HowToGalaxy, HowToLens, euclid_strong_lens_modeling_pipeline — set to the
oldest release whose API the workspace's scripts actually require (at
adoption time: the first real release after 2026-07-08, since workspace
mains depend on post-2026.7.6.649 API).
- Remove the now-dead `workspace_version` key and `version.txt` once the new
key is in place (they are only read as fallbacks).
- Document the bump-deliberately rule in each workspace README/AGENTS: the
floor moves only when scripts start needing new API, never per release.
- Coordinate claims: autofit_workspace (ep-examples-tests) and
autolens_workspace (kxs-core) are claimed as of filing — serialise or wait.
- Best sequenced AFTER the first real release (Q1 on PyAutoBuild#118), so the
floor value is an installable version.
3 changes: 2 additions & 1 deletion complete/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Token-light navigation over the finished-work records (schema:
only then grep a dated bucket. Curators: edit the band between the CURATED
markers; everything below GENERATED is rebuilt.

946 records across 7 buckets.
947 records across 7 buckets.

<!-- CURATED:START -->
## Highlights
Expand Down Expand Up @@ -334,6 +334,7 @@ _(curate hard-won records here — survives regeneration.)_
- [memory-structure-cleanup](2026/07/memory-structure-cleanup.md) — wiki/ + bibliography restructure, structure lint, template sync — SHIPPED
- [merge-adjacent-docstrings](2026/07/merge-adjacent-docstrings.md) — Phase 3 of the adjacent-docstrings arc (PyAutoHands#196 → PyAutoBrain#162 → here). Merged 80 adjacent-docstrin…
- [mind-guard-cd-fix](2026/07/mind-guard-cd-fix.md) — mind_commit_guard v1.2 — honour a leading `cd` away from Mind. v1.1 keyed is-Mind-commit off the ambient cwd t…
- [minimum-library-version-adoption](2026/07/minimum-library-version-adoption.md)
- [morning-status-release-rehearsal](2026/07/morning-status-release-rehearsal.md) — morning Slack digest + release rehearsal — RESOLVED
- [multi-galaxy-features-group-parity](2026/07/multi-galaxy-features-group-parity.md) — Arc close-out: all four phases of the multi_galaxy features group-parity campaign shipped across ten PRs — pha…
- [multi-galaxy-features-parity-phase-1](2026/07/multi-galaxy-features-parity-phase-1.md)
Expand Down
39 changes: 0 additions & 39 deletions draft/feature/autolens/restore_multiple_sources_lensing_of_lens.md

This file was deleted.

32 changes: 0 additions & 32 deletions draft/feature/workspaces/minimum_library_version_adoption.md

This file was deleted.

67 changes: 61 additions & 6 deletions scripts/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,44 @@ def issue_problems(root: Path, fetch=None) -> "list[str]":
return problems


def draft_issue_refs(root: Path) -> "list[tuple[str, str]]":
"""(draft_path, issue_url) for draft prompts citing a GitHub issue.

Only a handful do — drafts are pre-issue by definition — but `draft/` is
backlog no check grades, and it carries shipped work too (the 2026-08-08
sweep found `minimum_library_version_adoption` fully delivered across all
seven repos while still sitting in draft/)."""
draft = root / "draft"
if not draft.is_dir():
return []
refs = []
for f in sorted(draft.rglob("*.md")):
m = ISSUE_URL_RE.search(f.read_text(errors="replace"))
if m:
refs.append((str(f.relative_to(root)), m.group(0)))
return refs


def draft_issue_notes(root: Path, fetch=None) -> "list[str]":
"""ADVISORY notes on drafts whose cited issue is closed.

Deliberately weaker than `issue_problems`, and deliberately not drift. A
registry entry's `issue:` is its OWN tracking issue, so closed means done. A
draft usually cites an issue as CONTEXT — "Once #480 is fixed…", "Follow-up
to #57" — so closed can mean the draft is newly UNBLOCKED rather than
finished. Both readings are worth a human look; neither is a gate."""
refs = draft_issue_refs(root)
if not refs:
return []
fetch = fetch or _gh_issue_states
states = fetch([url for _, url in refs])
return [
f"{path}: cited issue is closed — shipped, or newly unblocked? {url}"
for path, url in refs
if states.get(url) == "closed"
]


def cmd_issues(args) -> int:
"""Cross-check every registry entry's tracking issue against GitHub."""
try:
Expand All @@ -331,13 +369,28 @@ def cmd_issues(args) -> int:
file=sys.stderr,
)
return 2
if not problems:
notes = []
if getattr(args, "drafts", False):
try:
notes = draft_issue_notes(ROOT)
except GhUnavailable:
pass # unreachable: issue_problems above would already have raised

if problems:
print("lifecycle issues: DRIFT")
for line in problems:
print(f" - {line}")
else:
print(f"lifecycle issues: OK ({len(registry_issue_refs(ROOT))} tracking issue(s) open)")
return 0
print("lifecycle issues: DRIFT")
for p in problems:
print(f" - {p}")
return 1

# Advisory only — never affects the exit code. A draft citing a closed issue
# may be shipped OR newly unblocked; that is a judgement, not drift.
if notes:
print(f"\nadvisory — {len(notes)} draft(s) citing a closed issue:")
for line in notes:
print(f" ? {line}")

return 1 if problems else 0


def orphan_prompts(root: Path) -> "list[Path]":
Expand Down Expand Up @@ -750,6 +803,8 @@ def main() -> int:
iss = sub.add_parser(
"issues", help="cross-check registry tracking issues against GitHub (needs gh)"
)
iss.add_argument("--drafts", action="store_true",
help="also flag draft/ prompts citing a closed issue (advisory)")
iss.set_defaults(func=cmd_issues)

o = sub.add_parser("orphans", help="report active/ prompts no registry claims")
Expand Down
28 changes: 28 additions & 0 deletions tests/test_lifecycle_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,34 @@ def test_merged_pr_links_are_not_treated_as_tracking_refs(tmp_path):
assert [r[2] for r in refs] == [GHOST_ISSUE]


def test_draft_citing_a_closed_issue_is_advisory(tmp_path):
"""draft/ is backlog no check grades, and it carries shipped work too. A
closed cited issue is worth a look — but NOT drift, because a draft usually
cites an issue as context ("Once #480 is fixed…"), so closed can mean newly
unblocked rather than finished."""
d = tmp_path / "draft" / "feature" / "flywheel"
d.mkdir(parents=True)
(d / "sprocket_calibration.md").write_text(f"Once {GHOST_ISSUE} is fixed, do X.\n")
notes = lifecycle.draft_issue_notes(tmp_path, fetch=_states({GHOST_ISSUE: "closed"}))
assert len(notes) == 1
assert "shipped, or newly unblocked?" in notes[0]


def test_draft_with_an_open_issue_is_silent(tmp_path):
d = tmp_path / "draft" / "feature" / "flywheel"
d.mkdir(parents=True)
(d / "sprocket_calibration.md").write_text(f"Blocked on {GHOST_ISSUE}.\n")
assert lifecycle.draft_issue_notes(tmp_path, fetch=_states({GHOST_ISSUE: "open"})) == []


def test_drafts_are_not_mixed_into_registry_drift(tmp_path):
"""The advisory must never leak into `issue_problems`, which is the gate."""
d = tmp_path / "draft" / "feature" / "flywheel"
d.mkdir(parents=True)
(d / "sprocket_calibration.md").write_text(f"Once {GHOST_ISSUE} is fixed.\n")
assert lifecycle.issue_problems(tmp_path, fetch=_states({GHOST_ISSUE: "closed"})) == []


def test_missing_gh_propagates_rather_than_reporting_all_clear(tmp_path):
""""gh is not installed" must never be mistaken for "no findings" — a check
that silently could not run is worse than one that fails loudly."""
Expand Down
Loading