cc-dev-template 0.1.19 → 0.1.21

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
@@ -20,7 +20,7 @@ console.log('='.repeat(50));
20
20
  console.log(`Installing to ${CLAUDE_DIR}...`);
21
21
 
22
22
  // Create directories
23
- const dirs = ['agents', 'commands', 'scripts', 'skills', 'mcp-servers'];
23
+ const dirs = ['agents', 'commands', 'scripts', 'skills', 'hooks', 'mcp-servers'];
24
24
  dirs.forEach(dir => {
25
25
  fs.mkdirSync(path.join(CLAUDE_DIR, dir), { recursive: true });
26
26
  });
@@ -92,6 +92,34 @@ if (fs.existsSync(skillsDir)) {
92
92
  console.log(' No skills to install');
93
93
  }
94
94
 
95
+ // Copy hooks
96
+ console.log('\nHooks:');
97
+ const hooksDir = path.join(SRC_DIR, 'hooks');
98
+ if (fs.existsSync(hooksDir)) {
99
+ const hookScripts = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
100
+ const hookConfigs = fs.readdirSync(hooksDir).filter(f => f.endsWith('.json'));
101
+
102
+ // Copy shell scripts and make executable
103
+ hookScripts.forEach(file => {
104
+ const src = path.join(hooksDir, file);
105
+ const dest = path.join(CLAUDE_DIR, 'hooks', file);
106
+ fs.copyFileSync(src, dest);
107
+ fs.chmodSync(dest, 0o755); // Make executable
108
+ console.log(` ${file}`);
109
+ });
110
+
111
+ // Copy JSON configs to scripts dir for merging later
112
+ hookConfigs.forEach(file => {
113
+ const src = path.join(hooksDir, file);
114
+ const dest = path.join(CLAUDE_DIR, 'scripts', file);
115
+ fs.copyFileSync(src, dest);
116
+ });
117
+
118
+ console.log(`✓ ${hookScripts.length} hooks installed`);
119
+ } else {
120
+ console.log(' No hooks to install');
121
+ }
122
+
95
123
  // Install MCP servers
96
124
  console.log('\nMCP Servers:');
97
125
  const mcpServersDir = path.join(SRC_DIR, 'mcp-servers');
