gsd-cc 1.2.1 → 1.3.0

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/bin/install.js CHANGED
@@ -155,9 +155,77 @@ function install(isGlobal) {
155
155
  fs.chmodSync(autoLoop, 0o755);
156
156
  }
157
157
 
158
+ // 5. Install hooks
159
+ const hooksSrc = path.join(__dirname, '..', 'hooks');
160
+ const hooksDest = path.join(skillsBase, 'gsd-cc-shared', 'hooks');
161
+ if (fs.existsSync(hooksSrc)) {
162
+ copyDir(hooksSrc, hooksDest);
163
+ // Make hooks executable
164
+ const hookFiles = fs.readdirSync(hooksDest);
165
+ for (const f of hookFiles) {
166
+ fs.chmodSync(path.join(hooksDest, f), 0o755);
167
+ }
168
+ fileCount += hookFiles.length;
169
+ }
170
+
171
+ // 6. Configure hooks in settings.json
172
+ installHooks(isGlobal, hooksDest);
173
+
158
174
  console.log(` ${green}✓${reset} Installed ${fileCount} files to ${label}`);
159
175
  }
160
176
 
177
+ /**
178
+ * Install hooks into .claude/settings.json or .claude/settings.local.json
179
+ */
180
+ function installHooks(isGlobal, hooksDir) {
181
+ const settingsPath = isGlobal
182
+ ? path.join(os.homedir(), '.claude', 'settings.json')
183
+ : path.join(process.cwd(), '.claude', 'settings.local.json');
184
+
185
+ let settings = {};
186
+ if (fs.existsSync(settingsPath)) {
187
+ try {
188
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
189
+ } catch (e) {
190
+ settings = {};
191
+ }
192
+ }
193
+
194
+ if (!settings.hooks) settings.hooks = {};
195
+
196
+ const boundaryGuard = path.join(hooksDir, 'gsd-boundary-guard.sh');
197
+ const contextMonitor = path.join(hooksDir, 'gsd-context-monitor.sh');
198
+ const workflowGuard = path.join(hooksDir, 'gsd-workflow-guard.sh');
199
+
200
+ // PreToolUse: boundary guard on Edit/Write
201
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
202
+ // Remove existing GSD-CC hooks before adding (idempotent)
203
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
204
+ h => !JSON.stringify(h).includes('gsd-boundary-guard')
205
+ );
206
+ settings.hooks.PreToolUse.push({
207
+ matcher: 'Edit|Write',
208
+ hooks: [{ type: 'command', command: boundaryGuard, timeout: 5000 }]
209
+ });
210
+
211
+ // PostToolUse: context monitor + workflow guard
212
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
213
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
214
+ h => !JSON.stringify(h).includes('gsd-context-monitor') && !JSON.stringify(h).includes('gsd-workflow-guard')
215
+ );
216
+ settings.hooks.PostToolUse.push({
217
+ hooks: [{ type: 'command', command: contextMonitor, timeout: 5000 }]
218
+ });
219
+ settings.hooks.PostToolUse.push({
220
+ matcher: 'Edit|Write',
221
+ hooks: [{ type: 'command', command: workflowGuard, timeout: 5000 }]
222
+ });
223
+
224
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
225
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
226
+ console.log(` ${green}✓${reset} Hooks configured in ${settingsPath.replace(os.homedir(), '~').replace(process.cwd(), '.')}`);
227
+ }
228
+
161
229
  /**
162
230
  * Write language to CLAUDE.md
163
231
  */
@@ -254,9 +322,32 @@ function uninstall() {
254
322
  }
255
323
  }
256
324
 
325
+ // Clean up hooks from settings files
326
+ for (const isGlobal of [true, false]) {
327
+ const settingsPath = isGlobal
328
+ ? path.join(os.homedir(), '.claude', 'settings.json')
329
+ : path.join(process.cwd(), '.claude', 'settings.local.json');
330
+ if (fs.existsSync(settingsPath)) {
331
+ try {
332
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
333
+ if (settings.hooks) {
334
+ for (const event of Object.keys(settings.hooks)) {
335
+ settings.hooks[event] = settings.hooks[event].filter(
336
+ h => !JSON.stringify(h).includes('gsd-')
337
+ );
338
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
339
+ }
340
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
341
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
342
+ }
343
+ } catch (e) { /* ignore parse errors */ }
344
+ }
345
+ }
346
+
257
347
  if (!removed) {
258
348
  console.log(` ${yellow}No GSD-CC installation found.${reset}`);
259
349
  } else {
350
+ console.log(` ${green}✓${reset} Hooks removed from settings`);
260
351
  console.log(`\n ${green}Done.${reset} GSD-CC has been removed.`);
261
352
  }
