sillyspec 3.11.7 → 3.11.9

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.
@@ -47,6 +47,24 @@ const fixedPrefix = [
47
47
  outputHint: '上下文摘要',
48
48
  optional: false
49
49
  },
50
+ {
51
+ name: '创建 worktree',
52
+ prompt: `为本次执行创建隔离的 git worktree。
53
+
54
+ ### 操作
55
+ 1. 运行 \`sillyspec worktree create <change-name>\`
56
+ 2. 记录输出的 worktree 路径
57
+ 3. 后续所有子代理的 cwd 设为该 worktree 路径
58
+ 4. 如果创建失败 → 报错并停止(不要在无隔离状态下继续)
59
+
60
+ ### 输出
61
+ worktree 路径 + 分支名
62
+
63
+ ### 完成后执行
64
+ sillyspec run execute --done --output "worktree 路径 + 分支名"`,
65
+ outputHint: 'worktree 路径 + 分支名',
66
+ optional: false
67
+ },
50
68
  {
51
69
  name: '确认执行范围',
52
70
  prompt: `解析任务,确认执行范围和确认模式。
@@ -149,7 +167,7 @@ const fixedSuffix = [
149
167
  prompt: `检查本轮执行产生的新知识。
150
168
 
151
169
  ### 操作
152
- 1. 检查 \`.sillyspec/knowledge/uncategorized.md\` 中待确认条目
170
+ 1. 检查 \.sillyspec/knowledge/uncategorized.md\` 中待确认条目
153
171
  2. 如有 → 提示用户审阅
154
172
  3. 用户确认后改为 [已确认],可归类到专题文件
155
173
 
@@ -162,18 +180,29 @@ const fixedSuffix = [
162
180
  name: '完成确认',
163
181
  prompt: `所有任务完成后的收尾。
164
182
 
165
- ### 操作
166
- 1. 建议下一步:
167
- - 运行 \`sillyspec run verify\` 进行验证(验证通过后才能归档)
168
- - 如果发现问题需要修复,先修复再验证
169
- - 用户也可以选择继续开发(不验证)
183
+ ### 操作(有 worktree)
184
+ 1. 运行 \`sillyspec worktree apply --check-only <change-name>\`
185
+ 2. 展示 diff 摘要(文件列表 + 变更统计)
186
+ 3. 检查结果说明(是否通过文件清单校验)
187
+ 4. 用户确认后运行 \`sillyspec worktree apply <change-name>\`
188
+ 5. apply 成功 → 自动 cleanup
189
+ 6. apply 失败 → 展示错误详情,用户选择重试或手动处理
190
+ 7. 如果用户不想 apply → 运行 \`sillyspec worktree cleanup <change-name>\` 丢弃
191
+ 8. 建议下一步:\`sillyspec run verify\`
192
+
193
+ ### 操作(无 worktree / --no-worktree 模式)
194
+ 1. 跳过 apply 和 cleanup 步骤(因为没有 worktree)
195
+ 2. 展示本次执行摘要
196
+ 3. 提示用户直接使用 \`git diff\` 查看变更
197
+ 4. 建议下一步:\`sillyspec run verify\`
170
198
 
171
199
  ### 输出
172
- 用户选择 + 下一步命令
200
+ apply 结果 + 下一步建议(或执行摘要)
173
201
 
174
202
  ### 注意
203
+ - 如果用户不想 apply → 运行 cleanup 丢弃
175
204
  - 完成后运行 \`sillyspec run execute --done\` 即可自动推进阶段`,
176
- outputHint: '用户选择',
205
+ outputHint: 'apply 结果',
177
206
  optional: false
178
207
  }
179
208
  ]
