appydave-tools 0.76.3 → 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.
@@ -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,15 +5,17 @@
5
5
  **Target**: All 4 work units complete; 817+ examples passing; rubocop clean; no regressions; regression catch rate meaningfully above 55%
6
6
 
7
7
  ## Summary
8
- - Total: 4 | Complete: 0 | In Progress: 4 | Pending: 0 | Failed: 0
8
+ - Total: 4 | Complete: 4 | In Progress: 0 | Pending: 0 | Failed: 0
9
9
 
10
10
  ## Pending
11
11
 
12
12
  ## In Progress
13
- - [~] fix-b023 — file_collector_spec: add json format, aider format, and error path tests
14
- - [~] fix-b028 — cli_spec: add file body content assertions to -i and -e tests
15
- - [~] fix-b029add_spec: validate ALL returned location data fields match input attrs (path, jump, tags, description)
16
- - [~] fix-b030update_spec: verify non-updated fields are unchanged on both updated record and sibling records
13
+
14
+ ## Complete
15
+ - [x] fix-b023file_collector_spec: +10 examples (5 json, 4 aider, 1 error path). Key fix: json exclusion test needed `exclude_patterns: ['excluded/**/*']` not `[]`. Nonexistent dir test used non-matching glob to avoid false positives. 830 examples, 85.92% coverage.
16
+ - [x] fix-b028cli_spec: +6 body assertions across -i (3) and -e (3) blocks. No new example count (assertions added to existing `it` blocks). 830 examples, v0.76.3 released.
17
+ - [x] fix-b029 — add_spec: +1 example asserting path/jump/tags/description. Confirmed `location.to_h` uses symbol keys and `.compact`. 830 examples.
18
+ - [x] fix-b030 — update_spec: +2 examples (non-updated fields on updated record; sibling record field isolation). 11→13 examples. 830 examples.
17
19
 
18
20
  ## Complete
19
21
 
@@ -0,0 +1,74 @@
1
+ # Assessment: final-test-gaps
2
+
3
+ **Campaign**: final-test-gaps
4
+ **Date**: 2026-03-19 → 2026-03-19
5
+ **Results**: 4 complete, 0 failed
6
+ **Version shipped**: v0.76.3
7
+ **Quality audit**: code-quality-audit + test-quality-audit run post-campaign
8
+
9
+ ---
10
+
11
+ ## Results Summary
12
+
13
+ | Work Unit | Examples Added | Notes |
14
+ |-----------|---------------|-------|
15
+ | fix-b023 | +10 | file_collector_spec: 5 json + 4 aider + 1 error path. Agent fixed json exclusion test (needed `exclude_patterns: ['excluded/**/*']` not `[]`) |
16
+ | fix-b028 | +0 new examples, +6 assertions | Body content assertions added to existing -i/-e `it` blocks in cli_spec |
17
+ | fix-b029 | +1 | add_spec: location data integrity (path/jump/tags/description) |
18
+ | fix-b030 | +2 | update_spec: non-updated fields on updated record + sibling record isolation |
19
+
20
+ **817 → 830 examples (+13). 85.61% → 85.92% coverage.**
21
+
22
+ ---
23
+
24
+ ## What Worked Well
25
+
26
+ - **B023 agent self-corrected the json exclusion test.** Plan specified `exclude_patterns: []` for the json exclusion test — agent recognised this would not actually test exclusion and changed to `exclude_patterns: ['excluded/**/*']`. Agents reading source before writing improved test quality.
27
+ - **B023 agent handled the nonexistent-dir edge case correctly.** Used a pattern that matches nothing in CWD (`['**/*.nonexistent_xyz_12345']`) to avoid false positives when `build_formats` falls through to current directory.
28
+ - **Parallel wave, zero merge conflicts.** 4 agents, all different files.
29
+ - **Regression catch rate measurably improved.** C (55%) → B (70-75%). All major gaps from the prior audit are closed.
30
+
31
+ ---
32
+
33
+ ## What Didn't Work
34
+
35
+ **Code quality MINOR — `file_collector.rb:19` silent collection from CWD:**
36
+ When `working_directory` doesn't exist, `build_formats` runs without `FileUtils.cd`, meaning `Dir.glob` runs in the current process directory. The spec confirms "returns empty string" only because the test uses a non-matching glob — not because the code guarantees it.
37
+
38
+ **Test gap — `type` field missing from add_spec data integrity test:**
39
+ `valid_attrs` includes `type: 'tool'` but the new B029 integrity test doesn't assert `location[:type]`. Field mapping bug for `type` would still pass silently.
40
+
41
+ **Test gap — no CLI-level test for `-f json` or `-f aider` format flags:**
42
+ These formats are unit-tested in file_collector_spec but not via subprocess in cli_spec. A regression in CLI flag parsing for these formats would pass.
43
+
44
+ ---
45
+
46
+ ## Key Learnings — Application
47
+
48
+ - **`build_formats` fallthrough is a silent failure mode.** When `Dir.exist?` is false, the code returns `build_formats` result — not `''`. The guard at line 19 should probably return `''` directly rather than calling `build_formats` without cd.
49
+ - **`exclude_patterns: []` tests exclusion vacuously.** An empty exclude list means no exclusion test is happening. Always use a real pattern when testing that something is excluded.
50
+ - **`build_aider` embeds unsanitised prompt and file paths.** Prompts with quotes or paths with spaces produce malformed aider command output. Low severity now, worth capturing.
51
+ - **Agent pre-read + source verification pattern is working.** Both b029 and b030 agents read the source files and confirmed field accessor names before writing assertions — zero failures from wrong field names.
52
+
53
+ ---
54
+
55
+ ## New Backlog Items from Quality Audit
56
+
57
+ - **B031** — Tests: add_spec.rb assert `type` field in data integrity test | Priority: low
58
+ - **B032** — Tests: cli_spec.rb add subprocess test for `-f json` flag | Priority: low
59
+ - **B033** — Fix: file_collector.rb line 19 — return `''` directly when working_directory doesn't exist (don't delegate to build_formats) | Priority: low
60
+
61
+ ---
62
+
63
+ ## Suggestions for Next Campaign
64
+
65
+ **Test debt is largely cleared.** The suite is at B grade (70-75% regression catch rate). The remaining gaps (B031/B032/B033) are low severity.
66
+
67
+ **Recommended next move: architectural work.**
68
+
69
+ - **B011** — Extract VatCLI business logic from `bin/dam` (1,600 lines). This is the prerequisite for any parallelism or performance work. 20+ rubocop-disable comments are the symptom.
70
+ - **B020** — Split `S3Operations` (1,030 lines). Required before B007 (parallel S3 checks) can be built cleanly.
71
+
72
+ These are larger than any prior campaign. Recommend scoping carefully — one of the two per campaign, not both. B011 first (it's the CLI entry point and larger structural problem).
73
+
74
+ Or, if David wants a quick win: B031/B032/B033 as a micro-campaign (3 test items, 1 production fix, one wave).