gsd-opencode 1.9.1 → 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 (65) hide show
  1. package/agents/gsd-debugger.md +5 -5
  2. package/agents/gsd-set-model.md +287 -0
  3. package/agents/gsd-set-profile.md +239 -0
  4. package/agents/gsd-settings.md +303 -0
  5. package/bin/gsd-install.js +105 -0
  6. package/bin/gsd.js +352 -0
  7. package/bin/install.js +81 -1
  8. package/{command → commands}/gsd/add-phase.md +1 -1
  9. package/{command → commands}/gsd/audit-milestone.md +1 -1
  10. package/{command → commands}/gsd/debug.md +3 -3
  11. package/{command → commands}/gsd/discuss-phase.md +1 -1
  12. package/{command → commands}/gsd/execute-phase.md +1 -1
  13. package/{command → commands}/gsd/list-phase-assumptions.md +1 -1
  14. package/{command → commands}/gsd/map-codebase.md +1 -1
  15. package/{command → commands}/gsd/new-milestone.md +1 -1
  16. package/{command → commands}/gsd/new-project.md +71 -6
  17. package/{command → commands}/gsd/plan-phase.md +2 -2
  18. package/{command → commands}/gsd/research-phase.md +1 -1
  19. package/commands/gsd/set-model.md +77 -0
  20. package/commands/gsd/set-profile.md +46 -0
  21. package/commands/gsd/settings.md +33 -0
  22. package/{command → commands}/gsd/verify-work.md +1 -1
  23. package/get-shit-done/references/model-profiles.md +67 -36
  24. package/get-shit-done/workflows/list-phase-assumptions.md +1 -1
  25. package/get-shit-done/workflows/verify-work.md +5 -5
  26. package/lib/constants.js +193 -0
  27. package/package.json +34 -20
  28. package/src/commands/check.js +329 -0
  29. package/src/commands/config.js +337 -0
  30. package/src/commands/install.js +608 -0
  31. package/src/commands/list.js +256 -0
  32. package/src/commands/repair.js +519 -0
  33. package/src/commands/uninstall.js +732 -0
  34. package/src/commands/update.js +444 -0
  35. package/src/services/backup-manager.js +585 -0
  36. package/src/services/config.js +262 -0
  37. package/src/services/file-ops.js +830 -0
  38. package/src/services/health-checker.js +475 -0
  39. package/src/services/manifest-manager.js +301 -0
  40. package/src/services/migration-service.js +831 -0
  41. package/src/services/repair-service.js +846 -0
  42. package/src/services/scope-manager.js +303 -0
  43. package/src/services/settings.js +553 -0
  44. package/src/services/structure-detector.js +240 -0
  45. package/src/services/update-service.js +863 -0
  46. package/src/utils/hash.js +71 -0
  47. package/src/utils/interactive.js +222 -0
  48. package/src/utils/logger.js +128 -0
  49. package/src/utils/npm-registry.js +255 -0
  50. package/src/utils/path-resolver.js +226 -0
  51. package/command/gsd/set-profile.md +0 -111
  52. package/command/gsd/settings.md +0 -136
  53. /package/{command → commands}/gsd/add-todo.md +0 -0
  54. /package/{command → commands}/gsd/check-todos.md +0 -0
  55. /package/{command → commands}/gsd/complete-milestone.md +0 -0
  56. /package/{command → commands}/gsd/help.md +0 -0
  57. /package/{command → commands}/gsd/insert-phase.md +0 -0
  58. /package/{command → commands}/gsd/pause-work.md +0 -0
  59. /package/{command → commands}/gsd/plan-milestone-gaps.md +0 -0
  60. /package/{command → commands}/gsd/progress.md +0 -0
  61. /package/{command → commands}/gsd/quick.md +0 -0
  62. /package/{command → commands}/gsd/remove-phase.md +0 -0
  63. /package/{command → commands}/gsd/resume-work.md +0 -0
  64. /package/{command → commands}/gsd/update.md +0 -0
  65. /package/{command → commands}/gsd/whats-new.md +0 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Path resolver utility with security-focused traversal protection.
