@paulduvall/claude-dev-toolkit 0.0.1-alpha.2 → 0.0.1-alpha.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/LICENSE +21 -0
- package/README.md +88 -37
- package/bin/claude-commands +307 -65
- package/commands/active/xarchitecture.md +393 -0
- package/commands/active/xconfig.md +127 -0
- package/commands/active/xcontinue.md +92 -0
- package/commands/active/xdebug.md +130 -0
- package/commands/active/xdocs.md +178 -0
- package/commands/active/xexplore.md +94 -0
- package/commands/active/xgit.md +149 -0
- package/commands/active/xpipeline.md +152 -0
- package/commands/active/xquality.md +96 -0
- package/commands/active/xrefactor.md +198 -0
- package/commands/active/xrelease.md +142 -0
- package/commands/active/xsecurity.md +92 -0
- package/commands/active/xspec.md +174 -0
- package/commands/active/xtdd.md +151 -0
- package/commands/active/xtest.md +89 -0
- package/commands/active/xverify.md +80 -0
- package/commands/experiments/xact.md +742 -0
- package/commands/experiments/xanalytics.md +113 -0
- package/commands/experiments/xanalyze.md +70 -0
- package/commands/experiments/xapi.md +161 -0
- package/commands/experiments/xatomic.md +112 -0
- package/commands/experiments/xaws.md +85 -0
- package/commands/experiments/xcicd.md +337 -0
- package/commands/experiments/xcommit.md +122 -0
- package/commands/experiments/xcompliance.md +182 -0
- package/commands/experiments/xconstraints.md +89 -0
- package/commands/experiments/xcoverage.md +90 -0
- package/commands/experiments/xdb.md +102 -0
- package/commands/experiments/xdesign.md +121 -0
- package/commands/experiments/xdevcontainer.md +238 -0
- package/commands/experiments/xevaluate.md +111 -0
- package/commands/experiments/xfootnote.md +12 -0
- package/commands/experiments/xgenerate.md +117 -0
- package/commands/experiments/xgovernance.md +149 -0
- package/commands/experiments/xgreen.md +66 -0
- package/commands/experiments/xiac.md +118 -0
- package/commands/experiments/xincident.md +137 -0
- package/commands/experiments/xinfra.md +115 -0
- package/commands/experiments/xknowledge.md +115 -0
- package/commands/experiments/xmaturity.md +120 -0
- package/commands/experiments/xmetrics.md +118 -0
- package/commands/experiments/xmonitoring.md +128 -0
- package/commands/experiments/xnew.md +903 -0
- package/commands/experiments/xobservable.md +114 -0
- package/commands/experiments/xoidc.md +165 -0
- package/commands/experiments/xoptimize.md +115 -0
- package/commands/experiments/xperformance.md +112 -0
- package/commands/experiments/xplanning.md +131 -0
- package/commands/experiments/xpolicy.md +115 -0
- package/commands/experiments/xproduct.md +98 -0
- package/commands/experiments/xreadiness.md +75 -0
- package/commands/experiments/xred.md +55 -0
- package/commands/experiments/xrisk.md +128 -0
- package/commands/experiments/xrules.md +124 -0
- package/commands/experiments/xsandbox.md +120 -0
- package/commands/experiments/xscan.md +102 -0
- package/commands/experiments/xsetup.md +123 -0
- package/commands/experiments/xtemplate.md +116 -0
- package/commands/experiments/xtrace.md +212 -0
- package/commands/experiments/xux.md +171 -0
- package/commands/experiments/xvalidate.md +104 -0
- package/commands/experiments/xworkflow.md +113 -0
- package/hooks/.smellrc.example.json +19 -0
- package/hooks/README.md +263 -0
- package/hooks/check-commit-signing.py +127 -0
- package/hooks/check-complexity.py +38 -0
- package/hooks/check-security.py +37 -0
- package/hooks/claude-wrapper.sh +29 -0
- package/hooks/config.py +110 -0
- package/hooks/file-logger.sh +100 -0
- package/hooks/lib/argument-parser.sh +427 -0
- package/hooks/lib/config-constants.sh +230 -0
- package/hooks/lib/context-manager.sh +560 -0
- package/hooks/lib/error-handler.sh +423 -0
- package/hooks/lib/execution-engine.sh +444 -0
- package/hooks/lib/execution-results.sh +113 -0
- package/hooks/lib/execution-simulation.sh +114 -0
- package/hooks/lib/field-validators.sh +104 -0
- package/hooks/lib/file-utils.sh +398 -0
- package/hooks/lib/subagent-discovery.sh +468 -0
- package/hooks/lib/subagent-validator.sh +407 -0
- package/hooks/lib/validation-reporter.sh +134 -0
- package/hooks/on-error-debug.sh +226 -0
- package/hooks/pre-commit-quality.sh +204 -0
- package/hooks/pre-commit-test-runner.sh +132 -0
- package/hooks/pre-write-security.sh +115 -0
- package/hooks/prevent-credential-exposure.sh +279 -0
- package/hooks/security_bandit.py +177 -0
- package/hooks/security_checks.py +97 -0
- package/hooks/security_secrets.py +81 -0
- package/hooks/security_trojan.py +61 -0
- package/hooks/settings.example.json +52 -0
- package/hooks/smell_checks.py +238 -0
- package/hooks/smell_javascript.py +231 -0
- package/hooks/smell_python.py +110 -0
- package/hooks/smell_ruff.py +70 -0
- package/hooks/smell_types.py +72 -0
- package/hooks/subagent-trigger-simple.sh +202 -0
- package/hooks/subagent-trigger.sh +253 -0
- package/hooks/suppression.py +82 -0
- package/hooks/tab-color.sh +70 -0
- package/hooks/verify-before-edit.sh +135 -0
- package/lib/backup-restore-command.js +140 -0
- package/lib/base/base-command.js +252 -0
- package/lib/base/command-result.js +184 -0
- package/lib/config/constants.js +255 -0
- package/lib/config.js +48 -6
- package/lib/configure-command.js +428 -0
- package/lib/dependency-validator.js +64 -5
- package/lib/hook-installer-core.js +2 -2
- package/lib/installation-instruction-generator.js +213 -495
- package/lib/installer.js +134 -56
- package/lib/oidc-command.js +740 -0
- package/lib/services/backup-list-service.js +226 -0
- package/lib/services/backup-service.js +230 -0
- package/lib/services/command-installer-service.js +217 -0
- package/lib/services/logger-service.js +201 -0
- package/lib/services/package-manager-service.js +319 -0
- package/lib/services/platform-instruction-service.js +294 -0
- package/lib/services/recovery-instruction-service.js +348 -0
- package/lib/services/restore-service.js +221 -0
- package/lib/setup-command.js +359 -0
- package/lib/setup-wizard.js +155 -262
- package/lib/uninstall-command.js +100 -0
- package/lib/utils/claude-path-config.js +184 -0
- package/lib/utils/file-system-utils.js +152 -0
- package/lib/utils.js +8 -4
- package/lib/verify-command.js +430 -0
- package/package.json +7 -3
- package/scripts/postinstall.js +172 -157
- package/subagents/debug-specialist.md +7 -0
- package/templates/README.md +115 -0
- package/templates/basic-settings.json +30 -0
- package/templates/comprehensive-settings.json +57 -0
- package/templates/global-claude.md +344 -0
- package/templates/hybrid-hook-config.yaml +132 -0
- package/templates/security-focused-settings.json +62 -0
- package/templates/subagent-hooks.yaml +188 -0
- package/lib/package-manager-service.js +0 -270
- package/subagents/debug-context.md +0 -197
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Shared types, thresholds, and helpers for code smell detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Thresholds
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
MAX_COMPLEXITY = 10
|
|
11
|
+
MAX_FUNCTION_LINES = 20
|
|
12
|
+
MAX_NESTING_DEPTH = 3
|
|
13
|
+
MAX_PARAMETERS = 4
|
|
14
|
+
MAX_FILE_LINES = 300
|
|
15
|
+
DUPLICATE_MIN_LINES = 4
|
|
16
|
+
DUPLICATE_MIN_OCCURRENCES = 2
|
|
17
|
+
|
|
18
|
+
FIXES = {
|
|
19
|
+
"complexity": "Use extract-method, early returns, guard clauses, or lookup tables.",
|
|
20
|
+
"long_function": "Extract helper functions for distinct logical steps.",
|
|
21
|
+
"deep_nesting": "Use guard clauses and early returns to flatten control flow.",
|
|
22
|
+
"too_many_params": "Group related parameters into a dataclass or options object.",
|
|
23
|
+
"duplicate_block": "Extract repeated code into a shared helper function.",
|
|
24
|
+
"long_file": "Split into smaller modules with clear single responsibilities.",
|
|
25
|
+
"secrets": "Move secrets to environment variables or a secrets manager.",
|
|
26
|
+
"B101": "Remove assert from non-test code; use proper validation instead.",
|
|
27
|
+
"B102": "Replace exec/eval with safer alternatives.",
|
|
28
|
+
"B105": "Move hardcoded passwords to environment variables.",
|
|
29
|
+
"B106": "Move hardcoded passwords to environment variables.",
|
|
30
|
+
"B110": "Handle exceptions explicitly instead of using bare except-pass.",
|
|
31
|
+
"B301": "Avoid pickle for untrusted data; use json or safer serialization.",
|
|
32
|
+
"B602": "Avoid shell=True; pass command as a list to subprocess.",
|
|
33
|
+
"trojan_bidi": "Remove Unicode bidi override characters that can disguise code.",
|
|
34
|
+
"trojan_zero_width": "Remove zero-width Unicode characters that hide content.",
|
|
35
|
+
"ruff_lint": "Fix the ruff lint violation manually.",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class Smell:
|
|
41
|
+
"""A single code-smell violation."""
|
|
42
|
+
|
|
43
|
+
kind: str
|
|
44
|
+
name: str
|
|
45
|
+
line: int
|
|
46
|
+
detail: str
|
|
47
|
+
fix: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Shared helpers
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
_DETAIL_TEMPLATES = {
|
|
55
|
+
"complexity": ("complexity={value} (max {max})", MAX_COMPLEXITY),
|
|
56
|
+
"long_function": ("{value} lines (max {max})", MAX_FUNCTION_LINES),
|
|
57
|
+
"deep_nesting": ("nesting depth {value} (max {max})", MAX_NESTING_DEPTH),
|
|
58
|
+
"too_many_params": ("{value} params (max {max})", MAX_PARAMETERS),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def make_smell(kind: str, name: str, line: int, value: int) -> Smell:
|
|
63
|
+
"""Create a Smell with a formatted detail string."""
|
|
64
|
+
tpl, mx = _DETAIL_TEMPLATES[kind]
|
|
65
|
+
return Smell(kind, name, line, tpl.format(value=value, max=mx), FIXES[kind])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def apply_threshold_checks(
|
|
69
|
+
name: str, line: int, checks: list[tuple[str, int, int]],
|
|
70
|
+
) -> list[Smell]:
|
|
71
|
+
"""Return Smell for each (kind, value, threshold) where value > threshold."""
|
|
72
|
+
return [make_smell(k, name, line, v) for k, v, t in checks if v > t]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Claude Code Hook: Simple Subagent Trigger
|
|
5
|
+
#
|
|
6
|
+
# Purpose: Lightweight trigger for delegating to any subagent
|
|
7
|
+
# Usage: subagent-trigger-simple.sh <subagent-name> [event-type] [context]
|
|
8
|
+
# Approach: Minimal orchestration - gather context and delegate to subagents
|
|
9
|
+
#
|
|
10
|
+
# This is the simplified version of the original 253-line subagent-trigger.sh,
|
|
11
|
+
# focusing on clean delegation rather than complex orchestration.
|
|
12
|
+
|
|
13
|
+
##################################
|
|
14
|
+
# Load Shared Libraries
|
|
15
|
+
##################################
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
LIB_DIR="$SCRIPT_DIR/lib"
|
|
18
|
+
|
|
19
|
+
# Load only essential modules for lightweight operation
|
|
20
|
+
source "$LIB_DIR/config-constants.sh"
|
|
21
|
+
source "$LIB_DIR/file-utils.sh"
|
|
22
|
+
source "$LIB_DIR/context-manager.sh"
|
|
23
|
+
source "$LIB_DIR/error-handler.sh"
|
|
24
|
+
|
|
25
|
+
##################################
|
|
26
|
+
# Usage Information
|
|
27
|
+
##################################
|
|
28
|
+
show_usage() {
|
|
29
|
+
cat <<EOF
|
|
30
|
+
Usage: subagent-trigger-simple.sh <subagent-name> [event-type] [additional-context]
|
|
31
|
+
|
|
32
|
+
Lightweight subagent trigger for event-driven AI assistance.
|
|
33
|
+
|
|
34
|
+
Arguments:
|
|
35
|
+
subagent-name Name of the subagent to invoke (required)
|
|
36
|
+
event-type Type of event (default: manual)
|
|
37
|
+
additional-context Additional context information (optional)
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
subagent-trigger-simple.sh security-auditor pre_write
|
|
41
|
+
subagent-trigger-simple.sh style-enforcer pre_commit "Check Python files"
|
|
42
|
+
subagent-trigger-simple.sh debug-specialist on_error "ImportError in main.py"
|
|
43
|
+
|
|
44
|
+
Available Events: ${SUPPORTED_EVENTS[*]}
|
|
45
|
+
EOF
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
##################################
|
|
49
|
+
# Simple Context Gathering
|
|
50
|
+
##################################
|
|
51
|
+
gather_simple_context() {
|
|
52
|
+
local subagent_name="$1"
|
|
53
|
+
local event_type="$2"
|
|
54
|
+
local additional_context="$3"
|
|
55
|
+
|
|
56
|
+
# Create lightweight context - much simpler than the original
|
|
57
|
+
local safe_name safe_event safe_ctx safe_user safe_wd safe_branch
|
|
58
|
+
safe_name=$(json_escape "$subagent_name")
|
|
59
|
+
safe_event=$(json_escape "$event_type")
|
|
60
|
+
safe_ctx=$(json_escape "$additional_context")
|
|
61
|
+
safe_user=$(json_escape "$USER")
|
|
62
|
+
safe_wd=$(json_escape "$(pwd)")
|
|
63
|
+
safe_branch=$(json_escape "$(git branch --show-current 2>/dev/null || echo 'not-in-git')")
|
|
64
|
+
|
|
65
|
+
local context_data
|
|
66
|
+
context_data=$(cat <<EOF
|
|
67
|
+
{
|
|
68
|
+
"trigger": "simple_subagent_trigger",
|
|
69
|
+
"subagent": "$safe_name",
|
|
70
|
+
"event": "$safe_event",
|
|
71
|
+
"additional_context": "$safe_ctx",
|
|
72
|
+
"environment": {
|
|
73
|
+
"tool": "${CLAUDE_TOOL:-unknown}",
|
|
74
|
+
"file": "${CLAUDE_FILE:-none}",
|
|
75
|
+
"user": "$safe_user",
|
|
76
|
+
"working_directory": "$safe_wd",
|
|
77
|
+
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
|
78
|
+
"session_id": "${CLAUDE_SESSION_ID:-$$}"
|
|
79
|
+
},
|
|
80
|
+
"project": {
|
|
81
|
+
"git_branch": "$safe_branch"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
EOF
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
echo "$context_data"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
##################################
|
|
91
|
+
# Subagent Validation
|
|
92
|
+
##################################
|
|
93
|
+
validate_subagent_exists() {
|
|
94
|
+
local subagent_name="$1"
|
|
95
|
+
|
|
96
|
+
# Simple existence check - leverage the existing validation if needed
|
|
97
|
+
local common_subagents=("security-auditor" "style-enforcer" "debug-specialist" "test-writer")
|
|
98
|
+
|
|
99
|
+
for known_subagent in "${common_subagents[@]}"; do
|
|
100
|
+
if [[ "$subagent_name" == "$known_subagent" ]]; then
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
# If not a known subagent, still allow but warn
|
|
106
|
+
log_warning "Subagent '$subagent_name' not in known list, proceeding anyway"
|
|
107
|
+
return 0
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
##################################
|
|
111
|
+
# Simple Delegation
|
|
112
|
+
##################################
|
|
113
|
+
delegate_to_subagent() {
|
|
114
|
+
local subagent_name="$1"
|
|
115
|
+
local event_type="$2"
|
|
116
|
+
local context="$3"
|
|
117
|
+
|
|
118
|
+
log_info "Delegating to subagent: $subagent_name for event: $event_type"
|
|
119
|
+
|
|
120
|
+
echo "🤖 SUBAGENT TRIGGER: Invoking $subagent_name"
|
|
121
|
+
echo ""
|
|
122
|
+
echo "Event: $event_type"
|
|
123
|
+
echo "Triggered by: ${CLAUDE_TOOL:-manual}"
|
|
124
|
+
echo "Target: ${CLAUDE_FILE:-general}"
|
|
125
|
+
echo "Time: $(date)"
|
|
126
|
+
echo ""
|
|
127
|
+
echo "Context for $subagent_name:"
|
|
128
|
+
echo "$context" | jq . 2>/dev/null || echo "$context"
|
|
129
|
+
echo ""
|
|
130
|
+
echo "🎯 REQUEST:"
|
|
131
|
+
echo "Please handle this $event_type event with your specialized expertise."
|
|
132
|
+
echo "Analyze the context above and provide appropriate assistance."
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
##################################
|
|
136
|
+
# Input Validation
|
|
137
|
+
##################################
|
|
138
|
+
validate_arguments() {
|
|
139
|
+
local subagent_name="$1"
|
|
140
|
+
local event_type="$2"
|
|
141
|
+
|
|
142
|
+
# Basic argument validation
|
|
143
|
+
if [[ -z "$subagent_name" ]]; then
|
|
144
|
+
echo "ERROR: Subagent name is required" >&2
|
|
145
|
+
show_usage >&2
|
|
146
|
+
return 1
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Validate event type if provided
|
|
150
|
+
if [[ -n "$event_type" ]] && [[ "$event_type" != "manual" ]]; then
|
|
151
|
+
if ! is_supported_event "$event_type"; then
|
|
152
|
+
log_warning "Event type '$event_type' not in supported list"
|
|
153
|
+
log_info "Supported events: ${SUPPORTED_EVENTS[*]}"
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
##################################
|
|
161
|
+
# Main Logic (Simplified)
|
|
162
|
+
##################################
|
|
163
|
+
main() {
|
|
164
|
+
local subagent_name="${1:-}"
|
|
165
|
+
local event_type="${2:-manual}"
|
|
166
|
+
local additional_context="${3:-}"
|
|
167
|
+
|
|
168
|
+
# Handle help request
|
|
169
|
+
if [[ "$subagent_name" == "--help" ]] || [[ "$subagent_name" == "-h" ]] || [[ -z "$subagent_name" ]]; then
|
|
170
|
+
show_usage
|
|
171
|
+
exit 0
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Initialize minimal error handling
|
|
175
|
+
initialize_error_handling || {
|
|
176
|
+
echo "ERROR: Failed to initialize error handling" >&2
|
|
177
|
+
exit 1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Validate input arguments
|
|
181
|
+
if ! validate_arguments "$subagent_name" "$event_type"; then
|
|
182
|
+
exit 1
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# Validate subagent exists (lenient check)
|
|
186
|
+
validate_subagent_exists "$subagent_name"
|
|
187
|
+
|
|
188
|
+
# Gather context (much simpler than original)
|
|
189
|
+
local context
|
|
190
|
+
context=$(gather_simple_context "$subagent_name" "$event_type" "$additional_context")
|
|
191
|
+
|
|
192
|
+
# Delegate to subagent (no complex execution engine needed)
|
|
193
|
+
delegate_to_subagent "$subagent_name" "$event_type" "$context"
|
|
194
|
+
|
|
195
|
+
log_info "Simple trigger completed for: $subagent_name"
|
|
196
|
+
exit 0
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
##################################
|
|
200
|
+
# Execute (Clean and Simple)
|
|
201
|
+
##################################
|
|
202
|
+
main "$@"
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Claude Code Hook: Subagent Event Trigger
|
|
5
|
+
#
|
|
6
|
+
# Purpose: Bridge between Claude Code hooks and subagents, enabling event-driven subagent execution
|
|
7
|
+
# Usage: subagent-trigger.sh [OPTIONS] <subagent-name> [event-type] [additional-context]
|
|
8
|
+
# subagent-trigger.sh [OPTIONS] --event <event-type>
|
|
9
|
+
# Trigger: Can be used in PreToolUse, PostToolUse, or custom hook configurations
|
|
10
|
+
#
|
|
11
|
+
# This hook enables automatic invocation of specialized subagents based on
|
|
12
|
+
# specific events, ensuring the right expertise is applied at the right time.
|
|
13
|
+
|
|
14
|
+
##################################
|
|
15
|
+
# Module Loading
|
|
16
|
+
##################################
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
LIB_DIR="$SCRIPT_DIR/lib"
|
|
19
|
+
|
|
20
|
+
# Source all required modules in dependency order
|
|
21
|
+
source "$LIB_DIR/config-constants.sh"
|
|
22
|
+
source "$LIB_DIR/file-utils.sh"
|
|
23
|
+
source "$LIB_DIR/error-handler.sh"
|
|
24
|
+
source "$LIB_DIR/argument-parser.sh"
|
|
25
|
+
source "$LIB_DIR/subagent-discovery.sh"
|
|
26
|
+
source "$LIB_DIR/subagent-validator.sh"
|
|
27
|
+
source "$LIB_DIR/context-manager.sh"
|
|
28
|
+
source "$LIB_DIR/execution-engine.sh"
|
|
29
|
+
|
|
30
|
+
##################################
|
|
31
|
+
# Initialization
|
|
32
|
+
##################################
|
|
33
|
+
initialize_all_modules() {
|
|
34
|
+
log_debug "Initializing all modules"
|
|
35
|
+
|
|
36
|
+
initialize_error_handling || {
|
|
37
|
+
echo "FATAL: Error handling initialization failed" >&2
|
|
38
|
+
exit $EXIT_GENERAL_ERROR
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
initialize_argument_parser || {
|
|
42
|
+
log_error "Argument parser initialization failed"
|
|
43
|
+
return $EXIT_GENERAL_ERROR
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
initialize_subagent_discovery || {
|
|
47
|
+
log_error "Subagent discovery initialization failed"
|
|
48
|
+
return $EXIT_GENERAL_ERROR
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
initialize_subagent_validator || {
|
|
52
|
+
log_error "Subagent validator initialization failed"
|
|
53
|
+
return $EXIT_GENERAL_ERROR
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
initialize_context_manager || {
|
|
57
|
+
log_error "Context manager initialization failed"
|
|
58
|
+
return $EXIT_GENERAL_ERROR
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
initialize_execution_engine || {
|
|
62
|
+
log_error "Execution engine initialization failed"
|
|
63
|
+
return $EXIT_GENERAL_ERROR
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
log_debug "All modules initialized successfully"
|
|
67
|
+
return $EXIT_SUCCESS
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
##################################
|
|
71
|
+
# Core Workflow Functions
|
|
72
|
+
##################################
|
|
73
|
+
execute_single_subagent() {
|
|
74
|
+
local subagent_name="$1"
|
|
75
|
+
local event_type="$2"
|
|
76
|
+
local additional_context="$3"
|
|
77
|
+
|
|
78
|
+
log_info "Executing single subagent: $subagent_name for event: $event_type"
|
|
79
|
+
|
|
80
|
+
# Find the subagent file
|
|
81
|
+
local subagent_file
|
|
82
|
+
if ! subagent_file=$(find_subagent "$subagent_name"); then
|
|
83
|
+
handle_missing_subagent "$subagent_name"
|
|
84
|
+
return $EXIT_SUBAGENT_NOT_FOUND
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
log_debug "Found subagent file: $subagent_file"
|
|
88
|
+
|
|
89
|
+
# Validate the subagent
|
|
90
|
+
if ! validate_subagent_file "$subagent_file" "strict"; then
|
|
91
|
+
handle_validation_failure "$subagent_name" "file validation failed"
|
|
92
|
+
return $EXIT_VALIDATION_FAILED
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Create context file
|
|
96
|
+
if ! create_context_file "$subagent_name" "$event_type"; then
|
|
97
|
+
log_error "Failed to create context file"
|
|
98
|
+
return $EXIT_GENERAL_ERROR
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
local context_file
|
|
102
|
+
context_file=$(get_context_file)
|
|
103
|
+
|
|
104
|
+
# Gather context information
|
|
105
|
+
if ! gather_complete_context "$event_type" "$subagent_name" "$additional_context" "false"; then
|
|
106
|
+
log_error "Failed to gather context"
|
|
107
|
+
cleanup_context_file
|
|
108
|
+
return $EXIT_GENERAL_ERROR
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Write context to file
|
|
112
|
+
if ! write_context_to_file "$context_file"; then
|
|
113
|
+
log_error "Failed to write context to file"
|
|
114
|
+
cleanup_context_file
|
|
115
|
+
return $EXIT_GENERAL_ERROR
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Determine execution mode and timeout
|
|
119
|
+
local execution_mode timeout
|
|
120
|
+
execution_mode=$(determine_execution_mode "$event_type" "$(get_parsed_execution_mode)")
|
|
121
|
+
timeout=$(get_timeout_for_execution "$event_type" "$subagent_name")
|
|
122
|
+
|
|
123
|
+
# Check for dry run mode
|
|
124
|
+
if is_dry_run; then
|
|
125
|
+
execution_mode="dry-run"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Execute the subagent
|
|
129
|
+
local execution_result
|
|
130
|
+
if ! execute_subagent "$subagent_file" "$context_file" "$execution_mode" "$timeout"; then
|
|
131
|
+
log_error "Subagent execution failed: $subagent_name"
|
|
132
|
+
cleanup_context_file
|
|
133
|
+
return $EXIT_EXECUTION_FAILED
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# Clean up context file
|
|
137
|
+
cleanup_context_file
|
|
138
|
+
|
|
139
|
+
log_info "Single subagent execution completed successfully: $subagent_name"
|
|
140
|
+
return $EXIT_SUCCESS
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
execute_event_based_subagents() {
|
|
144
|
+
local event_type="$1"
|
|
145
|
+
local additional_context="$2"
|
|
146
|
+
|
|
147
|
+
log_info "Executing event-based subagents for event: $event_type"
|
|
148
|
+
|
|
149
|
+
# Create shared context file for all subagents
|
|
150
|
+
if ! create_context_file "event-$event_type" "$event_type"; then
|
|
151
|
+
log_error "Failed to create context file for event execution"
|
|
152
|
+
return $EXIT_GENERAL_ERROR
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
local context_file
|
|
156
|
+
context_file=$(get_context_file)
|
|
157
|
+
|
|
158
|
+
# Gather context information
|
|
159
|
+
if ! gather_complete_context "$event_type" "event-based" "$additional_context" "false"; then
|
|
160
|
+
log_error "Failed to gather context for event execution"
|
|
161
|
+
cleanup_context_file
|
|
162
|
+
return $EXIT_GENERAL_ERROR
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Write context to file
|
|
166
|
+
if ! write_context_to_file "$context_file"; then
|
|
167
|
+
log_error "Failed to write context to file for event execution"
|
|
168
|
+
cleanup_context_file
|
|
169
|
+
return $EXIT_GENERAL_ERROR
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Determine execution mode
|
|
173
|
+
local execution_mode
|
|
174
|
+
execution_mode=$(determine_execution_mode "$event_type" "$(get_parsed_execution_mode)")
|
|
175
|
+
|
|
176
|
+
# Execute all subagents for this event
|
|
177
|
+
local execution_result
|
|
178
|
+
if ! execute_multiple_subagents "$event_type" "$context_file" "$execution_mode"; then
|
|
179
|
+
log_error "Event-based subagent execution failed for event: $event_type"
|
|
180
|
+
cleanup_context_file
|
|
181
|
+
return $EXIT_EXECUTION_FAILED
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Clean up context file
|
|
185
|
+
cleanup_context_file
|
|
186
|
+
|
|
187
|
+
log_info "Event-based subagent execution completed successfully: $event_type"
|
|
188
|
+
return $EXIT_SUCCESS
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
##################################
|
|
192
|
+
# Main Hook Logic
|
|
193
|
+
##################################
|
|
194
|
+
main() {
|
|
195
|
+
# Initialize all modules
|
|
196
|
+
if ! initialize_all_modules; then
|
|
197
|
+
echo "FATAL: Module initialization failed" >&2
|
|
198
|
+
exit $EXIT_GENERAL_ERROR
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# Parse arguments
|
|
202
|
+
if ! parse_arguments "$@"; then
|
|
203
|
+
# Error messages already logged by parser
|
|
204
|
+
safe_exit $EXIT_VALIDATION_FAILED
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Handle help request
|
|
208
|
+
if is_help_requested; then
|
|
209
|
+
show_usage
|
|
210
|
+
safe_exit $EXIT_SUCCESS
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Log parsed arguments in debug mode
|
|
214
|
+
if is_debug_mode; then
|
|
215
|
+
log_parsed_arguments
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Get execution parameters
|
|
219
|
+
local subagent_name event_type additional_context execution_mode
|
|
220
|
+
subagent_name=$(get_parsed_subagent_name)
|
|
221
|
+
event_type=$(get_parsed_event_type)
|
|
222
|
+
additional_context=$(get_parsed_additional_context)
|
|
223
|
+
execution_mode=$(get_parsed_execution_mode)
|
|
224
|
+
|
|
225
|
+
log_info "Starting subagent hook execution"
|
|
226
|
+
log_info "Mode: $execution_mode, Event: $event_type"
|
|
227
|
+
|
|
228
|
+
# Execute based on mode
|
|
229
|
+
local exit_code
|
|
230
|
+
case "$execution_mode" in
|
|
231
|
+
"event-based")
|
|
232
|
+
execute_event_based_subagents "$event_type" "$additional_context"
|
|
233
|
+
exit_code=$?
|
|
234
|
+
;;
|
|
235
|
+
*)
|
|
236
|
+
execute_single_subagent "$subagent_name" "$event_type" "$additional_context"
|
|
237
|
+
exit_code=$?
|
|
238
|
+
;;
|
|
239
|
+
esac
|
|
240
|
+
|
|
241
|
+
if [[ $exit_code -eq $EXIT_SUCCESS ]]; then
|
|
242
|
+
log_info "Subagent hook completed successfully"
|
|
243
|
+
else
|
|
244
|
+
log_error "Subagent hook failed with exit code: $exit_code"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
safe_exit $exit_code
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
##################################
|
|
251
|
+
# Execute Main Function
|
|
252
|
+
##################################
|
|
253
|
+
main "$@"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Inline suppression parser for code smell and security hooks.
|
|
2
|
+
|
|
3
|
+
Supports per-line suppression comments:
|
|
4
|
+
# smell: ignore[complexity,long_function]
|
|
5
|
+
# security: ignore[secrets,B101]
|
|
6
|
+
|
|
7
|
+
Applies to the same line or the line immediately following the comment.
|
|
8
|
+
No wildcards supported -- explicit check names required.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from smell_types import Smell
|
|
17
|
+
|
|
18
|
+
_SUPPRESS_RE = re.compile(
|
|
19
|
+
r"#\s*(?P<ns>smell|security):\s*ignore\[(?P<names>[^\]]+)\]"
|
|
20
|
+
)
|
|
21
|
+
_MAX_SUPPRESSIONS_PER_FILE = 5
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _parse_suppression(line: str) -> dict[str, set[str]]:
|
|
25
|
+
"""Parse suppression directives from a single line.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dict mapping namespace to set of suppressed check names.
|
|
29
|
+
"""
|
|
30
|
+
result: dict[str, set[str]] = {}
|
|
31
|
+
for match in _SUPPRESS_RE.finditer(line):
|
|
32
|
+
ns = match.group("ns")
|
|
33
|
+
names = {n.strip() for n in match.group("names").split(",")}
|
|
34
|
+
result.setdefault(ns, set()).update(names)
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _build_suppression_map(lines: list[str]) -> dict[int, dict[str, set[str]]]:
|
|
39
|
+
"""Build a map of line numbers to their active suppressions.
|
|
40
|
+
|
|
41
|
+
A suppression on line N applies to line N and line N+1.
|
|
42
|
+
"""
|
|
43
|
+
suppression_map: dict[int, dict[str, set[str]]] = {}
|
|
44
|
+
for lineno, line in enumerate(lines, start=1):
|
|
45
|
+
parsed = _parse_suppression(line)
|
|
46
|
+
if not parsed:
|
|
47
|
+
continue
|
|
48
|
+
for target_line in (lineno, lineno + 1):
|
|
49
|
+
existing = suppression_map.setdefault(target_line, {})
|
|
50
|
+
for ns, names in parsed.items():
|
|
51
|
+
existing.setdefault(ns, set()).update(names)
|
|
52
|
+
return suppression_map
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_suppressed(smell: Smell, namespace: str, active: dict[str, set[str]]) -> bool:
|
|
56
|
+
"""Check if a smell is suppressed by the active suppressions."""
|
|
57
|
+
names = active.get(namespace, set())
|
|
58
|
+
return smell.kind in names or smell.name in names
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _warn_excessive(count: int, total_lines: int) -> None:
|
|
62
|
+
"""Warn to stderr if too many suppressions are used."""
|
|
63
|
+
if count > _MAX_SUPPRESSIONS_PER_FILE:
|
|
64
|
+
print(
|
|
65
|
+
f"Warning: {count} suppression comments in file "
|
|
66
|
+
f"({_MAX_SUPPRESSIONS_PER_FILE} max recommended)",
|
|
67
|
+
file=sys.stderr,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def filter_suppressed(
|
|
72
|
+
smells: list[Smell], lines: list[str], namespace: str,
|
|
73
|
+
) -> list[Smell]:
|
|
74
|
+
"""Remove smells covered by inline suppression comments."""
|
|
75
|
+
suppression_map = _build_suppression_map(lines)
|
|
76
|
+
_warn_excessive(len(suppression_map), len(lines))
|
|
77
|
+
result: list[Smell] = []
|
|
78
|
+
for smell in smells:
|
|
79
|
+
active = suppression_map.get(smell.line, {})
|
|
80
|
+
if not _is_suppressed(smell, namespace, active):
|
|
81
|
+
result.append(smell)
|
|
82
|
+
return result
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tab-color.sh - Sets iTerm2 tab color for Claude Code hook events
|
|
3
|
+
# Usage: tab-color.sh [gray|blue|green|red|reset]
|
|
4
|
+
#
|
|
5
|
+
# gray = session idle (no prompt running)
|
|
6
|
+
# blue = prompt in progress (working)
|
|
7
|
+
# green = prompt complete (done) -- decays to gray after 3 minutes
|
|
8
|
+
# red = error
|
|
9
|
+
# reset = restore default tab color
|
|
10
|
+
#
|
|
11
|
+
# Tab TITLE (directory name) is set by claude-wrapper.sh at launch.
|
|
12
|
+
# This script only manages COLOR -- Claude Code overrides mid-session title changes.
|
|
13
|
+
|
|
14
|
+
DECAY_SECONDS=180
|
|
15
|
+
|
|
16
|
+
# Consume stdin (hook protocol sends JSON on stdin)
|
|
17
|
+
cat > /dev/null
|
|
18
|
+
|
|
19
|
+
# Per-session state file to prevent decay timer from overwriting blue
|
|
20
|
+
get_state_file() {
|
|
21
|
+
local tty_id
|
|
22
|
+
tty_id=$(tty < /dev/tty 2>/dev/null | tr '/' '_' || echo "unknown")
|
|
23
|
+
echo "/tmp/claude-tab-state${tty_id}"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set_tab_color() {
|
|
27
|
+
printf "\033]6;1;bg;red;brightness;%s\a" "$1" > /dev/tty 2>/dev/null
|
|
28
|
+
printf "\033]6;1;bg;green;brightness;%s\a" "$2" > /dev/tty 2>/dev/null
|
|
29
|
+
printf "\033]6;1;bg;blue;brightness;%s\a" "$3" > /dev/tty 2>/dev/null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
STATE_FILE=$(get_state_file)
|
|
33
|
+
|
|
34
|
+
case "${1:-blue}" in
|
|
35
|
+
gray)
|
|
36
|
+
echo "gray" > "$STATE_FILE" 2>/dev/null
|
|
37
|
+
set_tab_color 130 130 130
|
|
38
|
+
;;
|
|
39
|
+
blue)
|
|
40
|
+
echo "blue" > "$STATE_FILE" 2>/dev/null
|
|
41
|
+
set_tab_color 20 80 255
|
|
42
|
+
;;
|
|
43
|
+
green)
|
|
44
|
+
echo "green" > "$STATE_FILE" 2>/dev/null
|
|
45
|
+
set_tab_color 0 255 0
|
|
46
|
+
# Ring terminal bell -- triggers iTerm2 notification for background tabs
|
|
47
|
+
printf "\a" > /dev/tty 2>/dev/null
|
|
48
|
+
# Auto-decay to gray after 3 minutes (only if still green)
|
|
49
|
+
# Fully detach: redirect all fds so Claude Code doesn't wait for child
|
|
50
|
+
nohup bash -c "
|
|
51
|
+
sleep $DECAY_SECONDS
|
|
52
|
+
state_file='$STATE_FILE'
|
|
53
|
+
if [[ -f \"\$state_file\" && \"\$(cat \"\$state_file\" 2>/dev/null)\" == 'green' ]]; then
|
|
54
|
+
echo 'gray' > \"\$state_file\"
|
|
55
|
+
printf '\033]6;1;bg;red;brightness;130\a' > /dev/tty 2>/dev/null
|
|
56
|
+
printf '\033]6;1;bg;green;brightness;130\a' > /dev/tty 2>/dev/null
|
|
57
|
+
printf '\033]6;1;bg;blue;brightness;130\a' > /dev/tty 2>/dev/null
|
|
58
|
+
fi
|
|
59
|
+
" </dev/null >/dev/null 2>&1 &
|
|
60
|
+
;;
|
|
61
|
+
red)
|
|
62
|
+
echo "red" > "$STATE_FILE" 2>/dev/null
|
|
63
|
+
set_tab_color 220 0 0
|
|
64
|
+
printf "\a" > /dev/tty 2>/dev/null
|
|
65
|
+
;;
|
|
66
|
+
reset)
|
|
67
|
+
rm -f "$STATE_FILE" 2>/dev/null
|
|
68
|
+
printf "\033]6;1;bg;*;default\a" > /dev/tty 2>/dev/null
|
|
69
|
+
;;
|
|
70
|
+
esac
|