get-shit-done-cc 1.9.4 → 1.9.6

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/README.md CHANGED
@@ -75,7 +75,11 @@ People who want to describe what they want and have it built correctly — witho
75
75
  npx get-shit-done-cc
76
76
  ```
77
77
 
78
- That's it. Verify with `/gsd:help` inside your Claude Code interface.
78
+ The installer prompts you to choose:
79
+ 1. **Runtime** — Claude Code, OpenCode, or both
80
+ 2. **Location** — Global (all projects) or local (current project only)
81
+
82
+ Verify with `/gsd:help` inside your Claude Code or OpenCode interface.
79
83
 
80
84
  ### Staying Updated
81
85
 
@@ -95,11 +99,19 @@ npx get-shit-done-cc@latest
95
99
  <summary><strong>Non-interactive Install (Docker, CI, Scripts)</strong></summary>
96
100
 
97
101
  ```bash
98
- npx get-shit-done-cc --global # Install to ~/.claude/
99
- npx get-shit-done-cc --local # Install to ./.claude/
102
+ # Claude Code
103
+ npx get-shit-done-cc --claude --global # Install to ~/.claude/
104
+ npx get-shit-done-cc --claude --local # Install to ./.claude/
105
+
106
+ # OpenCode (open source, free models)
107
+ npx get-shit-done-cc --opencode --global # Install to ~/.opencode/
108
+
109
+ # Both runtimes
110
+ npx get-shit-done-cc --both --global # Install to both directories
100
111
  ```
101
112
 
102
- Use `--global` (`-g`) or `--local` (`-l`) to skip the interactive prompt.
113
+ Use `--global` (`-g`) or `--local` (`-l`) to skip the location prompt.
114
+ Use `--claude`, `--opencode`, or `--both` to skip the runtime prompt.
103
115
 
104
116
  </details>
105
117
 
@@ -111,7 +123,7 @@ Clone the repository and run the installer locally:
111
123
  ```bash
112
124
  git clone https://github.com/glittercowboy/get-shit-done.git
113
125
  cd get-shit-done
114
- node bin/install.js --local
126
+ node bin/install.js --claude --local
115
127
  ```
116
128
 
117
129
  Installs to `./.claude/` for testing modifications before contributing.
@@ -543,6 +555,15 @@ This ensures absolute paths are used instead of `~` which may not expand correct
543
555
 
544
556
  ---
545
557
 
558
+ ## Community Ports
559
+
560
+ | Project | Platform | Description |
561
+ |---------|----------|-------------|
562
+ | [gsd-opencode](https://github.com/rokicool/gsd-opencode) | OpenCode | GSD adapted for OpenCode CLI |
563
+ | [gsd-gemini](https://github.com/uberfuzzy/gsd-gemini) | Gemini CLI | GSD adapted for Google's Gemini CLI |
564
+
565
+ ---
566
+
546
567
  ## Star History
547
568
 
548
569
  <a href="https://star-history.com/#glittercowboy/get-shit-done&Date">
package/bin/install.js CHANGED
@@ -15,6 +15,29 @@ const reset = '\x1b[0m';
15
15
  // Get version from package.json
16
16
  const pkg = require('../package.json');
17
17
 
18
+ // Parse args
19
+ const args = process.argv.slice(2);
20
+ const hasGlobal = args.includes('--global') || args.includes('-g');
21
+ const hasLocal = args.includes('--local') || args.includes('-l');
22
+ const hasOpencode = args.includes('--opencode');
23
+ const hasClaude = args.includes('--claude');
24
+ const hasBoth = args.includes('--both');
25
+
26
+ // Runtime selection - can be set by flags or interactive prompt
27
+ let selectedRuntimes = [];
28
+ if (hasBoth) {
29
+ selectedRuntimes = ['claude', 'opencode'];
30
+ } else if (hasOpencode) {
31
+ selectedRuntimes = ['opencode'];
32
+ } else if (hasClaude) {
33
+ selectedRuntimes = ['claude'];
34
+ }
35
+
36
+ // Helper to get directory name for a runtime
37
+ function getDirName(runtime) {
38
+ return runtime === 'opencode' ? '.opencode' : '.claude';
39
+ }
40
+
18
41
  const banner = `
19
42
  ${cyan} ██████╗ ███████╗██████╗
20
43
  ██╔════╝ ██╔════╝██╔══██╗
@@ -25,14 +48,9 @@ ${cyan} ██████╗ ███████╗██████╗
25
48
 
26
49
  Get Shit Done ${dim}v${pkg.version}${reset}
27
50
  A meta-prompting, context engineering and spec-driven
28
- development system for Claude Code by TÂCHES.
51
+ development system for Claude Code (and opencode) by TÂCHES.
29
52
  `;
30
53
 
31
- // Parse args
32
- const args = process.argv.slice(2);
33
- const hasGlobal = args.includes('--global') || args.includes('-g');
34
- const hasLocal = args.includes('--local') || args.includes('-l');
35
-
36
54
  // Parse --config-dir argument
37
55
  function parseConfigDirArg() {
38
56
  const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
@@ -68,24 +86,33 @@ if (hasHelp) {
68
86
  console.log(` ${yellow}Usage:${reset} npx get-shit-done-cc [options]
69
87
 
70
88
  ${yellow}Options:${reset}
71
- ${cyan}-g, --global${reset} Install globally (to Claude config directory)
72
- ${cyan}-l, --local${reset} Install locally (to ./.claude in current directory)
73
- ${cyan}-c, --config-dir <path>${reset} Specify custom Claude config directory
89
+ ${cyan}-g, --global${reset} Install globally (to config directory)
90
+ ${cyan}-l, --local${reset} Install locally (to current directory)
91
+ ${cyan}--claude${reset} Install for Claude Code only
92
+ ${cyan}--opencode${reset} Install for OpenCode only
93
+ ${cyan}--both${reset} Install for both Claude Code and OpenCode
94
+ ${cyan}-c, --config-dir <path>${reset} Specify custom config directory
74
95
  ${cyan}-h, --help${reset} Show this help message
75
96
  ${cyan}--force-statusline${reset} Replace existing statusline config
76
97
 
77
98
  ${yellow}Examples:${reset}
78
- ${dim}# Install to default ~/.claude directory${reset}
79
- npx get-shit-done-cc --global
99
+ ${dim}# Interactive install (prompts for runtime and location)${reset}
100
+ npx get-shit-done-cc
101
+
102
+ ${dim}# Install for Claude Code globally${reset}
103
+ npx get-shit-done-cc --claude --global
104
+
105
+ ${dim}# Install for OpenCode globally${reset}
106
+ npx get-shit-done-cc --opencode --global
80
107
 
81
- ${dim}# Install to custom config directory (for multiple Claude accounts)${reset}
82
- npx get-shit-done-cc --global --config-dir ~/.claude-bc
108
+ ${dim}# Install for both runtimes globally${reset}
109
+ npx get-shit-done-cc --both --global
83
110
 
84
- ${dim}# Using environment variable${reset}
85
- CLAUDE_CONFIG_DIR=~/.claude-bc npx get-shit-done-cc --global
111
+ ${dim}# Install to custom config directory${reset}
112
+ npx get-shit-done-cc --claude --global --config-dir ~/.claude-bc
86
113
 
