@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.
Files changed (84) hide show
  1. package/README.md +57 -54
  2. package/README.zh.md +91 -0
  3. package/assets/agents/dev-lifecycle.md +28 -22
  4. package/assets/agents/team/architect.md +14 -0
  5. package/assets/agents/team/backend.md +14 -0
  6. package/assets/agents/team/frontend.md +14 -0
  7. package/assets/agents/team/goal-verify.md +54 -0
  8. package/assets/agents/team/reviewer.md +10 -0
  9. package/assets/skills/flow-code/SKILL.md +13 -0
  10. package/assets/skills/flow-review/SKILL.md +7 -0
  11. package/assets/skills/flow-tasks/SKILL.md +42 -0
  12. package/assets/skills/flow-tdd/SKILL.md +208 -0
  13. package/dist/flowrun/adapter.d.ts +49 -0
  14. package/dist/flowrun/adapter.js +293 -0
  15. package/dist/flowrun/adapter.js.map +1 -0
  16. package/dist/flowrun/ci.d.ts +64 -0
  17. package/dist/flowrun/ci.js +184 -0
  18. package/dist/flowrun/ci.js.map +1 -0
  19. package/dist/flowrun/coverage.d.ts +70 -0
  20. package/dist/flowrun/coverage.js +207 -0
  21. package/dist/flowrun/coverage.js.map +1 -0
  22. package/dist/flowrun/digest.d.ts +17 -0
  23. package/dist/flowrun/digest.js +301 -0
  24. package/dist/flowrun/digest.js.map +1 -0
  25. package/dist/flowrun/evaluator.d.ts +38 -0
  26. package/dist/flowrun/evaluator.js +185 -0
  27. package/dist/flowrun/evaluator.js.map +1 -0
  28. package/dist/flowrun/gate.d.ts +20 -0
  29. package/dist/flowrun/gate.js +40 -3
  30. package/dist/flowrun/gate.js.map +1 -1
  31. package/dist/flowrun/github.d.ts +13 -9
  32. package/dist/flowrun/github.js +46 -26
  33. package/dist/flowrun/github.js.map +1 -1
  34. package/dist/flowrun/index.d.ts +12 -4
  35. package/dist/flowrun/index.js +11 -3
  36. package/dist/flowrun/index.js.map +1 -1
  37. package/dist/flowrun/merge.d.ts +35 -2
  38. package/dist/flowrun/merge.js +72 -8
  39. package/dist/flowrun/merge.js.map +1 -1
  40. package/dist/flowrun/migration.d.ts +11 -0
  41. package/dist/flowrun/migration.js +276 -0
  42. package/dist/flowrun/migration.js.map +1 -0
  43. package/dist/flowrun/pr.d.ts +132 -0
  44. package/dist/flowrun/pr.js +343 -0
  45. package/dist/flowrun/pr.js.map +1 -0
  46. package/dist/flowrun/state.d.ts +79 -0
  47. package/dist/flowrun/state.js +363 -0
  48. package/dist/flowrun/state.js.map +1 -0
  49. package/dist/flowrun/transitions.d.ts +66 -0
  50. package/dist/flowrun/transitions.js +168 -0
  51. package/dist/flowrun/transitions.js.map +1 -0
  52. package/dist/flowrun/types.d.ts +279 -4
  53. package/dist/flowrun/types.js +2 -1
  54. package/dist/flowrun/types.js.map +1 -1
  55. package/dist/flowrun/validator.d.ts +15 -5
  56. package/dist/flowrun/validator.js +117 -5
  57. package/dist/flowrun/validator.js.map +1 -1
  58. package/dist/plugin/agents.d.ts +4 -0
  59. package/dist/plugin/agents.js +19 -0
  60. package/dist/plugin/agents.js.map +1 -1
  61. package/dist/plugin/broker.d.ts +73 -0
  62. package/dist/plugin/broker.js +140 -0
  63. package/dist/plugin/broker.js.map +1 -0
  64. package/dist/plugin/flow-pr-tool.d.ts +37 -0
  65. package/dist/plugin/flow-pr-tool.js +176 -0
  66. package/dist/plugin/flow-pr-tool.js.map +1 -0
  67. package/dist/plugin/goal.d.ts +39 -1
  68. package/dist/plugin/goal.js +80 -1
  69. package/dist/plugin/goal.js.map +1 -1
  70. package/dist/plugin/prompt-lint.js +129 -0
  71. package/dist/plugin/prompt-lint.js.map +1 -1
  72. package/dist/plugin/server.d.ts +8 -0
  73. package/dist/plugin/server.js +306 -11
  74. package/dist/plugin/server.js.map +1 -1
  75. package/dist/plugin/shell.d.ts +37 -0
  76. package/dist/plugin/shell.js +144 -0
  77. package/dist/plugin/shell.js.map +1 -0
  78. package/dist/plugin/tdd-tool.d.ts +62 -0
  79. package/dist/plugin/tdd-tool.js +336 -0
  80. package/dist/plugin/tdd-tool.js.map +1 -0
  81. package/dist/util/gh.d.ts +8 -1
  82. package/dist/util/gh.js +10 -2
  83. package/dist/util/gh.js.map +1 -1
  84. package/package.json +2 -1
@@ -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>;
@@ -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>;