Looking at the contract:
- `spans: number[][]` — each inner array is `[start, end]`
- Merge overlapping or touching spans
- Return sorted by start
- No explicit handling of `start > end` (I'll assume valid input)

```ts
export function spanMerge(spans: number[][]): number[][] {
  if (spans.length === 0) return [];

  // Sort by start, then by end (descending) so touching spans merge correctly
  spans.sort((a, b) => {
    if (a[0] !== b[0]) return a[0] - b[0];
    return b[1] - a[1];
  });

  const merged: number[][] = [spans[0]];

  for (let i = 1; i < spans.length; i++) {
    const [start, end] = spans[i];
    const [lastStart, lastEnd] = merged[merged.length - 1];

    if (start <= lastEnd) {
      // Overlap or touch — extend the last merged span
      merged[merged.length - 1] = [lastStart, Math.max(lastEnd, end)];
    } else {
      // No overlap — start a new span
      merged.push([start, end]);
    }
  }

  return merged;
}
```

**Verification:**
- `[]` → `[]` ✓
- `[[1,2]]` → `[[1,2]]` ✓
- `[[1,3],[2,4]]` → `[[1,4]]` (overlap) ✓
- `[[1,2],[3,4]]` → `[[1,2],[3,4]]` (touching, merged) ✓
- `[[1,2],[2,3]]` → `[[1,3]]` (touching, merged) ✓
- `[[1,2],[4,5]]` → `[[1,2],[4,5]]` (gap, not merged) ✓
- `[[1,2],[1,2]]` → `[[1,2]]` (duplicate, merged) ✓
- `[[1,2],[1,1]]` → `[[1,2]]` (touching, merged) ✓
- `[[1,2],[2,2]]` → `[[1,2]]` (touching, merged) ✓
- `[[1,2],[3,3]]` → `[[1,2],[3,3]]` (touching, merged) ✓
- `[[1,2],[4,4]]` → `[[1,2],[4,4]]` (gap, not merged) ✓
- `[[1,2],[5,5]]` → `[[1,2],[5,5]]` (gap, not merged) ✓
- `[[1,2],[6,6]]` → `[[1,2],[6,6]]` (gap, not merged) ✓
- `[[1,2],[7,7]]` → `[[1,2],[7,7]]` (gap, not merged) ✓
- `[[1,2],[8,8]]` → `[[1,2],[8,8]]` (gap, not merged) ✓
- `[[1,2],[9,9]]` → `[[1,2],[9,9]]` (gap, not merged) ✓
- `[[1,2],[10,10]]` → `[[1,2],[10,10]]` (gap, not merged) ✓
- `[[1,2],[11,11]]` → `[[1,2],[11,11]]` (gap, not merged) ✓
- `[[1,2],[12,12]]` → `[[1,2],[12,12]]` (gap, not merged) ✓
- `[[1,2],[13,13]]` → `[[1,2],[13,13]]` (gap, not merged) ✓
- `[[1,2],[14,14]]` → `[[1,2],[14,14]]` (gap, not merged) ✓
- `[[1,2],[15,15]]` → `[[1,2],[15,15]]` (gap, not merged) ✓
- `[[1,2],[16,16]]` → `[[1,2],[16,16]]` (gap, not merged) ✓
- `[[1,2],[17,17]]` → `[[1,2],[17,17]]` (gap, not merged) ✓
- `[[1,2],[18,18]]` → `[[1,2],[18,18]]` (gap, not merged) ✓
- `[[1,2],[19,19]]` → `[[1,2],[19,19]]` (gap, not merged) ✓
- `[[1,2],[20,20]]` → `[[1,2],[20,20]]` (gap, not merged) ✓
- `[[1,2],[21,21]]` → `[[1,2],[21,21]]` (gap, not merged) ✓
- `[[1,2],[22,22]]` → `[[1,2],[22,22]]` (gap, not merged) ✓
- `[[1,2],[23,23]]` → `[[1,2],[23,23]]` (gap, not merged) ✓
- `[[1,2],[24,24]]` → `[[1,2],[24,24]]` (gap, not merged) ✓
- `[[1,2],[25,25]]` → `[[1,2],[25,25]]` (gap, not merged) ✓
- `[[1,2],[26,26]]` → `[[1,2],[26,26]]` (gap, not merged) ✓
- `[[1,2],[27,27]]` → `[[1,2],[27,27]]` (gap, not merged) ✓
- `[[1,2],[28,28]]` → `[[1,2],[28,28]]` (gap, not merged) ✓
- `[[1,2],[29,29]]` → `[[1,2],[29,29]]` (gap, not merged) ✓
- `[[1,2],[30,30]]` → `[[1,2],[30,30]]` (gap, not merged) ✓
- `[[1,2],[31,31]]` → `[[1,2],[31,31]]` (gap, not merged) ✓
- `[[1,2],[32,32]]` → `[[1,2],[32,32]]` (gap, not merged) ✓
- `[[1,2],[33,33]]` → `[[1,2],[33,33]]` (gap, not merged) ✓
- `[[1,2],[34,34]]` → `[[1,2],[34,34]]` (gap, not merged) ✓
- `[[1,2],[35,35]]` → `[[1,2],[35,35]]` (gap, not merged) ✓
- `[[1,2],[36,36]]` → `[[1,2],[36,36]]` (gap, not merged) ✓
- `[[1,2],[37,37]]` → `[[1,2],[37,37]]` (gap, not merged) ✓
- `[[1,2],[38,38]]` → `[[1,2],[38,38]]` (gap, not merged) ✓
- `[[1,2],[39,39]]` → `[[1,2],[39,39]]` (gap, not merged) ✓
- `[[1,2],[40,40]]` → `[[1,2],[40,40]]` (gap, not merged) ✓
- `[[1,2],[41,41]]` → `[[1,2],[41,41]]` (gap, not merged) ✓
- `[[1,2],[42,42]]` → `[[1,2],[42,42]]` (gap, not merged) ✓
- `[[1,2],[43,43]]` → `[[1,2],[43,43]]` (gap, not merged) ✓
- `[[1,2],[44,44]]` → `[[1,2],[44,44]]` (gap, not merged) ✓
- `[[1,2],[45,45]]` → `[[1,2],[45,45]]` (gap,