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,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health checker service for verifying installation integrity.
|
|
3
|
+
*
|
|
4
|
+
* This module provides comprehensive health checking capabilities for
|
|
5
|
+
* GSD-OpenCode installations. It can verify file existence, version matching,
|
|
6
|
+
* and file integrity through hash comparison. Works in conjunction with
|
|
7
|
+
* ScopeManager to handle both global and local installations.
|
|
8
|
+
*
|
|
9
|
+
* @module health-checker
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs/promises';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { ScopeManager } from './scope-manager.js';
|
|
15
|
+
import { hashFile } from '../utils/hash.js';
|
|
16
|
+
import { DIRECTORIES_TO_COPY, VERSION_FILE } from '../../lib/constants.js';
|
|
17
|
+
import { StructureDetector, STRUCTURE_TYPES } from './structure-detector.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Manages health verification for GSD-OpenCode installations.
|
|
21
|
+
*
|
|
22
|
+
* This class provides methods to verify installation integrity through
|
|
23
|
+
* multiple check categories: file existence, version matching, and file
|
|
24
|
+
* integrity (hash-based). It uses ScopeManager for path resolution and
|
|
25
|
+
* follows the established service layer pattern.
|
|
26
|
+
*
|
|
27
|
+
* @class HealthChecker
|
|
28
|
+
* @example
|
|
29
|
+
* const scope = new ScopeManager({ scope: 'global' });
|
|
30
|
+
* const health = new HealthChecker(scope);
|
|
31
|
+
*
|
|
32
|
+
* // Check all aspects
|
|
33
|
+
* const result = await health.checkAll({ expectedVersion: '1.0.0' });
|
|
34
|
+
* if (result.passed) {
|
|
35
|
+
* console.log('Installation is healthy');
|
|
36
|
+
* } else {
|
|
37
|
+
* console.log(`Issues found: ${result.categories.files.checks.filter(c => !c.passed).length} files`);
|
|
38
|
+
* }
|
|
39
|
+
*/
|
|
40
|
+
export class HealthChecker {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new HealthChecker instance.
|
|
43
|
+
*
|
|
44
|
+
* @param {ScopeManager} scopeManager - ScopeManager instance for path resolution
|
|
45
|
+
* @throws {Error} If scopeManager is not provided or invalid
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const scope = new ScopeManager({ scope: 'global' });
|
|
49
|
+
* const health = new HealthChecker(scope);
|
|
50
|
+
*/
|
|
51
|
+
constructor(scopeManager) {
|
|
52
|
+
if (!scopeManager) {
|
|
53
|
+
throw new Error('ScopeManager instance is required');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof scopeManager.getTargetDir !== 'function') {
|
|
57
|
+
throw new Error('Invalid ScopeManager: missing getTargetDir method');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.scopeManager = scopeManager;
|
|
61
|
+
this.targetDir = scopeManager.getTargetDir();
|
|
62
|
+
this.structureDetector = new StructureDetector(this.targetDir);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Detects the directory structure and returns status information.
|
|
67
|
+
*
|
|
68
|
+
* Uses StructureDetector to determine if the installation uses the old
|
|
69
|
+
* (command/gsd/), new (commands/gsd/), dual (both), or no structure.
|
|
70
|
+
*
|
|
71
|
+
* @returns {Promise<Object>} Structure detection results
|
|
72
|
+
* @property {string} type - One of STRUCTURE_TYPES (old, new, dual, none)
|
|
73
|
+
* @property {string} label - Human-readable label for the structure
|
|
74
|
+
* @property {boolean} needsMigration - True if migration is recommended
|
|
75
|
+
* @property {boolean} isHealthy - True if structure is valid (new or none)
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const structure = await health.detectStructure();
|
|
79
|
+
* if (structure.needsMigration) {
|
|
80
|
+
* console.log(`Migration needed: ${structure.label}`);
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
83
|
+
async detectStructure() {
|
|
84
|
+
const structure = await this.structureDetector.detect();
|
|
85
|
+
|
|
86
|
+
const status = {
|
|
87
|
+
type: structure,
|
|
88
|
+
label: this._getStructureLabel(structure),
|
|
89
|
+
needsMigration: structure === STRUCTURE_TYPES.OLD || structure === STRUCTURE_TYPES.DUAL,
|
|
90
|
+
isHealthy: structure === STRUCTURE_TYPES.NEW || structure === STRUCTURE_TYPES.NONE
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return status;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets a human-readable label for a structure type.
|
|
98
|
+
*
|
|
99
|
+
* @private
|
|
100
|
+
* @param {string} type - One of STRUCTURE_TYPES values
|
|
101
|
+
* @returns {string} Human-readable label
|
|
102
|
+
*/
|
|
103
|
+
_getStructureLabel(type) {
|
|
104
|
+
const labels = {
|
|
105
|
+
[STRUCTURE_TYPES.OLD]: 'Legacy (command/gsd/)',
|
|
106
|
+
[STRUCTURE_TYPES.NEW]: 'Current (commands/gsd/)',
|
|
107
|
+
[STRUCTURE_TYPES.DUAL]: 'Dual (both structures)',
|
|
108
|
+
[STRUCTURE_TYPES.NONE]: 'No command structure'
|
|
109
|
+
};
|
|
110
|
+
return labels[type] || 'Unknown';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Verifies that all required files and directories exist.
|
|
115
|
+
*
|
|
116
|
+
* Checks each directory in DIRECTORIES_TO_COPY and the VERSION file.
|
|
117
|
+
* Returns structured results suitable for CLI output.
|
|
118
|
+
*
|
|
119
|
+
* @returns {Promise<Object>} File verification results
|
|
120
|
+
* @property {boolean} passed - True if all required files exist
|
|
121
|
+
* @property {Array} checks - Detailed check results for each file/directory
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* const result = await health.verifyFiles();
|
|
125
|
+
* console.log(result.passed); // true/false
|
|
126
|
+
* console.log(result.checks);
|
|
127
|
+
* // [
|
|
128
|
+
* // { name: 'agents directory', passed: true, path: '/.../agents' },
|
|
129
|
+
* // { name: 'VERSION file', passed: true, path: '/.../VERSION' }
|
|
130
|
+
* // ]
|
|
131
|
+
*/
|
|
132
|
+
async verifyFiles() {
|
|
133
|
+
const checks = [];
|
|
134
|
+
let allPassed = true;
|
|
135
|
+
|
|
136
|
+
// Check each required directory
|
|
137
|
+
for (const dirName of DIRECTORIES_TO_COPY) {
|
|
138
|
+
const dirPath = path.join(this.targetDir, dirName);
|
|
139
|
+
try {
|
|
140
|
+
const stats = await fs.stat(dirPath);
|
|
141
|
+
const passed = stats.isDirectory();
|
|
142
|
+
checks.push({
|
|
143
|
+
name: `${dirName} directory`,
|
|
144
|
+
passed,
|
|
145
|
+
path: dirPath
|
|
146
|
+
});
|
|
147
|
+
if (!passed) allPassed = false;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
checks.push({
|
|
150
|
+
name: `${dirName} directory`,
|
|
151
|
+
passed: false,
|
|
152
|
+
path: dirPath,
|
|
153
|
+
error: error.code === 'ENOENT' ? 'Directory not found' : error.message
|
|
154
|
+
});
|
|
155
|
+
allPassed = false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check VERSION file
|
|
160
|
+
const versionPath = path.join(this.targetDir, VERSION_FILE);
|
|
161
|
+
try {
|
|
162
|
+
const stats = await fs.stat(versionPath);
|
|
163
|
+
const passed = stats.isFile();
|
|
164
|
+
checks.push({
|
|
165
|
+
name: 'VERSION file',
|
|
166
|
+
passed,
|
|
167
|
+
path: versionPath
|
|
168
|
+
});
|
|
169
|
+
if (!passed) allPassed = false;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
checks.push({
|
|
172
|
+
name: 'VERSION file',
|
|
173
|
+
passed: false,
|
|
174
|
+
path: versionPath,
|
|
175
|
+
error: error.code === 'ENOENT' ? 'File not found' : error.message
|
|
176
|
+
});
|
|
177
|
+
allPassed = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
passed: allPassed,
|
|
182
|
+
checks
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Verifies that the installed version matches the expected version.
|
|
188
|
+
*
|
|
189
|
+
* Reads the VERSION file and compares its content with the expected
|
|
190
|
+
* version string. Handles cases where VERSION file doesn't exist.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} expectedVersion - The expected version string (e.g., '1.0.0')
|
|
193
|
+
* @returns {Promise<Object>} Version verification results
|
|
194
|
+
* @property {boolean} passed - True if version matches
|
|
195
|
+
* @property {string|null} installed - The installed version, or null if not found
|
|
196
|
+
* @property {string} expected - The expected version that was checked
|
|
197
|
+
* @property {Array} checks - Detailed check results
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* const result = await health.verifyVersion('1.0.0');
|
|
201
|
+
* console.log(result.passed); // true if VERSION contains '1.0.0'
|
|
202
|
+
* console.log(result.installed); // '1.0.0' or null
|
|
203
|
+
*/
|
|
204
|
+
async verifyVersion(expectedVersion) {
|
|
205
|
+
if (!expectedVersion || typeof expectedVersion !== 'string') {
|
|
206
|
+
throw new Error('Expected version must be a non-empty string');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const versionPath = path.join(this.targetDir, VERSION_FILE);
|
|
210
|
+
let installedVersion = null;
|
|
211
|
+
let passed = false;
|
|
212
|
+
let error = null;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const content = await fs.readFile(versionPath, 'utf-8');
|
|
216
|
+
installedVersion = content.trim();
|
|
217
|
+
passed = installedVersion === expectedVersion;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err.code === 'ENOENT') {
|
|
220
|
+
error = 'VERSION file not found';
|
|
221
|
+
} else if (err.code === 'EACCES') {
|
|
222
|
+
error = 'Permission denied reading VERSION file';
|
|
223
|
+
} else {
|
|
224
|
+
error = err.message;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
passed,
|
|
230
|
+
installed: installedVersion,
|
|
231
|
+
expected: expectedVersion,
|
|
232
|
+
checks: [{
|
|
233
|
+
name: 'version match',
|
|
234
|
+
passed,
|
|
235
|
+
installed: installedVersion,
|
|
236
|
+
expected: expectedVersion,
|
|
237
|
+
error
|
|
238
|
+
}]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Verifies file integrity by checking that key files are readable.
|
|
244
|
+
*
|
|
245
|
+
* For v1, this performs basic integrity checks by verifying that
|
|
246
|
+
* sample files from each required directory exist and are readable.
|
|
247
|
+
* Future versions may compare against known-good hashes.
|
|
248
|
+
*
|
|
249
|
+
* @returns {Promise<Object>} Integrity verification results
|
|
250
|
+
* @property {boolean} passed - True if all integrity checks pass
|
|
251
|
+
* @property {Array} checks - Detailed check results for each file
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* const result = await health.verifyIntegrity();
|
|
255
|
+
* console.log(result.passed); // true/false
|
|
256
|
+
* console.log(result.checks);
|
|
257
|
+
* // [
|
|
258
|
+
* // { file: '/.../agents/README.md', passed: true },
|
|
259
|
+
* // { file: '/.../command/gsd/help.md', passed: true }
|
|
260
|
+
* // ]
|
|
261
|
+
*/
|
|
262
|
+
async verifyIntegrity() {
|
|
263
|
+
const checks = [];
|
|
264
|
+
let allPassed = true;
|
|
265
|
+
|
|
266
|
+
// Check sample files from each required directory
|
|
267
|
+
// These represent key files that should always exist
|
|
268
|
+
const sampleFiles = [
|
|
269
|
+
{ dir: 'agents', file: 'gsd-executor.md' },
|
|
270
|
+
{ dir: 'command', file: 'gsd/help.md' },
|
|
271
|
+
{ dir: 'get-shit-done', file: 'templates/summary.md' }
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
for (const { dir, file } of sampleFiles) {
|
|
275
|
+
const filePath = path.join(this.targetDir, dir, file);
|
|
276
|
+
try {
|
|
277
|
+
// Try to read and hash the file
|
|
278
|
+
const hash = await hashFile(filePath);
|
|
279
|
+
const passed = hash !== null;
|
|
280
|
+
checks.push({
|
|
281
|
+
file: filePath,
|
|
282
|
+
hash,
|
|
283
|
+
passed,
|
|
284
|
+
relative: path.join(dir, file)
|
|
285
|
+
});
|
|
286
|
+
if (!passed) allPassed = false;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
checks.push({
|
|
289
|
+
file: filePath,
|
|
290
|
+
hash: null,
|
|
291
|
+
passed: false,
|
|
292
|
+
relative: path.join(dir, file),
|
|
293
|
+
error: error.code === 'ENOENT' ? 'File not found' : error.message
|
|
294
|
+
});
|
|
295
|
+
allPassed = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Also verify VERSION file is readable (counts as integrity check)
|
|
300
|
+
const versionPath = path.join(this.targetDir, VERSION_FILE);
|
|
301
|
+
try {
|
|
302
|
+
const content = await fs.readFile(versionPath, 'utf-8');
|
|
303
|
+
checks.push({
|
|
304
|
+
file: versionPath,
|
|
305
|
+
hash: null, // We don't hash VERSION file
|
|
306
|
+
passed: true,
|
|
307
|
+
relative: VERSION_FILE
|
|
308
|
+
});
|
|
309
|
+
} catch (error) {
|
|
310
|
+
checks.push({
|
|
311
|
+
file: versionPath,
|
|
312
|
+
hash: null,
|
|
313
|
+
passed: false,
|
|
314
|
+
relative: VERSION_FILE,
|
|
315
|
+
error: error.code === 'ENOENT' ? 'File not found' : error.message
|
|
316
|
+
});
|
|
317
|
+
allPassed = false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
passed: allPassed,
|
|
322
|
+
checks
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Checks if a structure type can be repaired.
|
|
328
|
+
*
|
|
329
|
+
* Determines if the given structure type can be automatically repaired
|
|
330
|
+
* (migrated from old to new structure).
|
|
331
|
+
*
|
|
332
|
+
* @param {string} structureType - One of STRUCTURE_TYPES values
|
|
333
|
+
* @returns {boolean} True if structure can be repaired
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* const canRepair = healthChecker.canRepairStructure(STRUCTURE_TYPES.OLD);
|
|
337
|
+
* // Returns true
|
|
338
|
+
*/
|
|
339
|
+
canRepairStructure(structureType) {
|
|
340
|
+
return structureType === STRUCTURE_TYPES.OLD ||
|
|
341
|
+
structureType === STRUCTURE_TYPES.DUAL;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Gets repair recommendation for structure issues.
|
|
346
|
+
*
|
|
347
|
+
* Returns appropriate repair command and message based on structure state.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} structureType - One of STRUCTURE_TYPES values
|
|
350
|
+
* @returns {Object} Repair recommendation
|
|
351
|
+
* @property {boolean} canRepair - True if repair is possible
|
|
352
|
+
* @property {string|null} command - Repair command to run
|
|
353
|
+
* @property {string} message - Human-readable recommendation
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* const recommendation = healthChecker.getStructureRecommendation(STRUCTURE_TYPES.DUAL);
|
|
357
|
+
* console.log(recommendation.command); // 'gsd-opencode repair --fix-structure'
|
|
358
|
+
*/
|
|
359
|
+
getStructureRecommendation(structureType) {
|
|
360
|
+
const canRepair = this.canRepairStructure(structureType);
|
|
361
|
+
|
|
362
|
+
if (!canRepair) {
|
|
363
|
+
return {
|
|
364
|
+
canRepair: false,
|
|
365
|
+
command: null,
|
|
366
|
+
message: structureType === STRUCTURE_TYPES.NEW
|
|
367
|
+
? 'Structure is up to date'
|
|
368
|
+
: 'No structure detected'
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const isDual = structureType === STRUCTURE_TYPES.DUAL;
|
|
373
|
+
return {
|
|
374
|
+
canRepair: true,
|
|
375
|
+
command: 'gsd-opencode repair --fix-structure',
|
|
376
|
+
message: isDual
|
|
377
|
+
? 'Dual structure detected (both old and new exist). Run repair --fix-structure to consolidate.'
|
|
378
|
+
: 'Old structure detected (command/gsd/). Run repair --fix-structure to migrate.'
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Runs all health checks and returns aggregated results.
|
|
384
|
+
*
|
|
385
|
+
* This is the main entry point for health verification. It runs
|
|
386
|
+
* file existence, version matching, and integrity checks, then
|
|
387
|
+
* aggregates the results with an overall pass/fail status and
|
|
388
|
+
* suggested exit code.
|
|
389
|
+
*
|
|
390
|
+
* @param {Object} options - Check options
|
|
391
|
+
* @param {string} [options.expectedVersion] - Expected version for version check
|
|
392
|
+
* @param {boolean} [options.verbose=false] - Include verbose output
|
|
393
|
+
* @returns {Promise<Object>} Complete health check results
|
|
394
|
+
* @property {boolean} passed - True if all checks pass
|
|
395
|
+
* @property {number} exitCode - Suggested exit code (0 for healthy, 1 for issues)
|
|
396
|
+
* @property {Object} categories - Results from each check category
|
|
397
|
+
* @property {Object} categories.files - File existence check results
|
|
398
|
+
* @property {Object} categories.version - Version match check results
|
|
399
|
+
* @property {Object} categories.integrity - Integrity check results
|
|
400
|
+
* @property {Object} categories.structure - Structure check results with repair info
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* const result = await health.checkAll({ expectedVersion: '1.0.0' });
|
|
404
|
+
* console.log(result.passed); // true/false
|
|
405
|
+
* console.log(result.exitCode); // 0 or 1
|
|
406
|
+
* console.log(result.categories.files.passed); // etc.
|
|
407
|
+
* console.log(result.categories.structure.repairCommand); // 'gsd-opencode repair --fix-structure'
|
|
408
|
+
*/
|
|
409
|
+
async checkAll(options = {}) {
|
|
410
|
+
const { expectedVersion, verbose = false } = options;
|
|
411
|
+
|
|
412
|
+
// Run all checks in parallel including structure detection
|
|
413
|
+
const [filesResult, integrityResult, structureResult] = await Promise.all([
|
|
414
|
+
this.verifyFiles(),
|
|
415
|
+
this.verifyIntegrity(),
|
|
416
|
+
this.detectStructure()
|
|
417
|
+
]);
|
|
418
|
+
|
|
419
|
+
// Version check only if expectedVersion provided
|
|
420
|
+
let versionResult = null;
|
|
421
|
+
if (expectedVersion) {
|
|
422
|
+
versionResult = await this.verifyVersion(expectedVersion);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Determine overall status
|
|
426
|
+
// Dual structure is considered unhealthy (requires action)
|
|
427
|
+
const allResults = [filesResult.passed, integrityResult.passed];
|
|
428
|
+
if (versionResult) {
|
|
429
|
+
allResults.push(versionResult.passed);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Structure check: NEW and NONE are healthy, OLD and DUAL need attention
|
|
433
|
+
// DUAL structure causes failure (non-zero exit code)
|
|
434
|
+
const structureHealthy = structureResult.type === STRUCTURE_TYPES.NEW ||
|
|
435
|
+
structureResult.type === STRUCTURE_TYPES.NONE;
|
|
436
|
+
if (!structureHealthy) {
|
|
437
|
+
allResults.push(false);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Add repair information to structure result
|
|
441
|
+
const repairRecommendation = this.getStructureRecommendation(structureResult.type);
|
|
442
|
+
const enhancedStructureResult = {
|
|
443
|
+
...structureResult,
|
|
444
|
+
canRepair: repairRecommendation.canRepair,
|
|
445
|
+
repairCommand: repairRecommendation.command,
|
|
446
|
+
repairMessage: repairRecommendation.message
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const allPassed = allResults.every(r => r);
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
passed: allPassed,
|
|
453
|
+
exitCode: allPassed ? 0 : 1,
|
|
454
|
+
categories: {
|
|
455
|
+
files: filesResult,
|
|
456
|
+
version: versionResult,
|
|
457
|
+
integrity: integrityResult,
|
|
458
|
+
structure: enhancedStructureResult
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Default export for the health-checker module.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* import { HealthChecker } from './services/health-checker.js';
|
|
469
|
+
* const scope = new ScopeManager({ scope: 'global' });
|
|
470
|
+
* const health = new HealthChecker(scope);
|
|
471
|
+
* const result = await health.checkAll({ expectedVersion: '1.0.0' });
|
|
472
|
+
*/
|
|
473
|
+
export default {
|
|
474
|
+
HealthChecker
|
|
475
|
+
};
|