workflow-loop 0.3.7__tar.gz → 0.3.8__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.
Files changed (112) hide show
  1. {workflow_loop-0.3.7/src/workflow_loop.egg-info → workflow_loop-0.3.8}/PKG-INFO +9 -9
  2. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/README.md +8 -8
  3. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/pyproject.toml +1 -1
  4. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/__init__.py +2 -2
  5. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/artifact_paths.py +23 -3
  6. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/artifact_validation.py +102 -31
  7. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/bug_record.py +63 -0
  8. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/cli.py +66 -1
  9. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/global/workflow_lifecycle.md +1 -0
  10. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/installer.py +5 -0
  11. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/records.py +198 -22
  12. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/rollback.py +21 -1
  13. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/stages/stages.py +5 -2
  14. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/test_execution.py +25 -3
  15. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/test_mapping.py +99 -15
  16. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/verification.py +23 -0
  17. {workflow_loop-0.3.7 → workflow_loop-0.3.8/src/workflow_loop.egg-info}/PKG-INFO +9 -9
  18. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop.egg-info/SOURCES.txt +3 -0
  19. workflow_loop-0.3.8/tests/test_gate_plan_path_diagnostics.py +290 -0
  20. workflow_loop-0.3.8/tests/test_gate_rework_stability.py +817 -0
  21. workflow_loop-0.3.8/tests/test_table_entry_registration.py +273 -0
  22. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/LICENSE +0 -0
  23. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/setup.cfg +0 -0
  24. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/acceptance_records.py +0 -0
  25. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance.md +0 -0
  26. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/acceptance/acceptance_plan.md +0 -0
  27. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/code_design/code_design.md +0 -0
  28. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/code_design/project_design_init.md +0 -0
  29. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/code_design/update_code_design.md +0 -0
  30. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/global/document_writing.md +0 -0
  31. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/impl/code_implementation.md +0 -0
  32. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/impl/impl.md +0 -0
  33. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/qa/test.md +0 -0
  34. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/qa/test_code.md +0 -0
  35. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/qa/test_code_implementation.md +0 -0
  36. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/qa/test_plan.md +2 -2
  37. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/reproduce/reproduce.md +0 -0
  38. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/spec/spec.md +0 -0
  39. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Standardized_Repository/spike/spike.md +0 -0
  40. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_plan.md +0 -0
  41. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/acceptance/acceptance_result.md +0 -0
  42. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/code_design/code_design.md +0 -0
  43. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/code_design/project_design_init_evidence.md +0 -0
  44. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/impl/impl.md +0 -0
  45. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/qa/test.md +0 -0
  46. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/qa/test_plan.md +0 -0
  47. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/reproduce/reproduce.md +0 -0
  48. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/spec/spec.md +0 -0
  49. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/data/Template_Repository/spike/spike.md +0 -0
  50. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/diagnostics.py +0 -0
  51. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/journal.py +0 -0
  52. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/markdown_links.py +0 -0
  53. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/path_composer.py +0 -0
  54. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/process_runner.py +0 -0
  55. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/project.py +0 -0
  56. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/role_doc.py +0 -0
  57. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/snapshots.py +0 -0
  58. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/spike_reuse.py +0 -0
  59. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/spike_validation.py +0 -0
  60. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/stage_materials.py +0 -0
  61. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/stages/__init__.py +0 -0
  62. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/stages/base.py +0 -0
  63. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/state.py +0 -0
  64. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/test_entry.py +0 -0
  65. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/test_report.py +0 -0
  66. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/test_runner.py +0 -0
  67. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/topic.py +0 -0
  68. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/topic_relations.py +0 -0
  69. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop/traceability.py +0 -0
  70. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop.egg-info/dependency_links.txt +0 -0
  71. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop.egg-info/entry_points.txt +0 -0
  72. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop.egg-info/requires.txt +0 -0
  73. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/src/workflow_loop.egg-info/top_level.txt +0 -0
  74. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_acceptance_records.py +0 -0
  75. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_architecture_validation.py +0 -0
  76. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_artifact_paths.py +0 -0
  77. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_bug_record.py +0 -0
  78. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_bug_validation.py +0 -0
  79. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_commands.py +0 -0
  80. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_current_workflow_contracts.py +0 -0
  81. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_current_workflow_qa.py +0 -0
  82. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_diagnostics.py +0 -0
  83. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_installer.py +0 -0
  84. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_light_task.py +0 -0
  85. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_maintenance.py +0 -0
  86. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_maintenance_scripts.py +0 -0
  87. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_markdown_links.py +0 -0
  88. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_path_composer.py +0 -0
  89. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_process_runner.py +0 -0
  90. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_project.py +0 -0
  91. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_project_design_init.py +0 -0
  92. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_public_project.py +0 -0
  93. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_records.py +0 -0
  94. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_release_script.py +0 -0
  95. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_release_workflow.py +0 -0
  96. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_return_rework_flow.py +0 -0
  97. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_rollback.py +0 -0
  98. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_snapshots.py +0 -0
  99. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_spike_assets.py +0 -0
  100. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_spike_validation.py +0 -0
  101. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_stage_materials.py +0 -0
  102. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_stages.py +0 -0
  103. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_state.py +0 -0
  104. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_test_entry.py +0 -0
  105. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_test_execution.py +0 -0
  106. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_test_mapping.py +0 -0
  107. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_test_report.py +0 -0
  108. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_test_runner.py +0 -0
  109. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_topic_relations.py +0 -0
  110. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_traceability.py +0 -0
  111. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/tests/test_verification.py +0 -0
  112. {workflow_loop-0.3.7 → workflow_loop-0.3.8}/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.7
