@andyqiu/codeforge 0.5.26 → 0.5.27

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.
@@ -0,0 +1,221 @@
1
+ # CodeForge 完整模板
2
+
3
+ > 此文档由 `agents/codeforge.md` 剥离而来,承接 4 块派 subagent 的完整 `task({...})` prompt 模板。
4
+ > **LLM runtime 不会自动加载此文件** —— 只在 codeforge agent 派 task 时主动 `read` 取对应模板。
5
+ >
6
+ > 主体约束、能力边界场景表、跨 subagent 上下文传递规则(**plan_id 机制**)、与其他 agent 边界等见 [agents/codeforge.md](../../agents/codeforge.md)。
7
+
8
+ ---
9
+
10
+ ## 派 planner(复杂多步任务)
11
+
12
+ ```
13
+ task({
14
+ description: "规划 <一句话需求>",
15
+ subagent_type: "planner",
16
+ prompt: `请按你的工作流程出实现方案。
17
+
18
+ # 需求
19
+ <完整复述用户需求,自包含>
20
+
21
+ # 关键背景(如果有)
22
+ <我已查到的项目地图 / 历史经验摘要,自包含>
23
+
24
+ # 走 spec 路径时额外塞(用户已确认 candidate-specs 后)
25
+ spec=<slug>
26
+ <!-- planner Step 2 必须 read .codeforge/specs/<slug>/handoff.yaml -->
27
+
28
+ # 你必须做的
29
+ 1. 按 planner.md 工作流程出方案
30
+ 2. 方案 ≥ 50 行必须调 plan_write({ title, content }) 写入,工具返回 plan_id
31
+ 3. boomerang 摘要必须含独占一行的「plan_id: plan-YYYYMMDD-HHmmss-NNN」+「建议下一步派: coder / 需要 reviewer 先看」+「关键风险一句话」
32
+ 4. 不要自己派 task(派 subagent 是 codeforge 的职责)
33
+ 5. 不要用 pending_changes.stage 写方案(已废弃,统一用 plan_write)
34
+ 6. 含 spec= 时方案必须有「需求溯源」段(引用 PRE-x / AC-x 作为 evidence)`,
35
+ })
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 派 coder(执行方案,有 planner 的 plan_id)
41
+
42
+ ```
43
+ task({
44
+ description: "执行方案 <一句话>",
45
+ subagent_type: "<按难度分级节选定的变体:coder-quick | coder | coder-deep>",
46
+ prompt: `方案已通过 plan_write 写入,第一步调用 plan_read 拿完整内容:
47
+
48
+ # 方案 plan_id
49
+ plan_id=<planner 回报的 plan-YYYYMMDD-HHmmss-NNN>
50
+
51
+ # 当前 session 信息(guard plugin 用于 gate 闭合校验)
52
+ sessionId=<当前 codeforge session id>
53
+
54
+ # 走 spec 路径时额外塞(用户已确认 candidate-specs 后)
55
+ spec=<slug>
56
+ pre_ack=PRE-1,PRE-2 # 多条逗号分隔;仅列出用户已逐条授权的 PRE id
57
+ <!-- coder Step 0「PRE 阻断校验」会先 read handoff.yaml 核对;任一 PRE 未在 pre_ack 中 → 拒绝启动 -->
58
+
59
+ # 你必须做的
60
+ 1. 立刻 plan_read({ plan_id: "<plan_id>" }) 获取完整方案(同时闭合 session-worktree-guard plan-read hard gate;不调则所有写操作被阻断)
61
+ 2. 含 spec= 时先跑工作流 Step 0(PRE 阻断校验),通过后再 Step 1
62
+ 3. 按方案【步骤】章节顺序执行
63
+ 4. 每步直接在 session worktree 内写文件(edit / write / ast_edit / bash 均可,无需 stage)
64
+ 5. 失败立即停下不要硬修,汇报错误首行
65
+ 6. 全部完成后跑测试,回报「plan_id + 改动文件列表(git diff --stat)+ 测试结果 + 关键风险」
66
+
67
+ ⚠️ 你只在 worktree 内写文件;merge 由 codeforge 在 APPROVE 后调 session_merge,或用户通过 /merge 命令触发 —— coder 自身不要调 session_merge`,
68
+ })
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 派 coder(小改动 short-circuit,无 planner 方案)
74
+
75
+ ```
76
+ task({
77
+ description: "<一句话改动>",
78
+ subagent_type: "<coder-quick | coder | coder-deep>",
79
+ prompt: `# 改动需求
80
+ <自包含描述:改哪个文件 / 改什么 / 为什么>
81
+
82
+ # 当前 session 信息
83
+ sessionId=<当前 codeforge session id>
84
+
85
+ # 你必须做的
86
+ 1. 直接在 session worktree 内写文件(edit / write / ast_edit 均可,worktree 已被 guard plugin 隔离)
87
+ 2. 跑相关测试
88
+ 3. 回报改动文件列表(git diff --stat) + 测试结果`,
89
+ })
90
+ ```
91
+
92
+ > small short-circuit 路径**无 plan_id**(无方案 → guard plugin 不强制 plan_read gate),也**不走 spec=/pre_ack=**。
93
+
94
+ ---
95
+
96
+ ## 派 reviewer(审 session worktree 改动 —— 新模式,默认)
97
+
98
+ ```
99
+ task({
100
+ description: "审阅 session worktree 改动",
101
+ subagent_type: "reviewer",
102
+ prompt: `[Session Merge Review]
103
+
104
+ 请审阅当前 session worktree 内的全部改动并给出 APPROVE / REQUEST_CHANGES / BLOCK:
105
+
106
+ # session 信息(4 项均必填)
107
+ sessionId=<当前 session id> # reviewer 调 review_approval 时用 session:<sessionId>
108
+ worktreePath=<worktree 绝对路径>
109
+ baseSha=<bind 时主仓 HEAD sha,作为 diff base>
110
+ plan_id=<planner 的 plan-xxx>
111
+
112
+ # 你必须做的(并发 emit 以下 3 个调用)
113
+ 1. plan_read({ plan_id: "<plan_id>" }) # 拿方案原文对照
114
+ 2. bash: cd <worktreePath> && git diff <baseSha>..HEAD # 全部改动
115
+ 3. bash: cd <worktreePath> && git diff --stat <baseSha>..HEAD # 文件清单
116
+
117
+ 不要调 pending_changes.list(新模式没有 pending 概念)
118
+
119
+ # 上下文
120
+ - 测试结果:<coder 回报的 pass / fail 摘要>
121
+ - 重点关注:<安全 / 性能 / 与方案一致性 等>
122
+
123
+ # 回报要求
124
+ - APPROVE 前必须先调 review_approval({ verdict: "APPROVE", pendingIds: ["session:<sessionId>"], notes: "<摘要>" })
125
+ - boomerang 摘要 = Decision (APPROVE/REQUEST_CHANGES/BLOCK) + File-by-File 关键意见,不要复制 diff 全文`,
126
+ })
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 派 reviewer(审方案 `plan_only`)
132
+
133
+ ```
134
+ task({
135
+ description: "审方案",
136
+ subagent_type: "reviewer",
137
+ prompt: `请审阅方案并给出 APPROVE / REQUEST_CHANGES / BLOCK:
138
+
139
+ review_target=plan_only
140
+ plan_id=<plan-xxx>
141
+
142
+ # 你必须做的
143
+ - plan_read({ plan_id: "<plan_id>" }) 拿方案原文
144
+ - 按 reviewer.md「审方案」章节出意见(章节-by-章节)
145
+ - 不审代码 diff,不调 bash git diff,不调 pending_changes.list
146
+
147
+ # 回报要求
148
+ - APPROVE 前必须先调 review_approval({ verdict: "APPROVE", pendingIds: ["plan:<plan_id>"], notes: "<摘要>" })
149
+ - boomerang 摘要 = Decision + 章节-by-章节关键意见`,
150
+ })
151
+ ```
152
+
153
+ ---
154
+
155
+ ## 派 reviewer(审决策 `decision_only`)
156
+
157
+ ```
158
+ task({
159
+ description: "审派单决策",
160
+ subagent_type: "reviewer",
161
+ prompt: `请审阅以下决策并给出 APPROVE / REQUEST_CHANGES / BLOCK:
162
+
163
+ review_target=decision_only
164
+ user_choice=<用户选项>
165
+ proposed_dispatch=<候选派单方案:派哪个 agent 做什么>
166
+
167
+ # 你必须做的
168
+ - 审决策风险 / 是否有更好替代方案
169
+ - 如无明显问题直接 APPROVE(避免过度干预用户决策)
170
+ - REQUEST_CHANGES 时给替代建议(≤ 2 条),不必给"改哪行"
171
+
172
+ # 回报要求
173
+ - APPROVE 前必须先调 review_approval({ verdict: "APPROVE", pendingIds: ["decision:<hash>"], notes: "<摘要>" })`,
174
+ })
175
+ ```
176
+
177
+ ---
178
+
179
+ ## 派 reviewer(legacy pending-changes 审,过渡期兼容)
180
+
181
+ > 仅当还有 pending-changes 待审场景(Phase 5 删除 pending_changes 工具前)才使用。
182
+
183
+ ```
184
+ task({
185
+ description: "审阅 pending-changes (legacy)",
186
+ subagent_type: "reviewer",
187
+ prompt: `请审阅 pending-changes 并给出 APPROVE / REQUEST_CHANGES / BLOCK:
188
+
189
+ # 待审 pending id 列表
190
+ <pc-xxx, pc-yyy, ...>
191
+
192
+ # 上下文
193
+ - 测试结果:<coder 回报的 pass / fail 摘要>
194
+ - 重点关注:<安全 / 性能 / 与方案一致性 等>
195
+
196
+ # 回报要求
197
+ - APPROVE 前必须先调 review_approval({ verdict: "APPROVE", pendingIds: ["pc-xxx", "pc-yyy"], notes: "<摘要>" })
198
+ - boomerang 摘要 = Decision + File-by-File 关键意见,不要复制 diff 全文`,
199
+ })
200
+ ```
201
+
202
+ ---
203
+
204
+ ## review_target 选择说明(ADR:reviewer-multi-profile)
205
+
206
+ 派 reviewer 时**必须**根据待审内容选 `review_target`,reviewer 会动态 `read review-profiles/<target>.md` 加载专属清单。
207
+
208
+ | 待审内容 | review_target | 备注 |
209
+ |---|---|---|
210
+ | pending 含 `plans/*.md` | `plan_only` | 7 维度方案审(需求覆盖 / 架构合理性 / ADR 覆盖 / Phase 拆分 / 接口稳定 / 风险 / 可回滚) |
211
+ | pending 含 `docs/adr/*.md` | `adr` | ADR 决策审 + 三向引用机审清单(code-refs / prd-refs / tests 实际存在性) |
212
+ | pending 含 `docs/**/*.md` / `README.md`(非 ADR、非 plan) | `docs` | 7 维度文档审(目标读者 / 步骤可复现 / 信息完整 / 准确性 / 结构 / 陷阱 / 链接) |
213
+ | pending 主要是代码文件(混合或语言不确定) | `code` | 默认;reviewer 自动按 pending 扩展名追加 `code-typescript` / `code-python` / `code-csharp-lua-c` |
214
+ | ≥ 80% 是 TS/JS 文件(.ts/.tsx/.js/.jsx/.mjs/.cjs) | `code:typescript` | 显式提示,跳过 reviewer 端自动检测扫描 |
215
+ | ≥ 80% 是 Python 文件(.py/.pyi) | `code:python` | 显式提示 |
216
+ | ≥ 80% 是 C# / Lua / C 文件 | `code:csharp-lua-c` | 显式提示 |
217
+ | 用户选项驱动派单的中审(Q3-a 范围) | `decision_only` | 中审:识别明显风险 + 替代建议 ≤ 2 条,避免过度干预用户决策 |
218
+
219
+ **禁止自创未列出的 `review_target`**(如 `code:rust` / `code:go`)—— reviewer 会 fallback 到 `code` + 在 Summary 段 warn,不如直接显式传 `code` 让 reviewer 自动按扩展名追加专项 profile。
220
+
221
+ **派 reviewer 模板里如何传**:把 `review_target=<value>` 作为 prompt 内独立一行(参考上方四个 reviewer 派单模板的 `review_target=plan_only` / `review_target=decision_only` 写法),reviewer 会从 prompt 中解析该行(若 runtime 提供 inject_context.review_target 则优先使用结构化值);
@@ -0,0 +1,73 @@
1
+ # Coder 完整模板
2
+
3
+ > 此文档由 `agents/coder.md` 剥离而来,承接派 reviewer fallback 的完整 `task({...})` 模板 + 手动 `Tab` 切 agent 指令 + Step 0 PRE 拒绝启动 boomerang 详细示例。
4
+ > **LLM runtime 不会自动加载此文件** —— 只在 coder agent 走 fallback 路径(无 codeforge 上游)或需要拒绝启动时主动 `read` 取模板。
5
+ >
6
+ > 主体约束、3 项不可删的微步进度 / pending-only 写入约束、工作流程、失败回退等见 [agents/coder.md](../../agents/coder.md)。
7
+
8
+ ---
9
+
10
+ ## 🛑 PRE 拒绝启动 boomerang 详细示例(Step 0 校验未通过时)
11
+
12
+ > agents/coder.md 已含极简模板(≤ 6 行),下面是含真实数据的完整示例,供首次走 spec 路径时参照填写。
13
+
14
+ **场景**:codeforge 派 prompt 含 `spec=user-feed-recsys`,但未含 `pre_ack=`,而 handoff.yaml 含 2 条 `pre_coding_blockers[]`。
15
+
16
+ ```markdown
17
+ ❌ 拒绝启动:spec=user-feed-recsys 含未解除 PRE 阻断
18
+
19
+ **检测 PRE**(来源 .codeforge/specs/user-feed-recsys/handoff.yaml `pre_coding_blockers[]`,v1.2 显式字段):
20
+ - `PRE-1`: 上游推荐 API 鉴权方案未定(OAuth2 vs API key?)— source: assumption / must_resolve_by: user
21
+ - `PRE-2`: redis 集群是否支持 BITCOUNT 命令需运维确认 — source: open_issue / must_resolve_by: codeforge
22
+
23
+ **已检测父 prompt pre_ack**:无
24
+
25
+ **建议下一步**(codeforge 决定,本 coder 不自动派下一棒):
26
+ - a) 用户已逐条授权 → codeforge 派回时补 `pre_ack=PRE-1,PRE-2`
27
+ - b) PRE 未真正解除 → 退回 discover / planner,让 discover 把 must_resolve_by 字段升 `resolved` 或方案显式说明绕过路径
28
+ - c) 用户明文「跳过 PRE 阻断校验,我接受风险」 → codeforge 下次派 prompt 含该短语
29
+
30
+ **未做**:尚未 stage 任何改动;仅完成 `read .codeforge/specs/user-feed-recsys/handoff.yaml`(只读操作)
31
+ **已做**:解析 PRE 集合 + 核对 pre_ack 解除路径
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 🚀 派 reviewer 审阅(fallback — 仅当无 codeforge 上游时)
37
+
38
+ ⚠️ 默认行为:完成后 boomerang 摘要回报 codeforge,由 codeforge 决定是否派 reviewer。
39
+ 只有被用户直接 `@coder` 或 `/quick` 调出(无 codeforge 上游)时才走本节自派 reviewer。
40
+
41
+ 全部步骤完成且 pending-changes 列表已生成后,调 `task` 让 reviewer 审:
42
+
43
+ ```
44
+ task({
45
+ description: "审阅 pending-changes",
46
+ subagent_type: "reviewer",
47
+ prompt: `请审阅 pending-changes 并给出 APPROVE / REQUEST_CHANGES / BLOCK:
48
+ - 待审改动:<N 个文件,+X / -Y 行;附 pending change ID 列表>
49
+ - 测试结果:<pass / fail,失败请保留输出摘要>
50
+ - 方案出处:<复制 planner 方案的【涉及文件】+【步骤】两节>
51
+ - 重点关注:<本次特别要看的维度,如安全 / 性能 / 与方案一致性>`,
52
+ })
53
+ ```
54
+
55
+ opencode 会创建 reviewer 子 session 并把 Decision 回传。子 session 出现在父 session 树里,TUI 按 `Ctrl+→` / `Ctrl+←` 切换查看。
56
+
57
+ 如果 `REQUEST_CHANGES`,把意见复述给用户并问"要不要直接派回我修复"。
58
+
59
+ ---
60
+
61
+ ## 🔀 手动切到 reviewer(task 工具不可用时回退)
62
+
63
+ - **TUI**: 按 `Tab` 切到 `reviewer`,把下面这段话作为新消息发出
64
+ - **Mention**: 在新消息开头写 `@reviewer` + 下面这段话
65
+
66
+ **给 reviewer 的指令**(复制粘贴即可):
67
+
68
+ ```text
69
+ 请审阅 pending-changes 并给出 APPROVE / REQUEST_CHANGES / BLOCK 决策:
70
+ - 待审改动:<N 个文件,+X / -Y 行>
71
+ - 测试已跑:<pass / fail>
72
+ - 方案出处:上方 planner 的方案 + 步骤清单
73
+ ```
@@ -0,0 +1,85 @@
1
+ # Planner 完整模板
2
+
3
+ > 此文档由 `agents/planner.md` 剥离而来,承接方案输出的完整 Markdown 模板示例。
4
+ > **LLM runtime 不会自动加载此文件** —— 只在 planner agent 出方案前主动 `read` 取模板套。
5
+ >
6
+ > 主体约束、工作流程 8 步、行为约束 MUST / MUST NOT 等见 [agents/planner.md](../../agents/planner.md)。
7
+
8
+ ---
9
+
10
+ ## 方案输出格式(强制 Markdown 模板)
11
+
12
+ ```markdown
13
+ ## 需求理解
14
+
15
+ <一句话复述>
16
+
17
+ **关键问题**(如有):
18
+ - Q1: ...
19
+ - Q2: ...
20
+
21
+ ## 项目上下文
22
+
23
+ - repo_map 摘要:<2-3 行>
24
+ - KH 历史经验:<引用 KH 命中条目,无则说"无相关经验">
25
+
26
+ ## 需求溯源(**走 spec 路径时必含;否则省略整段**)
27
+
28
+ 来源:`.codeforge/specs/<slug>/handoff.yaml`
29
+
30
+ **Needs**:
31
+ - `needs[0]` (must): <statement>
32
+ - `needs[1]` (should): <statement>
33
+
34
+ **Boundaries(excluded)**:
35
+ - <excluded> — <reason>
36
+
37
+ **Pre-coding Blockers**(v1.2 显式 / v1.1 fallback 推断):
38
+ - `PRE-1`: <blocker>(source: assumption, must_resolve_by: user)— 本方案**已解除**:<理由>
39
+ - `PRE-2`: <blocker>(source: red_flag, must_resolve_by: codeforge)— 留给 coder 启动时 `pre_ack`
40
+
41
+ **Acceptance Criteria 覆盖**:
42
+ - `AC-1`: <description> → 由 Step 3 / Step 5 覆盖
43
+ - `AC-2`: <description> → 由 Step 4 + 测试 X 覆盖
44
+
45
+ ## 实现方案
46
+
47
+ ### 涉及文件
48
+ - `path/a.ts` — 改 X
49
+ - `path/b.ts` — 新增 Y
50
+
51
+ ### 步骤
52
+ 1. ...
53
+ 2. ...
54
+ 3. ...
55
+
56
+ ### 测试策略
57
+ - 单元测试:...
58
+ - 集成测试:...
59
+
60
+ ## 风险与缓解
61
+ | # | 风险 | 缓解 |
62
+ |---|---|---|
63
+ | 1 | ... | ... |
64
+ | 2 | ... | ... |
65
+
66
+ ## ADR-Draft
67
+
68
+ **Context**: <为什么要做这件事 / 当前痛点>
69
+ **Options Considered**: <选项 A / B / C + 各自优缺点>
70
+ **Decision**: <选哪个 + 关键理由 — 走 spec 路径时必须显式引用 PRE-x / AC-x 作为 evidence,如「Decision 选 A:解除 PRE-1(外部 API 已确认可用)+ 覆盖 AC-1 / AC-3」>
71
+
72
+ <!-- trivial 改动可写:「无需 ADR:理由 ____」 -->
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 套模板要点
78
+
79
+ 1. **章节顺序不可调换** —— executor / 后续 agent 按章节名定位
80
+ 2. **`## ADR-Draft` 必含** —— trivial 改动也要写"无需 ADR:理由 ____",不允许整段省略
81
+ 3. **`## 需求溯源` 段**:走 spec 路径必含;非 spec 路径可整段省略(不要写"无 spec"占位)
82
+ 4. **`### 涉及文件` 必含路径列表** —— codeforge 派 coder 时会按此清单判定是否需要拆 phase
83
+ 5. **风险表至少 2 条** —— 不允许"无明显风险"作为唯一行
84
+ 6. **方案 ≥ 50 行必须 `pending_changes.stage` 到 `plans/<ts>-<slug>.md`**,回报 pending id 给 codeforge
85
+ 7. **走 spec 路径时 ADR-Draft Decision 段必须有 PRE-x / AC-x 引用**,让 reviewer 能机械核对需求溯源链
@@ -0,0 +1,75 @@
1
+ # Reviewer 完整模板
2
+
3
+ > 此文档由 `agents/reviewer.md` 剥离而来,承接决策后下一步 fallback 的完整 `task({...})` 模板 + 手动 `Tab` 切 agent 指令。
4
+ > **LLM runtime 不会自动加载此文件** —— 只在 reviewer agent 走 fallback 路径(无 codeforge 上游)时主动 `read` 取对应档模板。
5
+ >
6
+ > 主体约束、Decision 首行格式约束、审阅清单 8 项、输出格式 Markdown 模板等见 [agents/reviewer.md](../../agents/reviewer.md)。
7
+
8
+ ---
9
+
10
+ ## 决策后下一步(fallback — 仅当无 codeforge 上游时)
11
+
12
+ ⚠️ 默认行为:boomerang 摘要回报 codeforge(含 Decision + 关键意见),由 codeforge 决定下一棒。
13
+ 只有被用户直接 `@reviewer` 或工作流显式调出(无 codeforge 上游)时才走本节自派后续 agent。
14
+
15
+ reviewer 永远不直接改代码。`REQUEST_CHANGES` / `BLOCK` 时**优先用 `task` 工具移交 coder / planner**(opencode 1.14+ 主线 Task,自动闭环、子 session 隔离);不可用时回退到手动 `Tab` 切 agent。
16
+
17
+ ---
18
+
19
+ ## 如果 `APPROVE`
20
+
21
+ 切回主 agent(如 `plan`)应用 pending-changes:
22
+
23
+ - **TUI**: 按 `Tab` 切回主 agent,发新消息:
24
+
25
+ ```text
26
+ APPROVE 已通过,请 apply-changes 把 pending 全部落盘
27
+ ```
28
+
29
+ ---
30
+
31
+ ## 如果 `REQUEST_CHANGES`
32
+
33
+ **首选 — 调 task 派 coder 修复**:
34
+
35
+ ```
36
+ task({
37
+ description: "按 review 意见修复",
38
+ subagent_type: "coder",
39
+ prompt: `请按以下意见修复 pending-changes,修完跑测试并列出新的 pending-changes 等我复审:
40
+ 1. 修改 \`path/a.ts:88\` 为 prepared statement(理由:SQL 注入风险)
41
+ 2. <其他 REQUEST_CHANGES 意见,每条带文件:行 + 改成什么 + 为什么>`,
42
+ })
43
+ ```
44
+
45
+ opencode 会创建 coder 子 session,结束后回传摘要给 reviewer 继续第二轮审阅。
46
+
47
+ **手动回退**:按 `Tab` 选 `coder`,发:
48
+
49
+ ```text
50
+ 请按以下意见修复 pending-changes 后让我重新审:
51
+ 1. 修改 `path/a.ts:88` 为 prepared statement
52
+ 2. 重新跑 `npm test`
53
+ 3. 列出新的 pending-changes
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 如果 `BLOCK`
59
+
60
+ **首选 — 调 task 派 planner 重新设计**:
61
+
62
+ ```
63
+ task({
64
+ description: "重新规划(绕过被 BLOCK 的方案)",
65
+ subagent_type: "planner",
66
+ prompt: `reviewer 给了 BLOCK:<原因摘要 + 涉及文件>。原方案存在 <根本性风险描述>。请重新规划方案,避开此风险。原方案概要:<复制原方案的【涉及文件】+【步骤】两节>`,
67
+ })
68
+ ```
69
+
70
+ **手动回退**:按 `Tab` 选 `planner`,发:
71
+
72
+ ```text
73
+ reviewer 给了 BLOCK:<原因摘要>。
74
+ 请重新规划方案,避开此风险。
75
+ ```
@@ -22,7 +22,7 @@ model_thinking:
22
22
  type: enabled
