diff --git a/CHANGELOG.md b/CHANGELOG.md index 380deb68..13bd6c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file. **Important:** Changes need to be documented below this block as this is the header section. Each section should be separated by a horizontal rule. Newer changelog entries need to be added on top of prior ones to keep the history chronological with most recent changes first. +--- + +## [0.40.1] - 2026-03-06 + +### Fixed + +- Restored the published `pip install specfact-cli` wheel payload so the core `specfact_cli` package is included again, including `specfact_cli/cli.py`. +- Restored the standard `specfact` console command for installed users; both `specfact` and `specfact-cli` now resolve to `specfact_cli.cli:cli_main` from the built artifact. +- Hardened Hatch wheel packaging for the `src/` layout by using explicit source mapping, preventing release artifacts that contain only force-included resources/modules without the actual CLI package code. + --- ## [0.40.0] - 2026-02-28 diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index 8d4d4b0b..436bd56a 100644 --- a/openspec/CHANGE_ORDER.md +++ b/openspec/CHANGE_ORDER.md @@ -118,6 +118,12 @@ These are derived extensions of the same 2026-02-15 plan and are required to ope |--------|-------|----------------|----------|------------| | — | — | ✅ ci-01 implemented 2026-02-16 (see Implemented above) | — | — | +### Packaging and distribution + +| Module | Order | Change folder | GitHub # | Blocked by | +|--------|-------|---------------|----------|------------| +| packaging | 01 | packaging-01-wheel-package-inclusion | TBD | module-migration-06 ✅; release artifact regression discovered post-0.40.0 publish | + ### Developer workflow (parallel branch operations) | Module | Order | Change folder | GitHub # | Blocked by | diff --git a/openspec/changes/packaging-01-wheel-package-inclusion/CHANGE_VALIDATION.md b/openspec/changes/packaging-01-wheel-package-inclusion/CHANGE_VALIDATION.md new file mode 100644 index 00000000..ee2ef9d4 --- /dev/null +++ b/openspec/changes/packaging-01-wheel-package-inclusion/CHANGE_VALIDATION.md @@ -0,0 +1,31 @@ +# Change Validation + +## Scope check + +This change is narrowly scoped to Python package distribution for `pip install specfact-cli`. + +## Implemented + +- Switched Hatch wheel target from shorthand `packages = ["src/specfact_cli"]` to explicit: + - `only-include = ["src/specfact_cli"]` + - `sources = ["src"]` +- Added regression coverage for explicit wheel source mapping and console script entrypoints. +- Verified built wheel contents include `specfact_cli/__init__.py` and `specfact_cli/cli.py`. + +## Validation summary + +- packaging regression tests: pass +- built wheel artifact inspection: pass +- direct import from built wheel: pass + +## Release note + +A patch release is required before PyPI users receive this fix, because `0.40.0` is already published with the broken wheel payload. + +## Install verification + +A clean temp venv install of `dist/specfact_cli-0.40.1-py3-none-any.whl` was verified with both: +- `specfact -v` +- `specfact-cli -v` + +Both commands returned `SpecFact CLI version 0.40.1`. diff --git a/openspec/changes/packaging-01-wheel-package-inclusion/TDD_EVIDENCE.md b/openspec/changes/packaging-01-wheel-package-inclusion/TDD_EVIDENCE.md new file mode 100644 index 00000000..dfe72354 --- /dev/null +++ b/openspec/changes/packaging-01-wheel-package-inclusion/TDD_EVIDENCE.md @@ -0,0 +1,91 @@ +# TDD Evidence + +## Failing Evidence + +### 2026-03-06 installed artifact inspection + +Commands: + +```bash +python - <<'PY' +import site, pathlib +root = pathlib.Path(site.getusersitepackages()) / 'specfact_cli' +print('cli.py exists:', (root / 'cli.py').exists()) +print('__init__.py exists:', (root / '__init__.py').exists()) +PY +``` + +Observed failure summary: +- installed `specfact_cli` package contained only `modules/` and `resources/` +- `specfact_cli/cli.py` missing from installed `0.40.0` wheel payload +- console script `specfact-cli` failed with `ModuleNotFoundError: No module named 'specfact_cli.cli'` + +### 2026-03-06 pre-fix regression test + +Command: + +```bash +HATCH_DATA_DIR=/tmp/hatch-data HATCH_CACHE_DIR=/tmp/hatch-cache VIRTUALENV_OVERRIDE_APP_DATA=/tmp/virtualenv-appdata hatch run pytest tests/unit/packaging/test_core_package_includes.py -q +``` + +Observed failure summary: +- `test_pyproject_wheel_explicitly_maps_src_package_root` failed because wheel config lacked explicit `only-include` / `sources` mapping for `src/specfact_cli` + +## Passing Evidence + +### 2026-03-06 post-fix regression test + +Command: + +```bash +HATCH_DATA_DIR=/tmp/hatch-data HATCH_CACHE_DIR=/tmp/hatch-cache VIRTUALENV_OVERRIDE_APP_DATA=/tmp/virtualenv-appdata hatch run pytest tests/unit/packaging/test_core_package_includes.py -q +``` + +Result: +- `6 passed` + +### 2026-03-06 built wheel artifact verification + +Commands: + +```bash +HATCH_DATA_DIR=/tmp/hatch-data HATCH_CACHE_DIR=/tmp/hatch-cache VIRTUALENV_OVERRIDE_APP_DATA=/tmp/virtualenv-appdata hatch build -t wheel +python - <<'PY' +import zipfile +from pathlib import Path +wheel = Path('dist/specfact_cli-0.40.1-py3-none-any.whl') +with zipfile.ZipFile(wheel) as zf: + assert 'specfact_cli/__init__.py' in zf.namelist() + assert 'specfact_cli/cli.py' in zf.namelist() + print(zf.read('specfact_cli-0.40.0.dist-info/entry_points.txt').decode('utf-8')) +PY +python - <<'PY' +import sys +sys.path.insert(0, 'dist/specfact_cli-0.40.1-py3-none-any.whl') +import specfact_cli.cli as cli +print(cli.__file__) +print(hasattr(cli, 'cli_main')) +PY +``` + +Result summary: +- built wheel contains `specfact_cli/__init__.py` +- built wheel contains `specfact_cli/cli.py` +- entry points include both `specfact` and `specfact-cli` targeting `specfact_cli.cli:cli_main` +- importing `specfact_cli.cli` from the wheel succeeds + +### 2026-03-06 clean install command verification + +Commands: + +```bash +python -m venv /tmp/specfact-cli-wheel-verify --system-site-packages +/tmp/specfact-cli-wheel-verify/bin/pip install --force-reinstall --no-deps dist/specfact_cli-0.40.1-py3-none-any.whl +/tmp/specfact-cli-wheel-verify/bin/specfact -v +/tmp/specfact-cli-wheel-verify/bin/specfact-cli -v +``` + +Result summary: +- installed wheel exposes `specfact` +- installed wheel exposes `specfact-cli` +- both commands return `SpecFact CLI version 0.40.1` diff --git a/openspec/changes/packaging-01-wheel-package-inclusion/proposal.md b/openspec/changes/packaging-01-wheel-package-inclusion/proposal.md new file mode 100644 index 00000000..be7030dd --- /dev/null +++ b/openspec/changes/packaging-01-wheel-package-inclusion/proposal.md @@ -0,0 +1,23 @@ +# packaging-01-wheel-package-inclusion + +## Summary + +Fix the released `specfact-cli` wheel so a plain `pip install specfact-cli` installs the core `specfact_cli` Python package, including `specfact_cli.cli`, and both declared console scripts can start successfully. + +## Problem + +The published `0.40.0` artifact installs console script wrappers but the wheel payload omits the actual `specfact_cli` package code. The installed site-packages directory contains only force-included `modules/` and `resources/`, causing: + +- `specfact` missing or unusable after install +- `specfact-cli` wrapper failing with `ModuleNotFoundError: No module named 'specfact_cli.cli'` + +## Scope + +- Correct wheel packaging configuration for core package inclusion +- Add regression coverage that inspects the built wheel contents +- Verify the built wheel contains `specfact_cli/cli.py` and both console scripts resolve to `specfact_cli.cli:cli_main` + +## Out of Scope + +- Broader release automation changes +- Marketplace/module bundle behavior changes diff --git a/openspec/changes/packaging-01-wheel-package-inclusion/specs/package-distribution/spec.md b/openspec/changes/packaging-01-wheel-package-inclusion/specs/package-distribution/spec.md new file mode 100644 index 00000000..b7be683d --- /dev/null +++ b/openspec/changes/packaging-01-wheel-package-inclusion/specs/package-distribution/spec.md @@ -0,0 +1,19 @@ +# package-distribution + +## ADDED Requirements + +### Requirement: Released wheel includes core CLI package + +The published wheel MUST include the importable `specfact_cli` Python package required by declared console scripts. + +#### Scenario: Wheel contains core CLI module +- **GIVEN** a wheel is built from the repository release configuration +- **WHEN** its contents are inspected +- **THEN** it includes `specfact_cli/cli.py` +- **AND** it includes `specfact_cli/__init__.py` + +#### Scenario: Console scripts target importable CLI entrypoint +- **GIVEN** the built distribution metadata +- **WHEN** console script entrypoints are inspected +- **THEN** both `specfact` and `specfact-cli` resolve to `specfact_cli.cli:cli_main` +- **AND** importing `specfact_cli.cli` from the built artifact succeeds diff --git a/openspec/changes/packaging-01-wheel-package-inclusion/tasks.md b/openspec/changes/packaging-01-wheel-package-inclusion/tasks.md new file mode 100644 index 00000000..97c45753 --- /dev/null +++ b/openspec/changes/packaging-01-wheel-package-inclusion/tasks.md @@ -0,0 +1,7 @@ +# Tasks + +- [x] 1. Record failing evidence for the installed-artifact regression and build-content regression +- [x] 2. Add/adjust regression tests for wheel contents and console-script entrypoint expectations +- [x] 3. Fix build configuration so the wheel includes the `specfact_cli` package code +- [x] 4. Rebuild or inspect artifact locally and rerun targeted regression tests +- [x] 5. Update TDD_EVIDENCE.md and CHANGE_VALIDATION.md with failing/passing evidence diff --git a/pyproject.toml b/pyproject.toml index e6102a3e..74dd1008 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "specfact-cli" -version = "0.40.0" +version = "0.40.1" description = "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases." readme = "README.md" requires-python = ">=3.11" @@ -377,9 +377,10 @@ method = "thread" dev-mode-dirs = ["src","tools"] [tool.hatch.build.targets.wheel] -packages = [ +only-include = [ "src/specfact_cli", ] +sources = ["src"] [tool.hatch.build.targets.wheel.force-include] "resources/prompts" = "specfact_cli/resources/prompts" diff --git a/setup.py b/setup.py index 56f04f93..3c305a56 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ if __name__ == "__main__": _setup = setup( name="specfact-cli", - version="0.40.0", + version="0.40.1", description=( "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with " "validation and contract enforcement for new projects and long-lived codebases." diff --git a/src/__init__.py b/src/__init__.py index 4992b2ee..3ebce126 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,4 +3,4 @@ """ # Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py -__version__ = "0.40.0" +__version__ = "0.40.1" diff --git a/src/specfact_cli/__init__.py b/src/specfact_cli/__init__.py index a701815c..68e2b918 100644 --- a/src/specfact_cli/__init__.py +++ b/src/specfact_cli/__init__.py @@ -42,6 +42,6 @@ def _bootstrap_bundle_paths() -> None: _bootstrap_bundle_paths() -__version__ = "0.40.0" +__version__ = "0.40.1" __all__ = ["__version__"] diff --git a/tests/unit/packaging/test_core_package_includes.py b/tests/unit/packaging/test_core_package_includes.py index e96b9175..a94bdb53 100644 --- a/tests/unit/packaging/test_core_package_includes.py +++ b/tests/unit/packaging/test_core_package_includes.py @@ -3,6 +3,7 @@ from __future__ import annotations import re +import tomllib from pathlib import Path import pytest @@ -43,6 +44,22 @@ def test_pyproject_wheel_packages_exist() -> None: assert "specfact_cli" in raw +def test_pyproject_wheel_explicitly_maps_src_package_root() -> None: + """Wheel build config must explicitly map the src package root for specfact_cli.""" + data = tomllib.loads(PYPROJECT.read_text(encoding="utf-8")) + wheel = data["tool"]["hatch"]["build"]["targets"]["wheel"] + assert wheel.get("only-include") == ["src/specfact_cli"] + assert wheel.get("sources") == ["src"] + + +def test_project_scripts_target_cli_main() -> None: + """Both console scripts must resolve to the importable CLI entrypoint.""" + data = tomllib.loads(PYPROJECT.read_text(encoding="utf-8")) + scripts = data["project"]["scripts"] + assert scripts["specfact"] == "specfact_cli.cli:cli_main" + assert scripts["specfact-cli"] == "specfact_cli.cli:cli_main" + + def test_pyproject_force_include_does_not_reference_deleted_modules() -> None: """force-include must not reference the 17 deleted module dirs (exact key match).""" raw = PYPROJECT.read_text(encoding="utf-8")