```ts
/**
 * Runs a patrol bot in a corridor and returns its final position, number of bumps,
 * and visited cells.
 *
 * @param width - The width of the corridor (positive integer).
 * @param start - The starting position of the bot (integer within the corridor).
 * @param moves - An array of moves to apply to the bot (non-zero integers).
 * @returns An object containing the final position, number of bumps, and visited cells.
 */
export function runPatrol(width: number, start: number, moves: number[]): Record<string, number> {
  if (!Number.isInteger(width) || width <= 0) {
    throw new Error('Width must be a positive integer.');
  }
  if (!Number.isInteger(start) || start < 0 || start >= width) {
    throw new Error('Start position must be an integer within the corridor.');
  }
  if (!Array.isArray(moves)) {
    throw new Error('Moves must be an array of integers.');
  }

  let position = start;
  let bumps = 0;
  const visited = new Set<number>();

  for (const move of moves) {
    if (!Number.isInteger(move) || move === 0) {
      throw new Error('Move must be a non-zero integer.');
    }
    [position, bump] = clampMove(position, move, width);
    bumps += bump ? 1 : 0;
    visited.add(position);
  }

  return { position, bumps, visited: visited.size };
}

/**
 * Clamps a move to the corridor and returns the new position and whether a wall was hit.
 *
 * @param position - The current position of the bot (integer within the corridor).
 * @param delta - The move to apply (signed integer).
 * @param width - The width of the corridor (positive integer).
 * @returns A tuple containing the new position and a boolean indicating if a wall was hit.
 */
export function clampMove(position: number, delta: number, width: number): [number, boolean] {
  const newPosition = position + delta;
  return newPosition === 0 || newPosition === width - 1 ? [newPosition, true] : [newPosition, false];
}
```