easy-coding-harness 0.10.0-beta.3 → 0.10.0-beta.4
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.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -1
- package/package.json +1 -1
- package/templates/claude/agents/ec-implementer.md +1 -0
- package/templates/codex/agents/ec-implementer.toml +1 -0
- package/templates/common/skills/ec-analysis/SKILL.md +36 -5
- package/templates/common/skills/ec-implementing/SKILL.md +18 -0
- package/templates/main-constraint/AGENTS.md.tpl +8 -4
- package/templates/main-constraint/CLAUDE.md.tpl +8 -4
- package/templates/qoder/agents/ec-implementer.md +1 -0
- package/templates/runtime/templates/dev-spec-skeleton.md +5 -0
- package/templates/shared-hooks/easy_coding_state.py +152 -8
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
- `y`:常规功能升级
|
|
7
7
|
- `z`:日常 bug 修复
|
|
8
8
|
|
|
9
|
+
## 0.10.0-beta.4
|
|
10
|
+
|
|
11
|
+
- ANALYSIS 完成后不再向会话回贴整份 `dev-spec.md`,改为展示核心方案、验收摘要、
|
|
12
|
+
Workflow Mode 与主要风险,并在客户端支持时提供完整 Dev-Spec 的绝对 Markdown 文件链接,
|
|
13
|
+
不支持本地链接时保留可复制的绝对路径。
|
|
14
|
+
- Dev-Spec 新增结构化“决策闭环”章节;技术路线、接口、模型、状态、范围或验收存在未决问题时,
|
|
15
|
+
Agent 必须停留在 ANALYSIS 逐项问答并回填结论。状态 API 要求唯一的
|
|
16
|
+
`decision_status: closed` 标记,未闭合方案不能进入 IMPLEMENT。
|
|
17
|
+
- `ec-implementing` 新增注释交付门禁:作者署名必须使用当前宿主 Agent 与 Easy Coding 的组合,
|
|
18
|
+
例如 `Codex with Easy Coding`;新增数据模型字段、枚举成员和常量必须逐项说明语义及适用的
|
|
19
|
+
单位、格式、取值、空值或兼容约束。
|
|
20
|
+
|
|
9
21
|
## 0.10.0-beta.3
|
|
10
22
|
|
|
11
23
|
- TDD 业务任务的 `VERIFICATION` 门禁收口为本地证据:每个仓库(Canonical 场景下每个
|
package/README.md
CHANGED
|
@@ -86,6 +86,9 @@ any stage --[user abort via ec-task-close]--> CLOSED
|
|
|
86
86
|
ANALYSIS → IMPLEMENT 确认一次,随后各阶段在质量门禁通过后自动推进,`auto` 从开始即
|
|
87
87
|
自动推进。
|
|
88
88
|
- 工作流模式优先级为 session 覆盖 > 项目 `behavior.workflow_mode` > `adaptive`。Adaptive 在 ANALYSIS 结束时根据风险解析、展示并冻结为 `fast`、`standard` 或 `strict`,用户可在风险下限之上调整。
|
|
89
|
+
- ANALYSIS 会先通过问答闭合影响技术路线、接口、模型、状态、范围或验收的实质性问题,
|
|
90
|
+
并在 Dev-Spec 中记录唯一的 `decision_status: closed`。会话只展示核心方案、验收摘要、
|
|
91
|
+
Workflow Mode 与主要风险;完整 `dev-spec.md` 通过绝对本地链接或路径按需查看。
|
|
89
92
|
- Java TDD 默认关闭;优先级为 session 覆盖 > 项目配置 > `false/90%`。首次开启前必须运行 `ec-tdd-init`,只建设 JUnit/JaCoCo/GitLab 增量覆盖率基础设施,不补存量业务单测;readiness 通过后才允许显式开启。开启后在 ANALYSIS → IMPLEMENT 冻结开关、baseline 与阈值,只验收本任务新增/修改生产代码行,执行 RED/GREEN/REFACTOR(纯重构使用 characterization GREEN → GREEN),并要求本地单测通过、本地差异覆盖率达到冻结阈值。GitLab TEST-stage job 仍会生成,但远程 pipeline 结果不属于 Harness 验收证据,也不会触发中间提交推送。关闭时普通任务不扫描 CI/JaCoCo、不增加命令或提高原工作流验收深度。
|
|
90
93
|
- 所有新代码任务都完整进入 REVIEW;不同工作流模式只调整各状态内部的上下文加载、执行主体、审查独立性、验证范围和记忆深度,不绕过状态或证据门禁。
|
|
91
94
|
- 显式 `doc` / `analysis` / `report` 只读任务不生成 `test-strategy.md`;展示完整报告后按生效模式进入 COMPLETE,不执行 REVIEW、VERIFICATION 或 MEMORY,也不写任务记忆。
|
|
@@ -192,7 +195,8 @@ session 临时覆盖统一通过 `ec-config` 对话修改。升级到 0.10.0-bet
|
|
|
192
195
|
5;未完成 `ec-tdd-init` readiness 的项目/session TDD 请求迁移为关闭并保留阈值,同时
|
|
193
196
|
部署共享 Java 差异覆盖率与 readiness 工具。0.10.0-beta.3 起,TDD 业务任务只依赖本地
|
|
194
197
|
单测与本地差异覆盖率,历史远程 CI 证据保留但不再参与验收。已经冻结的活动任务合同
|
|
195
|
-
不会被静默改写。
|
|
198
|
+
不会被静默改写。0.10.0-beta.4 起,仍停在 ANALYSIS 的旧任务必须补齐决策闭环后才能
|
|
199
|
+
进入 IMPLEMENT;已经进入后续阶段的任务不受影响。
|
|
196
200
|
|
|
197
201
|
若当前会话不希望 Harness 接管,显式调用 `/ec-no-harness`(Codex 使用
|
|
198
202
|
`$ec-no-harness`)。它只旁路 Easy Coding,不关闭其他 hooks,也不忽略其他 skills;
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ complete exactly that unit. Your reply IS the return value, not a message to a h
|
|
|
16
16
|
file. All context you need is already in the task card.
|
|
17
17
|
- Make no workflow stage-transition decisions. You do not know the state machine exists.
|
|
18
18
|
- Follow the coding rules and architecture context embedded in the card.
|
|
19
|
+
- Treat the task card's `Code Comments` author value and field/member/constant rules as mandatory.
|
|
19
20
|
- Treat acceptance criteria, test points, contracts, and risks in the card as required inputs.
|
|
20
21
|
- Run the exact targeted checks requested by the card and report their real outcome.
|
|
21
22
|
- Preserve each existing file's original encoding; never silently convert.
|
|
@@ -15,6 +15,7 @@ Hard constraints:
|
|
|
15
15
|
the card.
|
|
16
16
|
- Make no workflow stage-transition decisions; you do not know the state machine exists.
|
|
17
17
|
- Follow the coding rules and architecture context embedded in the card.
|
|
18
|
+
- Treat the task card's "Code Comments" author value and field/member/constant rules as mandatory.
|
|
18
19
|
- Treat acceptance criteria, test points, contracts, and risks in the card as required inputs.
|
|
19
20
|
- Run the exact targeted checks requested by the card and report their real outcome.
|
|
20
21
|
- Preserve each existing file's original encoding; never silently convert.
|
|
@@ -42,7 +42,28 @@ read. `scope-drifted` requires current-code conflict analysis before confirmatio
|
|
|
42
42
|
## Analysis artifacts
|
|
43
43
|
|
|
44
44
|
Copy `.easy-coding/templates/dev-spec-skeleton.md` first, then replace every `[[EC_TODO:...]]`.
|
|
45
|
-
Keep every mandatory section. `### Workflow Mode`
|
|
45
|
+
Keep every mandatory section. The `### 决策闭环` (Decision Closure) and `### Workflow Mode`
|
|
46
|
+
sections are required. The decision section must contain exactly one standalone
|
|
47
|
+
`decision_status: closed` marker, and no other `decision_status` marker may appear elsewhere in
|
|
48
|
+
the document. Record every material question and its resolved conclusion in that section, or
|
|
49
|
+
record that no extra decision was needed.
|
|
50
|
+
|
|
51
|
+
## Decision closure before implementation
|
|
52
|
+
|
|
53
|
+
Treat uncertainty that can change the technical route, public or internal contract, data model,
|
|
54
|
+
state flow, edit scope, compatibility behavior, or acceptance criteria as a material open
|
|
55
|
+
question. While any such question remains:
|
|
56
|
+
|
|
57
|
+
1. stay in ANALYSIS and ask the user focused questions, preferably one decision at a time;
|
|
58
|
+
2. do not present a final analysis summary, propose the final Workflow Mode, request
|
|
59
|
+
ANALYSIS -> IMPLEMENT, or suggest that implementation can begin;
|
|
60
|
+
3. update the Dev-Spec with each confirmed answer and its evidence;
|
|
61
|
+
4. use `decision_status: open` while the artifact is still being developed, then replace it with
|
|
62
|
+
the single `decision_status: closed` marker only after every material question is resolved.
|
|
63
|
+
|
|
64
|
+
Risks, integration work that is intentionally deferred by a frozen Spec, and environmental
|
|
65
|
+
verification limits are not automatically open questions. Describe them as risks or explicit
|
|
66
|
+
acceptance boundaries. Never use `closed` to hide a decision that still needs the user.
|
|
46
67
|
|
|
47
68
|
Execution plan records use:
|
|
48
69
|
|
|
@@ -183,14 +204,22 @@ while still in ANALYSIS.
|
|
|
183
204
|
|
|
184
205
|
## User presentation and transition
|
|
185
206
|
|
|
186
|
-
|
|
207
|
+
After decision closure and before the boundary, present a concise session summary instead of
|
|
208
|
+
pasting the full `dev-spec.md`. The summary must contain:
|
|
187
209
|
|
|
188
|
-
-
|
|
189
|
-
- acceptance and test
|
|
210
|
+
- the core solution and affected scope/units;
|
|
211
|
+
- acceptance and test-strategy highlights;
|
|
190
212
|
- configured, minimum, and selected workflow modes with reasons;
|
|
191
|
-
-
|
|
213
|
+
- the material risks and explicit acceptance boundaries;
|
|
192
214
|
- explicit user ability to request a higher mode or a permitted lower mode.
|
|
193
215
|
|
|
216
|
+
End the summary with the absolute path to
|
|
217
|
+
`.easy-coding/tasks/<task-id>/dev-spec.md`. When the current client supports local-file Markdown
|
|
218
|
+
links, render `[View full Dev-Spec](</absolute/path/to/dev-spec.md>)`; otherwise print the
|
|
219
|
+
copyable absolute path. Do not dump the full artifact merely because the client cannot link it.
|
|
220
|
+
If the user asks to inspect the full plan, open or read that stored file on demand using the
|
|
221
|
+
current Agent's supported file capability.
|
|
222
|
+
|
|
194
223
|
Then request or auto-apply ANALYSIS -> IMPLEMENT according to `effective_approval_mode`.
|
|
195
224
|
The state API atomically freezes the proposal when the transition is applied. `approval_mode`
|
|
196
225
|
controls waiting; it never changes the selected execution depth.
|
|
@@ -201,6 +230,8 @@ controls waiting; it never changes the selected execution depth.
|
|
|
201
230
|
- No unresolved skeleton placeholders.
|
|
202
231
|
- No code task with an empty change scope.
|
|
203
232
|
- No unit without acceptance criteria, test points, contracts, and risks.
|
|
233
|
+
- No final summary, workflow proposal, or transition while a material decision is unresolved.
|
|
234
|
+
- No transition without exactly one `decision_status: closed` marker in `dev-spec.md`.
|
|
204
235
|
- No transition without a valid workflow proposal.
|
|
205
236
|
- No Canonical-backed transition with changed source SHA, unresolved repository identity,
|
|
206
237
|
incomplete selected-task coverage, or an open Unit/Step/File/Symbol/Test traceability gap.
|
|
@@ -36,6 +36,17 @@ Communicate with the user in the user's language.
|
|
|
36
36
|
enters REVIEW.
|
|
37
37
|
7. Read-only `doc` / `analysis` / `report` tasks remain `single` with `files:[]`, make no writes,
|
|
38
38
|
return a non-empty `deliverable`, then follow the mode-aware IMPLEMENT -> COMPLETE edge.
|
|
39
|
+
8. When a project template, local convention, or new source header uses author attribution, the
|
|
40
|
+
author value must be `<Current Agent Name> with Easy Coding`, for example
|
|
41
|
+
`Codex with Easy Coding`. `Current Agent Name` means the user-facing host Agent (for example,
|
|
42
|
+
Codex, Claude, or Qoder), never an implementation sub-agent role such as `ec-implementer`.
|
|
43
|
+
Never copy a previous human or Agent name into newly authored code.
|
|
44
|
+
9. Every newly added field in a data-bearing model must have a meaningful field-level comment.
|
|
45
|
+
This includes new or extended entity/DO/DTO/VO/BO, request/response, configuration, and similar
|
|
46
|
+
model types. Every new enum member and every new declared constant requires the same treatment.
|
|
47
|
+
Describe the semantic meaning and, when relevant, units, format, allowed values, nullability,
|
|
48
|
+
default behavior, or compatibility constraints. A type-level comment does not replace comments
|
|
49
|
+
on its fields or members; do not add low-value comments to ordinary local variables.
|
|
39
50
|
|
|
40
51
|
## Choose the execution owner
|
|
41
52
|
|
|
@@ -81,6 +92,7 @@ Sub-agents never dispatch other sub-agents or read `.easy-coding` workflow asset
|
|
|
81
92
|
## Test Points {unit.test_points and exact targeted commands}
|
|
82
93
|
## Contracts {inputs, outputs, invariants shared with other units}
|
|
83
94
|
## Risks {known edge cases and compatibility risks}
|
|
95
|
+
## Code Comments {resolved host Agent author value; model-field, enum-member, and constant rules}
|
|
84
96
|
## Coding Rules {pre-digested RULES sections}
|
|
85
97
|
## Architecture {pre-digested ABSTRACT sections}
|
|
86
98
|
## Output
|
|
@@ -92,8 +104,12 @@ deliverable|null, issues:[], needs_attention:[]
|
|
|
92
104
|
|
|
93
105
|
1. Append a `dispatch` record before work begins. Canonical-backed records include `repo_id` and
|
|
94
106
|
`source_task_id`; resolve every file relative to `task.repo_paths[repo_id]` before dispatch.
|
|
107
|
+
Populate `Code Comments` on every code task card with the resolved user-facing host Agent
|
|
108
|
+
author value and the field/member/constant rules above; sub-agents do not read this Skill.
|
|
95
109
|
2. Execute according to dependency order and selected owner.
|
|
96
110
|
3. Run targeted unit tests and self-audit scope, contracts, TODOs, and introduced warnings.
|
|
111
|
+
Also audit new author attributions and every new model field, enum member, and constant against
|
|
112
|
+
the comment requirements above before recording success.
|
|
97
113
|
4. Append one `result` record. Only a successful unit uses `status:"completed"`; include
|
|
98
114
|
unresolved issues rather than hiding them, and do not advance while `issues` or
|
|
99
115
|
`needs_attention` is non-empty.
|
|
@@ -119,4 +135,6 @@ conversation overhead while keeping work observable.
|
|
|
119
135
|
- [ ] Every unit has a dispatch/result pair and satisfied its acceptance criteria.
|
|
120
136
|
- [ ] Targeted tests ran or a concrete blocker is recorded.
|
|
121
137
|
- [ ] Cross-unit contracts still match.
|
|
138
|
+
- [ ] New author attributions use the user-facing host `<Current Agent Name> with Easy Coding`.
|
|
139
|
+
- [ ] Every new model field, enum member, and constant has a meaningful field-level comment.
|
|
122
140
|
- [ ] Code tasks enter REVIEW, regardless of workflow mode.
|
|
@@ -76,10 +76,14 @@ First run `ec-init`; daily work goes through `ec-workflow`.
|
|
|
76
76
|
only Easy Coding workflow/stage orchestration for this session. Continue honoring every
|
|
77
77
|
non-Easy-Coding skill, hook, and instruction. Do not clear or mutate the suspended task.
|
|
78
78
|
- ANALYSIS must follow template-first: read `.easy-coding/templates/dev-spec-skeleton.md` then
|
|
79
|
-
write its exact content to the task's dev-spec.md as the FIRST tool calls. Next inspect evidence
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
write its exact content to the task's dev-spec.md as the FIRST tool calls. Next inspect evidence,
|
|
80
|
+
set `decision_status: open`, ask every unresolved material decision, and progressively record
|
|
81
|
+
each confirmed answer and its evidence in `### 决策闭环`. Only after all material decisions are
|
|
82
|
+
resolved may the agent set the single `decision_status: closed`, finalize the artifacts, and
|
|
83
|
+
propose IMPLEMENT. The session presentation is a concise core-solution, acceptance, workflow,
|
|
84
|
+
and risk summary with an absolute local link/path to the full dev-spec.md; never paste the full
|
|
85
|
+
artifact by default. The final artifact contains neither `[阶段:ANALYSIS]` nor a
|
|
86
|
+
`待用户决策` section.
|
|
83
87
|
- REVIEW and VERIFICATION are fingerprinted hard gates. Review evidence must match the final
|
|
84
88
|
implementation; verification evidence must match final implementation and config. The frozen
|
|
85
89
|
workflow mode selects targeted, impacted, or full commands without weakening the green gate.
|
|
@@ -74,10 +74,14 @@ First run `/ec-init`; daily work goes through `/ec-workflow`.
|
|
|
74
74
|
only Easy Coding workflow/stage orchestration for this session. Continue honoring every
|
|
75
75
|
non-Easy-Coding skill, hook, and instruction. Do not clear or mutate the suspended task.
|
|
76
76
|
- ANALYSIS must follow template-first: read `.easy-coding/templates/dev-spec-skeleton.md` then
|
|
77
|
-
write its exact content to the task's dev-spec.md as the FIRST tool calls. Next inspect evidence
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
write its exact content to the task's dev-spec.md as the FIRST tool calls. Next inspect evidence,
|
|
78
|
+
set `decision_status: open`, ask every unresolved material decision, and progressively record
|
|
79
|
+
each confirmed answer and its evidence in `### 决策闭环`. Only after all material decisions are
|
|
80
|
+
resolved may the agent set the single `decision_status: closed`, finalize the artifacts, and
|
|
81
|
+
propose IMPLEMENT. The session presentation is a concise core-solution, acceptance, workflow,
|
|
82
|
+
and risk summary with an absolute local link/path to the full dev-spec.md; never paste the full
|
|
83
|
+
artifact by default. The final artifact contains neither `[阶段:ANALYSIS]` nor a
|
|
84
|
+
`待用户决策` section.
|
|
81
85
|
- REVIEW and VERIFICATION are fingerprinted hard gates. Review evidence must match the final
|
|
82
86
|
implementation; verification evidence must match final implementation and config. The frozen
|
|
83
87
|
workflow mode selects targeted, impacted, or full commands without weakening the green gate.
|
|
@@ -18,6 +18,7 @@ complete exactly that unit. Your reply IS the return value, not a message to a h
|
|
|
18
18
|
context is in the card.
|
|
19
19
|
- Make no workflow stage-transition decisions; you do not know the state machine exists.
|
|
20
20
|
- Follow the coding rules and architecture context embedded in the card.
|
|
21
|
+
- Treat the task card's `Code Comments` author value and field/member/constant rules as mandatory.
|
|
21
22
|
- Treat acceptance criteria, test points, contracts, and risks in the card as required inputs.
|
|
22
23
|
- Run the exact targeted checks requested by the card and report their real outcome.
|
|
23
24
|
- Preserve each existing file's original encoding; never silently convert.
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
- 需求 vs 现有代码:[[EC_TODO:填写结果或“无冲突”]]
|
|
25
25
|
- Dev-Spec vs 现有代码:[[EC_TODO:填写结果或“无冲突”]]
|
|
26
26
|
|
|
27
|
+
### 决策闭环
|
|
28
|
+
decision_status: [[EC_TODO:仅当所有实质性问题均已解决并回填后写 closed]]
|
|
29
|
+
- **已解决问题与结论**:[[EC_TODO:逐项记录影响技术路线、接口、模型、状态、范围或验收的问题及最终结论;无则写“无”]]
|
|
30
|
+
- **确认依据**:[[EC_TODO:用户答复、冻结 Spec、现有代码证据或“无额外决策”]]
|
|
31
|
+
|
|
27
32
|
### Canonical Spec 来源
|
|
28
33
|
- **来源**:[[EC_TODO:非 Canonical 任务写“无”;否则填写 repo-relative path、spec_id、revision、SHA-256]]
|
|
29
34
|
- **选择任务 / 仓库**:[[EC_TODO:非 Canonical 任务写“无”;否则填写 selected task IDs 与 repo IDs]]
|
|
@@ -38,6 +38,7 @@ MANDATORY_DEV_SPEC_HEADERS: list[str] = [
|
|
|
38
38
|
"### 需求解析",
|
|
39
39
|
"### 现状",
|
|
40
40
|
"### 冲突摘要",
|
|
41
|
+
"### 决策闭环",
|
|
41
42
|
"### 影响面分析",
|
|
42
43
|
"### 改动范围",
|
|
43
44
|
"### 修改方案",
|
|
@@ -117,6 +118,19 @@ SHORT_MEMORY_UUID_V7_PATTERN = re.compile(
|
|
|
117
118
|
)
|
|
118
119
|
LEGACY_SHORT_MEMORY_ID_PATTERN = re.compile(r"^SM-\d{8}-\d+$")
|
|
119
120
|
DEV_SPEC_PLACEHOLDER_PATTERN = re.compile(r"\[\[EC_TODO:[^\]\n]+\]\]")
|
|
121
|
+
DECISION_STATUS_PATTERN = re.compile(
|
|
122
|
+
r"\s*decision_status\s*:\s*([a-z][a-z0-9_-]*)\s*", re.IGNORECASE
|
|
123
|
+
)
|
|
124
|
+
DECISION_CONCLUSIONS_PATTERN = re.compile(
|
|
125
|
+
r"\s*(?:[-+*]\s+)?(?:\*\*)?已解决问题与结论(?:\*\*)?\s*[::]\s*(.+?)\s*"
|
|
126
|
+
)
|
|
127
|
+
DECISION_EVIDENCE_PATTERN = re.compile(
|
|
128
|
+
r"\s*(?:[-+*]\s+)?(?:\*\*)?确认依据(?:\*\*)?\s*[::]\s*(.+?)\s*"
|
|
129
|
+
)
|
|
130
|
+
UNRESOLVED_DECISION_VALUE_PATTERN = re.compile(
|
|
131
|
+
r"(?:待确认|待决策|未确认|未决|todo|tbd|unknown|open|pending|unresolved)[。.!!]?",
|
|
132
|
+
re.IGNORECASE,
|
|
133
|
+
)
|
|
120
134
|
MARKDOWN_HEADING_PATTERN = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
|
|
121
135
|
TABLE_HEADER_CELLS = {
|
|
122
136
|
"改动文件",
|
|
@@ -2910,19 +2924,35 @@ def validate_read_only_completion(root: Path, task_id: str) -> None:
|
|
|
2910
2924
|
)
|
|
2911
2925
|
|
|
2912
2926
|
|
|
2927
|
+
def markdown_fence_token(line: str) -> tuple[str, int, str] | None:
|
|
2928
|
+
stripped = line.lstrip()
|
|
2929
|
+
if not stripped or stripped[0] not in {"`", "~"}:
|
|
2930
|
+
return None
|
|
2931
|
+
marker = stripped[0]
|
|
2932
|
+
run_length = len(stripped) - len(stripped.lstrip(marker))
|
|
2933
|
+
if run_length < 3:
|
|
2934
|
+
return None
|
|
2935
|
+
return marker, run_length, stripped[run_length:]
|
|
2936
|
+
|
|
2937
|
+
|
|
2913
2938
|
def markdown_headings(content: str) -> list[tuple[int, int, str]]:
|
|
2914
2939
|
headings: list[tuple[int, int, str]] = []
|
|
2915
|
-
fence_marker: str | None = None
|
|
2940
|
+
fence_marker: tuple[str, int] | None = None
|
|
2916
2941
|
for index, line in enumerate(content.splitlines()):
|
|
2917
|
-
|
|
2918
|
-
if
|
|
2919
|
-
marker =
|
|
2920
|
-
if
|
|
2921
|
-
|
|
2922
|
-
|
|
2942
|
+
fence = markdown_fence_token(line)
|
|
2943
|
+
if fence_marker is not None:
|
|
2944
|
+
marker, run_length, remainder = fence or ("", 0, "")
|
|
2945
|
+
if (
|
|
2946
|
+
marker == fence_marker[0]
|
|
2947
|
+
and run_length >= fence_marker[1]
|
|
2948
|
+
and not remainder.strip()
|
|
2949
|
+
):
|
|
2923
2950
|
fence_marker = None
|
|
2924
2951
|
continue
|
|
2925
|
-
if
|
|
2952
|
+
if fence is not None:
|
|
2953
|
+
marker, run_length, remainder = fence
|
|
2954
|
+
if marker != "`" or "`" not in remainder:
|
|
2955
|
+
fence_marker = (marker, run_length)
|
|
2926
2956
|
continue
|
|
2927
2957
|
match = MARKDOWN_HEADING_PATTERN.match(line.strip())
|
|
2928
2958
|
if match:
|
|
@@ -2930,6 +2960,55 @@ def markdown_headings(content: str) -> list[tuple[int, int, str]]:
|
|
|
2930
2960
|
return headings
|
|
2931
2961
|
|
|
2932
2962
|
|
|
2963
|
+
def markdown_section_body(content: str, title: str, level: int = 3) -> str | None:
|
|
2964
|
+
lines = content.splitlines()
|
|
2965
|
+
headings = markdown_headings(content)
|
|
2966
|
+
heading_index = next(
|
|
2967
|
+
(
|
|
2968
|
+
index
|
|
2969
|
+
for index, (_, heading_level, heading_title) in enumerate(headings)
|
|
2970
|
+
if heading_level == level and heading_title == title
|
|
2971
|
+
),
|
|
2972
|
+
None,
|
|
2973
|
+
)
|
|
2974
|
+
if heading_index is None:
|
|
2975
|
+
return None
|
|
2976
|
+
line_index, heading_level, _ = headings[heading_index]
|
|
2977
|
+
next_line_index = len(lines)
|
|
2978
|
+
for candidate_line, candidate_level, _ in headings[heading_index + 1 :]:
|
|
2979
|
+
if candidate_level <= heading_level:
|
|
2980
|
+
next_line_index = candidate_line
|
|
2981
|
+
break
|
|
2982
|
+
return "\n".join(lines[line_index + 1 : next_line_index])
|
|
2983
|
+
|
|
2984
|
+
|
|
2985
|
+
def markdown_standalone_field_values(content: str, pattern: re.Pattern[str]) -> list[str]:
|
|
2986
|
+
values: list[str] = []
|
|
2987
|
+
fence_marker: tuple[str, int] | None = None
|
|
2988
|
+
for line in content.splitlines():
|
|
2989
|
+
fence = markdown_fence_token(line)
|
|
2990
|
+
if fence_marker is not None:
|
|
2991
|
+
marker, run_length, remainder = fence or ("", 0, "")
|
|
2992
|
+
if (
|
|
2993
|
+
marker == fence_marker[0]
|
|
2994
|
+
and run_length >= fence_marker[1]
|
|
2995
|
+
and not remainder.strip()
|
|
2996
|
+
):
|
|
2997
|
+
fence_marker = None
|
|
2998
|
+
continue
|
|
2999
|
+
if fence is not None:
|
|
3000
|
+
marker, run_length, remainder = fence
|
|
3001
|
+
if marker != "`" or "`" not in remainder:
|
|
3002
|
+
fence_marker = (marker, run_length)
|
|
3003
|
+
continue
|
|
3004
|
+
if line.startswith(("\t", " ")):
|
|
3005
|
+
continue
|
|
3006
|
+
match = pattern.fullmatch(line)
|
|
3007
|
+
if match:
|
|
3008
|
+
values.append(match.group(1).strip())
|
|
3009
|
+
return values
|
|
3010
|
+
|
|
3011
|
+
|
|
2933
3012
|
def has_meaningful_markdown_body(content: str) -> bool:
|
|
2934
3013
|
for line in content.splitlines():
|
|
2935
3014
|
stripped = line.strip()
|
|
@@ -3046,6 +3125,71 @@ def validate_analysis_readiness(
|
|
|
3046
3125
|
if "[阶段:ANALYSIS]" in dev_spec_content or "### 待用户决策" in dev_spec_content:
|
|
3047
3126
|
reasons.append("dev-spec.md contains forbidden analysis-only sections")
|
|
3048
3127
|
|
|
3128
|
+
decision_headings = [
|
|
3129
|
+
heading
|
|
3130
|
+
for heading in markdown_headings(dev_spec_content)
|
|
3131
|
+
if heading[1] == 3 and heading[2] == "决策闭环"
|
|
3132
|
+
]
|
|
3133
|
+
if len(decision_headings) != 1:
|
|
3134
|
+
reasons.append(
|
|
3135
|
+
"dev-spec.md must contain exactly one `### 决策闭环` section; "
|
|
3136
|
+
f"found {len(decision_headings)}"
|
|
3137
|
+
)
|
|
3138
|
+
decision_section = markdown_section_body(dev_spec_content, "决策闭环") or ""
|
|
3139
|
+
all_decision_statuses = [
|
|
3140
|
+
value.lower()
|
|
3141
|
+
for value in markdown_standalone_field_values(
|
|
3142
|
+
dev_spec_content, DECISION_STATUS_PATTERN
|
|
3143
|
+
)
|
|
3144
|
+
]
|
|
3145
|
+
section_decision_statuses = [
|
|
3146
|
+
value.lower()
|
|
3147
|
+
for value in markdown_standalone_field_values(
|
|
3148
|
+
decision_section, DECISION_STATUS_PATTERN
|
|
3149
|
+
)
|
|
3150
|
+
]
|
|
3151
|
+
if not all_decision_statuses:
|
|
3152
|
+
reasons.append(
|
|
3153
|
+
"dev-spec.md is missing the decision closure marker `decision_status: closed`; "
|
|
3154
|
+
"resume ec-analysis, resolve material questions, and record the conclusions first"
|
|
3155
|
+
)
|
|
3156
|
+
elif len(all_decision_statuses) != 1:
|
|
3157
|
+
reasons.append(
|
|
3158
|
+
"dev-spec.md must contain exactly one decision_status marker; "
|
|
3159
|
+
f"found {len(all_decision_statuses)}"
|
|
3160
|
+
)
|
|
3161
|
+
elif len(section_decision_statuses) != 1:
|
|
3162
|
+
reasons.append(
|
|
3163
|
+
"dev-spec.md decision_status marker must be inside the `### 决策闭环` section"
|
|
3164
|
+
)
|
|
3165
|
+
elif section_decision_statuses[0] != "closed":
|
|
3166
|
+
reasons.append(
|
|
3167
|
+
"dev-spec.md has unresolved material decisions: "
|
|
3168
|
+
f"decision_status is {section_decision_statuses[0]!r}, expected 'closed'"
|
|
3169
|
+
)
|
|
3170
|
+
decision_conclusions = markdown_standalone_field_values(
|
|
3171
|
+
decision_section, DECISION_CONCLUSIONS_PATTERN
|
|
3172
|
+
)
|
|
3173
|
+
decision_evidence = markdown_standalone_field_values(
|
|
3174
|
+
decision_section, DECISION_EVIDENCE_PATTERN
|
|
3175
|
+
)
|
|
3176
|
+
for field_name, values in (
|
|
3177
|
+
("已解决问题与结论", decision_conclusions),
|
|
3178
|
+
("确认依据", decision_evidence),
|
|
3179
|
+
):
|
|
3180
|
+
if len(values) != 1:
|
|
3181
|
+
reasons.append(
|
|
3182
|
+
"dev-spec.md decision closure must contain exactly one non-empty "
|
|
3183
|
+
f"`{field_name}` field; found {len(values)}"
|
|
3184
|
+
)
|
|
3185
|
+
elif UNRESOLVED_DECISION_VALUE_PATTERN.fullmatch(
|
|
3186
|
+
re.sub(r"[`*_]", "", values[0]).strip()
|
|
3187
|
+
):
|
|
3188
|
+
reasons.append(
|
|
3189
|
+
"dev-spec.md has unresolved decision closure evidence: "
|
|
3190
|
+
f"`{field_name}` is {values[0]!r}"
|
|
3191
|
+
)
|
|
3192
|
+
|
|
3049
3193
|
if not skeleton.exists():
|
|
3050
3194
|
reasons.append("dev-spec skeleton template is missing")
|
|
3051
3195
|
else:
|