appydave-tools 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +44 -7
- data/README.md +19 -7
- data/bin/{vat → dam} +163 -83
- data/docs/{vat → dam}/dam-vision.md +13 -13
- data/docs/dam/session-summary-2025-11-09.md +478 -0
- data/docs/{vat → dam}/usage.md +177 -86
- data/docs/{vat → dam}/vat-testing-plan.md +94 -94
- data/docs/development/CODEX-recommendations.md +204 -86
- data/lib/appydave/tools/configuration/models/brands_config.rb +18 -3
- data/lib/appydave/tools/{vat → dam}/config.rb +32 -13
- data/lib/appydave/tools/{vat → dam}/config_loader.rb +1 -1
- data/lib/appydave/tools/{vat → dam}/manifest_generator.rb +3 -3
- data/lib/appydave/tools/{vat → dam}/project_listing.rb +1 -1
- data/lib/appydave/tools/{vat → dam}/project_resolver.rb +1 -1
- data/lib/appydave/tools/{vat → dam}/s3_operations.rb +3 -3
- data/lib/appydave/tools/dam/sync_from_ssd.rb +241 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +7 -6
- data/package.json +1 -1
- metadata +14 -13
- data/docs/vat/session-summary-2025-11-09.md +0 -297
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Appydave
|
|
7
|
+
module Tools
|
|
8
|
+
module Dam
|
|
9
|
+
# Sync light files from SSD to local for a brand
|
|
10
|
+
# Only copies non-video files (subtitles, images, docs)
|
|
11
|
+
class SyncFromSsd
|
|
12
|
+
attr_reader :brand, :brand_info, :brand_path
|
|
13
|
+
|
|
14
|
+
# Light file patterns to include (everything except heavy video files)
|
|
15
|
+
LIGHT_FILE_PATTERNS = %w[
|
|
16
|
+
**/*.srt
|
|
17
|
+
**/*.vtt
|
|
18
|
+
**/*.txt
|
|
19
|
+
**/*.md
|
|
20
|
+
**/*.jpg
|
|
21
|
+
**/*.jpeg
|
|
22
|
+
**/*.png
|
|
23
|
+
**/*.webp
|
|
24
|
+
**/*.json
|
|
25
|
+
**/*.yml
|
|
26
|
+
**/*.yaml
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
# Heavy file patterns to exclude (video files)
|
|
30
|
+
HEAVY_FILE_PATTERNS = %w[
|
|
31
|
+
*.mp4
|
|
32
|
+
*.mov
|
|
33
|
+
*.avi
|
|
34
|
+
*.mkv
|
|
35
|
+
*.webm
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
def initialize(brand, brand_info: nil, brand_path: nil)
|
|
39
|
+
@brand_info = brand_info || load_brand_info(brand)
|
|
40
|
+
@brand = @brand_info.key # Use resolved brand key, not original input
|
|
41
|
+
@brand_path = brand_path || Config.brand_path(@brand)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sync light files from SSD for all projects in manifest
|
|
45
|
+
def sync(dry_run: false)
|
|
46
|
+
puts dry_run ? '🔍 DRY-RUN MODE - No files will be copied' : '📦 Syncing from SSD...'
|
|
47
|
+
puts ''
|
|
48
|
+
|
|
49
|
+
# Validate SSD is mounted
|
|
50
|
+
ssd_backup = brand_info.locations.ssd_backup
|
|
51
|
+
unless ssd_backup && !ssd_backup.empty?
|
|
52
|
+
puts "❌ SSD backup location not configured for brand '#{brand}'"
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
unless Dir.exist?(ssd_backup)
|
|
57
|
+
puts "❌ SSD not mounted at #{ssd_backup}"
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Load manifest
|
|
62
|
+
manifest_file = File.join(brand_path, 'projects.json')
|
|
63
|
+
unless File.exist?(manifest_file)
|
|
64
|
+
puts '❌ projects.json not found!'
|
|
65
|
+
puts " Run: vat manifest #{brand}"
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
manifest = load_manifest(manifest_file)
|
|
70
|
+
puts "📋 Loaded manifest: #{manifest[:projects].size} projects"
|
|
71
|
+
puts " Last updated: #{manifest[:config][:last_updated]}"
|
|
72
|
+
puts ''
|
|
73
|
+
|
|
74
|
+
# Filter projects to sync
|
|
75
|
+
projects_to_sync = manifest[:projects].select { |p| should_sync_project?(p) }
|
|
76
|
+
|
|
77
|
+
puts '🔍 Analysis:'
|
|
78
|
+
puts " Total projects in manifest: #{manifest[:projects].size}"
|
|
79
|
+
puts " Projects to sync: #{projects_to_sync.size}"
|
|
80
|
+
puts " Skipped (already local): #{manifest[:projects].size - projects_to_sync.size}"
|
|
81
|
+
puts ''
|
|
82
|
+
|
|
83
|
+
if projects_to_sync.empty?
|
|
84
|
+
puts '✅ Nothing to sync - all projects either already local or not on SSD'
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Sync each project
|
|
89
|
+
total_stats = { files: 0, bytes: 0, skipped: 0 }
|
|
90
|
+
|
|
91
|
+
projects_to_sync.each do |project|
|
|
92
|
+
stats = sync_project(project, ssd_backup, dry_run: dry_run)
|
|
93
|
+
|
|
94
|
+
# Only show project if there are files to sync or a warning
|
|
95
|
+
if stats[:reason] || stats[:files]&.positive?
|
|
96
|
+
puts "📁 #{project[:id]}"
|
|
97
|
+
|
|
98
|
+
puts " ⚠️ Skipped: #{stats[:reason]}" if stats[:reason]
|
|
99
|
+
|
|
100
|
+
puts " #{stats[:files]} file(s), #{format_bytes(stats[:bytes])}" if stats[:files]&.positive?
|
|
101
|
+
puts ''
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
total_stats[:files] += stats[:files] || 0
|
|
105
|
+
total_stats[:bytes] += stats[:bytes] || 0
|
|
106
|
+
total_stats[:skipped] += stats[:skipped] || 0
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
puts ''
|
|
110
|
+
puts '=' * 60
|
|
111
|
+
puts 'Summary:'
|
|
112
|
+
puts " Projects scanned: #{projects_to_sync.size}"
|
|
113
|
+
puts " Files #{dry_run ? 'to copy' : 'copied'}: #{total_stats[:files]}"
|
|
114
|
+
puts " Total size: #{format_bytes(total_stats[:bytes])}"
|
|
115
|
+
puts ''
|
|
116
|
+
puts '✅ Sync complete!'
|
|
117
|
+
puts ' Run without --dry-run to perform the sync' if dry_run
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def load_brand_info(brand)
|
|
123
|
+
Appydave::Tools::Configuration::Config.configure
|
|
124
|
+
Appydave::Tools::Configuration::Config.brands.get_brand(brand)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def load_manifest(manifest_file)
|
|
128
|
+
JSON.parse(File.read(manifest_file), symbolize_names: true)
|
|
129
|
+
rescue JSON::ParserError => e
|
|
130
|
+
puts "❌ Error parsing projects.json: #{e.message}"
|
|
131
|
+
exit 1
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Determine if project should be synced
|
|
135
|
+
def should_sync_project?(project)
|
|
136
|
+
# Only sync if project exists on SSD but NOT in local flat structure
|
|
137
|
+
return false unless project[:storage][:ssd][:exists]
|
|
138
|
+
|
|
139
|
+
# Skip if exists locally in flat structure
|
|
140
|
+
return false if project[:storage][:local][:exists] && project[:storage][:local][:structure] == 'flat'
|
|
141
|
+
|
|
142
|
+
true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Sync a single project from SSD to local
|
|
146
|
+
def sync_project(project, ssd_backup, dry_run: false)
|
|
147
|
+
project_id = project[:id]
|
|
148
|
+
ssd_path = File.join(ssd_backup, project_id)
|
|
149
|
+
|
|
150
|
+
return { skipped: 1, files: 0, bytes: 0, reason: 'SSD path not found' } unless Dir.exist?(ssd_path)
|
|
151
|
+
|
|
152
|
+
# Check for flat folder conflict (stale manifest)
|
|
153
|
+
flat_path = File.join(brand_path, project_id)
|
|
154
|
+
return { skipped: 1, files: 0, bytes: 0, reason: 'Flat folder exists (stale manifest?)' } if Dir.exist?(flat_path)
|
|
155
|
+
|
|
156
|
+
# Determine local destination path (archived structure)
|
|
157
|
+
# Extract range from project ID (e.g., b65 → 60-69 range)
|
|
158
|
+
range = determine_range(project_id)
|
|
159
|
+
local_dir = File.join(brand_path, 'archived', range, project_id)
|
|
160
|
+
|
|
161
|
+
# Create local directory
|
|
162
|
+
FileUtils.mkdir_p(local_dir) if !dry_run && !Dir.exist?(local_dir)
|
|
163
|
+
|
|
164
|
+
# Sync light files
|
|
165
|
+
sync_light_files(ssd_path, local_dir, dry_run: dry_run)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Determine range folder for project (e.g., b65 → 60-69)
|
|
169
|
+
def determine_range(project_id)
|
|
170
|
+
# FliVideo pattern: b40, b41, ... b99
|
|
171
|
+
if project_id =~ /^b(\d+)/
|
|
172
|
+
tens = (Regexp.last_match(1).to_i / 10) * 10
|
|
173
|
+
"#{tens}-#{tens + 9}"
|
|
174
|
+
else
|
|
175
|
+
# Legacy pattern or unknown: use first 3 chars
|
|
176
|
+
'000-099'
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Sync light files from SSD to local
|
|
181
|
+
def sync_light_files(ssd_path, local_dir, dry_run: false)
|
|
182
|
+
stats = { files: 0, bytes: 0 }
|
|
183
|
+
|
|
184
|
+
LIGHT_FILE_PATTERNS.each do |pattern|
|
|
185
|
+
Dir.glob(File.join(ssd_path, pattern)).each do |source_file|
|
|
186
|
+
next if heavy_file?(source_file)
|
|
187
|
+
|
|
188
|
+
copy_stats = copy_light_file(source_file, ssd_path, local_dir, dry_run: dry_run)
|
|
189
|
+
stats[:files] += copy_stats[:files]
|
|
190
|
+
stats[:bytes] += copy_stats[:bytes]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
stats
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Check if file is a heavy video file
|
|
198
|
+
def heavy_file?(source_file)
|
|
199
|
+
HEAVY_FILE_PATTERNS.any? { |pattern| File.fnmatch(pattern, File.basename(source_file)) }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Copy a single light file
|
|
203
|
+
def copy_light_file(source_file, ssd_path, local_dir, dry_run: false)
|
|
204
|
+
relative_path = source_file.sub("#{ssd_path}/", '')
|
|
205
|
+
dest_file = File.join(local_dir, relative_path)
|
|
206
|
+
|
|
207
|
+
# Skip if already synced (same size)
|
|
208
|
+
return { files: 0, bytes: 0 } if file_already_synced?(source_file, dest_file)
|
|
209
|
+
|
|
210
|
+
file_size = File.size(source_file)
|
|
211
|
+
|
|
212
|
+
if dry_run
|
|
213
|
+
puts " [DRY-RUN] Would copy: #{relative_path} (#{format_bytes(file_size)})"
|
|
214
|
+
else
|
|
215
|
+
FileUtils.mkdir_p(File.dirname(dest_file))
|
|
216
|
+
FileUtils.cp(source_file, dest_file, preserve: true)
|
|
217
|
+
puts " ✓ Copied: #{relative_path} (#{format_bytes(file_size)})"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
{ files: 1, bytes: file_size }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Check if file is already synced (by size comparison)
|
|
224
|
+
def file_already_synced?(source_file, dest_file)
|
|
225
|
+
File.exist?(dest_file) && File.size(dest_file) == File.size(source_file)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Format bytes into human-readable format
|
|
229
|
+
def format_bytes(bytes)
|
|
230
|
+
if bytes < 1024
|
|
231
|
+
"#{bytes}B"
|
|
232
|
+
elsif bytes < 1024 * 1024
|
|
233
|
+
"#{(bytes / 1024.0).round(1)}KB"
|
|
234
|
+
else
|
|
235
|
+
"#{(bytes / 1024.0 / 1024.0).round(1)}MB"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
data/lib/appydave/tools.rb
CHANGED
|
@@ -52,12 +52,13 @@ require 'appydave/tools/prompt_tools/prompt_completion'
|
|
|
52
52
|
require 'appydave/tools/subtitle_processor/clean'
|
|
53
53
|
require 'appydave/tools/subtitle_processor/join'
|
|
54
54
|
|
|
55
|
-
require 'appydave/tools/
|
|
56
|
-
require 'appydave/tools/
|
|
57
|
-
require 'appydave/tools/
|
|
58
|
-
require 'appydave/tools/
|
|
59
|
-
require 'appydave/tools/
|
|
60
|
-
require 'appydave/tools/
|
|
55
|
+
require 'appydave/tools/dam/config'
|
|
56
|
+
require 'appydave/tools/dam/project_resolver'
|
|
57
|
+
require 'appydave/tools/dam/config_loader'
|
|
58
|
+
require 'appydave/tools/dam/s3_operations'
|
|
59
|
+
require 'appydave/tools/dam/project_listing'
|
|
60
|
+
require 'appydave/tools/dam/manifest_generator'
|
|
61
|
+
require 'appydave/tools/dam/sync_from_ssd'
|
|
61
62
|
|
|
62
63
|
require 'appydave/tools/youtube_automation/gpt_agent'
|
|
63
64
|
|
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: appydave-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cruwys
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activemodel
|
|
@@ -214,6 +214,7 @@ files:
|
|
|
214
214
|
- bin/bank_reconciliation.rb
|
|
215
215
|
- bin/configuration.rb
|
|
216
216
|
- bin/console
|
|
217
|
+
- bin/dam
|
|
217
218
|
- bin/generate_manifest.rb
|
|
218
219
|
- bin/gpt_context.rb
|
|
219
220
|
- bin/move_images.rb
|
|
@@ -223,7 +224,6 @@ files:
|
|
|
223
224
|
- bin/subtitle_manager.rb
|
|
224
225
|
- bin/subtitle_processor.rb
|
|
225
226
|
- bin/sync_from_ssd.rb
|
|
226
|
-
- bin/vat
|
|
227
227
|
- bin/youtube_automation.rb
|
|
228
228
|
- bin/youtube_manager.rb
|
|
229
229
|
- docs/README.md
|
|
@@ -237,6 +237,10 @@ files:
|
|
|
237
237
|
- docs/configuration/README.md
|
|
238
238
|
- docs/configuration/channels.example.json
|
|
239
239
|
- docs/configuration/settings.example.json
|
|
240
|
+
- docs/dam/dam-vision.md
|
|
241
|
+
- docs/dam/session-summary-2025-11-09.md
|
|
242
|
+
- docs/dam/usage.md
|
|
243
|
+
- docs/dam/vat-testing-plan.md
|
|
240
244
|
- docs/development/CODEX-recommendations.md
|
|
241
245
|
- docs/development/README.md
|
|
242
246
|
- docs/development/cli-architecture-patterns.md
|
|
@@ -254,10 +258,6 @@ files:
|
|
|
254
258
|
- docs/tools/subtitle-processor.md
|
|
255
259
|
- docs/tools/youtube-automation.md
|
|
256
260
|
- docs/tools/youtube-manager.md
|
|
257
|
-
- docs/vat/dam-vision.md
|
|
258
|
-
- docs/vat/session-summary-2025-11-09.md
|
|
259
|
-
- docs/vat/usage.md
|
|
260
|
-
- docs/vat/vat-testing-plan.md
|
|
261
261
|
- exe/ad_config
|
|
262
262
|
- exe/gpt_context
|
|
263
263
|
- exe/prompt_tools
|
|
@@ -281,6 +281,13 @@ files:
|
|
|
281
281
|
- lib/appydave/tools/configuration/models/settings_config.rb
|
|
282
282
|
- lib/appydave/tools/configuration/models/youtube_automation_config.rb
|
|
283
283
|
- lib/appydave/tools/configuration/openai.rb
|
|
284
|
+
- lib/appydave/tools/dam/config.rb
|
|
285
|
+
- lib/appydave/tools/dam/config_loader.rb
|
|
286
|
+
- lib/appydave/tools/dam/manifest_generator.rb
|
|
287
|
+
- lib/appydave/tools/dam/project_listing.rb
|
|
288
|
+
- lib/appydave/tools/dam/project_resolver.rb
|
|
289
|
+
- lib/appydave/tools/dam/s3_operations.rb
|
|
290
|
+
- lib/appydave/tools/dam/sync_from_ssd.rb
|
|
284
291
|
- lib/appydave/tools/debuggable.rb
|
|
285
292
|
- lib/appydave/tools/gpt_context/_doc.md
|
|
286
293
|
- lib/appydave/tools/gpt_context/file_collector.rb
|
|
@@ -301,12 +308,6 @@ files:
|
|
|
301
308
|
- lib/appydave/tools/types/base_model.rb
|
|
302
309
|
- lib/appydave/tools/types/hash_type.rb
|
|
303
310
|
- lib/appydave/tools/types/indifferent_access_hash.rb
|
|
304
|
-
- lib/appydave/tools/vat/config.rb
|
|
305
|
-
- lib/appydave/tools/vat/config_loader.rb
|
|
306
|
-
- lib/appydave/tools/vat/manifest_generator.rb
|
|
307
|
-
- lib/appydave/tools/vat/project_listing.rb
|
|
308
|
-
- lib/appydave/tools/vat/project_resolver.rb
|
|
309
|
-
- lib/appydave/tools/vat/s3_operations.rb
|
|
310
311
|
- lib/appydave/tools/version.rb
|
|
311
312
|
- lib/appydave/tools/youtube_automation/_doc.md
|
|
312
313
|
- lib/appydave/tools/youtube_automation/gpt_agent.rb
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
# VAT S3 Operations - Session Summary
|
|
2
|
-
**Date**: 2025-11-09
|
|
3
|
-
**Status**: Ready to Continue - Archive/SSD Commands Next
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## What We Accomplished Today
|
|
8
|
-
|
|
9
|
-
### 1. Fixed RuboCop Issues Properly ✅
|
|
10
|
-
|
|
11
|
-
**Problem**: Initially disabled RuboCop cops instead of fixing underlying issues.
|
|
12
|
-
|
|
13
|
-
**Solution**: Refactored S3Operations to use **dependency injection**:
|
|
14
|
-
```ruby
|
|
15
|
-
# Before (mocking singletons):
|
|
16
|
-
allow_any_instance_of(BrandsConfig).to receive(:get_brand)
|
|
17
|
-
S3Operations.new('test', 'test-project')
|
|
18
|
-
|
|
19
|
-
# After (dependency injection):
|
|
20
|
-
S3Operations.new('test', 'test-project',
|
|
21
|
-
brand_info: real_brand_info_object,
|
|
22
|
-
brand_path: brand_path,
|
|
23
|
-
s3_client: mock_s3_client
|
|
24
|
-
)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Benefits**:
|
|
28
|
-
- Eliminated ALL `allow_any_instance_of` usage
|
|
29
|
-
- Tests use real `BrandInfo` objects (not mocks)
|
|
30
|
-
- More flexible, testable, SOLID design
|
|
31
|
-
- Backward compatible (production code unchanged)
|
|
32
|
-
|
|
33
|
-
**RuboCop Config**: Disabled `RSpec/MessageSpies` and `RSpec/StubbedMock` globally (legitimate for AWS SDK integration tests)
|
|
34
|
-
|
|
35
|
-
### 2. Added S3 Cleanup Local Command ✅
|
|
36
|
-
|
|
37
|
-
**New Command**: `vat s3-cleanup-local <brand> <project> --force [--dry-run]`
|
|
38
|
-
|
|
39
|
-
**Features**:
|
|
40
|
-
- Deletes files from local `s3-staging/` directory
|
|
41
|
-
- Requires `--force` flag for safety
|
|
42
|
-
- Supports `--dry-run` mode
|
|
43
|
-
- Removes empty directories after cleanup
|
|
44
|
-
- Shows deleted/failed counts
|
|
45
|
-
- Auto-detection from PWD
|
|
46
|
-
|
|
47
|
-
**Implementation**:
|
|
48
|
-
- Added `cleanup_local` method to S3Operations (lib/appydave/tools/vat/s3_operations.rb:212-275)
|
|
49
|
-
- Added `delete_local_file` private method (lib/appydave/tools/vat/s3_operations.rb:359-371)
|
|
50
|
-
- Added CLI command handler
|
|
51
|
-
- Added comprehensive help documentation
|
|
52
|
-
|
|
53
|
-
**Tests**: 6 tests covering all scenarios (289 total tests, 90.59% coverage)
|
|
54
|
-
|
|
55
|
-
### 3. Fixed S3 Status Command ✅
|
|
56
|
-
|
|
57
|
-
**Problem**: Status only showed 3 states (synced, modified, S3 only) - missing "local only" files.
|
|
58
|
-
|
|
59
|
-
**Solution**: Enhanced status to show all 4 states:
|
|
60
|
-
- ✓ **[synced]** - Files match (MD5)
|
|
61
|
-
- ⚠️ **[modified]** - Files differ
|
|
62
|
-
- ☁️ **[S3 only]** - File in S3 but not local
|
|
63
|
-
- 📁 **[local only]** - File local but not in S3 ⭐ NEW
|
|
64
|
-
|
|
65
|
-
**Implementation**:
|
|
66
|
-
- Rewrote `status` method to check both S3 AND local files (lib/appydave/tools/vat/s3_operations.rb:139-202)
|
|
67
|
-
- Added `list_local_files` helper method (lib/appydave/tools/vat/s3_operations.rb:424-434)
|
|
68
|
-
- Enhanced summary: Shows file counts and sizes for both S3 and local
|
|
69
|
-
|
|
70
|
-
**Tests**: Added 2 new tests (local-only files, comprehensive summary)
|
|
71
|
-
|
|
72
|
-
### 4. Renamed Cleanup Commands for Consistency ✅
|
|
73
|
-
|
|
74
|
-
**Old Names**:
|
|
75
|
-
- `vat s3-cleanup` → Delete S3 files
|
|
76
|
-
- `vat cleanup-local` → Delete local files
|
|
77
|
-
|
|
78
|
-
**New Names**:
|
|
79
|
-
- `vat s3-cleanup-remote` → Delete S3 files
|
|
80
|
-
- `vat s3-cleanup-local` → Delete local files
|
|
81
|
-
|
|
82
|
-
**Backward Compatibility**: Old names still work but show deprecation warning
|
|
83
|
-
|
|
84
|
-
**Changes**:
|
|
85
|
-
- Updated command registration (bin/vat:13-24)
|
|
86
|
-
- Renamed methods
|
|
87
|
-
- Updated all help documentation
|
|
88
|
-
- Added deprecation notices
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Testing Status
|
|
93
|
-
|
|
94
|
-
### Automated Tests ✅
|
|
95
|
-
- **Session 1**: 289 examples, 0 failures, 90.59% coverage
|
|
96
|
-
- **Session 2**: 297 examples, 0 failures, 90.69% coverage (+8 tests, +0.10% coverage)
|
|
97
|
-
- **RuboCop**: Clean (no offenses)
|
|
98
|
-
|
|
99
|
-
### Manual Testing (David) ✅
|
|
100
|
-
**Session 1**:
|
|
101
|
-
- ✅ `vat s3-cleanup-remote` - Tested and working
|
|
102
|
-
- ✅ `vat s3-cleanup-local` - Tested and working
|
|
103
|
-
- ✅ `vat s3-status` - Shows all 4 states correctly (synced, modified, S3 only, local only)
|
|
104
|
-
|
|
105
|
-
**Session 2**:
|
|
106
|
-
- ⏳ `vat archive` - Ready for manual testing
|
|
107
|
-
|
|
108
|
-
### S3Operations Test Coverage
|
|
109
|
-
- **Session 1**: 33 tests (upload, download, status, cleanup, cleanup_local)
|
|
110
|
-
- **Session 2**: 41 tests (+8 archive tests)
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Current State
|
|
115
|
-
|
|
116
|
-
### Implemented VAT Commands
|
|
117
|
-
1. ✅ `vat list [brand] [pattern]` - List brands/projects
|
|
118
|
-
2. ✅ `vat s3-up <brand> <project>` - Upload to S3
|
|
119
|
-
3. ✅ `vat s3-down <brand> <project>` - Download from S3
|
|
120
|
-
4. ✅ `vat s3-status <brand> <project>` - Check sync status (all 4 states)
|
|
121
|
-
5. ✅ `vat s3-cleanup-remote <brand> <project>` - Delete S3 files
|
|
122
|
-
6. ✅ `vat s3-cleanup-local <brand> <project>` - Delete local files
|
|
123
|
-
7. ✅ `vat archive <brand> <project>` - Copy to SSD backup ⭐ NEW (2025-11-09 Session 2)
|
|
124
|
-
|
|
125
|
-
### Not Yet Implemented
|
|
126
|
-
1. ⏳ `vat sync-ssd <brand>` - Restore from SSD
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## Session 2 (2025-11-09 Evening) - Archive Command Implementation ✅
|
|
131
|
-
|
|
132
|
-
### What We Accomplished
|
|
133
|
-
|
|
134
|
-
#### 1. Implemented Archive Command ✅
|
|
135
|
-
**New Command**: `vat archive <brand> <project> [--force] [--dry-run]`
|
|
136
|
-
|
|
137
|
-
**Features**:
|
|
138
|
-
- Copies entire project directory to SSD backup location
|
|
139
|
-
- Verifies SSD is mounted before archiving
|
|
140
|
-
- Shows project size before copying
|
|
141
|
-
- Optional: Delete local copy after successful archive (--force)
|
|
142
|
-
- Dry-run support for preview
|
|
143
|
-
- Auto-detection from PWD
|
|
144
|
-
|
|
145
|
-
**Implementation**:
|
|
146
|
-
- Added `archive` method to S3Operations (lib/appydave/tools/vat/s3_operations.rb:298-340)
|
|
147
|
-
- Added helper methods:
|
|
148
|
-
- `copy_to_ssd` (lib/appydave/tools/vat/s3_operations.rb:493-522)
|
|
149
|
-
- `delete_local_project` (lib/appydave/tools/vat/s3_operations.rb:524-547)
|
|
150
|
-
- `calculate_directory_size` (lib/appydave/tools/vat/s3_operations.rb:549-556)
|
|
151
|
-
- Added CLI command handler (bin/vat:148-156)
|
|
152
|
-
- Added comprehensive help documentation (bin/vat:480-515)
|
|
153
|
-
|
|
154
|
-
**Tests**: 8 new tests covering all scenarios (297 total tests, 90.69% coverage)
|
|
155
|
-
|
|
156
|
-
**Test Coverage**:
|
|
157
|
-
- ✅ SSD backup location not configured
|
|
158
|
-
- ✅ SSD not mounted
|
|
159
|
-
- ✅ Project does not exist
|
|
160
|
-
- ✅ Dry-run preview
|
|
161
|
-
- ✅ Copy without deleting local (default)
|
|
162
|
-
- ✅ Warning when not using --force
|
|
163
|
-
- ✅ Copy and delete with --force
|
|
164
|
-
- ✅ Skip when already exists on SSD
|
|
165
|
-
|
|
166
|
-
**Storage Strategy**: Local → S3 (90-day collaboration) → SSD (long-term archive)
|
|
167
|
-
|
|
168
|
-
**Configuration**: Uses `ssd_backup` location from brands.json (already configured for all 6 brands)
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Next Steps (For Future Session)
|
|
173
|
-
|
|
174
|
-
### Priority 1: Sync from SSD Command ⏳
|
|
175
|
-
Implement `vat sync-ssd <brand>` to restore projects from SSD:
|
|
176
|
-
|
|
177
|
-
**Requirements** (from original discussion):
|
|
178
|
-
- Copy entire project directory to SSD backup location
|
|
179
|
-
- Verify copy completed successfully
|
|
180
|
-
- Option to remove local copy after successful archive
|
|
181
|
-
- Support dry-run mode
|
|
182
|
-
- Show progress for large projects
|
|
183
|
-
|
|
184
|
-
**Configuration** (already in brands.json):
|
|
185
|
-
```json
|
|
186
|
-
"locations": {
|
|
187
|
-
"video_projects": "/Users/davidcruwys/dev/video-projects/v-appydave",
|
|
188
|
-
"ssd_backup": "/Volumes/T7/youtube-PUBLISHED/appydave"
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**Implementation Plan**:
|
|
193
|
-
1. Add `archive` method to new class (or S3Operations? Or separate ArchiveOperations?)
|
|
194
|
-
2. CLI command handler
|
|
195
|
-
3. Tests
|
|
196
|
-
4. Help documentation
|
|
197
|
-
|
|
198
|
-
### Priority 2: Sync from SSD Command
|
|
199
|
-
Implement `vat sync-ssd <brand>` to restore projects from SSD:
|
|
200
|
-
|
|
201
|
-
**Requirements**:
|
|
202
|
-
- List available projects on SSD
|
|
203
|
-
- Copy selected project(s) back to local
|
|
204
|
-
- Smart sync (skip if already exists? overwrite? merge?)
|
|
205
|
-
- Dry-run support
|
|
206
|
-
|
|
207
|
-
### Priority 3: Documentation
|
|
208
|
-
- Document AWS permissions strategy for team members
|
|
209
|
-
- Update CLAUDE.md with latest VAT commands
|
|
210
|
-
- Update usage documentation
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## Architecture Notes
|
|
215
|
-
|
|
216
|
-
### Dependency Injection Pattern Established
|
|
217
|
-
All new code should follow the pattern established in S3Operations:
|
|
218
|
-
|
|
219
|
-
```ruby
|
|
220
|
-
class MyOperations
|
|
221
|
-
def initialize(brand, project_id, brand_info: nil, brand_path: nil)
|
|
222
|
-
@brand = brand
|
|
223
|
-
@project_id = project_id
|
|
224
|
-
@brand_info = brand_info || load_from_config(brand)
|
|
225
|
-
@brand_path = brand_path || Config.brand_path(brand)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
private
|
|
229
|
-
|
|
230
|
-
def load_from_config(brand)
|
|
231
|
-
Config.configure
|
|
232
|
-
Config.brands.get_brand(brand)
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Tests**:
|
|
238
|
-
```ruby
|
|
239
|
-
let(:brand_info) { BrandInfo.new('test', test_data) } # Real object
|
|
240
|
-
|
|
241
|
-
def create_operations
|
|
242
|
-
described_class.new('test', 'test-project',
|
|
243
|
-
brand_info: brand_info,
|
|
244
|
-
brand_path: brand_path
|
|
245
|
-
)
|
|
246
|
-
end
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### File Locations
|
|
250
|
-
- **S3 operations**: `lib/appydave/tools/vat/s3_operations.rb`
|
|
251
|
-
- **S3 tests**: `spec/appydave/tools/vat/s3_operations_spec.rb`
|
|
252
|
-
- **VAT CLI**: `bin/vat`
|
|
253
|
-
- **Brands config**: `lib/appydave/tools/configuration/models/brands_config.rb`
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Questions for Tomorrow
|
|
258
|
-
|
|
259
|
-
1. **Archive behavior**: Should archive DELETE local files after successful copy, or just copy and leave local intact?
|
|
260
|
-
2. **Archive scope**: Archive just the project directory, or also s3-staging/?
|
|
261
|
-
3. **SSD sync**: Should it sync ALL projects from a brand, or let user select specific ones?
|
|
262
|
-
4. **Conflict handling**: If project exists both locally and on SSD, what should sync-ssd do?
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
## Key Files Modified
|
|
267
|
-
|
|
268
|
-
### Session 1
|
|
269
|
-
1. `lib/appydave/tools/vat/s3_operations.rb` - Refactored with DI, added cleanup_local, fixed status
|
|
270
|
-
2. `spec/appydave/tools/vat/s3_operations_spec.rb` - Rewrote to use DI, added new tests
|
|
271
|
-
3. `bin/vat` - Renamed cleanup commands, updated help
|
|
272
|
-
4. `.rubocop.yml` - Disabled MessageSpies and StubbedMock cops
|
|
273
|
-
|
|
274
|
-
### Session 2
|
|
275
|
-
1. `lib/appydave/tools/vat/s3_operations.rb` - Added archive method and helper methods
|
|
276
|
-
2. `spec/appydave/tools/vat/s3_operations_spec.rb` - Added 8 archive tests
|
|
277
|
-
3. `bin/vat` - Added archive command handler and help documentation
|
|
278
|
-
4. `docs/vat/session-summary-2025-11-09.md` - Updated with Session 2 accomplishments
|
|
279
|
-
|
|
280
|
-
---
|
|
281
|
-
|
|
282
|
-
## Git Status
|
|
283
|
-
**Session 1**:
|
|
284
|
-
- 289 tests passing
|
|
285
|
-
- RuboCop clean
|
|
286
|
-
- S3 cleanup commands complete
|
|
287
|
-
|
|
288
|
-
**Session 2**:
|
|
289
|
-
- 297 tests passing (+8 archive tests)
|
|
290
|
-
- 90.69% code coverage (+0.10%)
|
|
291
|
-
- RuboCop clean
|
|
292
|
-
- Archive command complete
|
|
293
|
-
- Ready to commit
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
**Next Session**: Implement `vat sync-ssd <brand>` command for restoring projects from SSD backup.
|