23
23
  budget_tokens: 4000
24
24
  fallback_models:
25
- - anthropic/claude-opus-4-7 # 路由判断失败 / 复杂场景升档
25
+ - anthropic/claude-opus-4-8 # 路由判断失败 / 复杂场景升档
26
26
  - openai/gpt-5.5
27
27
  - google/gemini-3-pro
28
28
  ---
@@ -180,7 +180,7 @@ codeforge 在 session 内维护两个 loop 计数器:
180
180
 
181
181
  ## 派 subagent 模板
182
182
 
183
- 派 planner / coder / reviewer 完整 prompt 模板见 **[docs/agent-templates/codeforge.md](../docs/agent-templates/codeforge.md)** —— 派 task 时主动 `read` 该文件取对应模板,禁止凭印象拼 prompt。
183
+ 派 planner / coder / reviewer 完整 prompt 模板见 `~/.config/opencode/agent-templates/codeforge.md`(Windows:`%APPDATA%\opencode\agent-templates\codeforge.md`)—— 派 task 时主动 `read` 该绝对路径取对应模板(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/codeforge.md`),禁止凭印象拼 prompt。
184
184
 
185
185
  ## 失败回退
186
186
 
@@ -99,7 +99,7 @@ fallback_models:
99
99
 
100
100
  只有被用户直接 `@coder` 或 `/deep` 调出(无 codeforge 上游)时才走 fallback:调 `task` 派 reviewer 或手动 `Tab` 切 agent。
101
101
 
102
- 完整 `task({...})` prompt 模板 + 手动 `Tab` / `@reviewer` mention 指令模板见 **[docs/agent-templates/coder.md](../docs/agent-templates/coder.md)** —— 走 fallback 时主动 `read` 该文件,禁止凭印象拼 prompt(漏字段就破坏 reviewer 回报契约)。
102
+ 完整 `task({...})` prompt 模板 + 手动 `Tab` / `@reviewer` mention 指令模板见 `~/.config/opencode/agent-templates/coder.md` —— 走 fallback 时主动 `read` 该绝对路径(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/coder.md`),禁止凭印象拼 prompt(漏字段就破坏 reviewer 回报契约)。
103
103
 
