Skip to content

Commit 431dd86

Browse files
committed
cc updates
1 parent 67b579f commit 431dd86

10 files changed

+3167
-1
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
#!/bin/bash
2+
# Claude Code Integration Hook for git-agentic
3+
# This script is called by Claude Code hooks to track AI contributions
4+
5+
set -e # Exit on any error
6+
7+
# Configuration
8+
CLAUDE_AGENT_ID="${CLAUDE_AGENT_ID:-claude}" # Default to 'claude' agent
9+
GIT_AGENTIC_CMD="${GIT_AGENTIC_CMD:-git-agentic}" # Command to run git-agentic
10+
ENABLE_TRACING="${ENABLE_TRACING:-true}" # Enable/disable trace creation
11+
ENABLE_ATTRIBUTION="${ENABLE_ATTRIBUTION:-true}" # Enable/disable attribution
12+
LOG_LEVEL="${LOG_LEVEL:-info}" # Log level: debug, info, warn, error
13+
14+
# Log function with level support
15+
log() {
16+
local level="${1:-info}"
17+
shift
18+
19+
case "$LOG_LEVEL" in
20+
"debug")
21+
echo "[CLAUDE-AGENTIC:$level] $*" >&2
22+
;;
23+
"info")
24+
if [ "$level" != "debug" ]; then
25+
echo "[CLAUDE-AGENTIC] $*" >&2
26+
fi
27+
;;
28+
"warn")
29+
if [ "$level" = "warn" ] || [ "$level" = "error" ]; then
30+
echo "[CLAUDE-AGENTIC:$level] $*" >&2
31+
fi
32+
;;
33+
"error")
34+
if [ "$level" = "error" ]; then
35+
echo "[CLAUDE-AGENTIC:$level] $*" >&2
36+
fi
37+
;;
38+
esac
39+
}
40+
41+
# Function to process single file edit
42+
process_single_edit() {
43+
local tool_input="$1"
44+
45+
# Extract file information
46+
file_path=$(echo "$tool_input" | jq -r '.file_path // .path // empty')
47+
old_string=$(echo "$tool_input" | jq -r '.old_string // empty')
48+
new_string=$(echo "$tool_input" | jq -r '.new_string // empty')
49+
50+
if [ -z "$file_path" ]; then
51+
log "warn" "No file_path found in Edit tool input"
52+
return
53+
fi
54+
55+
log "info" "Processing single file edit: $file_path"
56+
57+
# Create trace entry if enabled
58+
if [ "$ENABLE_TRACING" = "true" ]; then
59+
create_trace_entry "Edit" "$tool_input"
60+
fi
61+
62+
# Create attribution entries if enabled
63+
if [ "$ENABLE_ATTRIBUTION" = "true" ]; then
64+
create_attribution_entries "$file_path" "$old_string" "$new_string"
65+
fi
66+
}
67+
68+
# Function to process multi-file edit
69+
process_multi_edit() {
70+
local tool_input="$1"
71+
72+
# MultiEdit can affect multiple files
73+
# Extract the files array or individual file operations
74+
files=$(echo "$tool_input" | jq -r '.files // empty')
75+
76+
if [ -n "$files" ] && [ "$files" != "null" ]; then
77+
# Process each file in the files array
78+
echo "$files" | jq -c '.[]' | while read -r file_data; do
79+
file_path=$(echo "$file_data" | jq -r '.file_path // .path // empty')
80+
old_string=$(echo "$file_data" | jq -r '.old_string // empty')
81+
new_string=$(echo "$file_data" | jq -r '.new_string // empty')
82+
83+
if [ -n "$file_path" ]; then
84+
log "info" "Processing multi-edit file: $file_path"
85+
create_attribution_entries "$file_path" "$old_string" "$new_string"
86+
fi
87+
done
88+
else
89+
log "warn" "Could not parse MultiEdit files structure"
90+
fi
91+
92+
# Create trace entry for the multi-edit operation
93+
if [ "$ENABLE_TRACING" = "true" ]; then
94+
create_trace_entry "MultiEdit" "$tool_input"
95+
fi
96+
}
97+
98+
# Function to process file write operations
99+
process_file_write() {
100+
local tool_input="$1"
101+
102+
# Extract file information
103+
file_path=$(echo "$tool_input" | jq -r '.file_path // .path // empty')
104+
content=$(echo "$tool_input" | jq -r '.content // empty')
105+
106+
if [ -z "$file_path" ]; then
107+
log "warn" "No file_path found in Write tool input"
108+
return
109+
fi
110+
111+
log "info" "Processing file write: $file_path"
112+
113+
# Create trace entry if enabled
114+
if [ "$ENABLE_TRACING" = "true" ]; then
115+
create_trace_entry "Write" "$tool_input"
116+
fi
117+
118+
# For file writes, attribute all lines in the file (since it's newly created/modified)
119+
if [ "$ENABLE_ATTRIBUTION" = "true" ] && [ -f "$file_path" ] && [ -n "$content" ]; then
120+
total_lines=$(wc -l < "$file_path")
121+
if [ "$total_lines" -gt 0 ]; then
122+
log "info" "Attributing all $total_lines lines in new file $file_path"
123+
for ((line=1; line<=total_lines; line++)); do
124+
"$GIT_AGENTIC_CMD" attr "$file_path" "$line" ai --agent="$CLAUDE_AGENT_ID" 2>/dev/null || true
125+
done
126+
fi
127+
fi
128+
}
129+
130+
# Function to process bash commands
131+
process_bash_command() {
132+
local tool_input="$1"
133+
134+
command=$(echo "$tool_input" | jq -r '.command // empty')
135+
description=$(echo "$tool_input" | jq -r '.description // empty')
136+
137+
if [ -z "$command" ]; then
138+
log "warn" "No command found in Bash tool input"
139+
return
140+
fi
141+
142+
log "info" "Processing bash command: ${command:0:50}..."
143+
144+
# Create trace entry if enabled
145+
if [ "$ENABLE_TRACING" = "true" ]; then
146+
create_trace_entry "Bash" "$tool_input"
147+
fi
148+
}
149+
150+
# Function to create a trace entry
151+
create_trace_entry() {
152+
local tool_name="$1"
153+
local tool_input="$2"
154+
155+
# Create a simple trace entry
156+
timestamp="$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
157+
tool_args="$(echo "$tool_input" | jq -c .)"
158+
trace_data="[{\"type\": \"tool_call\", \"tool\": \"$tool_name\", \"args\": $tool_args, \"timestamp\": \"$timestamp\"}]"
159+
160+
log "debug" "Creating trace entry for $tool_name"
161+
162+
# Use git-agentic to store the trace (fixed: pass JSON as single argument)
163+
if "$GIT_AGENTIC_CMD" store "$trace_data" 2>/dev/null; then
164+
log "debug" "Successfully created trace entry"
165+
else
166+
log "warn" "Failed to create trace entry"
167+
fi
168+
}
169+
170+
# Function to create attribution entries for modified lines
171+
create_attribution_entries() {
172+
local file_path="$1"
173+
local old_string="$2"
174+
local new_string="$3"
175+
176+
# Skip if file doesn't exist
177+
if [ ! -f "$file_path" ]; then
178+
log "warn" "File $file_path does not exist"
179+
return
180+
fi
181+
182+
# If we have both old and new strings, try to find the changed lines more precisely
183+
if [ -n "$old_string" ] && [ -n "$new_string" ]; then
184+
find_changed_lines "$file_path" "$old_string" "$new_string"
185+
elif [ -n "$new_string" ]; then
186+
# For new content without old content, attribute based on content matching
187+
find_new_content_lines "$file_path" "$new_string"
188+
else
189+
log "warn" "Cannot determine line attribution without content information"
190+
fi
191+
}
192+
193+
# Function to find lines that contain new content
194+
find_new_content_lines() {
195+
local file_path="$1"
196+
local new_content="$2"
197+
198+
log "debug" "Finding lines with new content in $file_path"
199+
200+
# Escape special regex characters in new_content for grep
201+
escaped_content=$(echo "$new_content" | sed 's/[[.*^$()+?{|]/\\&/g' | tr '\n' '\\n')
202+
203+
# Find lines containing parts of the new content
204+
line_numbers=$(grep -n "$escaped_content" "$file_path" 2>/dev/null | cut -d: -f1 || true)
205+
206+
if [ -n "$line_numbers" ]; then
207+
log "debug" "Found content matches on lines: $line_numbers"
208+
echo "$line_numbers" | while read -r line_num; do
209+
if [ -n "$line_num" ] && [ "$line_num" -gt 0 ]; then
210+
"$GIT_AGENTIC_CMD" attr "$file_path" "$line_num" ai --agent="$CLAUDE_AGENT_ID" 2>/dev/null || true
211+
fi
212+
done
213+
else
214+
# Fallback: attribute last few lines if content matching fails
215+
total_lines=$(wc -l < "$file_path")
216+
if [ "$total_lines" -gt 0 ]; then
217+
lines_to_attribute=3 # Attribute last 3 lines as fallback
218+
start_line=$((total_lines - lines_to_attribute + 1))
219+
[ "$start_line" -lt 1 ] && start_line=1
220+
221+
log "debug" "Fallback: Attributing lines $start_line-$total_lines"
222+
for ((line=start_line; line<=total_lines; line++)); do
223+
"$GIT_AGENTIC_CMD" attr "$file_path" "$line" ai --agent="$CLAUDE_AGENT_ID" 2>/dev/null || true
224+
done
225+
fi
226+
fi
227+
}
228+
229+
# Function to find changed lines using diff-like approach
230+
find_changed_lines() {
231+
local file_path="$1"
232+
local old_string="$2"
233+
local new_string="$3"
234+
235+
log "debug" "Analyzing changes in $file_path"
236+
237+
# Create temporary files for diff
238+
temp_old=$(mktemp)
239+
temp_new=$(mktemp)
240+
241+
echo "$old_string" > "$temp_old"
242+
echo "$new_string" > "$temp_new"
243+
244+
# Use diff to find changes, but this is complex for multi-line changes
245+
# For now, we'll use a simpler approach and attribute based on new content
246+
find_new_content_lines "$file_path" "$new_string"
247+
248+
# Clean up temp files
249+
rm -f "$temp_old" "$temp_new"
250+
}
251+
252+
# Check if git-agentic is available
253+
if ! command -v "$GIT_AGENTIC_CMD" &> /dev/null; then
254+
log "error" "git-agentic command not found. Please ensure it's installed and in PATH."
255+
exit 1
256+
fi
257+
258+
# Check if we're in a git repository
259+
if ! git rev-parse --git-dir &> /dev/null; then
260+
log "warn" "Not in a git repository. Skipping attribution."
261+
exit 0
262+
fi
263+
264+
# Read JSON input from Claude Code
265+
input_data=$(cat)
266+
log "debug" "Received hook data: ${input_data:0:200}..."
267+
268+
# Parse tool information
269+
tool_name=$(echo "$input_data" | jq -r '.tool_name // empty')
270+
tool_input=$(echo "$input_data" | jq -r '.tool_input // empty')
271+
272+
if [ -z "$tool_name" ] || [ -z "$tool_input" ]; then
273+
log "warn" "Missing tool_name or tool_input in hook data"
274+
exit 0
275+
fi
276+
277+
log "info" "Processing $tool_name tool execution"
278+
279+
# Process different tool types
280+
case "$tool_name" in
281+
"Edit")
282+
process_single_edit "$tool_input"
283+
;;
284+
"MultiEdit")
285+
process_multi_edit "$tool_input"
286+
;;
287+
"Write")
288+
process_file_write "$tool_input"
289+
;;
290+
"Bash")
291+
process_bash_command "$tool_input"
292+
;;
293+
*)
294+
log "info" "Tool $tool_name not tracked (only Edit/MultiEdit/Write/Bash are supported)"
295+
;;
296+
esac
297+
298+
log "info" "Hook processing complete"
299+
300+
# Main execution
301+
log "debug" "Configuration: agent=$CLAUDE_AGENT_ID, tracing=$ENABLE_TRACING, attribution=$ENABLE_ATTRIBUTION, log_level=$LOG_LEVEL"
302+
log "info" "Claude Code hook started for $tool_name"

.claude/settings.local.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hooks": {
3+
"PostToolUse": [
4+
{
5+
"matcher": "Edit|MultiEdit|Write|Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "/Users/jonhack/CS/CODEGEN/codegen-ide/.claude/hooks/claude_agentic_hook.sh"
10+
}
11+
]
12+
}
13+
]
14+
}
15+
}

.git-agentic/agents.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"claude": {
3+
"name": "Claude",
4+
"model": "Claude-3.5-Sonnet",
5+
"website": "https://claude.ai",
6+
"description": "Anthropic's AI assistant with strong reasoning capabilities"
7+
}
8+
}

0 commit comments

Comments
 (0)