tool updates

This commit is contained in:
Window Maker 2026-05-16 13:26:41 +02:00
parent 0a295350cd
commit f20ecbc699
5 changed files with 669 additions and 90 deletions

View File

@ -5,48 +5,80 @@ Agent-assisted task execution for the Wayland port, driven by `ACTION_PLAN.md` a
## Quick Start ## Quick Start
```bash ```bash
# Add to PATH (or use full paths)
export PATH="$PWD/tools/wm-port:$PATH" export PATH="$PWD/tools/wm-port:$PATH"
# See what's available # See what's available
wm-tasks wm-tasks
# Dispatch a single task (foreground — waits for completion) # Dispatch a task from the action plan
wm-dispatch B.1 wm-dispatch B.1
# Dispatch and return immediately (background) # Generate an ad-hoc task from a template
wm-dispatch --bg B.1 B.2 B.3 wm-gen 3 "Replace XClearArea at wrlib/context.c:730 with wm_backend->window_clear_area"
# Monitor running agents # Monitor
wm-status --watch wm-status --watch
# Follow a specific agent's work live # Collect results from background tasks
wm-status --follow B.1 wm-collect
# Review completed work # Review diffs
wm-review --pending
wm-review B.1 wm-review B.1
# Dispatch all unblocked Wave 1 tasks in parallel
wm-dispatch --wave 1 --bg
``` ```
## Scripts ## Scripts
| Script | Purpose | | Script | Purpose | Default model |
|---|---| |---|---|---|
| `wm-dispatch` | Dispatch tasks to agent sessions | | `wm-dispatch` | Execute action plan tasks | `qwen/qwen3-coder` |
| `wm-tasks` | Show task board (status, waves, blockers) | | `wm-gen` | Generate ad-hoc tasks from templates | `anthropic/claude-sonnet-4` |
| `wm-status` | Monitor active agent sessions | | `wm-tasks` | Show task board (status, waves, blockers) | — |
| `wm-review` | Interactive diff review, approve/reject | | `wm-status` | Monitor active agent sessions | — |
| `wm-done` | Manually mark tasks done/undone | | `wm-collect` | Harvest results from background sessions | — |
| `wm-kill` | Kill running agent sessions | | `wm-review` | Interactive diff review, approve/reject | — |
| `wm-done` | Manually mark tasks done/undone | — |
| `wm-kill` | Kill running agent sessions | — |
## Two ways to dispatch
### `wm-dispatch` — action plan tasks
Executes pre-defined tasks from `ACTION_PLAN.md`. Each task is mapped to a template, and the prompt is assembled automatically.
```bash
wm-dispatch B.1 # single task, foreground
wm-dispatch --bg A5.1 B.1 B.3 C.1 # batch, background
wm-dispatch --wave 1 --bg # entire wave in parallel
wm-dispatch --next 5 --bg # next 5 unblocked tasks
wm-dispatch --dry-run A5.1 # preview prompt without dispatching
```
### `wm-gen` — ad-hoc tasks from templates
Generates a task from a template number + plain English description. Use this for follow-ups, fixes from verification findings, or anything not in the action plan.
```bash
# List templates
wm-gen --list
# Template + description
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
# Pipe findings from a verification log
grep 'STOP\|issue' tools/wm-port/logs/F.2.log | wm-gen 3 --name fix-F2
# Preview
wm-gen --dry-run 4 "Guard the X11 block at wrlib/context.c:725-740"
```
## Workflow ## Workflow
### 1. Pick tasks ### 1. Pick tasks
```bash ```bash
wm-tasks # full board
wm-tasks --ready # what can be dispatched now? wm-tasks --ready # what can be dispatched now?
wm-tasks --wave 1 # what's in wave 1? wm-tasks --wave 1 # what's in wave 1?
wm-tasks B.1 # details on a specific task wm-tasks B.1 # details on a specific task
@ -55,49 +87,37 @@ wm-tasks B.1 # details on a specific task
### 2. Dispatch ### 2. Dispatch
```bash ```bash
# Single task, wait for result # From the action plan
wm-dispatch A5.1 wm-dispatch B.1
# Preview the prompt without dispatching
wm-dispatch --dry-run A5.1
# Multiple tasks in parallel (background)
wm-dispatch --bg A5.1 B.1 B.3 C.1
# All ready tasks in a wave
wm-dispatch --wave 1 --bg wm-dispatch --wave 1 --bg
# Next 5 unblocked tasks regardless of wave # Ad-hoc from template
wm-dispatch --next 5 --bg wm-gen 3 "Replace XFoo at src/bar.c:42 with wm_backend->baz"
# Use a different model
wm-dispatch --model anthropic/claude-opus-4 D.1
``` ```
### 3. Monitor ### 3. Monitor
```bash ```bash
# Dashboard (refreshes every 2s) wm-status --watch # dashboard (refreshes every 2s)
wm-status --watch wm-status --follow A5.1 # tail one agent's chat live
wm-kill A5.1 # kill a stuck agent
# Follow one agent's chat live wm-kill --all # kill all
wm-status --follow A5.1
# Kill a stuck agent
wm-kill A5.1
# Kill all
wm-kill --all
``` ```
### 4. Review ### 4. Collect (background tasks)
```bash ```bash
# List branches ready for review wm-collect # harvest all finished sessions
wm-review --pending wm-collect --dry-run # preview without acting
```
# Interactive review (shows diff, offers approve/reject/edit) Scans idle `wm-port-*` sessions, reads results, updates `done.txt`, saves logs, kills successful sessions. Failed/unclear sessions are kept for inspection.
wm-review A5.1
### 5. Review (code change tasks)
```bash
wm-review --pending # list branches ready for review
wm-review B.1 # interactive review
``` ```
The review flow: The review flow:
@ -111,26 +131,40 @@ The review flow:
- **edit** — opens changed files in `$EDITOR` - **edit** — opens changed files in `$EDITOR`
- **skip** — come back later - **skip** — come back later
### 5. Track ### 6. Track
```bash ```bash
# Mark a task done manually (e.g., verified by hand) wm-done G.1 # manually mark done
wm-done G.1 wm-done --undo G.1 # re-open
wm-tasks # updated board
# Re-open a task
wm-done --undo G.1
# Full board
wm-tasks
``` ```
## How It Works ## What updates `done.txt`
| Path | When |
|---|---|
| `wm-dispatch` (foreground) | Agent reports DONE → auto-marked |
| `wm-collect` | Harvests background sessions → marks DONE ones |
| `wm-review --approve` | Human approves diff → marked |
| `wm-done` | Manual mark/unmark |
## Session lifecycle
Sessions are always created with `-keep`. Cleanup is handled by the scripts:
- **DONE** → session killed (unless `--keep`)
- **STOP / FAIL / unclear** → session kept for inspection, path printed
- `wm-collect` kills DONE sessions, keeps everything else
- `wm-kill` for manual cleanup
## How it works
1. `wm-dispatch` extracts the task block from `ACTION_PLAN.md` and the matching template from `ACTION_PLAN_PROMPTS.md` 1. `wm-dispatch` extracts the task block from `ACTION_PLAN.md` and the matching template from `ACTION_PLAN_PROMPTS.md`
2. It composes a prompt: task description + template procedure + reference tables 2. `wm-gen` takes a template number + description directly
3. It spawns an ollie agent session (`coding` agent config) via `$OLLIE/s/b` 3. Both compose a prompt: task description + template procedure + reference tables
4. The agent creates a `port/<id>` git branch, makes the changes, verifies, and reports status 4. An ollie agent session is spawned via `$OLLIE/s/b`
5. You review the branch with `wm-review` 5. The agent creates a `port/<id>` git branch, makes changes, verifies, reports status
6. You review with `wm-review` or results are harvested by `wm-collect`
## Files ## Files
@ -138,10 +172,12 @@ wm-tasks
tools/wm-port/ tools/wm-port/
task-map.sh # Task database: ID→template, ID→wave, ID→blockers task-map.sh # Task database: ID→template, ID→wave, ID→blockers
done.txt # Completed task IDs (one per line) done.txt # Completed task IDs (one per line)
logs/ # Agent output logs (one per task) logs/ # Agent output logs (one per task/name)
wm-dispatch # Dispatcher wm-dispatch # Action plan task dispatcher
wm-gen # Ad-hoc task generator from templates
wm-tasks # Task board wm-tasks # Task board
wm-status # Session monitor wm-status # Session monitor
wm-collect # Background session harvester
wm-review # Diff reviewer wm-review # Diff reviewer
wm-done # Manual done/undo wm-done # Manual done/undo
wm-kill # Session killer wm-kill # Session killer
@ -149,9 +185,10 @@ tools/wm-port/
## Configuration ## Configuration
| Variable | Default | Description | Defaults can be overridden per-call with flags:
| Flag | `wm-dispatch` default | `wm-gen` default |
|---|---|---| |---|---|---|
| `OLLIE` | `~/mnt/ollie` | Ollie mount path | | `--backend` | `openrouter` | `openrouter` |
| `--backend` | `openrouter` | LLM backend | | `--model` | `qwen/qwen3-coder` | `anthropic/claude-sonnet-4` |
| `--model` | `anthropic/claude-sonnet-4` | LLM model | | `--agent` | `coding` | `coding` |
| `--agent` | `coding` | Ollie agent config |

