easy-coding-harness 0.10.0-beta.2 → 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 CHANGED
@@ -6,6 +6,30 @@
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
+
21
+ ## 0.10.0-beta.3
22
+
23
+ - TDD 业务任务的 `VERIFICATION` 门禁收口为本地证据:每个仓库(Canonical 场景下每个
24
+ source task)必须有通过的本地单测,以及达到冻结阈值的本地 JaCoCo changed-line
25
+ coverage;不再要求远程 GitLab pipeline URL、job identity 或成功状态。
26
+ - `ec-tdd-init` 继续在 TDD 关闭态生成并校验参数化 GitLab TEST-stage job、构建配置、
27
+ JaCoCo XML 与 readiness receipt,但远程 CI 只作为项目自动化能力,不再触发 Harness
28
+ 中间提交、推送或等待远程结果。
29
+ - beta.1/beta.2 任务中已有的 `coverage_scope=gitlab` 记录保持原样并从新验收集合中忽略,
30
+ 因此 pending/failed 远程记录不会阻塞升级后的本地门禁;本地 baseline、阈值、报告
31
+ 指纹及 TDD 生命周期/review 约束保持不变。
32
+
9
33
  ## 0.10.0-beta.2
10
34
 
11
35
  - 新增 `ec-tdd-init`,在 TDD 关闭态初始化或刷新 Java 单测执行、JaCoCo XML、GitLab
package/README.md CHANGED
@@ -86,7 +86,10 @@ 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
- - Java TDD 默认关闭;优先级为 session 覆盖 > 项目配置 > `false/90%`。首次开启前必须运行 `ec-tdd-init`,只建设 JUnit/JaCoCo/GitLab 增量覆盖率基础设施,不补存量业务单测;readiness 通过后才允许显式开启。开启后在 ANALYSIS → IMPLEMENT 冻结开关、baseline 与阈值,只验收本任务新增/修改生产代码行,执行 RED/GREEN/REFACTOR(纯重构使用 characterization GREEN → GREEN),并以同一工具执行本地与 GitLab TEST-stage 差异覆盖率门禁。关闭时普通任务不扫描 CI/JaCoCo、不增加命令或提高原工作流验收深度。
89
+ - ANALYSIS 会先通过问答闭合影响技术路线、接口、模型、状态、范围或验收的实质性问题,
90
+ 并在 Dev-Spec 中记录唯一的 `decision_status: closed`。会话只展示核心方案、验收摘要、
91
+ Workflow Mode 与主要风险;完整 `dev-spec.md` 通过绝对本地链接或路径按需查看。
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,也不写任务记忆。
92
95
  - `VERIFICATION` 是验证硬门控:Fast 运行最小充分检查,Standard 运行受影响范围检查,
@@ -190,7 +193,10 @@ easy-coding upgrade
190
193
  项目 Harness 与 CLI 版本完全一致,否则先执行 `easy-coding upgrade` 或更新 CLI);当前
191
194
  session 临时覆盖统一通过 `ec-config` 对话修改。升级到 0.10.0-beta.2 时配置 schema 升至
192
195
  5;未完成 `ec-tdd-init` readiness 的项目/session TDD 请求迁移为关闭并保留阈值,同时
193
- 部署共享 Java 差异覆盖率与 readiness 工具。已经冻结的活动任务合同不会被静默改写。
196
+ 部署共享 Java 差异覆盖率与 readiness 工具。0.10.0-beta.3 起,TDD 业务任务只依赖本地
197
+ 单测与本地差异覆盖率,历史远程 CI 证据保留但不再参与验收。已经冻结的活动任务合同
198
+ 不会被静默改写。0.10.0-beta.4 起,仍停在 ANALYSIS 的旧任务必须补齐决策闭环后才能
199
+ 进入 IMPLEMENT;已经进入后续阶段的任务不受影响。
194
200
 
195
201
  若当前会话不希望 Harness 接管,显式调用 `/ec-no-harness`(Codex 使用
196
202
  `$ec-no-harness`)。它只旁路 Easy Coding,不关闭其他 hooks,也不忽略其他 skills;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-coding-harness",
3
- "version": "0.10.0-beta.2",
3
+ "version": "0.10.0-beta.4",
4
4
  "description": "CLI scaffold for installing Easy Coding harness files into agent-native directories.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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.
