configure-precommit 1.0.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/lib/sensor.sh ADDED
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env bash
2
+ # Phase 1: Contextual Sensor
3
+ # Gathers repository metadata for LLM analysis
4
+
5
+ set -euo pipefail
6
+
7
+ # Directories to exclude from file tree
8
+ EXCLUDED_DIRS=(
9
+ ".git"
10
+ # JavaScript/Node
11
+ "node_modules"
12
+ ".next"
13
+ ".nuxt"
14
+ "coverage"
15
+ # Python
16
+ "venv"
17
+ ".venv"
18
+ "env"
19
+ ".env"
20
+ "__pycache__"
21
+ ".mypy_cache"
22
+ ".pytest_cache"
23
+ ".ruff_cache"
24
+ ".tox"
25
+ "*.egg-info"
26
+ # Go
27
+ "vendor"
28
+ # Java/Build
29
+ "target"
30
+ "build"
31
+ "dist"
32
+ ".gradle"
33
+ # Infrastructure
34
+ ".terraform"
35
+ ".terragrunt-cache"
36
+ )
37
+
38
+ # Manifest files to harvest
39
+ MANIFEST_PATTERNS=(
40
+ # Python
41
+ "pyproject.toml"
42
+ "requirements.txt"
43
+ "setup.py"
44
+ "setup.cfg"
45
+ "Pipfile"
46
+ # JavaScript/TypeScript
47
+ "package.json"
48
+ "tsconfig.json"
49
+ ".eslintrc"
50
+ ".eslintrc.js"
51
+ ".eslintrc.json"
52
+ ".eslintrc.yml"
53
+ ".prettierrc"
54
+ ".prettierrc.js"
55
+ ".prettierrc.json"
56
+ ".prettierrc.yml"
57
+ # Go
58
+ "go.mod"
59
+ "go.sum"
60
+ # Java
61
+ "pom.xml"
62
+ "build.gradle"
63
+ "build.gradle.kts"
64
+ "settings.gradle"
65
+ # Infrastructure
66
+ "Dockerfile"
67
+ "docker-compose.yml"
68
+ "docker-compose.yaml"
69
+ "*.tf"
70
+ "terragrunt.hcl"
71
+ # Config
72
+ ".pre-commit-config.yaml"
73
+ ".yamllint"
74
+ ".yamllint.yml"
75
+ ".yamllint.yaml"
76
+ )
77
+
78
+ # Build find exclusion arguments
79
+ build_exclude_args() {
80
+ local args=""
81
+ for dir in "${EXCLUDED_DIRS[@]}"; do
82
+ args="$args -name '$dir' -prune -o"
83
+ done
84
+ echo "$args"
85
+ }
86
+
87
+ # Generate file tree as JSON array
88
+ generate_file_tree() {
89
+ local repo_root="$1"
90
+
91
+ # Change to repo root so paths are relative
92
+ cd "$repo_root"
93
+
94
+ # Find all files, exclude directories, output as JSON array
95
+ echo "["
96
+ local first=true
97
+ while IFS= read -r file; do
98
+ # Skip empty lines
99
+ [[ -z "$file" ]] && continue
100
+
101
+ # Remove leading ./
102
+ file="${file#./}"
103
+
104
+ # Skip if file is in excluded directory
105
+ local skip=false
106
+ for dir in "${EXCLUDED_DIRS[@]}"; do
107
+ if [[ "$file" == *"/$dir/"* ]] || [[ "$file" == "$dir/"* ]] || [[ "$file" == *"/$dir" ]] || [[ "$file" == ".git/"* ]]; then
108
+ skip=true
109
+ break
110
+ fi
111
+ done
112
+ [[ "$skip" == true ]] && continue
113
+
114
+ if [[ "$first" == true ]]; then
115
+ first=false
116
+ else
117
+ echo ","
118
+ fi
119
+ # Escape quotes and backslashes for JSON
120
+ file="${file//\\/\\\\}"
121
+ file="${file//\"/\\\"}"
122
+ printf ' "%s"' "$file"
123
+ done < <(find . -type f 2>/dev/null | sort)
124
+ echo ""
125
+ echo " ]"
126
+ }
127
+
128
+ # Harvest manifest files and their contents
129
+ harvest_manifests() {
130
+ local repo_root="$1"
131
+
132
+ echo "{"
133
+ local first=true
134
+
135
+ for pattern in "${MANIFEST_PATTERNS[@]}"; do
136
+ # Find files matching pattern
137
+ while IFS= read -r file; do
138
+ [[ -z "$file" ]] && continue
139
+ [[ ! -f "$file" ]] && continue
140
+
141
+ # Skip if in excluded directory
142
+ local skip=false
143
+ for dir in "${EXCLUDED_DIRS[@]}"; do
144
+ if [[ "$file" == *"/$dir/"* ]] || [[ "$file" == "./$dir/"* ]]; then
145
+ skip=true
146
+ break
147
+ fi
148
+ done
149
+ [[ "$skip" == true ]] && continue
150
+
151
+ # Get relative path
152
+ local rel_path="${file#"$repo_root"/}"
153
+ rel_path="${rel_path#./}"
154
+
155
+ # Read file content (limit to 50KB to avoid huge manifests)
156
+ local content
157
+ content=$(head -c 51200 "$file" 2>/dev/null || echo "")
158
+
159
+ # Escape for JSON
160
+ content="${content//\\/\\\\}"
161
+ content="${content//\"/\\\"}"
162
+ content="${content//$'\n'/\\n}"
163
+ content="${content//$'\r'/\\r}"
164
+ content="${content//$'\t'/\\t}"
165
+
166
+ rel_path="${rel_path//\\/\\\\}"
167
+ rel_path="${rel_path//\"/\\\"}"
168
+
169
+ if [[ "$first" == true ]]; then
170
+ first=false
171
+ else
172
+ echo ","
173
+ fi
174
+ printf ' "%s": "%s"' "$rel_path" "$content"
175
+ done < <(find "$repo_root" -name "$pattern" -type f 2>/dev/null)
176
+ done
177
+
178
+ echo ""
179
+ echo " }"
180
+ }
181
+
182
+ # Check if .pre-commit-config.yaml exists
183
+ check_existing_precommit() {
184
+ local repo_root="$1"
185
+ if [[ -f "$repo_root/.pre-commit-config.yaml" ]]; then
186
+ echo "true"
187
+ else
188
+ echo "false"
189
+ fi
190
+ }
191
+
192
+ # Main function: Generate complete repo context
193
+ generate_repo_context() {
194
+ local repo_root="${1:-.}"
195
+
196
+ # Resolve to absolute path
197
+ repo_root="$(cd "$repo_root" && pwd)"
198
+
199
+ # Verify it's a git repo
200
+ if [[ ! -d "$repo_root/.git" ]]; then
201
+ echo "ERROR: Not a git repository: $repo_root" >&2
202
+ return 1
203
+ fi
204
+
205
+ local existing_precommit
206
+ existing_precommit=$(check_existing_precommit "$repo_root")
207
+
208
+ # Generate JSON output
209
+ cat <<EOF
210
+ {
211
+ "repo_root": "$repo_root",
212
+ "existing_precommit": $existing_precommit,
213
+ "file_tree": $(generate_file_tree "$repo_root"),
214
+ "manifests": $(harvest_manifests "$repo_root")
215
+ }
216
+ EOF
217
+ }
218
+
219
+ # If run directly (not sourced), execute main function
220
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
221
+ generate_repo_context "$@"
222
+ fi
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env bash
2
+ # Phase 4: Verifier + Self-Healing Loop
3
+ # Runs hooks and triggers debugger on config errors
4
+
5
+ set -euo pipefail
6
+
7
+ # Colors for output
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ NC='\033[0m' # No Color
13
+
14
+ # Logging functions - write to stderr to avoid polluting stdout
15
+ log_info() {
16
+ echo -e "${GREEN}[INFO]${NC} $1" >&2
17
+ }
18
+
19
+ log_warn() {
20
+ echo -e "${YELLOW}[WARN]${NC} $1" >&2
21
+ }
22
+
23
+ log_error() {
24
+ echo -e "${RED}[ERROR]${NC} $1" >&2
25
+ }
26
+
27
+ log_debug() {
28
+ if [[ "${DEBUG:-false}" == "true" ]]; then
29
+ echo -e "${BLUE}[DEBUG]${NC} $1" >&2
30
+ fi
31
+ }
32
+
33
+ # Maximum retry attempts for self-healing
34
+ MAX_RETRIES="${PRECOMMIT_MAX_RETRIES:-3}"
35
+
36
+ # Function to invoke debugger agent (to be implemented by integration layer)
37
+ # This is a placeholder - the main script will override this
38
+ invoke_debugger() {
39
+ local output="$1"
40
+ local exit_code="$2"
41
+ local config_content="$3"
42
+
43
+ # Default implementation - should be overridden
44
+ echo "ERROR: invoke_debugger not implemented"
45
+ return 1
46
+ }
47
+
48
+ # Parse debugger response
49
+ parse_debugger_response() {
50
+ local response="$1"
51
+
52
+ # Extract category (first line)
53
+ local category
54
+ category=$(echo "$response" | head -1 | tr -d '[:space:]')
55
+
56
+ # Extract explanation (second line)
57
+ local explanation
58
+ explanation=$(echo "$response" | sed -n '2p')
59
+
60
+ # Extract payload (line 3 onwards)
61
+ local payload
62
+ payload=$(echo "$response" | tail -n +3)
63
+
64
+ echo "$category"
65
+ echo "$explanation"
66
+ echo "$payload"
67
+ }
68
+
69
+ # Run pre-commit and capture output
70
+ run_precommit() {
71
+ local output
72
+ local exit_code
73
+
74
+ log_info "Running pre-commit on all files..."
75
+
76
+ # Capture both stdout and stderr
77
+ output=$(pre-commit run --all-files 2>&1) || true
78
+ exit_code=${PIPESTATUS[0]}
79
+
80
+ echo "$output"
81
+ return "$exit_code"
82
+ }
83
+
84
+ # Analyze if failure is likely a config error vs code issue
85
+ # Returns: "config_error", "code_issue", "network_error", or "unknown"
86
+ quick_classify_error() {
87
+ local output="$1"
88
+
89
+ # Config/setup errors
90
+ if echo "$output" | grep -qiE "InvalidConfigError|InvalidManifestError|RepositoryNotFoundError|does not provide|An error has occurred|Unexpected key|YAMLError|yaml.scanner|yaml.parser"; then
91
+ echo "config_error"
92
+ return
93
+ fi
94
+
95
+ # Missing dependencies
96
+ if echo "$output" | grep -qiE "Executable.*not found|command not found|FileNotFoundError|No such file or directory.*bin/"; then
97
+ echo "missing_dep"
98
+ return
99
+ fi
100
+
101
+ # Network errors
102
+ if echo "$output" | grep -qiE "Connection refused|timeout|rate limit|Unable to clone|fatal: unable to access"; then
103
+ echo "network_error"
104
+ return
105
+ fi
106
+
107
+ # If we see file paths with line numbers, likely code issues found
108
+ if echo "$output" | grep -qE "^[a-zA-Z0-9_./-]+:[0-9]+:"; then
109
+ echo "code_issue"
110
+ return
111
+ fi
112
+
113
+ # Check for common "hook found issues" patterns
114
+ if echo "$output" | grep -qiE "Fixed|Modified|would reformat|error:|warning:|W[0-9]+:|E[0-9]+:"; then
115
+ echo "code_issue"
116
+ return
117
+ fi
118
+
119
+ echo "unknown"
120
+ }
121
+
122
+ # Main verification loop
123
+ run_verification_loop() {
124
+ local attempt=0
125
+ local output
126
+ local exit_code
127
+ local category
128
+ local explanation
129
+ local new_config
130
+
131
+ while [[ $attempt -lt $MAX_RETRIES ]]; do
132
+ log_info "Verification attempt $((attempt + 1)) of $MAX_RETRIES"
133
+
134
+ # Run pre-commit
135
+ output=$(run_precommit) && exit_code=0 || exit_code=$?
136
+
137
+ log_debug "Exit code: $exit_code"
138
+
139
+ # Success - all hooks passed
140
+ if [[ $exit_code -eq 0 ]]; then
141
+ log_info "All hooks passed successfully!"
142
+ echo "$output" >&2
143
+ return 0
144
+ fi
145
+
146
+ # Quick classification to avoid unnecessary LLM calls
147
+ local quick_class
148
+ quick_class=$(quick_classify_error "$output")
149
+ log_debug "Quick classification: $quick_class"
150
+
151
+ case "$quick_class" in
152
+ "code_issue")
153
+ # Hooks are working - they found issues in code
154
+ log_info "Hooks are working correctly. Found issues in code:"
155
+ echo "$output" >&2
156
+ echo "" >&2
157
+ log_warn "Your code has issues that the hooks detected. This is expected behavior."
158
+ log_info "Fix the issues above and run 'git commit' to re-run hooks."
159
+ return 0 # This is success for our setup
160
+ ;;
161
+ "network_error")
162
+ log_error "Network error detected. Please check your internet connection."
163
+ echo "$output" >&2
164
+ return 1
165
+ ;;
166
+ "config_error"|"missing_dep"|"unknown")
167
+ # Need to invoke debugger
168
+ log_warn "Potential configuration issue detected. Invoking debugger..."
169
+ ;;
170
+ esac
171
+
172
+ # Read current config
173
+ local config_content
174
+ config_content=$(cat .pre-commit-config.yaml)
175
+
176
+ # Invoke debugger agent
177
+ local debugger_response
178
+ if ! debugger_response=$(invoke_debugger "$output" "$exit_code" "$config_content"); then
179
+ log_error "Debugger agent failed"
180
+ return 1
181
+ fi
182
+
183
+ # Parse response
184
+ category=$(echo "$debugger_response" | head -1 | tr -d '[:space:]')
185
+ explanation=$(echo "$debugger_response" | sed -n '2p')
186
+ new_config=$(echo "$debugger_response" | tail -n +3)
187
+
188
+ log_info "Debugger classification: $category"
189
+ log_info "Explanation: $explanation"
190
+
191
+ case "$category" in
192
+ A|B)
193
+ log_info "Applying corrected configuration..."
194
+ echo "$new_config" > .pre-commit-config.yaml
195
+
196
+ # Re-run autoupdate if versions changed
197
+ if grep -q "rev: AUTOUPDATE" .pre-commit-config.yaml 2>/dev/null; then
198
+ sed -i.bak 's/rev: AUTOUPDATE/rev: v0.0.1/g' .pre-commit-config.yaml 2>/dev/null || \
199
+ sed -i '' 's/rev: AUTOUPDATE/rev: v0.0.1/g' .pre-commit-config.yaml
200
+ rm -f .pre-commit-config.yaml.bak
201
+ pre-commit autoupdate 2>/dev/null || true
202
+ fi
203
+
204
+ # Reinstall hooks with new config
205
+ pre-commit install-hooks 2>/dev/null || true
206
+
207
+ ((attempt++))
208
+ ;;
209
+ C)
210
+ log_info "Hooks are working correctly. Code has issues to address."
211
+ echo "$output" >&2
212
+ return 0
213
+ ;;
214
+ D)
215
+ log_error "Network error. Please check connectivity and retry."
216
+ return 1
217
+ ;;
218
+ *)
219
+ log_error "Unknown debugger response: $category"
220
+ log_error "Manual intervention required."
221
+ echo "$output" >&2
222
+ return 1
223
+ ;;
224
+ esac
225
+ done
226
+
227
+ # Max retries exceeded
228
+ log_error "Could not fix configuration after $MAX_RETRIES attempts"
229
+ log_error "Last error output:"
230
+ echo "$output" >&2
231
+ return 1
232
+ }
233
+
234
+ # Summary generation - all output to stderr
235
+ generate_summary() {
236
+ local repo_root="${1:-.}"
237
+
238
+ {
239
+ echo ""
240
+ echo "========================================"
241
+ echo " PRE-COMMIT SETUP COMPLETE "
242
+ echo "========================================"
243
+ echo ""
244
+ echo "Repository: $repo_root"
245
+ echo "Config: .pre-commit-config.yaml"
246
+ echo ""
247
+ echo "INSTALLED HOOKS:"
248
+
249
+ # Parse installed hooks from config
250
+ if [[ -f .pre-commit-config.yaml ]]; then
251
+ grep -E "^\s+- id:" .pre-commit-config.yaml | sed 's/.*- id: / - /' || true
252
+ fi
253
+
254
+ echo ""
255
+ echo "NEXT STEPS:"
256
+ echo " 1. Review any issues found above"
257
+ echo " 2. Fix issues and stage your changes: git add ."
258
+ echo " 3. Commit: git commit -m 'your message'"
259
+ echo " 4. Hooks will run automatically on each commit"
260
+ echo ""
261
+ echo "USEFUL COMMANDS:"
262
+ echo " pre-commit run --all-files # Run all hooks manually"
263
+ echo " pre-commit autoupdate # Update hook versions"
264
+ echo " pre-commit run <hook-id> # Run specific hook"
265
+ echo ""
266
+ echo "========================================"
267
+ } >&2
268
+ }
269
+
270
+ # If run directly (not sourced)
271
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
272
+ echo "This script is meant to be sourced, not run directly."
273
+ echo "It provides: run_verification_loop, generate_summary"
274
+ exit 1
275
+ fi
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "configure-precommit",
3
+ "version": "1.0.0",
4
+ "description": "Agentic pre-commit setup - automatically configures pre-commit hooks for any repository using AI",
5
+ "keywords": [
6
+ "pre-commit",
7
+ "git-hooks",
8
+ "linting",
9
+ "code-quality",
10
+ "devops",
11
+ "automation",
12
+ "ai",
13
+ "llm",
14
+ "claude",
15
+ "openai"
16
+ ],
17
+ "author": "Aviv Kaplan",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/avivkaplan/configure-precommit.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/avivkaplan/configure-precommit/issues"
25
+ },
26
+ "homepage": "https://github.com/avivkaplan/configure-precommit#readme",
27
+ "bin": {
28
+ "configure-precommit": "./bin/configure-precommit"
29
+ },
30
+ "files": [
31
+ "bin/",
32
+ "lib/",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "engines": {
37
+ "node": ">=14.0.0"
38
+ },
39
+ "os": [
40
+ "darwin",
41
+ "linux"
42
+ ],
43
+ "scripts": {
44
+ "test": "./tests/run_tests.sh",
45
+ "lint": "shellcheck lib/*.sh bin/configure-precommit setup-precommit.sh"
46
+ }
47
+ }