- executor.test.ts: remove unused imports (Column, StuckTaskDetector),
replace Function type with EventListener, add MockTaskStore interface
- restart.integration.test.ts: replace require() with ESM import,
replace Function types with proper function signatures
- All tests pass
- Remove unused imports across 25 files in engine package
- Remove unused variable declarations in ipc-worker.ts, child-process-runtime.ts, and mission-autopilot.ts
- Clean up unnecessary imports in agent-instructions.ts, agent-tools.ts, cron-runner.ts, executor.ts, and other modules
- Minor cleanup in notifier.ts, peer-exchange-service.ts, pi.ts, plugin-runner.ts, and other files
- Improves code quality and reduces potential confusion from unused code
Multiple engine processes (dashboard + serve) share the same SQLite database
but each has its own in-memory merge queue. Without a cross-process check,
two processes can start merging different tasks simultaneously.
Added store.getActiveMergingTask() as a DB-level check before any merge
starts. The drainMergeQueue defers with pollIntervalMs delay, and both
aiMergeTask and processPullRequestMergeTask have safety-net checks.
Also moved stale merge status cleanup to run regardless of autoMerge setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: if a merge crashed or the process restarted mid-merge, the
"merging" status was never cleared. On next startup the stale task kept
its "merging" status while the queue moved on to the next task, resulting
in two tasks appearing to merge at once.
Two fixes:
1. Add "merging"/"merging-pr" to BLOCKING_TASK_STATUSES so tasks with
active merge status are not re-enqueued by the retry sweep.
2. Clear stale "merging" statuses during startup merge sweep — no merge
is actually running at engine start, so any such status is a leftover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The onMerge() path (dashboard "merge now" button) bypassed the
drainMergeQueue serialization, allowing two tasks to enter "merging"
status simultaneously within the same project. Route manual merges
through the same queue so only one merge runs at a time per project.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a merge completes but auto-recovery moves the task back to
in-review, the retry gating (mergeRetries >= 3) blocked re-processing.
Now canMergeTask always accepts mergeConfirmed tasks and drainMergeQueue
fast-paths them directly to done without re-running the merge agent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>