223 lines
7.0 KiB
Bash
Executable File
223 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# wm-gen: Generate and dispatch a task from a template + description.
|
|
#
|
|
# Usage:
|
|
# wm-gen <template> <description> # dispatch immediately
|
|
# wm-gen <template> <description> --name ID # dispatch with tracking ID
|
|
# wm-gen --list # list available templates
|
|
# echo "description" | wm-gen <template> # description from stdin
|
|
#
|
|
# Templates:
|
|
# 1 Signature XEvent*→WMEvent*
|
|
# 2 Eliminate _x11_raw
|
|
# 3 Xlib→wm_backend vtable
|
|
# 4 Guard X11 block (if scr->display)
|
|
# 5 Split into _x11/_wl pair
|
|
# 6 Xlib drawing→render backend
|
|
# 7 Remove #ifdef guard
|
|
# 8 WPrefs Xlib→WINGs API
|
|
# 9 Verification
|
|
#
|
|
# Options:
|
|
# --name ID Tracking name (default: gen-<timestamp>)
|
|
# --dry-run Print the prompt, don't dispatch
|
|
# --backend B Override backend
|
|
# --model M Override model
|
|
# --bg Background dispatch
|
|
# --keep Keep session after completion
|
|
#
|
|
# Examples:
|
|
# wm-gen 3 "Replace XClearArea at wrlib/context.c:730 with wm_backend->window_clear_area"
|
|
# wm-gen 4 "Guard X11 init block at WINGs/widgets.c:637-739"
|
|
# wm-gen 2 "Eliminate _x11_raw at src/framewin.c:1165" --name fix-framewin
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
TOOLS="$ROOT/tools/wm-port"
|
|
MOUNT="${OLLIE:-$HOME/mnt/ollie}"
|
|
LOG_DIR="$TOOLS/logs"
|
|
|
|
source "$TOOLS/task-map.sh"
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
die() { printf 'wm-gen: %s\n' "$*" >&2; exit 1; }
|
|
|
|
if [ "${1:-}" = "--list" ]; then
|
|
for t in 1 2 3 4 5 6 7 8 9; do
|
|
printf ' %s %s\n' "$t" "${TEMPLATE_NAME[$t]}"
|
|
done
|
|
exit 0
|
|
fi
|
|
|
|
# Defaults
|
|
backend="openrouter"
|
|
model="anthropic/claude-sonnet-4"
|
|
dry_run=0
|
|
keep=0
|
|
bg=0
|
|
name=""
|
|
tnum=""
|
|
desc_args=()
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift ;;
|
|
--dry-run) dry_run=1 ;;
|
|
--backend) backend="$2"; shift ;;
|
|
--model) model="$2"; shift ;;
|
|
--bg) bg=1 ;;
|
|
--keep) keep=1 ;;
|
|
--help|-h) sed -n '2,/^$/{ s/^# //; s/^#//; p; }' "$0"; exit 0 ;;
|
|
-*) die "unknown flag: $1" ;;
|
|
*) if [ -z "$tnum" ] && [[ "$1" =~ ^[1-9]$ ]]; then
|
|
tnum="$1"
|
|
else
|
|
desc_args+=("$1")
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[ -n "$tnum" ] || die "template number required (1-9). Use --list to see templates."
|
|
[ -n "${TEMPLATE_NAME[$tnum]:-}" ] || die "invalid template number: $tnum"
|
|
|
|
# Read description from args or stdin
|
|
if [ ${#desc_args[@]} -gt 0 ] && ! [ -t 0 ]; then
|
|
description="${desc_args[*]}"$'\n'"$(cat)"
|
|
elif [ ${#desc_args[@]} -gt 0 ]; then
|
|
description="${desc_args[*]}"
|
|
elif ! [ -t 0 ]; then
|
|
description=$(cat)
|
|
else
|
|
die "description required: pass as arguments or pipe via stdin"
|
|
fi
|
|
|
|
[ -n "$description" ] || die "empty description"
|
|
|
|
name="${name:-gen-$(date +%s)}"
|
|
|
|
# Extract the template procedure
|
|
extract_template() {
|
|
local t="$1"
|
|
local prompts="$ROOT/ACTION_PLAN_PROMPTS.md"
|
|
awk -v tnum="$t" '
|
|
BEGIN { found=0; in_code=0 }
|
|
/^## Template / {
|
|
if (found && !in_code) exit
|
|
if ($0 ~ "Template " tnum ":" || $0 ~ "Template " tnum " ") {
|
|
found=1
|
|
next
|
|
}
|
|
}
|
|
/^## Template [0-9]/ { if (found) exit }
|
|
/^---$/ { if (found && !in_code) exit }
|
|
found { print }
|
|
' "$prompts"
|
|
}
|
|
|
|
extract_reference_tables() {
|
|
local prompts="$ROOT/ACTION_PLAN_PROMPTS.md"
|
|
awk '
|
|
/^## Reference Tables/,/^## Template 1/ {
|
|
if (/^## Template 1/) exit
|
|
print
|
|
}
|
|
' "$prompts"
|
|
}
|
|
|
|
template=$(extract_template "$tnum")
|
|
ref_tables=$(extract_reference_tables)
|
|
tname="${TEMPLATE_NAME[$tnum]}"
|
|
|
|
prompt=$(cat <<PROMPT
|
|
You are executing a mechanical Wayland porting task for WindowMaker.
|
|
|
|
# Task: $name ($tname)
|
|
|
|
## What to do
|
|
|
|
$description
|
|
|
|
## Procedure (Template $tnum: $tname)
|
|
|
|
$template
|
|
|
|
$ref_tables
|
|
|
|
## Important Rules
|
|
|
|
1. Work in the git repository at $ROOT. Create a git branch named "port/$name" before making changes.
|
|
2. Make exactly the changes described. No more, no less.
|
|
3. After making changes, verify with the grep commands in the template.
|
|
4. Attempt to compile with: cd $ROOT && make -j\$(nproc) 2>&1 | tail -30
|
|
5. If compilation fails, fix the errors. If you cannot fix them, revert and report.
|
|
6. If you hit a STOP condition (missing vtable slot, missing API), revert changes and report exactly what is missing.
|
|
7. When done, run: git add -A && git diff --cached --stat
|
|
8. Report the final status as one of:
|
|
- DONE: task completed, compiles clean
|
|
- STOP: blocked on <reason>
|
|
- FAIL: could not complete because <reason>
|
|
|
|
Line numbers may have drifted. Use grep to find current line numbers before editing.
|
|
PROMPT
|
|
)
|
|
|
|
if [ "$dry_run" -eq 1 ]; then
|
|
echo "════════════════════════════════════════"
|
|
echo "TASK: $name (Template $tnum: $tname)"
|
|
echo "════════════════════════════════════════"
|
|
echo "$prompt"
|
|
exit 0
|
|
fi
|
|
|
|
session_name="wm-port-${name}"
|
|
session_path="$MOUNT/s/$session_name"
|
|
flags=(
|
|
-name "$session_name"
|
|
-cwd "$ROOT"
|
|
-agent coding
|
|
-backend "$backend"
|
|
-model "$model"
|
|
-keep
|
|
)
|
|
[ "$bg" -eq 1 ] && flags+=(-bg)
|
|
|
|
printf '\033[1;36m▶ Dispatching %s\033[0m (T%s: %s)\n' "$name" "$tnum" "$tname" >&2
|
|
|
|
if [ "$bg" -eq 1 ]; then
|
|
sp=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}")
|
|
printf '%s\t%s\n' "$name" "$sp"
|
|
printf '\033[1;33m → %s\033[0m\n' "$sp" >&2
|
|
else
|
|
result=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}" 2>/dev/null) || true
|
|
printf '%s\n' "$result" > "$LOG_DIR/${name}.log"
|
|
|
|
if [ -z "$result" ] || echo "$result" | head -1 | grep -q '^user:'; then
|
|
printf '\033[1;31m✗ %s: FAIL (agent produced no response)\033[0m\n' "$name" >&2
|
|
printf ' Session: %s\n' "$session_path" >&2
|
|
elif echo "$result" | grep -qiE '\*\*DONE\*\*|^DONE:|^DONE\b|DONE: task completed'; then
|
|
printf '\033[1;32m✓ %s: DONE\033[0m\n' "$name" >&2
|
|
if [ "$keep" -eq 0 ]; then
|
|
echo kill > "$session_path/ctl" 2>/dev/null || true
|
|
else
|
|
printf ' Session: %s\n' "$session_path" >&2
|
|
fi
|
|
elif echo "$result" | grep -qiE '\*\*STOP\*\*|^STOP:|^STOP\b'; then
|
|
printf '\033[1;33m⚠ %s: STOP\033[0m\n' "$name" >&2
|
|
echo "$result" | grep -iE 'STOP' | head -3 | sed 's/^/ /' >&2
|
|
printf ' Session: %s\n' "$session_path" >&2
|
|
elif echo "$result" | grep -qiE '\*\*FAIL\*\*|^FAIL:|^FAIL\b'; then
|
|
printf '\033[1;31m✗ %s: FAIL\033[0m\n' "$name" >&2
|
|
echo "$result" | grep -iE 'FAIL' | head -3 | sed 's/^/ /' >&2
|
|
printf ' Session: %s\n' "$session_path" >&2
|
|
else
|
|
printf '\033[1;35m? %s: status unclear\033[0m\n' "$name" >&2
|
|
echo "$result" | grep -v '^$' | tail -5 | sed 's/^/ /' >&2
|
|
printf ' Session: %s\n' "$session_path" >&2
|
|
fi
|
|
|
|
printf ' Log: %s\n' "$LOG_DIR/${name}.log" >&2
|
|
fi
|