// lib/parsers/arg-parser.mjs // Parse $ARGUMENTS strings for the four ultra commands. // // Each command has its own valid-flag set; passing flags from another command // produces an `unknown_flags` array but does not error — the caller decides. const FLAG_SCHEMA = { ultrabrief: { boolean: ['--quick', '--fg'], valued: [], aliases: {}, }, ultraresearch: { boolean: ['--quick', '--local', '--external', '--fg'], valued: ['--project'], aliases: {}, }, ultraplan: { boolean: ['--quick', '--fg'], valued: ['--project', '--brief', '--export', '--decompose'], multi: ['--research'], aliases: {}, }, ultraexecute: { boolean: ['--resume', '--dry-run', '--validate', '--fg'], valued: ['--project', '--step', '--session'], aliases: {}, }, ultrareview: { boolean: ['--quick', '--fg', '--dry-run', '--validate'], valued: ['--project', '--since'], aliases: {}, }, }; /** * @param {string} argString Raw $ARGUMENTS as the command sees it. * @param {keyof FLAG_SCHEMA} command * @returns {{ * command: string, * flags: Record, * positional: string[], * unknown: string[], * errors: Array<{code: string, message: string}>, * }} */ export function parseArgs(argString, command) { const schema = FLAG_SCHEMA[command]; if (!schema) { return { command, flags: {}, positional: [], unknown: [], errors: [{ code: 'ARG_UNKNOWN_COMMAND', message: `Unknown command: ${command}` }], }; } const tokens = tokenize(argString); const flags = {}; const positional = []; const unknown = []; const errors = []; for (let i = 0; i < tokens.length; i++) { const tok = tokens[i]; if (!tok.startsWith('--')) { positional.push(tok); continue; } if (schema.boolean.includes(tok)) { flags[tok] = true; continue; } if (schema.valued.includes(tok)) { const next = tokens[i + 1]; if (next === undefined || next.startsWith('--')) { errors.push({ code: 'ARG_MISSING_VALUE', message: `Flag ${tok} requires a value` }); } else { flags[tok] = next; i++; } continue; } if (schema.multi && schema.multi.includes(tok)) { const collected = []; while (i + 1 < tokens.length && !tokens[i + 1].startsWith('--')) { collected.push(tokens[i + 1]); i++; } if (collected.length === 0) { errors.push({ code: 'ARG_MISSING_VALUE', message: `Flag ${tok} requires at least one value` }); } else { flags[tok] = collected; } continue; } unknown.push(tok); } return { command, flags, positional, unknown, errors }; } function tokenize(s) { if (typeof s !== 'string') return []; const trimmed = s.trim(); if (trimmed === '') return []; const out = []; const re = /"([^"]*)"|'([^']*)'|(\S+)/g; let m; while ((m = re.exec(trimmed)) !== null) { out.push(m[1] !== undefined ? m[1] : m[2] !== undefined ? m[2] : m[3]); } return out; } export { FLAG_SCHEMA };