clawt 3.10.4 → 3.10.6
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/AGENTS.md +16 -0
- package/dist/index.js +228 -86
- package/dist/postinstall.js +27 -0
- package/docs/create.md +1 -0
- package/docs/list.md +21 -10
- package/docs/merge.md +1 -0
- package/docs/remove.md +2 -0
- package/docs/spec.md +4 -1
- package/docs/status.md +9 -1
- package/docs/superpowers/findings/2026-06-01-sync-validate-diverged-findings.md +203 -0
- package/docs/superpowers/findings/2026-06-09-worktree-base-branch-findings.md +58 -0
- package/docs/superpowers/plans/2026-06-01-validate-ignored-files-conflict.md +412 -0
- package/docs/superpowers/plans/2026-06-09-worktree-base-branch.md +386 -0
- package/docs/superpowers/specs/2026-06-01-validate-ignored-files-conflict-design.md +76 -0
- package/docs/superpowers/specs/2026-06-09-worktree-base-branch-design.md +169 -0
- package/docs/validate.md +42 -5
- package/package.json +1 -1
- package/src/commands/list.ts +5 -3
- package/src/commands/merge.ts +1 -1
- package/src/commands/remove.ts +3 -0
- package/src/commands/status.ts +5 -0
- package/src/constants/messages/validate.ts +17 -0
- package/src/types/status.ts +2 -0
- package/src/types/worktree.ts +12 -0
- package/src/utils/formatter.ts +22 -0
- package/src/utils/git-core.ts +23 -0
- package/src/utils/index.ts +4 -2
- package/src/utils/interactive-panel-render.ts +6 -3
- package/src/utils/validate-core.ts +52 -0
- package/src/utils/worktree-metadata.ts +82 -0
- package/src/utils/worktree.ts +29 -10
- package/tests/helpers/fixtures.ts +1 -0
- package/tests/unit/commands/cover-validate.test.ts +4 -4
- package/tests/unit/commands/create.test.ts +3 -3
- package/tests/unit/commands/list.test.ts +66 -3
- package/tests/unit/commands/merge.test.ts +1 -1
- package/tests/unit/commands/remove.test.ts +24 -18
- package/tests/unit/commands/resume.test.ts +21 -21
- package/tests/unit/commands/run.test.ts +17 -17
- package/tests/unit/commands/status.test.ts +85 -10
- package/tests/unit/commands/sync.test.ts +4 -4
- package/tests/unit/commands/validate.test.ts +1 -1
- package/tests/unit/utils/git-core.test.ts +43 -0
- package/tests/unit/utils/interactive-panel-render.test.ts +124 -0
- package/tests/unit/utils/validate-core.test.ts +60 -0
- package/tests/unit/utils/worktree-matcher.test.ts +2 -2
- package/tests/unit/utils/worktree-metadata.test.ts +91 -0
- package/tests/unit/utils/worktree.test.ts +65 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# validate 被忽略文件冲突检测 Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** 在 validate 的 patch apply 之前检测被 `.gitignore` 忽略的残留文件(幽灵文件),输出清晰的错误提示和清理命令,替代当前误导性的 "diverged too far" 信息。
|
|
6
|
+
|
|
7
|
+
**Architecture:** 在 `migrateChangesViaPatch` 中,`git apply` 之前新增检测步骤:通过 `git diff --name-only` 获取 patch 文件列表 → `git check-ignore` 筛选被忽略的文件 → `fs.existsSync` 确认物理存在 → 有冲突则输出提示并返回失败。
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js, Git CLI (`git check-ignore`, `git diff --name-only`), Vitest
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: 新增 `gitCheckIgnored` 函数
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `src/utils/git-core.ts`
|
|
17
|
+
- Test: `tests/unit/utils/git-core.test.ts`(新建)
|
|
18
|
+
|
|
19
|
+
- [ ] **Step 1: 编写 `gitCheckIgnored` 的失败测试**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// tests/unit/utils/git-core.test.ts
|
|
23
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
24
|
+
import { gitCheckIgnored } from '../../../src/utils/git-core.js';
|
|
25
|
+
|
|
26
|
+
// mock execSync
|
|
27
|
+
vi.mock('node:child_process', () => ({
|
|
28
|
+
execSync: vi.fn(),
|
|
29
|
+
execFileSync: vi.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
import { execSync } from 'node:child_process';
|
|
33
|
+
const mockExecSync = vi.mocked(execSync);
|
|
34
|
+
|
|
35
|
+
describe('gitCheckIgnored', () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
vi.clearAllMocks();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('空数组输入时返回空数组', () => {
|
|
41
|
+
const result = gitCheckIgnored([]);
|
|
42
|
+
expect(result).toEqual([]);
|
|
43
|
+
expect(mockExecSync).not.toHaveBeenCalled();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('全部被忽略时返回全部路径', () => {
|
|
47
|
+
mockExecSync.mockReturnValue('docs/superpowers/a.md\ndocs/superpowers/b.md\n');
|
|
48
|
+
const result = gitCheckIgnored(['docs/superpowers/a.md', 'docs/superpowers/b.md']);
|
|
49
|
+
expect(result).toEqual(['docs/superpowers/a.md', 'docs/superpowers/b.md']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('全部不被忽略时返回空数组', () => {
|
|
53
|
+
// git check-ignore 无匹配时退出码为 1,execSync 抛出异常
|
|
54
|
+
mockExecSync.mockImplementation(() => { throw new Error('exit code 1'); });
|
|
55
|
+
const result = gitCheckIgnored(['src/index.ts']);
|
|
56
|
+
expect(result).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('混合场景时仅返回被忽略的路径', () => {
|
|
60
|
+
mockExecSync.mockReturnValue('docs/superpowers/a.md\n');
|
|
61
|
+
const result = gitCheckIgnored(['docs/superpowers/a.md', 'src/index.ts']);
|
|
62
|
+
expect(result).toEqual(['docs/superpowers/a.md']);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- [ ] **Step 2: 运行测试确认失败**
|
|
68
|
+
|
|
69
|
+
Run: `npx vitest run tests/unit/utils/git-core.test.ts`
|
|
70
|
+
Expected: FAIL — `gitCheckIgnored` 未定义
|
|
71
|
+
|
|
72
|
+
- [ ] **Step 3: 实现 `gitCheckIgnored`**
|
|
73
|
+
|
|
74
|
+
在 `src/utils/git-core.ts` 中新增:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
/**
|
|
78
|
+
* 批量检测文件是否被 .gitignore 忽略
|
|
79
|
+
* 使用 git check-ignore 命令,退出码 1 表示无匹配(非错误)
|
|
80
|
+
* @param {string[]} paths - 要检测的文件路径列表
|
|
81
|
+
* @param {string} [cwd] - 工作目录
|
|
82
|
+
* @returns {string[]} 被忽略的文件路径列表
|
|
83
|
+
*/
|
|
84
|
+
export function gitCheckIgnored(paths: string[], cwd?: string): string[] {
|
|
85
|
+
if (paths.length === 0) return [];
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const output = execSync(`git check-ignore ${paths.map(p => `"${p}"`).join(' ')}`, {
|
|
89
|
+
cwd,
|
|
90
|
+
encoding: 'utf-8',
|
|
91
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
92
|
+
});
|
|
93
|
+
return output.trim().split('\n').filter(Boolean);
|
|
94
|
+
} catch {
|
|
95
|
+
// git check-ignore 退出码 1 表示无匹配文件,属于正常情况
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- [ ] **Step 4: 运行测试确认通过**
|
|
102
|
+
|
|
103
|
+
Run: `npx vitest run tests/unit/utils/git-core.test.ts`
|
|
104
|
+
Expected: PASS
|
|
105
|
+
|
|
106
|
+
- [ ] **Step 5: 在 `src/utils/index.ts` 中导出**
|
|
107
|
+
|
|
108
|
+
在 `src/utils/index.ts` 的 git-core 导出块中添加 `gitCheckIgnored`:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// 在现有的 git-core 导出列表中添加 gitCheckIgnored
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
具体位置:在 `export { ... } from './git.js'` 块中添加 `gitCheckIgnored`(因为 `git.ts` 通过 `export * from './git-core.js'` 重导出)。
|
|
115
|
+
|
|
116
|
+
- [ ] **Step 6: 提交**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
git add src/utils/git-core.ts src/utils/index.ts tests/unit/utils/git-core.test.ts
|
|
120
|
+
git commit -m "feat: add gitCheckIgnored for batch gitignore detection"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### Task 2: 新增 `detectIgnoredFilesInPatch` 函数
|
|
126
|
+
|
|
127
|
+
**Files:**
|
|
128
|
+
- Modify: `src/utils/validate-core.ts`
|
|
129
|
+
- Test: `tests/unit/utils/validate-core.test.ts`(新建)
|
|
130
|
+
|
|
131
|
+
- [ ] **Step 1: 编写 `detectIgnoredFilesInPatch` 的失败测试**
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// tests/unit/utils/validate-core.test.ts
|
|
135
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
136
|
+
|
|
137
|
+
vi.mock('../../../src/utils/git-core.js', async () => {
|
|
138
|
+
const actual = await vi.importActual('../../../src/utils/git-core.js');
|
|
139
|
+
return { ...actual, gitCheckIgnored: vi.fn(), execSync: vi.fn() };
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
vi.mock('node:fs', () => ({
|
|
143
|
+
existsSync: vi.fn(),
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
import { detectIgnoredFilesInPatch } from '../../../src/utils/validate-core.js';
|
|
147
|
+
import { gitCheckIgnored } from '../../../src/utils/git-core.js';
|
|
148
|
+
import { existsSync } from 'node:fs';
|
|
149
|
+
import { execSync } from 'node:child_process';
|
|
150
|
+
|
|
151
|
+
const mockGitCheckIgnored = vi.mocked(gitCheckIgnored);
|
|
152
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
153
|
+
const mockExecSync = vi.mocked(execSync);
|
|
154
|
+
|
|
155
|
+
describe('detectIgnoredFilesInPatch', () => {
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
vi.clearAllMocks();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('无幽灵文件时返回空数组', () => {
|
|
161
|
+
mockExecSync.mockReturnValue('src/a.ts\nsrc/b.ts\n');
|
|
162
|
+
mockGitCheckIgnored.mockReturnValue([]);
|
|
163
|
+
const result = detectIgnoredFilesInPatch('feature', '/main');
|
|
164
|
+
expect(result).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('检测到幽灵文件时返回文件列表', () => {
|
|
168
|
+
mockExecSync.mockReturnValue('docs/superpowers/a.md\nsrc/b.ts\n');
|
|
169
|
+
mockGitCheckIgnored.mockReturnValue(['docs/superpowers/a.md']);
|
|
170
|
+
mockExistsSync.mockImplementation((p: string) => p === '/main/docs/superpowers/a.md');
|
|
171
|
+
const result = detectIgnoredFilesInPatch('feature', '/main');
|
|
172
|
+
expect(result).toEqual(['docs/superpowers/a.md']);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('被忽略但物理不存在的文件不包含在结果中', () => {
|
|
176
|
+
mockExecSync.mockReturnValue('docs/superpowers/a.md\n');
|
|
177
|
+
mockGitCheckIgnored.mockReturnValue(['docs/superpowers/a.md']);
|
|
178
|
+
mockExistsSync.mockReturnValue(false);
|
|
179
|
+
const result = detectIgnoredFilesInPatch('feature', '/main');
|
|
180
|
+
expect(result).toEqual([]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('git diff --name-only 失败时返回空数组(降级)', () => {
|
|
184
|
+
mockExecSync.mockImplementation(() => { throw new Error('fatal'); });
|
|
185
|
+
const result = detectIgnoredFilesInPatch('feature', '/main');
|
|
186
|
+
expect(result).toEqual([]);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- [ ] **Step 2: 运行测试确认失败**
|
|
192
|
+
|
|
193
|
+
Run: `npx vitest run tests/unit/utils/validate-core.test.ts`
|
|
194
|
+
Expected: FAIL — `detectIgnoredFilesInPatch` 未定义
|
|
195
|
+
|
|
196
|
+
- [ ] **Step 3: 实现 `detectIgnoredFilesInPatch`**
|
|
197
|
+
|
|
198
|
+
在 `src/utils/validate-core.ts` 中新增:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { existsSync } from 'node:fs';
|
|
202
|
+
import { join } from 'node:path';
|
|
203
|
+
import { execSync } from 'node:child_process';
|
|
204
|
+
import { EXEC_MAX_BUFFER } from '../constants/index.js';
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 检测 patch 中被 .gitignore 忽略且物理存在于主 worktree 的文件(幽灵文件)
|
|
208
|
+
* 这些文件会导致 git apply 失败("已经存在于工作区中")
|
|
209
|
+
* @param {string} branchName - 目标分支名
|
|
210
|
+
* @param {string} mainWorktreePath - 主 worktree 路径
|
|
211
|
+
* @returns {string[]} 幽灵文件的相对路径列表
|
|
212
|
+
*/
|
|
213
|
+
export function detectIgnoredFilesInPatch(branchName: string, mainWorktreePath: string): string[] {
|
|
214
|
+
let patchFiles: string[];
|
|
215
|
+
try {
|
|
216
|
+
const output = execSync(`git diff --name-only HEAD...${branchName}`, {
|
|
217
|
+
cwd: mainWorktreePath,
|
|
218
|
+
encoding: 'utf-8',
|
|
219
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
220
|
+
maxBuffer: EXEC_MAX_BUFFER,
|
|
221
|
+
});
|
|
222
|
+
patchFiles = output.trim().split('\n').filter(Boolean);
|
|
223
|
+
} catch {
|
|
224
|
+
// diff 失败时跳过检测,降级为当前行为(让 apply 自行报错)
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (patchFiles.length === 0) return [];
|
|
229
|
+
|
|
230
|
+
const ignoredFiles = gitCheckIgnored(patchFiles, mainWorktreePath);
|
|
231
|
+
if (ignoredFiles.length === 0) return [];
|
|
232
|
+
|
|
233
|
+
// 仅保留物理存在的文件(幽灵文件)
|
|
234
|
+
return ignoredFiles.filter(file => existsSync(join(mainWorktreePath, file)));
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
注意:需要在文件顶部的 import 中添加 `gitCheckIgnored`(从 `./index.js` 导入)和 `EXEC_MAX_BUFFER`(从 `../constants/index.js` 导入)。
|
|
239
|
+
|
|
240
|
+
- [ ] **Step 4: 运行测试确认通过**
|
|
241
|
+
|
|
242
|
+
Run: `npx vitest run tests/unit/utils/validate-core.test.ts`
|
|
243
|
+
Expected: PASS
|
|
244
|
+
|
|
245
|
+
- [ ] **Step 5: 在 `src/utils/index.ts` 中导出**
|
|
246
|
+
|
|
247
|
+
在 `src/utils/index.ts` 的 validate-core 导出行中添加 `detectIgnoredFilesInPatch`:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
export { migrateChangesViaPatch, computeCurrentTreeHash, saveCurrentSnapshotTree, loadOldSnapshotToStage, switchToValidateBranch, detectIgnoredFilesInPatch } from './validate-core.js';
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- [ ] **Step 6: 提交**
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
git add src/utils/validate-core.ts src/utils/index.ts tests/unit/utils/validate-core.test.ts
|
|
257
|
+
git commit -m "feat: add detectIgnoredFilesInPatch for ghost file detection"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### Task 3: 新增消息常量 `VALIDATE_IGNORED_FILES_CONFLICT`
|
|
263
|
+
|
|
264
|
+
**Files:**
|
|
265
|
+
- Modify: `src/constants/messages/validate.ts`
|
|
266
|
+
|
|
267
|
+
- [ ] **Step 1: 添加双语消息常量**
|
|
268
|
+
|
|
269
|
+
在 `src/constants/messages/validate.ts` 的 `VALIDATE_MESSAGES_I18N` 对象中,在 `VALIDATE_PATCH_APPLY_FAILED` 之后添加:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
/** validate 检测到被 .gitignore 忽略的残留文件冲突 */
|
|
273
|
+
VALIDATE_IGNORED_FILES_CONFLICT: {
|
|
274
|
+
en: (files: string[], cleanCommands: string[]) => {
|
|
275
|
+
const maxDisplay = 10;
|
|
276
|
+
const displayed = files.slice(0, maxDisplay).map(f => ` - ${f}`).join('\n');
|
|
277
|
+
const more = files.length > maxDisplay ? `\n ...(${files.length} files total)` : '';
|
|
278
|
+
const cmds = cleanCommands.map(c => ` ${c}`).join('\n');
|
|
279
|
+
return `Ignored files left in main worktree are blocking patch apply:\n${displayed}${more}\n\nPlease clean up manually and retry:\n${cmds}`;
|
|
280
|
+
},
|
|
281
|
+
'zh-CN': (files: string[], cleanCommands: string[]) => {
|
|
282
|
+
const maxDisplay = 10;
|
|
283
|
+
const displayed = files.slice(0, maxDisplay).map(f => ` - ${f}`).join('\n');
|
|
284
|
+
const more = files.length > maxDisplay ? `\n ...(共 ${files.length} 个文件)` : '';
|
|
285
|
+
const cmds = cleanCommands.map(c => ` ${c}`).join('\n');
|
|
286
|
+
return `检测到被 .gitignore 忽略的文件残留在主 worktree 中,导致变更无法应用:\n${displayed}${more}\n\n请手动清理后重试:\n${cmds}`;
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
- [ ] **Step 2: 提交**
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
git add src/constants/messages/validate.ts
|
|
295
|
+
git commit -m "feat: add VALIDATE_IGNORED_FILES_CONFLICT message constant"
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### Task 4: 修改 `migrateChangesViaPatch` 集成检测逻辑
|
|
301
|
+
|
|
302
|
+
**Files:**
|
|
303
|
+
- Modify: `src/utils/validate-core.ts`
|
|
304
|
+
|
|
305
|
+
- [ ] **Step 1: 修改 `migrateChangesViaPatch`**
|
|
306
|
+
|
|
307
|
+
在 `migrateChangesViaPatch` 函数中,`gitApplyFromStdin` 调用之前,添加幽灵文件检测逻辑:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
export function migrateChangesViaPatch(targetWorktreePath: string, mainWorktreePath: string, branchName: string, hasUncommitted: boolean): { success: boolean } {
|
|
311
|
+
let didTempCommit = false;
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// 如果有未提交修改,先做临时 commit 以便 diff 能捕获全部变更
|
|
315
|
+
if (hasUncommitted) {
|
|
316
|
+
gitAddAll(targetWorktreePath);
|
|
317
|
+
gitCommit('clawt:temp-commit-for-validate', targetWorktreePath);
|
|
318
|
+
didTempCommit = true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 在主 worktree 执行三点 diff,获取目标分支自分叉点以来的全量变更
|
|
322
|
+
const patch = gitDiffBinaryAgainstBranch(branchName, mainWorktreePath);
|
|
323
|
+
|
|
324
|
+
// 检测被 .gitignore 忽略的残留文件(幽灵文件),在 apply 之前拦截
|
|
325
|
+
const ignoredFiles = detectIgnoredFilesInPatch(branchName, mainWorktreePath);
|
|
326
|
+
if (ignoredFiles.length > 0) {
|
|
327
|
+
const cleanCommands = buildCleanCommands(ignoredFiles);
|
|
328
|
+
logger.warn(`检测到 ${ignoredFiles.length} 个被忽略的残留文件冲突`);
|
|
329
|
+
printWarning(MESSAGES.VALIDATE_IGNORED_FILES_CONFLICT(ignoredFiles, cleanCommands));
|
|
330
|
+
return { success: false };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 应用 patch 到主 worktree 工作目录
|
|
334
|
+
if (patch.length > 0) {
|
|
335
|
+
try {
|
|
336
|
+
gitApplyFromStdin(patch, mainWorktreePath);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.warn(`patch apply 失败: ${error}`);
|
|
339
|
+
printWarning(MESSAGES.VALIDATE_PATCH_APPLY_FAILED(branchName));
|
|
340
|
+
return { success: false };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { success: true };
|
|
345
|
+
} finally {
|
|
346
|
+
// ...(finally 块保持不变)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
- [ ] **Step 2: 实现 `buildCleanCommands` 辅助函数**
|
|
352
|
+
|
|
353
|
+
在 `src/utils/validate-core.ts` 中新增(不导出,仅内部使用):
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
/**
|
|
357
|
+
* 根据冲突文件列表生成 git clean 清理命令
|
|
358
|
+
* 按直接父目录去重,生成针对性的清理命令
|
|
359
|
+
* @param {string[]} files - 冲突文件的相对路径列表
|
|
360
|
+
* @returns {string[]} 清理命令列表
|
|
361
|
+
*/
|
|
362
|
+
function buildCleanCommands(files: string[]): string[] {
|
|
363
|
+
const dirs = new Set<string>();
|
|
364
|
+
for (const file of files) {
|
|
365
|
+
const lastSlash = file.lastIndexOf('/');
|
|
366
|
+
const dir = lastSlash > 0 ? file.substring(0, lastSlash) : '.';
|
|
367
|
+
dirs.add(dir);
|
|
368
|
+
}
|
|
369
|
+
return Array.from(dirs).map(dir => `git clean -fdx ${dir}/`);
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
- [ ] **Step 3: 确保 import 完整**
|
|
374
|
+
|
|
375
|
+
在 `src/utils/validate-core.ts` 顶部确认以下 import 存在:
|
|
376
|
+
- `MESSAGES` 从 `'../constants/index.js'`(已有)
|
|
377
|
+
- `printWarning` 从 `'./index.js'`(已有)
|
|
378
|
+
- `detectIgnoredFilesInPatch` 在同文件中定义,无需额外 import
|
|
379
|
+
- `buildCleanCommands` 在同文件中定义,无需额外 import
|
|
380
|
+
|
|
381
|
+
- [ ] **Step 4: 运行全部测试确认无回归**
|
|
382
|
+
|
|
383
|
+
Run: `npx vitest run`
|
|
384
|
+
Expected: 全部 PASS
|
|
385
|
+
|
|
386
|
+
- [ ] **Step 5: 提交**
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
git add src/utils/validate-core.ts
|
|
390
|
+
git commit -m "feat: integrate ghost file detection into migrateChangesViaPatch"
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
### Task 5: 集成验证
|
|
396
|
+
|
|
397
|
+
- [ ] **Step 1: 构建项目确认无编译错误**
|
|
398
|
+
|
|
399
|
+
Run: `npm run build`
|
|
400
|
+
Expected: 构建成功
|
|
401
|
+
|
|
402
|
+
- [ ] **Step 2: 运行全部测试**
|
|
403
|
+
|
|
404
|
+
Run: `npm test`
|
|
405
|
+
Expected: 全部 PASS
|
|
406
|
+
|
|
407
|
+
- [ ] **Step 3: 提交全部变更(如有遗漏)**
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
git add -A
|
|
411
|
+
git commit -m "feat: detect ignored ghost files before patch apply in validate"
|
|
412
|
+
```
|