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
|
@@ -46,7 +46,7 @@ Store resolved models for use in Task calls below.
|
|
|
46
46
|
find .planning/phases -name "*-UAT.md" -type f 2>/dev/null | head -5
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
**If active sessions exist AND no
|
|
49
|
+
**If active sessions exist AND no `$ARGUMENTS` provided:**
|
|
50
50
|
|
|
51
51
|
read each file's frontmatter (status, phase) and Current Test section.
|
|
52
52
|
|
|
@@ -68,12 +68,12 @@ Wait for user response.
|
|
|
68
68
|
- If user replies with number (1, 2) → Load that file, go to `resume_from_file`
|
|
69
69
|
- If user replies with phase number → Treat as new session, go to `create_uat_file`
|
|
70
70
|
|
|
71
|
-
**If active sessions exist AND
|
|
71
|
+
**If active sessions exist AND `$ARGUMENTS` provided:**
|
|
72
72
|
|
|
73
73
|
Check if session exists for that phase. If yes, offer to resume or restart.
|
|
74
74
|
If no, continue to `create_uat_file`.
|
|
75
75
|
|
|
76
|
-
**If no active sessions AND no
|
|
76
|
+
**If no active sessions AND no `$ARGUMENTS`:**
|
|
77
77
|
|
|
78
78
|
```
|
|
79
79
|
No active UAT sessions.
|
|
@@ -81,7 +81,7 @@ No active UAT sessions.
|
|
|
81
81
|
Provide a phase number to start testing (e.g., /gsd-verify-work 4)
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
**If no active sessions AND
|
|
84
|
+
**If no active sessions AND `$ARGUMENTS` provided:**
|
|
85
85
|
|
|
86
86
|
Continue to `create_uat_file`.
|
|
87
87
|
</step>
|
|
@@ -89,7 +89,7 @@ Continue to `create_uat_file`.
|
|
|
89
89
|
<step name="find_summaries">
|
|
90
90
|
**Find what to test:**
|
|
91
91
|
|
|
92
|
-
Parse
|
|
92
|
+
Parse `$ARGUMENTS` as phase number (e.g., "4") or plan number (e.g., "04-02").
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
# Find phase directory (match both zero-padded and unpadded)
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the GSD-OpenCode CLI.
|
|
3
|
+
*
|
|
4
|
+
* This module centralizes all configuration values, file paths, and error codes
|
|
5
|
+
* used throughout the CLI to ensure consistency and maintainability.
|
|
6
|
+
*
|
|
7
|
+
* All exports are immutable constants. Do not modify these values at runtime.
|
|
8
|
+
*
|
|
9
|
+
* @module constants
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default global configuration directory path.
|
|
14
|
+
* Resolved relative to user's home directory.
|
|
15
|
+
* @type {string}
|
|
16
|
+
* @example
|
|
17
|
+
* // On macOS/Linux: ~/.config/opencode
|
|
18
|
+
* // On Windows: ~\AppData\Roaming\opencode
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_CONFIG_DIR = '.config/opencode';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Local configuration directory name.
|
|
24
|
+
* Created in the current working directory for project-specific configuration.
|
|
25
|
+
* @type {string}
|
|
26
|
+
* @example
|
|
27
|
+
* // Creates: ./.opencode/ in project root
|
|
28
|
+
*/
|
|
29
|
+
export const LOCAL_CONFIG_DIR = '.opencode';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Name of the version tracking file.
|
|
33
|
+
* Used to store the installed version of GSD-OpenCode.
|
|
34
|
+
* Stored in get-shit-done/ directory since it's fully owned by gsd-opencode.
|
|
35
|
+
* @type {string}
|
|
36
|
+
*/
|
|
37
|
+
export const VERSION_FILE = 'get-shit-done/VERSION';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Regex patterns for path replacement in markdown files.
|
|
41
|
+
* These patterns are used to update internal references during installation.
|
|
42
|
+
* @type {Object.<string, RegExp>}
|
|
43
|
+
*/
|
|
44
|
+
export const PATH_PATTERNS = {
|
|
45
|
+
/**
|
|
46
|
+
* Pattern to match @gsd-opencode/ references in markdown files.
|
|
47
|
+
* Used for replacing package references with actual paths.
|
|
48
|
+
*/
|
|
49
|
+
gsdReference: /@gsd-opencode\//g
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Source directories to copy during installation.
|
|
54
|
+
* These directories contain the core GSD-OpenCode assets.
|
|
55
|
+
*
|
|
56
|
+
* All directories use the 'commands' (plural) structure consistently
|
|
57
|
+
* in both source and destination. The FileOperations service copies
|
|
58
|
+
* files directly from source to target without path transformation.
|
|
59
|
+
*
|
|
60
|
+
* @type {string[]}
|
|
61
|
+
*/
|
|
62
|
+
export const DIRECTORIES_TO_COPY = ['agents', 'commands', 'get-shit-done'];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Command directory mapping for source-to-destination path transformation.
|
|
66
|
+
*
|
|
67
|
+
* Since the source package now uses 'commands/' (plural) and the destination
|
|
68
|
+
* also uses 'commands/' (plural), this mapping ensures consistency.
|
|
69
|
+
* This enables future transformations if needed.
|
|
70
|
+
*
|
|
71
|
+
* @type {Object.<string, string>}
|
|
72
|
+
* @example
|
|
73
|
+
* // During install, files from sourceDir/commands/gsd/ are copied to targetDir/commands/gsd/
|
|
74
|
+
* const sourceDirName = COMMAND_DIR_MAPPING[destDirName]; // 'commands'
|
|
75
|
+
*/
|
|
76
|
+
export const COMMAND_DIR_MAPPING = {
|
|
77
|
+
'commands': 'commands' // Both source and destination use 'commands/'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Name of the manifest file that tracks all installed files.
|
|
82
|
+
* Used for safe uninstallation with namespace protection.
|
|
83
|
+
* Stored in get-shit-done/ directory since it's fully owned by gsd-opencode.
|
|
84
|
+
* @type {string}
|
|
85
|
+
*/
|
|
86
|
+
export const MANIFEST_FILENAME = 'get-shit-done/INSTALLED_FILES.json';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Directory name for uninstall backups.
|
|
90
|
+
* Created within the installation directory to store backups before removal.
|
|
91
|
+
* @type {string}
|
|
92
|
+
*/
|
|
93
|
+
export const UNINSTALL_BACKUP_DIR = '.backups';
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Legacy command directory name (singular).
|
|
97
|
+
* Used for detecting and migrating old installations.
|
|
98
|
+
* @type {string}
|
|
99
|
+
*/
|
|
100
|
+
export const OLD_COMMAND_DIR = 'command';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* New command directory name (plural).
|
|
104
|
+
* Used as the default for fresh installations.
|
|
105
|
+
* @type {string}
|
|
106
|
+
*/
|
|
107
|
+
export const NEW_COMMAND_DIR = 'commands';
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Structure type constants for directory structure detection.
|
|
111
|
+
* Used to identify which command directory structure is present.
|
|
112
|
+
* @type {Object.<string, string>}
|
|
113
|
+
*/
|
|
114
|
+
export const STRUCTURE_TYPES = {
|
|
115
|
+
/** Legacy structure: command/gsd/ (singular) */
|
|
116
|
+
OLD: 'old',
|
|
117
|
+
|
|
118
|
+
/** New structure: commands/gsd/ (plural) */
|
|
119
|
+
NEW: 'new',
|
|
120
|
+
|
|
121
|
+
/** Both structures exist (dual/migration state) */
|
|
122
|
+
DUAL: 'dual',
|
|
123
|
+
|
|
124
|
+
/** Neither structure exists (fresh install) */
|
|
125
|
+
NONE: 'none'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Allowed namespace patterns for safe uninstallation.
|
|
130
|
+
* Files in these namespaces are safe to delete during uninstall.
|
|
131
|
+
* Files outside these namespaces are NEVER deleted, even if tracked.
|
|
132
|
+
*
|
|
133
|
+
* Patterns:
|
|
134
|
+
* - agents/gsd-* (gsd-opencode specific agents)
|
|
135
|
+
* - command/gsd/* (gsd-opencode specific commands - legacy)
|
|
136
|
+
* - commands/gsd/* (gsd-opencode specific commands - new)
|
|
137
|
+
* - skills/gsd-* (gsd-opencode specific skills)
|
|
138
|
+
* - get-shit-done/* (fully owned by gsd-opencode)
|
|
139
|
+
*
|
|
140
|
+
* @type {RegExp[]}
|
|
141
|
+
*/
|
|
142
|
+
export const ALLOWED_NAMESPACES = [
|
|
143
|
+
/^agents\/gsd-/, // agents/gsd-* directories
|
|
144
|
+
/^command\/gsd\//, // command/gsd/* files (legacy structure)
|
|
145
|
+
/^commands\/gsd\//, // commands/gsd/* files (new structure)
|
|
146
|
+
/^skills\/gsd-/, // skills/gsd-* directories
|
|
147
|
+
/^get-shit-done\// // get-shit-done/ directory - fully owned
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Exit codes for different failure modes.
|
|
152
|
+
* Follows Unix convention where 0 = success, non-zero = error.
|
|
153
|
+
* @type {Object.<string, number>}
|
|
154
|
+
*/
|
|
155
|
+
export const ERROR_CODES = {
|
|
156
|
+
/** Operation completed successfully. */
|
|
157
|
+
SUCCESS: 0,
|
|
158
|
+
|
|
159
|
+
/** General error occurred (unspecified failure). */
|
|
160
|
+
GENERAL_ERROR: 1,
|
|
161
|
+
|
|
162
|
+
/** Permission denied (insufficient file system permissions). */
|
|
163
|
+
PERMISSION_ERROR: 2,
|
|
164
|
+
|
|
165
|
+
/** Path traversal detected (security violation). */
|
|
166
|
+
PATH_TRAVERSAL: 3,
|
|
167
|
+
|
|
168
|
+
/** Process interrupted by user (SIGINT, Ctrl+C). */
|
|
169
|
+
INTERRUPTED: 130
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Default export combining all constants.
|
|
174
|
+
* Useful for importing all constants at once.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* import constants from './lib/constants.js';
|
|
178
|
+
* console.log(constants.DEFAULT_CONFIG_DIR);
|
|
179
|
+
*/
|
|
180
|
+
export default {
|
|
181
|
+
DEFAULT_CONFIG_DIR,
|
|
182
|
+
LOCAL_CONFIG_DIR,
|
|
183
|
+
VERSION_FILE,
|
|
184
|
+
PATH_PATTERNS,
|
|
185
|
+
DIRECTORIES_TO_COPY,
|
|
186
|
+
MANIFEST_FILENAME,
|
|
187
|
+
UNINSTALL_BACKUP_DIR,
|
|
188
|
+
OLD_COMMAND_DIR,
|
|
189
|
+
NEW_COMMAND_DIR,
|
|
190
|
+
STRUCTURE_TYPES,
|
|
191
|
+
ALLOWED_NAMESPACES,
|
|
192
|
+
ERROR_CODES
|
|
193
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-opencode",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"ai",
|
|
8
|
-
"meta-prompting",
|
|
9
|
-
"context-engineering",
|
|
10
|
-
"spec-driven-development"
|
|
11
|
-
],
|
|
3
|
+
"version": "1.10.1",
|
|
4
|
+
"description": "GSD-OpenCode distribution manager - install, verify, and maintain your GSD-OpenCode installation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "bin/gsd.js",
|
|
12
7
|
"homepage": "https://github.com/rokicool/gsd-opencode#readme",
|
|
13
8
|
"bugs": {
|
|
14
9
|
"url": "https://github.com/rokicool/gsd-opencode/issues"
|
|
@@ -17,23 +12,42 @@
|
|
|
17
12
|
"type": "git",
|
|
18
13
|
"url": "git+https://github.com/rokicool/gsd-opencode.git"
|
|
19
14
|
},
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"author": "TÂCHES & rokicool",
|
|
22
|
-
"type": "commonjs",
|
|
23
|
-
"main": "index.js",
|
|
24
15
|
"bin": {
|
|
25
|
-
"gsd-opencode": "bin/
|
|
16
|
+
"gsd-opencode": "bin/gsd.js",
|
|
17
|
+
"gsd-install": "bin/gsd-install.js"
|
|
26
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"opencode",
|
|
25
|
+
"ai",
|
|
26
|
+
"meta-prompting",
|
|
27
|
+
"context-engineering",
|
|
28
|
+
"spec-driven-development"
|
|
29
|
+
],
|
|
30
|
+
"author": "TÂCHES & rokicool",
|
|
31
|
+
"license": "MIT",
|
|
27
32
|
"files": [
|
|
28
33
|
"agents",
|
|
29
34
|
"bin",
|
|
30
|
-
"
|
|
31
|
-
"get-shit-done"
|
|
35
|
+
"commands",
|
|
36
|
+
"get-shit-done",
|
|
37
|
+
"src",
|
|
38
|
+
"lib"
|
|
32
39
|
],
|
|
33
|
-
"scripts": {
|
|
34
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
35
|
-
},
|
|
36
40
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@iarna/toml": "^2.2.5",
|
|
45
|
+
"@inquirer/prompts": "^8.2.0",
|
|
46
|
+
"chalk": "^5.6.2",
|
|
47
|
+
"commander": "^12.1.0",
|
|
48
|
+
"ora": "^9.3.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"vitest": "^3.0.0"
|
|
38
52
|
}
|
|
39
53
|
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check command for GSD-OpenCode CLI.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the check functionality to verify installation health,
|
|
5
|
+
* showing detailed pass/fail results for file existence, version matching,
|
|
6
|
+
* and file integrity checks.
|
|
7
|
+
*
|
|
8
|
+
* Implements requirements:
|
|
9
|
+
* - CLI-03: User can run gsd-opencode check to verify installation health
|
|
10
|
+
* - CHECK-01: Check verifies all required files exist
|
|
11
|
+
* - CHECK-02: Check verifies installed version matches expected version
|
|
12
|
+
* - CHECK-03: Check detects corrupted or modified files
|
|
13
|
+
* - CHECK-04: Check provides clear pass/fail output for each verification
|
|
14
|
+
* - CHECK-05: Check returns appropriate exit codes (0 for healthy, non-zero for issues)
|
|
15
|
+
* - ERROR-03: All commands support --verbose flag for detailed debugging output
|
|
16
|
+
* - ERROR-06: CLI shows consistent branding and formatted output using colors
|
|
17
|
+
*
|
|
18
|
+
* @module check
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { ScopeManager } from '../services/scope-manager.js';
|
|
22
|
+
import { HealthChecker } from '../services/health-checker.js';
|
|
23
|
+
import { logger, setVerbose } from '../utils/logger.js';
|
|
24
|
+
import { ERROR_CODES } from '../../lib/constants.js';
|
|
25
|
+
import fs from 'fs/promises';
|
|
26
|
+
import path from 'path';
|
|
27
|
+
import { fileURLToPath } from 'url';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the package version from package.json.
|
|
31
|
+
*
|
|
32
|
+
* @returns {Promise<string>} The package version
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
async function getPackageVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
38
|
+
const __dirname = path.dirname(__filename);
|
|
39
|
+
const packageRoot = path.resolve(__dirname, '../..');
|
|
40
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
41
|
+
|
|
42
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
43
|
+
const pkg = JSON.parse(content);
|
|
44
|
+
return pkg.version || '1.0.0';
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.warning('Could not read package version, using 1.0.0');
|
|
47
|
+
return '1.0.0';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Displays health check results with pass/fail indicators.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} results - Health check results from HealthChecker
|
|
55
|
+
* @param {string} scopeLabel - Label for this scope
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
function displayCheckResults(results, scopeLabel) {
|
|
59
|
+
logger.heading(`${scopeLabel} Installation Health`);
|
|
60
|
+
logger.dim('================================');
|
|
61
|
+
|
|
62
|
+
if (!results) {
|
|
63
|
+
logger.info('Not installed');
|
|
64
|
+
logger.dim('');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { categories } = results;
|
|
69
|
+
|
|
70
|
+
// Files check section
|
|
71
|
+
logger.dim('');
|
|
72
|
+
logger.info('Required Files');
|
|
73
|
+
if (categories.files && categories.files.checks) {
|
|
74
|
+
for (const check of categories.files.checks) {
|
|
75
|
+
const status = check.passed ? 'OK' : (check.error || 'Missing');
|
|
76
|
+
if (check.passed) {
|
|
77
|
+
logger.success(`${check.name}: ${status}`);
|
|
78
|
+
} else {
|
|
79
|
+
logger.error(`${check.name}: ${status}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Version check section
|
|
85
|
+
if (categories.version) {
|
|
86
|
+
logger.dim('');
|
|
87
|
+
logger.info('Version Verification');
|
|
88
|
+
const versionCheck = categories.version.checks[0];
|
|
89
|
+
if (versionCheck.passed) {
|
|
90
|
+
logger.success(`Version: ${versionCheck.installed} - OK`);
|
|
91
|
+
} else {
|
|
92
|
+
const errorMsg = versionCheck.error
|
|
93
|
+
? `Version check failed - ${versionCheck.error}`
|
|
94
|
+
: `Version mismatch (installed: ${versionCheck.installed || 'none'}, expected: ${versionCheck.expected})`;
|
|
95
|
+
logger.error(errorMsg);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Integrity check section
|
|
100
|
+
logger.dim('');
|
|
101
|
+
logger.info('File Integrity');
|
|
102
|
+
if (categories.integrity && categories.integrity.checks) {
|
|
103
|
+
for (const check of categories.integrity.checks) {
|
|
104
|
+
const relativePath = check.relative || path.basename(check.file);
|
|
105
|
+
const message = check.passed
|
|
106
|
+
? `${relativePath} - OK`
|
|
107
|
+
: `${relativePath} - ${check.error || 'Corrupted or missing'}`;
|
|
108
|
+
|
|
109
|
+
if (check.passed) {
|
|
110
|
+
logger.success(message);
|
|
111
|
+
} else {
|
|
112
|
+
logger.error(message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Structure check section (NEW)
|
|
118
|
+
if (categories.structure) {
|
|
119
|
+
logger.dim('');
|
|
120
|
+
logger.info('Directory Structure');
|
|
121
|
+
const structure = categories.structure;
|
|
122
|
+
const statusText = structure.label;
|
|
123
|
+
|
|
124
|
+
if (structure.type === 'dual') {
|
|
125
|
+
logger.error(`${statusText} - Action required`);
|
|
126
|
+
logger.dim(' Both old (command/gsd/) and new (commands/gsd/) structures detected.');
|
|
127
|
+
logger.dim(' This can happen if an update was interrupted.');
|
|
128
|
+
logger.dim(" Run 'gsd-opencode update' to complete migration");
|
|
129
|
+
} else if (structure.needsMigration) {
|
|
130
|
+
logger.warning(`${statusText} - Migration recommended`);
|
|
131
|
+
logger.dim(" Run 'gsd-opencode update' to migrate to new structure");
|
|
132
|
+
} else if (structure.type === 'new') {
|
|
133
|
+
logger.success(`${statusText} - OK`);
|
|
134
|
+
} else if (structure.type === 'none') {
|
|
135
|
+
logger.info(`${statusText}`);
|
|
136
|
+
} else {
|
|
137
|
+
logger.info(statusText);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Overall status
|
|
142
|
+
logger.dim('');
|
|
143
|
+
if (results.passed) {
|
|
144
|
+
logger.success('All checks passed - Installation is healthy');
|
|
145
|
+
} else {
|
|
146
|
+
logger.error('Some checks failed - Issues detected');
|
|
147
|
+
}
|
|
148
|
+
logger.dim('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Checks health for a single scope.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} scope - 'global' or 'local'
|
|
155
|
+
* @param {Object} options - Options
|
|
156
|
+
* @param {boolean} options.verbose - Enable verbose output
|
|
157
|
+
* @returns {Promise<Object>} Health check results
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
async function checkScope(scope, options = {}) {
|
|
161
|
+
const scopeManager = new ScopeManager({ scope });
|
|
162
|
+
const scopeLabel = scope.charAt(0).toUpperCase() + scope.slice(1);
|
|
163
|
+
|
|
164
|
+
logger.debug(`Checking ${scope} installation...`);
|
|
165
|
+
|
|
166
|
+
const isInstalled = await scopeManager.isInstalled();
|
|
167
|
+
if (!isInstalled) {
|
|
168
|
+
logger.debug(`No ${scope} installation found`);
|
|
169
|
+
return {
|
|
170
|
+
installed: false,
|
|
171
|
+
scope,
|
|
172
|
+
scopeLabel,
|
|
173
|
+
results: null,
|
|
174
|
+
passed: false
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
logger.debug(`${scope} installation detected, running health checks...`);
|
|
179
|
+
|
|
180
|
+
const healthChecker = new HealthChecker(scopeManager);
|
|
181
|
+
const expectedVersion = await getPackageVersion();
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const results = await healthChecker.checkAll({
|
|
185
|
+
expectedVersion,
|
|
186
|
+
verbose: options.verbose
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
installed: true,
|
|
191
|
+
scope,
|
|
192
|
+
scopeLabel,
|
|
193
|
+
results,
|
|
194
|
+
passed: results.passed
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
logger.debug(`Error during health check: ${error.message}`);
|
|
198
|
+
return {
|
|
199
|
+
installed: true,
|
|
200
|
+
scope,
|
|
201
|
+
scopeLabel,
|
|
202
|
+
results: null,
|
|
203
|
+
passed: false,
|
|
204
|
+
error: error.message
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Handles errors with helpful messages.
|
|
211
|
+
*
|
|
212
|
+
* @param {Error} error - The error to handle
|
|
213
|
+
* @param {boolean} verbose - Whether verbose mode is enabled
|
|
214
|
+
* @returns {number} Exit code for the error
|
|
215
|
+
* @private
|
|
216
|
+
*/
|
|
217
|
+
function handleError(error, verbose) {
|
|
218
|
+
if (verbose) {
|
|
219
|
+
logger.debug(`Error details: ${error.stack || error.message}`);
|
|
220
|
+
logger.debug(`Error code: ${error.code}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (error.code === 'EACCES') {
|
|
224
|
+
logger.error('Permission denied: Cannot access installation directory');
|
|
225
|
+
logger.dim('');
|
|
226
|
+
logger.dim('Suggestion: Check directory permissions or run with appropriate privileges.');
|
|
227
|
+
return ERROR_CODES.PERMISSION_ERROR;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
logger.error(`Failed to check installation health: ${error.message}`);
|
|
231
|
+
|
|
232
|
+
if (!verbose) {
|
|
233
|
+
logger.dim('');
|
|
234
|
+
logger.dim('Suggestion: Run with --verbose for detailed error information');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return ERROR_CODES.GENERAL_ERROR;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Main check command function.
|
|
242
|
+
*
|
|
243
|
+
* @param {Object} options - Command options
|
|
244
|
+
* @param {boolean} [options.global] - Check global installation only
|
|
245
|
+
* @param {boolean} [options.local] - Check local installation only
|
|
246
|
+
* @param {boolean} [options.verbose] - Enable verbose output
|
|
247
|
+
* @returns {Promise<number>} Exit code
|
|
248
|
+
*/
|
|
249
|
+
export async function checkCommand(options = {}) {
|
|
250
|
+
const verbose = options.verbose || false;
|
|
251
|
+
setVerbose(verbose);
|
|
252
|
+
|
|
253
|
+
logger.debug('Starting check command');
|
|
254
|
+
logger.debug(`Options: global=${options.global}, local=${options.local}, verbose=${verbose}`);
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
logger.heading('GSD-OpenCode Installation Health');
|
|
258
|
+
logger.dim('================================');
|
|
259
|
+
|
|
260
|
+
const scopesToCheck = [];
|
|
261
|
+
if (options.global) {
|
|
262
|
+
scopesToCheck.push('global');
|
|
263
|
+
} else if (options.local) {
|
|
264
|
+
scopesToCheck.push('local');
|
|
265
|
+
} else {
|
|
266
|
+
scopesToCheck.push('global', 'local');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let anyInstalled = false;
|
|
270
|
+
let allPassed = true;
|
|
271
|
+
|
|
272
|
+
for (const scope of scopesToCheck) {
|
|
273
|
+
try {
|
|
274
|
+
const result = await checkScope(scope, options);
|
|
275
|
+
|
|
276
|
+
if (result.installed) {
|
|
277
|
+
anyInstalled = true;
|
|
278
|
+
if (!result.passed) {
|
|
279
|
+
allPassed = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (scopesToCheck.length > 1) {
|
|
284
|
+
logger.dim('');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
displayCheckResults(result.results, result.scopeLabel);
|
|
288
|
+
|
|
289
|
+
if (verbose && result.error) {
|
|
290
|
+
logger.debug(`Error checking ${scope}: ${result.error}`);
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logger.error(`Failed to check ${scope} installation: ${error.message}`);
|
|
294
|
+
allPassed = false;
|
|
295
|
+
|
|
296
|
+
if (verbose) {
|
|
297
|
+
logger.debug(error.stack);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!anyInstalled) {
|
|
303
|
+
logger.dim('');
|
|
304
|
+
logger.dim('Not installed anywhere');
|
|
305
|
+
logger.dim('');
|
|
306
|
+
logger.info("Run 'gsd-opencode install' to install");
|
|
307
|
+
logger.dim('');
|
|
308
|
+
return ERROR_CODES.SUCCESS;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const exitCode = allPassed ? ERROR_CODES.SUCCESS : ERROR_CODES.GENERAL_ERROR;
|
|
312
|
+
|
|
313
|
+
if (verbose) {
|
|
314
|
+
logger.debug(`Check complete. Exit code: ${exitCode}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return exitCode;
|
|
318
|
+
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error.name === 'AbortPromptError' || error.message?.includes('cancel')) {
|
|
321
|
+
logger.info('Command cancelled by user');
|
|
322
|
+
return ERROR_CODES.INTERRUPTED;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return handleError(error, verbose);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export default checkCommand;
|