appydave-tools 0.17.0 → 0.18.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.
@@ -1,123 +1,241 @@
1
- # CODEX Recommendations
1
+ # CODEX Recommendations - Review & Status
2
2
 
3
- > Last updated: 2025-11-09 13:36:08 UTC
4
- > Maintenance note: whenever files outside this document change, review them and reflect any new patterns/risks here so the recommendations stay authoritative.
3
+ > Last updated: 2025-11-10
4
+ > Original recommendations provided by Codex (GPT-5) on 2025-11-09
5
5
 
6
- Guide created by Codex (GPT-5) to capture near-term cleanups and deeper refactors that will make `appydave-tools` easier to extend and test.
6
+ This document captures Codex's architectural recommendations with implementation status and verdicts after engineering review.
7
7
 
8
- ## Snapshot
8
+ ## Executive Summary
9
9
 
10
- - **Primary friction**: global configuration access makes VAT objects hard to test, forcing `allow_any_instance_of` overrides in specs such as `spec/appydave/tools/vat/config_spec.rb:6`.
11
- - **Secondary friction**: command classes mix terminal IO with core logic (see `lib/appydave/tools/vat/project_resolver.rb:19-50`), which complicates automation and reuse.
12
- - **Tooling debt**: RuboCop is configured (`.rubocop.yml`) but not enforced; the current run emits 108 offenses (93 auto-correctable per Claude logs) and engineers silence cops instead of resolving root causes.
10
+ **Overall Assessment:** Mixed recommendations - some valuable, some outdated, some architecturally inappropriate.
13
11
 
