Shared Command Infrastructure
Overview
Section titled “Overview”The Aichaku CLI uses a shared command infrastructure pattern to reduce code duplication and ensure consistent behavior across all configuration commands (standards, methodologies, principles).
Architecture
Section titled “Architecture”Core Components
Section titled “Core Components”1. BaseCommand (src/utils/base-command.ts)
Section titled “1. BaseCommand (src/utils/base-command.ts)”Abstract base class that implements common command operations:
--list- Display all available items--show- Show current selection or item details--add- Add items to configuration--remove- Remove items from configuration--search- Search items by keyword--categories- Display categories--current- Show current selection
2. CommandExecutor (src/utils/command-executor.ts)
Section titled “2. CommandExecutor (src/utils/command-executor.ts)”Central command router that:
- Maps command names to implementations
- Handles command execution
- Provides consistent error handling
3. Argument Parser (src/utils/argument-parser.ts)
Section titled “3. Argument Parser (src/utils/argument-parser.ts)”Handles parseArgs quirks and provides:
- Consistent argument extraction
- Special handling for
--showwith values - Type-safe parsed arguments
Interfaces
Section titled “Interfaces”// Core configuration iteminterface ConfigItem { id: string; name: string; description: string; category?: string; tags?: string[];}
// Item loader for data accessinterface ItemLoader<T extends ConfigItem> { loadAll(): Promise<T[]>; loadById(id: string): Promise<T | null>; search(query: string): Promise<T[]>; getCategories?(): Promise<string[]>;}
// Item formatter for displayinterface ItemFormatter<T extends ConfigItem> { formatList(items: T[]): string; formatDetails(item: T, verbose?: boolean): string; formatCurrent(selected: string[]): string; formatCategories?(categories: string[]): string;}Creating a New Command
Section titled “Creating a New Command”1. Define Your Item Type
Section titled “1. Define Your Item Type”interface MyItem extends ConfigItem { // Add custom fields customField: string;}2. Create a Loader
Section titled “2. Create a Loader”class MyItemLoader implements ItemLoader<MyItem> { async loadAll(): Promise<MyItem[]> { // Load items from filesystem/API }
async loadById(id: string): Promise<MyItem | null> { // Load specific item }
async search(query: string): Promise<MyItem[]> { // Search implementation }}3. Create a Formatter
Section titled “3. Create a Formatter”class MyItemFormatter implements ItemFormatter<MyItem> { formatList(items: MyItem[]): string { // Format list display }
formatDetails(item: MyItem, verbose?: boolean): string { // Format detailed view }
formatCurrent(selected: string[]): string { // Format current selection }}4. Extend BaseCommand
Section titled “4. Extend BaseCommand”class MyCommand extends BaseCommand<MyItem> { constructor() { super({ name: "mycommand", configKey: "myitems", loader: new MyItemLoader(), formatter: new MyItemFormatter(), supportedOperations: { list: true, show: true, showDetails: true, add: true, remove: true, search: true, current: true, }, helpText: { description: "Manage my items", examples: [ "aichaku mycommand --list", "aichaku mycommand --add item1,item2", ], }, }); }
// Implement required methods protected async addItemsToConfig(configManager: any, ids: string[]): Promise<void> { // Add items to configuration }
protected async removeItemsFromConfig(configManager: any, ids: string[]): Promise<void> { // Remove items from configuration }
protected getSelectedItems(configManager: any): string[] { // Get current selection }}5. Register with CommandExecutor
Section titled “5. Register with CommandExecutor”// In command-executor.tsthis.commands.set("mycommand", new MyCommand());Branding Guidelines
Section titled “Branding Guidelines”All commands must use the Brand utility for consistent output:
import { AichakuBrand as Brand } from "../utils/branded-messages.ts";
// Main operationsBrand.log("Starting operation...");
// Success messagesBrand.success("Operation completed!");
// Error messagesBrand.error("Operation failed");
// Tips and guidanceBrand.tip("Use --help for more options");
// General informationBrand.info("Processing items...");Testing
Section titled “Testing”Test Utilities (src/utils/test-helpers.ts)
Section titled “Test Utilities (src/utils/test-helpers.ts)”// Capture command outputconst output = await captureCommandOutput(async () => { await command.execute(args);});
// Assert output contains expected contentassertStringIncludes(output.stdout, "Expected text");
// Negative assertionsassertDoesNotInclude(output.stdout, "Unexpected text");Example Test
Section titled “Example Test”Deno.test("MyCommand - List operation", async () => { const command = new MyCommand(); const output = await captureCommandOutput(async () => { await command.execute({ list: true }); });
assertStringIncludes(output.stdout, "Available Items"); assertEquals(output.stderr, "");});Runtime Validation
Section titled “Runtime Validation”Commands use Zod for runtime validation of external inputs:
- Configuration Loading: Validates aichaku.json structure
- CLI Arguments: Type-safe argument parsing with validation
- YAML Content: Ensures YAML files match expected schemas
See Runtime Validation Strategy for detailed information.
Benefits
Section titled “Benefits”- Code Reuse: ~65% reduction in command implementation code
- Consistency: All commands behave the same way
- Maintainability: Bug fixes apply to all commands
- Testability: Shared test utilities and patterns
- Extensibility: Easy to add new commands
- Type Safety: Runtime validation via Zod schemas
Example Commands
Section titled “Example Commands”src/commands/standards.ts- Standards managementsrc/commands/methodologies.ts- Methodology selectionsrc/commands/principles.ts- Principle configuration
Each demonstrates the pattern with command-specific customizations.