agent-project-sdlc 0.1.0 → 0.1.1
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/assets/agents/AGENTS_CORE.md +28 -8
- package/assets/policies/allowed_paths.yaml +0 -1
- package/assets/policies/phase_contracts.yaml +10 -11
- package/assets/skills/{architect_design → pjsdlc_architect_design}/SKILL.md +4 -4
- package/assets/skills/{dev_sprint → pjsdlc_dev_sprint}/SKILL.md +20 -17
- package/assets/skills/{implementation_doc → pjsdlc_implementation_doc}/SKILL.md +2 -2
- package/assets/skills/{manager → pjsdlc_manager}/SKILL.md +16 -5
- package/assets/skills/{pm_prd → pjsdlc_pm_prd}/SKILL.md +2 -2
- package/assets/skills/{release_manager → pjsdlc_release_manager}/SKILL.md +2 -2
- package/assets/skills/{reviewer → pjsdlc_reviewer}/SKILL.md +2 -2
- package/assets/skills/{rfc_recalibrate → pjsdlc_rfc_recalibrate}/SKILL.md +6 -3
- package/assets/skills/{tester → pjsdlc_tester}/SKILL.md +2 -2
- package/assets/templates/PLAN_TEMPLATE.yaml +3 -3
- package/dist/cli.js +0 -0
- package/dist/lib/config.js +4 -4
- package/dist/lib/init.js +3 -4
- package/dist/lib/managed-file.d.ts +16 -5
- package/dist/lib/managed-file.js +18 -5
- package/dist/lib/migrations.js +127 -10
- package/dist/lib/package-source.js +7 -5
- package/dist/lib/sync-engine.js +43 -31
- package/dist/lib/validators.js +19 -12
- package/package.json +1 -1
- package/source-mappings.yaml +4 -4
|
@@ -29,23 +29,26 @@
|
|
|
29
29
|
|
|
30
30
|
## Plan Protocol
|
|
31
31
|
|
|
32
|
-
- `plan.yaml`
|
|
33
|
-
-
|
|
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,先在
|
|
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/
|
|
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/
|
|
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
|
|
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.
|
|
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-*` 目标。
|
|
@@ -2,7 +2,7 @@ phases:
|
|
|
2
2
|
IDLE:
|
|
3
3
|
goal: "等待项目启动"
|
|
4
4
|
role: "manager"
|
|
5
|
-
skill: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
133
|
+
skill: "pjsdlc_manager"
|
|
135
134
|
inputs:
|
|
136
135
|
- "<harnessRoot>/state/lifecycle.yaml"
|
|
137
136
|
outputs:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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>/
|
|
27
|
-
- `<harnessRoot>/
|
|
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>/
|
|
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:
|
|
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
|
|
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
|
-
-
|
|
32
|
+
- 当前 task `working_notes` 或 implementation doc `Verification` 中的 gate evidence
|
|
33
33
|
- 更新后的 `<harnessRoot>/state/plan.yaml`
|
|
34
34
|
- 更新后的 `.docs/INDEX.md`
|
|
35
|
-
-
|
|
36
|
-
-
|
|
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
|
|
45
|
-
- task completion ledger commit 发生在 implementation commit 之后,只负责将该 task
|
|
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`
|
|
59
|
-
5. implementation commit
|
|
60
|
-
6.
|
|
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
|
|
73
|
-
10. task implementation commit
|
|
74
|
-
11. implementation commit
|
|
75
|
-
12.
|
|
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
|
-
- [ ]
|
|
85
|
-
- [ ] task implementation commit 已在 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:
|
|
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>/
|
|
27
|
+
- `<harnessRoot>/pjsdlc_managed/templates/IMPLEMENTATION_DOC_TEMPLATE.md`
|
|
28
28
|
|
|
29
29
|
## 输出
|
|
30
30
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
-
|
|
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>/
|
|
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
|
|
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
|
|
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:
|
|
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>/
|
|
26
|
+
- `<harnessRoot>/pjsdlc_managed/templates/PRD_TEMPLATE.md`
|
|
27
27
|
|
|
28
28
|
## 输出
|
|
29
29
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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>/
|
|
25
|
+
- `<harnessRoot>/pjsdlc_managed/templates/RELEASE_TEMPLATE.md`
|
|
26
26
|
|
|
27
27
|
## 输出
|
|
28
28
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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>/
|
|
27
|
+
- `<harnessRoot>/pjsdlc_managed/templates/REVIEW_TEMPLATE.md`
|
|
28
28
|
|
|
29
29
|
## 输出
|
|
30
30
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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:
|
|
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>/
|
|
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: "
|
|
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
|
package/dist/lib/config.js
CHANGED
|
@@ -13,12 +13,12 @@ export function defaultConfig(root) {
|
|
|
13
13
|
{ path: "AGENTS.md", strategy: "merge-block" },
|
|
14
14
|
{ path: "Makefile", strategy: "merge-block" },
|
|
15
15
|
{ path: harnessPath(root, "skills"), strategy: "managed" },
|
|
16
|
-
{ path: harnessPath(root, "
|
|
17
|
-
{ path: harnessPath(root, "
|
|
18
|
-
{ path: harnessPath(root, "
|
|
16
|
+
{ path: harnessPath(root, "pjsdlc_managed", "templates"), strategy: "managed" },
|
|
17
|
+
{ path: harnessPath(root, "pjsdlc_managed", "policies"), strategy: "merge-with-local" },
|
|
18
|
+
{ path: harnessPath(root, "pjsdlc_managed", "make", "sdlc-harness.mk"), strategy: "managed" },
|
|
19
19
|
{ path: ".github/workflows/harness.yml", strategy: "create-if-missing" }
|
|
20
20
|
],
|
|
21
|
-
local_overrides: [harnessPath(root, "overrides/**"), harnessPath(root, "
|
|
21
|
+
local_overrides: [harnessPath(root, "overrides/**"), harnessPath(root, "pjsdlc_managed", "policies", "*.local.yaml")],
|
|
22
22
|
never_overwrite: [".docs/**", harnessPath(root, "state/**"), "src/**", "tests/**"]
|
|
23
23
|
};
|
|
24
24
|
}
|
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: "
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare const
|
|
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[];
|
package/dist/lib/managed-file.js
CHANGED
|
@@ -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
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
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
|
+
];
|
package/dist/lib/migrations.js
CHANGED
|
@@ -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
|
|
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, "
|
|
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, "
|
|
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, "
|
|
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 {
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
}
|
package/dist/lib/sync-engine.js
CHANGED
|
@@ -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, "
|
|
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, "
|
|
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, "
|
|
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
|
-
|
|
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, "/")}/
|
|
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
|
-
|
|
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
|
|
133
|
-
|
|
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 (
|
|
133
|
+
if (block.status === "invalid") {
|
|
138
134
|
return false;
|
|
139
135
|
}
|
|
140
|
-
const before = existing.slice(0, startIndex);
|
|
141
|
-
const after = existing.slice(endIndex +
|
|
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,
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
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 (
|
|
155
|
-
|
|
156
|
-
|
|
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));
|
package/dist/lib/validators.js
CHANGED
|
@@ -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, "
|
|
37
|
-
harnessPath(root, "
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
package/source-mappings.yaml
CHANGED
|
@@ -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/
|
|
8
|
+
- source: ".agent/pjsdlc_managed/templates"
|
|
9
9
|
target: "packages/sdlc-harness/assets/templates"
|
|
10
10
|
mode: "copy-tree"
|
|
11
|
-
- source: ".agent/
|
|
11
|
+
- source: ".agent/pjsdlc_managed/policies"
|
|
12
12
|
target: "packages/sdlc-harness/assets/policies"
|
|
13
13
|
mode: "copy-tree"
|
|
14
|
-
- source: "
|
|
14
|
+
- source: ".agent/pjsdlc_managed/make/sdlc-harness.mk"
|
|
15
15
|
target: "packages/sdlc-harness/assets/make/sdlc-harness.mk"
|
|
16
|
-
mode: "
|
|
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"
|