148
tools/wm-port/logs/F.2.log Normal file
View File

@ -0,0 +1,148 @@
user: You are executing a mechanical Wayland porting task for WindowMaker.
# Task: F.2 (Verification only)
## Task Description (from ACTION_PLAN.md)
### F.2 Verify `RCreateContextWayland()` completeness
- **File**: `wrlib/context.c:725-775`
- **Operation**: Read `RCreateContextWayland()`. Verify it sets all fields that `RConvertImage` and image loaders need: `depth`, `vclass`, `red_offset`, `green_offset`, `blue_offset`, `black`, `white`, `convert_image`, `convert_image_mask`. Verify `dpy`, `cmap`, `copy_gc`, `visual`, `drawable` are NULL/0.
- **Test**: Call `RCreateContextWayland(1920, 1080, NULL)`, then `RLoadImage(ctx, "test.png", 0)`, then `RConvertImage(ctx, img, &pixmap)`. Confirm the pixmap is a `pixman_image_t*` cast to `Pixmap`.
- **Blocked by**: nothing
## Procedure (Template 9)
**Use for**: F.2, F.3, F.4, G.1, I.1, I.2, C.3, H.5, and any verification task
```
TASK: Verify that {{FILE}} has no X11 dependencies that would break on the
Wayland backend.
STEPS:
1. Run: `grep -nE '\bX[A-Z][a-z]' {{FILE}}`
This finds direct Xlib function calls.
EXPECTED: {{EXPECTED_XLIB_COUNT}} matches.
2. Run: `grep -nE '\bDisplay\b|\bXEvent\b|\bXImage\b|\bXColor\b' {{FILE}}`
This finds X11 type usage.
EXPECTED: {{EXPECTED_TYPE_COUNT}} matches.
3. Run: `grep -n '_x11_raw' {{FILE}}`
This finds raw event escape hatches.
EXPECTED: 0 matches.
4. Run: `grep -n '#ifdef.*USE_X\|#ifdef.*HAVE_X\|#ifdef.*XINERAMA\|#ifdef.*XSHAPE\|#ifdef.*XDND' {{FILE}}`
This finds X11 feature guards.
EXPECTED: {{EXPECTED_IFDEF_COUNT}} matches.
5. If any count exceeds expected:
- For each unexpected match, report the line number and the X11 reference.
- Classify as: (a) actual dependency, (b) comment/string, (c) type used
but never passed to Xlib.
REPORT FORMAT:
```
FILE: {{FILE}}
Xlib calls: N (expected {{EXPECTED_XLIB_COUNT}})
X11 types: N (expected {{EXPECTED_TYPE_COUNT}})
_x11_raw: N (expected 0)
#ifdef guards: N (expected {{EXPECTED_IFDEF_COUNT}})
STATUS: CLEAN / NEEDS WORK
ISSUES: (list any unexpected findings)
```
CONSTRAINTS:
- Do NOT modify the file. This is a read-only verification task.
- Report findings exactly. Do not interpret or fix.
```
## Reference Tables (embed in every prompt)
### X11 Event Type → WMEvent Type
```
KeyPress/KeyRelease → WME_KEY_PRESS/WME_KEY_RELEASE → event->u.key.*
ButtonPress/ButtonRelease → WME_BUTTON_PRESS/WME_BUTTON_RELEASE → event->u.button.*
MotionNotify → WME_MOTION → event->u.motion.*
EnterNotify/LeaveNotify → WME_ENTER/WME_LEAVE → event->u.crossing.*
Expose → WME_EXPOSE → event->u.expose.*
ConfigureNotify → WME_CONFIGURE → event->u.configure.*
ConfigureRequest → WME_CONFIGURE_REQUEST → event->u.configureRequest.*
MapNotify → WME_MAP → event->u.lifecycle.*
MapRequest → WME_MAP_REQUEST → event->u.mapRequest.*
UnmapNotify → WME_UNMAP → event->u.lifecycle.*
DestroyNotify → WME_DESTROY → event->u.lifecycle.*
FocusIn/FocusOut → WME_FOCUS_IN/WME_FOCUS_OUT → event->u.focus.*
PropertyNotify → WME_PROPERTY → event->u.property.*
ClientMessage → WME_CLIENT_MESSAGE → event->u.clientMessage.*
SelectionNotify → WME_SELECTION → event->u.selection.*
SelectionRequest → WME_SELECTION_REQUEST → event->u.selection.* (check)
SelectionClear → WME_SELECTION_CLEAR → event->u.selection.* (check)
VisibilityNotify → WME_VISIBILITY → event->u.visibility.*
ColormapNotify → WME_COLORMAP → event->u.colormap.*
MappingNotify → WME_MAPPING → event->u.mapping.*
ReparentNotify → WME_REPARENT → event->u.reparent.*
```
### X11 Event Field → WMEvent Field
```
event->xkey.keycode → event->u.key.keycode
event->xkey.state → event->u.key.state
event->xkey.time → event->u.key.time
event->xkey.window → event->u.key.window
event->xkey.x, y → event->u.key.x, y
event->xkey.x_root → event->u.key.x_root
event->xbutton.button → event->u.button.button
event->xbutton.state → event->u.button.state
event->xbutton.time → event->u.button.time
event->xbutton.window → event->u.button.window
event->xbutton.x, y → event->u.button.x, y
event->xbutton.x_root → event->u.button.x_root
event->xmotion.state → event->u.motion.state
event->xmotion.time → event->u.motion.time
event->xmotion.window → event->u.motion.window
event->xmotion.x, y → event->u.motion.x, y
event->xmotion.x_root → event->u.motion.x_root
event->xcrossing.window → event->u.crossing.window
event->xcrossing.state → event->u.crossing.state
event->xcrossing.time → event->u.crossing.time
event->xcrossing.mode → event->u.crossing.mode
event->xcrossing.detail → event->u.crossing.detail
event->xconfigure.window → event->u.configure.window
event->xconfigure.x, y → event->u.configure.x, y
event->xconfigure.width → event->u.configure.width
event->xconfigure.height → event->u.configure.height
event->xclient.data.l[N] → event->u.clientMessage.data.l[N]
event->xclient.message_type → event->u.clientMessage.atom
event->xclient.window → event->u.clientMessage.window
event->xproperty.atom → event->u.property.atom
event->xproperty.time → event->u.property.time
event->xproperty.window → event->u.property.window
```
---
## Important Rules
1. Work in the git repository at /home/lkn/src/WindowMaker-0.96.0. Create a git branch named "port/F.2" 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 /home/lkn/src/WindowMaker-0.96.0 && 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 in ACTION_PLAN.md may have drifted. Use grep to find current line numbers before editing.

