mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
resolveQueryArgv only expanded `init.execute-phase` → `init execute-phase` when the tokens array had length 1. Argv like `init.execute-phase 1` has length 2, skipped the expansion, and resolved to no registered handler. All 50+ workflow files use the dotted form with arguments, so this broke every non-argless query route (`init.execute-phase`, `state.update`, `phase.add`, `milestone.complete`, etc.) at runtime. Rename `expandSingleDottedToken` → `expandFirstDottedToken`: split only the first token on its dots (guarding against `--` flags) and preserve the tail as positional args. Identity comparison at the call site still detects "no expansion" since we return the input array unchanged. Adds regression tests for the three failure patterns reported: `init.execute-phase 1`, `state.update status X`, `phase.add desc`. Closes #2597
This commit is contained in:
@@ -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'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user