3
+ Version: 0.3.8
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.7
98
+ ## 安装 0.3.8
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.7/install.sh | bash
105
+ curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.8/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.7/install.sh | bash
111
+ curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.8/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.7/install.ps1 | iex"
117
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.3.8/install.ps1 | iex"
118
118
  ```
119
119
 
120
- 安装命令固定读取 `v0.3.7` 正式发布中的脚本;不会跟随内容可能变化的 `latest`(最新版本)地址。
120
+ 安装命令固定读取 `v0.3.8` 正式发布中的脚本;不会跟随内容可能变化的 `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.7
128
+ workflow update --version 0.3.8
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.7` 替换成要发布的新版本号:
232
+ 项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.3.8` 替换成要发布的新版本号:
233
233
 
234
234
  ```bash
235
- uv run python scripts/release.py 0.3.7
235
+ uv run python scripts/release.py 0.3.8
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.7
79
+ ## 安装 0.3.8
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.7/install.sh | bash
86
+ curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.8/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.7/install.sh | bash
92
+ curl -fsSL https://github.com/yuzyf/workflow_loop/releases/download/v0.3.8/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.7/install.ps1 | iex"
98
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/yuzyf/workflow_loop/releases/download/v0.3.8/install.ps1 | iex"
99
99
  ```
100
100
 
101
- 安装命令固定读取 `v0.3.7` 正式发布中的脚本;不会跟随内容可能变化的 `latest`(最新版本)地址。
101
+ 安装命令固定读取 `v0.3.8` 正式发布中的脚本;不会跟随内容可能变化的 `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.7
109
+ workflow update --version 0.3.8
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.7` 替换成要发布的新版本号:
213
+ 项目维护者确认当前仓库内容可以直接发布后,在仓库根目录执行下面的命令,并把 `0.3.8` 替换成要发布的新版本号:
214
214
 
215
215
  ```bash
216
- uv run python scripts/release.py 0.3.7
216
+ uv run python scripts/release.py 0.3.8
217
217
  ```
218
218
 
219
219
  运行这条命令本身就表示维护者接受当前仓库状态并承担跳过本地发布检查的风险。脚本不会再次确认,也不会在本地运行测试、构建或远程版本查询。它依次执行:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "workflow-loop"
3
- version = "0.3.7"
3
+ version = "0.3.8"
4
4
  description = "为 AI 驱动的软件开发提供有状态、可验证、可回退的工作流管理。"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  # 包版本号,用于 pip/uv 安装时版本匹配
2
- __version__ = "0.3.7"
2
+ __version__ = "0.3.8"
3
3
  # 产品身份标识:workflow --version 输出、安装脚本身份核对和安装事务校验共用同一组常量
4
- # 本次发布版本为 0.3.7;后续发布使用尚未被 PyPI 占用的新版本号
4
+ # 本次发布版本为 0.3.8;后续发布使用尚未被 PyPI 占用的新版本号
5
5
  PRODUCT_NAME = "workflow-loop"
6
6
  PRODUCT_IDENTITY = f"{PRODUCT_NAME} {__version__}"
@@ -82,15 +82,23 @@ def resolve_file_key(
82
82
  saved_keys: dict[str, str],
83
83
  all_used_keys: set[str],
84
84
  display_name: str,
85
+ keys_by_name_all: dict[str, str] | None = None,
85
86
  ) -> str:
