agent-project-sdlc 0.1.11 → 0.1.13

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/README.md CHANGED
@@ -76,10 +76,22 @@ The CLI does not promise to automatically launch Codex agents. Workers do not ne
76
76
 
77
77
  Every stage's agent work is plan-controlled. Conversational PRD or design creation, existing document slicing, fact-source-based synthesis, development, review, testing, release preparation and RFC recalibration should create or resume one small `TASK-*` task in `plan.yaml` with a valid `phase`, write the current task's `result_docs` or `implementation_doc`, update indexes/overviews, run `validate-plan`, and remove the task after completion. Phase exit validators reject remaining open tasks.
78
78
 
79
+ The generic rule is that any workflow promoting a draft task into a formal `TASK-*` in `plan.yaml` must remove the source draft from its draft queue in the same state update. The formal task is then recovered only from `plan.yaml`; completed history lives in implementation docs, git, PR and CI records. The built-in Harness draft queue is currently `plan.draft.yaml.tasks[]`, which means unadopted development drafts only. `/devloop` treats the development queue as exhausted only when both `plan.yaml.tasks[]` and `plan.draft.yaml.tasks[]` have no executable task.
80
+
79
81
  Before development starts, `ARCHITECTING` can return to `REQUIREMENT_GATHERING` for PRD edits. The manager uses `python3 tools/transition.py --to REQUIREMENT_GATHERING`, the PM workflow updates the PRD through one `TASK-*`, then `validate-pm` and `python3 tools/transition.py --to ARCHITECTING` return the project to design. Requirement changes after `SPRINTING` still use RFC recalibration.
80
82
 
81
83
  `validate-design` treats semantic slicing as a hard gate. Generated `overview.md` files do not count as deliverables, development draft tasks in `plan.draft.yaml` must reference existing tech plan slices through `docs.tech_plan`, multiple development draft tasks need distinct primary tech plan slices, and explicit AI provider/copilot, external-system, or compliance/permission/audit themes require dedicated architecture slices.
82
84
 
85
+ SPRINTING Definition of Done includes runnable entry/exit boundaries. API, CLI, server route, adapter, worker, provider, config-contract and fixture/live boundaries promised by a technical plan or task must be implemented or marked `BLOCKED` during development. REVIEWING treats missing entry/exit as blocking, and TESTING only exercises existing entrypoints; it must not add product runtime, bootstrap, provider adapter, deploy code or package runtime scripts.
86
+
87
+ `validate-test` keeps its command name as the TESTING phase gate. The canonical TESTING deliverable is `.docs/07_test/TEST_REPORT.md`, which records test matrix, regression evidence, runnable entry/exit coverage, coverage gaps and final decision. Legacy `.docs/07_test/TEST_PLAN.md` remains accepted for existing projects, but new test evidence should use `TEST_REPORT.md`.
88
+
89
+ ## ADR And Memory Boundaries
90
+
91
+ `.docs/05_decisions/` stores ADRs, or Architecture Decision Records. ADRs answer why a key architecture choice was made instead of another option. Architecture and tech plan slices may include local design rationale; create an ADR when a decision has real alternatives, affects multiple modules or stages, is likely to be challenged later, or would be expensive to reverse.
92
+
93
+ `<harnessRoot>/state/memory.md` is only a short cross-stage reminder and navigation surface. It answers what an agent should remember next time and where to find the source. Memory may link to ADRs, PRDs, tech plans or implementation docs; full context, alternatives, tradeoffs and long-term consequences belong in `.docs/05_decisions/` ADRs or other formal `.docs/**` fact sources.
94
+
83
95
  ## Common Commands
84
96
 
