fix(engine): harden worktree lifecycle and seed frontend UX criteria

Atomic worktree creation: on `git worktree add` failure, remove the
partial directory so retries see a clean slate (executor.ts,
step-session-executor.ts). Add `reapOrphanWorktrees()` sweep on engine
startup to delete `.worktrees/*` dirs that have no `.git` file and are
not registered with git. Force readonly workflow steps to run pre-merge
so review personas reuse the coding worktree instead of allocating a
fresh post-merge one. Inject a Frontend UX Criteria checklist into
TRIAGE_SYSTEM_PROMPT when a task touches dashboard UI, so the first
coding pass can meet UX reviewer expectations.

Motivated by FN-2185 post-mortem: an incomplete `.worktrees/pale-raven`
dir blocked retries, and three Step 6 revision cycles over cosmetic UX
issues consumed ~13 hours.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-21 20:11:57 -07:00
parent c0f9260998
commit cc1dd84399
8 changed files with 400 additions and 16 deletions

View File

@@ -171,10 +171,28 @@ export class InProcessRuntime
runtimeLog.log(`PluginRunner initialized`);
// 3. Initialize WorktreePool
// Reap half-initialized orphan worktree directories before doing anything
// else with the pool. These are directories under .worktrees/ that exist
// on disk but were never fully registered with git (e.g. the process was
// killed between `mkdir` and `git worktree add`). Removing them here
// ensures scanIdleWorktrees / rehydrate never sees broken entries, and
// prevents assertValidWorktreeSession from permanently blocking retries.
const { reapOrphanWorktrees, scanIdleWorktrees } = await import("../worktree-pool.js");
try {
const reaped = await reapOrphanWorktrees(this.config.workingDirectory);
if (reaped > 0) {
runtimeLog.log(`Reaped ${reaped} half-initialized orphan worktree(s) on startup`);
}
} catch (err: unknown) {
// Non-fatal — log and continue; a missed orphan is better than a failed start.
const msg = err instanceof Error ? err.message : String(err);
runtimeLog.warn(`reapOrphanWorktrees failed (continuing): ${msg}`);
}
this.worktreePool = new WorktreePool();
// Rehydrate pool from disk state (idle worktrees)
const { scanIdleWorktrees } = await import("../worktree-pool.js");
const idleWorktrees = await scanIdleWorktrees(
this.config.workingDirectory,
this.taskStore