agent-project-sdlc 0.1.0 → 0.1.2

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.
@@ -29,23 +29,26 @@
29
29
 
30
30
  ## Plan Protocol
31
31
 
32
- - `plan.yaml` 是当前执行计划事实源。open task 直接包含 `allowed_paths`、`required_gates`、`acceptance_criteria` 和必要的 `working_notes`。
33
- - task 完成并写入 implementation doc 后,删除 open task 的详细执行字段,只保留简短 `summary`、`implementation_doc` 和 `gate_result`。
32
+ - `plan.yaml` 是当前和未来任务的短期执行计划事实源。open task 直接包含 `allowed_paths`、`required_gates`、`acceptance_criteria` 和必要的 `working_notes`。
33
+ - `next_task_sequence` 记录下一个可分配的 `DEV-*` 序号,避免删除历史 task 后发生 id 冲突。
34
+ - task 完成并写入 implementation doc 后,从 `plan.yaml` 的 `tasks` 列表移除该 task;不要长期保留 done/cancelled task 摘要。
34
35
  - `plan.draft.yaml` 是架构阶段生成的计划草案,不自动覆盖 `plan.yaml`。
35
36
  - 不维护 checkpoint 文件;任务现场只存在于 open task 的 plan 条目里。
36
37
  - 历史动作记录以 git commit 为准,产物结果以 implementation doc 为准。
37
- - `SPRINTING` 阶段每完成一个 task,先在 open task 仍保留完整 `allowed_paths`、`required_gates`、`acceptance_criteria` 等执行合同时创建 task implementation commit;随后再压缩 `plan.yaml` done 摘要并创建 task completion ledger commit。两段提交 push 成功前不进入下一个 task。
38
+ - `SPRINTING` 阶段每完成一个 task,先在 task 仍位于 `plan.yaml` 时创建 task implementation commit;随后再从 `plan.yaml` 移除该 task 并创建 task completion ledger commit。两段提交 push 成功前不进入下一个 task。
39
+ - 历史 task 查询默认面向产物结果和变更意图,读取 implementation doc、RFC、PRD、tech plan 和代码;`allowed_paths`、`required_gates`、临时 `working_notes` 是执行期约束,不作为历史查询 API。
40
+ - 过去 phase/task/gate 执行流水不是默认上下文;除非用户明确要求 forensic/audit/regression 追溯,否则不要读取或恢复历史执行信息。
38
41
 
39
42
  ## Prompt Language Contract
40
43
 
41
44
  - 面向人阅读的说明、规则、SOP、检查清单使用中文。
42
45
  - 机器契约保持英文,包括字段名、路径、命令、阶段枚举、状态枚举、脚本参数。
43
- - 不翻译 `.agent/state/*.yaml`、`.agent/managed/policies/*.yaml` 中的 key。
46
+ - 不翻译 `.agent/state/*.yaml`、`.agent/pjsdlc_managed/policies/*.yaml` 中的 key。
44
47
  - 不翻译 `current_phase`、`active_skill`、`allowed_paths`、`required_gates`、`implementation_doc` 等字段名。
45
48
  - 不翻译 `REQUIREMENT_GATHERING`、`ARCHITECTING`、`SPRINTING`、`REVIEWING`、`TESTING`、`RELEASING`、`RFC_RECALIBRATION`、`BLOCKED` 等阶段枚举。
46
49
  - 不翻译 `pending`、`in_progress`、`done`、`blocked`、`pending_revision`、`cancelled` 等任务状态。
47
50
  - 不翻译 `make validate-*`、`python3 tools/transition.py --to <PHASE>`、`.docs/01_product/`、`.agent/state/plan.yaml` 等命令和路径。
48
- - 后续更新 `.agent/skills/*/SKILL.md` 或 `.agent/managed/templates/*.md` 时,遵循“中文解释 + 英文精确标识符”。Harness 根目录由 `package.json#sdlcHarness.harnessFolderName` 或 `sdlc-harness.config.json#harnessFolderName` 决定;未配置的项目默认使用 `.agent`。
51
+ - 后续更新 `.agent/skills/*/SKILL.md` 或 `.agent/pjsdlc_managed/templates/*.md` 时,遵循“中文解释 + 英文精确标识符”。Harness 根目录由 `package.json#sdlcHarness.harnessFolderName` 或 `sdlc-harness.config.json#harnessFolderName` 决定;未配置的项目默认使用 `.agent`。
49
52
 
50
53
  ## 通用执行原则
51
54
 
@@ -136,13 +139,30 @@ Strong success criteria 可以让你 independent loop。Weak criteria,例如
136
139
  8. `reviewer` 角色只读,不直接修改源码。
137
140
  9. 需求变更必须进入 RFC 工作流。
138
141
  10. task/release 历史动作记录使用 git commit、tag 或外部 release 系统,不维护 `.agent/archive/` 常规归档。
139
- 11. 在 `SPRINTING` 阶段,task 完成闭环必须先提交保留完整 open task 合同的 task implementation commit,再提交压缩 `plan.yaml` 后的 task completion ledger commit;如果没有 remote/upstream、权限或凭证导致无法 push,不要开始下一个 task,先报告 blocker。
142
+ 11. 在 `SPRINTING` 阶段,task 完成闭环必须先创建 task implementation commit,再提交移除该 task 后的 task completion ledger commit;如果没有 remote/upstream、权限或凭证导致无法 push,不要开始下一个 task,先报告 blocker。
140
143
  12. 文档 slice 发生变化后,运行 `make docs-overview` 刷新对应 `overview.md`。
141
144
  13. open task 必须在 `plan.yaml` 中包含完整执行合同,再继续推进或交接。
142
- 14. 如果信息缺失,或 gate 因基础设施原因失败,停止推进并报告 blocker。
145
+ 14. 默认不读取过去 task phase 执行流水;修 bug、补功能和阶段交付以当前代码、测试、PRD、技术方案和 implementation doc 为准。
146
+ 15. gate 证据写入当前 task `working_notes` 或 implementation doc 的 `Verification`;不要维护独立 gate results state。
147
+ 16. 如果信息缺失,或 gate 因基础设施原因失败,停止推进并报告 blocker。
143
148
 
144
149
  ## 宏指令
145
150
 
151
+ 用户不需要记忆或使用宏指令。自然语言是主要控制方式;以下 `/xxx` 只作为快捷入口、调试入口或自动化入口保留。
152
+
153
+ 当用户用自然语言表达意图时,Agent 应先读取 `.agent/state/lifecycle.yaml` 和必要的 `.agent/state/plan.yaml`,再映射到对应 workflow action:
154
+
155
+ - “现在到哪了 / 状态如何” → 等价 `/status`。
156
+ - “继续 / 下一步 / 推进” → 等价 `/next`。
157
+ - “能进入下一阶段吗 / 进入下一步” → 等价 `/advance`。
158
+ - “需求变了 / 这个设计要改” → 进入 RFC workflow。
159
+ - “开始开发 / 做当前任务” → 在 `SPRINTING` 执行当前 open task。
160
+ - “跑测试 / 验证一下” → 运行当前 task 或阶段对应 gate。
161
+ - “准备 review / 帮我 review” → 进入只读 Review workflow。
162
+ - “刷新文档总览 / 同步 overview” → 等价 `/overview`。
163
+
164
+ 如果自然语言意图会改变阶段、创建或删除 task、提交、push 或发布,Agent 先用一句话说明将执行的动作和验证方式,再继续。
165
+
146
166
  - `/status`:报告当前阶段、角色、任务、阻塞项和下一步动作。
147
167
  - `/next`:运行当前阶段映射的 Skill。
148
168
  - `/advance`:校验当前阶段出口 gate,通过后才尝试流转。