3
+ *
4
+ * This module provides safe path resolution functions that prevent directory
5
+ * traversal attacks. All functions normalize paths and validate that resolved
6
+ * paths remain within allowed directory boundaries.
7
+ *
8
+ * SECURITY NOTE: Always validate paths before using them in file operations.
9
+ * The validatePath() function throws descriptive errors for traversal attempts.
10
+ *
11
+ * @module path-resolver
12
+ */
13
+
14
+ import path from 'path';
15
+ import os from 'os';
16
+ import fs from 'fs';
17
+
18
+ /**
19
+ * Expand a path string, resolving ~ to home directory and relative paths.
20
+ *
21
+ * @param {string} pathStr - Path string to expand (may contain ~ or relative segments)
22
+ * @returns {string} Absolute, normalized path
23
+ * @throws {Error} If path contains null bytes (potential injection attack)
24
+ *
25
+ * @example
26
+ * expandPath('~/.config/opencode')
27
+ * // Returns: '/Users/username/.config/opencode' (macOS)
28
+ *
29
+ * expandPath('./relative/path')
30
+ * // Returns: '/absolute/path/to/relative/path'
31
+ */
32
+ export function expandPath(pathStr) {
33
+ if (typeof pathStr !== 'string') {
34
+ throw new Error('Path must be a string');
35
+ }
36
+
37
+ // Security: Reject null bytes which could be used for injection attacks
38
+ if (pathStr.includes('\0')) {
39
+ throw new Error('Path contains null bytes');
40
+ }
41
+
42
+ let expanded = pathStr;
43
+
44
+ // Expand ~ to home directory
45
+ if (expanded.startsWith('~')) {
46
+ expanded = expanded.replace('~', os.homedir());
47
+ }
48
+
49
+ // Resolve to absolute path
50
+ const resolved = path.resolve(expanded);
51
+
52
+ // Normalize to handle .. and . segments consistently
53
+ return normalizePath(resolved);
54
+ }
55
+
56
+ /**
57
+ * Normalize a path consistently across platforms.
58
+ *
59
+ * @param {string} pathStr - Path to normalize
60
+ * @returns {string} Normalized path with consistent separators
61
+ */
62
+ export function normalizePath(pathStr) {
63
+ if (typeof pathStr !== 'string') {
64
+ throw new Error('Path must be a string');
65
+ }
66
+
67
+ // Use path.normalize() to resolve .. and . segments
68
+ // This ensures consistent path representation across platforms
69
+ return path.normalize(pathStr);
70
+ }
71
+
72
+ /**
73
+ * Check if a child path is within a parent path.
74
+ *
75
+ * @param {string} childPath - Path to check (child candidate)
76
+ * @param {string} parentPath - Parent directory path
77
+ * @returns {boolean} True if child is within or equal to parent
78
+ *
79
+ * @example
80
+ * isSubPath('/home/user/.config', '/home/user') // true
81
+ * isSubPath('/home/user', '/home/user') // true
82
+ * isSubPath('/etc/passwd', '/home/user') // false
83
+ */
84
+ export function isSubPath(childPath, parentPath) {
85
+ if (typeof childPath !== 'string' || typeof parentPath !== 'string') {
86
+ throw new Error('Paths must be strings');
87
+ }
88
+
89
+ // Normalize both paths to ensure consistent comparison
90
+ const normalizedChild = normalizePath(childPath);
91
+ const normalizedParent = normalizePath(parentPath);
92
+
93
+ // Get relative path from parent to child
94
+ const relative = path.relative(normalizedParent, normalizedChild);
95
+
96
+ // If relative path starts with '..', child is outside parent
97
+ // Empty string means same path
98
+ // Path starting without '..' means child is inside or equal to parent
99
+ if (relative === '') {
100
+ return true; // Same path
101
+ }
102
+
103
+ // On Windows, relative paths don't use .. for same-level directories
104
+ // Check if relative path contains '..' at the start
105
+ return !relative.startsWith('..');
106
+ }
107
+
108
+ /**
109
+ * Validate that a target path does not escape an allowed base directory.
110
+ *
111
+ * SECURITY CRITICAL: This function must be called before any file operation
112
+ * that uses user-provided paths. It prevents directory traversal attacks
113
+ * where malicious input like '../../../etc/passwd' attempts to access
114
+ * files outside the intended directory.
115
+ *
116
+ * @param {string} targetPath - Path to validate
117
+ * @param {string} allowedBasePath - Base directory the target must remain within
118
+ * @returns {string} The resolved, validated absolute path
119
+ * @throws {Error} If path escapes allowed base directory (traversal detected)
120
+ * @throws {Error} If path contains null bytes
121
+ *
122
+ * @example
123
+ * validatePath('/home/user/.config/opencode', '/home/user') // OK
124
+ * validatePath('/etc/passwd', '/home/user') // Throws error
125
+ *
126
+ * // Handles symlink resolution
127
+ * const realTarget = fs.realpathSync(targetPath);
128
+ * validatePath(realTarget, allowedBasePath);
129
+ */
130
+ export function validatePath(targetPath, allowedBasePath) {
131
+ if (typeof targetPath !== 'string' || typeof allowedBasePath !== 'string') {
132
+ throw new Error('Paths must be strings');
133
+ }
134
+
135
+ // Security: Reject null bytes
136
+ if (targetPath.includes('\0')) {
137
+ throw new Error('Path contains null bytes');
138
+ }
139
+
140
+ // Expand and normalize both paths
141
+ const resolvedTarget = expandPath(targetPath);
142
+ const resolvedBase = expandPath(allowedBasePath);
143
+
144
+ // Check if target is within allowed base
145
+ if (!isSubPath(resolvedTarget, resolvedBase)) {
146
+ throw new Error(
147
+ 'Path traversal detected. Use absolute or relative paths within allowed directories.'
148
+ );
149
+ }
150
+
151
+ return resolvedTarget;
152
+ }
153
+
154
+ /**
155
+ * Validate a path after resolving symlinks.
156
+ *
157
+ * Symlinks can be used to bypass directory restrictions by pointing to
158
+ * locations outside the allowed base. This function resolves symlinks
159
+ * before validation to ensure the actual file location is checked.
160
+ *
161
+ * @param {string} targetPath - Path to validate (may contain symlinks)
162
+ * @param {string} allowedBasePath - Base directory the target must remain within
163
+ * @returns {Promise<string>} The real, resolved absolute path
164
+ * @throws {Error} If path escapes allowed base directory after symlink resolution
165
+ *
166
+ * @example
167
+ * // If ~/.config is a symlink to /etc/config:
168
+ * await validatePathSafe('~/.config/opencode', os.homedir());
169
+ * // Throws error because resolved path is outside homedir
170
+ */
171
+ export async function validatePathSafe(targetPath, allowedBasePath) {
172
+ const expandedTarget = expandPath(targetPath);
173
+
174
+ try {
175
+ // Resolve symlinks to get the real path
176
+ const realTarget = await fs.promises.realpath(expandedTarget);
177
+
178
+ // Now validate the resolved path
179
+ return validatePath(realTarget, allowedBasePath);
180
+ } catch (error) {
181
+ // If realpath fails (file doesn't exist), validate the non-resolved path
182
+ // This allows validation of paths that will be created
183
+ if (error.code === 'ENOENT') {
184
+ return validatePath(expandedTarget, allowedBasePath);
185
+ }
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Synchronous version of validatePathSafe.
192
+ *
193
+ * @param {string} targetPath - Path to validate (may contain symlinks)
194
+ * @param {string} allowedBasePath - Base directory the target must remain within
195
+ * @returns {string} The real, resolved absolute path
196
+ * @throws {Error} If path escapes allowed base directory after symlink resolution
197
+ */
198
+ export function validatePathSafeSync(targetPath, allowedBasePath) {
199
+ const expandedTarget = expandPath(targetPath);
200
+
201
+ try {
202
+ // Resolve symlinks synchronously
203
+ const realTarget = fs.realpathSync(expandedTarget);
204
+
205
+ // Now validate the resolved path
206
+ return validatePath(realTarget, allowedBasePath);
207
+ } catch (error) {
208
+ // If realpath fails (file doesn't exist), validate the non-resolved path
209
+ if (error.code === 'ENOENT') {
210
+ return validatePath(expandedTarget, allowedBasePath);
211
+ }
212
+ throw error;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Default export combining all path resolver functions.
218
+ */
219
+ export default {
220
+ expandPath,
221
+ normalizePath,
222
+ isSubPath,
223
+ validatePath,
224
+ validatePathSafe,
225
+ validatePathSafeSync
226
+ };
@@ -1,111 +0,0 @@
1
- ---
2
- name: gsd-set-profile
3
- description: Switch model profile for GSD agents (quality/balanced/budget)
4
- arguments:
5
- - name: profile
6
- description: "Profile name: quality, balanced, or budget"
7
- required: true
8
- ---
9
-
10
- <objective>
11
- Switch the model profile used by GSD agents. This controls which OpenCode model each agent uses, balancing quality vs token spend.
12
- </objective>
13
-
14
- <profiles>
15
- | Profile | Description |
16
- |---------|-------------|
17
- | **quality** | Opus everywhere except read-only verification |
18
- | **balanced** | Opus for planning, Sonnet for execution/verification (default) |
19
- | **budget** | Sonnet for writing, Haiku for research/verification |
20
- </profiles>
21
-
22
- <process>
23
-
24
- ## 1. Validate argument
25
-
26
- ```
27
- if $ARGUMENTS.profile not in ["quality", "balanced", "budget"]:
28
- Error: Invalid profile "$ARGUMENTS.profile"
29
- Valid profiles: quality, balanced, budget
30
- STOP
31
- ```
32
-
33
- ## 2. Check for project
34
-
35
- ```bash
36
- ls .planning/config.json 2>/dev/null
37
- ```
38
-
39
- If no `.planning/` directory:
40
-
41
- ```
42
- Error: No GSD project found.
43
- Run /gsd-new-project first to initialize a project.
44
- ```
45
-
46
- ## 3. Update config.json
47
-
48
- read current config:
49
-
50
- ```bash
51
- cat .planning/config.json
52
- ```
53
-
54
- Update `model_profile` field (or add if missing):
55
-
56
- ```json
57
- {
58
- "model_profile": "$ARGUMENTS.profile"
59
- }
60
- ```
61
-
62
- write updated config back to `.planning/config.json`.
63
-
64
- ## 4. Confirm
65
-
66
- ```
67
- ✓ Model profile set to: $ARGUMENTS.profile
68
-
69
- Agents will now use:
70
- [Show table from model-profiles.md for selected profile]
71
-
72
- Next spawned agents will use the new profile.
73
- ```
74
-
75
- </process>
76
-
77
- <examples>
78
-
79
- **Switch to budget mode:**
80
-
81
- ```
82
- /gsd-set-profile budget
83
-
84
- ✓ Model profile set to: budget
85
-
86
- Agents will now use:
87
- | Agent | Model |
88
- |-------|-------|
89
- | gsd-planner | sonnet |
90
- | gsd-executor | sonnet |
91
- | gsd-verifier | haiku |
92
- | ... | ... |
93
- ```
94
-
95
- **Switch to quality mode:**
96
-
97
- ```
98
- /gsd-set-profile quality
99
-
100
- ✓ Model profile set to: quality
101
-
102
- Agents will now use:
103
- | Agent | Model |
104
- |-------|-------|
105
- | gsd-planner | opus |
106
- | gsd-executor | opus |
107
- | gsd-verifier | sonnet |
108
- | ... | ... |
109
- ```
110
-
111
- </examples>
@@ -1,136 +0,0 @@
1
- ---
2
- name: gsd-settings
3
- description: Configure GSD workflow toggles and model profile
4
- tools:
5
- - read
6
- - write
7
- - question
8
- ---
9
-
10
- <objective>
11
- Allow users to toggle workflow agents on/off and select model profile via interactive settings.
12
-
13
- Updates `.planning/config.json` with workflow preferences and model profile selection.
14
- </objective>
15
-
16
- <process>
17
-
18
- ## 1. Validate Environment
19
-
20
- ```bash
21
- ls .planning/config.json 2>/dev/null
22
- ```
23
-
24
- **If not found:** Error - run `/gsd-new-project` first.
25
-
26
- ## 2. read Current Config
27
-
28
- ```bash
29
- cat .planning/config.json
30
- ```
31
-
32
- Parse current values (default to `true` if not present):
33
- - `workflow.research` — spawn researcher during plan-phase
34
- - `workflow.plan_check` — spawn plan checker during plan-phase
35
- - `workflow.verifier` — spawn verifier during execute-phase
36
- - `model_profile` — which model each agent uses (default: `balanced`)
37
-
38
- ## 3. Present Settings
39
-
40
- Use question with current values shown:
41
-
42
- ```
43
- question([
44
- {
45
- question: "Which model profile for agents?",
46
- header: "Model",
47
- multiSelect: false,
48
- options: [
49
- { label: "Quality", description: "Opus everywhere except verification (highest cost)" },
50
- { label: "Balanced (Recommended)", description: "Opus for planning, Sonnet for execution/verification" },
51
- { label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost)" }
52
- ]
53
- },
54
- {
55
- question: "Spawn Plan Researcher? (researches domain before planning)",
56
- header: "Research",
57
- multiSelect: false,
58
- options: [
59
- { label: "Yes", description: "Research phase goals before planning" },
60
- { label: "No", description: "Skip research, plan directly" }
61
- ]
62
- },
63
- {
64
- question: "Spawn Plan Checker? (verifies plans before execution)",
65
- header: "Plan Check",
66
- multiSelect: false,
67
- options: [
68
- { label: "Yes", description: "Verify plans meet phase goals" },
69
- { label: "No", description: "Skip plan verification" }
70
- ]
71
- },
72
- {
73
- question: "Spawn Execution Verifier? (verifies phase completion)",
74
- header: "Verifier",
75
- multiSelect: false,
76
- options: [
77
- { label: "Yes", description: "Verify must-haves after execution" },
78
- { label: "No", description: "Skip post-execution verification" }
79
- ]
80
- }
81
- ])
82
- ```
83
-
84
- **Pre-select based on current config values.**
85
-
86
- ## 4. Update Config
87
-
88
- Merge new settings into existing config.json:
89
-
90
- ```json
91
- {
92
- ...existing_config,
93
- "model_profile": "quality" | "balanced" | "budget",
94
- "workflow": {
95
- "research": true/false,
96
- "plan_check": true/false,
97
- "verifier": true/false
98
- }
99
- }
100
- ```
101
-
102
- write updated config to `.planning/config.json`.
103
-
104
- ## 5. Confirm Changes
105
-
106
- Display:
107
-
108
- ```
109
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
110
- GSD ► SETTINGS UPDATED
111
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
112
-
113
- | Setting | Value |
114
- |----------------------|-------|
115
- | Model Profile | {quality/balanced/budget} |
116
- | Plan Researcher | {On/Off} |
117
- | Plan Checker | {On/Off} |
118
- | Execution Verifier | {On/Off} |
119
-
120
- These settings apply to future /gsd-plan-phase and /gsd-execute-phase runs.
121
-
122
- Quick commands:
123
- - /gsd-set-profile <profile> — switch model profile
124
- - /gsd-plan-phase --research — force research
125
- - /gsd-plan-phase --skip-research — skip research
126
- - /gsd-plan-phase --skip-verify — skip plan check
127
- ```
128
-
129
- </process>
130
-
131
- <success_criteria>
132
- - [ ] Current config read
133
- - [ ] User presented with 4 settings (profile + 3 toggles)
134
- - [ ] Config updated with model_profile and workflow section
135
- - [ ] Changes confirmed to user
136
- </success_criteria>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes