appydave-tools 0.76.6 → 0.76.8

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: f4172ac86ad4f32c89534b2129ffed582137abc47e21fe1fb2e1d18c5a56608e
4
- data.tar.gz: ed067098e65634fce56abf446313a7f58657dbc7fc45499915cb34d6a9ae5afe
3
+ metadata.gz: 837cfa631ce3f72655208add542964a605efa385aea01abfe49c55fb9110cdbb
4
+ data.tar.gz: 4adfd72dad7dc7a41f909857a21616d3385b259ee11969d94641c75731713924
5
5
  SHA512:
6
- metadata.gz: 79f3caf203405a2cf7337da3129595433515c04e6b4f9f1203b70b5ef89e07caa07ac212d6e9bb85244185f5d5d3b8e20f175e01e7587fb1fe1920630dadf8e1
7
- data.tar.gz: e42dfc787b1331d7fdfbd32c731f13e03f946bf5e2fa9d89f948f9cb59dc393a80e054b075b650d51d72eb7070f846f0253beef5e011cf3c632b8181b5b30f7f
6
+ metadata.gz: 2748e05e28b1c0cd4a7e6ec99f13a513c68ec49115d6b90bd0090d25467958eb0dd759cbf6fb8b94f210fc1835ee5d2c77cac79e3d7043cac2127513d982d89f
7
+ data.tar.gz: cf039ebec627c4899f5abc6c309829894c4dbca2ec71daf6ceb1143f61b72a46edb480223c9a8ab6acd5892ef343980f5395d6f6363276352056f111c3753026
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [0.76.7](https://github.com/appydave/appydave-tools/compare/v0.76.6...v0.76.7) (2026-03-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * extract S3ArgParser module from VatCLI; add spec ([9bbd709](https://github.com/appydave/appydave-tools/commit/9bbd7099c26b774b68103f4d37d0a25f22f4abc2))
7
+
8
+ ## [0.76.6](https://github.com/appydave/appydave-tools/compare/v0.76.5...v0.76.6) (2026-03-19)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * extract S3ScanCommand class from VatCLI; add smoke-test spec ([cd03606](https://github.com/appydave/appydave-tools/commit/cd03606e3307665c7f69391d6c160f50df1773ba))
14
+ * remove redundant rubocop disable directives from S3ScanCommand (CI rubocop 1.85.1) ([4c9deb4](https://github.com/appydave/appydave-tools/commit/4c9deb4298d4577a99d0e82e7b72918a26275d40))
15
+
1
16
  ## [0.76.5](https://github.com/appydave/appydave-tools/compare/v0.76.4...v0.76.5) (2026-03-19)
2
17
 
3
18
 
data/bin/dam CHANGED
@@ -154,7 +154,7 @@ class VatCLI
154
154
 
155
155
  # S3 Upload
156
156
  def s3_up_command(args)
157
- options = parse_s3_args(args, 's3-up')
157
+ options = Appydave::Tools::Dam::S3ArgParser.parse_s3(args, 's3-up')
158
158
  s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
159
159
  s3_ops.upload(dry_run: options[:dry_run])
160
160
  rescue StandardError => e
@@ -164,7 +164,7 @@ class VatCLI
164
164
 
165
165
  # S3 Download
166
166
  def s3_down_command(args)
167
- options = parse_s3_args(args, 's3-down')
167
+ options = Appydave::Tools::Dam::S3ArgParser.parse_s3(args, 's3-down')
168
168
  s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
169
169
  s3_ops.download(dry_run: options[:dry_run])
170
170
  rescue StandardError => e
@@ -174,7 +174,7 @@ class VatCLI
174
174
 
175
175
  # S3 Status
176
176
  def s3_status_command(args)
177
- options = parse_s3_args(args, 's3-status')
177
+ options = Appydave::Tools::Dam::S3ArgParser.parse_s3(args, 's3-status')
178
178
  s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
179
179
  s3_ops.status
180
180
  rescue StandardError => e
@@ -184,7 +184,7 @@ class VatCLI
184
184
 
185
185
  # S3 Cleanup Remote
186
186
  def s3_cleanup_remote_command(args)
187
- options = parse_s3_args(args, 's3-cleanup-remote')
187
+ options = Appydave::Tools::Dam::S3ArgParser.parse_s3(args, 's3-cleanup-remote')
188
188
  s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
189
189
  s3_ops.cleanup(force: options[:force], dry_run: options[:dry_run])
190
190
  rescue StandardError => e
@@ -194,7 +194,7 @@ class VatCLI
194
194
 
195
195
  # S3 Cleanup Local
196
196
  def s3_cleanup_local_command(args)
197
- options = parse_s3_args(args, 's3-cleanup-local')
197
+ options = Appydave::Tools::Dam::S3ArgParser.parse_s3(args, 's3-cleanup-local')
198
198
  s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
199
199
  s3_ops.cleanup_local(force: options[:force], dry_run: options[:dry_run])
200
200
  rescue StandardError => e
@@ -204,7 +204,7 @@ class VatCLI
204
204
 
205
205
  # Share file via pre-signed URL
206
206
  def s3_share_command(args)
207
- options = parse_share_args(args)
207
+ options = Appydave::Tools::Dam::S3ArgParser.parse_share(args)
208
208
 
209
209
  share_ops = Appydave::Tools::Dam::ShareOperations.new(options[:brand], options[:project])
210
210
  share_ops.generate_links(files: options[:file], expires: options[:expires], download: options[:download])
@@ -215,7 +215,7 @@ class VatCLI
215
215
 
216
216
  # Discover files in S3 for a project
217
217
  def s3_discover_command(args)
218
- options = parse_discover_args(args)
218
+ options = Appydave::Tools::Dam::S3ArgParser.parse_discover(args)
219
219
  files = fetch_s3_files(options[:brand_key], options[:project_id])
220
220
 
221
221
  return if handle_empty_files?(files, options[:brand_key], options[:project_id])
@@ -228,7 +228,7 @@ class VatCLI
228
228
 
229
229
  # Archive project to SSD
230
230
  def archive_command(args)
231
- options = parse_s3_args(args, 'archive')
231
+ options = Appydave::Tools::Dam::S3ArgParser.parse_s3(args, 'archive')
232
232
  s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
233
233
  s3_ops.archive(force: options[:force], dry_run: options[:dry_run])
234
234
  rescue StandardError => e
@@ -461,132 +461,6 @@ class VatCLI
461
461
  exit 1
462
462
  end
463
463
 
464
- # Parse S3 command arguments
465
- # rubocop:disable Metrics/MethodLength
466
- def parse_s3_args(args, command)
467
- dry_run = args.include?('--dry-run')
468
- force = args.include?('--force')
469
- args = args.reject { |arg| arg.start_with?('--') }
470
-
471
- brand_arg = args[0]
472
- project_arg = args[1]
473
-
474
- if brand_arg.nil?
475
- # Auto-detect from PWD
476
- brand, project_id = Appydave::Tools::Dam::ProjectResolver.detect_from_pwd
477
- if brand.nil? || project_id.nil?
478
- puts '❌ Could not auto-detect brand/project from current directory'
479
- puts "Usage: dam #{command} <brand> <project> [--dry-run]"
480
- exit 1
481
- end
482
- brand_key = brand # Already detected, use as-is
483
- else
484
- # Validate brand exists before trying to resolve project
485
- unless valid_brand?(brand_arg)
486
- puts "❌ Invalid brand: '#{brand_arg}'"
487
- puts ''
488
- puts 'Valid brands:'
489
- puts ' appydave → v-appydave (AppyDave brand)'
490
- puts ' voz → v-voz (VOZ client)'
491
- puts ' aitldr → v-aitldr (AITLDR brand)'
492
- puts ' kiros → v-kiros (Kiros client)'
493
- puts ' joy → v-beauty-and-joy (Beauty & Joy)'
494
- puts ' ss → v-supportsignal (SupportSignal)'
495
- puts ''
496
- puts "Usage: dam #{command} <brand> <project> [--dry-run]"
497
- exit 1
498
- end
499
-
500
- brand_key = brand_arg # Use the shortcut/key (e.g., 'appydave')
501
- brand = Appydave::Tools::Dam::Config.expand_brand(brand_arg) # Expand for path resolution
502
- project_id = Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg)
503
- end
504
-
505
- # Set ENV for compatibility with ConfigLoader
506
- ENV['BRAND_PATH'] = Appydave::Tools::Dam::Config.brand_path(brand)
507
-
508
- { brand: brand_key, project: project_id, dry_run: dry_run, force: force }
509
- end
510
- # rubocop:enable Metrics/MethodLength
511
-
512
- def valid_brand?(brand_key)
513
- Appydave::Tools::Configuration::Config.configure
514
- brands = Appydave::Tools::Configuration::Config.brands
515
- brands.key?(brand_key) || brands.shortcut?(brand_key)
516
- end
517
-
518
- def parse_share_args(args)
519
- # Extract --expires flag
520
- expires = '7d' # default
521
- if (expires_index = args.index('--expires'))
522
- expires = args[expires_index + 1]
523
- args.delete_at(expires_index + 1)
524
- args.delete_at(expires_index)
525
- end
526
-
527
- # Extract --download flag
528
- download = args.include?('--download')
529
-
530
- # Remove other flags
531
- args = args.reject { |arg| arg.start_with?('--') }
532
-
533
- brand_arg = args[0]
534
- project_arg = args[1]
535
- file_arg = args[2]
536
-
537
- show_share_usage_and_exit if brand_arg.nil? || project_arg.nil? || file_arg.nil?
538
-
539
- brand_key = brand_arg
540
- brand = Appydave::Tools::Dam::Config.expand_brand(brand_arg)
541
- project_id = Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg)
542
-
543
- # Set ENV for compatibility with ConfigLoader
544
- ENV['BRAND_PATH'] = Appydave::Tools::Dam::Config.brand_path(brand)
545
-
546
- { brand: brand_key, project: project_id, file: file_arg, expires: expires, download: download }
547
- end
548
-
549
- def show_share_usage_and_exit
550
- puts 'Usage: dam s3-share <brand> <project> <file> [--expires 7d] [--download]'
551
- puts ''
552
- puts 'Options:'
553
- puts ' --expires TIME Expiry time (default: 7d)'
554
- puts ' --download Force download instead of viewing in browser'
555
- puts ''
556
- puts 'Examples:'
557
- puts ' dam s3-share appydave b70 video.mp4'
558
- puts ' dam s3-share appydave b70 video.mp4 --expires 24h'
559
- puts ' dam s3-share appydave b70 video.mp4 --download'
560
- puts ' dam s3-share voz boy-baker final-edit.mov --expires 3d --download'
561
- exit 1
562
- end
563
-
564
- def parse_discover_args(args)
565
- shareable = args.include?('--shareable')
566
- args = args.reject { |arg| arg.start_with?('--') }
567
-
568
- brand_arg = args[0]
569
- project_arg = args[1]
570
-
571
- if brand_arg.nil? || project_arg.nil?
572
- puts 'Usage: dam s3-discover <brand> <project> [--shareable]'
573
- puts ''
574
- puts 'Examples:'
575
- puts ' dam s3-discover appydave b70 # List files'
576
- puts ' dam s3-discover appydave b70 --shareable # Generate share commands'
577
- exit 1
578
- end
579
-
580
- brand_key = brand_arg
581
- brand = Appydave::Tools::Dam::Config.expand_brand(brand_arg)
582
- project_id = Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg)
583
-
584
- # Set ENV for compatibility with ConfigLoader
585
- ENV['BRAND_PATH'] = Appydave::Tools::Dam::Config.brand_path(brand)
586
-
587
- { brand_key: brand_key, project_id: project_id, shareable: shareable }
588
- end
589
-
590
464
  def fetch_s3_files(brand_key, project_id)
