@sebastienrousseau/dotfiles 0.2.511 → 0.2.512

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +2 -2
  3. package/docs/AI.md +2 -1
  4. package/docs/COPYRIGHT +1 -1
  5. package/docs/architecture/AI_COST_OPTIMIZATION.md +1 -0
  6. package/docs/archive/LEGACY_ROADMAP.md +4 -158
  7. package/docs/manual/00-introduction.md +1 -1
  8. package/docs/manual/02-tutorials/02-add-wallpaper.md +6 -6
  9. package/docs/manual/02-tutorials/03-create-profile.md +1 -1
  10. package/docs/manual/02-tutorials/05-deploy-fleet.md +1 -1
  11. package/docs/manual/03-reference/02-config-files.md +1 -1
  12. package/docs/manual/command-index.md +1 -0
  13. package/docs/operations/ARCHITECTURE_ROADMAP.md +5 -143
  14. package/docs/operations/COVERAGE.md +27 -4
  15. package/docs/operations/ROADMAP.md +3 -159
  16. package/docs/operations/ROADMAP_2026.md +5 -663
  17. package/docs/operations/ROADMAP_V0_2_503.md +5 -129
  18. package/docs/reference/THEMES.md +1 -1
  19. package/docs/reference/TOOLS.md +1 -0
  20. package/docs/reference/UTILS.md +1 -0
  21. package/install.sh +5 -5
  22. package/package.json +1 -1
  23. package/scripts/diagnostics/aliases-manifest.sh +33 -6
  24. package/scripts/diagnostics/benchmark.sh +27 -1
  25. package/scripts/diagnostics/doctor.sh +18 -11
  26. package/scripts/diagnostics/health.sh +8 -3
  27. package/scripts/diagnostics/mcp-doctor.sh +3 -0
  28. package/scripts/diagnostics/secret-governance.sh +7 -1
  29. package/scripts/diagnostics/security-score.sh +14 -5
  30. package/scripts/diagnostics/verify_state.sh +9 -1
  31. package/scripts/dot/commands/ai.sh +37 -30
  32. package/scripts/dot/commands/aliases.sh +28 -4
  33. package/scripts/dot/commands/appearance.sh +16 -0
  34. package/scripts/dot/commands/core.sh +17 -0
  35. package/scripts/dot/commands/diagnostics.sh +19 -0
  36. package/scripts/dot/commands/manual.sh +4 -4
  37. package/scripts/dot/commands/meta.sh +87 -12
  38. package/scripts/dot/commands/registry.sh +1 -1
  39. package/scripts/dot/commands/secrets.sh +17 -0
  40. package/scripts/dot/commands/security.sh +17 -0
  41. package/scripts/dot/commands/tools.sh +19 -0
  42. package/scripts/git-hooks/pre-commit-audit.sh +1 -1
  43. package/scripts/ops/ai-setup.sh +13 -6
  44. package/scripts/ops/bundle.sh +31 -5
  45. package/scripts/ops/chezmoi-apply.sh +5 -0
  46. package/scripts/ops/release.sh +14 -5
  47. package/scripts/qa/docs-coverage.sh +1 -1
  48. package/scripts/qa/reliability-audit.sh +2 -2
  49. package/scripts/secrets/age-init.sh +15 -4
  50. package/scripts/theme/apply-gnome-theme.sh +6 -2
  51. package/scripts/theme/extract-theme.py +97 -33
  52. package/scripts/theme/rebuild-themes.sh +240 -35
  53. package/scripts/theme/switch.sh +26 -16
  54. package/scripts/theme/wallpaper-sync.sh +95 -7
  55. package/scripts/version-sync.sh +81 -44
@@ -78,15 +78,33 @@ fi
78
78
  # Theme Database
79
79
  # =============================================================================
80
80
 
81
- # Default preferences
82
- DEFAULT_DARK="macos-monterey-dark"
83
- DEFAULT_LIGHT="macos-monterey-light"
81
+ # Default preferences (must be present keys in themes.toml — the theme
82
+ # regeneration removed the old macos-monterey-* names; bloom is the current
83
+ # default family and always ships a dark+light pair).
84
+ DEFAULT_DARK="bloom-dark"
85
+ DEFAULT_LIGHT="bloom-light"
86
+
87
+ # Machine-local chezmoi override (chezmoi.toml [data] theme) takes precedence
88
+ # over .chezmoidata.toml when rendering, so it is the authoritative "current"
89
+ # value. Kept in sync by dot-theme-sync's write_theme().
90
+ CHEZMOI_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/chezmoi/chezmoi.toml"
84
91
 
85
92
  # =============================================================================
86
93
  # Theme Functions
87
94
  # =============================================================================
88
95
 
89
96
  current_theme() {
97
+ # Prefer the machine-local override (what chezmoi actually renders), so
98
+ # `dot theme current` never disagrees with the deployed configs. Fall back
99
+ # to the repo default in .chezmoidata.toml when no override is set.
100
+ if [[ -f "$CHEZMOI_CFG" ]]; then
101
+ local override
102
+ override="$(awk -F'"' '/^theme =/ {print $2}' "$CHEZMOI_CFG" | head -n 1)"
103
+ if [[ -n "$override" ]]; then
104
+ echo "$override"
105
+ return 0
106
+ fi
107
+ fi
90
108
  awk -F'"' '/^theme =/ {print $2}' "$DATA_FILE" | head -n 1
91
109
  }
92
110
 
@@ -124,6 +142,9 @@ paired_families() {
124
142
  done < <(all_theme_names)
125
143
 
126
144
  for family in $(printf '%s\n' "${!has_dark[@]}" | sort); do
145
+ # `fallback` is a synthetic safety theme (see themes.toml) that templates
146
+ # degrade to when .theme is unset/invalid — never a user-selectable one.
147
+ [[ "$family" == "fallback" ]] && continue
127
148
  [[ -n "${has_light[$family]+x}" ]] && echo "$family"
128
149
  done
129
150
  }
@@ -185,12 +206,6 @@ set_theme() {
185
206
 
186
207
  # Interactive theme picker
187
208
  pick_theme() {
188
- if ! command -v fzf &>/dev/null; then
189
- ui_err "fzf" "required for interactive picker"
190
- ui_info "Usage" "dot theme set <name>"
191
- exit 1
192
- fi
193
-
194
209
  local current
195
210
  current="$(current_theme)"
196
211
 
@@ -220,14 +235,9 @@ pick_theme() {
220
235
  done < <(paired_families)
221
236
 
222
237
  local selected_family
223
- selected_family="$(echo "$theme_list" | fzf \
238
+ selected_family="$(printf '%s' "$theme_list" | ui_pick \
224
239
  --header "Select wallpaper theme (current: $current_family [$current_mode])" \
225
- --prompt "Theme > " \
226
- --height 30 \
227
- --reverse \
228
- --no-sort \
229
- --no-preview \
230
- --ansi |
240
+ --prompt "Theme >" |
231
241
  awk '$1 !~ /^#/ && NF >= 2 {print $2}')" || return 0
232
242
 
233
243
  if [[ -n "$selected_family" ]]; then
@@ -43,6 +43,16 @@ detect_mode() {
43
43
  esac
44
44
  fi
45
45
 
46
+ if [[ "$(uname -s)" == "Darwin" ]]; then
47
+ # AppleInterfaceStyle is "Dark" in dark mode and unset in light mode.
48
+ if [[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null || true)" == "Dark" ]]; then
49
+ echo "dark"
50
+ else
51
+ echo "light"
52
+ fi
53
+ return 0
54
+ fi
55
+
46
56
  if command -v gsettings &>/dev/null; then
47
57
  local scheme
48
58
  scheme="$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null || echo "")"
@@ -125,10 +135,16 @@ wallpaper_for_theme() {
125
135
  found && /^wallpaper/ { sub(/.*= *"/, ""); sub(/".*/, ""); print; exit }
126
136
  ' "$themes_file")"
127
137
  if [[ -n "$stored_wp" ]]; then
128
- # Cross-platform: resolve macOS paths on Linux
138
+ # Home-relative form (current generator): ~/Pictures/... → $HOME/Pictures/...
139
+ # Prefix-strip comparison avoids a quoted-tilde glob (SC2088); the tilde
140
+ # here is a literal leading character, not a path to expand.
141
+ if [[ "$stored_wp" != "${stored_wp#\~/}" ]]; then
142
+ stored_wp="${HOME}/${stored_wp#\~/}"
143
+ fi
144
+ # Cross-platform: resolve legacy absolute macOS paths on Linux
129
145
  if [[ ! -f "$stored_wp" ]]; then
130
146
  if [[ "$stored_wp" == /Users/* ]]; then
131
- # /Users/<user>/Pictures/... → $HOME/Pictures/...
147
+ # /Users/<user>/Pictures/... → $HOME/Pictures/... (pre-relativize data)
132
148
  stored_wp="${HOME}/${stored_wp#/Users/*/}"
133
149
  elif [[ "$stored_wp" == /System/* ]]; then
134
150
  # macOS system wallpapers don't exist on Linux — skip to next fallback
@@ -397,6 +413,56 @@ ensure_linux_compatible() {
397
413
  printf '%s\n' "$wp"
398
414
  }
399
415
 
416
+ # macOS: a dynamic appearance HEIC (two frames + apple_desktop:apr metadata)
417
+ # set as a plain imageFile gets pinned to a single frame and stops tracking
418
+ # Light/Dark — so Light mode can show the dark frame. Extract the frame that
419
+ # matches the requested mode (0 = light, 1 = dark, per Apple's apr convention,
420
+ # same as the Linux -0/-1 path) to a small single-image HEIC and apply that
421
+ # instead. Single-image HEICs and non-HEICs are returned unchanged.
422
+ macos_appearance_frame() {
423
+ local wp="$1" mode="$2"
424
+ [[ "${wp##*.}" == "heic" ]] || {
425
+ printf '%s\n' "$wp"
426
+ return
427
+ }
428
+ command -v magick &>/dev/null || {
429
+ printf '%s\n' "$wp"
430
+ return
431
+ }
432
+ local frames
433
+ frames="$(magick identify "$wp" 2>/dev/null | wc -l | tr -d ' ')"
434
+ [[ "${frames:-0}" -ge 2 ]] || {
435
+ printf '%s\n' "$wp"
436
+ return
437
+ }
438
+
439
+ local idx=0
440
+ [[ "$mode" == "dark" ]] && idx=1
441
+ local cache_dir="$WALLPAPER_DIR/.dot-frames"
442
+ mkdir -p "$cache_dir" 2>/dev/null || {
443
+ printf '%s\n' "$wp"
444
+ return
445
+ }
446
+ local frame
447
+ frame="$cache_dir/$(basename "${wp%.heic}")-${mode}.heic"
448
+
449
+ # Reuse a cached frame that is newer than its source wallpaper.
450
+ if [[ -f "$frame" && "$frame" -nt "$wp" ]]; then
451
+ printf '%s\n' "$frame"
452
+ return
453
+ fi
454
+ local tmp
455
+ tmp="$(mktemp "${frame%.heic}.XXXXXX.heic")"
456
+ if magick "${wp}[${idx}]" "$tmp" 2>/dev/null && [[ -s "$tmp" ]]; then
457
+ if mv -f "$tmp" "$frame" 2>/dev/null; then
458
+ printf '%s\n' "$frame"
459
+ return
460
+ fi
461
+ fi
462
+ rm -f "$tmp" 2>/dev/null
463
+ printf '%s\n' "$wp"
464
+ }
465
+
400
466
  # Apply wallpaper based on platform and compositor
401
467
  apply_wallpaper() {
402
468
  local wp="$1"
@@ -408,6 +474,10 @@ apply_wallpaper() {
408
474
 
409
475
  case "$(uname -s)" in
410
476
  Darwin)
477
+ # Resolve a dynamic HEIC down to the mode-appropriate static frame so
478
+ # macOS renders the right appearance instead of pinning one frame.
479
+ wp="$(macos_appearance_frame "$wp" "$mode")"
480
+
411
481
  # macOS Sonoma+ moved wallpaper state to ~/Library/Application Support/
412
482
  # com.apple.wallpaper/Store/Index.plist, owned by WallpaperAgent. Each
413
483
  # Space and Display has its own entry, and AppleScript's `every desktop`
@@ -494,12 +564,29 @@ if updated:
494
564
  print(updated)
495
565
  PYEOF
496
566
 
497
- # Restart WallpaperAgent so it re-reads the store. launchd respawns it.
498
- killall WallpaperAgent 2>/dev/null || true
567
+ # Restart WallpaperAgent so it re-reads the store and repaints EVERY
568
+ # Space (the store rewrite above covers all Spaces; the agent only
569
+ # applies it on respawn). Then BLOCK until it's actually back and the
570
+ # wallpaper has re-applied — the caller reports "Done" once this returns,
571
+ # so it must not return while Spaces are still repainting. Set
572
+ # DOT_THEME_SKIP_WALLPAPER_AGENT=1 to skip the restart (faster, but
573
+ # background Spaces won't refresh until next login).
574
+ if [[ "${DOT_THEME_SKIP_WALLPAPER_AGENT:-0}" != "1" ]]; then
575
+ killall WallpaperAgent 2>/dev/null || true
576
+
577
+ # Wait for launchd to bring WallpaperAgent back (KeepAlive respawns it
578
+ # within ~1s; cap the wait so a theme switch can never hang).
579
+ local _waited=0
580
+ while ! pgrep -x WallpaperAgent >/dev/null 2>&1; do
581
+ sleep 0.1
582
+ _waited=$((_waited + 1))
583
+ [[ "$_waited" -ge 60 ]] && break
584
+ done
585
+ fi
499
586
 
500
- # Also nudge the active Space via the public API. This covers the
501
- # initial render before WallpaperAgent comes back, and handles screens
502
- # the Index.plist rewrite somehow missed.
587
+ # Re-assert the wallpaper through the (now running) agent covers the
588
+ # active Space's first paint and any screen the store rewrite missed —
589
+ # then give that paint a moment to finish before returning.
503
590
  if command -v wallpaper &>/dev/null; then
504
591
  wallpaper set "$wp" --screen all 2>/dev/null || true
505
592
  else
@@ -511,6 +598,7 @@ tell application \"System Events\"
511
598
  end repeat
512
599
  end tell" 2>/dev/null || true
513
600
  fi
601
+ sleep 0.5
514
602
  ;;
515
603
  Linux)
516
604
  # DMS owns wallpaper display and runs matugen for color extraction.
@@ -86,7 +86,7 @@ EXCLUDE_FILES=(
86
86
  "docs/operations/ARCHITECTURE_ROADMAP.md"
87
87
 
88
88
  # Dated release write-ups: each article records the release it
89
- # shipped in (e.g. "shipped in v0.2.510" + a link to that release
89
+ # shipped in (e.g. "shipped in vX.Y.Z" + a link to that release
90
90
  # tag). Those refs are historical fact, not current-version claims —
91
91
  # bumping them would falsify the history.
92
92
  "docs/articles/2026-07-05-fish-startup-abbr.md"
@@ -156,7 +156,11 @@ get_package_version() {
156
156
  fi
157
157
 
158
158
  local version
159
- version=$(jq -r '.version' "$package_file" 2>/dev/null)
159
+ if command -v jq &>/dev/null; then
160
+ version=$(jq -r '.version' "$package_file" 2>/dev/null)
161
+ else
162
+ version=$(sed -nE 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$package_file" | head -n 1)
163
+ fi
160
164
  if [[ "$version" == "null" || -z "$version" ]]; then
161
165
  log_error "Could not extract version from package.json"
162
166
  exit 1
@@ -186,22 +190,32 @@ find_version_files() {
186
190
  # the paths match the EXCLUDE_FILES entries (which have no "./"),
187
191
  # otherwise excluded historical docs get rewritten.
188
192
  cd "$PROJECT_ROOT"
189
- rg -l "v?$VERSION_PATTERN" --type md . 2>/dev/null | sed 's|^\./||' >"$temp_file" || true
193
+ if command -v rg &>/dev/null; then
194
+ rg -l "v?$VERSION_PATTERN" --type md . 2>/dev/null | sed 's|^\./||' >"$temp_file" || true
195
+ else
196
+ {
197
+ while IFS= read -r file; do
198
+ if grep -Eq "v?$VERSION_PATTERN" "$file" 2>/dev/null; then
199
+ printf '%s\n' "${file#./}"
200
+ fi
201
+ done < <(find . -type f -name '*.md' -print)
202
+ } >"$temp_file"
203
+ fi
190
204
 
191
205
  # Add known files that should be checked even if they don't have versions yet
192
- echo "README.md" >>"$temp_file"
193
- echo "docs/reference/FEATURES.md" >>"$temp_file"
194
- echo "docs/COPYRIGHT" >>"$temp_file"
195
-
196
206
  # Non-markdown and hidden-directory surfaces rg's `--type md` scan above
197
207
  # cannot reach: a shell script, and READMEs under the dotfile-hidden
198
208
  # `.chezmoitemplates/` tree (rg skips dot-dirs without --hidden). These
199
209
  # carry "current version" stamps that check-version-consistency.sh and
200
210
  # the test_version_consistency unit test enforce, so keep them in sync.
201
- echo "scripts/git-hooks/pre-commit-audit.sh" >>"$temp_file"
202
- echo "defaults/.chezmoitemplates/README.md" >>"$temp_file"
203
- echo "defaults/.chezmoitemplates/functions/README.md" >>"$temp_file"
204
- echo "defaults/.chezmoitemplates/aliases/README.md" >>"$temp_file"
211
+ printf '%s\n' \
212
+ "README.md" \
213
+ "docs/reference/FEATURES.md" \
214
+ "docs/COPYRIGHT" \
215
+ "scripts/git-hooks/pre-commit-audit.sh" \
216
+ "defaults/.chezmoitemplates/README.md" \
217
+ "defaults/.chezmoitemplates/functions/README.md" \
218
+ "defaults/.chezmoitemplates/aliases/README.md" >>"$temp_file"
205
219
 
206
220
  # Remove duplicates and filter existing files
207
221
  sort -u "$temp_file" | while IFS= read -r file; do
@@ -268,6 +282,7 @@ update_version_references() {
268
282
  log_warning "File not found: $file"
269
283
  continue
270
284
  fi
285
+ files_processed=$((files_processed + 1))
271
286
 
272
287
  # Skip milestone and other historical files
273
288
  if [[ "$(basename "$file")" == MILESTONE_* ]]; then
@@ -328,6 +343,7 @@ update_version_references() {
328
343
  cat "$temp_file" >"$file"
329
344
  log_success "Updated: $file"
330
345
  fi
346
+ rm -f "$temp_file"
331
347
  changes_made=$((changes_made + 1))
332
348
  else
333
349
  log_info "No changes needed: $file"
@@ -367,26 +383,49 @@ verify_version_consistency() {
367
383
 
368
384
  # Extract only dotfiles-targeted version references (ignore unrelated tool versions).
369
385
  local versions_in_file=()
370
- while IFS= read -r match; do
371
- while IFS= read -r version; do
372
- versions_in_file+=("$version")
373
- done < <(printf "%s\n" "$match" | rg -o "v?$VERSION_PATTERN" || true)
374
- done < <(
375
- rg -v "MILESTONE" "$file" 2>/dev/null | rg -o \
376
- -e "Version-v$VERSION_PATTERN" \
377
- -e "/releases/tag/v$VERSION_PATTERN" \
378
- -e "/dotfiles/v$VERSION_PATTERN/" \
379
- -e "\\*\\*Version\\*\\*:[[:space:]]*v?$VERSION_PATTERN" \
380
- -e "\\*\\*Dotfiles Version\\*\\*:[[:space:]]*v?$VERSION_PATTERN" \
381
- -e "(^|[[:space:]])Version:[[:space:]]*v?$VERSION_PATTERN" \
382
- -e "(^|[[:space:]])Dotfiles Version:[[:space:]]*v?$VERSION_PATTERN" \
383
- -e "Version[[:space:]]*\`v?$VERSION_PATTERN\`" \
384
- -e "\\(v$VERSION_PATTERN\\)" \
385
- -e "/v$VERSION_PATTERN/" \
386
- -e "v$VERSION_PATTERN\\b" \
387
- -e "dotfiles:v?$VERSION_PATTERN" \
388
- -e "notes — v$VERSION_PATTERN" || true
389
- )
386
+ if command -v rg &>/dev/null; then
387
+ while IFS= read -r match; do
388
+ while IFS= read -r version; do
389
+ versions_in_file+=("$version")
390
+ done < <(printf "%s\n" "$match" | rg -o "v?$VERSION_PATTERN" || true)
391
+ done < <(
392
+ rg -v "MILESTONE" "$file" 2>/dev/null | rg -o \
393
+ -e "Version-v$VERSION_PATTERN" \
394
+ -e "/releases/tag/v$VERSION_PATTERN" \
395
+ -e "/dotfiles/v$VERSION_PATTERN/" \
396
+ -e "\\*\\*Version\\*\\*:[[:space:]]*v?$VERSION_PATTERN" \
397
+ -e "\\*\\*Dotfiles Version\\*\\*:[[:space:]]*v?$VERSION_PATTERN" \
398
+ -e "(^|[[:space:]])Version:[[:space:]]*v?$VERSION_PATTERN" \
399
+ -e "(^|[[:space:]])Dotfiles Version:[[:space:]]*v?$VERSION_PATTERN" \
400
+ -e "Version[[:space:]]*\`v?$VERSION_PATTERN\`" \
401
+ -e "\\(v$VERSION_PATTERN\\)" \
402
+ -e "/v$VERSION_PATTERN/" \
403
+ -e "v$VERSION_PATTERN\\b" \
404
+ -e "dotfiles:v?$VERSION_PATTERN" \
405
+ -e "notes — v$VERSION_PATTERN" || true
406
+ )
407
+ else
408
+ while IFS= read -r match; do
409
+ while IFS= read -r version; do
410
+ versions_in_file+=("$version")
411
+ done < <(printf "%s\n" "$match" | grep -Eo "v?$VERSION_PATTERN" || true)
412
+ done < <(
413
+ grep -Ev "MILESTONE" "$file" 2>/dev/null | grep -Eo \
414
+ -e "Version-v$VERSION_PATTERN" \
415
+ -e "/releases/tag/v$VERSION_PATTERN" \
416
+ -e "/dotfiles/v$VERSION_PATTERN/" \
417
+ -e "\\*\\*Version\\*\\*:[[:space:]]*v?$VERSION_PATTERN" \
418
+ -e "\\*\\*Dotfiles Version\\*\\*:[[:space:]]*v?$VERSION_PATTERN" \
419
+ -e "(^|[[:space:]])Version:[[:space:]]*v?$VERSION_PATTERN" \
420
+ -e "(^|[[:space:]])Dotfiles Version:[[:space:]]*v?$VERSION_PATTERN" \
421
+ -e "Version[[:space:]]*\`v?$VERSION_PATTERN\`" \
422
+ -e "\\(v$VERSION_PATTERN\\)" \
423
+ -e "/v$VERSION_PATTERN/" \
424
+ -e "v$VERSION_PATTERN([^0-9.]|$)" \
425
+ -e "dotfiles:v?$VERSION_PATTERN" \
426
+ -e "notes — v$VERSION_PATTERN" || true
427
+ )
428
+ fi
390
429
 
391
430
  if [[ ${#versions_in_file[@]} -eq 0 ]]; then
392
431
  continue
@@ -457,6 +496,12 @@ main() {
457
496
  esac
458
497
  done
459
498
 
499
+ if [[ -n "${DOTFILES_COV_TMPDIR:-}" && "${DOTFILES_ALLOW_COVERAGE_WRITES:-0}" != "1" ]]; then
500
+ dry_run="true"
501
+ create_backup_flag="false"
502
+ log_info "Coverage sandbox detected; forcing --dry-run"
503
+ fi
504
+
460
505
  # Change to project root
461
506
  cd "$PROJECT_ROOT"
462
507
 
@@ -470,8 +515,11 @@ main() {
470
515
  fi
471
516
 
472
517
  # Find files with version references
473
- local version_files
474
- readarray -t version_files < <(find_version_files)
518
+ local version_files=()
519
+ local version_file
520
+ while IFS= read -r version_file; do
521
+ version_files+=("$version_file")
522
+ done < <(find_version_files)
475
523
 
476
524
  if [[ ${#version_files[@]} -eq 0 ]]; then
477
525
  log_warning "No files with version references found"
@@ -574,16 +622,5 @@ EOF
574
622
  fi
575
623
  }
576
624
 
577
- # Ensure we have required tools
578
- if ! command -v jq &>/dev/null; then
579
- log_warning "jq is not installed — skipping version sync"
580
- exit 0
581
- fi
582
-
583
- if ! command -v rg &>/dev/null; then
584
- log_warning "ripgrep (rg) is not installed — skipping version sync"
585
- exit 0
586
- fi
587
-
588
625
  # Run main function
589
626
  main "$@"