gsd-cc 1.3.0 → 1.3.2

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
@@ -194,25 +194,31 @@ function installHooks(isGlobal, hooksDir) {
194
194
  if (!settings.hooks) settings.hooks = {};
195
195
 
196
196
  const boundaryGuard = path.join(hooksDir, 'gsd-boundary-guard.sh');
197
+ const promptGuard = path.join(hooksDir, 'gsd-prompt-guard.sh');
197
198
  const contextMonitor = path.join(hooksDir, 'gsd-context-monitor.sh');
198
199
  const workflowGuard = path.join(hooksDir, 'gsd-workflow-guard.sh');
200
+ const statusline = path.join(hooksDir, 'gsd-statusline.sh');
201
+
202
+ // Remove all existing GSD-CC hooks before adding (idempotent)
203
+ for (const event of Object.keys(settings.hooks)) {
204
+ settings.hooks[event] = settings.hooks[event].filter(
205
+ h => !JSON.stringify(h).includes('gsd-')
206
+ );
207
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
208
+ }
199
209
 
200
- // PreToolUse: boundary guard on Edit/Write
210
+ // PreToolUse: boundary guard + prompt injection guard on Edit/Write
201
211
  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
212
  settings.hooks.PreToolUse.push({
207
213
  matcher: 'Edit|Write',
208
- hooks: [{ type: 'command', command: boundaryGuard, timeout: 5000 }]
214
+ hooks: [
215
+ { type: 'command', command: boundaryGuard, timeout: 5000 },
216
+ { type: 'command', command: promptGuard, timeout: 5000 }
217
+ ]
209
218
  });
210
219
 
211
- // PostToolUse: context monitor + workflow guard
220
+ // PostToolUse: context monitor (all tools) + workflow guard (Edit/Write)
212
221
  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
222
  settings.hooks.PostToolUse.push({
217
223
  hooks: [{ type: 'command', command: contextMonitor, timeout: 5000 }]
218
224
  });
@@ -221,6 +227,12 @@ function installHooks(isGlobal, hooksDir) {
221
227
  hooks: [{ type: 'command', command: workflowGuard, timeout: 5000 }]
222
228
  });
223
229
 
230
+ // Notification: statusline
231
+ if (!settings.hooks.Notification) settings.hooks.Notification = [];
232
+ settings.hooks.Notification.push({
233
+ hooks: [{ type: 'command', command: statusline, timeout: 3000 }]
234
+ });
235
+
224
236
  fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
