Awesome Reviewers

When errors occur—especially in multi-step operations—avoid accidental data loss and avoid “leaking” raw failures. Instead, preserve rollback artifacts and convert errors into documented, structured outcomes.

Apply this as two rules: 1) Recovery-first failure handling

Example pattern (rollback-safe):

async function atomicSwapDir(stagedDir, targetDir) {
  const parent = path.dirname(targetDir);
  await fsp.mkdir(parent, { recursive: true });
  const suffix = crypto.randomBytes(6).toString('hex');
  const sibling = path.join(parent, `.${path.basename(targetDir)}.tmp-${suffix}`);
  const backup  = path.join(parent, `.${path.basename(targetDir)}.old-${suffix}`);

  try {
    await fsp.rename(stagedDir, sibling);
    const hadTarget = await fsp.stat(targetDir).then(() => true).catch(() => false);
    if (hadTarget) await fsp.rename(targetDir, backup);
    await fsp.rename(sibling, targetDir);
    if (hadTarget) await fsp.rm(backup, { recursive: true, force: true });
  } catch (e) {
    await fsp.rm(sibling, { recursive: true, force: true });
    // IMPORTANT: do NOT delete backup in the outer catch.
    // Preserve backup for recovery; optionally print its path to stderr.
    // console.error(`Swap failed; preserved backup at: ${backup}`);
    throw e;
  }
}

2) Structured, documented CLI outcomes

This combination prevents both worst-case outcomes: irrecoverable state loss and confusing/undocumented runtime failures.