diff --git a/sdk/src/query/registry.test.ts b/sdk/src/query/registry.test.ts index 5e867b8e..ea42b147 100644 --- a/sdk/src/query/registry.test.ts +++ b/sdk/src/query/registry.test.ts @@ -178,4 +178,31 @@ describe('resolveQueryArgv', () => { args: [], }); }); + + // Regression: #2597 — dotted command token followed by positional args. + // Before the fix, argv like ['init.execute-phase', '1'] returned null because + // expansion only ran for single-token input. + it('matches a dotted command token when positional args follow (#2597)', () => { + const registry = createRegistry(); + expect(resolveQueryArgv(['init.execute-phase', '1'], registry)).toEqual({ + cmd: 'init.execute-phase', + args: ['1'], + }); + }); + + it('matches dotted state.update with trailing args (#2597)', () => { + const registry = createRegistry(); + expect(resolveQueryArgv(['state.update', 'status', 'X'], registry)).toEqual({ + cmd: 'state.update', + args: ['status', 'X'], + }); + }); + + it('matches dotted phase.add with trailing args (#2597)', () => { + const registry = createRegistry(); + expect(resolveQueryArgv(['phase.add', 'desc'], registry)).toEqual({ + cmd: 'phase.add', + args: ['desc'], + }); + }); }); diff --git a/sdk/src/query/registry.ts b/sdk/src/query/registry.ts index f31080fa..01a143d0 100644 --- a/sdk/src/query/registry.ts +++ b/sdk/src/query/registry.ts @@ -126,15 +126,28 @@ export class QueryRegistry { } } -function expandSingleDottedToken(tokens: string[]): string[] { - if (tokens.length !== 1 || tokens[0].startsWith('--')) { +/** + * If the first token contains a dot (e.g. `init.execute-phase`), split it into + * segments and prepend those segments in place of the original token. Args that + * follow the dotted token are preserved. + * + * Examples: + * ['init.new-project'] -> ['init', 'new-project'] + * ['init.execute-phase', '1'] -> ['init', 'execute-phase', '1'] + * ['state.update', 'status', 'X'] -> ['state', 'update', 'status', 'X'] + * + * Returns the original array (by reference) when no expansion applies so callers + * can detect "nothing changed" via identity comparison. + */ +function expandFirstDottedToken(tokens: string[]): string[] { + if (tokens.length === 0) { return tokens; } - const t = tokens[0]; - if (!t.includes('.')) { + const first = tokens[0]; + if (first.startsWith('--') || !first.includes('.')) { return tokens; } - return t.split('.'); + return [...first.split('.'), ...tokens.slice(1)]; } function matchRegisteredPrefix( @@ -166,7 +179,7 @@ export function resolveQueryArgv( ): { cmd: string; args: string[] } | null { let matched = matchRegisteredPrefix(tokens, registry); if (!matched) { - const expanded = expandSingleDottedToken(tokens); + const expanded = expandFirstDottedToken(tokens); if (expanded !== tokens) { matched = matchRegisteredPrefix(expanded, registry); }