@@ -161,7 +161,8 @@ agent must be able to see what was generated and on what evidence.
161
161
  you verified exist (read package.json scripts or equivalent — do not guess).
162
162
  If effective TDD is disabled, do not inspect JaCoCo or GitLab and do not add TDD-specific
163
163
  requirements. If it is enabled for Java, additionally document JUnit/JaCoCo commands,
164
- production/test source roots, XML report paths, and the existing GitLab TEST-stage gate.
164
+ production/test source roots, XML report paths, the local acceptance gate, and the generated
165
+ GitLab TEST-stage job as non-blocking automation infrastructure.
165
166
  5. **Memory migration probe** — Check for old-format memory files:
166
167
  - `.easy-coding/memory/long/MEMORY.md` exists but lacks `memory_schema: 2` frontmatter
167
168
  - `.easy-coding/memory/long/BUSINESS.md` or `TECHNICAL.md` missing
@@ -58,8 +58,9 @@ and config fingerprints, and MEMORY keeps the conditional long-memory threshold
58
58
  Java TDD is a third independent, default-off control managed by `ec-config`. Session overrides
59
59
  project configuration; ANALYSIS freezes enabled state and the 1..100 changed-line threshold
60
60
  (default 90) on entry to IMPLEMENT. Disabled TDD changes no ordinary workflow test depth. Enabled
61
- TDD adds lifecycle evidence, a TDD review dimension, and the same JaCoCo diff gate locally and in
62
- GitLab TEST-stage CI.
61
+ TDD adds lifecycle evidence, a TDD review dimension, passed local unit-test evidence, and a local
62
+ JaCoCo diff gate. `ec-tdd-init` still generates the equivalent GitLab TEST-stage job, but remote
63
+ pipeline execution and status are not Harness acceptance dependencies.
63
64
 
64
65
  The active task pointer lives in `sessions/{agent}-{session-id}.json` (with an agent-prefixed PPID fallback only
65
66
  when a hook payload has no logical session ID);
@@ -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` is required.
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
 
@@ -113,20 +134,24 @@ When TDD is enabled for a Java code task, make `test-strategy.md` record:
113
134
  toward 100% while treating the threshold as the mechanical minimum;
114
135
  - feature/bug RED -> GREEN -> REFACTOR evidence, or for pure refactors a pre-change
115
136
  characterization GREEN -> post-change GREEN sequence without inventing a RED failure;
116
- - `.gitlab-ci.yml` and included configuration, the TEST-stage job, JUnit/JaCoCo artifacts, and an
117
- equivalent changed-line gate. Reuse a gate only when its threshold is at least the configured
118
- value; otherwise include the CI change in the confirmed implementation scope.
137
+ - the local unit-test command and local changed-line acceptance command. Record that
138
+ `ec-tdd-init` generated the GitLab TEST-stage job, but remote execution, pipeline identity, and
139
+ status are non-blocking and never require an intermediate commit or push. Include these exact,
140
+ language-independent contract markers: `local_test_gate: required` and
141
+ `remote_ci_acceptance: non-blocking`.
119
142
  - current `tdd_readiness_status=ready`; if missing or drifted, stop before IMPLEMENT and route to
120
143
  `ec-tdd-init`. Never plan to initialize CI inside an already-enabled TDD feature task.
121
144
 
122
145
  The state API mechanically freezes current Git `HEAD` per repository into `task.tdd_baselines`
123
- when ANALYSIS advances to IMPLEMENT. Plan both local and GitLab commands with that exact SHA and
124
- the frozen threshold; never use a mutable `HEAD` fallback at verification time. Non-Canonical TDD
125
- is limited to one Git repository; multi-repository TDD must use Canonical repository bindings.
146
+ when ANALYSIS advances to IMPLEMENT. Plan the local command with that exact SHA and the frozen
147
+ threshold. The generated GitLab job remains parameterized for infrastructure parity, but the
148
+ Harness acceptance plan never waits for remote CI. Never use a mutable `HEAD` fallback at
149
+ verification time. Non-Canonical TDD is limited to one Git repository; multi-repository TDD must
150
+ use Canonical repository bindings.
126
151
 
127
152
  Also append a `### TDD Mode` section to `dev-spec.md` with enabled state, frozen threshold,