85
97
  ```sh
@@ -34,10 +34,12 @@
34
34
  - `current_phase` 只保存在 `lifecycle.yaml`;不要在 `plan.yaml`、`plan.draft.yaml` 或 `parallel_execution` 中重复保存当前阶段。
35
35
  - 新建任务统一使用 `TASK-*` id,并通过 `phase` 标明属于 `REQUIREMENT_GATHERING`、`ARCHITECTING`、`SPRINTING`、`REVIEWING`、`TESTING`、`RELEASING` 或 `RFC_RECALIBRATION`;历史 `PRD-*`、`DES-*`、`DEV-*` 只作为兼容旧记录和旧提交的 provenance。
36
36
  - `next_task_sequence` 记录下一个可分配的 `TASK-*` 序号,避免删除历史 task 后发生 id 冲突。
37
- - 文档、Review、测试、发布和 RFC 类 task 使用 `result_docs` 指向本 task 产出的 PRD、architecture、tech plan、ADR、review report、test plan、release note、RFC 或 `plan.draft.yaml`;开发 task 使用 `implementation_doc` 指向模块级实现事实。
37
+ - 文档、Review、测试、发布和 RFC 类 task 使用 `result_docs` 指向本 task 产出的 PRD、architecture、tech plan、ADR、review report、test report、release note、RFC 或 `plan.draft.yaml`;开发 task 使用 `implementation_doc` 指向模块级实现事实。
38
38
  - task 完成并写入或更新相关事实源后,从 `plan.yaml` 的 `tasks` 列表移除该 task;不要长期保留 done/cancelled task 摘要。
39
39
  - `plan.draft.yaml` 是架构阶段生成的计划草案,不自动覆盖 `plan.yaml`。
40
40
  - `plan.draft.yaml` 不保存 `current_phase` 或 `current_task_id`,只保存待采用的 task 草案和必要的 `next_task_sequence`。
41
+ - 通用规则:任何阶段或工作流如果把 draft task promote 成 `plan.yaml` 中的正式 `TASK-*`,必须同次从源 draft queue 删除该 draft;draft queue 永远表示尚未采用的草案,不承担完成历史。
42
+ - 当前内置 draft queue 只有 `plan.draft.yaml.tasks[]`,默认用于 `ARCHITECTING` 产出开发草案、`SPRINTING` 消费开发草案。
41
43
  - 不维护 checkpoint 文件;任务现场只存在于 open task 的 plan 条目里。
42
44
  - 历史动作记录以 git commit 为准,产物结果以模块、子系统或核心数据流级 implementation doc 为准。
43
45
  - `SPRINTING` 阶段每完成一个 task,先在 task 仍位于 `plan.yaml` 时创建 task implementation commit;随后再从 `plan.yaml` 移除该 task 并创建 task completion ledger commit。两段提交 push 成功前不进入下一个 task。
@@ -181,12 +183,12 @@ Parallel Execution 是可选协作协议,不是默认模式,也不是 CLI
181
183
  - `/rfc <file>`:挂起当前流程并进入 RFC recalibration。
182
184
  - `/prd`:在 `REQUIREMENT_GATHERING` 创建或选择一个最小 `TASK-*` task,澄清需求或切片文档,并只更新当前 task 对应的 PRD、验收标准、open questions、`.docs/INDEX.md` 和 overview;如果当前是 `ARCHITECTING` 且尚未进入开发,可先回到 `REQUIREMENT_GATHERING`。
183
185
  - `/design`:在 `ARCHITECTING` 创建或选择一个最小 `TASK-*` task,基于 PRD 生成或切分当前 task 对应的 architecture、tech plan 和 `plan.draft.yaml`。
184
- - `/dev`:在 `SPRINTING` 创建或选择下一个最小 `TASK-*` development task,执行一个 task,跑 gate,更新模块级 implementation doc,按两段 commit/push 闭环后停止。
185
- - `/devloop`:在 `SPRINTING` 连续运行 `/dev` 循环,直到没有明确可创建/执行的任务,或遇到需求、架构、allowed_paths、gate、commit/push blocker。
186
+ - `/dev`:在 `SPRINTING` 创建或选择下一个最小 `TASK-*` development task;如果从 `plan.draft.yaml.tasks[]` promote draft,必须同次消费并删除该 draft;随后执行一个 task,跑 gate,更新模块级 implementation doc,按两段 commit/push 闭环后停止。
187
+ - `/devloop`:在 `SPRINTING` 连续运行 `/dev` 循环,直到 `plan.yaml.tasks[]` 和 `plan.draft.yaml.tasks[]` 都没有明确可执行任务,或遇到需求、架构、allowed_paths、gate、commit/push blocker。
186
188
  - `/syncdocs`:同步 `.docs/INDEX.md` 与当前文档事实源。
187
189
  - `/overview`:运行 `make docs-overview`,刷新 `.docs/<stage>/overview.md` 派生视图。
188
190
  - `/review`:运行只读 Review 工作流。
189
- - `/test`:运行测试计划和验证工作流。
191
+ - `/test`:运行测试报告和验证工作流。
190
192
 
191
193
  ## 阶段流转
192
194
 
@@ -97,10 +97,24 @@ npx sdlc-harness init --adopt
97
97
 
98
98
  Agent 会读取 `<harnessRoot>/state/lifecycle.yaml` 和 `<harnessRoot>/state/plan.yaml`,再按当前阶段选择对应 workflow skill、产物和 gate。任何阶段的 Agent 主任务都不是一次性长生成:产品方案、技术方案、文档切片、基于上一阶段事实源生成、Review、测试、发布和 RFC 处理,都应先落成一个最小 `TASK-*` open task,并设置对应 `phase`;当前轮只执行一个 task,写入 `result_docs` 或 `implementation_doc`、更新索引和 overview,运行 `make validate-plan`,任务完成后再从 `plan.yaml` 移除。
99
99
 
100
+ 通用规则是:任何阶段或工作流如果把 draft task promote 成 `plan.yaml` 中的正式 `TASK-*`,必须在同一次状态更新里从源 draft queue 删除该 draft;正式 task 的恢复现场只保存在 `plan.yaml`,完成历史由 implementation docs、git/PR/CI 记录承担。当前 Harness 内置的 draft queue 只有 `plan.draft.yaml.tasks[]`,它表示尚未采用的开发草案;`/devloop` 只有在 `plan.yaml.tasks[]` 和 `plan.draft.yaml.tasks[]` 都没有明确可执行任务时,才把开发队列视为耗尽。
101
+
102
+ 技术方案阶段需要产出 `plan.draft.yaml`,是为了解决跨阶段交接和当前执行队列纯净性的冲突。`ARCHITECTING` 必须在进入开发前证明方案可以拆成具体、可验证的开发单元,包括修改范围、gate、implementation doc 和执行顺序;但这些未来开发 task 如果直接进入 `plan.yaml`,会和当前架构阶段 task 混在一起,让阶段 gate 无法区分“架构任务未完成”和“下一阶段任务已预拆”。因此开发任务先作为 draft 暂存,进入 `SPRINTING` 后再逐个 promote 成正式 `TASK-*`。其它阶段默认根据上一阶段已经稳定的事实源即时创建当前阶段 task,只有当某个阶段也需要提前为后续阶段生成具体执行任务时,才应引入同类 draft queue。
103
+
100
104
  在尚未进入开发前,`ARCHITECTING` 可以回到 `REQUIREMENT_GATHERING` 修改 PRD:Manager 使用 `python3 tools/transition.py --to REQUIREMENT_GATHERING` 切回 PM/PRD 工作流,完成 PRD task 和 `validate-pm` 后,再用 `python3 tools/transition.py --to ARCHITECTING` 回到设计阶段。进入 `SPRINTING` 后的需求变化仍走 RFC workflow。
101
105
 
102
106
  `validate-design` 会把架构阶段的语义切片作为硬 gate:`overview.md` 不计入 deliverables,`plan.draft.yaml` 中每个开发 draft task 必须通过 `docs.tech_plan` 指向存在的 tech plan slice;多个开发 draft task 默认需要不同 primary tech plan slice。PRD、tech plan 或 draft task 明确出现 AI provider / copilot、外部系统边界、合规 / 权限 / 审计等横切主题时,也需要对应的专门 architecture slice。
103
107
 
108
+ SPRINTING 的 Definition of Done 包含可运行入口/出口:技术方案或 task 承诺的 API、CLI、server route、adapter、worker、provider、配置契约和 fixture/live 边界必须在开发阶段实现或明确 `BLOCKED`。REVIEWING 会把缺少入口/出口作为阻断项;TESTING 只调用既有入口做输入输出验证,不能新增 product runtime、bootstrap、provider adapter、deploy 或 package runtime script。
109
+
110
+ `validate-test` 仍然是 TESTING 阶段 gate 名称。TESTING 的 canonical 产物是 `.docs/07_test/TEST_REPORT.md`,记录 test matrix、regression evidence、runnable entry/exit coverage、coverage gaps 和 final decision;历史 `.docs/07_test/TEST_PLAN.md` 只作为 existing project 的 legacy alias 兼容,新测试证据应使用 `TEST_REPORT.md`。
111
+
112
+ ### ADR 与 Memory 的边界
113
+
114
+ `.docs/05_decisions/` 保存 ADR(Architecture Decision Record)。ADR 是软件工程中常见的架构决策记录实践,用来回答“为什么当时选择这个方案,而不是别的方案”。architecture / tech plan 可以写当前方案里的局部设计理由;如果一个决定有备选方案、影响多个模块或阶段、未来容易被质疑,或修改成本高,就应写成 ADR,记录背景、备选方案、理由、后果和替代关系。
115
+
116
+ `<harnessRoot>/state/memory.md` 只做跨阶段快捷提示和导航,回答“下次进来要先记住什么、去哪里找”。memory 可以链接到 ADR、PRD、tech plan 或 implementation doc;完整背景、备选方案、取舍和长期后果放在 `.docs/05_decisions/` ADR 或其它正式 `.docs/**` 事实源里。
117
+
104
118
  ### Workflow skill 如何生效
105
119
 
106
120
  `<harnessRoot>/skills/<name>/SKILL.md` 是 Harness 的 workflow skill 事实源,也是稳定的 hard file index。它有两种使用方式:
@@ -156,7 +170,7 @@ Harness CLI v1 不承诺自动启动 Codex agent,也不要求 worker 之间通
156
170
  | `/prd` | 完善产品方案 | 在需求阶段创建或选择一个最小 `TASK-*` task;如果当前仍在架构阶段且未进入开发,可先回到 `REQUIREMENT_GATHERING` 修改 PRD |
157
171
  | `/design` | 设计技术方案 | 在架构阶段创建或选择一个最小 `TASK-*` task,生成或切分当前 architecture / tech plan / `plan.draft.yaml` 产物 |
158
172
  | `/dev` | 做下一个任务 | 创建或选择下一个最小 `TASK-*` development task,完成一个 task 闭环后停止 |
159
- | `/devloop` | 开始循环:写任务,执行任务 | 连续运行 `/dev`,直到没有明确任务或遇到 blocker |
173
+ | `/devloop` | 开始循环:写任务,执行任务 | 连续运行 `/dev`,直到 `plan.yaml` 和 `plan.draft.yaml` 都没有明确任务或遇到 blocker |
160
174
  | `/test` | 跑一下当前验证 | 运行当前 task 或阶段对应 gate |
161
175
  | `/review` | 准备 review | 进入只读 Review workflow |
162
176
 
@@ -212,12 +226,14 @@ make docs-overview
212
226
  |---|---|
213
227
  | `<harnessRoot>/state/lifecycle.yaml` | 当前生命周期阶段和 active skill |
214
228
  | `<harnessRoot>/state/plan.yaml` | 当前和未来 task 的短期执行计划 |
229
+ | `<harnessRoot>/state/memory.md` | 跨阶段稳定知识的摘要和正式事实源链接 |
215
230
  | `.docs/01_product/` | PRD、用户场景、验收标准 |
216
231
  | `.docs/02_architecture/` | 架构边界和高层设计 |
217
232
  | `.docs/03_tech_plan/` | 技术方案、接口契约、任务拆分 |
218
233
  | `.docs/04_implementation/` | 模块、子系统和核心数据流的真实实现事实 |
234
+ | `.docs/05_decisions/` | ADR,长期关键决策及其背景、备选方案、理由和后果 |
219
235
  | `.docs/06_review/` | Review 报告 |
220
- | `.docs/07_test/` | 测试计划和回归记录 |
236
+ | `.docs/07_test/` | 测试报告、测试矩阵、回归证据和覆盖缺口 |
221
237
  | `.docs/08_release/` | 发布记录和回滚方案 |
222
238
  | `.docs/rfc/` | 需求变更和影响分析 |
223
239
 
@@ -12,9 +12,9 @@ help:
12
12
  @echo " make validate-plan 校验 plan.yaml task 合同,允许当前 open task"
13
13
  @echo " make validate-pm 校验产品需求产物"
14
14
  @echo " make validate-design 校验架构设计、技术方案和任务草案"
15
- @echo " make validate-dev 校验 sprint 任务状态、路径、代码 gate 和实现文档"
15
+ @echo " make validate-dev 校验 sprint 任务状态、draft 消费、路径、代码 gate 和实现文档"
16
16
  @echo " make validate-review 校验 Review report"
17
- @echo " make validate-test 校验 regression/test plan"
17
+ @echo " make validate-test 校验 regression/test report"
18
18
  @echo " make validate-release 校验 release note、smoke result 和 rollback plan"
19
19
  @echo " make validate-rfc 校验 RFC 产物并运行完整回归入口"
20
20
 
@@ -49,6 +49,7 @@ validate-design:
49
49
 
50
50
  validate-dev:
51
51
  $(PYTHON) tools/validate_plan.py
52
+ $(PYTHON) tools/validate_dev_state.py
52
53
  $(PYTHON) tools/validate_allowed_paths.py
53
54
  $(MAKE) lint
54
55
  $(MAKE) test-current-domain
@@ -27,6 +27,7 @@ phases:
27
27
  - ".docs/04_implementation/**"
28
28
  - ".docs/INDEX.md"
29
29
  - "<harnessRoot>/state/plan.yaml"
30
+ - "<harnessRoot>/state/plan.draft.yaml"
30
31
 
31
32
  REVIEWING:
32
33
  read_only_source: true
@@ -31,7 +31,7 @@ gates:
31
31
 
32
32
  validate-dev:
33
33
  command: "make validate-dev"
34
- purpose: "验证任务状态、allowed_paths、代码检查、测试和实现文档"
34
+ purpose: "验证任务状态、已消费 draft、allowed_paths、代码检查、测试和实现文档"
35
35
  required_for:
36
36
  - "SPRINTING"
37
37
 
@@ -45,11 +45,12 @@ phases:
45
45
  - "REQUIREMENT_GATHERING"
46
46
 
47
47
  SPRINTING:
48
- goal: "按任务状态执行开发、测试和实现文档沉淀"
48
+ goal: "按任务状态执行开发、消费已采用草案、测试和实现文档沉淀"
49
49
  role: "developer"
50
50
  skill: "pjsdlc_dev_sprint"
51
51
  inputs:
52
52
  - "<harnessRoot>/state/plan.yaml"
53
+ - "<harnessRoot>/state/plan.draft.yaml"
53
54
  - "current open task plan contract"
54
55
  - ".docs/01_product/"
55
56
  - ".docs/03_tech_plan/"
@@ -57,6 +58,7 @@ phases:
57
58
  - "src/"
58
59
  - "tests/"
59
60
  - ".docs/04_implementation/"
61
+ - "<harnessRoot>/state/plan.draft.yaml"
60
62
  gates:
61
63
  - "make validate-dev"
62
64
  next: "REVIEWING"
@@ -79,7 +81,7 @@ phases:
79
81
  next: "TESTING"
80
82
 
81
83
  TESTING:
82
- goal: "形成测试矩阵并完成回归验证"
84
+ goal: "形成测试报告、测试矩阵、回归证据和覆盖缺口结论"
83
85
  role: "tester"
84
86
  skill: "pjsdlc_tester"
85
87
  inputs:
@@ -17,6 +17,8 @@ description: Use during ARCHITECTING to create architecture docs, technical plan
17
17
 
18
18
  架构产物应区分稳定边界和实现计划:architecture slice 记录领域边界、子系统、关键风险和长期约束;tech plan slice 记录接口契约、数据模型、模块方案、任务拆分和 gate。不要把重大架构变化藏在 task 描述里。
19
19
 
20
+ ADR 用来解决“后来的人只看到结果,看不到当年取舍”的问题。architecture / tech plan 可以记录当前方案的局部设计理由;如果一个决定有明确备选方案、影响多个模块或阶段、未来容易被质疑、修改成本高,或需要保留 supersede 关系,就写入 `.docs/05_decisions/`。`<harnessRoot>/state/memory.md` 只保留这类决策的简短提示和链接,不承载完整背景、备选方案、取舍和后果。
21
+
20
22
  架构和技术方案产出本身也是 workflow task,而不是一次性长文档生成。无论来源是对话式设计、既有完整技术方案切片,还是根据 PRD/architecture 事实源生成新方案,都要先在 `<harnessRoot>/state/plan.yaml` 创建或选择一个足够小的 `TASK-*` open task,并设置 `phase: "ARCHITECTING"`,只完成当前 `current_task_id` 对应的一片 architecture / tech plan / ADR / `plan.draft.yaml` 产物。不要在一个任务里连续创建大量设计文件;如果需要多个 slices,先拆出 pending tasks,当前轮只执行一个 task。
21
23
 
22
24
  如果在 `ARCHITECTING` 中发现 PRD 缺失、验收标准不清或产品边界需要调整,且项目尚未进入 `SPRINTING`,不要用架构文档替代产品事实。先收尾或移除当前 open design task,再请 Manager 使用 `python3 tools/transition.py --to REQUIREMENT_GATHERING` 回到 PM/PRD 工作流修改 `.docs/01_product/**`。进入 `SPRINTING` 后的需求变化走 RFC workflow。
@@ -45,6 +47,7 @@ description: Use during ARCHITECTING to create architecture docs, technical plan
45
47
  - `.docs/02_architecture/` 按领域边界、子系统、跨模块架构问题或关键技术风险切片。
46
48
  - `.docs/03_tech_plan/` 按可实现范围、接口契约、数据模型、模块方案或任务组切片。
47
49
  - `.docs/05_decisions/` 按单个架构决策切片,即一份 ADR 只记录一个 durable decision。
50
+ - 写 ADR 的判断标准:存在备选方案、影响多个产物或阶段、未来容易被质疑、修改成本高、或需要说明 `Supersedes / Superseded by` 时,写 ADR;只影响当前模块内部实现细节、且理由已能在 architecture / tech plan 中局部说明时,不单独写 ADR。
48
51
  - 如果一个技术方案跨越多个独立模块,应拆成多个 tech plan slice,并在 `plan.draft.yaml` 中分别引用。
49
52
  - `plan.draft.yaml` 中每个开发 draft task 必须在 `docs.tech_plan` 引用已有 `.docs/03_tech_plan/` slice;多个开发 draft task 默认应引用不同的 primary tech plan slice,不能用一个总纲 tech plan 覆盖全部模块任务。
50
53
  - `overview.md` 是 generated artifact,不算 architecture / tech plan deliverable,也不能作为 `docs.tech_plan` 引用。
@@ -15,7 +15,9 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
15
15
 
16
16
  开始编码前,先确认当前 open task 是否完整,修改范围是否覆盖必要文件,验收标准是否能被测试或 gate 验证。如果发现任务边界、产品行为或技术方案不清晰,要停下来说明 blocker、给出可能解释和推荐下一步,而不是扩大范围继续写。
17
17
 
18
- `/dev` `/devloop` 是开发阶段的两个入口。`/dev` 创建或选择下一个最小 `TASK-*` development task,设置 `phase: "SPRINTING"`,并只完成一个 task 闭环后停止。`/devloop` 连续运行 `/dev`,直到技术方案中没有明确可创建/执行的任务,或遇到需求、架构、allowed_paths、gate、commit/push blocker。
18
+ 开发阶段的 Definition of Done 包含可运行的系统入口/出口。凡技术方案或 task 承诺 API、CLI、server route、adapter、worker、provider、外部发送/写入执行器、配置契约或 live/fixture 双模式边界,当前实现必须提供对应入口、调用方式、输出/副作用边界和验证方式;如果真实入口/出口尚不可运行,不能把 task 当作完成,也不能把缺口留给 TESTING 补 runtime。Implementation doc 必须写明 `Runnable Entry/Exit`;确实不适用时也要显式写 `Not applicable` 和原因。此时应保留或创建 `BLOCKED`/后续 dev task,或通过 RFC/ARCHITECTING 处理边界变更。
19
+
20
+ `/dev` 和 `/devloop` 是开发阶段的两个入口。`/dev` 创建或选择下一个最小 `TASK-*` development task,设置 `phase: "SPRINTING"`,并只完成一个 task 闭环后停止。通用规则是从任何 draft queue promote 正式 `TASK-*` 时都必须同次消费源 draft;当前开发阶段的内置 draft queue 是 `plan.draft.yaml.tasks[]`,因此如果这个 task 来自 `plan.draft.yaml.tasks[]`,promote 时必须同次删除源 draft,避免已采用草案继续显示为 `pending`。`/devloop` 连续运行 `/dev`,直到 `plan.yaml.tasks[]` 和 `plan.draft.yaml.tasks[]` 都没有明确可创建/执行的任务,或遇到需求、架构、allowed_paths、gate、commit/push blocker。
19
21
 
20
22
  实现时遵循小步闭环:先检查 `git status`,确认工作区没有未归属到当前 task 的脏变更;再定位相关代码和测试,做必要修改,运行 gate,修复失败,写入或更新相关 implementation doc 并刷新文档派生视图。此时先不要从 `plan.yaml` 移除当前 task,要在当前 task 仍位于 `plan.yaml` 时创建 task implementation commit;随后再移除 task,创建 task completion ledger commit,并 push 两个 commit。不要顺手重构、重排格式或处理无关问题;如果发现无关风险,只记录或报告。
21
23
 
@@ -25,6 +27,7 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
25
27
 
26
28
  - `<harnessRoot>/state/lifecycle.yaml`
27
29
  - `<harnessRoot>/state/plan.yaml`
30
+ - `<harnessRoot>/state/plan.draft.yaml`
28
31
  - 当前任务关联的 PRD 和技术方案
29
32
  - 当前源码和测试文件
30
33
 
@@ -34,7 +37,9 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
34
37
  - 当前 task `allowed_paths` 范围内的测试改动
35
38
  - `.docs/04_implementation/` 下相关模块、子系统或核心数据流的 implementation doc
36
39
  - 当前 task `working_notes` 或 implementation doc `Verification` 中的 gate evidence
40
+ - implementation doc 中的 runnable entry/exit、配置契约和 fixture/live 边界事实
37
41
  - 更新后的 `<harnessRoot>/state/plan.yaml`
42
+ - 如果本轮 promote draft,更新后的 `<harnessRoot>/state/plan.draft.yaml`
38
43
  - 更新后的 `.docs/INDEX.md`
39
44
  - 当前 task 移除前创建的 task implementation commit
40
45
  - 从 `plan.yaml` 移除当前 task 后的 task completion ledger commit
@@ -51,8 +56,8 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
51
56
  - 本 Skill 不直接重切 PRD 或 tech plan;如果发现上游语义边界错误,进入 `BLOCKED`、创建 RFC,或请求回到 `ARCHITECTING`。
52
57
  - gate 通过后调用 `pjsdlc_implementation_doc`,由该 Skill 按真实实现更新或新增 `.docs/04_implementation/` 模块级 slice。
53
58
  - 如果一个任务实际变成多个独立实现边界,应停止扩大范围,拆分后续任务或回到任务规划。
54
- - `/dev` 是单任务执行入口:没有 open task 时,先根据 PRD、architecture、tech plan 和 `plan.draft.yaml` 创建一个最小 `TASK-*` open task;已有 open task 时,直接执行该 task;完成后停止。
55
- - `/devloop` 是连续执行入口:每完成一个 task 并 push 两段提交后,重新读取 lifecycleplanPRD、architecture 和 tech plan,再决定是否创建/执行下一个最小 task;没有明确任务或出现 blocker 时停止并报告。
59
+ - `/dev` 是单任务执行入口:没有 open task 时,先根据 PRD、architecture、tech plan 和 `plan.draft.yaml` 创建一个最小 `TASK-*` open task;如果从 `plan.draft.yaml.tasks[]` 采用 draft,必须同次从 draft 列表删除源项;已有 open task 时,直接执行该 task;完成后停止。
60
+ - `/devloop` 是连续执行入口:每完成一个 task 并 push 两段提交后,重新读取 lifecycle、`plan.yaml`、`plan.draft.yaml`、PRD、architecture 和 tech plan,再决定是否创建/执行下一个最小 task;没有 open task 且没有未采用 draft,或出现 blocker 时停止并报告。
56
61
  - Parallel Execution 是当前 task 的可选协作方式,不替代 task completion protocol;`SPRINTING` 并行从 lifecycle 的 `current_phase` 和 plan 的 `current_task_id` 推断上下文,不在 `parallel_execution` 内重复保存。
57
62
 
58
63
  ## Plan Protocol
@@ -61,10 +66,11 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
61
66
 
62
67
  1. `current_task_id` 指向正在执行的 open task。
63
68
  2. open task 直接声明 `phase: "SPRINTING"`、`docs`、`allowed_paths`、`required_gates`、`acceptance_criteria` 和 `implementation_doc`。
64
- 3. 任务执行中只保留恢复所需的简短 `working_notes`。
65
- 4. gate、implementation doc、`.docs/INDEX.md` 和 `overview.md` 完成后,在当前 task 仍位于 `plan.yaml` 时创建 task implementation commit。
66
- 5. implementation commit 完成后,再把该 task `plan.yaml` `tasks` 列表移除,并保留/递增 `next_task_sequence`。
67
- 6. 将移除当前 task 后的 `plan.yaml` 提交为 task completion ledger commit,并 `git push` 两个 commit 到当前 upstream branch。
69
+ 3. 如果 open task 是由 `plan.draft.yaml.tasks[]` promote 而来,创建正式 `TASK-*` 和删除源 draft 必须发生在同一次状态更新中;正式 task 的恢复现场只保存在 `plan.yaml`。
70
+ 4. 任务执行中只保留恢复所需的简短 `working_notes`。
71
+ 5. gate、implementation doc、`.docs/INDEX.md` `overview.md` 完成后,在当前 task 仍位于 `plan.yaml` 时创建 task implementation commit。
72
+ 6. implementation commit 完成后,再把该 task `plan.yaml` `tasks` 列表移除,并保留/递增 `next_task_sequence`。
73
+ 7. 将移除当前 task 后的 `plan.yaml` 提交为 task completion ledger commit,并 `git push` 两个 commit 到当前 upstream branch。
68
74
 
69
75
  done task 的执行流水不在当前 `plan.yaml` 长期保留,也不是默认上下文。修 bug、补功能和继续开发时,优先读取当前代码、测试、PRD、技术方案和模块级 implementation doc;历史 task 查询主要看“做了什么、为什么做、影响哪个模块、验证了什么”,task id 和 commit 作为 implementation doc 的 provenance。`allowed_paths`、`required_gates`、临时 `working_notes` 是执行期约束,不作为历史查询 API。只有用户明确要求 forensic/audit/regression 追溯时,才临时使用 git、PR 或 CI 记录。
70
76
 
@@ -72,18 +78,18 @@ done task 的执行流水不在当前 `plan.yaml` 长期保留,也不是默认
72
78
 
73
79
  1. 一次只执行一个任务。
74
80
  2. 开始修改前检查 `git status`;如果存在不属于当前 task 的未提交变更,先完成对应 task 的 commit/push,或报告 blocker,不要混入当前 task。
75
- 3. 只编辑当前 task 的 `allowed_paths` 允许的文件,以及 `SPRINTING` 阶段允许的 Harness 记账文件。
81
+ 3. 只编辑当前 task 的 `allowed_paths` 允许的文件,以及 `SPRINTING` 阶段允许的 Harness 记账文件;如果本轮 promote draft,允许同步编辑 `<harnessRoot>/state/plan.draft.yaml` 来消费源 draft。
76
82
  4. 必须运行当前 task 的 `required_gates`。
77
83
  5. 如果 gate 因代码或测试逻辑失败,在任务范围内修复。
78
84
  6. 如果 gate 因基础设施、凭证缺失、产品行为不清或高风险架构变化失败,进入 `BLOCKED`。
79
85
  7. gate 通过后调用 `pjsdlc_implementation_doc`。
80
- 8. 只有 gate 通过且 implementation doc 校验通过后,才能把任务标记为 `done`。
86
+ 8. 只有 gate 通过、承诺的 runnable entry/exit 已实现或明确 `BLOCKED`,且 implementation doc 校验通过后,才能把任务标记为 `done`。
81
87
  9. 任务完成并写入或更新相关 implementation doc、刷新 `overview.md`、记录 gate 后,先创建 task implementation commit;此时不要移除该 task。
82
88
  10. task implementation commit 必须发生在 task 移除前;后续默认不要读取其中的执行期字段,历史查询以模块级 implementation doc、RFC、PRD、tech plan 和代码为主。
83
89
  11. implementation commit 完成后,从当前 `plan.yaml` 移除该 task,并创建 task completion ledger commit。
84
90
  12. 默认不追溯已完成 task 的执行流水;只有显式 forensic/audit/regression 任务才临时查询 git、PR 或 CI 记录。
85
91
  13. 两个 commit 后必须 `git push` 到当前 upstream branch;如果没有 remote/upstream、权限或凭证导致无法 push,停止推进并报告 blocker。
86
- 14. `/devloop` 每轮都必须重新读取当前状态,不得在一次上下文中假设 plan、代码或远端状态未变化。
92
+ 14. `/devloop` 每轮都必须重新读取当前状态,不得在一次上下文中假设 plan、draft、代码或远端状态未变化。
87
93
  15. 只有用户明确要求并行、多 agent 或多 worktree 时,才允许创建 `parallel_execution`;否则不得默认并行。
88
94
  16. `runtime_managed` 只在当前 runtime 支持 subagent 时使用;没有该能力时,输出 `user_orchestrated` worker prompt,由用户手动打开对话或 worktree 后粘贴。
89
95
  17. worker 不更新主事实源;主 Agent 才能更新 `plan.yaml`、`.docs/INDEX.md`、overview、implementation doc 和最终 gate 证据。
@@ -94,6 +100,8 @@ done task 的执行流水不在当前 `plan.yaml` 长期保留,也不是默认
94
100
  - [ ] 当前 task `required_gates` 已通过,或 blocker 已记录。
95
101
  - [ ] open task 在 `plan.yaml` 中包含完整执行合同。
96
102
  - [ ] 当前任务仍然是单一清晰的执行单元。
103
+ - [ ] 技术方案或 task 承诺的 API/CLI/adapter/worker/provider、配置契约、输出/副作用和 fixture/live 边界已可运行并写入 implementation doc,或已明确 `BLOCKED`/后续 dev task。
104
+ - [ ] 如果当前 task 来自 `plan.draft.yaml.tasks[]`,源 draft 已在 promote 时从 draft 列表删除。
97
105
  - [ ] implementation doc 已生成或更新,并反映相关模块的真实代码。
98
106
  - [ ] 如果启用了 `parallel_execution`,worker owned paths、forbidden paths、required gates 和主 Agent 集成结果已记录。
99
107
  - [ ] gate 结果已写入 implementation doc `Verification`,必要时当前 task `working_notes` 也记录了恢复现场所需的 gate evidence。
@@ -17,6 +17,8 @@ description: Use after development gates pass to update module-level implementat
17
17
 
18
18
  文档应帮助后来者快速理解:某个模块或核心数据流的当前实现是什么、关键对象/函数职责是什么、行为如何从输入流到输出、测试覆盖了什么、还有什么未覆盖。task id 只作为 provenance,不作为默认切片粒度。
19
19
 
20
+ 如果模块包含或承诺可运行系统边界,implementation doc 必须记录 runnable entry/exit:API/CLI/server route/adapter/worker/provider 的调用方式、配置契约、输入来源、输出或副作用、fixture/live 模式边界,以及哪些真实外部执行器尚未实现。不能把未来才会实现的入口写成当前事实。
21
+
20
22
  ## 输入
21
23
 
22
24
  - `<harnessRoot>/state/plan.yaml` 中当前 task 的 `implementation_doc` 路径和 task ID
@@ -45,8 +47,9 @@ description: Use after development gates pass to update module-level implementat
45
47
  1. implementation doc 描述当前代码事实,而不是期望中的未来设计。
46
48
  2. 每个被记录的文件都应说明它在该模块或数据流中的作用和关键函数/对象。
47
49
  3. 与技术方案的偏移必须明确记录,即便该偏移是合理的。
48
- 4. 测试覆盖必须列出具体测试,或明确记录覆盖缺口。
49
- 5. 文档粒度保持在模块、子系统或核心数据流级别;不要默认按 task 建文档,也不要写成跨全项目的巨型百科。
50
+ 4. runnable entry/exit、配置契约和 fixture/live 边界必须记录当前事实;缺失项写入 `未覆盖(Not covered)` 或方案偏移。
51
+ 5. 测试覆盖必须列出具体测试,或明确记录覆盖缺口。
52
+ 6. 文档粒度保持在模块、子系统或核心数据流级别;不要默认按 task 建文档,也不要写成跨全项目的巨型百科。
50
53
 
51
54
  ## 完成检查
52
55
 
@@ -54,6 +57,7 @@ description: Use after development gates pass to update module-level implementat
54
57
  - [ ] Task ID、commit 和关联产物路径已作为 provenance 记录。
55
58
  - [ ] 真实代码结构表已填写。
56
59
  - [ ] 核心数据流已说明。
60
+ - [ ] runnable entry/exit、配置契约和 fixture/live 边界已记录,或缺失项已明确标注。
57
61
  - [ ] 已判断 implementation doc 的语义切片边界。
58
62
  - [ ] 方案偏移和测试覆盖已记录。
59
63
  - [ ] `.docs/INDEX.md` 已链接 implementation doc。
@@ -47,8 +47,8 @@ Parallel Execution 是显式 opt-in:只有用户明确提出“并行”“多
47
47
  14. 用户自然语言表达需求或设计变化时,先判断阶段:如果当前是 `ARCHITECTING` 且尚未进入开发,可以说明将回到 `REQUIREMENT_GATHERING` 并用 `python3 tools/transition.py --to REQUIREMENT_GATHERING` 切回 PM/PRD 工作流;如果当前是 `SPRINTING` 或之后,进入 RFC workflow。
48
48
  15. 用户输入 `/prd`,或自然语言要求“完善产品方案”“写 PRD”“文档切片”“我提供信息,你帮我完善产品方案”时,如果 `current_phase` 是 `REQUIREMENT_GATHERING`,调用产品方案工作流;如果 `current_phase` 是 `ARCHITECTING`,先确认没有 open design task 需要收尾,说明将开发前回退到 `REQUIREMENT_GATHERING`,再用 `python3 tools/transition.py --to REQUIREMENT_GATHERING` 切换到 PM/PRD 工作流;该工作流必须先创建或选择一个最小 `TASK-*` open task,并设置 `phase: "REQUIREMENT_GATHERING"`,再执行一个 PRD 生成或切片 task;否则说明当前阶段冲突和推荐路径。
49
49
  16. 用户输入 `/design`,或自然语言要求“设计技术方案”“做架构方案”“根据 PRD 做技术方案”“切技术方案”时,如果 `current_phase` 是 `ARCHITECTING`,调用架构和技术方案工作流;该工作流必须先创建或选择一个最小 `TASK-*` open task,并设置 `phase: "ARCHITECTING"`,再执行一个 architecture / tech plan / `plan.draft.yaml` 生成或切片 task;否则说明当前阶段冲突和推荐路径。
50
- 17. 用户输入 `/dev`,或自然语言要求“开始开发”“做当前任务”“做下一个任务”“继续开发下一个任务”时,如果 `current_phase` 是 `SPRINTING`,创建或选择一个最小 `TASK-*` development task 并执行一个 task 闭环;否则说明当前阶段冲突和推荐路径。
51
- 18. 用户输入 `/devloop`,或自然语言要求“开始循环:写任务,执行任务”“把开发循环跑完”“连续开发”时,如果 `current_phase` 是 `SPRINTING`,连续运行 `/dev` 循环,直到没有明确可做任务或遇到 blocker;否则说明当前阶段冲突和推荐路径。
50
+ 17. 用户输入 `/dev`,或自然语言要求“开始开发”“做当前任务”“做下一个任务”“继续开发下一个任务”时,如果 `current_phase` 是 `SPRINTING`,创建或选择一个最小 `TASK-*` development task 并执行一个 task 闭环;如果 task 来自 `plan.draft.yaml.tasks[]`,promote 时必须同次删除源 draft;否则说明当前阶段冲突和推荐路径。
51
+ 18. 用户输入 `/devloop`,或自然语言要求“开始循环:写任务,执行任务”“把开发循环跑完”“连续开发”时,如果 `current_phase` 是 `SPRINTING`,连续运行 `/dev` 循环,直到 `plan.yaml.tasks[]` 和 `plan.draft.yaml.tasks[]` 都没有明确可做任务或遇到 blocker;否则说明当前阶段冲突和推荐路径。
52
52
  19. 用户自然语言要求跑测试或验证时,运行当前 task 或当前阶段的对应 gate。
53
53
  20. 用户明确要求并行、多 agent 或多 worktree 时,先判断当前阶段是否是 `REQUIREMENT_GATHERING`、`SPRINTING` 或 `TESTING`;如果是,生成或使用 `parallel_execution.trigger: "user_requested"` 合同;否则说明当前阶段不支持并行合同。
54
54
  21. `runtime_managed` 模式只在当前 Agent runtime 真实具备 subagent 能力时使用;否则使用 `user_orchestrated` 并输出每个 worker 的可复制 prompt。
@@ -59,7 +59,7 @@ Parallel Execution 是显式 opt-in:只有用户明确提出“并行”“多
59
59
 
60
60
  ## Plan Protocol
61
61
 
62
- 每个 open task 都必须在 `plan.yaml` 中包含 `id`、`phase`、`docs`、`allowed_paths`、`required_gates` 和 `acceptance_criteria`;新 task 统一使用 `TASK-*` id,历史 `DEV-*`、`PRD-*`、`DES-*` task 只作为兼容输入保留。文档和流程产物 task 使用 `result_docs` 指向本 task 产出的 PRD、architecture、tech plan、ADR、review、test、release、RFC 或 `plan.draft.yaml`,开发 task 使用 `implementation_doc` 指向模块级实现事实。done/cancelled task 不长期留在当前 `plan.yaml`。完成后的产物事实以对应 `.docs/**` slice、`plan.draft.yaml` 或模块级 implementation doc 为准,动作历史以 git/PR/CI/release 系统作为 cold archive,`next_task_sequence` 负责继续分配后续 task id。
62
+ 每个 open task 都必须在 `plan.yaml` 中包含 `id`、`phase`、`docs`、`allowed_paths`、`required_gates` 和 `acceptance_criteria`;新 task 统一使用 `TASK-*` id,历史 `DEV-*`、`PRD-*`、`DES-*` task 只作为兼容输入保留。文档和流程产物 task 使用 `result_docs` 指向本 task 产出的 PRD、architecture、tech plan、ADR、review、test、release、RFC 或 `plan.draft.yaml`,开发 task 使用 `implementation_doc` 指向模块级实现事实。任何阶段如果从 draft queue promote 正式 `TASK-*`,必须同次消费并删除源 draft;当前内置 draft queue 是 `plan.draft.yaml.tasks[]`,用于保存尚未采用的开发草案。done/cancelled task 不长期留在当前 `plan.yaml`。完成后的产物事实以对应 `.docs/**` slice 或模块级 implementation doc 为准,动作历史以 git/PR/CI/release 系统作为 cold archive,`next_task_sequence` 负责继续分配后续 task id。
63
63
 
64
64
  `/prd`、`/design`、`/dev`、`/review`、`/test`、`/release` 和 `/rfc` 都是单 task 推进:默认只完成一个 `TASK-*`。`validate-plan` 用于检查当前 open task 合同是否完整;阶段出口 gate `validate-pm`、`validate-design`、`validate-dev`、`validate-review`、`validate-test`、`validate-release` 和 `validate-rfc` 都要求没有 open task 残留。
65
65
 
@@ -17,6 +17,8 @@ Review 时先建立证据链:PRD 说什么、技术方案承诺什么、implem
17
17
 
18
18
  不要把个人偏好包装成 blocker。区分 blocking issue、follow-up improvement 和 open question。如果没有发现问题,要明确说明,同时列出剩余测试缺口或残余风险。
19
19
 
20
+ Review 必须把“当前模块没有可运行入口/出口”视为阻断项,而不是普通测试缺口。凡 PRD、技术方案或 implementation doc 承诺 API、CLI、server route、adapter、worker、provider、外部发送/写入执行器、配置契约或 live/fixture 双模式边界,Review 都要核对真实代码和实现文档是否提供可调用入口、输出/副作用边界和验证方式;缺失时 gate decision 应为 `BLOCKED`,并要求回到 SPRINTING/RFC,而不是允许进入 TESTING 后补 runtime。
21
+
20
22
  Review 产出本身也是 workflow task。开始 review 前,先在 `<harnessRoot>/state/plan.yaml` 创建或选择一个足够小的 `TASK-*` open task,并设置 `phase: "REVIEWING"`;当前轮只产出一个 review batch、一个风险主题 slice 或一次 PR review 结论。不要在一个任务里覆盖多个互不相关的 review 主题。
21
23
 
22
24
  ## 输入
@@ -35,6 +37,7 @@ Review 产出本身也是 workflow task。开始 review 前,先在 `<harnessRo
35
37
  - 更新后的 `<harnessRoot>/state/plan.yaml`
36
38
  - 风险清单
37
39
  - 重构建议
40
+ - runnable entry/exit readiness 结论
38
41
  - 是否允许进入 `TESTING` 的结论
39
42
 
40
43
  ## 语义切片
@@ -62,8 +65,9 @@ Review 阶段受 `plan.yaml` 管控:
62
65
  2. Findings 放在最前面,并按严重程度排序。
63
66
  3. 每条 finding 尽量引用文件、需求、任务或文档路径。
64
67
  4. 区分 blocking issues 和 follow-up improvements。
65
- 5. 如果未发现问题,明确说明,并列出剩余测试缺口或残余风险。
66
- 6. Review 阶段一次只执行一个 `TASK-*` task。
68
+ 5. 缺少已承诺的 runnable entry/exit、配置契约或 fixture/live 边界时,必须作为 P0/P1 blocking finding。
69
+ 6. 如果未发现问题,明确说明,并列出剩余测试缺口或残余风险。
70
+ 7. Review 阶段一次只执行一个 `TASK-*` task。
67
71
 
68
72
  ## 完成检查
69
73
 
@@ -72,6 +76,7 @@ Review 阶段受 `plan.yaml` 管控:
72
76
  - [ ] 当前 task 已从 `plan.yaml` 移除,或因中断/blocker 保留为可恢复 open task。
73
77
  - [ ] 已评估需求一致性。
74
78
  - [ ] 已评估架构和可维护性风险。
79
+ - [ ] 已评估 runnable entry/exit、配置契约和 fixture/live 边界是否足以进入 TESTING。
75
80
  - [ ] 已判断 review slice 的范围和风险主题边界。
76
81
  - [ ] 已列出测试缺口。
77
82
  - [ ] 已运行 `make docs-overview` 刷新 `.docs/<stage>/overview.md`。
@@ -42,7 +42,7 @@ RFC recalibration 本身也是 workflow task。开始处理变更前,先在 `<
42
42
 
43
43
  - `.docs/rfc/` 按一次需求变更切片,一份 RFC 只描述一个可独立评估、实现和回归的变更。
44
44
  - 如果用户一次提出多个互不依赖的变更,应拆成多份 RFC。
45
- - RFC 的 impact analysis 负责判断是否需要重切 PRD、tech plan、implementation doc 或 test plan,并覆盖 state、tools、package assets、tests、migration 和 generated overview。
45
+ - RFC 的 impact analysis 负责判断是否需要重切 PRD、tech plan、implementation doc 或 test report,并覆盖 state、tools、package assets、tests、migration 和 generated overview。
46
46
  - 对受影响产物做局部补丁,不重写无关稳定 slice。
47
47
  - 每次 RFC 影响了文档边界,都要更新 `.docs/INDEX.md` 并记录受影响任务状态。
48
48
 
@@ -11,13 +11,15 @@ description: Use during TESTING to produce a test matrix, run regression, and do
11
11
 
12
12
  ## 角色提示词
13
13
 
14
- 你是测试负责人,目标是把需求、风险和实现变化转成可执行、可追踪、可复用的测试计划。你不只是列测试项,而是要判断哪些路径最容易出错、哪些验收标准必须被自动化或手动验证覆盖。
14
+ 你是测试负责人,目标是把需求、风险和实现变化转成可执行、可追踪、可复用的测试报告:测试矩阵、回归证据、覆盖缺口和最终结论。你不只是列测试项,而是要判断哪些路径最容易出错、哪些验收标准必须被自动化或手动验证覆盖。
15
15
 
16
16
  开始测试规划前,先建立映射关系:PRD acceptance criteria、技术方案关键接口/数据模型、implementation doc 的真实改动、Review findings 和现有测试。对每个测试项说明它覆盖的需求或风险;对暂不覆盖的内容说明原因、残余风险和 follow-up。
17
17
 
18
18
  执行回归时,优先选择能证明阶段出口的 gate。测试无法运行、环境缺失或数据不可得时,不要宣布通过,应记录 blocker、已完成检查和恢复条件。
19
19
 
20
- 测试计划和回归证据产出本身也是 workflow task。开始测试前,先在 `<harnessRoot>/state/plan.yaml` 创建或选择一个足够小的 `TASK-*` open task,并设置 `phase: "TESTING"`;当前轮只产出一个测试矩阵 slice、一个回归批次、一个风险验证片区或一组 scoped test changes。
20
+ TESTING 只能调用 SPRINTING 已经交付的入口做输入/输出验证。可以补充测试、fixture、mock、assertion helper 和测试文档,但不能在 TESTING 中新增或长期维护 product runtime、server/API/CLI/adapter、direct poller、cloud bootstrap、systemd unit、真实 provider adapter、package runtime script 或部署脚本。如果发现真实入口/出口不存在、live 模式不可调用、配置契约缺失或用户目标与已实现通道不一致,应记录 `BLOCKED`、生成 RFC 或后续 dev task 建议,并停止把测试阶段扩大成开发/集成搭建。
21
+
22
+ 测试报告和回归证据产出本身也是 workflow task。开始测试前,先在 `<harnessRoot>/state/plan.yaml` 创建或选择一个足够小的 `TASK-*` open task,并设置 `phase: "TESTING"`;当前轮只产出一个测试矩阵 slice、一个回归批次、一个风险验证片区或一组 scoped test changes。`plan.yaml` 仍是唯一执行计划事实源,`.docs/07_test/**` 只记录 test matrix、regression evidence、coverage gaps 和 final decision,不表达“下一步如何开发”。
21
23
 
22
24
  如果用户明确要求并行、多 agent 或多 worktree,测试阶段可以启用 `parallel_execution`,让 worker 分别执行互不依赖的回归片区、smoke、兼容性或风险验证。worker 只提交证据和必要的 scoped test changes;最终 `.docs/07_test/**`、coverage gaps、PASS/BLOCKED 决策和阶段 gate 由主 Agent 汇总。没有用户显式要求时,测试 workflow 保持串行。
23
25
 
@@ -29,22 +31,23 @@ description: Use during TESTING to produce a test matrix, run regression, and do
29
31
  - `.docs/04_implementation/`
30
32
  - `.docs/06_review/REVIEW_REPORT.md`
31
33
  - 现有测试
32
- - `<harnessRoot>/pjsdlc_managed/templates/TEST_PLAN_TEMPLATE.md`
34
+ - `<harnessRoot>/pjsdlc_managed/templates/TEST_REPORT_TEMPLATE.md`
33
35
 
34
36
  ## 输出
35
37
 
36
- - `.docs/07_test/TEST_PLAN.md`
38
+ - `.docs/07_test/TEST_REPORT.md`
37
39
  - 必要时在 `tests/` 下补充测试
38
40
  - 更新后的 `<harnessRoot>/state/plan.yaml`
39
- - 回归测试记录
41
+ - 回归证据记录
40
42
  - 覆盖缺口清单
43
+ - `BLOCKED` 时的 RFC/dev follow-up 建议和恢复条件
41
44
 
42
45
  ## 语义切片
43
46
 
44
- - `.docs/07_test/` 默认按测试计划、测试矩阵、回归批次或领域测试范围切片。
47
+ - `.docs/07_test/` 默认按测试报告、测试矩阵、回归批次或领域测试范围切片。
45
48
  - Test matrix 的语义原子是 PRD acceptance criteria、Review findings 和关键风险路径。
46
- - 如果多个领域的测试范围互不依赖,应拆成多个 test plan slices,并在主 `TEST_PLAN.md` 汇总。
47
- - 如果新增测试只是覆盖同一验收标准,应更新原 test slice,不要创建重复测试计划。
49
+ - 如果多个领域的测试范围互不依赖,应拆成多个 test evidence slices,并在主 `TEST_REPORT.md` 汇总。
50
+ - 如果新增测试只是覆盖同一验收标准,应更新原 test slice,不要创建重复测试报告。
48
51
  - 每次新增、拆分或合并 test slice 后,都要更新 `.docs/INDEX.md`。
49
52
 
50
53
  ## Plan Protocol
@@ -63,9 +66,12 @@ description: Use during TESTING to produce a test matrix, run regression, and do
63
66
  1. 测试用例必须追溯到 PRD acceptance criteria 或 Review findings。
64
67
  2. 根据风险补充边界、负向、回归和集成测试。
65
68
  3. 如果有意延后覆盖,必须记录风险和 follow-up。
66
- 4. 并行测试必须使用 `parallel_execution.trigger: "user_requested"`;`runtime_managed` 只在当前 runtime 支持 subagent 时使用,否则输出 `user_orchestrated` worker prompt
67
- 5. 宣布阶段完成前运行 `make test-all`。
68
- 6. 测试阶段一次只执行一个 `TASK-*` task。
69
+ 4. 不得新增 product runtime、server/API/CLI/adapter、poller、cloud bootstrap、systemd unit、真实 provider adapter、package runtime script 或部署脚本;这些属于 SPRINTING/RFC
70
+ 5. 测试发现入口/出口缺失时,Final decision 必须为 `BLOCKED`,并指出回到 SPRINTING/RFC 的具体条件。
71
+ 6. 新测试文档使用 `.docs/07_test/TEST_REPORT.md`;历史 `.docs/07_test/TEST_PLAN.md` 只作为 legacy alias / index 兼容,不作为新事实源命名。
72
+ 7. 并行测试必须使用 `parallel_execution.trigger: "user_requested"`;`runtime_managed` 只在当前 runtime 支持 subagent 时使用,否则输出 `user_orchestrated` worker prompt。
73
+ 8. 宣布阶段完成前运行 `make test-all`。
74
+ 9. 测试阶段一次只执行一个 `TASK-*` task。
69
75
 
70
76
  ## 完成检查
71
77
 
@@ -73,7 +79,8 @@ description: Use during TESTING to produce a test matrix, run regression, and do
73
79
  - [ ] 当前测试工作已绑定 `plan.yaml` 中一个最小 `TASK-*` task,并设置 `phase: "TESTING"`。
74
80
  - [ ] 当前 task 已从 `plan.yaml` 移除,或因中断/blocker 保留为可恢复 open task。
75
81
  - [ ] Regression checklist 已完成。
76
- - [ ] 已判断 test plan / test matrix 的语义切片边界。
82
+ - [ ] 测试只调用既有 runnable entry/exit;未在 TESTING 中新增 product runtime、bootstrap、provider adapter、deploy 或 package runtime script。
83
+ - [ ] 已判断 test report / test matrix 的语义切片边界。
77
84
  - [ ] Coverage gaps 已明确。
78
85
  - [ ] 如果启用了并行测试,worker evidence 已由主 Agent 汇总到测试产物。
79
86
  - [ ] 已运行 `make docs-overview` 刷新 `.docs/<stage>/overview.md`。
@@ -2,18 +2,44 @@
2
2
 
3
3
  ## Status(状态)
4
4
 
5
- Proposed
5
+ Proposed / Accepted / Superseded
6
6
 
7
7
  ## Context(背景)
8
8
 
9
- -
9
+ - 这个决策要解决什么问题?
10
+ - 哪些需求、架构边界、技术方案或约束让这个决策必须长期保留?
11
+
12
+ ## Options(备选方案)
13
+
14
+ - Option A:
15
+ - 优点:
16
+ - 代价/风险:
17
+ - Option B:
18
+ - 优点:
19
+ - 代价/风险:
10
20
 
11
21
  ## Decision(决策)
12
22
 
13
23
  -
14
24
 
25
+ ## Rationale(理由)
26
+
27
+ - 为什么选择这个方案,而不是其它备选方案?
28
+ - 这个选择依赖哪些前提?哪些变化会触发重新评估?
29
+
15
30
  ## Consequences(影响)
16
31
 
17
32
  - 正向影响(Positive):
18
33
  - 负向影响(Negative):
19
34
  - 后续动作(Follow-up):
35
+
36
+ ## Supersedes / Superseded By(替代关系)
37
+
38
+ - Supersedes:
39
+ - Superseded by:
40
+
41
+ ## Links(链接)
42
+
43
+ - Related PRD:
44
+ - Related architecture / tech plan:
45
+ - Related implementation:
@@ -32,7 +32,15 @@ Input
32
32
  -> Output
33
33
  ```
34
34
 
35
- ## 5. 关键实现逻辑
35
+ ## 5. Runnable Entry/Exit(可运行入口/出口)
36
+
37
+ - Entry points:
38
+ - Exit / side effects:
39
+ - Config contract:
40
+ - Fixture/live boundary:
41
+ - Missing runtime boundaries:
42
+
43
+ ## 6. 关键实现逻辑
36
44
 
37
45
  - 输入校验(Input validation):
38
46
  - 核心分支(Core branches):
@@ -40,22 +48,22 @@ Input
40
48
  - 边界兜底(Boundary fallback):
41
49
  - 性能或并发注意事项(Performance or concurrency notes):
42
50
 
43
- ## 6. 与技术方案的偏移
51
+ ## 7. 与技术方案的偏移
44
52
 
45
53
  -
46
54
 
47
- ## 7. 测试覆盖(Test Coverage)
55
+ ## 8. 测试覆盖(Test Coverage)
48
56
 
49
57
  | 测试(Test) | 覆盖范围(Coverage) | 结果(Result) |
50
58
  |---|---|---|
51
59
  | | | |
52
60
 
53
- ## 8. 变更记录(Change Log)
61
+ ## 9. 变更记录(Change Log)
54
62
 
55
63
  | 日期(Date) | Task ID | Commit | 摘要(Summary) |
56
64
  |---|---|---|---|
57
65
  | | | | |
58
66
 
59
- ## 9. 后续维护注意事项
67
+ ## 10. 后续维护注意事项
60
68
 
61
69
  -
@@ -25,7 +25,15 @@
25
25
 
26
26
  -
27
27
 
28
- ## 6. Gate Result(阶段结论)
28
+ ## 6. Runnable Entry/Exit Readiness(可运行入口/出口)
29
+
30
+ - Entry points:
31
+ - Exit / side effects:
32
+ - Config contract:
33
+ - Fixture/live boundary:
34
+ - Blocking gaps before TESTING:
35
+
36
+ ## 7. Gate Result(阶段结论)
29
37
 
30
38
  - Decision: `PASS` / `BLOCKED`
31
39
  - Required before testing:
@@ -1,4 +1,4 @@
1
- # Test Plan(测试计划)
1
+ # Test Report(测试报告)
2
2
 
3
3
  ## 1. Scope(范围)
4
4
 
@@ -13,17 +13,25 @@
13
13
  |---|---|---|---|---|
14
14
  | | | unit/integration/e2e/regression | | pending |
15
15
 
16
- ## 3. Regression Checklist(回归检查清单)
16
+ ## 3. Regression Evidence(回归证据)
17
17
 
18
- - [ ]
18
+ -
19
19
 
20
- ## 4. Coverage Gaps(覆盖缺口)
20
+ ## 4. Runnable Entry/Exit Coverage(可运行入口/出口覆盖)
21
+
22
+ - Existing entry points under test:
23
+ - Expected exits / side effects:
24
+ - Config contract used:
25
+ - Fixture/live boundary:
26
+ - Missing entry/exit blocker:
27
+
28
+ ## 5. Coverage Gaps(覆盖缺口)
21
29
 
22
30
  | 缺口(Gap) | 风险(Risk) | 后续动作(Follow-up) |
23
31
  |---|---|---|
24
32
  | | | |
25
33
 
26
- ## 5. Final Result(最终结论)
34
+ ## 6. Final Decision(最终结论)
27
35
 
28
36
  - Decision: `PASS` / `BLOCKED`
29
37
  - Evidence:
package/dist/lib/init.js CHANGED
@@ -55,7 +55,10 @@ async function createProjectState(projectRoot, root, report) {
55
55
  ],
56
56
  [harnessPath(root, "state", "plan.yaml"), `current_task_id: ""\nnext_task_sequence: 1\ntasks: []\n`],
57
57
  [harnessPath(root, "state", "plan.draft.yaml"), `next_task_sequence: 1\ntasks: []\n`],
58
- [harnessPath(root, "state", "memory.md"), "# Project Memory\n\n短期执行计划写入 plan.yaml;长期稳定知识简短记录在这里,并链接到 `.docs/` 正式出处。\n"]
58
+ [
59
+ harnessPath(root, "state", "memory.md"),
60
+ "# Project Memory\n\n短期执行计划写入 plan.yaml;长期稳定知识只在这里记录简短摘要和链接。完整决策背景、备选方案、取舍和后果写入 `.docs/05_decisions/` ADR 或其它 `.docs/**` 正式事实源。\n"
61
+ ]
59
62
  ];
60
63
  for (const [relative, content] of files) {
61
64
  if (await writeTextIfChanged(path.join(projectRoot, relative), content)) {
@@ -356,7 +356,7 @@ async function ensureMemory(projectRoot, root, report) {
356
356
  report.skipped.push(relativeMemoryPath);
357
357
  return;
358
358
  }
359
- const content = "# Project Memory\n\n记录跨阶段长期有效的稳定知识,并链接到 `.docs/` 正式出处。\n";
359
+ const content = "# Project Memory\n\n记录跨阶段长期有效知识的简短摘要和链接。完整决策背景、备选方案、取舍和后果写入 `.docs/05_decisions/` ADR 或其它 `.docs/**` 正式事实源。\n";
360
360
  if (await writeTextIfChanged(memoryPath, content)) {
361
361
  report.changed.push(relativeMemoryPath);
362
362
  }
@@ -300,7 +300,7 @@ function renderSkillWithOverride(baseContent, override) {
300
300
  "",
301
301
  `${guidance} Keep package-managed Skill files unchanged; edit the override source instead.`,
302
302
  "",
303
- "After sync, review the merged Skill for semantic conflicts between the package base and local override, especially phase boundaries, `allowed_paths`, `required_gates`, commit/release rules and completion checks.",
303
+ "After sync, review the merged Skill for semantic conflicts between the package base and local override, especially phase boundaries, `allowed_paths`, `required_gates`, commit/release rules and completion checks. Package-managed phase boundaries stay authoritative; overrides may narrow local behavior but must not expand TESTING, REVIEWING or other roles into implementation/runtime ownership.",
304
304
  ""
305
305
  ].join("\n");
306
306
  return `${renderedBase.trimEnd()}${header}\n${override.content.trim()}\n`;
@@ -27,6 +27,58 @@ const DESIGN_CATEGORIES = [
27
27
  architectureTerms: ["compliance", "permission", "authorization", "audit", "合规", "权限", "审计", "授权", "客户确认", "回执归档"]
28
28
  }
29
29
  ];
30
+ const TESTING_DISALLOWED_ALLOWED_PATHS = [
31
+ "package.json",
32
+ "**/package.json",
33
+ "package-lock.json",
34
+ "**/package-lock.json",
35
+ "npm-shrinkwrap.json",
36
+ "**/npm-shrinkwrap.json",
37
+ "pnpm-lock.yaml",
38
+ "**/pnpm-lock.yaml",
39
+ "yarn.lock",
40
+ "**/yarn.lock",
41
+ "bun.lock",
42
+ "**/bun.lock",
43
+ "bun.lockb",
44
+ "**/bun.lockb",
45
+ "src/**",
46
+ "app/**",
47
+ "lib/**",
48
+ "server/**",
49
+ "bin/**",
50
+ "cli/**",
51
+ "runtime/**",
52
+ "scripts/**",
53
+ "tools/**",
54
+ "deploy/**",
55
+ "deployment/**",
56
+ "infra/**",
57
+ "ops/**",
58
+ "systemd/**",
59
+ ".github/workflows/**",
60
+ "dockerfile",
61
+ "dockerfile.*",
62
+ "docker-compose*.yml",
63
+ "docker-compose*.yaml",
64
+ "*.service",
65
+ "tests/runtime/**",
66
+ "tests/**/runtime/**"
67
+ ];
68
+ const TESTING_DISALLOWED_CHANGED_PATHS = [...TESTING_DISALLOWED_ALLOWED_PATHS, "scripts/**", "tools/**"];
69
+ const TESTING_RUNTIME_FILE_TERMS = ["bootstrap", "cloud", "daemon", "poller", "provider", "runtime", "service", "systemd"];
70
+ const TESTING_ALLOWED_TEST_FILE_TERMS = ["assertion", "fixture", "mock", "smoke"];
71
+ const TEST_REPORT_PATH = ".docs/07_test/TEST_REPORT.md";
72
+ const LEGACY_TEST_PLAN_PATH = ".docs/07_test/TEST_PLAN.md";
73
+ const RUNNABLE_ENTRY_EXIT_TERMS = [
74
+ "runnable entry/exit",
75
+ "entry/exit",
76
+ "entry points",
77
+ "entry point",
78
+ "可运行入口/出口",
79
+ "入口/出口",
80
+ "not applicable"
81
+ ];
30
82
  const validators = {
31
83
  "validate-harness": validateHarness,
32
84
  "validate-current": validateCurrent,
@@ -262,8 +314,27 @@ async function validateCrossCuttingArchitecture(projectRoot, productFiles, techP
262
314
  return errors;
263
315
  }
264
316
  async function validateDev(projectRoot) {
317
+ const root = await harnessRoot(projectRoot);
265
318
  const plan = await validatePlanState(projectRoot, false);
266
- return { info: [`validate-dev checked ${plan.taskCount} task(s)`], errors: plan.errors };
319
+ const draftErrors = await validateDevDraftConsumed(projectRoot, root);
320
+ const implementationDocErrors = await validateImplementationDocRunnableEntryExit(projectRoot);
321
+ return { info: [`validate-dev checked ${plan.taskCount} task(s)`], errors: [...plan.errors, ...draftErrors, ...implementationDocErrors] };
322
+ }
323
+ async function validateDevDraftConsumed(projectRoot, root) {
324
+ const errors = [];
325
+ const draft = await readYamlObject(path.join(projectRoot, root, "state", "plan.draft.yaml"));
326
+ if ("current_phase" in draft) {
327
+ errors.push("plan.draft.yaml must not define current_phase; lifecycle.yaml is the single source for current_phase");
328
+ }
329
+ if ("current_task_id" in draft) {
330
+ errors.push("plan.draft.yaml must not define current_task_id because drafts are not active task state");
331
+ }
332
+ const tasks = Array.isArray(draft.tasks) ? draft.tasks : [];
333
+ if (tasks.length > 0) {
334
+ const ids = tasks.map((task) => (isRecord(task) ? String(task.id ?? "<missing id>") : "<missing id>")).join(", ");
335
+ errors.push(`Unconsumed draft tasks remain in plan.draft.yaml: ${ids}. Promote the next draft into plan.yaml or remove already-consumed drafts before validate-dev.`);
336
+ }
337
+ return errors;
267
338
  }
268
339
  async function validateReview(projectRoot) {
269
340
  const plan = await validatePlanState(projectRoot, false);
@@ -273,23 +344,37 @@ async function validateReview(projectRoot) {
273
344
  errors.push("Review report must include findings or risks");
274
345
  if (!containsAny(text, ["test gap", "测试缺口", "coverage"]))
275
346
  errors.push("Review report must include test gaps or coverage notes");
347
+ if (!containsAny(text, ["entry/exit", "entrypoint", "入口", "出口", "runnable", "可运行"])) {
348
+ errors.push("Review report must assess runnable entry/exit readiness before TESTING");
349
+ }
276
350
  if (!containsAny(text, ["pass", "blocked", "通过", "阻塞"]))
277
351
  errors.push("Review report must include PASS/BLOCKED decision");
278
352
  return { info: ["validate-review checked review report"], errors };
279
353
  }
280
354
  async function validateTest(projectRoot) {
355
+ const root = await harnessRoot(projectRoot);
356
+ const lifecycle = await readYamlObject(path.join(projectRoot, root, "state", "lifecycle.yaml"));
281
357
  const plan = await validatePlanState(projectRoot, false);
282
- const text = (await readText(path.join(projectRoot, ".docs/07_test/TEST_PLAN.md"))).toLowerCase();
283
358
  const errors = [...plan.errors];
359
+ const report = await readTestReport(projectRoot);
360
+ const text = report ? report.text.toLowerCase() : "";
361
+ if (!report)
362
+ errors.push(`Missing test report: expected ${TEST_REPORT_PATH} or legacy ${LEGACY_TEST_PLAN_PATH}`);
284
363
  if (!containsAny(text, ["matrix", "矩阵"]))
285
- errors.push("Test plan must include a test matrix");
364
+ errors.push("Test report must include a test matrix");
286
365
  if (!containsAny(text, ["regression", "回归"]))
287
- errors.push("Test plan must include regression coverage");
366
+ errors.push("Test report must include regression evidence");
288
367
  if (!containsAny(text, ["coverage gap", "覆盖缺口", "gap"]))
289
- errors.push("Test plan must include coverage gaps");
368
+ errors.push("Test report must include coverage gaps");
369
+ if (!containsAny(text, ["entry/exit", "entrypoint", "入口", "出口", "runnable", "可运行"])) {
370
+ errors.push("Test report must state existing runnable entry/exit coverage or blocker status");
371
+ }
290
372
  if (!containsAny(text, ["pass", "blocked", "通过", "阻塞"]))
291
- errors.push("Test plan must include PASS/BLOCKED decision");
292
- return { info: ["validate-test checked test plan"], errors };
373
+ errors.push("Test report must include PASS/BLOCKED decision");
374
+ if (lifecycle.current_phase === "TESTING") {
375
+ errors.push(...testingBoundaryErrorsForChangedFiles(await changedFiles(projectRoot)));
376
+ }
377
+ return { info: [`validate-test checked ${report?.source ?? "missing test report"}`], errors };
293
378
  }
294
379
  async function validateRelease(projectRoot) {
295
380
  const plan = await validatePlanState(projectRoot, false);
@@ -399,6 +484,7 @@ async function validatePlanState(projectRoot, allowOpen) {
399
484
  if (!Array.isArray(task.acceptance_criteria) || task.acceptance_criteria.length === 0) {
400
485
  errors.push(`Open task ${task.id} must define acceptance_criteria`);
401
486
  }
487
+ errors.push(...testingBoundaryErrorsForAllowedPaths(task));
402
488
  }
403
489
  else {
404
490
  errors.push(`Completed task ${task.id} must not remain in plan.yaml`);
@@ -546,6 +632,28 @@ async function readYamlObject(filePath) {
546
632
  return {};
547
633
  return (parseYaml(await readText(filePath)) ?? {});
548
634
  }
635
+ async function readTestReport(projectRoot) {
636
+ const canonical = path.join(projectRoot, TEST_REPORT_PATH);
637
+ if (await pathExists(canonical)) {
638
+ return { text: await readText(canonical), source: TEST_REPORT_PATH };
639
+ }
640
+ const legacy = path.join(projectRoot, LEGACY_TEST_PLAN_PATH);
641
+ if (await pathExists(legacy)) {
642
+ return { text: await readText(legacy), source: LEGACY_TEST_PLAN_PATH };
643
+ }
644
+ return undefined;
645
+ }
646
+ async function validateImplementationDocRunnableEntryExit(projectRoot) {
647
+ const docs = await markdownFiles(path.join(projectRoot, ".docs/04_implementation"));
648
+ const errors = [];
649
+ for (const doc of docs) {
650
+ const text = await readText(doc);
651
+ if (!containsAny(text, RUNNABLE_ENTRY_EXIT_TERMS)) {
652
+ errors.push(`Implementation doc must include Runnable Entry/Exit facts or explicit Not applicable: ${repoRelative(projectRoot, doc)}`);
653
+ }
654
+ }
655
+ return errors;
656
+ }
549
657
  async function markdownFiles(root) {
550
658
  const files = await listFiles(root);
551
659
  return files.filter((file) => {
@@ -561,6 +669,47 @@ function containsAny(text, needles) {
561
669
  const lowered = text.toLowerCase();
562
670
  return needles.some((needle) => lowered.includes(needle.toLowerCase()));
563
671
  }
672
+ function testingBoundaryErrorsForAllowedPaths(task) {
673
+ if (task.phase !== "TESTING")
674
+ return [];
675
+ const allowed = Array.isArray(task.allowed_paths) ? task.allowed_paths.map((item) => String(item)) : [];
676
+ const blocked = allowed.filter((item) => isTestingBoundaryAllowedPath(item));
677
+ if (blocked.length === 0)
678
+ return [];
679
+ return [
680
+ `TESTING task allowed_paths must not include product runtime, package/deploy config, or long-running runtime paths: ${blocked.join(", ")}`
681
+ ];
682
+ }
683
+ function testingBoundaryErrorsForChangedFiles(files) {
684
+ const blocked = files.filter((file) => isTestingRuntimeBoundaryChange(file));
685
+ if (blocked.length === 0)
686
+ return [];
687
+ return [
688
+ `TESTING changes must use existing product entrypoints only; move runtime, bootstrap, provider, deploy, or package script changes to SPRINTING/RFC: ${blocked.join(", ")}`
689
+ ];
690
+ }
691
+ function isTestingBoundaryAllowedPath(file) {
692
+ const lowered = file.replace(/\\/g, "/").toLowerCase();
693
+ if (["package.json", "package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"].includes(lowered)) {
694
+ return true;
695
+ }
696
+ return matchesAny(lowered, TESTING_DISALLOWED_ALLOWED_PATHS);
697
+ }
698
+ function isTestingRuntimeBoundaryChange(file) {
699
+ const normalized = file.replace(/\\/g, "/");
700
+ const lowered = normalized.toLowerCase();
701
+ if (isTestingBoundaryAllowedPath(lowered) || matchesAny(lowered, TESTING_DISALLOWED_CHANGED_PATHS)) {
702
+ return true;
703
+ }
704
+ if (lowered.startsWith("tests/")) {
705
+ const name = path.basename(lowered);
706
+ if (TESTING_ALLOWED_TEST_FILE_TERMS.some((term) => name.includes(term))) {
707
+ return false;
708
+ }
709
+ return TESTING_RUNTIME_FILE_TERMS.some((term) => name.includes(term));
710
+ }
711
+ return false;
712
+ }
564
713
  function isDevelopmentDraft(task) {
565
714
  const taskId = String(task.id ?? "");
566
715
  return Boolean(task.implementation_doc) || task.phase === "SPRINTING" || taskId.startsWith("DEV-");
@@ -591,7 +740,7 @@ function taskText(task) {
591
740
  }
592
741
  export async function changedFiles(projectRoot) {
593
742
  try {
594
- const { stdout } = await execFileAsync("git", ["status", "--porcelain"], { cwd: projectRoot });
743
+ const { stdout } = await execFileAsync("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: projectRoot });
595
744
  return stdout
596
745
  .split("\n")
597
746
  .map((line) => line.slice(3).trim())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-project-sdlc",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "CLI and canonical assets for the AI SDLC Harness workflow.",
5
5
  "type": "module",
6
6
  "bin": {