workflow-loop 0.3.9__tar.gz → 0.4.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.
- {workflow_loop-0.3.9/src/workflow_loop.egg-info → workflow_loop-0.4.0}/PKG-INFO +9 -9
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/README.md +8 -8
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/pyproject.toml +1 -1
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/__init__.py +2 -2
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/artifact_validation.py +106 -2
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/cli.py +155 -2
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/project.py +31 -2
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/records.py +156 -11
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/rollback.py +71 -8
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/stages/stages.py +10 -4
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/state.py +5 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/test_mapping.py +10 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/traceability.py +16 -5
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/verification.py +56 -1
- {workflow_loop-0.3.9 → workflow_loop-0.4.0/src/workflow_loop.egg-info}/PKG-INFO +9 -9
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/SOURCES.txt +5 -0
- workflow_loop-0.4.0/tests/test_gate_delete_files.py +143 -0
- workflow_loop-0.4.0/tests/test_gate_no_change_declaration.py +174 -0
- workflow_loop-0.4.0/tests/test_gate_spike_reuse.py +148 -0
- workflow_loop-0.4.0/tests/test_gate_tmp_artifacts.py +89 -0
- workflow_loop-0.4.0/tests/test_mixed_topic_test_result.py +353 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/LICENSE +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/setup.cfg +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/acceptance_records.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/artifact_paths.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/bug_record.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/code_symbols.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance_plan.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/code_design/code_design.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/code_design/project_design_init.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/code_design/update_code_design.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/global/document_writing.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/global/workflow_lifecycle.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/impl/code_implementation.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/impl/impl.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test_code.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test_code_implementation.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test_plan.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/reproduce/reproduce.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/spec/spec.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/spike/spike.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_plan.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_result.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/code_design/code_design.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/code_design/project_design_init_evidence.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/impl/impl.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/qa/test.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/qa/test_plan.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/reproduce/reproduce.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/spec/spec.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/spike/spike.md +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/design_sync.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/diagnostics.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/installer.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/journal.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/machine_collect.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/markdown_links.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/path_composer.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/process_runner.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/role_doc.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/snapshots.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/spike_reuse.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/spike_validation.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/stage_materials.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/stages/__init__.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/stages/base.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/test_entry.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/test_execution.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/test_report.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/test_runner.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/topic.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop/topic_relations.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/dependency_links.txt +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/entry_points.txt +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/requires.txt +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/top_level.txt +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_acceptance_records.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_architecture_validation.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_artifact_paths.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_bug_record.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_bug_validation.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_commands.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_current_workflow_contracts.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_current_workflow_qa.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_design_sync_tables.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_diagnostics.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_empty_result_table_guard.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_gate_plan_path_diagnostics.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_gate_rework_stability.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_gate_self_heal_output.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_git_baseline_exit.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_installer.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_light_task.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_machine_collect.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_machine_collect_extension.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_maintenance.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_maintenance_scripts.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_markdown_links.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_multibug_workflow.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_path_composer.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_process_runner.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_project.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_project_design_init.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_public_project.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_records.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_recovery_journal_boundary.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_registration_guidance.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_regression_skip.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_release_script.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_release_workflow.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_return_rework_flow.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_rollback.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_snapshots.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_spike_assets.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_spike_validation.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_stage_materials.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_stages.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_state.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_table_entry_registration.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_test_entry.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_test_execution.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_test_mapping.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_test_report.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_test_runner.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_topic_relations.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_traceability.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_verification.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_version_document_sources.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_workflow_acceptance.py +0 -0
- {workflow_loop-0.3.9 → workflow_loop-0.4.0}/tests/test_workflow_guidance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: workflow-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: 为 AI 驱动的软件开发提供有状态、可验证、可回退的工作流管理。
|
|
5
5
|
Author: yuzyf
|
|
6
6
|
License-Expression: MIT
|
|
@@ -95,29 +95,29 @@ flowchart TD
|
|
|
95
95
|
- 安装时能够访问 GitHub 和 PyPI(Python 公共软件包仓库)。
|
|
96
96
|
- 执行安装命令前,先进入要由 Workflow Loop 管理的项目根目录。
|
|
97
97
|
|
|
98
|
-
## 安装 0.
|
|
98
|
+
## 安装 0.4.0
|
|
99
99
|
|
|
100
100
|
安装器先进行只读检查并列出项目侧和电脑侧可能发生的全部持久修改。用户确认一次后,安装器才安装或复用全局 `workflow` 命令,并把智能体契约、产物模板和工作规范写入当前项目;用户取消或安装失败时不会留下只完成一部分的安装。
|
|
101
101
|
|
|
102
102
|
### macOS
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.
|
|
105
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.4.0/install.sh | bash
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
### Linux
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.
|
|
111
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.4.0/install.sh | bash
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
### 原生 Windows
|
|
115
115
|
|
|
116
116
|
```powershell
|
|
117
|
-
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.
|
|
117
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.4.0/install.ps1 | iex"
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
安装命令固定读取 `v0.
|
|
120
|
+
安装命令固定读取 `v0.4.0` 正式发布中的脚本;不会跟随内容可能变化的 `latest`(最新版本)地址。
|
|
121
121
|
|
|
122
122
|
## 更新与卸载
|
|
123
123
|
|
|
@@ -125,7 +125,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/y
|
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
127
|
workflow update
|
|
128
|
-
workflow update --version 0.
|
|
128
|
+
workflow update --version 0.4.0
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
更新按需补齐电脑全局命令和当前项目,只直接覆盖项目根 `AGENTS.md`、`.workflow_loop/Template_Repository/`、`.workflow_loop/Standardized_Repository/`,以及 `.workflow_loop/project.json` 中的安装版本字段。更新不创建备份,不回滚已经完成的步骤;当前轮次状态、历史、回退资料、业务代码和正式产物保持不变。失败后重新执行同一命令即可继续补齐。
|
|
@@ -229,10 +229,10 @@ uv run python -m build
|
|
|
229
229
|
|
|
230
230
|
## 发布正式版本
|
|
231
231
|
|
|
232
|
-
项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.
|
|
232
|
+
项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.4.0` 替换成要发布的新版本号:
|
|
233
233
|
|
|
234
234
|
```bash
|
|
235
|
-
uv run python scripts/release.py 0.
|
|
235
|
+
uv run python scripts/release.py 0.4.0
|
|
236
236
|
```
|
|
237
237
|
|
|
238
238
|
运行这条命令本身就表示维护者接受当前仓库状态并承担跳过本地发布检查的风险。脚本不会再次确认,也不会在本地运行测试、构建或远程版本查询。它依次执行:
|
|
@@ -76,29 +76,29 @@ flowchart TD
|
|
|
76
76
|
- 安装时能够访问 GitHub 和 PyPI(Python 公共软件包仓库)。
|
|
77
77
|
- 执行安装命令前,先进入要由 Workflow Loop 管理的项目根目录。
|
|
78
78
|
|
|
79
|
-
## 安装 0.
|
|
79
|
+
## 安装 0.4.0
|
|
80
80
|
|
|
81
81
|
安装器先进行只读检查并列出项目侧和电脑侧可能发生的全部持久修改。用户确认一次后,安装器才安装或复用全局 `workflow` 命令,并把智能体契约、产物模板和工作规范写入当前项目;用户取消或安装失败时不会留下只完成一部分的安装。
|
|
82
82
|
|
|
83
83
|
### macOS
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.
|
|
86
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.4.0/install.sh | bash
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
### Linux
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.
|
|
92
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.4.0/install.sh | bash
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
### 原生 Windows
|
|
96
96
|
|
|
97
97
|
```powershell
|
|
98
|
-
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.
|
|
98
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.4.0/install.ps1 | iex"
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
安装命令固定读取 `v0.
|
|
101
|
+
安装命令固定读取 `v0.4.0` 正式发布中的脚本;不会跟随内容可能变化的 `latest`(最新版本)地址。
|
|
102
102
|
|
|
103
103
|
## 更新与卸载
|
|
104
104
|
|
|
@@ -106,7 +106,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/y
|
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
workflow update
|
|
109
|
-
workflow update --version 0.
|
|
109
|
+
workflow update --version 0.4.0
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
更新按需补齐电脑全局命令和当前项目,只直接覆盖项目根 `AGENTS.md`、`.workflow_loop/Template_Repository/`、`.workflow_loop/Standardized_Repository/`,以及 `.workflow_loop/project.json` 中的安装版本字段。更新不创建备份,不回滚已经完成的步骤;当前轮次状态、历史、回退资料、业务代码和正式产物保持不变。失败后重新执行同一命令即可继续补齐。
|
|
@@ -210,10 +210,10 @@ uv run python -m build
|
|
|
210
210
|
|
|
211
211
|
## 发布正式版本
|
|
212
212
|
|
|
213
|
-
项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.
|
|
213
|
+
项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.4.0` 替换成要发布的新版本号:
|
|
214
214
|
|
|
215
215
|
```bash
|
|
216
|
-
uv run python scripts/release.py 0.
|
|
216
|
+
uv run python scripts/release.py 0.4.0
|
|
217
217
|
```
|
|
218
218
|
|
|
219
219
|
运行这条命令本身就表示维护者接受当前仓库状态并承担跳过本地发布检查的风险。脚本不会再次确认,也不会在本地运行测试、构建或远程版本查询。它依次执行:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 包版本号,用于 pip/uv 安装时版本匹配
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.4.0"
|
|
3
3
|
# 产品身份标识:workflow --version 输出、安装脚本身份核对和安装事务校验共用同一组常量
|
|
4
|
-
# 本次发布版本为 0.
|
|
4
|
+
# 本次发布版本为 0.4.0;后续发布使用尚未被 PyPI 占用的新版本号
|
|
5
5
|
PRODUCT_NAME = "workflow-loop"
|
|
6
6
|
PRODUCT_IDENTITY = f"{PRODUCT_NAME} {__version__}"
|
|
@@ -645,6 +645,11 @@ def changed_stage_paths(
|
|
|
645
645
|
启用工作记录表的轮次里正式文档是程序按表的确定性输出:表没有变化时文档必然
|
|
646
646
|
逐字节相同,"文档没变"不等于"本阶段没有产出"。这种情况下按表判断产出,并把
|
|
647
647
|
本阶段登记的产物路径交给下游检查,不要求 AI 制造无实质内容的修改。
|
|
648
|
+
|
|
649
|
+
退回核对场景(推进 R47):变化清单为空、且当前环节任一主题表填有有效的
|
|
650
|
+
"核对结论:无需修改"声明(核对对象和核对依据都有实质内容)时,返回通过并
|
|
651
|
+
标注声明放行——产物确实不用改也是合法完成状态。产物有实际变化时声明不参与
|
|
652
|
+
判定,按普通流程走。
|
|
648
653
|
"""
|
|
649
654
|
state = load_state(project_root)
|
|
650
655
|
if state is None or stage_name not in state.stages:
|
|
@@ -671,15 +676,90 @@ def changed_stage_paths(
|
|
|
671
676
|
# 变化清单如实为空:确实没有文件变化。调用方要判断“本阶段的产出是哪些”时
|
|
672
677
|
# 按内容识别,不能把未变化的文件当成本阶段改过——最终设计同步正是用这份
|
|
673
678
|
# 清单确认产品文档没有被改动。
|
|
679
|
+
declaration = _valid_no_change_declaration(project_root, state, stage_name)
|
|
680
|
+
if declaration is not None:
|
|
681
|
+
return (
|
|
682
|
+
True,
|
|
683
|
+
"核对结论:无需修改——产物相对基线无变化,声明的核对依据有效"
|
|
684
|
+
f"(核对对象:{declaration['核对对象']})",
|
|
685
|
+
[],
|
|
686
|
+
)
|
|
674
687
|
return (
|
|
675
688
|
True,
|
|
676
689
|
"表模式:正式文档由程序按工作记录表生成,内容与上次相同时按表判断产出",
|
|
677
690
|
[],
|
|
678
691
|
)
|
|
692
|
+
# 文档模式退回核对:声明同样适用(R47 不限于表模式)。
|
|
693
|
+
declaration = _valid_no_change_declaration(project_root, state, stage_name)
|
|
694
|
+
if declaration is not None:
|
|
695
|
+
return (
|
|
696
|
+
True,
|
|
697
|
+
"核对结论:无需修改——产物相对基线无变化,声明的核对依据有效"
|
|
698
|
+
f"(核对对象:{declaration['核对对象']})",
|
|
699
|
+
[],
|
|
700
|
+
)
|
|
679
701
|
return (False, "相关文件与讨论完成时相同,不能证明本阶段已经生成或修改产物", [])
|
|
680
702
|
return (True, f"本阶段发生变化的文件: {changed}", changed)
|
|
681
703
|
|
|
682
704
|
|
|
705
|
+
def _valid_no_change_declaration(
|
|
706
|
+
project_root: str,
|
|
707
|
+
state,
|
|
708
|
+
stage_name: str,
|
|
709
|
+
) -> dict | None:
|
|
710
|
+
"""读当前环节工作记录表的“核对结论:无需修改”声明;有效时返回声明行。
|
|
711
|
+
|
|
712
|
+
有效 = 声明行存在、核对对象非空、核对依据有实质内容(非占位词且达到
|
|
713
|
+
最小长度)。表不存在或声明无效时返回 None,调用方维持原有判定。
|
|
714
|
+
|
|
715
|
+
查找范围:主题级表(impl_record、acceptance_plan、test_plan 等)按主题
|
|
716
|
+
逐个查;只有轮次级表的环节(如 spec 的 product_features)查轮次级表——
|
|
717
|
+
退回核对发生在哪个环节,声明就填在那个环节的表里。
|
|
718
|
+
"""
|
|
719
|
+
from . import records as records_mod
|
|
720
|
+
|
|
721
|
+
if state is None:
|
|
722
|
+
return None
|
|
723
|
+
kinds = records_mod.stage_table_kinds(stage_name)
|
|
724
|
+
topic_level = [kind for kind in kinds if kind not in records_mod.WORKFLOW_LEVEL_KINDS]
|
|
725
|
+
workflow_level = [kind for kind in kinds if kind in records_mod.WORKFLOW_LEVEL_KINDS]
|
|
726
|
+
|
|
727
|
+
for kind in topic_level:
|
|
728
|
+
for topic in list(state.topics or []):
|
|
729
|
+
declaration = records_mod.no_change_declaration(
|
|
730
|
+
project_root,
|
|
731
|
+
state.workflow_id,
|
|
732
|
+
kind,
|
|
733
|
+
topic,
|
|
734
|
+
)
|
|
735
|
+
if declaration is None:
|
|
736
|
+
continue
|
|
737
|
+
if not records_mod.declaration_rationale_has_substance(
|
|
738
|
+
declaration.get("核对依据", "")
|
|
739
|
+
):
|
|
740
|
+
continue
|
|
741
|
+
return declaration
|
|
742
|
+
|
|
743
|
+
# 轮次级表:表定位时 topic 用空串(records 的轮次级表约定)
|
|
744
|
+
for kind in workflow_level:
|
|
745
|
+
if kind == "topic_relations":
|
|
746
|
+
continue
|
|
747
|
+
declaration = records_mod.no_change_declaration(
|
|
748
|
+
project_root,
|
|
749
|
+
state.workflow_id,
|
|
750
|
+
kind,
|
|
751
|
+
"",
|
|
752
|
+
)
|
|
753
|
+
if declaration is None:
|
|
754
|
+
continue
|
|
755
|
+
if not records_mod.declaration_rationale_has_substance(
|
|
756
|
+
declaration.get("核对依据", "")
|
|
757
|
+
):
|
|
758
|
+
continue
|
|
759
|
+
return declaration
|
|
760
|
+
return None
|
|
761
|
+
|
|
762
|
+
|
|
683
763
|
def validate_project_design_init_evidence(
|
|
684
764
|
project_root: str,
|
|
685
765
|
workflow_id: str,
|
|
@@ -2469,9 +2549,33 @@ def _validate_topic_test_execution_result(
|
|
|
2469
2549
|
failures.append(f"{rel_path} 有人工验收内容,但缺少具体人工验收交接")
|
|
2470
2550
|
|
|
2471
2551
|
sections = _test_result_sections(content)
|
|
2472
|
-
|
|
2473
|
-
|
|
2552
|
+
# 单一数据源:自动化项编号集合与生成器同源(R12/R14),不再各写各的筛选
|
|
2553
|
+
from . import test_mapping as test_mapping_mod
|
|
2554
|
+
|
|
2555
|
+
try:
|
|
2556
|
+
all_plan_items = parse_test_plan_items(project_root, topic)
|
|
2557
|
+
except ValueError:
|
|
2558
|
+
all_plan_items = list(items)
|
|
2559
|
+
automated_ids = test_mapping_mod.automated_test_ids_in_plan(all_plan_items)
|
|
2560
|
+
if not automated_ids:
|
|
2561
|
+
automated_ids = {item.test_id for item in items}
|
|
2562
|
+
expected_ids = automated_ids if automated_ids else {item.test_id for item in items}
|
|
2563
|
+
# 第 3 节的自动化项小节必须正好覆盖自动化项;人工项小节是合法呈现,
|
|
2564
|
+
# 不计入"正好覆盖"集合(生成器对人工项渲染"测试方式:人工验收"小节)。
|
|
2565
|
+
automated_sections = {tid for tid in sections if tid in expected_ids}
|
|
2566
|
+
manual_sections = set(sections) - automated_sections
|
|
2567
|
+
if automated_sections != expected_ids:
|
|
2474
2568
|
failures.append(f"{rel_path} 的测试项结果必须正好覆盖 {sorted(expected_ids)}")
|
|
2569
|
+
for tid in sorted(manual_sections):
|
|
2570
|
+
section = sections[tid]
|
|
2571
|
+
if "测试方式:人工验收" not in section:
|
|
2572
|
+
failures.append(
|
|
2573
|
+
f"{rel_path} 的 {tid} 是人工验收项小节,必须标注“测试方式:人工验收”"
|
|
2574
|
+
)
|
|
2575
|
+
if "自动化测试结果:未执行" in section:
|
|
2576
|
+
failures.append(
|
|
2577
|
+
f"{rel_path} 的 {tid} 人工验收项不能写“自动化测试结果:未执行”(人工项转人工验收交接)"
|
|
2578
|
+
)
|
|
2475
2579
|
|
|
2476
2580
|
for item in items:
|
|
2477
2581
|
task = tasks.get(item.test_id)
|
|
@@ -1676,6 +1676,23 @@ def cmd_discuss(args) -> None:
|
|
|
1676
1676
|
print("\n【当前主题验收进度】")
|
|
1677
1677
|
for line in acceptance_records_mod.acceptance_progress(project_root, wf_state):
|
|
1678
1678
|
print(f"- {line}")
|
|
1679
|
+
if stage.name() == "spike":
|
|
1680
|
+
# 验证技术不确定性 R32:返回重走决策树——三条路径的判断条件和建议动作,
|
|
1681
|
+
# AI 不再靠翻源码或猜来选择跳过还是复用。
|
|
1682
|
+
current_assets = [
|
|
1683
|
+
asset
|
|
1684
|
+
for asset in wf_state.spike_assets
|
|
1685
|
+
if asset.workflow_id == wf_state.workflow_id
|
|
1686
|
+
]
|
|
1687
|
+
print("\n【返回重走决策树】按当前事实选择路径,用户决定:")
|
|
1688
|
+
print(" 1. 设计变更引入了新技术不确定性 → 重新穿刺(正常执行穿刺项)")
|
|
1689
|
+
print(" 2. 没有引入,且本工作流从未登记穿刺资产 → workflow gate spike --skip")
|
|
1690
|
+
if current_assets:
|
|
1691
|
+
print(f" 3. 没有引入,但本工作流已登记 {len(current_assets)} 个穿刺资产 → "
|
|
1692
|
+
"workflow gate spike --reuse --reuse-rationale <每个被复用结论为何仍成立>")
|
|
1693
|
+
print(f" 已登记资产:{';'.join(asset.relative_path for asset in current_assets)}")
|
|
1694
|
+
else:
|
|
1695
|
+
print(" (本工作流没有已登记穿刺资产,--reuse 不适用)")
|
|
1679
1696
|
|
|
1680
1697
|
# 写 journal:材料清单登记(记录本次清单的组成与内容指纹)
|
|
1681
1698
|
journal_mod.append_entry(project_root, "材料清单登记", "workflow.py",
|
|
@@ -3902,13 +3919,18 @@ def cmd_gate(args) -> None:
|
|
|
3902
3919
|
sys.exit(1)
|
|
3903
3920
|
|
|
3904
3921
|
# 旧基线参数只为兼容历史调用;当前产品规则不再把它们作为门禁流程。
|
|
3922
|
+
# 旧基线参数只为兼容历史调用;当前产品规则不再把它们作为门禁流程。
|
|
3923
|
+
# getattr 容错:测试和历史调用方可能用 SimpleNamespace 模拟参数对象,
|
|
3924
|
+
# 新参数(--reuse/--reuse-rationale)在其中不存在时按未使用处理。
|
|
3925
|
+
_has_reuse = bool(getattr(args, "reuse", False))
|
|
3926
|
+
_has_reuse_rationale = bool(getattr(args, "reuse_rationale", ""))
|
|
3905
3927
|
if (
|
|
3906
3928
|
args.rebaseline
|
|
3907
3929
|
or args.prepare_code
|
|
3908
3930
|
or args.accept_existing_code
|
|
3909
3931
|
or args.accept_existing_test_code
|
|
3910
3932
|
) and (
|
|
3911
|
-
args.skip or args.discuss_done or args.confirmed
|
|
3933
|
+
args.skip or _has_reuse or args.discuss_done or args.confirmed
|
|
3912
3934
|
):
|
|
3913
3935
|
print(
|
|
3914
3936
|
"错误:--rebaseline、--prepare-code、--accept-existing-code 和 "
|
|
@@ -3916,12 +3938,129 @@ def cmd_gate(args) -> None:
|
|
|
3916
3938
|
)
|
|
3917
3939
|
sys.exit(1)
|
|
3918
3940
|
|
|
3941
|
+
if args.skip and _has_reuse:
|
|
3942
|
+
print("错误:--skip 和 --reuse 互斥;skip 表示本轮不做穿刺,reuse 表示复用既有穿刺结论")
|
|
3943
|
+
sys.exit(1)
|
|
3944
|
+
|
|
3945
|
+
# ── 特殊:--reuse(仅 spike,验证技术不确定性 R30/R31)──
|
|
3946
|
+
if _has_reuse:
|
|
3947
|
+
if stage_name != "spike":
|
|
3948
|
+
print(f"错误:--reuse 仅适用于 spike stage,不适用于 {stage_name}")
|
|
3949
|
+
sys.exit(1)
|
|
3950
|
+
current_assets = [
|
|
3951
|
+
asset
|
|
3952
|
+
for asset in wf_state.spike_assets
|
|
3953
|
+
if asset.workflow_id == wf_state.workflow_id
|
|
3954
|
+
]
|
|
3955
|
+
if not current_assets:
|
|
3956
|
+
print(
|
|
3957
|
+
"错误:本工作流没有已登记的穿刺资产,无内容可复用;"
|
|
3958
|
+
"没有新技术不确定性时请用 --skip"
|
|
3959
|
+
)
|
|
3960
|
+
sys.exit(1)
|
|
3961
|
+
rationale = (getattr(args, "reuse_rationale", "") or "").strip()
|
|
3962
|
+
if len(rationale) < 12 or rationale in {"无", "暂无", "待定", "待补充"}:
|
|
3963
|
+
print(
|
|
3964
|
+
"错误:--reuse 必须用 --reuse-rationale 写明复用依据——"
|
|
3965
|
+
"每个被复用穿刺项的结论为什么在当前设计变更后仍然成立"
|
|
3966
|
+
"(不少于 12 个字符,不能写占位词)"
|
|
3967
|
+
)
|
|
3968
|
+
sys.exit(1)
|
|
3969
|
+
_clear_pending_spike_assets(wf_state)
|
|
3970
|
+
try:
|
|
3971
|
+
cleanup_plan = plan_spike_tmp_cleanup(project_root, wf_state)
|
|
3972
|
+
cleaned_paths = clean_spike_tmp(
|
|
3973
|
+
project_root,
|
|
3974
|
+
wf_state,
|
|
3975
|
+
cleanup_plan=cleanup_plan,
|
|
3976
|
+
)
|
|
3977
|
+
except (OSError, ValueError) as exc:
|
|
3978
|
+
_print_gate_failure(
|
|
3979
|
+
stage_name="spike",
|
|
3980
|
+
gate_name="复用前临时内容清理",
|
|
3981
|
+
details=exc,
|
|
3982
|
+
command="workflow gate spike --reuse --reuse-rationale <复用依据>",
|
|
3983
|
+
side_effects="只删除当前工作流未登记的穿刺半成品,保留已登记资产和其它工作流目录",
|
|
3984
|
+
success_condition="定向清理完成且没有触碰已登记或历史穿刺资产",
|
|
3985
|
+
)
|
|
3986
|
+
return
|
|
3987
|
+
# 复用路径:跳过新穿刺但保留既有结论和资产的追踪表引用(R30/R31)。
|
|
3988
|
+
wf_state.spike_reused = True
|
|
3989
|
+
wf_state.spike_skipped = False
|
|
3990
|
+
wf_state.meta["spike_reuse_rationales"] = [
|
|
3991
|
+
{
|
|
3992
|
+
"asset_count": len(current_assets),
|
|
3993
|
+
"rationale": rationale,
|
|
3994
|
+
"recorded_at": state_mod.now_iso(),
|
|
3995
|
+
}
|
|
3996
|
+
]
|
|
3997
|
+
# 退回重走路径:待复核文本在有资产引用时不动,无引用行回补为复用文本。
|
|
3998
|
+
try:
|
|
3999
|
+
traceability_mod.resolve_spike_recheck_for_skip(
|
|
4000
|
+
project_root, wf_state.workflow_id
|
|
4001
|
+
)
|
|
4002
|
+
except ValueError:
|
|
4003
|
+
pass
|
|
4004
|
+
wf_state.stages[stage_name].gate.discussion_complete = True
|
|
4005
|
+
wf_state.stages[stage_name].gate.code_validated = True
|
|
4006
|
+
wf_state.stages[stage_name].gate.user_confirmed = True
|
|
4007
|
+
wf_state.stages[stage_name].status = "done"
|
|
4008
|
+
stage_names = list(wf_state.stages.keys())
|
|
4009
|
+
current_idx = stage_names.index(stage_name)
|
|
4010
|
+
if current_idx + 1 < len(stage_names):
|
|
4011
|
+
next_stage = stage_names[current_idx + 1]
|
|
4012
|
+
wf_state.current_stage = next_stage
|
|
4013
|
+
wf_state.stages[next_stage].status = "in_progress"
|
|
4014
|
+
state_mod.save_state(project_root, wf_state)
|
|
4015
|
+
journal_mod.append_entry(
|
|
4016
|
+
project_root,
|
|
4017
|
+
"spike 复用",
|
|
4018
|
+
"workflow.py",
|
|
4019
|
+
workflow_id=wf_state.workflow_id,
|
|
4020
|
+
reused_assets=[asset.relative_path for asset in current_assets],
|
|
4021
|
+
rationale=rationale,
|
|
4022
|
+
preserved_paths=cleanup_plan["preserved"],
|
|
4023
|
+
cleaned_paths=cleaned_paths,
|
|
4024
|
+
)
|
|
4025
|
+
journal_mod.append_entry(project_root, "阶段推进", "workflow.py",
|
|
4026
|
+
from_=stage_name, to=wf_state.current_stage)
|
|
4027
|
+
print(f"═══ {stage_name} 复用既有穿刺 ═══")
|
|
4028
|
+
print(f"复用的穿刺资产: {[asset.relative_path for asset in current_assets]}")
|
|
4029
|
+
print(f"复用依据: {rationale}")
|
|
4030
|
+
_print_spike_cleanup_plan(cleanup_plan)
|
|
4031
|
+
print(f"进入 {wf_state.current_stage}")
|
|
4032
|
+
print_next_step(f"调 `workflow discuss` 加载 {wf_state.current_stage} stage 提示词")
|
|
4033
|
+
return
|
|
4034
|
+
|
|
3919
4035
|
# ── 特殊:--skip(仅 spike)──
|
|
3920
4036
|
if args.skip:
|
|
3921
4037
|
# --skip 只适用于 spike stage
|
|
3922
4038
|
if stage_name != "spike":
|
|
3923
4039
|
print(f"错误:--skip 仅适用于 spike stage,不适用于 {stage_name}")
|
|
3924
4040
|
sys.exit(1)
|
|
4041
|
+
# 验证技术不确定性 R30:本工作流已有登记资产时先警告后果,让人在
|
|
4042
|
+
# 犯错前看到选择;确认后才继续。
|
|
4043
|
+
current_assets = [
|
|
4044
|
+
asset
|
|
4045
|
+
for asset in wf_state.spike_assets
|
|
4046
|
+
if asset.workflow_id == wf_state.workflow_id
|
|
4047
|
+
]
|
|
4048
|
+
if current_assets:
|
|
4049
|
+
print("⚠️ 警告:本工作流已登记 "
|
|
4050
|
+
f"{len(current_assets)} 个穿刺资产:")
|
|
4051
|
+
for asset in current_assets:
|
|
4052
|
+
print(f" - {asset.relative_path}")
|
|
4053
|
+
print("执行 --skip 后,验收计划将无法引用这些资产(追踪表校验会拒绝"
|
|
4054
|
+
"本轮资产引用);如需保留并复用它们,请改用:")
|
|
4055
|
+
print(" workflow gate spike --reuse --reuse-rationale <每个被复用结论为何仍成立>")
|
|
4056
|
+
print("确认要放弃引用继续 --skip 时,请再次执行本命令(用户已知情)。")
|
|
4057
|
+
# R30:警告先于跳过动作出现——第一次执行只警告并退出;用户知情后
|
|
4058
|
+
# 再次执行同一命令才真正跳过。
|
|
4059
|
+
already_warned = wf_state.meta.get("spike_skip_warned_at")
|
|
4060
|
+
if not already_warned:
|
|
4061
|
+
wf_state.meta["spike_skip_warned_at"] = state_mod.now_iso()
|
|
4062
|
+
state_mod.save_state(project_root, wf_state)
|
|
4063
|
+
sys.exit(1)
|
|
3925
4064
|
_clear_pending_spike_assets(wf_state)
|
|
3926
4065
|
try:
|
|
3927
4066
|
cleanup_plan = plan_spike_tmp_cleanup(project_root, wf_state)
|
|
@@ -3943,6 +4082,8 @@ def cmd_gate(args) -> None:
|
|
|
3943
4082
|
|
|
3944
4083
|
# 清理成功后再标记 spike 跳过并推进,失败时始终停留在当前阶段。
|
|
3945
4084
|
wf_state.spike_skipped = True
|
|
4085
|
+
# 确认放弃后清除警告标记,避免污染后续轮次判断。
|
|
4086
|
+
wf_state.meta.pop("spike_skip_warned_at", None)
|
|
3946
4087
|
# 退回重走路径:回溯重置过的穿刺列停在待复核文本,本轮确认跳过后回补为跳过文本。
|
|
3947
4088
|
# 首轮 spike 早于 acceptance_plan、追踪表还不存在时静默跳过回补。
|
|
3948
4089
|
try:
|
|
@@ -5675,6 +5816,12 @@ def cmd_abort(args) -> None:
|
|
|
5675
5816
|
print(f" - {item.get('path')}:{action}")
|
|
5676
5817
|
elif item.get("kind") == "project_fields":
|
|
5677
5818
|
print(" - .workflow_loop/project.json:恢复本轮受管项目字段")
|
|
5819
|
+
no_copy_paths = abort_manifest.get("no_copy_paths")
|
|
5820
|
+
if isinstance(no_copy_paths, list) and no_copy_paths:
|
|
5821
|
+
# 整轮作废并恢复 R22:无副本的删除文件保持删除,不阻塞作废。
|
|
5822
|
+
print("无副本的删除文件(实施前内容无法取得,作废时保持当前删除状态):")
|
|
5823
|
+
for entry in no_copy_paths:
|
|
5824
|
+
print(f" - {entry}")
|
|
5678
5825
|
print("清单外文件不会读取、恢复或删除。")
|
|
5679
5826
|
if isinstance(abort_spike_plan, dict):
|
|
5680
5827
|
_print_spike_cleanup_plan(abort_spike_plan)
|
|
@@ -6324,7 +6471,13 @@ def main() -> None:
|
|
|
6324
6471
|
help="第三道门:记录用户看过当前结果并同意,随后进入下一环节")
|
|
6325
6472
|
# --skip:跳过 stage(仅 spike)
|
|
6326
6473
|
gate_parser.add_argument("--skip", action="store_true",
|
|
6327
|
-
help="跳过 stage(仅 spike
|
|
6474
|
+
help="跳过 stage(仅 spike;本工作流已登记穿刺资产时先警告后果)")
|
|
6475
|
+
# --reuse:跳过新穿刺但复用本工作流已登记的穿刺结论和资产(仅 spike)
|
|
6476
|
+
gate_parser.add_argument("--reuse", action="store_true",
|
|
6477
|
+
help="跳过新穿刺,保留并复用本工作流已登记的穿刺结论和资产(仅 spike)")
|
|
6478
|
+
# --reuse-rationale:复用依据(--reuse 时必填,说明每个被复用结论为何仍成立)
|
|
6479
|
+
gate_parser.add_argument("--reuse-rationale", default="",
|
|
6480
|
+
help="复用依据:--reuse 时必填,写清每个被复用穿刺项的结论为什么在当前设计下仍然成立")
|
|
6328
6481
|
# --rebaseline:用户确认当前代码作为 impl 的新实施前基线
|
|
6329
6482
|
gate_parser.add_argument("--rebaseline", action="store_true",
|
|
6330
6483
|
help="重设 impl 实施前代码基线(仅用户确认后使用)")
|
|
@@ -34,6 +34,7 @@ MANAGED_PROJECT_FIELDS = (
|
|
|
34
34
|
"test_entry",
|
|
35
35
|
"test_parallelism",
|
|
36
36
|
"artifact_file_keys",
|
|
37
|
+
"extra_code_suffixes",
|
|
37
38
|
)
|
|
38
39
|
|
|
39
40
|
|
|
@@ -62,6 +63,10 @@ class ProjectState:
|
|
|
62
63
|
# 显示名称到稳定中文文件标识的项目级映射,按 feature/topic/spike/bug 分组。
|
|
63
64
|
# 显示名称仍写入正文;文件标识只进入文件名。跨轮次保存,不随新 Run 覆盖。
|
|
64
65
|
artifact_file_keys: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
66
|
+
# 项目级扩展代码后缀:项目主要产物不是常规代码格式(例如纯 HTML 站点)时,
|
|
67
|
+
# 维护者登记这些后缀,实际改动判定把它们当代码文件识别(实施并保护项目修改 R34)。
|
|
68
|
+
# 存[".html", ".htm"]这类小写后缀列表;空列表表示不扩展。
|
|
69
|
+
extra_code_suffixes: list[str] = field(default_factory=list)
|
|
65
70
|
|
|
66
71
|
|
|
67
72
|
# 项目骨架检查结果:
|
|
@@ -124,6 +129,12 @@ def load_project(project_root: str) -> ProjectState | None:
|
|
|
124
129
|
for category, mapping in raw_keys.items()
|
|
125
130
|
if isinstance(mapping, dict)
|
|
126
131
|
}
|
|
132
|
+
raw_suffixes = data.get("extra_code_suffixes", [])
|
|
133
|
+
extra_code_suffixes = [
|
|
134
|
+
suffix.strip().lower()
|
|
135
|
+
for suffix in raw_suffixes
|
|
136
|
+
if isinstance(suffix, str) and suffix.strip()
|
|
137
|
+
]
|
|
127
138
|
return ProjectState(
|
|
128
139
|
installer_version=data.get("installer_version", ""),
|
|
129
140
|
installed_at=data.get("installed_at", ""),
|
|
@@ -132,6 +143,7 @@ def load_project(project_root: str) -> ProjectState | None:
|
|
|
132
143
|
test_entry=data.get("test_entry", DEFAULT_TEST_ENTRY),
|
|
133
144
|
test_parallelism=max(1, int(data.get("test_parallelism", DEFAULT_TEST_PARALLELISM))),
|
|
134
145
|
artifact_file_keys=artifact_file_keys,
|
|
146
|
+
extra_code_suffixes=extra_code_suffixes,
|
|
135
147
|
)
|
|
136
148
|
|
|
137
149
|
|
|
@@ -179,9 +191,17 @@ def save_project(project_root: str, project: ProjectState) -> None:
|
|
|
179
191
|
def _validate_managed_fields(fields: dict) -> None:
|
|
180
192
|
if not isinstance(fields, dict):
|
|
181
193
|
raise ValueError("项目受管字段快照必须是 JSON 对象")
|
|
182
|
-
|
|
194
|
+
# extra_code_suffixes 是后加字段:旧轮次的作废清单快照没有它,恢复旧快照
|
|
195
|
+
# 时按空列表处理,不因缺新字段拒绝历史恢复。
|
|
196
|
+
legacy_tolerated = {"extra_code_suffixes"}
|
|
197
|
+
missing = [
|
|
198
|
+
name
|
|
199
|
+
for name in MANAGED_PROJECT_FIELDS
|
|
200
|
+
if name not in fields and name not in legacy_tolerated
|
|
201
|
+
]
|
|
183
202
|
if missing:
|
|
184
203
|
raise ValueError(f"项目受管字段快照缺少字段: {missing}")
|
|
204
|
+
fields = {**{name: [] for name in legacy_tolerated if name not in fields}, **fields}
|
|
185
205
|
if not isinstance(fields["project_design_initialized"], bool):
|
|
186
206
|
raise ValueError("project_design_initialized(项目设计已初始化)必须是布尔值")
|
|
187
207
|
if (
|
|
@@ -203,6 +223,14 @@ def _validate_managed_fields(fields: dict) -> None:
|
|
|
203
223
|
for category, mapping in mappings.items():
|
|
204
224
|
if not isinstance(category, str) or not isinstance(mapping, dict):
|
|
205
225
|
raise ValueError("正式文件标识映射的分类和值必须是对象")
|
|
226
|
+
if (
|
|
227
|
+
not isinstance(fields["extra_code_suffixes"], list)
|
|
228
|
+
or not all(
|
|
229
|
+
isinstance(item, str) and item.startswith(".") and item.strip(".")
|
|
230
|
+
for item in fields["extra_code_suffixes"]
|
|
231
|
+
)
|
|
232
|
+
):
|
|
233
|
+
raise ValueError("extra_code_suffixes(项目扩展代码后缀)必须是以点开头的字符串数组")
|
|
206
234
|
if not all(
|
|
207
235
|
isinstance(display_name, str) and isinstance(file_key, str)
|
|
208
236
|
for display_name, file_key in mapping.items()
|
|
@@ -221,6 +249,7 @@ def snapshot_managed_fields(project_root: str) -> dict:
|
|
|
221
249
|
"test_entry": copy.deepcopy(project.test_entry),
|
|
222
250
|
"test_parallelism": project.test_parallelism,
|
|
223
251
|
"artifact_file_keys": copy.deepcopy(project.artifact_file_keys),
|
|
252
|
+
"extra_code_suffixes": list(project.extra_code_suffixes),
|
|
224
253
|
}
|
|
225
254
|
_validate_managed_fields(fields)
|
|
226
255
|
return fields
|
|
@@ -241,7 +270,7 @@ def restore_managed_fields(project_root: str, fields: dict) -> None:
|
|
|
241
270
|
raise ValueError(f"{PROJECT_FILE} 必须是 JSON 对象")
|
|
242
271
|
|
|
243
272
|
for name in MANAGED_PROJECT_FIELDS:
|
|
244
|
-
data[name] = copy.deepcopy(fields[name
|
|
273
|
+
data[name] = copy.deepcopy(fields.get(name, [] if name == "extra_code_suffixes" else None))
|
|
245
274
|
_atomic_write_json(path, data)
|
|
246
275
|
if snapshot_managed_fields(project_root) != fields:
|
|
247
276
|
raise ValueError("项目受管字段写回后复核不一致")
|