@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,104 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
# Field Validators Module
|
|
5
|
+
#
|
|
6
|
+
# Provides validation functions for individual subagent metadata fields
|
|
7
|
+
# (name, description, version, tools, tags).
|
|
8
|
+
# Extracted from subagent-validator.sh.
|
|
9
|
+
|
|
10
|
+
# Include guard
|
|
11
|
+
[[ -n "${_FIELD_VALIDATORS_LOADED:-}" ]] && return 0
|
|
12
|
+
_FIELD_VALIDATORS_LOADED=1
|
|
13
|
+
|
|
14
|
+
# Source required modules
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
source "$SCRIPT_DIR/config-constants.sh"
|
|
17
|
+
source "$SCRIPT_DIR/error-handler.sh"
|
|
18
|
+
|
|
19
|
+
##################################
|
|
20
|
+
# Field Validation Functions
|
|
21
|
+
##################################
|
|
22
|
+
|
|
23
|
+
validate_subagent_name_field() {
|
|
24
|
+
local name="$1"
|
|
25
|
+
|
|
26
|
+
if [[ -z "$name" ]]; then
|
|
27
|
+
log_error "Subagent name cannot be empty"
|
|
28
|
+
return $EXIT_VALIDATION_FAILED
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if [[ ${#name} -gt $MAX_SUBAGENT_NAME_LENGTH ]]; then
|
|
32
|
+
log_error "Subagent name too long: ${#name} chars (max: $MAX_SUBAGENT_NAME_LENGTH)"
|
|
33
|
+
return $EXIT_VALIDATION_FAILED
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [[ ! "$name" =~ $SUBAGENT_NAME_PATTERN ]]; then
|
|
37
|
+
log_error "Invalid subagent name format: $name"
|
|
38
|
+
return $EXIT_VALIDATION_FAILED
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
return $EXIT_SUCCESS
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
validate_description_field() {
|
|
45
|
+
local description="$1"
|
|
46
|
+
|
|
47
|
+
if [[ -z "$description" ]]; then
|
|
48
|
+
log_error "Description cannot be empty"
|
|
49
|
+
return $EXIT_VALIDATION_FAILED
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [[ ${#description} -lt $MIN_DESCRIPTION_LENGTH ]]; then
|
|
53
|
+
log_error "Description too short: ${#description} chars (min: $MIN_DESCRIPTION_LENGTH)"
|
|
54
|
+
return $EXIT_VALIDATION_FAILED
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [[ ${#description} -gt $MAX_DESCRIPTION_LENGTH ]]; then
|
|
58
|
+
log_error "Description too long: ${#description} chars (max: $MAX_DESCRIPTION_LENGTH)"
|
|
59
|
+
return $EXIT_VALIDATION_FAILED
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
return $EXIT_SUCCESS
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
validate_version_field() {
|
|
66
|
+
local version="$1"
|
|
67
|
+
|
|
68
|
+
# Semantic versioning pattern
|
|
69
|
+
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
|
|
70
|
+
return $EXIT_SUCCESS
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Simple versioning pattern
|
|
74
|
+
if [[ "$version" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
|
|
75
|
+
return $EXIT_SUCCESS
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
log_error "Invalid version format: $version"
|
|
79
|
+
return $EXIT_VALIDATION_FAILED
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
validate_tools_field() {
|
|
83
|
+
local tools="$1"
|
|
84
|
+
|
|
85
|
+
# Tools can be comma-separated or a single word
|
|
86
|
+
if [[ "$tools" =~ ^[a-zA-Z][a-zA-Z0-9_]*([[:space:]]*,[[:space:]]*[a-zA-Z][a-zA-Z0-9_]*)*$ ]]; then
|
|
87
|
+
return $EXIT_SUCCESS
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
log_error "Invalid tools format: $tools"
|
|
91
|
+
return $EXIT_VALIDATION_FAILED
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
validate_tags_field() {
|
|
95
|
+
local tags="$1"
|
|
96
|
+
|
|
97
|
+
# Tags can be array format or comma-separated
|
|
98
|
+
if [[ "$tags" =~ ^\[.*\]$ ]] || [[ "$tags" =~ ^[a-zA-Z][a-zA-Z0-9_,-\s]*$ ]]; then
|
|
99
|
+
return $EXIT_SUCCESS
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
log_error "Invalid tags format: $tags"
|
|
103
|
+
return $EXIT_VALIDATION_FAILED
|
|
104
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
# File Utilities Module for Subagent-Hook Integration
|
|
5
|
+
#
|
|
6
|
+
# This module provides standardized file operations with proper error handling,
|
|
7
|
+
# security checks, and logging for the subagent-hook integration system.
|
|
8
|
+
|
|
9
|
+
# Include guard
|
|
10
|
+
[[ -n "${_FILE_UTILS_LOADED:-}" ]] && return 0
|
|
11
|
+
_FILE_UTILS_LOADED=1
|
|
12
|
+
|
|
13
|
+
# Source required modules
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
source "$SCRIPT_DIR/config-constants.sh"
|
|
16
|
+
|
|
17
|
+
##################################
|
|
18
|
+
# JSON Utilities
|
|
19
|
+
##################################
|
|
20
|
+
|
|
21
|
+
json_escape() {
|
|
22
|
+
local input="$1"
|
|
23
|
+
input="${input//\\/\\\\}"
|
|
24
|
+
input="${input//\"/\\\"}"
|
|
25
|
+
input="${input//$'\n'/\\n}"
|
|
26
|
+
input="${input//$'\r'/\\r}"
|
|
27
|
+
input="${input//$'\t'/\\t}"
|
|
28
|
+
printf '%s' "$input"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
##################################
|
|
32
|
+
# Directory Operations
|
|
33
|
+
##################################
|
|
34
|
+
|
|
35
|
+
ensure_directory_exists() {
|
|
36
|
+
local dir_path="$1"
|
|
37
|
+
local permissions="${2:-$SECURE_DIR_PERMISSIONS}"
|
|
38
|
+
|
|
39
|
+
if [[ -z "$dir_path" ]]; then
|
|
40
|
+
return $EXIT_GENERAL_ERROR
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [[ ! -d "$dir_path" ]]; then
|
|
44
|
+
if ! mkdir -p "$dir_path" 2>/dev/null; then
|
|
45
|
+
return $EXIT_GENERAL_ERROR
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if ! chmod "$permissions" "$dir_path" 2>/dev/null; then
|
|
50
|
+
return $EXIT_GENERAL_ERROR
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
return $EXIT_SUCCESS
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ensure_required_directories() {
|
|
57
|
+
ensure_directory_exists "$CLAUDE_BASE_DIR" "$SECURE_DIR_PERMISSIONS" || return $?
|
|
58
|
+
ensure_directory_exists "$SUBAGENTS_DIR" "$SECURE_DIR_PERMISSIONS" || return $?
|
|
59
|
+
ensure_directory_exists "$LOGS_DIR" "$SECURE_DIR_PERMISSIONS" || return $?
|
|
60
|
+
|
|
61
|
+
return $EXIT_SUCCESS
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
##################################
|
|
65
|
+
# File Reading Operations
|
|
66
|
+
##################################
|
|
67
|
+
|
|
68
|
+
read_file_safely() {
|
|
69
|
+
local file_path="$1"
|
|
70
|
+
|
|
71
|
+
if [[ -z "$file_path" ]]; then
|
|
72
|
+
return $EXIT_GENERAL_ERROR
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [[ ! -f "$file_path" ]]; then
|
|
76
|
+
return $EXIT_GENERAL_ERROR
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [[ ! -r "$file_path" ]]; then
|
|
80
|
+
return $EXIT_GENERAL_ERROR
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Check if file is binary (avoid reading binary files)
|
|
84
|
+
if file "$file_path" 2>/dev/null | grep -q "binary"; then
|
|
85
|
+
return $EXIT_GENERAL_ERROR
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
cat "$file_path" 2>/dev/null
|
|
89
|
+
return $?
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
read_file_with_size_limit() {
|
|
93
|
+
local file_path="$1"
|
|
94
|
+
local max_size="${2:-$MAX_CONTEXT_FILE_SIZE}"
|
|
95
|
+
|
|
96
|
+
if [[ -z "$file_path" ]]; then
|
|
97
|
+
return $EXIT_GENERAL_ERROR
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Check file size
|
|
101
|
+
if [[ -f "$file_path" ]]; then
|
|
102
|
+
local file_size
|
|
103
|
+
file_size=$(stat -f%z "$file_path" 2>/dev/null || stat -c%s "$file_path" 2>/dev/null)
|
|
104
|
+
|
|
105
|
+
if [[ "$file_size" -gt "$max_size" ]]; then
|
|
106
|
+
return $EXIT_GENERAL_ERROR
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
read_file_safely "$file_path"
|
|
111
|
+
return $?
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
##################################
|
|
115
|
+
# File Writing Operations
|
|
116
|
+
##################################
|
|
117
|
+
|
|
118
|
+
write_file_safely() {
|
|
119
|
+
local file_path="$1"
|
|
120
|
+
local content="$2"
|
|
121
|
+
local permissions="${3:-$SECURE_FILE_PERMISSIONS}"
|
|
122
|
+
|
|
123
|
+
if [[ -z "$file_path" ]]; then
|
|
124
|
+
return $EXIT_GENERAL_ERROR
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Ensure parent directory exists
|
|
128
|
+
local parent_dir
|
|
129
|
+
parent_dir="$(dirname "$file_path")"
|
|
130
|
+
if ! ensure_directory_exists "$parent_dir"; then
|
|
131
|
+
return $EXIT_GENERAL_ERROR
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Write content
|
|
135
|
+
if ! echo "$content" > "$file_path" 2>/dev/null; then
|
|
136
|
+
return $EXIT_GENERAL_ERROR
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Set permissions
|
|
140
|
+
if ! chmod "$permissions" "$file_path" 2>/dev/null; then
|
|
141
|
+
return $EXIT_GENERAL_ERROR
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
return $EXIT_SUCCESS
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
create_temp_file() {
|
|
148
|
+
local prefix="${1:-$CONTEXT_FILE_PREFIX}"
|
|
149
|
+
local _suffix="${2:-}" # kept for API compat; mktemp adds randomness
|
|
150
|
+
|
|
151
|
+
# Use mktemp for unpredictable temp file names
|
|
152
|
+
local temp_file
|
|
153
|
+
temp_file=$(mktemp "${prefix}-XXXXXX") || return $EXIT_GENERAL_ERROR
|
|
154
|
+
|
|
155
|
+
if ! chmod "$SECURE_FILE_PERMISSIONS" "$temp_file" 2>/dev/null; then
|
|
156
|
+
rm -f "$temp_file" 2>/dev/null
|
|
157
|
+
return $EXIT_GENERAL_ERROR
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
echo "$temp_file"
|
|
161
|
+
return $EXIT_SUCCESS
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
write_context_file() {
|
|
165
|
+
local context_data="$1"
|
|
166
|
+
local temp_file="$2"
|
|
167
|
+
|
|
168
|
+
if [[ -z "$context_data" ]] || [[ -z "$temp_file" ]]; then
|
|
169
|
+
return $EXIT_GENERAL_ERROR
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
write_file_safely "$temp_file" "$context_data" "$SECURE_FILE_PERMISSIONS"
|
|
173
|
+
return $?
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
##################################
|
|
177
|
+
# File Validation Operations
|
|
178
|
+
##################################
|
|
179
|
+
|
|
180
|
+
file_exists_and_readable() {
|
|
181
|
+
local file_path="$1"
|
|
182
|
+
|
|
183
|
+
[[ -n "$file_path" ]] && [[ -f "$file_path" ]] && [[ -r "$file_path" ]]
|
|
184
|
+
return $?
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
file_has_extension() {
|
|
188
|
+
local file_path="$1"
|
|
189
|
+
local expected_extension="$2"
|
|
190
|
+
|
|
191
|
+
if [[ -z "$file_path" ]] || [[ -z "$expected_extension" ]]; then
|
|
192
|
+
return $EXIT_GENERAL_ERROR
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
[[ "$file_path" == *"$expected_extension" ]]
|
|
196
|
+
return $?
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
is_valid_subagent_file() {
|
|
200
|
+
local file_path="$1"
|
|
201
|
+
|
|
202
|
+
# Check file exists and is readable
|
|
203
|
+
if ! file_exists_and_readable "$file_path"; then
|
|
204
|
+
return $EXIT_GENERAL_ERROR
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Check file extension
|
|
208
|
+
if ! file_has_extension "$file_path" "$SUBAGENT_FILE_EXTENSION"; then
|
|
209
|
+
return $EXIT_GENERAL_ERROR
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Check file size
|
|
213
|
+
local file_content
|
|
214
|
+
if ! file_content=$(read_file_with_size_limit "$file_path"); then
|
|
215
|
+
return $EXIT_GENERAL_ERROR
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Basic YAML frontmatter validation
|
|
219
|
+
if ! echo "$file_content" | head -1 | grep -q "$YAML_FRONTMATTER_START"; then
|
|
220
|
+
return $EXIT_GENERAL_ERROR
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
return $EXIT_SUCCESS
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
##################################
|
|
227
|
+
# File Cleanup Operations
|
|
228
|
+
##################################
|
|
229
|
+
|
|
230
|
+
cleanup_temp_files() {
|
|
231
|
+
local pattern="${1:-$TEMP_FILE_PATTERN}"
|
|
232
|
+
local max_age_minutes="${2:-60}"
|
|
233
|
+
|
|
234
|
+
# Find and remove old temp files
|
|
235
|
+
find /tmp -name "$pattern" -type f -mmin +$max_age_minutes -delete 2>/dev/null || true
|
|
236
|
+
|
|
237
|
+
return $EXIT_SUCCESS
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
cleanup_specific_temp_file() {
|
|
241
|
+
local temp_file="$1"
|
|
242
|
+
|
|
243
|
+
if [[ -n "$temp_file" ]] && [[ -f "$temp_file" ]]; then
|
|
244
|
+
rm -f "$temp_file" 2>/dev/null || true
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
return $EXIT_SUCCESS
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
##################################
|
|
251
|
+
# File Path Operations
|
|
252
|
+
##################################
|
|
253
|
+
|
|
254
|
+
resolve_subagent_path() {
|
|
255
|
+
local subagent_name="$1"
|
|
256
|
+
|
|
257
|
+
if [[ -z "$subagent_name" ]]; then
|
|
258
|
+
return $EXIT_GENERAL_ERROR
|
|
259
|
+
fi
|
|
260
|
+
|
|
261
|
+
local subagent_file="${subagent_name}${SUBAGENT_FILE_EXTENSION}"
|
|
262
|
+
|
|
263
|
+
# Check project-level subagents first (higher priority)
|
|
264
|
+
local project_path="${PROJECT_SUBAGENTS_DIR}/${subagent_file}"
|
|
265
|
+
if file_exists_and_readable "$project_path"; then
|
|
266
|
+
echo "$project_path"
|
|
267
|
+
return $EXIT_SUCCESS
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# Check user-level subagents
|
|
271
|
+
local user_path="${SUBAGENTS_DIR}/${subagent_file}"
|
|
272
|
+
if file_exists_and_readable "$user_path"; then
|
|
273
|
+
echo "$user_path"
|
|
274
|
+
return $EXIT_SUCCESS
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
return $EXIT_SUBAGENT_NOT_FOUND
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
get_absolute_path() {
|
|
281
|
+
local path="$1"
|
|
282
|
+
|
|
283
|
+
if [[ -z "$path" ]]; then
|
|
284
|
+
return $EXIT_GENERAL_ERROR
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
# Convert to absolute path
|
|
288
|
+
if [[ "$path" = /* ]]; then
|
|
289
|
+
echo "$path"
|
|
290
|
+
else
|
|
291
|
+
echo "$(pwd)/$path"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
return $EXIT_SUCCESS
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
##################################
|
|
298
|
+
# File Permission Operations
|
|
299
|
+
##################################
|
|
300
|
+
|
|
301
|
+
validate_file_permissions() {
|
|
302
|
+
local file_path="$1"
|
|
303
|
+
|
|
304
|
+
if [[ -z "$file_path" ]] || [[ ! -f "$file_path" ]]; then
|
|
305
|
+
return $EXIT_GENERAL_ERROR
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# Check if file is world-writable (security risk)
|
|
309
|
+
local permissions
|
|
310
|
+
permissions=$(stat -f%A "$file_path" 2>/dev/null || stat -c%a "$file_path" 2>/dev/null)
|
|
311
|
+
|
|
312
|
+
if [[ -z "$permissions" ]]; then
|
|
313
|
+
return $EXIT_GENERAL_ERROR
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# Check if world-writable (last digit should not be writable)
|
|
317
|
+
local world_perms=$((permissions % 10))
|
|
318
|
+
if [[ $((world_perms & 2)) -eq 2 ]]; then
|
|
319
|
+
return $EXIT_SECURITY_VIOLATION
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
return $EXIT_SUCCESS
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
set_secure_permissions() {
|
|
326
|
+
local file_path="$1"
|
|
327
|
+
local permissions="${2:-$SECURE_FILE_PERMISSIONS}"
|
|
328
|
+
|
|
329
|
+
if [[ -z "$file_path" ]] || [[ ! -f "$file_path" ]]; then
|
|
330
|
+
return $EXIT_GENERAL_ERROR
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
chmod "$permissions" "$file_path" 2>/dev/null
|
|
334
|
+
return $?
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
##################################
|
|
338
|
+
# Logging File Operations
|
|
339
|
+
##################################
|
|
340
|
+
|
|
341
|
+
ensure_log_files() {
|
|
342
|
+
# Create log files with secure permissions if they don't exist
|
|
343
|
+
if [[ ! -f "$LOG_FILE" ]]; then
|
|
344
|
+
write_file_safely "$LOG_FILE" "" "$SECURE_FILE_PERMISSIONS" || return $?
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
if [[ ! -f "$VIOLATION_LOG" ]]; then
|
|
348
|
+
write_file_safely "$VIOLATION_LOG" "" "$SECURE_FILE_PERMISSIONS" || return $?
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
return $EXIT_SUCCESS
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
##################################
|
|
355
|
+
# Path Validation (Security)
|
|
356
|
+
##################################
|
|
357
|
+
|
|
358
|
+
validate_path_safety() {
|
|
359
|
+
local path="$1"
|
|
360
|
+
|
|
361
|
+
if [[ -z "$path" ]]; then
|
|
362
|
+
return $EXIT_GENERAL_ERROR
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
# Resolve to canonical path to prevent traversal via symlinks/..
|
|
366
|
+
local resolved
|
|
367
|
+
resolved=$(realpath -m "$path" 2>/dev/null) || resolved="$path"
|
|
368
|
+
|
|
369
|
+
# Check for path traversal in the resolved path
|
|
370
|
+
if [[ "$resolved" == *".."* ]] || [[ "$resolved" == *"/./"* ]]; then
|
|
371
|
+
return $EXIT_SECURITY_VIOLATION
|
|
372
|
+
fi
|
|
373
|
+
|
|
374
|
+
# Check for absolute paths outside allowed directories
|
|
375
|
+
if [[ "$resolved" = /* ]]; then
|
|
376
|
+
# Allow paths within Claude base directory
|
|
377
|
+
if [[ "$resolved" == "$CLAUDE_BASE_DIR"* ]]; then
|
|
378
|
+
return $EXIT_SUCCESS
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
# Allow paths within project directory
|
|
382
|
+
local cwd
|
|
383
|
+
cwd=$(pwd)
|
|
384
|
+
if [[ "$resolved" == "$cwd"* ]]; then
|
|
385
|
+
return $EXIT_SUCCESS
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# Allow temp files (TMPDIR-aware)
|
|
389
|
+
local tmpdir="${TMPDIR:-/tmp}"
|
|
390
|
+
if [[ "$resolved" == "${tmpdir}/claude-subagent"* ]]; then
|
|
391
|
+
return $EXIT_SUCCESS
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
return $EXIT_SECURITY_VIOLATION
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
return $EXIT_SUCCESS
|
|
398
|
+
}
|