86
87
  """在一个分类内解析显示名称的文件标识。
87
88
 
88
89
  saved_keys 是该分类已保存的 显示名称→文件标识 映射;all_used_keys 是全部
89
- 分类已占用的标识(大小写不敏感比较,避免只在大小写上不同的路径冲突)。
90
+ 分类已占用的标识(大小写不敏感比较,避免只在大小写上不同的路径冲突);
91
+ keys_by_name_all 是全部分类的 显示名称→文件标识 映射。
90
92
  已保存的名称直接返回旧标识;新名称生成标识,冲突时依次追加 `_2`、`_3`。
93
+
94
+ 同一显示名称在不同分类表示同一件事(缺陷名与验收主题名相同是常态),
95
+ 因此先在全部分类中按名称复用同一标识;否则第二个分类必然被迫追加 `_2`,
96
+ 使生成路径与登记路径分叉。不同名称之间仍不允许共用标识。
91
97
  """
92
98
  if display_name in saved_keys:
93
99
  return saved_keys[display_name]
100
+ if keys_by_name_all is not None and display_name in keys_by_name_all:
101
+ return keys_by_name_all[display_name]
94
102
  base_key = make_file_key(display_name)
95
103
  used_lower = {key.lower() for key in all_used_keys}
96
104
  candidate = base_key
@@ -115,12 +123,13 @@ def register_file_keys(project, category: str, display_names: list[str]) -> dict
115
123
  for mapping in project.artifact_file_keys.values()
116
124
  for key in mapping.values()
117
125
  }
126
+ keys_by_name_all = _keys_by_name(project)
118
127
  reverse = {key: name for name, key in keys.items()}
119
128
  added: dict[str, str] = {}
120
129
  for display_name in display_names:
121
130
  if display_name in keys:
122
131
  continue
123
- key = resolve_file_key(keys, all_used, display_name)
132
+ key = resolve_file_key(keys, all_used, display_name, keys_by_name_all)
124
133
  owner = reverse.get(key)
125
134
  if owner is not None and owner != display_name:
126
135
  raise ValueError(
@@ -129,10 +138,21 @@ def register_file_keys(project, category: str, display_names: list[str]) -> dict
129
138
  keys[display_name] = key
130
139
  reverse[key] = display_name
131
140
  all_used.add(key)
141
+ keys_by_name_all[display_name] = key
132
142
  added[display_name] = key
133
143
  return added
134
144
 
135
145
 
146
+ def _keys_by_name(project) -> dict[str, str]:
147
+ """全部分类的 显示名称→文件标识 映射,供同名跨分类复用同一标识。"""
148
+ if project is None:
149
+ return {}
150
+ merged: dict[str, str] = {}
151
+ for mapping in project.artifact_file_keys.values():
152
+ merged.update(mapping)
153
+ return merged
154
+
155
+
136
156
  def lookup_file_key(project, category: str, display_name: str) -> str | None:
137
157
  """读取已保存的文件标识;没有登记时返回 None。"""
138
158
  if project is None:
@@ -152,7 +172,7 @@ def resolve_key_for(project, category: str, display_name: str) -> str:
152
172
  if project is not None
153
173
  else set()
154
174
  )
155
- return resolve_file_key(saved, all_used, display_name)
175
+ return resolve_file_key(saved, all_used, display_name, _keys_by_name(project))
156
176
 
157
177
 
158
178
  # ─── 按文件标识生成动态正式产物路径 ───
@@ -233,20 +233,47 @@ def _markdown_tables(content: str) -> list[tuple[list[str], list[list[str]]]]:
233
233
  return tables
234
234
 
235
235
 
236
- def _has_real_text(value: str | None, *, allow_none: bool = False) -> bool:
236
+ # 模板占位写法:整行被尖括号包住,例如 `<用户在什么操作中看到了什么问题。>`。
237
+ # 只按整行判定;正文里合法出现的半角小于号(路径占位写法、HTML 锚点、比较运算)
238
+ # 不影响判定,否则含实质内容的长章节会被整段误判为未填写。
239
+ _TEMPLATE_PLACEHOLDER_LINE_RE = re.compile(r"^<[^<>]*>$")
240
+
241
+
242
+ def _placeholder_lines(value: str) -> list[str]:
243
+ """返回值中仍是模板占位写法的行;全部非空行都命中才算整体未填写。"""
244
+ stripped_lines = [
245
+ re.sub(r"^[-*]\s+", "", line).strip()
246
+ for line in value.splitlines()
247
+ if line.strip()
248
+ ]
249
+ if not stripped_lines:
250
+ return []
251
+ hits = [line for line in stripped_lines if _TEMPLATE_PLACEHOLDER_LINE_RE.match(line)]
252
+ return hits if len(hits) == len(stripped_lines) else []
253
+
254
+
255
+ def _no_real_text_reason(value: str | None, *, allow_none: bool = False) -> str | None:
256
+ """值不算已填写时返回具体原因和命中内容;已填写时返回 None。"""
237
257
  if value is None:
