gsd-opencode 1.9.2 → 1.10.1
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/gsd-debugger.md +5 -5
- package/bin/gsd-install.js +105 -0
- package/bin/gsd.js +352 -0
- package/{command → commands}/gsd/add-phase.md +1 -1
- package/{command → commands}/gsd/audit-milestone.md +1 -1
- package/{command → commands}/gsd/debug.md +3 -3
- package/{command → commands}/gsd/discuss-phase.md +1 -1
- package/{command → commands}/gsd/execute-phase.md +1 -1
- package/{command → commands}/gsd/list-phase-assumptions.md +1 -1
- package/{command → commands}/gsd/map-codebase.md +1 -1
- package/{command → commands}/gsd/new-milestone.md +1 -1
- package/{command → commands}/gsd/new-project.md +3 -3
- package/{command → commands}/gsd/plan-phase.md +2 -2
- package/{command → commands}/gsd/research-phase.md +1 -1
- package/{command → commands}/gsd/verify-work.md +1 -1
- package/get-shit-done/workflows/list-phase-assumptions.md +1 -1
- package/get-shit-done/workflows/verify-work.md +5 -5
- package/lib/constants.js +193 -0
- package/package.json +34 -20
- package/src/commands/check.js +329 -0
- package/src/commands/config.js +337 -0
- package/src/commands/install.js +608 -0
- package/src/commands/list.js +256 -0
- package/src/commands/repair.js +519 -0
- package/src/commands/uninstall.js +732 -0
- package/src/commands/update.js +444 -0
- package/src/services/backup-manager.js +585 -0
- package/src/services/config.js +262 -0
- package/src/services/file-ops.js +830 -0
- package/src/services/health-checker.js +475 -0
- package/src/services/manifest-manager.js +301 -0
- package/src/services/migration-service.js +831 -0
- package/src/services/repair-service.js +846 -0
- package/src/services/scope-manager.js +303 -0
- package/src/services/settings.js +553 -0
- package/src/services/structure-detector.js +240 -0
- package/src/services/update-service.js +863 -0
- package/src/utils/hash.js +71 -0
- package/src/utils/interactive.js +222 -0
- package/src/utils/logger.js +128 -0
- package/src/utils/npm-registry.js +255 -0
- package/src/utils/path-resolver.js +226 -0
- /package/{command → commands}/gsd/add-todo.md +0 -0
- /package/{command → commands}/gsd/check-todos.md +0 -0
- /package/{command → commands}/gsd/complete-milestone.md +0 -0
- /package/{command → commands}/gsd/help.md +0 -0
- /package/{command → commands}/gsd/insert-phase.md +0 -0
- /package/{command → commands}/gsd/pause-work.md +0 -0
- /package/{command → commands}/gsd/plan-milestone-gaps.md +0 -0
- /package/{command → commands}/gsd/progress.md +0 -0
- /package/{command → commands}/gsd/quick.md +0 -0
- /package/{command → commands}/gsd/remove-phase.md +0 -0
- /package/{command → commands}/gsd/resume-work.md +0 -0
- /package/{command → commands}/gsd/set-model.md +0 -0
- /package/{command → commands}/gsd/set-profile.md +0 -0
- /package/{command → commands}/gsd/settings.md +0 -0
- /package/{command → commands}/gsd/update.md +0 -0
- /package/{command → commands}/gsd/whats-new.md +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair command for GSD-OpenCode CLI.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the repair functionality to detect and fix broken
|
|
5
|
+
* installations, showing a summary of issues before fixing, requiring
|
|
6
|
+
* interactive confirmation, and displaying detailed post-repair reports.
|
|
7
|
+
*
|
|
8
|
+
* Implements requirements:
|
|
9
|
+
* - CLI-04: User can run gsd-opencode repair to fix broken installations
|
|
10
|
+
* - REPAIR-01: Repair detects and reinstalls missing files
|
|
11
|
+
* - REPAIR-02: Repair detects and replaces corrupted files
|
|
12
|
+
* - REPAIR-03: Repair fixes broken path references in .md files
|
|
13
|
+
* - REPAIR-04: Repair shows summary of issues found before fixing
|
|
14
|
+
* - REPAIR-05: Repair requires interactive confirmation before making changes
|
|
15
|
+
* - ERROR-01: All commands handle permission errors (EACCES) with exit code 2
|
|
16
|
+
* - ERROR-02: All commands handle signal interrupts (SIGINT/SIGTERM) gracefully
|
|
17
|
+
* - ERROR-03: All commands support --verbose flag for detailed debugging output
|
|
18
|
+
*
|
|
19
|
+
* @module commands/repair
|
|
20
|
+
* @description Repair command for fixing broken GSD-OpenCode installations
|
|
21
|
+
* @example
|
|
22
|
+
* // Repair with auto-detection
|
|
23
|
+
* await repairCommand({});
|
|
24
|
+
*
|
|
25
|
+
* // Repair global installation
|
|
26
|
+
* await repairCommand({ global: true });
|
|
27
|
+
*
|
|
28
|
+
* // Repair with verbose output
|
|
29
|
+
* await repairCommand({ verbose: true });
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { ScopeManager } from '../services/scope-manager.js';
|
|
33
|
+
import { BackupManager } from '../services/backup-manager.js';
|
|
34
|
+
import { FileOperations } from '../services/file-ops.js';
|
|
35
|
+
import { RepairService } from '../services/repair-service.js';
|
|
36
|
+
import { STRUCTURE_TYPES } from '../services/structure-detector.js';
|
|
37
|
+
import { promptConfirmation } from '../utils/interactive.js';
|
|
38
|
+
import { logger, setVerbose } from '../utils/logger.js';
|
|
39
|
+
import { ERROR_CODES } from '../../lib/constants.js';
|
|
40
|
+
import fs from 'fs/promises';
|
|
41
|
+
import path from 'path';
|
|
42
|
+
import { fileURLToPath } from 'url';
|
|
43
|
+
import ora from 'ora';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Gets the package version from package.json.
|
|
47
|
+
*
|
|
48
|
+
* @returns {Promise<string>} The package version
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
async function getPackageVersion() {
|
|
52
|
+
try {
|
|
53
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
54
|
+
const __dirname = path.dirname(__filename);
|
|
55
|
+
const packageRoot = path.resolve(__dirname, '../..');
|
|
56
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
57
|
+
|
|
58
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
59
|
+
const pkg = JSON.parse(content);
|
|
60
|
+
return pkg.version || '1.0.0';
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.warning('Could not read package version, using 1.0.0');
|
|
63
|
+
return '1.0.0';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Displays a formatted summary of detected issues.
|
|
69
|
+
*
|
|
70
|
+
* Groups issues by category: Missing Files, Corrupted Files, Path Issues.
|
|
71
|
+
* Shows count and lists files for each category.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} issues - Issues object from detectIssues()
|
|
74
|
+
* @param {string} scopeLabel - Label for this scope (Global/Local)
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
function displayIssuesSummary(issues, scopeLabel) {
|
|
78
|
+
logger.heading(`${scopeLabel} Installation Repair`);
|
|
79
|
+
logger.dim('================================');
|
|
80
|
+
logger.dim('');
|
|
81
|
+
|
|
82
|
+
// Missing Files
|
|
83
|
+
if (issues.missingFiles && issues.missingFiles.length > 0) {
|
|
84
|
+
logger.info(`Missing Files (${issues.missingFiles.length}):`);
|
|
85
|
+
for (const file of issues.missingFiles) {
|
|
86
|
+
logger.dim(` ✗ ${file.name}`);
|
|
87
|
+
}
|
|
88
|
+
logger.dim('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Corrupted Files
|
|
92
|
+
if (issues.corruptedFiles && issues.corruptedFiles.length > 0) {
|
|
93
|
+
logger.info(`Corrupted Files (${issues.corruptedFiles.length}):`);
|
|
94
|
+
for (const file of issues.corruptedFiles) {
|
|
95
|
+
logger.dim(` ✗ ${file.relative}`);
|
|
96
|
+
}
|
|
97
|
+
logger.dim('');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Path Issues
|
|
101
|
+
if (issues.pathIssues && issues.pathIssues.length > 0) {
|
|
102
|
+
logger.info(`Path Issues (${issues.pathIssues.length}):`);
|
|
103
|
+
for (const file of issues.pathIssues) {
|
|
104
|
+
logger.dim(` ✗ ${file.relative}`);
|
|
105
|
+
}
|
|
106
|
+
logger.dim('');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Total
|
|
110
|
+
logger.info(`Total issues: ${issues.totalIssues}`);
|
|
111
|
+
logger.dim('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Displays detailed post-repair results.
|
|
116
|
+
*
|
|
117
|
+
* Shows success/failure status for each repaired file grouped by category.
|
|
118
|
+
*
|
|
119
|
+
* @param {Object} results - Repair results from repairService.repair()
|
|
120
|
+
* @param {string} scopeLabel - Label for this scope (Global/Local)
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
function displayRepairResults(results, scopeLabel) {
|
|
124
|
+
logger.heading(`Repair Results for ${scopeLabel} Installation`);
|
|
125
|
+
logger.dim('=====================================');
|
|
126
|
+
logger.dim('');
|
|
127
|
+
|
|
128
|
+
// Missing Files results
|
|
129
|
+
if (results.results.missing && results.results.missing.length > 0) {
|
|
130
|
+
const succeeded = results.results.missing.filter(r => r.success).length;
|
|
131
|
+
const failed = results.results.missing.filter(r => !r.success).length;
|
|
132
|
+
const status = failed === 0 ? `${succeeded} fixed` : `${succeeded} fixed, ${failed} failed`;
|
|
133
|
+
logger.info(`Missing Files: ${status}`);
|
|
134
|
+
|
|
135
|
+
for (const result of results.results.missing) {
|
|
136
|
+
if (result.success) {
|
|
137
|
+
logger.success(` ✓ ${path.basename(result.file)}`);
|
|
138
|
+
} else {
|
|
139
|
+
logger.error(` ✗ ${path.basename(result.file)} (${result.error})`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
logger.dim('');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Corrupted Files results
|
|
146
|
+
if (results.results.corrupted && results.results.corrupted.length > 0) {
|
|
147
|
+
const succeeded = results.results.corrupted.filter(r => r.success).length;
|
|
148
|
+
const failed = results.results.corrupted.filter(r => !r.success).length;
|
|
149
|
+
const status = failed === 0 ? `${succeeded} fixed` : `${succeeded} fixed, ${failed} failed`;
|
|
150
|
+
logger.info(`Corrupted Files: ${status}`);
|
|
151
|
+
|
|
152
|
+
for (const result of results.results.corrupted) {
|
|
153
|
+
const fileName = path.basename(result.file);
|
|
154
|
+
if (result.success) {
|
|
155
|
+
logger.success(` ✓ ${fileName}`);
|
|
156
|
+
} else {
|
|
157
|
+
logger.error(` ✗ ${fileName} (${result.error})`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
logger.dim('');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Path Issues results
|
|
164
|
+
if (results.results.paths && results.results.paths.length > 0) {
|
|
165
|
+
const succeeded = results.results.paths.filter(r => r.success).length;
|
|
166
|
+
const failed = results.results.paths.filter(r => !r.success).length;
|
|
167
|
+
const status = failed === 0 ? `${succeeded} fixed` : `${succeeded} fixed, ${failed} failed`;
|
|
168
|
+
logger.info(`Path Issues: ${status}`);
|
|
169
|
+
|
|
170
|
+
for (const result of results.results.paths) {
|
|
171
|
+
const fileName = path.basename(result.file);
|
|
172
|
+
if (result.success) {
|
|
173
|
+
logger.success(` ✓ ${fileName}`);
|
|
174
|
+
} else {
|
|
175
|
+
logger.error(` ✗ ${fileName} (${result.error})`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
logger.dim('');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Summary
|
|
182
|
+
const totalSucceeded = results.stats.succeeded;
|
|
183
|
+
const totalFailed = results.stats.failed;
|
|
184
|
+
logger.info(`Summary: ${totalSucceeded} succeeded, ${totalFailed} failed`);
|
|
185
|
+
logger.dim('');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Checks a single scope for issues and returns results.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} scope - 'global' or 'local'
|
|
192
|
+
* @param {Object} options - Options
|
|
193
|
+
* @param {boolean} options.verbose - Enable verbose output
|
|
194
|
+
* @param {boolean} options.fixStructure - Fix structure issues
|
|
195
|
+
* @param {boolean} options.fixAll - Fix all issues including structure
|
|
196
|
+
* @returns {Promise<Object>} Check results with installed flag, issues, and scopeLabel
|
|
197
|
+
* @private
|
|
198
|
+
*/
|
|
199
|
+
async function checkScope(scope, options = {}) {
|
|
200
|
+
const scopeManager = new ScopeManager({ scope });
|
|
201
|
+
const scopeLabel = scope.charAt(0).toUpperCase() + scope.slice(1);
|
|
202
|
+
|
|
203
|
+
logger.debug(`Checking ${scope} installation for issues...`);
|
|
204
|
+
|
|
205
|
+
const isInstalled = await scopeManager.isInstalled();
|
|
206
|
+
if (!isInstalled) {
|
|
207
|
+
logger.debug(`No ${scope} installation found`);
|
|
208
|
+
return {
|
|
209
|
+
installed: false,
|
|
210
|
+
scope,
|
|
211
|
+
scopeLabel,
|
|
212
|
+
issues: null
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
logger.debug(`${scope} installation detected, detecting issues...`);
|
|
217
|
+
|
|
218
|
+
const backupManager = new BackupManager(scopeManager, logger);
|
|
219
|
+
const fileOps = new FileOperations(scopeManager, logger);
|
|
220
|
+
const expectedVersion = await getPackageVersion();
|
|
221
|
+
|
|
222
|
+
const repairService = new RepairService({
|
|
223
|
+
scopeManager,
|
|
224
|
+
backupManager,
|
|
225
|
+
fileOps,
|
|
226
|
+
logger,
|
|
227
|
+
expectedVersion
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
// Check structure first
|
|
232
|
+
const structureCheck = await repairService.checkStructure();
|
|
233
|
+
|
|
234
|
+
// Handle structure issues if requested
|
|
235
|
+
if ((options.fixStructure || options.fixAll) && structureCheck.canRepair) {
|
|
236
|
+
logger.info(`Repairing ${scope} structure (${structureCheck.type})...`);
|
|
237
|
+
|
|
238
|
+
const structureResult = structureCheck.type === STRUCTURE_TYPES.DUAL
|
|
239
|
+
? await repairService.fixDualStructure()
|
|
240
|
+
: await repairService.repairStructure();
|
|
241
|
+
|
|
242
|
+
if (structureResult.repaired || structureResult.fixed) {
|
|
243
|
+
logger.success(`Structure repaired: ${structureResult.message}`);
|
|
244
|
+
if (structureResult.backup) {
|
|
245
|
+
logger.dim(`Backup created: ${structureResult.backup}`);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
logger.warning(`Structure repair: ${structureResult.message}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
logger.dim('');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Detect file issues
|
|
255
|
+
const issues = await repairService.detectIssues();
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
installed: true,
|
|
259
|
+
scope,
|
|
260
|
+
scopeLabel,
|
|
261
|
+
issues,
|
|
262
|
+
repairService,
|
|
263
|
+
backupManager,
|
|
264
|
+
structureCheck
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
logger.debug(`Error during issue detection: ${error.message}`);
|
|
268
|
+
return {
|
|
269
|
+
installed: true,
|
|
270
|
+
scope,
|
|
271
|
+
scopeLabel,
|
|
272
|
+
issues: null,
|
|
273
|
+
error: error.message
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Main repair command function.
|
|
280
|
+
*
|
|
281
|
+
* Orchestrates the repair process:
|
|
282
|
+
* 1. Parse options and set verbose mode
|
|
283
|
+
* 2. Determine scopes to check (global, local, or both)
|
|
284
|
+
* 3. For each scope:
|
|
285
|
+
* - Detect installation issues using RepairService
|
|
286
|
+
* - Check for structure issues (dual/old structure)
|
|
287
|
+
* - Repair structure if --fix-structure or --fix-all flag provided
|
|
288
|
+
* - Display summary of issues found
|
|
289
|
+
* - Prompt for user confirmation
|
|
290
|
+
* - Perform repairs with progress indication
|
|
291
|
+
* - Display post-repair results
|
|
292
|
+
* 4. Return appropriate exit code
|
|
293
|
+
*
|
|
294
|
+
* @param {Object} options - Command options
|
|
295
|
+
* @param {boolean} [options.global] - Repair global installation only
|
|
296
|
+
* @param {boolean} [options.local] - Repair local installation only
|
|
297
|
+
* @param {boolean} [options.verbose] - Enable verbose output for debugging
|
|
298
|
+
* @param {boolean} [options.fixStructure] - Fix structure issues (migrate old to new)
|
|
299
|
+
* @param {boolean} [options.fixAll] - Fix all issues including structure
|
|
300
|
+
* @returns {Promise<number>} Exit code (0=success, 1=error, 2=permission, 130=interrupted)
|
|
301
|
+
* @async
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* // Repair with auto-detection
|
|
305
|
+
* const exitCode = await repairCommand({});
|
|
306
|
+
*
|
|
307
|
+
* // Repair global installation
|
|
308
|
+
* const exitCode = await repairCommand({ global: true });
|
|
309
|
+
*
|
|
310
|
+
* // Repair with verbose output
|
|
311
|
+
* const exitCode = await repairCommand({ verbose: true });
|
|
312
|
+
*
|
|
313
|
+
* // Fix structure issues only
|
|
314
|
+
* const exitCode = await repairCommand({ fixStructure: true });
|
|
315
|
+
*
|
|
316
|
+
* // Fix all issues including structure
|
|
317
|
+
* const exitCode = await repairCommand({ fixAll: true });
|
|
318
|
+
*/
|
|
319
|
+
export async function repairCommand(options = {}) {
|
|
320
|
+
const verbose = options.verbose || false;
|
|
321
|
+
const fixStructure = options.fixStructure || false;
|
|
322
|
+
const fixAll = options.fixAll || false;
|
|
323
|
+
setVerbose(verbose);
|
|
324
|
+
|
|
325
|
+
logger.debug('Starting repair command');
|
|
326
|
+
logger.debug(`Options: global=${options.global}, local=${options.local}, verbose=${verbose}, fixStructure=${fixStructure}, fixAll=${fixAll}`);
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
logger.heading('GSD-OpenCode Installation Repair');
|
|
330
|
+
logger.dim('================================');
|
|
331
|
+
logger.dim('');
|
|
332
|
+
|
|
333
|
+
// Determine scopes to check
|
|
334
|
+
const scopesToCheck = [];
|
|
335
|
+
if (options.global) {
|
|
336
|
+
scopesToCheck.push('global');
|
|
337
|
+
} else if (options.local) {
|
|
338
|
+
scopesToCheck.push('local');
|
|
339
|
+
} else {
|
|
340
|
+
scopesToCheck.push('global', 'local');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let anyInstalled = false;
|
|
344
|
+
let anyRepaired = false;
|
|
345
|
+
let anyFailed = false;
|
|
346
|
+
|
|
347
|
+
for (const scope of scopesToCheck) {
|
|
348
|
+
try {
|
|
349
|
+
const result = await checkScope(scope, options);
|
|
350
|
+
|
|
351
|
+
if (!result.installed) {
|
|
352
|
+
logger.info(`No installation found at ${result.scopeLabel.toLowerCase()} scope`);
|
|
353
|
+
logger.dim('');
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
anyInstalled = true;
|
|
358
|
+
|
|
359
|
+
if (result.error) {
|
|
360
|
+
logger.error(`Failed to check ${scope} installation: ${result.error}`);
|
|
361
|
+
anyFailed = true;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check if there are structure issues to report
|
|
366
|
+
const hasStructureIssues = result.structureCheck &&
|
|
367
|
+
(result.structureCheck.type === STRUCTURE_TYPES.OLD ||
|
|
368
|
+
result.structureCheck.type === STRUCTURE_TYPES.DUAL);
|
|
369
|
+
|
|
370
|
+
// Handle structure-only repair mode
|
|
371
|
+
if (fixStructure || fixAll) {
|
|
372
|
+
// Structure was already repaired in checkScope
|
|
373
|
+
if (!result.issues.hasIssues && !hasStructureIssues) {
|
|
374
|
+
logger.success(`No issues detected at ${result.scopeLabel.toLowerCase()} scope`);
|
|
375
|
+
logger.dim('');
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
} else if (!result.issues.hasIssues) {
|
|
379
|
+
// No file issues - but check for structure issues to report
|
|
380
|
+
if (hasStructureIssues) {
|
|
381
|
+
logger.warning(`No file issues, but structure requires attention`);
|
|
382
|
+
logger.info(` Current structure: ${result.structureCheck.type}`);
|
|
383
|
+
logger.info(` Run with --fix-structure to repair`);
|
|
384
|
+
logger.dim('');
|
|
385
|
+
} else {
|
|
386
|
+
logger.success(`No issues detected at ${result.scopeLabel.toLowerCase()} scope`);
|
|
387
|
+
logger.dim('');
|
|
388
|
+
}
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Display issues summary
|
|
393
|
+
if (scopesToCheck.length > 1) {
|
|
394
|
+
logger.dim('');
|
|
395
|
+
}
|
|
396
|
+
displayIssuesSummary(result.issues, result.scopeLabel);
|
|
397
|
+
|
|
398
|
+
// Prompt for confirmation (repair always requires confirmation)
|
|
399
|
+
logger.debug('Requesting user confirmation...');
|
|
400
|
+
const confirmed = await promptConfirmation('Proceed with repairs?', false);
|
|
401
|
+
|
|
402
|
+
// Handle SIGINT (Ctrl+C) - user cancelled with interrupt
|
|
403
|
+
if (confirmed === null) {
|
|
404
|
+
logger.info('Repair cancelled');
|
|
405
|
+
return ERROR_CODES.INTERRUPTED;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Handle explicit "no" response
|
|
409
|
+
if (!confirmed) {
|
|
410
|
+
logger.info('Repair cancelled');
|
|
411
|
+
return ERROR_CODES.SUCCESS;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
logger.debug('User confirmed repair');
|
|
415
|
+
|
|
416
|
+
// Perform repairs with progress indication
|
|
417
|
+
const spinner = ora({
|
|
418
|
+
text: 'Starting repairs...',
|
|
419
|
+
spinner: 'dots',
|
|
420
|
+
color: 'cyan'
|
|
421
|
+
}).start();
|
|
422
|
+
|
|
423
|
+
const repairResult = await result.repairService.repair(result.issues, {
|
|
424
|
+
onProgress: ({ current, total }) => {
|
|
425
|
+
const percent = Math.round((current / total) * 100);
|
|
426
|
+
spinner.text = `Repairing ${current}/${total} files... (${percent}%)`;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (repairResult.success) {
|
|
431
|
+
spinner.succeed('Repairs completed');
|
|
432
|
+
} else {
|
|
433
|
+
spinner.fail('Some repairs failed');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
logger.dim('');
|
|
437
|
+
|
|
438
|
+
// Display post-repair results
|
|
439
|
+
displayRepairResults(repairResult, result.scopeLabel);
|
|
440
|
+
|
|
441
|
+
// Show backup location
|
|
442
|
+
const backupDir = result.backupManager.getBackupDir();
|
|
443
|
+
logger.dim(`Backups saved to: ${backupDir}`);
|
|
444
|
+
logger.dim('');
|
|
445
|
+
|
|
446
|
+
// Track overall status
|
|
447
|
+
if (repairResult.stats.succeeded > 0) {
|
|
448
|
+
anyRepaired = true;
|
|
449
|
+
}
|
|
450
|
+
if (repairResult.stats.failed > 0) {
|
|
451
|
+
anyFailed = true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
} catch (error) {
|
|
455
|
+
logger.error(`Failed to repair ${scope} installation: ${error.message}`);
|
|
456
|
+
anyFailed = true;
|
|
457
|
+
|
|
458
|
+
if (verbose) {
|
|
459
|
+
logger.debug(error.stack);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Overall status message
|
|
465
|
+
logger.dim('');
|
|
466
|
+
|
|
467
|
+
if (!anyInstalled) {
|
|
468
|
+
logger.info('No GSD-OpenCode installation found to repair');
|
|
469
|
+
logger.dim('');
|
|
470
|
+
logger.info("Run 'gsd-opencode install' to install");
|
|
471
|
+
logger.dim('');
|
|
472
|
+
return ERROR_CODES.SUCCESS;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (anyFailed) {
|
|
476
|
+
logger.error('Some repairs failed. Run gsd-opencode check for details.');
|
|
477
|
+
return ERROR_CODES.GENERAL_ERROR;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (anyRepaired) {
|
|
481
|
+
logger.success('All repairs completed successfully');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return ERROR_CODES.SUCCESS;
|
|
485
|
+
|
|
486
|
+
} catch (error) {
|
|
487
|
+
// Handle Ctrl+C during async operations (AbortPromptError from @inquirer/prompts)
|
|
488
|
+
if (error.name === 'AbortPromptError' || error.message?.includes('cancel')) {
|
|
489
|
+
logger.info('Repair cancelled by user');
|
|
490
|
+
return ERROR_CODES.INTERRUPTED;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Handle permission errors (EACCES)
|
|
494
|
+
if (error.code === 'EACCES') {
|
|
495
|
+
logger.error('Permission denied: Cannot access installation directory');
|
|
496
|
+
logger.dim('');
|
|
497
|
+
logger.dim('Suggestion: Check directory permissions or run with appropriate privileges');
|
|
498
|
+
return ERROR_CODES.PERMISSION_ERROR;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Handle all other errors
|
|
502
|
+
logger.error(`Repair failed: ${error.message}`);
|
|
503
|
+
|
|
504
|
+
if (verbose && error.stack) {
|
|
505
|
+
logger.dim(error.stack);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return ERROR_CODES.GENERAL_ERROR;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Default export for the repair command.
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* import repairCommand from './commands/repair.js';
|
|
517
|
+
* const exitCode = await repairCommand({ global: true });
|
|
518
|
+
*/
|
|
519
|
+
export default repairCommand;
|