diff --git a/tools/wm-port/README.md b/tools/wm-port/README.md index 3c6675b..4ac84e3 100644 --- a/tools/wm-port/README.md +++ b/tools/wm-port/README.md @@ -5,48 +5,80 @@ Agent-assisted task execution for the Wayland port, driven by `ACTION_PLAN.md` a ## Quick Start ```bash -# Add to PATH (or use full paths) export PATH="$PWD/tools/wm-port:$PATH" # See what's available wm-tasks -# Dispatch a single task (foreground — waits for completion) +# Dispatch a task from the action plan wm-dispatch B.1 -# Dispatch and return immediately (background) -wm-dispatch --bg B.1 B.2 B.3 +# Generate an ad-hoc task from a template +wm-gen 3 "Replace XClearArea at wrlib/context.c:730 with wm_backend->window_clear_area" -# Monitor running agents +# Monitor wm-status --watch -# Follow a specific agent's work live -wm-status --follow B.1 +# Collect results from background tasks +wm-collect -# Review completed work -wm-review --pending +# Review diffs wm-review B.1 - -# Dispatch all unblocked Wave 1 tasks in parallel -wm-dispatch --wave 1 --bg ``` ## Scripts -| Script | Purpose | -|---|---| -| `wm-dispatch` | Dispatch tasks to agent sessions | -| `wm-tasks` | Show task board (status, waves, blockers) | -| `wm-status` | Monitor active agent sessions | -| `wm-review` | Interactive diff review, approve/reject | -| `wm-done` | Manually mark tasks done/undone | -| `wm-kill` | Kill running agent sessions | +| Script | Purpose | Default model | +|---|---|---| +| `wm-dispatch` | Execute action plan tasks | `qwen/qwen3-coder` | +| `wm-gen` | Generate ad-hoc tasks from templates | `anthropic/claude-sonnet-4` | +| `wm-tasks` | Show task board (status, waves, blockers) | — | +| `wm-status` | Monitor active agent sessions | — | +| `wm-collect` | Harvest results from background 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 ### 1. Pick tasks ```bash +wm-tasks # full board wm-tasks --ready # what can be dispatched now? wm-tasks --wave 1 # what's in wave 1? wm-tasks B.1 # details on a specific task @@ -55,49 +87,37 @@ wm-tasks B.1 # details on a specific task ### 2. Dispatch ```bash -# Single task, wait for result -wm-dispatch A5.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 +# From the action plan +wm-dispatch B.1 wm-dispatch --wave 1 --bg -# Next 5 unblocked tasks regardless of wave -wm-dispatch --next 5 --bg - -# Use a different model -wm-dispatch --model anthropic/claude-opus-4 D.1 +# Ad-hoc from template +wm-gen 3 "Replace XFoo at src/bar.c:42 with wm_backend->baz" ``` ### 3. Monitor ```bash -# Dashboard (refreshes every 2s) -wm-status --watch - -# Follow one agent's chat live -wm-status --follow A5.1 - -# Kill a stuck agent -wm-kill A5.1 - -# Kill all -wm-kill --all +wm-status --watch # dashboard (refreshes every 2s) +wm-status --follow A5.1 # tail one agent's chat live +wm-kill A5.1 # kill a stuck agent +wm-kill --all # kill all ``` -### 4. Review +### 4. Collect (background tasks) ```bash -# List branches ready for review -wm-review --pending +wm-collect # harvest all finished sessions +wm-collect --dry-run # preview without acting +``` -# Interactive review (shows diff, offers approve/reject/edit) -wm-review A5.1 +Scans idle `wm-port-*` sessions, reads results, updates `done.txt`, saves logs, kills successful sessions. Failed/unclear sessions are kept for inspection. + +### 5. Review (code change tasks) + +```bash +wm-review --pending # list branches ready for review +wm-review B.1 # interactive review ``` The review flow: @@ -111,26 +131,40 @@ The review flow: - **edit** — opens changed files in `$EDITOR` - **skip** — come back later -### 5. Track +### 6. Track ```bash -# Mark a task done manually (e.g., verified by hand) -wm-done G.1 - -# Re-open a task -wm-done --undo G.1 - -# Full board -wm-tasks +wm-done G.1 # manually mark done +wm-done --undo G.1 # re-open +wm-tasks # updated board ``` -## 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` -2. It composes a prompt: task description + template procedure + reference tables -3. It spawns an ollie agent session (`coding` agent config) via `$OLLIE/s/b` -4. The agent creates a `port/` git branch, makes the changes, verifies, and reports status -5. You review the branch with `wm-review` +2. `wm-gen` takes a template number + description directly +3. Both compose a prompt: task description + template procedure + reference tables +4. An ollie agent session is spawned via `$OLLIE/s/b` +5. The agent creates a `port/` git branch, makes changes, verifies, reports status +6. You review with `wm-review` or results are harvested by `wm-collect` ## Files @@ -138,10 +172,12 @@ wm-tasks tools/wm-port/ task-map.sh # Task database: ID→template, ID→wave, ID→blockers done.txt # Completed task IDs (one per line) - logs/ # Agent output logs (one per task) - wm-dispatch # Dispatcher + logs/ # Agent output logs (one per task/name) + wm-dispatch # Action plan task dispatcher + wm-gen # Ad-hoc task generator from templates wm-tasks # Task board wm-status # Session monitor + wm-collect # Background session harvester wm-review # Diff reviewer wm-done # Manual done/undo wm-kill # Session killer @@ -149,9 +185,10 @@ tools/wm-port/ ## 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` | LLM backend | -| `--model` | `anthropic/claude-sonnet-4` | LLM model | -| `--agent` | `coding` | Ollie agent config | +| `--backend` | `openrouter` | `openrouter` | +| `--model` | `qwen/qwen3-coder` | `anthropic/claude-sonnet-4` | +| `--agent` | `coding` | `coding` | diff --git a/tools/wm-port/logs/F.2.log b/tools/wm-port/logs/F.2.log new file mode 100644 index 0000000..fbe1e62 --- /dev/null +++ b/tools/wm-port/logs/F.2.log @@ -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 + - FAIL: could not complete because + +Line numbers in ACTION_PLAN.md may have drifted. Use grep to find current line numbers before editing. diff --git a/tools/wm-port/wm-collect b/tools/wm-port/wm-collect new file mode 100755 index 0000000..6c9efeb --- /dev/null +++ b/tools/wm-port/wm-collect @@ -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 diff --git a/tools/wm-port/wm-dispatch b/tools/wm-port/wm-dispatch index 2634b46..579a51d 100755 --- a/tools/wm-port/wm-dispatch +++ b/tools/wm-port/wm-dispatch @@ -1,8 +1,8 @@ #!/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: -# wm-dispatch # dispatch one task +# wm-dispatch # dispatch one task (foreground) # wm-dispatch ... # dispatch multiple in parallel # wm-dispatch --wave # dispatch all unblocked tasks in wave N # wm-dispatch --next [N] # dispatch next N unblocked tasks (default 1) @@ -10,9 +10,11 @@ # Options: # --dry-run Print the prompt instead of dispatching # --backend B Override backend (default: openrouter) -# --model M Override model (default: anthropic/claude-sonnet-4) -# --keep Don't auto-kill session on completion -# --bg Submit and return immediately (print session paths) +# --model M Override model (default: qwen/qwen3-coder) +# --keep Keep session on success (failures always kept) +# --bg Submit and return immediately (use wm-collect to harvest) +# +# See also: wm-gen (ad-hoc tasks from templates) set -euo pipefail @@ -26,7 +28,7 @@ source "$TOOLS/task-map.sh" # Defaults backend="openrouter" -model="anthropic/claude-sonnet-4" +model="qwen/qwen3-coder" dry_run=0 keep=0 bg=0 @@ -163,7 +165,34 @@ build_prompt() { local ref_tables ref_tables=$(extract_reference_tables) - cat <&2 if [ "$bg" -eq 1 ]; then - local session_path - session_path=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}") - printf '%s\t%s\n' "$tid" "$session_path" - printf '\033[1;33m → %s\033[0m\n' "$session_path" >&2 + local sp + sp=$(printf '%s' "$prompt" | "$MOUNT/s/b" "${flags[@]}") + printf '%s\t%s\n' "$tid" "$sp" + printf '\033[1;33m → %s\033[0m\n' "$sp" >&2 else 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 printf '%s\n' "$result" > "$LOG_DIR/${tid}.log" - # Parse result status + # Parse result status — search broadly since agents format varies 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" echo "$tid" >> "$DONE_FILE" 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" + should_keep=1 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" + should_keep=1 printf '\033[1;31m✗ %s: FAIL\033[0m\n' "$tid" >&2 + echo "$result" | grep -iE 'FAIL' | head -3 | sed 's/^/ /' >&2 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 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 } diff --git a/tools/wm-port/wm-gen b/tools/wm-port/wm-gen new file mode 100755 index 0000000..8fbcdc4 --- /dev/null +++ b/tools/wm-port/wm-gen @@ -0,0 +1,222 @@ +#!/usr/bin/env bash +# wm-gen: Generate and dispatch a task from a template + description. +# +# Usage: +# wm-gen