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 +91 -0
- package/hooks/gsd-boundary-guard.sh +62 -0
- package/hooks/gsd-context-monitor.sh +55 -0
- package/hooks/gsd-workflow-guard.sh +52 -0
- package/package.json +1 -1
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.
|
|
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",
|