591
465
  s3_ops = Appydave::Tools::Dam::S3Operations.new(brand_key, project_id)
592
466
  s3_ops.list_s3_files
@@ -45,7 +45,7 @@ kfeat "add feature description" # Minor version bump
45
45
  kfix "fix bug description" # Patch version bump
46
46
  ```
47
47
 
48
- **Baseline (2026-03-19):** 831 examples, 0 failures, ~85.92% line coverage
48
+ **Baseline (2026-03-19):** 847 examples, 0 failures, ~86.21% line coverage
49
49
 
50
50
  ---
51
51
 
@@ -221,9 +221,9 @@ Appydave::Tools::Configuration::Config.configure
221
221
 
222
222
  ## Quality Gates
223
223
 
224
- - **Tests:** `bundle exec rspec` — 831 examples, 0 failures (do not ship if any fail)
224
+ - **Tests:** `bundle exec rspec` — 847 examples, 0 failures (do not ship if any fail)
225
225
  - **Lint:** `bundle exec rubocop --format clang` — 0 offenses (CI will reject)
226
- - **Coverage:** ≥ 85.92% line coverage
226
+ - **Coverage:** ≥ 86.21% line coverage
227
227
  - **frozen_string_literal:** Required on every new `.rb` file
228
228
  - **Commit format:** `kfeat`/`kfix` only — triggers semantic versioning + CI wait
229
229
 
@@ -248,6 +248,12 @@ Appydave::Tools::Configuration::Config.configure
248
248
  - **Jump Commands layer is undertested:** `Commands::Remove`, `Commands::Add`, `Commands::Update` have zero dedicated specs. Auto-regenerate CLI spec does not substitute for command-layer unit tests verifying `--force` guards, error codes, and suggestion logic (see B018).
249
249
  - **Jump report commands** got `--limit` and `--skip-unassigned` flags after initial implementation. Jump tool scope grows incrementally.
250
250
 
251
+ ### From extract-vat-cli (2026-03-19)
252
+
253
+ - **Do NOT carry over rubocop-disable comments when extracting methods.** Run rubocop on the new file first — methods that needed disables in a 1,600-line God class often don't exceed thresholds in a properly-scoped library class. Carrying them over causes CI to flag `Lint/RedundantCopDisableDirective` and requires a second fix commit.
254
+ - **grep for callers before writing the plan.** `format_bytes` had 4 callers in bin/dam, not 3 as counted from memory. The orphaned-projects loop was missed.
255
+ - **`valid_brand?` needs `Config.brands` mock, not just `SettingsConfig` mock.** The shared `'with vat filesystem and brands'` context only mocks `SettingsConfig#video_projects_root`. Any method calling `Config.brands` directly needs an explicit brands config mock.
256
+
251
257
  ### From Three-Lens Audit (2026-03-19)