128
- baseline, local gate, GitLab TEST gate, and lifecycle evidence. Do not add this section when TDD
129
- is disabled.
153
+ baseline, local unit-test gate, local coverage gate, generated GitLab job as non-blocking
154
+ infrastructure, and lifecycle evidence. Do not add this section when TDD is disabled.
130
155
 
131
156
  If the task is not a Java project, explain that Java-only TDD cannot be activated and obtain a
132
157
  mode decision before advancing. The CLI never installs JaCoCo or edits CI automatically.
@@ -179,14 +204,22 @@ while still in ANALYSIS.
179
204
 
180
205
  ## User presentation and transition
181
206
 
182
- Before the boundary, present:
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:
183
209
 
184
- - proposed scope and units;
185
- - acceptance and test strategy;
210
+ - the core solution and affected scope/units;
211
+ - acceptance and test-strategy highlights;
186
212
  - configured, minimum, and selected workflow modes with reasons;
187
- - how IMPLEMENT, REVIEW, VERIFICATION, and MEMORY will run;
213
+ - the material risks and explicit acceptance boundaries;
188
214
  - explicit user ability to request a higher mode or a permitted lower mode.
189
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
+
190
223
  Then request or auto-apply ANALYSIS -> IMPLEMENT according to `effective_approval_mode`.
191
224
  The state API atomically freezes the proposal when the transition is applied. `approval_mode`
192
225
  controls waiting; it never changes the selected execution depth.
@@ -197,6 +230,8 @@ controls waiting; it never changes the selected execution depth.
197
230
  - No unresolved skeleton placeholders.
198
231
  - No code task with an empty change scope.
199
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`.
200
235
  - No transition without a valid workflow proposal.
201
236
  - No Canonical-backed transition with changed source SHA, unresolved repository identity,
202
237
  incomplete selected-task coverage, or an open Unit/Step/File/Symbol/Test traceability gap.
@@ -60,7 +60,9 @@ After explicit user selection, use the current logical session file:
60
60
  Turning TDD off must preserve the existing Fast/Standard/Strict test depth exactly: do not inspect
61
61
  CI, request JaCoCo, add TDD artifacts, run coverage commands, or strengthen acceptance criteria.
62
62
  When TDD is on, explain that it applies only to Java code tasks and activates RED/GREEN/REFACTOR,
63
- TDD review, local changed-line JaCoCo coverage, and GitLab TEST-stage gate planning.
63
+ TDD review, a passed local unit-test gate, and local changed-line JaCoCo coverage. `ec-tdd-init`
64
+ still generates the GitLab TEST-stage job, but Harness does not wait for or record remote pipeline
65
+ results as acceptance evidence.
64
66
 
65
67
  Before any project/session enable action, require `tdd_readiness_status=ready`. If it is not ready,
66
68
  offer only `ec-tdd-init` or cancellation; never offer or persist "enable now, initialize later".
@@ -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.
@@ -28,9 +28,10 @@ Name it `{memory_id}_{YYYYMMDD}_{smart_name}.md` and set
28
28
  `.easy-coding/memory/short/`, then register it with
29
29
  `memory-short-complete`. Never invent test results or commit hashes.
30
30
 
31
- When frozen TDD is enabled, add its threshold, lifecycle evidence, local changed-line result,
32
- and remote CI status to the short memory's execution evidence. When TDD is off, omit TDD fields
33
- entirely so ordinary tasks incur no additional memory work.
31
+ When frozen TDD is enabled, add its threshold, lifecycle evidence, passed local unit-test result,
32
+ and local changed-line result to the short memory's execution evidence. Remote CI status is not
33
+ part of Harness acceptance or task memory. When TDD is off, omit TDD fields entirely so ordinary
34
+ tasks incur no additional memory work.
34
35
 
35
36
  Ask the state API for `memory-instruction`. Distill only when it returns `action:distill`;
36
37
  otherwise record `no-op`. Long memory receives reusable facts only, not file dumps, transient
@@ -24,8 +24,10 @@ review dimension. A global record without source ownership cannot satisfy the ga
24
24
  When frozen TDD is enabled, add a passed review dimension named exactly `tdd` for each source
25
25
  task. Review whether RED/GREEN/REFACTOR (or characterization GREEN for pure refactors) is genuine,
26
26
  tests exercise changed behavior and boundaries, mocks do not merely mirror implementation, and
27
- the local/CI changed-line coverage gates share the frozen threshold. When TDD is off, do not add
28
- this dimension or raise the ordinary review depth.
27
+ the local unit-test command genuinely passes while the changed-line coverage command uses the
28
+ frozen baseline and threshold. Generated CI configuration may be reviewed when it changed, but
29
+ remote CI status is never a review or acceptance dependency. When TDD is off, do not add this
30
+ dimension or raise the ordinary review depth.
29
31
 
30
32
  ## Depth by workflow mode
31
33
 
@@ -64,7 +64,9 @@ purpose is to cover unchanged production code.
64
64
 
65
65
  The reusable GitLab job must consume a baseline SHA and threshold supplied for the future task;
66
66
  do not hardcode the initialization commit or the default 90% threshold. The same Python coverage
67
- tool must be used locally and remotely.
67
+ tool must be usable locally and remotely. This generated job is remote automation infrastructure,
68
+ not a Harness task acceptance dependency: later tasks require local unit-test and coverage
69
+ evidence only, and never wait for a pipeline URL, job identity, or remote success status.
68
70
 
69
71
  ## Readiness receipt and verification
70
72
 
@@ -30,8 +30,8 @@ coverage tool, inspect GitLab, or add a coverage record. The explicit `type=tdd-
30
30
  infrastructure exception: run its planned build/CI syntax checks and readiness tool, but do not
