@devcxl/opencode-cabbage 0.2.0 → 0.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/README.md +57 -54
- package/README.zh.md +91 -0
- package/assets/agents/dev-lifecycle.md +28 -22
- package/assets/agents/team/architect.md +14 -0
- package/assets/agents/team/backend.md +14 -0
- package/assets/agents/team/frontend.md +14 -0
- package/assets/agents/team/goal-verify.md +54 -0
- package/assets/agents/team/reviewer.md +10 -0
- package/assets/skills/flow-code/SKILL.md +13 -0
- package/assets/skills/flow-review/SKILL.md +7 -0
- package/assets/skills/flow-tasks/SKILL.md +42 -0
- package/assets/skills/flow-tdd/SKILL.md +208 -0
- package/dist/flowrun/adapter.d.ts +49 -0
- package/dist/flowrun/adapter.js +293 -0
- package/dist/flowrun/adapter.js.map +1 -0
- package/dist/flowrun/ci.d.ts +64 -0
- package/dist/flowrun/ci.js +184 -0
- package/dist/flowrun/ci.js.map +1 -0
- package/dist/flowrun/coverage.d.ts +70 -0
- package/dist/flowrun/coverage.js +207 -0
- package/dist/flowrun/coverage.js.map +1 -0
- package/dist/flowrun/digest.d.ts +17 -0
- package/dist/flowrun/digest.js +301 -0
- package/dist/flowrun/digest.js.map +1 -0
- package/dist/flowrun/evaluator.d.ts +38 -0
- package/dist/flowrun/evaluator.js +185 -0
- package/dist/flowrun/evaluator.js.map +1 -0
- package/dist/flowrun/gate.d.ts +20 -0
- package/dist/flowrun/gate.js +40 -3
- package/dist/flowrun/gate.js.map +1 -1
- package/dist/flowrun/github.d.ts +13 -9
- package/dist/flowrun/github.js +46 -26
- package/dist/flowrun/github.js.map +1 -1
- package/dist/flowrun/index.d.ts +12 -4
- package/dist/flowrun/index.js +11 -3
- package/dist/flowrun/index.js.map +1 -1
- package/dist/flowrun/merge.d.ts +35 -2
- package/dist/flowrun/merge.js +72 -8
- package/dist/flowrun/merge.js.map +1 -1
- package/dist/flowrun/migration.d.ts +11 -0
- package/dist/flowrun/migration.js +276 -0
- package/dist/flowrun/migration.js.map +1 -0
- package/dist/flowrun/pr.d.ts +132 -0
- package/dist/flowrun/pr.js +343 -0
- package/dist/flowrun/pr.js.map +1 -0
- package/dist/flowrun/state.d.ts +79 -0
- package/dist/flowrun/state.js +363 -0
- package/dist/flowrun/state.js.map +1 -0
- package/dist/flowrun/transitions.d.ts +66 -0
- package/dist/flowrun/transitions.js +168 -0
- package/dist/flowrun/transitions.js.map +1 -0
- package/dist/flowrun/types.d.ts +279 -4
- package/dist/flowrun/types.js +2 -1
- package/dist/flowrun/types.js.map +1 -1
- package/dist/flowrun/validator.d.ts +15 -5
- package/dist/flowrun/validator.js +117 -5
- package/dist/flowrun/validator.js.map +1 -1
- package/dist/plugin/agents.d.ts +4 -0
- package/dist/plugin/agents.js +19 -0
- package/dist/plugin/agents.js.map +1 -1
- package/dist/plugin/broker.d.ts +73 -0
- package/dist/plugin/broker.js +140 -0
- package/dist/plugin/broker.js.map +1 -0
- package/dist/plugin/flow-pr-tool.d.ts +37 -0
- package/dist/plugin/flow-pr-tool.js +176 -0
- package/dist/plugin/flow-pr-tool.js.map +1 -0
- package/dist/plugin/goal.d.ts +39 -1
- package/dist/plugin/goal.js +80 -1
- package/dist/plugin/goal.js.map +1 -1
- package/dist/plugin/prompt-lint.js +129 -0
- package/dist/plugin/prompt-lint.js.map +1 -1
- package/dist/plugin/server.d.ts +8 -0
- package/dist/plugin/server.js +306 -11
- package/dist/plugin/server.js.map +1 -1
- package/dist/plugin/shell.d.ts +37 -0
- package/dist/plugin/shell.js +144 -0
- package/dist/plugin/shell.js.map +1 -0
- package/dist/plugin/tdd-tool.d.ts +62 -0
- package/dist/plugin/tdd-tool.js +336 -0
- package/dist/plugin/tdd-tool.js.map +1 -0
- package/dist/util/gh.d.ts +8 -1
- package/dist/util/gh.js +10 -2
- package/dist/util/gh.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-tdd
|
|
3
|
+
description: TDD Prompt 协议 — Phase A Advisory 层,定义 RED→GREEN self-reported 流程
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# flow-tdd
|
|
7
|
+
|
|
8
|
+
TDD(Test-Driven Development)Prompt 协议,为所有编码阶段提供统一的 TDD 流程约束。
|
|
9
|
+
本 skill 是 TDD 协议的唯一来源,其他 skills 和 agents 通过引用本协议获得一致的 TDD 行为。
|
|
10
|
+
|
|
11
|
+
## Advisory Procedure
|
|
12
|
+
|
|
13
|
+
Phase A Advisory 层协议:Agent 自行遵循 TDD 流程并 self-report 状态,无需工具拦截。
|
|
14
|
+
|
|
15
|
+
### Cycle 状态机
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────┐
|
|
19
|
+
│ cycle-start │ ← 开始新的 TDD 循环
|
|
20
|
+
└──────┬──────┘
|
|
21
|
+
│
|
|
22
|
+
▼
|
|
23
|
+
┌─────────────┐
|
|
24
|
+
│ red │ ← 编写/更新测试,验证测试失败(RED)
|
|
25
|
+
└──────┬──────┘
|
|
26
|
+
│
|
|
27
|
+
▼
|
|
28
|
+
┌─────────────┐
|
|
29
|
+
│ green │ ← 最小实现使测试通过(GREEN),可在此阶段重构
|
|
30
|
+
└──────┬──────┘
|
|
31
|
+
│
|
|
32
|
+
┌──────────┼──────────┐
|
|
33
|
+
▼ ▼ ▼
|
|
34
|
+
┌─────────┐ ┌─────────┐ ┌──────────────┐
|
|
35
|
+
│ 下一个 │ │ abandon │ │ 所有 cycle │
|
|
36
|
+
│ cycle │ │ -cycle │ │ 完成 │
|
|
37
|
+
└─────────┘ └─────────┘ └──────┬───────┘
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
┌──────────────────┐
|
|
41
|
+
│ final-regression │ ← 运行全部测试,确认无回归
|
|
42
|
+
└────────┬─────────┘
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
┌───────────────────┐
|
|
46
|
+
│ final-verification│ ← 对照 acceptance_criteria 逐条验证
|
|
47
|
+
└───────────────────┘
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### cycle-start
|
|
51
|
+
|
|
52
|
+
开始一个新的 RED→GREEN 循环。Agent 在开始实现前声明当前 cycle 的目标(要实现的测试/功能)。
|
|
53
|
+
|
|
54
|
+
**self-report 格式:**
|
|
55
|
+
```
|
|
56
|
+
## TDD cycle-start
|
|
57
|
+
- cycle: <序号>
|
|
58
|
+
- target: <本 cycle 要实现的功能/测试描述>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### red
|
|
62
|
+
|
|
63
|
+
编写或更新测试用例,验证测试在当前实现下失败。
|
|
64
|
+
|
|
65
|
+
**约束:**
|
|
66
|
+
- 测试必须有明确的 fail/pass 边界
|
|
67
|
+
- 测试运行命令必须与 `test_commands` 中定义的一致
|
|
68
|
+
- 记录测试失败输出作为 RED evidence
|
|
69
|
+
|
|
70
|
+
**self-report 格式:**
|
|
71
|
+
```
|
|
72
|
+
## TDD red
|
|
73
|
+
- test: <测试名称>
|
|
74
|
+
- command: <运行命令>
|
|
75
|
+
- result: FAIL(预期)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### green
|
|
79
|
+
|
|
80
|
+
编写最小实现使 RED 阶段编写的测试通过。可在此阶段进行局部重构(消除重复、改善命名等),但不应扩展范围。
|
|
81
|
+
|
|
82
|
+
**约束:**
|
|
83
|
+
- 只写使当前测试通过的最少代码
|
|
84
|
+
- 不新增与当前 test 无关的功能
|
|
85
|
+
- 重构仅限于当前 cycle 涉及的代码
|
|
86
|
+
|
|
87
|
+
**self-report 格式:**
|
|
88
|
+
```
|
|
89
|
+
## TDD green
|
|
90
|
+
- test: <测试名称>
|
|
91
|
+
- command: <运行命令>
|
|
92
|
+
- result: PASS
|
|
93
|
+
- changes: <涉及的文件列表>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### abandon-cycle
|
|
97
|
+
|
|
98
|
+
当发现当前 cycle 的目标不合理、设计有缺陷或需要回退时,废弃当前 cycle 的所有修改。
|
|
99
|
+
|
|
100
|
+
**触发条件:**
|
|
101
|
+
- RED 阶段发现测试设计有误
|
|
102
|
+
- GREEN 阶段发现需要根本性重新设计
|
|
103
|
+
- 发现更简单的替代方案
|
|
104
|
+
|
|
105
|
+
**流程:**
|
|
106
|
+
1. 放弃当前 cycle 的修改(`git checkout -- <files>` 或 `git stash drop`)
|
|
107
|
+
2. 记录 abandon 原因
|
|
108
|
+
3. 重新从 cycle-start 开始
|
|
109
|
+
|
|
110
|
+
**self-report 格式:**
|
|
111
|
+
```
|
|
112
|
+
## TDD abandon-cycle
|
|
113
|
+
- cycle: <序号>
|
|
114
|
+
- reason: <废弃原因>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### final-regression
|
|
118
|
+
|
|
119
|
+
所有 cycle 完成后,运行项目全部测试套件,确认无回归。
|
|
120
|
+
|
|
121
|
+
**约束:**
|
|
122
|
+
- 必须运行 `test_commands` 中定义的全部命令
|
|
123
|
+
- 所有测试必须通过
|
|
124
|
+
- 如有失败 → 修复(不要求新 cycle,但需记录修复内容)
|
|
125
|
+
|
|
126
|
+
**self-report 格式:**
|
|
127
|
+
```
|
|
128
|
+
## TDD final-regression
|
|
129
|
+
- passed: <通过的测试数>
|
|
130
|
+
- failed: <失败的测试数>
|
|
131
|
+
- total: <测试总数>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### final-verification
|
|
135
|
+
|
|
136
|
+
对照 Task 的 `acceptance_criteria` 逐条验证,确认全部满足。
|
|
137
|
+
|
|
138
|
+
**self-report 格式:**
|
|
139
|
+
```
|
|
140
|
+
## TDD final-verification
|
|
141
|
+
- criteria_total: <总数>
|
|
142
|
+
- criteria_met: <已满足>
|
|
143
|
+
- criteria_pending: <未满足>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Runtime Procedure
|
|
147
|
+
|
|
148
|
+
> **Phase C 启用** — 以下为 Runtime Enforcement 协议占位,当前阶段不生效。
|
|
149
|
+
>
|
|
150
|
+
> Phase C 将引入 `tdd_checkpoint` 工具在运行时拦截 RED/GREEN 状态切换,
|
|
151
|
+
> 并将 evidence 写入 FlowRun 存储。Agent 不直接调用这些工具的时机和方式
|
|
152
|
+
> 由 Phase C 的 `tdd_checkpoint` 工具实现决定。
|
|
153
|
+
|
|
154
|
+
<!--
|
|
155
|
+
Phase C 启用后的 Runtime Procedure:
|
|
156
|
+
1. cycle-start → tdd_checkpoint({ stage: "cycle-start", task_id })
|
|
157
|
+
2. red → tdd_checkpoint({ stage: "red", evidence: test_output, task_id })
|
|
158
|
+
3. green → tdd_checkpoint({ stage: "green", evidence: test_output, task_id })
|
|
159
|
+
4. abandon-cycle → tdd_checkpoint({ stage: "abandon-cycle", reason, task_id })
|
|
160
|
+
5. final-regression → tdd_checkpoint({ stage: "final-regression", evidence: full_test_output, task_id })
|
|
161
|
+
6. final-verification → tdd_checkpoint({ stage: "final-verification", evidence: criteria_checklist, task_id })
|
|
162
|
+
-->
|
|
163
|
+
|
|
164
|
+
## Contract
|
|
165
|
+
|
|
166
|
+
### Trigger
|
|
167
|
+
由编码阶段(`flow-code`)自动触发。Agent 在开始编码任务时加载 `flow-tdd` skill 获取 TDD 流程约束。
|
|
168
|
+
|
|
169
|
+
### Inputs
|
|
170
|
+
- Task 定义中的 `acceptance_criteria`(来源:`flow-tasks` 产出)
|
|
171
|
+
- Task 定义中的 `test_commands`(来源:`flow-tasks` 产出)
|
|
172
|
+
- Task 定义中的 `verify_commands`(来源:`flow-tasks` 产出)
|
|
173
|
+
- Task 定义中的 `tdd` 配置块 — `mode`(`strict`/`advisory`)、`min_cycles`(来源:`flow-tasks` 产出)
|
|
174
|
+
|
|
175
|
+
### Preconditions
|
|
176
|
+
- Task 文件存在,包含 `acceptance_criteria`、`test_commands`、`tdd` 配置块
|
|
177
|
+
- 测试运行环境就绪(`npm install` 已完成)
|
|
178
|
+
|
|
179
|
+
### Procedure
|
|
180
|
+
1. 读取 Task 的 `acceptance_criteria`、`test_commands`、`verify_commands`
|
|
181
|
+
2. **Advisory Mode**:为每个验收标准识别对应的测试用例
|
|
182
|
+
3. 执行 `cycle-start` → 声明当前 cycle 目标
|
|
183
|
+
4. 执行 `red` → 编写测试,验证失败
|
|
184
|
+
5. 执行 `green` → 最小实现,验证通过
|
|
185
|
+
6. 重复 cycle 直到所有 criterion 覆盖
|
|
186
|
+
7. 执行 `final-regression` → 运行全部测试
|
|
187
|
+
8. 执行 `final-verification` → 逐条对照 acceptance_criteria
|
|
188
|
+
|
|
189
|
+
### Outputs
|
|
190
|
+
- 每个 cycle 的 self-report(提交到 commit message 或 PR body)
|
|
191
|
+
- `final-regression` 报告(测试通过/失败统计)
|
|
192
|
+
- `final-verification` 报告(criterion 覆盖情况)
|
|
193
|
+
|
|
194
|
+
### Failure
|
|
195
|
+
- RED 阶段测试意外通过 → 检查测试是否有意义,可能需要 abandon-cycle
|
|
196
|
+
- GREEN 阶段测试持续失败 → 检查实现逻辑,记录失败原因
|
|
197
|
+
- final-regression 失败 → 修复回归,不要求新 cycle
|
|
198
|
+
- final-verification 未全部满足 → 返回未完成的 criterion,补充 cycle
|
|
199
|
+
|
|
200
|
+
### Idempotency
|
|
201
|
+
- 同一 cycle 重复执行 → 以最后一次结果为准
|
|
202
|
+
- final-regression 重复执行 → 覆盖上次结果
|
|
203
|
+
|
|
204
|
+
### Prohibited Actions
|
|
205
|
+
- 不跳过 RED 阶段直接进入 GREEN
|
|
206
|
+
- 不跳过 final-regression 直接 commit
|
|
207
|
+
- 不在 abandon-cycle 后保留修改
|
|
208
|
+
- 不修改 `tdd` 配置块中的 `mode` 和 `min_cycles` 值
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { TddRunnerPolicy, TddCommandEvidence, TddFailureKind } from "./types.js";
|
|
2
|
+
export interface VitestOutput {
|
|
3
|
+
exitCode: number | null;
|
|
4
|
+
testsCollected: number | null;
|
|
5
|
+
testsFailed: number | null;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
timedOut: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** classifyVitestFailure 的输入 */
|
|
11
|
+
export interface FailureInput {
|
|
12
|
+
exitCode: number | null;
|
|
13
|
+
testsCollected: number | null;
|
|
14
|
+
testsFailed: number | null;
|
|
15
|
+
stderr: string;
|
|
16
|
+
timedOut?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 校验 test selector 语法安全。
|
|
20
|
+
* 拒绝 shell 元字符、命令替换、换行等。
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateSelector(selector: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* 将 baseCommand 字符串解析为 argv 数组并附加 selector。
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildVitestArgs(policy: TddRunnerPolicy, selector: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* 执行 vitest 命令并捕获输出。
|
|
29
|
+
*/
|
|
30
|
+
export declare function executeVitest(args: string[], cwd: string, timeoutMs: number): Promise<VitestOutput>;
|
|
31
|
+
/**
|
|
32
|
+
* 根据 vitest 输出分类失败类型。
|
|
33
|
+
*
|
|
34
|
+
* 规则:
|
|
35
|
+
* - timeout → "timeout"
|
|
36
|
+
* - 成功启动 + 收集测试 + assertion 失败 → "assertion"(RED)
|
|
37
|
+
* - 成功启动 + 目标代码不存在 → "missing-behavior"(RED)
|
|
38
|
+
* - config/transform/dep 错误 → "infrastructure"
|
|
39
|
+
* - 零测试收集 → "infrastructure"
|
|
40
|
+
*/
|
|
41
|
+
export declare function classifyVitestFailure(input: FailureInput): TddFailureKind | null;
|
|
42
|
+
/**
|
|
43
|
+
* 执行 RED check:运行 vitest focused test,返回 TddCommandEvidence。
|
|
44
|
+
*
|
|
45
|
+
* @param policy - TDD runner policy(含 baseCommand、timeoutMs 等)
|
|
46
|
+
* @param selector - focused test selector(文件路径或 vitest flag)
|
|
47
|
+
* @param cwd - 工作目录
|
|
48
|
+
*/
|
|
49
|
+
export declare function executeRedCheck(policy: TddRunnerPolicy, selector: string, cwd: string): Promise<TddCommandEvidence>;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
// ─── Selector 校验 ───
|
|
4
|
+
/** 危险字符正则 */
|
|
5
|
+
const DANGEROUS_SELECTOR = /[|;&$()`\x00-\x08\x0b\x0c\x0e-\x1f\r\n]/;
|
|
6
|
+
/**
|
|
7
|
+
* 校验 test selector 语法安全。
|
|
8
|
+
* 拒绝 shell 元字符、命令替换、换行等。
|
|
9
|
+
*/
|
|
10
|
+
export function validateSelector(selector) {
|
|
11
|
+
if (!selector || !selector.trim()) {
|
|
12
|
+
throw new Error("Selector must not be empty");
|
|
13
|
+
}
|
|
14
|
+
if (DANGEROUS_SELECTOR.test(selector)) {
|
|
15
|
+
throw new Error(`Selector contains invalid or dangerous characters: ${JSON.stringify(selector)}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// ─── 命令构建 ───
|
|
19
|
+
/**
|
|
20
|
+
* 将 baseCommand 字符串解析为 argv 数组并附加 selector。
|
|
21
|
+
*/
|
|
22
|
+
export function buildVitestArgs(policy, selector) {
|
|
23
|
+
const args = policy.baseCommand.trim().split(/\s+/).filter(Boolean);
|
|
24
|
+
args.push(selector);
|
|
25
|
+
return args;
|
|
26
|
+
}
|
|
27
|
+
// ─── 进程执行 ───
|
|
28
|
+
/**
|
|
29
|
+
* 执行 vitest 命令并捕获输出。
|
|
30
|
+
*/
|
|
31
|
+
export function executeVitest(args, cwd, timeoutMs) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const child = spawn(args[0], args.slice(1), {
|
|
34
|
+
cwd,
|
|
35
|
+
env: { ...process.env },
|
|
36
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
37
|
+
timeout: timeoutMs,
|
|
38
|
+
});
|
|
39
|
+
let stdout = "";
|
|
40
|
+
let stderr = "";
|
|
41
|
+
let timedOut = false;
|
|
42
|
+
child.stdout?.on("data", (chunk) => {
|
|
43
|
+
stdout += chunk.toString("utf-8");
|
|
44
|
+
});
|
|
45
|
+
child.stderr?.on("data", (chunk) => {
|
|
46
|
+
stderr += chunk.toString("utf-8");
|
|
47
|
+
});
|
|
48
|
+
child.on("error", (err) => {
|
|
49
|
+
// spawn 本身失败(如找不到命令)
|
|
50
|
+
if (err.code === "ENOENT") {
|
|
51
|
+
resolve({
|
|
52
|
+
exitCode: 127,
|
|
53
|
+
testsCollected: null,
|
|
54
|
+
testsFailed: null,
|
|
55
|
+
stdout,
|
|
56
|
+
stderr: `${stderr}\nCommand not found: ${args[0]}`,
|
|
57
|
+
timedOut: false,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
reject(err);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
child.on("close", (code, signal) => {
|
|
65
|
+
timedOut = signal === "SIGTERM" || signal === "SIGKILL";
|
|
66
|
+
resolve({
|
|
67
|
+
exitCode: code,
|
|
68
|
+
testsCollected: null, // will be enriched later
|
|
69
|
+
testsFailed: null,
|
|
70
|
+
stdout,
|
|
71
|
+
stderr,
|
|
72
|
+
timedOut,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// ─── 输出解析 ───
|
|
78
|
+
/**
|
|
79
|
+
* 从 vitest stdout 解析 JSON reporter 输出,提取测试统计数据。
|
|
80
|
+
*/
|
|
81
|
+
function parseVitestJson(stdout) {
|
|
82
|
+
// 在 stdout 中查找 vitest JSON reporter 的输出(通常是一个 JSON 对象)
|
|
83
|
+
// 支持多行 JSON 输出,查找以 "numTotalTests" 为特征的 JSON
|
|
84
|
+
const jsonMatch = stdout.match(/\{[\s\S]*"numTotalTests"\s*:\s*\d+[\s\S]*\}/);
|
|
85
|
+
if (!jsonMatch)
|
|
86
|
+
return null;
|
|
87
|
+
try {
|
|
88
|
+
const json = JSON.parse(jsonMatch[0]);
|
|
89
|
+
const collected = typeof json.numTotalTests === "number" ? json.numTotalTests : null;
|
|
90
|
+
const failed = typeof json.numFailedTests === "number" ? json.numFailedTests : null;
|
|
91
|
+
if (collected === null || failed === null)
|
|
92
|
+
return null;
|
|
93
|
+
return { testsCollected: collected, testsFailed: failed };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 从 stderr 解析 vitest 错误,提取测试计数(兜底解析)。
|
|
101
|
+
*/
|
|
102
|
+
function parseVitestSummary(stderr) {
|
|
103
|
+
// vitest verbose 输出: "Tests 2 passed (2) | 1 failed (1)"
|
|
104
|
+
// 或 "Tests 2 passed (2)"
|
|
105
|
+
const summaryMatch = stderr.match(/Tests\s+\d+\s+failed\s*\((\d+)\)/);
|
|
106
|
+
const totalMatch = stderr.match(/Tests\s+\d+\s+failed[\s\S]*?\|\s*(\d+)\s+passed/);
|
|
107
|
+
const passedMatch = stderr.match(/(\d+)\s+passed/);
|
|
108
|
+
let testsCollected = null;
|
|
109
|
+
let testsFailed = null;
|
|
110
|
+
if (summaryMatch) {
|
|
111
|
+
testsFailed = parseInt(summaryMatch[1], 10);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// 检查是否有 "No test files found" 或 "0 tests"
|
|
115
|
+
if (stderr.includes("No test files found") || stderr.includes("no tests found")) {
|
|
116
|
+
return { testsCollected: 0, testsFailed: 0 };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 尝试计算 collected = passed + failed
|
|
120
|
+
const totalPassedMatch = stderr.match(/(\d+)\s+passed/);
|
|
121
|
+
if (totalPassedMatch && testsFailed !== null) {
|
|
122
|
+
testsCollected = parseInt(totalPassedMatch[1], 10) + testsFailed;
|
|
123
|
+
}
|
|
124
|
+
else if (testsFailed === null) {
|
|
125
|
+
// 可能全部通过
|
|
126
|
+
const allPassedMatch = stderr.match(/Tests\s+(\d+)\s+passed/);
|
|
127
|
+
if (allPassedMatch) {
|
|
128
|
+
testsCollected = parseInt(allPassedMatch[1], 10);
|
|
129
|
+
testsFailed = 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { testsCollected, testsFailed };
|
|
133
|
+
}
|
|
134
|
+
// ─── 失败分类 ───
|
|
135
|
+
/**
|
|
136
|
+
* 根据 vitest 输出分类失败类型。
|
|
137
|
+
*
|
|
138
|
+
* 规则:
|
|
139
|
+
* - timeout → "timeout"
|
|
140
|
+
* - 成功启动 + 收集测试 + assertion 失败 → "assertion"(RED)
|
|
141
|
+
* - 成功启动 + 目标代码不存在 → "missing-behavior"(RED)
|
|
142
|
+
* - config/transform/dep 错误 → "infrastructure"
|
|
143
|
+
* - 零测试收集 → "infrastructure"
|
|
144
|
+
*/
|
|
145
|
+
export function classifyVitestFailure(input) {
|
|
146
|
+
// timeout
|
|
147
|
+
if (input.timedOut)
|
|
148
|
+
return "timeout";
|
|
149
|
+
// 测试全部通过
|
|
150
|
+
if (input.exitCode === 0 && input.testsFailed === 0)
|
|
151
|
+
return null;
|
|
152
|
+
// 退出码正常但无法判断
|
|
153
|
+
if (input.exitCode === 0 && input.testsFailed === null)
|
|
154
|
+
return null;
|
|
155
|
+
// 失败的退出码
|
|
156
|
+
if (input.exitCode !== null && input.exitCode !== 0) {
|
|
157
|
+
const stderr = input.stderr;
|
|
158
|
+
// ── 基础设施错误 ──
|
|
159
|
+
// 配置加载失败
|
|
160
|
+
if (/failed to load config/i.test(stderr) ||
|
|
161
|
+
/cannot find module.*vitest/i.test(stderr) ||
|
|
162
|
+
/cannot find package.*vitest/i.test(stderr)) {
|
|
163
|
+
return "infrastructure";
|
|
164
|
+
}
|
|
165
|
+
// Transform 错误
|
|
166
|
+
if (/transform failed/i.test(stderr) ||
|
|
167
|
+
/unexpected token/i.test(stderr)) {
|
|
168
|
+
return "infrastructure";
|
|
169
|
+
}
|
|
170
|
+
// 依赖缺失(非实现代码的模块)
|
|
171
|
+
if (/cannot find module/i.test(stderr)) {
|
|
172
|
+
// 区分:如果缺失的模块匹配实现文件路径模式,则是 missing-behavior
|
|
173
|
+
// 否则是 infrastructure(缺少外部依赖)
|
|
174
|
+
const moduleMatch = stderr.match(/cannot find module\s+['"]([^'"]+)['"]/i);
|
|
175
|
+
if (moduleMatch) {
|
|
176
|
+
const missingModule = moduleMatch[1];
|
|
177
|
+
// 相对路径导入 → missing-behavior(目标代码不存在)
|
|
178
|
+
if (missingModule.startsWith(".") || missingModule.startsWith("/")) {
|
|
179
|
+
return "missing-behavior";
|
|
180
|
+
}
|
|
181
|
+
// 绝对包名 → infrastructure(缺少依赖)
|
|
182
|
+
return "infrastructure";
|
|
183
|
+
}
|
|
184
|
+
// 无法判断是什么模块,默认为 infrastructure
|
|
185
|
+
return "infrastructure";
|
|
186
|
+
}
|
|
187
|
+
// 零测试收集
|
|
188
|
+
if (input.testsCollected === 0)
|
|
189
|
+
return "infrastructure";
|
|
190
|
+
// ── assertion 失败 ──
|
|
191
|
+
// 需要 testsCollected > 0 AND testsFailed > 0 才算是 assertion
|
|
192
|
+
if (input.testsCollected !== null && input.testsCollected > 0 &&
|
|
193
|
+
input.testsFailed !== null && input.testsFailed > 0) {
|
|
194
|
+
return "assertion";
|
|
195
|
+
}
|
|
196
|
+
// ── 有测试收集但 testsFailed 为 0/null 且 exitCode 非零 ── 无法确定失败原因
|
|
197
|
+
if (input.testsCollected !== null && input.testsCollected > 0) {
|
|
198
|
+
return "unknown";
|
|
199
|
+
}
|
|
200
|
+
// 无法分类
|
|
201
|
+
return "unknown";
|
|
202
|
+
}
|
|
203
|
+
// 无退出码(被 kill 等)
|
|
204
|
+
if (input.exitCode === null && input.testsCollected !== null && input.testsCollected === 0) {
|
|
205
|
+
return "infrastructure";
|
|
206
|
+
}
|
|
207
|
+
return "unknown";
|
|
208
|
+
}
|
|
209
|
+
// ─── SHA-256 辅助 ───
|
|
210
|
+
function sha256Hex(content) {
|
|
211
|
+
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
212
|
+
}
|
|
213
|
+
// ─── 主函数 ───
|
|
214
|
+
/**
|
|
215
|
+
* 执行 RED check:运行 vitest focused test,返回 TddCommandEvidence。
|
|
216
|
+
*
|
|
217
|
+
* @param policy - TDD runner policy(含 baseCommand、timeoutMs 等)
|
|
218
|
+
* @param selector - focused test selector(文件路径或 vitest flag)
|
|
219
|
+
* @param cwd - 工作目录
|
|
220
|
+
*/
|
|
221
|
+
export async function executeRedCheck(policy, selector, cwd) {
|
|
222
|
+
// 1. 校验 selector
|
|
223
|
+
validateSelector(selector);
|
|
224
|
+
// 2. 构建命令
|
|
225
|
+
const args = buildVitestArgs(policy, selector);
|
|
226
|
+
const commandStr = args.join(" ");
|
|
227
|
+
// 3. 执行
|
|
228
|
+
const startedAt = new Date().toISOString();
|
|
229
|
+
const startMs = Date.now();
|
|
230
|
+
const output = await executeVitest(args, cwd, policy.timeoutMs);
|
|
231
|
+
const durationMs = Date.now() - startMs;
|
|
232
|
+
const finishedAt = new Date().toISOString();
|
|
233
|
+
// 4. 解析 vitest 输出获取测试统计数据
|
|
234
|
+
const jsonStats = parseVitestJson(output.stdout);
|
|
235
|
+
const summaryStats = parseVitestSummary(output.stderr);
|
|
236
|
+
const testsCollected = jsonStats?.testsCollected ?? summaryStats.testsCollected ?? null;
|
|
237
|
+
const testsFailed = jsonStats?.testsFailed ?? summaryStats.testsFailed ?? null;
|
|
238
|
+
// 5. 分类失败
|
|
239
|
+
const failureKind = classifyVitestFailure({
|
|
240
|
+
exitCode: output.exitCode,
|
|
241
|
+
testsCollected,
|
|
242
|
+
testsFailed,
|
|
243
|
+
stderr: output.stderr,
|
|
244
|
+
timedOut: output.timedOut,
|
|
245
|
+
});
|
|
246
|
+
// 6. 计算 outputDigest(stdout + stderr 的 SHA-256)
|
|
247
|
+
const outputDigest = {
|
|
248
|
+
algorithm: "sha256-output-v1",
|
|
249
|
+
value: sha256Hex(output.stdout + "\n" + output.stderr),
|
|
250
|
+
};
|
|
251
|
+
// 7. workspaceDigest 和 executionInputDigest 留空(由调用方/broker 填充)
|
|
252
|
+
const placeholderDigest = {
|
|
253
|
+
algorithm: "sha256-content-v1",
|
|
254
|
+
value: "0".repeat(64),
|
|
255
|
+
};
|
|
256
|
+
// 8. 构建 summary
|
|
257
|
+
let summary;
|
|
258
|
+
if (output.timedOut) {
|
|
259
|
+
summary = `Test execution timed out after ${policy.timeoutMs}ms`;
|
|
260
|
+
}
|
|
261
|
+
else if (failureKind === "assertion") {
|
|
262
|
+
summary = `${testsFailed}/${testsCollected} tests failed (assertion)`;
|
|
263
|
+
}
|
|
264
|
+
else if (failureKind === "missing-behavior") {
|
|
265
|
+
summary = `Target code not found: ${selector}`;
|
|
266
|
+
}
|
|
267
|
+
else if (failureKind === "infrastructure") {
|
|
268
|
+
summary = `Infrastructure error: ${output.stderr.slice(0, 200)}`;
|
|
269
|
+
}
|
|
270
|
+
else if (output.exitCode === 0) {
|
|
271
|
+
summary = `${testsCollected ?? "?"} tests passed`;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
summary = `Exit code ${output.exitCode}: ${output.stderr.slice(0, 200)}`;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
command: commandStr,
|
|
278
|
+
testSelector: selector,
|
|
279
|
+
exitCode: output.exitCode,
|
|
280
|
+
failureKind,
|
|
281
|
+
testsCollected,
|
|
282
|
+
testsFailed,
|
|
283
|
+
startedAt,
|
|
284
|
+
finishedAt,
|
|
285
|
+
durationMs,
|
|
286
|
+
changedFiles: [],
|
|
287
|
+
outputDigest,
|
|
288
|
+
workspaceDigest: placeholderDigest,
|
|
289
|
+
executionInputDigest: placeholderDigest,
|
|
290
|
+
summary,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/flowrun/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RepositoryQualityPolicy, TaskCommand, CoveragePolicy } from "./types.js";
|
|
2
|
+
/** 从 gh pr view --json statusCheckRollup 获取的 PR 检查结果 */
|
|
3
|
+
export interface PRCheckResult {
|
|
4
|
+
context: string;
|
|
5
|
+
state: "EXPECTED" | "ERROR" | "FAILURE" | "PENDING" | "SUCCESS";
|
|
6
|
+
app: {
|
|
7
|
+
id: number;
|
|
8
|
+
name: string;
|
|
9
|
+
} | null;
|
|
10
|
+
}
|
|
11
|
+
/** checkRequiredChecks 的返回结果 */
|
|
12
|
+
export interface CICheckResult {
|
|
13
|
+
allPassed: boolean;
|
|
14
|
+
missingContexts: string[];
|
|
15
|
+
failedContexts: string[];
|
|
16
|
+
pendingContexts: string[];
|
|
17
|
+
untrustedSources: Array<{
|
|
18
|
+
context: string;
|
|
19
|
+
expectedAppId: number;
|
|
20
|
+
actualAppId: number | null;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
/** validateRequiredWorkflow 的返回结果 */
|
|
24
|
+
export interface RequiredWorkflowValidation {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
errors: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 验证 PR 的状态检查是否满足 RepositoryQualityPolicy 中定义的 required checks。
|
|
30
|
+
*
|
|
31
|
+
* 规则:
|
|
32
|
+
* - mode 为 "off" 时不校验,直接返回 allPassed: true
|
|
33
|
+
* - mode 为 "required" 时:
|
|
34
|
+
* 1. 每个 requiredChecks 中的 context 必须在 PR checks 中存在
|
|
35
|
+
* 2. 状态必须为 SUCCESS
|
|
36
|
+
* 3. 发布该 check 的 GitHub App ID 必须与配置的 appId 匹配
|
|
37
|
+
* (appId 为 0 时不校验来源)
|
|
38
|
+
*/
|
|
39
|
+
export declare function checkRequiredChecks(policy: RepositoryQualityPolicy, prChecks: PRCheckResult[]): CICheckResult;
|
|
40
|
+
/**
|
|
41
|
+
* 验证 RepositoryQualityPolicy 的 requiredChecks 配置有效性。
|
|
42
|
+
*
|
|
43
|
+
* 规则:
|
|
44
|
+
* - mode 为 "off" 时跳过所有校验
|
|
45
|
+
* - mode 为 "required" 时:
|
|
46
|
+
* 1. requiredChecks 不能为空
|
|
47
|
+
* 2. 每个 entry 必须有 workflowPath、workflowRef
|
|
48
|
+
* 3. 不允许重复的 context
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateRequiredWorkflow(policy: RepositoryQualityPolicy): RequiredWorkflowValidation;
|
|
51
|
+
/**
|
|
52
|
+
* checkBranchProtection 委托给 merge.ts 中已有的实现。
|
|
53
|
+
* 这里作为 re-export wrapper,保持 CI 模块的 API 统一性。
|
|
54
|
+
*/
|
|
55
|
+
export { checkBranchProtection } from "./merge.js";
|
|
56
|
+
/**
|
|
57
|
+
* 计算质量契约摘要(Quality Contract Digest)。
|
|
58
|
+
*
|
|
59
|
+
* 绑定 testCommands + verifyCommands + coveragePolicy,
|
|
60
|
+
* 使用 JCS (JSON Canonicalization Scheme) + SHA-256 生成确定性摘要。
|
|
61
|
+
*
|
|
62
|
+
* 用于在 PR checkpoint 中记录期望的契约版本,确保 CI 执行时契约未被修改。
|
|
63
|
+
*/
|
|
64
|
+
export declare function computeQualityContractDigest(testCommands: TaskCommand[], verifyCommands: TaskCommand[], coveragePolicy: CoveragePolicy | null): string;
|