14
- ## Priority Map
12
+ **Implemented:** P3 (Filesystem fixtures)
13
+ **Rejected:** ❌ P0 (Configuration DI), P1 (RuboCop - already clean), P4 (method_missing removal)
14
+ **Deferred:** ⚠️ P2 (IO separation - CLI tool doesn't need it)
15
+ **Future Work:** 🔍 VAT Manifest bugs (valid technical debt)
15
16
 
16
- | Order | Theme | Why it matters |
17
- | --- | --- | --- |
18
- | P0 | Make configuration/services injectable | Unlocks clean tests, eliminates global stubs, and keeps future CLIs composable. |
19
- | P1 | Re-enable effective lint/test automation | 93 auto-fixes are “free”; the remaining 15 surface real architecture problems that deserve explicit debt tracking. |
20
- | P2 | Separate IO from business logic in CLI helpers | Enables automation agents (Claude) and humans to reuse services without TTY prompts. |
21
- | P3 | Standardize filesystem fixtures | Reduces the bespoke `Dir.mktmpdir` + `FileUtils` boilerplate sprinkled through VAT specs; faster, safer tests. |
17
+ ---
22
18
 
23
- ## Detailed Recommendations
19
+ ## Priority Recommendations
24
20
 
25
- ### 1. Introduce configuration adapters
21
+ ### P3: Standardize Filesystem Fixtures (IMPLEMENTED)
26
22
 
27
- - **Problem**: `Appydave::Tools::Vat::Config.projects_root` reaches directly into `Configuration::Config.settings` (`lib/appydave/tools/vat/config.rb:13-21`), so specs must stub *every* `SettingsConfig` instance (`spec/appydave/tools/vat/config_spec.rb:14`).
28
- - **Action**:
29
- 1. Create a lightweight `SettingsProvider` (duck-typed or interface module) that exposes `video_projects_root`.
30
- 2. Update VAT entry points to accept `settings:` or `config:` keyword args defaulting to the singleton, e.g. `def projects_root(settings: default_settings)`.
31
- 3. Provide a `Config.with_settings(temp_settings) { ... }` helper for specs and CLI overrides.
32
- - **Impact**: RSpec can inject fakes without `allow_any_instance_of`; RuboCop warning disappears with no cop disable required.
23
+ **Recommendation:** Extract `Dir.mktmpdir + FileUtils.mkdir_p` boilerplate into shared RSpec context.
33
24
 
34
- ### 2. Capture the remaining RuboCop debt deliberately
25
+ **Status:** **Implemented** (2025-11-10)
35
26
 
36
- - **Problem**: Engineers currently choose between “ignore 108 offenses” or “disable cops,” which erodes lint value. `.rubocop.yml` already sets generous limits (200-char lines, spec exclusions), so remaining issues are meaningful.
37
- - **Action plan**:
38
- 1. Run `bundle exec rubocop --auto-correct` and commit format-only fixes (expect ~93 files touched).
39
- 2. For the 15 blocking offenses, open a short-lived spreadsheet (or GH issue tracker) that records *cop → file → fix owner*. This keeps the queue visible without blocking merges.
40
- 3. Add a CI step (GitHub Action or Circle job) that executes `bundle exec rubocop` plus `bundle exec rake spec`, failing PRs that reintroduce offenses.
41
- - **Fallback**: If any `allow_any_instance_of` remains after Step 1, convert it to the adapter pattern described above; avoid new `rubocop:disable` directives unless there’s a written justification beside them.
27
+ **What was done:**
28
+ - Created `spec/support/vat_filesystem_helpers.rb` with shared contexts
29
+ - `include_context 'vat filesystem'` - provides temp_folder, projects_root, auto-cleanup
30
+ - `include_context 'vat filesystem with brands', brands: %w[appydave voz]` - adds brand path helpers
31
+ - Refactored 3 VAT specs: config_spec, project_resolver_spec, config_loader_spec
32
+ - All tests passing (149 examples, 0 failures)
42
33
 
43
- ### 3. Decouple terminal IO from VAT services
34
+ **Benefits delivered:**
35
+ - Reduced duplication across VAT specs
36
+ - Centralized cleanup logic (safer tests)
37
+ - Easier to maintain and extend
44
38
 
45
- - **Problem**: `ProjectResolver.resolve` blends filesystem globbing with interactive prompts (`puts`/`$stdin.gets`, `lib/appydave/tools/vat/project_resolver.rb:41-48`). These prompts block automation agents, and the logic cannot be reused inside other CLIs or tests without stubbing global IO.
46
- - **Action**:
47
- 1. Extract the pure resolution logic so it always returns a result set (possibly multi-match) and never prints.
48
- 2. Create an `InteractiveResolver` (or CLI layer) that takes `io_in:`/`io_out:` and handles messaging/selection.
49
- 3. Update `bin/vat` commands to use the interactive wrapper; specs can cover both the pure resolver and the IO adapter with deterministic streams.
50
- - **Bonus**: While refactoring, move the repeated `Dir.glob` filtering into a shared helper (e.g., `Projects::Scanner`) so CLAUDE/Code agents can reuse it for listing commands.
39
+ ---
51
40
 
52
- ### 4. Standardize filesystem fixtures
41
+ ### P1: RuboCop Cleanup (ALREADY COMPLETE)
53
42
 
54
- - **Observation**: VAT specs repeatedly open temp dirs, `mkdir_p` brand/project skeletons, and remember to clean up (see blocks at `spec/appydave/tools/vat/config_spec.rb:22-178` and `spec/appydave/tools/vat/project_resolver_spec.rb:9-70`). Minor mistakes (missing cleanup, duplicated brand shortcuts) cause flaky tests locally.
55
- - **Action**:
56
- 1. Add `spec/support/filesystem_helpers.rb` with helpers like `with_projects_root(brands: %w[appydave]) { |root| ... }`.
57
- 2. Include the helper in `spec/spec_helper.rb`, then convert VAT specs to the helper to remove boilerplate and centralize cleanup.
58
- 3. Consider shipping seeded sample trees under `spec/samples/video-projects` for regression-style tests that need deeper structures.
43
+ **Recommendation:** Run `rubocop --auto-correct` to fix 93 offenses, track 15 manual fixes.
59
44
 
60
- ### 5. Harden `Configuration::Config`
45
+ **Status:** **Obsolete** - RuboCop already clean
61
46
 
62
- - **Problem**: `Configuration::Config` uses `method_missing` to expose registered configs (`lib/appydave/tools/configuration/config.rb:31-39`), which hides failures until runtime and complicates auto-complete.
63
- - **Action**:
64
- 1. Replace `method_missing` with explicit reader methods or `Forwardable`, or at minimum raise descriptive errors that list the registered keys (`configurations.keys`).
65
- 2. Expose a `fetch(:settings)` API that returns nil-friendly values for easier dependency injection.
66
- 3. Document the registration flow in `docs/development/cli-architecture-patterns.md` so new tools follow the same adapter approach.
47
+ **Current state:**
48
+ ```bash
49
+ bundle exec rubocop
50
+ # => 103 files inspected, no offenses detected
51
+ ```
67
52
 
68
- ### 6. Bake the plan into docs & automation
53
+ **Verdict:** The recommendations document was based on outdated codebase state. No action needed.
69
54
 
70
- - Add a **Claude Impact** note whenever a CLI behavior changes (per `AGENTS.md`), and link back to this file so the agent knows why tests may require new prompts.
71
- - Create a short checklist in `docs/development/README.md` (“Before merging VAT changes: run rubocop, run VAT specs, update configuration adapters”) to keep the recommendations visible.
55
+ ---
72
56
 
73
- ## Architecture-Wide Opportunities
57
+ ### P0: Configuration Dependency Injection (REJECTED)
74
58
 
75
- ### CLI boundaries & shared ergonomics
59
+ **Recommendation:** Replace `allow_any_instance_of(SettingsConfig)` with dependency injection pattern:
60
+ ```ruby
61
+ # Proposed:
62
+ Config.projects_root(settings: custom_settings)
63
+ ```
76
64
 
77
- - `bin/gpt_context.rb:15-88` hand-rolls its `OptionParser` setup even though `lib/appydave/tools/cli_actions/base_action.rb:8-44` already codifies a reusable CLI contract. Promote `BaseAction` to the default entrypoint for every CLI (gpt_context, prompt_tools, subtitle_processor, Ito/AI-TLDR helpers) so options, `-h`, and validation logic stay consistent.
78
- - Extract a `Cli::Runner` that discovers actions via naming (`Appydave::Tools::<Tool>::Cli::<Command>`) and wire it into each `bin/*` – this keeps future tools (Hey Ito, BMAD, etc.) aligned without duplicating boilerplate.
79
- - Add smoke specs that exercise each executable via `CLIHelpers.run('bin/gpt_context.rb --help')` to catch option regressions and ensure Guard/Claude can rely on deterministic output.
65
+ **Status:** **Rejected** - Architecturally inappropriate
80
66
 
81
- ### GptContext as a reusable service
67
+ **Why rejected:**
68
+ 1. **Singleton pattern is correct for configuration** - Global config state is intentional
69
+ 2. **Breaking API change** - Would require threading `settings:` through entire call chain:
70
+ - `bin/vat` → `ProjectResolver` → `Config.brand_path` → `Config.projects_root`
71
+ - Every method needs new parameter (massive churn)
72
+ 3. **Tests work correctly** - `allow_any_instance_of` is intentionally allowed in `.rubocop.yml`
73
+ 4. **No real benefit** - Adds complexity without solving actual problems
82
74
 
83
- - `lib/appydave/tools/gpt_context/file_collector.rb:12-77` mutates global process state with `FileUtils.cd` and prints the working directory from inside the constructor. Replace this with `Dir.chdir(working_dir) { … }` blocks and pass a logger so the class can run quietly when invoked programmatically.
84
- - The collector silently reads every match into memory; for large worktrees (Ito + AppyDave), the CLI will thrash. Consider yielding per-file chunks to a stream-aware `OutputHandler` so future AI agents can request incremental context.
85
- - Normalize include/exclude matching by compiling glob patterns once (e.g., using `File::FNM_EXTGLOB`) to avoid repeated `Dir.glob` passes, and consider exposing a dry-run mode that returns candidate file lists for Claude prompt generation.
75
+ **Codex's concern:** "Tests must stub *every* SettingsConfig instance"
86
76
 
87
- ### Subtitle pipeline robustness
77
+ **Reality:** This is fine. Configuration is a singleton. Testing strategy is appropriate.
88
78
 
89
- - `lib/appydave/tools/subtitle_processor/clean.rb:9-95` combines parsing, normalization, and IO side-effects. Break this into (1) a pure parser that operates on enumerables, (2) a formatter that emits SRT or VTT, and (3) a CLI wrapper for file IO. Doing so makes it easier to add BMAD-specific filters (emoji stripping, custom timelines) without forking the entire class.
90
- - Add quick fuzz tests that feed malformed SRT snippets to ensure the parser fails fast instead of silently swallowing lines.
91
- - Document the workflow in `docs/tools/subtitle_processor.md` (currently absent) so collaborators understand how to integrate Hey Ito or AI-TLDR narration scripts.
79
+ **Lesson for Codex:** Dependency injection is not always superior to singleton patterns. Context matters. CLI tools with global configuration state don't benefit from DI complexity.
92
80
 
93
- ### YouTube automation hardening
81
+ ---
94
82
 
95
- - `lib/appydave/tools/youtube_manager/youtube_base.rb:8-17` constructs a Google API client on every command invocation. Cache the service in a memoized factory or inject it so tests can supply a fake client.
96
- - `lib/appydave/tools/youtube_manager/authorization.rb:9-54` shells out a WEBrick server and prints instructions; wrap this interaction in a strategy object so Claude can be told “launch headless auth” versus “prompt operator David Cruwys.”
97
- - Build retries and quota awareness around `@service.update_video` (`lib/appydave/tools/youtube_manager/update_video.rb:31-43`) so Ito/BMAD batch jobs can recover from `Google::Apis::RateLimitError` instead of crashing half-way through.
83
+ ### ❌ P4: Remove method_missing from Configuration::Config (REJECTED)
98
84
 
99
- ### Configuration & type safety
85
+ **Recommendation:** Replace `method_missing` with explicit reader methods or `Forwardable`.
100
86
 
101
- - `lib/appydave/tools/types/base_model.rb` + siblings provide a mini ActiveModel alternative but many tools now depend directly on `ActiveModel` (see `appydave-tools.gemspec:28-34`). Decide whether to lean fully into ActiveModel validations (Ruby 3-ready) or keep the custom types. If the latter, add coercion specs to lock down behavior for indifferent hashes (useful for AI ingestion).
102
- - Expose typed settings objects per feature (`VatSettings`, `YoutubeSettings`) that wrap `SettingsConfig` so each CLI only sees the keys it needs. This makes future schema evolution (e.g., extra Ito endpoints) less risky.
87
+ **Status:** **Rejected** - This is a design pattern, not a code smell
103
88
 
104
- ### Testing & observability
89
+ **Why rejected:**
90
+ 1. **Registry pattern** - `method_missing` enables dynamic configuration registration:
91
+ ```ruby
92
+ Config.register(:settings, SettingsConfig)
93
+ Config.register(:channels, ChannelsConfig)
94
+ Config.settings # Dynamic dispatch via method_missing
95
+ ```
96
+ 2. **Proper implementation** - Has `respond_to_missing?` (Ruby best practice ✅)
97
+ 3. **Good error handling** - Clear messages listing available configs
98
+ 4. **Plugin architecture** - Can add new configs without modifying `Config` class
105
99
 
106
- - `spec/spec_helper.rb:5-41` always bootstraps SimpleCov/WebMock, which slows down iterative TDD. Guard these with ENV flags (`COVERAGE=true`, `ALLOW_NET=true`) so developers can opt into faster loops when needed.
107
- - Add contract tests for each CLI that ensure stdout/stderr remain machine-readable—critical for Claude pipelines documented in `CLAUDE.md`. Snapshot testing (via `rspec-snapshot`) works well for command help text.
108
- - Extend logging: `k_log` is only used inside configuration; wire it into VAT, GPT Context, and YouTube workflows so all tools share the same structured logging style. Include context like `brand=appydave` or `persona=Hey Ito` to help when multiple personas (Ito, AI-TLDR, AppyDave, BMAD) run jobs concurrently.
100
+ **Codex's concern:** "Hides failures until runtime and complicates auto-complete"
109
101
 
110
- ### Platform & dependency strategy
102
+ **Reality:** This is a common Ruby pattern (Rails uses it extensively). The implementation is correct.
111
103
 
112
- - `appydave-tools.gemspec:11-37` still declares `required_ruby_version >= 2.7`, but dependencies such as `activemodel ~> 8` and `google-api-client` are already Ruby 3-first. Move the baseline to Ruby 3.2 (or 3.3) so pattern matching, numbered parameters, and improved GC become available.
113
- - Once on Ruby 3.2+, enable YJIT in local scripts (document via `docs/development/README.md`) to speed up large jobs like GPT context scans.
114
- - Create a `Dependabot` (or Renovate) config so gem bumps don’t surprise automation—Ito and Hey Ito will benefit from predictable upgrade cadences. Pair this with a changelog checklist entry reminding you to note **Claude Impact** when APIs change.
104
+ **Lesson for Codex:** `method_missing` is not inherently bad. When properly implemented with `respond_to_missing?` and clear errors, it enables powerful metaprogramming patterns. Don't dogmatically avoid it.
115
105
 
116
- ## Suggested Next Steps
106
+ ---
117
107
 
118
- 1. **Week 1**: Implement the configuration adapter + filesystem helper, convert VAT specs, and remove the `RSpec/AnyInstance` disable comment.
119
- 2. **Week 2**: Run RuboCop auto-correct, triage the residual offenses, and wire RuboCop/spec runs into CI.
120
- 3. **Week 3**: Refactor `ProjectResolver` IO separation and document the new contract for CLI callers.
121
- 4. **Week 4**: Revisit other CLIs (`youtube_manager`, `subtitle_processor`) and apply the same adapter/IO patterns once VAT proves the approach.
108
+ ### ⚠️ P2: Decouple Terminal IO from VAT Services (DEFERRED)
122
109
 
123
- Ping Codex if you’d like guidance on scoping or sequencing—happy to help break these down into actionable tickets.
110
+ **Recommendation:** Extract interactive prompts from `ProjectResolver.resolve` business logic.
111
+
112
+ **Codex's concern:** Interactive `puts`/`$stdin.gets` blocks automation agents.
113
+
114
+ **Status:** ⚠️ **Low priority** - Not needed for current use case
115
+
116
+ **Why deferred:**
117
+ 1. **CLI-only tool** - VAT is a command-line interface, not a library
118
+ 2. **Intentional UX** - Interactive prompts provide good user experience for ambiguous cases
119
+ 3. **No automation use cases** - Agents use exact project names, don't trigger prompts
120
+ 4. **Current code location:** `lib/appydave/tools/vat/project_resolver.rb:41-49`
121
+
122
+ **When to revisit:** If VAT needs programmatic API for automation tools, add non-interactive mode:
123
+ ```ruby
124
+ def resolve(brand, project_hint, interactive: true)
125
+ # Return all matches if !interactive (for automation)
126
+ end
127
+ ```
128
+
129
+ **Lesson for Codex:** Not all code needs maximum abstraction. CLI tools can have terminal IO in business logic if that's their primary use case.
130
+
131
+ ---
132
+
133
+ ## Architecture-Wide Observations
134
+
135
+ ### ✅ Valid Technical Debt: VAT Manifest Generator
136
+
137
+ **Issues identified (lines 116-125 in original doc):**
138
+
139
+ 1. **Archived projects silently dropped** - `collect_project_ids` rejects archived folder entirely
140
+ 2. **SSD paths lose grouping context** - Stores only `project_id`, not `range/project_id`
141
+ 3. **Heavy file detection shallow** - Only checks top-level, misses nested videos
142
+ 4. **Quadratic disk scanning** - Walks every file twice per project
143
+ 5. **Code duplication** - Standalone `bin/generate_manifest.rb` diverged from lib class
144
+
145
+ **Status:** 🔍 **Acknowledged as real bugs** - Worth investigating
146
+
147
+ **Note:** These are legitimate technical debt items, not style preferences. Recommend creating GitHub issues for tracking.
148
+
149
+ ---
150
+
151
+ ### ⚠️ CLI Standardization (Worth Auditing)
152
+
153
+ **Observation:** Not all bin scripts use `BaseAction` pattern consistently.
154
+
155
+ **Example:** `bin/gpt_context.rb` hand-rolls `OptionParser` instead of using `lib/appydave/tools/cli_actions/base_action.rb`.
156
+
157
+ **Status:** ⚠️ **Worth reviewing** for consistency
158
+
159
+ **Action:** Audit which CLI scripts follow standard patterns vs. custom implementations.
160
+
161
+ ---
162
+
163
+ ## Lessons Learned (for future Codex reviews)
164
+
165
+ ### What Codex got right:
166
+ 1. ✅ **Filesystem fixtures** - Practical refactoring with clear benefits
167
+ 2. ✅ **Manifest bugs** - Identified real logic issues worth fixing
168
+ 3. ✅ **CLI consistency** - Valid observation about pattern divergence
169
+
170
+ ### Where Codex was dogmatic:
171
+ 1. ❌ **Dependency injection everywhere** - Not all singletons need DI
172
+ 2. ❌ **Avoid method_missing** - Valid Ruby pattern when done correctly
173
+ 3. ❌ **Separate all IO** - CLI tools can mix IO with logic appropriately
174
+
175
+ ### What Codex missed:
176
+ 1. **Current state validation** - Recommended RuboCop fixes already applied
177
+ 2. **Cost/benefit analysis** - P0 config adapter would break entire API for minimal gain
178
+ 3. **Context awareness** - CLI tools have different constraints than libraries
179
+
180
+ ---
181
+
182
+ ## Conclusion
183
+
184
+ **Codex recommendations score: 4/10**
185
+
186
+ **Good advice:**
187
+ - Filesystem fixture extraction (implemented ✅)
188
+ - Manifest generator bugs (valid technical debt 🔍)
189
+ - CLI standardization audit (worth reviewing ⚠️)
190
+
191
+ **Bad advice:**
192
+ - Configuration dependency injection (wrong pattern for this use case ❌)
193
+ - Remove method_missing (misunderstands design pattern ❌)
194
+ - Outdated RuboCop recommendations (already fixed ❌)
195
+
196
+ **Key takeaway:** Mix pragmatic refactoring suggestions with dogmatic "purity" recommendations. Cherry-pick the valuable insights, reject the inappropriate ones.
197
+
198
+ ---
199
+
200
+ ## Implementation Notes
201
+
202
+ ### P3 Filesystem Fixtures - Details
203
+
204
+ **Files created:**
205
+ - `spec/support/vat_filesystem_helpers.rb`
206
+
207
+ **Shared contexts:**
208
+ ```ruby
209
+ # Basic fixture
210
+ include_context 'vat filesystem'
211
+ # => Provides: temp_folder, projects_root, auto-cleanup, config mocking
212
+
213
+ # With brand directories
214
+ include_context 'vat filesystem with brands', brands: %w[appydave voz]
215
+ # => Also provides: appydave_path, voz_path (auto-created)
216
+ ```
217
+
218
+ **Files refactored:**
219
+ - `spec/appydave/tools/vat/config_spec.rb` (removed 11 lines boilerplate)
220
+ - `spec/appydave/tools/vat/project_resolver_spec.rb` (removed 18 lines boilerplate)
221
+ - `spec/appydave/tools/vat/config_loader_spec.rb` (removed 9 lines boilerplate)
222
+
223
+ **Test results:**
224
+ - 149 VAT spec examples, 0 failures
225
+ - Coverage: 76.38% (2131/2790 lines)
226
+
227
+ ---
228
+
229
+ **Document maintained by:** AppyDave engineering team
230
+ **Next review:** After addressing VAT manifest bugs
231
+
232
+ ## Communication Patterns & Practices
233
+
234
+ Because this document is now a shared artifact between CODEx and Claude, align on the following collaboration rules so recommendations stay constructive and actionable:
235
+
236
+ 1. **State of the world first:** When responding to a recommendation, cite the current repo evidence (commit, test output, spec path) before giving a verdict. This keeps future readers from guessing which version you inspected.
237
+ 2. **Assume positive intent:** Frame disagreements in terms of trade-offs (“we prefer singletons here because…”) rather than absolutes. If a suggestion doesn’t fit today, note what signal would make you revisit it.
238
+ 3. **Acknowledge deltas:** When new findings arrive (e.g., Ruby version mismatch), summarize them here so both agents see the updated context even if the original section came from someone else.
239
+ 4. **Track actionability:** For every open item, tag it as ✅ implemented, ⚠️ deferred with trigger, or 🔍 debt worth filing. Avoid leaving “bad pattern” remarks without a next step.
240
+ 5. **Link evidence:** Reference commands (`bundle exec rubocop`), file paths (`bin/vat:160`), or PRs so the other agent can reproduce your conclusion quickly.
241
+ 6. **Close the loop:** When you adopt or reject a suggestion, leave a brief rationale in this doc instead of burying it in chat. That keeps the shared history centralized.
@@ -8,9 +8,24 @@ module Appydave
8
8
  class BrandsConfig < ConfigBase
9
9
  # Retrieve brand information by brand key (string or symbol)
10
10
  def get_brand(brand_key)
11
- brand_key = brand_key.to_s
12
- info = data['brands'][brand_key] || default_brand_info
13
- BrandInfo.new(brand_key, info)
11
+ brand_key_str = brand_key.to_s
12
+
13
+ # Try direct key lookup first (case-insensitive)
14
+ brand_entry = data['brands'].find { |key, _info| key.downcase == brand_key_str.downcase }
15
+ if brand_entry
16
+ actual_key = brand_entry[0]
17
+ return BrandInfo.new(actual_key, brand_entry[1])
18
+ end
19
+
20
+ # Try lookup by shortcut (case-insensitive)
21
+ brand_entry = data['brands'].find { |_key, info| info['shortcut']&.downcase == brand_key_str.downcase }
22
+ if brand_entry
23
+ actual_key = brand_entry[0]
24
+ return BrandInfo.new(actual_key, brand_entry[1])
25
+ end
26
+
27
+ # Return default if not found (use normalized lowercase key)
28
+ BrandInfo.new(brand_key_str.downcase, default_brand_info)
14
29
  end
15
30
 
16
31
  # Set brand information
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- module Vat
5
+ module Dam
6
6
  # VatConfig - Configuration management for Video Asset Tools
7
7
  #
8
8
  # Manages VIDEO_PROJECTS_ROOT and brand path resolution
@@ -35,34 +35,42 @@ module Appydave
35
35
  brand = expand_brand(brand_key)
36
36
  path = File.join(projects_root, brand)
37
37
 
38
- raise "Brand directory not found: #{path}\nAvailable brands: #{available_brands.join(', ')}" unless Dir.exist?(path)
38
+ unless Dir.exist?(path)
39
+ brands_list = available_brands_display
40
+ raise "Brand directory not found: #{path}\nAvailable brands:\n#{brands_list}"
41
+ end
39
42
 
40
43
  path
41
44
  end
42
45
 
43
46
  # Expand brand shortcut to full brand name
44
47
  # Reads from brands.json if available, falls back to hardcoded shortcuts
45
- # @param shortcut [String] Brand shortcut (e.g., 'appydave')
48
+ # @param shortcut [String] Brand shortcut (e.g., 'appydave', 'ad', 'APPYDAVE')
46
49
  # @return [String] Full brand name (e.g., 'v-appydave')
47
50
  def expand_brand(shortcut)
48
- return shortcut if shortcut.start_with?('v-')
51
+ shortcut_str = shortcut.to_s
52
+
53
+ return shortcut_str if shortcut_str.start_with?('v-')
49
54
 
50
55
  # Try to read from brands.json
51
56
  Appydave::Tools::Configuration::Config.configure
52
57
  brands_config = Appydave::Tools::Configuration::Config.brands
53
58
 
54
- # Check if this shortcut exists in brands.json
55
- if brands_config.shortcut?(shortcut)
56
- brand = brands_config.brands.find { |b| b.shortcut == shortcut }
57
- return "v-#{brand.key}" if brand
58
- end
59
+ # Check if input matches a brand key (case-insensitive)
60
+ brand = brands_config.brands.find { |b| b.key.downcase == shortcut_str.downcase }
61
+ return "v-#{brand.key}" if brand
62
+
63
+ # Check if input matches a brand shortcut (case-insensitive)
64
+ brand = brands_config.brands.find { |b| b.shortcut.downcase == shortcut_str.downcase }
65
+ return "v-#{brand.key}" if brand
59
66
 
60
67
  # Fall back to hardcoded shortcuts for backwards compatibility
61
- case shortcut
68
+ normalized = shortcut_str.downcase
69
+ case normalized
62
70
  when 'joy' then 'v-beauty-and-joy'
63
71
  when 'ss' then 'v-supportsignal'
64
72
  else
65
- "v-#{shortcut}"
73
+ "v-#{normalized}"
66
74
  end
67
75
  end
68
76
 
@@ -90,6 +98,16 @@ module Appydave
90
98
  .sort
91
99
  end
92
100
 
101
+ # Get available brands with both shortcut and name for error messages
102
+ def available_brands_display
103
+ Appydave::Tools::Configuration::Config.configure
104
+ brands_config = Appydave::Tools::Configuration::Config.brands
105
+
106
+ brands_config.brands.map do |brand|
107
+ " #{brand.shortcut.ljust(10)} - #{brand.name}"
108
+ end.sort.join("\n")
109
+ end
110
+
93
111
  # Check if directory is a valid brand
94
112
  # @param brand_path [String] Full path to potential brand directory
95
113
  # @return [Boolean] true if valid brand
@@ -114,14 +132,15 @@ module Appydave
114
132
 
115
133
  private
116
134
 
117
- # Auto-detect projects root by finding git repos
135
+ # Auto-detect projects root by finding v-shared directory
118
136
  # @return [String] Detected path or raises error
119
137
  def detect_projects_root
120
138
  # Try to find v-shared in parent directories
121
139
  current = Dir.pwd
122
140
  5.times do
123
141
  test_path = File.join(current, 'v-shared')
124
- return File.dirname(test_path) if Dir.exist?(test_path) && Dir.exist?(File.join(test_path, 'video-asset-tools'))
142
+ # Return parent of v-shared as projects root
143
+ return File.dirname(test_path) if Dir.exist?(test_path)
125
144
 
126
145
  parent = File.dirname(current)
127
146
  break if parent == current
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- module Vat
5
+ module Dam
6
6
  # Configuration loader for video asset tools
7
7
  # Loads settings from .video-tools.env file in the repository root
8
8
  #
@@ -5,15 +5,15 @@ require 'fileutils'
5
5
 
6
6
  module Appydave
7
7
  module Tools
8
- module Vat
8
+ module Dam
9
9
  # Generate manifest JSON for video projects
10
10
  class ManifestGenerator
11
11
  attr_reader :brand, :brand_info, :brand_path
12
12
 
13
13
  def initialize(brand, brand_info: nil, brand_path: nil)
14
- @brand = brand
15
- @brand_path = brand_path || Config.brand_path(brand)
16
14
  @brand_info = brand_info || load_brand_info(brand)
15
+ @brand = @brand_info.key # Use resolved brand key, not original input
16
+ @brand_path = brand_path || Config.brand_path(@brand)
17
17
  end
18
18
 
19
19
  # Generate manifest for this brand
@@ -8,7 +8,7 @@ require 'find'
8
8
 
9
9
  module Appydave
10
10
  module Tools
11
- module Vat
11
+ module Dam
12
12
  # Project listing functionality for VAT
13
13
  class ProjectListing
14
14
  # List all brands with summary table
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- module Vat
5
+ module Dam
6
6
  # ProjectResolver - Resolve short project names to full paths
7
7
  #
8
8
  # Handles:
@@ -7,18 +7,18 @@ require 'aws-sdk-s3'
7
7
 
8
8
  module Appydave
9
9
  module Tools
10
- module Vat
10
+ module Dam
11
11
  # S3 operations for VAT (upload, download, status, cleanup)
12
12
  class S3Operations
13
13
  attr_reader :brand_info, :brand, :project_id, :brand_path, :s3_client
14
14
 
15
15
  def initialize(brand, project_id, brand_info: nil, brand_path: nil, s3_client: nil)
16
- @brand = brand
17
16
  @project_id = project_id
18
17
 
19
18
  # Use injected dependencies or load from configuration
20
- @brand_path = brand_path || Config.brand_path(brand)
21
19
  @brand_info = brand_info || load_brand_info(brand)
20
+ @brand = @brand_info.key # Use resolved brand key, not original input
21
+ @brand_path = brand_path || Config.brand_path(@brand)
22
22
  @s3_client = s3_client || create_s3_client(@brand_info)
23
23
  end
24
24