@@ -242,7 +271,7 @@ function parseWavesFromPlan(planContent) {
242
271
  /**
243
272
  * 为 Wave 生成 prompt(强制子代理执行)
244
273
  */
245
- function buildWavePrompt(wave, waveIndex, changeDir) {
274
+ function buildWavePrompt(wave, waveIndex, changeDir, worktreePath) {
246
275
  // 构建任务摘要(不再内联完整蓝图,减少上下文污染)
247
276
  const taskSummary = wave.tasks.map((t, ti) => {
248
277
  const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
@@ -260,6 +289,17 @@ function buildWavePrompt(wave, waveIndex, changeDir) {
260
289
  return s
261
290
  }).join('\n')
262
291
 
292
+ const worktreeSection = (worktreePath)
293
+ ? `
294
+ ### 工作目录
295
+ 你必须在以下 worktree 中工作(子代理的 cwd 设为此路径):
296
+ \`${worktreePath}\`
297
+
298
+ 不要在主工作区修改源码文件。所有代码变更只在 worktree 中进行。
299
+ 子代理的 cwd 参数设为 \`${worktreePath}\`。
300
+ `
301
+ : ''
302
+
263
303
  return `## Wave ${waveIndex}: 执行以下任务
264
304
 
265
305
  ## 执行方式(必须严格遵守)
@@ -272,6 +312,7 @@ function buildWavePrompt(wave, waveIndex, changeDir) {
272
312
  3. 勾选 plan.md 中的 checkbox
273
313
  4. 记录改动文件和测试结果
274
314
 
315
+ ${worktreeSection}
275
316
  ### 任务摘要(按需读取完整蓝图)
276
317
  为每个任务启动子代理时,**只需告知任务目标和蓝图文件路径,让子代理按需读取**:
277
318
 
@@ -315,9 +356,11 @@ ${taskList}
315
356
  /**
316
357
  * 动态构建 execute 步骤列表
317
358
  * @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
359
+ * @param {{ worktreePath?: string, noWorktree?: boolean }} options
318
360
  * @returns {Array} 步骤列表
319
361
  */
320
- export function buildExecuteSteps(planFilePath = null) {
362
+ export function buildExecuteSteps(planFilePath = null, options = {}) {
363
+ const noWorktree = !!options.noWorktree
321
364
  let waves
322
365
  let changeDir = null
323
366
 
@@ -336,10 +379,13 @@ export function buildExecuteSteps(planFilePath = null) {
336
379
  }
337
380
  }
338
381
 
382
+ // 尝试获取 worktree 路径(可能由前缀步骤创建)
383
+ const worktreePath = options.worktreePath || null
384
+
339
385
  const waveSteps = waves.map((wave, i) => ({
340
386
  name: `Wave ${i + 1} 执行`,
341
387
  mode: 'implementation',
342
- prompt: buildWavePrompt(wave, i + 1, changeDir),
388
+ prompt: buildWavePrompt(wave, i + 1, changeDir, worktreePath),
343
389
  outputHint: `Wave ${i + 1} 执行结果`,
344
390
  optional: false
345
391
  }))
@@ -20,7 +20,7 @@ export const definition = {
20
20
  ### 创建任务记录(必须执行)
21
21
  理解完任务后,立即创建记录文件:
22
22
  1. \`git config user.name\` 获取用户名
23
- 2. 无 \`--change\`:创建 \.sillyspec/quicklog/QUICKLOG-<git用户名>.md\`(已存在则追加),写入:
23
+ 2. 无 \`--change\`:创建 .sillyspec/quicklog/QUICKLOG-<git用户名>.md\`(已存在则追加),写入:
24
24
  \`\`\`
25
25
  ## YYYY-MM-DD HH:mm:ss — <一句话任务描述>
26
26
  状态:进行中
@@ -28,19 +28,40 @@ export const definition = {
28
28
  \`\`\`
29
29
  3. 有 \`--change\`:在 \`.sillyspec/changes/<change-name>/tasks.md\` 追加未勾选的 task
30
30
 
31
- 这样 Gate 检测到 \.sillyspec/\` 下有变更,就不会拦截后续的代码修改。
31
+ 这样 Gate 检测到 .sillyspec/\` 下有变更,就不会拦截后续的代码修改。
32
32
 
33
33
  ### 输出
34
34
  任务理解 + 上下文摘要 + quicklog 已创建`,
35
35
  outputHint: '任务理解',
36
36
  optional: false
37
37
  },
38
+ {
39
+ name: '创建 worktree',
40
+ prompt: `为本次 quick 任务创建隔离的 git worktree。
41
+
42
+ ### 操作
43
+ 1. 确定变更名(change name):
44
+ - 如携带 \`--change <变更名>\`,使用该变更名
45
+ - 否则,生成临时变更名:\`quick-<当前时间戳 YYYYMMDD-HHmmss>\`
46
+ 2. 运行 \`sillyspec worktree create <变更名>\`
47
+ 3. 记录输出的 worktree 路径(后续步骤需要使用)
48
+ 4. 如果创建失败 → 报错并停止(不要在无隔离状态下继续)
49
+
50
+ ### 输出
51
+ worktree 路径 + 变更名 + 分支名`,
52
+ outputHint: 'worktree 路径',
53
+ optional: false
54
+ },
38
55
  {
39
56
  name: '实现并验证',
40
57
  prompt: `实现任务。
41
58
 
59
+ ### 工作目录
60
+ 你必须在 worktree 中工作(将子代理的 cwd 设为上一步记录的 worktree 路径)。
61
+ 不要在主工作区修改源码文件。所有代码变更只在 worktree 中进行。
62
+
42
63
  ### 操作
43
- 1. 先读后写:调用已有方法前 \`cat\` 源文件确认签名,\`grep\` 确认方法存在
64
+ 1. 先读后写:调用已有方法前 \`cat\` 源文件确认签名,\`grep\` 确认方法存在(在 worktree 中读取)
44
65
  2. 写代码完成任务
45
66
  3. 如涉及逻辑变更,建议写单元测试验证(不强制,纯配置/文档/小改动可跳过)
46
67
  4. **不要编译!** 除非用户明确要求或改动量很大
@@ -55,6 +76,24 @@ export const definition = {
55
76
  outputHint: '实现摘要',
56
77
  optional: false
57
78
  },
79
+ {
80
+ name: 'apply 并 cleanup',
81
+ prompt: `将 worktree 中的变更应用到主工作区并清理。
82
+
83
+ ### 操作
84
+ 1. 运行 \`sillyspec worktree apply --check-only <变更名>\`
85
+ 2. 展示 diff 摘要(文件列表 + 变更统计)
86
+ 3. 展示检查结果(是否通过文件清单校验)
87
+ 4. 用户确认后运行 \`sillyspec worktree apply <变更名>\`
88
+ 5. apply 成功 → 自动 cleanup,进入下一步
89
+ 6. apply 失败 → 展示错误详情,用户选择重试或手动处理
90
+ 7. 如果用户不想 apply → 运行 \`sillyspec worktree cleanup <变更名>\` 丢弃变更
91
+
92
+ ### 输出
93
+ apply 结果 + 下一步建议`,
94
+ outputHint: 'apply 结果',
95
+ optional: false
96
+ },
58
97
  {
59
98
  name: '暂存和更新记录',
60
99
  prompt: `Git 暂存并更新任务记录。
@@ -4,6 +4,38 @@ export const definition = {
4
4
  description: '分析项目结构、约定和架构',
5
5
  auxiliary: true,
6
6
  steps: [
7
+ {
8
+ name: '探测项目结构并建议子项目',
9
+ prompt: `扫描项目顶层目录结构,自动发现可能的子项目,**需用户确认后才创建 projects 配置**。
10
+
11
+ ### 操作
12
+ 1. 列出项目顶层目录:\`ls -d */ 2>/dev/null | grep -v node_modules | grep -v '.git' | grep -v '.sillyspec'\`
13
+ 2. 对每个顶层目录,快速判断是否为独立项目(检查 package.json / pom.xml / build.gradle / pyproject.toml / go.mod 等构建文件)
14
+ 3. 对每个疑似独立项目,检测技术栈:\`cat <dir>/package.json 2>/dev/null | head -5\` 或类似
15
+ 4. 对比 \`.sillyspec/projects/\` 已有配置,找出未注册的子项目
16
+
17
+ ### 判断标准(满足任一即为子项目)
18
+ - 有独立的构建文件(package.json, pom.xml, build.gradle, pyproject.toml 等)
19
+ - 有独立的源码目录结构(src/, app/, lib/ 等)
20
+ - 有独立的测试目录(test/, tests/, __tests__/ 等)
21
+ - 不是 .git / node_modules / .sillyspec / dist / build 等工具目录
22
+
23
+ ### 输出格式
24
+ 列出发现的可能子项目列表,每个含:
25
+ - 目录名
26
+ - 技术栈(如 Next.js + TypeScript、FastAPI + Python)
27
+ - 是否已注册到 projects/
28
+ - 建议:注册 / 跳过
29
+
30
+ ### ⛔ 红线
31
+ - **不要自动创建 projects 配置文件**,只列出建议供用户确认
32
+ - **不要修改任何文件**,只做探测和报告
33
+
34
+ ### 输出
35
+ 子项目建议列表(含技术栈和注册状态)`,
36
+ outputHint: '子项目建议列表',
37
+ optional: false
38
+ },
7
39
  {
8
40
  name: '检查已有扫描文档和子项目列表',
9
41
  prompt: `检查已有扫描文档和子项目列表。
@@ -0,0 +1,266 @@
1
+ /**
2
+ * SillySpec applyWorktree — 将 worktree 中的变更应用到主工作区
3
+ *
4
+ * 流程:
5
+ * 1. 读取 meta.json 获取 baseHash
6
+ * 2. git diff --name-only baseHash 获取 worktree 中所有变更文件
7
+ * 3. 从 design.md 解析文件变更清单(无清单 = 允许所有)
8
+ * 4. 校验:变更文件 ⊆ 清单
9
+ * 5. 校验:主工作区文件 base hash 一致
10
+ * 6. --check-only 模式只输出检查结果
11
+ * 7. 非 checkOnly:生成 patch → apply --check → apply --3way
12
+ * 8. 成功后自动 cleanup
13
+ */
14
+
15
+ import { execSync } from 'child_process';
16
+ import { existsSync, unlinkSync, writeFileSync, mkdtempSync, rmSync } from 'fs';
17
+ import { join, resolve } from 'path';
18
+ import { tmpdir } from 'os';
19
+ import { WorktreeManager } from './worktree.js';
20
+ import { parseFileChangeList } from './change-list.js';
21
+
22
+ const CHANGES_REL = '.sillyspec/changes';
23
+
24
+ function git(cwd, args) {
25
+ return execSync(`git ${args}`, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
26
+ }
27
+
28
+ function gitQuiet(cwd, args) {
29
+ try {
30
+ return execSync(`git ${args}`, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 获取文件在 git 中的 blob hash(基于某个 commit/tree)
38
+ * @param {string} cwd - git 工作区路径
39
+ * @param {string} treeish - commit hash、分支等
40
+ * @param {string} filePath - 相对路径
41
+ * @returns {string|null} blob hash,文件不存在返回 null
42
+ */
43
+ function getFileBlobHash(cwd, treeish, filePath) {
44
+ return gitQuiet(cwd, `rev-parse ${treeish}:${filePath}`);
45
+ }
46
+
47
+ /**
48
+ * apply worktree 变更到主工作区
49
+ *
50
+ * @param {string} changeName - 变更名
51
+ * @param {{ cwd?: string, checkOnly?: boolean }} opts
52
+ * @returns {{
53
+ * ok: boolean,
54
+ * changedFiles: string[],
55
+ * extraFiles: string[],
56
+ * hashMismatchFiles: string[],
57
+ * patchPath: string|null,
58
+ * errors: string[]
59
+ * }}
60
+ */
61
+ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
62
+ const projectRoot = cwd || process.cwd();
63
+ const wm = new WorktreeManager({ cwd: projectRoot });
64
+ const meta = wm.getMeta(changeName);
65
+ const result = {
66
+ ok: false,
67
+ changedFiles: [],
68
+ extraFiles: [],
69
+ hashMismatchFiles: [],
70
+ patchPath: null,
71
+ errors: [],
72
+ };
73
+
74
+ // --- 1. 校验 worktree 存在 + meta.json 有效 ---
75
+ if (!meta) {
76
+ result.errors.push(`worktree not found: ${changeName}。meta.json 不存在或已损坏。`);
77
+ return result;
78
+ }
79
+
80
+ const { worktreePath, baseHash } = meta;
81
+
82
+ if (!existsSync(worktreePath)) {
83
+ result.errors.push(`worktree 目录不存在: ${worktreePath}`);
84
+ return result;
85
+ }
86
+
87
+ // --- 2. 获取变更文件列表 ---
88
+ // worktree 内修改可能没有 commit,用 git diff <baseHash>(比较 baseHash 到工作区内容)
89
+ // 同时检测 untracked 新文件(git diff 不包含 untracked)
90
+ let changedFiles;
91
+ try {
92
+ // tracked 文件的变更(modified/deleted)
93
+ const trackedRaw = git(worktreePath, `diff --name-only ${baseHash}`);
94
+ const trackedFiles = trackedRaw ? trackedRaw.split('\n').filter(Boolean) : [];
95
+
96
+ // untracked 新文件(baseHash 中不存在的文件)
97
+ const untrackedRaw = gitQuiet(worktreePath, `ls-files --others --exclude-standard`);
98
+ const untrackedFiles = untrackedRaw
99
+ ? untrackedRaw.split('\n').filter(Boolean).filter(f => !f.startsWith('.sillyspec/') && f !== 'meta.json')
100
+ : [];
101
+
102
+ changedFiles = [...new Set([...trackedFiles, ...untrackedFiles])];
103
+ } catch (e) {
104
+ result.errors.push(`获取变更文件列表失败: ${e.message}`);
105
+ return result;
106
+ }
107
+
108
+ result.changedFiles = changedFiles;
109
+
110
+ if (changedFiles.length === 0) {
111
+ // 没有变更
112
+ if (!checkOnly) {
113
+ wm.cleanup(changeName);
114
+ }
115
+ result.ok = true;
116
+ return result;
117
+ }
118
+
119
+ // --- 3. 解析 design.md 文件变更清单 ---
120
+ const designPath = join(projectRoot, CHANGES_REL, changeName, 'design.md');
121
+ const allowSet = parseFileChangeList(designPath);
122
+ const hasAllowList = allowSet.size > 0;
123
+
124
+ // --- 4. 校验:变更文件 ⊆ 清单(无清单则跳过)---
125
+ if (hasAllowList) {
126
+ for (const f of changedFiles) {
127
+ if (!allowSet.has(f)) {
128
+ result.extraFiles.push(f);
129
+ }
130
+ }
131
+ if (result.extraFiles.length > 0) {
132
+ result.errors.push(
133
+ `文件清单校验失败:以下变更文件不在 design.md 清单中:\n ${result.extraFiles.join('\n ')}`
134
+ );
135
+ return result;
136
+ }
137
+ }
138
+
139
+ // --- 5. 校验:主工作区文件 base hash 一致 ---
140
+ // 5a. 检查主工作区是否有未 commit 的脏文件(会影响 apply)
141
+ const mainDirtyRaw = gitQuiet(projectRoot, 'diff --name-only HEAD');
142
+ const mainDirtyFiles = mainDirtyRaw ? mainDirtyRaw.split('\n').filter(Boolean) : [];
143
+ if (mainDirtyFiles.length > 0) {
144
+ // 如果脏文件和本次 apply 的文件有交集 → 报错
145
+ const conflictDirty = mainDirtyFiles.filter(f => changedFiles.includes(f));
146
+ if (conflictDirty.length > 0) {
147
+ result.errors.push(
148
+ `主工作区有以下未 commit 的变更,会影响 apply:\n ${conflictDirty.join('\n ')}\n请先 commit 或 stash 这些变更。`
149
+ );
150
+ return result;
151
+ }
152
+ }
153
+
154
+ // 5b. 对比 worktree 的 baseHash 和主工作区 HEAD 中每个清单文件的 blob hash
155
+ const targetFiles = hasAllowList ? [...allowSet] : changedFiles;
156
+ for (const f of targetFiles) {
157
+ const wtBlob = getFileBlobHash(worktreePath, baseHash, f);
158
+ const mainBlob = getFileBlobHash(projectRoot, 'HEAD', f);
159
+
160
+ // 两者都为 null(文件在 base 时不存在)→ OK
161
+ if (wtBlob === null && mainBlob === null) continue;
162
+ // 两者一致 → OK
163
+ if (wtBlob === mainBlob) continue;
164
+ // 不一致 → 主工作区已被修改
165
+ result.hashMismatchFiles.push(f);
166
+ }
167
+
168
+ if (result.hashMismatchFiles.length > 0) {
169
+ result.errors.push(
170
+ `base hash 不一致:以下文件在主工作区已被修改:\n ${result.hashMismatchFiles.join('\n ')}`
171
+ );
172
+ return result;
173
+ }
174
+
175
+ // --- 6. checkOnly 模式:到此返回 ---
176
+ if (checkOnly) {
177
+ result.ok = true;
178
+ return result;
179
+ }
180
+
181
+ // --- 7. 生成 patch 并 apply ---
182
+ // 确定要包含在 patch 中的文件:有清单用清单交集,无清单用全部变更
183
+ const patchFiles = hasAllowList
184
+ ? [...allowSet].filter(f => changedFiles.includes(f))
185
+ : changedFiles;
186
+ const fileArgs = patchFiles.map(f => `-- ${f}`).join(' ');
187
+
188
+ // 创建临时文件
189
+ const tmpDir = mkdtempSync(join(tmpdir(), 'sillyspec-patch-'));
190
+ const patchPath = join(tmpDir, 'apply.patch');
191
+ result.patchPath = patchPath;
192
+
193
+ try {
194
+ let patchContent = '';
195
+
196
+ // 分 tracked 变更和 untracked 新文件生成 patch
197
+ const trackedFiles = patchFiles.filter(f => {
198
+ // untracked 文件在 baseHash 的 tree 中不存在
199
+ return gitQuiet(worktreePath, `cat-file -e ${baseHash}:${f}`) !== null;
200
+ });
201
+ const untrackedPatchFiles = patchFiles.filter(f => !trackedFiles.includes(f));
202
+
203
+ // tracked 文件:git diff baseHash
204
+ if (trackedFiles.length > 0) {
205
+ const trackedArgs = trackedFiles.map(f => `-- ${f}`).join(' ');
206
+ patchContent += execSync(
207
+ `git diff --binary ${baseHash} ${trackedArgs}`,
208
+ { cwd: worktreePath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
209
+ );
210
+ }
211
+
212
+ // untracked 新文件:git add 到 index,git diff --cached,然后 reset
213
+ if (untrackedPatchFiles.length > 0) {
214
+ const addArgs = untrackedPatchFiles.map(f => `-- ${f}`).join(' ');
215
+ git(worktreePath, `add ${addArgs}`);
216
+ try {
217
+ patchContent += execSync(
218
+ `git diff --binary --cached ${untrackedPatchFiles.map(f => `-- ${f}`).join(' ')}`,
219
+ { cwd: worktreePath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
220
+ );
221
+ } finally {
222
+ // 重置 index(不保留 staged 状态)
223
+ gitQuiet(worktreePath, `reset HEAD -- ${addArgs}`);
224
+ }
225
+ }
226
+
227
+ if (!patchContent.trim()) {
228
+ // patch 为空(清单中部分文件可能没实际变更)
229
+ result.ok = true;
230
+ rmSync(tmpDir, { recursive: true, force: true });
231
+ return result;
232
+ }
233
+
234
+ writeFileSync(patchPath, patchContent);
235
+
236
+ // apply --check 预检
237
+ try {
238
+ git(projectRoot, `apply --check ${patchPath}`);
239
+ } catch (e) {
240
+ result.errors.push(`patch 预检失败: ${e.message}`);
241
+ return result;
242
+ }
243
+
244
+ // apply --3way 正式应用
245
+ try {
246
+ git(projectRoot, `apply --3way ${patchPath}`);
247
+ } catch (e) {
248
+ result.errors.push(`patch apply 失败: ${e.message}`);
249
+ return result;
250
+ }
251
+
252
+ result.ok = true;
253
+
254
+ // --- 8. 成功后自动 cleanup ---
255
+ wm.cleanup(changeName);
256
+
257
+ } catch (e) {
258
+ result.errors.push(`patch 生成/应用异常: ${e.message}`);
259
+ return result;
260
+ } finally {
261
+ // 清理临时目录
262
+ try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
263
+ }
264
+
265
+ return result;
266
+ }