workflow-loop 0.3.6__tar.gz → 0.3.7__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.6/src/workflow_loop.egg-info → workflow_loop-0.3.7}/PKG-INFO +9 -9
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/README.md +8 -8
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/pyproject.toml +1 -1
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/__init__.py +2 -2
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/cli.py +38 -3
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/records.py +24 -5
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/rollback.py +16 -1
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/stages/stages.py +11 -4
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/topic.py +22 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/traceability.py +205 -28
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/verification.py +7 -3
- {workflow_loop-0.3.6 → workflow_loop-0.3.7/src/workflow_loop.egg-info}/PKG-INFO +9 -9
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop.egg-info/SOURCES.txt +1 -0
- workflow_loop-0.3.7/tests/test_return_rework_flow.py +924 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/LICENSE +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/setup.cfg +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/acceptance_records.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/artifact_paths.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/artifact_validation.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/bug_record.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance_plan.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/code_design/code_design.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/code_design/project_design_init.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/code_design/update_code_design.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/global/document_writing.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/global/workflow_lifecycle.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/impl/code_implementation.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/impl/impl.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/qa/test.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/qa/test_code.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/qa/test_code_implementation.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/qa/test_plan.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/reproduce/reproduce.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/spec/spec.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Standardized_Repository/spike/spike.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_plan.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_result.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/code_design/code_design.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/code_design/project_design_init_evidence.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/impl/impl.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/qa/test.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/qa/test_plan.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/reproduce/reproduce.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/spec/spec.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/data/Template_Repository/spike/spike.md +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/diagnostics.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/installer.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/journal.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/markdown_links.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/path_composer.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/process_runner.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/project.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/role_doc.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/snapshots.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/spike_reuse.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/spike_validation.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/stage_materials.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/stages/__init__.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/stages/base.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/state.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/test_entry.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/test_execution.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/test_mapping.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/test_report.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/test_runner.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop/topic_relations.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop.egg-info/dependency_links.txt +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop.egg-info/entry_points.txt +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop.egg-info/requires.txt +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/src/workflow_loop.egg-info/top_level.txt +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_acceptance_records.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_architecture_validation.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_artifact_paths.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_bug_record.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_bug_validation.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_commands.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_current_workflow_contracts.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_current_workflow_qa.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_diagnostics.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_installer.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_light_task.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_maintenance.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_maintenance_scripts.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_markdown_links.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_path_composer.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_process_runner.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_project.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_project_design_init.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_public_project.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_records.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_release_script.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_release_workflow.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_rollback.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_snapshots.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_spike_assets.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_spike_validation.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_stage_materials.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_stages.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_state.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_test_entry.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_test_execution.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_test_mapping.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_test_report.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_test_runner.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_topic_relations.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_traceability.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/tests/test_verification.py +0 -0
- {workflow_loop-0.3.6 → workflow_loop-0.3.7}/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.
|
|
3
|
+
Version: 0.3.7
|
|
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.3.
|
|
98
|
+
## 安装 0.3.7
|
|
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.3.
|
|
105
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.7/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.3.
|
|
111
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.7/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.3.
|
|
117
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.3.7/install.ps1 | iex"
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
安装命令固定读取 `v0.3.
|
|
120
|
+
安装命令固定读取 `v0.3.7` 正式发布中的脚本;不会跟随内容可能变化的 `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.3.
|
|
128
|
+
workflow update --version 0.3.7
|
|
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.3.
|
|
232
|
+
项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.3.7` 替换成要发布的新版本号:
|
|
233
233
|
|
|
234
234
|
```bash
|
|
235
|
-
uv run python scripts/release.py 0.3.
|
|
235
|
+
uv run python scripts/release.py 0.3.7
|
|
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.3.
|
|
79
|
+
## 安装 0.3.7
|
|
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.3.
|
|
86
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.7/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.3.
|
|
92
|
+
curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.7/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.3.
|
|
98
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.3.7/install.ps1 | iex"
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
安装命令固定读取 `v0.3.
|
|
101
|
+
安装命令固定读取 `v0.3.7` 正式发布中的脚本;不会跟随内容可能变化的 `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.3.
|
|
109
|
+
workflow update --version 0.3.7
|
|
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.3.
|
|
213
|
+
项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.3.7` 替换成要发布的新版本号:
|
|
214
214
|
|
|
215
215
|
```bash
|
|
216
|
-
uv run python scripts/release.py 0.3.
|
|
216
|
+
uv run python scripts/release.py 0.3.7
|
|
217
217
|
```
|
|
218
218
|
|
|
219
219
|
运行这条命令本身就表示维护者接受当前仓库状态并承担跳过本地发布检查的风险。脚本不会再次确认,也不会在本地运行测试、构建或远程版本查询。它依次执行:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 包版本号,用于 pip/uv 安装时版本匹配
|
|
2
|
-
__version__ = "0.3.
|
|
2
|
+
__version__ = "0.3.7"
|
|
3
3
|
# 产品身份标识:workflow --version 输出、安装脚本身份核对和安装事务校验共用同一组常量
|
|
4
|
-
# 本次发布版本为 0.3.
|
|
4
|
+
# 本次发布版本为 0.3.7;后续发布使用尚未被 PyPI 占用的新版本号
|
|
5
5
|
PRODUCT_NAME = "workflow-loop"
|
|
6
6
|
PRODUCT_IDENTITY = f"{PRODUCT_NAME} {__version__}"
|
|
@@ -461,7 +461,24 @@ def clear_completed_material_recovery(project_root: str, wf_state) -> bool:
|
|
|
461
461
|
|
|
462
462
|
|
|
463
463
|
def _freeze_impl_code_baseline(project_root: str, wf_state, stage_state) -> str:
|
|
464
|
-
"""同时冻结登记产品快照和不受计划范围收窄的完整实施快照。
|
|
464
|
+
"""同时冻结登记产品快照和不受计划范围收窄的完整实施快照。
|
|
465
|
+
|
|
466
|
+
同一 Run 退回后重进 impl 时继承首次入场基线(meta 的 impl_entry_baseline):
|
|
467
|
+
第一次实施产生的改动必须继续出现在「入场以来实际改动」里,不能因为
|
|
468
|
+
第二次入场重新冻结快照而被语义上抹平(BUG-04)。
|
|
469
|
+
"""
|
|
470
|
+
entry = wf_state.meta.get(rollback_mod.IMPL_ENTRY_BASELINE_META_KEY)
|
|
471
|
+
if (
|
|
472
|
+
isinstance(entry, dict)
|
|
473
|
+
and entry.get("workflow_id") == wf_state.workflow_id
|
|
474
|
+
and entry.get("code_baseline_hash")
|
|
475
|
+
and isinstance(entry.get("product_snapshot"), dict)
|
|
476
|
+
and isinstance(entry.get("complete_snapshot"), dict)
|
|
477
|
+
):
|
|
478
|
+
stage_state.code_baseline_hash = str(entry["code_baseline_hash"])
|
|
479
|
+
wf_state.meta[rollback_mod.IMPL_CODE_BASELINE_SNAPSHOT_KEY] = entry["product_snapshot"]
|
|
480
|
+
wf_state.meta[rollback_mod.IMPL_COMPLETE_BASELINE_SNAPSHOT_KEY] = entry["complete_snapshot"]
|
|
481
|
+
return stage_state.code_baseline_hash
|
|
465
482
|
snapshot = verification_mod.compute_registered_file_snapshot(
|
|
466
483
|
project_root,
|
|
467
484
|
scope="product",
|
|
@@ -474,6 +491,12 @@ def _freeze_impl_code_baseline(project_root: str, wf_state, stage_state) -> str:
|
|
|
474
491
|
stage_state.code_baseline_hash = code_hash
|
|
475
492
|
wf_state.meta[rollback_mod.IMPL_CODE_BASELINE_SNAPSHOT_KEY] = snapshot
|
|
476
493
|
wf_state.meta[rollback_mod.IMPL_COMPLETE_BASELINE_SNAPSHOT_KEY] = complete_snapshot
|
|
494
|
+
wf_state.meta[rollback_mod.IMPL_ENTRY_BASELINE_META_KEY] = {
|
|
495
|
+
"code_baseline_hash": code_hash,
|
|
496
|
+
"product_snapshot": snapshot,
|
|
497
|
+
"complete_snapshot": complete_snapshot,
|
|
498
|
+
"workflow_id": wf_state.workflow_id,
|
|
499
|
+
}
|
|
477
500
|
return code_hash
|
|
478
501
|
|
|
479
502
|
|
|
@@ -3799,6 +3822,14 @@ def cmd_gate(args) -> None:
|
|
|
3799
3822
|
|
|
3800
3823
|
# 清理成功后再标记 spike 跳过并推进,失败时始终停留在当前阶段。
|
|
3801
3824
|
wf_state.spike_skipped = True
|
|
3825
|
+
# 退回重走路径:回溯重置过的穿刺列停在待复核文本,本轮确认跳过后回补为跳过文本。
|
|
3826
|
+
# 首轮 spike 早于 acceptance_plan、追踪表还不存在时静默跳过回补。
|
|
3827
|
+
try:
|
|
3828
|
+
traceability_mod.resolve_spike_recheck_for_skip(
|
|
3829
|
+
project_root, wf_state.workflow_id
|
|
3830
|
+
)
|
|
3831
|
+
except ValueError:
|
|
3832
|
+
pass
|
|
3802
3833
|
# 绕过三道门(全设 True)
|
|
3803
3834
|
wf_state.stages[stage_name].gate.discussion_complete = True
|
|
3804
3835
|
wf_state.stages[stage_name].gate.code_validated = True
|
|
@@ -4617,8 +4648,12 @@ def cmd_gate(args) -> None:
|
|
|
4617
4648
|
print("错误:缺陷复现记录没有验收主题")
|
|
4618
4649
|
print_next_step("在 bug/<缺陷记录>.md 中补充验收主题后重新执行缺陷复现门禁")
|
|
4619
4650
|
return
|
|
4651
|
+
# 同一 Run 退回后重走 reproduce 确认时,本 Run 已登记的主题不再重复注册;
|
|
4652
|
+
# 与验收计划分支同一规则:只登记 state.topics 之外的新主题,跨 Run 重名仍拒绝。
|
|
4653
|
+
previous_topics = set(wf_state.topics or ([wf_state.topic] if wf_state.topic else []))
|
|
4654
|
+
new_topics = [topic for topic in topics if topic not in previous_topics]
|
|
4620
4655
|
try:
|
|
4621
|
-
project_mod.register_topics(project_root,
|
|
4656
|
+
project_mod.register_topics(project_root, new_topics)
|
|
4622
4657
|
except ValueError as exc:
|
|
4623
4658
|
print(f"错误:{exc}")
|
|
4624
4659
|
print_next_step("修改重复的主题名称后重新执行缺陷复现门禁")
|
|
@@ -5136,7 +5171,7 @@ def cmd_scaffold(args) -> None:
|
|
|
5136
5171
|
elif stage == wf_state.current_stage:
|
|
5137
5172
|
created = records_mod.ensure_stage_tables(project_root, wf_state)
|
|
5138
5173
|
else:
|
|
5139
|
-
topics = topic_mod.
|
|
5174
|
+
topics = topic_mod.acceptance_topics(project_root, wf_state.intent, stage)
|
|
5140
5175
|
created = []
|
|
5141
5176
|
for kind in records_mod.stage_table_kinds(stage):
|
|
5142
5177
|
if kind in records_mod.WORKFLOW_LEVEL_KINDS:
|
|
@@ -2085,13 +2085,13 @@ def _topic_relations_rows(project_root: str, workflow_id: str) -> list[dict]:
|
|
|
2085
2085
|
|
|
2086
2086
|
def ensure_stage_tables(project_root: str, wf_state: state_mod.WorkflowState) -> list[str]:
|
|
2087
2087
|
"""在环节加载材料时为当前环节生成缺失的工作记录表;返回创建的表路径。"""
|
|
2088
|
-
from .topic import
|
|
2088
|
+
from .topic import acceptance_topics
|
|
2089
2089
|
|
|
2090
2090
|
stage = wf_state.current_stage
|
|
2091
2091
|
kinds = stage_table_kinds(stage)
|
|
2092
2092
|
if not kinds:
|
|
2093
2093
|
return []
|
|
2094
|
-
topics =
|
|
2094
|
+
topics = acceptance_topics(project_root, wf_state.intent, stage, list(wf_state.topics))
|
|
2095
2095
|
created: list[str] = []
|
|
2096
2096
|
for kind in kinds:
|
|
2097
2097
|
if kind in {"acceptance_plan", "acceptance_result", "impl_record", "test_plan", "test_result"}:
|
|
@@ -2536,13 +2536,19 @@ def sync_stage_tables(
|
|
|
2536
2536
|
wf_state: state_mod.WorkflowState,
|
|
2537
2537
|
) -> tuple[list[tuple[str, str]], list[str]]:
|
|
2538
2538
|
"""第二道门前同步当前环节的工作记录表:校验、按表生成文档、检测手改。"""
|
|
2539
|
-
from .topic import
|
|
2539
|
+
from .topic import acceptance_topics
|
|
2540
2540
|
|
|
2541
2541
|
stage = wf_state.current_stage
|
|
2542
2542
|
kinds = stage_table_kinds(stage)
|
|
2543
2543
|
if not kinds:
|
|
2544
2544
|
return [], []
|
|
2545
|
-
|
|
2545
|
+
try:
|
|
2546
|
+
topics = acceptance_topics(
|
|
2547
|
+
project_root, wf_state.intent, stage, list(wf_state.topics)
|
|
2548
|
+
)
|
|
2549
|
+
except ValueError:
|
|
2550
|
+
# 索引格式错误由各阶段校验报告;此处回退 state 主题,不中断同步。
|
|
2551
|
+
topics = list(wf_state.topics) or []
|
|
2546
2552
|
if not topics:
|
|
2547
2553
|
# 主题尚未写入 state.topics 时,从 topic_relations 工作记录表读(断言三:表为唯一输入,不靠 state.topics)
|
|
2548
2554
|
_rel = table_relative_path(project_root, wf_state.workflow_id, "topic_relations", "")
|
|
@@ -2821,7 +2827,20 @@ def _refresh_stage_document(
|
|
|
2821
2827
|
if (current_hash is not None and table.get(DOC_HASH_KEY) is not None
|
|
2822
2828
|
and current_hash != table.get(DOC_HASH_KEY)):
|
|
2823
2829
|
return [] # 手改检测已在本环节报告,不重复处理
|
|
2824
|
-
|
|
2830
|
+
# 回补必须与主生成路径使用同一台机器事实来源:test_result 文档渲染依赖
|
|
2831
|
+
# qa 阶段 test_tasks,缺少 wf_state 时空任务集会覆盖已通过结果(BUG-09)。
|
|
2832
|
+
wf_state = None
|
|
2833
|
+
if kind == "test_result":
|
|
2834
|
+
wf_state = state_mod.load_state(project_root)
|
|
2835
|
+
if (
|
|
2836
|
+
wf_state is None
|
|
2837
|
+
or wf_state.workflow_id != workflow_id
|
|
2838
|
+
or wf_state.stages.get("qa") is None
|
|
2839
|
+
or not wf_state.stages["qa"].test_tasks.get(topic)
|
|
2840
|
+
):
|
|
2841
|
+
# 拿不到当前机器任务集时不重写,保留磁盘上已有的正式结果文档。
|
|
2842
|
+
return []
|
|
2843
|
+
content = generate_document(kind, table, project_root=project_root, wf_state=wf_state)
|
|
2825
2844
|
if current_hash is not None and content == _read_document(doc_full):
|
|
2826
2845
|
return [doc_relative]
|
|
2827
2846
|
_write_text(doc_full, content)
|
|
@@ -111,6 +111,15 @@ _RECORD_PLACEHOLDERS = {
|
|
|
111
111
|
}
|
|
112
112
|
IMPL_CODE_BASELINE_SNAPSHOT_KEY = "impl_code_baseline_snapshot"
|
|
113
113
|
IMPL_COMPLETE_BASELINE_SNAPSHOT_KEY = "impl_complete_baseline_snapshot"
|
|
114
|
+
# 同一 Run 退回后重进 impl 的入场锚点:首次冻结的基线在项目 meta 中永久保留,
|
|
115
|
+
# 第二次入场继承它,让第一次实施的改动始终处于“入场以来”的观察范围内。
|
|
116
|
+
IMPL_ENTRY_BASELINE_META_KEY = "impl_entry_baseline"
|
|
117
|
+
# 复用既有实现豁免:报错文案承诺“补充复用说明”即可,代码必须真正识别该说明。
|
|
118
|
+
# 要求出现“复用”和“既有实现/既有代码”两类词,避免单字“复用”误豁免无关记录。
|
|
119
|
+
_REUSE_EXEMPTION_RE = re.compile(
|
|
120
|
+
r"复用[^。\n]{0,20}(既有实现|既有代码|已有实现|已有代码)"
|
|
121
|
+
r"|(?:既有实现|既有代码|已有实现|已有代码)[^。\n]{0,20}复用"
|
|
122
|
+
)
|
|
114
123
|
IMPL_COMPLETE_INVENTORY_MANIFEST_KEY = "implementation_inventory_before"
|
|
115
124
|
_FINAL_LINE_RANGE_PATTERN = re.compile(
|
|
116
125
|
r"^L(?P<start>[1-9][0-9]*)[ \t]*-[ \t]*L(?P<end>[1-9][0-9]*)$"
|
|
@@ -1629,9 +1638,15 @@ def validate_actual_implementation_changes_report(
|
|
|
1629
1638
|
impact="后续测试和验收无法判断该文件为何属于本轮实现",
|
|
1630
1639
|
next_action="在实施结果中补充该文件的实际逻辑、修改理由、AC 编号和测试证据",
|
|
1631
1640
|
)
|
|
1632
|
-
#
|
|
1641
|
+
# 记录中出现了没有真实变化的文件时,检查是否明确说明这是复用的既有实现;
|
|
1642
|
+
# 只有豁免文本都不出现才报错(BUG-05:报错文案承诺的豁免必须真实生效)。
|
|
1633
1643
|
for path in sorted(recorded_paths - actual):
|
|
1634
1644
|
change = next(item for item in recorded_changes if item.path == path)
|
|
1645
|
+
explanation = " ".join(
|
|
1646
|
+
[change.change_reason, change.observable_change, change.logic]
|
|
1647
|
+
)
|
|
1648
|
+
if _REUSE_EXEMPTION_RE.search(explanation):
|
|
1649
|
+
continue
|
|
1635
1650
|
report.add_error(
|
|
1636
1651
|
check_id=f"impl.actual_changes.recorded_without_change:{path}",
|
|
1637
1652
|
location=f"{change.document_path}:{change.line}",
|
|
@@ -36,7 +36,7 @@ from ..test_mapping import (
|
|
|
36
36
|
validate_workflow_test_markers,
|
|
37
37
|
)
|
|
38
38
|
from ..topic import (
|
|
39
|
-
|
|
39
|
+
acceptance_topics,
|
|
40
40
|
current_workflow_topics,
|
|
41
41
|
missing_topic_documents,
|
|
42
42
|
topic_paths,
|
|
@@ -80,7 +80,13 @@ def _stage_table_sync(
|
|
|
80
80
|
返回 None 表示当前环节没有启用表(旧轮次走原有检查);否则返回
|
|
81
81
|
(通过, 详情)。表有问题时详情带 [格式问题]/[内容问题] 类别标注。
|
|
82
82
|
"""
|
|
83
|
-
|
|
83
|
+
try:
|
|
84
|
+
topics = acceptance_topics(
|
|
85
|
+
project_root, workflow_state.intent, stage_name, list(workflow_state.topics)
|
|
86
|
+
)
|
|
87
|
+
except ValueError:
|
|
88
|
+
# 索引格式错误由本阶段校验报告为「验收主题索引」问题;表同步回退 state 主题,不中断。
|
|
89
|
+
topics = list(workflow_state.topics) or current_workflow_topics(project_root)
|
|
84
90
|
enabled = records_mod.has_any_table_file(project_root, workflow_state.workflow_id, stage_name, topics)
|
|
85
91
|
if not enabled:
|
|
86
92
|
return None
|
|
@@ -329,8 +335,9 @@ class AcceptancePlanStage(StageStrategy):
|
|
|
329
335
|
if not topics:
|
|
330
336
|
errors.append("修 bug 的验收主题必须先在缺陷复现阶段确定")
|
|
331
337
|
else:
|
|
332
|
-
#
|
|
333
|
-
|
|
338
|
+
# 校验集 = state 已记录主题 ∪ 索引中未使用过的新主题(退回后追加主题
|
|
339
|
+
# 时不能钉死在旧主题);索引无可用主题时回退 state 主题。
|
|
340
|
+
topics = acceptance_topics(project_root, state.intent, "acceptance_plan")
|
|
334
341
|
except ValueError as exc:
|
|
335
342
|
topics = []
|
|
336
343
|
topic_index_error = str(exc)
|
|
@@ -124,6 +124,28 @@ def candidate_topics(project_root: str) -> list[str]:
|
|
|
124
124
|
]
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
def acceptance_topics(
|
|
128
|
+
project_root: str,
|
|
129
|
+
intent: str,
|
|
130
|
+
stage: str,
|
|
131
|
+
state_topics: list[str] | None = None,
|
|
132
|
+
) -> list[str]:
|
|
133
|
+
"""返回当前环节应使用的验收主题集。
|
|
134
|
+
|
|
135
|
+
验收计划环节(非修 bug)用 candidate_topics:保留 state 已记录主题,并接纳
|
|
136
|
+
acceptance/索引.md 中未使用过的新主题——退回后往索引追加主题时校验集不能钉死
|
|
137
|
+
在旧主题;candidate_topics 为空时回退 state 主题。其他环节主题已在验收计划
|
|
138
|
+
第三道门写入 state,直接取 state_topics(未传入时读磁盘 state)。
|
|
139
|
+
|
|
140
|
+
state_topics 供调用方传入内存中的 wf_state.topics,避免依赖磁盘 state 的写入时机。
|
|
141
|
+
"""
|
|
142
|
+
if state_topics is None:
|
|
143
|
+
state_topics = current_workflow_topics(project_root)
|
|
144
|
+
if stage == "acceptance_plan" and intent != "bugfix":
|
|
145
|
+
return candidate_topics(project_root) or list(state_topics)
|
|
146
|
+
return list(state_topics) or current_workflow_topics(project_root)
|
|
147
|
+
|
|
148
|
+
|
|
127
149
|
def missing_topic_documents(
|
|
128
150
|
project_root: str,
|
|
129
151
|
kind: str,
|
|
@@ -635,21 +635,185 @@ def _current_requirement_text(project_root: str, workflow_id: str) -> str:
|
|
|
635
635
|
return f"详见 spec/产品总说明.md 第 9 章工作流 {workflow_id} 的修改记录。"
|
|
636
636
|
|
|
637
637
|
|
|
638
|
+
def _topic_row_lines(
|
|
639
|
+
project_root: str,
|
|
640
|
+
workflow_id: str,
|
|
641
|
+
topic: str,
|
|
642
|
+
headers: list[str],
|
|
643
|
+
spike_initial: str = SPIKE_SKIPPED_TEXT,
|
|
644
|
+
) -> list[str]:
|
|
645
|
+
"""按主题的验收计划记录表生成追踪表行,单元格初值与新建章节完全一致。
|
|
646
|
+
|
|
647
|
+
spike_initial 只在补行路径由调用方按当前穿刺事实传入待复核文本;
|
|
648
|
+
建章节路径保持修复前的固定跳过初值,保证首轮行为逐字节不变。
|
|
649
|
+
"""
|
|
650
|
+
from . import records as records_mod
|
|
651
|
+
|
|
652
|
+
rel = records_mod.table_relative_path(project_root, workflow_id, "acceptance_plan", topic)
|
|
653
|
+
if not records_mod.table_exists(project_root, rel):
|
|
654
|
+
return []
|
|
655
|
+
table = records_mod.load_table(os.path.join(project_root, rel))
|
|
656
|
+
plan_path = topic_paths(project_root, topic)["acceptance_plan"]
|
|
657
|
+
plan_link = f"[{_trace_cell(topic)}](./{_trace_cell(plan_path)})"
|
|
658
|
+
rows: list[str] = []
|
|
659
|
+
for ac in table.get("验收条件", []):
|
|
660
|
+
ac_id = str(ac.get("验收条件编号", "")).strip()
|
|
661
|
+
cond = str(ac.get("通过标准", "")).strip()
|
|
662
|
+
basis = str(ac.get("产品设计依据", "")).strip()
|
|
663
|
+
cond_cell = _trace_cell(f"{ac_id}:{cond}") if ac_id and cond else _trace_cell(ac_id or cond)
|
|
664
|
+
cells_by_header = {
|
|
665
|
+
"需求来源与设计依据": _source_link(basis),
|
|
666
|
+
"验收主题": plan_link,
|
|
667
|
+
"验收条件": cond_cell,
|
|
668
|
+
"穿刺结论与可复用内容": spike_initial,
|
|
669
|
+
**_INITIAL_VALUES_BY_HEADER,
|
|
670
|
+
}
|
|
671
|
+
try:
|
|
672
|
+
row = [cells_by_header.get(header, "") for header in headers]
|
|
673
|
+
except TypeError:
|
|
674
|
+
return rows
|
|
675
|
+
rows.append(_format_table_line(row))
|
|
676
|
+
return rows
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _topic_row_lines_with_ids(
|
|
680
|
+
project_root: str,
|
|
681
|
+
workflow_id: str,
|
|
682
|
+
topic: str,
|
|
683
|
+
headers: list[str],
|
|
684
|
+
spike_initial: str = SPIKE_SKIPPED_TEXT,
|
|
685
|
+
) -> list[tuple[str, str]]:
|
|
686
|
+
"""与 _topic_row_lines 同行序,返回每条生成行的 AC 编号,供按 AC 粒度补行。"""
|
|
687
|
+
from . import records as records_mod
|
|
688
|
+
|
|
689
|
+
rel = records_mod.table_relative_path(project_root, workflow_id, "acceptance_plan", topic)
|
|
690
|
+
if not records_mod.table_exists(project_root, rel):
|
|
691
|
+
return []
|
|
692
|
+
table = records_mod.load_table(os.path.join(project_root, rel))
|
|
693
|
+
ac_ids = [
|
|
694
|
+
str(ac.get("验收条件编号", "")).strip()
|
|
695
|
+
for ac in table.get("验收条件", [])
|
|
696
|
+
]
|
|
697
|
+
lines = _topic_row_lines(
|
|
698
|
+
project_root, workflow_id, topic, headers, spike_initial=spike_initial
|
|
699
|
+
)
|
|
700
|
+
return list(zip(ac_ids, lines))
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _is_table_separator(line: str) -> bool:
|
|
704
|
+
stripped = line.strip()
|
|
705
|
+
if not (stripped.startswith("|") and stripped.endswith("|")):
|
|
706
|
+
return False
|
|
707
|
+
cells = [cell.strip() for cell in stripped.strip("|").split("|")]
|
|
708
|
+
return bool(cells) and all(re.fullmatch(r"[-:]+", cell) for cell in cells)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _delivery_table_bounds(section: str) -> tuple[int, int, list[str]] | None:
|
|
712
|
+
"""返回章节内最后一个追踪表头块的(表头行下标、数据末尾行下标、表头列)。"""
|
|
713
|
+
lines = section.splitlines()
|
|
714
|
+
bounds: tuple[int, int, list[str]] | None = None
|
|
715
|
+
i = 0
|
|
716
|
+
while i < len(lines):
|
|
717
|
+
cells = _split_table_line(lines[i])
|
|
718
|
+
if cells is not None and any(cells == h for h in SUPPORTED_HEADERS):
|
|
719
|
+
headers = next(h for h in SUPPORTED_HEADERS if h == cells)
|
|
720
|
+
j = i + 1
|
|
721
|
+
end = i
|
|
722
|
+
if j < len(lines) and _is_table_separator(lines[j]):
|
|
723
|
+
end = j
|
|
724
|
+
j += 1
|
|
725
|
+
while j < len(lines):
|
|
726
|
+
next_cells = _split_table_line(lines[j])
|
|
727
|
+
if next_cells is None or len(next_cells) != len(headers):
|
|
728
|
+
break
|
|
729
|
+
end = j
|
|
730
|
+
j += 1
|
|
731
|
+
bounds = (i, end, headers)
|
|
732
|
+
i = j
|
|
733
|
+
continue
|
|
734
|
+
i += 1
|
|
735
|
+
return bounds
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _append_missing_topic_rows(
|
|
739
|
+
project_root: str,
|
|
740
|
+
relative: str,
|
|
741
|
+
content: str,
|
|
742
|
+
heading_match: re.Match[str],
|
|
743
|
+
workflow_id: str,
|
|
744
|
+
topics: list[str],
|
|
745
|
+
) -> bool:
|
|
746
|
+
"""章节已存在时为表内无行的主题在交付链路表末尾补行;有追加才写文件。"""
|
|
747
|
+
section = heading_match.group(1)
|
|
748
|
+
bounds = _delivery_table_bounds(section)
|
|
749
|
+
if bounds is None:
|
|
750
|
+
return False
|
|
751
|
+
_, last_row_index, headers = bounds
|
|
752
|
+
# 补行只发生在退回重走路径:本轮已确认跳过穿刺时新行直接写跳过文本;
|
|
753
|
+
# 尚未重新确认(spike 未跑/未跳过)时写待复核文本,与旧行重置状态一致。
|
|
754
|
+
spike_initial = SPIKE_RECHECK_TEXT
|
|
755
|
+
try:
|
|
756
|
+
state = load_state(project_root)
|
|
757
|
+
if state is not None and state.workflow_id == workflow_id and state.spike_skipped:
|
|
758
|
+
spike_initial = SPIKE_SKIPPED_TEXT
|
|
759
|
+
except Exception:
|
|
760
|
+
pass
|
|
761
|
+
appended: list[str] = []
|
|
762
|
+
for topic in topics:
|
|
763
|
+
existing_ids = set()
|
|
764
|
+
for _line_index, cells in _topic_rows(project_root, section, topic):
|
|
765
|
+
try:
|
|
766
|
+
criterion_cell = _cell(cells, "验收条件")
|
|
767
|
+
except ValueError:
|
|
768
|
+
continue
|
|
769
|
+
match = _CRITERION_ID_RE.search(criterion_cell)
|
|
770
|
+
if match:
|
|
771
|
+
existing_ids.add(match.group(1))
|
|
772
|
+
# 粒度到验收条件:主题已有旧行时,表里新增的 AC 仍要补行;已有 AC 的行不动。
|
|
773
|
+
rows = [
|
|
774
|
+
line
|
|
775
|
+
for ac_id, line in _topic_row_lines_with_ids(
|
|
776
|
+
project_root, workflow_id, topic, headers, spike_initial=spike_initial
|
|
777
|
+
)
|
|
778
|
+
if ac_id not in existing_ids
|
|
779
|
+
]
|
|
780
|
+
if rows:
|
|
781
|
+
appended.extend(rows)
|
|
782
|
+
if not appended:
|
|
783
|
+
return False
|
|
784
|
+
section_lines = section.split("\n")
|
|
785
|
+
merged = (
|
|
786
|
+
section_lines[: last_row_index + 1]
|
|
787
|
+
+ appended
|
|
788
|
+
+ section_lines[last_row_index + 1 :]
|
|
789
|
+
)
|
|
790
|
+
new_section = "\n".join(merged)
|
|
791
|
+
content = content[: heading_match.start(1)] + new_section + content[heading_match.end(1) :]
|
|
792
|
+
_write_traceability(project_root, relative, content)
|
|
793
|
+
return True
|
|
794
|
+
|
|
795
|
+
|
|
638
796
|
def ensure_workflow_section(
|
|
639
797
|
project_root: str,
|
|
640
798
|
workflow_id: str,
|
|
641
799
|
topics: list[str],
|
|
642
800
|
) -> bool:
|
|
643
|
-
"""
|
|
801
|
+
"""追踪表没有当前工作流章节时按模板生成;章节已存在时为缺行主题补交付行。
|
|
802
|
+
|
|
803
|
+
断言三:表流程生成全部文档。退回 acceptance_plan 后向索引追加新主题时,
|
|
804
|
+
旧章节保留,新主题必须按建章节同一初值规则自动补行,不能要求手工添加。
|
|
805
|
+
"""
|
|
644
806
|
relative = _trace_relative_path(project_root)
|
|
645
807
|
full = os.path.join(project_root, relative)
|
|
646
808
|
if os.path.isfile(full):
|
|
647
809
|
content = Path(full).read_text(encoding="utf-8")
|
|
648
810
|
else:
|
|
649
811
|
content = "# 需求交付追踪表\n"
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
812
|
+
heading_match = _workflow_heading_pattern(workflow_id).search(content)
|
|
813
|
+
if heading_match is not None:
|
|
814
|
+
return _append_missing_topic_rows(
|
|
815
|
+
project_root, relative, content, heading_match, workflow_id, topics
|
|
816
|
+
)
|
|
653
817
|
lines = [
|
|
654
818
|
"## " + workflow_id,
|
|
655
819
|
"",
|
|
@@ -663,30 +827,7 @@ def ensure_workflow_section(
|
|
|
663
827
|
"|" + "---|" * len(TRACEABILITY_HEADERS),
|
|
664
828
|
]
|
|
665
829
|
for topic in topics:
|
|
666
|
-
|
|
667
|
-
if not records_mod.table_exists(project_root, rel):
|
|
668
|
-
continue
|
|
669
|
-
table = records_mod.load_table(os.path.join(project_root, rel))
|
|
670
|
-
plan_path = topic_paths(project_root, topic)["acceptance_plan"]
|
|
671
|
-
plan_link = f"[{_trace_cell(topic)}](./{_trace_cell(plan_path)})"
|
|
672
|
-
for ac in table.get("验收条件", []):
|
|
673
|
-
ac_id = str(ac.get("验收条件编号", "")).strip()
|
|
674
|
-
cond = str(ac.get("通过标准", "")).strip()
|
|
675
|
-
basis = str(ac.get("产品设计依据", "")).strip()
|
|
676
|
-
cond_cell = _trace_cell(f"{ac_id}:{cond}") if ac_id and cond else _trace_cell(ac_id or cond)
|
|
677
|
-
row = [
|
|
678
|
-
_source_link(basis),
|
|
679
|
-
plan_link,
|
|
680
|
-
cond_cell,
|
|
681
|
-
"本轮未执行穿刺,无可复用资产",
|
|
682
|
-
"待制定",
|
|
683
|
-
"待制定",
|
|
684
|
-
"待执行",
|
|
685
|
-
"待执行",
|
|
686
|
-
"待执行",
|
|
687
|
-
"待更新",
|
|
688
|
-
]
|
|
689
|
-
lines.append("| " + " | ".join(row) + " |")
|
|
830
|
+
lines += _topic_row_lines(project_root, workflow_id, topic, TRACEABILITY_HEADERS)
|
|
690
831
|
lines += [
|
|
691
832
|
"",
|
|
692
833
|
"### 阻塞和退回记录",
|
|
@@ -835,6 +976,42 @@ _RETURN_REMOVE_CONCLUSIONS: dict[
|
|
|
835
976
|
}
|
|
836
977
|
|
|
837
978
|
|
|
979
|
+
def resolve_spike_recheck_for_skip(project_root: str, workflow_id: str) -> str:
|
|
980
|
+
"""本轮确认跳过穿刺后,把当前工作流待复核的穿刺列回补为跳过文本。
|
|
981
|
+
|
|
982
|
+
退回会把穿刺列重置为“待重新确认;已登记资产保留”;第二遍 spike --skip 之后
|
|
983
|
+
校验要求无资产引用的行精确写跳过文本。 引用了穿刺资产路径的行不动,
|
|
984
|
+
交给穿刺关联校验处理。"""
|
|
985
|
+
content, relative_path = _read_traceability(project_root)
|
|
986
|
+
match = _workflow_heading_pattern(workflow_id).search(content)
|
|
987
|
+
if match is None:
|
|
988
|
+
# 本轮追踪表章节尚未创建(新轮次 spike 先于 acceptance_plan),无需回补。
|
|
989
|
+
return f"{TRACEABILITY_PATH} 尚无当前工作流章节,无需回补穿刺列"
|
|
990
|
+
section_lines = match.group(1).splitlines(keepends=True)
|
|
991
|
+
updated_rows = 0
|
|
992
|
+
for line_index, line in enumerate(section_lines):
|
|
993
|
+
cells = _split_table_line(line)
|
|
994
|
+
if cells is None or cells in SUPPORTED_HEADERS:
|
|
995
|
+
continue
|
|
996
|
+
try:
|
|
997
|
+
headers = _headers_for_row(cells)
|
|
998
|
+
except ValueError:
|
|
999
|
+
continue
|
|
1000
|
+
if "穿刺结论与可复用内容" not in headers:
|
|
1001
|
+
continue
|
|
1002
|
+
if _cell(cells, "穿刺结论与可复用内容").strip() != SPIKE_RECHECK_TEXT:
|
|
1003
|
+
continue
|
|
1004
|
+
original_line = section_lines[line_index]
|
|
1005
|
+
line_ending = original_line[len(original_line.rstrip("\r\n")) :]
|
|
1006
|
+
_set_cell(cells, "穿刺结论与可复用内容", SPIKE_SKIPPED_TEXT)
|
|
1007
|
+
section_lines[line_index] = _format_table_line(cells) + line_ending
|
|
1008
|
+
updated_rows += 1
|
|
1009
|
+
updated = _replace_workflow_section(content, workflow_id, "".join(section_lines))
|
|
1010
|
+
if updated != content:
|
|
1011
|
+
_write_traceability(project_root, relative_path, updated)
|
|
1012
|
+
return f"已回补 {TRACEABILITY_PATH} 穿刺列 {updated_rows} 行为跳过文本"
|
|
1013
|
+
|
|
1014
|
+
|
|
838
1015
|
def reset_topics_for_return(
|
|
839
1016
|
project_root: str,
|
|
840
1017
|
workflow_id: str,
|
|
@@ -2600,10 +2600,14 @@ def inspect_invalidation(state: WorkflowState, project_root: str) -> Invalidatio
|
|
|
2600
2600
|
current = compute_impl_hash(project_root, topics)
|
|
2601
2601
|
if current != expected:
|
|
2602
2602
|
try:
|
|
2603
|
+
# 精确失效按登记责任范围比较:基线是登记快照时,当前值也必须按
|
|
2604
|
+
# 同一登记范围计算。只有轮次确实另存了入场完整观察基线
|
|
2605
|
+
# (impl_actual,当前无写入方)时才用完整范围比较;把登记基线
|
|
2606
|
+
# 和完整观察当前值交叉比较会把登记集合外的既有文件报成新增(BUG-10)。
|
|
2603
2607
|
impl_snapshot = stored.get("impl_actual") or stored.get("impl")
|
|
2604
|
-
if
|
|
2605
|
-
impl_snapshot
|
|
2606
|
-
):
|
|
2608
|
+
if stored.get("impl_actual") is not None and isinstance(
|
|
2609
|
+
impl_snapshot, dict
|
|
2610
|
+
) and isinstance(impl_snapshot.get("files"), list):
|
|
2607
2611
|
code_differences = compare_complete_implementation_file_snapshot(
|
|
2608
2612
|
project_root,
|
|
2609
2613
|
impl_snapshot,
|