gsd-opencode 1.9.2 → 1.10.2
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/agents/gsd-settings.md +476 -30
- 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 +199 -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 +855 -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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest manager service for tracking installed files.
|
|
3
|
+
*
|
|
4
|
+
* This module provides safe tracking of all files installed by gsd-opencode,
|
|
5
|
+
* with strict namespace protection to ensure ONLY files in allowed namespaces
|
|
6
|
+
* are eligible for deletion during uninstall.
|
|
7
|
+
*
|
|
8
|
+
* Safety Principles:
|
|
9
|
+
* - Track ALL files touched during installation (complete audit trail)
|
|
10
|
+
* - Only allow deletion of files in allowed namespaces (gsd-*)
|
|
11
|
+
* - Never delete files outside allowed namespaces even if tracked
|
|
12
|
+
* - Preserve directories containing non-gsd-opencode files
|
|
13
|
+
*
|
|
14
|
+
* @module manifest-manager
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs/promises';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { createHash } from 'crypto';
|
|
20
|
+
import { MANIFEST_FILENAME, ALLOWED_NAMESPACES } from '../../lib/constants.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Represents a tracked file in the manifest.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {Object} ManifestEntry
|
|
26
|
+
* @property {string} path - Full absolute path to file
|
|
27
|
+
* @property {string} relativePath - Path relative to installation root
|
|
28
|
+
* @property {number} size - File size in bytes
|
|
29
|
+
* @property {string} hash - SHA256 hash of file content (prefixed with 'sha256:')
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Manages the manifest of installed files for safe uninstallation.
|
|
34
|
+
*
|
|
35
|
+
* The ManifestManager tracks all files installed by gsd-opencode and provides
|
|
36
|
+
* namespace-based filtering to ensure only files in allowed namespaces can be
|
|
37
|
+
* removed during uninstall.
|
|
38
|
+
*
|
|
39
|
+
* @class ManifestManager
|
|
40
|
+
* @example
|
|
41
|
+
* const manifestManager = new ManifestManager('/home/user/.config/opencode');
|
|
42
|
+
*
|
|
43
|
+
* // Add files during installation
|
|
44
|
+
* manifestManager.addFile('/home/user/.config/opencode/agents/gsd-debugger/SKILL.md', 'agents/gsd-debugger/SKILL.md', 2847, 'sha256:a1b2c3...');
|
|
45
|
+
*
|
|
46
|
+
* // Save manifest
|
|
47
|
+
* await manifestManager.save();
|
|
48
|
+
*
|
|
49
|
+
* // Load existing manifest
|
|
50
|
+
* const entries = await manifestManager.load();
|
|
51
|
+
*
|
|
52
|
+
* // Get files in allowed namespaces only
|
|
53
|
+
* const safeToRemove = manifestManager.getFilesInNamespaces(ALLOWED_NAMESPACES);
|
|
54
|
+
*/
|
|
55
|
+
export class ManifestManager {
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new ManifestManager instance.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} installPath - Root installation directory path
|
|
60
|
+
* @throws {Error} If installPath is not provided
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const manifestManager = new ManifestManager('/home/user/.config/opencode');
|
|
64
|
+
*/
|
|
65
|
+
constructor(installPath) {
|
|
66
|
+
if (!installPath) {
|
|
67
|
+
throw new Error('installPath is required');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this._installPath = installPath;
|
|
71
|
+
this._manifestPath = path.join(installPath, MANIFEST_FILENAME);
|
|
72
|
+
/**
|
|
73
|
+
* @type {ManifestEntry[]}
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
this._entries = [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Adds a file to the manifest.
|
|
81
|
+
*
|
|
82
|
+
* Records file metadata including path, size, and hash for tracking.
|
|
83
|
+
* This does not write to disk - call save() to persist.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} absolutePath - Full absolute path to the file
|
|
86
|
+
* @param {string} relativePath - Path relative to installation root
|
|
87
|
+
* @param {number} size - File size in bytes
|
|
88
|
+
* @param {string} hash - SHA256 hash (should include 'sha256:' prefix)
|
|
89
|
+
* @returns {ManifestEntry} The created manifest entry
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* manifestManager.addFile(
|
|
93
|
+
* '/home/user/.config/opencode/agents/gsd-debugger/SKILL.md',
|
|
94
|
+
* 'agents/gsd-debugger/SKILL.md',
|
|
95
|
+
* 2847,
|
|
96
|
+
* 'sha256:a1b2c3...'
|
|
97
|
+
* );
|
|
98
|
+
*/
|
|
99
|
+
addFile(absolutePath, relativePath, size, hash) {
|
|
100
|
+
const entry = {
|
|
101
|
+
path: absolutePath,
|
|
102
|
+
relativePath,
|
|
103
|
+
size,
|
|
104
|
+
hash
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
this._entries.push(entry);
|
|
108
|
+
return entry;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Calculates SHA256 hash of file content.
|
|
113
|
+
*
|
|
114
|
+
* Convenience method for generating hashes during installation.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} filePath - Path to file
|
|
117
|
+
* @returns {Promise<string>} SHA256 hash with 'sha256:' prefix
|
|
118
|
+
* @throws {Error} If file cannot be read
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* const hash = await ManifestManager.calculateHash('/path/to/file.md');
|
|
122
|
+
* // Returns: 'sha256:a1b2c3d4e5f6...'
|
|
123
|
+
*/
|
|
124
|
+
static async calculateHash(filePath) {
|
|
125
|
+
const content = await fs.readFile(filePath);
|
|
126
|
+
const hash = createHash('sha256').update(content).digest('hex');
|
|
127
|
+
return `sha256:${hash}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Saves the manifest to INSTALLED_FILES.json.
|
|
132
|
+
*
|
|
133
|
+
* Writes all tracked entries to disk in JSON format.
|
|
134
|
+
*
|
|
135
|
+
* @returns {Promise<string>} Path to saved manifest file
|
|
136
|
+
* @throws {Error} If write fails
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* const manifestPath = await manifestManager.save();
|
|
140
|
+
* console.log(`Manifest saved to: ${manifestPath}`);
|
|
141
|
+
*/
|
|
142
|
+
async save() {
|
|
143
|
+
const data = JSON.stringify(this._entries, null, 2);
|
|
144
|
+
// Ensure parent directory exists (for get-shit-done/INSTALLED_FILES.json)
|
|
145
|
+
const parentDir = path.dirname(this._manifestPath);
|
|
146
|
+
await fs.mkdir(parentDir, { recursive: true });
|
|
147
|
+
await fs.writeFile(this._manifestPath, data, 'utf-8');
|
|
148
|
+
return this._manifestPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Loads the manifest from INSTALLED_FILES.json.
|
|
153
|
+
*
|
|
154
|
+
* Reads and parses the manifest file. Returns null if manifest doesn't exist.
|
|
155
|
+
*
|
|
156
|
+
* @returns {Promise<ManifestEntry[]|null>} Array of manifest entries, or null if not found
|
|
157
|
+
* @throws {Error} If file exists but cannot be parsed
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* const entries = await manifestManager.load();
|
|
161
|
+
* if (entries === null) {
|
|
162
|
+
* console.log('No manifest found - using fallback mode');
|
|
163
|
+
* } else {
|
|
164
|
+
* console.log(`Found ${entries.length} tracked files`);
|
|
165
|
+
* }
|
|
166
|
+
*/
|
|
167
|
+
async load() {
|
|
168
|
+
try {
|
|
169
|
+
const data = await fs.readFile(this._manifestPath, 'utf-8');
|
|
170
|
+
this._entries = JSON.parse(data);
|
|
171
|
+
return this._entries;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error.code === 'ENOENT') {
|
|
174
|
+
// Manifest doesn't exist - return null for fallback mode
|
|
175
|
+
this._entries = [];
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Gets all tracked entries.
|
|
184
|
+
*
|
|
185
|
+
* Returns a copy of the internal entries array.
|
|
186
|
+
*
|
|
187
|
+
* @returns {ManifestEntry[]} Array of all manifest entries
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const allFiles = manifestManager.getAllEntries();
|
|
191
|
+
* console.log(`Total tracked files: ${allFiles.length}`);
|
|
192
|
+
*/
|
|
193
|
+
getAllEntries() {
|
|
194
|
+
return [...this._entries];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Filters entries by allowed namespaces.
|
|
199
|
+
*
|
|
200
|
+
* Returns only entries whose relativePath matches at least one
|
|
201
|
+
* of the provided namespace patterns.
|
|
202
|
+
*
|
|
203
|
+
* @param {RegExp[]} namespaces - Array of regex patterns for allowed namespaces
|
|
204
|
+
* @returns {ManifestEntry[]} Entries in allowed namespaces
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* const safeFiles = manifestManager.getFilesInNamespaces(ALLOWED_NAMESPACES);
|
|
208
|
+
* // Returns only files in agents/gsd-*, command/gsd/*, skills/gsd-*, get-shit-done/*
|
|
209
|
+
*/
|
|
210
|
+
getFilesInNamespaces(namespaces) {
|
|
211
|
+
return this._entries.filter(entry =>
|
|
212
|
+
this.isInAllowedNamespace(entry.relativePath, namespaces)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Checks if a path is in an allowed namespace.
|
|
218
|
+
*
|
|
219
|
+
* Tests the path against all provided namespace patterns.
|
|
220
|
+
* Returns true if the path matches at least one pattern.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} filePath - File path to check (relative or absolute)
|
|
223
|
+
* @param {RegExp[]} namespaces - Array of regex patterns for allowed namespaces
|
|
224
|
+
* @returns {boolean} True if path is in an allowed namespace
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* const isSafe = manifestManager.isInAllowedNamespace(
|
|
228
|
+
* 'agents/gsd-debugger/SKILL.md',
|
|
229
|
+
* ALLOWED_NAMESPACES
|
|
230
|
+
* );
|
|
231
|
+
* // Returns: true
|
|
232
|
+
*
|
|
233
|
+
* const isSafe2 = manifestManager.isInAllowedNamespace(
|
|
234
|
+
* 'agents/user-custom-agent/SKILL.md',
|
|
235
|
+
* ALLOWED_NAMESPACES
|
|
236
|
+
* );
|
|
237
|
+
* // Returns: false
|
|
238
|
+
*/
|
|
239
|
+
isInAllowedNamespace(filePath, namespaces) {
|
|
240
|
+
// Normalize to relative path if absolute
|
|
241
|
+
const relativePath = filePath.startsWith(this._installPath)
|
|
242
|
+
? path.relative(this._installPath, filePath)
|
|
243
|
+
: filePath;
|
|
244
|
+
|
|
245
|
+
// Normalize path separators for cross-platform compatibility
|
|
246
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
247
|
+
|
|
248
|
+
// Check against all namespace patterns
|
|
249
|
+
return namespaces.some(pattern => pattern.test(normalizedPath));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clears all tracked entries.
|
|
254
|
+
*
|
|
255
|
+
* Removes all entries from memory. Does not affect saved manifest file.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* manifestManager.clear();
|
|
259
|
+
* console.log(`Entries cleared: ${manifestManager.getAllEntries().length}`);
|
|
260
|
+
*/
|
|
261
|
+
clear() {
|
|
262
|
+
this._entries = [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Gets the manifest file path.
|
|
267
|
+
*
|
|
268
|
+
* @returns {string} Full path to INSTALLED_FILES.json
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* const manifestPath = manifestManager.getManifestPath();
|
|
272
|
+
* // Returns: '/home/user/.config/opencode/INSTALLED_FILES.json'
|
|
273
|
+
*/
|
|
274
|
+
getManifestPath() {
|
|
275
|
+
return this._manifestPath;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Gets the installation root path.
|
|
280
|
+
*
|
|
281
|
+
* @returns {string} Installation directory path
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* const installPath = manifestManager.getInstallPath();
|
|
285
|
+
* // Returns: '/home/user/.config/opencode'
|
|
286
|
+
*/
|
|
287
|
+
getInstallPath() {
|
|
288
|
+
return this._installPath;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Default export for the manifest-manager module.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* import { ManifestManager } from './services/manifest-manager.js';
|
|
297
|
+
* const manifestManager = new ManifestManager('/home/user/.config/opencode');
|
|
298
|
+
*/
|
|
299
|
+
export default {
|
|
300
|
+
ManifestManager
|
|
301
|
+
};
|