( command: string, commandSet: Set<string>, aliasMap: Record<string, string> = COMMAND_ALIASES, newInVersion: Record<string, string> = NEW_IN_VERSION, )
| 263 | * as more commands land. |
| 264 | */ |
| 265 | export function buildUnknownCommandError( |
| 266 | command: string, |
| 267 | commandSet: Set<string>, |
| 268 | aliasMap: Record<string, string> = COMMAND_ALIASES, |
| 269 | newInVersion: Record<string, string> = NEW_IN_VERSION, |
| 270 | ): string { |
| 271 | let msg = `Unknown command: '${command}'.`; |
| 272 | |
| 273 | // Suggestion via Levenshtein, gated on input length to avoid noisy short-input matches. |
| 274 | // Candidates are pre-sorted alphabetically, so strict "d < bestDist" gives us the |
| 275 | // closest match with alphabetical tiebreak for free — first equal-distance candidate |
| 276 | // wins because subsequent equal-distance candidates fail the strict-less check. |
| 277 | if (command.length >= 4) { |
| 278 | let best: string | undefined; |
| 279 | let bestDist = 3; // sentinel: distance 3 would be rejected by the <= 2 gate below |
| 280 | const candidates = [...commandSet, ...Object.keys(aliasMap)].sort(); |
| 281 | for (const cand of candidates) { |
| 282 | const d = levenshtein(command, cand); |
| 283 | if (d <= 2 && d < bestDist) { |
| 284 | best = cand; |
| 285 | bestDist = d; |
| 286 | } |
| 287 | } |
| 288 | if (best) msg += ` Did you mean '${best}'?`; |
| 289 | } |
| 290 | |
| 291 | if (newInVersion[command]) { |
| 292 | msg += ` This command was added in browse v${newInVersion[command]}. Upgrade: cd ~/.claude/skills/gstack && git pull && bun run build.`; |
| 293 | } |
| 294 | |
| 295 | return msg; |
| 296 | } |
no test coverage detected