workflow-loop 0.3.8__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.8/src/workflow_loop.egg-info → workflow_loop-0.4.0}/PKG-INFO +9 -9
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/README.md +8 -8
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/pyproject.toml +1 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/__init__.py +2 -2
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/artifact_validation.py +152 -5
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/bug_record.py +59 -15
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/cli.py +341 -40
- workflow_loop-0.4.0/src/workflow_loop/code_symbols.py +145 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/code_design/update_code_design.md +9 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test.md +3 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test_code.md +1 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test_code_implementation.md +1 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/qa/test_plan.md +1 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/reproduce/reproduce.md +7 -3
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/code_design/code_design.md +2 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/reproduce/reproduce.md +8 -1
- workflow_loop-0.4.0/src/workflow_loop/design_sync.py +209 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/journal.py +20 -0
- workflow_loop-0.4.0/src/workflow_loop/machine_collect.py +641 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/markdown_links.py +176 -40
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/project.py +31 -2
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/records.py +1079 -130
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/rollback.py +155 -15
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/stages/stages.py +47 -1
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/state.py +10 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/test_execution.py +14 -2
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/test_mapping.py +39 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/test_runner.py +48 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/topic.py +6 -8
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/traceability.py +92 -8
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/verification.py +58 -3
- {workflow_loop-0.3.8 → workflow_loop-0.4.0/src/workflow_loop.egg-info}/PKG-INFO +9 -9
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/SOURCES.txt +21 -1
- workflow_loop-0.4.0/tests/test_design_sync_tables.py +215 -0
- workflow_loop-0.4.0/tests/test_empty_result_table_guard.py +71 -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_self_heal_output.py +224 -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_git_baseline_exit.py +171 -0
- workflow_loop-0.4.0/tests/test_machine_collect.py +437 -0
- workflow_loop-0.4.0/tests/test_machine_collect_extension.py +234 -0
- workflow_loop-0.4.0/tests/test_mixed_topic_test_result.py +353 -0
- workflow_loop-0.4.0/tests/test_multibug_workflow.py +269 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_records.py +24 -10
- workflow_loop-0.4.0/tests/test_recovery_journal_boundary.py +136 -0
- workflow_loop-0.4.0/tests/test_registration_guidance.py +164 -0
- workflow_loop-0.4.0/tests/test_regression_skip.py +189 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_rollback.py +2 -1
- workflow_loop-0.4.0/tests/test_version_document_sources.py +70 -0
- workflow_loop-0.4.0/tests/test_workflow_guidance.py +588 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/LICENSE +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/setup.cfg +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/acceptance_records.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/artifact_paths.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance_plan.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/code_design/code_design.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/code_design/project_design_init.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/global/document_writing.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/global/workflow_lifecycle.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/impl/code_implementation.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/impl/impl.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/spec/spec.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Standardized_Repository/spike/spike.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_plan.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_result.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/code_design/project_design_init_evidence.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/impl/impl.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/qa/test.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/qa/test_plan.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/spec/spec.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/data/Template_Repository/spike/spike.md +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/diagnostics.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/installer.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/path_composer.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/process_runner.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/role_doc.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/snapshots.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/spike_reuse.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/spike_validation.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/stage_materials.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/stages/__init__.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/stages/base.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/test_entry.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/test_report.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop/topic_relations.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/dependency_links.txt +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/entry_points.txt +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/requires.txt +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/src/workflow_loop.egg-info/top_level.txt +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_acceptance_records.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_architecture_validation.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_artifact_paths.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_bug_record.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_bug_validation.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_commands.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_current_workflow_contracts.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_current_workflow_qa.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_diagnostics.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_gate_plan_path_diagnostics.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_gate_rework_stability.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_installer.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_light_task.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_maintenance.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_maintenance_scripts.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_markdown_links.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_path_composer.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_process_runner.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_project.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_project_design_init.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_public_project.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_release_script.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_release_workflow.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_return_rework_flow.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_snapshots.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_spike_assets.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_spike_validation.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_stage_materials.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_stages.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_state.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_table_entry_registration.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_test_entry.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_test_execution.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_test_mapping.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_test_report.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_test_runner.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_topic_relations.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_traceability.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_verification.py +0 -0
- {workflow_loop-0.3.8 → workflow_loop-0.4.0}/tests/test_workflow_acceptance.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__}"
|
|
@@ -614,6 +614,10 @@ def _required_final_machine_record_ids(wf_state) -> tuple[set[str] | None, str]:
|
|
|
614
614
|
required.update(record.test_record_ids)
|
|
615
615
|
|
|
616
616
|
regression = wf_state.regression_test
|
|
617
|
+
if regression.status == "skipped-by-no-change":
|
|
618
|
+
# R9/R9a:无代码改动跳过的轮次没有机器执行记录;设计同步照常,
|
|
619
|
+
# 结论按真实差异为无需修改,不要求不存在的 REG 编号。
|
|
620
|
+
return required, ""
|
|
617
621
|
if regression.status != "passed" or not regression.record_id:
|
|
618
622
|
return None, "最终全量回归没有当前有效的机器记录编号"
|
|
619
623
|
required.add(regression.record_id)
|
|
@@ -641,6 +645,11 @@ def changed_stage_paths(
|
|
|
641
645
|
启用工作记录表的轮次里正式文档是程序按表的确定性输出:表没有变化时文档必然
|
|
642
646
|
逐字节相同,"文档没变"不等于"本阶段没有产出"。这种情况下按表判断产出,并把
|
|
643
647
|
本阶段登记的产物路径交给下游检查,不要求 AI 制造无实质内容的修改。
|
|
648
|
+
|
|
649
|
+
退回核对场景(推进 R47):变化清单为空、且当前环节任一主题表填有有效的
|
|
650
|
+
"核对结论:无需修改"声明(核对对象和核对依据都有实质内容)时,返回通过并
|
|
651
|
+
标注声明放行——产物确实不用改也是合法完成状态。产物有实际变化时声明不参与
|
|
652
|
+
判定,按普通流程走。
|
|
644
653
|
"""
|
|
645
654
|
state = load_state(project_root)
|
|
646
655
|
if state is None or stage_name not in state.stages:
|
|
@@ -667,15 +676,90 @@ def changed_stage_paths(
|
|
|
667
676
|
# 变化清单如实为空:确实没有文件变化。调用方要判断“本阶段的产出是哪些”时
|
|
668
677
|
# 按内容识别,不能把未变化的文件当成本阶段改过——最终设计同步正是用这份
|
|
669
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
|
+
)
|
|
670
687
|
return (
|
|
671
688
|
True,
|
|
672
689
|
"表模式:正式文档由程序按工作记录表生成,内容与上次相同时按表判断产出",
|
|
673
690
|
[],
|
|
674
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
|
+
)
|
|
675
701
|
return (False, "相关文件与讨论完成时相同,不能证明本阶段已经生成或修改产物", [])
|
|
676
702
|
return (True, f"本阶段发生变化的文件: {changed}", changed)
|
|
677
703
|
|
|
678
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
|
+
|
|
679
763
|
def validate_project_design_init_evidence(
|
|
680
764
|
project_root: str,
|
|
681
765
|
workflow_id: str,
|
|
@@ -1263,6 +1347,8 @@ def validate_project_design_feature_consistency(project_root: str) -> tuple[bool
|
|
|
1263
1347
|
def validate_final_code_design_document(
|
|
1264
1348
|
project_root: str,
|
|
1265
1349
|
workflow_id: str,
|
|
1350
|
+
*,
|
|
1351
|
+
candidate_content: str | None = None,
|
|
1266
1352
|
) -> tuple[bool, str]:
|
|
1267
1353
|
"""校验最终架构文档已经完成产品、功能、架构和真实代码映射。
|
|
1268
1354
|
|
|
@@ -1272,8 +1358,8 @@ def validate_final_code_design_document(
|
|
|
1272
1358
|
relative_path = artifact_paths_mod.CODE_DESIGN_DOC
|
|
1273
1359
|
full_path = os.path.join(project_root, relative_path)
|
|
1274
1360
|
errors: list[str] = []
|
|
1275
|
-
architecture_exists = os.path.isfile(full_path)
|
|
1276
|
-
content = _read_text(project_root, relative_path) if architecture_exists else ""
|
|
1361
|
+
architecture_exists = candidate_content is not None or os.path.isfile(full_path)
|
|
1362
|
+
content = candidate_content if candidate_content is not None else (_read_text(project_root, relative_path) if architecture_exists else "")
|
|
1277
1363
|
if not architecture_exists:
|
|
1278
1364
|
errors.append(f"{relative_path} 不存在")
|
|
1279
1365
|
|
|
@@ -1438,7 +1524,9 @@ def validate_final_code_design_document(
|
|
|
1438
1524
|
extra_ids = sorted(actual_record_ids - required_record_ids)
|
|
1439
1525
|
errors.append(
|
|
1440
1526
|
f"{relative_path} 第 9 章“核对依据”字段必须精确列出当前机器记录编号;"
|
|
1441
|
-
f"
|
|
1527
|
+
f"当前缺少={missing_ids},不在当前有效集合中的编号={extra_ids}。"
|
|
1528
|
+
"测试或最终全量回归重跑会使旧编号失效;额外编号也可能是拼写或拼接错误,"
|
|
1529
|
+
"请从当前工作流状态重新取得有效编号集合,不要沿用历史编号"
|
|
1442
1530
|
)
|
|
1443
1531
|
|
|
1444
1532
|
if errors:
|
|
@@ -1517,6 +1605,27 @@ def validate_reproduce_documents(
|
|
|
1517
1605
|
]
|
|
1518
1606
|
|
|
1519
1607
|
topics: list[str] = []
|
|
1608
|
+
from . import records as records_mod
|
|
1609
|
+
|
|
1610
|
+
workflow = load_state(project_root)
|
|
1611
|
+
uses_tables = workflow is not None and records_mod.workflow_uses_tables(workflow, project_root)
|
|
1612
|
+
fact_tables = {}
|
|
1613
|
+
table_problems, tables = records_mod.bug_record_tables(
|
|
1614
|
+
project_root, workflow_id, list(workflow.topics) if uses_tables else None,
|
|
1615
|
+
)
|
|
1616
|
+
failures.extend(detail for _, detail in table_problems)
|
|
1617
|
+
for table_relative, table in tables:
|
|
1618
|
+
fact_tables[table["验收主题"]] = table
|
|
1619
|
+
failures.extend(
|
|
1620
|
+
f"{table_relative}:{detail}" for _category, detail in
|
|
1621
|
+
records_mod.validate_table("bug_record", table, project_root=project_root)
|
|
1622
|
+
)
|
|
1623
|
+
if uses_tables:
|
|
1624
|
+
changed_bug_docs = list(dict.fromkeys(changed_bug_docs + _current_workflow_bug_documents(project_root, workflow_id)))
|
|
1625
|
+
for table in fact_tables.values():
|
|
1626
|
+
expected = f"bug/缺陷_{records_mod.bug_file_key(project_root, table['验收主题'])}.md"
|
|
1627
|
+
if expected not in changed_bug_docs:
|
|
1628
|
+
failures.append(f"主题「{table['验收主题']}」缺少本轮正式缺陷记录 {expected}")
|
|
1520
1629
|
for rel_path in changed_bug_docs:
|
|
1521
1630
|
filename = os.path.basename(rel_path)
|
|
1522
1631
|
if not BUG_FILENAME_RE.match(filename):
|
|
@@ -1547,6 +1656,15 @@ def validate_reproduce_documents(
|
|
|
1547
1656
|
else:
|
|
1548
1657
|
topics.append(topic or "")
|
|
1549
1658
|
|
|
1659
|
+
fact_table = fact_tables.get(topic)
|
|
1660
|
+
if uses_tables and fact_table is None:
|
|
1661
|
+
failures.append(f"{filename} 的主题「{topic}」缺少对应的缺陷记录工作记录表")
|
|
1662
|
+
if fact_table is not None and fact_table.get("表版本") == "3":
|
|
1663
|
+
for heading in required_sections:
|
|
1664
|
+
if _section(content, heading) is None:
|
|
1665
|
+
failures.append(f"{filename} 缺少“{heading}”章节")
|
|
1666
|
+
continue
|
|
1667
|
+
|
|
1550
1668
|
for heading in required_sections:
|
|
1551
1669
|
allow_none = heading.startswith("7.")
|
|
1552
1670
|
section_text = _section(content, heading)
|
|
@@ -2431,9 +2549,33 @@ def _validate_topic_test_execution_result(
|
|
|
2431
2549
|
failures.append(f"{rel_path} 有人工验收内容,但缺少具体人工验收交接")
|
|
2432
2550
|
|
|
2433
2551
|
sections = _test_result_sections(content)
|
|
2434
|
-
|
|
2435
|
-
|
|
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:
|
|
2436
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
|
+
)
|
|
2437
2579
|
|
|
2438
2580
|
for item in items:
|
|
2439
2581
|
task = tasks.get(item.test_id)
|
|
@@ -2646,6 +2788,11 @@ def validate_final_regression_state(
|
|
|
2646
2788
|
return (False, "找不到当前工作流状态,不能确认最终全量回归")
|
|
2647
2789
|
result = state.regression_test
|
|
2648
2790
|
failures: list[str] = []
|
|
2791
|
+
if result.status == "skipped-by-no-change":
|
|
2792
|
+
# R9:无代码改动跳过的轮次按判定依据核对,不要求不存在的执行事实
|
|
2793
|
+
if not result.skip_reason or not result.skipped_at:
|
|
2794
|
+
return (False, "最终全量回归标记为无代码改动跳过,但缺少跳过依据或判定时间")
|
|
2795
|
+
return (True, f"最终全量回归按无代码改动跳过(依据已记录:{result.skipped_at})")
|
|
2649
2796
|
if result.status == "unavailable":
|
|
2650
2797
|
failures.append("最终全量回归明确失败:当前平台没有可执行的项目全量测试入口")
|
|
2651
2798
|
elif result.status != "passed":
|
|
@@ -4,6 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from urllib.parse import unquote
|
|
8
|
+
|
|
9
|
+
from markdown_it import MarkdownIt
|
|
10
|
+
from markdown_it.common.utils import unescapeAll
|
|
7
11
|
|
|
8
12
|
from . import artifact_paths as artifact_paths_mod
|
|
9
13
|
from .state import load_state
|
|
@@ -77,28 +81,68 @@ def _replace_result_update(content: str, stage_label: str, workflow_id: str, bod
|
|
|
77
81
|
return content[: section_match.start(1)] + section + content[section_match.end(1) :]
|
|
78
82
|
|
|
79
83
|
|
|
84
|
+
def index_entry(content: str, filename: str) -> tuple[tuple[int, int], list[str]] | None:
|
|
85
|
+
"""定位一个缺陷索引行,返回原文行范围和单元格;重复入口直接拒绝。"""
|
|
86
|
+
found = []
|
|
87
|
+
row_range = None
|
|
88
|
+
cells: list[str] = []
|
|
89
|
+
matches = False
|
|
90
|
+
for token in MarkdownIt("commonmark").enable("table").parse(content):
|
|
91
|
+
if token.type == "tr_open":
|
|
92
|
+
row_range, cells, matches = token.map, [], False
|
|
93
|
+
elif token.type == "inline" and row_range is not None:
|
|
94
|
+
if not cells:
|
|
95
|
+
matches = any(
|
|
96
|
+
child.type == "link_open"
|
|
97
|
+
and unquote(child.attrGet("href") or "") in {filename, f"./{filename}"}
|
|
98
|
+
for child in token.children or []
|
|
99
|
+
)
|
|
100
|
+
cells.append(unescapeAll(token.content))
|
|
101
|
+
elif token.type == "tr_close":
|
|
102
|
+
if matches:
|
|
103
|
+
if len(cells) < 2:
|
|
104
|
+
raise ValueError(f"缺陷索引中 {filename} 的条目缺少入口或状态列")
|
|
105
|
+
found.append((tuple(row_range), cells))
|
|
106
|
+
row_range = None
|
|
107
|
+
if len(found) > 1:
|
|
108
|
+
raise ValueError(f"缺陷索引中 {filename} 有重复入口,不能确定应更新哪一行")
|
|
109
|
+
return found[0] if found else None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def replace_index_entry(content: str, entry: tuple[int, int], cells: list[str]) -> str:
|
|
113
|
+
lines = content.splitlines(keepends=True)
|
|
114
|
+
rendered = [cell.replace("\\", "\\\\").replace("|", "\\|").replace("\n", "<br>") for cell in cells]
|
|
115
|
+
lines[entry[0]:entry[1]] = ["| " + " | ".join(rendered) + " |\n"]
|
|
116
|
+
return "".join(lines)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def index_width(content: str) -> int:
|
|
120
|
+
"""旧索引可只有入口和状态两列;新增条目沿用其表格结构。"""
|
|
121
|
+
width = 0
|
|
122
|
+
for token in MarkdownIt("commonmark").enable("table").parse(content):
|
|
123
|
+
if token.type == "th_open":
|
|
124
|
+
width += 1
|
|
125
|
+
elif token.type == "thead_close":
|
|
126
|
+
if width not in {2, 4}:
|
|
127
|
+
raise ValueError("缺陷索引必须使用旧版两列或当前四列表格,不能覆盖未知表格结构")
|
|
128
|
+
return width
|
|
129
|
+
if content.strip():
|
|
130
|
+
raise ValueError("缺陷索引没有可解析的表格,保留原内容")
|
|
131
|
+
return 4
|
|
132
|
+
|
|
133
|
+
|
|
80
134
|
def _update_index_status(project_root: str, filename: str, status: str) -> None:
|
|
81
135
|
index_path = Path(project_root) / BUG_INDEX
|
|
82
136
|
if not index_path.is_file():
|
|
83
137
|
raise ValueError(f"{BUG_INDEX} 不存在,无法更新缺陷索引")
|
|
84
138
|
|
|
85
139
|
content = index_path.read_text(encoding="utf-8")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
found = False
|
|
89
|
-
for index, line in enumerate(lines):
|
|
90
|
-
if not any(marker in line for marker in markers) or not line.strip().startswith("|"):
|
|
91
|
-
continue
|
|
92
|
-
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
|
93
|
-
if len(cells) < 2:
|
|
94
|
-
continue
|
|
95
|
-
cells[-1] = status
|
|
96
|
-
lines[index] = "| " + " | ".join(cells) + " |"
|
|
97
|
-
found = True
|
|
98
|
-
break
|
|
99
|
-
if not found:
|
|
140
|
+
entry = index_entry(content, filename)
|
|
141
|
+
if entry is None:
|
|
100
142
|
raise ValueError(f"{BUG_INDEX} 没有链接缺陷记录: {filename}")
|
|
101
|
-
|
|
143
|
+
row_range, cells = entry
|
|
144
|
+
cells[-1] = status
|
|
145
|
+
index_path.write_text(replace_index_entry(content, row_range, cells), encoding="utf-8")
|
|
102
146
|
|
|
103
147
|
|
|
104
148
|
def update_status(
|