225
237
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
226
238
  console.log(` ${green}✓${reset} Hooks configured in ${settingsPath.replace(os.homedir(), '~').replace(process.cwd(), '.')}`);
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ # GSD-CC Prompt Injection Guard — PreToolUse hook
3
+ # Scans Write/Edit operations targeting .gsd/ files for prompt injection patterns.
4
+ # Blocks suspicious content from being written into planning artifacts.
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
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
16
+ if [ -z "$FILE_PATH" ]; then
17
+ exit 0
18
+ fi
19
+
20
+ # Only scan writes to .gsd/ directory (planning artifacts)
21
+ if [[ "$FILE_PATH" != *".gsd/"* ]]; then
22
+ exit 0
23
+ fi
24
+
25
+ # Get the content being written
26
+ if [ "$TOOL_NAME" = "Write" ]; then
27
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty')
28
+ elif [ "$TOOL_NAME" = "Edit" ]; then
29
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
30
+ fi
31
+
32
+ if [ -z "$CONTENT" ]; then
33
+ exit 0
34
+ fi
35
+
36
+ # Check for prompt injection patterns
37
+ SUSPICIOUS=false
38
+ REASON=""
39
+
40
+ # Pattern 1: Direct instruction override attempts
41
+ if echo "$CONTENT" | grep -iqE 'ignore (previous|prior|above|all) (instructions|prompts|rules)'; then
42
+ SUSPICIOUS=true
43
+ REASON="Detected 'ignore previous instructions' pattern"
44
+ fi
45
+
46
+ # Pattern 2: Role reassignment
47
+ if echo "$CONTENT" | grep -iqE '(you are now|act as|pretend to be|your new role|forget your|disregard your)'; then
48
+ SUSPICIOUS=true
49
+ REASON="Detected role reassignment pattern"
50
+ fi
51
+
52
+ # Pattern 3: System prompt extraction
53
+ if echo "$CONTENT" | grep -iqE '(show|reveal|print|output|display) (your|the|system) (prompt|instructions|rules)'; then
54
+ SUSPICIOUS=true
55
+ REASON="Detected system prompt extraction attempt"
56
+ fi
57
+
58
+ # Pattern 4: Invisible Unicode characters (zero-width spaces, RTL override, etc.)
59
+ if echo "$CONTENT" | grep -qP '[\x{200B}\x{200C}\x{200D}\x{FEFF}\x{202A}-\x{202E}\x{2066}-\x{2069}]' 2>/dev/null; then
60
+ SUSPICIOUS=true
61
+ REASON="Detected invisible Unicode characters"
62
+ fi
63
+
64
+ # Pattern 5: Base64-encoded instructions
65
+ if echo "$CONTENT" | grep -qE '[A-Za-z0-9+/]{50,}={0,2}'; then
66
+ # Only flag if it's in a suspicious context (not normal code)
67
+ if echo "$CONTENT" | grep -iqE '(decode|eval|execute|base64).*[A-Za-z0-9+/]{50,}'; then
68
+ SUSPICIOUS=true
69
+ REASON="Detected potentially encoded instructions"
70
+ fi
71
+ fi
72
+
73
+ # Pattern 6: HTML/script injection in markdown
74
+ if echo "$CONTENT" | grep -iqE '<script|javascript:|on(load|error|click)='; then
75
+ SUSPICIOUS=true
76
+ REASON="Detected script injection in planning artifact"
77
+ fi
78
+
79
+ if [ "$SUSPICIOUS" = true ]; then
80
+ jq -n --arg reason "$REASON" --arg file "$FILE_PATH" '{
81
+ "hookSpecificOutput": {
82
+ "hookEventName": "PreToolUse",
83
+ "permissionDecision": "deny",
84
+ "permissionDecisionReason": ("PROMPT INJECTION BLOCKED: " + $reason + " in " + $file + ". This content cannot be written to planning artifacts. If this is a false positive, the user can write the file manually.")
85
+ }
86
+ }'
87
+ exit 0
88
+ fi
89
+
90
+ # Content is clean
91
+ exit 0
@@ -0,0 +1,73 @@
1
+ #!/bin/bash
2
+ # GSD-CC Statusline — Notification hook
3
+ # Renders project status in the terminal statusline.
4
+ # Shows: current phase, milestone/slice/task position, and project type.
5
+
6
+ INPUT=$(cat)
7
+ CWD=$(echo "$INPUT" | jq -r '.cwd')
8
+
9
+ # Only render if this is a GSD-CC project
10
+ STATE_FILE="$CWD/.gsd/STATE.md"
11
+ if [ ! -f "$STATE_FILE" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Parse STATE.md frontmatter
16
+ PHASE=$(grep '^phase:' "$STATE_FILE" | head -1 | sed 's/phase: *//')
17
+ MILESTONE=$(grep '^milestone:' "$STATE_FILE" | head -1 | sed 's/milestone: *//')
18
+ SLICE=$(grep '^current_slice:' "$STATE_FILE" | head -1 | sed 's/current_slice: *//')
19
+ TASK=$(grep '^current_task:' "$STATE_FILE" | head -1 | sed 's/current_task: *//')
20
+ RIGOR=$(grep '^rigor:' "$STATE_FILE" | head -1 | sed 's/rigor: *//')
21
+
22
+ # Build position string
23
+ POSITION="$MILESTONE"
24
+ if [ "$SLICE" != "—" ] && [ -n "$SLICE" ]; then
25
+ POSITION="$POSITION / $SLICE"
26
+ fi
27
+ if [ "$TASK" != "—" ] && [ -n "$TASK" ]; then
28
+ POSITION="$POSITION / $TASK"
29
+ fi
30
+
31
+ # Map phase to display name
32
+ case "$PHASE" in
33
+ "seed") PHASE_DISPLAY="Seed" ;;
34
+ "seed-complete") PHASE_DISPLAY="Seed ✓" ;;
35
+ "stack-complete") PHASE_DISPLAY="Stack ✓" ;;
36
+ "roadmap-complete") PHASE_DISPLAY="Roadmap ✓" ;;
37
+ "discuss-complete") PHASE_DISPLAY="Discuss ✓" ;;
38
+ "plan-complete") PHASE_DISPLAY="Plan ✓" ;;
39
+ "planning") PHASE_DISPLAY="Planning..." ;;
40
+ "applying") PHASE_DISPLAY="Executing..." ;;
41
+ "apply-complete") PHASE_DISPLAY="UNIFY required" ;;
42
+ "unified") PHASE_DISPLAY="Unified ✓" ;;
43
+ *) PHASE_DISPLAY="$PHASE" ;;
44
+ esac
45
+
46
+ # Count progress
47
+ TOTAL_SLICES=$(grep -c '| S[0-9]' "$STATE_FILE" 2>/dev/null || echo "0")
48
+ DONE_SLICES=$(grep '| done' "$STATE_FILE" | wc -l | xargs)
49
+
50
+ # Write status to bridge file for other hooks
51
+ BRIDGE_FILE="/tmp/gsd-cc-status-$(echo "$CWD" | md5sum 2>/dev/null | cut -c1-8 || echo "default").json"
52
+ jq -n \
53
+ --arg phase "$PHASE" \
54
+ --arg position "$POSITION" \
55
+ --arg rigor "$RIGOR" \
56
+ --arg total "$TOTAL_SLICES" \
57
+ --arg done "$DONE_SLICES" \
58
+ '{phase: $phase, position: $position, rigor: $rigor, total_slices: ($total|tonumber), done_slices: ($done|tonumber)}' \
59
+ > "$BRIDGE_FILE" 2>/dev/null
60
+
61
+ # Output statusline data
62
+ jq -n \
63
+ --arg pos "$POSITION" \
64
+ --arg phase "$PHASE_DISPLAY" \
65
+ --arg rigor "$RIGOR" \
66
+ --arg progress "${DONE_SLICES}/${TOTAL_SLICES}" \
67
+ '{
68
+ "hookSpecificOutput": {
69
+ "hookEventName": "Notification",
70
+ "additionalContext": ("GSD-CC: " + $pos + " | " + $phase + " | " + $rigor + " | " + $progress + " slices")
71
+ }
72
+ }'
73
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-cc",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
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",
@@ -38,7 +38,7 @@ If not: "jq is required for auto-mode. Install with: `brew install jq`"
38
38
 
