refactor(query): deepen command/dispatch seams and resolve coderabbit findings

* refactor(query): deepen command definition seam and fold fallback mapping cleanup

* refactor(query): add shared dispatch formatting module seam

* fix(query): restore QueryResult type import in dispatch deps

* test/query: align raw-output policy and definition normalization contracts

* refactor(query): deepen diagnosis, invariant report, and error taxonomy seams

* refactor(query): deepen dispatch plan, fallback bridge, policy snapshot, and hints seams

* refactor(query): deepen validation, fallback policy, capability, and result builder seams

* refactor(query): deepen resolution strategy, output classifier, observability, and policy-capability seams

* refactor(query): finalize deep strategy/classifier/observability/capability seams

* test/query: address coderabbit inline and out-of-diff dispatch nits

* fix(query): address remaining coderabbit input-validation and bridge stderr threads

* fix(query): address remaining coderabbit dispatch and strategy/output nits
This commit is contained in:
Tom Boucher
2026-05-03 15:29:34 -04:00
committed by GitHub
parent c3f896f311
commit b6c401dc90
49 changed files with 1159 additions and 484 deletions

View File

@@ -1,65 +1,11 @@
import {
STATE_SUBCOMMANDS,
VERIFY_SUBCOMMANDS,
INIT_SUBCOMMANDS,
PHASE_SUBCOMMANDS,
PHASES_SUBCOMMANDS,
VALIDATE_SUBCOMMANDS,
ROADMAP_SUBCOMMANDS,
STATE_MUTATION_COMMANDS,
PHASE_MUTATION_COMMANDS,
PHASES_MUTATION_COMMANDS,
ROADMAP_MUTATION_COMMANDS,
} from './command-aliases.generated.js';
export interface QueryCommandRegistryLike {
has(command: string): boolean;
}
export type QueryMatchMode = 'dotted' | 'spaced';
export type QueryResolutionSource = 'normalized' | 'expanded';
export interface QueryCommandResolution {
cmd: string;
args: string[];
matchedBy: QueryMatchMode;
expanded: boolean;
source: QueryResolutionSource;
}
export interface QueryCommandNoMatch {
normalized: { command: string; args: string[]; tokens: string[] };
attempted: { dotted: string[]; spaced: string[]; expandedTokens: string[] | null };
}
const MERGE_FIRST_WITH_SUBCOMMAND = new Set<string>([
'state',
'template',
'frontmatter',
'verify',
'phase',
'requirements',
'init',
'workstream',
'intel',
'learnings',
'uat',
'todo',
'milestone',
'check',
'detect',
'route',
]);
import { FAMILY_MUTATION_COMMANDS, FAMILY_RAW_OUTPUT_COMMANDS } from './command-definition.js';
export const QUERY_MUTATION_COMMAND_LIST: readonly string[] = [
...STATE_MUTATION_COMMANDS,
...FAMILY_MUTATION_COMMANDS,
'frontmatter.set', 'frontmatter.merge', 'frontmatter.validate', 'frontmatter validate',
'config-set', 'config-set-model-profile', 'config-new-project', 'config-ensure-section',
'commit', 'check-commit', 'commit-to-subrepo',
'template.fill', 'template.select', 'template select',
...PHASE_MUTATION_COMMANDS,
...PHASES_MUTATION_COMMANDS,
...ROADMAP_MUTATION_COMMANDS,
'requirements.mark-complete', 'requirements mark-complete',
'todo.complete', 'todo complete',
'milestone.complete', 'milestone complete',
@@ -73,7 +19,7 @@ export const QUERY_MUTATION_COMMAND_LIST: readonly string[] = [
'write-profile', 'generate-claude-profile', 'generate-dev-preferences', 'generate-claude-md',
] as const;
export const TRANSPORT_RAW_COMMANDS: readonly string[] = [
const NON_FAMILY_RAW_OUTPUT_COMMANDS = [
'commit',
'config-set',
'verify-summary',
@@ -81,134 +27,25 @@ export const TRANSPORT_RAW_COMMANDS: readonly string[] = [
'verify summary',
] as const;
export const TRANSPORT_RAW_COMMANDS: readonly string[] = [
...FAMILY_RAW_OUTPUT_COMMANDS,
...NON_FAMILY_RAW_OUTPUT_COMMANDS,
] as const;
const QUERY_MUTATION_COMMAND_SET = new Set(QUERY_MUTATION_COMMAND_LIST);
export function isQueryMutationCommand(command: string): boolean {
return QUERY_MUTATION_COMMAND_SET.has(command);
}
export function normalizeQueryCommand(command: string, args: string[]): [string, string[]] {
if (command === 'scaffold') return ['phase.scaffold', args];
if (command === 'state' && args.length === 0) return ['state.load', []];
if (command === 'state' && args.length > 0) {
const sub = args[0];
if (STATE_SUBCOMMANDS.has(sub)) return [`state.${sub}`, args.slice(1)];
return [command, args];
}
if (command === 'verify' && args.length > 0) {
const sub = args[0];
if (VERIFY_SUBCOMMANDS.has(sub)) return [`verify.${sub}`, args.slice(1)];
return [command, args];
}
if (command === 'init' && args.length > 0) {
const sub = args[0];
if (INIT_SUBCOMMANDS.has(sub)) return [`init.${sub}`, args.slice(1)];
return [command, args];
}
if (command === 'phase' && args.length > 0) {
const sub = args[0];
if (PHASE_SUBCOMMANDS.has(sub)) return [`phase.${sub}`, args.slice(1)];
return [command, args];
}
if (command === 'phases' && args.length > 0) {
const sub = args[0];
if (PHASES_SUBCOMMANDS.has(sub)) return [`phases.${sub}`, args.slice(1)];
return [command, args];
}
if (command === 'validate' && args.length > 0) {
const sub = args[0];
if (VALIDATE_SUBCOMMANDS.has(sub)) return [`validate.${sub}`, args.slice(1)];
return [command, args];
}
if (command === 'roadmap' && args.length > 0) {
const sub = args[0];
if (ROADMAP_SUBCOMMANDS.has(sub)) return [`roadmap.${sub}`, args.slice(1)];
return [command, args];
}
if (MERGE_FIRST_WITH_SUBCOMMAND.has(command) && args.length > 0) {
return [`${command}.${args[0]}`, args.slice(1)];
}
if ((command === 'progress' || command === 'stats') && args.length > 0 && !args[0].startsWith('-')) {
return [`${command}.${args[0]}`, args.slice(1)];
}
return [command, args];
}
function expandFirstDottedToken(tokens: string[]): string[] {
if (tokens.length === 0) return tokens;
const first = tokens[0];
if (first.startsWith('--') || !first.includes('.')) return tokens;
return [...first.split('.'), ...tokens.slice(1)];
}
function matchRegisteredPrefix(
tokens: string[],
registry: QueryCommandRegistryLike,
track?: { dotted: string[]; spaced: string[] },
): { cmd: string; args: string[]; matchedBy: QueryMatchMode } | null {
for (let i = tokens.length; i >= 1; i--) {
const head = tokens.slice(0, i);
const dotted = head.join('.');
const spaced = head.join(' ');
track?.dotted.push(dotted);
track?.spaced.push(spaced);
if (registry.has(dotted)) return { cmd: dotted, args: tokens.slice(i), matchedBy: 'dotted' };
if (registry.has(spaced)) return { cmd: spaced, args: tokens.slice(i), matchedBy: 'spaced' };
}
return null;
}
export function resolveQueryTokens(
tokens: string[],
registry: QueryCommandRegistryLike,
): QueryCommandResolution | null {
const direct = matchRegisteredPrefix(tokens, registry);
if (direct) return { ...direct, expanded: false, source: 'normalized' };
const expanded = expandFirstDottedToken(tokens);
if (expanded !== tokens) {
const afterExpand = matchRegisteredPrefix(expanded, registry);
if (afterExpand) return { ...afterExpand, expanded: true, source: 'expanded' };
}
return null;
}
export function resolveQueryCommand(
command: string,
args: string[],
registry: QueryCommandRegistryLike,
): QueryCommandResolution | null {
const [normCmd, normArgs] = normalizeQueryCommand(command, args);
return resolveQueryTokens([normCmd, ...normArgs], registry);
}
export function explainQueryCommandNoMatch(
command: string,
args: string[],
registry: QueryCommandRegistryLike,
): QueryCommandNoMatch {
const [normalizedCommand, normalizedArgs] = normalizeQueryCommand(command, args);
const normalizedTokens = [normalizedCommand, ...normalizedArgs];
const attempted = { dotted: [] as string[], spaced: [] as string[] };
matchRegisteredPrefix(normalizedTokens, registry, attempted);
const expandedTokens = expandFirstDottedToken(normalizedTokens);
if (expandedTokens !== normalizedTokens) {
matchRegisteredPrefix(expandedTokens, registry, attempted);
}
return {
normalized: {
command: normalizedCommand,
args: normalizedArgs,
tokens: normalizedTokens,
},
attempted: {
dotted: attempted.dotted,
spaced: attempted.spaced,
expandedTokens: expandedTokens !== normalizedTokens ? expandedTokens : null,
},
};
}
export {
normalizeQueryCommand,
resolveQueryTokens,
resolveQueryCommand,
explainQueryCommandNoMatch,
type QueryCommandRegistryLike,
type QueryCommandResolution,
type QueryMatchMode,
type QueryResolutionSource,
type QueryCommandNoMatch,
} from './query-command-resolution-strategy.js';