create-forge 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {create_forge-0.4.0 → create_forge-0.5.0}/CHANGELOG.md +16 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/PKG-INFO +6 -5
- {create_forge-0.4.0 → create_forge-0.5.0}/README.md +4 -3
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/README.md +4 -1
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/adr/README.md +7 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v3/README.md +14 -12
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v3/roadmap/18-engine-default-client-delivery-and-validation/README.md +10 -9
- {create_forge-0.4.0 → create_forge-0.5.0}/pyproject.toml +14 -6
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/_engine_worker.py +27 -4
- create_forge-0.5.0/src/create_forge/capture.py +156 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/cli.py +61 -24
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/compat.py +14 -8
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/engine_source.py +13 -12
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/lifecycle.py +6 -14
- create_forge-0.5.0/src/create_forge/paths.py +202 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/staging.py +20 -51
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/templates.toml +2 -2
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/update.py +295 -40
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/conftest.py +10 -1
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/installed_client.py +43 -7
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/legacy_template.py +3 -9
- create_forge-0.5.0/tests/process.py +71 -0
- create_forge-0.5.0/tests/recovery_recipes.py +32 -0
- create_forge-0.5.0/tests/streamlit_recipes.py +50 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_archetype_parity.py +45 -0
- create_forge-0.5.0/tests/test_candidate_evidence.py +376 -0
- create_forge-0.5.0/tests/test_capture.py +254 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_cli.py +101 -12
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_cutover_acceptance_contract.py +13 -7
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_data_science_pipeline.py +1 -1
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_downstream_reference.py +1 -1
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_drift.py +5 -10
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_e2e_engine_generation.py +29 -88
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_e2e_generation.py +5 -23
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_e2e_installed_cutover.py +56 -25
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_e2e_installed_data_science.py +2 -2
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_e2e_installed_rollout.py +5 -6
- create_forge-0.5.0/tests/test_e2e_installed_streamlit.py +601 -0
- create_forge-0.5.0/tests/test_e2e_installed_update_safety.py +686 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_engine_adapter.py +8 -7
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_engine_contract.py +56 -2
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_engine_cross_repository.py +8 -7
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_engine_source.py +14 -13
- create_forge-0.5.0/tests/test_process_helper.py +93 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_roadmap_packs.py +3 -3
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_sources.py +2 -8
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_staging.py +57 -3
- create_forge-0.5.0/tests/test_streamlit_adoption.py +152 -0
- create_forge-0.5.0/tests/test_subprocess_decoding.py +494 -0
- create_forge-0.5.0/tests/test_subprocess_policy.py +203 -0
- create_forge-0.5.0/tests/test_supplied_candidate_wheel.py +68 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_update.py +2 -4
- create_forge-0.5.0/tests/test_update_containment.py +774 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_update_engine.py +35 -43
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_update_network.py +2 -8
- create_forge-0.5.0/tests/test_update_recovery.py +640 -0
- create_forge-0.5.0/tests/test_update_safety_evidence.py +92 -0
- create_forge-0.5.0/tests/test_user_guide_recipes.py +159 -0
- create_forge-0.4.0/tests/test_user_guide_recipes.py +0 -68
- {create_forge-0.4.0 → create_forge-0.5.0}/.gitignore +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/LICENSE +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/00-governance-and-principles/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/01-python-core/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/02-developer-experience/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/03-quality-and-ci/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/04-runtime-and-configuration/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/05-security-and-supply-chain/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/06-extension-and-composition-contract/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/07-forge-cli-integration/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/08-reference-archetype-validation/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v1/roadmap/09-blueprint-compatibility/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v2/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v2/roadmap/10-data-science-architecture-contract/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v2/roadmap/11-reusable-data-science-capabilities/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v2/roadmap/12-data-science-archetype/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v2/roadmap/13-data-science-cli-integration/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v2/roadmap/14-data-science-validation-and-rollout/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v3/roadmap/15-engine-default-provider-contracts/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v3/roadmap/16-engine-default-client-contracts/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v3/roadmap/17-engine-default-provider-implementation-and-release/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v4/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v4/roadmap/19-streamlit-architecture-contracts/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v4/roadmap/20-streamlit-provider-implementation-and-release/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/docs/roadmap-v4/roadmap/21-streamlit-client-adoption-and-rollout/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/examples/README.md +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/__init__.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/config.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/descriptors.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/engine.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/models.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/pipeline.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/prompts.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/registry.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/runner.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/sources.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/src/create_forge/spec.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/__init__.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_adr.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_component_selection.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_config.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_copier_cache.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_engine_default_contract.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_engine_lifecycle_contract.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_labels.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_lifecycle.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_models.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_pipeline.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_policy_hook.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_prompts.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_reference_client_boundary.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_registry.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_runner.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_spec.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_update_routing.py +0 -0
- {create_forge-0.4.0 → create_forge-0.5.0}/tests/test_workflows.py +0 -0
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
Generated by git-cliff from Conventional Commits.
|
|
4
|
+
## [0.5.0] - 2026-09-21
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Contain every engine-update filesystem target (CF-22.01) (#207)
|
|
9
|
+
- Recover failed updates from the actual Git state (CF-22.02) (#208)
|
|
10
|
+
- Capture subprocess output as bytes and decode by explicit rule (CF-23.01) (#215)
|
|
11
|
+
|
|
12
|
+
### Documentation
|
|
13
|
+
|
|
14
|
+
- Record create-forge 0.4.0 publication evidence (CF-18.07) (#185)
|
|
15
|
+
|
|
16
|
+
### Testing
|
|
17
|
+
|
|
18
|
+
- Validate installed Streamlit generation and document usage (CF-21.02) (#187)
|
|
19
|
+
- Verify update safety through the installed console (CF-22.03) (#210)
|
|
4
20
|
## [0.4.0] - 2026-09-14
|
|
5
21
|
|
|
6
22
|
### Documentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: create-forge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Scaffold modern Python projects from maintained templates.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Sandsy09/create-forge
|
|
6
6
|
Project-URL: Repository, https://github.com/Sandsy09/create-forge
|
|
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
20
20
|
Classifier: Topic :: Software Development :: Code Generators
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
|
-
Requires-Dist: forge-template<0.
|
|
23
|
+
Requires-Dist: forge-template<0.7,>=0.6
|
|
24
24
|
Requires-Dist: pydantic>=2.10
|
|
25
25
|
Requires-Dist: pyyaml>=6.0
|
|
26
26
|
Requires-Dist: questionary>=2.0
|
|
@@ -170,9 +170,10 @@ uv run poe check
|
|
|
170
170
|
`update` runs the engine-native Git-backed three-way merge against the
|
|
171
171
|
project's committed `.forge/generation.json`. Review the resulting diff and
|
|
172
172
|
resolve any conflict markers before committing. `--dry-run` prints the
|
|
173
|
-
per-target classification without writing anything
|
|
174
|
-
update
|
|
175
|
-
|
|
173
|
+
per-target classification without writing anything. A failed or interrupted
|
|
174
|
+
update leaves a working tree you can restore to your last commit — the
|
|
175
|
+
[updates guide](https://sandsy09.github.io/create-forge/updates/) has the
|
|
176
|
+
recovery procedure, including for an update that is already staged.
|
|
176
177
|
|
|
177
178
|
## The `--legacy` Copier route
|
|
178
179
|
|
|
@@ -136,9 +136,10 @@ uv run poe check
|
|
|
136
136
|
`update` runs the engine-native Git-backed three-way merge against the
|
|
137
137
|
project's committed `.forge/generation.json`. Review the resulting diff and
|
|
138
138
|
resolve any conflict markers before committing. `--dry-run` prints the
|
|
139
|
-
per-target classification without writing anything
|
|
140
|
-
update
|
|
141
|
-
|
|
139
|
+
per-target classification without writing anything. A failed or interrupted
|
|
140
|
+
update leaves a working tree you can restore to your last commit — the
|
|
141
|
+
[updates guide](https://sandsy09.github.io/create-forge/updates/) has the
|
|
142
|
+
recovery procedure, including for an update that is already staged.
|
|
142
143
|
|
|
143
144
|
## The `--legacy` Copier route
|
|
144
145
|
|
|
@@ -40,7 +40,7 @@ still usually cites the ADR that authorised it.
|
|
|
40
40
|
|
|
41
41
|
| Contract | Governs |
|
|
42
42
|
| --- | --- |
|
|
43
|
-
| [filesystem-generation.md](filesystem-generation.md) | Destination-conflict, staging, finalisation, and cleanup rules in `staging.py` (ADR 0015). |
|
|
43
|
+
| [filesystem-generation.md](filesystem-generation.md) | Destination-conflict, staging, finalisation, and cleanup rules in `staging.py` (ADR 0015), and the shared target-safety boundary in `paths.py` that both generation and engine-native `update` resolve every engine-supplied path through (ADR 0052). |
|
|
44
44
|
| [end-to-end-tests.md](end-to-end-tests.md) | The fast/`network`/`e2e` test-tier split and what the real console script is proven to do (ADR 0016). |
|
|
45
45
|
|
|
46
46
|
## Validation records
|
|
@@ -55,6 +55,9 @@ Acceptance-checklist-to-named-test maps, each closing an epic or a release.
|
|
|
55
55
|
| [release-0-3-0-validation.md](release-0-3-0-validation.md) | The published `create-forge 0.3.0` / `forge-template 0.4.1` pair verified against its own artefacts (ADR 0034). |
|
|
56
56
|
| [engine-cutover-validation.md](engine-cutover-validation.md) | The installed-console cutover acceptance matrix's remaining rows, and the rewritten user guide (ADR 0048). |
|
|
57
57
|
| [release-0-4-0-validation.md](release-0-4-0-validation.md) | The published `create-forge 0.4.0` / `forge-template 0.5.0` pair verified against its own artefacts, and the Engine-Default Cutover roadmap close-out (ADR 0049). |
|
|
58
|
+
| [installed-streamlit-validation.md](installed-streamlit-validation.md) | The four accepted Streamlit compositions, the previous-line provider, and the Streamlit failure cases through the installed candidate wheel (ADR 0051). |
|
|
59
|
+
| [subprocess-output.md](subprocess-output.md) | How every process the client starts is captured as bytes and decoded by an explicit rule (diagnostic, protocol, path/binary), the site inventory, the worker's UTF-8 wire format, and the test-harness rule (ADR 0055). |
|
|
60
|
+
| [update-safety-validation.md](update-safety-validation.md) | Update containment and recovery proven through the installed candidate wheel, the candidate's hashes and safety-relevant source digest, and CF-21.03's release prerequisite (ADR 0054). |
|
|
58
61
|
|
|
59
62
|
## Process and security
|
|
60
63
|
|
|
@@ -52,6 +52,13 @@ format](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
|
|
|
52
52
|
- [0047 — Reject a pre-cutover `--engine-preview` project with one diagnostic, add no migration helper, and keep the Copier route reachable without a usable engine](0047-legacy-copier-retention-and-preview-transition.md)
|
|
53
53
|
- [0048 — Complete the installed cutover acceptance evidence and the user guide's post-cutover voice](0048-installed-cutover-acceptance-evidence.md)
|
|
54
54
|
- [0049 — Publish create-forge 0.4.0 and close the Engine-Default Cutover roadmap](0049-publish-0-4-0-and-close-roadmap-v3.md)
|
|
55
|
+
- [0050 — Adopt the forge-template 0.6 Streamlit provider line](0050-adopt-the-0-6-streamlit-provider-line.md)
|
|
56
|
+
- [0051 — Validate Streamlit through the installed create-forge candidate](0051-validate-installed-streamlit-generation.md)
|
|
57
|
+
- [0052 — Contain every client filesystem target behind one shared boundary](0052-contain-every-client-filesystem-target.md)
|
|
58
|
+
- [0053 — Recover a failed update from the repository's actual Git state](0053-recover-updates-from-the-actual-git-state.md)
|
|
59
|
+
- [0054 — Verify update safety through the installed console, and bind it to the release candidate](0054-verify-installed-update-safety-on-the-release-candidate.md)
|
|
60
|
+
- [0055 — Capture subprocess output as bytes, and decode it by an explicit rule](0055-capture-subprocess-output-as-bytes-and-decode-by-rule.md)
|
|
61
|
+
- [0056 — Publish create-forge 0.5.0 and close the Streamlit roadmap's client work](0056-publish-create-forge-0-5-0.md)
|
|
55
62
|
|
|
56
63
|
Add a new record by copying the most recent one and incrementing the number.
|
|
57
64
|
Records are immutable: supersede them rather than editing.
|
|
@@ -2,22 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
Filed and
|
|
5
|
+
Filed and delivered. This pack recorded 5 repository-owned epics and
|
|
6
6
|
21 children for Stages 15–18, with verified issue numbers, labels,
|
|
7
|
-
milestones, native parents and direct dependencies.
|
|
8
|
-
|
|
7
|
+
milestones, native parents and direct dependencies. The filed manifest, the
|
|
8
|
+
issue bodies, and `scripts/check_roadmaps.py`'s expected output stay
|
|
9
|
+
unchanged (`filed-open`) by design — completion is recorded in prose here
|
|
10
|
+
and in the evidence record below, not in the pack's own filing status; see
|
|
11
|
+
ADR 0049 decision 5.
|
|
9
12
|
|
|
10
|
-
The
|
|
11
|
-
|
|
12
|
-
CF-18.07
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
The engine-default cutover this pack planned has shipped:
|
|
14
|
+
[FT-18.01](https://github.com/Sandsy09/forge-template/issues/156) closed the
|
|
15
|
+
provider-side integrated validation, and CF-18.07
|
|
16
|
+
([#164](https://github.com/Sandsy09/create-forge/issues/164), ADR 0049)
|
|
17
|
+
published it as **`create-forge 0.4.0`**. See
|
|
18
|
+
`docs/release-0-4-0-validation.md` for the full publication evidence and
|
|
19
|
+
roadmap reconciliation.
|
|
17
20
|
|
|
18
21
|
Working engine-native updates and continued support for existing Copier
|
|
19
|
-
projects
|
|
20
|
-
subject to the explicit decision children.
|
|
22
|
+
projects gated the engine-default release; both shipped.
|
|
21
23
|
|
|
22
24
|
## Read this pack
|
|
23
25
|
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Status and epics
|
|
4
4
|
|
|
5
|
-
Filed and
|
|
6
|
-
[CF-EPIC-18](https://github.com/Sandsy09/create-forge/issues/153)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
Filed and delivered on the `create-forge` side.
|
|
6
|
+
[CF-EPIC-18](https://github.com/Sandsy09/create-forge/issues/153) is closed:
|
|
7
|
+
all seven children merged, and CF-18.07 (ADR 0049) published
|
|
8
|
+
`create-forge 0.4.0` after
|
|
9
|
+
[FT-18.01](https://github.com/Sandsy09/forge-template/issues/156) closed the
|
|
10
|
+
provider-side integrated validation. See `docs/release-0-4-0-validation.md`
|
|
11
|
+
for the full publication evidence.
|
|
12
|
+
[FT-EPIC-18](https://github.com/Sandsy09/forge-template/issues/143) is
|
|
13
|
+
`forge-template`'s own epic; see that repository's roadmap record for its
|
|
14
|
+
status.
|
|
14
15
|
|
|
15
16
|
## Entry criteria
|
|
16
17
|
|
|
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "create-forge"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.5.0"
|
|
11
11
|
description = "Scaffold modern Python projects from maintained templates."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -32,11 +32,12 @@ dependencies = [
|
|
|
32
32
|
# moves from the optional `engine` extra into a required dependency. ADR
|
|
33
33
|
# 0018 assigned the first released range; ADR 0026 moved it to the
|
|
34
34
|
# `forge-template` 0.4 line; ADR 0031 raised the floor to the reviewed
|
|
35
|
-
# 0.4.1 release;
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
35
|
+
# 0.4.1 release; ADR 0042 adopted the reviewed 0.5.0 cutover release
|
|
36
|
+
# (docs/engine-cutover-acceptance.md); ADR 0050 (CF-21.01) adopts the
|
|
37
|
+
# reviewed 0.6.0 Streamlit provider release. src/create_forge/engine.py
|
|
38
|
+
# stays the only module that imports it; src/create_forge/compat.py holds
|
|
39
|
+
# the range itself so `doctor` can report it without importing the engine.
|
|
40
|
+
"forge-template>=0.6,<0.7",
|
|
40
41
|
# ADR 0021 adds bounded uv to finalise the engine render's dynamic
|
|
41
42
|
# lockfile; ADR 0038 reviewed that floor against the advisory record and
|
|
42
43
|
# held it -- the whole `>=0.12,<0.13` range is already advisory-free. Now
|
|
@@ -223,6 +224,13 @@ docs = "mkdocs serve"
|
|
|
223
224
|
# entry point -- see scripts/check_workflows.py.
|
|
224
225
|
"check:workflows" = "python scripts/check_workflows.py"
|
|
225
226
|
|
|
227
|
+
# Prints the table that binds the installed update-safety evidence to one
|
|
228
|
+
# candidate: commit, wheel/sdist hashes, forge-template artefact hashes, tool
|
|
229
|
+
# versions, and the safety-relevant source digest a fast-suite test checks
|
|
230
|
+
# (ADR 0054, CF-22.03). Re-run on the release commit to refresh it; `--no-build`
|
|
231
|
+
# skips the build. See scripts/candidate_evidence.py.
|
|
232
|
+
"evidence:candidate" = "python scripts/candidate_evidence.py"
|
|
233
|
+
|
|
226
234
|
# Reconciles a repo's GitHub labels against .github/labels.toml. Idempotent:
|
|
227
235
|
# `gh label create --force` creates or updates. Pass extra args after `--`,
|
|
228
236
|
# e.g. `uv run poe labels:sync --dry-run` or `--repo OWNER/NAME --prune`
|
|
@@ -148,6 +148,29 @@ def _dispatch(op: str, request: dict[str, object]) -> dict[str, object]:
|
|
|
148
148
|
raise ValueError(msg)
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
def _read_request() -> str:
|
|
152
|
+
"""The request, as strict UTF-8 from the binary stdin.
|
|
153
|
+
|
|
154
|
+
The protocol is UTF-8 in both directions whatever this interpreter's locale
|
|
155
|
+
is (CF-23.01, docs/subprocess-output.md), so the text layer -- which would
|
|
156
|
+
decode with the locale -- is bypassed. A byte that is not valid UTF-8 raises
|
|
157
|
+
here and is reported as a structured error by `main`, like any other bad
|
|
158
|
+
request. This file cannot import `create_forge.capture`: it runs where
|
|
159
|
+
`create_forge` is not installed.
|
|
160
|
+
"""
|
|
161
|
+
return sys.stdin.buffer.read().decode("utf-8")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _emit(response: dict[str, object]) -> None:
|
|
165
|
+
"""Write exactly one JSON object and a newline, as UTF-8, to the binary stdout.
|
|
166
|
+
|
|
167
|
+
`json.dumps` keeps its default `ensure_ascii=True`, so the bytes are ASCII --
|
|
168
|
+
valid UTF-8 -- and there is no newline translation to differ by platform.
|
|
169
|
+
"""
|
|
170
|
+
sys.stdout.buffer.write((json.dumps(response) + "\n").encode("utf-8"))
|
|
171
|
+
sys.stdout.buffer.flush()
|
|
172
|
+
|
|
173
|
+
|
|
151
174
|
def main(argv: list[str]) -> int:
|
|
152
175
|
"""Read one JSON request from stdin, write one JSON response to stdout.
|
|
153
176
|
|
|
@@ -167,19 +190,19 @@ def main(argv: list[str]) -> int:
|
|
|
167
190
|
"details": [],
|
|
168
191
|
},
|
|
169
192
|
}
|
|
170
|
-
|
|
193
|
+
_emit(response)
|
|
171
194
|
return 0
|
|
172
195
|
|
|
173
196
|
op = argv[1]
|
|
174
197
|
try:
|
|
175
|
-
raw =
|
|
198
|
+
raw = _read_request()
|
|
176
199
|
request = json.loads(raw) if raw.strip() else {}
|
|
177
200
|
result = _dispatch(op, request)
|
|
178
201
|
except Exception as exc:
|
|
179
|
-
|
|
202
|
+
_emit({"ok": False, "error": _error_payload(exc)})
|
|
180
203
|
return 0
|
|
181
204
|
|
|
182
|
-
|
|
205
|
+
_emit({"ok": True, "result": result})
|
|
183
206
|
return 0
|
|
184
207
|
|
|
185
208
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""The one place subprocess output is captured, and the rules for decoding it.
|
|
2
|
+
|
|
3
|
+
CF-23.01 (ADR 0055; canonical `docs/subprocess-output.md`). `subprocess.run(...,
|
|
4
|
+
text=True)` decodes with the *host locale* -- `cp1252` on a default Windows
|
|
5
|
+
install -- so what a child writes and how this process reads it were only ever
|
|
6
|
+
lined up by accident. `git` and `uv` write UTF-8; a Python child writes the
|
|
7
|
+
locale encoding when piped. On a non-UTF-8 host a single undecodable byte in a
|
|
8
|
+
child's stderr raised inside `subprocess.run` and left `stdout` as `None`.
|
|
9
|
+
`PYTHONUTF8=1` hides that; it does not define what the bytes are.
|
|
10
|
+
|
|
11
|
+
So nothing in this package captures text. `run_captured` returns **bytes**, and a
|
|
12
|
+
caller decodes only what it genuinely reads, with one of three explicit rules:
|
|
13
|
+
|
|
14
|
+
- **Diagnostic** (`decode_diagnostic`): UTF-8, `errors="replace"`. For a fact
|
|
15
|
+
extracted from human-oriented output (a version token, whether a value is
|
|
16
|
+
set). It never raises, and its result is never shown raw: raw argv, stdout and
|
|
17
|
+
stderr are never echoed (ADR 0044/0045/0046), because they can carry a
|
|
18
|
+
credential.
|
|
19
|
+
- **Protocol** (`decode_protocol`, `parse_protocol_json`): UTF-8, **strict**.
|
|
20
|
+
Machine-readable output either parses or is rejected. Malformed bytes, a byte
|
|
21
|
+
order mark, or a body that is not JSON raise `ProtocolDecodeError`; they can
|
|
22
|
+
never quietly become valid protocol data.
|
|
23
|
+
- **Path or binary data**: bytes are left alone (`git merge-file`'s content), or
|
|
24
|
+
are turned into a path with `decode_path`, which is `os.fsdecode`: exactly the
|
|
25
|
+
filesystem's own round trip, with **no replacement character**. Replacing
|
|
26
|
+
bytes in a path would name a different file.
|
|
27
|
+
|
|
28
|
+
Most call sites read only the return code and so decode nothing at all.
|
|
29
|
+
|
|
30
|
+
Deliberately engine-free, like `staging.py`, `lifecycle.py`, `update.py` and
|
|
31
|
+
`engine_source.py`: nothing here imports `forge_template`
|
|
32
|
+
(`tests/test_engine_contract.py`'s `_SHIPPED_MODULES` guard covers it), and
|
|
33
|
+
`tests/test_subprocess_policy.py` fails if any other module in this package calls
|
|
34
|
+
`subprocess.run`/`Popen`/`check_output`, or asks `subprocess` to decode.
|
|
35
|
+
|
|
36
|
+
`OSError` (a missing or unlaunchable executable) and `subprocess.TimeoutExpired`
|
|
37
|
+
propagate unchanged, so each caller keeps translating them into its own safe
|
|
38
|
+
error, and return codes -- including the negative "killed by signal" codes on
|
|
39
|
+
POSIX -- reach the caller exactly as `subprocess.run` reports them.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import json
|
|
45
|
+
import os
|
|
46
|
+
import subprocess
|
|
47
|
+
from dataclasses import dataclass
|
|
48
|
+
from typing import TYPE_CHECKING
|
|
49
|
+
|
|
50
|
+
if TYPE_CHECKING:
|
|
51
|
+
from collections.abc import Mapping, Sequence
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ProtocolDecodeError(ValueError):
|
|
56
|
+
"""Protocol output that is not valid UTF-8 JSON.
|
|
57
|
+
|
|
58
|
+
Carries no output bytes: what a misbehaving child wrote is never forwarded.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class Captured:
|
|
64
|
+
"""One finished child process: its status and its raw, undecoded streams."""
|
|
65
|
+
|
|
66
|
+
returncode: int
|
|
67
|
+
stdout: bytes
|
|
68
|
+
stderr: bytes
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_captured(
|
|
72
|
+
argv: Sequence[str],
|
|
73
|
+
*,
|
|
74
|
+
cwd: Path | None = None,
|
|
75
|
+
env: Mapping[str, str] | None = None,
|
|
76
|
+
stdin: bytes | None = None,
|
|
77
|
+
timeout: float | None = None,
|
|
78
|
+
) -> Captured:
|
|
79
|
+
"""Run `argv` without a shell and return its status and raw bytes.
|
|
80
|
+
|
|
81
|
+
Never passes `text=`, `encoding=` or `universal_newlines`, so nothing is
|
|
82
|
+
decoded and no newline is translated. `stdin`, when given, must already be
|
|
83
|
+
bytes: the caller states the encoding it sends. Never raises for a non-zero
|
|
84
|
+
exit (`check=False`); `OSError` and `subprocess.TimeoutExpired` propagate.
|
|
85
|
+
"""
|
|
86
|
+
result = subprocess.run( # noqa: S603 - callers pass reviewed argv, never a shell
|
|
87
|
+
list(argv),
|
|
88
|
+
cwd=cwd,
|
|
89
|
+
env=dict(env) if env is not None else None,
|
|
90
|
+
input=stdin,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
check=False,
|
|
93
|
+
timeout=timeout,
|
|
94
|
+
)
|
|
95
|
+
return Captured(result.returncode, result.stdout, result.stderr)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def decode_diagnostic(data: bytes) -> str:
|
|
99
|
+
"""Decode human-oriented output as UTF-8, replacing what is not.
|
|
100
|
+
|
|
101
|
+
For extracting a fact, never for showing the output: see the module
|
|
102
|
+
docstring's rule on echoing raw process output.
|
|
103
|
+
"""
|
|
104
|
+
return data.decode("utf-8", errors="replace")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def decode_protocol(data: bytes) -> str:
|
|
108
|
+
"""Decode machine-readable output as strict UTF-8.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ProtocolDecodeError: `data` is not valid UTF-8.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
return data.decode("utf-8")
|
|
115
|
+
except UnicodeDecodeError as exc:
|
|
116
|
+
msg = "protocol output is not valid UTF-8"
|
|
117
|
+
raise ProtocolDecodeError(msg) from exc
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def parse_protocol_json(data: bytes) -> object:
|
|
121
|
+
"""Parse machine-readable output: strict UTF-8, then strict JSON.
|
|
122
|
+
|
|
123
|
+
A byte order mark is left in place by the decode and refused by the parser,
|
|
124
|
+
so it is rejected rather than skipped.
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ProtocolDecodeError: `data` is not valid UTF-8, or is not JSON.
|
|
128
|
+
"""
|
|
129
|
+
text = decode_protocol(data)
|
|
130
|
+
try:
|
|
131
|
+
return json.loads(text)
|
|
132
|
+
except (ValueError, RecursionError) as exc:
|
|
133
|
+
msg = "protocol output is not valid JSON"
|
|
134
|
+
raise ProtocolDecodeError(msg) from exc
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def decode_path(data: bytes) -> str:
|
|
138
|
+
"""Turn path bytes a child printed into a path, with no replacement.
|
|
139
|
+
|
|
140
|
+
`os.fsdecode` is the filesystem's own encoding on every host (UTF-8 on
|
|
141
|
+
Windows since Python 3.6, the locale with `surrogateescape` on POSIX), so the
|
|
142
|
+
result names the file the child meant and round-trips through
|
|
143
|
+
`os.fsencode`.
|
|
144
|
+
"""
|
|
145
|
+
return os.fsdecode(data)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def display_safe(text: str) -> str:
|
|
149
|
+
r"""Make `text` printable without raising, without changing what it names.
|
|
150
|
+
|
|
151
|
+
A path decoded with `decode_path` can hold lone surrogates (bytes the
|
|
152
|
+
filesystem encoding could not decode); printing one raises. They are shown as
|
|
153
|
+
a visible `\udcXX` instead. The original string is untouched: use this only
|
|
154
|
+
at the point of display.
|
|
155
|
+
"""
|
|
156
|
+
return text.encode("utf-8", errors="backslashreplace").decode("utf-8")
|
|
@@ -18,7 +18,7 @@ from rich.panel import Panel
|
|
|
18
18
|
from rich.table import Table
|
|
19
19
|
from rich.text import Text
|
|
20
20
|
|
|
21
|
-
from create_forge import compat
|
|
21
|
+
from create_forge import capture, compat
|
|
22
22
|
from create_forge.compat import (
|
|
23
23
|
ENGINE_DISTRIBUTION,
|
|
24
24
|
INTEGRATION_LINE,
|
|
@@ -1442,15 +1442,27 @@ def _run_engine_update( # noqa: PLR0915 - one branch per prepare/apply/degraded
|
|
|
1442
1442
|
"""The engine-native `update` route (ADR 0041 rules 9-23, ADR 0046)."""
|
|
1443
1443
|
from create_forge import engine, pipeline, update # noqa: PLC0415
|
|
1444
1444
|
|
|
1445
|
+
# ADR 0053: recovery guidance is only ever printed once the clean-tree
|
|
1446
|
+
# precondition has passed. Before it, whatever is dirty is the user's own
|
|
1447
|
+
# work, and telling them to restore it away would destroy it.
|
|
1448
|
+
started = False
|
|
1445
1449
|
try:
|
|
1446
1450
|
update.require_clean_tree(project)
|
|
1451
|
+
started = True
|
|
1447
1452
|
degraded_reason: str | None = None
|
|
1448
1453
|
|
|
1449
1454
|
if degraded:
|
|
1450
1455
|
recorded, new = pipeline.prepare_degraded_update(project)
|
|
1456
|
+
new_files = {file.target: file.content for file in new.files}
|
|
1457
|
+
update.preflight_update(
|
|
1458
|
+
project,
|
|
1459
|
+
metadata_filename=engine.generation_metadata_target(),
|
|
1460
|
+
targets=new_files,
|
|
1461
|
+
recorded_targets=recorded.digests,
|
|
1462
|
+
)
|
|
1451
1463
|
outcome = update.degraded_plan(
|
|
1452
1464
|
project,
|
|
1453
|
-
|
|
1465
|
+
new_files,
|
|
1454
1466
|
recorded_digests=recorded.digests,
|
|
1455
1467
|
dry_run=dry_run,
|
|
1456
1468
|
)
|
|
@@ -1463,15 +1475,31 @@ def _run_engine_update( # noqa: PLR0915 - one branch per prepare/apply/degraded
|
|
|
1463
1475
|
err.print(f"[red]{exc}[/red]")
|
|
1464
1476
|
raise typer.Exit(3) from exc
|
|
1465
1477
|
recorded, new = pipeline.prepare_degraded_update(project)
|
|
1478
|
+
new_files = {file.target: file.content for file in new.files}
|
|
1479
|
+
update.preflight_update(
|
|
1480
|
+
project,
|
|
1481
|
+
metadata_filename=engine.generation_metadata_target(),
|
|
1482
|
+
targets=new_files,
|
|
1483
|
+
recorded_targets=recorded.digests,
|
|
1484
|
+
)
|
|
1466
1485
|
outcome = update.degraded_plan(
|
|
1467
1486
|
project,
|
|
1468
|
-
|
|
1487
|
+
new_files,
|
|
1469
1488
|
recorded_digests=recorded.digests,
|
|
1470
1489
|
dry_run=dry_run,
|
|
1471
1490
|
)
|
|
1472
1491
|
degraded_reason = str(exc)
|
|
1473
1492
|
else:
|
|
1474
1493
|
new = preparation.new
|
|
1494
|
+
# ADR 0052: refuse the whole update before its first mutation.
|
|
1495
|
+
# Plan targets already cover every recorded target the plan
|
|
1496
|
+
# acts on, so the recorded document itself is not re-read here.
|
|
1497
|
+
update.preflight_update(
|
|
1498
|
+
project,
|
|
1499
|
+
metadata_filename=engine.generation_metadata_target(),
|
|
1500
|
+
targets=[item.target for item in preparation.plan.targets],
|
|
1501
|
+
renames=preparation.plan.renames,
|
|
1502
|
+
)
|
|
1475
1503
|
update.apply_renames(project, preparation.plan.renames)
|
|
1476
1504
|
outcome = update.apply_plan(
|
|
1477
1505
|
project,
|
|
@@ -1492,8 +1520,10 @@ def _run_engine_update( # noqa: PLR0915 - one branch per prepare/apply/degraded
|
|
|
1492
1520
|
msg = "the engine returned no generation metadata for this render"
|
|
1493
1521
|
raise StagingError(msg)
|
|
1494
1522
|
refreshed = engine.metadata_json(new.metadata, degraded_reason=degraded_reason)
|
|
1495
|
-
|
|
1496
|
-
|
|
1523
|
+
update.write_recorded(
|
|
1524
|
+
project,
|
|
1525
|
+
metadata_filename=engine.generation_metadata_target(),
|
|
1526
|
+
content=refreshed,
|
|
1497
1527
|
)
|
|
1498
1528
|
update.stage_result(project)
|
|
1499
1529
|
|
|
@@ -1502,21 +1532,37 @@ def _run_engine_update( # noqa: PLR0915 - one branch per prepare/apply/degraded
|
|
|
1502
1532
|
_report_update_result(outcome)
|
|
1503
1533
|
except KeyboardInterrupt:
|
|
1504
1534
|
err.print("\n[dim]Cancelled.[/dim]")
|
|
1505
|
-
|
|
1535
|
+
_print_recovery(project, started=started)
|
|
1506
1536
|
raise typer.Exit(130) from None
|
|
1507
1537
|
except (update.UpdateError, StagingError) as exc:
|
|
1508
1538
|
err.print(f"[red]{exc}[/red]")
|
|
1509
|
-
|
|
1539
|
+
_print_recovery(project, started=started)
|
|
1510
1540
|
raise typer.Exit(1) from exc
|
|
1511
1541
|
except engine.EngineCompatibilityError as exc:
|
|
1512
1542
|
err.print(f"[red]{exc}[/red]")
|
|
1513
1543
|
raise typer.Exit(3) from exc
|
|
1514
1544
|
except engine.ForgeEngineError as exc:
|
|
1515
1545
|
err.print(f"[red]{engine.explain(exc)}[/red]")
|
|
1516
|
-
|
|
1546
|
+
_print_recovery(project, started=started)
|
|
1517
1547
|
raise typer.Exit(1) from exc
|
|
1518
1548
|
|
|
1519
1549
|
|
|
1550
|
+
def _print_recovery(project: Path, *, started: bool) -> None:
|
|
1551
|
+
"""Rule 18 (ADR 0053): guidance for the repository's *actual* Git state.
|
|
1552
|
+
|
|
1553
|
+
Read from Git at failure time, so a failure that changed nothing says so
|
|
1554
|
+
rather than printing a command, and a staged or half-renamed tree gets one
|
|
1555
|
+
that actually restores it. Printed, never run. `soft_wrap` and no markup or
|
|
1556
|
+
highlighting keep a command on one line, unmangled, ready to paste.
|
|
1557
|
+
"""
|
|
1558
|
+
if not started:
|
|
1559
|
+
return
|
|
1560
|
+
from create_forge import update # noqa: PLC0415
|
|
1561
|
+
|
|
1562
|
+
for line in update.recovery_guidance(project).lines():
|
|
1563
|
+
err.print(line, style="dim", markup=False, highlight=False, soft_wrap=True)
|
|
1564
|
+
|
|
1565
|
+
|
|
1520
1566
|
def _markers(target: Console) -> tuple[str, str]:
|
|
1521
1567
|
"""Return (pass, fail) markers the console's encoding can actually render.
|
|
1522
1568
|
|
|
@@ -1889,16 +1935,11 @@ def _uv_version(uv_path: str | None) -> str | None:
|
|
|
1889
1935
|
if not uv_path:
|
|
1890
1936
|
return None
|
|
1891
1937
|
try:
|
|
1892
|
-
result =
|
|
1893
|
-
[uv_path, "--version"],
|
|
1894
|
-
capture_output=True,
|
|
1895
|
-
text=True,
|
|
1896
|
-
check=False,
|
|
1897
|
-
timeout=5,
|
|
1898
|
-
)
|
|
1938
|
+
result = capture.run_captured([uv_path, "--version"], timeout=5)
|
|
1899
1939
|
except (OSError, subprocess.TimeoutExpired): # pragma: no cover
|
|
1900
1940
|
return None
|
|
1901
|
-
|
|
1941
|
+
# A diagnostic read (CF-23.01): lenient, and only a validated token escapes.
|
|
1942
|
+
match capture.decode_diagnostic(result.stdout).split():
|
|
1902
1943
|
case [_, token, *_] if re.fullmatch(r"[0-9][0-9A-Za-z.+-]*", token):
|
|
1903
1944
|
return token
|
|
1904
1945
|
case _:
|
|
@@ -2048,13 +2089,9 @@ def config_show() -> None:
|
|
|
2048
2089
|
|
|
2049
2090
|
def _git_config(key: str) -> str | None:
|
|
2050
2091
|
try:
|
|
2051
|
-
result =
|
|
2052
|
-
["git", "config", "--get", key], # noqa: S607
|
|
2053
|
-
capture_output=True,
|
|
2054
|
-
text=True,
|
|
2055
|
-
check=False,
|
|
2056
|
-
timeout=5,
|
|
2057
|
-
)
|
|
2092
|
+
result = capture.run_captured(["git", "config", "--get", key], timeout=5)
|
|
2058
2093
|
except (OSError, subprocess.TimeoutExpired): # pragma: no cover
|
|
2059
2094
|
return None
|
|
2060
|
-
|
|
2095
|
+
# A diagnostic read (CF-23.01): a name may be non-ASCII, and doctor only asks
|
|
2096
|
+
# whether one is set, so a lenient decode is enough and cannot raise.
|
|
2097
|
+
return capture.decode_diagnostic(result.stdout).strip() or None
|
|
@@ -27,21 +27,24 @@ from packaging.version import Version
|
|
|
27
27
|
ENGINE_DISTRIBUTION = "forge-template"
|
|
28
28
|
"""The PyPI distribution name the engine dependency declares."""
|
|
29
29
|
|
|
30
|
-
INTEGRATION_LINE = "v0.
|
|
30
|
+
INTEGRATION_LINE = "v0.5.x-engine"
|
|
31
31
|
"""The create-forge release line and its default generation architecture.
|
|
32
32
|
|
|
33
33
|
This is explicit rather than derived from installed metadata so a new release
|
|
34
34
|
line requires a deliberate compatibility review. ADR 0040 (CF-18.01) makes the
|
|
35
35
|
engine the default `new` architecture; `new` is Copier-backed only under the
|
|
36
36
|
explicit `--legacy` flag. ADR 0049 (CF-18.07) moved this from `v0.3.x-engine`
|
|
37
|
-
to `v0.4.x-engine` for the published cutover release
|
|
38
|
-
|
|
37
|
+
to `v0.4.x-engine` for the published cutover release, and ADR 0056 (CF-21.03)
|
|
38
|
+
moves it to `v0.5.x-engine` for the release that crosses to the `forge-template`
|
|
39
|
+
`0.6` provider line (client `0.N.x` pairs with provider `0.(N+1)`) -- a shipped
|
|
40
|
+
diagnostic surface (`doctor --json`'s `integration.line`) reviewed by hand, not
|
|
41
|
+
derived.
|
|
39
42
|
`tests/test_engine_contract.py`'s
|
|
40
43
|
`test_diagnostic_integration_line_matches_package_release_line` checks this
|
|
41
44
|
literal against `pyproject.toml`'s own `major.minor`.
|
|
42
45
|
"""
|
|
43
46
|
|
|
44
|
-
SUPPORTED_ENGINE_RANGE = ">=0.
|
|
47
|
+
SUPPORTED_ENGINE_RANGE = ">=0.6,<0.7"
|
|
45
48
|
"""The supported `forge-template` compatibility range.
|
|
46
49
|
|
|
47
50
|
Pre-1.0, a supported range stays within one minor line -- see the
|
|
@@ -52,14 +55,17 @@ human-authored line crossing (ADR 0012), never a Dependabot proposal. ADR
|
|
|
52
55
|
`forge-template` 0.4 line; ADR 0031 raised the lower bound to the reviewed
|
|
53
56
|
`0.4.1` release. ADR 0040/0042 (CF-18.01) adopt the reviewed `0.5.0`
|
|
54
57
|
engine-default cutover release, the first to publish `metadata_version` and
|
|
55
|
-
component-manifest protocol `3`.
|
|
56
|
-
|
|
58
|
+
component-manifest protocol `3`. ADR 0050 (CF-21.01) adopts the reviewed
|
|
59
|
+
`0.6.0` Streamlit provider release, which moves only the package version and
|
|
60
|
+
the discovered catalogue (a fifteenth component) and no protocol tuple.
|
|
61
|
+
`engine.py` checks an installed package against this range with
|
|
62
|
+
`packaging.specifiers.SpecifierSet`.
|
|
57
63
|
"""
|
|
58
64
|
|
|
59
65
|
SUPPORTED_PROJECTSPEC_PROTOCOLS: tuple[int, ...] = (1,)
|
|
60
66
|
"""ProjectSpec wire protocols this create-forge release has implemented
|
|
61
|
-
against. Unchanged across the `0.3.x` through `0.
|
|
62
|
-
(ADR 0026, ADR 0042).
|
|
67
|
+
against. Unchanged across the `0.3.x` through `0.6.x` engine lines
|
|
68
|
+
(ADR 0026, ADR 0042, ADR 0050).
|
|
63
69
|
|
|
64
70
|
Deliberately not read from the installed engine's own advertised protocols
|
|
65
71
|
-- negotiation in `engine.py` compares the two sides rather than assuming
|