@@ -161,4 +181,4 @@ python3 tools/transition.py --to <PHASE>
161
181
  ```
162
182
 
163
183
  流转前先运行阶段 gate,通常使用 `make validate-current`,或使用
164
- `.agent/policies/phase_contracts.yaml` 中列出的具体 `make validate-*` 目标。
184
+ `.agent/pjsdlc_managed/policies/phase_contracts.yaml` 中列出的具体 `make validate-*` 目标。
@@ -27,7 +27,6 @@ phases:
27
27
  - ".docs/04_implementation/**"
28
28
  - ".docs/INDEX.md"
29
29
  - "<harnessRoot>/state/plan.yaml"
30
- - "<harnessRoot>/state/gate_results.log"
31
30
 
32
31
  REVIEWING:
33
32
  read_only_source: true
@@ -2,7 +2,7 @@ phases:
2
2
  IDLE:
3
3
  goal: "等待项目启动"
4
4
  role: "manager"
5
- skill: "manager"
5
+ skill: "pjsdlc_manager"
6
6
  inputs:
7
7
  - "<harnessRoot>/state/lifecycle.yaml"
8
8
  outputs:
@@ -13,7 +13,7 @@ phases:
13
13
  REQUIREMENT_GATHERING:
14
14
  goal: "收集需求并形成产品方案"
15
15
  role: "pm"
16
- skill: "pm_prd"
16
+ skill: "pjsdlc_pm_prd"
17
17
  inputs:
18
18
  - ".docs/00_raw/"
19
19
  outputs:
@@ -26,7 +26,7 @@ phases:
26
26
  ARCHITECTING:
27
27
  goal: "根据产品方案生成架构设计、技术方案和任务草案"
28
28
  role: "architect"
29
- skill: "architect_design"
29
+ skill: "pjsdlc_architect_design"
30
30
  inputs:
31
31
  - ".docs/01_product/"
32
32
  - ".docs/02_architecture/"
@@ -41,7 +41,7 @@ phases:
41
41
  SPRINTING:
42
42
  goal: "按任务状态执行开发、测试和实现文档沉淀"
43
43
  role: "developer"
44
- skill: "dev_sprint"
44
+ skill: "pjsdlc_dev_sprint"
45
45
  inputs:
46
46
  - "<harnessRoot>/state/plan.yaml"
47
47
  - "current open task plan contract"
@@ -51,7 +51,6 @@ phases:
51
51
  - "src/"
52
52
  - "tests/"
53
53
  - ".docs/04_implementation/"
54
- - "<harnessRoot>/state/gate_results.log"
55
54
  gates:
56
55
  - "make validate-dev"
57
56
  next: "REVIEWING"
@@ -59,7 +58,7 @@ phases:
59
58
  REVIEWING:
60
59
  goal: "只读审查实现质量、需求一致性和架构风险"
61
60
  role: "reviewer"
62
- skill: "reviewer"
61
+ skill: "pjsdlc_reviewer"
63
62
  inputs:
64
63
  - ".docs/01_product/"
65
64
  - ".docs/03_tech_plan/"
@@ -74,7 +73,7 @@ phases:
74
73
  TESTING:
75
74
  goal: "形成测试矩阵并完成回归验证"
76
75
  role: "tester"
77
- skill: "tester"
76
+ skill: "pjsdlc_tester"
78
77
  inputs:
79
78
  - ".docs/01_product/"
80
79
  - ".docs/03_tech_plan/"
@@ -90,7 +89,7 @@ phases:
90
89
  RELEASING:
91
90
  goal: "发布准备、发布检查和回滚方案"
92
91
  role: "release_manager"
93
- skill: "release_manager"
92
+ skill: "pjsdlc_release_manager"
94
93
  inputs:
95
94
  - ".docs/07_test/"
96
95
  - "build artifacts"
@@ -103,7 +102,7 @@ phases:
103
102
  COMPLETED:
104
103
  goal: "当前里程碑已完成"
105
104
  role: "manager"
106
- skill: "manager"
105
+ skill: "pjsdlc_manager"
107
106
  inputs:
108
107
  - ".docs/08_release/"
109
108
  outputs:
@@ -114,7 +113,7 @@ phases:
114
113
  RFC_RECALIBRATION:
115
114
  goal: "处理需求变更、影响分析、局部补丁和任务回退"
116
115
  role: "rfc_owner"
117
- skill: "rfc_recalibrate"
116
+ skill: "pjsdlc_rfc_recalibrate"
118
117
  inputs:
119
118
  - ".docs/rfc/"
120
119
  - ".docs/01_product/"
@@ -131,7 +130,7 @@ phases:
131
130
  BLOCKED:
132
131
  goal: "等待人工补充信息、授权或基础设施处理"
133
132
  role: "manager"
134
- skill: "manager"
133
+ skill: "pjsdlc_manager"
135
134
  inputs:
136
135
  - "<harnessRoot>/state/lifecycle.yaml"
137
136
  outputs:
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: architect_design
2
+ name: pjsdlc_architect_design
3
3
  description: Use during ARCHITECTING to create architecture docs, technical plans, interface contracts, and task drafts.
4
4
  ---
5
5
 
@@ -23,8 +23,8 @@ description: Use during ARCHITECTING to create architecture docs, technical plan
23
23
  - 相关 `.docs/01_product/` PRD
24
24
  - 现有 `.docs/02_architecture/`
25
25
  - 当前代码结构概览
26
- - `<harnessRoot>/managed/templates/TECH_DESIGN_TEMPLATE.md`
27
- - `<harnessRoot>/managed/templates/PLAN_TEMPLATE.yaml`
26
+ - `<harnessRoot>/pjsdlc_managed/templates/TECH_DESIGN_TEMPLATE.md`
27
+ - `<harnessRoot>/pjsdlc_managed/templates/PLAN_TEMPLATE.yaml`
28
28
 
29
29
  ## 输出
30
30
 
@@ -48,7 +48,7 @@ description: Use during ARCHITECTING to create architecture docs, technical plan
48
48
  1. 技术方案必须引用 PRD 路径和 requirement IDs。
49
49
  2. 每个 open task 必须包含 `id`、`title`、`status`、`summary`、`docs`、`allowed_paths`、`required_gates`、`acceptance_criteria` 和 `implementation_doc`。
50
50
  3. `plan.draft.yaml` 不得自动覆盖 `plan.yaml`。
51
- 4. 风险或不清晰的问题按 `<harnessRoot>/managed/policies/risk_matrix.yaml` 标记。
51
+ 4. 风险或不清晰的问题按 `<harnessRoot>/pjsdlc_managed/policies/risk_matrix.yaml` 标记。
52
52
  5. 任务边界应足够小,能在一次开发执行和一份 implementation doc 内闭环。
53
53
 
54
54
  ## 完成检查
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: dev_sprint
2
+ name: pjsdlc_dev_sprint
3
3
  description: Use during SPRINTING to execute one task from plan.yaml, respecting the active plan contract.
4
4
  ---
5
5
 
@@ -15,7 +15,7 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
15
15
 
16
16
  开始编码前,先确认当前 open task 是否完整,修改范围是否覆盖必要文件,验收标准是否能被测试或 gate 验证。如果发现任务边界、产品行为或技术方案不清晰,要停下来说明 blocker、给出可能解释和推荐下一步,而不是扩大范围继续写。
17
17
 
18
- 实现时遵循小步闭环:先检查 `git status`,确认工作区没有未归属到当前 task 的脏变更;再定位相关代码和测试,做必要修改,运行 gate,修复失败,写 implementation doc 并刷新文档派生视图。此时先不要压缩 `plan.yaml`,要在当前 task 仍保留完整执行合同的状态下创建 task implementation commit;随后再压缩 task,创建 task completion ledger commit,并 push 两个 commit。不要顺手重构、重排格式或处理无关问题;如果发现无关风险,只记录或报告。
18
+ 实现时遵循小步闭环:先检查 `git status`,确认工作区没有未归属到当前 task 的脏变更;再定位相关代码和测试,做必要修改,运行 gate,修复失败,写 implementation doc 并刷新文档派生视图。此时先不要从 `plan.yaml` 移除当前 task,要在当前 task 仍位于 `plan.yaml` 时创建 task implementation commit;随后再移除 task,创建 task completion ledger commit,并 push 两个 commit。不要顺手重构、重排格式或处理无关问题;如果发现无关风险,只记录或报告。
19
19
 
20
20
  ## 输入
21
21
 
@@ -29,11 +29,11 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
29
29
  - 当前 task `allowed_paths` 范围内的源码改动
30
30
  - 当前 task `allowed_paths` 范围内的测试改动
31
31
  - `.docs/04_implementation/` 下的 implementation doc
32
- - `<harnessRoot>/state/gate_results.log` 中的 gate 结果
32
+ - 当前 task `working_notes` 或 implementation doc `Verification` 中的 gate evidence
33
33
  - 更新后的 `<harnessRoot>/state/plan.yaml`
34
34
  - 更新后的 `.docs/INDEX.md`
35
- - 保留完整 open task 合同的 task implementation commit
36
- - 压缩 `plan.yaml` 后的 task completion ledger commit
35
+ - 当前 task 移除前创建的 task implementation commit
36
+ - `plan.yaml` 移除当前 task 后的 task completion ledger commit
37
37
  - 已 push 到当前 upstream branch 的远端提交
38
38
 
39
39
  ## 语义切片
@@ -41,8 +41,8 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
41
41
  - `SPRINTING` 阶段的执行单元是 `current_task_id`,不要在开发中重新生成整个 Sprint 计划。
42
42
  - 当前任务就是开发阶段的主要语义切片,代码、测试、gate 记录和 implementation doc 都围绕该任务闭环。
43
43
  - open task 在 `plan.yaml` 中直接保存 `docs`、`allowed_paths`、`required_gates`、`acceptance_criteria` 和必要的 `working_notes`。
44
- - task implementation commit 必须发生在 task 压缩之前,此时 `plan.yaml` 中当前 task 仍保留 `docs`、`allowed_paths`、`required_gates`、`acceptance_criteria` 和必要 `working_notes`。
45
- - task completion ledger commit 发生在 implementation commit 之后,只负责将该 task 压缩为 `summary`、`implementation_doc`、`gate_result` 等简短摘要。
44
+ - task implementation commit 必须发生在 task 移除之前,避免实现变更和计划短期化混在同一个提交里。
45
+ - task completion ledger commit 发生在 implementation commit 之后,只负责将该 task 从当前 `plan.yaml` 移除。
46
46
  - 一个开发 task 默认对应一个主要 implementation commit 和一个轻量 completion ledger commit。implementation commit message 应包含 task id,例如 `DEV-003: implement login rate limit`;push 成功前,不进入下一个 task。
47
47
  - 本 Skill 不直接重切 PRD 或 tech plan;如果发现上游语义边界错误,进入 `BLOCKED`、创建 RFC,或请求回到 `ARCHITECTING`。
48
48
  - gate 通过后调用 `implementation_doc`,由该 Skill 按真实实现生成 `.docs/04_implementation/` slice。
@@ -55,9 +55,11 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
55
55
  1. `current_task_id` 指向正在执行的 open task。
56
56
  2. open task 直接声明 `docs`、`allowed_paths`、`required_gates`、`acceptance_criteria`。
57
57
  3. 任务执行中只保留恢复所需的简短 `working_notes`。
58
- 4. gate、implementation doc、`.docs/INDEX.md` 和 `overview.md` 完成后,先保持 open task 完整合同不变,创建 task implementation commit。
59
- 5. implementation commit 完成后,再把 task 标记为 `done`,并移除 `docs`、`allowed_paths`、`required_gates`、`acceptance_criteria`、`working_notes`。
60
- 6. 将压缩后的 `plan.yaml` 和必要 gate 记录提交为 task completion ledger commit,并 `git push` 两个 commit 到当前 upstream branch。
58
+ 4. gate、implementation doc、`.docs/INDEX.md` 和 `overview.md` 完成后,在当前 task 仍位于 `plan.yaml` 时创建 task implementation commit。
59
+ 5. implementation commit 完成后,再把该 task `plan.yaml` `tasks` 列表移除,并保留/递增 `next_task_sequence`。
60
+ 6. 将移除当前 task 后的 `plan.yaml` 提交为 task completion ledger commit,并 `git push` 两个 commit 到当前 upstream branch。
61
+
62
+ done task 的执行流水不在当前 `plan.yaml` 长期保留,也不是默认上下文。修 bug、补功能和继续开发时,优先读取当前代码、测试、PRD、技术方案和 implementation doc;历史 task 查询主要看“做了什么、为什么做、产物在哪里、验证了什么”。`allowed_paths`、`required_gates`、临时 `working_notes` 是执行期约束,不作为历史查询 API。只有用户明确要求 forensic/audit/regression 追溯时,才临时使用 git、PR 或 CI 记录。
61
63
 
62
64
  ## 规则
63
65
 
@@ -69,10 +71,11 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
69
71
  6. 如果 gate 因基础设施、凭证缺失、产品行为不清或高风险架构变化失败,进入 `BLOCKED`。
70
72
  7. gate 通过后调用 `implementation_doc`。
71
73
  8. 只有 gate 通过且 implementation doc 校验通过后,才能把任务标记为 `done`。
72
- 9. 任务完成并写入 implementation doc、刷新 `overview.md`、记录 gate 后,先创建 task implementation commit;此时不要压缩该 task。
73
- 10. task implementation commit 必须包含未压缩的 open task 合同,确保 git history 能看到当时的 `allowed_paths`、`required_gates` 和 `acceptance_criteria`。
74
- 11. implementation commit 完成后,压缩该 task,只保留简短摘要和 gate 结果,并创建 task completion ledger commit。
75
- 12. 两个 commit 后必须 `git push` 到当前 upstream branch;如果没有 remote/upstream、权限或凭证导致无法 push,停止推进并报告 blocker。
74
+ 9. 任务完成并写入 implementation doc、刷新 `overview.md`、记录 gate 后,先创建 task implementation commit;此时不要移除该 task。
75
+ 10. task implementation commit 必须发生在 task 移除前;后续默认不要读取其中的执行期字段,历史查询以 implementation doc、RFC、PRD、tech plan 和代码为主。
76
+ 11. implementation commit 完成后,从当前 `plan.yaml` 移除该 task,并创建 task completion ledger commit。
77
+ 12. 默认不追溯已完成 task 的执行流水;只有显式 forensic/audit/regression 任务才临时查询 git、PR CI 记录。
78
+ 13. 两个 commit 后必须 `git push` 到当前 upstream branch;如果没有 remote/upstream、权限或凭证导致无法 push,停止推进并报告 blocker。
76
79
 
77
80
  ## 完成检查
78
81
 
@@ -81,9 +84,9 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
81
84
  - [ ] open task 在 `plan.yaml` 中包含完整执行合同。
82
85
  - [ ] 当前任务仍然是单一清晰的开发语义切片。
83
86
  - [ ] implementation doc 已生成并反映真实代码。
84
- - [ ] 任务状态和 gate 结果已更新。
85
- - [ ] task implementation commit 已在 task 压缩前创建,且包含完整 open task 合同。
86
- - [ ] done task 已在 implementation commit 之后压缩为简短摘要。
87
+ - [ ] gate 结果已写入 implementation doc `Verification`,必要时当前 task `working_notes` 也记录了恢复现场所需的 gate evidence。
88
+ - [ ] task implementation commit 已在 task 移除前创建。
89
+ - [ ] done task 已在 implementation commit 之后从当前 `plan.yaml` 移除。
87
90
  - [ ] `.docs/INDEX.md` 已链接 implementation doc。
88
91
  - [ ] 已运行 `make docs-overview` 刷新 `.docs/<stage>/overview.md`。
89
92
  - [ ] 已创建 task completion ledger commit。
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: implementation_doc
2
+ name: pjsdlc_implementation_doc
3
3
  description: Use after a development task passes code gates to document the real implementation and plan deviations.
4
4
  ---
5
5
 
@@ -24,7 +24,7 @@ description: Use after a development task passes code gates to document the real
24
24
  - 关联 PRD
25
25
  - 关联技术方案
26
26
  - 变更后的源码和测试文件
27
- - `<harnessRoot>/managed/templates/IMPLEMENTATION_DOC_TEMPLATE.md`
27
+ - `<harnessRoot>/pjsdlc_managed/templates/IMPLEMENTATION_DOC_TEMPLATE.md`
28
28
 
29
29
  ## 输出
30
30
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: manager
2
+ name: pjsdlc_manager
3
3
  description: Use when checking project phase, routing macro commands, validating exit gates, or switching lifecycle phase.
4
4
  ---
5
5
 
@@ -16,14 +16,14 @@ Skill、执行出口 gate,并记录 blocker。
16
16
 
17
17
  与用户对话时,先读取 lifecycle 和 plan,再说明当前阶段、active_skill、当前任务、阻塞项和下一步。不要基于猜测切换阶段;如果用户要求的动作与当前阶段冲突,说明冲突、可选路径和推荐处理方式。
18
18
 
19
- 执行 `/status`、`/next`、`/advance`、`/rfc` 等宏指令时,输出要短而明确:当前事实是什么、将调用哪个 gate 或 Skill、成功后会进入哪里、失败时如何保持状态安全。
19
+ 自然语言是默认控制方式。用户说“状态如何”“继续”“下一步”“开始开发”“跑测试”“准备 review”“需求变了”等,不应要求用户记忆 `/xxx`;你应先读取 lifecycle 和 plan,再把意图映射到对应 workflow action。执行 `/status`、`/next`、`/advance`、`/rfc` 等宏指令时,输出要短而明确:当前事实是什么、将调用哪个 gate 或 Skill、成功后会进入哪里、失败时如何保持状态安全。
20
20
 
21
21
  ## 输入
22
22
 
23
23
  - `<harnessRoot>/state/lifecycle.yaml`
24
24
  - `<harnessRoot>/state/plan.yaml`
25
25
  - `.docs/INDEX.md`
26
- - `<harnessRoot>/managed/policies/phase_contracts.yaml`
26
+ - `<harnessRoot>/pjsdlc_managed/policies/phase_contracts.yaml`
27
27
 
28
28
  ## 规则
29
29
 
@@ -37,14 +37,25 @@ Skill、执行出口 gate,并记录 blocker。
37
37
  8. 用户输入 `/advance` 时,运行 `make validate-current`,通过后流转到配置的 `next` 阶段。
38
38
  9. 用户输入 `/rfc <file>` 时,流转到 `RFC_RECALIBRATION` 并调用 `rfc_recalibrate`。
39
39
  10. 如果当前 task 处于 `blocked` 或缺少 open task 必需的 plan 字段,不要推进阶段,先要求 `plan.yaml` 完整。
40
+ 11. 用户自然语言询问状态时,等价执行 `/status`。
41
+ 12. 用户自然语言要求继续、推进或下一步时,等价执行 `/next`。
42
+ 13. 用户自然语言要求进入下一阶段或检查是否可进入下一阶段时,等价执行 `/advance`。
43
+ 14. 用户自然语言表达需求或设计变化时,进入 RFC workflow。
44
+ 15. 用户自然语言要求开始开发或做当前任务时,如果 `current_phase` 是 `SPRINTING`,执行当前 open task;否则说明当前阶段冲突和推荐路径。
45
+ 16. 用户自然语言要求跑测试或验证时,运行当前 task 或当前阶段的对应 gate。
46
+ 17. 用户自然语言要求 review 时,进入只读 Review workflow,不直接改源码。
47
+ 18. 用户自然语言要求刷新文档总览时,运行 `make docs-overview`。
48
+ 19. 如果动作会改变阶段、创建或删除 task、提交、push 或发布,先用一句话说明即将执行的动作和验证方式,再继续。
40
49
 
41
50
  ## Plan Protocol
42
51
 
43
- 每个 open task 都必须在 `plan.yaml` 中包含 `docs`、`allowed_paths`、`required_gates` 和 `acceptance_criteria`;done/cancelled task 只保留简短摘要、implementation doc gate result。完成后的历史事实以 git commit implementation doc 为准。
52
+ 每个 open task 都必须在 `plan.yaml` 中包含 `docs`、`allowed_paths`、`required_gates` 和 `acceptance_criteria`;done/cancelled task 不长期留在当前 `plan.yaml`。完成后的产物事实以 implementation doc 为准,动作历史以 git/PR/CI/release 系统作为 cold archive,`next_task_sequence` 负责继续分配后续 task id。
53
+
54
+ `lifecycle.yaml` 和 `plan.yaml` 只用于当前可执行状态。默认不要读取过去 phase/task/gate 执行流水;只有用户明确要求 forensic/audit/regression 追溯时,才临时查询 git、PR、CI 或 release 记录。
44
55
 
45
56
  ## 完成检查
46
57
 
47
58
  - [ ] 已确认 `current_phase`、`active_role`、`active_skill` 和下一阶段。
48
- - [ ] gate 结果已记录到 `<harnessRoot>/state/gate_results.log`。
59
+ - [ ] 当前 task/phase 的 gate 结果已进入 task notes、implementation doc、CI 或 release 记录。
49
60
  - [ ] 如当前 task 是 open task,`plan.yaml` 中的执行合同完整。
50
61
  - [ ] 生命周期只通过 `tools/transition.py` 发生变化。
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: pm_prd
2
+ name: pjsdlc_pm_prd
3
3
  description: Use during REQUIREMENT_GATHERING to turn raw input into PRD slices with acceptance criteria and boundaries.
4
4
  ---
5
5
 
@@ -23,7 +23,7 @@ description: Use during REQUIREMENT_GATHERING to turn raw input into PRD slices
23
23
  - `.docs/00_raw/`
24
24
  - 现有 `.docs/01_product/`
25
25
  - 现有 `.docs/rfc/`
26
- - `<harnessRoot>/managed/templates/PRD_TEMPLATE.md`
26
+ - `<harnessRoot>/pjsdlc_managed/templates/PRD_TEMPLATE.md`
27
27
 
28
28
  ## 输出
29
29
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: release_manager
2
+ name: pjsdlc_release_manager
3
3
  description: Use during RELEASING to prepare release notes, smoke evidence, deployment checks, and rollback plan.
4
4
  ---
5
5
 
@@ -22,7 +22,7 @@ Release note 面向人类读者,必须说明变更价值、影响范围和注
22
22
  - `.docs/07_test/`
23
23
  - build artifacts
24
24
  - changelog 或 task list
25
- - `<harnessRoot>/managed/templates/RELEASE_TEMPLATE.md`
25
+ - `<harnessRoot>/pjsdlc_managed/templates/RELEASE_TEMPLATE.md`
26
26
 
27
27
  ## 输出
28
28
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: reviewer
2
+ name: pjsdlc_reviewer
3
3
  description: Use during REVIEWING for read-only review of requirement fit, implementation risk, and test adequacy.
4
4
  ---
5
5
 
@@ -24,7 +24,7 @@ Review 时先建立证据链:PRD 说什么、技术方案承诺什么、implem
24
24
  - `.docs/04_implementation/`
25
25
  - `git diff`
26
26
  - gate/test 结果
27
- - `<harnessRoot>/managed/templates/REVIEW_TEMPLATE.md`
27
+ - `<harnessRoot>/pjsdlc_managed/templates/REVIEW_TEMPLATE.md`
28
28
 
29
29
  ## 输出
30
30
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: rfc_recalibrate
2
+ name: pjsdlc_rfc_recalibrate
3
3
  description: Use during RFC_RECALIBRATION to process requirement changes with impact analysis and localized patches.
4
4
  ---
5
5
 
@@ -17,6 +17,8 @@ description: Use during RFC_RECALIBRATION to process requirement changes with im
17
17
 
18
18
  输出应包含 impact analysis、受影响产物、任务状态调整、回归要求和恢复路径。只修改受影响 slice;如果变化跨越多个独立能力,应拆分 RFC 或生成增量任务。
19
19
 
20
+ 影响面分析必须先于补丁。至少检查 docs/state/skills/policies/templates/tools/package assets/tests/migrations/generated artifacts 是否受影响;如果某一类不受影响,也要显式说明不受影响或不需要修改。对于 Harness package 相关变更,还要检查 `sync`、`upgrade`、source mappings、package assets 和用户项目迁移行为。
21
+
20
22
  ## 输入
21
23
 
22
24
  - `.docs/rfc/RFC_*.md`
@@ -37,14 +39,14 @@ description: Use during RFC_RECALIBRATION to process requirement changes with im
37
39
 
38
40
  - `.docs/rfc/` 按一次需求变更切片,一份 RFC 只描述一个可独立评估、实现和回归的变更。
39
41
  - 如果用户一次提出多个互不依赖的变更,应拆成多份 RFC。
40
- - RFC 的 impact analysis 负责判断是否需要重切 PRD、tech plan、implementation doc 或 test plan。
42
+ - RFC 的 impact analysis 负责判断是否需要重切 PRD、tech plan、implementation doc 或 test plan,并覆盖 state、tools、package assets、tests、migration 和 generated overview
41
43
  - 对受影响产物做局部补丁,不重写无关稳定 slice。
42
44
  - 每次 RFC 影响了文档边界,都要更新 `.docs/INDEX.md` 并记录受影响任务状态。
43
45
 
44
46
  ## 规则
45
47
 
46
48
  1. 影响已接受产物的需求变化,必须先进入本 Skill。
47
- 2. 修改下游文档或任务前,先运行 impact analysis
49
+ 2. 修改下游文档或任务前,先运行 impact analysis,并列出受影响/不受影响的文件类别。
48
50
  3. 受影响的已完成任务标记为 `pending_revision`。
49
51
  4. 受影响的 `pending` 或 `in_progress` 任务追加 revision notes。
50
52
  5. 不重写无关的稳定文档。
@@ -55,6 +57,7 @@ description: Use during RFC_RECALIBRATION to process requirement changes with im
55
57
  - [ ] RFC 包含有效 status 和 acceptance criteria。
56
58
  - [ ] Product impact 和 technical impact 已记录。
57
59
  - [ ] 已判断 RFC 是否需要拆分,以及是否影响其它阶段 slice。
60
+ - [ ] 已列出 docs/state/skills/policies/templates/tools/package assets/tests/migrations/generated artifacts 的影响面。
58
61
  - [ ] 受影响任务已标记或新增。
59
62
  - [ ] Regression requirements 已明确。
60
63
  - [ ] `.docs/INDEX.md` 已链接 RFC 和受影响产物。
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: tester
2
+ name: pjsdlc_tester
3
3
  description: Use during TESTING to produce a test matrix, run regression, and document coverage gaps.
4
4
  ---
5
5
 
@@ -24,7 +24,7 @@ description: Use during TESTING to produce a test matrix, run regression, and do
24
24
  - `.docs/04_implementation/`
25
25
  - `.docs/06_review/REVIEW_REPORT.md`
26
26
  - 现有测试
27
- - `<harnessRoot>/managed/templates/TEST_PLAN_TEMPLATE.md`
27
+ - `<harnessRoot>/pjsdlc_managed/templates/TEST_PLAN_TEMPLATE.md`
28
28
 
29
29
  ## 输出
30
30
 
@@ -1,10 +1,11 @@
1
1
  current_phase: "SPRINTING"
2
2
  current_task_id: "DEV-001"
3
+ next_task_sequence: 2
3
4
  tasks:
4
5
  - id: "DEV-001"
5
6
  title: "实现示例任务"
6
7
  status: "pending"
7
- summary: "一句话描述任务目标和交付边界;done 后保留这类简短摘要。"
8
+ summary: "一句话描述任务目标和交付边界。"
8
9
  docs:
9
10
  product:
10
11
  - ".docs/01_product/example.md"
@@ -18,8 +19,7 @@ tasks:
18
19
  - "make lint"
19
20
  - "make test-current-domain"
20
21
  acceptance_criteria:
21
- - "验收标准写在 open task 内,完成后可压缩删除。"
22
+ - "验收标准写在 open task 内,完成后从当前 plan 删除。"
22
23
  working_notes:
23
24
  - "执行现场备注只在 open task 保留。"
24
25
  implementation_doc: ".docs/04_implementation/example/dev_001.md"
25
- gate_result: ""
package/dist/cli.js CHANGED
File without changes
@@ -1,24 +1,27 @@
1
1
  import path from "node:path";
2
+ import { createRequire } from "node:module";
2
3
  import { harnessConfigPath, harnessPath, harnessRoot } from "./harness-root.js";
3
4
  import { pathExists, readText, writeTextIfChanged } from "./fs.js";
4
5
  import { parseYaml, stringifyYaml } from "./yaml.js";
6
+ const require = createRequire(import.meta.url);
7
+ const packageMetadata = require("../../package.json");
5
8
  export function defaultConfig(root) {
6
9
  return {
7
10
  core: {
8
11
  package: "agent-project-sdlc",
9
- version: "0.1.0",
12
+ version: packageMetadata.version ?? "0.0.0",
10
13
  schema_version: "1"
11
14
  },
12
15
  managed_files: [
13
16
  { path: "AGENTS.md", strategy: "merge-block" },
14
17
  { path: "Makefile", strategy: "merge-block" },
15
18
  { path: harnessPath(root, "skills"), strategy: "managed" },
16
- { path: harnessPath(root, "managed", "templates"), strategy: "managed" },
17
- { path: harnessPath(root, "managed", "policies"), strategy: "merge-with-local" },
18
- { path: harnessPath(root, "managed", "make", "sdlc-harness.mk"), strategy: "managed" },
19
+ { path: harnessPath(root, "pjsdlc_managed", "templates"), strategy: "managed" },
20
+ { path: harnessPath(root, "pjsdlc_managed", "policies"), strategy: "merge-with-local" },
21
+ { path: harnessPath(root, "pjsdlc_managed", "make", "sdlc-harness.mk"), strategy: "managed" },
19
22
  { path: ".github/workflows/harness.yml", strategy: "create-if-missing" }
20
23
  ],
21
- local_overrides: [harnessPath(root, "overrides/**"), harnessPath(root, "managed", "policies", "*.local.yaml")],
24
+ local_overrides: [harnessPath(root, "overrides/**"), harnessPath(root, "pjsdlc_managed", "policies", "*.local.yaml")],
22
25
  never_overwrite: [".docs/**", harnessPath(root, "state/**"), "src/**", "tests/**"]
23
26
  };
24
27
  }
package/dist/lib/init.js CHANGED
@@ -51,11 +51,10 @@ async function createProjectState(projectRoot, root, report) {
51
51
  const files = [
52
52
  [
53
53
  harnessPath(root, "state", "lifecycle.yaml"),
54
- `project_name: "Project"\nversion: "v0.1"\ncurrent_phase: "REQUIREMENT_GATHERING"\nactive_role: "pm"\nactive_skill: "pm_prd"\ncurrent_milestone: "MVP"\nblocked_reason: ""\nsuspended_phase: ""\nallowed_next_phases:\n - "ARCHITECTING"\nhistory: []\n`
54
+ `project_name: "Project"\nversion: "v0.1"\ncurrent_phase: "REQUIREMENT_GATHERING"\nactive_role: "pm"\nactive_skill: "pjsdlc_pm_prd"\ncurrent_milestone: "MVP"\nblocked_reason: ""\nsuspended_phase: ""\nallowed_next_phases:\n - "ARCHITECTING"\n`
55
55
  ],
56
- [harnessPath(root, "state", "plan.yaml"), `current_phase: "SPRINTING"\ncurrent_task_id: ""\ntasks: []\n`],
57
- [harnessPath(root, "state", "plan.draft.yaml"), `current_phase: "SPRINTING"\ncurrent_task_id: ""\ntasks: []\n`],
58
- [harnessPath(root, "state", "gate_results.log"), "# Gate results are appended by sdlc-harness.\n"],
56
+ [harnessPath(root, "state", "plan.yaml"), `current_phase: "SPRINTING"\ncurrent_task_id: ""\nnext_task_sequence: 1\ntasks: []\n`],
57
+ [harnessPath(root, "state", "plan.draft.yaml"), `current_phase: "SPRINTING"\ncurrent_task_id: ""\nnext_task_sequence: 1\ntasks: []\n`],
59
58
  [harnessPath(root, "state", "memory.md"), "# Project Memory\n\n短期执行计划写入 plan.yaml;长期稳定知识简短记录在这里,并链接到 `.docs/` 正式出处。\n"]
60
59
  ];
61
60
  for (const [relative, content] of files) {
@@ -1,6 +1,17 @@
1
- export declare const MANAGED_BLOCK_START = "<!-- sdlc-harness:begin -->";
2
- export declare const MANAGED_BLOCK_END = "<!-- sdlc-harness:end -->";
3
- export declare const MAKEFILE_BLOCK_START = "# sdlc-harness:make:begin";
4
- export declare const MAKEFILE_BLOCK_END = "# sdlc-harness:make:end";
5
- export declare const MANAGED_METADATA_START = "<!-- sdlc-harness-managed";
1
+ export interface ManagedBlockMarkers {
2
+ start: string;
3
+ end: string;
4
+ }
5
+ export declare const MANAGED_BLOCK_START = "<!-- pjsdlc:sdlc-harness:begin -->";
6
+ export declare const MANAGED_BLOCK_END = "<!-- pjsdlc:sdlc-harness:end -->";
7
+ export declare const LEGACY_MANAGED_BLOCK_START = "<!-- sdlc-harness:begin -->";
8
+ export declare const LEGACY_MANAGED_BLOCK_END = "<!-- sdlc-harness:end -->";
9
+ export declare const MAKEFILE_BLOCK_START = "# pjsdlc:sdlc-harness:make:begin";
10
+ export declare const MAKEFILE_BLOCK_END = "# pjsdlc:sdlc-harness:make:end";
11
+ export declare const LEGACY_MAKEFILE_BLOCK_START = "# sdlc-harness:make:begin";
12
+ export declare const LEGACY_MAKEFILE_BLOCK_END = "# sdlc-harness:make:end";
13
+ export declare const MANAGED_METADATA_START = "<!-- pjsdlc:sdlc-harness-managed";
14
+ export declare const LEGACY_MANAGED_METADATA_START = "<!-- sdlc-harness-managed";
6
15
  export declare const MANAGED_METADATA_END = "-->";
16
+ export declare const AGENTS_BLOCK_MARKERS: ManagedBlockMarkers[];
17
+ export declare const MAKEFILE_BLOCK_MARKERS: ManagedBlockMarkers[];
@@ -1,6 +1,19 @@
1
- export const MANAGED_BLOCK_START = "<!-- sdlc-harness:begin -->";
2
- export const MANAGED_BLOCK_END = "<!-- sdlc-harness:end -->";
3
- export const MAKEFILE_BLOCK_START = "# sdlc-harness:make:begin";
4
- export const MAKEFILE_BLOCK_END = "# sdlc-harness:make:end";
5
- export const MANAGED_METADATA_START = "<!-- sdlc-harness-managed";
1
+ export const MANAGED_BLOCK_START = "<!-- pjsdlc:sdlc-harness:begin -->";
2
+ export const MANAGED_BLOCK_END = "<!-- pjsdlc:sdlc-harness:end -->";
3
+ export const LEGACY_MANAGED_BLOCK_START = "<!-- sdlc-harness:begin -->";
4
+ export const LEGACY_MANAGED_BLOCK_END = "<!-- sdlc-harness:end -->";
5
+ export const MAKEFILE_BLOCK_START = "# pjsdlc:sdlc-harness:make:begin";
6
+ export const MAKEFILE_BLOCK_END = "# pjsdlc:sdlc-harness:make:end";
7
+ export const LEGACY_MAKEFILE_BLOCK_START = "# sdlc-harness:make:begin";
8
+ export const LEGACY_MAKEFILE_BLOCK_END = "# sdlc-harness:make:end";
9
+ export const MANAGED_METADATA_START = "<!-- pjsdlc:sdlc-harness-managed";
10
+ export const LEGACY_MANAGED_METADATA_START = "<!-- sdlc-harness-managed";
6
11
  export const MANAGED_METADATA_END = "-->";
12
+ export const AGENTS_BLOCK_MARKERS = [
13
+ { start: MANAGED_BLOCK_START, end: MANAGED_BLOCK_END },
14
+ { start: LEGACY_MANAGED_BLOCK_START, end: LEGACY_MANAGED_BLOCK_END }
15
+ ];
16
+ export const MAKEFILE_BLOCK_MARKERS = [
17
+ { start: MAKEFILE_BLOCK_START, end: MAKEFILE_BLOCK_END },
18
+ { start: LEGACY_MAKEFILE_BLOCK_START, end: LEGACY_MAKEFILE_BLOCK_END }
19
+ ];
@@ -1,17 +1,31 @@
1
1
  import path from "node:path";
2
- import { rm } from "node:fs/promises";
2
+ import { rename, rm } from "node:fs/promises";
3
3
  import { readConfig } from "./config.js";
4
- import { pathExists, readText, writeTextIfChanged } from "./fs.js";
4
+ import { ensureDir, pathExists, readText, writeTextIfChanged } from "./fs.js";
5
5
  import { harnessConfigPath, harnessPath, harnessRoot } from "./harness-root.js";
6
6
  import { parseYaml, stringifyYaml } from "./yaml.js";
7
7
  export const CURRENT_SCHEMA_VERSION = "1";
8
8
  export const migrations = [];
9
+ const SKILL_RENAMES = {
10
+ manager: "pjsdlc_manager",
11
+ pm_prd: "pjsdlc_pm_prd",
12
+ architect_design: "pjsdlc_architect_design",
13
+ dev_sprint: "pjsdlc_dev_sprint",
14
+ implementation_doc: "pjsdlc_implementation_doc",
15
+ reviewer: "pjsdlc_reviewer",
16
+ tester: "pjsdlc_tester",
17
+ release_manager: "pjsdlc_release_manager",
18
+ rfc_recalibrate: "pjsdlc_rfc_recalibrate"
19
+ };
9
20
  export async function runMigrations(projectRoot) {
10
21
  const report = { changed: [], skipped: [] };
11
22
  const root = await harnessRoot(projectRoot);
12
23
  await migrateConfig(projectRoot, root, report);
24
+ await migrateLegacyManagedPaths(projectRoot, root, report);
25
+ await migrateLifecycle(projectRoot, root, report);
13
26
  await migratePlan(projectRoot, root, report, "plan.yaml", "tasks.yaml");
14
27
  await migratePlan(projectRoot, root, report, "plan.draft.yaml", "tasks.draft.yaml");
28
+ await removeLegacyGateResults(projectRoot, root, report);
15
29
  await removeLegacyCheckpoints(projectRoot, root, report);
16
30
  await ensureMemory(projectRoot, root, report);
17
31
  return report;
@@ -26,7 +40,7 @@ async function migrateConfig(projectRoot, root, report) {
26
40
  const config = await readConfig(projectRoot);
27
41
  config.core.schema_version = CURRENT_SCHEMA_VERSION;
28
42
  config.managed_files = migrateManagedFiles(config.managed_files, root);
29
- config.local_overrides = config.local_overrides.map((item) => item === ".harness/policies/*.local.yaml" ? harnessPath(root, "managed", "policies", "*.local.yaml") : item);
43
+ config.local_overrides = config.local_overrides.map((item) => migrateLocalOverride(item, root));
30
44
  if (await writeTextIfChanged(configPath, stringifyYaml(config))) {
31
45
  report.changed.push(relativeConfigPath);
32
46
  }
@@ -34,6 +48,34 @@ async function migrateConfig(projectRoot, root, report) {
34
48
  report.skipped.push(relativeConfigPath);
35
49
  }
36
50
  }
51
+ async function migrateLegacyManagedPaths(projectRoot, root, report) {
52
+ await moveIfDestinationMissing(projectRoot, harnessPath(root, "managed"), harnessPath(root, "pjsdlc_managed"), report);
53
+ await moveIfDestinationMissing(projectRoot, harnessPath(root, "templates"), harnessPath(root, "pjsdlc_managed", "templates"), report);
54
+ await moveIfDestinationMissing(projectRoot, harnessPath(root, "policies"), harnessPath(root, "pjsdlc_managed", "policies"), report);
55
+ await moveIfDestinationMissing(projectRoot, harnessPath(root, "make"), harnessPath(root, "pjsdlc_managed", "make"), report);
56
+ }
57
+ async function moveIfDestinationMissing(projectRoot, legacyRelativePath, nextRelativePath, report) {
58
+ const legacyPath = path.join(projectRoot, legacyRelativePath);
59
+ if (!(await pathExists(legacyPath))) {
60
+ report.skipped.push(legacyRelativePath);
61
+ return;
62
+ }
63
+ const nextPath = path.join(projectRoot, nextRelativePath);
64
+ if (await pathExists(nextPath)) {
65
+ report.skipped.push(`${legacyRelativePath} -> ${nextRelativePath}`);
66
+ return;
67
+ }
68
+ await ensureDir(path.dirname(nextPath));
69
+ await rename(legacyPath, nextPath);
70
+ report.changed.push(`${legacyRelativePath} -> ${nextRelativePath}`);
71
+ }
72
+ function migrateLocalOverride(item, root) {
73
+ if (item === ".harness/policies/*.local.yaml" ||
74
+ item === harnessPath(root, "managed", "policies", "*.local.yaml")) {
75
+ return harnessPath(root, "pjsdlc_managed", "policies", "*.local.yaml");
76
+ }
77
+ return item;
78
+ }
37
79
  function migrateManagedFiles(managedFiles, root) {
38
80
  const migrated = [];
39
81
  const seen = new Set();
@@ -49,16 +91,16 @@ function migrateManagedFiles(managedFiles, root) {
49
91
  push({ path: harnessPath(root, "skills"), strategy: "managed" });
50
92
  continue;
51
93
  }
52
- if (item.path === ".harness/templates") {
53
- push({ path: harnessPath(root, "managed", "templates"), strategy: "managed" });
94
+ if (item.path === ".harness/templates" || item.path === harnessPath(root, "managed", "templates")) {
95
+ push({ path: harnessPath(root, "pjsdlc_managed", "templates"), strategy: "managed" });
54
96
  continue;
55
97
  }
56
- if (item.path === ".harness/policies") {
57
- push({ path: harnessPath(root, "managed", "policies"), strategy: "merge-with-local" });
98
+ if (item.path === ".harness/policies" || item.path === harnessPath(root, "managed", "policies")) {
99
+ push({ path: harnessPath(root, "pjsdlc_managed", "policies"), strategy: "merge-with-local" });
58
100
  continue;
59
101
  }
60
- if (item.path === ".harness/make/sdlc-harness.mk") {
61
- push({ path: harnessPath(root, "managed", "make", "sdlc-harness.mk"), strategy: "managed" });
102
+ if (item.path === ".harness/make/sdlc-harness.mk" || item.path === harnessPath(root, "managed", "make", "sdlc-harness.mk")) {
103
+ push({ path: harnessPath(root, "pjsdlc_managed", "make", "sdlc-harness.mk"), strategy: "managed" });
62
104
  continue;
63
105
  }
64
106
  push(item);
@@ -75,6 +117,35 @@ function migrateManagedFiles(managedFiles, root) {
75
117
  }
76
118
  return migrated;
77
119
  }
120
+ async function migrateLifecycle(projectRoot, root, report) {
121
+ const relativeLifecyclePath = harnessPath(root, "state", "lifecycle.yaml");
122
+ const lifecyclePath = path.join(projectRoot, relativeLifecyclePath);
123
+ if (!(await pathExists(lifecyclePath))) {
124
+ report.skipped.push(relativeLifecyclePath);
125
+ return;
126
+ }
127
+ const data = (parseYaml(await readText(lifecyclePath)) ?? {});
128
+ let changed = false;
129
+ const activeSkill = String(data.active_skill ?? "");
130
+ if (SKILL_RENAMES[activeSkill]) {
131
+ data.active_skill = SKILL_RENAMES[activeSkill];
132
+ changed = true;
133
+ }
134
+ if ("history" in data) {
135
+ delete data.history;
136
+ changed = true;
137
+ }
138
+ if (!changed) {
139
+ report.skipped.push(relativeLifecyclePath);
140
+ return;
141
+ }
142
+ if (await writeTextIfChanged(lifecyclePath, stringifyYaml(data))) {
143
+ report.changed.push(relativeLifecyclePath);
144
+ }
145
+ else {
146
+ report.skipped.push(relativeLifecyclePath);
147
+ }
148
+ }
78
149
  async function migratePlan(projectRoot, root, report, planFileName, legacyFileName) {
79
150
  const relativePlanPath = harnessPath(root, "state", planFileName);
80
151
  const planPath = path.join(projectRoot, relativePlanPath);
@@ -99,9 +170,18 @@ async function migratePlan(projectRoot, root, report, planFileName, legacyFileNa
99
170
  changed = true;
100
171
  }
101
172
  if (Array.isArray(data.tasks)) {
173
+ const retainedTasks = [];
174
+ let maxTaskSequence = 0;
102
175
  for (const task of data.tasks) {
103
- if (!isRecord(task))
176
+ if (!isRecord(task)) {
177
+ retainedTasks.push(task);
104
178
  continue;
179
+ }
180
+ const taskId = String(task.id ?? "");
181
+ const match = taskId.match(/^DEV-(\d+)$/);
182
+ if (match) {
183
+ maxTaskSequence = Math.max(maxTaskSequence, Number(match[1]));
184
+ }
105
185
  if ("checkpoint" in task) {
106
186
  const checkpoint = String(task.checkpoint ?? "");
107
187
  if (isOpenTask(task) && checkpoint) {
@@ -115,7 +195,30 @@ async function migratePlan(projectRoot, root, report, planFileName, legacyFileNa
115
195
  delete task.checkpoint;
116
196
  changed = true;
117
197
  }
198
+ if ("gate_result" in task && isOpenTask(task)) {
199
+ delete task.gate_result;
200
+ changed = true;
201
+ }
202
+ if (["done", "cancelled"].includes(String(task.status))) {
203
+ changed = true;
204
+ continue;
205
+ }
206
+ retainedTasks.push(task);
118
207
  }
208
+ if (retainedTasks.length !== data.tasks.length) {
209
+ data.tasks = retainedTasks;
210
+ changed = true;
211
+ }
212
+ const nextTaskSequence = data.next_task_sequence;
213
+ if (!Number.isInteger(nextTaskSequence) || Number(nextTaskSequence) <= maxTaskSequence) {
214
+ data.next_task_sequence = maxTaskSequence + 1;
215
+ changed = true;
216
+ }
217
+ }
218
+ const currentTaskId = String(data.current_task_id ?? "");
219
+ if (currentTaskId && !taskById(data, currentTaskId)) {
220
+ data.current_task_id = "";
221
+ changed = true;
119
222
  }
120
223
  if (changed || sourcePath !== planPath) {
121
224
  if (await writeTextIfChanged(planPath, stringifyYaml(data))) {
@@ -139,6 +242,10 @@ function isRecord(value) {
139
242
  function isOpenTask(task) {
140
243
  return ["pending", "in_progress", "blocked", "pending_revision"].includes(String(task.status));
141
244
  }
245
+ function taskById(planData, taskId) {
246
+ const tasks = Array.isArray(planData.tasks) ? planData.tasks : [];
247
+ return tasks.find((task) => isRecord(task) && task.id === taskId);
248
+ }
142
249
  async function readLegacyCheckpointContract(projectRoot, root, checkpoint) {
143
250
  const relative = checkpoint.replace("<harnessRoot>", root);
144
251
  const checkpointPath = path.join(projectRoot, relative);
@@ -162,6 +269,16 @@ async function removeLegacyCheckpoints(projectRoot, root, report) {
162
269
  await rm(checkpointPath, { recursive: true, force: true });
163
270
  report.changed.push(relativeCheckpointPath);
164
271
  }
272
+ async function removeLegacyGateResults(projectRoot, root, report) {
273
+ const relativeGateResultsPath = harnessPath(root, "state", "gate_results.log");
274
+ const gateResultsPath = path.join(projectRoot, relativeGateResultsPath);
275
+ if (!(await pathExists(gateResultsPath))) {
276
+ report.skipped.push(relativeGateResultsPath);
277
+ return;
278
+ }
279
+ await rm(gateResultsPath, { force: true });
280
+ report.changed.push(relativeGateResultsPath);
281
+ }
165
282
  async function ensureMemory(projectRoot, root, report) {
166
283
  const relativeMemoryPath = harnessPath(root, "state", "memory.md");
167
284
  const memoryPath = path.join(projectRoot, relativeMemoryPath);
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { promises as fs } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { ensureDir, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
5
- import { MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
5
+ import { AGENTS_BLOCK_MARKERS } from "./managed-file.js";
6
6
  import { SOURCE_MAPPINGS_PATH } from "./paths.js";
7
7
  import { parseYaml } from "./yaml.js";
8
8
  export async function syncSource(projectRoot) {
@@ -87,10 +87,12 @@ async function renderMapping(projectRoot, mapping) {
87
87
  }
88
88
  if (mapping.mode === "extract-managed-block") {
89
89
  const content = await readText(source);
90
- const start = content.indexOf(MANAGED_BLOCK_START);
91
- const end = content.indexOf(MANAGED_BLOCK_END);
92
- if (start >= 0 && end > start) {
93
- return `${content.slice(start + MANAGED_BLOCK_START.length, end).trim()}\n`;
90
+ for (const markers of AGENTS_BLOCK_MARKERS) {
91
+ const start = content.indexOf(markers.start);
92
+ const end = content.indexOf(markers.end);
93
+ if (start >= 0 && end > start) {
94
+ return `${content.slice(start + markers.start.length, end).trim()}\n`;
95
+ }
94
96
  }
95
97
  return content;
96
98
  }
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { readConfig } from "./config.js";
3
3
  import { harnessRoot } from "./harness-root.js";
4
4
  import { copyTree, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
5
- import { MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
5
+ import { AGENTS_BLOCK_MARKERS, MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_MARKERS, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
6
6
  import { packageAssetPath } from "./paths.js";
7
7
  export function emptySyncReport() {
8
8
  return {
@@ -38,16 +38,15 @@ async function syncManagedFile(projectRoot, root, managedFile, report) {
38
38
  await syncTree(packageAssetPath("skills"), destination, report);
39
39
  return;
40
40
  }
41
- if (managedFile.path === path.join(root, "managed", "templates") || managedFile.path === ".harness/templates") {
41
+ if (managedFile.path === path.join(root, "pjsdlc_managed", "templates")) {
42
42
  await syncTree(packageAssetPath("templates"), destination, report);
43
43
  return;
44
44
  }
45
- if (managedFile.path === path.join(root, "managed", "policies") || managedFile.path === ".harness/policies") {
45
+ if (managedFile.path === path.join(root, "pjsdlc_managed", "policies")) {
46
46
  await syncTree(packageAssetPath("policies"), destination, report);
47
47
  return;
48
48
  }
49
- if (managedFile.path === path.join(root, "managed", "make", "sdlc-harness.mk") ||
50
- managedFile.path === ".harness/make/sdlc-harness.mk") {
49
+ if (managedFile.path === path.join(root, "pjsdlc_managed", "make", "sdlc-harness.mk")) {
51
50
  await syncFile(packageAssetPath("make", "sdlc-harness.mk"), destination, report, "skip-if-missing");
52
51
  return;
53
52
  }
@@ -73,8 +72,7 @@ async function syncAgentsBlock(destination, root, report) {
73
72
  const next = mergeManagedBlock({
74
73
  existing,
75
74
  block,
76
- start: MANAGED_BLOCK_START,
77
- end: MANAGED_BLOCK_END,
75
+ markers: AGENTS_BLOCK_MARKERS,
78
76
  pathLabel: "AGENTS.md",
79
77
  insert: "append",
80
78
  report
@@ -95,7 +93,7 @@ function renderAgentsCore(content, root) {
95
93
  async function syncMakefileInclude(destination, root, report) {
96
94
  const existing = (await pathExists(destination)) ? await readText(destination) : "";
97
95
  const resetDefaultGoal = shouldResetMakeDefaultGoal(existing);
98
- const includePath = `${root.replace(/\\/g, "/")}/managed/make/sdlc-harness.mk`;
96
+ const includePath = `${root.replace(/\\/g, "/")}/pjsdlc_managed/make/sdlc-harness.mk`;
99
97
  const blockLines = [
100
98
  MAKEFILE_BLOCK_START,
101
99
  "# Included before project targets so project recipes win on name conflicts.",
@@ -109,8 +107,7 @@ async function syncMakefileInclude(destination, root, report) {
109
107
  const next = mergeManagedBlock({
110
108
  existing,
111
109
  block,
112
- start: MAKEFILE_BLOCK_START,
113
- end: MAKEFILE_BLOCK_END,
110
+ markers: MAKEFILE_BLOCK_MARKERS,
114
111
  pathLabel: "Makefile",
115
112
  insert: "prepend",
116
113
  report
@@ -129,36 +126,27 @@ function shouldResetMakeDefaultGoal(existing) {
129
126
  if (!existing.trim()) {
130
127
  return false;
131
128
  }
132
- const startIndex = existing.indexOf(MAKEFILE_BLOCK_START);
133
- const endIndex = existing.indexOf(MAKEFILE_BLOCK_END);
134
- if (startIndex < 0 && endIndex < 0) {
129
+ const block = findManagedBlock(existing, MAKEFILE_BLOCK_MARKERS);
130
+ if (block.status === "missing") {
135
131
  return true;
136
132
  }
137
- if (startIndex < 0 || endIndex < startIndex) {
133
+ if (block.status === "invalid") {
138
134
  return false;
139
135
  }
140
- const before = existing.slice(0, startIndex);
141
- const after = existing.slice(endIndex + MAKEFILE_BLOCK_END.length);
136
+ const before = existing.slice(0, block.startIndex);
137
+ const after = existing.slice(block.endIndex + block.markers.end.length);
142
138
  return !before.trim() && Boolean(after.trim());
143
139
  }
144
140
  function mergeManagedBlock(options) {
145
- const { existing, block, start, end, pathLabel, insert, report } = options;
146
- const startIndex = existing.indexOf(start);
147
- const endIndex = existing.indexOf(end);
148
- const hasStart = startIndex >= 0;
149
- const hasEnd = endIndex >= 0;
150
- if (hasStart !== hasEnd || (hasStart && endIndex < startIndex)) {
151
- report.blocked.push(`${pathLabel}: incomplete managed block markers`);
141
+ const { existing, block, markers, pathLabel, insert, report } = options;
142
+ const found = findManagedBlock(existing, markers);
143
+ if (found.status === "invalid") {
144
+ report.blocked.push(`${pathLabel}: ${found.reason}`);
152
145
  return undefined;
153
146
  }
154
- if (hasStart &&
155
- (existing.indexOf(start, startIndex + start.length) >= 0 || existing.indexOf(end, endIndex + end.length) >= 0)) {
156
- report.blocked.push(`${pathLabel}: duplicate managed block markers`);
157
- return undefined;
158
- }
159
- if (hasStart) {
160
- const before = existing.slice(0, startIndex);
161
- const after = existing.slice(endIndex + end.length);
147
+ if (found.status === "found") {
148
+ const before = existing.slice(0, found.startIndex);
149
+ const after = existing.slice(found.endIndex + found.markers.end.length);
162
150
  return `${before}${block}${after}`;
163
151
  }
164
152
  if (!existing.trim()) {
@@ -169,6 +157,30 @@ function mergeManagedBlock(options) {
169
157
  }
170
158
  return `${existing.trimEnd()}\n\n${block}\n`;
171
159
  }
160
+ function findManagedBlock(existing, markersList) {
161
+ const matches = [];
162
+ for (const markers of markersList) {
163
+ const startIndex = existing.indexOf(markers.start);
164
+ const endIndex = existing.indexOf(markers.end);
165
+ const hasStart = startIndex >= 0;
166
+ const hasEnd = endIndex >= 0;
167
+ if (!hasStart && !hasEnd) {
168
+ continue;
169
+ }
170
+ if (hasStart !== hasEnd || endIndex < startIndex) {
171
+ return { status: "invalid", reason: "incomplete managed block markers" };
172
+ }
173
+ if (existing.indexOf(markers.start, startIndex + markers.start.length) >= 0 ||
174
+ existing.indexOf(markers.end, endIndex + markers.end.length) >= 0) {
175
+ return { status: "invalid", reason: "duplicate managed block markers" };
176
+ }
177
+ matches.push({ markers, startIndex, endIndex });
178
+ }
179
+ if (matches.length > 1) {
180
+ return { status: "invalid", reason: "conflicting managed block marker namespaces" };
181
+ }
182
+ return matches[0] ? { status: "found", ...matches[0] } : { status: "missing" };
183
+ }
172
184
  async function syncTree(source, destination, report) {
173
185
  if (!(await pathExists(source))) {
174
186
  report.skipped.push(path.basename(destination));
@@ -33,8 +33,8 @@ async function validateHarness(projectRoot) {
33
33
  harnessPath(root, "state", "lifecycle.yaml"),
34
34
  harnessPath(root, "state", "plan.yaml"),
35
35
  harnessPath(root, "skills"),
36
- harnessPath(root, "managed", "templates"),
37
- harnessPath(root, "managed", "policies")
36
+ harnessPath(root, "pjsdlc_managed", "templates"),
37
+ harnessPath(root, "pjsdlc_managed", "policies")
38
38
  ]) {
39
39
  if (!(await pathExists(path.join(projectRoot, required)))) {
40
40
  errors.push(`missing ${required}`);
@@ -89,17 +89,28 @@ async function validateDev(projectRoot) {
89
89
  const root = await harnessRoot(projectRoot);
90
90
  const tasksData = await readYamlObject(path.join(projectRoot, root, "state", "plan.yaml"));
91
91
  const tasks = Array.isArray(tasksData.tasks) ? tasksData.tasks : [];
92
- if (tasks.length === 0)
93
- errors.push("plan.yaml must contain at least one task");
92
+ const nextTaskSequence = tasksData.next_task_sequence;
93
+ if (!Number.isInteger(nextTaskSequence) || Number(nextTaskSequence) <= 0) {
94
+ errors.push("plan.yaml must define positive integer next_task_sequence");
95
+ }
94
96
  const open = tasks.filter((task) => ["pending", "in_progress", "blocked", "pending_revision"].includes(String(task.status)));
95
97
  if (open.length > 0)
96
98
  errors.push(`Open tasks remain: ${open.map((task) => task.id).join(", ")}`);
99
+ let maxTaskSequence = 0;
97
100
  for (const task of tasks) {
98
101
  for (const field of ["id", "title", "status", "summary", "implementation_doc"]) {
99
102
  if (!task[field])
100
103
  errors.push(`Task missing ${field}: ${String(task.id ?? "unknown")}`);
101
104
  }
105
+ const taskId = String(task.id ?? "");
106
+ const match = taskId.match(/^DEV-(\d+)$/);
107
+ if (match) {
108
+ maxTaskSequence = Math.max(maxTaskSequence, Number(match[1]));
109
+ }
102
110
  if (["pending", "in_progress", "blocked", "pending_revision"].includes(String(task.status))) {
111
+ if ("gate_result" in task) {
112
+ errors.push(`Open task ${task.id} must not define gate_result`);
113
+ }
103
114
  for (const field of ["docs", "allowed_paths", "required_gates", "acceptance_criteria"]) {
104
115
  if (!task[field])
105
116
  errors.push(`Open task ${task.id} missing ${field}`);
@@ -112,19 +123,15 @@ async function validateDev(projectRoot) {
112
123
  }
113
124
  }
114
125
  else {
115
- for (const field of ["docs", "allowed_paths", "required_gates", "acceptance_criteria", "working_notes"]) {
126
+ errors.push(`Completed task ${task.id} must not remain in plan.yaml`);
127
+ for (const field of ["docs", "allowed_paths", "required_gates", "acceptance_criteria", "working_notes", "gate_result"]) {
116
128
  if (task[field])
117
129
  errors.push(`Closed task ${task.id} must not retain ${field}`);
118
130
  }
119
131
  }
120
132
  }
121
- for (const task of tasks.filter((task) => task.status === "done")) {
122
- if (task.gate_result !== "PASS")
123
- errors.push(`Done task ${task.id} must have gate_result PASS`);
124
- const implementationDoc = String(task.implementation_doc ?? "");
125
- if (implementationDoc && !(await pathExists(path.join(projectRoot, implementationDoc)))) {
126
- errors.push(`Implementation doc missing for ${task.id}: ${implementationDoc}`);
127
- }
133
+ if (Number.isInteger(nextTaskSequence) && Number(nextTaskSequence) <= maxTaskSequence) {
134
+ errors.push("next_task_sequence must be greater than task ids currently in plan.yaml");
128
135
  }
129
136
  return { info: [`validate-dev checked ${tasks.length} task(s)`], errors };
130
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-project-sdlc",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI and canonical assets for the AI SDLC Harness workflow.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,15 +5,15 @@ source_mappings:
5
5
  - source: ".agent/skills"
6
6
  target: "packages/sdlc-harness/assets/skills"
7
7
  mode: "copy-tree"
8
- - source: ".agent/managed/templates"
8
+ - source: ".agent/pjsdlc_managed/templates"
9
9
  target: "packages/sdlc-harness/assets/templates"
10
10
  mode: "copy-tree"
11
- - source: ".agent/managed/policies"
11
+ - source: ".agent/pjsdlc_managed/policies"
12
12
  target: "packages/sdlc-harness/assets/policies"
13
13
  mode: "copy-tree"
14
- - source: "Makefile"
14
+ - source: ".agent/pjsdlc_managed/make/sdlc-harness.mk"
15
15
  target: "packages/sdlc-harness/assets/make/sdlc-harness.mk"
16
- mode: "extract-harness-targets"
16
+ mode: "copy-file"
17
17
  - source: ".github/workflows/harness.yml"
18
18
  target: "packages/sdlc-harness/assets/github/harness.yml"
19
19
  mode: "copy-file"