116
tools/wm-port/wm-collect Executable file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env bash
# wm-collect: Harvest results from completed wm-port agent sessions.
#
# Scans all wm-port-* sessions. For each idle session, reads the result,
# updates done.txt, saves the log, and kills the session.
#
# Usage:
# wm-collect # collect all finished sessions
# wm-collect --dry-run # show what would be collected, don't act
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
TOOLS="$ROOT/tools/wm-port"
MOUNT="${OLLIE:-$HOME/mnt/ollie}"
DONE_FILE="$TOOLS/done.txt"
LOG_DIR="$TOOLS/logs"
source "$TOOLS/task-map.sh"
mkdir -p "$LOG_DIR"
touch "$DONE_FILE"
dry_run=0
[ "${1:-}" = "--dry-run" ] && dry_run=1
[ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ] && {
sed -n '2,/^$/{ s/^# //; s/^#//; p; }' "$0"; exit 0
}
collected=0
skipped=0
while IFS=$'\t' read -r sid state cwd backend model; do
[[ "$sid" == wm-port-* ]] || continue
tid="${sid#wm-port-}"
session_path="$MOUNT/s/$sid"
# Skip sessions still running
if [ "$state" != "idle" ]; then
(( ++skipped ))
continue
fi
# Skip already-collected tasks
task_is_done "$tid" "$DONE_FILE" 2>/dev/null && {
if [ "$dry_run" -eq 0 ]; then
echo kill > "$session_path/ctl" 2>/dev/null || true
fi
continue
}
# Read the result
local_offset=$(cat "$session_path/offset" 2>/dev/null || echo 0)
result=$(tail -c +$((local_offset + 1)) "$session_path/chat" 2>/dev/null || true)
# Determine status
status="UNKNOWN"
if [ -z "$result" ] || echo "$result" | head -1 | grep -q '^user:'; then
status="FAIL"
elif echo "$result" | grep -qiE '\*\*DONE\*\*|^DONE:|^DONE\b|Status:.*DONE|DONE: task completed|DONE: verified'; then
status="DONE"
elif echo "$result" | grep -qiE '\*\*STOP\*\*|^STOP:|^STOP\b|Status:.*STOP|STOP: blocked'; then
status="STOP"
elif echo "$result" | grep -qiE '\*\*FAIL\*\*|^FAIL:|^FAIL\b|Status:.*FAIL|FAIL: could not'; then
status="FAIL"
elif echo "$result" | grep -qiE 'compiles clean|no action needed'; then
status="DONE"
fi
# Report
case "$status" in
DONE)
printf '\033[1;32m\u2713 %s: %s\033[0m\n' "$tid" "$status"
;;
STOP)
printf '\033[1;33m\u26a0 %s: %s\033[0m\n' "$tid" "$status"
;;
FAIL)
printf '\033[1;31m\u2717 %s: %s\033[0m\n' "$tid" "$status"
;;
*)
printf '\033[1;35m? %s: %s\033[0m\n' "$tid" "$status"
;;
esac
if [ "$dry_run" -eq 1 ]; then
(( ++collected ))
continue
fi
# Save log
printf '%s\n' "$result" > "$LOG_DIR/${tid}.log"
# Update done.txt for successful tasks
case "$status" in
DONE)
echo "$tid" >> "$DONE_FILE"
echo kill > "$session_path/ctl" 2>/dev/null || true
;;
*)
# Keep session alive for inspection
printf ' Session: %s\n' "$session_path"
;;
esac
(( ++collected ))
done < <(cat "$MOUNT/s/idx" 2>/dev/null)
if [ "$collected" -eq 0 ] && [ "$skipped" -eq 0 ]; then
echo "No wm-port sessions found."
elif [ "$collected" -eq 0 ]; then
printf '%d session(s) still running.\n' "$skipped"
else
printf '\nCollected %d result(s)' "$collected"
[ "$skipped" -gt 0 ] && printf ', %d still running' "$skipped"
printf '.\n'
fi

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# wm-dispatch: Dispatch a WindowMaker Wayland port task to an agent. # wm-dispatch: Execute action plan tasks via agent sessions.
# #
# Usage: # Usage:
# wm-dispatch <task-id> # dispatch one task # wm-dispatch <task-id> # dispatch one task (foreground)
# wm-dispatch <task-id> <task-id>... # dispatch multiple in parallel # wm-dispatch <task-id> <task-id>... # dispatch multiple in parallel
# wm-dispatch --wave <N> # dispatch all unblocked tasks in wave N # wm-dispatch --wave <N> # dispatch all unblocked tasks in wave N
# wm-dispatch --next [N] # dispatch next N unblocked tasks (default 1) # wm-dispatch --next [N] # dispatch next N unblocked tasks (default 1)
@ -10,9 +10,11 @@
# Options: # Options:
# --dry-run Print the prompt instead of dispatching # --dry-run Print the prompt instead of dispatching
# --backend B Override backend (default: openrouter) # --backend B Override backend (default: openrouter)
# --model M Override model (default: anthropic/claude-sonnet-4) # --model M Override model (default: qwen/qwen3-coder)
# --keep Don't auto-kill session on completion # --keep Keep session on success (failures always kept)
# --bg Submit and return immediately (print session paths) # --bg Submit and return immediately (use wm-collect to harvest)
#
# See also: wm-gen (ad-hoc tasks from templates)
set -euo pipefail set -euo pipefail
@ -26,7 +28,7 @@ source "$TOOLS/task-map.sh"
# Defaults # Defaults
backend="openrouter" backend="openrouter"
model="anthropic/claude-sonnet-4" model="qwen/qwen3-coder"
dry_run=0 dry_run=0
keep=0 keep=0
bg=0 bg=0
@ -163,7 +165,34 @@ build_prompt() {
local ref_tables local ref_tables
ref_tables=$(extract_reference_tables) ref_tables=$(extract_reference_tables)
cat <<PROMPT if [ "$tnum" -eq 9 ]; then
# Verification tasks: read-only audit, report findings
cat <<PROMPT
You are executing a verification task for the WindowMaker Wayland port.
This is a READ-ONLY audit. Do NOT modify any files. Do NOT create branches.
# Task: $tid
## Task Description (from ACTION_PLAN.md)
$task_block
## Procedure (Template $tnum)
$template
## Important Rules
1. Work in the git repository at $ROOT.
2. Run the verification checks described in the procedure.
3. Report findings using the REPORT FORMAT from the procedure.
4. End with exactly one of:
- DONE: verified clean, no issues found.
- STOP: found issues that need human decision. List each issue with file:line and a one-line description.
PROMPT
else
# Code modification tasks: branch, edit, compile
cat <<PROMPT
You are executing a mechanical Wayland porting task for WindowMaker. You are executing a mechanical Wayland porting task for WindowMaker.
# Task: $tid ($tname) # Task: $tid ($tname)
@ -194,6 +223,7 @@ $ref_tables
Line numbers in ACTION_PLAN.md may have drifted. Use grep to find current line numbers before editing. Line numbers in ACTION_PLAN.md may have drifted. Use grep to find current line numbers before editing.
PROMPT PROMPT
fi
} }
# ── Dispatch a single task ── # ── Dispatch a single task ──
@ -212,48 +242,74 @@ dispatch_one() {
fi fi
local session_name="wm-port-${tid}" local session_name="wm-port-${tid}"
local session_path="$MOUNT/s/$session_name"
local flags=( local flags=(
-name "$session_name" -name "$session_name"
-cwd "$ROOT" -cwd "$ROOT"
-agent coding -agent coding
-backend "$backend" -backend "$backend"
-model "$model" -model "$model"
-keep
) )
[ "$keep" -eq 1 ] && flags+=(-keep)
[ "$bg" -eq 1 ] && flags+=(-bg) [ "$bg" -eq 1 ] && flags+=(-bg)
printf '\033[1;36m▶ Dispatching %s\033[0m (%s)\n' \ printf '\033[1;36m▶ Dispatching %s\033[0m (%s)\n' \
"$tid" "${TEMPLATE_NAME[${TASK_TEMPLATE[$tid]}]}" >&2 "$tid" "${TEMPLATE_NAME[${TASK_TEMPLATE[$tid]}]}" >&2
if [ "$bg" -eq 1 ]; then if [ "$bg" -eq 1 ]; then
local session_path local sp
session_path=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}") sp=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}")
printf '%s\t%s\n' "$tid" "$session_path" printf '%s\t%s\n' "$tid" "$sp"
printf '\033[1;33m → %s\033[0m\n' "$session_path" >&2 printf '\033[1;33m → %s\033[0m\n' "$sp" >&2
else else
local result local result
result=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}" 2>&1) || true result=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}" 2>/dev/null) || true
# Save log # Save log
printf '%s\n' "$result" > "$LOG_DIR/${tid}.log" printf '%s\n' "$result" > "$LOG_DIR/${tid}.log"
# Parse result status # Parse result status — search broadly since agents format varies
local status="UNKNOWN" local status="UNKNOWN"
if echo "$result" | grep -q '^DONE\b\|^\*\*DONE\*\*\|Status:.*DONE'; then local should_keep="$keep"
if [ -z "$result" ] || echo "$result" | head -1 | grep -q '^user:'; then
status="FAIL"
should_keep=1
printf '\033[1;31m✗ %s: FAIL (agent produced no response)\033[0m\n' "$tid" >&2
elif echo "$result" | grep -qiE '\*\*DONE\*\*|^DONE:|^DONE\b|Status:.*DONE|DONE: task completed|DONE: verified'; then
status="DONE" status="DONE"
echo "$tid" >> "$DONE_FILE" echo "$tid" >> "$DONE_FILE"
printf '\033[1;32m✓ %s: DONE\033[0m\n' "$tid" >&2 printf '\033[1;32m✓ %s: DONE\033[0m\n' "$tid" >&2
elif echo "$result" | grep -q '^STOP\b\|^\*\*STOP\*\*\|Status:.*STOP'; then elif echo "$result" | grep -qiE '\*\*STOP\*\*|^STOP:|^STOP\b|Status:.*STOP|STOP: blocked'; then
status="STOP" status="STOP"
should_keep=1
printf '\033[1;33m⚠ %s: STOP\033[0m\n' "$tid" >&2 printf '\033[1;33m⚠ %s: STOP\033[0m\n' "$tid" >&2
elif echo "$result" | grep -q '^FAIL\b\|^\*\*FAIL\*\*\|Status:.*FAIL'; then echo "$result" | grep -iE 'STOP' | head -3 | sed 's/^/ /' >&2
elif echo "$result" | grep -qiE '\*\*FAIL\*\*|^FAIL:|^FAIL\b|Status:.*FAIL|FAIL: could not'; then
status="FAIL" status="FAIL"
should_keep=1
printf '\033[1;31m✗ %s: FAIL\033[0m\n' "$tid" >&2 printf '\033[1;31m✗ %s: FAIL\033[0m\n' "$tid" >&2
echo "$result" | grep -iE 'FAIL' | head -3 | sed 's/^/ /' >&2
else else
printf '\033[1;35m? %s: status unclear — check log\033[0m\n' "$tid" >&2 if echo "$result" | grep -qiE 'compiles clean|no action needed'; then
status="DONE"
echo "$tid" >> "$DONE_FILE"
printf '\033[1;32m✓ %s: DONE (inferred from output)\033[0m\n' "$tid" >&2
else
status="UNKNOWN"
should_keep=1
printf '\033[1;35m? %s: status unclear — review log\033[0m\n' "$tid" >&2
echo "$result" | grep -v '^$' | tail -5 | sed 's/^/ /' >&2
fi
fi fi
printf ' Log: %s\n' "$LOG_DIR/${tid}.log" >&2 printf ' Log: %s\n' "$LOG_DIR/${tid}.log" >&2
# Clean up session: keep on failure/unclear for inspection, kill on success
if [ "$should_keep" -eq 1 ]; then
printf ' Session: %s\n' "$session_path" >&2
elif [ -d "$session_path" ]; then
echo kill > "$session_path/ctl" 2>/dev/null || true
fi
fi fi
} }

222
tools/wm-port/wm-gen Executable file
View File

@ -0,0 +1,222 @@
#!/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