get-shit-done-cc 1.6.2 → 1.6.4

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.
@@ -1296,7 +1296,7 @@ Execute: `/gsd:execute-phase {phase}`
1296
1296
 
1297
1297
  ### Next Steps
1298
1298
 
1299
- Execute: `/gsd:execute-phase {phase}`
1299
+ Execute: `/gsd:execute-phase {phase} --gaps-only`
1300
1300
  ```
1301
1301
 
1302
1302
  ## Revision Complete
package/bin/install.js CHANGED
@@ -151,6 +151,96 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
151
151
  }
152
152
  }
153
153
 
154
+ /**
155
+ * Clean up orphaned files from previous GSD versions
156
+ */
157
+ function cleanupOrphanedFiles(claudeDir) {
158
+ const orphanedFiles = [
159
+ 'hooks/gsd-notify.sh', // Removed in v1.6.x
160
+ ];
161
+
162
+ for (const relPath of orphanedFiles) {
163
+ const fullPath = path.join(claudeDir, relPath);
164
+ if (fs.existsSync(fullPath)) {
165
+ fs.unlinkSync(fullPath);
166
+ console.log(` ${green}✓${reset} Removed orphaned ${relPath}`);
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Clean up orphaned hook registrations from settings.json
173
+ */
174
+ function cleanupOrphanedHooks(settings) {
175
+ const orphanedHookPatterns = [
176
+ 'gsd-notify.sh', // Removed in v1.6.x
177
+ ];
178
+
179
+ let cleaned = false;
180
+
181
+ // Check all hook event types (Stop, SessionStart, etc.)
182
+ if (settings.hooks) {
183
+ for (const eventType of Object.keys(settings.hooks)) {
184
+ const hookEntries = settings.hooks[eventType];
185
+ if (Array.isArray(hookEntries)) {
186
+ // Filter out entries that contain orphaned hooks
187
+ const filtered = hookEntries.filter(entry => {
188
+ if (entry.hooks && Array.isArray(entry.hooks)) {
189
+ // Check if any hook in this entry matches orphaned patterns
190
+ const hasOrphaned = entry.hooks.some(h =>
191
+ h.command && orphanedHookPatterns.some(pattern => h.command.includes(pattern))
192
+ );
193
+ if (hasOrphaned) {
194
+ cleaned = true;
195
+ return false; // Remove this entry
196
+ }
197
+ }
198
+ return true; // Keep this entry
199
+ });
200
+ settings.hooks[eventType] = filtered;
201
+ }
202
+ }
203
+ }
204
+
205
+ if (cleaned) {
206
+ console.log(` ${green}✓${reset} Removed orphaned hook registrations`);
207
+ }
208
+
209
+ return settings;
210
+ }
211
+
212
+ /**
213
+ * Verify a directory exists and contains files
214
+ */
215
+ function verifyInstalled(dirPath, description) {
216
+ if (!fs.existsSync(dirPath)) {
217
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: directory not created`);
218
+ return false;
219
+ }
220
+ try {
221
+ const entries = fs.readdirSync(dirPath);
222
+ if (entries.length === 0) {
223
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: directory is empty`);
224
+ return false;
225
+ }
226
+ } catch (e) {
227
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: ${e.message}`);
228
+ return false;
229
+ }
230
+ return true;
231
+ }
232
+
233
+ /**
234
+ * Verify a file exists
235
+ */
236
+ function verifyFileInstalled(filePath, description) {
237
+ if (!fs.existsSync(filePath)) {
238
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: file not created`);
239
+ return false;
240
+ }
241
+ return true;
242
+ }
243
+
154
244
  /**
155
245
  * Install to the specified directory
156
246
  */
@@ -175,6 +265,12 @@ function install(isGlobal) {
175
265
 
176
266
  console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
177
267
 
268
+ // Track installation failures
269
+ const failures = [];
270
+
271
+ // Clean up orphaned files from previous versions
272
+ cleanupOrphanedFiles(claudeDir);
273
+
178
274
  // Create commands directory
179
275
  const commandsDir = path.join(claudeDir, 'commands');
180
276
  fs.mkdirSync(commandsDir, { recursive: true });
@@ -183,13 +279,21 @@ function install(isGlobal) {
183
279
  const gsdSrc = path.join(src, 'commands', 'gsd');
184
280
  const gsdDest = path.join(commandsDir, 'gsd');
185
281
  copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix);
186
- console.log(` ${green}✓${reset} Installed commands/gsd`);
282
+ if (verifyInstalled(gsdDest, 'commands/gsd')) {
283
+ console.log(` ${green}✓${reset} Installed commands/gsd`);
284
+ } else {
285
+ failures.push('commands/gsd');
286
+ }
187
287
 
188
288
  // Copy get-shit-done skill with path replacement
189
289
  const skillSrc = path.join(src, 'get-shit-done');
190
290
  const skillDest = path.join(claudeDir, 'get-shit-done');
191
291
  copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
192
- console.log(` ${green}✓${reset} Installed get-shit-done`);
292
+ if (verifyInstalled(skillDest, 'get-shit-done')) {
293
+ console.log(` ${green}✓${reset} Installed get-shit-done`);
294
+ } else {
295
+ failures.push('get-shit-done');
296
+ }
193
297
 
194
298
  // Copy agents to ~/.claude/agents (subagents must be at root level)
195
299
  // Only delete gsd-*.md files to preserve user's custom agents
@@ -216,7 +320,11 @@ function install(isGlobal) {
216
320
  fs.writeFileSync(path.join(agentsDest, entry.name), content);
217
321
  }
218
322
  }
219
- console.log(` ${green}✓${reset} Installed agents`);
323
+ if (verifyInstalled(agentsDest, 'agents')) {
324
+ console.log(` ${green}✓${reset} Installed agents`);
325
+ } else {
326
+ failures.push('agents');
327
+ }
220
328
  }
221
329
 
222
330
  // Copy CHANGELOG.md
@@ -224,13 +332,21 @@ function install(isGlobal) {
224
332
  const changelogDest = path.join(claudeDir, 'get-shit-done', 'CHANGELOG.md');
225
333
  if (fs.existsSync(changelogSrc)) {
226
334
  fs.copyFileSync(changelogSrc, changelogDest);
227
- console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
335
+ if (verifyFileInstalled(changelogDest, 'CHANGELOG.md')) {
336
+ console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
337
+ } else {
338
+ failures.push('CHANGELOG.md');
339
+ }
228
340
  }
229
341
 
230
342
  // Write VERSION file for whats-new command
231
343
  const versionDest = path.join(claudeDir, 'get-shit-done', 'VERSION');
232
344
  fs.writeFileSync(versionDest, pkg.version);
233
- console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
345
+ if (verifyFileInstalled(versionDest, 'VERSION')) {
346
+ console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
347
+ } else {
348
+ failures.push('VERSION');
349
+ }
234
350
 
235
351
  // Copy hooks
236
352
  const hooksSrc = path.join(src, 'hooks');
@@ -243,12 +359,23 @@ function install(isGlobal) {
243
359
  const destFile = path.join(hooksDest, entry);
244
360
  fs.copyFileSync(srcFile, destFile);
245
361
  }
246
- console.log(` ${green}✓${reset} Installed hooks`);
362
+ if (verifyInstalled(hooksDest, 'hooks')) {
363
+ console.log(` ${green}✓${reset} Installed hooks`);
364
+ } else {
365
+ failures.push('hooks');
366
+ }
367
+ }
368
+
369
+ // If critical components failed, exit with error
370
+ if (failures.length > 0) {
371
+ console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
372
+ console.error(` Try running directly: node ~/.npm/_npx/*/node_modules/get-shit-done-cc/bin/install.js --global\n`);
373
+ process.exit(1);
247
374
  }
248
375
 
249
376
  // Configure statusline and hooks in settings.json
250
377
  const settingsPath = path.join(claudeDir, 'settings.json');
251
- const settings = readSettings(settingsPath);
378
+ const settings = cleanupOrphanedHooks(readSettings(settingsPath));
252
379
  const statuslineCommand = isGlobal
253
380
  ? 'node "$HOME/.claude/hooks/statusline.js"'
254
381
  : 'node .claude/hooks/statusline.js';
@@ -364,11 +491,37 @@ function handleStatusline(settings, isInteractive, callback) {
364
491
  * Prompt for install location
365
492
  */
366
493
  function promptLocation() {
494
+ // Check if stdin is a TTY - if not, fall back to global install
495
+ // This handles npx execution in environments like WSL2 where stdin may not be properly connected
496
+ if (!process.stdin.isTTY) {
497
+ console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
498
+ const { settingsPath, settings, statuslineCommand } = install(true);
499
+ handleStatusline(settings, false, (shouldInstallStatusline) => {
500
+ finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
501
+ });
502
+ return;
503
+ }
504
+
367
505
  const rl = readline.createInterface({
368
506
  input: process.stdin,
369
507
  output: process.stdout
370
508
  });
371
509
 
510
+ // Track whether we've processed the answer to prevent double-execution
511
+ let answered = false;
512
+
513
+ // Handle readline close event to detect premature stdin closure
514
+ rl.on('close', () => {
515
+ if (!answered) {
516
+ answered = true;
517
+ console.log(`\n ${yellow}Input stream closed, defaulting to global install${reset}\n`);
518
+ const { settingsPath, settings, statuslineCommand } = install(true);
519
+ handleStatusline(settings, false, (shouldInstallStatusline) => {
520
+ finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
521
+ });
522
+ }
523
+ });
524
+
372
525
  const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
373
526
  const globalPath = configDir || path.join(os.homedir(), '.claude');
374
527
  const globalLabel = globalPath.replace(os.homedir(), '~');
@@ -380,6 +533,7 @@ function promptLocation() {
380
533
  `);
381
534
 
382
535
  rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
536
+ answered = true;
383
537
  rl.close();
384
538
  const choice = answer.trim() || '1';
385
539
  const isGlobal = choice !== '2';
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: gsd:execute-phase
3
3
  description: Execute all plans in a phase with wave-based parallelization
4
- argument-hint: "<phase-number>"
4
+ argument-hint: "<phase-number> [--gaps-only]"
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Write
@@ -30,6 +30,9 @@ Context budget: ~15% orchestrator, 100% fresh per subagent.
30
30
  <context>
31
31
  Phase: $ARGUMENTS
32
32
 
33
+ **Flags:**
34
+ - `--gaps-only` — Execute only gap closure plans (plans with `gap_closure: true` in frontmatter). Use after verify-work creates fix plans.
35
+
33
36
  @.planning/ROADMAP.md
34
37
  @.planning/STATE.md
35
38
  </context>
@@ -43,6 +46,7 @@ Phase: $ARGUMENTS
43
46
  2. **Discover plans**
44
47
  - List all *-PLAN.md files in phase directory
45
48
  - Check which have *-SUMMARY.md (already complete)
49
+ - If `--gaps-only`: filter to only plans with `gap_closure: true`
46
50
  - Build list of incomplete plans
47
51
 
48
52
  3. **Group by wave**
@@ -156,7 +156,7 @@ Fix plans verified ✓
156
156
 
157
157
  **Execute fix plans** — run diagnosed fixes
158
158
 
159
- /gsd:execute-phase {Z}
159
+ /gsd:execute-phase {Z} --gaps-only
160
160
 
161
161
  <sub>/clear first → fresh context window</sub>
162
162
 
@@ -71,15 +71,21 @@ ls -1 "$PHASE_DIR"/*-SUMMARY.md 2>/dev/null | sort
71
71
  For each plan, read frontmatter to extract:
72
72
  - `wave: N` - Execution wave (pre-computed)
73
73
  - `autonomous: true/false` - Whether plan has checkpoints
74
+ - `gap_closure: true/false` - Whether plan closes gaps from verification/UAT
74
75
 
75
76
  Build plan inventory:
76
77
  - Plan path
77
78
  - Plan ID (e.g., "03-01")
78
79
  - Wave number
79
80
  - Autonomous flag
81
+ - Gap closure flag
80
82
  - Completion status (SUMMARY exists = complete)
81
83
 
82
- Skip completed plans. If all complete, report "Phase already executed" and exit.
84
+ **Filtering:**
85
+ - Skip completed plans (have SUMMARY.md)
86
+ - If `--gaps-only` flag: also skip plans where `gap_closure` is not `true`
87
+
88
+ If all plans filtered out, report "No matching incomplete plans" and exit.
83
89
  </step>
84
90
 
85
91
  <step name="group_by_wave">
@@ -433,9 +439,9 @@ Present gaps and offer next command:
433
439
 
434
440
  User runs `/gsd:plan-phase {X} --gaps` which:
435
441
  1. Reads VERIFICATION.md gaps
436
- 2. Creates additional plans (04, 05, etc.) to close gaps
437
- 3. User then runs `/gsd:execute-phase {X}` again
438
- 4. Execute-phase runs incomplete plans (04-05)
442
+ 2. Creates additional plans (04, 05, etc.) with `gap_closure: true` to close gaps
443
+ 3. User then runs `/gsd:execute-phase {X} --gaps-only`
444
+ 4. Execute-phase runs only gap closure plans (04-05)
439
445
  5. Verifier runs again after new plans complete
440
446
 
441
447
  User stays in control at each decision point.
@@ -505,7 +505,7 @@ Plans verified and ready for execution.
505
505
 
506
506
  **Execute fixes** — run fix plans
507
507
 
508
- `/clear` then `/gsd:execute-phase {phase}`
508
+ `/clear` then `/gsd:execute-phase {phase} --gaps-only`
509
509
 
510
510
  ───────────────────────────────────────────────────────────────
511
511
  ```
@@ -559,5 +559,5 @@ Default to **major** if unclear. User can correct if needed.
559
559
  - [ ] If issues: gsd-planner creates fix plans (gap_closure mode)
560
560
  - [ ] If issues: gsd-plan-checker verifies fix plans
561
561
  - [ ] If issues: revision loop until plans pass (max 3 iterations)
562
- - [ ] Ready for `/gsd:execute-phase` when complete
562
+ - [ ] Ready for `/gsd:execute-phase --gaps-only` when complete
563
563
  </success_criteria>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-shit-done-cc",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
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"