@devcxl/opencode-cabbage 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/assets/agents/dev-lifecycle.md +23 -19
  2. package/assets/agents/team/architect.md +14 -0
  3. package/assets/agents/team/backend.md +14 -0
  4. package/assets/agents/team/frontend.md +14 -0
  5. package/assets/agents/team/goal-verify.md +54 -0
  6. package/assets/agents/team/reviewer.md +10 -0
  7. package/assets/skills/flow-code/SKILL.md +13 -0
  8. package/assets/skills/flow-review/SKILL.md +7 -0
  9. package/assets/skills/flow-tasks/SKILL.md +42 -0
  10. package/assets/skills/flow-tdd/SKILL.md +208 -0
  11. package/dist/flowrun/adapter.d.ts +49 -0
  12. package/dist/flowrun/adapter.js +293 -0
  13. package/dist/flowrun/adapter.js.map +1 -0
  14. package/dist/flowrun/ci.d.ts +64 -0
  15. package/dist/flowrun/ci.js +184 -0
  16. package/dist/flowrun/ci.js.map +1 -0
  17. package/dist/flowrun/coverage.d.ts +70 -0
  18. package/dist/flowrun/coverage.js +207 -0
  19. package/dist/flowrun/coverage.js.map +1 -0
  20. package/dist/flowrun/digest.d.ts +17 -0
  21. package/dist/flowrun/digest.js +301 -0
  22. package/dist/flowrun/digest.js.map +1 -0
  23. package/dist/flowrun/evaluator.d.ts +38 -0
  24. package/dist/flowrun/evaluator.js +185 -0
  25. package/dist/flowrun/evaluator.js.map +1 -0
  26. package/dist/flowrun/gate.d.ts +10 -0
  27. package/dist/flowrun/gate.js +12 -3
  28. package/dist/flowrun/gate.js.map +1 -1
  29. package/dist/flowrun/github.d.ts +13 -9
  30. package/dist/flowrun/github.js +46 -26
  31. package/dist/flowrun/github.js.map +1 -1
  32. package/dist/flowrun/index.d.ts +10 -3
  33. package/dist/flowrun/index.js +9 -2
  34. package/dist/flowrun/index.js.map +1 -1
  35. package/dist/flowrun/merge.d.ts +35 -2
  36. package/dist/flowrun/merge.js +72 -8
  37. package/dist/flowrun/merge.js.map +1 -1
  38. package/dist/flowrun/migration.d.ts +11 -0
  39. package/dist/flowrun/migration.js +276 -0
  40. package/dist/flowrun/migration.js.map +1 -0
  41. package/dist/flowrun/state.d.ts +79 -0
  42. package/dist/flowrun/state.js +363 -0
  43. package/dist/flowrun/state.js.map +1 -0
  44. package/dist/flowrun/transitions.d.ts +66 -0
  45. package/dist/flowrun/transitions.js +168 -0
  46. package/dist/flowrun/transitions.js.map +1 -0
  47. package/dist/flowrun/types.d.ts +279 -4
  48. package/dist/flowrun/types.js +2 -1
  49. package/dist/flowrun/types.js.map +1 -1
  50. package/dist/flowrun/validator.d.ts +15 -5
  51. package/dist/flowrun/validator.js +117 -5
  52. package/dist/flowrun/validator.js.map +1 -1
  53. package/dist/plugin/agents.d.ts +4 -0
  54. package/dist/plugin/agents.js +19 -0
  55. package/dist/plugin/agents.js.map +1 -1
  56. package/dist/plugin/broker.d.ts +73 -0
  57. package/dist/plugin/broker.js +140 -0
  58. package/dist/plugin/broker.js.map +1 -0
  59. package/dist/plugin/goal.d.ts +39 -1
  60. package/dist/plugin/goal.js +80 -1
  61. package/dist/plugin/goal.js.map +1 -1
  62. package/dist/plugin/prompt-lint.js +129 -0
  63. package/dist/plugin/prompt-lint.js.map +1 -1
  64. package/dist/plugin/server.js +241 -11
  65. package/dist/plugin/server.js.map +1 -1
  66. package/dist/plugin/shell.d.ts +37 -0
  67. package/dist/plugin/shell.js +144 -0
  68. package/dist/plugin/shell.js.map +1 -0
  69. package/dist/plugin/tdd-tool.d.ts +62 -0
  70. package/dist/plugin/tdd-tool.js +336 -0
  71. package/dist/plugin/tdd-tool.js.map +1 -0
  72. package/dist/util/gh.d.ts +8 -1
  73. package/dist/util/gh.js +10 -2
  74. package/dist/util/gh.js.map +1 -1
  75. package/package.json +2 -1
