From 417845e44d624ae82bab4729e82f327142256c0c Mon Sep 17 00:00:00 2001 From: Tom Softreck Date: Tue, 13 Oct 2026 13:52:01 +0200 Subject: [PATCH] fix(ticket-185): skip detected setup.py with computed version as a version source [ticket-185] A setup.py that reads its version from VERSION or pyproject.toml has no literal to read or bump; treating every root-level file as primary made 'goal -a' fail with 'setup.py:version: selector version was not found'. A configured setup.py:version source without a literal still fails. Co-Authored-By: Claude --- goal/cli/version_state.py | 8 ++++ project/ticket-185/README.md | 28 ++++++++++++ project/ticket-185/intent.json | 82 ++++++++++++++++++++++++++++++++++ tests/test_version_state.py | 30 +++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 project/ticket-185/README.md create mode 100644 project/ticket-185/intent.json diff --git a/goal/cli/version_state.py b/goal/cli/version_state.py index 4658d55..0c2c5f7 100644 --- a/goal/cli/version_state.py +++ b/goal/cli/version_state.py @@ -445,6 +445,14 @@ def collect_version_sources( for spec in ordered_specs: initial_origin = "configured" if spec in configured_set else "detected" source = read_version_source(spec, initial_origin) + if ( + initial_origin == "detected" + and Path(source.path).name == "setup.py" + and source.value is None + ): + # No literal version: setup.py computes it (e.g. from VERSION or + # pyproject.toml), so it is not a declaration to read or bump. + continue if initial_origin == "detected" and _is_primary_source(source): source = VersionSource(**{**source.__dict__, "origin": "primary"}) observed.append(source) diff --git a/project/ticket-185/README.md b/project/ticket-185/README.md new file mode 100644 index 0000000..c653791 --- /dev/null +++ b/project/ticket-185/README.md @@ -0,0 +1,28 @@ +# Ticket 185: skip detected setup.py with computed version as a version source + +- **ID**: ticket-185 +- **Owner**: claude +- **Status**: IN_PROGRESS +- **Workflow state**: EDIT +- **Created**: 2026-10-13 + +## Goal and scope + +`goal -a --bump patch` in semcod/fixos (2026-10-13) stopped with +`Version sources are unreadable or unsupported - setup.py:version: selector +version was not found`. fixos's `setup.py` reads its version from `VERSION` +(single source of truth), but every root-level detected file was treated as a +primary source. `collect_version_sources` now skips a detected `setup.py` +without a version literal; a configured `setup.py:version` still fails closed. + +SESSION_EXECUTION_AUTHORIZATION: user requested publishing fixos through `goal -a` and verifying it works. + +## Acceptance criteria + +- [x] AC-01: `tests/test_version_state.py` covers the computed (skipped) and configured (still failing) cases. +- [x] AC-02: `./project/governance-check.sh` passes. + +## Tracking boundary + +This directory contains the minimal reviewed intent. Optional participant prose +and raw command logs are not required delivery output. diff --git a/project/ticket-185/intent.json b/project/ticket-185/intent.json new file mode 100644 index 0000000..15f7f83 --- /dev/null +++ b/project/ticket-185/intent.json @@ -0,0 +1,82 @@ +{ + "schema": "new-project.intent/v3", + "ticket": "ticket-185", + "summary": "skip detected setup.py with computed version as a version source", + "workstream": "application", + "classification": { + "kind": "BUG", + "priority": "P1", + "origin": "requested" + }, + "allowedPaths": [ + "project/ticket-185/**", + "TODO.md", + "project/TICKETS.md", + "goal/cli/version_state.py", + "tests/test_version_state.py" + ], + "forbiddenPaths": [ + "project/ticket-*/user-*.md" + ], + "stacks": [ + "python" + ], + "dependsOn": [], + "conflictsWith": [], + "integrationTicket": null, + "delivery": { + "acceptedBaseSha": "ce911b7e8983b5914ac0eeb31d3a0ad6e5549582", + "targetBranch": "main", + "outcome": "goal no longer fails a release with 'setup.py:version: selector version was not found' when an auto-detected setup.py computes its version (for example from VERSION); an explicitly configured setup.py source without a literal still fails.", + "nonGoals": [ + "No change to literal setup.py version reading or writing." + ], + "complexity": "XS", + "estimatedMinutes": 10, + "budgets": { + "maxImplementationFiles": 2, + "maxAffectedComponents": 1, + "maxPublicInterfaceChanges": 0, + "maxRuntimeDependencies": 0 + }, + "architecture": { + "status": "accepted", + "decision": "Skip detected setup.py sources without a version literal in collect_version_sources.", + "components": [ + { + "name": "version-state", + "paths": [ + "goal/cli/version_state.py", + "tests/test_version_state.py" + ] + } + ], + "responsibilityChanges": false, + "interfaceChanges": [], + "dataChanges": [], + "ui": { + "impact": "none", + "states": [], + "evidence": [] + }, + "rollback": "Revert the commit." + }, + "runtimeDependencies": [], + "validation": [ + { + "criterion": "AC-01", + "commands": [ + "pytest -q tests/test_version_state.py" + ], + "evidence": "project/ticket-185/README.md" + }, + { + "criterion": "AC-02", + "commands": [ + "./project/governance-check.sh" + ], + "evidence": "project/ticket-185/README.md" + } + ] + } +} diff --git a/tests/test_version_state.py b/tests/test_version_state.py index 212db6c..428f431 100644 --- a/tests/test_version_state.py +++ b/tests/test_version_state.py @@ -112,6 +112,36 @@ def test_setup_py_keyword_version_is_resolved_and_written_safely( assert "version='1.2.4'" in content +def test_detected_setup_py_with_computed_version_is_not_a_source( + tmp_path, monkeypatch +): + config = _init_release(tmp_path, "1.2.3") + (tmp_path / "setup.py").write_text( + "from pathlib import Path\n" + "from setuptools import setup\n" + 'version = (Path(__file__).parent / "VERSION").read_text().strip()\n' + 'setup(name="fixture", version=version)\n' + ) + monkeypatch.chdir(tmp_path) + + decision = resolve_version_decision(config=config, registry_versions={}) + + assert decision.target_version == "1.2.4" + assert "setup.py" not in {Path(s.path).name for s in collect_version_sources(config)} + + +def test_configured_setup_py_without_literal_still_fails(tmp_path, monkeypatch): + config = _init_release(tmp_path, "1.2.3") + (tmp_path / "setup.py").write_text( + "from setuptools import setup\nsetup(name='fixture', version=compute())\n" + ) + config["versioning"]["files"].append("setup.py:version") + monkeypatch.chdir(tmp_path) + + with pytest.raises(VersionStateError): + resolve_version_decision(config=config, registry_versions={}) + + def test_partial_local_prebump_repairs_forward(tmp_path, monkeypatch): config = _init_release(tmp_path, "1.2.3") (tmp_path / "VERSION").write_text("1.2.4\n")