appydave-tools 0.70.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/brainstorming-agent.md +227 -0
  3. data/.claude/commands/cli-test.md +251 -0
  4. data/.claude/commands/dev.md +234 -0
  5. data/.claude/commands/po.md +227 -0
  6. data/.claude/commands/progress.md +51 -0
  7. data/.claude/commands/uat.md +321 -0
  8. data/.rubocop.yml +9 -0
  9. data/AGENTS.md +43 -0
  10. data/CHANGELOG.md +12 -0
  11. data/CLAUDE.md +26 -3
  12. data/README.md +15 -0
  13. data/bin/dam +21 -1
  14. data/bin/jump.rb +29 -0
  15. data/bin/subtitle_processor.rb +54 -1
  16. data/bin/zsh_history.rb +846 -0
  17. data/docs/README.md +162 -69
  18. data/docs/architecture/cli/exe-bin-convention.md +434 -0
  19. data/docs/architecture/cli-patterns.md +631 -0
  20. data/docs/architecture/gpt-context/gpt-context-architecture.md +325 -0
  21. data/docs/architecture/gpt-context/gpt-context-implementation-guide.md +419 -0
  22. data/docs/architecture/gpt-context/gpt-context-vision.md +179 -0
  23. data/docs/architecture/testing/testing-patterns.md +762 -0
  24. data/docs/backlog.md +120 -0
  25. data/docs/cli-tests/FR-3-jump-location-tool.md +515 -0
  26. data/docs/specs/fr-002-gpt-context-help-system.md +265 -0
  27. data/docs/specs/fr-003-jump-location-tool.md +779 -0
  28. data/docs/specs/zsh-history-tool.md +820 -0
  29. data/docs/uat/FR-3-jump-location-tool.md +741 -0
  30. data/exe/jump +11 -0
  31. data/exe/{subtitle_manager → subtitle_processor} +1 -1
  32. data/exe/zsh_history +11 -0
  33. data/lib/appydave/tools/configuration/openai.rb +1 -1
  34. data/lib/appydave/tools/dam/file_helper.rb +28 -0
  35. data/lib/appydave/tools/dam/project_listing.rb +4 -30
  36. data/lib/appydave/tools/dam/s3_operations.rb +2 -1
  37. data/lib/appydave/tools/dam/ssd_status.rb +226 -0
  38. data/lib/appydave/tools/dam/status.rb +3 -51
  39. data/lib/appydave/tools/jump/cli.rb +561 -0
  40. data/lib/appydave/tools/jump/commands/add.rb +52 -0
  41. data/lib/appydave/tools/jump/commands/base.rb +43 -0
  42. data/lib/appydave/tools/jump/commands/generate.rb +153 -0
  43. data/lib/appydave/tools/jump/commands/remove.rb +58 -0
  44. data/lib/appydave/tools/jump/commands/report.rb +214 -0
  45. data/lib/appydave/tools/jump/commands/update.rb +42 -0
  46. data/lib/appydave/tools/jump/commands/validate.rb +54 -0
  47. data/lib/appydave/tools/jump/config.rb +233 -0
  48. data/lib/appydave/tools/jump/formatters/base.rb +48 -0
  49. data/lib/appydave/tools/jump/formatters/json_formatter.rb +19 -0
  50. data/lib/appydave/tools/jump/formatters/paths_formatter.rb +21 -0
  51. data/lib/appydave/tools/jump/formatters/table_formatter.rb +183 -0
  52. data/lib/appydave/tools/jump/location.rb +134 -0
  53. data/lib/appydave/tools/jump/path_validator.rb +47 -0
  54. data/lib/appydave/tools/jump/search.rb +230 -0
  55. data/lib/appydave/tools/subtitle_processor/transcript.rb +51 -0
  56. data/lib/appydave/tools/version.rb +1 -1
  57. data/lib/appydave/tools/zsh_history/command.rb +37 -0
  58. data/lib/appydave/tools/zsh_history/config.rb +235 -0
  59. data/lib/appydave/tools/zsh_history/filter.rb +184 -0
  60. data/lib/appydave/tools/zsh_history/formatter.rb +75 -0
  61. data/lib/appydave/tools/zsh_history/parser.rb +101 -0
  62. data/lib/appydave/tools.rb +25 -0
  63. data/package.json +1 -1
  64. metadata +51 -4