39
39
  ### claude -p works
40
40
  ```bash
41
- claude -p "echo test" --output-format json --bare --max-turns 1
41
+ claude -p "echo test" --output-format json --max-turns 1
42
42
  ```
43
43
  If fails: "claude -p is not working. Make sure Claude Code is installed and you're logged in with a Max plan."
44
44
 
@@ -145,7 +145,7 @@ while true; do
145
145
  RESULT_FILE="/tmp/gsd-result-$$.json"
146
146
  timeout 600 claude -p "$(cat "$PROMPT_FILE")" \
147
147
  --allowedTools "Read,Write,Edit,Glob,Grep,Bash(git checkout *),Bash(git merge *),Bash(git commit *)" \
148
- --output-format json --bare \
148
+ --output-format json \
149
149
  --max-turns 15 > "$RESULT_FILE" 2>/dev/null || {
150
150
  echo "❌ UNIFY dispatch failed. Check .gsd/auto.lock for recovery."
151
151
  break
@@ -164,7 +164,7 @@ while true; do
164
164
  # Check roadmap for remaining slices
165
165
  NEXT_RESULT=$(claude -p "Read .gsd/STATE.md and all .gsd/M*-ROADMAP.md and .gsd/S*-UNIFY.md files. Determine the next slice that needs work (no PLAN.md or no UNIFY.md). Output ONLY valid JSON: {\"slice\":\"S01\",\"phase\":\"plan\"} or {\"done\":true} if all slices are unified." \
166
166
  --allowedTools "Read,Glob" \
167
- --output-format json --bare --max-turns 3 2>/dev/null) || {
167
+ --output-format json --max-turns 3 2>/dev/null) || {
168
168
  echo "❌ Failed to determine next unit."
169
169
  break
170
170
  }
@@ -274,7 +274,7 @@ while true; do
274
274
 
275
275
  timeout "$TIMEOUT" claude -p "$(cat "$PROMPT_FILE")" \
276
276
  --allowedTools "$ALLOWED_TOOLS" \
277
- --output-format json --bare \
277
+ --output-format json \
278
278
  --max-turns "$MAX_TURNS" > "$RESULT_FILE" 2>/dev/null || {
279
279
  EXIT_CODE=$?
280
280
  if [[ $EXIT_CODE -eq 124 ]]; then