appydave-tools 0.76.4 → 0.76.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63d45ba7b83fca823b63a907473c424701e50c117ccac57cccdc1d31f2ae277c
4
- data.tar.gz: 1dff3941a4d00ecc51b84b5b955c7640c955ee0037f52040e4469f5d9b007c4f
3
+ metadata.gz: 0531bc72c6c4aeac9830f72f552a4d683775d7efc61ee483069d882cdbbbf5af
4
+ data.tar.gz: 1c8e032ddc4e8dfc4c196452465abea40958107d2d68c5e4063c1ec2c36a3a9d
5
5
  SHA512:
6
- metadata.gz: fa0a3525cae67422cde5aa4cc880362fbeb24b6ee5d914ece224965daa07ce61878da662aec431edf3c10d9feae53cc8ec3e383bc10fa6c8267f92ade321a8ff
7
- data.tar.gz: d36d104f67799d434b592effa0e6a029b92ec1e1005216c68269ea0637f67c5fdd2867505c42b900f497d16218a425f480503c6d18cffc6afc3d64b3c9313774
6
+ metadata.gz: 1787f781bdf758fae98c3388cce0437aca330a3161937f5c214651885c80e05598682e64afc3adea1e2ffd3b2f70d09287a9acca12c8fada89077b7d48791a39
7
+ data.tar.gz: 6cadabed0aa0f0be9d993b4dbda2c304c9a34f717fcc7b86b8cc059777e7cb0a57a494ddc2c3e5432fd4d4d85990330a6d401aa75c89bb7b0be5f5207c68d12b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.76.4](https://github.com/appydave/appydave-tools/compare/v0.76.3...v0.76.4) (2026-03-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add subprocess test for -f json flag to cli_spec ([de6a28a](https://github.com/appydave/appydave-tools/commit/de6a28a39790d29f5141fbf313fa5e44782b90a6))
7
+ * restore youtube_automation_config require accidentally removed in previous commit ([e4180db](https://github.com/appydave/appydave-tools/commit/e4180db9ad90040ec4faf2811fad713a20e6be8e))
8
+
1
9
  ## [0.76.3](https://github.com/appydave/appydave-tools/compare/v0.76.2...v0.76.3) (2026-03-19)
2
10
 
3
11
 
data/bin/dam CHANGED
@@ -515,63 +515,6 @@ class VatCLI
515
515
  brands.key?(brand_key) || brands.shortcut?(brand_key)
516
516
  end
517
517
 
518
- # Add local sync status to matched projects data
519
- # Mutates the matched_projects hash to add :local_status key
520
- # @param matched_projects [Hash] Map of project_id => S3 data
521
- # @param brand_key [String] Brand key (e.g., 'appydave', 'ss')
522
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
523
- def add_local_sync_status!(matched_projects, brand_key)
524
- matched_projects.each do |project_id, data|
525
- project_path = Appydave::Tools::Dam::Config.project_path(brand_key, project_id)
526
- s3_staging_path = File.join(project_path, 's3-staging')
527
-
528
- if !Dir.exist?(project_path)
529
- data[:local_status] = :no_project # Project directory doesn't exist
530
- elsif !Dir.exist?(s3_staging_path)
531
- data[:local_status] = :no_files # Project exists but no downloads yet
532
- else
533
- # Count local files in s3-staging
534
- local_files = Dir.glob(File.join(s3_staging_path, '**', '*'))
535
- .select { |f| File.file?(f) }
536
- .reject { |f| File.basename(f).include?('Zone.Identifier') } # Exclude Windows metadata
537
-
538
- s3_file_count = data[:file_count]
539
- local_file_count = local_files.size
540
-
541
- data[:local_status] = if local_file_count.zero?
542
- :no_files
543
- elsif local_file_count == s3_file_count
544
- :synced # Fully synced
545
- else
546
- :partial # Some files downloaded
547
- end
548
-
549
- data[:local_file_count] = local_file_count
550
- end
551
- end
552
- end
553
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
554
-
555
- # Format local sync status for display
556
- # @param status [Symbol] :synced, :no_files, :partial, :no_project
557
- # @param local_count [Integer, nil] Number of local files
558
- # @param s3_count [Integer] Number of S3 files
559
- # @return [String] Formatted status string
560
- def format_local_status(status, local_count, s3_count)
561
- case status
562
- when :synced
563
- '✓ Synced'
564
- when :no_files
565
- '⚠ None'
566
- when :partial
567
- "⚠ #{local_count}/#{s3_count}"
568
- when :no_project
569
- '✗ Missing'
570
- else
571
- 'Unknown'
572
- end
573
- end
574
-
575
518
  def parse_share_args(args)
576
519
  # Extract --expires flag
577
520
  expires = '7d' # default
@@ -693,14 +636,14 @@ class VatCLI
693
636
 
694
637
  # Format columns
695
638
  file_col = filename.ljust(40)
696
- size_col = format_bytes(size).rjust(10)
639
+ size_col = Appydave::Tools::Dam::FileHelper.format_size(size).rjust(10)
697
640
  date_col = last_modified ? last_modified.strftime('%Y-%m-%d %H:%M') : 'N/A'.ljust(16)
698
641
 
699
642
  puts "#{file_col} #{size_col} #{date_col}"
700
643
  end
701
644
 
702
645
  puts '-' * 72
703
- puts "Total: #{files.size} #{files.size == 1 ? 'file' : 'files'}, #{format_bytes(total_bytes)}"
646
+ puts "Total: #{files.size} #{files.size == 1 ? 'file' : 'files'}, #{Appydave::Tools::Dam::FileHelper.format_size(total_bytes)}"
704
647
 
705
648
  # Quick actions section
706
649
  puts ''
@@ -1478,7 +1421,7 @@ class VatCLI
1478
1421
  File.write(manifest_path, JSON.pretty_generate(manifest))
1479
1422
 
1480
1423
  # Add local sync status to matched projects
1481
- add_local_sync_status!(matched_projects, brand_key)
1424
+ Appydave::Tools::Dam::LocalSyncStatus.enrich!(matched_projects, brand_key)
1482
1425
 
1483
1426
  # Display table
1484
1427
  display_s3_scan_table(matched_projects, orphaned_projects, bucket, prefix, region)
@@ -1507,8 +1450,8 @@ class VatCLI
1507
1450
  # Display matched projects first (sorted alphabetically)
1508
1451
  matched_projects.sort.each do |project_id, data|
1509
1452
  files = data[:file_count].to_s.rjust(5)
1510
- size = format_bytes(data[:total_bytes]).rjust(10)
1511
- local_status = format_local_status(data[:local_status], data[:local_file_count], data[:file_count])
1453
+ size = Appydave::Tools::Dam::FileHelper.format_size(data[:total_bytes]).rjust(10)
1454
+ local_status = Appydave::Tools::Dam::LocalSyncStatus.format(data[:local_status], data[:local_file_count], data[:file_count])
1512
1455
  modified = data[:last_modified] ? Time.parse(data[:last_modified]).strftime('%Y-%m-%d %H:%M') : 'N/A'
1513
1456
 
1514
1457
  puts format('%-36s %5s %10s %-9s %s', project_id, files, size, local_status, modified)
@@ -1520,7 +1463,7 @@ class VatCLI
1520
1463
  puts '-' * 92
1521
1464
  orphaned_projects.sort.each do |project_id, data|
1522
1465
  files = data[:file_count].to_s.rjust(5)
1523
- size = format_bytes(data[:total_bytes]).rjust(10)
1466
+ size = Appydave::Tools::Dam::FileHelper.format_size(data[:total_bytes]).rjust(10)
1524
1467
  local_status = 'N/A'
1525
1468
  modified = data[:last_modified] ? Time.parse(data[:last_modified]).strftime('%Y-%m-%d %H:%M') : 'N/A'
1526
1469
 
@@ -1582,19 +1525,6 @@ class VatCLI
1582
1525
  puts "Total brands scanned: #{successful.size}/#{results.size}"
1583
1526
  end
1584
1527
  # rubocop:enable Metrics/MethodLength
1585
-
1586
- # Format bytes in human-readable format
1587
- # rubocop:disable Style/FormatStringToken
1588
- def format_bytes(bytes)
1589
- return '0 B' if bytes.zero?
1590
-
1591
- units = %w[B KB MB GB TB]
1592
- exp = (Math.log(bytes) / Math.log(1024)).to_i
1593
- exp = [exp, units.length - 1].min
1594
-
1595
- format('%.1f %s', bytes.to_f / (1024**exp), units[exp])
1596
- end
1597
- # rubocop:enable Style/FormatStringToken
1598
1528
  end
1599
1529
 
1600
1530
  # Run CLI
@@ -14,6 +14,18 @@
14
14
 
15
15
  ---
16
16
 
17
+ ## ⚠️ Pre-Commit Check (Mandatory Every Commit)
18
+
19
+ Before running `kfix`, always run:
20
+ ```bash
21
+ git status
22
+ ```
23
+ Confirm ONLY the files you intentionally changed are staged. If unexpected files appear, run `git diff` to investigate before proceeding.
24
+
25
+ **Why:** micro-cleanup (2026-03-19) accidentally staged a pre-existing uncommitted change in `lib/appydave/tools.rb` when running `kfix`.
26
+
27
+ ---
28
+
17
29
  ## Build & Run Commands
18
30
 
19
31
  ```bash
@@ -33,7 +45,7 @@ kfeat "add feature description" # Minor version bump
33
45
  kfix "fix bug description" # Patch version bump
34
46
  ```
35
47
 
36
- **Baseline (2026-03-19):** 748 examples, 0 failures, 84.88% line coverage (7680/9048)
48
+ **Baseline (2026-03-19):** 831 examples, 0 failures, ~85.92% line coverage
37
49
 
38
50
  ---
39
51
 
@@ -82,9 +94,9 @@ docs/
82
94
 
83
95
  Every work unit must satisfy ALL of the following before marking `[x]`:
84
96
 
85
- - [ ] `bundle exec rspec` — 748+ examples, 0 failures
97
+ - [ ] `bundle exec rspec` — 831+ examples, 0 failures
86
98
  - [ ] `bundle exec rubocop --format clang` — 0 offenses
87
- - [ ] Line coverage stays ≥ 84.88%
99
+ - [ ] Line coverage stays ≥ 85.92%
88
100
  - [ ] Any new functionality has ≥ 1 spec covering it
89
101
  - [ ] All new `.rb` files start with `# frozen_string_literal: true`
90
102
  - [ ] No hardcoded brand transformation strings (always use BrandResolver)
@@ -209,9 +221,9 @@ Appydave::Tools::Configuration::Config.configure
209
221
 
210
222
  ## Quality Gates
211
223
 
212
- - **Tests:** `bundle exec rspec` — 748 examples, 0 failures (do not ship if any fail)
224
+ - **Tests:** `bundle exec rspec` — 831 examples, 0 failures (do not ship if any fail)
213
225
  - **Lint:** `bundle exec rubocop --format clang` — 0 offenses (CI will reject)
214
- - **Coverage:** ≥ 84.88% line coverage
226
+ - **Coverage:** ≥ 85.92% line coverage
215
227
  - **frozen_string_literal:** Required on every new `.rb` file
216
228
  - **Commit format:** `kfeat`/`kfix` only — triggers semantic versioning + CI wait
217
229
 
@@ -1,7 +1,7 @@
1
1
  # Project Backlog — AppyDave Tools
2
2
 
3
- **Last updated**: 2026-03-19 (final-test-gaps campaign complete; B031/B032/B033 added from quality audit)
4
- **Total**: 33 | Pending: 11 | Done: 22 | Deferred: 0 | Rejected: 0
3
+ **Last updated**: 2026-03-19 (micro-cleanup campaign complete)
4
+ **Total**: 33 | Pending: 7 | In Progress: 1 | Done: 25 | Deferred: 0 | Rejected: 0
5
5
 
6
6
  ---
7
7
 
@@ -10,16 +10,11 @@
10
10
  ### Medium Priority
11
11
  - [ ] B001 — FR-1: GPT Context token counting | Priority: medium
12
12
  - [ ] B012 — Arch: add integration tests for brand resolution end-to-end | Priority: medium
13
-
14
- ### Low Priority
15
- - [ ] B031 — Tests: add_spec.rb assert `type` field in location data integrity test | Priority: low
16
- - [ ] B032 — Tests: cli_spec.rb add subprocess test for `-f json` flag | Priority: low
17
- - [ ] B033 — Fix: file_collector.rb line 19 return `''` directly when working_directory doesn't exist | Priority: low
18
13
  - [ ] B007 — Performance: parallel git/S3 status checks for dam list | Priority: low
19
14
  - [ ] B008 — Performance: cache git/S3 status with 5-min TTL | Priority: low
20
15
  - [ ] B009 — UX: progress indicators for dam operations > 5s | Priority: low
21
16
  - [ ] B010 — UX: auto-adjust dam table column widths to terminal width | Priority: low
22
- - [ ] B011 — Arch: extract VatCLI business logic from bin/dam (1,600-line God class) | Priority: low
17
+ - [~] B011 — Arch: extract VatCLI business logic from bin/dam (1,600-line God class) | Campaign: extract-vat-cli
23
18
  - [ ] B020 — Arch: split S3Operations (1,030 lines, mixed I/O + logic) | Priority: low
24
19
 
25
20
  ---
@@ -48,6 +43,9 @@
48
43
  - [x] B028 — Tests: cli_spec file body content assertions in -i/-e tests | Completed: final-test-gaps (2026-03-19)
49
44
  - [x] B029 — Tests: add_spec validate all returned location data fields | Completed: final-test-gaps (2026-03-19)
50
45
  - [x] B030 — Tests: update_spec verify non-updated fields unchanged | Completed: final-test-gaps (2026-03-19)
46
+ - [x] B031 — Tests: add_spec `type` field assertion | Completed: already in commit 8eec40c; closed micro-cleanup (2026-03-19)
47
+ - [x] B032 — Tests: cli_spec `-f json` subprocess test | Completed: micro-cleanup (2026-03-19), v0.76.4
48
+ - [x] B033 — Fix: file_collector.rb return `''` when working_directory missing | Completed: already in commit 13d5f87; closed micro-cleanup (2026-03-19)
51
49
 
52
50
  ---
53
51
 
@@ -0,0 +1,363 @@
1
+ # AGENTS.md — AppyDave Tools / extract-vat-cli campaign
2
+
3
+ > Operational knowledge for every background agent. Self-contained — you receive only this file + your work unit prompt.
4
+ > Last updated: 2026-03-19 (extract-vat-cli campaign)
5
+
6
+ ---
7
+
8
+ ## Project Overview
9
+
10
+ **What:** Ruby gem providing CLI productivity tools for AppyDave's YouTube content creation workflow.
11
+ **Stack:** Ruby 3.4.2, Bundler 2.6.2, RSpec, RuboCop, semantic-release CI/CD.
12
+ **Active campaign:** extract-vat-cli — extracting business logic from `bin/dam` (1,600-line God class) into proper library classes.
13
+ **Commits:** Trigger automated semantic versioning via GitHub Actions. Always use `kfeat`/`kfix` — never `git commit`.
14
+
15
+ ---
16
+
17
+ ## ⚠️ Pre-Commit Check (Mandatory Every Commit)
18
+
19
+ Before running `kfix`, always run:
20
+ ```bash
21
+ git status
22
+ ```
23
+ Confirm ONLY the files you intentionally changed are staged. If unexpected files appear, run `git diff` to investigate before proceeding. Never commit files you didn't intentionally change.
24
+
25
+ **Why:** Prior campaign (micro-cleanup) accidentally staged a pre-existing uncommitted change in `lib/appydave/tools.rb` when running `kfix`. This required a follow-up fix commit.
26
+
27
+ ---
28
+
29
+ ## Build & Run Commands
30
+
31
+ ```bash
32
+ # Initialize rbenv (required if rbenv not in PATH)
33
+ eval "$(rbenv init -)"
34
+
35
+ # Run tests
36
+ bundle exec rspec # All tests
37
+ bundle exec rspec spec/path/to/file_spec.rb # Single file
38
+ RUBYOPT="-W0" bundle exec rspec # Suppress Ruby 3.4 platform warnings
39
+
40
+ # Lint
41
+ bundle exec rubocop --format clang # Standard lint check (matches CI)
42
+
43
+ # Commit (never use git commit directly)
44
+ kfeat "add feature description" # Minor version bump
45
+ kfix "fix bug description" # Patch version bump
46
+ ```
47
+
48
+ **Baseline (2026-03-19):** 831 examples, 0 failures, ~85.92% line coverage
49
+
50
+ ---
51
+
52
+ ## Directory Structure
53
+
54
+ ```
55
+ bin/ CLI scripts (development, .rb extension)
56
+ exe/ Thin wrappers for gem installation (no .rb extension)
57
+ lib/appydave/tools/
58
+ dam/ Digital Asset Management — main active area
59
+ brand_resolver.rb Centralizes ALL brand name transformations (appydave ↔ v-appydave)
60
+ errors.rb Custom exception hierarchy (DamError, BrandNotFoundError, etc.)
61
+ fuzzy_matcher.rb Levenshtein distance for "did you mean?" suggestions
62
+ git_helper.rb Extracted git command wrappers (current_branch, commits_ahead, etc.)
63
+ file_helper.rb File utility methods (calculate_directory_size, format_size, format_age)
64
+ config.rb Delegates brand resolution to BrandResolver; memoized Config loading
65
+ project_resolver.rb Project name resolution with regex pattern matching
66
+ project_listing.rb Table display for `dam list` command (format() for headers + data)
67
+ s3_operations.rb S3 upload/download/status with MD5 comparison
68
+ s3_scanner.rb S3 bucket scanner for s3-scan command
69
+ status.rb Project git/S3 status display
70
+ manifest_generator.rb Video project manifest
71
+ sync_from_ssd.rb SSD sync operations
72
+ ssd_status.rb SSD backup status
73
+ share_operations.rb Pre-signed URL generation
74
+ config_loader.rb Loads .video-tools.env per brand
75
+ repo_push.rb, repo_status.rb, repo_sync.rb
76
+ # NEW — being created in this campaign:
77
+ local_sync_status.rb [WU2] Enrich project data with local s3-staging sync status
78
+ s3_scan_command.rb [WU3] S3 scan orchestration + display (extracted from VatCLI)
79
+ s3_arg_parser.rb [WU4] CLI argument parsing for S3 commands (extracted from VatCLI)
80
+ lib/appydave/tools.rb Require file — ADD new dam files here after creating them
81
+ spec/
82
+ appydave/tools/dam/ One spec file per dam/ class
83
+ support/
84
+ dam_filesystem_helpers.rb Shared contexts for DAM filesystem testing
85
+ ```
86
+
87
+ ---
88
+
89
+ ## This Campaign: What We're Extracting from bin/dam
90
+
91
+ `bin/dam` is a 1,600-line `VatCLI` class with 20+ `rubocop-disable` comments. Four clusters of business logic are being extracted.
92
+
93
+ ### Work Unit Dependencies
94
+
95
+ All 4 work units touch `bin/dam`. Run SEQUENTIALLY — never in parallel.
96
+
97
+ ```
98
+ WU1: extract-format-bytes (no deps)
99
+ WU2: extract-local-sync-status (no deps — but WU3 depends on it)
100
+ WU3: extract-s3-scan-command (DEPENDS ON WU2 — uses LocalSyncStatus)
101
+ WU4: extract-s3-arg-parser (no deps on WU1-3 — run after WU3 for safety)
102
+ ```
103
+
104
+ ### WU1: extract-format-bytes
105
+
106
+ **What:** `VatCLI#format_bytes` is a duplicate of `FileHelper.format_size` (already exists, already tested).
107
+
108
+ **Change:**
109
+ - Replace 3 callers in `bin/dam` with `Appydave::Tools::Dam::FileHelper.format_size(x)`:
110
+ - Line 1510 in `display_s3_scan_table`
111
+ - Line 696 in `display_s3_files`
112
+ - Line 702 in `display_s3_files`
113
+ - Delete `format_bytes` method from VatCLI (lines 1586-1597, including rubocop-disable/enable wrapper)
114
+ - No new spec needed — `FileHelper.format_size` already has specs
115
+
116
+ **Done when:** rubocop 0 offenses, 831 examples passing, `format_bytes` gone from bin/dam.
117
+
118
+ ### WU2: extract-local-sync-status
119
+
120
+ **What:** `add_local_sync_status!` and `format_local_status` are VatCLI private methods. They contain business logic (filesystem inspection + status classification) that belongs in a library class.
121
+
122
+ **Create** `lib/appydave/tools/dam/local_sync_status.rb`:
123
+ ```ruby
124
+ # frozen_string_literal: true
125
+
126
+ module Appydave
127
+ module Tools
128
+ module Dam
129
+ # Enriches S3 scan project data with local s3-staging sync status
130
+ module LocalSyncStatus
131
+ module_function
132
+
133
+ # Mutates matched_projects hash to add :local_status key
134
+ # @param matched_projects [Hash] Map of project_id => S3 data
135
+ # @param brand_key [String] Brand key (e.g., 'appydave')
136
+ def enrich!(matched_projects, brand_key)
137
+ # (move add_local_sync_status! body here — change Config.project_path call to use Dam::Config)
138
+ end
139
+
140
+ # Format local sync status for display
141
+ # @param status [Symbol] :synced, :no_files, :partial, :no_project
142
+ # @param local_count [Integer, nil] Number of local files
143
+ # @param s3_count [Integer] Number of S3 files
144
+ # @return [String] Formatted status string
145
+ def format(status, local_count, s3_count)
146
+ # (move format_local_status body here)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ ```
153
+
154
+ **Update callers in bin/dam:**
155
+ - `scan_single_brand_s3`: replace `add_local_sync_status!(matched_projects, brand_key)` with `Appydave::Tools::Dam::LocalSyncStatus.enrich!(matched_projects, brand_key)`
156
+ - `display_s3_scan_table`: replace `format_local_status(data[:local_status], data[:local_file_count], data[:file_count])` with `Appydave::Tools::Dam::LocalSyncStatus.format(data[:local_status], data[:local_file_count], data[:file_count])`
157
+
158
+ **Register** in `lib/appydave/tools.rb` after line 79: `require 'appydave/tools/dam/local_sync_status'`
159
+
160
+ **Add spec** `spec/appydave/tools/dam/local_sync_status_spec.rb` — test `enrich!` with a temp filesystem (use `include_context 'with vat filesystem and brands'`), test `format` for all 4 status symbols.
161
+
162
+ **Done when:** rubocop 0 offenses, 832+ examples passing (new specs), `add_local_sync_status!` and `format_local_status` gone from bin/dam.
163
+
164
+ ### WU3: extract-s3-scan-command
165
+
166
+ **What:** `scan_single_brand_s3`, `scan_all_brands_s3`, and `display_s3_scan_table` are 140 lines of orchestration + display logic in VatCLI. Extract to a new class.
167
+
168
+ **Prerequisite:** WU2 must be complete — `S3ScanCommand` uses `LocalSyncStatus.enrich!` and `LocalSyncStatus.format`.
169
+
170
+ **Create** `lib/appydave/tools/dam/s3_scan_command.rb` — class with:
171
+ - `scan_single(brand_key)` — body from `scan_single_brand_s3`
172
+ - `scan_all` — body from `scan_all_brands_s3`
173
+ - `display_table(matched_projects, orphaned_projects, bucket, prefix, region)` — body from `display_s3_scan_table` (private, called from scan_single)
174
+
175
+ **Update bin/dam:**
176
+ ```ruby
177
+ def s3_scan_command(args)
178
+ all_brands = args.include?('--all')
179
+ args = args.reject { |arg| arg.start_with?('--') }
180
+ brand_arg = args[0]
181
+
182
+ if all_brands
183
+ Appydave::Tools::Dam::S3ScanCommand.new.scan_all
184
+ elsif brand_arg
185
+ Appydave::Tools::Dam::S3ScanCommand.new.scan_single(brand_arg)
186
+ else
187
+ # show usage (keep inline — 4 lines)
188
+ end
189
+ rescue StandardError => e
190
+ puts "❌ Error: #{e.message}"
191
+ puts e.backtrace.first(5).join("\n") if ENV['DEBUG']
192
+ exit 1
193
+ end
194
+ ```
195
+ Delete `scan_single_brand_s3`, `scan_all_brands_s3`, `display_s3_scan_table` from VatCLI.
196
+
197
+ **Register** in `lib/appydave/tools.rb`: `require 'appydave/tools/dam/s3_scan_command'`
198
+
199
+ **Add spec** `spec/appydave/tools/dam/s3_scan_command_spec.rb` — smoke test that the class loads and methods exist; mock `S3Scanner` and `Config`; no full S3 integration needed.
200
+
201
+ **Done when:** rubocop 0 offenses, 832+ examples passing, 3 methods gone from bin/dam.
202
+
203
+ ### WU4: extract-s3-arg-parser
204
+
205
+ **What:** `parse_s3_args`, `valid_brand?`, `parse_share_args`, `show_share_usage_and_exit`, `parse_discover_args` are 130 lines of argument parsing in VatCLI. All set `ENV['BRAND_PATH']` and share the same brand/project resolution chain.
206
+
207
+ **Create** `lib/appydave/tools/dam/s3_arg_parser.rb` — module with `module_function`:
208
+ - `parse_s3(args, command)` — from `parse_s3_args`
209
+ - `parse_share(args)` — from `parse_share_args`
210
+ - `parse_discover(args)` — from `parse_discover_args`
211
+ - `valid_brand?(brand_key)` — from `valid_brand?` (private helper, keep as module_function)
212
+ - `show_share_usage_and_exit` — from `show_share_usage_and_exit` (private helper)
213
+
214
+ **Update callers in bin/dam** (5 methods to update):
215
+ - `s3_up_command`, `s3_down_command`, `s3_status_command`, `s3_cleanup_remote_command`, `s3_cleanup_local_command`, `archive_command`: replace `parse_s3_args(args, '...')` with `Appydave::Tools::Dam::S3ArgParser.parse_s3(args, '...')`
216
+ - `s3_share_command`: replace `parse_share_args(args)` with `Appydave::Tools::Dam::S3ArgParser.parse_share(args)`
217
+ - `s3_discover_command`: replace `parse_discover_args(args)` with `Appydave::Tools::Dam::S3ArgParser.parse_discover(args)`
218
+
219
+ **Register** in `lib/appydave/tools.rb`: `require 'appydave/tools/dam/s3_arg_parser'`
220
+
221
+ **Add spec** `spec/appydave/tools/dam/s3_arg_parser_spec.rb` — test `parse_s3` with brand+project args, with PWD auto-detect (mock `ProjectResolver.detect_from_pwd`), with invalid brand; test `valid_brand?` with known brands.
222
+
223
+ **Done when:** rubocop 0 offenses, 833+ examples passing, 5 methods gone from bin/dam.
224
+
225
+ ---
226
+
227
+ ## Success Criteria
228
+
229
+ Every work unit must satisfy ALL of the following before marking `[x]`:
230
+
231
+ - [ ] `RUBYOPT="-W0" bundle exec rspec` — 831+ examples, 0 failures
232
+ - [ ] `bundle exec rubocop --format clang` — 0 offenses
233
+ - [ ] Line coverage stays ≥ 85.92%
234
+ - [ ] Any new `.rb` files start with `# frozen_string_literal: true`
235
+ - [ ] New class/module registered in `lib/appydave/tools.rb`
236
+ - [ ] At least 1 spec for each new library file
237
+ - [ ] Extracted methods removed from VatCLI in `bin/dam`
238
+ - [ ] `git status` confirmed clean before `kfix`
239
+
240
+ ---
241
+
242
+ ## Reference Patterns
243
+
244
+ ### Shared Context for DAM Specs — THE STANDARD PATTERN
245
+
246
+ ```ruby
247
+ # spec/appydave/tools/dam/some_class_spec.rb
248
+ # frozen_string_literal: true
249
+
250
+ require 'spec_helper'
251
+
252
+ RSpec.describe Appydave::Tools::Dam::SomeClass do
253
+ include_context 'with vat filesystem and brands', brands: %w[appydave voz]
254
+
255
+ before do
256
+ FileUtils.mkdir_p(File.join(appydave_path, 'b65-test-project'))
257
+ end
258
+
259
+ describe '.some_method' do
260
+ it 'does the thing' do
261
+ expect(described_class.some_method('appydave')).to eq('expected')
262
+ end
263
+ end
264
+ end
265
+ ```
266
+
267
+ **Available from shared context:**
268
+ - `temp_folder` — temp root (auto-cleaned after each example)
269
+ - `projects_root` — `/path/to/temp/video-projects`
270
+ - `appydave_path`, `voz_path`, etc. — brand dirs (created on demand)
271
+ - `SettingsConfig#video_projects_root` is mocked to return `projects_root`
272
+
273
+ ### FileHelper — Use These, Don't Duplicate
274
+
275
+ ```ruby
276
+ Appydave::Tools::Dam::FileHelper.format_size(bytes) # "1.5 GB"
277
+ Appydave::Tools::Dam::FileHelper.calculate_directory_size(path) # Integer bytes
278
+ Appydave::Tools::Dam::FileHelper.format_age(time) # "3d", "2w", etc.
279
+ ```
280
+
281
+ ### BrandResolver — All Brand Name Transformations
282
+
283
+ ```ruby
284
+ BrandResolver.expand('appydave') # => 'v-appydave'
285
+ BrandResolver.expand('ad') # => 'v-appydave' (shortcut)
286
+ BrandResolver.normalize('v-appydave') # => 'appydave'
287
+ BrandResolver.validate('appydave') # => 'appydave' or raises BrandNotFoundError
288
+ ```
289
+
290
+ ### Typed Exception Pattern
291
+
292
+ ```ruby
293
+ raise ProjectNotFoundError, 'Project name is required' if project_hint.nil?
294
+ raise BrandNotFoundError.new(brand, available_brands, fuzzy_suggestions)
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Anti-Patterns to Avoid
300
+
301
+ - ❌ Inline brand transformations — never write `"v-#{brand}"` outside BrandResolver
302
+ - ❌ `format_bytes` — does not exist anymore; use `FileHelper.format_size`
303
+ - ❌ Duplicating `format_size` / byte formatting logic — use `FileHelper.format_size`
304
+ - ❌ Mocking Config class methods in DAM specs — use shared filesystem context instead
305
+ - ❌ Multiple `before` blocks in same RSpec context — merge them (triggers RSpec/ScatteredSetup)
306
+ - ❌ `$?` for subprocess status — use `$CHILD_STATUS` (rubocop Style/SpecialGlobalVars)
307
+ - ❌ `raise 'string error'` in DAM module — use typed exceptions from `errors.rb`
308
+ - ❌ `include FileUtils` — use dam's `FileHelper` instead
309
+ - ❌ Hardcoded header strings for table output — always use `format()` matching data row format
310
+ - ❌ Adding new `Config.configure` calls — memoized but called redundantly; don't spread further
311
+
312
+ ---
313
+
314
+ ## Mock Patterns
315
+
316
+ ### ENV Stubbing (if needed in specs)
317
+
318
+ ```ruby
319
+ allow(ENV).to receive(:[]).and_call_original
320
+ allow(ENV).to receive(:[]).with('BRAND_PATH').and_return('/tmp/test/v-appydave')
321
+ ```
322
+
323
+ **Do NOT use climate_control gem** — project doesn't have it.
324
+
325
+ ### External Services
326
+
327
+ ```ruby
328
+ # S3 calls
329
+ stub_request(:get, /s3\.amazonaws\.com/).to_return(body: '...')
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Quality Gates
335
+
336
+ - **Tests:** `RUBYOPT="-W0" bundle exec rspec` — 831+ examples, 0 failures
337
+ - **Lint:** `bundle exec rubocop --format clang` — 0 offenses (CI will reject)
338
+ - **Coverage:** ≥ 85.92% line coverage
339
+ - **frozen_string_literal:** Required on every new `.rb` file
340
+ - **Commit format:** `kfeat`/`kfix` only — triggers semantic versioning + CI wait
341
+ - **Pre-commit:** Always run `git status` before `kfix` — confirm staged files
342
+
343
+ ---
344
+
345
+ ## Learnings
346
+
347
+ ### From DAM Enhancement Sprint (Jan 2025)
348
+
349
+ - **BrandResolver is the critical path.** All `dam` commands flow through it. Any change to brand resolution must be tested with all shortcuts.
350
+ - **`Regexp.last_match` is reset by `.sub()` calls.** Always capture regex groups BEFORE any string transformation.
351
+ - **`Config.configure` is memoized but called redundantly.** Don't add new calls.
352
+ - **Table format() pattern is non-obvious.** Headers misaligned 3 times in UAT. Always verify with real data.
353
+
354
+ ### From micro-cleanup (2026-03-19)
355
+
356
+ - **Dirty working tree + kfix = accidental staging.** Always run `git status` before committing. micro-cleanup accidentally staged a pre-existing `lib/appydave/tools.rb` change.
357
+ - **Pre-existing "already fixed" items:** Check B-items aren't already done before acting (B031, B033 were already committed; B015, B019 also).
358
+
359
+ ### From Architectural Review (2026-03-19)
360
+
361
+ - **bin/dam is the primary DAM CLI entry point.** Regressions here affect real workflows. Test every command path after extraction.
362
+ - **`ENV['BRAND_PATH']` is set in 5 places in bin/dam.** Three are in parse methods (being extracted). Two remain in `generate_single_manifest` and `sync_ssd_command` — out of scope.
363
+ - **Do NOT attempt B020 (split S3Operations) in this campaign.** Different class, different risk profile.
@@ -0,0 +1,61 @@
1
+ # IMPLEMENTATION_PLAN.md — extract-vat-cli
2
+
3
+ **Goal**: Extract 4 clusters of business logic from `bin/dam` (1,600-line VatCLI God class) into proper library classes. Prerequisite for parallelism (B007) and S3Operations split (B020).
4
+ **Started**: 2026-03-19
5
+ **Target**: All 4 complete; 831+ examples passing; rubocop 0 offenses; no regressions
6
+
7
+ ## Summary
8
+ - Total: 4 | Complete: 0 | In Progress: 0 | Pending: 4 | Failed: 0
9
+
10
+ ## Pending
11
+ - [x] extract-format-bytes — Replaced 4 callers (plan said 3; orphaned-projects loop in display_s3_scan_table was a 4th). format_bytes deleted. rubocop 0 offenses. Commit: 3cd362f.
12
+ - [~] extract-local-sync-status — Extract `add_local_sync_status!` + `format_local_status` → new `LocalSyncStatus` module; add spec; update callers in bin/dam
13
+ - [ ] extract-s3-scan-command — Extract `scan_single_brand_s3` + `scan_all_brands_s3` + `display_s3_scan_table` → new `S3ScanCommand` class; add spec; **depends on extract-local-sync-status completing first**
14
+ - [ ] extract-s3-arg-parser — Extract `parse_s3_args` + `valid_brand?` + `parse_share_args` + `show_share_usage_and_exit` + `parse_discover_args` → new `S3ArgParser` class; add spec
15
+
16
+ ## In Progress
17
+
18
+ ## Complete
19
+
20
+ ## Failed / Needs Retry
21
+
22
+ ## Notes & Decisions
23
+
24
+ ### Sequencing
25
+ All 4 work units touch `bin/dam` — they MUST run sequentially, not in parallel. Wave size = 1.
26
+ Sequence: extract-format-bytes → extract-local-sync-status → extract-s3-scan-command → extract-s3-arg-parser
27
+ `extract-s3-scan-command` depends on `LocalSyncStatus` created in `extract-local-sync-status`.
28
+
29
+ ### New files to create
30
+ - `lib/appydave/tools/dam/local_sync_status.rb`
31
+ - `lib/appydave/tools/dam/s3_scan_command.rb`
32
+ - `lib/appydave/tools/dam/s3_arg_parser.rb`
33
+
34
+ ### Register new files
35
+ Add require lines to `lib/appydave/tools.rb` after line 79 (after `repo_push`):
36
+ ```ruby
37
+ require 'appydave/tools/dam/local_sync_status'
38
+ require 'appydave/tools/dam/s3_scan_command'
39
+ require 'appydave/tools/dam/s3_arg_parser'
40
+ ```
41
+
42
+ ### format_bytes caller locations in bin/dam
43
+ - Line 1510: `display_s3_scan_table` → `format_bytes(data[:total_bytes])`
44
+ - Line 696: `display_s3_files` → `format_bytes(size)`
45
+ - Line 702: `display_s3_files` → `format_bytes(total_bytes)`
46
+ Replace all with: `Appydave::Tools::Dam::FileHelper.format_size(x)`
47
+
48
+ ### FileHelper.format_size vs format_bytes
49
+ Both return identical output. `format_size` uses named format tokens (`%<size>.1f`) which satisfies rubocop Style/FormatStringToken. `format_bytes` used positional tokens (`%.1f`) — hence the rubocop-disable. After extraction, the disable comment goes away.
50
+
51
+ ### parse_share_args scope decision
52
+ Included `parse_share_args` + `show_share_usage_and_exit` + `parse_discover_args` in S3ArgParser (same ENV['BRAND_PATH'] pattern, same brand/project resolution chain). All 5 parser methods share identical structure and belong together.
53
+
54
+ ### ENV['BRAND_PATH'] residuals
55
+ After S3ArgParser extraction, 2 ENV['BRAND_PATH'] assignments remain in VatCLI:
56
+ - `generate_single_manifest` (line 278)
57
+ - `sync_ssd_command` (line 325)
58
+ These are out of scope — leave for a future campaign.
59
+
60
+ ### Do NOT attempt B020 (S3Operations split) in this campaign
61
+ Scope is VatCLI extraction only. S3Operations refactor is a separate campaign.
@@ -5,14 +5,16 @@
5
5
  **Target**: All 3 complete; 830+ examples passing; rubocop clean; no regressions
6
6
 
7
7
  ## Summary
8
- - Total: 3 | Complete: 0 | In Progress: 3 | Pending: 0 | Failed: 0
8
+ - Total: 3 | Complete: 3 | In Progress: 0 | Pending: 0 | Failed: 0
9
9
 
10
10
  ## Pending
11
11
 
12
12
  ## In Progress
13
- - [~] fix-b031 — add_spec: add `type` field assertion to location data integrity test
14
- - [~] fix-b032 — cli_spec: add subprocess test for `-f json` flag
15
- - [~] fix-b033file_collector.rb: return `''` directly when working_directory doesn't exist (line 19)
13
+
14
+ ## Complete
15
+ - [x] fix-b031already committed in prior pass (commit 8eec40c). Assertion `expect(location[:type]).to eq('tool')` found at add_spec.rb line 51. Closed without action.
16
+ - [x] fix-b033 — already fixed in commit 13d5f87 (`return '' unless` already in place). Closed without action.
17
+ - [x] fix-b032 — added `-f json` subprocess test to cli_spec. 831 examples, 0 failures, v0.76.4. Side issue: kfix accidentally staged pre-existing uncommitted changes to lib/appydave/tools.rb (removed youtube_automation_config require). Fixed in follow-up commit.
16
18
 
17
19
  ## Complete
18
20
 
@@ -0,0 +1,68 @@
1
+ # Assessment: micro-cleanup
2
+
3
+ **Campaign**: micro-cleanup
4
+ **Date**: 2026-03-19 → 2026-03-19
5
+ **Results**: 3 complete, 0 failed
6
+ **Version shipped**: v0.76.4
7
+ **Quality audit**: code-quality-audit + test-quality-audit run post-campaign
8
+
9
+ ---
10
+
11
+ ## Results Summary
12
+
13
+ | Work Unit | Action | Notes |
14
+ |-----------|--------|-------|
15
+ | fix-b031 | Already done | `expect(location[:type]).to eq('tool')` found committed in 8eec40c — closed without action |
16
+ | fix-b033 | Already done | `return '' unless` on line 19 found committed in 13d5f87 — closed without action |
17
+ | fix-b032 | +1 example | `-f json` subprocess test added to cli_spec. 831 examples, 0 failures, v0.76.4 |
18
+
19
+ **830 → 831 examples (+1). Coverage stable at ~85.92%.**
20
+
21
+ ---
22
+
23
+ ## What Worked Well
24
+
25
+ - **Agents check before acting.** Both B031 and B033 agents read the files, found the work already done, and correctly closed without creating duplicate commits. No false-positive churn.
26
+ - **B032 json test is clean.** `Dir.mktmpdir` + subprocess + `JSON.parse` + key assertions — follows the established cli_spec pattern exactly.
27
+ - **CI caught the staging error immediately.** The accidental require removal was detected by CI on the first push — the safety net worked as intended.
28
+
29
+ ---
30
+
31
+ ## What Didn't Work
32
+
33
+ **kfix staged pre-existing uncommitted changes.**
34
+
35
+ The B032 agent's `kfix` commit accidentally included local changes to `lib/appydave/tools.rb` that deleted `require 'appydave/tools/configuration/models/youtube_automation_config'` and related lines. These were pre-existing uncommitted modifications floating in the working tree — unrelated to fix-b032. CI failed. A follow-up commit restored the requires and CI passed.
36
+
37
+ **Root cause:** A prior session left the working tree in a dirty state. The agent did not run `git status` before committing, so it didn't catch the unintended changes in the staging area.
38
+
39
+ ---
40
+
41
+ ## Key Learnings — Application
42
+
43
+ - **Run `git status` before `kfix`.** Agents must check what's actually staged before committing. If unexpected files appear, abort and investigate. Add this to AGENTS.md as a mandatory pre-commit step.
44
+ - **kfix commits everything staged** — it does not limit itself to files the agent touched. A dirty working tree is a silent risk on every campaign.
45
+ - **B033 and B031 were already closed by prior agents.** This confirms the prior campaign agents were doing thorough work — they fixed things beyond their assigned scope. Good behaviour, but worth tracking so plans don't redundantly assign already-done work.
46
+
47
+ ---
48
+
49
+ ## New Backlog Items
50
+
51
+ None. The quality audit found no new gaps. Suite is at B+ for GptContext CLI, B overall.
52
+
53
+ ---
54
+
55
+ ## Suggestions for Next Campaign
56
+
57
+ **Start B011 — extract VatCLI from bin/dam.**
58
+
59
+ The test suite is at B grade (75-80% regression catch rate). The production code is clean. The CI pipeline is reliable. All prerequisites for architectural work are met.
60
+
61
+ **Pre-campaign mandatory steps for B011:**
62
+ 1. Run `git status` — confirm working tree is clean before launching agents
63
+ 2. Read `bin/dam` in full before writing AGENTS.md — 1,600 lines, surprises expected
64
+ 3. Run rubocop on bin/dam to count current offenses (20+ rubocop-disable comments)
65
+ 4. Baseline: 831 examples, 0 failures — confirm before starting
66
+
67
+ **Add to AGENTS.md for B011:**
68
+ > Before running `kfix`, always run `git status` and confirm only the expected files appear in the staged/unstaged list. If unexpected files appear, run `git diff` to investigate before committing.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module Dam
6
+ # Enriches S3 scan project data with local s3-staging sync status
7
+ module LocalSyncStatus
8
+ module_function
9
+
10
+ # Mutates matched_projects hash to add :local_status and :local_file_count keys
11
+ # @param matched_projects [Hash] Map of project_id => S3 data hash
12
+ # @param brand_key [String] Brand key (e.g., 'appydave')
13
+ def enrich!(matched_projects, brand_key)
14
+ matched_projects.each do |project_id, data|
15
+ project_path = Appydave::Tools::Dam::Config.project_path(brand_key, project_id)
16
+ s3_staging_path = File.join(project_path, 's3-staging')
17
+
18
+ if !Dir.exist?(project_path)
19
+ data[:local_status] = :no_project # Project directory doesn't exist
20
+ elsif !Dir.exist?(s3_staging_path)
21
+ data[:local_status] = :no_files # Project exists but no downloads yet
22
+ else
23
+ # Count local files in s3-staging
24
+ local_files = Dir.glob(File.join(s3_staging_path, '**', '*'))
25
+ .select { |f| File.file?(f) }
26
+ .reject { |f| File.basename(f).include?('Zone.Identifier') } # Exclude Windows metadata
27
+
28
+ s3_file_count = data[:file_count]
29
+ local_file_count = local_files.size
30
+
31
+ data[:local_status] = if local_file_count.zero?
32
+ :no_files
33
+ elsif local_file_count == s3_file_count
34
+ :synced # Fully synced
35
+ else
36
+ :partial # Some files downloaded
37
+ end
38
+
39
+ data[:local_file_count] = local_file_count
40
+ end
41
+ end
42
+ end
43
+
44
+ # Format local sync status symbol for display
45
+ # @param status [Symbol] :synced, :no_files, :partial, :no_project
46
+ # @param local_count [Integer, nil] Number of local files
47
+ # @param s3_count [Integer] Number of S3 files
48
+ # @return [String] Formatted status string
49
+ def format(status, local_count, s3_count)
50
+ case status
51
+ when :synced
52
+ '✓ Synced'
53
+ when :no_files
54
+ '⚠ None'
55
+ when :partial
56
+ "⚠ #{local_count}/#{s3_count}"
57
+ when :no_project
58
+ '✗ Missing'
59
+ else
60
+ 'Unknown'
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.76.4'
5
+ VERSION = '0.76.5'
6
6
  end
7
7
  end
@@ -78,6 +78,7 @@ require 'appydave/tools/dam/ssd_status'
78
78
  require 'appydave/tools/dam/repo_status'
79
79
  require 'appydave/tools/dam/repo_sync'
80
80
  require 'appydave/tools/dam/repo_push'
81
+ require 'appydave/tools/dam/local_sync_status'
81
82
 
82
83
  require 'appydave/tools/jump/path_validator'
83
84
  require 'appydave/tools/jump/location'
@@ -96,8 +97,6 @@ require 'appydave/tools/jump/commands/report'
96
97
  require 'appydave/tools/jump/commands/generate'
97
98
  require 'appydave/tools/jump/cli'
98
99
 
99
- require 'appydave/tools/youtube_automation/gpt_agent'
100
-
101
100
  require 'appydave/tools/youtube_manager/models/youtube_details'
102
101
  require 'appydave/tools/youtube_manager/models/captions'
103
102
  require 'appydave/tools/youtube_manager/youtube_base'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.76.4",
3
+ "version": "0.76.5",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.76.4
4
+ version: 0.76.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
@@ -300,6 +300,8 @@ files:
300
300
  - docs/planning/bugfix-and-security/AGENTS.md
301
301
  - docs/planning/bugfix-and-security/IMPLEMENTATION_PLAN.md
302
302
  - docs/planning/bugfix-and-security/assessment.md
303
+ - docs/planning/extract-vat-cli/AGENTS.md
304
+ - docs/planning/extract-vat-cli/IMPLEMENTATION_PLAN.md
303
305
  - docs/planning/final-test-gaps/AGENTS.md
304
306
  - docs/planning/final-test-gaps/IMPLEMENTATION_PLAN.md
305
307
  - docs/planning/final-test-gaps/assessment.md
@@ -308,6 +310,7 @@ files:
308
310
  - docs/planning/fr2-gpt-context-help/assessment.md
309
311
  - docs/planning/micro-cleanup/AGENTS.md
310
312
  - docs/planning/micro-cleanup/IMPLEMENTATION_PLAN.md
313
+ - docs/planning/micro-cleanup/assessment.md
311
314
  - docs/planning/test-coverage-gaps/AGENTS.md
312
315
  - docs/planning/test-coverage-gaps/IMPLEMENTATION_PLAN.md
313
316
  - docs/planning/test-coverage-gaps/assessment.md
@@ -351,6 +354,7 @@ files:
351
354
  - lib/appydave/tools/dam/file_helper.rb
352
355
  - lib/appydave/tools/dam/fuzzy_matcher.rb
353
356
  - lib/appydave/tools/dam/git_helper.rb
357
+ - lib/appydave/tools/dam/local_sync_status.rb
354
358
  - lib/appydave/tools/dam/manifest_generator.rb
355
359
  - lib/appydave/tools/dam/project_listing.rb
356
360
  - lib/appydave/tools/dam/project_resolver.rb