238
- return False
258
+ return "缺少这一节"
239
259
  normalized = value.strip()
240
260
  if not normalized:
241
- return False
261
+ return "内容为空"
242
262
  if allow_none and normalized in {"无", "暂无"}:
243
- return True
244
- lowered = normalized.lower()
245
- return not (
246
- normalized in {"", "暂无", "待补充", "未填写"}
247
- or "<" in normalized
248
- or "todo" in lowered
249
- )
263
+ return None
264
+ if normalized in {"无", "暂无", "待补充", "未填写"}:
265
+ return f"整段只写了占位词 {normalized!r}"
266
+ if "todo" in normalized.lower():
267
+ return "内容里仍留着 TODO 标记"
268
+ hits = _placeholder_lines(normalized)
269
+ if hits:
270
+ sample = hits[0] if len(hits[0]) <= 40 else hits[0][:40] + "…"
271
+ return f"整段仍是模板占位写法,例如 {sample}"
272
+ return None
273
+
274
+
275
+ def _has_real_text(value: str | None, *, allow_none: bool = False) -> bool:
276
+ return _no_real_text_reason(value, allow_none=allow_none) is None
250
277
 
251
278
 
252
279
  def _format_validation_failures(failures: list[str]) -> str:
@@ -609,7 +636,12 @@ def changed_stage_paths(
609
636
  stage_name: str,
610
637
  current_paths: list[str],
611
638
  ) -> tuple[bool, str, list[str]]:
612
- """比较讨论完成时的基线,返回本阶段新增、修改或删除的文件。"""
639
+ """比较讨论完成时的基线,返回本阶段新增、修改或删除的文件。
640
+
641
+ 启用工作记录表的轮次里正式文档是程序按表的确定性输出:表没有变化时文档必然
642
+ 逐字节相同,"文档没变"不等于"本阶段没有产出"。这种情况下按表判断产出,并把
643
+ 本阶段登记的产物路径交给下游检查,不要求 AI 制造无实质内容的修改。
644
+ """
613
645
  state = load_state(project_root)
614
646
  if state is None or stage_name not in state.stages:
615
647
  return (False, "找不到当前工作流阶段状态,无法判断文件是否属于本次工作", [])
@@ -627,6 +659,19 @@ def changed_stage_paths(
627
659
  if baseline_hashes.get(rel_path) != current_hashes.get(rel_path)
628
660
  ]
629
661
  if not changed:
662
+ from . import records as records_mod
663
+
664
+ if records_mod.stage_table_kinds(stage_name) and records_mod.workflow_uses_tables(
665
+ state, project_root
666
+ ):
667
+ # 变化清单如实为空:确实没有文件变化。调用方要判断“本阶段的产出是哪些”时
668
+ # 按内容识别,不能把未变化的文件当成本阶段改过——最终设计同步正是用这份
669
+ # 清单确认产品文档没有被改动。
670
+ return (
671
+ True,
672
+ "表模式:正式文档由程序按工作记录表生成,内容与上次相同时按表判断产出",
673
+ [],
674
+ )
630
675
  return (False, "相关文件与讨论完成时相同,不能证明本阶段已经生成或修改产物", [])
631
676
  return (True, f"本阶段发生变化的文件: {changed}", changed)
632
677
 
@@ -1405,6 +1450,21 @@ def validate_final_code_design_document(
1405
1450
  )
1406
1451
 
1407
1452
 
