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
package/bin/gsd.js ADDED
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Main CLI entry point for GSD-OpenCode package manager.
5
+ *
6
+ * This is the primary executable that routes commands to their respective
7
+ * handlers using Commander.js. Supports both new subcommand-based interface
8
+ * and legacy argument patterns for backward compatibility.
9
+ *
10
+ * Commands:
11
+ * - install: Install GSD-OpenCode distribution
12
+ * - list: Show installation status
13
+ *
14
+ * Legacy compatibility:
15
+ * - Direct flags like --global, --local are routed to install command
16
+ * - No arguments defaults to interactive install
17
+ *
18
+ * @module gsd
19
+ */
20
+
21
+ import { Command } from 'commander';
22
+ import chalk from 'chalk';
23
+ import { installCommand } from '../src/commands/install.js';
24
+ import { listCommand } from '../src/commands/list.js';
25
+ import { uninstallCommand } from '../src/commands/uninstall.js';
26
+ import { configGetCommand, configSetCommand, configResetCommand, configListCommand } from '../src/commands/config.js';
27
+ import { checkCommand } from '../src/commands/check.js';
28
+ import { repairCommand } from '../src/commands/repair.js';
29
+ import { updateCommand } from '../src/commands/update.js';
30
+ import { logger, setVerbose } from '../src/utils/logger.js';
31
+ import { ERROR_CODES } from '../lib/constants.js';
32
+ import { readFileSync } from 'fs';
33
+ import { fileURLToPath } from 'url';
34
+ import path from 'path';
35
+
36
+ /**
37
+ * Gets the package version from package.json.
38
+ *
39
+ * @returns {string} The package version
40
+ * @private
41
+ */
42
+ function getPackageVersion() {
43
+ try {
44
+ const __filename = fileURLToPath(import.meta.url);
45
+ const __dirname = path.dirname(__filename);
46
+ const packageRoot = path.resolve(__dirname, '..');
47
+ const packageJsonPath = path.join(packageRoot, 'package.json');
48
+
49
+ const content = readFileSync(packageJsonPath, 'utf-8');
50
+ const pkg = JSON.parse(content);
51
+ return pkg.version || '1.0.0';
52
+ } catch (error) {
53
+ return '1.0.0';
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Checks if arguments are legacy-style (direct flags without subcommand).
59
+ *
60
+ * Legacy patterns:
61
+ * - --global, -g
62
+ * - --local, -l
63
+ * - --config-dir, -c
64
+ * - (no args)
65
+ *
66
+ * @param {string[]} args - Process arguments
67
+ * @returns {boolean} True if legacy pattern detected
68
+ * @private
69
+ */
70
+ function isLegacyArgs(args) {
71
+ // If no args beyond node and script, it's legacy (default to install)
72
+ if (args.length <= 2) {
73
+ return true;
74
+ }
75
+
76
+ const userArgs = args.slice(2);
77
+
78
+ // Check for any known command
79
+ const knownCommands = ['install', 'list', 'uninstall', 'config', 'check', 'repair', 'update', '--help', '-h', '--version', '-V'];
80
+ const hasKnownCommand = knownCommands.some(cmd => userArgs.includes(cmd));
81
+
82
+ if (hasKnownCommand) {
83
+ return false;
84
+ }
85
+
86
+ // Check for legacy flags
87
+ const legacyFlags = ['--global', '-g', '--local', '-l', '--config-dir', '-c'];
88
+ const hasLegacyFlags = legacyFlags.some(flag =>
89
+ userArgs.some(arg => arg.startsWith(flag))
90
+ );
91
+
92
+ return hasLegacyFlags || userArgs.length === 0;
93
+ }
94
+
95
+ /**
96
+ * Transform legacy arguments to new subcommand format.
97
+ *
98
+ * @param {string[]} args - Process arguments
99
+ * @returns {string[]} Transformed arguments
100
+ * @private
101
+ */
102
+ function transformLegacyArgs(args) {
103
+ const userArgs = args.slice(2);
104
+
105
+ // If no args, transform to 'install'
106
+ if (userArgs.length === 0) {
107
+ return [...args.slice(0, 2), 'install'];
108
+ }
109
+
110
+ // Check if already has 'install' subcommand
111
+ if (userArgs[0] === 'install') {
112
+ return args;
113
+ }
114
+
115
+ // Transform: [flags...] -> ['install', flags...]
116
+ // But don't add 'install' if the first arg starts with a flag
117
+ const firstArg = userArgs[0];
118
+ if (firstArg.startsWith('-')) {
119
+ return [...args.slice(0, 2), 'install', ...userArgs];
120
+ }
121
+
122
+ return args;
123
+ }
124
+
125
+ /**
126
+ * Main CLI function.
127
+ *
128
+ * Sets up Commander program, registers commands, handles legacy
129
+ * compatibility, and executes the appropriate command.
130
+ *
131
+ * @returns {Promise<void>}
132
+ * @private
133
+ */
134
+ async function main() {
135
+ const program = new Command();
136
+
137
+ // Basic program setup
138
+ program
139
+ .name('gsd-opencode')
140
+ .description('GSD-OpenCode distribution manager')
141
+ .version(getPackageVersion(), '-v, --version', 'Display version number')
142
+ .helpOption('-h, --help', 'Display help for command')
143
+ .configureOutput({
144
+ writeErr: (str) => logger.error(str.trim()),
145
+ outputError: (str, write) => write(chalk.red(str))
146
+ });
147
+
148
+ // Global options (available to all subcommands)
149
+ program.option('--verbose', 'Enable verbose output for debugging', false);
150
+
151
+ // Install command
152
+ program
153
+ .command('install')
154
+ .description('Install GSD-OpenCode distribution to your system')
155
+ .option('-g, --global', 'Install globally to ~/.config/opencode/')
156
+ .option('-l, --local', 'Install locally to ./.opencode/')
157
+ .option('-c, --config-dir <path>', 'Specify custom configuration directory')
158
+ .action(async (options, command) => {
159
+ // Get global verbose option from parent
160
+ const globalOptions = command.parent.opts();
161
+ const fullOptions = {
162
+ ...options,
163
+ verbose: globalOptions.verbose || options.verbose
164
+ };
165
+
166
+ const exitCode = await installCommand(fullOptions);
167
+ process.exit(exitCode);
168
+ });
169
+
170
+ // List command
171
+ program
172
+ .command('list')
173
+ .alias('ls')
174
+ .description('Show GSD-OpenCode installation status and version')
175
+ .option('-g, --global', 'Show global installation only')
176
+ .option('-l, --local', 'Show local installation only')
177
+ .action(async (options, command) => {
178
+ // Get global verbose option from parent
179
+ const globalOptions = command.parent.opts();
180
+ const fullOptions = {
181
+ ...options,
182
+ verbose: globalOptions.verbose || options.verbose
183
+ };
184
+
185
+ const exitCode = await listCommand(fullOptions);
186
+ process.exit(exitCode);
187
+ });
188
+
189
+ // Check command
190
+ program
191
+ .command('check')
192
+ .alias('verify')
193
+ .description('Verify GSD-OpenCode installation health')
194
+ .option('-g, --global', 'Check global installation only')
195
+ .option('-l, --local', 'Check local installation only')
196
+ .action(async (options, command) => {
197
+ const globalOptions = command.parent.opts();
198
+ const fullOptions = {
199
+ ...options,
200
+ verbose: globalOptions.verbose || options.verbose
201
+ };
202
+
203
+ const exitCode = await checkCommand(fullOptions);
204
+ process.exit(exitCode);
205
+ });
206
+
207
+ // Repair command
208
+ program
209
+ .command('repair')
210
+ .description('Repair broken GSD-OpenCode installation')
211
+ .option('-g, --global', 'Repair global installation only')
212
+ .option('-l, --local', 'Repair local installation only')
213
+ .action(async (options, command) => {
214
+ const globalOptions = command.parent.opts();
215
+ const fullOptions = {
216
+ ...options,
217
+ verbose: globalOptions.verbose || options.verbose
218
+ };
219
+
220
+ const exitCode = await repairCommand(fullOptions);
221
+ process.exit(exitCode);
222
+ });
223
+
224
+ // Update command
225
+ program
226
+ .command('update [version]')
227
+ .description('Update GSD-OpenCode to latest or specified version')
228
+ .option('-g, --global', 'Update global installation only')
229
+ .option('-l, --local', 'Update local installation only')
230
+ .option('--beta', 'Update to beta version from @rokicool/gsd-opencode')
231
+ .option('-f, --force', 'Skip confirmation prompt')
232
+ .option('--dry-run', 'Show what would be updated without making changes')
233
+ .option('--skip-migration', 'Skip automatic structure migration (not recommended)')
234
+ .action(async (version, options, command) => {
235
+ const globalOptions = command.parent.opts();
236
+ const fullOptions = {
237
+ ...options,
238
+ version,
239
+ verbose: globalOptions.verbose || options.verbose
240
+ };
241
+
242
+ const exitCode = await updateCommand(fullOptions);
243
+ process.exit(exitCode);
244
+ });
245
+
246
+ // Uninstall command
247
+ program
248
+ .command('uninstall')
249
+ .alias('rm')
250
+ .description('Remove GSD-OpenCode installation')
251
+ .option('-g, --global', 'Remove global installation')
252
+ .option('-l, --local', 'Remove local installation')
253
+ .option('-f, --force', 'Skip confirmation prompt')
254
+ .option('--dry-run', 'Show what would be removed without removing')
255
+ .option('--no-backup', 'Skip backup creation before removal')
256
+ .action(async (options, command) => {
257
+ const globalOptions = command.parent.opts();
258
+ const fullOptions = {
259
+ ...options,
260
+ verbose: globalOptions.verbose || options.verbose
261
+ };
262
+
263
+ const exitCode = await uninstallCommand(fullOptions);
264
+ process.exit(exitCode);
265
+ });
266
+
267
+ // Config command with subcommands
268
+ const configCmd = program
269
+ .command('config')
270
+ .description('Manage GSD-OpenCode configuration');
271
+
272
+ configCmd
273
+ .command('get <key>')
274
+ .description('Get a configuration value')
275
+ .action(async (key, options, command) => {
276
+ const globalOptions = command.parent.parent.opts();
277
+ const fullOptions = {
278
+ verbose: globalOptions.verbose || options.verbose
279
+ };
280
+
281
+ const exitCode = await configGetCommand(key, fullOptions);
282
+ process.exit(exitCode);
283
+ });
284
+
285
+ configCmd
286
+ .command('set <key> <value>')
287
+ .description('Set a configuration value')
288
+ .action(async (key, value, options, command) => {
289
+ const globalOptions = command.parent.parent.opts();
290
+ const fullOptions = {
291
+ verbose: globalOptions.verbose || options.verbose
292
+ };
293
+
294
+ const exitCode = await configSetCommand(key, value, fullOptions);
295
+ process.exit(exitCode);
296
+ });
297
+
298
+ configCmd
299
+ .command('reset [key]')
300
+ .description('Reset configuration to defaults')
301
+ .option('--all', 'Reset all settings')
302
+ .action(async (key, options, command) => {
303
+ const globalOptions = command.parent.parent.opts();
304
+ const fullOptions = {
305
+ ...options,
306
+ verbose: globalOptions.verbose || options.verbose
307
+ };
308
+
309
+ const exitCode = await configResetCommand(key, fullOptions);
310
+ process.exit(exitCode);
311
+ });
312
+
313
+ configCmd
314
+ .command('list')
315
+ .alias('ls')
316
+ .description('List all configuration settings')
317
+ .option('--json', 'Output as JSON')
318
+ .action(async (options, command) => {
319
+ const globalOptions = command.parent.parent.opts();
320
+ const fullOptions = {
321
+ ...options,
322
+ verbose: globalOptions.verbose || options.verbose
323
+ };
324
+
325
+ const exitCode = await configListCommand(fullOptions);
326
+ process.exit(exitCode);
327
+ });
328
+
329
+ // Handle legacy argument patterns
330
+ const args = process.argv;
331
+
332
+ if (isLegacyArgs(args)) {
333
+ // Transform legacy args to new format
334
+ process.argv = transformLegacyArgs(args);
335
+
336
+ // Show deprecation notice in verbose mode
337
+ const userArgs = args.slice(2);
338
+ if (userArgs.some(arg => arg === '--verbose' || arg === '-v')) {
339
+ setVerbose(true);
340
+ logger.debug('Legacy argument pattern detected, routing to install command');
341
+ }
342
+ }
343
+
344
+ // Parse and execute
345
+ await program.parseAsync(process.argv);
346
+ }
347
+
348
+ // Run CLI
349
+ main().catch((error) => {
350
+ logger.error('Unexpected error:', error);
351
+ process.exit(ERROR_CODES.GENERAL_ERROR);
352
+ });
package/bin/install.js CHANGED
@@ -126,10 +126,25 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
126
126
  if (entry.isDirectory()) {
127
127
  copyWithPathReplacement(srcPath, destPath, pathPrefix);
128
128
  } else if (entry.name.endsWith(".md")) {
129
- // Replace ~/.claude/ and ./.claude/ with OpenCode paths
129
+ // Replace repo-local prompt references so installed prompts work outside this repo.
130
+ // IMPORTANT: order matters to avoid double-rewrites.
130
131
  let content = fs.readFileSync(srcPath, "utf8");
132
+
133
+ // 1) @-references to this repo → install-relative @-references
134
+ // @gsd-opencode/... → @~/.config/opencode/... (global)
135
+ // @gsd-opencode/... → @./.opencode/... (local)
136
+ content = content.replace(/@gsd-opencode\//g, `@${pathPrefix}`);
137
+
138
+ // 2) Plain (non-@) repo-local paths → install-relative paths
139
+ // gsd-opencode/... → ~/.config/opencode/... (global)
140
+ // gsd-opencode/... → ./.opencode/... (local)
141
+ content = content.replace(/\bgsd-opencode\//g, pathPrefix);
142
+
143
+ // 3) Back-compat: rewrite legacy Claude paths → OpenCode paths
144
+ // NOTE: keep these rewrites verbatim for backward compatibility.
131
145
  content = content.replace(/~\/\.claude\//g, pathPrefix);
132
146
  content = content.replace(/\.\/\.claude\//g, "./.opencode/");
147
+
133
148
  fs.writeFileSync(destPath, content);
134
149
  } else {
135
150
  fs.copyFileSync(srcPath, destPath);
@@ -164,6 +179,68 @@ function install(isGlobal) {
164
179
  : "~/.config/opencode/"
165
180
  : "./.opencode/";
166
181
 
182
+ function scanForUnresolvedRepoLocalTokens(destRoot) {
183
+ const tokenRegex = /@gsd-opencode\/|\bgsd-opencode\//g;
184
+ const maxHits = 10;
185
+ const hits = [];
186
+
187
+ function walk(dir) {
188
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
189
+ for (const entry of entries) {
190
+ if (hits.length >= maxHits) return;
191
+
192
+ const filePath = path.join(dir, entry.name);
193
+ if (entry.isDirectory()) {
194
+ walk(filePath);
195
+ continue;
196
+ }
197
+
198
+ if (!entry.name.endsWith(".md")) continue;
199
+
200
+ const content = fs.readFileSync(filePath, "utf8");
201
+ tokenRegex.lastIndex = 0;
202
+ if (!tokenRegex.test(content)) continue;
203
+
204
+ // Capture a readable snippet (first matching line)
205
+ const lines = content.split(/\r?\n/);
206
+ for (let i = 0; i < lines.length; i++) {
207
+ tokenRegex.lastIndex = 0;
208
+ if (tokenRegex.test(lines[i])) {
209
+ hits.push({
210
+ file: filePath,
211
+ line: i + 1,
212
+ snippet: lines[i].trim().slice(0, 200),
213
+ });
214
+ break;
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ walk(destRoot);
221
+
222
+ if (hits.length > 0) {
223
+ console.log(
224
+ `\n ${yellow}⚠️ Install sanity check: unresolved repo-local tokens found${reset}`,
225
+ );
226
+ console.log(
227
+ ` ${yellow}This may cause commands like /gsd-settings to fail in other repos (ENOENT).${reset}`,
228
+ );
229
+ console.log(` ${dim}Showing up to ${maxHits} matches:${reset}`);
230
+
231
+ for (const hit of hits) {
232
+ const displayPath = isGlobal
233
+ ? hit.file.replace(os.homedir(), "~")
234
+ : hit.file.replace(process.cwd(), ".");
235
+ console.log(
236
+ ` - ${displayPath}:${hit.line}\n ${dim}${hit.snippet}${reset}`,
237
+ );
238
+ }
239
+
240
+ console.log("");
241
+ }
242
+ }
243
+
167
244
  console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
168
245
 
169
246
  // Create commands directory (singular "command" not "commands")
@@ -188,6 +265,9 @@ function install(isGlobal) {
188
265
  copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
189
266
  console.log(` ${green}✓${reset} Installed get-shit-done`);
190
267
 
268
+ // Post-install diagnostic (do not fail install).
269
+ scanForUnresolvedRepoLocalTokens(opencodeDir);
270
+
191
271
  // Create VERSION file
192
272
  fs.writeFileSync(path.join(skillDest, "VERSION"), `v${pkg.version}`);
193
273
  console.log(` ${green}✓${reset} Created VERSION file`);
@@ -25,7 +25,7 @@ Purpose: Add planned work discovered during execution that belongs at the end of
25
25
 
26
26
  <step name="parse_arguments">
27
27
  Parse the command arguments:
28
- - $ARGUMENTS the phase description
28
+ - `$ARGUMENTS` the phase description
29
29
  - Example: `/gsd-add-phase Add authentication` → description = "Add authentication"
30
30
  - Example: `/gsd-add-phase Fix critical performance issues` → description = "Fix critical performance issues"
31
31
 
@@ -22,7 +22,7 @@ Verify milestone achieved its definition of done. Check requirements coverage, c
22
22
  </execution_context>
23
23
 
24
24
  <context>
25
- Version: $ARGUMENTS (optional — defaults to current milestone)
25
+ Version: `$ARGUMENTS` (optional — defaults to current milestone)
26
26
 
27
27
  **Original Intent:**
28
28
  @.planning/PROJECT.md
@@ -18,7 +18,7 @@ Debug issues using scientific method with subagent isolation.
18
18
  </objective>
19
19
 
20
20
  <context>
21
- User's issue: $ARGUMENTS
21
+ User's issue: `$ARGUMENTS`
22
22
 
23
23
  Check for active sessions:
24
24
  ```bash
@@ -48,11 +48,11 @@ Store resolved model for use in Task calls below.
48
48
 
49
49
  ## 1. Check Active Sessions
50
50
 
51
- If active sessions exist AND no $ARGUMENTS:
51
+ If active sessions exist AND no `$ARGUMENTS`:
52
52
  - List sessions with status, hypothesis, next action
53
53
  - User picks number to resume OR describes new issue
54
54
 
55
- If $ARGUMENTS provided OR user describes new issue:
55
+ If `$ARGUMENTS` provided OR user describes new issue:
56
56
  - Continue to symptom gathering
57
57
 
58
58
  ## 2. Gather Symptoms (if new issue)
@@ -29,7 +29,7 @@ Extract implementation decisions that downstream agents need — researcher and
29
29
  </execution_context>
30
30
 
31
31
  <context>
32
- Phase number: $ARGUMENTS (required)
32
+ Phase number: `$ARGUMENTS` (required)
33
33
 
34
34
  **Load project state:**
35
35
  @.planning/STATE.md
@@ -28,7 +28,7 @@ Context budget: ~15% orchestrator, 100% fresh per subagent.
28
28
  </execution_context>
29
29
 
30
30
  <context>
31
- Phase: $ARGUMENTS
31
+ Phase: `$ARGUMENTS`
32
32
 
33
33
  **Flags:**
34
34
  - `--gaps-only` — Execute only gap closure plans (plans with `gap_closure: true` in frontmatter). Use after verify-work creates fix plans.
@@ -21,7 +21,7 @@ Output: Conversational output only (no file creation) - ends with "What do you t
21
21
  </execution_context>
22
22
 
23
23
  <context>
24
- Phase number: $ARGUMENTS (required)
24
+ Phase number: `$ARGUMENTS` (required)
25
25
 
26
26
  **Load project state first:**
27
27
  @.planning/STATE.md
@@ -24,7 +24,7 @@ Output: .planning/codebase/ folder with 7 structured documents about the codebas
24
24
  </execution_context>
25
25
 
26
26
  <context>
27
- Focus area: $ARGUMENTS (optional - if provided, tells agents to focus on specific subsystem)
27
+ Focus area: `$ARGUMENTS` (optional - if provided, tells agents to focus on specific subsystem)
28
28
 
29
29
  **Load project state if exists:**
30
30
  Check for .planning/STATE.md - loads context if project already initialized
@@ -33,7 +33,7 @@ This is the brownfield equivalent of new-project. The project exists, PROJECT.md
33
33
  </execution_context>
34
34
 
35
35
  <context>
36
- Milestone name: $ARGUMENTS (optional - will prompt if not provided)
36
+ Milestone name: `$ARGUMENTS` (optional - will prompt if not provided)
37
37
 
38
38
  **Load project context:**
39
39
  @.planning/PROJECT.md
@@ -327,9 +327,9 @@ questions: [
327
327
  question: "Which AI models for planning agents?",
328
328
  multiSelect: false,
329
329
  options: [
330
- { label: "Balanced (Recommended)", description: "Sonnet for most agents — good quality/cost ratio" },
331
- { label: "Quality", description: "Opus for research/roadmap — higher cost, deeper analysis" },
332
- { label: "Budget", description: "Haiku where possible — fastest, lowest cost" }
330
+ { label: "Balanced", description: "planning/verifier: opencode/glm-4.7-free, execution: opencode/minimax-m2.1-free" },
331
+ { label: "Quality", description: "All stages: opencode/glm-4.7-free" },
332
+ { label: "Budget", description: "planning/verifier: opencode/minimax-m2.1-free, execution: opencode/grok-code" }
333
333
  ]
334
334
  }
335
335
  ]
@@ -344,6 +344,31 @@ Create `.planning/config.json` with all settings:
344
344
  "parallelization": true|false,
345
345
  "commit_docs": true|false,
346
346
  "model_profile": "quality|balanced|budget",
347
+ "profiles": {
348
+ "active_profile": "balanced",
349
+ "presets": {
350
+ "quality": {
351
+ "planning": "opencode/glm-4.7-free",
352
+ "execution": "opencode/glm-4.7-free",
353
+ "verification": "opencode/glm-4.7-free"
354
+ },
355
+ "balanced": {
356
+ "planning": "opencode/glm-4.7-free",
357
+ "execution": "opencode/minimax-m2.1-free",
358
+ "verification": "opencode/glm-4.7-free"
359
+ },
360
+ "budget": {
361
+ "planning": "opencode/minimax-m2.1-free",
362
+ "execution": "opencode/grok-code",
363
+ "verification": "opencode/minimax-m2.1-free"
364
+ }
365
+ },
366
+ "custom_overrides": {
367
+ "quality": {},
368
+ "balanced": {},
369
+ "budget": {}
370
+ }
371
+ },
347
372
  "workflow": {
348
373
  "research": true|false,
349
374
  "plan_check": true|false,
@@ -374,6 +399,46 @@ EOF
374
399
  )"
375
400
  ```
376
401
 
402
+ **Generate opencode.json from active profile:**
403
+
404
+ Create `opencode.json` in the project root. This is a derived config that assigns a model to each GSD agent.
405
+
406
+ Use the effective stage models from `.planning/config.json`:
407
+
408
+ - planning model = `profiles.presets.{active_profile}.planning` (unless overridden)
409
+ - execution model = `profiles.presets.{active_profile}.execution` (unless overridden)
410
+ - verification model = `profiles.presets.{active_profile}.verification` (unless overridden)
411
+
412
+ Write `opencode.json`:
413
+
414
+ ```json
415
+ {
416
+ "$schema": "https://opencode.ai/config.json",
417
+ "agent": {
418
+ "gsd-planner": { "model": "{planning model}" },
419
+ "gsd-plan-checker": { "model": "{planning model}" },
420
+ "gsd-phase-researcher": { "model": "{planning model}" },
421
+ "gsd-roadmapper": { "model": "{planning model}" },
422
+ "gsd-project-researcher": { "model": "{planning model}" },
423
+ "gsd-research-synthesizer": { "model": "{planning model}" },
424
+ "gsd-codebase-mapper": { "model": "{planning model}" },
425
+ "gsd-executor": { "model": "{execution model}" },
426
+ "gsd-debugger": { "model": "{execution model}" },
427
+ "gsd-verifier": { "model": "{verification model}" },
428
+ "gsd-integration-checker": { "model": "{verification model}" }
429
+ }
430
+ }
431
+ ```
432
+
433
+ **Commit opencode.json:**
434
+
435
+ ```bash
436
+ git add opencode.json
437
+ git commit -m "chore: configure opencode agent models"
438
+ ```
439
+
440
+ **Important:** OpenCode loads `opencode.json` at session start and does not hot-reload. If you change profiles later via `/gsd-set-profile` or `/gsd-settings`, run `/new` (or restart OpenCode) for it to take effect.
441
+
377
442
  **Note:** Run `/gsd-settings` anytime to update these preferences.
378
443
 
379
444
  ## Phase 5.5: Resolve Model Profile
@@ -949,14 +1014,14 @@ Present completion with next steps:
949
1014
 
950
1015
  **Phase 1: [Phase Name]** — [Goal from ROADMAP.md]
951
1016
 
952
- `/gsd-discuss-phase 1` — gather context and clarify approach
1017
+ /gsd-discuss-phase 1 — gather context and clarify approach
953
1018
 
954
- *`/new` first → fresh context window*
1019
+ */new first → fresh context window*
955
1020
 
956
1021
  ---
957
1022
 
958
1023
  **Also available:**
959
- - `/gsd-plan-phase 1` — skip discussion, plan directly
1024
+ - /gsd-plan-phase 1 — skip discussion, plan directly
960
1025
 
961
1026
  ───────────────────────────────────────────────────────────────
962
1027
  ```
@@ -29,7 +29,7 @@ Create executable phase prompts (PLAN.md files) for a roadmap phase with integra
29
29
  </objective>
30
30
 
31
31
  <context>
32
- Phase number: $ARGUMENTS (optional - auto-detects next unplanned phase if not provided)
32
+ Phase number: `$ARGUMENTS` (optional - auto-detects next unplanned phase if not provided)
33
33
 
34
34
  **Flags:**
35
35
  - `--research` — Force re-research even if RESEARCH.md exists
@@ -70,7 +70,7 @@ Store resolved models for use in Task calls below.
70
70
 
71
71
  ## 2. Parse and Normalize Arguments
72
72
 
73
- Extract from $ARGUMENTS:
73
+ Extract from `$ARGUMENTS`:
74
74
 
75
75
  - Phase number (integer or decimal like `2.1`)
76
76
  - `--research` flag to force re-research
@@ -24,7 +24,7 @@ Research how to implement a phase. Spawns gsd-phase-researcher agent with phase
24
24
  </objective>
25
25
 
26
26
  <context>
27
- Phase number: $ARGUMENTS (required)
27
+ Phase number: `$ARGUMENTS` (required)
28
28
 
29
29
  Normalize phase input in step 1 before any directory lookups.
30
30
  </context>