@@ -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;
@@ -0,0 +1,184 @@
1
+ import { createHash } from "node:crypto";
2
+ // ─── checkRequiredChecks ───
3
+ /**
4
+ * 验证 PR 的状态检查是否满足 RepositoryQualityPolicy 中定义的 required checks。
5
+ *
6
+ * 规则:
7
+ * - mode 为 "off" 时不校验,直接返回 allPassed: true
8
+ * - mode 为 "required" 时:
9
+ * 1. 每个 requiredChecks 中的 context 必须在 PR checks 中存在
10
+ * 2. 状态必须为 SUCCESS
11
+ * 3. 发布该 check 的 GitHub App ID 必须与配置的 appId 匹配
12
+ * (appId 为 0 时不校验来源)
13
+ */
14
+ export function checkRequiredChecks(policy, prChecks) {
15
+ // mode off — 不增加额外约束
16
+ if (policy.mode === "off") {
17
+ return {
18
+ allPassed: true,
19
+ missingContexts: [],
20
+ failedContexts: [],
21
+ pendingContexts: [],
22
+ untrustedSources: [],
23
+ };
24
+ }
25
+ // mode required — 逐项验证
26
+ const prCheckMap = new Map();
27
+ for (const c of prChecks) {
28
+ prCheckMap.set(c.context, c);
29
+ }
30
+ const missingContexts = [];
31
+ const failedContexts = [];
32
+ const pendingContexts = [];
33
+ const untrustedSources = [];
34
+ for (const required of policy.requiredChecks) {
35
+ const prCheck = prCheckMap.get(required.context);
36
+ // 1. context 缺失
37
+ if (!prCheck) {
38
+ missingContexts.push(required.context);
39
+ continue;
40
+ }
41
+ // 2. 状态检查
42
+ if (prCheck.state === "SUCCESS") {
43
+ // 通过 — 继续检查来源
44
+ }
45
+ else if (prCheck.state === "PENDING" || prCheck.state === "EXPECTED") {
46
+ pendingContexts.push(required.context);
47
+ continue;
48
+ }
49
+ else {
50
+ // FAILURE, ERROR
51
+ failedContexts.push(required.context);
52
+ continue;
53
+ }
54
+ // 3. 来源验证(appId 为 0 时不校验)
55
+ if (required.appId !== 0) {
56
+ if (!prCheck.app || prCheck.app.id !== required.appId) {
57
+ untrustedSources.push({
58
+ context: required.context,
59
+ expectedAppId: required.appId,
60
+ actualAppId: prCheck.app?.id ?? null,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ const allPassed = missingContexts.length === 0 &&
66
+ failedContexts.length === 0 &&
67
+ pendingContexts.length === 0 &&
68
+ untrustedSources.length === 0;
69
+ return {
70
+ allPassed,
71
+ missingContexts,
72
+ failedContexts,
73
+ pendingContexts,
74
+ untrustedSources,
75
+ };
76
+ }
77
+ // ─── validateRequiredWorkflow ───
78
+ /**
79
+ * 验证 RepositoryQualityPolicy 的 requiredChecks 配置有效性。
80
+ *
81
+ * 规则:
82
+ * - mode 为 "off" 时跳过所有校验
83
+ * - mode 为 "required" 时:
84
+ * 1. requiredChecks 不能为空
85
+ * 2. 每个 entry 必须有 workflowPath、workflowRef
86
+ * 3. 不允许重复的 context
87
+ */
88
+ export function validateRequiredWorkflow(policy) {
89
+ // mode off — 无需校验
90
+ if (policy.mode === "off") {
91
+ return { valid: true, errors: [] };
92
+ }
93
+ const errors = [];
94
+ // 1. requiredChecks 不能为空
95
+ if (policy.requiredChecks.length === 0) {
96
+ errors.push("requiredChecks must not be empty when mode is 'required'");
97
+ return { valid: false, errors };
98
+ }
99
+ // 2. 校验每个 entry
100
+ const seenContexts = new Set();
101
+ for (let i = 0; i < policy.requiredChecks.length; i++) {
102
+ const entry = policy.requiredChecks[i];
103
+ const prefix = `requiredChecks[${i}]`;
104
+ // 检查 context 重复
105
+ if (seenContexts.has(entry.context)) {
106
+ errors.push(`${prefix}: duplicate context "${entry.context}"`);
107
+ }
108
+ seenContexts.add(entry.context);
109
+ // 检查 workflowPath
110
+ if (!entry.workflowPath) {
111
+ errors.push(`${prefix}: workflowPath must not be empty`);
112
+ }
113
+ // 检查 workflowRef
114
+ if (!entry.workflowRef) {
115
+ errors.push(`${prefix}: workflowRef must not be empty`);
116
+ }
117
+ }
118
+ return { valid: errors.length === 0, errors };
119
+ }
120
+ // ─── checkBranchProtection ───
121
+ /**
122
+ * checkBranchProtection 委托给 merge.ts 中已有的实现。
123
+ * 这里作为 re-export wrapper,保持 CI 模块的 API 统一性。
124
+ */
125
+ export { checkBranchProtection } from "./merge.js";
126
+ // ─── computeQualityContractDigest ───
127
+ /**
128
+ * JCS (JSON Canonicalization Scheme) 序列化:
129
+ * - 对象的 key 按 UTF-8 字节序排序
130
+ * - 无多余空白
131
+ * - 数字保留原始精度
132
+ */
133
+ function jcsStringify(value) {
134
+ if (value === null)
135
+ return "null";
136
+ if (typeof value === "boolean")
137
+ return value ? "true" : "false";
138
+ if (typeof value === "number") {
139
+ if (Number.isFinite(value)) {
140
+ // 使用标准数字序列化
141
+ return String(value);
142
+ }
143
+ return "null";
144
+ }
145
+ if (typeof value === "string") {
146
+ return JSON.stringify(value);
147
+ }
148
+ if (Array.isArray(value)) {
149
+ const items = value.map(jcsStringify);
150
+ return `[${items.join(",")}]`;
151
+ }
152
+ if (typeof value === "object") {
153
+ // 按 key 排序
154
+ const keys = Object.keys(value).sort((a, b) => {
155
+ const bufA = Buffer.from(a, "utf-8");
156
+ const bufB = Buffer.from(b, "utf-8");
157
+ return Buffer.compare(bufA, bufB);
158
+ });
159
+ const pairs = keys.map(k => {
160
+ const v = value[k];
161
+ return `${JSON.stringify(k)}:${jcsStringify(v)}`;
162
+ });
163
+ return `{${pairs.join(",")}}`;
164
+ }
165
+ return "null";
166
+ }
167
+ /**
168
+ * 计算质量契约摘要(Quality Contract Digest)。
169
+ *
170
+ * 绑定 testCommands + verifyCommands + coveragePolicy,
171
+ * 使用 JCS (JSON Canonicalization Scheme) + SHA-256 生成确定性摘要。
172
+ *
173
+ * 用于在 PR checkpoint 中记录期望的契约版本,确保 CI 执行时契约未被修改。
174
+ */
175
+ export function computeQualityContractDigest(testCommands, verifyCommands, coveragePolicy) {
176
+ const contract = {
177
+ testCommands,
178
+ verifyCommands,
179
+ coveragePolicy,
180
+ };
181
+ const json = jcsStringify(contract);
182
+ return createHash("sha256").update(json, "utf-8").digest("hex");
183
+ }
184
+ //# sourceMappingURL=ci.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.js","sourceRoot":"","sources":["../../src/flowrun/ci.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import type { CoverageEvidence } from "./types.js";
2
+ /**
3
+ * 检验 coverage report 路径的安全性。
4
+ *
5
+ * 规则:
6
+ * - 拒绝空路径
7
+ * - 拒绝绝对路径
8
+ * - 拒绝包含 null 字节等危险字符的路径
9
+ * - 拒绝包含 `..` 路径遍历的路径
10
+ *
11
+ * 注意:此函数仅检查路径结构,不处理 symlink。symlink 检查由 resolveCoveragePath 通过 realpath 完成。
12
+ */
13
+ export declare function validateCoveragePath(reportPath: string, sandboxRoot: string): void;
14
+ /**
15
+ * 在 sandbox 内安全地解析 coverage report 路径。
16
+ *
17
+ * 1. 先执行 validateCoveragePath 结构检查
18
+ * 2. join sandbox + reportPath
19
+ * 3. resolve 后确保不逃逸 sandbox
20
+ * 4. (调用方负责)通过 realpath 检查 symlink 逃逸
21
+ *
22
+ * 返回解析后的绝对路径。
23
+ */
24
+ export declare function resolveCoveragePath(reportPath: string, sandboxRoot: string): string;
25
+ export interface ParsedCoverageData {
26
+ actual: number;
27
+ metric: "lines";
28
+ }
29
+ /**
30
+ * 解析 istanbul-json-summary 格式的 coverage 报告。
31
+ *
32
+ * 抽取 `total.lines.pct`,执行严格的数值校验:
33
+ * - 拒绝缺少 `total.lines` 字段
34
+ * - 拒绝 `pct` 为 non-number、NaN、`"Unknown"` 等非数值
35
+ * - 拒绝 `pct` 超出 [0, 100] 范围
36
+ *
37
+ * 返回 ParsedCoverageData 或 null(解析失败/数据无效)。
38
+ */
39
+ export declare function parseCoverageReport(reportPath: string, metric: "lines"): Promise<ParsedCoverageData | null>;
40
+ export interface BuildCoverageEvidenceInput {
41
+ actual: number | null;
42
+ threshold: number;
43
+ metric: "lines";
44
+ headSha: string;
45
+ reportDigest: string | null;
46
+ }
47
+ /**
48
+ * 构建 CoverageEvidence。
49
+ *
50
+ * - actual >= threshold → pass
51
+ * - actual < threshold → fail
52
+ * - actual 为 null → pending(报告解析失败或不存在)
53
+ */
54
+ export declare function buildCoverageEvidence(input: BuildCoverageEvidenceInput): CoverageEvidence;
55
+ /**
56
+ * 简单的阈值校验(纯数字比较)。
57
+ */
58
+ export declare function checkCoverageThreshold(actual: number, threshold: number): boolean;
59
+ /**
60
+ * 对 coverage report 文件计算 digest。
61
+ * 用于绑定 report 内容与 evidence。
62
+ */
63
+ export declare function computeReportDigest(reportPath: string): Promise<string | null>;
64
+ /**
65
+ * 检查 symlink 是否逃逸 sandbox。
66
+ * 在 resolveCoveragePath 之后调用,对已解析路径做 realpath 检查。
67
+ *
68
+ * 返回 safe absolute path,或抛出错误。
69
+ */
70
+ export declare function verifySymlinkSafe(resolvedPath: string, sandboxRoot: string): Promise<string>;