31
31
  measure repository-wide coverage or append TDD coverage evidence for unchanged production code.
32
32
 
33
- When frozen TDD is on, first run the planned Java unit command and generate JaCoCo XML, then run
34
- the same deterministic gate intended for GitLab:
33
+ When frozen TDD is on, first run the planned local Java unit command and generate JaCoCo XML,
34
+ then run the deterministic local acceptance gate:
35
35
 
36
36
  ```bash
37
37
  python3 .easy-coding/tools/easy_coding_java_coverage.py check \
@@ -43,10 +43,10 @@ The tool measures covered added/modified production Java executable lines only.
43
43
  comment, blank, import, and test-source lines are excluded by diff/JaCoCo intersection. Missing
44
44
  or ambiguous source files and reports older than their modified source fail; zero modified
45
45
  executable lines is explicit N/A. Always regenerate JaCoCo XML after the final source change.
46
- Record CI as pending until the remote pipeline actually passes; local green is not remote green.
47
46
  Never substitute `HEAD`, a mutable ref, project defaults, or current session settings for the
48
- task-frozen baseline SHA and threshold. GitLab must invoke the tool with the same two frozen
49
- values; a session override therefore requires the CI command/variable for this task to match it.
47
+ task-frozen baseline SHA and threshold. `ec-tdd-init` still generates a GitLab job that can run
48
+ the same tool, but remote pipeline execution and status are outside Harness acceptance. Never
49
+ request an intermediate commit or push merely to obtain CI evidence.
50
50
 
51
51
  The main Agent may run commands inline. Dispatch verifier sub-agents only when checks are
52
52
  independent and parallel execution materially saves time or isolates specialist environments.
@@ -86,23 +86,11 @@ For TDD coverage, copy the tool output into `coverage`: `baseline_sha`, `covered
86
86
  `applicable:false` plus the tool's reason only for zero executable modified lines. A percentage
87
87
  below the frozen threshold fails even when ordinary tests pass.
88
88
 
89
- Append two coverage records per repository (and per Canonical source task): one with
90
- `coverage_scope:"local"`, and one with `coverage_scope:"gitlab"`. The GitLab record may be
91
- appended only after the remote job succeeds and must also include:
92
-
93
- ```json
94
- {
95
- "ci": {
96
- "provider": "gitlab",
97
- "pipeline_url": "https://gitlab.example/.../pipelines/123",
98
- "job_name": "changed-line-coverage",
99
- "status": "success"
100
- }
101
- }
102
- ```
103
-
104
- Both records must preserve the same task-frozen baseline and threshold. A local-only result,
105
- pending/failed pipeline, missing job identity, or synthetic remote pass cannot satisfy MEMORY.
89
+ Append one coverage record with `coverage_scope:"local"` per repository (and per Canonical
90
+ source task). The state gate also requires a passed local `check_type:"test"` record for the same
91
+ owner. The coverage record preserves the task-frozen baseline and threshold. Do not append or
92
+ wait for GitLab pipeline evidence; historical remote coverage records are ignored by acceptance
93
+ without modifying or deleting the stored records.
106
94
 
