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,386 @@
|
|
|
1
|
+
# Worktree Base Branch 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:** Show each clawt worktree's creation-time base branch in `status`, `status -i`, and `list`.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Store per-worktree metadata under `~/.clawt/projects/<projectName>/worktrees/<branchName>.json` when worktrees are created. Enrich `WorktreeInfo` with `baseBranch` at the data layer so text, JSON, and interactive renderers share the same source.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Commander, Vitest, Node `fs`/`path`, existing clawt utilities.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Structure
|
|
14
|
+
|
|
15
|
+
- Create: `src/utils/worktree-metadata.ts`
|
|
16
|
+
- Single responsibility: generate metadata paths, save metadata, load metadata, remove metadata.
|
|
17
|
+
- Modify: `src/types/worktree.ts`
|
|
18
|
+
- Add `baseBranch: string | null` to `WorktreeInfo`.
|
|
19
|
+
- Add `WorktreeMetadata`.
|
|
20
|
+
- Modify: `src/types/status.ts`
|
|
21
|
+
- Add `baseBranch: string | null` to `WorktreeDetailedStatus`.
|
|
22
|
+
- Modify: `src/utils/worktree.ts`
|
|
23
|
+
- Capture current branch during creation, save metadata, read metadata when listing, remove metadata during cleanup.
|
|
24
|
+
- Modify: `src/commands/list.ts`
|
|
25
|
+
- Include `baseBranch` in text and JSON output.
|
|
26
|
+
- Modify: `src/commands/status.ts`
|
|
27
|
+
- Carry `baseBranch` into status data and render it in text output.
|
|
28
|
+
- Modify: `src/utils/interactive-panel-render.ts`
|
|
29
|
+
- Render base branch line in `status -i`.
|
|
30
|
+
- Test: `tests/unit/utils/worktree-metadata.test.ts`
|
|
31
|
+
- Test: `tests/unit/utils/worktree.test.ts`
|
|
32
|
+
- Test: `tests/unit/commands/list.test.ts`
|
|
33
|
+
- Test: `tests/unit/commands/status.test.ts`
|
|
34
|
+
- Test: `tests/unit/utils/interactive-panel.test.ts`
|
|
35
|
+
|
|
36
|
+
### Task 1: Metadata Utility
|
|
37
|
+
|
|
38
|
+
**Files:**
|
|
39
|
+
- Create: `src/utils/worktree-metadata.ts`
|
|
40
|
+
- Modify: `src/types/worktree.ts`
|
|
41
|
+
- Test: `tests/unit/utils/worktree-metadata.test.ts`
|
|
42
|
+
|
|
43
|
+
- [ ] **Step 1: Write failing metadata path and save/load tests**
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
47
|
+
|
|
48
|
+
vi.mock('node:fs', () => ({
|
|
49
|
+
existsSync: vi.fn(),
|
|
50
|
+
mkdirSync: vi.fn(),
|
|
51
|
+
readFileSync: vi.fn(),
|
|
52
|
+
rmSync: vi.fn(),
|
|
53
|
+
writeFileSync: vi.fn(),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
vi.mock('../../../src/constants/index.js', async (importOriginal) => {
|
|
57
|
+
const actual = await importOriginal<typeof import('../../../src/constants/index.js')>();
|
|
58
|
+
return {
|
|
59
|
+
...actual,
|
|
60
|
+
PROJECTS_CONFIG_DIR: '/tmp/clawt-projects',
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
65
|
+
import {
|
|
66
|
+
getWorktreeMetadataPath,
|
|
67
|
+
loadWorktreeMetadata,
|
|
68
|
+
saveWorktreeMetadata,
|
|
69
|
+
} from '../../../src/utils/worktree-metadata.js';
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
vi.clearAllMocks();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('worktree metadata', () => {
|
|
76
|
+
it('生成项目 worktree 元数据路径', () => {
|
|
77
|
+
expect(getWorktreeMetadataPath('demo', 'feature')).toBe('/tmp/clawt-projects/demo/worktrees/feature.json');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('保存并读取来源分支元数据', () => {
|
|
81
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
82
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({
|
|
83
|
+
branch: 'feature',
|
|
84
|
+
baseBranch: 'test',
|
|
85
|
+
createdAt: '2026-06-09T10:30:00.000Z',
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
saveWorktreeMetadata('demo', {
|
|
89
|
+
branch: 'feature',
|
|
90
|
+
baseBranch: 'test',
|
|
91
|
+
createdAt: '2026-06-09T10:30:00.000Z',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(writeFileSync).toHaveBeenCalledWith(
|
|
95
|
+
'/tmp/clawt-projects/demo/worktrees/feature.json',
|
|
96
|
+
expect.stringContaining('"baseBranch": "test"'),
|
|
97
|
+
'utf-8',
|
|
98
|
+
);
|
|
99
|
+
expect(loadWorktreeMetadata('demo', 'feature')?.baseBranch).toBe('test');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('元数据不存在时返回 null', () => {
|
|
103
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
104
|
+
expect(loadWorktreeMetadata('demo', 'missing')).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
110
|
+
|
|
111
|
+
Run: `pnpm vitest run tests/unit/utils/worktree-metadata.test.ts`
|
|
112
|
+
Expected: FAIL with module not found for `src/utils/worktree-metadata.js`.
|
|
113
|
+
|
|
114
|
+
- [ ] **Step 3: Add types and metadata utility**
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
/** worktree 来源分支元数据 */
|
|
118
|
+
export interface WorktreeMetadata {
|
|
119
|
+
/** worktree 分支名 */
|
|
120
|
+
branch: string;
|
|
121
|
+
/** 创建 worktree 时所在的真实当前分支 */
|
|
122
|
+
baseBranch: string;
|
|
123
|
+
/** 元数据创建时间 */
|
|
124
|
+
createdAt: string;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Implement `getWorktreeMetadataPath(projectName, branchName)`, `saveWorktreeMetadata(projectName, metadata)`, `loadWorktreeMetadata(projectName, branchName)`, and `removeWorktreeMetadata(projectName, branchName)` with Chinese JSDoc comments. Use `safeStringify()` for writes and `ensureDir()` for directory creation.
|
|
129
|
+
|
|
130
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
131
|
+
|
|
132
|
+
Run: `pnpm vitest run tests/unit/utils/worktree-metadata.test.ts`
|
|
133
|
+
Expected: PASS.
|
|
134
|
+
|
|
135
|
+
- [ ] **Step 5: Commit**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git add src/utils/worktree-metadata.ts src/types/worktree.ts tests/unit/utils/worktree-metadata.test.ts
|
|
139
|
+
git commit -m "feat: add worktree metadata storage"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Task 2: Save And Load Base Branch In Worktree Data
|
|
143
|
+
|
|
144
|
+
**Files:**
|
|
145
|
+
- Modify: `src/utils/worktree.ts`
|
|
146
|
+
- Modify: `src/utils/index.ts`
|
|
147
|
+
- Test: `tests/unit/utils/worktree.test.ts`
|
|
148
|
+
|
|
149
|
+
- [ ] **Step 1: Write failing worktree data tests**
|
|
150
|
+
|
|
151
|
+
Add tests asserting:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
it('创建 worktree 时记录当前分支为来源分支', () => {
|
|
155
|
+
mockedGetCurrentBranch.mockReturnValue('test');
|
|
156
|
+
const result = createWorktrees('feature', 1);
|
|
157
|
+
expect(result[0].baseBranch).toBe('test');
|
|
158
|
+
expect(saveWorktreeMetadata).toHaveBeenCalledWith('my-project', {
|
|
159
|
+
branch: 'feature',
|
|
160
|
+
baseBranch: 'test',
|
|
161
|
+
createdAt: expect.any(String),
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('获取 worktree 时读取来源分支', () => {
|
|
166
|
+
mockedLoadWorktreeMetadata.mockReturnValue({
|
|
167
|
+
branch: 'feature',
|
|
168
|
+
baseBranch: 'test',
|
|
169
|
+
createdAt: '2026-06-09T10:30:00.000Z',
|
|
170
|
+
});
|
|
171
|
+
const result = getProjectWorktrees();
|
|
172
|
+
expect(result[0].baseBranch).toBe('test');
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
177
|
+
|
|
178
|
+
Run: `pnpm vitest run tests/unit/utils/worktree.test.ts`
|
|
179
|
+
Expected: FAIL because `baseBranch` is not returned and metadata functions are not called.
|
|
180
|
+
|
|
181
|
+
- [ ] **Step 3: Implement worktree metadata integration**
|
|
182
|
+
|
|
183
|
+
In `createWorktrees()` and `createWorktreesByBranches()`:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const projectName = getProjectName();
|
|
187
|
+
const baseBranch = getCurrentBranch();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
After each successful worktree creation, call `saveWorktreeMetadata(projectName, { branch: name, baseBranch, createdAt: new Date().toISOString() })` and return `{ path, branch, baseBranch }`.
|
|
191
|
+
|
|
192
|
+
In `getProjectWorktrees()`, call `loadWorktreeMetadata(projectName, entry.name)` and return `baseBranch: metadata?.baseBranch ?? null`.
|
|
193
|
+
|
|
194
|
+
In `cleanupWorktrees()`, call `removeWorktreeMetadata(projectName, wt.branch)` inside the existing per-worktree cleanup `try` block after validate branch deletion.
|
|
195
|
+
|
|
196
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
197
|
+
|
|
198
|
+
Run: `pnpm vitest run tests/unit/utils/worktree.test.ts`
|
|
199
|
+
Expected: PASS.
|
|
200
|
+
|
|
201
|
+
- [ ] **Step 5: Commit**
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
git add src/utils/worktree.ts src/utils/index.ts tests/unit/utils/worktree.test.ts
|
|
205
|
+
git commit -m "feat: track worktree base branches"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Task 3: Show Base Branch In List
|
|
209
|
+
|
|
210
|
+
**Files:**
|
|
211
|
+
- Modify: `src/commands/list.ts`
|
|
212
|
+
- Test: `tests/unit/commands/list.test.ts`
|
|
213
|
+
|
|
214
|
+
- [ ] **Step 1: Write failing list output tests**
|
|
215
|
+
|
|
216
|
+
Add JSON assertion:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
expect(parsed.worktrees[0].baseBranch).toBe('test');
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Add text assertion:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
expect(mockedPrintInfo).toHaveBeenCalledWith(expect.stringContaining('<- test'));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Add missing metadata assertion:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
expect(mockedPrintInfo).toHaveBeenCalledWith(expect.stringContaining('未记录'));
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
235
|
+
|
|
236
|
+
Run: `pnpm vitest run tests/unit/commands/list.test.ts`
|
|
237
|
+
Expected: FAIL because `baseBranch` is not printed or serialized.
|
|
238
|
+
|
|
239
|
+
- [ ] **Step 3: Implement list JSON and text display**
|
|
240
|
+
|
|
241
|
+
In JSON output include:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
baseBranch: wt.baseBranch,
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
In text output build one display helper:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
/**
|
|
251
|
+
* 格式化来源分支展示文本
|
|
252
|
+
* @param {string | null} baseBranch - 来源分支
|
|
253
|
+
* @returns {string} 来源分支展示文本
|
|
254
|
+
*/
|
|
255
|
+
function formatBaseBranchInline(baseBranch: string | null): string {
|
|
256
|
+
const fallback = getCurrentLanguage() === 'en' ? 'Not recorded' : '未记录';
|
|
257
|
+
return `<- ${baseBranch ?? fallback}`;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Use it in the first worktree line after `[${wt.branch}]`.
|
|
262
|
+
|
|
263
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
264
|
+
|
|
265
|
+
Run: `pnpm vitest run tests/unit/commands/list.test.ts`
|
|
266
|
+
Expected: PASS.
|
|
267
|
+
|
|
268
|
+
- [ ] **Step 5: Commit**
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
git add src/commands/list.ts tests/unit/commands/list.test.ts
|
|
272
|
+
git commit -m "feat: show base branch in list"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Task 4: Show Base Branch In Status And Interactive Panel
|
|
276
|
+
|
|
277
|
+
**Files:**
|
|
278
|
+
- Modify: `src/types/status.ts`
|
|
279
|
+
- Modify: `src/commands/status.ts`
|
|
280
|
+
- Modify: `src/utils/interactive-panel-render.ts`
|
|
281
|
+
- Test: `tests/unit/commands/status.test.ts`
|
|
282
|
+
- Test: `tests/unit/utils/interactive-panel.test.ts`
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 1: Write failing status tests**
|
|
285
|
+
|
|
286
|
+
Add JSON assertion:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
expect(parsed.worktrees[0].baseBranch).toBe('test');
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Add text assertion:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
expect(mockedPrintInfo).toHaveBeenCalledWith(expect.stringContaining('来源分支: test'));
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Add interactive renderer assertion:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
const lines = renderWorktreeBlock({
|
|
302
|
+
path: '/path/feature',
|
|
303
|
+
branch: 'feature',
|
|
304
|
+
baseBranch: 'test',
|
|
305
|
+
changeStatus: 'clean',
|
|
306
|
+
commitsAhead: 0,
|
|
307
|
+
commitsBehind: 0,
|
|
308
|
+
snapshotTime: null,
|
|
309
|
+
insertions: 0,
|
|
310
|
+
deletions: 0,
|
|
311
|
+
createdAt: null,
|
|
312
|
+
}, false);
|
|
313
|
+
expect(lines.join('\n')).toContain('来源分支: test');
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
317
|
+
|
|
318
|
+
Run: `pnpm vitest run tests/unit/commands/status.test.ts tests/unit/utils/interactive-panel.test.ts`
|
|
319
|
+
Expected: FAIL because status detail and renderer do not include `baseBranch`.
|
|
320
|
+
|
|
321
|
+
- [ ] **Step 3: Carry and render baseBranch**
|
|
322
|
+
|
|
323
|
+
In `collectWorktreeDetailedStatusAsync()`, add:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
baseBranch: worktree.baseBranch,
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
In `printWorktreeItem()`, render:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
printInfo(` ${chalk.gray(formatBaseBranchLine(wt.baseBranch))}`);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
In `renderWorktreeBlock()`, add the same line near the top after branch status or after diff line. Keep helper logic focused and documented with Chinese JSDoc.
|
|
336
|
+
|
|
337
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
338
|
+
|
|
339
|
+
Run: `pnpm vitest run tests/unit/commands/status.test.ts tests/unit/utils/interactive-panel.test.ts`
|
|
340
|
+
Expected: PASS.
|
|
341
|
+
|
|
342
|
+
- [ ] **Step 5: Commit**
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
git add src/types/status.ts src/commands/status.ts src/utils/interactive-panel-render.ts tests/unit/commands/status.test.ts tests/unit/utils/interactive-panel.test.ts
|
|
346
|
+
git commit -m "feat: show base branch in status"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Task 5: Documentation And Full Verification
|
|
350
|
+
|
|
351
|
+
**Files:**
|
|
352
|
+
- Modify: `docs/create.md`
|
|
353
|
+
- Modify: `docs/list.md`
|
|
354
|
+
- Modify: `docs/status.md`
|
|
355
|
+
|
|
356
|
+
- [ ] **Step 1: Update command docs**
|
|
357
|
+
|
|
358
|
+
Document:
|
|
359
|
+
|
|
360
|
+
```md
|
|
361
|
+
创建 worktree 时,clawt 会记录创建瞬间所在的真实当前分支为来源分支,并保存到 `~/.clawt/projects/<projectName>/worktrees/<branchName>.json`。
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Update `list` and `status` JSON examples to include `"baseBranch": "test"` and explain `null` means not recorded.
|
|
365
|
+
|
|
366
|
+
- [ ] **Step 2: Run focused tests**
|
|
367
|
+
|
|
368
|
+
Run: `pnpm vitest run tests/unit/utils/worktree-metadata.test.ts tests/unit/utils/worktree.test.ts tests/unit/commands/list.test.ts tests/unit/commands/status.test.ts tests/unit/utils/interactive-panel.test.ts`
|
|
369
|
+
Expected: PASS.
|
|
370
|
+
|
|
371
|
+
- [ ] **Step 3: Run full test suite**
|
|
372
|
+
|
|
373
|
+
Run: `pnpm test`
|
|
374
|
+
Expected: PASS.
|
|
375
|
+
|
|
376
|
+
- [ ] **Step 4: Run type check or build**
|
|
377
|
+
|
|
378
|
+
Run: `pnpm build`
|
|
379
|
+
Expected: PASS.
|
|
380
|
+
|
|
381
|
+
- [ ] **Step 5: Commit**
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
git add docs/create.md docs/list.md docs/status.md
|
|
385
|
+
git commit -m "docs: document worktree base branch metadata"
|
|
386
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# validate 被忽略文件冲突检测 — 设计文档
|
|
2
|
+
|
|
3
|
+
## 问题
|
|
4
|
+
|
|
5
|
+
当目标分支跟踪了某些在 `.gitignore` 中的文件(如 AI Agent 用 `git add -f` 强制提交),validate 通过 `git apply` 将这些文件创建到主 worktree 工作目录后,`git clean -fd` 无法清理它们(因为被 `.gitignore` 忽略)。后续 validate 的 patch apply 因文件已存在而失败,且 sync 无法打破这个死循环。
|
|
6
|
+
|
|
7
|
+
## 修复目标
|
|
8
|
+
|
|
9
|
+
在 `migrateChangesViaPatch` 中,`git apply` 之前检测被 `.gitignore` 忽略且物理存在于主 worktree 工作目录的文件("幽灵文件"),若检测到冲突则输出清晰的错误提示和可执行的清理命令,而非当前的误导性信息 "diverged too far from main"。
|
|
10
|
+
|
|
11
|
+
## 验收标准
|
|
12
|
+
|
|
13
|
+
1. 检测到幽灵文件时,输出文件列表和 `git clean -fdx <dir>` 清理命令
|
|
14
|
+
2. 未检测到幽灵文件时,行为与当前完全一致(无回归)
|
|
15
|
+
3. 提示信息为双语(中英)
|
|
16
|
+
4. 有单元测试覆盖检测逻辑
|
|
17
|
+
|
|
18
|
+
## 架构
|
|
19
|
+
|
|
20
|
+
### 检测流程
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
migrateChangesViaPatch()
|
|
24
|
+
├── gitDiffBinaryAgainstBranch() → 获取 patch
|
|
25
|
+
├── detectIgnoredFilesInPatch() → 新增:检测幽灵文件
|
|
26
|
+
│ ├── git diff --name-only HEAD...branch → 获取 patch 涉及的文件列表
|
|
27
|
+
│ ├── git check-ignore <files> → 筛选被忽略的文件
|
|
28
|
+
│ └── fs.existsSync → 筛选物理存在的文件
|
|
29
|
+
├── 有冲突 → printWarning + return { success: false }
|
|
30
|
+
└── 无冲突 → gitApplyFromStdin() → 正常 apply
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 新增函数
|
|
34
|
+
|
|
35
|
+
| 函数 | 文件 | 职责 |
|
|
36
|
+
|------|------|------|
|
|
37
|
+
| `gitCheckIgnored(paths, cwd)` | `src/utils/git-core.ts` | 批量检测文件是否被 `.gitignore` 忽略 |
|
|
38
|
+
| `detectIgnoredFilesInPatch(branchName, mainWorktreePath)` | `src/utils/validate-core.ts` | 检测 patch 中的幽灵文件列表 |
|
|
39
|
+
|
|
40
|
+
### 修改函数
|
|
41
|
+
|
|
42
|
+
| 函数 | 文件 | 变更 |
|
|
43
|
+
|------|------|------|
|
|
44
|
+
| `migrateChangesViaPatch` | `src/utils/validate-core.ts` | apply 前调用检测函数 |
|
|
45
|
+
|
|
46
|
+
### 新增消息常量
|
|
47
|
+
|
|
48
|
+
| 常量 | 文件 | 用途 |
|
|
49
|
+
|------|------|------|
|
|
50
|
+
| `VALIDATE_IGNORED_FILES_CONFLICT` | `src/constants/messages/validate.ts` | 幽灵文件冲突提示(含文件列表和清理命令) |
|
|
51
|
+
|
|
52
|
+
### 提示格式
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
⚠ 检测到被 .gitignore 忽略的文件残留在主 worktree 中,导致变更无法应用:
|
|
56
|
+
- docs/superpowers/findings/2026-05-30-chat-message-block-findings.md
|
|
57
|
+
- docs/superpowers/plans/2026-05-30-chat-message-block.md
|
|
58
|
+
...(共 18 个文件)
|
|
59
|
+
|
|
60
|
+
请手动清理后重试:
|
|
61
|
+
git clean -fdx docs/superpowers/
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
清理命令按冲突文件的直接父目录去重生成。
|
|
65
|
+
|
|
66
|
+
## 错误处理
|
|
67
|
+
|
|
68
|
+
- `git check-ignore` 无匹配时退出码为 1(非错误),需 catch 后返回空数组
|
|
69
|
+
- `git diff --name-only` 失败时不阻断流程,跳过检测继续 apply(降级为当前行为)
|
|
70
|
+
- 检测函数本身的异常不应阻断 validate 流程
|
|
71
|
+
|
|
72
|
+
## 回归测试要求
|
|
73
|
+
|
|
74
|
+
- 无幽灵文件时:validate 正常通过(现有测试覆盖)
|
|
75
|
+
- 有幽灵文件时:validate 返回 `{ success: false }` 并输出提示
|
|
76
|
+
- `gitCheckIgnored` 单元测试:空输入、全部忽略、全部不忽略、混合场景
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Worktree Base Branch Design
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
当前 `clawt status`、`clawt status -i` 和 `clawt list` 会把同一项目下的 worktree 展示在一起,但不会标明这些 worktree 分支最初基于哪个分支创建。当用户同时从 `master`、`test` 等多个分支创建 worktree 时,容易把基于 `test` 的 worktree 误合并到 `master`,风险较高。
|
|
6
|
+
|
|
7
|
+
## 目标
|
|
8
|
+
|
|
9
|
+
- 在 `clawt create` 创建 worktree 时记录创建瞬间的真实当前分支,作为该 worktree 的来源分支。
|
|
10
|
+
- 在 `clawt status`、`clawt status -i`、`clawt list` 中展示来源分支。
|
|
11
|
+
- JSON 输出包含稳定字段,便于脚本消费。
|
|
12
|
+
- 历史 worktree 没有元数据时明确展示未记录,不通过不可靠 git 推断伪造结果。
|
|
13
|
+
|
|
14
|
+
## 非目标
|
|
15
|
+
|
|
16
|
+
- 不新增历史回填命令。
|
|
17
|
+
- 不修改 `clawt merge` 的合并目标逻辑。
|
|
18
|
+
- 不改变 `clawt init` 的项目主工作分支配置语义。
|
|
19
|
+
- 不把 worktree 元数据写入 `~/.clawt/projects/<projectName>/config.json`。
|
|
20
|
+
|
|
21
|
+
## 来源分支语义
|
|
22
|
+
|
|
23
|
+
来源分支定义为执行 `clawt create` 时,创建 worktree 前主 worktree 当前所在的真实分支。示例:用户当时位于 `test` 分支并创建 `feature-login`,则 `feature-login` 的 `baseBranch` 为 `test`。
|
|
24
|
+
|
|
25
|
+
该语义优先于项目配置中的 `clawtMainWorkBranch`。如果现有前置检查在创建前切换了分支,则记录切换后的真实当前分支,因为它才是实际执行 `git worktree add -b` 的基准。
|
|
26
|
+
|
|
27
|
+
## 存储设计
|
|
28
|
+
|
|
29
|
+
每个 worktree 分支使用一个独立 JSON 文件:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
~/.clawt/projects/<projectName>/worktrees/<branchName>.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
文件内容:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"branch": "feature-login",
|
|
40
|
+
"baseBranch": "test",
|
|
41
|
+
"createdAt": "2026-06-09T10:30:00.000Z"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
字段说明:
|
|
46
|
+
|
|
47
|
+
| 字段 | 类型 | 说明 |
|
|
48
|
+
|------|------|------|
|
|
49
|
+
| `branch` | `string` | worktree 对应分支名 |
|
|
50
|
+
| `baseBranch` | `string` | 创建 worktree 时所在的真实当前分支 |
|
|
51
|
+
| `createdAt` | `string` | 元数据写入时间,ISO 8601 字符串 |
|
|
52
|
+
|
|
53
|
+
元数据属于项目级动态数据,应放在 `~/.clawt/projects/<projectName>/worktrees/` 下。项目配置 `config.json` 继续只保存配置项,避免运行元数据污染配置文件。
|
|
54
|
+
|
|
55
|
+
## 代码结构
|
|
56
|
+
|
|
57
|
+
新增 `src/utils/worktree-metadata.ts`,职责限定为 worktree 元数据路径、读写、删除:
|
|
58
|
+
|
|
59
|
+
- `getWorktreeMetadataPath(projectName, branchName)`:生成单个分支元数据路径。
|
|
60
|
+
- `saveWorktreeMetadata(projectName, metadata)`:保存来源分支元数据。
|
|
61
|
+
- `loadWorktreeMetadata(projectName, branchName)`:读取元数据,缺失或解析失败时返回 `null`。
|
|
62
|
+
- `removeWorktreeMetadata(projectName, branchName)`:删除元数据,文件不存在时不报错。
|
|
63
|
+
|
|
64
|
+
新增类型 `WorktreeMetadata`,包含 `branch`、`baseBranch`、`createdAt`。现有 `WorktreeInfo` 和 `WorktreeDetailedStatus` 增加 `baseBranch: string | null`。
|
|
65
|
+
|
|
66
|
+
## 数据流
|
|
67
|
+
|
|
68
|
+
创建流程:
|
|
69
|
+
|
|
70
|
+
1. `clawt create` 完成前置检查。
|
|
71
|
+
2. `createWorktrees()` 在创建前读取 `getCurrentBranch()`。
|
|
72
|
+
3. 每成功创建一个 worktree 和 validate 分支后,写入对应 metadata 文件。
|
|
73
|
+
4. 命令输出继续展示目录、分支、验证分支;是否在创建输出中展示来源分支由实现保持简洁,可不新增。
|
|
74
|
+
|
|
75
|
+
读取流程:
|
|
76
|
+
|
|
77
|
+
1. `getProjectWorktrees()` 扫描 `~/.clawt/worktrees/<projectName>/` 并与 `git worktree list` 交叉验证。
|
|
78
|
+
2. 对每个有效 worktree 读取 `~/.clawt/projects/<projectName>/worktrees/<branch>.json`。
|
|
79
|
+
3. 返回 `WorktreeInfo` 时带上 `baseBranch`,没有元数据时为 `null`。
|
|
80
|
+
4. `status` 的收集层把 `baseBranch` 传入 `WorktreeDetailedStatus`。
|
|
81
|
+
5. `list`、`status` 文本渲染和交互面板只负责展示已有字段。
|
|
82
|
+
|
|
83
|
+
## 展示规则
|
|
84
|
+
|
|
85
|
+
`clawt status` 普通文本输出中,每个 worktree 条目增加一行:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
来源分支: test
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
英文语言环境显示:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
Base branch: test
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
没有元数据时显示:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
来源分支: 未记录
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
英文语言环境显示:
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
Base branch: Not recorded
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`clawt status -i` 交互面板在每个 worktree 块中同样显示来源分支行。
|
|
110
|
+
|
|
111
|
+
`clawt list` 文本输出在路径与分支行中追加来源分支:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
~/.clawt/worktrees/project/feature-login [feature-login] <- test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
没有元数据时:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
~/.clawt/worktrees/project/feature-login [feature-login] <- 未记录
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
JSON 输出新增 `baseBranch` 字段:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"path": "~/.clawt/worktrees/project/feature-login",
|
|
128
|
+
"branch": "feature-login",
|
|
129
|
+
"baseBranch": "test"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
历史 worktree 无元数据时:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"path": "~/.clawt/worktrees/project/legacy",
|
|
138
|
+
"branch": "legacy",
|
|
139
|
+
"baseBranch": null
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 清理规则
|
|
144
|
+
|
|
145
|
+
`cleanupWorktrees()` 删除 worktree、普通分支和 validate 分支时,同步删除对应 metadata 文件。删除 metadata 失败不阻断主清理流程,只记录日志。
|
|
146
|
+
|
|
147
|
+
## 错误处理
|
|
148
|
+
|
|
149
|
+
- 元数据文件不存在:返回 `null`,展示未记录。
|
|
150
|
+
- 元数据 JSON 解析失败:记录 warning,返回 `null`。
|
|
151
|
+
- 保存 metadata 失败:记录错误并抛出,让创建流程暴露问题,因为创建后无法记录来源会削弱防误操作能力。
|
|
152
|
+
- 删除 metadata 失败:记录错误,不影响删除 worktree 的主流程。
|
|
153
|
+
|
|
154
|
+
## 测试要求
|
|
155
|
+
|
|
156
|
+
- `createWorktrees()` 和 `createWorktreesByBranches()` 创建成功后写入 `baseBranch`。
|
|
157
|
+
- `getProjectWorktrees()` 能读取已有 `baseBranch`,缺失时返回 `null`。
|
|
158
|
+
- `cleanupWorktrees()` 删除对应 metadata。
|
|
159
|
+
- `clawt list --json` 输出 `baseBranch`。
|
|
160
|
+
- `clawt status --json` 输出 `baseBranch`。
|
|
161
|
+
- `status` 文本输出和 `status -i` 渲染显示来源分支。
|
|
162
|
+
- 元数据解析失败不导致 status/list 崩溃。
|
|
163
|
+
|
|
164
|
+
## 验收标准
|
|
165
|
+
|
|
166
|
+
- 新创建的 worktree 在 `status`、`status -i`、`list` 中能看到来源分支。
|
|
167
|
+
- 旧 worktree 没有 metadata 时显示未记录,JSON 为 `null`。
|
|
168
|
+
- 删除 worktree 时对应 metadata 文件被清理。
|
|
169
|
+
- 现有 `status`、`list`、`create` 单元测试通过,新增测试覆盖来源分支功能。
|