gsd-cc 1.2.0 → 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.0",
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",
@@ -156,25 +156,31 @@ When running in full-auto mode (`auto_mode_scope: milestone`), Discuss is NOT sk
156
156
 
157
157
  ### How it works
158
158
 
159
- 1. Read `.gsd/PROFILE.md` — this is the user's decision-making profile
160
- 2. For each gray area, simulate a discussion:
161
- - **You (Planner):** Ask the question as you would ask the user
162
- - **Synthetic Stakeholder (Profile):** Answer based on PROFILE.md using their instincts, preferences, strong opinions, and red lines
163
- - The stakeholder MUST cite which part of the profile drives the decision
164
- 3. Write the results to `.gsd/S{nn}-DISCUSS-AUTO.md` with full transparency:
159
+ 1. Read `.gsd/PROFILE.md` — this is the user's decision-making profile (if it exists)
160
+ 2. For each gray area, simulate a real discussion between two roles:
161
+ - **Planner:** Analyzes the technical options. Brings expertise about what works best for THIS project. Considers tradeoffs, risks, maintainability, project requirements.
162
+ - **Stakeholder:** Represents the user's perspective. Influenced by PROFILE.md but not controlled by it. The profile is a **nudge, not a mandate** — it shapes preferences but doesn't override what's technically best for this project.
163
+ 3. The discussion should feel like a real debate, not a rubber stamp:
164
+ - Planner proposes with reasoning
165
+ - Stakeholder reacts based on profile + common sense
166
+ - If they disagree, they work it out with arguments
167
+ - The final decision considers BOTH technical merit AND user preferences
168
+ 4. Write the results to `.gsd/S{nn}-DISCUSS-AUTO.md` with full transparency:
165
169
 