107
95
  For `type=tdd-init`, the infrastructure receipt must already have been recorded during IMPLEMENT
108
96
  and reviewed with the rest of the implementation. Run only `easy_coding_tdd_readiness.py check`
@@ -53,8 +53,9 @@ First run `ec-init`; daily work goes through `ec-workflow`.
53
53
  there is no enable-now/init-later state. The dedicated `tdd-init` task always freezes TDD off and
54
54
  initializes only changed-line coverage infrastructure, never historical business-test coverage.
55
55
  Disabled TDD adds no CI scan, JaCoCo work, commands, artifacts, or stronger gates. Enabled TDD
56
- applies only to Java code tasks and requires lifecycle, review, local coverage, and GitLab
57
- TEST-stage evidence for production lines changed since the task baseline.
56
+ applies only to Java code tasks and requires lifecycle, review, passed local unit tests, and
57
+ local coverage for production lines changed since the task baseline. `ec-tdd-init` still
58
+ generates GitLab TEST-stage automation, but remote CI status is not Harness acceptance evidence.
58
59
  - Confirmation-required edges use `pending_transition`; automatic edges use the restricted
59
60
  `auto-transition` API. A read-only task creates no test-strategy.md, never enters REVIEW,
60
61
  VERIFICATION, or MEMORY, and writes no task memory.
@@ -75,10 +76,14 @@ First run `ec-init`; daily work goes through `ec-workflow`.
75
76
  only Easy Coding workflow/stage orchestration for this session. Continue honoring every
76
77
  non-Easy-Coding skill, hook, and instruction. Do not clear or mutate the suspended task.
77
78
  - ANALYSIS must follow template-first: read `.easy-coding/templates/dev-spec-skeleton.md` then
78
- write its exact content to the task's dev-spec.md as the FIRST tool calls. Next inspect evidence
79
- without editing the skeleton, ask every unresolved decision during analysis, and wait. Only
80
- after all decisions are resolved may the agent fill the complete dev-spec.md. The final report
81
- contains neither `[阶段:ANALYSIS]` nor a `待用户决策` section.
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.
82
87
  - REVIEW and VERIFICATION are fingerprinted hard gates. Review evidence must match the final
83
88
  implementation; verification evidence must match final implementation and config. The frozen
84
89
  workflow mode selects targeted, impacted, or full commands without weakening the green gate.
@@ -51,8 +51,9 @@ First run `/ec-init`; daily work goes through `/ec-workflow`.
51
51
  there is no enable-now/init-later state. The dedicated `tdd-init` task always freezes TDD off and
52
52
  initializes only changed-line coverage infrastructure, never historical business-test coverage.
53
53
  Disabled TDD adds no CI scan, JaCoCo work, commands, artifacts, or stronger gates. Enabled TDD
54
- applies only to Java code tasks and requires lifecycle, review, local coverage, and GitLab
55
- TEST-stage evidence for production lines changed since the task baseline.
54
+ applies only to Java code tasks and requires lifecycle, review, passed local unit tests, and
55
+ local coverage for production lines changed since the task baseline. `ec-tdd-init` still
56
+ generates GitLab TEST-stage automation, but remote CI status is not Harness acceptance evidence.
56
57
  - Confirmation-required edges use `pending_transition`; automatic edges use the restricted
57
58
  `auto-transition` API. A read-only task creates no test-strategy.md, never enters REVIEW,
58
59
  VERIFICATION, or MEMORY, and writes no task memory.
@@ -73,10 +74,14 @@ First run `/ec-init`; daily work goes through `/ec-workflow`.
73
74
  only Easy Coding workflow/stage orchestration for this session. Continue honoring every
74
75
  non-Easy-Coding skill, hook, and instruction. Do not clear or mutate the suspended task.
75
76
  - ANALYSIS must follow template-first: read `.easy-coding/templates/dev-spec-skeleton.md` then
76
- write its exact content to the task's dev-spec.md as the FIRST tool calls. Next inspect evidence
77
- without editing the skeleton, ask every unresolved decision during analysis, and wait. Only
78
- after all decisions are resolved may the agent fill the complete dev-spec.md. The final report
79
- contains neither `[阶段:ANALYSIS]` nor a `待用户决策` section.
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.
80
85
  - REVIEW and VERIFICATION are fingerprinted hard gates. Review evidence must match the final