262
353
  }
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # GSD-CC Boundary Guard — PreToolUse hook
3
+ # Blocks Write/Edit operations on files listed in .gsd/STATE.md boundaries.
4
+ # This is a HARD enforcement — Claude cannot bypass this regardless of prompting.
5
+
6
+ INPUT=$(cat)
7
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
8
+ CWD=$(echo "$INPUT" | jq -r '.cwd')
9
+
10
+ # Only check Edit and Write operations
11
+ if [ "$TOOL_NAME" != "Edit" ] && [ "$TOOL_NAME" != "Write" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Get the file being edited/written
16
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
17
+ if [ -z "$FILE_PATH" ]; then
18
+ exit 0
19
+ fi
20
+
21
+ # Check if STATE.md exists and has boundaries
22
+ STATE_FILE="$CWD/.gsd/STATE.md"
23
+ if [ ! -f "$STATE_FILE" ]; then
24
+ exit 0
25
+ fi
26
+
27
+ # Extract boundary files from STATE.md
28
+ # Boundaries section contains lines like: - path/to/file (reason)
29
+ BOUNDARIES=$(sed -n '/^## Boundaries Active/,/^##/p' "$STATE_FILE" | grep '^ *- ' | sed 's/^ *- //' | sed 's/ (.*//')
30
+
31
+ if [ -z "$BOUNDARIES" ]; then
32
+ exit 0
33
+ fi
34
+
35
+ # Normalize the target file path (make relative to CWD if absolute)
36
+ RELATIVE_PATH="$FILE_PATH"
37
+ if [[ "$FILE_PATH" == "$CWD"* ]]; then
38
+ RELATIVE_PATH="${FILE_PATH#$CWD/}"
39
+ fi
40
+
41
+ # Check each boundary file
42
+ while IFS= read -r BOUNDARY_FILE; do
43
+ BOUNDARY_FILE=$(echo "$BOUNDARY_FILE" | xargs) # trim whitespace
44
+ if [ -z "$BOUNDARY_FILE" ]; then
45
+ continue
46
+ fi
47
+
48
+ # Check exact match or path containment
49
+ if [ "$RELATIVE_PATH" = "$BOUNDARY_FILE" ] || [ "$FILE_PATH" = "$BOUNDARY_FILE" ]; then
50
+ jq -n --arg file "$BOUNDARY_FILE" '{
51
+ "hookSpecificOutput": {
52
+ "hookEventName": "PreToolUse",
53
+ "permissionDecision": "deny",
54
+ "permissionDecisionReason": ("BOUNDARY VIOLATION: " + $file + " is in the DO NOT CHANGE list for this task. This file is protected. If you need to modify it, stop and discuss with the user first.")
55
+ }
56
+ }'
57
+ exit 0
58
+ fi
59
+ done <<< "$BOUNDARIES"
60
+
61
+ # File not in boundaries — allow
62
+ exit 0
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # GSD-CC Context Monitor — PostToolUse hook
3
+ # Injects warnings when context usage is getting high.
4
+ # Uses the transcript file size as a proxy for context consumption.
5
+
6
+ INPUT=$(cat)
7
+ CWD=$(echo "$INPUT" | jq -r '.cwd')
8
+ TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty')
9
+
10
+ # Skip if no transcript path
11
+ if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Check if we're in a GSD-CC project
16
+ if [ ! -d "$CWD/.gsd" ]; then
17
+ exit 0
18
+ fi
19
+
20
+ # Use transcript line count as context proxy
21
+ # Typical context window: ~200K tokens ≈ ~2000 transcript lines for a heavy session
22
+ LINE_COUNT=$(wc -l < "$TRANSCRIPT" | xargs)
23
+
24
+ # Debounce: only warn every 20 tool calls
25
+ DEBOUNCE_FILE="/tmp/gsd-cc-ctx-monitor-$$"
26
+ if [ -f "$DEBOUNCE_FILE" ]; then
27
+ LAST_WARN=$(cat "$DEBOUNCE_FILE")
28
+ DIFF=$((LINE_COUNT - LAST_WARN))
29
+ if [ "$DIFF" -lt 50 ]; then
30
+ exit 0
31
+ fi
32
+ fi
33
+
34
+ # Warning thresholds (based on transcript lines)
35
+ if [ "$LINE_COUNT" -gt 1500 ]; then
36
+ echo "$LINE_COUNT" > "$DEBOUNCE_FILE"
37
+ jq -n '{
38
+ "hookSpecificOutput": {
39
+ "hookEventName": "PostToolUse",
40
+ "additionalContext": "⚠️ CRITICAL: Context window is very full. You MUST wrap up the current task immediately, write the summary, and instruct the user to start a fresh session. Do NOT start new work."
41
+ }
42
+ }'
43
+ exit 0
44
+ elif [ "$LINE_COUNT" -gt 1000 ]; then
45
+ echo "$LINE_COUNT" > "$DEBOUNCE_FILE"
46
+ jq -n '{
47
+ "hookSpecificOutput": {
48
+ "hookEventName": "PostToolUse",
49
+ "additionalContext": "⚠️ WARNING: Context window is filling up. Finish the current task soon and prepare to hand off to a fresh session."
50
+ }
51
+ }'
52
+ exit 0
53
+ fi
54
+
55
+ exit 0
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # GSD-CC Workflow Guard — PostToolUse hook
3
+ # Nudges Claude back into the GSD-CC flow when it drifts.
4
+ # Advisory only — does not block operations.
5
+
6
+ INPUT=$(cat)
7
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
8
+ CWD=$(echo "$INPUT" | jq -r '.cwd')
9
+
10
+ # Only check Edit and Write on source files (not .gsd/ files)
11
+ if [ "$TOOL_NAME" != "Edit" ] && [ "$TOOL_NAME" != "Write" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Skip if not a GSD-CC project
16
+ STATE_FILE="$CWD/.gsd/STATE.md"
17
+ if [ ! -f "$STATE_FILE" ]; then
18
+ exit 0
19
+ fi
20
+
21
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
22
+ if [ -z "$FILE_PATH" ]; then
23
+ exit 0
24
+ fi
25
+
26
+ # Allow writes to .gsd/ directory (that's the workflow itself)
27
+ if [[ "$FILE_PATH" == *".gsd/"* ]] || [[ "$FILE_PATH" == *".claude/"* ]]; then
28
+ exit 0
29
+ fi
30
+
31
+ # Check if we're in an active execution phase
32
+ PHASE=$(grep '^phase:' "$STATE_FILE" | head -1 | sed 's/phase: *//')
33
+
34
+ case "$PHASE" in
35
+ "seed"|"seed-complete"|"stack-complete"|"roadmap-complete"|"plan-complete"|"discuss-complete")
36
+ # Not in execution — source file edits are unexpected
37
+ jq -n --arg phase "$PHASE" --arg file "$FILE_PATH" '{
38
+ "hookSpecificOutput": {
39
+ "hookEventName": "PostToolUse",
40
+ "additionalContext": ("Note: You edited " + $file + " but the current GSD-CC phase is \"" + $phase + "\" which is a planning phase, not execution. Source file changes should happen during the apply phase. If this was intentional, carry on. If not, consider running /gsd-cc to check the current state.")
41
+ }
42
+ }'
43
+ exit 0
44
+ ;;
45
+ "applying")
46
+ # In execution — this is expected
47
+ exit 0
48
+ ;;
49
+ *)
50
+ exit 0
51
+ ;;
52
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-cc",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Get Shit Done on Claude Code — structured AI development with your Max plan",
5
5
  "author": "Philipp Briese (https://github.com/0ui-labs)",
6
6
  "homepage": "https://github.com/0ui-labs/GSD-CC#readme",