My agentic slop goes here. Not intended for anyone else!
1#!/bin/bash
2set -e
3
4# slopper - Unified Claude Code devcontainer workflow manager
5# Handles update, execution, and interactive modes for Claude in devcontainers
6
7# Colors for output (disabled in non-TTY or quiet mode)
8if [ -t 1 ] && [ "${QUIET:-false}" != "true" ]; then
9 RED='\033[0;31m'
10 GREEN='\033[0;32m'
11 YELLOW='\033[1;33m'
12 BLUE='\033[0;34m'
13 NC='\033[0m' # No Color
14else
15 RED=''
16 GREEN=''
17 YELLOW=''
18 BLUE=''
19 NC=''
20fi
21
22# Configuration
23SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24LOG_DIR="${HOME}/.slopper/logs"
25LOG_FILE="${LOG_DIR}/slopper-$(date +%Y%m%d).log"
26WORKSPACE="${SLOPPER_WORKSPACE:-$(pwd)}"
27# For OCaml mode, use workspace-local .slopper directory to work with container mount
28HISTORY_DIR="${WORKSPACE}/.slopper/history"
29TODO_FILE="${WORKSPACE}/.slopper/TODO.md"
30FORCE_REBUILD=false
31QUIET=false
32MODE=""
33EXEC_PROMPT=""
34TODO_LIMIT=""
35
36# Ensure log and history directories exist
37mkdir -p "${LOG_DIR}"
38mkdir -p "${HISTORY_DIR}"
39mkdir -p "$(dirname "${TODO_FILE}")"
40
41# Logging function
42log() {
43 local level="$1"
44 shift
45 local message="$*"
46 local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
47
48 # Always log to file
49 echo "[$timestamp] [$level] $message" >> "${LOG_FILE}"
50
51 # Output to console based on quiet mode and level
52 if [ "$QUIET" != "true" ]; then
53 case "$level" in
54 ERROR)
55 echo -e "${RED}ERROR: $message${NC}" >&2
56 ;;
57 INFO)
58 echo -e "${GREEN}$message${NC}"
59 ;;
60 WARN)
61 echo -e "${YELLOW}$message${NC}"
62 ;;
63 DEBUG)
64 if [ "${DEBUG:-false}" = "true" ]; then
65 echo -e "${BLUE}DEBUG: $message${NC}"
66 fi
67 ;;
68 esac
69 elif [ "$level" = "ERROR" ]; then
70 # Even in quiet mode, show errors
71 echo "ERROR: $message" >&2
72 fi
73}
74
75# Log rotation (keep last 30 days)
76rotate_logs() {
77 find "${LOG_DIR}" -name "slopper-*.log" -type f -mtime +30 -delete 2>/dev/null || true
78 log DEBUG "Rotated old log files"
79}
80
81# Initialize TODO.md file if it doesn't exist
82init_todo_file() {
83 if [ ! -f "$TODO_FILE" ]; then
84 cat > "$TODO_FILE" << 'EOF'
85# Slopper Development TODOs
86
87## Pending Setup Improvements
88
89## Pending Code Fixes
90
91## Pending Features
92
93## Completed
94EOF
95 log DEBUG "Initialized TODO file: $TODO_FILE"
96 fi
97}
98
99# Add a TODO item to the file
100add_todo_item() {
101 local category="$1"
102 local description="$2"
103 local session_id="$3"
104 local timestamp="$(date '+%Y-%m-%d')"
105
106 init_todo_file
107
108 # Find the appropriate section and add the item
109 local section_line
110 case "$category" in
111 "setup"|"Setup")
112 section_line="## Pending Setup Improvements"
113 ;;
114 "fix"|"Fix"|"fixes"|"Fixes")
115 section_line="## Pending Code Fixes"
116 ;;
117 "feature"|"Feature"|"features"|"Features")
118 section_line="## Pending Features"
119 ;;
120 *)
121 section_line="## Pending Setup Improvements"
122 ;;
123 esac
124
125 # Use awk to insert the item after the section header
126 awk -v section="$section_line" -v item="- [ ] [$timestamp $session_id] $description" '
127 $0 == section {
128 print $0
129 print item
130 next
131 }
132 { print }
133 ' "$TODO_FILE" > "$TODO_FILE.tmp" && mv "$TODO_FILE.tmp" "$TODO_FILE"
134
135 log INFO "Added TODO: $description"
136}
137
138# Mark a TODO item as completed
139complete_todo_item() {
140 local description="$1"
141 local timestamp="$(date '+%Y-%m-%d %H:%M')"
142
143 if [ ! -f "$TODO_FILE" ]; then
144 log WARN "TODO file does not exist"
145 return 1
146 fi
147
148 # Move completed item to Completed section
149 awk -v desc="$description" -v timestamp="$timestamp" '
150 BEGIN { in_completed = 0; moved = 0 }
151 /^## Completed/ { in_completed = 1; print; next }
152 /^##/ && !/^## Completed/ { in_completed = 0; print; next }
153 /^- \[ \].*/ && index($0, desc) > 0 && !moved {
154 # Add to completed section
155 if (!in_completed) {
156 completed_items[++completed_count] = "- [x] " substr($0, 7) " (completed " timestamp ")"
157 }
158 moved = 1
159 next
160 }
161 in_completed && !moved && completed_count > 0 {
162 for (i = 1; i <= completed_count; i++) {
163 print completed_items[i]
164 }
165 completed_count = 0
166 }
167 { print }
168 END {
169 if (completed_count > 0) {
170 for (i = 1; i <= completed_count; i++) {
171 print completed_items[i]
172 }
173 }
174 }
175 ' "$TODO_FILE" > "$TODO_FILE.tmp" && mv "$TODO_FILE.tmp" "$TODO_FILE"
176
177 log INFO "Marked TODO as completed: $description"
178}
179
180# Get pending TODO items
181get_pending_todos() {
182 if [ ! -f "$TODO_FILE" ]; then
183 return 0
184 fi
185
186 grep "^- \[ \]" "$TODO_FILE" || true
187}
188
189# Parse Claude suggestions and add them to TODO.md
190process_claude_suggestions_to_todos() {
191 local suggestions_file="$1"
192 local session_id="$2"
193
194 if [ ! -f "$suggestions_file" ] || [ ! -s "$suggestions_file" ]; then
195 log WARN "No suggestions file to process"
196 return 0
197 fi
198
199 log INFO "Processing Claude suggestions and adding to TODO.md..."
200
201 # Simple parsing - look for bullet points or numbered lists
202 # This is a basic implementation - could be enhanced with better parsing
203 while IFS= read -r line; do
204 # Skip empty lines and headers
205 if [[ -z "$line" || "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*[=#-] ]]; then
206 continue
207 fi
208
209 # Look for lines that seem like actionable items
210 if [[ "$line" =~ ^[[:space:]]*[-*][[:space:]]+(.+)$ ]] ||
211 [[ "$line" =~ ^[[:space:]]*[0-9]+\.[[:space:]]+(.+)$ ]]; then
212
213 local item="${BASH_REMATCH[1]}"
214
215 # Categorize the item based on keywords
216 local category="setup"
217 if [[ "$item" =~ (fix|bug|error|issue) ]]; then
218 category="fix"
219 elif [[ "$item" =~ (add|implement|feature|new) ]]; then
220 category="feature"
221 elif [[ "$item" =~ (install|setup|configure|environment) ]]; then
222 category="setup"
223 fi
224
225 add_todo_item "$category" "$item" "$session_id"
226 fi
227 done < "$suggestions_file"
228}
229
230# Function to check if devcontainer CLI is available
231check_devcontainer_cli() {
232 if ! command -v npx &> /dev/null; then
233 log ERROR "npx not found. Please install Node.js and npm"
234 exit 1
235 fi
236
237 if ! npx @devcontainers/cli --version &> /dev/null; then
238 log WARN "Installing @devcontainers/cli..."
239 npm install -g @devcontainers/cli
240 fi
241}
242
243# Function to check if Claude CLI is available in container
244check_claude_cli() {
245 local result
246 result=$(npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- which claude 2>/dev/null || echo "")
247 if [ -z "$result" ]; then
248 log ERROR "Claude CLI not found in devcontainer"
249 return 1
250 fi
251 return 0
252}
253
254# Function to ensure devcontainer is up
255ensure_devcontainer() {
256 log INFO "Ensuring devcontainer is ready..."
257
258 cd "${WORKSPACE}"
259
260 if [ ! -f ".devcontainer/devcontainer.json" ]; then
261 log ERROR ".devcontainer/devcontainer.json not found in ${WORKSPACE}"
262 exit 1
263 fi
264
265 # Check if we need to force rebuild
266 if [ "$FORCE_REBUILD" = "true" ]; then
267 log INFO "Force rebuilding devcontainer..."
268 npx @devcontainers/cli down --workspace-folder "${WORKSPACE}" 2>/dev/null || true
269 npx @devcontainers/cli up --workspace-folder "${WORKSPACE}" --remove-existing-container --build-no-cache
270 else
271 log INFO "Starting devcontainer..."
272 npx @devcontainers/cli up --workspace-folder "${WORKSPACE}"
273 fi
274
275 # Verify container is running
276 if ! npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- echo "Container ready" &>/dev/null; then
277 log ERROR "Failed to start devcontainer"
278 exit 1
279 fi
280
281 log INFO "Devcontainer is ready"
282}
283
284# Function to stop devcontainer
285stop_devcontainer() {
286 log DEBUG "Stopping devcontainer..."
287 npx @devcontainers/cli down --workspace-folder "${WORKSPACE}" 2>/dev/null || true
288}
289
290# Update mode - suitable for cron
291mode_update() {
292 log INFO "Running update mode"
293 rotate_logs
294
295 check_devcontainer_cli
296
297 # Force rebuild for updates to ensure fresh environment
298 FORCE_REBUILD=true
299 ensure_devcontainer
300
301 # Run a test command to verify everything works
302 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- echo "Update test successful" &>/dev/null; then
303 log INFO "Update completed successfully"
304 stop_devcontainer
305 exit 0
306 else
307 log ERROR "Update failed - container test failed"
308 stop_devcontainer
309 exit 1
310 fi
311}
312
313# Execute mode - run a single Claude prompt
314mode_exec() {
315 local prompt="$1"
316
317 if [ -z "$prompt" ]; then
318 log ERROR "No prompt provided for exec mode"
319 exit 1
320 fi
321
322 log INFO "Running exec mode with prompt: $prompt"
323
324 check_devcontainer_cli
325 ensure_devcontainer
326
327 # Check if Claude is available
328 if ! check_claude_cli; then
329 stop_devcontainer
330 exit 1
331 fi
332
333 # Execute Claude with the prompt in non-interactive mode
334 log INFO "Executing Claude prompt..."
335
336 local claude_output
337 local exit_code
338
339 # Create a temporary file for the prompt to handle complex prompts safely
340 local prompt_file=$(mktemp)
341 echo "$prompt" > "$prompt_file"
342
343 # Run Claude in non-interactive mode
344 claude_output=$(npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
345 bash -c "cd /workspace && claude --dangerously-skip-permissions < '$prompt_file'" 2>&1) || exit_code=$?
346
347 rm -f "$prompt_file"
348
349 if [ -z "$exit_code" ] || [ "$exit_code" -eq 0 ]; then
350 echo "$claude_output"
351 log INFO "Claude execution completed successfully"
352 stop_devcontainer
353 exit 0
354 else
355 echo "$claude_output" >&2
356 log ERROR "Claude execution failed with exit code: ${exit_code:-unknown}"
357 stop_devcontainer
358 exit ${exit_code:-1}
359 fi
360}
361
362# Shell mode - interactive shell in devcontainer
363mode_shell() {
364 log INFO "Running shell mode"
365
366 check_devcontainer_cli
367 ensure_devcontainer
368
369 log INFO "Starting interactive shell in devcontainer..."
370
371 # Start interactive bash session
372 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- bash
373
374 local exit_code=$?
375
376 stop_devcontainer
377
378 if [ $exit_code -eq 0 ]; then
379 log INFO "Shell session ended normally"
380 else
381 log WARN "Shell session ended with exit code: $exit_code"
382 fi
383
384 exit $exit_code
385}
386
387# Claude interactive mode
388mode_claude() {
389 log INFO "Running Claude interactive mode"
390
391 check_devcontainer_cli
392 ensure_devcontainer
393
394 if ! check_claude_cli; then
395 stop_devcontainer
396 exit 1
397 fi
398
399 log INFO "Starting interactive Claude session..."
400
401 # Start interactive Claude session
402 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
403 bash -c "cd /workspace && claude"
404
405 local exit_code=$?
406
407 stop_devcontainer
408
409 if [ $exit_code -eq 0 ]; then
410 log INFO "Claude session ended normally"
411 else
412 log WARN "Claude session ended with exit code: $exit_code"
413 fi
414
415 exit $exit_code
416}
417
418# OCaml mode - interactive shell with history logging and Claude integration
419mode_ocaml() {
420 log INFO "Running OCaml development mode with history logging"
421
422 check_devcontainer_cli
423 ensure_devcontainer
424
425 # Create session ID and file paths
426 local session_id="ocaml-$(date +%Y%m%d-%H%M%S)"
427 local typescript_file="${HISTORY_DIR}/${session_id}.typescript"
428 local clean_log="${HISTORY_DIR}/${session_id}.log"
429
430 log INFO "Starting OCaml development session: $session_id"
431 log INFO "Session will be recorded to: $typescript_file"
432
433 # Create session recording script
434 local tracker_script=$(mktemp)
435 cat > "$tracker_script" << 'EOF'
436#!/bin/bash
437
438# OCaml development session with complete recording using script
439SESSION_ID="$1"
440TYPESCRIPT_FILE="$2"
441CLEAN_LOG="$3"
442
443echo "=== OCaml Development Session Started ==="
444echo "Session ID: $SESSION_ID"
445echo "Timestamp: $(date)"
446echo "Workspace: $(pwd)"
447echo ""
448
449# Check OCaml installation
450if command -v ocaml >/dev/null 2>&1; then
451 echo "OCaml version: $(ocaml -version)"
452else
453 echo "Note: OCaml not found - you may need to install it"
454fi
455
456if command -v opam >/dev/null 2>&1; then
457 echo "OPAM version: $(opam --version)"
458 echo "Current switch: $(opam switch show 2>/dev/null || echo 'none')"
459else
460 echo "Note: OPAM not found - you may need to install it"
461fi
462
463echo ""
464echo "OCaml Development Environment Ready!"
465echo "- Complete session recording enabled"
466echo "- Session ID: $SESSION_ID"
467echo "- Type 'exit' when done to process with Claude"
468echo ""
469
470# Record the entire interactive session using script
471# -f: flush output immediately
472# -q: quiet mode (don't show start/stop messages)
473script -f -q "$TYPESCRIPT_FILE" -c "bash --login -i"
474
475echo ""
476echo "=== OCaml Development Session Ended ==="
477echo "Timestamp: $(date)"
478
479# Clean the typescript file to remove colors and control sequences
480if command -v col >/dev/null 2>&1; then
481 echo "Cleaning session log..."
482 cat "$TYPESCRIPT_FILE" | col -b > "$CLEAN_LOG"
483 echo "Clean session log saved to: $CLEAN_LOG"
484else
485 echo "Warning: 'col' command not found, using raw typescript"
486 cp "$TYPESCRIPT_FILE" "$CLEAN_LOG"
487fi
488EOF
489
490 chmod +x "$tracker_script"
491
492 # Ensure the history directory exists in the container first
493 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
494 mkdir -p "/home/node/.slopper/history"
495
496 # Copy tracker script to container and run the tracked session
497 local container_script="/tmp/tracker_script.sh"
498 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
499 bash -c "cat > '$container_script' << 'EOF'
500$(cat "$tracker_script")
501EOF"
502
503 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
504 chmod +x "$container_script"
505
506 # Run with container paths for the session files
507 local container_typescript="/home/node/.slopper/history/${session_id}.typescript"
508 local container_clean_log="/home/node/.slopper/history/${session_id}.log"
509
510 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
511 bash "$container_script" "$session_id" "$container_typescript" "$container_clean_log"
512
513 local exit_code=$?
514
515 # Clean up tracker script
516 rm -f "$tracker_script"
517
518 log INFO "OCaml session ended. Processing with Claude..."
519
520 # Check if we have session data to process (files are accessible via the mount)
521 if [ -f "$clean_log" ] && [ -s "$clean_log" ]; then
522 process_ocaml_session_with_claude "$session_id" "$typescript_file" "$clean_log"
523 else
524 log WARN "No session data captured, skipping Claude processing"
525 fi
526
527 stop_devcontainer
528
529 if [ $exit_code -eq 0 ]; then
530 log INFO "OCaml development session completed successfully"
531 else
532 log WARN "OCaml development session ended with exit code: $exit_code"
533 fi
534
535 exit $exit_code
536}
537
538# Function to process OCaml session with Claude
539process_ocaml_session_with_claude() {
540 local session_id="$1"
541 local typescript_file="$2"
542 local clean_log="$3"
543
544 log INFO "Processing OCaml session $session_id with Claude..."
545 log DEBUG "Session files - typescript: $typescript_file, clean_log: $clean_log"
546
547 # Check file sizes for debugging
548 if [ -f "$clean_log" ]; then
549 local log_size=$(wc -l < "$clean_log" 2>/dev/null || echo "0")
550 log DEBUG "Clean log file exists with $log_size lines"
551 log DEBUG "First few lines of clean log:"
552 head -10 "$clean_log" 2>/dev/null | while read line; do
553 log DEBUG " $line"
554 done
555 else
556 log WARN "Clean log file does not exist: $clean_log"
557 return 1
558 fi
559
560 # Check if Claude is available in the devcontainer
561 log DEBUG "Checking for Claude CLI in devcontainer..."
562 if ! npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- which claude &>/dev/null; then
563 log WARN "Claude CLI not found in devcontainer, skipping setup-ocaml script refinement"
564 return 0
565 fi
566 log DEBUG "Claude CLI found in devcontainer"
567
568 # Create analysis prompt (inside container paths)
569 local analysis_file="/home/node/.slopper/history/${session_id}.analysis"
570 log DEBUG "Creating analysis file: $analysis_file"
571 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
572 bash -c "cat > '$analysis_file' << 'EOF'
573Please analyze this OCaml development session and help refine the setup-ocaml script.
574
575SESSION DETAILS:
576- Session ID: $session_id
577- Workspace: /workspace
578
579COMPLETE SESSION RECORDING:
580\$(if [ -f \"/home/node/.slopper/history/${session_id}.log\" ]; then cat \"/home/node/.slopper/history/${session_id}.log\"; else echo \"No session recording available\"; fi)
581
582CURRENT setup-ocaml.sh SCRIPT:
583\$(cat \"/usr/local/bin/setup-ocaml.sh\")
584
585TASK:
586Based on the complete session recording above, please analyze my OCaml development workflow and suggest improvements to the setup-ocaml.sh script. Focus on:
587
5881. Any missing dependencies or tools that were needed during the session
5892. Additional opam packages that should be pre-installed
5903. Environment setup that could be automated
5914. Common development patterns that could be scripted
5925. Any pins or specific package versions that were useful
5936. Error patterns that suggest missing configurations
594
595The session recording shows everything I typed and all output, so you can see the complete workflow including any errors, installations, or manual setup steps.
596
597Please provide specific, actionable improvements that would make OCaml development setup more efficient. In particular, pay attention to the .devcontainer files.
598EOF'"
599
600 log DEBUG "Analysis file created, verifying it exists..."
601 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
602 bash -c "if [ -f '$analysis_file' ]; then echo 'Analysis file exists, size:' \$(wc -c < '$analysis_file'); else echo 'Analysis file missing!'; fi" || log WARN "Failed to verify analysis file"
603
604 log INFO "Sending session analysis to Claude..."
605
606 # Run Claude analysis inside the devcontainer
607 # Since .slopper/history is bind-mounted, write directly to /workspace/.slopper/history
608 local claude_output_container="/workspace/.slopper/history/${session_id}.claude-suggestions"
609 local claude_output_host="${HISTORY_DIR}/${session_id}.claude-suggestions"
610
611 log DEBUG "Running Claude with analysis file, output will go to: $claude_output_container (mounted path)"
612 log DEBUG "Claude command: cd /workspace && claude -p '$analysis_file' > '$claude_output_container'"
613
614 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
615 bash -c "cd /workspace && claude --dangerously-skip-permissions -p '$analysis_file' > '$claude_output_container' 2>&1"; then
616 log INFO "Claude analysis completed successfully"
617
618 # Check if output file exists and has content (directly on host via mount)
619 if [ -f "$claude_output_host" ]; then
620 local output_size=$(wc -c < "$claude_output_host" 2>/dev/null || echo "0")
621 log DEBUG "Claude output file size: $output_size bytes"
622 else
623 local output_size=0
624 log DEBUG "Claude output file does not exist on host"
625 fi
626
627 if [ "$output_size" -gt 0 ]; then
628 # Output is already on host via bind mount, display it and add to TODO.md
629 echo ""
630 echo "=== Claude Analysis and Suggestions ==="
631 cat "$claude_output_host"
632 echo ""
633 echo "Analysis saved to: $claude_output_host"
634
635 # Process suggestions and add to TODO.md
636 process_claude_suggestions_to_todos "$claude_output_host" "$session_id"
637 echo "Suggestions added to TODO.md: $TODO_FILE"
638 else
639 log WARN "Claude output file is empty or missing"
640 log DEBUG "Attempting to show stderr from Claude command..."
641 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
642 bash -c "if [ -f '$claude_output_container' ]; then cat '$claude_output_container'; else echo 'Output file does not exist'; fi"
643 fi
644 else
645 local exit_code=$?
646 log ERROR "Claude analysis failed with exit code: $exit_code"
647
648 # Try to get error output
649 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
650 test -f "$claude_output_container"; then
651 echo "Error output:"
652 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
653 cat "$claude_output_container"
654 else
655 log DEBUG "No error output file found"
656 fi
657
658 # Additional debugging - check if Claude is actually available
659 log DEBUG "Verifying Claude CLI accessibility:"
660 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
661 bash -c "which claude; claude --version 2>&1 || echo 'Claude version check failed'"
662 fi
663
664 # Clean up analysis file (but keep it for debugging if Claude failed)
665 if [ "$output_size" -gt 0 ] 2>/dev/null; then
666 log DEBUG "Cleaning up analysis file (Claude succeeded)"
667 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
668 rm -f "$analysis_file"
669 else
670 log DEBUG "Keeping analysis file for debugging (Claude failed or produced no output)"
671 log DEBUG "Analysis file preserved at: $analysis_file"
672 fi
673}
674
675# Show TODO.md contents
676mode_show_todos() {
677 log INFO "Displaying current TODOs"
678
679 if [ ! -f "$TODO_FILE" ]; then
680 echo "No TODO file found. Run with --ocaml mode first to generate suggestions."
681 exit 0
682 fi
683
684 cat "$TODO_FILE"
685 exit 0
686}
687
688# Clear completed TODOs from TODO.md
689mode_clear_todos() {
690 log INFO "Clearing completed TODOs"
691
692 if [ ! -f "$TODO_FILE" ]; then
693 log WARN "No TODO file found"
694 exit 0
695 fi
696
697 # Create a new file without completed items
698 awk '
699 BEGIN { in_completed = 0; skip_empty = 0 }
700 /^## Completed/ {
701 in_completed = 1
702 print $0
703 skip_empty = 1
704 next
705 }
706 /^##/ && !/^## Completed/ {
707 in_completed = 0
708 skip_empty = 0
709 print
710 next
711 }
712 in_completed && /^- \[x\]/ { next }
713 in_completed && /^[[:space:]]*$/ && skip_empty { next }
714 in_completed { skip_empty = 0 }
715 { print }
716 ' "$TODO_FILE" > "$TODO_FILE.tmp" && mv "$TODO_FILE.tmp" "$TODO_FILE"
717
718 log INFO "Completed TODOs cleared from $TODO_FILE"
719 exit 0
720}
721
722# Implement pending TODOs non-interactively
723mode_implement_todos() {
724 log INFO "Implementing pending TODOs"
725
726 check_devcontainer_cli
727 ensure_devcontainer
728
729 if ! check_claude_cli; then
730 stop_devcontainer
731 exit 1
732 fi
733
734 if [ ! -f "$TODO_FILE" ]; then
735 echo "No TODO file found. Run with --ocaml mode first to generate suggestions."
736 stop_devcontainer
737 exit 0
738 fi
739
740 # Get pending TODOs
741 local pending_todos
742 pending_todos=$(get_pending_todos)
743
744 if [ -z "$pending_todos" ]; then
745 echo "No pending TODOs to implement."
746 stop_devcontainer
747 exit 0
748 fi
749
750 local total_todos=$(echo "$pending_todos" | wc -l)
751 local implemented=0
752 local failed=0
753
754 # Apply limit if specified
755 if [ -n "$TODO_LIMIT" ] && [ "$TODO_LIMIT" -gt 0 ]; then
756 pending_todos=$(echo "$pending_todos" | head -n "$TODO_LIMIT")
757 total_todos=$(echo "$pending_todos" | wc -l)
758 log INFO "Processing first $total_todos TODOs (limited by --limit $TODO_LIMIT)"
759 fi
760
761 log INFO "Found $total_todos pending TODOs to implement"
762
763 # Process each TODO item
764 local line_num=0
765 while IFS= read -r todo_line; do
766 line_num=$((line_num + 1))
767
768 if [ -z "$todo_line" ]; then
769 continue
770 fi
771
772 # Extract the description (everything after the checkbox and metadata)
773 local description
774 if [[ "$todo_line" =~ \[[0-9-]+\ [^]]+\][[:space:]]*(.+)$ ]]; then
775 description="${BASH_REMATCH[1]}"
776 else
777 # Fallback parsing
778 description=$(echo "$todo_line" | sed 's/^- \[ \][[:space:]]*//')
779 fi
780
781 if [ -z "$description" ]; then
782 log WARN "Could not parse TODO description from: $todo_line"
783 continue
784 fi
785
786 echo ""
787 echo "=== Implementing TODO $line_num/$total_todos ==="
788 echo "Description: $description"
789 echo ""
790
791 # Create focused implementation prompt
792 local impl_prompt="Implement this specific task: $description
793
794Please make the necessary changes to implement this requirement. Focus on:
7951. Making the minimal necessary changes
7962. Following existing code patterns and conventions
7973. Testing that the implementation works
7984. Providing clear output about what was changed
799
800If this is a setup/configuration task, make the changes to the appropriate files.
801If this is a code fix, identify and fix the specific issue.
802If this is a feature implementation, add the functionality as described."
803
804 log INFO "Implementing: $description"
805
806 # Run Claude implementation inside devcontainer
807 # Use a heredoc to pass the prompt directly to claude inside the container
808 local claude_exit_code=0
809 local claude_output
810
811 claude_output=$(npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
812 bash -c "cd /workspace && claude --dangerously-skip-permissions << 'EOF'
813$impl_prompt
814EOF" 2>&1) || claude_exit_code=$?
815
816 if [ $claude_exit_code -eq 0 ]; then
817 echo "$claude_output"
818 echo ""
819 log INFO "Successfully implemented: $description"
820
821 # Mark as completed in TODO.md
822 complete_todo_item "$description"
823 implemented=$((implemented + 1))
824
825 # Optional: Create a git commit for this change
826 if command -v git &>/dev/null; then
827 local commit_msg="slopper: implement TODO - $description"
828 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \
829 bash -c "cd /workspace && git add -A && git diff --cached --quiet || git commit -m '$commit_msg'" 2>/dev/null; then
830 log DEBUG "Created commit for TODO implementation"
831 fi
832 fi
833 else
834 echo "Implementation failed:"
835 echo "$claude_output"
836 echo ""
837 log ERROR "Failed to implement: $description"
838 failed=$((failed + 1))
839 fi
840
841 # Brief pause between implementations
842 sleep 1
843
844 done <<< "$pending_todos"
845
846 echo ""
847 echo "=== Implementation Summary ==="
848 echo "Total TODOs processed: $total_todos"
849 echo "Successfully implemented: $implemented"
850 echo "Failed: $failed"
851 echo ""
852
853 if [ $implemented -gt 0 ]; then
854 echo "Updated TODO file: $TODO_FILE"
855 fi
856
857 stop_devcontainer
858
859 if [ $failed -eq 0 ]; then
860 log INFO "All TODO implementations completed successfully"
861 exit 0
862 else
863 log WARN "Some TODO implementations failed"
864 exit 1
865 fi
866}
867
868# Help function
869show_help() {
870 cat << EOF
871slopper - Unified Claude Code devcontainer workflow manager
872
873Usage: $(basename "$0") [OPTIONS]
874
875MODES (mutually exclusive):
876 -u, --update Update/rebuild devcontainer (suitable for cron)
877 -e, --exec PROMPT Execute a single Claude prompt non-interactively
878 -s, --shell Open interactive shell in devcontainer (default)
879 -c, --claude Start interactive Claude session
880 -o, --ocaml OCaml development mode with history logging
881 --implement-todos Implement pending TODOs from TODO.md non-interactively
882 --show-todos Display current TODO.md contents
883 --clear-todos Clear completed TODOs from TODO.md
884
885OPTIONS:
886 -f, --force Force rebuild devcontainer from scratch
887 -w, --workspace PATH Set workspace directory (default: current directory)
888 -q, --quiet Minimal output (errors only)
889 -h, --help Show this help message
890 --debug Enable debug output
891 --limit N Limit number of TODOs to implement (for --implement-todos)
892
893ENVIRONMENT:
894 SLOPPER_WORKSPACE Default workspace directory
895
896USE CASES:
897 Development:
898 ./slopper # Quick shell into dev environment
899 ./slopper --claude # Interactive Claude coding session
900 ./slopper --ocaml # OCaml dev mode with history logging
901
902 Automation:
903 ./slopper -e "fix the bug in main.ml" # CI/CD code fixes
904 ./slopper -e "write tests for module X" # Automated test generation
905 ./slopper --implement-todos # Implement all pending TODO suggestions
906 ./slopper --implement-todos --limit 3 # Implement first 3 TODOs
907
908 Maintenance:
909 0 3 * * * slopper -u -q # Cron: nightly container updates
910 ./slopper -f -s # Force rebuild after config changes
911 ./slopper --show-todos # View current TODO list
912 ./slopper --clear-todos # Clean up completed TODOs
913
914 Scripting:
915 SLOPPER_WORKSPACE=/project slopper -e "refactor all Python files"
916 for dir in */; do slopper -w "\$dir" -e "update dependencies"; done
917
918EXAMPLES:
919 $(basename "$0") # Interactive shell (default)
920 $(basename "$0") --update --quiet # Silent update for cron
921 $(basename "$0") --exec "analyze security" # One-off Claude task
922 $(basename "$0") --claude # Interactive Claude session
923 $(basename "$0") --force --shell # Rebuild and enter shell
924
925Logs: ${LOG_DIR}/
926EOF
927}
928
929# Parse command line arguments
930while [[ $# -gt 0 ]]; do
931 case $1 in
932 -u|--update)
933 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
934 MODE="update"
935 shift
936 ;;
937 -e|--exec)
938 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
939 MODE="exec"
940 EXEC_PROMPT="$2"
941 shift 2
942 ;;
943 -s|--shell)
944 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
945 MODE="shell"
946 shift
947 ;;
948 -c|--claude)
949 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
950 MODE="claude"
951 shift
952 ;;
953 -o|--ocaml)
954 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
955 MODE="ocaml"
956 shift
957 ;;
958 --implement-todos)
959 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
960 MODE="implement_todos"
961 shift
962 ;;
963 --show-todos)
964 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
965 MODE="show_todos"
966 shift
967 ;;
968 --clear-todos)
969 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; }
970 MODE="clear_todos"
971 shift
972 ;;
973 --limit)
974 TODO_LIMIT="$2"
975 shift 2
976 ;;
977 -f|--force)
978 FORCE_REBUILD=true
979 shift
980 ;;
981 -w|--workspace)
982 WORKSPACE="$2"
983 shift 2
984 ;;
985 -q|--quiet)
986 QUIET=true
987 shift
988 ;;
989 --debug)
990 DEBUG=true
991 shift
992 ;;
993 -h|--help)
994 show_help
995 exit 0
996 ;;
997 *)
998 log ERROR "Unknown option: $1"
999 echo "Use --help for usage information"
1000 exit 1
1001 ;;
1002 esac
1003done
1004
1005# Default to shell mode if no mode specified
1006[ -z "$MODE" ] && MODE="shell"
1007
1008# Validate workspace
1009WORKSPACE="$(cd "${WORKSPACE}" 2>/dev/null && pwd)" || {
1010 log ERROR "Invalid workspace directory: ${WORKSPACE}"
1011 exit 1
1012}
1013
1014log DEBUG "Starting slopper in $MODE mode"
1015log DEBUG "Workspace: $WORKSPACE"
1016log DEBUG "Force rebuild: $FORCE_REBUILD"
1017
1018# Execute the appropriate mode
1019case "$MODE" in
1020 update)
1021 mode_update
1022 ;;
1023 exec)
1024 mode_exec "$EXEC_PROMPT"
1025 ;;
1026 shell)
1027 mode_shell
1028 ;;
1029 claude)
1030 mode_claude
1031 ;;
1032 ocaml)
1033 mode_ocaml
1034 ;;
1035 implement_todos)
1036 mode_implement_todos
1037 ;;
1038 show_todos)
1039 mode_show_todos
1040 ;;
1041 clear_todos)
1042 mode_clear_todos
1043 ;;
1044 *)
1045 log ERROR "Invalid mode: $MODE"
1046 exit 1
1047 ;;
1048esac