@@ -0,0 +1,631 @@
1
+ # CLI Patterns Guide
2
+
3
+ This document analyzes the CLI patterns used across appydave-tools and provides guidance on which pattern to use for different scenarios.
4
+
5
+ ## Overview
6
+
7
+ The codebase uses **4 distinct CLI patterns**, each suited to different complexity levels:
8
+
9
+ | Pattern | Example Tools | Best For |
10
+ |---------|---------------|----------|
11
+ | [Simple Procedural](#pattern-1-simple-procedural) | GPT Context, Configuration | Single-purpose tools, < 5 options |
12
+ | [Action Class Dispatch](#pattern-2-action-class-dispatch) | YouTube Manager | Medium complexity, reusable actions |
13
+ | [Method Dispatch (Light)](#pattern-3-method-dispatch-light) | Subtitle Processor | Multi-command with per-command options |
14
+ | [Method Dispatch (Full)](#pattern-4-method-dispatch-full) | DAM | Complex tools, hierarchical help, 10+ commands |
15
+
16
+ ## Pattern Decision Flowchart
17
+
18
+ ```
19
+ How many commands/subcommands?
20
+
21
+ ├─ 1 (single purpose) ──────────────────────► Pattern 1: Simple Procedural
22
+
23
+ ├─ 2-5 commands
24
+ │ │
25
+ │ ├─ Actions reusable elsewhere? ─── Yes ─► Pattern 2: Action Class Dispatch
26
+ │ │
27
+ │ └─ Self-contained tool? ─────────── Yes ─► Pattern 3: Method Dispatch (Light)
28
+
29
+ └─ 6+ commands
30
+
31
+ ├─ Need hierarchical help? ──────── Yes ─► Pattern 4: Method Dispatch (Full)
32
+
33
+ └─ Simple help is fine? ─────────── Yes ─► Pattern 3: Method Dispatch (Light)
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Pattern 1: Simple Procedural
39
+
40
+ **Used by:** `gpt_context.rb`, `configuration.rb`
41
+
42
+ **Characteristics:**
43
+ - Direct OptionParser usage at script level
44
+ - No wrapper class (or minimal)
45
+ - Options collected into hash or struct
46
+ - `case/when` for command dispatch (if needed)
47
+ - Delegates to library classes for business logic
48
+
49
+ **When to use:**
50
+ - Single-purpose tools
51
+ - Fewer than 5 options/flags
52
+ - No subcommands, or very few (< 3)
53
+ - Quick scripts that don't need extensibility
54
+
55
+ ### Example: GPT Context (Single Purpose)
56
+
57
+ ```ruby
58
+ #!/usr/bin/env ruby
59
+ # frozen_string_literal: true
60
+
61
+ require 'appydave/tools'
62
+
63
+ options = Appydave::Tools::GptContext::Options.new(
64
+ working_directory: nil
65
+ )
66
+
67
+ OptionParser.new do |opts|
68
+ opts.banner = 'Usage: gpt_context [options]'
69
+
70
+ opts.on('-i', '--include PATTERN', 'Pattern to include') do |pattern|
71
+ options.include_patterns << pattern
72
+ end
73
+
74
+ opts.on('-e', '--exclude PATTERN', 'Pattern to exclude') do |pattern|
75
+ options.exclude_patterns << pattern
76
+ end
77
+
78
+ opts.on('-f', '--format FORMAT', 'Output format') do |format|
79
+ options.format = format
80
+ end
81
+
82
+ opts.on('-d', '--debug', 'Enable debug mode') do
83
+ options.debug = true
84
+ end
85
+
86
+ opts.on_tail('-h', '--help', 'Show this message') do
87
+ puts opts
88
+ exit
89
+ end
90
+ end.parse!
91
+
92
+ # Delegate to library classes
93
+ gatherer = Appydave::Tools::GptContext::FileCollector.new(options)
94
+ content = gatherer.build
95
+
96
+ output_handler = Appydave::Tools::GptContext::OutputHandler.new(content, options)
97
+ output_handler.execute
98
+ ```
99
+
100
+ ### Example: Configuration (Flag-based Commands)
101
+
102
+ ```ruby
103
+ #!/usr/bin/env ruby
104
+ # frozen_string_literal: true
105
+
106
+ require 'appydave/tools'
107
+
108
+ options = { keys: [] }
109
+
110
+ OptionParser.new do |opts|
111
+ opts.banner = 'Usage: ad_config [options]'
112
+
113
+ opts.on('-e', '--edit', 'Edit configuration') do
114
+ options[:command] = :edit
115
+ end
116
+
117
+ opts.on('-l', '--list', 'List configurations') do
118
+ options[:command] = :list
119
+ end
120
+
121
+ opts.on('-c', '--create', 'Create missing configs') do
122
+ options[:command] = :create
123
+ end
124
+
125
+ opts.on('-p', '--print [KEYS]', Array, 'Print config values') do |keys|
126
+ options[:command] = :print
127
+ options[:keys] = keys
128
+ end
129
+ end.parse!
130
+
131
+ # Simple case/when dispatch
132
+ case options[:command]
133
+ when :edit
134
+ Appydave::Tools::Configuration::Config.edit
135
+ when :list
136
+ # ... list logic
137
+ when :create
138
+ # ... create logic
139
+ when :print
140
+ Appydave::Tools::Configuration::Config.print(*options[:keys])
141
+ else
142
+ puts 'No command provided. Use --help for usage.'
143
+ end
144
+ ```
145
+
146
+ **Pros:**
147
+ - Minimal boilerplate
148
+ - Easy to understand
149
+ - Fast to implement
150
+ - Standard Ruby pattern
151
+
152
+ **Cons:**
153
+ - Doesn't scale well beyond 5 options
154
+ - No help hierarchy
155
+ - Hard to test CLI parsing separately
156
+
157
+ ---
158
+
159
+ ## Pattern 2: Action Class Dispatch
160
+
161
+ **Used by:** `youtube_manager.rb`
162
+
163
+ **Characteristics:**
164
+ - CLI class routes commands to Action objects
165
+ - Each Action is a separate class with `.action(args)` method
166
+ - Actions handle their own OptionParser
167
+ - Actions can be reused by other code (not just CLI)
168
+
169
+ **When to use:**
170
+ - Actions need to be callable from multiple places (CLI, tests, other code)
171
+ - Medium complexity (2-5 commands)
172
+ - Each command has distinct option sets
173
+ - Want clean separation between routing and execution
174
+
175
+ ### Example: YouTube Manager
176
+
177
+ ```ruby
178
+ #!/usr/bin/env ruby
179
+ # frozen_string_literal: true
180
+
181
+ require 'appydave/tools'
182
+
183
+ class YouTubeVideoManagerCLI
184
+ def initialize
185
+ @commands = {
186
+ 'get' => Appydave::Tools::CliActions::GetVideoAction.new,
187
+ 'update' => Appydave::Tools::CliActions::UpdateVideoAction.new
188
+ }
189
+ end
190
+
191
+ def run
192
+ command, *args = ARGV
193
+ if @commands.key?(command)
194
+ @commands[command].action(args)
195
+ else
196
+ puts "Unknown command: #{command}"
197
+ print_help
198
+ end
199
+ end
200
+
201
+ private
202
+
203
+ def print_help
204
+ puts 'Usage: youtube_manager [command] [options]'
205
+ puts 'Commands:'
206
+ puts ' get Get video details'
207
+ puts ' update Update video metadata'
208
+ end
209
+ end
210
+
211
+ YouTubeVideoManagerCLI.new.run
212
+ ```
213
+
214
+ **Action Class Structure:**
215
+
216
+ ```ruby
217
+ # lib/appydave/tools/cli_actions/get_video_action.rb
218
+ module Appydave::Tools::CliActions
219
+ class GetVideoAction
220
+ def action(args)
221
+ options = parse_options(args)
222
+
223
+ video = YouTubeManager::GetVideo.new
224
+ video.get(options[:video_id])
225
+ # ... display results
226
+ end
227
+
228
+ private
229
+
230
+ def parse_options(args)
231
+ options = {}
232
+ OptionParser.new do |opts|
233
+ opts.on('-v', '--video-id ID', 'Video ID') { |v| options[:video_id] = v }
234
+ opts.on('-h', '--help', 'Show help') { puts opts; exit }
235
+ end.parse!(args)
236
+ options
237
+ end
238
+ end
239
+ end
240
+ ```
241
+
242
+ **Pros:**
243
+ - Actions are testable independently
244
+ - Clean separation of concerns
245
+ - Actions reusable outside CLI context
246
+ - Easy to add new commands
247
+
248
+ **Cons:**
249
+ - More files to manage
250
+ - Slight overhead for simple cases
251
+ - Requires Action class convention
252
+
253
+ ---
254
+
255
+ ## Pattern 3: Method Dispatch (Light)
256
+
257
+ **Used by:** `subtitle_processor.rb`
258
+
259
+ **Characteristics:**
260
+ - CLI class with command map to `method(:name)`
261
+ - Each command method handles its own OptionParser
262
+ - Per-command help via `-h` flag
263
+ - Self-contained in single file
264
+
265
+ **When to use:**
266
+ - 3-8 commands with distinct option sets
267
+ - Commands are tool-specific (not reusable elsewhere)
268
+ - Want subcommand-style interface: `tool command --options`
269
+ - Moderate complexity, single-file preferred
270
+
271
+ ### Example: Subtitle Processor
272
+
273
+ ```ruby
274
+ #!/usr/bin/env ruby
275
+ # frozen_string_literal: true
276
+
277
+ require 'appydave/tools'
278
+
279
+ class SubtitleProcessorCLI
280
+ def initialize
281
+ @commands = {
282
+ 'clean' => method(:clean_subtitles),
283
+ 'join' => method(:join_subtitles),
284
+ 'transcript' => method(:transcript_subtitles)
285
+ }
286
+ end
287
+
288
+ def run
289
+ command, *args = ARGV
290
+ if command.nil?
291
+ print_help
292
+ exit
293
+ end
294
+
295
+ if @commands.key?(command)
296
+ @commands[command].call(args)
297
+ else
298
+ puts "Unknown command: #{command}"
299
+ print_help
300
+ end
301
+ end
302
+
303
+ private
304
+
305
+ def clean_subtitles(args)
306
+ options = { file: nil, output: nil }
307
+
308
+ parser = OptionParser.new do |opts|
309
+ opts.banner = 'Usage: subtitle_processor clean [options]'
310
+ opts.on('-f', '--file FILE', 'Input SRT file') { |v| options[:file] = v }
311
+ opts.on('-o', '--output FILE', 'Output file') { |v| options[:output] = v }
312
+ opts.on('-h', '--help', 'Show help') { puts opts; exit }
313
+ end
314
+ parser.parse!(args)
315
+
316
+ validate_required!(options, [:file, :output], parser)
317
+
318
+ cleaner = Appydave::Tools::SubtitleProcessor::Clean.new(file_path: options[:file])
319
+ cleaner.clean
320
+ cleaner.write(options[:output])
321
+ end
322
+
323
+ def join_subtitles(args)
324
+ options = { folder: './', files: '*.srt', output: 'merged.srt' }
325
+
326
+ parser = OptionParser.new do |opts|
327
+ opts.banner = 'Usage: subtitle_processor join [options]'
328
+ opts.on('-d', '--directory DIR', 'Directory') { |v| options[:folder] = v }
329
+ opts.on('-f', '--files PATTERN', 'File pattern') { |v| options[:files] = v }
330
+ opts.on('-o', '--output FILE', 'Output file') { |v| options[:output] = v }
331
+ opts.on('-h', '--help', 'Show help') { puts opts; exit }
332
+ end
333
+ parser.parse!(args)
334
+
335
+ joiner = Appydave::Tools::SubtitleProcessor::Join.new(**options)
336
+ joiner.join
337
+ end
338
+
339
+ def print_help
340
+ puts 'Usage: subtitle_processor [command] [options]'
341
+ puts 'Commands:'
342
+ puts ' clean Clean SRT files'
343
+ puts ' join Join multiple SRT files'
344
+ puts ' transcript Convert to plain text'
345
+ end
346
+
347
+ def validate_required!(options, required, parser)
348
+ missing = required.select { |k| options[k].nil? }
349
+ return if missing.empty?
350
+
351
+ puts "Error: Missing required options: #{missing.join(', ')}"
352
+ puts parser
353
+ exit 1
354
+ end
355
+ end
356
+
357
+ SubtitleProcessorCLI.new.run
358
+ ```
359
+
360
+ **Pros:**
361
+ - Clean subcommand interface
362
+ - Per-command help works naturally
363
+ - Single file, easy to navigate
364
+ - Scales to ~10 commands comfortably
365
+
366
+ **Cons:**
367
+ - File gets long with many commands
368
+ - No hierarchical help topics
369
+ - Command methods not reusable outside CLI
370
+
371
+ ---
372
+
373
+ ## Pattern 4: Method Dispatch (Full)
374
+
375
+ **Used by:** `dam` (bin/dam)
376
+
377
+ **Characteristics:**
378
+ - Large CLI class (1000+ lines)
379
+ - Command map to `method(:name)`
380
+ - **Hierarchical help system**: `tool help <topic>`
381
+ - Custom argument parsing per command (not just OptionParser)
382
+ - Deprecated command aliases
383
+ - Auto-detection features (e.g., detect brand from PWD)
384
+
385
+ **When to use:**
386
+ - 10+ commands
387
+ - Complex help needs (topics, workflows, configuration)
388
+ - Advanced features (auto-detection, patterns, shortcuts)
389
+ - Tool is central to workflow, worth the investment
390
+
391
+ ### Example: DAM Structure
392
+
393
+ ```ruby
394
+ #!/usr/bin/env ruby
395
+ # frozen_string_literal: true
396
+
397
+ require 'appydave/tools'
398
+
399
+ class VatCLI
400
+ def initialize
401
+ @commands = {
402
+ 'help' => method(:help_command),
403
+ 'list' => method(:list_command),
404
+ 's3-up' => method(:s3_up_command),
405
+ 's3-down' => method(:s3_down_command),
406
+ 's3-status' => method(:s3_status_command),
407
+ # ... 15+ more commands
408
+
409
+ # Deprecated aliases (backward compatibility)
410
+ 's3-cleanup' => method(:s3_cleanup_remote_command)
411
+ }
412
+ end
413
+
414
+ def run
415
+ command, *args = ARGV
416
+
417
+ # Version flag handling
418
+ if ['--version', '-v'].include?(command)
419
+ puts "DAM v#{Appydave::Tools::VERSION}"
420
+ exit
421
+ end
422
+
423
+ # No command = show quick usage
424
+ if command.nil?
425
+ puts 'DAM - Video Asset Tools'
426
+ puts 'Usage: dam [command] [options]'
427
+ puts "Run 'dam help' for more information."
428
+ exit
429
+ end
430
+
431
+ # Dispatch or error with helpful message
432
+ if @commands.key?(command)
433
+ @commands[command].call(args)
434
+ else
435
+ puts "Unknown command: #{command}"
436
+ puts 'Available: list, s3-up, s3-down, s3-status, ...'
437
+ exit 1
438
+ end
439
+ end
440
+
441
+ private
442
+
443
+ # ─────────────────────────────────────────────────────────────────
444
+ # HIERARCHICAL HELP SYSTEM
445
+ # ─────────────────────────────────────────────────────────────────
446
+
447
+ def help_command(args)
448
+ topic = args[0]
449
+
450
+ case topic
451
+ when 'brands'
452
+ show_brands_help
453
+ when 'workflows'
454
+ show_workflows_help
455
+ when 'config'
456
+ show_config_help
457
+ when 's3-up', 's3-down', 's3-status'
458
+ show_s3_help(topic)
459
+ when 's3-cleanup' # Deprecated
460
+ puts "⚠️ 's3-cleanup' is deprecated. Use 's3-cleanup-remote'."
461
+ show_s3_help('s3-cleanup-remote')
462
+ when nil
463
+ show_main_help
464
+ else
465
+ puts "Unknown help topic: #{topic}"
466
+ show_help_topics
467
+ end
468
+ end
469
+
470
+ def show_main_help
471
+ puts <<~HELP
472
+ DAM - Digital Asset Management for Video Projects
473
+
474
+ Usage: dam [command] [options]
475
+
476
+ Commands:
477
+ list List brands and projects
478
+ s3-up Upload project to S3
479
+ s3-down Download project from S3
480
+ ...
481
+
482
+ Help Topics:
483
+ dam help brands Brand shortcuts and configuration
484
+ dam help workflows FliVideo and Storyline workflows
485
+ dam help config Configuration setup
486
+ HELP
487
+ end
488
+
489
+ def show_brands_help
490
+ puts <<~HELP
491
+ Available Brands
492
+
493
+ DAM supports multi-tenant video project management:
494
+
495
+ appydave → v-appydave (AppyDave brand)
496
+ voz → v-voz (VOZ client)
497
+ aitldr → v-aitldr (AITLDR brand)
498
+ ...
499
+
500
+ Examples:
501
+ dam list appydave
502
+ dam s3-up voz boy-baker
503
+ HELP
504
+ end
505
+
506
+ # ─────────────────────────────────────────────────────────────────
507
+ # COMMAND IMPLEMENTATIONS
508
+ # ─────────────────────────────────────────────────────────────────
509
+
510
+ def s3_up_command(args)
511
+ options = parse_s3_args(args, 's3-up')
512
+ s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
513
+ s3_ops.upload(dry_run: options[:dry_run])
514
+ rescue StandardError => e
515
+ puts "Error: #{e.message}"
516
+ exit 1
517
+ end
518
+
519
+ # ─────────────────────────────────────────────────────────────────
520
+ # CUSTOM ARGUMENT PARSING
521
+ # ─────────────────────────────────────────────────────────────────
522
+
523
+ def parse_s3_args(args, command)
524
+ dry_run = args.include?('--dry-run')
525
+ force = args.include?('--force')
526
+ args = args.reject { |arg| arg.start_with?('--') }
527
+
528
+ brand_arg = args[0]
529
+ project_arg = args[1]
530
+
531
+ # Auto-detect from current directory if no args
532
+ if brand_arg.nil?
533
+ brand, project_id = Appydave::Tools::Dam::ProjectResolver.detect_from_pwd
534
+ if brand.nil?
535
+ puts "Could not auto-detect. Usage: dam #{command} <brand> <project>"
536
+ exit 1
537
+ end
538
+ else
539
+ # Validate and resolve
540
+ validate_brand!(brand_arg)
541
+ project_id = Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg)
542
+ end
543
+
544
+ { brand: brand_arg, project: project_id, dry_run: dry_run, force: force }
545
+ end
546
+ end
547
+
548
+ VatCLI.new.run
549
+ ```
550
+
551
+ **Pros:**
552
+ - Scales to 20+ commands
553
+ - Rich, hierarchical help system
554
+ - Supports deprecated aliases gracefully
555
+ - Advanced features (auto-detection, patterns)
556
+ - Professional CLI experience
557
+
558
+ **Cons:**
559
+ - Large file (1000+ lines)
560
+ - Complex to maintain
561
+ - Significant upfront investment
562
+ - Custom parsing logic to test
563
+
564
+ ---
565
+
566
+ ## Pattern Comparison Summary
567
+
568
+ | Aspect | Pattern 1 | Pattern 2 | Pattern 3 | Pattern 4 |
569
+ |--------|-----------|-----------|-----------|-----------|
570
+ | **Commands** | 1 | 2-5 | 3-10 | 10+ |
571
+ | **File Count** | 1 | Multiple | 1 | 1 (large) |
572
+ | **Help System** | `-h` flag | Per-command `-h` | Per-command `-h` | Hierarchical topics |
573
+ | **Reusability** | Low | High (Actions) | Medium | Low |
574
+ | **Testability** | Medium | High | Medium | Medium |
575
+ | **Boilerplate** | Minimal | Medium | Medium | High |
576
+ | **Learning Curve** | Low | Medium | Low | Medium |
577
+
578
+ ---
579
+
580
+ ## Recommendations by Scenario
581
+
582
+ ### New single-purpose tool
583
+ → **Pattern 1: Simple Procedural**
584
+
585
+ Example: A tool to format JSON files
586
+ ```bash
587
+ json_formatter -i input.json -o output.json --pretty
588
+ ```
589
+
590
+ ### Tool with actions used elsewhere
591
+ → **Pattern 2: Action Class Dispatch**
592
+
593
+ Example: A build tool where actions are also called by CI scripts
594
+ ```bash
595
+ build_tool compile # Also called by BuildAction.new.action([])
596
+ build_tool test
597
+ build_tool deploy
598
+ ```
599
+
600
+ ### Medium complexity, self-contained
601
+ → **Pattern 3: Method Dispatch (Light)**
602
+
603
+ Example: A subtitle/media processing tool
604
+ ```bash
605
+ media_tool convert -i video.mp4 -o video.webm
606
+ media_tool thumbnail -i video.mp4 -t 5
607
+ media_tool info -i video.mp4
608
+ ```
609
+
610
+ ### Complex workflow tool
611
+ → **Pattern 4: Method Dispatch (Full)**
612
+
613
+ Example: A project management CLI
614
+ ```bash
615
+ project help topics
616
+ project init --template react
617
+ project deploy staging --dry-run
618
+ project status --all
619
+ ```
620
+
621
+ ---
622
+
623
+ ## Migration Path
624
+
625
+ If a tool outgrows its pattern:
626
+
627
+ 1. **Pattern 1 → Pattern 3**: Extract command logic into methods, add command dispatch
628
+ 2. **Pattern 3 → Pattern 4**: Add hierarchical help, enhance argument parsing
629
+ 3. **Pattern 3 → Pattern 2**: Extract methods into Action classes if reuse needed
630
+
631
+ The patterns are designed to be progressive - start simple, add complexity only when needed.