252
258
 
253
259
  - **`file_collector.rb` has two landmines before FR-2:** `puts @working_directory` at line 15 pollutes stdout; `FileUtils.cd` without `ensure` leaves process in wrong directory on exception. Fix both before adding any code to this class.
@@ -1,7 +1,7 @@
1
1
  # Project Backlog — AppyDave Tools
2
2
 
3
- **Last updated**: 2026-03-19 (micro-cleanup campaign complete)
4
- **Total**: 33 | Pending: 7 | In Progress: 1 | Done: 25 | Deferred: 0 | Rejected: 0
3
+ **Last updated**: 2026-03-19 (extract-vat-cli campaign complete)
4
+ **Total**: 37 | Pending: 11 | Done: 26 | Deferred: 0 | Rejected: 0
5
5
 
6
6
  ---
7
7
 
@@ -14,8 +14,12 @@
14
14
  - [ ] B008 — Performance: cache git/S3 status with 5-min TTL | Priority: low
15
15
  - [ ] B009 — UX: progress indicators for dam operations > 5s | Priority: low
16
16
  - [ ] B010 — UX: auto-adjust dam table column widths to terminal width | Priority: low
17
- - [~] B011 — Arch: extract VatCLI business logic from bin/dam (1,600-line God class) | Campaign: extract-vat-cli
17
+ - [x] B011 — Arch: extract VatCLI business logic from bin/dam (1,600-line God class) | Completed: extract-vat-cli (2026-03-19)
18
18
  - [ ] B020 — Arch: split S3Operations (1,030 lines, mixed I/O + logic) | Priority: low
19
+ - [ ] B034 — Fix: replace exit 1 with typed exceptions in S3ScanCommand + S3ArgParser | Priority: high (blocks B007 + test coverage)
20
+ - [ ] B035 — Fix: remove ENV['BRAND_PATH'] side effect from S3ArgParser | Priority: high (blocks B007 parallelism)
21
+ - [ ] B036 — Tests: improve S3ScanCommand spec from D to B (depends on B034) | Priority: medium
22
+ - [ ] B037 — Tests: LocalSyncStatus :partial case, local_file_count assertion, Zone.Identifier exclusion | Priority: medium
19
23
 
20
24
  ---
21
25
 
@@ -5,13 +5,13 @@
5
5
  **Target**: All 4 complete; 831+ examples passing; rubocop 0 offenses; no regressions
6
6
 
7
7
  ## Summary
8
- - Total: 4 | Complete: 0 | In Progress: 0 | Pending: 4 | Failed: 0
8
+ - Total: 4 | Complete: 4 | In Progress: 0 | Pending: 0 | Failed: 0
9
9
 
10
10
  ## Pending
11
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
12
  - [x] extract-local-sync-status — LocalSyncStatus module created, 7 specs added (838 total), both methods gone from VatCLI. Side-fix: restored youtube_automation_config require incorrectly removed in prior commit. v0.76.5.
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
13
+ - [x] extract-s3-scan-command — S3ScanCommand created, 2 smoke tests added (840 total), 3 methods gone from VatCLI. Note: rubocop-disable directives became redundant once methods left God class — needed 2nd kfix to remove them. v0.76.6.
14
+ - [x] extract-s3-arg-parser — S3ArgParser module created, 7 specs added (847 total), 5 methods + 8 callers updated, all gone from VatCLI. Note: valid_brand? needed Config.brands mock (shared context only mocks SettingsConfig). v0.76.7.
15
15
 
16
16
  ## In Progress
17
17
 
@@ -0,0 +1,107 @@
1
+ # Assessment: extract-vat-cli
2
+
3
+ **Campaign**: extract-vat-cli
4
+ **Date**: 2026-03-19 → 2026-03-19
5
+ **Results**: 4/4 complete, 0 failed
6
+
7
+ ---
8
+
9
+ ## Results Summary
10
+
11
+ | Work Unit | Outcome | Version | Notes |
12
+ |---|---|---|---|
13
+ | extract-format-bytes | ✅ Complete | v0.76.4→ | 4 callers replaced (plan said 3; orphaned-projects loop was 4th) |
14
+ | extract-local-sync-status | ✅ Complete | v0.76.5 | Side-fix: restored youtube_automation_config require removed in prior commit |
15
+ | extract-s3-scan-command | ✅ Complete | v0.76.6 | Needed 2nd kfix: inherited rubocop-disable directives became redundant |
16
+ | extract-s3-arg-parser | ✅ Complete | v0.76.7 | valid_brand? needed BrandsConfig mock; parse_share fully tested |
17
+
18
+ **Suite:** 831 → 847 examples (+16), 0 failures, 86.21% coverage. rubocop 0 offenses. CI green on all 4 commits.
19
+
20
+ ---
21
+
22
+ ## What Worked Well
23
+
24
+ 1. **Sequential wave strategy was correct.** All 4 work units touched bin/dam — wave size = 1 was the only safe option. No conflicts, no merge issues, zero failed agents.
25
+
26
+ 2. **AGENTS.md quality was sufficient.** All agents executed cleanly without confusion about which methods to move or which callers to update. The detailed WU-specific instructions in the campaign AGENTS.md were the right level of specificity.
27
+
28
+ 3. **format_bytes extraction was genuinely trivial.** FileHelper.format_size already existed and was identical. WU1 was pure deletion + caller update — no logic risk.
29
+
30
+ 4. **LocalSyncStatus came out clean.** The module_function pattern worked correctly, the shared filesystem context covered most branches, and the side-fix of youtube_automation_config was a net win.
31
+
32
+ 5. **bin/dam went from 1,600 → 1,223 lines (−23%) and 20+ → 5 rubocop-disables.** The remaining 5 are structural (help text dispatch, not logic). Significant improvement.
33
+
34
+ ---
35
+
36
+ ## What Didn't Work
37
+
38
+ 1. **Inherited rubocop-disable directives caused 2nd kfix commits** (WU3, WU4). Methods that needed `Metrics/MethodLength` disable in the God class context don't exceed thresholds in properly-scoped library classes. CI rubocop 1.85.1 flags them as redundant. Rule: don't carry over rubocop-disable comments when extracting — run rubocop fresh on the new file.
39
+
40
+ 2. **Caller count was wrong in the plan.** Plan said 3 callers for format_bytes; there were 4 (orphaned-projects loop in display_s3_scan_table). This was a minor issue (caught and fixed by WU1 agent) but points to a planning discipline gap: grep for callers, don't count from memory.
41
+
42
+ 3. **exit 1 calls were carried into library code.** `S3ScanCommand` and `S3ArgParser` both call `exit 1` in response to invalid inputs. This was the pre-existing VatCLI pattern — the extraction was faithful — but it now lives in library classes where it's a boundary violation. Makes testing error paths impossible and blocks safe parallelism (B007).
43
+
44
+ 4. **S3ScanCommand spec is F-grade (confirmed by independent test audit).** Two `respond_to` tests confirm the class loads; nothing more. 90 lines of orchestration logic — manifest merging, orphan detection, LocalSyncStatus wiring — are completely unprotected. The `exit 1` call at line 55 makes the "no manifest" path untestable without production code changes first.
45
+
46
+ 5. **`Zone.Identifier` exclusion in LocalSyncStatus has no test.** The filter for Windows metadata files (line 26) is untested — removal or inversion would pass all current specs silently.
47
+
48
+ 6. **`ENV['BRAND_PATH']` side-effect never asserted in S3ArgParser spec.** All three parse methods set it; none of the specs check it. A key-name change would silently break ConfigLoader downstream.
49
+
50
+ ---
51
+
52
+ ## Key Learnings — Application
53
+
54
+ 1. **Don't carry over rubocop-disable comments when extracting.** Run rubocop fresh on the new file; only add disables if actually triggered.
55
+ 2. **grep for callers before writing the plan.** Don't count from memory or code review alone.
56
+ 3. **Library classes must not call `exit`.** Raise typed exceptions (`ConfigurationError`, `UsageError`) — let the CLI layer print and exit. Required before B007.
57
+ 4. **`valid_brand?` and anything calling `Config.brands` needs BrandsConfig mock** — the shared context only mocks `SettingsConfig#video_projects_root`.
58
+ 5. **ENV['BRAND_PATH'] side effect now in library code.** S3ArgParser sets a process-wide env var as a side effect of argument parsing. Must be resolved before parallelism (B007).
59
+
60
+ ---
61
+
62
+ ## Key Learnings — Ralph Loop
63
+
64
+ 1. **Wave size = 1 was correct for God-class extraction.** When all work units share a single target file, parallel waves cause conflicts. Plan for sequential from the start.
65
+ 2. **Detailed WU-specific AGENTS.md sections paid off.** Specifying exact line numbers, method names, and new class signatures eliminated agent confusion. Worth the upfront effort.
66
+ 3. **Side-fixes happen.** WU2 found and fixed a pre-existing require omission. Good — agents should fix what they find. Update the plan notes so the next session knows.
67
+ 4. **2nd kfix commits were needed on 2/4 work units** — predictable pattern for extractions. Build in a "check for redundant rubocop directives" step in future extraction AGENTS.md.
68
+
69
+ ---
70
+
71
+ ## Promote to Main KDD?
72
+
73
+ - "Don't carry over rubocop-disable when extracting" → yes, promote
74
+ - "grep for callers before planning" → yes, promote
75
+ - "Library classes must not call exit" → yes, promote (already in project AGENTS.md, promote to KDD)
76
+
77
+ ---
78
+
79
+ ## Suggestions for Next Campaign
80
+
81
+ ### Debt introduced by this campaign (should address before B007)
82
+
83
+ **B034 — Fix: replace `exit 1` with typed exceptions in S3ScanCommand + S3ArgParser**
84
+ - `S3ScanCommand#scan_single` calls `exit 1` at line 55 (no manifest path)
85
+ - `S3ArgParser` calls `exit 1` at 4 locations (invalid brand, PWD auto-detect fail, discover missing args, share missing args)
86
+ - Fix: raise `Appydave::Tools::Dam::ConfigurationError` / new `UsageError` subclass
87
+ - VatCLI `rescue StandardError` already handles these at command level
88
+ - Unblocks proper testing of error paths AND safe parallelism
89
+
90
+ **B035 — Fix: remove ENV['BRAND_PATH'] side effect from S3ArgParser**
91
+ - `parse_s3`, `parse_share`, `parse_discover` all set `ENV['BRAND_PATH']` as a side effect
92
+ - Acceptable in a single-process CLI; unsafe in parallel execution
93
+ - Fix: return the brand_path in the result hash and let VatCLI set it, or extract to explicit `S3ArgParser.configure_env!(brand)`
94
+
95
+ **B036 — Tests: improve S3ScanCommand spec from D to B**
96
+ - Depends on B034 (exit → exception) — can't test "no manifest" path until that's fixed
97
+ - Add: mocked S3Scanner + filesystem fixture, assert manifest merge logic, assert LocalSyncStatus wiring
98
+
99
+ **B037 — Tests: add LocalSyncStatus :partial case + local_file_count assertion**
100
+ - Add `:partial` context (1 file in staging, s3 has 3) — assert `:partial` status and `local_file_count: 1`
101
+ - Add assertion that `:synced` case sets `local_file_count` correctly
102
+
103
+ ### Mode recommendation for next session
104
+
105
+ **4. Extend** — these are small debt items, same stack, inherit this AGENTS.md.
106
+
107
+ Or, if you want to move forward to B007 (parallelism) instead: address B034 + B035 first (1-wave campaign), then B007 becomes buildable.
@@ -0,0 +1,36 @@
1
+ # Wave Learnings — extract-vat-cli
2
+
3
+ **Campaign:** extract-vat-cli
4
+ **Date:** 2026-03-19
5
+ **Result:** 4/4 complete. 831 → 847 examples (+16). v0.76.3 → v0.76.7.
6
+
7
+ ---
8
+
9
+ ## Application Learnings
10
+
11
+ ### 1. format_bytes had 4 callers, not 3
12
+ The orphaned-projects loop in `display_s3_scan_table` had its own `format_bytes` call. Plan said 3 callers; there were 4. Always grep for callers before writing the plan rather than counting from memory.
13
+
14
+ ### 2. rubocop-disable directives become redundant when methods leave God class
15
+ WU3 and WU4 both needed a second `kfix` to remove `rubocop:disable Metrics/MethodLength` and `Metrics/CyclomaticComplexity` directives that were valid in the 1,600-line VatCLI context but flagged as redundant by CI rubocop 1.85.1 once the methods were in properly-scoped library classes.
16
+
17
+ **Rule for future extractions:** Do NOT carry over rubocop-disable comments. Run rubocop locally on the new file first — only add disables if rubocop actually flags an offense.
18
+
19
+ ### 3. valid_brand? needed Config.brands mock, not just SettingsConfig mock
20
+ The shared context `'with vat filesystem and brands'` only mocks `SettingsConfig#video_projects_root`. Testing `valid_brand?` (which calls `Config.brands`) required an explicit `Config.configure` + `Config.brands` mock with a test `BrandsConfig` instance. Document this in AGENTS.md for future S3ArgParser specs.
21
+
22
+ ### 4. youtube_automation_config require was missing (pre-existing)
23
+ WU2 agent discovered that a prior `chore(cleanup)` commit had incorrectly removed `require 'appydave/tools/youtube_automation_config'` from `lib/appydave/tools.rb`. The CI was failing on a `NameError` for `YoutubeAutomationConfig` unrelated to our work. Agent restored it as a side-fix. This was confirmed pre-existing in WU1's CI output.
24
+
25
+ ---
26
+
27
+ ## Loop Meta-Learnings
28
+
29
+ ### Wave size = 1 was correct
30
+ All 4 work units touched `bin/dam`. Sequential was the only safe option. No conflicts, no merge issues.
31
+
32
+ ### Agents produced clean rubocop output when not inheriting disables
33
+ WU1 (no rubocop disables to inherit) was cleanest. WU2 produced clean output first-pass. WU3 and WU4 needed a second pass due to inherited directives — preventable with the "don't carry over disables" rule above.
34
+
35
+ ### 4 agents, all successful, no failures
36
+ Every work unit completed in a single run (plus occasional second kfix for rubocop cleanup). AGENTS.md quality was sufficient — agents didn't get confused about which methods to move or which callers to update.