87
114
  ${dim}# Install to current project only${reset}
88
- npx get-shit-done-cc --local
115
+ npx get-shit-done-cc --claude --local
89
116
 
90
117
  ${yellow}Notes:${reset}
91
118
  The --config-dir option is useful when you have multiple Claude Code
@@ -106,7 +133,17 @@ function expandTilde(filePath) {
106
133
  }
107
134
 
108
135
  /**
109
- * Read and parse settings.json, returning empty object if doesn't exist
136
+ * Build a hook command path using forward slashes for cross-platform compatibility.
137
+ * On Windows, $HOME is not expanded by cmd.exe/PowerShell, so we use the actual path.
138
+ */
139
+ function buildHookCommand(claudeDir, hookName) {
140
+ // Use forward slashes for Node.js compatibility on all platforms
141
+ const hooksPath = claudeDir.replace(/\\/g, '/') + '/hooks/' + hookName;
142
+ return `node "${hooksPath}"`;
143
+ }
144
+
145
+ /**
146
+ * Read and parse settings.json, returning empty object if it doesn't exist
110
147
  */
111
148
  function readSettings(settingsPath) {
112
149
  if (fs.existsSync(settingsPath)) {
@@ -126,11 +163,169 @@ function writeSettings(settingsPath, settings) {
126
163
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
127
164
  }
128
165
 
166
+ /**
167
+ * Convert Claude Code frontmatter to opencode format
168
+ * - Converts 'allowed-tools:' array to 'permission:' object
169
+ * @param {string} content - Markdown file content with YAML frontmatter
170
+ * @returns {string} - Content with converted frontmatter
171
+ */
172
+ // Color name to hex mapping for opencode compatibility
173
+ const colorNameToHex = {
174
+ cyan: '#00FFFF',
175
+ red: '#FF0000',
176
+ green: '#00FF00',
177
+ blue: '#0000FF',
178
+ yellow: '#FFFF00',
179
+ magenta: '#FF00FF',
180
+ orange: '#FFA500',
181
+ purple: '#800080',
182
+ pink: '#FFC0CB',
183
+ white: '#FFFFFF',
184
+ black: '#000000',
185
+ gray: '#808080',
186
+ grey: '#808080',
187
+ };
188
+
189
+ // Tool name mapping from Claude Code to OpenCode
190
+ // OpenCode uses lowercase tool names; special mappings for renamed tools
191
+ const claudeToOpencodeTools = {
192
+ AskUserQuestion: 'question',
193
+ SlashCommand: 'skill',
194
+ TodoWrite: 'todowrite',
195
+ WebFetch: 'webfetch',
196
+ WebSearch: 'websearch', // Plugin/MCP - keep for compatibility
197
+ };
198
+
199
+ /**
200
+ * Convert a Claude Code tool name to OpenCode format
201
+ * - Applies special mappings (AskUserQuestion -> question, etc.)
202
+ * - Converts to lowercase (except MCP tools which keep their format)
203
+ */
204
+ function convertToolName(claudeTool) {
205
+ // Check for special mapping first
206
+ if (claudeToOpencodeTools[claudeTool]) {
207
+ return claudeToOpencodeTools[claudeTool];
208
+ }
209
+ // MCP tools (mcp__*) keep their format
210
+ if (claudeTool.startsWith('mcp__')) {
211
+ return claudeTool;
212
+ }
213
+ // Default: convert to lowercase
214
+ return claudeTool.toLowerCase();
215
+ }
216
+
217
+ function convertClaudeToOpencodeFrontmatter(content) {
218
+ // Replace tool name references in content (applies to all files)
219
+ let convertedContent = content;
220
+ convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
221
+ convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
222
+ convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
223
+ // Replace /gsd:command with /gsd/command for opencode
224
+ convertedContent = convertedContent.replace(/\/gsd:/g, '/gsd/');
225
+ // Replace ~/.claude with ~/.opencode
226
+ convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.opencode');
227
+
228
+ // Check if content has frontmatter
229
+ if (!convertedContent.startsWith('---')) {
230
+ return convertedContent;
231
+ }
232
+
233
+ // Find the end of frontmatter
234
+ const endIndex = convertedContent.indexOf('---', 3);
235
+ if (endIndex === -1) {
236
+ return convertedContent;
237
+ }
238
+
239
+ const frontmatter = convertedContent.substring(3, endIndex).trim();
240
+ const body = convertedContent.substring(endIndex + 3);
241
+
242
+ // Parse frontmatter line by line (simple YAML parsing)
243
+ const lines = frontmatter.split('\n');
244
+ const newLines = [];
245
+ let inAllowedTools = false;
246
+ const allowedTools = [];
247
+
248
+ for (const line of lines) {
249
+ const trimmed = line.trim();
250
+
251
+ // Detect start of allowed-tools array
252
+ if (trimmed.startsWith('allowed-tools:')) {
253
+ inAllowedTools = true;
254
+ continue;
255
+ }
256
+
257
+ // Detect inline tools: field (comma-separated string)
258
+ if (trimmed.startsWith('tools:')) {
259
+ const toolsValue = trimmed.substring(6).trim();
260
+ if (toolsValue) {
261
+ // Parse comma-separated tools
262
+ const tools = toolsValue.split(',').map(t => t.trim()).filter(t => t);
263
+ allowedTools.push(...tools);
264
+ }
265
+ continue;
266
+ }
267
+
268
+ // Remove name: field - opencode uses filename for command name
269
+ if (trimmed.startsWith('name:')) {
270
+ continue;
271
+ }
272
+
273
+ // Convert color names to hex for opencode
274
+ if (trimmed.startsWith('color:')) {
275
+ const colorValue = trimmed.substring(6).trim().toLowerCase();
276
+ const hexColor = colorNameToHex[colorValue];
277
+ if (hexColor) {
278
+ newLines.push(`color: "${hexColor}"`);
279
+ } else if (colorValue.startsWith('#')) {
280
+ // Already hex, keep as is
281
+ newLines.push(line);
282
+ }
283
+ // Skip unknown color names
284
+ continue;
285
+ }
286
+
287
+ // Collect allowed-tools items
288
+ if (inAllowedTools) {
289
+ if (trimmed.startsWith('- ')) {
290
+ allowedTools.push(trimmed.substring(2).trim());
291
+ continue;
292
+ } else if (trimmed && !trimmed.startsWith('-')) {
293
+ // End of array, new field started
294
+ inAllowedTools = false;
295
+ }
296
+ }
297
+
298
+ // Keep other fields (including name: which opencode ignores)
299
+ if (!inAllowedTools) {
300
+ newLines.push(line);
301
+ }
302
+ }
303
+
304
+ // Add tools object if we had allowed-tools or tools
305
+ if (allowedTools.length > 0) {
306
+ newLines.push('tools:');
307
+ for (const tool of allowedTools) {
308
+ newLines.push(` ${convertToolName(tool)}: true`);
309
+ }
310
+ }
311
+
312
+ // Rebuild frontmatter (body already has tool names converted)
313
+ const newFrontmatter = newLines.join('\n').trim();
314
+ return `---\n${newFrontmatter}\n---${body}`;
315
+ }
316
+
129
317
  /**
130
318
  * Recursively copy directory, replacing paths in .md files
131
319
  * Deletes existing destDir first to remove orphaned files from previous versions
320
+ * @param {string} srcDir - Source directory
321
+ * @param {string} destDir - Destination directory
322
+ * @param {string} pathPrefix - Path prefix for file references
323
+ * @param {string} runtime - Target runtime ('claude' or 'opencode')
132
324
  */
133
- function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
325
+ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
326
+ const isOpencode = runtime === 'opencode';
327
+ const dirName = getDirName(runtime);
328
+
134
329
  // Clean install: remove existing destination to prevent orphaned files
135
330
  if (fs.existsSync(destDir)) {
136
331
  fs.rmSync(destDir, { recursive: true });
@@ -144,11 +339,16 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
144
339
  const destPath = path.join(destDir, entry.name);
145
340
 
146
341
  if (entry.isDirectory()) {
147
- copyWithPathReplacement(srcPath, destPath, pathPrefix);
342
+ copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime);
148
343
  } else if (entry.name.endsWith('.md')) {
149
- // Replace ~/.claude/ with the appropriate prefix in markdown files
344
+ // Replace ~/.claude/ with the appropriate prefix in Markdown files
150
345
  let content = fs.readFileSync(srcPath, 'utf8');
151
- content = content.replace(/~\/\.claude\//g, pathPrefix);
346
+ const claudeDirRegex = new RegExp(`~/${dirName.replace('.', '\\.')}/`, 'g');
347
+ content = content.replace(claudeDirRegex, pathPrefix);
348
+ // Convert frontmatter for opencode compatibility
349
+ if (isOpencode) {
350
+ content = convertClaudeToOpencodeFrontmatter(content);
351
+ }
152
352
  fs.writeFileSync(destPath, content);
153
353
  } else {
154
354
  fs.copyFileSync(srcPath, destPath);
@@ -219,6 +419,59 @@ function cleanupOrphanedHooks(settings) {
219
419
  return settings;
220
420
  }
221
421
 
422
+ /**
423
+ * Configure OpenCode permissions to allow reading GSD reference docs
424
+ * This prevents permission prompts when GSD accesses ~/.opencode/get-shit-done/
425
+ */
426
+ function configureOpencodePermissions() {
427
+ const configPath = path.join(os.homedir(), '.opencode.json');
428
+
429
+ // Read existing config or create empty object
430
+ let config = {};
431
+ if (fs.existsSync(configPath)) {
432
+ try {
433
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
434
+ } catch (e) {
435
+ // Invalid JSON - start fresh but warn user
436
+ console.log(` ${yellow}⚠${reset} ~/.opencode.json had invalid JSON, recreating`);
437
+ }
438
+ }
439
+
440
+ // Ensure permission structure exists
441
+ if (!config.permission) {
442
+ config.permission = {};
443
+ }
444
+
445
+ const gsdPath = '~/.opencode/get-shit-done/*';
446
+ let modified = false;
447
+
448
+ // Configure read permission
449
+ if (!config.permission.read || typeof config.permission.read !== 'object') {
450
+ config.permission.read = {};
451
+ }
452
+ if (config.permission.read[gsdPath] !== 'allow') {
453
+ config.permission.read[gsdPath] = 'allow';
454
+ modified = true;
455
+ }
456
+
457
+ // Configure external_directory permission (the safety guard for paths outside project)
458
+ if (!config.permission.external_directory || typeof config.permission.external_directory !== 'object') {
459
+ config.permission.external_directory = {};
460
+ }
461
+ if (config.permission.external_directory[gsdPath] !== 'allow') {
462
+ config.permission.external_directory[gsdPath] = 'allow';
463
+ modified = true;
464
+ }
465
+
466
+ if (!modified) {
467
+ return; // Already configured
468
+ }
469
+
470
+ // Write config back
471
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
472
+ console.log(` ${green}✓${reset} Configured read permission for GSD docs`);
473
+ }
474
+
222
475
  /**
223
476
  * Verify a directory exists and contains files
224
477
  */
@@ -252,43 +505,49 @@ function verifyFileInstalled(filePath, description) {
252
505
  }
253
506
 
254
507
  /**
255
- * Install to the specified directory
508
+ * Install to the specified directory for a specific runtime
509
+ * @param {boolean} isGlobal - Whether to install globally or locally
510
+ * @param {string} runtime - Target runtime ('claude' or 'opencode')
256
511
  */
257
- function install(isGlobal) {
512
+ function install(isGlobal, runtime = 'claude') {
513
+ const isOpencode = runtime === 'opencode';
514
+ const dirName = getDirName(runtime);
258
515
  const src = path.join(__dirname, '..');
259
- // Priority: explicit --config-dir arg > CLAUDE_CONFIG_DIR env var > default ~/.claude
516
+
517
+ // Priority: explicit --config-dir arg > CLAUDE_CONFIG_DIR env var > default dir
260
518
  const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
261
- const defaultGlobalDir = configDir || path.join(os.homedir(), '.claude');
262
- const claudeDir = isGlobal
519
+ const defaultGlobalDir = configDir || path.join(os.homedir(), dirName);
520
+ const targetDir = isGlobal
263
521
  ? defaultGlobalDir
264
- : path.join(process.cwd(), '.claude');
522
+ : path.join(process.cwd(), dirName);
265
523
 
266
524
  const locationLabel = isGlobal
267
- ? claudeDir.replace(os.homedir(), '~')
268
- : claudeDir.replace(process.cwd(), '.');
525
+ ? targetDir.replace(os.homedir(), '~')
526
+ : targetDir.replace(process.cwd(), '.');
269
527
 
270
528
  // Path prefix for file references
271
529
  // Use actual path when CLAUDE_CONFIG_DIR is set, otherwise use ~ shorthand
272
530
  const pathPrefix = isGlobal
273
- ? (configDir ? `${claudeDir}/` : '~/.claude/')
274
- : './.claude/';
531
+ ? (configDir ? `${targetDir}/` : `~/${dirName}/`)
532
+ : `./${dirName}/`;
275
533
 
276
- console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
534
+ const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
535
+ console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
277
536
 
278
537
  // Track installation failures
279
538
  const failures = [];
280
539
 
281
540
  // Clean up orphaned files from previous versions
282
- cleanupOrphanedFiles(claudeDir);
541
+ cleanupOrphanedFiles(targetDir);
283
542
 
284
543
  // Create commands directory
285
- const commandsDir = path.join(claudeDir, 'commands');
544
+ const commandsDir = path.join(targetDir, 'commands');
286
545
  fs.mkdirSync(commandsDir, { recursive: true });
287
546
 
288
547
  // Copy commands/gsd with path replacement
289
548
  const gsdSrc = path.join(src, 'commands', 'gsd');
290
549
  const gsdDest = path.join(commandsDir, 'gsd');
291
- copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix);
550
+ copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime);
292
551
  if (verifyInstalled(gsdDest, 'commands/gsd')) {
293
552
  console.log(` ${green}✓${reset} Installed commands/gsd`);
294
553
  } else {
@@ -297,19 +556,19 @@ function install(isGlobal) {
297
556
 
298
557
  // Copy get-shit-done skill with path replacement
299
558
  const skillSrc = path.join(src, 'get-shit-done');
300
- const skillDest = path.join(claudeDir, 'get-shit-done');
301
- copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
559
+ const skillDest = path.join(targetDir, 'get-shit-done');
560
+ copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime);
302
561
  if (verifyInstalled(skillDest, 'get-shit-done')) {
303
562
  console.log(` ${green}✓${reset} Installed get-shit-done`);
304
563
  } else {
305
564
  failures.push('get-shit-done');
306
565
  }
307
566
 
308
- // Copy agents to ~/.claude/agents (subagents must be at root level)
567
+ // Copy agents to agents directory (subagents must be at root level)
309
568
  // Only delete gsd-*.md files to preserve user's custom agents
310
569
  const agentsSrc = path.join(src, 'agents');
311
570
  if (fs.existsSync(agentsSrc)) {
312
- const agentsDest = path.join(claudeDir, 'agents');
571
+ const agentsDest = path.join(targetDir, 'agents');
313
572
  fs.mkdirSync(agentsDest, { recursive: true });
314
573
 
315
574
  // Remove old GSD agents (gsd-*.md) before copying new ones
@@ -326,7 +585,12 @@ function install(isGlobal) {
326
585
  for (const entry of agentEntries) {
327
586
  if (entry.isFile() && entry.name.endsWith('.md')) {
328
587
  let content = fs.readFileSync(path.join(agentsSrc, entry.name), 'utf8');
329
- content = content.replace(/~\/\.claude\//g, pathPrefix);
588
+ const dirRegex = new RegExp(`~/${dirName.replace('.', '\\.')}/`, 'g');
589
+ content = content.replace(dirRegex, pathPrefix);
590
+ // Convert frontmatter for opencode compatibility
591
+ if (isOpencode) {
592
+ content = convertClaudeToOpencodeFrontmatter(content);
593
+ }
330
594
  fs.writeFileSync(path.join(agentsDest, entry.name), content);
331
595
  }
332
596
  }
@@ -339,7 +603,7 @@ function install(isGlobal) {
339
603
 
340
604
  // Copy CHANGELOG.md
341
605
  const changelogSrc = path.join(src, 'CHANGELOG.md');
342
- const changelogDest = path.join(claudeDir, 'get-shit-done', 'CHANGELOG.md');
606
+ const changelogDest = path.join(targetDir, 'get-shit-done', 'CHANGELOG.md');
343
607
  if (fs.existsSync(changelogSrc)) {
344
608
  fs.copyFileSync(changelogSrc, changelogDest);
345
609
  if (verifyFileInstalled(changelogDest, 'CHANGELOG.md')) {
@@ -350,7 +614,7 @@ function install(isGlobal) {
350
614
  }
351
615
 
352
616
  // Write VERSION file for whats-new command
353
- const versionDest = path.join(claudeDir, 'get-shit-done', 'VERSION');
617
+ const versionDest = path.join(targetDir, 'get-shit-done', 'VERSION');
354
618
  fs.writeFileSync(versionDest, pkg.version);
355
619
  if (verifyFileInstalled(versionDest, 'VERSION')) {
356
620
  console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
@@ -361,7 +625,7 @@ function install(isGlobal) {
361
625
  // Copy hooks from dist/ (bundled with dependencies)
362
626
  const hooksSrc = path.join(src, 'hooks', 'dist');
363
627
  if (fs.existsSync(hooksSrc)) {
364
- const hooksDest = path.join(claudeDir, 'hooks');
628
+ const hooksDest = path.join(targetDir, 'hooks');
365
629
  fs.mkdirSync(hooksDest, { recursive: true });
366
630
  const hookEntries = fs.readdirSync(hooksSrc);
367
631
  for (const entry of hookEntries) {
@@ -387,48 +651,57 @@ function install(isGlobal) {
387
651
  }
388
652
 
389
653
  // Configure statusline and hooks in settings.json
390
- const settingsPath = path.join(claudeDir, 'settings.json');
654
+ const settingsPath = path.join(targetDir, 'settings.json');
391
655
  const settings = cleanupOrphanedHooks(readSettings(settingsPath));
392
656
  const statuslineCommand = isGlobal
393
- ? 'node "$HOME/.claude/hooks/gsd-statusline.js"'
394
- : 'node .claude/hooks/gsd-statusline.js';
657
+ ? buildHookCommand(targetDir, 'gsd-statusline.js')
658
+ : 'node ' + dirName + '/hooks/gsd-statusline.js';
395
659
  const updateCheckCommand = isGlobal
396
- ? 'node "$HOME/.claude/hooks/gsd-check-update.js"'
397
- : 'node .claude/hooks/gsd-check-update.js';
660
+ ? buildHookCommand(targetDir, 'gsd-check-update.js')
661
+ : 'node ' + dirName + '/hooks/gsd-check-update.js';
398
662
 
399
- // Configure SessionStart hook for update checking
400
- if (!settings.hooks) {
401
- settings.hooks = {};
402
- }
403
- if (!settings.hooks.SessionStart) {
404
- settings.hooks.SessionStart = [];
405
- }
406
-
407
- // Check if GSD update hook already exists
408
- const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
409
- entry.hooks && entry.hooks.some(h => h.command && h.command.includes('gsd-check-update'))
410
- );
663
+ // Configure SessionStart hook for update checking (skip for opencode - different hook system)
664
+ if (!isOpencode) {
665
+ if (!settings.hooks) {
666
+ settings.hooks = {};
667
+ }
668
+ if (!settings.hooks.SessionStart) {
669
+ settings.hooks.SessionStart = [];
670
+ }
411
671
 
412
- if (!hasGsdUpdateHook) {
413
- settings.hooks.SessionStart.push({
414
- hooks: [
415
- {
416
- type: 'command',
417
- command: updateCheckCommand
418
- }
419
- ]
420
- });
421
- console.log(` ${green}✓${reset} Configured update check hook`);
672
+ // Check if GSD update hook already exists
673
+ const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
674
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes('gsd-check-update'))
675
+ );
676
+
677
+ if (!hasGsdUpdateHook) {
678
+ settings.hooks.SessionStart.push({
679
+ hooks: [
680
+ {
681
+ type: 'command',
682
+ command: updateCheckCommand
683
+ }
684
+ ]
685
+ });
686
+ console.log(` ${green}✓${reset} Configured update check hook`);
687
+ }
422
688
  }
423
689
 
424
- return { settingsPath, settings, statuslineCommand };
690
+ return { settingsPath, settings, statuslineCommand, runtime };
425
691
  }
426
692
 
427
693
  /**
428
694
  * Apply statusline config, then print completion message
695
+ * @param {string} settingsPath - Path to settings.json
696
+ * @param {object} settings - Settings object
697
+ * @param {string} statuslineCommand - Statusline command
698
+ * @param {boolean} shouldInstallStatusline - Whether to install statusline
699
+ * @param {string} runtime - Target runtime ('claude' or 'opencode')
429
700
  */
430
- function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline) {
431
- if (shouldInstallStatusline) {
701
+ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude') {
702
+ const isOpencode = runtime === 'opencode';
703
+
704
+ if (shouldInstallStatusline && !isOpencode) {
432
705
  settings.statusLine = {
433
706
  type: 'command',
434
707
  command: statuslineCommand
@@ -439,8 +712,15 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
439
712
  // Always write settings (hooks were already configured in install())
440
713
  writeSettings(settingsPath, settings);
441
714
 
715
+ // Configure OpenCode permissions if needed
716
+ if (isOpencode) {
717
+ configureOpencodePermissions();
718
+ }
719
+
720
+ const program = isOpencode ? 'OpenCode' : 'Claude Code';
721
+ const command = isOpencode ? '/gsd/help' : '/gsd:help';
442
722
  console.log(`
443
- ${green}Done!${reset} Launch Claude Code and run ${cyan}/gsd:help${reset}.
723
+ ${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
444
724
  `);
445
725
  }
446
726
 
@@ -500,18 +780,57 @@ function handleStatusline(settings, isInteractive, callback) {
500
780
  });
501
781
  }
502
782
 
783
+ /**
784
+ * Prompt for runtime selection (Claude Code / OpenCode / Both)
785
+ * @param {function} callback - Called with array of selected runtimes
786
+ */
787
+ function promptRuntime(callback) {
788
+ const rl = readline.createInterface({
789
+ input: process.stdin,
790
+ output: process.stdout
791
+ });
792
+
793
+ let answered = false;
794
+
795
+ rl.on('close', () => {
796
+ if (!answered) {
797
+ answered = true;
798
+ console.log(`\n ${yellow}Installation cancelled${reset}\n`);
799
+ process.exit(0);
800
+ }
801
+ });
802
+
803
+ console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}
804
+
805
+ ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
806
+ ${cyan}2${reset}) OpenCode ${dim}(~/.opencode)${reset} - open source, free models
807
+ ${cyan}3${reset}) Both
808
+ `);
809
+
810
+ rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
811
+ answered = true;
812
+ rl.close();
813
+ const choice = answer.trim() || '1';
814
+ if (choice === '3') {
815
+ callback(['claude', 'opencode']);
816
+ } else if (choice === '2') {
817
+ callback(['opencode']);
818
+ } else {
819
+ callback(['claude']);
820
+ }
821
+ });
822
+ }
823
+
503
824
  /**
504
825
  * Prompt for install location
826
+ * @param {string[]} runtimes - Array of runtimes to install for
505
827
  */
506
- function promptLocation() {
828
+ function promptLocation(runtimes) {
507
829
  // Check if stdin is a TTY - if not, fall back to global install
508
830
  // This handles npx execution in environments like WSL2 where stdin may not be properly connected
509
831
  if (!process.stdin.isTTY) {
510
832
  console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
511
- const { settingsPath, settings, statuslineCommand } = install(true);
512
- handleStatusline(settings, false, (shouldInstallStatusline) => {
513
- finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
514
- });
833
+ installAllRuntimes(runtimes, true, false);
515
834
  return;
516
835
  }
517
836
 
@@ -523,26 +842,29 @@ function promptLocation() {
523
842
  // Track whether we've processed the answer to prevent double-execution
524
843
  let answered = false;
525
844
 
526
- // Handle readline close event to detect premature stdin closure
845
+ // Handle readline close event (Ctrl+C, Escape, etc.) - cancel installation
527
846
  rl.on('close', () => {
528
847
  if (!answered) {
529
848
  answered = true;
530
- console.log(`\n ${yellow}Input stream closed, defaulting to global install${reset}\n`);
531
- const { settingsPath, settings, statuslineCommand } = install(true);
532
- handleStatusline(settings, false, (shouldInstallStatusline) => {
533
- finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
534
- });
849
+ console.log(`\n ${yellow}Installation cancelled${reset}\n`);
850
+ process.exit(0);
535
851
  }
536
852
  });
537
853
 
854
+ // Show paths for selected runtimes
538
855
  const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
539
- const globalPath = configDir || path.join(os.homedir(), '.claude');
540
- const globalLabel = globalPath.replace(os.homedir(), '~');
856
+ const pathExamples = runtimes.map(r => {
857
+ const dir = getDirName(r);
858
+ const globalPath = configDir || path.join(os.homedir(), dir);
859
+ return globalPath.replace(os.homedir(), '~');
860
+ }).join(', ');
861
+
862
+ const localExamples = runtimes.map(r => `./${getDirName(r)}`).join(', ');
541
863
 
542
864
  console.log(` ${yellow}Where would you like to install?${reset}
543
865
 
544
- ${cyan}1${reset}) Global ${dim}(${globalLabel})${reset} - available in all projects
545
- ${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
866
+ ${cyan}1${reset}) Global ${dim}(${pathExamples})${reset} - available in all projects
867
+ ${cyan}2${reset}) Local ${dim}(${localExamples})${reset} - this project only
546
868
  `);
547
869
 
548
870
  rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
@@ -550,14 +872,44 @@ function promptLocation() {
550
872
  rl.close();
551
873
  const choice = answer.trim() || '1';
552
874
  const isGlobal = choice !== '2';
553
- const { settingsPath, settings, statuslineCommand } = install(isGlobal);
554
- // Interactive mode - prompt for optional features
555
- handleStatusline(settings, true, (shouldInstallStatusline) => {
556
- finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
557
- });
875
+ installAllRuntimes(runtimes, isGlobal, true);
558
876
  });
559
877
  }
560
878
 
879
+ /**
880
+ * Install GSD for all selected runtimes
881
+ * @param {string[]} runtimes - Array of runtimes to install for
882
+ * @param {boolean} isGlobal - Whether to install globally
883
+ * @param {boolean} isInteractive - Whether running interactively
884
+ */
885
+ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
886
+ const results = [];
887
+
888
+ for (const runtime of runtimes) {
889
+ const result = install(isGlobal, runtime);
890
+ results.push(result);
891
+ }
892
+
893
+ // Handle statusline for Claude Code only (OpenCode uses themes)
894
+ const claudeResult = results.find(r => r.runtime === 'claude');
895
+
896
+ if (claudeResult) {
897
+ handleStatusline(claudeResult.settings, isInteractive, (shouldInstallStatusline) => {
898
+ finishInstall(claudeResult.settingsPath, claudeResult.settings, claudeResult.statuslineCommand, shouldInstallStatusline, 'claude');
899
+
900
+ // Finish OpenCode install if present
901
+ const opencodeResult = results.find(r => r.runtime === 'opencode');
902
+ if (opencodeResult) {
903
+ finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
904
+ }
905
+ });
906
+ } else {
907
+ // Only OpenCode
908
+ const opencodeResult = results[0];
909
+ finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
910
+ }
911
+ }
912
+
561
913
  // Main
562
914
  if (hasGlobal && hasLocal) {
563
915
  console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
@@ -565,18 +917,26 @@ if (hasGlobal && hasLocal) {
565
917
  } else if (explicitConfigDir && hasLocal) {
566
918
  console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
567
919
  process.exit(1);
568
- } else if (hasGlobal) {
569
- const { settingsPath, settings, statuslineCommand } = install(true);
570
- // Non-interactive - respect flags
571
- handleStatusline(settings, false, (shouldInstallStatusline) => {
572
- finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
573
- });
574
- } else if (hasLocal) {
575
- const { settingsPath, settings, statuslineCommand } = install(false);
576
- // Non-interactive - respect flags
577
- handleStatusline(settings, false, (shouldInstallStatusline) => {
578
- finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
579
- });
920
+ } else if (selectedRuntimes.length > 0) {
921
+ // Non-interactive: runtime specified via flags
922
+ if (!hasGlobal && !hasLocal) {
923
+ // Need location but runtime is specified - prompt for location only
924
+ promptLocation(selectedRuntimes);
925
+ } else {
926
+ // Both runtime and location specified via flags
927
+ installAllRuntimes(selectedRuntimes, hasGlobal, false);
928
+ }
929
+ } else if (hasGlobal || hasLocal) {
930
+ // Location specified but no runtime - default to Claude Code
931
+ installAllRuntimes(['claude'], hasGlobal, false);
580
932
  } else {
581
- promptLocation();
933
+ // Fully interactive: prompt for runtime, then location
934
+ if (!process.stdin.isTTY) {
935
+ console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
936
+ installAllRuntimes(['claude'], true, false);
937
+ } else {
938
+ promptRuntime((runtimes) => {
939
+ promptLocation(runtimes);
940
+ });
941
+ }
582
942
  }
@@ -439,7 +439,8 @@ Display spawning indicator:
439
439
  Spawn 4 parallel gsd-project-researcher agents with rich context:
440
440
 
441
441
  ```
442
- Task(prompt="
442
+ Task(prompt="First, read ~/.claude/agents/gsd-project-researcher.md for your role and instructions.
443
+
443
444
  <research_type>
444
445
  Project Research — Stack dimension for [domain].
445
446
  </research_type>
@@ -476,9 +477,10 @@ Your STACK.md feeds into roadmap creation. Be prescriptive:
476
477
  Write to: .planning/research/STACK.md
477
478
  Use template: ~/.claude/get-shit-done/templates/research-project/STACK.md
478
479
  </output>
479
- ", subagent_type="gsd-project-researcher", model="{researcher_model}", description="Stack research")
480
+ ", subagent_type="general-purpose", model="{researcher_model}", description="Stack research")
481
+
482
+ Task(prompt="First, read ~/.claude/agents/gsd-project-researcher.md for your role and instructions.
480
483
 
481
- Task(prompt="
482
484
  <research_type>
483
485
  Project Research — Features dimension for [domain].
484
486
  </research_type>
@@ -515,9 +517,10 @@ Your FEATURES.md feeds into requirements definition. Categorize clearly:
515
517
  Write to: .planning/research/FEATURES.md
516
518
  Use template: ~/.claude/get-shit-done/templates/research-project/FEATURES.md
517
519
  </output>
518
- ", subagent_type="gsd-project-researcher", model="{researcher_model}", description="Features research")
520
+ ", subagent_type="general-purpose", model="{researcher_model}", description="Features research")
521
+
522
+ Task(prompt="First, read ~/.claude/agents/gsd-project-researcher.md for your role and instructions.
519
523
 
520
- Task(prompt="
521
524
  <research_type>
522
525
  Project Research — Architecture dimension for [domain].
523
526
  </research_type>
@@ -554,9 +557,10 @@ Your ARCHITECTURE.md informs phase structure in roadmap. Include:
554
557
  Write to: .planning/research/ARCHITECTURE.md
555
558
  Use template: ~/.claude/get-shit-done/templates/research-project/ARCHITECTURE.md
556
559
  </output>
557
- ", subagent_type="gsd-project-researcher", model="{researcher_model}", description="Architecture research")
560
+ ", subagent_type="general-purpose", model="{researcher_model}", description="Architecture research")
561
+
562
+ Task(prompt="First, read ~/.claude/agents/gsd-project-researcher.md for your role and instructions.
558
563
 
559
- Task(prompt="
560
564
  <research_type>
561
565
  Project Research — Pitfalls dimension for [domain].
562
566
  </research_type>
@@ -593,7 +597,7 @@ Your PITFALLS.md prevents mistakes in roadmap/planning. For each pitfall:
593
597
  Write to: .planning/research/PITFALLS.md
594
598
  Use template: ~/.claude/get-shit-done/templates/research-project/PITFALLS.md
595
599
  </output>
596
- ", subagent_type="gsd-project-researcher", model="{researcher_model}", description="Pitfalls research")
600
+ ", subagent_type="general-purpose", model="{researcher_model}", description="Pitfalls research")
597
601
  ```
598
602
 
599
603
  After all 4 agents complete, spawn synthesizer to create SUMMARY.md:
@@ -949,14 +953,14 @@ Present completion with next steps:
949
953
 
950
954
  **Phase 1: [Phase Name]** — [Goal from ROADMAP.md]
951
955
 
952
- `/gsd:discuss-phase 1` — gather context and clarify approach
956
+ /gsd:discuss-phase 1 — gather context and clarify approach
953
957
 
954
- <sub>`/clear` first → fresh context window</sub>
958
+ <sub>/clear first → fresh context window</sub>
955
959
 
956
960
  ---
957
961
 
958
962
  **Also available:**
959
- - `/gsd:plan-phase 1` — skip discussion, plan directly
963
+ - /gsd:plan-phase 1 — skip discussion, plan directly
960
964
 
961
965
  ───────────────────────────────────────────────────────────────
962
966
  ```
@@ -206,8 +206,8 @@ Write research findings to: {phase_dir}/{phase}-RESEARCH.md
206
206
 
207
207
  ```
208
208
  Task(
209
- prompt=research_prompt,
210
- subagent_type="gsd-phase-researcher",
209
+ prompt="First, read ~/.claude/agents/gsd-phase-researcher.md for your role and instructions.\n\n" + research_prompt,
210
+ subagent_type="general-purpose",
211
211
  model="{researcher_model}",
212
212
  description="Research Phase {phase}"
213
213
  )
@@ -315,8 +315,8 @@ Before returning PLANNING COMPLETE:
315
315
 
316
316
  ```
317
317
  Task(
318
- prompt=filled_prompt,
319
- subagent_type="gsd-planner",
318
+ prompt="First, read ~/.claude/agents/gsd-planner.md for your role and instructions.\n\n" + filled_prompt,
319
+ subagent_type="general-purpose",
320
320
  model="{planner_model}",
321
321
  description="Plan Phase {phase}"
322
322
  )
@@ -445,8 +445,8 @@ Return what changed.
445
445
 
446
446
  ```
447
447
  Task(
448
- prompt=revision_prompt,
449
- subagent_type="gsd-planner",
448
+ prompt="First, read ~/.claude/agents/gsd-planner.md for your role and instructions.\n\n" + revision_prompt,
449
+ subagent_type="general-purpose",
450
450
  model="{planner_model}",
451
451
  description="Revise Phase {phase} plans"
452
452
  )
@@ -148,8 +148,8 @@ Write to: .planning/phases/${PHASE}-{slug}/${PHASE}-RESEARCH.md
148
148
 
149
149
  ```
150
150
  Task(
151
- prompt=filled_prompt,
152
- subagent_type="gsd-phase-researcher",
151
+ prompt="First, read ~/.claude/agents/gsd-phase-researcher.md for your role and instructions.\n\n" + filled_prompt,
152
+ subagent_type="general-purpose",
153
153
  model="{researcher_model}",
154
154
  description="Research Phase {phase}"
155
155
  )
@@ -182,8 +182,8 @@ Research file: @.planning/phases/${PHASE}-{slug}/${PHASE}-RESEARCH.md
182
182
 
183
183
  ```
184
184
  Task(
185
- prompt=continuation_prompt,
186
- subagent_type="gsd-phase-researcher",
185
+ prompt="First, read ~/.claude/agents/gsd-phase-researcher.md for your role and instructions.\n\n" + continuation_prompt,
186
+ subagent_type="general-purpose",
187
187
  model="{researcher_model}",
188
188
  description="Continue research Phase {phase}"
189
189
  )
@@ -275,14 +275,6 @@ The output should answer: "What does the researcher need to investigate? What ch
275
275
  - "Fast and responsive"
276
276
  - "Easy to use"
277
277
 
278
- **Sections explained:**
279
-
280
- - **Domain** — The scope anchor. Copied/derived from ROADMAP.md. Fixed boundary.
281
- - **Decisions** — Organized by areas discussed (NOT predefined categories). Section headers come from the actual discussion — "Layout style", "Flag design", "Grouping criteria", etc.
282
- - **Claude's Discretion** — Explicit acknowledgment of what Claude can decide during implementation.
283
- - **Specifics** — Product references, examples, "like X but..." statements.
284
- - **Deferred** — Ideas captured but explicitly out of scope. Prevents scope creep while preserving good ideas.
285
-
286
278
  **After creation:**
287
279
  - File lives in phase directory: `.planning/phases/XX-name/{phase}-CONTEXT.md`
288
280
  - `gsd-phase-researcher` uses decisions to focus investigation
@@ -174,33 +174,3 @@ It's a DIGEST, not an archive. If accumulated context grows too large:
174
174
  The goal is "read once, know where we are" — if it's too long, that fails.
175
175
 
176
176
  </size_constraint>
177
-
178
- <guidelines>
179
-
180
- **When created:**
181
- - During project initialization (after ROADMAP.md)
182
- - Reference PROJECT.md (extract core value and current focus)
183
- - Initialize empty sections
184
-
185
- **When read:**
186
- - Every workflow starts by reading STATE.md
187
- - Then read PROJECT.md for full context
188
- - Provides instant context restoration
189
-
190
- **When updated:**
191
- - After each plan execution (update position, note decisions, update issues/blockers)
192
- - After phase transitions (update progress bar, clear resolved blockers, refresh project reference)
193
-
194
- **Size management:**
195
- - Keep under 100 lines total
196
- - Recent decisions only in STATE.md (full log in PROJECT.md)
197
- - Keep only active blockers
198
-
199
- **Sections:**
200
- - Project Reference: Pointer to PROJECT.md with core value
201
- - Current Position: Where we are now (phase, plan, status)
202
- - Performance Metrics: Velocity tracking
203
- - Accumulated Context: Recent decisions, pending todos, blockers
204
- - Session Continuity: Resume information
205
-
206
- </guidelines>
@@ -233,37 +233,14 @@ The one-liner should tell someone what actually shipped.
233
233
  </example>
234
234
 
235
235
  <guidelines>
236
- **When to create:**
237
- - After completing each phase plan
238
- - Required output from execute-plan workflow
239
- - Documents what actually happened vs what was planned
240
-
241
- **Frontmatter completion:**
242
- - MANDATORY: Complete all frontmatter fields during summary creation
243
- - See <frontmatter_guidance> for field purposes
244
- - Frontmatter enables automatic context assembly for future planning
245
-
246
- **One-liner requirements:**
247
- - Must be substantive (describe what shipped, not "phase complete")
248
- - Should tell someone what was accomplished
249
- - Examples: "JWT auth with refresh rotation using jose library" not "Authentication implemented"
250
-
251
- **Performance tracking:**
252
- - Include duration, start/end timestamps
253
- - Used for velocity metrics in STATE.md
254
-
255
- **Deviations section:**
256
- - Documents unplanned work handled via deviation rules
257
- - Separate from "Issues Encountered" (which is planned work problems)
258
- - Auto-fixed issues: What was wrong, how fixed, verification
236
+ **Frontmatter:** MANDATORY - complete all fields. Enables automatic context assembly for future planning.
237
+
238
+ **One-liner:** Must be substantive. "JWT auth with refresh rotation using jose library" not "Authentication implemented".
259
239
 
260
240
  **Decisions section:**
261
- - Key decisions made during execution
262
- - Include rationale (why this choice)
241
+ - Key decisions made during execution with rationale
263
242
  - Extracted to STATE.md accumulated context
264
243
  - Use "None - followed plan as specified" if no deviations
265
244
 
266
- **After creation:**
267
- - STATE.md updated with position, decisions, issues
268
- - Next plan can reference decisions made
245
+ **After creation:** STATE.md updated with position, decisions, issues.
269
246
  </guidelines>
@@ -304,20 +304,8 @@ curl -X POST http://localhost:3000/api/test-email \
304
304
 
305
305
  ## Guidelines
306
306
 
307
- **Include in USER-SETUP.md:**
308
- - Environment variable names and where to find values
309
- - Account creation URLs (if new service)
310
- - Dashboard configuration steps
311
- - Verification commands to confirm setup works
312
- - Local development alternatives (e.g., `stripe listen`)
313
-
314
- **Do NOT include:**
315
- - Actual secret values (never)
316
- - Steps Claude can automate (package installs, code changes, file creation)
317
- - Generic instructions ("set up your environment")
307
+ **Never include:** Actual secret values. Steps Claude can automate (package installs, code changes).
318
308
 
319
309
  **Naming:** `{phase}-USER-SETUP.md` matches the phase number pattern.
320
-
321
310
  **Status tracking:** User marks checkboxes and updates status line when complete.
322
-
323
311
  **Searchability:** `grep -r "USER-SETUP" .planning/` finds all phases with user requirements.
@@ -29,12 +29,7 @@ When a milestone completes, this workflow:
29
29
  5. Performs full PROJECT.md evolution review
30
30
  6. Offers to create next milestone inline
31
31
 
32
- **Context Efficiency:**
33
-
34
- - Completed milestones: One line each (~50 tokens)
35
- - Full details: In archive files (loaded only when needed)
36
- - Result: ROADMAP.md stays constant size forever
37
- - Result: REQUIREMENTS.md is always milestone-scoped (not cumulative)
32
+ **Context Efficiency:** Archives keep ROADMAP.md constant-size and REQUIREMENTS.md milestone-scoped.
38
33
 
39
34
  **Archive Format:**
40
35
 
@@ -201,21 +201,8 @@ Do NOT offer manual next steps - verify-work handles the rest.
201
201
  </process>
202
202
 
203
203
  <context_efficiency>
204
- **Orchestrator context:** ~15%
205
- - Parse UAT.md gaps
206
- - Fill template strings
207
- - Spawn parallel Task calls
208
- - Collect results
209
- - Update UAT.md
210
-
211
- **Each debug agent:** Fresh 200k context
212
- - Loads full debug workflow
213
- - Loads debugging references
214
- - Investigates with full capacity
215
- - Returns root cause
216
-
217
- **No symptom gathering.** Agents start with symptoms pre-filled from UAT.
218
- **No fix application.** Agents only diagnose - plan-phase --gaps handles fixes.
204
+ Agents start with symptoms pre-filled from UAT (no symptom gathering).
205
+ Agents only diagnose—plan-phase --gaps handles fixes (no fix application).
219
206
  </context_efficiency>
220
207
 
221
208
  <failure_handling>
@@ -550,24 +550,9 @@ All {N} phases executed.
550
550
  </process>
551
551
 
552
552
  <context_efficiency>
553
- **Why this works:**
554
-
555
- Orchestrator context usage: ~10-15%
556
- - Read plan frontmatter (small)
557
- - Analyze dependencies (logic, no heavy reads)
558
- - Fill template strings
559
- - Spawn Task calls
560
- - Collect results
561
-
562
- Each subagent: Fresh 200k context
563
- - Loads full execute-plan workflow
564
- - Loads templates, references
565
- - Executes plan with full capacity
566
- - Creates SUMMARY, commits
567
-
568
- **No polling.** Task tool blocks until completion. No TaskOutput loops.
569
-
570
- **No context bleed.** Orchestrator never reads workflow internals. Just paths and results.
553
+ Orchestrator: ~10-15% context (frontmatter, spawning, results).
554
+ Subagents: Fresh 200k each (full workflow + execution).
555
+ No polling (Task blocks). No context bleed.
571
556
  </context_efficiency>
572
557
 
573
558
  <failure_handling>
@@ -216,19 +216,7 @@ Tasks 2-5: Main context (need decision from checkpoint 1)
216
216
  No segmentation benefit - execute entirely in main
217
217
  ```
218
218
 
219
- **4. Why this works:**
220
-
221
- **Segmentation benefits:**
222
-
223
- - Fresh context for each autonomous segment (0% start every time)
224
- - Main context only for checkpoints (~10-20% total)
225
- - Can handle 10+ task plans if properly segmented
226
- - Quality impossible to degrade in autonomous segments
227
-
228
- **When segmentation provides no benefit:**
229
-
230
- - Checkpoint is decision/human-action and following tasks depend on outcome
231
- - Better to execute sequentially in main than break flow
219
+ **4. Why segment:** Fresh context per subagent preserves peak quality. Main context stays lean (~15% usage).
232
220
 
233
221
  **5. Implementation:**
234
222
 
@@ -533,18 +521,7 @@ Committing...
533
521
 
534
522
  ````
535
523
 
536
- **Benefits of this pattern:**
537
- - Main context usage: ~20% (just orchestration + checkpoints)
538
- - Subagent 1: Fresh 0-30% (tasks 1-3)
539
- - Subagent 2: Fresh 0-30% (tasks 5-6)
540
- - Subagent 3: Fresh 0-20% (task 8)
541
- - All autonomous work: Peak quality
542
- - Can handle large plans with many tasks if properly segmented
543
-
544
- **When NOT to use segmentation:**
545
- - Plan has decision/human-action checkpoints that affect following tasks
546
- - Following tasks depend on checkpoint outcome
547
- - Better to execute in main sequentially in those cases
524
+ **Benefit:** Each subagent starts fresh (~20-30% context), enabling larger plans without quality degradation.
548
525
  </step>
549
526
 
550
527
  <step name="load_prompt">
@@ -1068,13 +1045,6 @@ Store in array or list for SUMMARY generation:
1068
1045
  TASK_COMMITS+=("Task ${TASK_NUM}: ${TASK_COMMIT}")
1069
1046
  ```
1070
1047
 
1071
- **Atomic commit benefits:**
1072
- - Each task independently revertable
1073
- - Git bisect finds exact failing task
1074
- - Git blame traces line to specific task context
1075
- - Clear history for Claude in future sessions
1076
- - Better observability for AI-automated workflow
1077
-
1078
1048
  </task_commit>
1079
1049
 
1080
1050
  <step name="checkpoint_protocol">
@@ -132,7 +132,7 @@ Wait for user response.
132
132
  Acknowledge the corrections:
133
133
 
134
134
  ```
135
- Got it. Key corrections:
135
+ Key corrections:
136
136
  - [correction 1]
137
137
  - [correction 2]
138
138
 
@@ -142,7 +142,7 @@ This changes my understanding significantly. [Summarize new understanding]
142
142
  **If user confirms assumptions:**
143
143
 
144
144
  ```
145
- Great, assumptions validated.
145
+ Assumptions validated.
146
146
  ```
147
147
 
148
148
  Continue to offer_next.
@@ -7,10 +7,7 @@ Use this workflow when:
7
7
  </trigger>
8
8
 
9
9
  <purpose>
10
- Instantly restore full project context and present clear status.
11
- Enables seamless session continuity for fully autonomous workflows.
12
-
13
- "Where were we?" should have an immediate, complete answer.
10
+ Instantly restore full project context so "Where were we?" has an immediate, complete answer.
14
11
  </purpose>
15
12
 
16
13
  <required_reading>
@@ -290,17 +287,12 @@ This handles cases where:
290
287
  </reconstruction>
291
288
 
292
289
  <quick_resume>
293
- For users who want minimal friction:
294
-
295
- If user says just "continue" or "go":
296
-
290
+ If user says "continue" or "go":
297
291
  - Load state silently
298
292
  - Determine primary action
299
293
  - Execute immediately without presenting options
300
294
 
301
295
  "Continuing from [state]... [action]"
302
-
303
- This enables fully autonomous "just keep going" workflow.
304
296
  </quick_resume>
305
297
 
306
298
  <success_criteria>
@@ -514,15 +514,7 @@ Exit skill and invoke SlashCommand("/gsd:complete-milestone {version}")
514
514
  </process>
515
515
 
516
516
  <implicit_tracking>
517
-
518
- Progress tracking is IMPLICIT:
519
-
520
- - "Plan phase 2" → Phase 1 must be done (or ask)
521
- - "Plan phase 3" → Phases 1-2 must be done (or ask)
522
- - Transition workflow makes it explicit in ROADMAP.md
523
-
524
- No separate "update progress" step. Forward motion IS progress.
525
-
517
+ Progress tracking is IMPLICIT: planning phase N implies phases 1-(N-1) complete. No separate progress step—forward motion IS progress.
526
518
  </implicit_tracking>
527
519
 
528
520
  <partial_completion>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-shit-done-cc",
3
- "version": "1.9.4",
3
+ "version": "1.9.6",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.",
5
5
  "bin": {
6
6
  "get-shit-done-cc": "bin/install.js"