81
86
  implementation; verification evidence must match final implementation and config. The frozen
82
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
  "改动文件",
@@ -2526,6 +2540,13 @@ def validate_verification_readiness(root: Path, task_id: str, task: dict) -> Non
2526
2540
  and record.get("config_fingerprint") == fingerprints["config_fingerprint"]
2527
2541
  and is_non_empty_string(record.get("check"))
2528
2542
  ):
2543
+ if (
2544
+ task.get("tdd_enabled") is True
2545
+ and record.get("check_type") == "coverage"
2546
+ and record.get("coverage_scope") == "gitlab"
2547
+ ):
2548
+ # 远程 CI 只作为生成的自动化能力,历史 pending/failed 记录不再参与本地验收。
2549
+ continue
2529
2550
  check = str(record["check"])
2530
2551
  if task.get("tdd_enabled") is True and record.get("check_type") == "coverage":
2531
2552
  check = f"{check}\0{record.get('coverage_scope') or ''}"
@@ -2608,6 +2629,12 @@ def validate_verification_readiness(root: Path, task_id: str, task: dict) -> Non
2608
2629
  )
2609
2630
  if task.get("tdd_enabled") is True:
2610
2631
  require_tdd_readiness(root)
2632
+ test_records = [
2633
+ record
2634
+ for record in latest_by_check.values()
2635
+ if record.get("check_type") == "test"
2636
+ and record.get("applicable") is not False
2637
+ ]
2611
2638
  coverage_records = [
2612
2639
  record
2613
2640
  for record in latest_by_check.values()
@@ -2618,6 +2645,17 @@ def validate_verification_readiness(root: Path, task_id: str, task: dict) -> Non
2618
2645
  "TDD verification requires changed-production-line JaCoCo coverage evidence."
2619
2646
  )
2620
2647
  if is_spec_task:
2648
+ tested_source_tasks = {
2649
+ str(record.get("source_task_id") or "") for record in test_records
2650
+ }
2651
+ missing_test_tasks = sorted(
2652
+ set(task_repositories) - tested_source_tasks
2653
+ )
2654
+ if missing_test_tasks:
2655
+ raise StateError(
2656
+ "TDD Canonical verification requires local unit-test evidence for every selected source task: "
2657
+ + ", ".join(missing_test_tasks)
2658
+ )
2621
2659
  covered_source_tasks = {
2622
2660
  str(record.get("source_task_id") or "") for record in coverage_records
2623
2661
  }
@@ -2629,19 +2667,16 @@ def validate_verification_readiness(root: Path, task_id: str, task: dict) -> Non
2629
2667
  "TDD Canonical verification requires separate coverage evidence for every selected source task: "
2630
2668
  + ", ".join(missing_coverage_tasks)
2631
2669
  )
2632
- coverage_scopes_by_owner: dict[str, set[str]] = {}
2670
+ elif not test_records:
2671
+ raise StateError(
2672
+ "TDD verification requires passed local unit-test evidence."
2673
+ )
2633
2674
  for record in coverage_records:
2634
2675
  scope = str(record.get("coverage_scope") or "")
2635
- if scope not in {"local", "gitlab"}:
2676
+ if scope != "local":
2636
2677
  raise StateError(
2637
- "TDD coverage evidence must identify coverage_scope as local or gitlab."
2678
+ "TDD coverage evidence must identify coverage_scope as local."
2638
2679
  )
2639
- owner = (
2640
- str(record.get("source_task_id") or "")
2641
- if is_spec_task
2642
- else "project"
2643
- )
2644
- coverage_scopes_by_owner.setdefault(owner, set()).add(scope)
2645
2680
  expected_threshold = task.get("tdd_coverage_threshold")
2646
2681
  expected_baselines = task.get("tdd_baselines")