1453
+ def _current_workflow_bug_documents(project_root: str, workflow_id: str) -> list[str]:
1454
+ """按文件内的工作流编号找出本轮的缺陷记录,不依赖本阶段变化清单。"""
1455
+ bug_dir = os.path.join(project_root, "bug")
1456
+ if not os.path.isdir(bug_dir):
1457
+ return []
1458
+ found: list[str] = []
1459
+ for filename in sorted(os.listdir(bug_dir)):
1460
+ if not filename.endswith(".md") or filename in BUG_INDEX_FILENAMES:
1461
+ continue
1462
+ relative = f"bug/{filename}"
1463
+ if _field(_read_text(project_root, relative), "工作流编号") == workflow_id:
1464
+ found.append(relative)
1465
+ return found
1466
+
1467
+
1408
1468
  def validate_reproduce_documents(
1409
1469
  project_root: str,
1410
1470
  changed_paths: list[str],
@@ -1413,8 +1473,8 @@ def validate_reproduce_documents(
1413
1473
  index_path = artifact_paths_mod.BUG_INDEX_DOC
1414
1474
  normalized_changed = [path.replace(os.sep, "/") for path in changed_paths]
1415
1475
  failures: list[str] = []
1416
- if index_path not in normalized_changed:
1417
- failures.append(f"{index_path} 没有在本阶段更新")
1476
+ # 索引按内容判定:存在且链接本轮缺陷记录即可。索引由程序生成,内容已经正确时
1477
+ # 本阶段不需要再改它,"这一轮没有改过"不是问题。
1418
1478
  index_exists = os.path.isfile(os.path.join(project_root, index_path))
1419
1479
  if not index_exists:
1420
1480
  failures.append(f"{index_path} 不存在")
@@ -1438,6 +1498,10 @@ def validate_reproduce_documents(
1438
1498
  for rel_path in changed_bug_candidates
1439
1499
  if os.path.isfile(os.path.join(project_root, rel_path))
1440
1500
  ]
1501
+ if not changed_bug_docs:
1502
+ # 表模式下缺陷记录由程序按表生成,重走本环节时内容可能与上次逐字节相同,
1503
+ # 变化清单为空不等于本轮没有缺陷记录。按文件内的工作流编号识别本轮记录。
1504
+ changed_bug_docs = _current_workflow_bug_documents(project_root, workflow_id)
1441
1505
  if not changed_bug_docs:
1442
1506
  failures.append("本阶段没有新增或修改缺陷记录文档")
1443
1507
 
@@ -1484,8 +1548,11 @@ def validate_reproduce_documents(
1484
1548
  topics.append(topic or "")
1485
1549
 
1486
1550
  for heading in required_sections:
1487
- if not _has_real_text(_section(content, heading), allow_none=heading.startswith("7.")):
1488
- failures.append(f"{filename} 的“{heading}”缺少具体内容")
1551
+ allow_none = heading.startswith("7.")
1552
+ section_text = _section(content, heading)
1553
+ reason = _no_real_text_reason(section_text, allow_none=allow_none)
1554
+ if reason is not None:
1555
+ failures.append(f"{filename} 的“{heading}”缺少具体内容:{reason}")
1489
1556
 
1490
1557
  condition_section = _section(content, "2. 真实复现条件") or ""
1491
1558
  if not _has_real_text(_field(condition_section, "运行环境")):
@@ -1874,24 +1941,17 @@ def validate_downstream_traceability(
1874
1941
 
1875
1942
 
1876
1943
  def _table_mode_qa(project_root: str, workflow_id: str, topics: list[str]) -> bool:
1877
- """全部主题都有已填测试工作记录表时,qa 的文档校验走表核对。"""
1944
+ """本轮是否按测试工作记录表校验测试计划。
1945
+
1946
+ R11:只看开工时冻结在工作流状态里的表版本。表缺失、为空或解析失败时
1947
+ 仍留在表模式,由表校验报出具体问题,不退回按文档正文逐字检查的旧方式。
1948
+ """
1878
1949
  from . import records as records_mod
1879
1950
 
1880
- if not topics:
1951
+ state = load_state(project_root)
1952
+ if state is None or state.workflow_id != workflow_id:
1881
1953
  return False
1882
- for topic in topics:
1883
- relative = records_mod.table_relative_path(
1884
- project_root, workflow_id, "test_plan", topic
1885
- )
1886
- if not records_mod.table_exists(project_root, relative):
1887
- return False
1888
- try:
1889
- table = records_mod.load_table(os.path.join(project_root, relative))
1890
- except records_mod.RecordsError:
1891
- return False
1892
- if not (table.get("测试项") or table.get("测试范围说明")):
1893
- return False
1894
- return True
1954
+ return records_mod.workflow_uses_tables(state, project_root)
1895
1955
 
1896
1956
 
1897
1957
  def _validate_test_plan_from_tables(
@@ -1910,7 +1970,18 @@ def _validate_test_plan_from_tables(
1910
1970
  relative = records_mod.table_relative_path(
1911
1971
  project_root, workflow_id, "test_plan", topic
1912
1972
  )
1913
- table = records_mod.load_table(os.path.join(project_root, relative))
1973
+ # 表模式下缺表、空表和解析失败都在这里报具体问题;不退回文档检查(R11)。
1974
+ if not records_mod.table_exists(project_root, relative):
1975
+ failures.append(
1976
+ f"主题「{topic}」缺少测试计划工作记录表({relative});"
1977
+ "请执行 workflow scaffold 补齐后填写测试项,再重新门禁"
1978
+ )
1979
+ continue
1980
+ try:
1981
+ table = records_mod.load_table(os.path.join(project_root, relative))
1982
+ except records_mod.RecordsError as exc:
1983
+ failures.append(f"主题「{topic}」的测试计划工作记录表无法解析({relative}):{exc}")
1984
+ continue
1914
1985
  rows = table.get("测试项", [])
1915
1986
  if not rows:
1916
1987
  # 纯人工主题:自动化验证由最终全量回归承担,验收条件在主题验收人工核对
@@ -136,6 +136,69 @@ def update_status(
136
136
  return f"已更新缺陷状态“{status}”: {updated}"
137
137
 
138
138
 
139
+ # 各阶段追加到第 8 节的结论块标题,按流程顺序排列。退回时清除目标环节之后的块。
140
+ _RESULT_STAGE_LABELS = ("主题验收结果", "最终全量回归结果", "整体验收确认")
141
+ # 退回目标 → 该目标之后失效的结论块起点下标
142
+ _RETURN_TARGET_FIRST_INVALID = {
143
+ "spec": 0,
144
+ "reproduce": 0,
145
+ "spike": 0,
146
+ "acceptance_plan": 0,
147
+ "impl": 0,
148
+ "qa": 0,
149
+ "test_plan": 0,
150
+ "test_code": 0,
151
+ "test_execution": 0,
152
+ "topic_acceptance": 0,
153
+ "regression_test": 1,
154
+ "overall_acceptance": 2,
155
+ }
156
+
157
+
158
+ def reset_status_for_return(
159
+ project_root: str,
160
+ workflow_id: str,
161
+ topics: list[str],
162
+ target_stage: str,
163
+ ) -> str:
164
+ """退回时清除已经失效的阶段结论块,并把缺陷索引状态改回复现阶段状态。
165
+
166
+ 这些结论块由程序追加,块内链接指向测试结果和验收结果文档。退回会删除那些
167
+ 文档,块留在缺陷记录里就成了指向不存在文件的坏链接,同时也在冒充仍然有效的
168
+ 结论。目标环节之前已经完成的阶段结论保留不动。
169
+ """
170
+ first_invalid = _RETURN_TARGET_FIRST_INVALID.get(target_stage)
171
+ if first_invalid is None:
172
+ return ""
173
+ invalid_labels = _RESULT_STAGE_LABELS[first_invalid:]
174
+ if not invalid_labels:
175
+ return ""
176
+ try:
177
+ records = _records_for_workflow(project_root, workflow_id, topics)
178
+ except ValueError:
179
+ # 本轮没有缺陷记录(非修缺陷轮次)时无需处理,不把它当成退回失败。
180
+ return ""
181
+ updated: list[str] = []
182
+ for path, _topic in records:
183
+ content = path.read_text(encoding="utf-8")
184
+ new_content = content
185
+ for label in invalid_labels:
186
+ new_content = re.sub(
187
+ rf"^###\s+{re.escape(label)}(工作流 {re.escape(workflow_id)})\s*$\n"
188
+ r".*?(?=^###\s+|\Z)",
189
+ "",
190
+ new_content,
191
+ flags=re.MULTILINE | re.DOTALL,
192
+ )
193
+ if new_content != content:
194
+ path.write_text(new_content.rstrip() + "\n", encoding="utf-8")
195
+ _update_index_status(project_root, path.name, "根因已确认")
196
+ updated.append(path.name)
197
+ if not updated:
198
+ return ""
199
+ return f"已清除失效的缺陷结论块并把索引状态改回“根因已确认”: {updated}"
200
+
201
+
139
202
  def record_topic_acceptance_pass(project_root: str, workflow_id: str, topics: list[str]) -> str:
140
203
  return update_status(
141
204
  project_root,
@@ -2129,6 +2129,28 @@ def _stage_failure_diagnostics(
2129
2129
  return legacy_diagnostics + list(structured_report.diagnostics)
2130
2130
 
2131
2131
 
2132
+ def _sync_stage_tables_before_checks(
2133
+ project_root: str,
2134
+ wf_state: state_mod.WorkflowState,
2135
+ stage_name: str,
2136
+ ) -> None:
2137
+ """在本阶段全部固定检查之前,按当前工作记录表重新生成正式文档。
2138
+
2139
+ 只负责生成;表本身的问题由阶段校验统一报告,这里不产出诊断,也不因表有问题
2140
+ 而中断——那样会掩盖同一次输出中其它可独立确认的问题。
2141
+ """
2142
+ if stage_name != wf_state.current_stage:
2143
+ return
2144
+ if not records_mod.stage_table_kinds(stage_name):
2145
+ return
2146
+ if not records_mod.workflow_uses_tables(wf_state, project_root):
2147
+ return
2148
+ try:
2149
+ records_mod.sync_stage_tables(project_root, wf_state)
2150
+ except (records_mod.RecordsError, OSError, ValueError):
2151
+ return
2152
+
2153
+
2132
2154
  def validate_stage_output(
2133
2155
  project_root: str,
2134
2156
  wf_state: state_mod.WorkflowState,
@@ -2138,6 +2160,11 @@ def validate_stage_output(
2138
2160
  execute_regression: bool = True,
2139
2161
  ) -> tuple[bool, str]:
2140
2162
  """汇总安全只读检查;有前置错误时不执行最终回归。"""
2163
+ # 产品规则要求第二道门先按当前工作记录表重新生成正式文档,再执行包括链接在内的
2164
+ # 固定检查。先查链接会读到尚未重新生成的旧文档,于是第一次报坏链接、这一次顺带
2165
+ # 把文档生成好,第二次原样重跑就通过。阶段校验内部仍会再同步一次,内容相同时
2166
+ # 不写盘,因此这里提前同步不改变结果,只保证检查看到的是当前表对应的文档。
2167
+ _sync_stage_tables_before_checks(project_root, wf_state, stage_name)
2141
2168
  responsibility_paths = verification_mod.stage_responsibility_paths(
2142
2169
  project_root,
2143
2170
  wf_state,
@@ -3704,6 +3731,21 @@ def cmd_return(args) -> None:
3704
3731
  wf_state.regression_test = state_mod.RegressionTestState()
3705
3732
  # 返回整体验收或最终设计同步:不删除已经通过的回归记录
3706
3733
 
3734
+ # 删除下游结果文件后立即回补引用:指向已删文件的链接是程序自己写进上游文档和
3735
+ # 索引的,不在同一次动作里改回“(待生成)”,退回后第一次门禁就会把它们报成坏链接。
3736
+ reference_detail = ""
3737
+ refreshed = records_mod.refresh_references_after_removal(
3738
+ project_root, wf_state, affected_topics
3739
+ )
3740
+ if refreshed:
3741
+ reference_detail = f"已按当前文件存在情况回补 {len(refreshed)} 份文档的下游引用"
3742
+ bug_detail = bug_record_mod.reset_status_for_return(
3743
+ project_root,
3744
+ wf_state.workflow_id,
3745
+ affected_topics,
3746
+ args.to,
3747
+ )
3748
+
3707
3749
  journal_mod.append_entry(
3708
3750
  project_root,
3709
3751
  "流程退回",
@@ -3711,6 +3753,8 @@ def cmd_return(args) -> None:
3711
3753
  workflow_id=wf_state.workflow_id,
3712
3754
  from_stage=previous_stage,
3713
3755
  to_stage=args.to,
3756
+ references_refreshed=refreshed,
3757
+ bug_status_reset=bug_detail,
3714
3758
  topics=affected_topics,
3715
3759
  reason=args.reason.strip(),
3716
3760
  traceability=trace_detail,
@@ -3725,6 +3769,10 @@ def cmd_return(args) -> None:
3725
3769
  print(f"原因: {args.reason.strip()}")
3726
3770
  if trace_detail:
3727
3771
  print(trace_detail)
3772
+ if reference_detail:
3773
+ print(reference_detail)
3774
+ if bug_detail:
3775
+ print(bug_detail)
3728
3776
  print("未列出的独立主题保留当前测试和验收记录。")
3729
3777
  print_recovery_details(wf_state)
3730
3778
  print_next_step(current_stage_next_instruction(wf_state))
@@ -4242,6 +4290,7 @@ def cmd_gate(args) -> None:
4242
4290
  stage_state.internal_step = "test_code"
4243
4291
  # 写 journal:门禁讨论完毕
4244
4292
  journal_mod.append_entry(project_root, "门禁讨论完毕", "user",
4293
+ workflow_id=wf_state.workflow_id,
4245
4294
  stage=stage_name, passed=True,
4246
4295
  plan_confirmed_hash=stage_state.plan_confirmed_hash
4247
4296
  if stage_name == "impl" else None,
@@ -4450,6 +4499,7 @@ def cmd_gate(args) -> None:
4450
4499
  project_root,
4451
4500
  "门禁代码校验",
4452
4501
  "workflow.py",
4502
+ workflow_id=wf_state.workflow_id,
4453
4503
  stage=stage_name,
4454
4504
  passed=False,
4455
4505
  details=details,
@@ -4529,6 +4579,7 @@ def cmd_gate(args) -> None:
4529
4579
  project_root,
4530
4580
  "门禁代码校验",
4531
4581
  "workflow.py",
4582
+ workflow_id=wf_state.workflow_id,
4532
4583
  stage=stage_name,
4533
4584
  passed=True,
4534
4585
  details=details,
@@ -4996,6 +5047,7 @@ def cmd_gate(args) -> None:
4996
5047
 
4997
5048
  # 写 journal:门禁用户确认
4998
5049
  journal_mod.append_entry(project_root, "门禁用户确认", "user",
5050
+ workflow_id=wf_state.workflow_id,
4999
5051
  stage=stage_name, passed=True)
5000
5052
 
5001
5053
  # 找下一个 stage
@@ -6260,7 +6312,20 @@ def main() -> None:
6260
6312
  except SystemExit:
6261
6313
  raise
6262
6314
  except Exception as exc: # noqa: BLE001
6263
- print(f"错误:命令执行失败({type(exc).__name__}: {exc})")
6315
+ # 异常自带结构化诊断时按统一格式逐项展示,不把已经取得的位置、预期、
6316
+ # 实际、证据、影响和改法压成一行文字(R22)。
6317
+ carried = getattr(exc, "diagnostics", None)
6318
+ if carried:
6319
+ print(f"错误:命令执行失败({type(exc).__name__})")
6320
+ print(
6321
+ diagnostics_mod.ValidationReport(
6322
+ stage=getattr(args, "stage", "") or args.command,
6323
+ gate="命令执行中断",
6324
+ diagnostics=list(carried),
6325
+ ).render()
6326
+ )
6327
+ else:
6328
+ print(f"错误:命令执行失败({type(exc).__name__}: {exc})")
6264
6329
  print("工作流状态没有被本次失败修改;先处理上面指出的原因。")
6265
6330
  print_next_step("由 AI 执行 `workflow status` 查看当前状态;问题无法自行恢复时如实报告用户")
6266
6331
 
@@ -110,6 +110,7 @@ AI 每次执行命令前,先用直白中文说明四件事:
110
110
 
111
111
  某项检查依赖已经失败或缺失的前置事实时,不猜结论,明确写“未检查”,并说明依赖哪个前置问题。全部问题之后只给一条当前可执行的下一命令,同时写清执行者、会修改的状态或文件、成功条件,以及成功后进入的阶段。AI 必须原样单独执行这条命令,不加 `head`、`tail`、管道、分号或 `echo`,避免掩盖真实退出码和完整错误。
112
112
 
113
+ <a id="文档链接要求"></a>
113
114
  ### 文档链接要求
114
115
 
115
116
  只有目标文件已经存在时才写 Markdown 链接。后续阶段尚未生成的文档只写反引号包裹的普通项目相对路径和“(待生成)”;不得创建指向不存在文件的链接。文件生成后,再把对应普通路径改成可导航链接。
@@ -55,6 +55,11 @@ AGENTS_MD_CONTENT = """# Agent 契约
55
55
  - 失败或作废时保留已经发生的本地和外部状态,不自动回滚,并写清已完成、未执行和失败内容。
56
56
  - 执行中发现需要开发时,结束当前简单轮次,说明原因并推荐完整路线;用户确认后再开始新轮次。
57
57
 
58
+ ## 工作记录表
59
+
60
+ - 环节材料列出工作记录表时,AI 在表中填写固定事实(数据按栏目填,叙述一段一条),
61
+ 不手写正式文档的固定标题和表格;正式文档由程序按表生成,改内容只改表。
62
+
58
63
  ## 表达要求
59
64
 
60
65
  AI 回复用户和编写正式文档时: