@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,207 @@
1
+ import { readFile, lstat, realpath } from "node:fs/promises";
2
+ import { resolve, isAbsolute, join, normalize } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ // ─── 常量 ───
5
+ const DANGEROUS_PATH = /[\x00-\x1f\x7f]/;
6
+ // ─── 路径安全校验 ───
7
+ /**
8
+ * 检验 coverage report 路径的安全性。
9
+ *
10
+ * 规则:
11
+ * - 拒绝空路径
12
+ * - 拒绝绝对路径
13
+ * - 拒绝包含 null 字节等危险字符的路径
14
+ * - 拒绝包含 `..` 路径遍历的路径
15
+ *
16
+ * 注意:此函数仅检查路径结构,不处理 symlink。symlink 检查由 resolveCoveragePath 通过 realpath 完成。
17
+ */
18
+ export function validateCoveragePath(reportPath, sandboxRoot) {
19
+ if (!reportPath || reportPath.trim() === "") {
20
+ throw new Error("Coverage report path is empty");
21
+ }
22
+ if (DANGEROUS_PATH.test(reportPath)) {
23
+ throw new Error(`Coverage report path contains dangerous characters: ${reportPath}`);
24
+ }
25
+ const normalized = normalize(reportPath);
26
+ if (isAbsolute(normalized)) {
27
+ throw new Error("Coverage report path must be relative, got absolute path");
28
+ }
29
+ // 拒绝路径穿越:以 .. 开头或包含 /../
30
+ const segments = normalized.split("/");
31
+ if (segments.includes("..") || segments.includes("..\\")) {
32
+ throw new Error(`Coverage report path escapes sandbox: ${reportPath}`);
33
+ }
34
+ // 拒绝 Windows 风格的路径穿越
35
+ const winSegments = normalized.split("\\");
36
+ if (winSegments.includes("..")) {
37
+ throw new Error(`Coverage report path escapes sandbox: ${reportPath}`);
38
+ }
39
+ }
40
+ /**
41
+ * 在 sandbox 内安全地解析 coverage report 路径。
42
+ *
43
+ * 1. 先执行 validateCoveragePath 结构检查
44
+ * 2. join sandbox + reportPath
45
+ * 3. resolve 后确保不逃逸 sandbox
46
+ * 4. (调用方负责)通过 realpath 检查 symlink 逃逸
47
+ *
48
+ * 返回解析后的绝对路径。
49
+ */
50
+ export function resolveCoveragePath(reportPath, sandboxRoot) {
51
+ // 步骤 1: 结构检查
52
+ validateCoveragePath(reportPath, sandboxRoot);
53
+ // 步骤 2: 拼接并解析
54
+ const sandboxNorm = resolve(sandboxRoot);
55
+ const resolved = resolve(join(sandboxNorm, reportPath));
56
+ // 步骤 3: 确保未逃逸 sandbox
57
+ if (!resolved.startsWith(sandboxNorm + "/") && resolved !== sandboxNorm) {
58
+ throw new Error(`Coverage report path resolves outside sandbox: ${reportPath} → ${resolved}`);
59
+ }
60
+ return resolved;
61
+ }
62
+ /**
63
+ * 解析 istanbul-json-summary 格式的 coverage 报告。
64
+ *
65
+ * 抽取 `total.lines.pct`,执行严格的数值校验:
66
+ * - 拒绝缺少 `total.lines` 字段
67
+ * - 拒绝 `pct` 为 non-number、NaN、`"Unknown"` 等非数值
68
+ * - 拒绝 `pct` 超出 [0, 100] 范围
69
+ *
70
+ * 返回 ParsedCoverageData 或 null(解析失败/数据无效)。
71
+ */
72
+ export async function parseCoverageReport(reportPath, metric) {
73
+ let raw;
74
+ try {
75
+ raw = await readFile(reportPath, "utf-8");
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ let parsed;
81
+ try {
82
+ parsed = JSON.parse(raw);
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
88
+ return null;
89
+ }
90
+ const report = parsed;
91
+ // 检查 total 对象
92
+ const total = report.total;
93
+ if (!total || typeof total !== "object") {
94
+ return null;
95
+ }
96
+ // 检查 lines 对象
97
+ const lines = total.lines;
98
+ if (!lines || typeof lines !== "object") {
99
+ return null;
100
+ }
101
+ // 提取并校验 pct
102
+ const pct = lines.pct;
103
+ // 拒绝 "Unknown" 字符串值
104
+ if (typeof pct === "string") {
105
+ return null;
106
+ }
107
+ // 拒绝非 number 类型
108
+ if (typeof pct !== "number") {
109
+ return null;
110
+ }
111
+ // 拒绝 NaN
112
+ if (isNaN(pct)) {
113
+ return null;
114
+ }
115
+ // 拒绝无穷大
116
+ if (!isFinite(pct)) {
117
+ return null;
118
+ }
119
+ // 拒绝范围外数值
120
+ if (pct < 0 || pct > 100) {
121
+ return null;
122
+ }
123
+ return {
124
+ actual: pct,
125
+ metric,
126
+ };
127
+ }
128
+ /**
129
+ * 构建 CoverageEvidence。
130
+ *
131
+ * - actual >= threshold → pass
132
+ * - actual < threshold → fail
133
+ * - actual 为 null → pending(报告解析失败或不存在)
134
+ */
135
+ export function buildCoverageEvidence(input) {
136
+ let status;
137
+ let summary;
138
+ if (input.actual === null) {
139
+ status = "pending";
140
+ summary = `Coverage report not available or could not be parsed for head ${input.headSha.slice(0, 7)}`;
141
+ }
142
+ else if (input.actual >= input.threshold) {
143
+ status = "pass";
144
+ summary = `Coverage ${input.actual}% meets threshold ${input.threshold}% (${input.metric}) at head ${input.headSha.slice(0, 7)}`;
145
+ }
146
+ else {
147
+ status = "fail";
148
+ summary = `Coverage ${input.actual}% below threshold ${input.threshold}% (${input.metric}) at head ${input.headSha.slice(0, 7)}`;
149
+ }
150
+ return {
151
+ status,
152
+ headSha: input.headSha,
153
+ actual: input.actual,
154
+ threshold: input.threshold,
155
+ metric: input.metric,
156
+ reportDigest: input.reportDigest,
157
+ summary,
158
+ };
159
+ }
160
+ /**
161
+ * 简单的阈值校验(纯数字比较)。
162
+ */
163
+ export function checkCoverageThreshold(actual, threshold) {
164
+ return actual >= threshold;
165
+ }
166
+ /**
167
+ * 对 coverage report 文件计算 digest。
168
+ * 用于绑定 report 内容与 evidence。
169
+ */
170
+ export async function computeReportDigest(reportPath) {
171
+ try {
172
+ const content = await readFile(reportPath);
173
+ const hash = createHash("sha256");
174
+ hash.update(content);
175
+ return hash.digest("hex");
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ }
181
+ /**
182
+ * 检查 symlink 是否逃逸 sandbox。
183
+ * 在 resolveCoveragePath 之后调用,对已解析路径做 realpath 检查。
184
+ *
185
+ * 返回 safe absolute path,或抛出错误。
186
+ */
187
+ export async function verifySymlinkSafe(resolvedPath, sandboxRoot) {
188
+ const sandboxNorm = resolve(sandboxRoot);
189
+ let lstatResult;
190
+ try {
191
+ lstatResult = await lstat(resolvedPath);
192
+ }
193
+ catch {
194
+ // 文件不存在,由调用方处理
195
+ return resolvedPath;
196
+ }
197
+ if (lstatResult.isSymbolicLink()) {
198
+ const real = await realpath(resolvedPath);
199
+ const realNorm = resolve(real);
200
+ if (!realNorm.startsWith(sandboxNorm + "/") && realNorm !== sandboxNorm) {
201
+ throw new Error(`Symlink escapes sandbox: ${resolvedPath} → ${realNorm}`);
202
+ }
203
+ return realNorm;
204
+ }
205
+ return resolvedPath;
206
+ }
207
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/flowrun/coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { VersionedDigest } from "./types.js";
2
+ export interface DigestOptions {
3
+ testFilePatterns: string[];
4
+ implementationFilePatterns: string[];
5
+ generatedArtifactPatterns: string[];
6
+ }
7
+ /**
8
+ * 计算 worktree 的 Canonical Content Digest。
9
+ *
10
+ * 规则:
11
+ * 1. repo-relative paths,按 UTF-8 字节序排序
12
+ * 2. 纳入文件类型(含可执行位)、原始字节、删除标记
13
+ * 3. 拒绝逃逸 worktree 的 symlink
14
+ * 4. 忽略 .git/、node_modules/、coverage/、dist/、policy 声明的 generated artifacts
15
+ * 5. tracked + policy 范围内 untracked 文件均纳入
16
+ */
17
+ export declare function computeWorkspaceDigest(root: string, options: DigestOptions): Promise<VersionedDigest>;
@@ -0,0 +1,301 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, lstat, readdir, realpath } from "node:fs/promises";
3
+ import { join, relative, resolve, sep, isAbsolute } from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ // ─── 常量 ───
6
+ /** 始终忽略的目录 */
7
+ const ALWAYS_IGNORED = new Set([
8
+ ".git",
9
+ "node_modules",
10
+ "coverage",
11
+ "dist",
12
+ ".nyc_output",
13
+ ]);
14
+ /** 始终忽略的前缀(目录 + 分隔符) */
15
+ function isAlwaysIgnored(relPath) {
16
+ const parts = relPath.split(sep);
17
+ if (parts.length === 0)
18
+ return false;
19
+ const top = parts[0];
20
+ if (!top)
21
+ return false;
22
+ return ALWAYS_IGNORED.has(top);
23
+ }
24
+ /** 危险字符正则:拒绝可能逃逸路径的字符 */
25
+ const DANGEROUS_PATH = /[\x00-\x1f\x7f]/;
26
+ // ─── Git 辅助 ───
27
+ function runGit(args, cwd) {
28
+ try {
29
+ const output = execFileSync("git", args, {
30
+ cwd,
31
+ encoding: "utf-8",
32
+ env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1" },
33
+ maxBuffer: 10 * 1024 * 1024,
34
+ });
35
+ return output.trim().split("\n").filter(Boolean);
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
41
+ /** 获取 Git 已跟踪的文件列表(repo-relative paths) */
42
+ function getTrackedFiles(cwd) {
43
+ const lines = runGit(["ls-files", "--cached", "-z"], cwd);
44
+ if (lines.length === 0)
45
+ return new Set();
46
+ // -z 输出的行以 null 分隔,但 execFileSync 返回时 null 被转为换行
47
+ // 实际用 -z 时输出用 \0 分隔,这里按 \0 分割
48
+ const raw = execFileSync("git", ["ls-files", "--cached", "-z"], {
49
+ cwd, encoding: "utf-8",
50
+ env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1" },
51
+ maxBuffer: 10 * 1024 * 1024,
52
+ });
53
+ return new Set(raw.split("\0").filter(Boolean));
54
+ }
55
+ /** 获取已跟踪但已删除的文件列表 */
56
+ function getDeletedFiles(cwd) {
57
+ try {
58
+ const raw = execFileSync("git", ["ls-files", "--deleted", "-z"], {
59
+ cwd, encoding: "utf-8",
60
+ env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1" },
61
+ maxBuffer: 10 * 1024 * 1024,
62
+ });
63
+ return new Set(raw.split("\0").filter(Boolean));
64
+ }
65
+ catch {
66
+ return new Set();
67
+ }
68
+ }
69
+ // ─── 模式匹配 ───
70
+ /**
71
+ * 简单的 glob 匹配(支持 **、*)
72
+ * 不做完整 micromatch,仅覆盖常见 pattern
73
+ */
74
+ function matchPattern(filePath, pattern) {
75
+ // 将路径分隔符统一为 /
76
+ const normalized = filePath.split(sep).join("/");
77
+ // 构建正则
78
+ let regexStr = "";
79
+ let i = 0;
80
+ while (i < pattern.length) {
81
+ if (pattern[i] === "*" && pattern[i + 1] === "*") {
82
+ // **:零或多个路径段
83
+ i += 2;
84
+ // 跳过可选的 /(**/ 匹配零或多个目录)
85
+ if (i < pattern.length && pattern[i] === "/") {
86
+ regexStr += "(.*/)?";
87
+ i++;
88
+ }
89
+ else {
90
+ // 独立的 **(末尾):匹配所有
91
+ regexStr += ".*";
92
+ }
93
+ }
94
+ else if (pattern[i] === "*") {
95
+ // *:匹配单段内的任意字符(除 /)
96
+ regexStr += "[^/]*";
97
+ i++;
98
+ }
99
+ else if (pattern[i] === ".") {
100
+ regexStr += "\\.";
101
+ i++;
102
+ }
103
+ else if (pattern[i] === "?") {
104
+ regexStr += "[^/]";
105
+ i++;
106
+ }
107
+ else {
108
+ // 转义正则特殊字符
109
+ if ("+^$(){}[]|\\".includes(pattern[i])) {
110
+ regexStr += "\\" + pattern[i];
111
+ }
112
+ else {
113
+ regexStr += pattern[i];
114
+ }
115
+ i++;
116
+ }
117
+ }
118
+ const regex = new RegExp(`^${regexStr}$`);
119
+ return regex.test(normalized);
120
+ }
121
+ function matchesAnyPattern(filePath, patterns) {
122
+ return patterns.some(p => matchPattern(filePath, p));
123
+ }
124
+ // ─── 路径校验 ───
125
+ function validateRelPath(relPath) {
126
+ // 拒绝包含危险字符的路径
127
+ if (DANGEROUS_PATH.test(relPath)) {
128
+ throw new Error(`Dangerous characters in path: ${relPath}`);
129
+ }
130
+ // 拒绝以 .. 开头的路径
131
+ const normalized = relPath.split(sep).join("/");
132
+ if (normalized.startsWith("..") || normalized.includes("/../")) {
133
+ throw new Error(`Path escapes worktree: ${relPath}`);
134
+ }
135
+ if (isAbsolute(relPath)) {
136
+ throw new Error(`Path must be relative: ${relPath}`);
137
+ }
138
+ }
139
+ // ─── 文件扫描 ───
140
+ /**
141
+ * 递归列出目录下所有文件(repo-relative paths)
142
+ * 忽略 always-ignored 目录
143
+ */
144
+ async function scanDir(root, subDir = "", results = []) {
145
+ const fullPath = subDir ? join(root, subDir) : root;
146
+ let dirents;
147
+ try {
148
+ dirents = await readdir(fullPath, { withFileTypes: true });
149
+ }
150
+ catch {
151
+ return results;
152
+ }
153
+ for (const entry of dirents) {
154
+ const relPath = subDir ? `${subDir}${sep}${entry.name}` : entry.name;
155
+ const relPathSlash = relPath.split(sep).join("/");
156
+ // 跳过 always-ignored 顶层目录
157
+ if (isAlwaysIgnored(relPath))
158
+ continue;
159
+ if (entry.isDirectory()) {
160
+ await scanDir(root, relPath, results);
161
+ }
162
+ else if (entry.isFile() || entry.isSymbolicLink()) {
163
+ results.push(relPathSlash);
164
+ }
165
+ }
166
+ return results;
167
+ }
168
+ async function collectFileEntries(root, tracked, deleted, untrackedPaths, generatedPatterns) {
169
+ const entries = [];
170
+ // 处理已跟踪文件
171
+ for (const relPath of tracked) {
172
+ validateRelPath(relPath);
173
+ // 跳过生成的 artifacts
174
+ if (matchesAnyPattern(relPath, generatedPatterns))
175
+ continue;
176
+ // 跳过 always-ignored 目录
177
+ if (isAlwaysIgnored(relPath))
178
+ continue;
179
+ // 已删除文件:使用删除标记
180
+ if (deleted.has(relPath)) {
181
+ entries.push({ path: relPath, mode: 0, content: null });
182
+ continue;
183
+ }
184
+ const absPath = join(root, relPath);
185
+ try {
186
+ const fileLstat = await lstat(absPath);
187
+ // 检查 symlink 是否逃逸
188
+ if (fileLstat.isSymbolicLink()) {
189
+ const resolved = await realpath(absPath);
190
+ const resolvedNorm = resolve(resolved);
191
+ const rootNorm = resolve(root);
192
+ if (!resolvedNorm.startsWith(rootNorm + sep) && resolvedNorm !== rootNorm) {
193
+ throw new Error(`Symlink escapes worktree: ${relPath} → ${resolved}`);
194
+ }
195
+ }
196
+ if (fileLstat.isFile() || fileLstat.isSymbolicLink()) {
197
+ const content = await readFile(absPath);
198
+ // mode: 只保留权限位(低 9 位),用于区分可执行位
199
+ const mode = fileLstat.mode & 0o777;
200
+ entries.push({ path: relPath, mode, content });
201
+ }
202
+ // 目录、设备文件等跳过
203
+ }
204
+ catch (err) {
205
+ if (err instanceof Error && err.message.startsWith("Symlink escapes")) {
206
+ throw err;
207
+ }
208
+ // 文件不存在(可能被外部删除),添加删除标记
209
+ entries.push({ path: relPath, mode: 0, content: null });
210
+ }
211
+ }
212
+ // 处理 policy 范围内的 untracked 文件
213
+ for (const relPath of untrackedPaths) {
214
+ validateRelPath(relPath);
215
+ // 跳过已跟踪文件的重复
216
+ if (tracked.has(relPath))
217
+ continue;
218
+ // 跳过 always-ignored
219
+ if (isAlwaysIgnored(relPath))
220
+ continue;
221
+ // 跳过生成的 artifacts
222
+ if (matchesAnyPattern(relPath, generatedPatterns))
223
+ continue;
224
+ const absPath = join(root, relPath);
225
+ try {
226
+ const fileLstat = await lstat(absPath);
227
+ if (fileLstat.isSymbolicLink()) {
228
+ const resolved = await realpath(absPath);
229
+ const resolvedNorm = resolve(resolved);
230
+ const rootNorm = resolve(root);
231
+ if (!resolvedNorm.startsWith(rootNorm + sep) && resolvedNorm !== rootNorm) {
232
+ throw new Error(`Symlink escapes worktree: ${relPath} → ${resolved}`);
233
+ }
234
+ }
235
+ if (fileLstat.isFile() || fileLstat.isSymbolicLink()) {
236
+ const content = await readFile(absPath);
237
+ const mode = fileLstat.mode & 0o777;
238
+ entries.push({ path: relPath, mode, content });
239
+ }
240
+ }
241
+ catch (err) {
242
+ if (err instanceof Error && err.message.startsWith("Symlink escapes")) {
243
+ throw err;
244
+ }
245
+ // 无法读取的文件跳过
246
+ }
247
+ }
248
+ return entries;
249
+ }
250
+ // ─── 主函数 ───
251
+ /**
252
+ * 计算 worktree 的 Canonical Content Digest。
253
+ *
254
+ * 规则:
255
+ * 1. repo-relative paths,按 UTF-8 字节序排序
256
+ * 2. 纳入文件类型(含可执行位)、原始字节、删除标记
257
+ * 3. 拒绝逃逸 worktree 的 symlink
258
+ * 4. 忽略 .git/、node_modules/、coverage/、dist/、policy 声明的 generated artifacts
259
+ * 5. tracked + policy 范围内 untracked 文件均纳入
260
+ */
261
+ export async function computeWorkspaceDigest(root, options) {
262
+ const rootNorm = resolve(root);
263
+ // 1. 获取 Git 跟踪状态
264
+ const tracked = getTrackedFiles(rootNorm);
265
+ const deleted = getDeletedFiles(rootNorm);
266
+ // 2. 扫描文件系统获取 policy 范围内的 untracked 文件
267
+ const allFiles = await scanDir(rootNorm);
268
+ const includedPatterns = [...options.testFilePatterns, ...options.implementationFilePatterns];
269
+ const untrackedPaths = new Set(allFiles.filter(f => !tracked.has(f) && matchesAnyPattern(f, includedPatterns)));
270
+ // 3. 收集文件条目
271
+ const entries = await collectFileEntries(rootNorm, tracked, deleted, untrackedPaths, options.generatedArtifactPatterns);
272
+ // 4. 按 UTF-8 字节序排序
273
+ entries.sort((a, b) => {
274
+ const bufA = Buffer.from(a.path, "utf-8");
275
+ const bufB = Buffer.from(b.path, "utf-8");
276
+ return Buffer.compare(bufA, bufB);
277
+ });
278
+ // 5. 构建 canonical 内容
279
+ const hash = createHash("sha256");
280
+ for (const entry of entries) {
281
+ // path
282
+ hash.update(entry.path);
283
+ hash.update("\0");
284
+ // mode (8 进制字符串)
285
+ hash.update(entry.mode.toString(8));
286
+ hash.update("\0");
287
+ // content 或删除标记
288
+ if (entry.content === null) {
289
+ hash.update("\0DELETED\0");
290
+ }
291
+ else {
292
+ hash.update(entry.content);
293
+ }
294
+ hash.update("\0");
295
+ }
296
+ return {
297
+ algorithm: "sha256-content-v1",
298
+ value: hash.digest("hex"),
299
+ };
300
+ }
301
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/flowrun/digest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { TddPolicy, TddEvidence, AcceptanceCriterion } from "./types.js";
2
+ /**
3
+ * evaluateTddCompliance 的结果类型。
4
+ *
5
+ * - status = pass → 合规(advisory 下缺口记 warning)
6
+ * - status = fail → 不合规,阻断
7
+ * - status = waived → 豁免(bypass 模式下替代验证全部通过)
8
+ */
9
+ export interface EvaluationResult {
10
+ status: "pass" | "fail" | "waived";
11
+ warnings: string[];
12
+ }
13
+ /**
14
+ * 判定 TDD 模式是否合规的纯函数。
15
+ *
16
+ * 规则(参见 Spec Section 4.3):
17
+ *
18
+ * **strict**:
19
+ * - 必须至少有一个 verification=tDD 的 criterion
20
+ * - 每个 TDD criterion 都必须有 status=pass 的 cycle 覆盖
21
+ * - verification=regression 的 criterion 由 final regression 覆盖
22
+ * - verification=manual 不允许在 strict 模式下使用
23
+ * - final regression 和 final verification 必须 pass
24
+ *
25
+ * **relaxed**:
26
+ * - 仅要求 final regression 为 pass,不检查 cycle
27
+ * - final verification 必须 pass
28
+ *
29
+ * **bypass**:
30
+ * - 必须有 exception 且其 alternativeValidation 全部完成(evidence 中对应 status=pass)
31
+ * - final verification 仍必须 pass
32
+ * - 不要求 runner / cycle / regression
33
+ *
34
+ * **enforcement**:
35
+ * - advisory:缺失 evidence 仅产生 warning,不阻断(仍返回 pass/waived)
36
+ * - runtime:缺失 evidence 直接阻断(返回 fail)
37
+ */
38
+ export declare function evaluateTddCompliance(policy: TddPolicy, evidence: TddEvidence, criteria: AcceptanceCriterion[]): EvaluationResult;