@morodomi/ait3
Version:
AIT³ Development Platform - AI + Ticket + Test + Tool driven development methodology
187 lines (185 loc) • 7.26 kB
JavaScript
import { Command, Option } from 'commander';
import { planPhase } from './plan.js';
import { redPhase } from './red.js';
import { greenPhase } from './green.js';
import { refactorPhase } from './refactor.js';
import { squashPhase } from './squash.js';
import { ServiceFactory } from '../../services/ServiceFactory.js';
import { createMissingFlowIdErrorHandler } from '../ticket/error-handler.js';
import { handleCommandError, COMMON_TIPS } from '../../common/command-error-handler.js';
// Service container - centralized dependency injection
// Use ServiceFactory to ensure proper dependency inversion
// Services are created async per command to respect config changes
export const flowCommand = new Command('flow')
.description('AIT³ workflow commands - AI + Ticket + Test + Tool driven development')
.addHelpText('after', `
Examples:
$ ait3 flow plan 0001
$ ait3 flow plan 0001 --requirements "security,oauth"
$ ait3 flow plan 0001 --mode express
$ ait3 flow plan 72 --mode manual
$ ait3 flow red 0001
$ ait3 flow red 0001 --type both --interactive
$ ait3 flow green 0001
$ ait3 flow green 0001 --verbose --strict
$ ait3 flow refactor 0001
$ ait3 flow refactor 0001 --verbose --focus mocks,duplication
$ ait3 flow squash 0001
$ ait3 flow squash 0001 --pr --no-squash
Philosophy:
Claude proposes → Gemini refutes → Human decides
`);
// flow plan subcommand
flowCommand
.command('plan <ticketId>')
.description('PLANNING Phase - Socratic dialogue for approach validation')
.addOption(new Option('-m, --mode <mode>', 'Planning mode').choices(['guided', 'express', 'manual']).default('guided'))
.option('-r, --requirements <requirements>', 'Comma-separated requirements (e.g., "security,oauth,jwt")')
.action(async (ticketId, options) => {
try {
// Create services for this command execution
const services = await ServiceFactory.createServices();
// Parse requirements
const requirements = options.requirements?.split(',').map((r) => r.trim()).filter(Boolean) || [];
const result = await planPhase({
ticketId,
mode: options.mode,
requirements: requirements.length > 0 ? requirements : undefined
}, services);
console.log(result.message);
process.exit(result.success ? 0 : 1);
}
catch (error) {
handleCommandError(error, {
action: 'execute planning phase',
tips: {
ticketId: COMMON_TIPS.ticketId
}
});
}
});
// flow red subcommand
flowCommand
.command('red <ticketId>')
.description('RED Phase - Generate failing tests from ticket requirements')
.addOption(new Option('-t, --type <type>', 'Test type to generate').choices(['unit', 'integration', 'both']).default('unit'))
.option('-i, --interactive', 'Interactive mode for test customization')
.option('-d, --dry-run', 'Preview what would be generated without creating files')
.showHelpAfterError()
.exitOverride(createMissingFlowIdErrorHandler('red'))
.action(async (ticketId, options) => {
try {
// Create services for this command execution
const services = await ServiceFactory.createServices();
const result = await redPhase({
ticketId,
type: options.type,
interactive: options.interactive || false,
dryRun: options.dryRun || false
}, services);
console.log(result.message);
process.exit(result.success ? 0 : 1);
}
catch (error) {
handleCommandError(error, {
action: 'execute RED phase',
tips: {
ticketId: COMMON_TIPS.ticketId
}
});
}
});
// flow green subcommand
flowCommand
.command('green <ticketId>')
.description('GREEN Phase - Make tests pass with minimal implementation')
.option('-s, --strict', 'Enable strict mode for test immutability (default: true)', true)
.showHelpAfterError()
.exitOverride(createMissingFlowIdErrorHandler('green'))
.option('--no-strict', 'Disable strict mode (not recommended)')
.option('-v, --verbose', 'Show detailed progress and analysis')
.option('-t, --target <testFile>', 'Focus on specific test file')
.action(async (ticketId, options) => {
try {
// Create services for this command execution
const services = await ServiceFactory.createServices();
const result = await greenPhase({
ticketId,
strict: options.strict,
verbose: options.verbose || false,
target: options.target
}, services);
console.log(result.message);
process.exit(result.success ? 0 : 1);
}
catch (error) {
handleCommandError(error, {
action: 'execute GREEN phase',
tips: {
ticketId: COMMON_TIPS.ticketId
}
});
}
});
// flow refactor subcommand
flowCommand
.command('refactor <ticketId>')
.description('REFACTOR Phase - Analyze and suggest code optimizations')
.option('-v, --verbose', 'Show detailed analysis results')
.option('-f, --focus <areas>', 'Focus on specific areas (comma-separated: duplication,mocks,types,organization)')
.showHelpAfterError()
.exitOverride(createMissingFlowIdErrorHandler('refactor'))
.action(async (ticketId, options) => {
try {
// Create services for this command execution
const services = await ServiceFactory.createServices();
const result = await refactorPhase({
ticketId,
verbose: options.verbose || false,
focus: options.focus
}, services);
console.log(result.message);
process.exit(result.success ? 0 : 1);
}
catch (error) {
// Enhanced error handling
handleCommandError(error, {
action: 'execute REFACTOR phase',
tips: {
ticketId: COMMON_TIPS.ticketId,
focus: COMMON_TIPS.focus
}
});
}
});
// flow squash subcommand
flowCommand
.command('squash <ticketId>')
.description('SQUASH Phase - Git command suggestions for clean commit history')
.showHelpAfterError()
.exitOverride(createMissingFlowIdErrorHandler('squash'))
.option('--pr', 'Include PR creation commands')
.option('--no-squash', 'Skip squash suggestions, only show merge commands')
.option('--dry-run', 'Show what would be suggested without analysis')
.action(async (ticketId, options) => {
try {
// Create services for this command execution
const services = await ServiceFactory.createServices();
const result = await squashPhase({
ticketId,
pr: options.pr || false,
noSquash: options.squash === false, // Commander sets squash: false for --no-squash
dryRun: options.dryRun || false
}, services);
console.log(result.message);
process.exit(result.success ? 0 : 1);
}
catch (error) {
handleCommandError(error, {
action: 'execute SQUASH phase',
tips: {
ticketId: COMMON_TIPS.ticketId
}
});
}
});