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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +0 -34
- data/bin/dam +6 -76
- data/docs/planning/AGENTS.md +17 -5
- data/docs/planning/BACKLOG.md +19 -93
- data/docs/planning/extract-vat-cli/AGENTS.md +363 -0
- data/docs/planning/extract-vat-cli/IMPLEMENTATION_PLAN.md +61 -0
- data/docs/planning/final-test-gaps/IMPLEMENTATION_PLAN.md +7 -5
- data/docs/planning/final-test-gaps/assessment.md +74 -0
- data/docs/planning/micro-cleanup/AGENTS.md +181 -0
- data/docs/planning/micro-cleanup/IMPLEMENTATION_PLAN.md +29 -0
- data/docs/planning/micro-cleanup/assessment.md +68 -0
- data/lib/appydave/tools/dam/local_sync_status.rb +66 -0
- data/lib/appydave/tools/gpt_context/file_collector.rb +1 -1
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +1 -2
- data/package.json +1 -1
- metadata +8 -2
- data/docs/planning/next-round-brief.md +0 -42
|
@@ -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:
|
|
8
|
+
- Total: 4 | Complete: 4 | In Progress: 0 | Pending: 0 | Failed: 0
|
|
9
9
|
|
|
10
10
|
## Pending
|
|
11
11
|
|
|
12
12
|
## In Progress
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
13
|
+
|
|
14
|
+
## Complete
|
|
15
|
+
- [x] fix-b023 — file_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-b028 — cli_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).
|