From 505e6ae0843808c98bcfa94b3d994e88b0838886 Mon Sep 17 00:00:00 2001 From: Julian lechner Date: Thu, 8 Oct 2026 12:10:19 +0200 Subject: [PATCH] feat(install): ask for QMD local search models and run the QMD installer Co-Authored-By: Claude Opus 5.5 --- scripts/commands/install.ps1 | 4 ++-- scripts/commands/install.sh | 4 ++-- scripts/commands/uninstall.ps1 | 6 +++--- scripts/commands/uninstall.sh | 6 +++--- scripts/commands/update.ps1 | 4 ++-- scripts/commands/update.sh | 4 ++-- scripts/lib/install-options.ps1 | 4 ++-- scripts/lib/install-options.sh | 4 ++-- scripts/tests/test-install-guards.ps1 | 16 ++++++++++++++++ scripts/tests/test-install-guards.sh | 14 ++++++++++++++ 10 files changed, 48 insertions(+), 18 deletions(-) diff --git a/scripts/commands/install.ps1 b/scripts/commands/install.ps1 index aee5507..aa117af 100644 --- a/scripts/commands/install.ps1 +++ b/scripts/commands/install.ps1 @@ -12,7 +12,7 @@ $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent . (Join-Path $root 'scripts/lib/install-targets.ps1') . (Join-Path $root 'scripts/lib/selection-state.ps1') . (Join-Path $root 'scripts/lib/install-options.ps1') -. (Join-Path $root 'scripts/lib/semantic-retrieval.ps1') +. (Join-Path $root 'scripts/lib/qmd.ps1') $Shell = Read-InstallShell -Shell $Shell $shell = $Shell.ToLowerInvariant() @@ -44,8 +44,8 @@ foreach ($item in (Get-InstallTargets -Generated $generated -HomePath $homePath -AiConfigRoot $item.Destination.Replace('\','/') -ShellCommand $shellCommand -PowerShellCommand $powerShellCommand -ClaudeConcurrency $claudeConcurrency -FlashbangEnabled $options.flashbang -StatusLineEnabled $options.statusline -SemanticRetrievalEnabled $options.semantic_retrieval -DryRun:$DryRun -Summary:$Summary } +Sync-Qmd -RepositoryRoot $root -HomePath $homePath -Client $Client -Enabled $options.semantic_retrieval -DryRun:$DryRun -Summary:$Summary Sync-ConfiguredPlugins -RepositoryRoot $root -HomePath $homePath -Client $Client -DryRun:$DryRun -Summary:$Summary -Entries $plugins.Selected -Sync-SemanticRetrieval -RepositoryRoot $root -HomePath $homePath -Client $Client -Enabled $options.semantic_retrieval -DryRun:$DryRun -Summary:$Summary if (-not $DryRun) { Save-UpdateSelection -HomePath $homePath -RepositoryRoot $root -Shell $Shell -Client $Client -Plugins $plugins -Flashbang $options.flashbang -StatusLine $options.statusline -SemanticRetrieval $options.semantic_retrieval } if ($Summary) { Write-Output "Install: PASS | $Client, $Shell$(if ($DryRun) { ', dry-run' })" } else { Write-Output ($(if ($DryRun) { 'PASS install dry-run' } else { 'PASS install' })) } exit 0 diff --git a/scripts/commands/install.sh b/scripts/commands/install.sh index e160cec..b59b370 100644 --- a/scripts/commands/install.sh +++ b/scripts/commands/install.sh @@ -21,7 +21,7 @@ root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) . "$root/scripts/lib/install-targets.sh" . "$root/scripts/lib/selection-state.sh" . "$root/scripts/lib/install-options.sh" -. "$root/scripts/lib/semantic-retrieval.sh" +. "$root/scripts/lib/qmd.sh" shell=$(read_install_shell "$shell") client=$(read_install_client "$client") @@ -61,8 +61,8 @@ while IFS='|' read -r source destination; do sync_managed_destination "$source" "$destination" "$stamp" "$ai_config_root" "$shell_command" "$powershell_command" "$dry_run" "$summary" "$flashbang" "$claude_concurrency" "$statusline" "$semantic_retrieval" done < <(get_install_targets "$generated" "$home_path" "$shell" "$client") +sync_qmd "$root" "$client" "$home_path" "$semantic_retrieval" "$dry_run" false "$summary" sync_configured_plugins "$root" "$client" "$home_path" "$dry_run" false selected_plugins "$summary" -sync_semantic_retrieval "$root" "$client" "$home_path" "$semantic_retrieval" "$dry_run" false "$summary" if [ "$dry_run" = false ]; then save_update_selection; fi if [ "$summary" = true ]; then diff --git a/scripts/commands/uninstall.ps1 b/scripts/commands/uninstall.ps1 index fb84c26..71410b3 100644 --- a/scripts/commands/uninstall.ps1 +++ b/scripts/commands/uninstall.ps1 @@ -10,7 +10,7 @@ $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent . (Join-Path $root 'scripts/lib/plugins.ps1') . (Join-Path $root 'scripts/lib/manifest.ps1') . (Join-Path $root 'scripts/lib/install-targets.ps1') -. (Join-Path $root 'scripts/lib/semantic-retrieval.ps1') +. (Join-Path $root 'scripts/lib/qmd.ps1') $Client = Read-InstallClient -Client $Client $homePath = [Environment]::GetFolderPath('UserProfile') @@ -24,7 +24,7 @@ if (-not $DryRun -and -not $Force) { if ([Console]::IsInputRedirected -or $env:CI -eq 'true' -or $env:AI_CONFIG_NO_INTERACTIVE -eq '1') { throw 'Refusing to uninstall without confirmation in a non-interactive session. Pass -Force to proceed.' } - Write-Host "This removes the managed configuration, extensions, and semantic retrieval setup for $Client under $homePath." + Write-Host "This removes the managed configuration, extensions, and QMD local search setup for $Client under $homePath." Write-Host 'Backups already on disk are kept; anything this setup never installed is left untouched.' $confirm = Read-Host -Prompt 'Continue? [y/N]' if ($null -eq $confirm) { $confirm = '' } @@ -57,7 +57,7 @@ try { } Sync-ConfiguredPlugins -RepositoryRoot $root -HomePath $homePath -Client $Client -DryRun:$DryRun -Summary:$Summary -Entries @() -Sync-SemanticRetrieval -RepositoryRoot $root -HomePath $homePath -Client $Client -Enabled $false -DryRun:$DryRun -Summary:$Summary +Sync-Qmd -RepositoryRoot $root -HomePath $homePath -Client $Client -Enabled $false -DryRun:$DryRun -Summary:$Summary if (-not $DryRun) { $remaining = @($targets | Where-Object { Test-Path -LiteralPath (Get-ManagedManifestPath $_.Destination) }) diff --git a/scripts/commands/uninstall.sh b/scripts/commands/uninstall.sh index 26fe5b2..d4658cf 100644 --- a/scripts/commands/uninstall.sh +++ b/scripts/commands/uninstall.sh @@ -19,7 +19,7 @@ root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) . "$root/scripts/lib/plugins.sh" . "$root/scripts/lib/manifest.sh" . "$root/scripts/lib/install-targets.sh" -. "$root/scripts/lib/semantic-retrieval.sh" +. "$root/scripts/lib/qmd.sh" client=$(read_install_client "$client") home_path=${HOME:?HOME is required} @@ -40,7 +40,7 @@ if [ "$dry_run" = false ] && [ "$force" = false ]; then printf 'Refusing to uninstall without confirmation in a non-interactive session. Pass --force to proceed.\n' >&2 exit 1 fi - printf 'This removes the managed configuration, extensions, and semantic retrieval setup for %s under %s.\n' "$client" "$home_path" + printf 'This removes the managed configuration, extensions, and QMD local search setup for %s under %s.\n' "$client" "$home_path" printf 'Backups already on disk are kept; anything this setup never installed is left untouched.\n' read -r -p "Continue? [y/N]: " confirm || confirm="" case "$confirm" in @@ -75,7 +75,7 @@ done # shellcheck disable=SC2034 selected_plugins=() sync_configured_plugins "$root" "$client" "$home_path" "$dry_run" false selected_plugins "$summary" -sync_semantic_retrieval "$root" "$client" "$home_path" false "$dry_run" false "$summary" +sync_qmd "$root" "$client" "$home_path" false "$dry_run" false "$summary" if [ "$dry_run" = false ]; then remaining=0 diff --git a/scripts/commands/update.ps1 b/scripts/commands/update.ps1 index 9412c0b..fb6c30f 100644 --- a/scripts/commands/update.ps1 +++ b/scripts/commands/update.ps1 @@ -13,7 +13,7 @@ $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent . (Join-Path $root 'scripts/lib/install-targets.ps1') . (Join-Path $root 'scripts/lib/selection-state.ps1') . (Join-Path $root 'scripts/lib/install-options.ps1') -. (Join-Path $root 'scripts/lib/semantic-retrieval.ps1') +. (Join-Path $root 'scripts/lib/qmd.ps1') $homePath = [Environment]::GetFolderPath('UserProfile') if ($Quick) { @@ -54,6 +54,7 @@ foreach ($item in (Get-InstallTargets -Generated $generated -HomePath $homePath -AiConfigRoot $item.Destination.Replace('\','/') -ShellCommand $shellCommand -PowerShellCommand $powerShellCommand -FlashbangEnabled $options.flashbang -StatusLineEnabled $options.statusline -SemanticRetrievalEnabled $options.semantic_retrieval -DryRun:$DryRun -Summary:$Summary } +Sync-Qmd -RepositoryRoot $root -HomePath $homePath -Client $Client -Enabled $options.semantic_retrieval -DryRun:$DryRun -Update -Summary:$Summary $previousPluginNonInteractive = $script:PluginNonInteractive $previousCI = $env:CI try { @@ -64,7 +65,6 @@ try { $script:PluginNonInteractive = $previousPluginNonInteractive $env:CI = $previousCI } -Sync-SemanticRetrieval -RepositoryRoot $root -HomePath $homePath -Client $Client -Enabled $options.semantic_retrieval -DryRun:$DryRun -Update -Summary:$Summary if (-not $DryRun) { Save-UpdateSelection -HomePath $homePath -RepositoryRoot $root -Shell $Shell -Client $Client -Plugins $plugins -Flashbang $options.flashbang -StatusLine $options.statusline -SemanticRetrieval $options.semantic_retrieval } if ($Summary) { Write-Output "Update: PASS | $Client, $Shell$(if ($DryRun) { ', dry-run' })" } else { Write-Output ($(if ($DryRun) { 'PASS update dry-run' } else { 'PASS update' })) } exit 0 diff --git a/scripts/commands/update.sh b/scripts/commands/update.sh index e6b7930..a0ed139 100644 --- a/scripts/commands/update.sh +++ b/scripts/commands/update.sh @@ -23,7 +23,7 @@ root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) . "$root/scripts/lib/install-targets.sh" . "$root/scripts/lib/selection-state.sh" . "$root/scripts/lib/install-options.sh" -. "$root/scripts/lib/semantic-retrieval.sh" +. "$root/scripts/lib/qmd.sh" home_path=${HOME:?HOME is required} selected_plugins=() @@ -66,12 +66,12 @@ while IFS='|' read -r source destination; do sync_managed_destination "$source" "$destination" "$stamp" "$ai_config_root" "$shell_command" "$powershell_command" "$dry_run" "$summary" "$flashbang" 5 "$statusline" "$semantic_retrieval" done < <(get_install_targets "$generated" "$home_path" "$shell" "$client") +sync_qmd "$root" "$client" "$home_path" "$semantic_retrieval" "$dry_run" true "$summary" if [ "$quick" = true ]; then CI=true sync_configured_plugins "$root" "$client" "$home_path" "$dry_run" true selected_plugins "$summary" &2 + printf 'Enable local QMD search models (Qwen3 embedding, Qwen3 reranker, QMD query expansion)? [%s] ' "$hint" >&2 IFS= read -r answer || { printf 'Input ended before semantic retrieval was confirmed.\n' >&2; return 1; } answer=${answer%$'\r'} case "$answer" in @@ -49,7 +49,7 @@ read_semantic_retrieval_option() { y|Y|yes|Yes|YES) printf 'true\n'; return ;; n|N|no|No|NO) printf 'false\n'; return ;; a|A|auto|Auto|AUTO) - if test_semantic_retrieval_device "$root" "$home_path"; then printf 'true\n'; return; else status=$?; fi + if test_qmd_device "$root" "$home_path"; then printf 'true\n'; return; else status=$?; fi [ "$status" -eq 1 ] && { printf 'false\n'; return; } return "$status" ;; diff --git a/scripts/tests/test-install-guards.ps1 b/scripts/tests/test-install-guards.ps1 index c7212f4..faf6a60 100644 --- a/scripts/tests/test-install-guards.ps1 +++ b/scripts/tests/test-install-guards.ps1 @@ -17,6 +17,22 @@ try { if (Test-AllManifestsPresent @($installed, $missing)) { throw 'Update guard must fail when any selected destination is not installed.' } if (-not (Test-AllManifestsPresent @($installed))) { throw 'Update guard must pass when every selected destination is installed.' } + # QMD prompt: Auto maps device suitability; No never calls the benchmark. + . (Join-Path $root 'scripts/lib/install-options.ps1') + $originalIn = [Console]::In + try { + function Test-QmdDevice { param($RepositoryRoot, $HomePath) return $false } + [Console]::SetIn([IO.StringReader]::new("a`n")) + if ((Read-SemanticRetrievalOption -Default $false -RepositoryRoot $root -HomePath $work) -ne $false) { throw 'Auto unsuitable must yield false' } + function Test-QmdDevice { param($RepositoryRoot, $HomePath) return $true } + [Console]::SetIn([IO.StringReader]::new("a`n")) + if ((Read-SemanticRetrievalOption -Default $false -RepositoryRoot $root -HomePath $work) -ne $true) { throw 'Auto suitable must yield true' } + function Test-QmdDevice { throw 'must not run' } + [Console]::SetIn([IO.StringReader]::new("n`n")) + if ((Read-SemanticRetrievalOption -Default $true -RepositoryRoot $root -HomePath $work) -ne $false) { throw 'No must yield false' } + } finally { [Console]::SetIn($originalIn) } + if ((Get-Content -Raw (Join-Path $root 'scripts/lib/install-options.ps1')) -notmatch 'Enable local QMD search models \(Qwen3 embedding, Qwen3 reranker, QMD query expansion\)\? \[') { throw 'prompt text changed' } + if ($Summary) { Write-Output 'Tests: PASS | install guards' } else { Write-Output 'PASS install guards: already-installed and not-installed detection' } } finally { Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue diff --git a/scripts/tests/test-install-guards.sh b/scripts/tests/test-install-guards.sh index e87d562..716bfde 100644 --- a/scripts/tests/test-install-guards.sh +++ b/scripts/tests/test-install-guards.sh @@ -25,4 +25,18 @@ any_manifest_present "$installed" "$missing" || { printf 'Expected an installed ! all_manifests_present "$installed" "$missing" || { printf 'Update guard must fail when any selected destination is not installed.\n' >&2; exit 1; } all_manifests_present "$installed" || { printf 'Update guard must pass when every selected destination is installed.\n' >&2; exit 1; } +# QMD prompt: Auto maps benchmark exit codes; No never calls the benchmark. +( + home_path=$(mktemp -d) + . "$root/scripts/lib/install-options.sh" + test_qmd_device() { return 1; } + [ "$(printf 'a\n' | read_semantic_retrieval_option false 2>/dev/null)" = false ] || { echo 'Auto unsuitable must yield false' >&2; exit 1; } + test_qmd_device() { return 0; } + [ "$(printf 'a\n' | read_semantic_retrieval_option false 2>/dev/null)" = true ] || { echo 'Auto suitable must yield true' >&2; exit 1; } + test_qmd_device() { echo 'must not run' >&2; exit 9; } + [ "$(printf 'n\n' | read_semantic_retrieval_option true 2>/dev/null)" = false ] || { echo 'No must yield false' >&2; exit 1; } + printf 'n\n' | read_semantic_retrieval_option true 2>&1 >/dev/null | grep -q 'Enable local QMD search models' || { echo 'prompt text changed' >&2; exit 1; } + rm -rf -- "$home_path" +) + if [ "$summary" = true ]; then printf 'Tests: PASS | install guards\n'; else printf 'PASS install guards: already-installed and not-installed detection\n'; fi