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.
Files changed (58) hide show
  1. package/agents/gsd-debugger.md +5 -5
  2. package/bin/gsd-install.js +105 -0
  3. package/bin/gsd.js +352 -0
  4. package/{command → commands}/gsd/add-phase.md +1 -1
  5. package/{command → commands}/gsd/audit-milestone.md +1 -1
  6. package/{command → commands}/gsd/debug.md +3 -3
  7. package/{command → commands}/gsd/discuss-phase.md +1 -1
  8. package/{command → commands}/gsd/execute-phase.md +1 -1
  9. package/{command → commands}/gsd/list-phase-assumptions.md +1 -1
  10. package/{command → commands}/gsd/map-codebase.md +1 -1
  11. package/{command → commands}/gsd/new-milestone.md +1 -1
  12. package/{command → commands}/gsd/new-project.md +3 -3
  13. package/{command → commands}/gsd/plan-phase.md +2 -2
  14. package/{command → commands}/gsd/research-phase.md +1 -1
  15. package/{command → commands}/gsd/verify-work.md +1 -1
  16. package/get-shit-done/workflows/list-phase-assumptions.md +1 -1
  17. package/get-shit-done/workflows/verify-work.md +5 -5
  18. package/lib/constants.js +193 -0
  19. package/package.json +34 -20
  20. package/src/commands/check.js +329 -0
  21. package/src/commands/config.js +337 -0
  22. package/src/commands/install.js +608 -0
  23. package/src/commands/list.js +256 -0
  24. package/src/commands/repair.js +519 -0
  25. package/src/commands/uninstall.js +732 -0
  26. package/src/commands/update.js +444 -0
  27. package/src/services/backup-manager.js +585 -0
  28. package/src/services/config.js +262 -0
  29. package/src/services/file-ops.js +830 -0
  30. package/src/services/health-checker.js +475 -0
  31. package/src/services/manifest-manager.js +301 -0
  32. package/src/services/migration-service.js +831 -0
  33. package/src/services/repair-service.js +846 -0
  34. package/src/services/scope-manager.js +303 -0
  35. package/src/services/settings.js +553 -0
  36. package/src/services/structure-detector.js +240 -0
  37. package/src/services/update-service.js +863 -0
  38. package/src/utils/hash.js +71 -0
  39. package/src/utils/interactive.js +222 -0
  40. package/src/utils/logger.js +128 -0
  41. package/src/utils/npm-registry.js +255 -0
  42. package/src/utils/path-resolver.js +226 -0
  43. /package/{command → commands}/gsd/add-todo.md +0 -0
  44. /package/{command → commands}/gsd/check-todos.md +0 -0
  45. /package/{command → commands}/gsd/complete-milestone.md +0 -0
  46. /package/{command → commands}/gsd/help.md +0 -0
  47. /package/{command → commands}/gsd/insert-phase.md +0 -0
  48. /package/{command → commands}/gsd/pause-work.md +0 -0
  49. /package/{command → commands}/gsd/plan-milestone-gaps.md +0 -0
  50. /package/{command → commands}/gsd/progress.md +0 -0
  51. /package/{command → commands}/gsd/quick.md +0 -0
  52. /package/{command → commands}/gsd/remove-phase.md +0 -0
  53. /package/{command → commands}/gsd/resume-work.md +0 -0
  54. /package/{command → commands}/gsd/set-model.md +0 -0
  55. /package/{command → commands}/gsd/set-profile.md +0 -0
  56. /package/{command → commands}/gsd/settings.md +0 -0
  57. /package/{command → commands}/gsd/update.md +0 -0
  58. /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
+ };