2647
2682
  if (
@@ -2656,18 +2691,6 @@ def validate_verification_readiness(root: Path, task_id: str, task: dict) -> Non
2656
2691
  coverage = record.get("coverage")
2657
2692
  if not isinstance(coverage, dict):
2658
2693
  raise StateError("TDD coverage evidence must include the coverage result object.")
2659
- if record.get("coverage_scope") == "gitlab":
2660
- ci = record.get("ci")
2661
- if (
2662
- not isinstance(ci, dict)
2663
- or ci.get("provider") != "gitlab"
2664
- or ci.get("status") != "success"
2665
- or not is_non_empty_string(ci.get("pipeline_url"))
2666
- or not is_non_empty_string(ci.get("job_name"))
2667
- ):
2668
- raise StateError(
2669
- "GitLab coverage evidence requires a successful pipeline URL and job name."
2670
- )
2671
2694
  total = coverage.get("total_lines")
2672
2695
  covered = coverage.get("covered_lines")
2673
2696
  percentage = coverage.get("percentage")
@@ -2722,18 +2745,6 @@ def validate_verification_readiness(root: Path, task_id: str, task: dict) -> Non
2722
2745
  raise StateError(
2723
2746
  f"TDD changed-line coverage must meet the frozen {threshold}% threshold."
2724
2747
  )
2725
- expected_coverage_owners = set(task_repositories) if is_spec_task else {"project"}
2726
- missing_scopes = [
2727
- f"{owner}:{scope}"
2728
- for owner in sorted(expected_coverage_owners)
2729
- for scope in ("local", "gitlab")
2730
- if scope not in coverage_scopes_by_owner.get(owner, set())
2731
- ]
2732
- if missing_scopes:
2733
- raise StateError(
2734
- "TDD verification requires both local and successful GitLab coverage gates: "
2735
- + ", ".join(missing_scopes)
2736
- )
2737
2748
  if task.get("workflow_mode") == "strict":
2738
2749
  if is_spec_task:
2739
2750
  check_types_by_repository: dict[str, set[str]] = {
@@ -2913,19 +2924,35 @@ def validate_read_only_completion(root: Path, task_id: str) -> None:
2913
2924
  )
2914
2925
 
2915
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
+
2916
2938
  def markdown_headings(content: str) -> list[tuple[int, int, str]]:
2917
2939
  headings: list[tuple[int, int, str]] = []
2918
- fence_marker: str | None = None
2940
+ fence_marker: tuple[str, int] | None = None
2919
2941
  for index, line in enumerate(content.splitlines()):
2920
- stripped = line.lstrip()
2921
- if stripped.startswith(("```", "~~~")):
2922
- marker = stripped[:3]
2923
- if fence_marker is None:
2924
- fence_marker = marker
2925
- elif fence_marker == marker:
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
+ ):
2926
2950
  fence_marker = None
2927
2951
  continue
2928
- if fence_marker is not None:
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)
2929
2956
  continue
2930
2957
  match = MARKDOWN_HEADING_PATTERN.match(line.strip())
2931
2958
  if match:
@@ -2933,6 +2960,55 @@ def markdown_headings(content: str) -> list[tuple[int, int, str]]:
2933
2960
  return headings
2934
2961
 
2935
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
+
2936
3012
  def has_meaningful_markdown_body(content: str) -> bool:
2937
3013
  for line in content.splitlines():
2938
3014
  stripped = line.strip()
@@ -3049,6 +3125,71 @@ def validate_analysis_readiness(
3049
3125
  if "[阶段:ANALYSIS]" in dev_spec_content or "### 待用户决策" in dev_spec_content:
3050
3126
  reasons.append("dev-spec.md contains forbidden analysis-only sections")
3051
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
+
3052
3193
  if not skeleton.exists():
3053
3194
  reasons.append("dev-spec skeleton template is missing")
3054
3195
  else:
@@ -3098,7 +3239,13 @@ def validate_analysis_readiness(
3098
3239
  strategy_content = test_strategy.read_text(encoding="utf-8")
3099
3240
  except OSError:
3100
3241
  strategy_content = ""
3101
- required_tdd_markers = ["TDD", "JaCoCo", "baseline", "GitLab"]
3242
+ required_tdd_markers = [
3243
+ "TDD",
3244
+ "JaCoCo",
3245
+ "baseline",
3246
+ "local_test_gate: required",
3247
+ "remote_ci_acceptance: non-blocking",
3248
+ ]
3102
3249
  missing_tdd_markers = [
3103
3250
  marker for marker in required_tdd_markers if marker.lower() not in strategy_content.lower()
3104
3251
  ]