Skip to content

MAI-43: fix subcommand-flag validation contract#13

Merged
vsmash merged 2 commits into
developfrom
bug/MAI-43_subcommand_flag_validation_contract
May 13, 2026
Merged

MAI-43: fix subcommand-flag validation contract#13
vsmash merged 2 commits into
developfrom
bug/MAI-43_subcommand_flag_validation_contract

Conversation

@vsmash
Copy link
Copy Markdown
Owner

@vsmash vsmash commented May 13, 2026

Summary

Fixes the broader class of subcommand-flag rejection bugs surfaced by MAI-39's narrow config-only fix.

The global flag validator at maiass.mjs was rejecting any subcommand-specific flag (version --current, account-info --json, etc.) as "unrecognized option" before it reached the handler that would process it. MAI-39 patched this for config only with a wholesale skip, which then created a new problem — typos like config --globl silently fell through.

This PR generalises the fix with Option B (per-subcommand FLAGS allow-list) and gets useful typo-suggestion errors for free.

Architecture

Each subcommand handler now exports a FLAGS array of the flags it accepts. maiass.mjs looks up the active subcommand's flag list and validates the argv against the union of ALWAYS_VALID_FLAGS (globals like --help, --version) + the subcommand's FLAGS. Unknown flags are rejected with a "Did you mean '--foo'?" suggestion driven by a tiny inline Levenshtein implementation (no new deps).

Files

  • NEW lib/flag-validator.jsvalidateFlags(), ALWAYS_VALID_FLAGS, suggestFlag() + inline Levenshtein.
  • maiass.mjs — imports each handler's FLAGS; replaces the hardcoded validFlags array AND the MAI-39 command === 'config' skip with a per-command lookup. Auto-mode env-var assignment moved BELOW validation (was a contained leak before — account-info --auto would set MAIASS_AUTO_* before rejection).
  • lib/version-command.js, lib/config-command.js, lib/account-info.js, lib/maiass-command.jsexport const FLAGS = [...] of each handler's flags.
  • lib/git-info.js, lib/env-display.jsexport const FLAGS = [] (no CLI flags accepted today, but the contract is consistent).

Bugs fixed

  • maiass version --current — was rejected; now works (root cause of MAI-43).
  • config --globl ai_mode=ask"Did you mean '--global'?" (closes the MAI-39 code-review MINOR).
  • version --bogus, git-info --bogus, etc. → rejected with the valid-flags list.

Code review (already done)

Reviewer flagged 3 MINORs, all fixed in follow-up commit dfb6ab4:

  1. --json was in ALWAYS_VALID_FLAGS but is account-info-specific — moved exclusively to account-info's FLAGS export so version --json is now correctly rejected (was silently ignored).
  2. Auto-mode env-var assignment (MAIASS_AUTO_*) was happening BEFORE validation; moved to AFTER so rejected commands don't leak.
  3. Dead || validFlags.includes(arg) clause in validateFlags() removed (the = split makes both forms identical).

Test plan

  • version --current → prints version (previously rejected)
  • All 4 MAI-39 config invocations still pass — no regression
  • version --json, account-info --auto → rejected (loose contract tightened)
  • config --globl ai_mode=ask → "Did you mean '--global'?" — MAI-39 MINOR closed
  • Main workflow (maiass, maiass minor, maiass --auto) still runs
  • npm test → 10/10 pass

Intentional semantic narrowing (NOT a regression)

--account-info --auto is now rejected because --auto isn't a valid flag for account-info. Historically the global validator accepted any listed flag regardless of command — --auto was silently swallowed by handleAccountInfoCommand (which only reads { json }). Confirmed by code-reviewer as correct tightening, not a regression.

Follow-ups (will file post-merge)

  • Redundant version --current flag. Does exactly the same thing as maiass version with no arguments — both call getCurrentVersion() + displayVersionInfo(). Now reachable through the validator, but the redundancy is its own concern.
  • bashmaiass parity — likely has analogous flag-rejection bugs. Worth a bash-cli-specialist audit.

Ticket: https://velvary.atlassian.net/browse/MAI-43

Tyler Durton and others added 2 commits May 14, 2026 05:04
The global flag validator in maiass.mjs maintained a single hardcoded list
of valid flags and rejected anything else as "unrecognized option". This
meant legitimate subcommand flags like `version --current` were rejected
before reaching their handler. MAI-39 patched the config case with a
wholesale bypass, but typos on config flags then silently fell through.

This change introduces a per-subcommand FLAGS allow-list (Option B):

  * Each subcommand handler exports `FLAGS` — the flags it accepts.
  * `lib/flag-validator.js` validates argv against the union of always-
    valid built-ins and the active subcommand's FLAGS.
  * Unknown flags are still rejected with a useful error and a
    Levenshtein-based "Did you mean…?" suggestion (no new deps).

Effects:
  * `maiass version --current` now works (previously rejected).
  * `maiass config --globl ai_mode=ask` is now rejected with a typo hint
    (closes the MAI-39 MINOR).
  * `maiass config <four MAI-39 invocations>` continues to work.
  * Main pipeline (`maiass`, `maiass minor`, `maiass --auto`) unaffected.

Per-subcommand FLAGS:
  * version: --current, --dry-run, --tag, --force
  * config:  --global, --project, --edit, --list, --show-sensitive,
             --list-vars
  * account-info: --json
  * maiass (workflow): --auto/-a, --auto-commit/-ac, --commits-only/-c,
             --auto-stage, --dry-run/-d, --force/-f, --silent/-s, --tag/-t
  * hello, env, git-info: none currently — declared empty for the contract

Help text updated to document subcommand-specific flag conventions.
All 10 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove `--json` from ALWAYS_VALID_FLAGS (lib/flag-validator.js). It's
  already exported by account-info's FLAGS, so the global listing was
  a loose contract: `version --json` / `config --json` passed validation
  and were silently ignored by handlers that don't consume them.
- Drop the redundant `|| validFlags.includes(arg)` clause in
  validateFlags(). The `=` split already normalises both forms; the
  second check could never match.
- Move auto-mode env-var assignment (MAIASS_AUTO_*) to AFTER flag
  validation in maiass.mjs. Previously they were set before validation,
  so `account-info --auto` would write MAIASS_AUTO_STAGE_UNSTAGED etc.
  into process.env, then the validator would reject and exit. Contained
  (process exits 1) but order-of-operations was wrong.

Verified:
  - `version --json` -> "Unrecognized option '--json' for command 'version'"
  - `account-info --json` -> still works (in account-info's FLAGS export)
  - `account-info --auto` -> rejected before env-var leak
  - 10/10 tests pass

https://velvary.atlassian.net/browse/MAI-43
@vsmash vsmash merged commit b543684 into develop May 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant