appydave-tools 0.18.5 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94b533e9d8174f22fe36c1dbc435b1051ddef31ac461184741958e75ae451c47
4
- data.tar.gz: 252ea976c625cd3a473c46e219e4595e7f568d6f5fdbb96060b8e399cb52fd48
3
+ metadata.gz: d1d9c19286530b337db5170902dce174c889b50a7c2425f6e209efec097a0a81
4
+ data.tar.gz: 220392dd2eec9910e3c57e7c7de4a372303ab261d01a2db9d6c64769368bf71e
5
5
  SHA512:
6
- metadata.gz: 1078bed7c6b2b0f4144af45d572a96c10ffa8e31d34f6dc100bc4094a847be67b500dfdf00c8e67a790e71f5e6946d29105fd37466f4412a9326dbdc509d139a
7
- data.tar.gz: aa2c82acd177c562f5950b0610c05d4b278e76e44b9bd3af68d89a946dd8ffeb9cfc8359dd3e91f13d738058e7c0504978f9772640a3025bb3b2905044eec5d3
6
+ metadata.gz: 33dd5ec557282ce787d457cfaa6e4b996fae6d82b2b4ac4aa5e531ac17f1c310f000fd905428ce3fb969b35635aea9122347a7760e73f0c6282425ffce6e65e9
7
+ data.tar.gz: 5be8054544c85a36ef280ed6aa85096f386243df5e66c48f911728fe943350d66c69863b0005424df97e20697bc75126a37e1c4ce84cd8265728ca017e58a4e0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.18.5](https://github.com/appydave/appydave-tools/compare/v0.18.4...v0.18.5) (2025-11-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * update test plan to reflect all DAM commands completed (manifest, archive, sync-ssd) and clarify git repo scripts ([ac1436a](https://github.com/appydave/appydave-tools/commit/ac1436a09c4ae32009f1b98dc697da9a87f1c4ee))
7
+
1
8
  ## [0.18.4](https://github.com/appydave/appydave-tools/compare/v0.18.3...v0.18.4) (2025-11-10)
2
9
 
3
10
 
@@ -0,0 +1,871 @@
1
+ # PRD: Git Integration & Unified Status
2
+
3
+ **Status:** Draft
4
+ **Author:** David Cruwys
5
+ **Created:** 2025-11-10
6
+ **Last Updated:** 2025-11-10
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ DAM (Digital Asset Management) currently manages video projects across three storage layers: local disk, S3 cloud collaboration, and SSD archival. However, video projects also exist as git repositories for version control of light files (subtitles, images, markdown, metadata). Currently, git operations are handled by separate shell scripts (`status-all.sh`, `sync-all.sh`, `clone-all.sh`) outside the DAM system.
13
+
14
+ This PRD proposes integrating git repository management into DAM, creating a unified interface for managing both storage layers (heavy video files + light versioned files) from a single command-line tool.
15
+
16
+ ### Current State
17
+
18
+ **Dual Management Systems:**
19
+ - **DAM layer** - Manages heavy files (video) via S3/SSD sync commands
20
+ - **Git layer** - Manages light files (SRT, images, docs) via separate shell scripts
21
+
22
+ **Problem:** Two separate workflows, no unified view of project state across both layers
23
+
24
+ **Example current workflow:**
25
+ ```bash
26
+ # Check video file sync
27
+ dam s3-status appydave b65
28
+
29
+ # Separately check git status
30
+ cd /video-projects/v-appydave/b65-project
31
+ git status
32
+
33
+ # Separately sync git repo
34
+ cd /video-projects/v-appydave
35
+ git pull
36
+ ```
37
+
38
+ ### Proposed State
39
+
40
+ **Unified DAM Interface:**
41
+ ```bash
42
+ # Single command shows everything: local, S3, SSD, and git status
43
+ dam status appydave b65
44
+
45
+ # Git operations integrated into DAM
46
+ dam repo-status appydave # Check all projects
47
+ dam repo-sync appydave # Pull updates for all projects
48
+ dam repo-push appydave b65 # Push specific project
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Goals
54
+
55
+ ### Primary Goals
56
+
57
+ 1. **Unified Status View** - Single command shows project state across all layers (local, S3, SSD, git)
58
+ 2. **Git Integration** - Manage git operations through DAM commands (status, sync, push)
59
+ 3. **Self-Healing Config** - Automatically infer and populate git remote URLs when missing
60
+ 4. **S3 Tracking** - Add S3 staging to manifest for complete storage visibility
61
+
62
+ ### Secondary Goals
63
+
64
+ 5. **Dynamic Brand Discovery** - Eliminate hardcoded brand lists, use brands.json
65
+ 6. **Inferred Behavior** - Status command shows relevant info based on what exists
66
+ 7. **Consistent Interface** - Git commands follow same patterns as S3 commands
67
+
68
+ ### Non-Goals
69
+
70
+ - **File-level git operations** - Not implementing `dam git commit/add/etc` (use git directly)
71
+ - **Complex git workflows** - Not handling branches, rebasing, merging (use git directly)
72
+ - **GitHub API integration** - Not creating issues, PRs, releases
73
+
74
+ ---
75
+
76
+ ## User Stories
77
+
78
+ ### Story 1: Check Project Status (Primary Use Case)
79
+
80
+ **As a content creator,**
81
+ **I want to see complete project status in one command,**
82
+ **So I don't have to check multiple tools to understand project state.**
83
+
84
+ **Current workflow:**
85
+ ```bash
86
+ # Check S3 sync status
87
+ dam s3-status appydave b65
88
+
89
+ # Check SSD archive status
90
+ cd /video-projects/v-appydave && ls b65* && cd /Volumes/T7/youtube-PUBLISHED/appydave && ls b65*
91
+
92
+ # Check git status
93
+ cd /video-projects/v-appydave/b65-project
94
+ git status
95
+ ```
96
+
97
+ **Proposed workflow:**
98
+ ```bash
99
+ dam status appydave b65
100
+ ```
101
+
102
+ **Output:**
103
+ ```
104
+ 📊 Status: v-appydave/b65-guy-monroe-marketing-plan
105
+
106
+ Storage:
107
+ 📁 Local: ✓ exists (flat structure)
108
+ Heavy files: no
109
+ Light files: yes (5 SRT, 12 images, 3 docs)
110
+
111
+ ☁️ S3 Staging: ✓ exists
112
+ Files in sync: 3
113
+ Need upload: 2 (15.3 MB)
114
+ Need download: 0
115
+
116
+ 💾 SSD Backup: ✓ exists
117
+ Last archived: 2025-11-08
118
+
119
+ Git:
120
+ 🌿 Branch: main
121
+ 📡 Remote: git@github.com:klueless-io/v-appydave.git
122
+ ↕️ Status: 2 files modified, 1 untracked
123
+ 🔄 Sync: Behind by 0 commits
124
+ ```
125
+
126
+ **Acceptance Criteria:**
127
+ - Shows local/S3/SSD/git status in single view
128
+ - Displays only relevant sections (skips S3 if no s3-staging folder)
129
+ - Color-coded indicators (✓/✗/⚠️)
130
+ - Human-readable file counts and sizes
131
+
132
+ ---
133
+
134
+ ### Story 2: Sync All Brand Repos (Team Collaboration)
135
+
136
+ **As a team member,**
137
+ **I want to pull updates for all brand repositories at once,**
138
+ **So I can start work with latest changes across all projects.**
139
+
140
+ **Current workflow:**
141
+ ```bash
142
+ cd /video-projects
143
+ ./v-shared/sync-all.sh # Hardcoded REPOS array
144
+ ```
145
+
146
+ **Proposed workflow:**
147
+ ```bash
148
+ dam repo-sync appydave
149
+ ```
150
+
151
+ **Output:**
152
+ ```
153
+ 🔄 Syncing git repositories for appydave...
154
+
155
+ 📦 v-appydave
156
+ ✓ Already up to date
157
+
158
+ Summary:
159
+ Repos checked: 1
160
+ Updated: 0
161
+ Already current: 1
162
+ Errors: 0
163
+ ```
164
+
165
+ **Acceptance Criteria:**
166
+ - Uses brands.json (no hardcoded lists)
167
+ - Runs `git pull` on brand directory
168
+ - Shows summary of changes
169
+ - Handles errors gracefully (uncommitted changes, merge conflicts)
170
+
171
+ ---
172
+
173
+ ### Story 3: Self-Healing Git Remote (Bootstrap Scenario)
174
+
175
+ **As a new team member,**
176
+ **I want git remote URLs auto-populated,**
177
+ **So I don't have to manually configure repos.**
178
+
179
+ **Scenario:**
180
+ ```bash
181
+ # brands.json initially has: "git_remote": null
182
+ dam repo-status appydave
183
+
184
+ # DAM automatically:
185
+ # 1. Detects git_remote is null
186
+ # 2. Runs: cd /video-projects/v-appydave && git remote get-url origin
187
+ # 3. Finds: git@github.com:klueless-io/v-appydave.git
188
+ # 4. Updates brands.json: "git_remote": "git@github.com:klueless-io/v-appydave.git"
189
+ # 5. Continues with status command
190
+ ```
191
+
192
+ **Acceptance Criteria:**
193
+ - Infers remote URL from existing git repo
194
+ - Auto-saves to brands.json (with backup)
195
+ - Gracefully handles non-git folders (leaves null)
196
+ - Only runs once (subsequent calls use cached value)
197
+
198
+ ---
199
+
200
+ ### Story 4: Push Specific Project Changes
201
+
202
+ **As a content creator,**
203
+ **I want to push changes for a specific project,**
204
+ **So I don't accidentally push unrelated work.**
205
+
206
+ **Current workflow:**
207
+ ```bash
208
+ cd /video-projects/v-appydave/b65-project
209
+ git add .
210
+ git commit -m "Add subtitles for chapter 3"
211
+ git push
212
+ ```
213
+
214
+ **Proposed workflow:**
215
+ ```bash
216
+ # Add and commit done with git (not DAM)
217
+ cd /video-projects/v-appydave/b65-project
218
+ git add *.srt && git commit -m "Add subtitles for chapter 3"
219
+
220
+ # Push via DAM (validates project exists in manifest)
221
+ dam repo-push appydave b65
222
+ ```
223
+
224
+ **Acceptance Criteria:**
225
+ - Resolves project short name (b65 → b65-guy-monroe-marketing-plan)
226
+ - Validates project in manifest
227
+ - Runs `git push` from project directory
228
+ - Shows push result (commits pushed, branch tracking)
229
+
230
+ ---
231
+
232
+ ## Requirements
233
+
234
+ ### Functional Requirements
235
+
236
+ #### FR1: Git Remote Configuration
237
+
238
+ **FR1.1** - Add `git_remote` field to brands.json schema
239
+ - Type: string or null
240
+ - Optional field (can be null for non-git brands)
241
+ - Example: `"git_remote": "git@github.com:klueless-io/v-appydave.git"`
242
+
243
+ **FR1.2** - Self-healing git remote inference
244
+ - If `git_remote` is null, attempt to infer from git command
245
+ - Command: `git -C <brand_path> remote get-url origin`
246
+ - Auto-save inferred value to brands.json (with backup)
247
+ - Gracefully handle non-git folders (leave null, no error)
248
+
249
+ **FR1.3** - Update brands.json documentation
250
+ - Add `git_remote` to example configs
251
+ - Document self-healing behavior
252
+ - Explain null vs URL states
253
+
254
+ #### FR2: S3 Staging Tracking in Manifest
255
+
256
+ **FR2.1** - Add S3 storage to manifest schema
257
+ ```json
258
+ {
259
+ "id": "b65-guy-monroe-marketing-plan",
260
+ "storage": {
261
+ "local": { "exists": true, "structure": "flat", ... },
262
+ "ssd": { "exists": false, "path": null },
263
+ "s3": { "exists": true }
264
+ }
265
+ }
266
+ ```
267
+
268
+ **FR2.2** - Detect S3 staging presence in manifest_generator
269
+ - Check if `s3-staging/` directory exists
270
+ - Update `s3: { exists: true/false }` in manifest
271
+
272
+ **FR2.3** - Update manifest when S3 commands run
273
+ - `dam s3-up` → sets `s3.exists = true` after upload
274
+ - `dam s3-down` → sets `s3.exists = true` after download
275
+ - `dam s3-cleanup-remote` → sets `s3.exists = false` after cleanup
276
+
277
+ #### FR3: Unified Status Command
278
+
279
+ **FR3.1** - Create `dam status [brand] [project]` command
280
+ - Shows local, S3, SSD, and git status in unified view
281
+ - Auto-detects brand/project from PWD if not provided
282
+ - Inferred display (only shows relevant sections)
283
+
284
+ **FR3.2** - Storage status section
285
+ - Local: exists, structure type, file counts (heavy/light)
286
+ - S3: exists, sync status, files needing upload/download
287
+ - SSD: exists, last archived date
288
+
289
+ **FR3.3** - Git status section (live query, not stored)
290
+ - Branch name
291
+ - Remote URL
292
+ - Modified/untracked file counts
293
+ - Commits ahead/behind remote
294
+ - Skip section if not a git repo
295
+
296
+ **FR3.4** - Inferred behavior
297
+ - If no S3 staging folder → skip S3 section
298
+ - If not a git repo → skip git section
299
+ - If no SSD path configured → skip SSD section
300
+
301
+ #### FR4: Git Repository Commands
302
+
303
+ **FR4.1** - `dam repo-status [brand]` - Check git status for brand repos
304
+ - Shows git status for brand directory
305
+ - Option: `--all` to show status for all brands
306
+ - Uses git_remote from brands.json (triggers self-healing if null)
307
+
308
+ **FR4.2** - `dam repo-sync [brand]` - Pull updates for brand repos
309
+ - Runs `git pull` on brand directory
310
+ - Option: `--all` to sync all brands
311
+ - Handles errors (uncommitted changes, merge conflicts)
312
+ - Summary: repos checked, updated, errors
313
+
314
+ **FR4.3** - `dam repo-push [brand] [project]` - Push project changes
315
+ - Resolves project short name (b65 → full name)
316
+ - Validates project exists in manifest
317
+ - Runs `git push` from brand directory
318
+ - Shows commits pushed and branch tracking
319
+
320
+ **FR4.4** - Dynamic brand discovery
321
+ - Use brands.json to get list of brands (no hardcoded arrays)
322
+ - Automatically supports new brands added to config
323
+
324
+ ### Non-Functional Requirements
325
+
326
+ **NFR1: Performance**
327
+ - Status command completes in < 2 seconds for single project
328
+ - repo-sync for all brands completes in < 10 seconds
329
+
330
+ **NFR2: Error Handling**
331
+ - Graceful failures (git not installed, repo not found, network errors)
332
+ - Clear error messages with remediation steps
333
+ - No data loss on config updates (backup system)
334
+
335
+ **NFR3: Backward Compatibility**
336
+ - Existing commands continue to work unchanged
337
+ - brands.json without git_remote field still works (self-healing)
338
+ - Manifest without s3 field still works (regenerate manifest)
339
+
340
+ **NFR4: Documentation**
341
+ - Update usage.md with new commands
342
+ - Update test plan with Phase 4 tests
343
+ - Add examples to PRD and usage guide
344
+
345
+ ---
346
+
347
+ ## Technical Design
348
+
349
+ ### Architecture
350
+
351
+ ```
352
+ ┌─────────────────────────────────────────────────────────────┐
353
+ │ DAM CLI │
354
+ │ (dam status, dam repo-status, dam repo-sync, dam repo-push) │
355
+ └─────────────────────────────────────────────────────────────┘
356
+
357
+ ├── Uses brands.json (git_remote)
358
+ ├── Uses manifest (local/S3/SSD state)
359
+ └── Queries git (live status, not stored)
360
+
361
+ ┌─────────────┬─────────────┬─────────────┬─────────────┐
362
+ │ Local │ S3 │ SSD │ Git │
363
+ │ Storage │ Staging │ Archive │ Repos │
364
+ └─────────────┴─────────────┴─────────────┴─────────────┘
365
+ ```
366
+
367
+ ### Data Model Changes
368
+
369
+ #### brands.json (add git_remote field)
370
+
371
+ **Before:**
372
+ ```json
373
+ {
374
+ "brands": [
375
+ {
376
+ "key": "appydave",
377
+ "shortcut": "ad",
378
+ "name": "AppyDave",
379
+ "youtube_handle": "@appydave",
380
+ "locations": {
381
+ "video_projects": "/Users/davidcruwys/dev/video-projects/v-appydave",
382
+ "ssd_backup": "/Volumes/T7/youtube-PUBLISHED/appydave"
383
+ }
384
+ }
385
+ ]
386
+ }
387
+ ```
388
+
389
+ **After:**
390
+ ```json
391
+ {
392
+ "brands": [
393
+ {
394
+ "key": "appydave",
395
+ "shortcut": "ad",
396
+ "name": "AppyDave",
397
+ "youtube_handle": "@appydave",
398
+ "git_remote": "git@github.com:klueless-io/v-appydave.git",
399
+ "locations": {
400
+ "video_projects": "/Users/davidcruwys/dev/video-projects/v-appydave",
401
+ "ssd_backup": "/Volumes/T7/youtube-PUBLISHED/appydave"
402
+ }
403
+ },
404
+ {
405
+ "key": "voz",
406
+ "shortcut": "voz",
407
+ "name": "VOZ",
408
+ "youtube_handle": "@voz",
409
+ "git_remote": null,
410
+ "locations": {
411
+ "video_projects": "/Users/davidcruwys/dev/video-projects/v-voz",
412
+ "ssd_backup": "NOT-SET"
413
+ }
414
+ }
415
+ ]
416
+ }
417
+ ```
418
+
419
+ **Notes:**
420
+ - `git_remote` is optional (can be null)
421
+ - Self-healing: If null, DAM attempts to infer and populate
422
+ - Non-git brands: Leave as null (valid state)
423
+
424
+ #### projects.json (add S3 staging field)
425
+
426
+ **Before:**
427
+ ```json
428
+ {
429
+ "id": "b65-guy-monroe-marketing-plan",
430
+ "storage": {
431
+ "local": {
432
+ "exists": true,
433
+ "structure": "flat",
434
+ "has_heavy_files": false,
435
+ "has_light_files": true
436
+ },
437
+ "ssd": {
438
+ "exists": false,
439
+ "path": null
440
+ }
441
+ }
442
+ }
443
+ ```
444
+
445
+ **After:**
446
+ ```json
447
+ {
448
+ "id": "b65-guy-monroe-marketing-plan",
449
+ "storage": {
450
+ "local": {
451
+ "exists": true,
452
+ "structure": "flat",
453
+ "has_heavy_files": false,
454
+ "has_light_files": true
455
+ },
456
+ "s3": {
457
+ "exists": true
458
+ },
459
+ "ssd": {
460
+ "exists": false,
461
+ "path": null
462
+ }
463
+ }
464
+ }
465
+ ```
466
+
467
+ **Notes:**
468
+ - `s3.exists` is boolean (true/false)
469
+ - Updated by manifest_generator (checks for s3-staging/ directory)
470
+ - Updated by S3 commands (s3-up, s3-down, s3-cleanup-remote)
471
+
472
+ ### Self-Healing Git Remote Logic
473
+
474
+ **Flow:**
475
+ ```ruby
476
+ def get_git_remote(brand_info)
477
+ # 1. Check brands.json
478
+ return brand_info.git_remote if brand_info.git_remote.present?
479
+
480
+ # 2. Attempt inference
481
+ brand_path = Config.brand_path(brand_info.key)
482
+ remote_url = infer_git_remote(brand_path)
483
+
484
+ # 3. Auto-save if inferred
485
+ if remote_url
486
+ update_brand_git_remote(brand_info.key, remote_url)
487
+ return remote_url
488
+ end
489
+
490
+ # 4. Non-git folder (leave null)
491
+ nil
492
+ end
493
+
494
+ def infer_git_remote(brand_path)
495
+ result = `git -C #{brand_path} remote get-url origin 2>/dev/null`.strip
496
+ result.empty? ? nil : result
497
+ rescue
498
+ nil
499
+ end
500
+
501
+ def update_brand_git_remote(brand_key, remote_url)
502
+ brands_config = Appydave::Tools::Configuration::Config.brands
503
+ brand = brands_config.brands.find { |b| b.key == brand_key }
504
+ brand.git_remote = remote_url
505
+ brands_config.save # Uses backup system
506
+ end
507
+ ```
508
+
509
+ ### Git Status Query (Live, Not Stored)
510
+
511
+ **Why live query?**
512
+ - Git status changes frequently (commits, pulls, edits)
513
+ - Storing in manifest causes staleness issues
514
+ - Query is fast (< 100ms for typical repo)
515
+
516
+ **Implementation:**
517
+ ```ruby
518
+ def git_status(brand_path)
519
+ return nil unless git_repo?(brand_path)
520
+
521
+ {
522
+ branch: current_branch(brand_path),
523
+ remote: remote_url(brand_path),
524
+ modified_files: modified_count(brand_path),
525
+ untracked_files: untracked_count(brand_path),
526
+ ahead: commits_ahead(brand_path),
527
+ behind: commits_behind(brand_path)
528
+ }
529
+ end
530
+
531
+ def current_branch(path)
532
+ `git -C #{path} rev-parse --abbrev-ref HEAD`.strip
533
+ end
534
+
535
+ def commits_ahead(path)
536
+ `git -C #{path} rev-list --count @{upstream}..HEAD 2>/dev/null`.strip.to_i
537
+ end
538
+
539
+ def commits_behind(path)
540
+ `git -C #{path} rev-list --count HEAD..@{upstream} 2>/dev/null`.strip.to_i
541
+ end
542
+ ```
543
+
544
+ ### Unified Status Command Design
545
+
546
+ **Command:** `dam status [brand] [project]`
547
+
548
+ **Output format:**
549
+ ```
550
+ 📊 Status: v-appydave/b65-guy-monroe-marketing-plan
551
+
552
+ Storage:
553
+ 📁 Local: ✓ exists (flat structure)
554
+ Heavy files: no
555
+ Light files: yes (5 SRT, 12 images, 3 docs)
556
+
557
+ ☁️ S3 Staging: ✓ exists
558
+ Files in sync: 3
559
+ Need upload: 2 (15.3 MB)
560
+ Need download: 0
561
+
562
+ 💾 SSD Backup: ✓ exists
563
+ Last archived: 2025-11-08
564
+
565
+ Git:
566
+ 🌿 Branch: main
567
+ 📡 Remote: git@github.com:klueless-io/v-appydave.git
568
+ ↕️ Status: 2 files modified, 1 untracked
569
+ 🔄 Sync: Behind by 0 commits
570
+ ```
571
+
572
+ **Inferred display logic:**
573
+ ```ruby
574
+ def show_status(brand, project)
575
+ manifest = load_manifest(brand)
576
+ project_entry = manifest.projects.find { |p| p.id == project }
577
+
578
+ # Always show local (required)
579
+ show_local_status(project_entry)
580
+
581
+ # Show S3 only if s3-staging exists
582
+ show_s3_status(brand, project) if project_entry.storage.s3.exists
583
+
584
+ # Show SSD only if ssd_backup configured
585
+ show_ssd_status(project_entry) if brand_info.locations.ssd_backup != "NOT-SET"
586
+
587
+ # Show git only if git repo detected
588
+ show_git_status(brand_path) if git_repo?(brand_path)
589
+ end
590
+ ```
591
+
592
+ ---
593
+
594
+ ## Implementation Plan
595
+
596
+ ### Phase 1: Configuration & Manifest (Foundation)
597
+
598
+ **Tasks:**
599
+ 1. Add `git_remote` field to Brand model in Configuration module
600
+ 2. Update brands.json schema documentation
601
+ 3. Implement self-healing git remote inference logic
602
+ 4. Add S3 storage field to Manifest schema
603
+ 5. Update ManifestGenerator to detect S3 staging
604
+ 6. Add tests for config and manifest changes
605
+
606
+ **Deliverables:**
607
+ - Updated brands.json with git_remote field
608
+ - Updated manifest with S3 tracking
609
+ - Self-healing git remote logic working
610
+ - 100% test coverage for new code
611
+
612
+ **Estimated effort:** 4-6 hours
613
+
614
+ ---
615
+
616
+ ### Phase 2: Unified Status Command
617
+
618
+ **Tasks:**
619
+ 1. Create `DamStatus` class
620
+ 2. Implement storage status (local/S3/SSD)
621
+ 3. Implement git status query (live)
622
+ 4. Implement inferred display logic
623
+ 5. Add CLI command `dam status`
624
+ 6. Add tests and documentation
625
+
626
+ **Deliverables:**
627
+ - `dam status [brand] [project]` command working
628
+ - Unified output showing all layers
629
+ - Inferred behavior (skips irrelevant sections)
630
+ - Usage documentation updated
631
+
632
+ **Estimated effort:** 6-8 hours
633
+
634
+ ---
635
+
636
+ ### Phase 3: Git Repository Commands
637
+
638
+ **Tasks:**
639
+ 1. Create `RepoStatus` class
640
+ 2. Implement `dam repo-status [brand]` command
641
+ 3. Create `RepoSync` class
642
+ 4. Implement `dam repo-sync [brand]` command
643
+ 5. Create `RepoPush` class
644
+ 6. Implement `dam repo-push [brand] [project]` command
645
+ 7. Add dynamic brand discovery (use brands.json)
646
+ 8. Add tests and documentation
647
+
648
+ **Deliverables:**
649
+ - `dam repo-status`, `dam repo-sync`, `dam repo-push` commands working
650
+ - Dynamic brand discovery (no hardcoded lists)
651
+ - Error handling for git failures
652
+ - Usage documentation updated
653
+
654
+ **Estimated effort:** 8-10 hours
655
+
656
+ ---
657
+
658
+ ### Phase 4: Testing & Documentation
659
+
660
+ **Tasks:**
661
+ 1. Add Phase 4 to test plan
662
+ 2. Update usage.md with new commands
663
+ 3. Create integration tests
664
+ 4. Test self-healing behavior
665
+ 5. Test error scenarios
666
+ 6. Performance testing (status command < 2s)
667
+
668
+ **Deliverables:**
669
+ - Complete test coverage
670
+ - Updated documentation
671
+ - Performance benchmarks
672
+ - User acceptance testing complete
673
+
674
+ **Estimated effort:** 4-6 hours
675
+
676
+ ---
677
+
678
+ **Total estimated effort:** 22-30 hours
679
+
680
+ ---
681
+
682
+ ## Success Criteria
683
+
684
+ ### Must Have (MVP)
685
+
686
+ 1. ✅ **Unified status command works** - Shows local/S3/SSD/git in single view
687
+ 2. ✅ **Git remote self-healing works** - Auto-populates from git repo if null
688
+ 3. ✅ **S3 tracking in manifest** - projects.json includes S3 staging state
689
+ 4. ✅ **repo-status command works** - Shows git status for brand repos
690
+ 5. ✅ **repo-sync command works** - Pulls updates for brand repos
691
+ 6. ✅ **Dynamic brand discovery** - No hardcoded brand lists
692
+
693
+ ### Should Have
694
+
695
+ 7. ✅ **repo-push command works** - Pushes specific project changes
696
+ 8. ✅ **Inferred display** - Status skips irrelevant sections
697
+ 9. ✅ **Error handling** - Graceful failures with clear messages
698
+ 10. ✅ **Documentation complete** - Usage guide, test plan, PRD updated
699
+
700
+ ### Could Have (Future)
701
+
702
+ 11. ⏳ **repo-clone command** - Clone missing brand repos
703
+ 12. ⏳ **Batch operations** - `dam repo-sync --all` for all brands
704
+ 13. ⏳ **Status filtering** - `dam status --show-modified` (only changed files)
705
+ 14. ⏳ **Git hooks integration** - Auto-update manifest on git push
706
+
707
+ ---
708
+
709
+ ## Open Questions
710
+
711
+ 1. **Should `dam status` default to current project (PWD) or require explicit args?**
712
+ - Option A: Auto-detect from PWD (like s3-up/s3-down)
713
+ - Option B: Require explicit brand/project args
714
+ - **Recommendation:** Option A (consistent with existing commands)
715
+
716
+ 2. **Should repo-push auto-detect uncommitted changes and warn?**
717
+ - Option A: Warn if uncommitted changes detected
718
+ - Option B: Let git handle it (git push won't do anything)
719
+ - **Recommendation:** Option A (better UX)
720
+
721
+ 3. **Should manifest track S3 file-level details (file names, sizes)?**
722
+ - Option A: Boolean only (`s3: { exists: true }`)
723
+ - Option B: File inventory (`s3: { exists: true, files: [...] }`)
724
+ - **Recommendation:** Option A (simpler, avoids staleness)
725
+
726
+ 4. **Should git_remote support multiple remotes (origin, upstream)?**
727
+ - Option A: Single remote only (origin)
728
+ - Option B: Array of remotes
729
+ - **Recommendation:** Option A (YAGNI - simple is better)
730
+
731
+ 5. **What happens if git remote inference finds SSH URL but user needs HTTPS?**
732
+ - Option A: Store whatever is found, let user manually edit
733
+ - Option B: Prompt user to choose SSH vs HTTPS
734
+ - **Recommendation:** Option A (user can edit brands.json if needed)
735
+
736
+ ---
737
+
738
+ ## Risks & Mitigations
739
+
740
+ | Risk | Impact | Mitigation |
741
+ |------|--------|------------|
742
+ | Git not installed on system | High | Check for git binary, show clear error with install instructions |
743
+ | Git remote inference fails | Medium | Leave git_remote as null, document manual config process |
744
+ | Manifest becomes stale (S3 state outdated) | Medium | Update manifest on S3 commands, regenerate if inconsistent |
745
+ | Performance (git status slow for large repos) | Low | Use `--porcelain` flag for faster parsing, timeout after 5s |
746
+ | Breaking changes to brands.json | High | Use backup system, backward compatibility (null git_remote valid) |
747
+
748
+ ---
749
+
750
+ ## Dependencies
751
+
752
+ - **Ruby 3.4.2** - Already in use
753
+ - **Git CLI** - Must be installed on system
754
+ - **brands.json** - Must have valid configuration
755
+ - **Manifest system** - Must be generated (`dam manifest`)
756
+
757
+ ---
758
+
759
+ ## Alternatives Considered
760
+
761
+ ### Alternative 1: Keep git scripts separate (status quo)
762
+
763
+ **Pros:**
764
+ - No new code to write
765
+ - Simple shell scripts, easy to understand
766
+
767
+ **Cons:**
768
+ - Two separate tools (DAM + git scripts)
769
+ - No unified status view
770
+ - Hardcoded brand lists (maintenance burden)
771
+
772
+ **Decision:** Rejected - Integration provides better UX
773
+
774
+ ### Alternative 2: Store git status in manifest
775
+
776
+ **Pros:**
777
+ - Faster status command (no live queries)
778
+ - All data in one place (manifest)
779
+
780
+ **Cons:**
781
+ - Staleness problem (git changes frequently)
782
+ - Manifest updates required after every git operation
783
+ - Complex synchronization logic
784
+
785
+ **Decision:** Rejected - Live queries are fast enough, staleness not worth it
786
+
787
+ ### Alternative 3: Use GitHub API instead of git CLI
788
+
789
+ **Pros:**
790
+ - No git binary required
791
+ - Can check status remotely
792
+
793
+ **Cons:**
794
+ - Requires network connection
795
+ - Requires GitHub authentication
796
+ - Only works for GitHub (not GitLab, Bitbucket, self-hosted)
797
+ - Overkill for simple status checks
798
+
799
+ **Decision:** Rejected - git CLI is simpler and more universal
800
+
801
+ ---
802
+
803
+ ## Appendix: Example Workflows
804
+
805
+ ### Workflow 1: Morning Sync Routine
806
+
807
+ **Scenario:** Team member starts work, needs to sync all repos
808
+
809
+ ```bash
810
+ # Pull updates for all brands
811
+ dam repo-sync appydave
812
+
813
+ # Check unified status for active project
814
+ dam status appydave b65
815
+
816
+ # Work on project...
817
+ cd /video-projects/v-appydave/b65-guy-monroe-marketing-plan
818
+ # Edit subtitles, add images, etc.
819
+
820
+ # Commit changes
821
+ git add *.srt images/
822
+ git commit -m "Add chapter 3 subtitles and thumbnails"
823
+
824
+ # Push via DAM
825
+ dam repo-push appydave b65
826
+ ```
827
+
828
+ ### Workflow 2: Collaboration Handoff
829
+
830
+ **Scenario:** David uploads video files to S3 for Jan to edit
831
+
832
+ ```bash
833
+ # David: Upload raw footage
834
+ cd /video-projects/v-appydave/b65-guy-monroe-marketing-plan
835
+ mkdir -p s3-staging
836
+ cp ~/ecamm/chapter-3-raw.mp4 s3-staging/
837
+ dam s3-up appydave b65
838
+
839
+ # David: Push subtitle updates
840
+ git add *.srt && git commit -m "Add chapter 3 script"
841
+ dam repo-push appydave b65
842
+
843
+ # Jan: Pull git updates and S3 files
844
+ dam repo-sync appydave
845
+ dam s3-down appydave b65
846
+
847
+ # Jan: Check what needs work
848
+ dam status appydave b65
849
+ ```
850
+
851
+ ### Workflow 3: Project Archive & Cleanup
852
+
853
+ **Scenario:** Complete project, archive to SSD, clean up S3
854
+
855
+ ```bash
856
+ # Check final status
857
+ dam status appydave b63
858
+
859
+ # Archive to SSD
860
+ dam archive appydave b63
861
+
862
+ # Push final git state
863
+ dam repo-push appydave b63
864
+
865
+ # Clean up S3 (save storage costs)
866
+ dam s3-cleanup-remote appydave b63 --force
867
+ ```
868
+
869
+ ---
870
+
871
+ **End of PRD**
@@ -117,6 +117,7 @@ module Appydave
117
117
  'type' => 'owned',
118
118
  'youtube_channels' => [],
119
119
  'team' => [],
120
+ 'git_remote' => nil,
120
121
  'locations' => {
121
122
  'video_projects' => '',
122
123
  'ssd_backup' => ''
@@ -144,7 +145,7 @@ module Appydave
144
145
 
145
146
  # Type-safe class to access brand properties
146
147
  class BrandInfo
147
- attr_accessor :key, :name, :shortcut, :type, :youtube_channels, :team, :locations, :aws, :settings
148
+ attr_accessor :key, :name, :shortcut, :type, :youtube_channels, :team, :git_remote, :locations, :aws, :settings
148
149
 
149
150
  def initialize(key, data)
150
151
  @key = key
@@ -153,6 +154,7 @@ module Appydave
153
154
  @type = data['type'] || 'owned'
154
155
  @youtube_channels = data['youtube_channels'] || []
155
156
  @team = data['team'] || []
157
+ @git_remote = data['git_remote']
156
158
  @locations = BrandLocation.new(data['locations'] || {})
157
159
  @aws = BrandAws.new(data['aws'] || {})
158
160
  @settings = BrandSettings.new(data['settings'] || {})
@@ -165,6 +167,7 @@ module Appydave
165
167
  'type' => @type,
166
168
  'youtube_channels' => @youtube_channels,
167
169
  'team' => @team,
170
+ 'git_remote' => @git_remote,
168
171
  'locations' => @locations.to_h,
169
172
  'aws' => @aws.to_h,
170
173
  'settings' => @settings.to_h
@@ -43,6 +43,31 @@ module Appydave
43
43
  path
44
44
  end
45
45
 
46
+ # Get git remote URL for a brand (with self-healing)
47
+ # @param brand_key [String] Brand key (e.g., 'appydave', 'voz')
48
+ # @return [String, nil] Git remote URL or nil if not a git repo
49
+ def git_remote(brand_key)
50
+ Appydave::Tools::Configuration::Config.configure
51
+ brands_config = Appydave::Tools::Configuration::Config.brands
52
+ brand_info = brands_config.get_brand(brand_key)
53
+
54
+ # 1. Check if git_remote is already configured
55
+ return brand_info.git_remote if brand_info.git_remote && !brand_info.git_remote.empty?
56
+
57
+ # 2. Try to infer from git command
58
+ brand_path_dir = brand_path(brand_key)
59
+ inferred_remote = infer_git_remote(brand_path_dir)
60
+
61
+ # 3. Auto-save if inferred successfully
62
+ if inferred_remote
63
+ brand_info.git_remote = inferred_remote
64
+ brands_config.set_brand(brand_info.key, brand_info)
65
+ brands_config.save
66
+ end
67
+
68
+ inferred_remote
69
+ end
70
+
46
71
  # Expand brand shortcut to full brand name
47
72
  # Reads from brands.json if available, falls back to hardcoded shortcuts
48
73
  # @param shortcut [String] Brand shortcut (e.g., 'appydave', 'ad', 'APPYDAVE')
@@ -165,6 +190,19 @@ module Appydave
165
190
  config.save
166
191
  ERROR
167
192
  end
193
+
194
+ # Infer git remote URL from git repository
195
+ # @param path [String] Path to git repository
196
+ # @return [String, nil] Remote URL or nil if not a git repo
197
+ def infer_git_remote(path)
198
+ return nil unless Dir.exist?(path)
199
+
200
+ # Try to get git remote URL
201
+ result = `git -C "#{path}" remote get-url origin 2>/dev/null`.strip
202
+ result.empty? ? nil : result
203
+ rescue StandardError
204
+ nil
205
+ end
168
206
  end
169
207
  end
170
208
  end
@@ -157,6 +157,14 @@ module Appydave
157
157
  'archived'
158
158
  end
159
159
 
160
+ # Check S3 staging (only if local exists)
161
+ s3_staging_path = File.join(local_path, 's3-staging')
162
+ s3_exists = local_exists && Dir.exist?(s3_staging_path)
163
+
164
+ # Check for storyline.json
165
+ storyline_json_path = File.join(local_path, 'data', 'storyline.json')
166
+ has_storyline_json = local_exists && File.exist?(storyline_json_path)
167
+
160
168
  # Check SSD (try both flat and range-based structures)
161
169
  ssd_exists = if ssd_available
162
170
  flat_ssd_path = File.join(ssd_backup, project_id)
@@ -168,11 +176,16 @@ module Appydave
168
176
 
169
177
  {
170
178
  id: project_id,
179
+ type: has_storyline_json ? 'storyline-app' : 'flivideo',
180
+ hasStorylineJson: has_storyline_json,
171
181
  storage: {
172
182
  ssd: {
173
183
  exists: ssd_exists,
174
184
  path: ssd_exists ? project_id : nil
175
185
  },
186
+ s3: {
187
+ exists: s3_exists
188
+ },
176
189
  local: {
177
190
  exists: local_exists,
178
191
  structure: structure,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.18.5'
5
+ VERSION = '0.19.0'
6
6
  end
7
7
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.18.5",
3
+ "version": "0.19.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.5
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
@@ -243,6 +243,7 @@ files:
243
243
  - docs/configuration/settings.example.json
244
244
  - docs/dam/dam-testing-plan.md
245
245
  - docs/dam/dam-vision.md
246
+ - docs/dam/prd-git-integration.md
246
247
  - docs/dam/session-summary-2025-11-09.md
247
248
  - docs/dam/usage.md
248
249
  - docs/dam/windows-testing-guide.md