@@ -198,7 +226,10 @@ if (fs.existsSync(mergeSettingsPath)) {
198
226
  const configs = [
199
227
  { file: 'yaml-validation-hook.json', name: 'YAML validation hook' },
200
228
  { file: 'read-guard-hook.json', name: 'Context guard for large reads' },
201
- { file: 'statusline-config.json', name: 'Custom status line' }
229
+ { file: 'statusline-config.json', name: 'Custom status line' },
230
+ { file: 'orchestration-hook.json', name: 'Orchestration guidance hook' },
231
+ { file: 'bash-overflow-hook.json', name: 'Bash overflow guard hook' },
232
+ { file: 'bash-precheck-hook.json', name: 'Bash precheck hook' }
202
233
  ];
203
234
 
204
235
  configs.forEach(({ file, name }) => {
@@ -225,6 +256,7 @@ Installed to:
225
256
  Commands: ${CLAUDE_DIR}/commands/
226
257
  Scripts: ${CLAUDE_DIR}/scripts/
227
258
  Skills: ${CLAUDE_DIR}/skills/
259
+ Hooks: ${CLAUDE_DIR}/hooks/
228
260
  MCP Servers: ${CLAUDE_DIR}/mcp-servers/
229
261
  Settings: ${CLAUDE_DIR}/settings.json
230
262
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-dev-template",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Structured AI-assisted development framework for Claude Code",
5
5
  "bin": {
6
6
  "cc-dev-template": "./bin/install.js"
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # bash-overflow-guard.sh - Intercept large Bash outputs to preserve context
3
+ #
4
+ # PostToolUse hook that catches large Bash outputs, saves them to a file,
5
+ # and tells Claude to use Grep to search the file instead of consuming context.
6
+
7
+ set -e
8
+
9
+ # Configuration
10
+ MAX_CHARS=${BASH_OVERFLOW_MAX_CHARS:-20000} # ~5k tokens
11
+ OVERFLOW_DIR="${HOME}/.claude/bash-overflow"
12
+
13
+ # Read hook input from stdin
14
+ input=$(cat)
15
+
16
+ # Parse the tool response
17
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty')
18
+
19
+ # Only process Bash tool
20
+ if [[ "$tool_name" != "Bash" ]]; then
21
+ exit 0
22
+ fi
23
+
24
+ # Extract stdout from the response
25
+ # tool_response can be a string or object depending on output
26
+ stdout=$(echo "$input" | jq -r '.tool_response // empty')
27
+
28
+ # If tool_response is an object, try to get stdout field
29
+ if echo "$stdout" | jq -e 'type == "object"' > /dev/null 2>&1; then
30
+ stdout=$(echo "$stdout" | jq -r '.stdout // .output // empty')
31
+ fi
32
+
33
+ # Measure size
34
+ char_count=${#stdout}
35
+ line_count=$(echo "$stdout" | wc -l | tr -d ' ')
36
+
37
+ # Check if output exceeds threshold
38
+ if [[ $char_count -gt $MAX_CHARS ]]; then
39
+ # Create overflow directory
40
+ mkdir -p "$OVERFLOW_DIR"
41
+
42
+ # Generate filename with timestamp
43
+ timestamp=$(date +%Y%m%d_%H%M%S)
44
+ overflow_file="$OVERFLOW_DIR/bash_output_${timestamp}.txt"
45
+
46
+ # Save output to file
47
+ echo "$stdout" > "$overflow_file"
48
+
49
+ # Calculate approximate token count (rough heuristic: 4 chars per token)
50
+ approx_tokens=$((char_count / 4))
51
+
52
+ # Return block decision with guidance
53
+ cat << EOF
54
+ {
55
+ "decision": "block",
56
+ "reason": "Bash output too large for context (${line_count} lines, ~${approx_tokens} tokens). Output saved to: ${overflow_file}\n\nUse the Grep tool to search this file:\n Grep(pattern: \"your-pattern\", path: \"${overflow_file}\")\n\nOr read specific sections:\n Read(file_path: \"${overflow_file}\", offset: 1, limit: 100)"
57
+ }
58
+ EOF
59
+ exit 0
60
+ fi
61
+
62
+ # Output is small enough, allow it through
63
+ exit 0
@@ -0,0 +1,15 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "$HOME/.claude/hooks/bash-overflow-guard.sh"
10
+ }
11
+ ]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "$HOME/.claude/hooks/bash-precheck.sh"
10
+ }
11
+ ]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1,117 @@
1
+ #!/bin/bash
2
+ # bash-precheck.sh - PreToolUse hook to wrap Bash commands with output limiting
3
+ #
4
+ # Intercepts Bash commands before execution and wraps them to:
5
+ # 1. Capture output to temp file
6
+ # 2. If output exceeds threshold, save and return truncated version
7
+ #
8
+ # This ACTUALLY prevents large outputs from consuming context.
9
+ # Compatible with bash and zsh on macOS and Linux.
10
+
11
+ set -e
12
+
13
+ # Configuration
14
+ MAX_CHARS=${BASH_OVERFLOW_MAX_CHARS:-20000}
15
+ OVERFLOW_DIR="${HOME}/.claude/bash-overflow"
16
+
17
+ # Read hook input from stdin
18
+ input=$(cat)
19
+
20
+ # Parse tool name and command
21
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty')
22
+
23
+ # Only process Bash tool
24
+ if [[ "$tool_name" != "Bash" ]]; then
25
+ exit 0
26
+ fi
27
+
28
+ # Get the original command
29
+ original_cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
30
+
31
+ # Skip if empty
32
+ if [[ -z "$original_cmd" ]]; then
33
+ exit 0
34
+ fi
35
+
36
+ # Skip commands that are already piped to head/tail or are simple commands
37
+ if [[ "$original_cmd" =~ \|[[:space:]]*(head|tail|wc|grep.*-c) ]]; then
38
+ exit 0
39
+ fi
40
+
41
+ # Skip very short commands (likely simple operations)
42
+ if [[ ${#original_cmd} -lt 10 ]]; then
43
+ exit 0
44
+ fi
45
+
46
+ # Create overflow directory
47
+ mkdir -p "$OVERFLOW_DIR"
48
+
49
+ # Generate unique filename
50
+ timestamp=$(date +%Y%m%d_%H%M%S)
51
+ rand_suffix=$RANDOM
52
+ overflow_file="$OVERFLOW_DIR/bash_output_${timestamp}_${rand_suffix}.txt"
53
+ temp_file="/tmp/claude_bash_${timestamp}_${rand_suffix}.tmp"
54
+
55
+ # Escape the original command for embedding in the wrapper
56
+ # Use base64 to safely embed arbitrary commands
57
+ original_cmd_b64=$(echo "$original_cmd" | base64)
58
+
59
+ # Create wrapper that uses temp file approach (more portable)
60
+ read -r -d '' wrapped_cmd << 'WRAPPER_EOF' || true
61
+ __cmd_b64="BASE64_CMD_HERE"
62
+ __cmd=$(echo "$__cmd_b64" | base64 -d)
63
+ __tmpfile="TEMP_FILE_HERE"
64
+ __overflow="OVERFLOW_FILE_HERE"
65
+ __max=MAX_CHARS_HERE
66
+
67
+ # Run command and capture to temp file
68
+ eval "$__cmd" > "$__tmpfile" 2>&1
69
+ __exit_code=$?
70
+
71
+ # Check size
72
+ __size=$(wc -c < "$__tmpfile" | tr -d ' ')
73
+
74
+ if [ "$__size" -gt "$__max" ]; then
75
+ # Save full output
76
+ cp "$__tmpfile" "$__overflow"
77
+ __lines=$(wc -l < "$__tmpfile" | tr -d ' ')
78
+ __tokens=$((__size / 4))
79
+
80
+ echo "=== OUTPUT TRUNCATED (${__lines} lines, ~${__tokens} tokens) ==="
81
+ echo ""
82
+ echo "--- First 40 lines ---"
83
+ head -n 40 "$__tmpfile"
84
+ echo ""
85
+ echo "--- Last 5 lines ---"
86
+ tail -n 5 "$__tmpfile"
87
+ echo ""
88
+ echo "=== Full output saved to: $__overflow ==="
89
+ echo "Use Grep(pattern, path) or Read(file_path, offset, limit) to explore"
90
+ else
91
+ cat "$__tmpfile"
92
+ fi
93
+
94
+ rm -f "$__tmpfile"
95
+ exit $__exit_code
96
+ WRAPPER_EOF
97
+
98
+ # Substitute placeholders
99
+ wrapped_cmd="${wrapped_cmd//BASE64_CMD_HERE/$original_cmd_b64}"
100
+ wrapped_cmd="${wrapped_cmd//TEMP_FILE_HERE/$temp_file}"
101
+ wrapped_cmd="${wrapped_cmd//OVERFLOW_FILE_HERE/$overflow_file}"
102
+ wrapped_cmd="${wrapped_cmd//MAX_CHARS_HERE/$MAX_CHARS}"
103
+
104
+ # Return the modified command
105
+ cat << EOF
106
+ {
107
+ "hookSpecificOutput": {
108
+ "hookEventName": "PreToolUse",
109
+ "permissionDecision": "allow",
110
+ "updatedInput": {
111
+ "command": $(echo "$wrapped_cmd" | jq -Rs .)
112
+ }
113
+ }
114
+ }
115
+ EOF
116
+
117
+ exit 0