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 +26 -5
- package/bin/install.js +467 -107
- package/commands/gsd/new-project.md +15 -11
- package/commands/gsd/plan-phase.md +6 -6
- package/commands/gsd/research-phase.md +4 -4
- package/get-shit-done/templates/context.md +0 -8
- package/get-shit-done/templates/state.md +0 -30
- package/get-shit-done/templates/summary.md +5 -28
- package/get-shit-done/templates/user-setup.md +1 -13
- package/get-shit-done/workflows/complete-milestone.md +1 -6
- package/get-shit-done/workflows/diagnose-issues.md +2 -15
- package/get-shit-done/workflows/execute-phase.md +3 -18
- package/get-shit-done/workflows/execute-plan.md +2 -32
- package/get-shit-done/workflows/list-phase-assumptions.md +2 -2
- package/get-shit-done/workflows/resume-project.md +2 -10
- package/get-shit-done/workflows/transition.md +1 -9
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
99
|
-
npx get-shit-done-cc --
|
|
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
|
|
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
|
|
72
|
-
${cyan}-l, --local${reset} Install locally (to
|
|
73
|
-
${cyan}
|
|
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}#
|
|
79
|
-
npx get-shit-done-cc
|
|
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
|
|
82
|
-
npx get-shit-done-cc --
|
|
108
|
+
${dim}# Install for both runtimes globally${reset}
|
|
109
|
+
npx get-shit-done-cc --both --global
|
|
83
110
|
|
|
84
|
-
${dim}#
|
|
85
|
-
|
|
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
|
-
*
|
|
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
|
|
344
|
+
// Replace ~/.claude/ with the appropriate prefix in Markdown files
|
|
150
345
|
let content = fs.readFileSync(srcPath, 'utf8');
|
|
151
|
-
|
|
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
|
-
|
|
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(),
|
|
262
|
-
const
|
|
519
|
+
const defaultGlobalDir = configDir || path.join(os.homedir(), dirName);
|
|
520
|
+
const targetDir = isGlobal
|
|
263
521
|
? defaultGlobalDir
|
|
264
|
-
: path.join(process.cwd(),
|
|
522
|
+
: path.join(process.cwd(), dirName);
|
|
265
523
|
|
|
266
524
|
const locationLabel = isGlobal
|
|
267
|
-
?
|
|
268
|
-
:
|
|
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 ? `${
|
|
274
|
-
:
|
|
531
|
+
? (configDir ? `${targetDir}/` : `~/${dirName}/`)
|
|
532
|
+
: `./${dirName}/`;
|
|
275
533
|
|
|
276
|
-
|
|
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(
|
|
541
|
+
cleanupOrphanedFiles(targetDir);
|
|
283
542
|
|
|
284
543
|
// Create commands directory
|
|
285
|
-
const commandsDir = path.join(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
654
|
+
const settingsPath = path.join(targetDir, 'settings.json');
|
|
391
655
|
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
|
392
656
|
const statuslineCommand = isGlobal
|
|
393
|
-
? '
|
|
394
|
-
: 'node
|
|
657
|
+
? buildHookCommand(targetDir, 'gsd-statusline.js')
|
|
658
|
+
: 'node ' + dirName + '/hooks/gsd-statusline.js';
|
|
395
659
|
const updateCheckCommand = isGlobal
|
|
396
|
-
? '
|
|
397
|
-
: 'node
|
|
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 (!
|
|
401
|
-
settings.hooks
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
413
|
-
settings.hooks.SessionStart.
|
|
414
|
-
hooks
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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}
|
|
531
|
-
|
|
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
|
|
540
|
-
|
|
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}(${
|
|
545
|
-
${cyan}2${reset}) Local ${dim}(
|
|
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
|
-
|
|
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 (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
|
|
956
|
+
/gsd:discuss-phase 1 — gather context and clarify approach
|
|
953
957
|
|
|
954
|
-
<sub
|
|
958
|
+
<sub>/clear first → fresh context window</sub>
|
|
955
959
|
|
|
956
960
|
---
|
|
957
961
|
|
|
958
962
|
**Also available:**
|
|
959
|
-
-
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
**
|
|
237
|
-
|
|
238
|
-
-
|
|
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
|
-
**
|
|
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
|
-
|
|
205
|
-
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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