166
170
  ```markdown
167
- # S{nn} Auto-Discuss — Synthetic Stakeholder Decisions
171
+ # S{nn} Auto-Discuss
168
172
 
169
- > These decisions were made by auto-mode using your decision profile.
170
- > Review after UNIFY. Update your profile with /gsd-cc-profile if
171
- > any decision doesn't match how you'd actually decide.
173
+ > These decisions were made by auto-mode.
174
+ > The user's profile influenced but did not dictate decisions.
175
+ > Review after UNIFY. Update your profile with /gsd-cc-profile if needed.
172
176
 
173
177
  ## Decision 1: {topic}
174
178
  **Question:** {what was ambiguous}
175
- **Stakeholder says:** {decision with reasoning}
176
- **Profile basis:** {which section/quote from PROFILE.md}
177
- **Confidence:** {high|medium|lowhow clearly does the profile cover this?}
179
+ **Planner says:** {technical analysis — options, tradeoffs, recommendation}
180
+ **Stakeholder says:** {reaction based on profile + common sense}
181
+ **Profile influence:** {how the profile shaped this or "N/A" if profile didn't cover this}
182
+ **Final decision:** {what was decided and why}
183
+ **Confidence:** {high|medium|low}
178
184
 
179
185
  ## Decision 2: {topic}
180
186
  ...
@@ -182,17 +188,24 @@ When running in full-auto mode (`auto_mode_scope: milestone`), Discuss is NOT sk
182
188
 
183
189
  ### Confidence levels
184
190
 
185
- - **High:** The profile explicitly covers this (e.g., profile says "always REST for MVPs" and the question is REST vs GraphQL for an MVP)
186
- - **Medium:** The profile gives strong hints but doesn't directly address this (e.g., profile says "simplicity over flexibility" and the question is about a specific pattern choice)
187
- - **Low:** The profile doesn't clearly address this — the stakeholder is guessing. Mark these clearly so the user knows to review them.
191
+ - **High:** Clear technical winner that also aligns with the profile
192
+ - **Medium:** Multiple valid options profile tipped the balance, or technical choice overrode a mild preference with good reason
193
+ - **Low:** Unclear technically AND the profile doesn't help — the decision is a best guess. Mark for user review.
188
194
 
189
- ### Rules for the Synthetic Stakeholder
195
+ ### How the Profile Influences (NOT Controls)
190
196
 
191
- - **Stay in character.** Answer as the user would, not as a senior dev or a textbook.
192
- - **Use their language.** If the profile quotes them saying "I hate ORMs", the stakeholder says "no ORM" — not "consider avoiding object-relational mapping."
193
- - **Respect red lines.** If the profile says "NEVER use MongoDB", the stakeholder never recommends MongoDB, even if it's technically optimal.
194
- - **Be honest about uncertainty.** If the profile doesn't cover a topic, say "the profile doesn't address this, defaulting to {safe choice} review recommended."
195
- - **Capture wildcards.** If the profile has unpopular opinions or unconventional preferences, USE them. That's the whole point.
197
+ The profile is one input among several. The weight depends on the type of decision:
198
+
199
+ - **Taste decisions** (UI style, naming conventions, code style) profile weighs heavily. There's no "right answer", so the user's preference matters most.
200
+ - **Technical decisions** (database choice, API design, auth strategy) → profile is a tiebreaker. If two options are technically equal, pick the one the user would prefer. But don't pick a bad option just because the profile likes it.
201
+ - **Red lines** → always respected. If the profile says "NEVER use X", don't use X. Period. But explain the cost if it matters.
202
+
203
+ ### Rules for Auto-Discuss
204
+
205
+ - **The Planner thinks independently.** Don't just ask "what would the user want?" — first figure out what's technically best, THEN check if the profile agrees.
206
+ - **Disagreements are good.** If the planner thinks X is better but the profile nudges toward Y, document the tension. Don't hide it.
207
+ - **Use the user's language** when representing their perspective. If they said "I hate ORMs", the stakeholder says "no ORM" — not "consider avoiding object-relational mapping."
208
+ - **Be honest about uncertainty.** If neither technical analysis nor the profile gives a clear answer, say so.
196
209
 
197
210
  ### If no PROFILE.md exists
198
211
 
@@ -111,27 +111,31 @@ If their reasoning is sound, support it. If it's risky, explain the risk honestl
111
111
 
112
112
  ### For auto-discuss (synthetic stakeholder):
113
113
 
114
- Read PROFILE.md. The stakeholder discusses each decision:
114
+ Read PROFILE.md (if it exists). For each stack decision, run a real discussion:
115
115
 
116
- ```
117
- ## Stack Discussion (Synthetic Stakeholder)
116
+ ```markdown
117
+ ## Stack Discussion (Auto)
118
118
 
119
119
  ### Language / Runtime
120
- Planner: "The project needs {requirement}. I'd suggest {language}
121
- because {reason}."
122
- Stakeholder: "{Response based on PROFILE.md — e.g. 'Philipp prefers
123
- TypeScript for anything with a frontend. He mentioned strict mode is
124
- worth the overhead.'}"
125
- Profile basis: §Tech Stack Defaults, §Strong Opinions
126
- Decision: {final choice}
127
- Confidence: {high|medium|low}
120
+ **Planner:** "For this project we need {requirement}. The best
121
+ options are {A} and {B}. {A} because {reason}. {B} because {reason}.
122
+ I'd lean toward {A}."
123
+ **Stakeholder:** "{Reaction agrees, disagrees, or adds context.
124
+ Profile is a nudge, not a script. E.g. 'The profile says TypeScript
125
+ for frontend work, and that aligns here. But even without the profile
126
+ TypeScript would be the right call because of {project-specific reason}.'}"
127
+ **Decision:** {final choice}
128
+ **Reasoning:** {why this is right for THIS project — not just because the profile says so}
129
+ **Confidence:** {high|medium|low}
128
130
 
129
131
  ### Framework
130
- Planner: ...
131
- Stakeholder: ...
132
+ **Planner:** ...
133
+ **Stakeholder:** ...
132
134
  ```
133
135
 
134
- **Every decision must be discussed.** Even if the profile clearly says "always use Next.js", the planner should validate that Next.js makes sense for THIS project and the stakeholder should confirm.
136
+ **The profile influences, it doesn't dictate.** The planner should first figure out what's technically best for this specific project, THEN check if the profile agrees. If the profile says "always Next.js" but this project is a CLI tool, don't use Next.js.
137
+
138
+ **Every decision must be discussed.** Even obvious ones. The discussion creates a record of WHY each choice was made.
135
139
 
136
140
  ## Step 4: Research When Needed
137
141