From bfbe001248e576979b65b5973efc38506159eaea Mon Sep 17 00:00:00 2001 From: Julian lechner Date: Thu, 17 Sep 2026 09:16:05 +0200 Subject: [PATCH] feat(selection): persist retrieval preference --- scripts/lib/install-options.ps1 | 3 ++- scripts/lib/install-options.sh | 4 +++- scripts/lib/selection-state.ps1 | 4 ++-- scripts/lib/selection-state.py | 8 ++++++-- scripts/lib/selection-state.sh | 3 ++- scripts/test-install-options.py | 2 +- scripts/test-quick-update.py | 8 ++++++++ 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/scripts/lib/install-options.ps1 b/scripts/lib/install-options.ps1 index 6526ee0..8953af0 100644 --- a/scripts/lib/install-options.ps1 +++ b/scripts/lib/install-options.ps1 @@ -15,7 +15,7 @@ function Read-InstallBoolean { function Read-InstallOptions { param([string]$HomePath, [string]$RepositoryRoot, [string]$Client, [switch]$DryRun) - $defaults = @{ update_agents = $false; flashbang = $true } + $defaults = @{ update_agents = $false; flashbang = $true; semantic_retrieval = $false } if (Test-Path -LiteralPath (Join-Path $HomePath '.my-ai-configuration/selection.json')) { try { $defaults = Read-UpdateSelection -HomePath $HomePath -RepositoryRoot $RepositoryRoot -AllowLegacy } catch { Write-Warning 'Saved options could not be read; confirm new options below.' } @@ -24,6 +24,7 @@ function Read-InstallOptions { return @{ update_agents = Read-InstallBoolean -Prompt "Update selected agent CLIs ($Client)?" -Default $defaults.update_agents flashbang = Read-InstallBoolean -Prompt 'Enable the Flashbang notification hook?' -Default $defaults.flashbang + semantic_retrieval = Read-InstallBoolean -Prompt 'Enable Qwen3-Embedding-0.6B semantic retrieval?' -Default $defaults.semantic_retrieval } } diff --git a/scripts/lib/install-options.sh b/scripts/lib/install-options.sh index d953b61..78699cf 100644 --- a/scripts/lib/install-options.sh +++ b/scripts/lib/install-options.sh @@ -18,12 +18,13 @@ read_install_boolean() { read_install_options() { update_agents=false flashbang=true + semantic_retrieval=false local saved key value if [ -f "$home_path/.my-ai-configuration/selection.json" ]; then if saved=$(python3 "$root/scripts/lib/selection-state.py" read --home "$home_path" --manifest "$root/adapters/plugins.tsv" --format tsv --allow-legacy); then while IFS=$'\t' read -r key value; do value=${value%$'\r'} - case "$key" in update_agents) update_agents=$value ;; flashbang) flashbang=$value ;; esac + case "$key" in update_agents) update_agents=$value ;; flashbang) flashbang=$value ;; semantic_retrieval) semantic_retrieval=$value ;; esac done <<< "$saved" else printf 'WARN Saved options could not be read; confirm new options below.\n' >&2 @@ -32,6 +33,7 @@ read_install_options() { [ "$dry_run" = false ] || return 0 update_agents=$(read_install_boolean "Update selected agent CLIs ($client)?" "$update_agents") || return flashbang=$(read_install_boolean 'Enable the Flashbang notification hook?' "$flashbang") || return + semantic_retrieval=$(read_install_boolean 'Enable Qwen3-Embedding-0.6B semantic retrieval?' "$semantic_retrieval") || return } is_windows_host() { diff --git a/scripts/lib/selection-state.ps1 b/scripts/lib/selection-state.ps1 index 4f3be44..6d16b56 100644 --- a/scripts/lib/selection-state.ps1 +++ b/scripts/lib/selection-state.ps1 @@ -9,8 +9,8 @@ function Read-UpdateSelection { function Save-UpdateSelection { param([string]$HomePath, [string]$RepositoryRoot, [string]$Shell, [string]$Client, [hashtable]$Plugins, - [Parameter(Mandatory=$true)][bool]$UpdateAgents, [Parameter(Mandatory=$true)][bool]$Flashbang) - $arguments = @((Join-Path $PSScriptRoot 'selection-state.py'), 'write', '--home', $HomePath, '--manifest', (Join-Path $RepositoryRoot 'adapters/plugins.tsv'), '--shell', $Shell.ToLowerInvariant(), '--client', $Client.ToLowerInvariant(), '--update-agents', $UpdateAgents.ToString().ToLowerInvariant(), '--flashbang', $Flashbang.ToString().ToLowerInvariant(), '--selected') + [Parameter(Mandatory=$true)][bool]$UpdateAgents, [Parameter(Mandatory=$true)][bool]$Flashbang, [bool]$SemanticRetrieval = $false) + $arguments = @((Join-Path $PSScriptRoot 'selection-state.py'), 'write', '--home', $HomePath, '--manifest', (Join-Path $RepositoryRoot 'adapters/plugins.tsv'), '--shell', $Shell.ToLowerInvariant(), '--client', $Client.ToLowerInvariant(), '--update-agents', $UpdateAgents.ToString().ToLowerInvariant(), '--flashbang', $Flashbang.ToString().ToLowerInvariant(), '--semantic-retrieval', $SemanticRetrieval.ToString().ToLowerInvariant(), '--selected') $arguments += @($Plugins.Selected | ForEach-Object { $_.name }) $arguments += '--deselected' $arguments += @($Plugins.Deselected | ForEach-Object { $_.name }) diff --git a/scripts/lib/selection-state.py b/scripts/lib/selection-state.py index 8b4a0b9..60324a2 100644 --- a/scripts/lib/selection-state.py +++ b/scripts/lib/selection-state.py @@ -18,7 +18,9 @@ def validate(state, allow_legacy=False): raise ValueError('Unsupported saved selection.') if state.get('shell') not in ('powershell', 'bash') or state.get('client') not in ('codex', 'claude', 'both'): raise ValueError('Invalid saved shell or client.') - if any(type(state.get(key)) is not bool for key in ('update_agents', 'flashbang')): + if 'semantic_retrieval' not in state: + state['semantic_retrieval'] = False + if any(type(state.get(key)) is not bool for key in ('update_agents', 'flashbang', 'semantic_retrieval')): raise ValueError('Invalid saved install options. Run update once without Quick.') for key in ('selected', 'deselected'): values = state.get(key) @@ -41,6 +43,7 @@ def main(): parser.add_argument('--allow-legacy', action='store_true') parser.add_argument('--update-agents', choices=('true', 'false')) parser.add_argument('--flashbang', choices=('true', 'false')) + parser.add_argument('--semantic-retrieval', choices=('true', 'false')) parser.add_argument('--client') parser.add_argument('--selected', nargs='*', default=[]) parser.add_argument('--deselected', nargs='*', default=[]) @@ -59,7 +62,7 @@ def main(): else: print('shell\t' + state['shell']) print('client\t' + state['client']) - for key in ('update_agents', 'flashbang'): + for key in ('update_agents', 'flashbang', 'semantic_retrieval'): print(key + '\t' + str(state[key]).lower()) for key in ('selected', 'deselected'): for name in state[key]: @@ -68,6 +71,7 @@ def main(): state = validate(dict(version=2, shell=args.shell, client=args.client, update_agents=None if args.update_agents is None else args.update_agents == 'true', flashbang=None if args.flashbang is None else args.flashbang == 'true', + semantic_retrieval=False if args.semantic_retrieval is None else args.semantic_retrieval == 'true', selected=args.selected, deselected=args.deselected)) if (set(state['selected']) | set(state['deselected'])) - names: raise ValueError('Unknown plugin in selection.') diff --git a/scripts/lib/selection-state.sh b/scripts/lib/selection-state.sh index ae6c7af..2d624ca 100644 --- a/scripts/lib/selection-state.sh +++ b/scripts/lib/selection-state.sh @@ -12,6 +12,7 @@ read_update_selection() { client) [ -n "$client" ] || client=$value ;; update_agents) update_agents=$value ;; flashbang) flashbang=$value ;; + semantic_retrieval) semantic_retrieval=$value ;; selected) selected_plugins+=("$value") ;; deselected) deselected_plugins+=("$value") ;; esac @@ -20,6 +21,6 @@ read_update_selection() { save_update_selection() { python3 "$root/scripts/lib/selection-state.py" write --home "$home_path" --manifest "$root/adapters/plugins.tsv" \ - --shell "$shell" --client "$client" --update-agents "$update_agents" --flashbang "$flashbang" \ + --shell "$shell" --client "$client" --update-agents "$update_agents" --flashbang "$flashbang" --semantic-retrieval "${semantic_retrieval:-false}" \ --selected "${selected_plugins[@]}" --deselected "${deselected_plugins[@]}" } diff --git a/scripts/test-install-options.py b/scripts/test-install-options.py index 1f9a090..dc3622e 100644 --- a/scripts/test-install-options.py +++ b/scripts/test-install-options.py @@ -184,7 +184,7 @@ class InstallOptionsTests(unittest.TestCase): 'select_configured_plugins() { :; }\nsync_configured_plugins() { :; }\n' 'run_plugin_command() { printf "CLI-MOCK %s\\n" "$2"; }\n') environment = dict(os.environ, HOME=home.as_posix(), TEST_INSTALL_HOME=str(home)) - for name, quick, answers in [('install', False, 'y\nn\n'), ('update', True, '')]: + for name, quick, answers in [('install', False, 'y\nn\nn\n'), ('update', True, '')]: entry = str(scripts / (name + '.' + suffix)) command = ([executable, '-NoProfile', '-File', entry, '-Shell', 'Bash', '-Client', 'Both', '-Summary'] if kind == 'powershell' else [executable, entry, '--shell', 'bash', '--client', 'both', '--summary']) diff --git a/scripts/test-quick-update.py b/scripts/test-quick-update.py index a67ef64..3401552 100644 --- a/scripts/test-quick-update.py +++ b/scripts/test-quick-update.py @@ -71,6 +71,14 @@ class SelectionTests(unittest.TestCase): self.path.write_text(json.dumps(state)) self.assertNotEqual(self.run_state('read').returncode, 0) + def test_semantic_retrieval_option_round_trips(self): + result = self.run_state('write', '--shell', 'bash', '--client', 'both', '--update-agents', 'false', + '--flashbang', 'true', '--semantic-retrieval', 'true') + self.assertEqual(result.returncode, 0, result.stderr) + state = json.loads(self.run_state('read').stdout) + self.assertTrue(state['semantic_retrieval']) + self.assertIn('semantic_retrieval\ttrue', self.run_state('read', '--format', 'tsv').stdout) + def test_invalid_write_preserves_last_selection(self): self.save('--selected', 'Superpowers') original = self.path.read_bytes()