@sylphx/flow 1.8.1 → 2.0.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.
- package/CHANGELOG.md +79 -0
- package/UPGRADE.md +140 -0
- package/assets/output-styles/silent.md +4 -0
- package/package.json +2 -1
- package/src/commands/flow/execute-v2.ts +278 -0
- package/src/commands/flow/execute.ts +1 -18
- package/src/commands/flow/types.ts +3 -2
- package/src/commands/flow-command.ts +32 -69
- package/src/commands/flow-orchestrator.ts +18 -55
- package/src/commands/run-command.ts +12 -6
- package/src/commands/settings-command.ts +529 -0
- package/src/core/attach-manager.ts +482 -0
- package/src/core/backup-manager.ts +308 -0
- package/src/core/cleanup-handler.ts +166 -0
- package/src/core/flow-executor.ts +323 -0
- package/src/core/git-stash-manager.ts +133 -0
- package/src/core/project-manager.ts +274 -0
- package/src/core/secrets-manager.ts +229 -0
- package/src/core/session-manager.ts +268 -0
- package/src/core/template-loader.ts +189 -0
- package/src/core/upgrade-manager.ts +79 -47
- package/src/index.ts +13 -27
- package/src/services/first-run-setup.ts +220 -0
- package/src/services/global-config.ts +337 -0
- package/src/utils/__tests__/package-manager-detector.test.ts +163 -0
- package/src/utils/agent-enhancer.ts +40 -22
- package/src/utils/errors.ts +9 -0
- package/src/utils/package-manager-detector.ts +139 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow Executor
|
|
3
|
+
* Orchestrates the complete attach-mode flow execution
|
|
4
|
+
* Handles backup → attach → run → restore lifecycle
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { ProjectManager } from './project-manager.js';
|
|
9
|
+
import { SessionManager } from './session-manager.js';
|
|
10
|
+
import { BackupManager } from './backup-manager.js';
|
|
11
|
+
import { AttachManager } from './attach-manager.js';
|
|
12
|
+
import { SecretsManager } from './secrets-manager.js';
|
|
13
|
+
import { CleanupHandler } from './cleanup-handler.js';
|
|
14
|
+
import { TemplateLoader } from './template-loader.js';
|
|
15
|
+
import { GitStashManager } from './git-stash-manager.js';
|
|
16
|
+
|
|
17
|
+
export interface FlowExecutorOptions {
|
|
18
|
+
verbose?: boolean;
|
|
19
|
+
skipBackup?: boolean;
|
|
20
|
+
skipSecrets?: boolean;
|
|
21
|
+
merge?: boolean; // Merge mode: keep user files (default: replace all)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class FlowExecutor {
|
|
25
|
+
private projectManager: ProjectManager;
|
|
26
|
+
private sessionManager: SessionManager;
|
|
27
|
+
private backupManager: BackupManager;
|
|
28
|
+
private attachManager: AttachManager;
|
|
29
|
+
private secretsManager: SecretsManager;
|
|
30
|
+
private cleanupHandler: CleanupHandler;
|
|
31
|
+
private templateLoader: TemplateLoader;
|
|
32
|
+
private gitStashManager: GitStashManager;
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.projectManager = new ProjectManager();
|
|
36
|
+
this.sessionManager = new SessionManager(this.projectManager);
|
|
37
|
+
this.backupManager = new BackupManager(this.projectManager);
|
|
38
|
+
this.attachManager = new AttachManager(this.projectManager);
|
|
39
|
+
this.secretsManager = new SecretsManager(this.projectManager);
|
|
40
|
+
this.cleanupHandler = new CleanupHandler(
|
|
41
|
+
this.projectManager,
|
|
42
|
+
this.sessionManager,
|
|
43
|
+
this.backupManager
|
|
44
|
+
);
|
|
45
|
+
this.templateLoader = new TemplateLoader();
|
|
46
|
+
this.gitStashManager = new GitStashManager();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute complete flow with attach mode (with multi-session support)
|
|
51
|
+
*/
|
|
52
|
+
async execute(
|
|
53
|
+
projectPath: string,
|
|
54
|
+
options: FlowExecutorOptions = {}
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
// Initialize Flow directories
|
|
57
|
+
await this.projectManager.initialize();
|
|
58
|
+
|
|
59
|
+
// Step 1: Crash recovery on startup
|
|
60
|
+
await this.cleanupHandler.recoverOnStartup();
|
|
61
|
+
|
|
62
|
+
// Step 2: Get project hash and paths
|
|
63
|
+
const projectHash = this.projectManager.getProjectHash(projectPath);
|
|
64
|
+
const target = await this.projectManager.detectTarget(projectPath);
|
|
65
|
+
|
|
66
|
+
if (options.verbose) {
|
|
67
|
+
console.log(chalk.dim(`Project: ${projectPath}`));
|
|
68
|
+
console.log(chalk.dim(`Hash: ${projectHash}`));
|
|
69
|
+
console.log(chalk.dim(`Target: ${target}\n`));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check for existing session
|
|
73
|
+
const existingSession = await this.sessionManager.getActiveSession(projectHash);
|
|
74
|
+
|
|
75
|
+
if (existingSession) {
|
|
76
|
+
// Joining existing session
|
|
77
|
+
console.log(chalk.cyan('🔗 Joining existing session...'));
|
|
78
|
+
|
|
79
|
+
const { session, isFirstSession } = await this.sessionManager.startSession(
|
|
80
|
+
projectPath,
|
|
81
|
+
projectHash,
|
|
82
|
+
target,
|
|
83
|
+
existingSession.backupPath
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Register cleanup hooks
|
|
87
|
+
this.cleanupHandler.registerCleanupHooks(projectHash);
|
|
88
|
+
|
|
89
|
+
console.log(chalk.green(` ✓ Joined session (${session.refCount} active session(s))\n`));
|
|
90
|
+
console.log(chalk.green('✓ Flow environment ready!\n'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// First session - stash settings changes, then create backup and attach
|
|
95
|
+
// Step 3: Stash git changes to hide Flow's modifications from git status
|
|
96
|
+
console.log(chalk.cyan('🔍 Checking git status...'));
|
|
97
|
+
await this.gitStashManager.stashSettingsChanges(projectPath);
|
|
98
|
+
|
|
99
|
+
console.log(chalk.cyan('💾 Creating backup...'));
|
|
100
|
+
const backup = await this.backupManager.createBackup(
|
|
101
|
+
projectPath,
|
|
102
|
+
projectHash,
|
|
103
|
+
target
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Step 4: Extract and save secrets
|
|
107
|
+
if (!options.skipSecrets) {
|
|
108
|
+
console.log(chalk.cyan('🔐 Extracting secrets...'));
|
|
109
|
+
const secrets = await this.secretsManager.extractMCPSecrets(
|
|
110
|
+
projectPath,
|
|
111
|
+
projectHash,
|
|
112
|
+
target
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (Object.keys(secrets.servers).length > 0) {
|
|
116
|
+
await this.secretsManager.saveSecrets(projectHash, secrets);
|
|
117
|
+
console.log(
|
|
118
|
+
chalk.green(` ✓ Saved ${Object.keys(secrets.servers).length} MCP secret(s)`)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 5: Start session (use backup's sessionId to ensure consistency)
|
|
124
|
+
const { session, isFirstSession } = await this.sessionManager.startSession(
|
|
125
|
+
projectPath,
|
|
126
|
+
projectHash,
|
|
127
|
+
target,
|
|
128
|
+
backup.backupPath,
|
|
129
|
+
backup.sessionId
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Step 6: Register cleanup hooks
|
|
133
|
+
this.cleanupHandler.registerCleanupHooks(projectHash);
|
|
134
|
+
|
|
135
|
+
// Step 7: Default replace mode - clear user files before attaching (unless merge flag is set)
|
|
136
|
+
if (!options.merge) {
|
|
137
|
+
console.log(chalk.cyan('🔄 Clearing existing settings...'));
|
|
138
|
+
await this.clearUserSettings(projectPath, target);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Step 8: Load templates
|
|
142
|
+
console.log(chalk.cyan('📦 Loading Flow templates...'));
|
|
143
|
+
const templates = await this.templateLoader.loadTemplates(target);
|
|
144
|
+
|
|
145
|
+
// Step 9: Attach Flow environment
|
|
146
|
+
console.log(chalk.cyan('🚀 Attaching Flow environment...'));
|
|
147
|
+
const manifest = await this.backupManager.getManifest(projectHash, session.sessionId);
|
|
148
|
+
|
|
149
|
+
if (!manifest) {
|
|
150
|
+
throw new Error('Backup manifest not found');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const attachResult = await this.attachManager.attach(
|
|
154
|
+
projectPath,
|
|
155
|
+
projectHash,
|
|
156
|
+
target,
|
|
157
|
+
templates,
|
|
158
|
+
manifest
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Update manifest with attach results
|
|
162
|
+
await this.backupManager.updateManifest(projectHash, session.sessionId, manifest);
|
|
163
|
+
|
|
164
|
+
// Show summary
|
|
165
|
+
this.showAttachSummary(attachResult);
|
|
166
|
+
|
|
167
|
+
console.log(chalk.green('\n✓ Flow environment ready!\n'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear user settings in replace mode
|
|
172
|
+
*/
|
|
173
|
+
private async clearUserSettings(
|
|
174
|
+
projectPath: string,
|
|
175
|
+
target: 'claude-code' | 'opencode'
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
const targetDir = this.projectManager.getTargetConfigDir(projectPath, target);
|
|
178
|
+
const fs = await import('node:fs/promises');
|
|
179
|
+
const path = await import('node:path');
|
|
180
|
+
const { existsSync } = await import('node:fs');
|
|
181
|
+
|
|
182
|
+
if (!existsSync(targetDir)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get directory names for this target
|
|
187
|
+
const dirs = target === 'claude-code'
|
|
188
|
+
? { agents: 'agents', commands: 'commands' }
|
|
189
|
+
: { agents: 'agent', commands: 'command' };
|
|
190
|
+
|
|
191
|
+
// Clear agents directory
|
|
192
|
+
const agentsDir = path.join(targetDir, dirs.agents);
|
|
193
|
+
if (existsSync(agentsDir)) {
|
|
194
|
+
const files = await fs.readdir(agentsDir);
|
|
195
|
+
for (const file of files) {
|
|
196
|
+
await fs.unlink(path.join(agentsDir, file));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Clear commands directory
|
|
201
|
+
const commandsDir = path.join(targetDir, dirs.commands);
|
|
202
|
+
if (existsSync(commandsDir)) {
|
|
203
|
+
const files = await fs.readdir(commandsDir);
|
|
204
|
+
for (const file of files) {
|
|
205
|
+
await fs.unlink(path.join(commandsDir, file));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Clear MCP configuration
|
|
210
|
+
const configPath = target === 'claude-code'
|
|
211
|
+
? path.join(targetDir, 'settings.json')
|
|
212
|
+
: path.join(targetDir, '.mcp.json');
|
|
213
|
+
|
|
214
|
+
if (existsSync(configPath)) {
|
|
215
|
+
// Clear MCP servers section only, keep other settings
|
|
216
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
217
|
+
if (target === 'claude-code') {
|
|
218
|
+
if (config.mcp?.servers) {
|
|
219
|
+
config.mcp.servers = {};
|
|
220
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// For opencode, clear the entire .mcp.json
|
|
224
|
+
await fs.writeFile(configPath, JSON.stringify({ servers: {} }, null, 2));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Clear hooks directory if exists
|
|
229
|
+
const hooksDir = path.join(targetDir, 'hooks');
|
|
230
|
+
if (existsSync(hooksDir)) {
|
|
231
|
+
const files = await fs.readdir(hooksDir);
|
|
232
|
+
for (const file of files) {
|
|
233
|
+
await fs.unlink(path.join(hooksDir, file));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Cleanup after execution
|
|
240
|
+
*/
|
|
241
|
+
async cleanup(projectPath: string): Promise<void> {
|
|
242
|
+
const projectHash = this.projectManager.getProjectHash(projectPath);
|
|
243
|
+
|
|
244
|
+
console.log(chalk.cyan('\n🧹 Cleaning up...'));
|
|
245
|
+
|
|
246
|
+
await this.cleanupHandler.cleanup(projectHash);
|
|
247
|
+
|
|
248
|
+
// Restore stashed git changes
|
|
249
|
+
await this.gitStashManager.popSettingsChanges(projectPath);
|
|
250
|
+
|
|
251
|
+
console.log(chalk.green(' ✓ Environment restored'));
|
|
252
|
+
console.log(chalk.green(' ✓ Secrets preserved for next run\n'));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Show attach summary
|
|
257
|
+
*/
|
|
258
|
+
private showAttachSummary(result: any): void {
|
|
259
|
+
const items = [];
|
|
260
|
+
|
|
261
|
+
if (result.agentsAdded.length > 0) {
|
|
262
|
+
items.push(
|
|
263
|
+
`${result.agentsAdded.length} agent${result.agentsAdded.length > 1 ? 's' : ''}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (result.commandsAdded.length > 0) {
|
|
268
|
+
items.push(
|
|
269
|
+
`${result.commandsAdded.length} command${result.commandsAdded.length > 1 ? 's' : ''}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (result.mcpServersAdded.length > 0) {
|
|
274
|
+
items.push(
|
|
275
|
+
`${result.mcpServersAdded.length} MCP server${result.mcpServersAdded.length > 1 ? 's' : ''}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (result.hooksAdded.length > 0) {
|
|
280
|
+
items.push(
|
|
281
|
+
`${result.hooksAdded.length} hook${result.hooksAdded.length > 1 ? 's' : ''}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (result.rulesAppended) {
|
|
286
|
+
items.push('rules');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (items.length > 0) {
|
|
290
|
+
console.log(chalk.green(` ✓ Added: ${items.join(', ')}`));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const overridden = result.agentsOverridden.length +
|
|
294
|
+
result.commandsOverridden.length +
|
|
295
|
+
result.mcpServersOverridden.length +
|
|
296
|
+
result.hooksOverridden.length;
|
|
297
|
+
|
|
298
|
+
if (overridden > 0) {
|
|
299
|
+
console.log(chalk.yellow(` ⚠ Overridden: ${overridden} item${overridden > 1 ? 's' : ''}`));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get project manager (for external use)
|
|
305
|
+
*/
|
|
306
|
+
getProjectManager(): ProjectManager {
|
|
307
|
+
return this.projectManager;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get session manager (for external use)
|
|
312
|
+
*/
|
|
313
|
+
getSessionManager(): SessionManager {
|
|
314
|
+
return this.sessionManager;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get cleanup handler (for external use)
|
|
319
|
+
*/
|
|
320
|
+
getCleanupHandler(): CleanupHandler {
|
|
321
|
+
return this.cleanupHandler;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Worktree Manager
|
|
3
|
+
* Uses git update-index --skip-worktree to hide Flow's settings changes from git status
|
|
4
|
+
* Prevents LLM from accidentally committing Flow's temporary changes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { exec } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
|
|
16
|
+
export class GitStashManager {
|
|
17
|
+
private skipWorktreeFiles: string[] = [];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if project is in a git repository
|
|
21
|
+
*/
|
|
22
|
+
async isGitRepo(projectPath: string): Promise<boolean> {
|
|
23
|
+
try {
|
|
24
|
+
await execAsync('git rev-parse --git-dir', { cwd: projectPath });
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get all tracked files in settings directories
|
|
33
|
+
*/
|
|
34
|
+
async getTrackedSettingsFiles(projectPath: string): Promise<string[]> {
|
|
35
|
+
const files: string[] = [];
|
|
36
|
+
|
|
37
|
+
// Check .claude directory
|
|
38
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
39
|
+
if (existsSync(claudeDir)) {
|
|
40
|
+
try {
|
|
41
|
+
const { stdout } = await execAsync('git ls-files .claude', { cwd: projectPath });
|
|
42
|
+
const claudeFiles = stdout.trim().split('\n').filter(f => f);
|
|
43
|
+
files.push(...claudeFiles);
|
|
44
|
+
} catch {
|
|
45
|
+
// Directory not tracked in git
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check .opencode directory
|
|
50
|
+
const opencodeDir = path.join(projectPath, '.opencode');
|
|
51
|
+
if (existsSync(opencodeDir)) {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execAsync('git ls-files .opencode', { cwd: projectPath });
|
|
54
|
+
const opencodeFiles = stdout.trim().split('\n').filter(f => f);
|
|
55
|
+
files.push(...opencodeFiles);
|
|
56
|
+
} catch {
|
|
57
|
+
// Directory not tracked in git
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Mark settings files as skip-worktree before attach
|
|
66
|
+
* This hides Flow's settings modifications from git status
|
|
67
|
+
*/
|
|
68
|
+
async stashSettingsChanges(projectPath: string): Promise<void> {
|
|
69
|
+
// Check if in git repo
|
|
70
|
+
const inGitRepo = await this.isGitRepo(projectPath);
|
|
71
|
+
if (!inGitRepo) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Get all tracked settings files
|
|
76
|
+
const files = await this.getTrackedSettingsFiles(projectPath);
|
|
77
|
+
if (files.length === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Mark each file as skip-worktree
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
try {
|
|
85
|
+
await execAsync(`git update-index --skip-worktree "${file}"`, { cwd: projectPath });
|
|
86
|
+
this.skipWorktreeFiles.push(file);
|
|
87
|
+
} catch {
|
|
88
|
+
// File might not exist or not tracked, skip it
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.skipWorktreeFiles.length > 0) {
|
|
93
|
+
console.log(chalk.dim(` ✓ Hiding ${this.skipWorktreeFiles.length} settings file(s) from git\n`));
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.log(chalk.yellow(' ⚠️ Could not hide settings from git\n'));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Unmark settings files as skip-worktree after restore
|
|
102
|
+
* This restores normal git tracking
|
|
103
|
+
*/
|
|
104
|
+
async popSettingsChanges(projectPath: string): Promise<void> {
|
|
105
|
+
if (this.skipWorktreeFiles.length === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Unmark each file
|
|
111
|
+
for (const file of this.skipWorktreeFiles) {
|
|
112
|
+
try {
|
|
113
|
+
await execAsync(`git update-index --no-skip-worktree "${file}"`, { cwd: projectPath });
|
|
114
|
+
} catch {
|
|
115
|
+
// File might have been deleted, skip it
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(chalk.dim(` ✓ Restored git tracking for ${this.skipWorktreeFiles.length} file(s)\n`));
|
|
120
|
+
this.skipWorktreeFiles = [];
|
|
121
|
+
} catch (error: any) {
|
|
122
|
+
console.log(chalk.yellow(' ⚠️ Could not restore git tracking'));
|
|
123
|
+
console.log(chalk.yellow(' Run manually: git update-index --no-skip-worktree .claude/* .opencode/*\n'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reset state (for cleanup)
|
|
129
|
+
*/
|
|
130
|
+
reset(): void {
|
|
131
|
+
this.skipWorktreeFiles = [];
|
|
132
|
+
}
|
|
133
|
+
}
|