Skip to content
Open
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
8 changes: 8 additions & 0 deletions goal/cli/version_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 28 additions & 0 deletions project/ticket-185/README.md
Original file line number Diff line number Diff line change
@@ -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.
82 changes: 82 additions & 0 deletions project/ticket-185/intent.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
}
30 changes: 30 additions & 0 deletions tests/test_version_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading