@devcxl/opencode-cabbage 1.1.0 → 1.3.0
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/.codex-plugin/plugin.json +36 -0
- package/README.md +53 -25
- package/README.zh.md +23 -23
- package/assets/agents/dev-lifecycle.md +208 -68
- package/assets/agents/team/architect.md +22 -7
- package/assets/agents/team/developer.md +13 -6
- package/assets/agents/team/goal-verify.md +4 -1
- package/assets/agents/team/researcher.md +59 -0
- package/assets/agents/team/reviewer.md +34 -18
- package/assets/commands/setup.md +1 -1
- package/assets/prompts/bootstrap.md +10 -9
- package/assets/prompts/stage-contract.md +25 -25
- package/assets/skills/flow-code/SKILL.md +23 -25
- package/assets/skills/flow-design/SKILL.md +94 -26
- package/assets/skills/flow-release/SKILL.md +41 -17
- package/assets/skills/flow-requirements/SKILL.md +19 -13
- package/assets/skills/flow-research/SKILL.md +77 -0
- package/assets/skills/flow-review/SKILL.md +55 -25
- package/assets/skills/flow-setup/SKILL.md +27 -24
- package/assets/skills/flow-tasks/SKILL.md +178 -35
- package/assets/skills/flow-tdd/SKILL.md +68 -40
- package/codex-skills/agent-architect/SKILL.md +79 -0
- package/codex-skills/agent-dev-lifecycle/SKILL.md +309 -0
- package/codex-skills/agent-developer/SKILL.md +51 -0
- package/codex-skills/agent-goal-verify/SKILL.md +45 -0
- package/codex-skills/agent-researcher/SKILL.md +32 -0
- package/codex-skills/agent-reviewer/SKILL.md +109 -0
- package/codex-skills/flow-code/SKILL.md +71 -0
- package/codex-skills/flow-design/SKILL.md +146 -0
- package/codex-skills/flow-release/SKILL.md +90 -0
- package/codex-skills/flow-requirements/SKILL.md +90 -0
- package/codex-skills/flow-research/SKILL.md +77 -0
- package/codex-skills/flow-review/SKILL.md +110 -0
- package/codex-skills/flow-setup/SKILL.md +75 -0
- package/codex-skills/flow-tasks/SKILL.md +220 -0
- package/codex-skills/flow-tdd/SKILL.md +235 -0
- package/dist/hooks/session-start.d.ts +1 -0
- package/dist/hooks/session-start.js +72 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/kernel/session-index.d.ts +8 -25
- package/dist/kernel/session-index.js +5 -55
- package/dist/kernel/session-index.js.map +1 -1
- package/dist/plugin/bootstrap.d.ts +0 -1
- package/dist/plugin/bootstrap.js +0 -3
- package/dist/plugin/bootstrap.js.map +1 -1
- package/dist/plugin/goal-store.d.ts +71 -0
- package/dist/plugin/goal-store.js +54 -0
- package/dist/plugin/goal-store.js.map +1 -0
- package/dist/plugin/goal.d.ts +47 -13
- package/dist/plugin/goal.js +228 -128
- package/dist/plugin/goal.js.map +1 -1
- package/dist/plugin/prompt-lint.js +3 -11
- package/dist/plugin/prompt-lint.js.map +1 -1
- package/dist/plugin/prompts.d.ts +0 -1
- package/dist/plugin/prompts.js +0 -4
- package/dist/plugin/prompts.js.map +1 -1
- package/dist/plugin/server.d.ts +3 -5
- package/dist/plugin/server.js +247 -155
- package/dist/plugin/server.js.map +1 -1
- package/dist/plugin/skills.d.ts +7 -1
- package/dist/plugin/skills.js +75 -12
- package/dist/plugin/skills.js.map +1 -1
- package/dist/plugin.d.ts +0 -1
- package/dist/plugin.js +1 -4
- package/dist/plugin.js.map +1 -1
- package/dist/util/gh.d.ts +0 -11
- package/dist/util/gh.js +0 -14
- package/dist/util/gh.js.map +1 -1
- package/dist/util/paths.d.ts +0 -14
- package/dist/util/paths.js +0 -69
- package/dist/util/paths.js.map +1 -1
- package/hooks/hooks.json +17 -0
- package/package.json +12 -4
- package/assets/prompts/ADR-FORMAT.md +0 -45
- package/assets/prompts/PRD-FORMAT.md +0 -17
- package/assets/templates/gitignore +0 -5
- package/dist/kernel/caller.d.ts +0 -48
- package/dist/kernel/caller.js +0 -88
- package/dist/kernel/caller.js.map +0 -1
- package/dist/kernel/flow.d.ts +0 -175
- package/dist/kernel/flow.js +0 -467
- package/dist/kernel/flow.js.map +0 -1
- package/dist/kernel/legacy.d.ts +0 -19
- package/dist/kernel/legacy.js +0 -34
- package/dist/kernel/legacy.js.map +0 -1
- package/dist/kernel/records.d.ts +0 -103
- package/dist/kernel/records.js +0 -240
- package/dist/kernel/records.js.map +0 -1
- package/dist/kernel/release.d.ts +0 -106
- package/dist/kernel/release.js +0 -241
- package/dist/kernel/release.js.map +0 -1
- package/dist/kernel/review.d.ts +0 -34
- package/dist/kernel/review.js +0 -82
- package/dist/kernel/review.js.map +0 -1
- package/dist/kernel/setup.d.ts +0 -95
- package/dist/kernel/setup.js +0 -401
- package/dist/kernel/setup.js.map +0 -1
- package/dist/kernel/slug.d.ts +0 -23
- package/dist/kernel/slug.js +0 -67
- package/dist/kernel/slug.js.map +0 -1
- package/dist/kernel/task.d.ts +0 -213
- package/dist/kernel/task.js +0 -873
- package/dist/kernel/task.js.map +0 -1
- package/dist/kernel/tdd/adapter.d.ts +0 -49
- package/dist/kernel/tdd/adapter.js +0 -294
- package/dist/kernel/tdd/adapter.js.map +0 -1
- package/dist/kernel/tdd/digest.d.ts +0 -18
- package/dist/kernel/tdd/digest.js +0 -303
- package/dist/kernel/tdd/digest.js.map +0 -1
- package/dist/kernel/tdd/evaluator.d.ts +0 -38
- package/dist/kernel/tdd/evaluator.js +0 -185
- package/dist/kernel/tdd/evaluator.js.map +0 -1
- package/dist/kernel/tdd/evidence.d.ts +0 -42
- package/dist/kernel/tdd/evidence.js +0 -48
- package/dist/kernel/tdd/evidence.js.map +0 -1
- package/dist/kernel/tdd/state.d.ts +0 -79
- package/dist/kernel/tdd/state.js +0 -363
- package/dist/kernel/tdd/state.js.map +0 -1
- package/dist/kernel/types.d.ts +0 -180
- package/dist/kernel/types.js +0 -2
- package/dist/kernel/types.js.map +0 -1
- package/dist/kernel/worktree.d.ts +0 -45
- package/dist/kernel/worktree.js +0 -156
- package/dist/kernel/worktree.js.map +0 -1
- package/dist/plugin/flow-control.d.ts +0 -91
- package/dist/plugin/flow-control.js +0 -287
- package/dist/plugin/flow-control.js.map +0 -1
- package/dist/plugin/release-control.d.ts +0 -39
- package/dist/plugin/release-control.js +0 -343
- package/dist/plugin/release-control.js.map +0 -1
- package/dist/plugin/setup-control.d.ts +0 -59
- package/dist/plugin/setup-control.js +0 -111
- package/dist/plugin/setup-control.js.map +0 -1
- package/dist/plugin/task-control.d.ts +0 -90
- package/dist/plugin/task-control.js +0 -208
- package/dist/plugin/task-control.js.map +0 -1
- package/dist/plugin/tdd-checkpoint.d.ts +0 -128
- package/dist/plugin/tdd-checkpoint.js +0 -664
- package/dist/plugin/tdd-checkpoint.js.map +0 -1
- package/dist/util/shell.d.ts +0 -1
- package/dist/util/shell.js +0 -4
- package/dist/util/shell.js.map +0 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-tasks
|
|
3
|
+
description: 将技术方案拆解为可独立实现的 tracer-bullet Task Plan 与 DAG
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# flow-tasks
|
|
7
|
+
|
|
8
|
+
将设计方案拆解为 Task Plan 与 DAG(有向无环图)。每个 Task 都是一条完整但狭窄的行为切片,可以交给一个 fresh-context developer agent 独立实现和验证。
|
|
9
|
+
|
|
10
|
+
本 skill 只定义任务拆解与 Task Plan 产出。`@agent-architect` 不创建 GitHub Issue、不操作 GitHub lifecycle;调用方负责检查并发布 Task Plan。
|
|
11
|
+
|
|
12
|
+
## Task 拆解规则
|
|
13
|
+
|
|
14
|
+
每个 Task 必须:
|
|
15
|
+
|
|
16
|
+
1. 交付一个可以观察或验证的新行为。
|
|
17
|
+
2. 是垂直切片,而不是 Controller / Service / DAO 等技术分层。
|
|
18
|
+
3. 可以交给一个 fresh-context developer agent 独立理解和实现。
|
|
19
|
+
4. 完成以后仓库处于可工作的状态,而不是为下一个 Task 留空壳。
|
|
20
|
+
5. `Blocked By` 只声明“前者未完成时,本 Task 无法开始”的真实阻塞关系。
|
|
21
|
+
|
|
22
|
+
每个 Task 应适合一个 fresh-context developer agent:
|
|
23
|
+
|
|
24
|
+
- 不需要读取整个 Flow 历史才能理解。
|
|
25
|
+
- Task、Design 与 repository context 足够开始工作。
|
|
26
|
+
- 正常情况下不需要再次进行架构设计。
|
|
27
|
+
- 可以在一次独立开发循环中实现、测试、提交。
|
|
28
|
+
|
|
29
|
+
## 何时拆分
|
|
30
|
+
|
|
31
|
+
不要为了制造并行度而拆 Task。
|
|
32
|
+
|
|
33
|
+
如果一个行为高度内聚、修改范围集中、一个 fresh context 可以完成,且没有独立可验证的中间行为,则保持为一个 Task。
|
|
34
|
+
|
|
35
|
+
如果一个 Task 同时包含三个以上相互独立的“用户可以……”行为,应考虑按这些可独立验证的行为拆分。
|
|
36
|
+
|
|
37
|
+
拆分前检查:
|
|
38
|
+
|
|
39
|
+
1. **能否用一个 Task 完成?** — 足够内聚时不强拆。
|
|
40
|
+
2. **拆开后能否独立验证?** — 每个 Task 都必须交付可观察行为。
|
|
41
|
+
3. **是否真的阻塞?** — 只有无法开始的前置关系才进入 DAG。
|
|
42
|
+
4. **拆开后耦合是否更低?** — 大量共享同一数据结构或模块时优先合并。
|
|
43
|
+
|
|
44
|
+
## Behavior-preserving Pre-refactor
|
|
45
|
+
|
|
46
|
+
行为 Task 的唯一例外是必要的 behavior-preserving pre-refactor:当现有结构使后续行为无法在一个可测试、可工作的垂直切片中安全实现时,可以先建立一个重构 Task。
|
|
47
|
+
|
|
48
|
+
Pre-refactor 必须同时满足:
|
|
49
|
+
|
|
50
|
+
- 不改变现有可观察行为,并用回归验证证明行为保持。
|
|
51
|
+
- 自身完成后仓库保持可工作,不留下迁移一半的状态。
|
|
52
|
+
- 只消除阻碍后续行为的具体结构问题,不搭通用框架、不预留未来扩展点。
|
|
53
|
+
- 后续行为确实无法安全开始时,才将 pre-refactor 声明为 blocking edge。
|
|
54
|
+
|
|
55
|
+
仅仅“这样更整洁”或“以后可能复用”不构成 pre-refactor。优先直接交付行为切片。
|
|
56
|
+
|
|
57
|
+
Pre-refactor 的 `Builds` 应说明“保持哪个现有行为不变,并消除哪个阻碍后续行为的具体结构问题”,不能写成文件或类的修改清单。
|
|
58
|
+
|
|
59
|
+
## 反模式
|
|
60
|
+
|
|
61
|
+
| 反模式 | 示例 | 正确做法 |
|
|
62
|
+
|--------|------|----------|
|
|
63
|
+
| 技术分层拆分 | `create-session-table` → `implement-session-service` → `implement-session-api` | `create-and-restore-session` |
|
|
64
|
+
| 测试单独成 Task | `add-session-tests` | 测试随行为切片一起交付 |
|
|
65
|
+
| 过度拆分 | 一个内聚 CRUD 拆成多个 Task | 保持为一个可独立验证的 Task |
|
|
66
|
+
| 预留式拆分 | 先搭框架,后续 Task 再填内容 | 每个 Task 完成后仓库都可工作 |
|
|
67
|
+
| 虚假依赖 | 因修改相邻文件而声明依赖 | 仅声明不完成前者就无法开始的依赖 |
|
|
68
|
+
|
|
69
|
+
## Task 定义
|
|
70
|
+
|
|
71
|
+
每个 Task 保存为 `docs/dev/tasks/<task-name>.md`,是 developer 使用的 **Agent Implementation Contract**:
|
|
72
|
+
|
|
73
|
+
`task-name` 使用简短的英文 kebab-case 行为短语,并作为后续 `feat/<task-name>` 分支名的基准。
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
---
|
|
77
|
+
issue: null
|
|
78
|
+
test_commands:
|
|
79
|
+
- <targeted test command>
|
|
80
|
+
verify_commands:
|
|
81
|
+
- <verification command>
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
# <task-slug>
|
|
85
|
+
|
|
86
|
+
## Builds
|
|
87
|
+
|
|
88
|
+
<这个 Task 完成后,哪个端到端行为真正可以工作?>
|
|
89
|
+
|
|
90
|
+
## Acceptance Criteria
|
|
91
|
+
|
|
92
|
+
- [ ] <可观察行为>
|
|
93
|
+
- [ ] <边界或异常行为>
|
|
94
|
+
- [ ] <回归要求>
|
|
95
|
+
|
|
96
|
+
## Blocked By
|
|
97
|
+
|
|
98
|
+
- <blocking-task-slug>
|
|
99
|
+
- 或 None
|
|
100
|
+
|
|
101
|
+
## Implementation Notes
|
|
102
|
+
|
|
103
|
+
<只记录真正限制实现方式的重要设计决策;不要复制具体实现步骤。>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`Builds` 必须直接说明完成后的可工作行为。`Acceptance Criteria` 是 `flow-tdd` 使用的行为规格;`test_commands` 与 `verify_commands` 只放在 Task 文件中。
|
|
107
|
+
|
|
108
|
+
Task 的行为测试必须沿用 Design `Testing Decisions` 中约定的 Test Seam。Task 不重新设计 Seam;如果既有 Seam 无法验证该行为,应返回 Design Gap,由调用方在当前 stage 完成 amendment。
|
|
109
|
+
|
|
110
|
+
### Design Gap
|
|
111
|
+
|
|
112
|
+
存量 Design 缺少 `Testing Decisions` 时,不回退 lifecycle stage,也不生成半成品 Task Plan。返回给调用方:
|
|
113
|
+
|
|
114
|
+
```markdown
|
|
115
|
+
## Design Gap
|
|
116
|
+
|
|
117
|
+
- Current Stage: tasks
|
|
118
|
+
- Design File: <docs/dev/specs/...>
|
|
119
|
+
- Missing Behaviors:
|
|
120
|
+
- <缺少 Test Seam 或 Observable Result 的行为>
|
|
121
|
+
- Existing Context: <已发现的公共 Interface 与测试惯例;没有则写 None>
|
|
122
|
+
- Required Amendment: 为以上行为补充 Testing Decisions
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
调用方在当前 tasks 阶段完成 Design Amendment 后,重新执行 Task Planning。`@agent-architect` 不在 flow-tasks 内自行修改测试设计。
|
|
126
|
+
|
|
127
|
+
规划阶段尚无 Issue 编号,`Blocked By` 使用 task slug。发布后由调用方将其更新为真实 `#<issue-number>`;无依赖时写 `None`。
|
|
128
|
+
|
|
129
|
+
## DAG
|
|
130
|
+
|
|
131
|
+
DAG 使用 Mermaid 语法,保存为 `docs/dev/tasks/<YYYY-MM-DD-NNN-slug>/DAG.md`。
|
|
132
|
+
|
|
133
|
+
- 节点对应行为 Task,不对应技术层。
|
|
134
|
+
- 边只表示真实 blocking edge。
|
|
135
|
+
- 发布前必须检查环形依赖。
|
|
136
|
+
- 发布后将 Task 节点与依赖更新为真实 Issue 编号。
|
|
137
|
+
|
|
138
|
+
## GitHub 发布边界
|
|
139
|
+
|
|
140
|
+
GitHub Sub Issue 是协作与生命周期记录,正文只提取 Task 中用户可读的内容:
|
|
141
|
+
|
|
142
|
+
```markdown
|
|
143
|
+
## Builds
|
|
144
|
+
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
## Acceptance Criteria
|
|
148
|
+
|
|
149
|
+
- [ ] ...
|
|
150
|
+
|
|
151
|
+
## Blocked By
|
|
152
|
+
|
|
153
|
+
None
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
不得把 `issue`、`test_commands`、`verify_commands` 或内部实现说明复制到 GitHub Issue。
|
|
157
|
+
|
|
158
|
+
发布由调用方执行:
|
|
159
|
+
|
|
160
|
+
- 普通 Flow:`@agent-dev-lifecycle` 检查后自动发布。
|
|
161
|
+
- 高风险 Flow:展示 Task Plan,用户确认后发布。
|
|
162
|
+
- 用户主动 `/tasks`:展示 Task Plan,等待用户确认后发布。
|
|
163
|
+
- `@agent-architect`:产出并返回 Task Plan 后停止,不执行发布。
|
|
164
|
+
|
|
165
|
+
## Output
|
|
166
|
+
|
|
167
|
+
- `docs/dev/tasks/*.md` — Agent Implementation Contracts
|
|
168
|
+
- `docs/dev/tasks/<YYYY-MM-DD-NNN-slug>/DAG.md` — Task DAG
|
|
169
|
+
- 返回给调用方的 Task Plan 摘要与拓扑顺序
|
|
170
|
+
|
|
171
|
+
## 后续
|
|
172
|
+
|
|
173
|
+
- `@agent-dev-lifecycle` 检查并发布 GitHub Sub Issues
|
|
174
|
+
- **/code** — 按 Task DAG 进入编码阶段
|
|
175
|
+
|
|
176
|
+
## Contract
|
|
177
|
+
|
|
178
|
+
### Trigger
|
|
179
|
+
由 `/tasks` 命令或 `@agent-dev-lifecycle` Phase 2 委派 `@agent-architect` 触发。
|
|
180
|
+
|
|
181
|
+
### Inputs
|
|
182
|
+
- `docs/dev/specs/<title>.md` — 技术方案
|
|
183
|
+
- 相关 PRD 与 ADR
|
|
184
|
+
- Parent Issue 编号
|
|
185
|
+
|
|
186
|
+
### Preconditions
|
|
187
|
+
- `/design` 已完成,技术方案和 ADR 存在
|
|
188
|
+
|
|
189
|
+
### Procedure
|
|
190
|
+
1. 读取设计基线与必要的 repository context。
|
|
191
|
+
2. 读取 Design `Testing Decisions`,确认关键行为已有公共 Test Seam;缺失时返回 Design Gap 并停止,不生成或更新 Task 文件。
|
|
192
|
+
3. 识别可独立验证的 tracer-bullet 行为切片;仅在必要时增加 behavior-preserving pre-refactor。
|
|
193
|
+
4. 为每个 Task 定义 `Builds`、`Acceptance Criteria`、`Blocked By` 与必要的 `Implementation Notes`。
|
|
194
|
+
5. 生成 Task 文件与 Mermaid DAG。
|
|
195
|
+
6. 检查技术分层式拆分、空壳 Task、虚假依赖、臆测性 pre-refactor 与环形依赖。
|
|
196
|
+
7. 返回 Task Plan、文件路径与拓扑顺序;不创建 GitHub Issue。
|
|
197
|
+
|
|
198
|
+
### Outputs
|
|
199
|
+
- Task 定义文件
|
|
200
|
+
- Task DAG
|
|
201
|
+
- 供调用方检查与发布的 Task Plan
|
|
202
|
+
- 或结构化 Design Gap(存量 Design 缺少 Testing Decisions 时)
|
|
203
|
+
|
|
204
|
+
### Failure
|
|
205
|
+
- 无法从设计中确定可验证行为 → 暂停并指出缺失信息
|
|
206
|
+
- Design Amendment 后仍缺少稳定 Test Seam → 报告未解决的 Design Gap,由调用方暂停当前阶段
|
|
207
|
+
- DAG 存在环形依赖 → 阻止并报告
|
|
208
|
+
- Task 无法由 fresh-context developer 独立理解 → 重新拆分或补足契约
|
|
209
|
+
|
|
210
|
+
### Idempotency
|
|
211
|
+
- Task 文件已存在 → 读取并更新,不创建重复定义
|
|
212
|
+
- 已有 `issue` 编号 → 保留并交给调用方核验,不重复发布
|
|
213
|
+
|
|
214
|
+
### Prohibited Actions
|
|
215
|
+
- 不按技术层拆分 Task
|
|
216
|
+
- 不为制造并行度而拆分 Task
|
|
217
|
+
- 不创建完成后不可工作的空壳 Task
|
|
218
|
+
- 不用 pre-refactor 包装搭框架、清理代码或预留扩展点
|
|
219
|
+
- 不把非阻塞关系写入 `Blocked By`
|
|
220
|
+
- `@agent-architect` 不执行 `gh issue create`、`git push`、PR、merge 或 worktree 操作
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-tdd
|
|
3
|
+
description: 公共 Test Seam 上的行为 TDD — RED→GREEN 状态机 + self-report 证据
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# flow-tdd
|
|
7
|
+
|
|
8
|
+
TDD(Test-Driven Development)Prompt 协议,为所有编码阶段提供统一的 TDD 流程约束。
|
|
9
|
+
本 skill 是 TDD 协议的唯一来源,其他 skills 和 agents 通过引用本协议获得一致的 TDD 行为。
|
|
10
|
+
|
|
11
|
+
## 核心原则
|
|
12
|
+
|
|
13
|
+
测试是行为规格:通过 Design `Testing Decisions` 约定的公共 Test Seam 验证可观察行为,而不是验证内部实现。只要公共行为不变,重构 Module 内部实现不应导致测试失败。
|
|
14
|
+
|
|
15
|
+
每个 TDD cycle 是一个最窄行为切片:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
确定 Test Seam 与一个行为
|
|
19
|
+
↓
|
|
20
|
+
写一个行为测试
|
|
21
|
+
↓
|
|
22
|
+
RED
|
|
23
|
+
↓
|
|
24
|
+
最小实现
|
|
25
|
+
↓
|
|
26
|
+
GREEN
|
|
27
|
+
↓
|
|
28
|
+
下一个行为
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 好测试
|
|
32
|
+
|
|
33
|
+
- 名称描述调用者或用户可以完成什么,而不是调用了哪个内部方法。
|
|
34
|
+
- 只通过约定的公共 Test Seam 触发并观察行为。
|
|
35
|
+
- 期望值来自 Acceptance Criteria、已知正确示例或其他独立来源。
|
|
36
|
+
- 实现重构但行为不变时仍然通过。
|
|
37
|
+
|
|
38
|
+
### 反模式
|
|
39
|
+
|
|
40
|
+
- **实现耦合** — 测试私有方法、内部协作对象、调用次数或调用顺序。
|
|
41
|
+
- **侧信道验证** — 绕过公共 Seam 读取数据库或内部状态来证明行为。
|
|
42
|
+
- **循环论证** — 用与实现相同的算法重新计算期望值。
|
|
43
|
+
- **水平批量** — 先写完全部测试再实现;应按一个行为测试 → 一个最小实现逐次推进。
|
|
44
|
+
- **为 mock 造抽象** — 仅允许在真实系统边界按既有 Interface 替换外部依赖,不为测试方便新增生产 Seam。
|
|
45
|
+
|
|
46
|
+
## Advisory Procedure
|
|
47
|
+
|
|
48
|
+
Agent 自行遵循 TDD 流程并 self-report 状态。测试质量由仓库 CI 把关,内核不强制证据。
|
|
49
|
+
|
|
50
|
+
### Cycle 状态机
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
┌─────────────┐
|
|
54
|
+
│ cycle-start │ ← 开始新的 TDD 循环
|
|
55
|
+
└──────┬──────┘
|
|
56
|
+
│
|
|
57
|
+
▼
|
|
58
|
+
┌─────────────┐
|
|
59
|
+
│ red │ ← 编写/更新测试,验证测试失败(RED)
|
|
60
|
+
└──────┬──────┘
|
|
61
|
+
│
|
|
62
|
+
▼
|
|
63
|
+
┌─────────────┐
|
|
64
|
+
│ green │ ← 最小实现使测试通过(GREEN),仅做局部行为保持整理
|
|
65
|
+
└──────┬──────┘
|
|
66
|
+
│
|
|
67
|
+
┌──────────┼──────────┐
|
|
68
|
+
▼ ▼ ▼
|
|
69
|
+
┌─────────┐ ┌─────────┐ ┌──────────────┐
|
|
70
|
+
│ 下一个 │ │ abandon │ │ 所有 cycle │
|
|
71
|
+
│ cycle │ │ -cycle │ │ 完成 │
|
|
72
|
+
└─────────┘ └─────────┘ └──────┬───────┘
|
|
73
|
+
│
|
|
74
|
+
▼
|
|
75
|
+
┌──────────────────┐
|
|
76
|
+
│ final-regression │ ← 运行全部测试,确认无回归
|
|
77
|
+
└────────┬─────────┘
|
|
78
|
+
│
|
|
79
|
+
▼
|
|
80
|
+
┌───────────────────┐
|
|
81
|
+
│ final-verification│ ← 对照 Acceptance Criteria 逐条验证
|
|
82
|
+
└───────────────────┘
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### cycle-start
|
|
86
|
+
|
|
87
|
+
开始一个新的 RED→GREEN 循环。Agent 在实现前从 Design `Testing Decisions` 读取 Test Seam,并声明本 cycle 的一个可观察行为。
|
|
88
|
+
|
|
89
|
+
**self-report 格式:**
|
|
90
|
+
```
|
|
91
|
+
## TDD cycle-start
|
|
92
|
+
- cycle: <序号>
|
|
93
|
+
- behavior: <本 cycle 要交付的可观察行为>
|
|
94
|
+
- test_seam: <约定的公共 Test Seam>
|
|
95
|
+
- test: <通过该 Seam 验证行为的测试名称>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### red
|
|
99
|
+
|
|
100
|
+
编写或更新测试用例,验证测试在当前实现下失败。
|
|
101
|
+
|
|
102
|
+
**约束:**
|
|
103
|
+
- 测试必须有明确的 fail/pass 边界
|
|
104
|
+
- 测试名称描述行为,且只通过 cycle-start 声明的 Test Seam 触发和观察结果
|
|
105
|
+
- RED 必须因目标行为尚未实现而失败,不能把环境、语法或夹具错误当作证据
|
|
106
|
+
- 测试运行命令必须与 Task 的 `test_commands` 中定义的一致
|
|
107
|
+
- 记录测试失败输出作为 RED evidence
|
|
108
|
+
|
|
109
|
+
**self-report 格式:**
|
|
110
|
+
```
|
|
111
|
+
## TDD red
|
|
112
|
+
- test: <测试名称>
|
|
113
|
+
- command: <运行命令>
|
|
114
|
+
- result: FAIL(预期)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### green
|
|
118
|
+
|
|
119
|
+
编写最小实现使 RED 阶段的行为测试通过。可进行局部、行为保持的整理,但不得改变约定的 Test Seam 或扩展范围。
|
|
120
|
+
|
|
121
|
+
**约束:**
|
|
122
|
+
- 只写使当前测试通过的最少代码
|
|
123
|
+
- 不新增与当前 test 无关的功能
|
|
124
|
+
- 重构仅限于当前 cycle 涉及的代码
|
|
125
|
+
- 不为通过测试而暴露内部状态、增加仅供测试使用的生产接口或改成实现细节断言
|
|
126
|
+
|
|
127
|
+
**self-report 格式:**
|
|
128
|
+
```
|
|
129
|
+
## TDD green
|
|
130
|
+
- test: <测试名称>
|
|
131
|
+
- command: <运行命令>
|
|
132
|
+
- result: PASS
|
|
133
|
+
- changes: <涉及的文件列表>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### abandon-cycle
|
|
137
|
+
|
|
138
|
+
当发现当前 cycle 的目标不合理、设计有缺陷或需要回退时,废弃当前 cycle 的所有修改。
|
|
139
|
+
|
|
140
|
+
**触发条件:**
|
|
141
|
+
- RED 阶段发现测试设计有误
|
|
142
|
+
- GREEN 阶段发现需要根本性重新设计
|
|
143
|
+
- 发现更简单的替代方案
|
|
144
|
+
|
|
145
|
+
**流程:**
|
|
146
|
+
1. 放弃当前 cycle 的修改(`git checkout -- <files>` 或 `git stash drop`)
|
|
147
|
+
2. 记录 abandon 原因
|
|
148
|
+
3. 重新从 cycle-start 开始
|
|
149
|
+
|
|
150
|
+
**self-report 格式:**
|
|
151
|
+
```
|
|
152
|
+
## TDD abandon-cycle
|
|
153
|
+
- cycle: <序号>
|
|
154
|
+
- reason: <废弃原因>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### final-regression
|
|
158
|
+
|
|
159
|
+
所有 cycle 完成后,运行项目全部测试套件,确认无回归。
|
|
160
|
+
|
|
161
|
+
**约束:**
|
|
162
|
+
- 必须运行 Task `test_commands` 中定义的全部命令
|
|
163
|
+
- 所有测试必须通过
|
|
164
|
+
- 如有失败 → 修复(不要求新 cycle,但需记录修复内容)
|
|
165
|
+
|
|
166
|
+
**self-report 格式:**
|
|
167
|
+
```
|
|
168
|
+
## TDD final-regression
|
|
169
|
+
- passed: <通过的测试数>
|
|
170
|
+
- failed: <失败的测试数>
|
|
171
|
+
- total: <测试总数>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### final-verification
|
|
175
|
+
|
|
176
|
+
对照 Task 的 `Acceptance Criteria` 逐条验证,确认每项都由公共 Test Seam 上的行为测试或 `verify_commands` 证明。
|
|
177
|
+
|
|
178
|
+
**self-report 格式:**
|
|
179
|
+
```
|
|
180
|
+
## TDD final-verification
|
|
181
|
+
- criteria_total: <总数>
|
|
182
|
+
- criteria_met: <已满足>
|
|
183
|
+
- criteria_pending: <未满足>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Contract
|
|
187
|
+
|
|
188
|
+
### Trigger
|
|
189
|
+
由编码阶段(`flow-code`)自动触发。Agent 在开始编码任务时加载 `flow-tdd` skill 获取 TDD 流程约束。
|
|
190
|
+
|
|
191
|
+
### Inputs
|
|
192
|
+
- Task 文件中的 `Builds` 与 `Acceptance Criteria`(来源:`flow-tasks`)
|
|
193
|
+
- Task frontmatter 中的 `test_commands` 与 `verify_commands`
|
|
194
|
+
- Design `Testing Decisions` 中约定的 Test Seam 与 Observable Result
|
|
195
|
+
|
|
196
|
+
### Preconditions
|
|
197
|
+
- Task 文件存在,包含 `Builds`、`Acceptance Criteria`、`test_commands`
|
|
198
|
+
- Design 已为目标行为定义公共 Test Seam;存量 Design 缺失时先向 primary 返回 Design Gap,由兼容门在当前 stage 补齐
|
|
199
|
+
- 测试运行环境就绪(worktree 内依赖已安装)
|
|
200
|
+
|
|
201
|
+
### Procedure
|
|
202
|
+
1. 读取 Task 的 `Builds`、`Acceptance Criteria`、`test_commands`、`verify_commands`。
|
|
203
|
+
2. 读取 Design `Testing Decisions`,确认目标行为的 Test Seam 与 Observable Result。
|
|
204
|
+
3. 选择一个尚未满足的行为,执行 `cycle-start` 声明 behavior、test_seam 与 test。
|
|
205
|
+
4. 执行 `red` → 只写该行为测试,验证因行为缺失而失败,记录 self-report。
|
|
206
|
+
5. 执行 `green` → 编写最小实现,验证通过,记录 self-report。
|
|
207
|
+
6. 按一个行为切片一个 cycle 重复,直到所有 Acceptance Criteria 被覆盖。
|
|
208
|
+
7. 执行 `final-regression` → 运行全部测试。
|
|
209
|
+
8. 执行 `final-verification` → 通过 Test Seam 或 `verify_commands` 逐条验证 Acceptance Criteria。
|
|
210
|
+
|
|
211
|
+
### Outputs
|
|
212
|
+
- 每个 cycle 的 self-report
|
|
213
|
+
- `final-regression` 报告(测试通过/失败统计)
|
|
214
|
+
- `final-verification` 报告(criterion 覆盖情况)
|
|
215
|
+
|
|
216
|
+
### Failure
|
|
217
|
+
- RED 阶段测试意外通过 → 检查测试是否有意义,可能需要 abandon-cycle
|
|
218
|
+
- RED 因环境、语法或夹具错误失败 → 修复测试基础设施,不能计为 RED evidence
|
|
219
|
+
- 只能通过内部实现观察目标行为 → 停止编码并向 primary 返回 Design Gap,不自行修改 Design、不回退 lifecycle stage
|
|
220
|
+
- GREEN 阶段测试持续失败 → 检查实现逻辑,记录失败原因
|
|
221
|
+
- final-regression 失败 → 修复回归,不要求新 cycle
|
|
222
|
+
- final-verification 未全部满足 → 返回未完成的 criterion,补充 cycle
|
|
223
|
+
|
|
224
|
+
### Idempotency
|
|
225
|
+
- 同一 cycle 重复执行 → 以最后一次结果为准
|
|
226
|
+
- final-regression 重复执行 → 覆盖上次结果
|
|
227
|
+
|
|
228
|
+
### Prohibited Actions
|
|
229
|
+
- 不跳过 RED 阶段直接进入 GREEN
|
|
230
|
+
- 不测试私有方法、内部调用次数、内部调用顺序或未约定的侧信道
|
|
231
|
+
- 不一次批量编写多个行为测试后再统一实现
|
|
232
|
+
- 不为了 mock 或测试方便新增生产抽象
|
|
233
|
+
- 不跳过 final-regression 直接 commit
|
|
234
|
+
- 不在 abandon-cycle 后保留修改
|
|
235
|
+
- 不伪造测试结果(实际运行命令后再 self-report)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cabbage Session Start Hook — loads plugin overview and project context.
|
|
3
|
+
* Runs on Codex SessionStart lifecycle event (matcher: startup|resume).
|
|
4
|
+
* Compiled to dist/hooks/session-start.js by tsc.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
// PLUGIN_ROOT is set by Codex hooks runtime
|
|
9
|
+
const PROJECT_DIR = process.cwd();
|
|
10
|
+
const MAX_OUTPUT_LENGTH = 12000;
|
|
11
|
+
function getHeader() {
|
|
12
|
+
return `## Cabbage Development Plugin
|
|
13
|
+
|
|
14
|
+
This plugin provides a full development lifecycle orchestration system.
|
|
15
|
+
Load skills by name: @agent-dev-lifecycle, @agent-architect, @agent-developer, @agent-reviewer, @agent-goal-verify, @agent-researcher
|
|
16
|
+
|
|
17
|
+
### Available Flow Skills
|
|
18
|
+
- \`@flow-setup\` — 初始化项目开发环境
|
|
19
|
+
- \`@flow-requirements\` — 需求分析产出 PRD
|
|
20
|
+
- \`@flow-design\` — 技术方案与 ADR
|
|
21
|
+
- \`@flow-tasks\` — DAG 任务拆解
|
|
22
|
+
- \`@flow-research\` — 调研/事实核查
|
|
23
|
+
- \`@flow-code\` — 编码实现(TDD)
|
|
24
|
+
- \`@flow-tdd\` — TDD 协议参考
|
|
25
|
+
- \`@flow-review\` — 代码审查
|
|
26
|
+
- \`@flow-release\` — 发布流程
|
|
27
|
+
|
|
28
|
+
### Available Agent Skills
|
|
29
|
+
- \`@agent-dev-lifecycle\` — 全流程编排器(主 agent,场景分诊)
|
|
30
|
+
- \`@agent-architect\` — 架构设计
|
|
31
|
+
- \`@agent-developer\` — 编码实现
|
|
32
|
+
- \`@agent-reviewer\` — 代码审查
|
|
33
|
+
- \`@agent-goal-verify\` — 目标验证
|
|
34
|
+
- \`@agent-researcher\` — 独立调研
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
function readProjectProfile() {
|
|
38
|
+
// Try root AGENTS.md from project directory
|
|
39
|
+
const agentsMd = join(PROJECT_DIR, "AGENTS.md");
|
|
40
|
+
if (!existsSync(agentsMd))
|
|
41
|
+
return "";
|
|
42
|
+
let content;
|
|
43
|
+
try {
|
|
44
|
+
content = readFileSync(agentsMd, "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
// Match only the exact "## Project Profile" heading line (not "## Project Profile Notes"),
|
|
50
|
+
// and stop at the next "## " heading or end of file.
|
|
51
|
+
const lines = content.split("\n");
|
|
52
|
+
const start = lines.findIndex((line) => line.trim() === "## Project Profile");
|
|
53
|
+
if (start === -1)
|
|
54
|
+
return "";
|
|
55
|
+
const end = lines.findIndex((line, i) => i > start && /^##\s/.test(line.trim()));
|
|
56
|
+
const slice = end === -1 ? lines.slice(start) : lines.slice(start, end);
|
|
57
|
+
return slice.join("\n").trim();
|
|
58
|
+
}
|
|
59
|
+
function main() {
|
|
60
|
+
const header = getHeader();
|
|
61
|
+
const profile = readProjectProfile();
|
|
62
|
+
const parts = [header];
|
|
63
|
+
if (profile)
|
|
64
|
+
parts.push(`## Project Context\n\n${profile}`);
|
|
65
|
+
let output = parts.join("\n\n");
|
|
66
|
+
if (output.length > MAX_OUTPUT_LENGTH) {
|
|
67
|
+
output = `${output.slice(0, MAX_OUTPUT_LENGTH)}\n\n[context truncated — output exceeds ${MAX_OUTPUT_LENGTH} chars]`;
|
|
68
|
+
}
|
|
69
|
+
console.log(output);
|
|
70
|
+
}
|
|
71
|
+
main();
|
|
72
|
+
//# sourceMappingURL=session-start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,4CAA4C;AAC5C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;AACjC,MAAM,iBAAiB,GAAG,KAAK,CAAA;AAE/B,SAAS,SAAS;IAChB,OAAO;;;;;;;;;;;;;;;;;;;;;;;CAuBR,CAAA;AACD,CAAC;AAED,SAAS,kBAAkB;IACzB,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC/C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAA;IAEpC,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;IAED,2FAA2F;IAC3F,qDAAqD;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,oBAAoB,CAAC,CAAA;IAC7E,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAE3B,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAChF,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACvE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AAChC,CAAC;AAED,SAAS,IAAI;IACX,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IAEpC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,IAAI,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAA;IAE3D,IAAI,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,2CAA2C,iBAAiB,SAAS,CAAA;IACrH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC;AAED,IAAI,EAAE,CAAA"}
|
|
@@ -5,6 +5,8 @@ export interface FlowSessionEntry {
|
|
|
5
5
|
continuationCount: number;
|
|
6
6
|
/** goal-verify 已完成验证(goal complete)——flow 级持久化,不依赖会话一致性 */
|
|
7
7
|
goalComplete?: boolean;
|
|
8
|
+
/** 最近一次实际发送续接的时刻(autoResume 重启双发防护用;区别于 updatedAt 心跳) */
|
|
9
|
+
lastContinuedAt?: number;
|
|
8
10
|
updatedAt: number;
|
|
9
11
|
}
|
|
10
12
|
export interface SessionIndex {
|
|
@@ -15,31 +17,12 @@ export declare function sessionIndexPath(projectDir: string): string;
|
|
|
15
17
|
export declare function readIndex(projectDir: string): Promise<SessionIndex>;
|
|
16
18
|
/** 原子写:先写临时文件(PID 后缀防跨进程 tmp 冲突)再 rename。 */
|
|
17
19
|
export declare function writeIndex(projectDir: string, index: SessionIndex): Promise<void>;
|
|
18
|
-
export declare function getFlowSession(index: SessionIndex, parentIssueNumber: number): FlowSessionEntry | null;
|
|
19
|
-
export type BindSessionResult = {
|
|
20
|
-
ok: true;
|
|
21
|
-
entry: FlowSessionEntry;
|
|
22
|
-
} | {
|
|
23
|
-
ok: false;
|
|
24
|
-
code: "FLOW_SESSION_CONFLICT";
|
|
25
|
-
message: string;
|
|
26
|
-
} | {
|
|
27
|
-
ok: false;
|
|
28
|
-
code: "TAKEOVER_NOT_CONFIRMED";
|
|
29
|
-
message: string;
|
|
30
|
-
};
|
|
31
20
|
/**
|
|
32
|
-
* 绑定 parentIssueNumber → sessionID
|
|
33
|
-
*
|
|
21
|
+
* 绑定 parentIssueNumber → sessionID(goal create 时建立,供插件重启后的 autoResume 恢复)。
|
|
22
|
+
* 已绑定其它 session 时更新绑定(覆盖式,无双 session 强制)。
|
|
34
23
|
*/
|
|
35
|
-
export declare function bindSession(projectDir: string, parentIssueNumber: number, sessionID: string): Promise<
|
|
36
|
-
/**
|
|
37
|
-
* 接管绑定:旧 session 置 paused,新 session 绑定 active(R7)。
|
|
38
|
-
* 旧 session 为另一 active session 时要求 userConfirmed(§10.3);
|
|
39
|
-
* 调用方还需将旧 session 的 goal status 置 paused(goalClient 层)。
|
|
40
|
-
*/
|
|
41
|
-
export declare function takeoverSession(projectDir: string, parentIssueNumber: number, newSessionID: string, userConfirmed: boolean): Promise<BindSessionResult>;
|
|
24
|
+
export declare function bindSession(projectDir: string, parentIssueNumber: number, sessionID: string): Promise<void>;
|
|
42
25
|
/** 更新已绑定 flow 的状态/计数/goal 完成标记;flow 未绑定时返回 null 且不创建 entry。 */
|
|
43
|
-
export declare function updateFlowSession(projectDir: string, parentIssueNumber: number, patch: Partial<Pick<FlowSessionEntry, "status" | "continuationCount" | "goalComplete">>): Promise<FlowSessionEntry | null>;
|
|
44
|
-
/**
|
|
45
|
-
export declare function
|
|
26
|
+
export declare function updateFlowSession(projectDir: string, parentIssueNumber: number, patch: Partial<Pick<FlowSessionEntry, "status" | "continuationCount" | "goalComplete" | "lastContinuedAt">>): Promise<FlowSessionEntry | null>;
|
|
27
|
+
/** 删除 flow 的索引 entry(goal cancel 时调用,避免重启 autoResume 恢复已取消 goal)。 */
|
|
28
|
+
export declare function removeFlowSession(projectDir: string, parentIssueNumber: number): Promise<boolean>;
|
|
@@ -35,25 +35,15 @@ export async function writeIndex(projectDir, index) {
|
|
|
35
35
|
await writeFile(tmp, JSON.stringify(index, null, 2), "utf8");
|
|
36
36
|
await rename(tmp, target);
|
|
37
37
|
}
|
|
38
|
-
export function getFlowSession(index, parentIssueNumber) {
|
|
39
|
-
return index.flows[String(parentIssueNumber)] ?? null;
|
|
40
|
-
}
|
|
41
38
|
/**
|
|
42
|
-
* 绑定 parentIssueNumber → sessionID
|
|
43
|
-
*
|
|
39
|
+
* 绑定 parentIssueNumber → sessionID(goal create 时建立,供插件重启后的 autoResume 恢复)。
|
|
40
|
+
* 已绑定其它 session 时更新绑定(覆盖式,无双 session 强制)。
|
|
44
41
|
*/
|
|
45
42
|
export async function bindSession(projectDir, parentIssueNumber, sessionID) {
|
|
46
43
|
return indexMutex.runExclusive(projectDir, async () => {
|
|
47
44
|
const index = await readIndex(projectDir);
|
|
48
45
|
const key = String(parentIssueNumber);
|
|
49
46
|
const existing = index.flows[key];
|
|
50
|
-
if (existing && existing.sessionID !== sessionID && existing.status === "active") {
|
|
51
|
-
return {
|
|
52
|
-
ok: false,
|
|
53
|
-
code: "FLOW_SESSION_CONFLICT",
|
|
54
|
-
message: `Flow #${parentIssueNumber} is already bound to active session "${existing.sessionID}". Use flow_control takeover with user confirmation to take over.`,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
47
|
const entry = {
|
|
58
48
|
sessionID,
|
|
59
49
|
status: "active",
|
|
@@ -62,45 +52,6 @@ export async function bindSession(projectDir, parentIssueNumber, sessionID) {
|
|
|
62
52
|
};
|
|
63
53
|
index.flows[key] = entry;
|
|
64
54
|
await writeIndex(projectDir, index);
|
|
65
|
-
return { ok: true, entry };
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* 接管绑定:旧 session 置 paused,新 session 绑定 active(R7)。
|
|
70
|
-
* 旧 session 为另一 active session 时要求 userConfirmed(§10.3);
|
|
71
|
-
* 调用方还需将旧 session 的 goal status 置 paused(goalClient 层)。
|
|
72
|
-
*/
|
|
73
|
-
export async function takeoverSession(projectDir, parentIssueNumber, newSessionID, userConfirmed) {
|
|
74
|
-
return indexMutex.runExclusive(projectDir, async () => {
|
|
75
|
-
const index = await readIndex(projectDir);
|
|
76
|
-
const key = String(parentIssueNumber);
|
|
77
|
-
const existing = index.flows[key];
|
|
78
|
-
if (existing && existing.sessionID !== newSessionID && existing.status === "active" && !userConfirmed) {
|
|
79
|
-
return {
|
|
80
|
-
ok: false,
|
|
81
|
-
code: "TAKEOVER_NOT_CONFIRMED",
|
|
82
|
-
message: `Flow #${parentIssueNumber} is bound to active session "${existing.sessionID}". Takeover requires user confirmation (user_confirmed: true).`,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (existing) {
|
|
86
|
-
if (existing.sessionID === newSessionID) {
|
|
87
|
-
const entry = { ...existing, status: "active", updatedAt: Date.now() };
|
|
88
|
-
index.flows[key] = entry;
|
|
89
|
-
await writeIndex(projectDir, index);
|
|
90
|
-
return { ok: true, entry };
|
|
91
|
-
}
|
|
92
|
-
existing.status = "paused";
|
|
93
|
-
existing.updatedAt = Date.now();
|
|
94
|
-
}
|
|
95
|
-
const entry = {
|
|
96
|
-
sessionID: newSessionID,
|
|
97
|
-
status: "active",
|
|
98
|
-
continuationCount: 0,
|
|
99
|
-
updatedAt: Date.now(),
|
|
100
|
-
};
|
|
101
|
-
index.flows[key] = entry;
|
|
102
|
-
await writeIndex(projectDir, index);
|
|
103
|
-
return { ok: true, entry };
|
|
104
55
|
});
|
|
105
56
|
}
|
|
106
57
|
/** 更新已绑定 flow 的状态/计数/goal 完成标记;flow 未绑定时返回 null 且不创建 entry。 */
|
|
@@ -120,13 +71,12 @@ export async function updateFlowSession(projectDir, parentIssueNumber, patch) {
|
|
|
120
71
|
return entry;
|
|
121
72
|
});
|
|
122
73
|
}
|
|
123
|
-
/**
|
|
124
|
-
export async function
|
|
74
|
+
/** 删除 flow 的索引 entry(goal cancel 时调用,避免重启 autoResume 恢复已取消 goal)。 */
|
|
75
|
+
export async function removeFlowSession(projectDir, parentIssueNumber) {
|
|
125
76
|
return indexMutex.runExclusive(projectDir, async () => {
|
|
126
77
|
const index = await readIndex(projectDir);
|
|
127
78
|
const key = String(parentIssueNumber);
|
|
128
|
-
|
|
129
|
-
if (!existing || existing.sessionID !== sessionID)
|
|
79
|
+
if (!index.flows[key])
|
|
130
80
|
return false;
|
|
131
81
|
delete index.flows[key];
|
|
132
82
|
await writeIndex(projectDir, index);
|