Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 350 lines 11 kB view raw
1#!/usr/bin/env fish 2# 🐜 Aesthetic Ant Colony 3# A dumb ant wakes every N minutes, reads the score, does one small thing. 4# 5# Usage: fish ants/colony.fish [--once] [--interval MINUTES] [--provider PROVIDER] [--model MODEL] 6# 7# Options: 8# --once Run one ant and exit (for testing) 9# --interval N Minutes between runs (default: 30) 10# --provider PROVIDER LLM provider (default: gh-models). See brain.fish. 11# --model MODEL Model name (default: per provider) 12 13set -g COLONY_DIR (realpath (dirname (status filename))) 14set -g REPO_DIR (realpath "$COLONY_DIR/..") 15set -g MAIN_SCORE_FILE "$REPO_DIR/SCORE.md" 16set -g ANT_MINDSET_FILE "$COLONY_DIR/mindset-and-rules.md" 17set -g LOG_FILE "$COLONY_DIR/colony.log" 18set -g PHEROMONE_FILE "$COLONY_DIR/pheromones.log" 19set -g BRAIN "$COLONY_DIR/brain.fish" 20set -g INTERVAL 30 21set -g PROVIDER "gh-models" 22set -g MODEL "" 23set -g ONCE false 24 25# Parse args 26set -l i 1 27while test $i -le (count $argv) 28 switch $argv[$i] 29 case --once 30 set ONCE true 31 case --interval 32 set i (math $i + 1) 33 set INTERVAL $argv[$i] 34 case --provider 35 set i (math $i + 1) 36 set PROVIDER $argv[$i] 37 case --model 38 set i (math $i + 1) 39 set MODEL $argv[$i] 40 end 41 set i (math $i + 1) 42end 43 44function log_msg 45 set -l msg (date "+%Y-%m-%d %H:%M:%S")" 🐜 $argv" 46 echo $msg 47 echo $msg >> $LOG_FILE 48end 49 50function log_pheromone 51 set -l msg (date "+%Y-%m-%d %H:%M:%S")" $argv" 52 echo $msg >> $PHEROMONE_FILE 53end 54 55function call_brain --argument-names system_prompt user_prompt 56 set -l args --provider $PROVIDER 57 if test -n "$MODEL" 58 set args $args --model $MODEL 59 end 60 fish $BRAIN $args --system "$system_prompt" --prompt "$user_prompt" 2>&1 61end 62 63function run_ant 64 set -l run_id (date "+%Y%m%d-%H%M%S") 65 log_msg "Ant $run_id waking up..." 66 67 cd $REPO_DIR 68 69 # Check for dirty tracked files (queen might be working) 70 set -l dirty (git diff --name-only HEAD 2>/dev/null | head -1) 71 set -l staged (git diff --cached --name-only 2>/dev/null | head -1) 72 if test -n "$dirty" -o -n "$staged" 73 log_msg "Tracked files modified/staged — queen is working. Sleeping." 74 log_pheromone "IDLE: ant $run_id — dirty tree, deferred to queen" 75 return 1 76 end 77 78 # Read the score from both sources: 79 # 1) ant-local mindset/rules, 2) main score/tasks 80 if not test -f $ANT_MINDSET_FILE 81 log_msg "ERROR: Ant mindset/rules not found at $ANT_MINDSET_FILE" 82 return 1 83 end 84 if not test -f $MAIN_SCORE_FILE 85 log_msg "ERROR: Main score not found at $MAIN_SCORE_FILE" 86 return 1 87 end 88 set -l ant_mindset_content (cat $ANT_MINDSET_FILE | string collect) 89 set -l main_score_content (cat $MAIN_SCORE_FILE | string collect) 90 set -l score_content (string join "\n\n" "$ant_mindset_content" "$main_score_content") 91 92 # Read recent pheromones 93 set -l recent_pheromones "(none yet)" 94 if test -f $PHEROMONE_FILE 95 set -l trail (tail -20 $PHEROMONE_FILE) 96 if test -n "$trail" 97 set recent_pheromones (string join \n $trail) 98 end 99 end 100 101 # Gather context 102 set -l test_output (cd $REPO_DIR; npm test 2>&1 | tail -30) 103 set -l recent_commits (cd $REPO_DIR; git log --oneline -10 2>/dev/null) 104 105 # --- Phase 1: SCOUT — pick a task and target file --- 106 log_msg "Phase 1: Scouting..." 107 108 set -l scout_system "You are a dumb but careful ant. Respond with exactly one line starting with SCOUT:" 109 set -l scout_prompt "You are aesthetic ant $run_id. You follow the score. 110 111## The Score 112$score_content 113 114## Recent Pheromones (what other ants did) 115$recent_pheromones 116 117## Current Test Output (last 30 lines) 118$test_output 119 120## Recent Git History 121$recent_commits 122 123--- 124 125Pick ONE small task from the Current Tasks in the score. 126Based on the test output and context, decide what specific file to look at. 127 128Respond with EXACTLY one line in this format: 129SCOUT: <task> | <file path relative to repo root> | <plan in one sentence> 130 131If nothing to do: 132SCOUT: IDLE | none | <reason> 133 134Respond with ONLY the SCOUT line. Nothing else." 135 136 set -l scout_output (call_brain "$scout_system" "$scout_prompt") 137 138 mkdir -p "$COLONY_DIR/runs" 139 echo "=== SCOUT ===" > "$COLONY_DIR/runs/$run_id.log" 140 echo "$scout_output" >> "$COLONY_DIR/runs/$run_id.log" 141 142 set -l scout_line (echo "$scout_output" | grep "^SCOUT:" | tail -1) 143 if test -z "$scout_line" 144 log_msg "Scout returned no SCOUT line. Raw output saved to runs/$run_id.log" 145 log_pheromone "ERROR: ant $run_id — scout failed (no SCOUT line)" 146 return 1 147 end 148 149 log_msg "Scout: $scout_line" 150 151 if string match -q "*IDLE*" "$scout_line" 152 set -l reason (echo $scout_line | sed 's/.*| *//') 153 log_msg "Nothing to do: $reason" 154 log_pheromone "IDLE: ant $run_id$reason" 155 return 0 156 end 157 158 # Parse: SCOUT: task | path | plan 159 set -l parts (string split "|" (string replace "SCOUT:" "" "$scout_line")) 160 set -l task_name (string trim $parts[1]) 161 set -l target_file (string trim $parts[2]) 162 set -l plan (string trim $parts[3]) 163 164 if test -z "$target_file" -o "$target_file" = "none" 165 log_msg "No target file." 166 log_pheromone "IDLE: ant $run_id — no target" 167 return 0 168 end 169 170 log_msg "Target: $target_file" 171 log_msg "Plan: $plan" 172 173 # Resolve the file 174 set -l full_path "$REPO_DIR/$target_file" 175 if not test -f "$full_path" 176 log_msg "File not found: $full_path" 177 log_pheromone "FAILURE: ant $run_id — file not found: $target_file" 178 return 1 179 end 180 181 # --- Phase 2: WORK — read the file and produce a diff --- 182 log_msg "Phase 2: Working on $target_file..." 183 184 set -l file_content (head -200 "$full_path") 185 set -l line_count (wc -l < "$full_path" | string trim) 186 187 set -l work_system "You output precise unified diffs. No preamble, no explanation outside the required format." 188 set -l work_prompt "You are aesthetic ant $run_id editing: $target_file ($line_count lines, showing first 200) 189Plan: $plan 190 191## File Content 192$file_content 193 194## Test Output 195$test_output 196 197## Rules 198- Make the SMALLEST change that accomplishes your plan. 199- You must be 98% confident your change is correct. 200- Do NOT change anything unrelated to your plan. 201- Include 3 lines of context before and after each hunk. 202 203## Response Format 204If you have a change, respond: 205WORK: CHANGE | <one-line description> 206\`\`\`diff 207--- a/$target_file 208+++ b/$target_file 209@@ <hunk header> @@ 210 context 211-old line 212+new line 213 context 214\`\`\` 215WORK_END 216 217If not confident enough: 218WORK: ABORT | <reason>" 219 220 set -l work_output (call_brain "$work_system" "$work_prompt") 221 222 echo "" >> "$COLONY_DIR/runs/$run_id.log" 223 echo "=== WORK ===" >> "$COLONY_DIR/runs/$run_id.log" 224 echo "$work_output" >> "$COLONY_DIR/runs/$run_id.log" 225 226 if string match -q "*ABORT*" "$work_output" 227 set -l reason (echo "$work_output" | grep "ABORT" | sed 's/.*ABORT *| *//') 228 log_msg "Aborted: $reason" 229 log_pheromone "IDLE: ant $run_id — aborted: $reason" 230 return 0 231 end 232 233 # Extract the diff block 234 set -l diff_content (echo "$work_output" | sed -n '/^```diff/,/^```/{/^```/d;p}') 235 if test -z "$diff_content" 236 # Try without fenced code block — raw diff 237 set diff_content (echo "$work_output" | sed -n '/^--- a\//,/WORK_END/{/WORK_END/d;p}') 238 end 239 240 if test -z "$diff_content" 241 log_msg "No valid diff produced." 242 log_pheromone "FAILURE: ant $run_id — no valid diff" 243 return 1 244 end 245 246 set -l description (echo "$work_output" | grep "^WORK: CHANGE" | sed 's/WORK: CHANGE *| *//') 247 if test -z "$description" 248 set description "$plan" 249 end 250 251 # --- Phase 3: APPLY & VERIFY --- 252 log_msg "Phase 3: Applying..." 253 254 set -l diff_file (mktemp /tmp/ant-XXXXXX.patch) 255 printf '%s\n' $diff_content > $diff_file 256 257 cd $REPO_DIR 258 259 # Dry run 260 git apply --check $diff_file 2>/dev/null 261 if test $status -ne 0 262 log_msg "Diff doesn't apply cleanly." 263 echo "" >> "$COLONY_DIR/runs/$run_id.log" 264 echo "=== FAILED DIFF ===" >> "$COLONY_DIR/runs/$run_id.log" 265 cat $diff_file >> "$COLONY_DIR/runs/$run_id.log" 266 rm -f $diff_file 267 log_pheromone "FAILURE: ant $run_id — diff didn't apply cleanly" 268 return 1 269 end 270 271 # Apply for real 272 git apply $diff_file 2>&1 273 set -l apply_exit $status 274 rm -f $diff_file 275 276 if test $apply_exit -ne 0 277 log_msg "Apply failed." 278 git checkout . 2>/dev/null 279 log_pheromone "FAILURE: ant $run_id — apply failed" 280 return 1 281 end 282 283 # Check we actually changed something 284 set -l changes (git diff --name-only 2>/dev/null) 285 if test -z "$changes" 286 log_msg "No changes after apply (diff was a no-op)." 287 log_pheromone "IDLE: ant $run_id — no-op diff" 288 return 0 289 end 290 291 # Verify tests pass 292 log_msg "Verifying tests..." 293 set -l verify_output (npm test 2>&1) 294 set -l verify_exit $status 295 296 echo "" >> "$COLONY_DIR/runs/$run_id.log" 297 echo "=== TEST VERIFY ===" >> "$COLONY_DIR/runs/$run_id.log" 298 echo "Exit: $verify_exit" >> "$COLONY_DIR/runs/$run_id.log" 299 echo "$verify_output" | tail -10 >> "$COLONY_DIR/runs/$run_id.log" 300 301 if test $verify_exit -ne 0 302 log_msg "Tests FAILED after apply. Reverting." 303 git checkout . 2>/dev/null 304 log_pheromone "REVERTED: ant $run_id — tests failed after change" 305 return 1 306 end 307 308 log_msg "Tests pass. Changed: $changes" 309 310 # Commit 311 git add -A 312 git commit -m "ant: $description 313 314Ant-ID: $run_id 315Provider: $PROVIDER 316Model: $MODEL 317Verified: tests pass" --no-verify 2>&1 318 319 if test $status -eq 0 320 log_msg "Committed! 🐜✅" 321 log_pheromone "SUCCESS: ant $run_id ($PROVIDER/$MODEL) — $description" 322 else 323 log_msg "Commit failed. Reverting." 324 git checkout . 2>/dev/null 325 git reset HEAD . 2>/dev/null 326 log_pheromone "ERROR: ant $run_id — commit failed" 327 return 1 328 end 329 330 return 0 331end 332 333# --- Main Loop --- 334 335log_msg "Colony starting. Interval: "$INTERVAL"m | Provider: $PROVIDER | Model: $MODEL | Once: $ONCE" 336log_msg "Score: $SCORE_FILE" 337log_msg "Repo: $REPO_DIR" 338 339if test "$ONCE" = true 340 run_ant 341 set -l result $status 342 log_msg "Single run complete. Exit: $result" 343 exit $result 344end 345 346while true 347 run_ant 348 log_msg "Sleeping for $INTERVAL minutes..." 349 sleep (math "$INTERVAL * 60") 350end