104
104
  ## 失败回退
105
105
 
@@ -98,7 +98,7 @@ fallback_models:
98
98
 
99
99
  只有被用户直接 `@coder` 或 `/quick` 调出(无 codeforge 上游)时才走 fallback:调 `task` 派 reviewer 或手动 `Tab` 切 agent。
100
100
 
101
- 完整 `task({...})` prompt 模板 + 手动 `Tab` / `@reviewer` mention 指令模板见 **[docs/agent-templates/coder.md](../docs/agent-templates/coder.md)** —— 走 fallback 时主动 `read` 该文件,禁止凭印象拼 prompt(漏字段就破坏 reviewer 回报契约)。
101
+ 完整 `task({...})` prompt 模板 + 手动 `Tab` / `@reviewer` mention 指令模板见 `~/.config/opencode/agent-templates/coder.md` —— 走 fallback 时主动 `read` 该绝对路径(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/coder.md`),禁止凭印象拼 prompt(漏字段就破坏 reviewer 回报契约)。
102
102
 
103
103
  ## 失败回退
104
104
 
package/agents/coder.md CHANGED
@@ -23,7 +23,7 @@ model_thinking:
23
23
  budget_tokens: 4000
24
24
  fallback_models:
25
25
  - openai/gpt-5.5
26
- - anthropic/claude-opus-4-7
26
+ - anthropic/claude-opus-4-8
27
27
  - google/gemini-3-pro
28
28
  ---
29
29
 
@@ -93,7 +93,7 @@ fallback_models:
93
93
  **未做**:未写任何文件;仅 read 了 handoff.yaml
94
94
  ```
95
95
 
96
- 完整模板示例(含派 reviewer fallback 的 `task({...})` prompt + 手动 `Tab` / `@reviewer` mention 指令)见 **[docs/agent-templates/coder.md](../docs/agent-templates/coder.md)** —— 走 fallback 时主动 `read` 该文件,禁止凭印象拼 prompt(漏字段就破坏 reviewer 回报契约)。
96
+ 完整模板示例(含派 reviewer fallback 的 `task({...})` prompt + 手动 `Tab` / `@reviewer` mention 指令)见 `~/.config/opencode/agent-templates/coder.md` —— 走 fallback 时主动 `read` 该绝对路径(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/coder.md`),禁止凭印象拼 prompt(漏字段就破坏 reviewer 回报契约)。
97
97
 
98
98
  ## 失败回退
99
99
 
@@ -17,14 +17,14 @@ allowed_tools:
17
17
  # 故意不给 save_chat_insight:challenger 是过程角色,不沉淀
18
18
  # 故意不给 task:物理禁止派子 agent(避免嵌套对抗)
19
19
  # 故意不给 edit/write:零写权(worktree 也不允许写)
20
- model: openai/gpt-5.5
20
+ model: openai/gpt-5.5-pro
21
21
  model_category: ultrabrain
22
22
  tier: deep
23
23
  model_thinking:
24
24
  type: enabled
25
25
  budget_tokens: 3000
26
26
  fallback_models:
27
- - anthropic/claude-opus-4-7
27
+ - anthropic/claude-opus-4-8
28
28
  - anthropic/claude-sonnet-4-6
29
29
  - google/gemini-3-pro
30
30
  ---
@@ -33,7 +33,7 @@ fallback_models:
33
33
 
34
34
  你是 discover 召唤的**强对抗专家**。职责:用第十人视角给现状/PRD 草稿挑刺,给出离散的红旗判定(YES / NO)。
35
35
 
36
- > **选 gpt-5.5 作为主模型的理由**:与 discover 主模型(claude-opus-4-7)跨家族,避免同源视角偏见(与 reviewer agent 选型一致);红旗 YES/NO 由 §3 离散触发组合锁死,模型迎合倾向只影响"理由文笔"而非"结论"。fallback 链 opus → sonnet → gemini 跨三家保高可用。
36
+ > **选 gpt-5.5 作为主模型的理由**:与 discover 主模型(claude-opus-4-8)跨家族,避免同源视角偏见(与 reviewer agent 选型一致);红旗 YES/NO 由 §3 离散触发组合锁死,模型迎合倾向只影响"理由文笔"而非"结论"。fallback 链 opus → sonnet → gemini 跨三家保高可用。
37
37
  >
38
38
  > **历史**:Phase 0 曾选 google/gemini-3-pro(看中"直说"倾向),但 C5 dogfooding 暴露 provider 注册缺失导致 ProviderModelNotFoundError,Phase 1.1 切换至 gpt-5.5。
39
39
 
package/agents/planner.md CHANGED
@@ -101,7 +101,7 @@ ADR: worktree-session-isolation Phase 4 —— planner 改用 plan_write 替代
101
101
  5. `## 风险与缓解` — Markdown 表格,至少 2 条风险
102
102
  6. `## ADR-Draft` — Context / Options Considered / Decision 三段(trivial 改动可写"无需 ADR:理由 ____")
103
103
 
104
- 完整模板示例(含每节字段、表格列、注释)见 **[docs/agent-templates/planner.md](../docs/agent-templates/planner.md)** —— 出方案前主动 `read` 该文件按模板套,禁止凭印象拼章节顺序或漏 ADR-Draft 章节。
104
+ 完整模板示例(含每节字段、表格列、注释)见 `~/.config/opencode/agent-templates/planner.md` —— 出方案前主动 `read` 该绝对路径按模板套(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/planner.md`),禁止凭印象拼章节顺序或漏 ADR-Draft 章节。
105
105
 
106
106
  ## 失败回退
107
107
 
@@ -108,7 +108,7 @@ reviewer 的 `read` 工具**仅允许**读以下路径(白名单):
108
108
  | 允许路径 | 用途 |
109
109
  |---|---|
110
110
  | `review-profiles/*.md` | 加载对应 review_target 的专项 profile |
111
- | `docs/agent-templates/*.md` | 兜底参考 reviewer 自身模板(仅 fallback 场景) |
111
+ | `~/.config/opencode/agent-templates/*.md` | 兜底参考 reviewer 自身模板(仅 fallback 场景) |
112
112
  | `.codeforge/specs/<slug>/handoff.yaml` | 走 spec 路径时核对需求溯源 |
113
113
  | `docs/adr/*.md` | 审 adr profile 时读对照 ADR 内容(仅 review_target=adr 场景) |
114
114
  | `docs/adr/template.md` / `docs/adr/README.md` | 审 ADR 时核对模板与索引(仅 adr 场景) |
@@ -237,7 +237,7 @@ reviewer 的 `read` 工具**仅允许**读以下路径(白名单):
237
237
  - **REQUEST_CHANGES** → 首选调 `task` 派 coder 修复(带具体到行的意见),不可用时手动 `Tab` 切 coder
238
238
  - **BLOCK** → 首选调 `task` 派 planner 重设计(带原 plan_id + BLOCK 原因),不可用时手动 `Tab` 切 planner
239
239
 
240
- 完整 `task({...})` prompt 模板见 **[docs/agent-templates/reviewer.md](../docs/agent-templates/reviewer.md)**。
240
+ 完整 `task({...})` prompt 模板见 `~/.config/opencode/agent-templates/reviewer.md`(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/reviewer.md`)。
241
241
 
242
242
  ## 失败回退
243
243
 
@@ -107,7 +107,7 @@ reviewer 的 `read` 工具**仅允许**读以下路径(白名单):
107
107
  | 允许路径 | 用途 |
108
108
  |---|---|
109
109
  | `review-profiles/*.md` | 加载对应 review_target 的专项 profile |
110
- | `docs/agent-templates/*.md` | 兜底参考 reviewer 自身模板(仅 fallback 场景) |
110
+ | `~/.config/opencode/agent-templates/*.md` | 兜底参考 reviewer 自身模板(仅 fallback 场景) |
111
111
  | `.codeforge/specs/<slug>/handoff.yaml` | 走 spec 路径时核对需求溯源 |
112
112
  | `docs/adr/*.md` | 审 adr profile 时读对照 ADR 内容(仅 review_target=adr 场景) |
113
113
  | `docs/adr/template.md` / `docs/adr/README.md` | 审 ADR 时核对模板与索引(仅 adr 场景) |
@@ -236,7 +236,7 @@ reviewer 的 `read` 工具**仅允许**读以下路径(白名单):
236
236
  - **REQUEST_CHANGES** → 首选调 `task` 派 coder 修复(带具体到行的意见),不可用时手动 `Tab` 切 coder
237
237
  - **BLOCK** → 首选调 `task` 派 planner 重设计(带原 plan_id + BLOCK 原因),不可用时手动 `Tab` 切 planner
238
238
 
239
- 完整 `task({...})` prompt 模板见 **[docs/agent-templates/reviewer.md](../docs/agent-templates/reviewer.md)**。
239
+ 完整 `task({...})` prompt 模板见 `~/.config/opencode/agent-templates/reviewer.md`(若 `read` 不展开 `~`,改用 `bash cat ~/.config/opencode/agent-templates/reviewer.md`)。
240
240
 
241
241
  ## 失败回退
242
242
 
package/commands/deep.md CHANGED
@@ -16,7 +16,7 @@ codeforge 元数据(opencode 不读,由 plugins / workflow-engine 解析)
16
16
 
17
17
  # /deep — 临时升档到 deep(Opus + 大 thinking)
18
18
 
19
- 把指定 agent 的模型临时升到 `deep` 档(默认 `anthropic/claude-opus-4-7` + 大 thinking budget)。
19
+ 把指定 agent 的模型临时升到 `deep` 档(默认 `anthropic/claude-opus-4-8` + 大 thinking budget)。
20
20
  改动会直接写入当前 session worktree(由 session-worktree-guard 隔离主仓),**由用户 `/merge` 拍板合并到主仓后才会真正生效**。
21
21
 
22
22
  ## 输入
package/dist/index.js CHANGED
@@ -13046,9 +13046,7 @@ async function mergeSessionBack(opts) {
13046
13046
  throw new Error(`${buildScript} 失败已 reset 主仓: ${msg}`);
13047
13047
  }
13048
13048
  }
13049
- } else {
13050
- console.debug(`[session-worktree] skip build step: merge.postMergeScript not configured in .codeforge/codeforge.json (project may not need post-merge build)`);
13051
- }
13049
+ } else {}
13052
13050
  const squashedRaw = await runGit2(wt, ["log", "--format=%s", `${baseSha}..HEAD`]);
13053
13051
  const squashedCommits = squashedRaw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
13054
13052
  const message = opts.commitMessage ?? buildMergeMessage(opts.sessionId, branch, baseSha, squashedCommits);
@@ -17526,6 +17524,40 @@ function extractCreatedChild(event) {
17526
17524
  return null;
17527
17525
  return { childID: session.id, parentID: session.parentID, agent: null };
17528
17526
  }
17527
+ var ERROR_REASON_MAX_LEN = 300;
17528
+ function extractErrorReason(props) {
17529
+ function sanitize(s) {
17530
+ const folded = s.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
17531
+ if (folded.length <= ERROR_REASON_MAX_LEN)
17532
+ return folded;
17533
+ return folded.slice(0, ERROR_REASON_MAX_LEN) + "…";
17534
+ }
17535
+ function extractFromValue(v) {
17536
+ if (typeof v === "string" && v.trim())
17537
+ return sanitize(v);
17538
+ if (v && typeof v === "object") {
17539
+ const obj = v;
17540
+ const dataMsg = obj["data"] && typeof obj["data"] === "object" ? obj["data"]["message"] : undefined;
17541
+ const fromMsg = obj["message"] ?? dataMsg ?? obj["reason"];
17542
+ if (typeof fromMsg === "string" && fromMsg.trim())
17543
+ return sanitize(fromMsg);
17544
+ }
17545
+ return null;
17546
+ }
17547
+ const fromError = extractFromValue(props["error"]);
17548
+ if (fromError !== null)
17549
+ return fromError;
17550
+ if (typeof props["message"] === "string" && props["message"].trim())
17551
+ return sanitize(props["message"]);
17552
+ if (typeof props["reason"] === "string" && props["reason"].trim())
17553
+ return sanitize(props["reason"]);
17554
+ if (props["info"] && typeof props["info"] === "object") {
17555
+ const fromInfoError = extractFromValue(props["info"]["error"]);
17556
+ if (fromInfoError !== null)
17557
+ return fromInfoError;
17558
+ }
17559
+ return null;
17560
+ }
17529
17561
  function extractEndedSessionID(event) {
17530
17562
  if (!event || typeof event !== "object")
17531
17563
  return null;
@@ -17537,15 +17569,19 @@ function extractEndedSessionID(event) {
17537
17569
  }
17538
17570
  const props = e.properties ?? {};
17539
17571
  const direct = props["sessionID"];
17540
- if (typeof direct === "string" && direct)
17541
- return { type: e.type, sessionID: direct };
17542
- const info = props["info"];
17543
- if (info && typeof info === "object") {
17544
- const sid = info.id;
17545
- if (typeof sid === "string" && sid)
17546
- return { type: e.type, sessionID: sid };
17547
- }
17548
- return null;
17572
+ const sessionID = typeof direct === "string" && direct ? direct : (() => {
17573
+ const info = props["info"];
17574
+ if (info && typeof info === "object") {
17575
+ const sid = info.id;
17576
+ if (typeof sid === "string" && sid)
17577
+ return sid;
17578
+ }
17579
+ return null;
17580
+ })();
17581
+ if (!sessionID)
17582
+ return null;
17583
+ const errorReason = e.type === "session.error" ? extractErrorReason(props) : null;
17584
+ return { type: e.type, sessionID, errorReason };
17549
17585
  }
17550
17586
  function extractTaskArgs(args) {
17551
17587
  if (!args || typeof args !== "object")
@@ -17765,6 +17801,15 @@ function buildAfterLogLine(toolName, ok, durationMs, now = Date.now()) {
17765
17801
  duration_ms: durationMs
17766
17802
  });
17767
17803
  }
17804
+ function buildErrorLogLine(errorReason, elapsedMs, now = Date.now()) {
17805
+ return JSON.stringify({
17806
+ ts: Math.floor(now / 1000),
17807
+ phase: "error",
17808
+ ok: false,
17809
+ error_reason: errorReason ?? "(unknown)",
17810
+ elapsed_ms: elapsedMs
17811
+ });
17812
+ }
17768
17813
  async function appendSubagentLog(filePath, line, log7) {
17769
17814
  try {
17770
17815
  await fsPromises.mkdir(path20.dirname(filePath), { recursive: true });
@@ -17952,6 +17997,40 @@ var subtaskHeartbeatServer = async (ctx) => {
17952
17997
  toast_sent: sent,
17953
17998
  end_toast_message: t.message
17954
17999
  });
18000
+ if (ended.type === "session.error" && isLogPersistenceEnabled(cwd)) {
18001
+ const elapsedMs = Date.now() - r.startedAt;
18002
+ const errLine = buildErrorLogLine(ended.errorReason, elapsedMs);
18003
+ const logFile = subagentLogPath(cwd, r.parentID, r.childID);
18004
+ appendSubagentLog(logFile, errLine, log7);
18005
+ const parentID = r.parentID;
18006
+ const who = r.agent ? titleCase(r.agent) : "subagent";
18007
+ const elapsed = fmtElapsed(elapsedMs);
18008
+ const noticeText = [
18009
+ `❌ ${who} 失败(${elapsed})`,
18010
+ ended.errorReason ? `错误:${ended.errorReason.slice(0, 150)}` : null,
18011
+ `日志:${logFile}`,
18012
+ `排查:cat "${logFile}" | tail -30`
18013
+ ].filter(Boolean).join(`
18014
+ `);
18015
+ const logAdapter = (level, msg, data) => {
18016
+ if (level === "warn" || level === "error") {
18017
+ log7.warn(`[sendParentNotice] ${msg}`, data);
18018
+ }
18019
+ };
18020
+ (async () => {
18021
+ try {
18022
+ await sendParentNotice(client, parentID, noticeText, { directory: cwd, log: logAdapter });
18023
+ safeWriteLog(PLUGIN_NAME11, {
18024
+ hook: "event",
18025
+ type: "session.error.notified",
18026
+ child: r.childID,
18027
+ parent: r.parentID,
18028
+ has_error_reason: ended.errorReason !== null,
18029
+ notice_text_len: noticeText.length
18030
+ });
18031
+ } catch {}
18032
+ })();
18033
+ }
17955
18034
  if (ended.type === "session.deleted") {
17956
18035
  const logFile = subagentLogPath(cwd, r.parentID, r.childID);
17957
18036
  fsPromises.unlink(logFile).catch(() => {});
@@ -21150,7 +21229,7 @@ import * as zlib from "node:zlib";
21150
21229
  // lib/version-injected.ts
21151
21230
  function getInjectedVersion() {
21152
21231
  try {
21153
- const v = "0.5.26";
21232
+ const v = "0.5.27";
21154
21233
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
21155
21234
  return v;
21156
21235
  }
package/install.ps1 CHANGED
@@ -88,7 +88,8 @@ $CodeforgeCfgDir = Join-Path $XdgConfigBase 'codeforge'
88
88
  # v0.1 之前 install.ps1 装的目录(卸载时一并清掉)
89
89
  $LegacyDirs = @('agent', 'command', 'tool', 'tools', 'plugin', 'plugins', 'lib')
90
90
  # v0.1+ 才有的目录(review-profiles 由 ADR:reviewer-multi-profile 引入)
91
- $ManagedDirs = @('codeforge', 'agents', 'commands', 'workflows', 'context-templates', 'review-profiles')
91
+ # agent-templates ADR:agent-templates-move-to-root 移到根目录(supersedes agent-templates-distribution)
92
+ $ManagedDirs = @('codeforge', 'agents', 'commands', 'workflows', 'context-templates', 'review-profiles', 'agent-templates')
92
93
 
93
94
  # v0.1+ 文件分发计划
94
95
  $BundleSrcRel = 'dist/index.js'
@@ -103,10 +104,12 @@ $MdCopyMap = @(
103
104
  @{ Src='commands'; Dst='commands' }
104
105
  )
105
106
  # 普通整目录 copy(review-profiles 由 ADR:reviewer-multi-profile 引入)
107
+ # ADR:agent-templates-move-to-root — agent-templates 随 install 分发到全局目录
106
108
  $CopyMap = @(
107
- @{ Src='workflows'; Dst='workflows' },
108
- @{ Src='context-templates'; Dst='context-templates' },
109
- @{ Src='review-profiles'; Dst='review-profiles' }
109
+ @{ Src='workflows'; Dst='workflows' },
110
+ @{ Src='context-templates'; Dst='context-templates' },
111
+ @{ Src='review-profiles'; Dst='review-profiles' },
112
+ @{ Src='agent-templates'; Dst='agent-templates' }
110
113
  )
111
114
 
112
115
  # ────────────── opencode.json 管理 ──────────────
package/install.sh CHANGED
@@ -118,7 +118,8 @@ CODEFORGE_CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/codeforge"
118
118
  # v0.1 之前装的目录(卸载时一并清掉)
119
119
  LEGACY_DIRS=(agent command tool tools plugin plugins lib)
120
120
  # v0.1+ 才有的目录(review-profiles 由 ADR:reviewer-multi-profile 引入)
121
- MANAGED_DIRS=(codeforge agents commands workflows context-templates review-profiles)
121
+ # agent-templates ADR:agent-templates-move-to-root 移到根目录(supersedes agent-templates-distribution)
122
+ MANAGED_DIRS=(codeforge agents commands workflows context-templates review-profiles agent-templates)
122
123
 
123
124
  BUNDLE_SRC_REL="dist/index.js"
124
125
  BUNDLE_DST_REL="codeforge/index.js"
@@ -126,7 +127,8 @@ BUNDLE_DST_REL="codeforge/index.js"
126
127
  # 文件级 copy 白名单(.md,排除 README/_*/.bak/.*)
127
128
  MD_COPY_DIRS=("agents:agents" "commands:commands")
128
129
  # 整目录拷贝(review-profiles 由 ADR:reviewer-multi-profile 引入,避开 maxdepth 1 限制)
129
- COPY_DIRS=("workflows:workflows" "context-templates:context-templates" "review-profiles:review-profiles")
130
+ # ADR:agent-templates-move-to-root — agent-templates 随 install 分发到全局目录
131
+ COPY_DIRS=("workflows:workflows" "context-templates:context-templates" "review-profiles:review-profiles" "agent-templates:agent-templates")
130
132
 
131
133
  # KH 行为规范模板(B3 智能合并的输入)
132
134
  KH_TEMPLATE_REL="context-templates/kh-instructions.md"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.5.26",
3
+ "version": "0.5.27",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,
@@ -110,6 +110,7 @@
110
110
  "dist/adr-init.js",
111
111
  "dist/adr-init.d.ts",
112
112
  "agents/",
113
+ "agent-templates/",
113
114
  "assets/",
114
115
  "bin/",
115
116
  "commands/",
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 强制章节检查(机审清单 — 等价章节可接受)
29
29
 
30
- 按 `docs/agent-templates/planner.md` 模板核对,**允许等价章节命名**:
30
+ 按 `agent-templates/planner.md` 模板核对,**允许等价章节命名**:
31
31
 
32
32
  - [ ] **需求理解**(标题包含「需求」/「理解」/「目标」之一即可,≤ 5 行)
33
33
  - [ ] **项目上下文**(标题含「上下文」/「背景」/「现状」之一)