refactor(scripts): organize script entry points

This commit is contained in:
fraujulian committed 2026-09-18 11:35:38 +02:00
1 parent 793eee5b61
commit 11c2b6dfcb
41 files changed
+102 -101

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
[CmdletBinding()]
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-build-validation-" + [Guid]::NewGuid())
New-Item $work -ItemType Directory -Force | Out-Null
function New-RepoCopy {
$copy = Join-Path $work ("case-" + [Guid]::NewGuid())
New-Item $copy -ItemType Directory -Force | Out-Null
Get-ChildItem $root -Force | Where-Object { $_.Name -notin @('.git','generated') } | ForEach-Object {
Copy-Item $_.FullName (Join-Path $copy $_.Name) -Recurse -Force
}
return $copy
}
function Test-BuildFails([string]$Label, [scriptblock]$Mutate) {
$copy = New-RepoCopy
& $Mutate $copy
$failed = $false
try { & (Join-Path $copy 'scripts/commands/build.ps1') 2>&1 | Out-Null }
catch { $failed = $true }
Remove-Item $copy -Recurse -Force -ErrorAction SilentlyContinue
if (-not $failed) { throw "Expected build to fail for case '$Label' but it succeeded." }
}
$baseline = New-RepoCopy
& (Join-Path $baseline 'scripts/commands/build.ps1') -Summary | Out-Null
Test-BuildFails 'invalid Claude settings.json' {
param($copy)
Add-Content (Join-Path $copy 'adapters/claude/config/settings.json') '} this is not json {'
}
Test-BuildFails 'duplicate agent name' {
param($copy)
$architect = Join-Path $copy 'shared/agents/architect/agent.yml'
(Get-Content $architect -Raw) -replace 'name:\s*\S+', 'name: implementer' | Set-Content $architect -NoNewline
}
Test-BuildFails 'unknown Claude tool name in capability manifest' {
param($copy)
$manifest = Join-Path $copy 'adapters/claude/capabilities.tsv'
$lines = Get-Content $manifest | ForEach-Object { if ($_ -match '^architect\t') { "architect`tShell" } else { $_ } }
Set-Content $manifest $lines
}
Test-BuildFails 'duplicate plugin name' {
param($copy)
$manifest = Join-Path $copy 'adapters/plugins.tsv'
$lines = Get-Content $manifest
Add-Content $manifest $lines[1]
}
Test-BuildFails 'leftover template placeholder' {
param($copy)
Add-Content (Join-Path $copy 'shared/global-instructions.md') "`n__NOT_A_REAL_PLACEHOLDER__"
}
Test-BuildFails 'unknown placeholder beside an allowed installer placeholder' {
param($copy)
$path = Join-Path $copy 'adapters/claude/config/settings.json'
(Get-Content $path -Raw).Replace('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__', '__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY____UNKNOWN_PLACEHOLDER__') | Set-Content $path
}
Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue
if ($Summary) { Write-Output 'Tests: PASS | build validation' } else { Write-Output 'PASS build validation: invalid JSON, duplicate agent, unknown Claude tool name, duplicate plugin, and leftover placeholders are all rejected' }
exit 0
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
summary=false
while [ $# -gt 0 ]; do
case "$1" in
--summary) summary=true; shift ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
work=$(mktemp -d)
cleanup() { rm -rf -- "$work"; }
trap cleanup EXIT
new_repo_copy() {
local copy
copy=$(mktemp -d "$work/case-XXXXXX")
(cd "$root" && tar -cf - --exclude='.git' --exclude='generated' .) | (cd "$copy" && tar -xf -)
printf '%s' "$copy"
}
test_build_fails() {
local label=$1 mutate_fn=$2 copy
copy=$(new_repo_copy)
"$mutate_fn" "$copy"
if bash "$copy/scripts/commands/build.sh" >/dev/null 2>&1; then
printf 'Expected build to fail for case "%s" but it succeeded.\n' "$label" >&2
exit 1
fi
rm -rf -- "$copy"
}
baseline=$(new_repo_copy)
bash "$baseline/scripts/commands/build.sh" --summary >/dev/null
mutate_invalid_json() {
printf '} this is not json {' >> "$1/adapters/claude/config/settings.json"
}
test_build_fails 'invalid Claude settings.json' mutate_invalid_json
mutate_duplicate_agent() {
sed -i 's/^name:.*/name: implementer/' "$1/shared/agents/architect/agent.yml"
}
test_build_fails 'duplicate agent name' mutate_duplicate_agent
mutate_invalid_tool_name() {
sed -i 's/^architect\t.*/architect\tShell/' "$1/adapters/claude/capabilities.tsv"
}
test_build_fails 'unknown Claude tool name in capability manifest' mutate_invalid_tool_name
mutate_duplicate_plugin() {
local manifest="$1/adapters/plugins.tsv"
sed -n '2p' "$manifest" >> "$manifest"
}
test_build_fails 'duplicate plugin name' mutate_duplicate_plugin
mutate_leftover_placeholder() {
printf '\n__NOT_A_REAL_PLACEHOLDER__\n' >> "$1/shared/global-instructions.md"
}
test_build_fails 'leftover template placeholder' mutate_leftover_placeholder
mutate_mixed_placeholders() {
sed -i 's/__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__/__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__ __UNKNOWN_PLACEHOLDER__/' "$1/adapters/claude/config/settings.json"
}
test_build_fails 'unknown placeholder beside an allowed installer placeholder' mutate_mixed_placeholders
if [ "$summary" = true ]; then printf 'Tests: PASS | build validation\n'; else printf 'PASS build validation: invalid JSON, duplicate agent, unknown Claude tool name, duplicate plugin, and leftover placeholders are all rejected\n'; fi
+24
View File
@@ -0,0 +1,24 @@
[CmdletBinding()]
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
. (Join-Path $root 'scripts/lib/manifest.ps1')
. (Join-Path $root 'scripts/lib/install-targets.ps1')
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-install-guard-test-" + [Guid]::NewGuid())
try {
$installed = Join-Path $work 'installed'
$missing = Join-Path $work 'missing'
New-Item $installed -ItemType Directory -Force | Out-Null
New-Item (Get-ManagedManifestPath $installed) -ItemType File -Force | Out-Null
if (-not (Test-AnyManifestPresent @($installed, $missing))) { throw 'Expected an installed destination to be detected.' }
if (Test-AnyManifestPresent @($missing)) { throw 'A destination without a manifest must not count as installed.' }
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.' }
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
}
exit 0
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
summary=false
while [ $# -gt 0 ]; do
case "$1" in
--summary) summary=true; shift ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
. "$root/scripts/lib/manifest.sh"
. "$root/scripts/lib/install-targets.sh"
work=$(mktemp -d)
trap 'rm -rf -- "$work"' EXIT
installed="$work/installed"
missing="$work/missing"
mkdir -p "$installed"
: > "$(manifest_path "$installed")"
any_manifest_present "$installed" "$missing" || { printf 'Expected an installed destination to be detected.\n' >&2; exit 1; }
! any_manifest_present "$missing" || { printf 'A destination without a manifest must not count as installed.\n' >&2; exit 1; }
! 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; }
if [ "$summary" = true ]; then printf 'Tests: PASS | install guards\n'; else printf 'PASS install guards: already-installed and not-installed detection\n'; fi
+208
View File
@@ -0,0 +1,208 @@
import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import tomllib
import unittest
ROOT = Path(__file__).resolve().parent.parent.parent
SPEC = importlib.util.spec_from_file_location('install_options', ROOT / 'scripts/lib/install-options.py')
OPTIONS = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(OPTIONS)
class InstallOptionsTests(unittest.TestCase):
def test_filter_preserves_other_claude_hooks_and_status(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'settings.json'
settings = {'hooks': {'Stop': [{'hooks': [
{'command': 'bash "root/hooks/flashbang.sh"'}, {'command': 'echo keep'}]}],
'PreCompact': [{'hooks': [{'command': 'echo compact'}]}]},
'statusLine': {'command': 'echo status'}}
path.write_text(json.dumps(settings))
result = json.loads(OPTIONS.filter_flashbang(path))
self.assertEqual(result['hooks']['Stop'][0]['hooks'], [{'command': 'echo keep'}])
self.assertEqual(result['hooks']['PreCompact'], settings['hooks']['PreCompact'])
self.assertEqual(result['statusLine'], settings['statusLine'])
def test_filter_preserves_other_codex_stop_hooks_and_settings(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'config.toml'
path.write_text('approval_policy = "on-request"\n[[hooks.Stop]]\n'
'[[hooks.Stop.hooks]]\ncommand = \'bash "root/flashbang.sh"\'\n'
'[[hooks.Stop.hooks]]\ncommand = "echo keep"\n'
'[tui]\nstatus_line = ["model"]\n')
result = tomllib.loads(OPTIONS.filter_flashbang(path))
self.assertEqual(result['hooks']['Stop'][0]['hooks'], [{'command': 'echo keep'}])
self.assertEqual(result['tui']['status_line'], ['model'])
def test_native_option_helpers_select_clients_and_restore_environment(self):
shells = [('powershell', shutil.which('powershell') or shutil.which('pwsh')),
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash'))]
for kind, executable in shells:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=kind):
if kind == 'powershell':
command = [executable, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/install-options.ps1; "
"function Test-CodexProcessActive { $false }; function Test-GlobalNpmCodex { $false }; "
"function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) "
"Write-Output ($Command + ':' + ($Arguments -join ' ')) }; "
"$env:CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE='original'; "
"Update-SelectedAgentClis -Client Both; "
"if ($env:CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE -ne 'original') { throw 'Environment leaked' }"]
else:
command = [executable, '-c',
'. ./scripts/lib/install-options.sh; '
'codex_process_active() { return 1; }; global_npm_codex() { return 1; }; '
'run_plugin_command() { shift; printf "%s:%s\\n" "$1" "$2"; }; '
'client=both; dry_run=false; summary=false; '
'CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=original; update_selected_agent_clis; '
'[ "$CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE" = original ]']
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(result.stdout.splitlines(), ['codex:update', 'claude:update'])
def test_running_codex_is_skipped_and_npm_install_is_used_when_available(self):
shell = shutil.which('powershell') or shutil.which('pwsh')
if shell:
skipped = subprocess.run([
shell, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/install-options.ps1; "
"function Test-CodexProcessActive { $true }; function Test-GlobalNpmCodex { $true }; "
"function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) Write-Output $Command }; "
"Update-SelectedAgentClis -Client Both"
], cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(skipped.returncode, 0, skipped.stdout + skipped.stderr)
self.assertEqual(skipped.stdout.splitlines()[-1], 'claude')
self.assertIn('Codex CLI update skipped', skipped.stdout + skipped.stderr)
npm = subprocess.run([
shell, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/install-options.ps1; "
"function Test-CodexProcessActive { $false }; function Test-GlobalNpmCodex { $true }; "
"function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) "
"Write-Output ($Command + ':' + ($Arguments -join ' ')) }; Update-SelectedAgentClis -Client Codex"
], cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(npm.returncode, 0, npm.stdout + npm.stderr)
self.assertEqual(npm.stdout.strip(), 'npm:install -g @openai/codex@latest')
bash = (str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash'))
if bash and Path(bash).exists():
result = subprocess.run([
bash, '-c',
'. ./scripts/lib/install-options.sh; codex_process_active() { return 0; }; '
'global_npm_codex() { return 0; }; run_plugin_command() { shift; printf "%s\\n" "$1"; }; '
'client=both; dry_run=false; summary=false; update_selected_agent_clis'
], cwd=ROOT, capture_output=True, text=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(result.stdout.splitlines(), ['claude'])
self.assertIn('Codex CLI update skipped', result.stderr)
def test_flashbang_toggle_is_applied_by_both_manifest_writers(self):
shells = [('powershell', shutil.which('powershell') or shutil.which('pwsh')),
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash'))]
for kind, executable in shells:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=kind), tempfile.TemporaryDirectory() as directory:
source = Path(directory) / 'source'
source.mkdir()
destination = Path(directory) / 'destination'
settings = {'hooks': {'Stop': [{'hooks': [{'command': 'bash "__AI_CONFIG_ROOT__/flashbang.sh"'}]}],
'PreCompact': [{'hooks': [{'command': 'echo keep'}]}]}}
(source / 'settings.json').write_text(json.dumps(settings))
(source / 'config.toml').write_text('[[hooks.Stop]]\n[[hooks.Stop.hooks]]\n'
'command = \'bash "__AI_CONFIG_ROOT__/flashbang.sh"\'\n')
environment = dict(os.environ, OPTION_SOURCE=str(source), OPTION_DESTINATION=str(destination))
for enabled in (False, True):
if kind == 'powershell':
command = [executable, '-NoProfile', '-Command',
"$ErrorActionPreference='Stop'; . ./scripts/lib/manifest.ps1; "
"Sync-ManagedDestination -Source $env:OPTION_SOURCE -Destination $env:OPTION_DESTINATION "
"-Stamp test -AiConfigRoot root -ShellCommand bash -PowerShellCommand pwsh "
"-Summary -FlashbangEnabled $" + str(enabled).lower()]
else:
command = [executable, '-c',
'set -euo pipefail; . ./scripts/lib/manifest.sh; '
'sync_managed_destination "$OPTION_SOURCE" "$OPTION_DESTINATION" test root bash pwsh false true '
+ str(enabled).lower()]
environment.update(OPTION_SOURCE=source.as_posix(), OPTION_DESTINATION=destination.as_posix())
result = subprocess.run(command, cwd=ROOT, env=environment, capture_output=True, text=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
installed = json.loads((destination / 'settings.json').read_text(encoding='utf-8-sig'))
self.assertEqual('Stop' in installed['hooks'], enabled)
self.assertIn('PreCompact', installed['hooks'])
codex = tomllib.loads((destination / 'config.toml').read_text(encoding='utf-8-sig'))
self.assertEqual(bool(codex.get('hooks', {}).get('Stop')), enabled)
self.assertTrue((destination / '.ai-config-manifest.tsv').is_file())
def test_entrypoints_save_normal_choices_and_replay_quick_without_prompts(self):
executables = [('powershell', shutil.which('powershell') or shutil.which('pwsh')),
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash'))]
for kind, executable in executables:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=kind), tempfile.TemporaryDirectory() as directory:
root = Path(directory) / 'repo'
scripts = root / 'scripts'
shutil.copytree(ROOT / 'scripts/lib', scripts / 'lib', ignore=shutil.ignore_patterns('__pycache__'))
home = Path(directory) / 'home'
home.mkdir()
(root / 'adapters').mkdir()
shutil.copyfile(ROOT / 'adapters/plugins.tsv', root / 'adapters/plugins.tsv')
for client in ('codex', 'claude'):
package = root / 'generated' / (client + '-bash')
(package / 'skills').mkdir(parents=True)
(package / 'skills' / 'test.txt').write_text('managed')
config = ('[[hooks.Stop]]\n[[hooks.Stop.hooks]]\ncommand = \'bash "__AI_CONFIG_ROOT__/flashbang.sh"\'\n'
if client == 'codex' else '{"hooks":{"Stop":[{"hooks":[{"command":"bash flashbang.sh"}]}]}}')
(package / ('config.toml' if client == 'codex' else 'settings.json')).write_text(config)
suffix = 'ps1' if kind == 'powershell' else 'sh'
for name in ('install', 'update'):
content = (ROOT / 'scripts' / (name + '.' + suffix)).read_text(encoding='utf-8-sig')
content = content.replace("[Environment]::GetFolderPath('UserProfile')", '$env:TEST_INSTALL_HOME')
(scripts / (name + '.' + suffix)).write_text(content)
(scripts / ('build.' + suffix)).write_text('param([switch]$Summary)' if kind == 'powershell' else 'exit 0\n')
if kind == 'powershell':
(scripts / 'lib/plugins.ps1').write_text(
'function Select-ConfiguredPlugins { @{Selected=@(); Deselected=@()} }\n'
'function Get-ConfiguredPluginEntries { @() }\n'
'function Sync-ConfiguredPlugins {}\n'
'function Invoke-PluginCommand { param($Command,$Arguments,[switch]$DryRun,[switch]$Summary) '
'Write-Output "CLI-MOCK $Command" }\n')
else:
(scripts / 'lib/plugins.sh').write_text(
'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\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'])
if quick:
command.append('-Quick' if kind == 'powershell' else '--quick')
result = subprocess.run(command, env=environment, input=answers, capture_output=True, text=True, timeout=25)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertTrue('CLI-MOCK codex' in result.stdout or
'Codex CLI update skipped' in result.stdout + result.stderr)
self.assertIn('CLI-MOCK claude', result.stdout)
if quick:
self.assertNotIn('Enable the Flashbang', result.stdout + result.stderr)
state = json.loads((home / '.my-ai-configuration/selection.json').read_text())
self.assertTrue(state['update_agents'])
self.assertFalse(state['flashbang'])
self.assertEqual(state['shell'], 'bash')
self.assertNotIn('Stop', json.loads((home / '.claude/settings.json').read_text()).get('hooks', {}))
if __name__ == '__main__':
unittest.main()
+226
View File
@@ -0,0 +1,226 @@
import contextlib
import importlib.util
import io
import json
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
spec = importlib.util.spec_from_file_location('extensions', Path(__file__).parent / 'lib' / 'managed-extensions.py')
extensions = importlib.util.module_from_spec(spec)
spec.loader.exec_module(extensions)
def plugin(name='Example'):
return dict(name=name, codex_method='plugin', codex_plugin=name.lower() + '@test', codex_marketplace='-',
claude_plugin=name.lower() + '@test', claude_marketplace='-')
def skill():
return dict(name='Humanizer', codex_method='skill', codex_source='blader/humanizer', codex_skill='humanizer', codex_plugin='-')
class ManagedExtensionTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.home = Path(self.temporary.name)
self.installed = {'codex': {}, 'claude': {}}
self.commands = []
self.addCleanup(patch.stopall)
patch.object(extensions, 'installed_plugins', side_effect=lambda client, home: dict(self.installed[client])).start()
patch.object(extensions, 'run_command', side_effect=self.command).start()
patch.object(extensions, 'fetch_skill', return_value=('humanizer', {'SKILL.md': (b'original', False), 'references/old.md': (b'old', False)})).start()
self.output = contextlib.redirect_stdout(io.StringIO())
self.output.__enter__()
self.addCleanup(self.output.__exit__, None, None, None)
def command(self, args, summary, home=None):
self.commands.append(args)
if len(args) > 3 and args[2] in ('add', 'install') and args[1] == 'plugin':
self.installed[args[0]][args[3]] = {'scope': 'user'}
if args[2] in ('remove', 'uninstall'):
self.installed[args[0]].pop(args[3], None)
return ''
def manager(self, **kwargs):
return extensions.Manager(self.home, **kwargs)
def test_foreign_plugin_is_never_claimed_or_removed(self):
self.installed['codex']['example@test'] = {}
self.manager(update=True).sync([plugin()], ['codex'], {'Example'})
self.manager().sync([], ['codex'], set())
self.assertEqual([], self.commands)
self.assertEqual([], self.manager().state['resources'])
def test_owned_plugin_removed_after_manifest_entry_disappears(self):
self.manager().sync([plugin()], ['codex'], {'Example'})
self.manager().sync([], ['codex'], set())
self.assertEqual(['codex', 'plugin', 'remove', 'example@test'], self.commands[-1])
self.assertEqual([], self.manager().state['resources'])
def test_client_scope_is_preserved(self):
self.manager().sync([plugin()], ['claude', 'codex'], {'Example'})
self.manager().sync([], ['codex'], set())
self.assertEqual(['claude'], [r['client'] for r in self.manager().state['resources']])
def test_partial_plugin_failure_retains_successful_ownership(self):
with patch.object(extensions, 'run_command', side_effect=[None, ValueError('failure')]):
with self.assertRaises(ValueError):
self.manager().sync([plugin(), plugin('Second')], ['codex'], {'Example', 'Second'})
self.assertEqual(['Example'], [r['name'] for r in self.manager().state['resources']])
def test_failed_removal_retains_ownership(self):
self.manager().sync([plugin()], ['codex'], {'Example'})
with patch.object(extensions, 'run_command', side_effect=ValueError('failure')):
with self.assertRaises(ValueError):
self.manager().sync([], ['codex'], set())
self.assertEqual(1, len(self.manager().state['resources']))
def test_foreign_skill_directory_is_preserved(self):
target = self.home / '.agents/skills/humanizer/SKILL.md'
target.parent.mkdir(parents=True)
target.write_text('foreign')
self.manager(update=True).sync([skill()], ['codex'], {'Humanizer'})
self.manager().sync([], ['codex'], set())
self.assertEqual('foreign', target.read_text())
self.assertEqual([], self.manager().state['resources'])
def test_skill_removal_preserves_modified_and_foreign_files(self):
self.manager().sync([skill()], ['codex'], {'Humanizer'})
directory = self.home / '.agents/skills/humanizer'
(directory / 'SKILL.md').write_text('modified')
(directory / 'foreign.txt').write_text('foreign')
self.manager().sync([], ['codex'], set())
self.assertEqual('modified', (directory / 'SKILL.md').read_text())
self.assertTrue((directory / 'foreign.txt').exists())
self.assertFalse((directory / 'references/old.md').exists())
self.assertEqual(1, len(self.manager().state['resources'][0]['files']))
def test_skill_update_cleans_only_owned_stale_files(self):
self.manager().sync([skill()], ['codex'], {'Humanizer'})
directory = self.home / '.agents/skills/humanizer'
(directory / 'foreign.txt').write_text('foreign')
with patch.object(extensions, 'fetch_skill', return_value=('humanizer', {'SKILL.md': (b'new', False)})):
self.manager(update=True).sync([skill()], ['codex'], {'Humanizer'})
self.assertEqual('new', (directory / 'SKILL.md').read_text())
self.assertTrue((directory / 'foreign.txt').exists())
self.assertFalse((directory / 'references/old.md').exists())
def test_skill_records_resolved_revision(self):
with patch.object(extensions, 'resolve_revision', return_value='a' * 40):
self.manager().sync([skill()], ['codex'], {'Humanizer'})
record = self.manager().state['resources'][0]
self.assertEqual(record['source'], 'blader/humanizer')
self.assertEqual(record['resolvedRevision'], 'a' * 40)
def test_skill_partial_failure_records_each_successful_file(self):
original_write = extensions.atomic_write
def write(path, content):
if path.name == 'old.md':
raise OSError('simulated disk failure')
original_write(path, content)
with patch.object(extensions, 'atomic_write', side_effect=write):
with self.assertRaises(OSError):
self.manager().sync([skill()], ['codex'], {'Humanizer'})
self.assertEqual(['.agents/skills/humanizer/SKILL.md'], list(self.manager().state['resources'][0]['files']))
self.manager().sync([skill()], ['codex'], {'Humanizer'})
self.assertEqual(2, len(self.manager().state['resources'][0]['files']))
self.assertTrue(self.manager().state['resources'][0]['complete'])
def test_dry_run_has_no_commands_network_or_writes(self):
self.manager(dry_run=True).sync([plugin(), skill()], ['codex'], {'Example', 'Humanizer'})
self.assertEqual([], list(self.home.iterdir()))
self.assertEqual([], self.commands)
extensions.fetch_skill.assert_not_called()
def test_traversal_ledger_is_rejected(self):
path = self.home / '.my-ai-configuration/extensions.json'
path.parent.mkdir()
path.write_text(json.dumps({'version': 1, 'resources': [dict(client='codex', name='Bad', kind='skill', directory='.agents/skills/bad', files={'.agents/skills/bad/../../../other': '0' * 64})]}))
with self.assertRaises(ValueError):
self.manager()
def test_marketplace_failure_never_removes_existing_source(self):
entry = plugin()
entry['codex_marketplace'] = 'owner/repository'
with patch.object(extensions, 'run_command', side_effect=ValueError('different source')) as command:
with self.assertRaises(ValueError):
self.manager().sync([entry], ['codex'], {'Example'})
self.assertEqual(1, command.call_count)
self.assertEqual('add', command.call_args.args[0][3])
def test_owned_codex_update_upgrades_marketplace(self):
entry = plugin()
entry['codex_marketplace'] = 'owner/repository'
self.manager().sync([entry], ['codex'], {'Example'})
self.commands.clear()
self.manager(update=True).sync([entry], ['codex'], {'Example'})
self.assertEqual([['codex', 'plugin', 'marketplace', 'upgrade', 'test'], ['codex', 'plugin', 'add', 'example@test']], self.commands)
def test_owned_disabled_claude_is_enabled(self):
self.manager().sync([plugin()], ['claude'], {'Example'})
self.installed['claude']['example@test']['enabled'] = False
self.manager(update=True).sync([plugin()], ['claude'], {'Example'})
self.assertEqual(['claude', 'plugin', 'enable', 'example@test'], self.commands[-1])
def test_dry_run_summary_has_one_line(self):
with contextlib.redirect_stdout(io.StringIO()) as output:
self.manager(dry_run=True, summary=True).sync([plugin(), skill()], ['codex'], {'Example', 'Humanizer'})
self.assertEqual('EXTENSIONS: reconciliation complete (dry run)\n', output.getvalue())
def test_qmd_installs_once_as_a_cli_without_registering_mcp(self):
entry = dict(name='QMD', codex_method='qmd', codex_source='@tobilu/qmd', codex_skill='qmd', codex_plugin='-', codex_marketplace='-', claude_plugin='qmd@qmd', claude_marketplace='tobi/qmd')
self.manager().sync([entry], ['codex'], {'QMD'})
self.assertEqual([['npm', 'install', '--global', '@tobilu/qmd']], self.commands)
self.manager().sync([], ['codex'], set())
self.assertEqual(['npm', 'uninstall', '--global', '@tobilu/qmd'], self.commands[-1])
def test_qmd_installs_once_for_both_clients(self):
entry = dict(name='QMD', codex_method='qmd', codex_source='@tobilu/qmd', codex_skill='qmd', codex_plugin='-', codex_marketplace='-', claude_plugin='qmd@qmd', claude_marketplace='tobi/qmd')
self.manager().sync([entry], ['claude', 'codex'], {'QMD'})
self.assertEqual(1, self.commands.count(['npm', 'install', '--global', '@tobilu/qmd']))
self.assertIn(['claude', 'plugin', 'install', 'qmd@qmd', '--scope', 'user'], self.commands)
self.assertNotIn(['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp'], self.commands)
def test_foreign_empty_directory_survives_skill_removal(self):
self.manager().sync([skill()], ['codex'], {'Humanizer'})
foreign = self.home / '.agents/skills/humanizer/foreign-empty'
foreign.mkdir()
self.manager().sync([], ['codex'], set())
self.assertTrue(foreign.is_dir())
def test_foreign_skill_link_is_preserved(self):
foreign = self.home / 'foreign'
foreign.mkdir()
target = self.home / '.agents/skills/humanizer'
target.parent.mkdir(parents=True)
try:
target.symlink_to(foreign, target_is_directory=True)
except OSError:
self.skipTest('Symbolic links are unavailable on this host.')
self.manager().sync([skill()], ['codex'], {'Humanizer'})
self.assertTrue(target.is_symlink())
extensions.fetch_skill.assert_not_called()
class CommandTests(unittest.TestCase):
def test_subprocess_uses_intended_home_and_cmd_launcher(self):
with tempfile.TemporaryDirectory(prefix='managed extensions ') as temporary:
home = Path(temporary)
executable = home / ('mock.cmd' if os.name == 'nt' else 'mock')
if os.name == 'nt':
executable.write_text('@echo off\necho %CODEX_HOME%\necho %CLAUDE_CONFIG_DIR%\n')
else:
executable.write_text('#!/bin/sh\nprintf "%s\\n" "$CODEX_HOME" "$CLAUDE_CONFIG_DIR"\n')
executable.chmod(0o700)
with contextlib.redirect_stdout(io.StringIO()):
result = extensions.run_command([str(executable), 'plugin', 'list', '--json'], True, home)
self.assertEqual([str(home / '.codex'), str(home / '.claude')], result.splitlines())
if __name__ == '__main__':
unittest.main()
+109
View File
@@ -0,0 +1,109 @@
[CmdletBinding()]
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
. (Join-Path $root 'scripts/lib/manifest.ps1')
. (Join-Path $root 'scripts/lib/install-targets.ps1')
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-manifest-test-" + [Guid]::NewGuid())
New-Item $work -ItemType Directory -Force | Out-Null
try {
$source = Join-Path $work 'source'
$destination = Join-Path $work 'destination'
New-Item $source -ItemType Directory -Force | Out-Null
Set-Content (Join-Path $source 'a.md') 'A' -Encoding UTF8 -NoNewline
Set-Content (Join-Path $source 'b.md') 'B' -Encoding UTF8 -NoNewline
Set-Content (Join-Path $source 'c.md') 'C' -Encoding UTF8 -NoNewline
$out1 = @(Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'run1')
if (@($out1 | Where-Object { $_ -like 'CREATE*' }).Count -ne 3) { throw "Expected three CREATE lines on first install: $($out1 -join '; ')" }
$out2 = @(Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'run2')
if (@($out2 | Where-Object { $_ -notlike 'UNCHANGED*' -and $_ -notlike 'SOURCE *' }).Count -ne 0) { throw "Second identical install was not a no-op: $($out2 -join '; ')" }
if (Test-Path (Join-Path $destination 'backups/run2')) { throw 'Idempotent install must not create a backup.' }
Set-Content (Join-Path $destination 'foreign.md') 'mine' -Encoding UTF8 -NoNewline
Remove-Item (Join-Path $source 'b.md') -Force
Remove-Item (Join-Path $source 'c.md') -Force
Set-Content (Join-Path $destination 'c.md') 'locally changed' -Encoding UTF8 -NoNewline
$out3 = @(Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'run3')
if (@($out3 | Where-Object { $_ -match 'REMOVE.*b\.md' }).Count -ne 1) { throw "Expected b.md to be removed: $($out3 -join '; ')" }
if (Test-Path (Join-Path $destination 'b.md')) { throw 'b.md should have been removed: it was untouched by the user and dropped from source.' }
if (@($out3 | Where-Object { $_ -match 'WARN.*c\.md.*changed locally' }).Count -ne 1) { throw "Expected c.md to be flagged as locally modified: $($out3 -join '; ')" }
if (-not (Test-Path (Join-Path $destination 'c.md'))) { throw 'A locally modified stale file must not be removed.' }
if ((Get-Content (Join-Path $destination 'c.md') -Raw) -ne 'locally changed') { throw 'c.md content must be preserved exactly.' }
if ((Get-Content (Join-Path $destination 'foreign.md') -Raw) -ne 'mine') { throw 'A file this setup never installed must never be touched.' }
if (-not (Test-Path (Join-Path $destination 'backups/run3/b.md'))) { throw 'b.md should have been backed up before removal.' }
if (-not (Test-Path (Join-Path $destination 'backups/run3/c.md'))) { throw 'c.md should have been backed up even though it was left in place.' }
Remove-Item (Join-Path $source 'a.md') -Force
$beforeDry = @(Get-ChildItem $destination -Recurse -File | Sort-Object FullName | ForEach-Object FullName)
$outDry = @(Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'dry' -DryRun)
$afterDry = @(Get-ChildItem $destination -Recurse -File | Sort-Object FullName | ForEach-Object FullName)
if (Compare-Object $beforeDry $afterDry) { throw 'A dry run must not change the destination at all.' }
if (@($outDry | Where-Object { $_ -match 'DRYRUN REMOVE.*a\.md' }).Count -ne 1) { throw "Dry run should report the planned removal without performing it: $($outDry -join '; ')" }
$configPath = Join-Path $destination 'config.toml'
$pluginState = @'
[marketplaces.ponytail]
source_type = "git"
source = "https://github.com/DietrichGebert/ponytail.git"
[plugins."ponytail@ponytail"]
enabled = true
[plugins."disabled@market"]
enabled = false
'@
[System.IO.File]::WriteAllText((Join-Path $source 'config.toml'), "approvals_reviewer = 'auto_review'`n`n[tui]`n")
[System.IO.File]::WriteAllText($configPath, "model = 'old'`nmodel_reasoning_effort = 'high'`napprovals_reviewer = 'user'`n$pluginState`n[other]`nvalue = true`n", (New-Object System.Text.UTF8Encoding($true)))
$originalConfig = [System.IO.File]::ReadAllText($configPath)
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'plugins-dry' -DryRun
if ([System.IO.File]::ReadAllText($configPath) -ne $originalConfig) { throw 'Dry run must preserve plugin configuration.' }
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'plugins'
$mergedConfig = [System.IO.File]::ReadAllText($configPath)
if (-not ($mergedConfig -replace "`r`n", "`n").Contains(($pluginState -replace "`r`n", "`n"))) { throw 'Updating config.toml must preserve local marketplace and plugin tables, including disabled plugins.' }
if (-not $mergedConfig.Contains("model = 'old'") -or -not $mergedConfig.Contains("model_reasoning_effort = 'high'") -or -not $mergedConfig.Contains("approvals_reviewer = 'auto_review'") -or $mergedConfig.Contains("approvals_reviewer = 'user'") -or $mergedConfig.Contains('[other]')) { throw 'Updating config.toml must preserve local model settings and enforce automatic review.' }
if ($mergedConfig.IndexOf("model = 'old'") -gt $mergedConfig.IndexOf('[tui]') -or $mergedConfig.IndexOf("model_reasoning_effort = 'high'") -gt $mergedConfig.IndexOf('[tui]')) { throw 'Model settings must remain in the TOML root table.' }
if ([System.IO.File]::ReadAllText((Join-Path $destination 'backups/plugins/config.toml')) -ne $originalConfig) { throw 'Original plugin configuration must be backed up.' }
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'plugins-repeat'
if ([System.IO.File]::ReadAllText($configPath) -ne $mergedConfig -or (Test-Path (Join-Path $destination 'backups/plugins-repeat'))) { throw 'Preserving plugin configuration must be idempotent.' }
$outSummary = @(Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'summary' -Summary)
if (@($outSummary | Where-Object { $_ -like 'CREATE*' -or $_ -like 'UPDATE*' -or $_ -like 'UNCHANGED*' -or $_ -like 'BACKUP*' }).Count -ne 0) { throw "Summary mode must not print per-file lines: $($outSummary -join '; ')" }
if (@($outSummary | Where-Object { $_ -match '^SYNC .* unchanged' }).Count -ne 1) { throw "Summary mode must print exactly one SYNC tally line: $($outSummary -join '; ')" }
$generated = Join-Path $work 'generated'
$package = Join-Path $generated 'codex-powershell'
$testHome = Join-Path $work 'home'
$legacy = Join-Path $testHome '.codex'
New-Item (Join-Path $package 'skills/example') -ItemType Directory -Force | Out-Null
Set-Content (Join-Path $package 'skills/example/SKILL.md') 'managed skill' -NoNewline
New-Item (Join-Path $legacy 'skills/example') -ItemType Directory -Force | Out-Null
Copy-Item (Join-Path $package 'skills/example/SKILL.md') (Join-Path $legacy 'skills/example/SKILL.md')
Set-Content (Join-Path $legacy 'skills/example/custom.md') 'customized' -NoNewline
Set-Content (Join-Path $legacy 'skills/example/foreign.md') 'foreign' -NoNewline
Write-ManagedManifest (Get-ManagedManifestPath $legacy) @{
'skills/example/SKILL.md' = (Get-Sha256Hash (Join-Path $legacy 'skills/example/SKILL.md'))
'skills/example/custom.md' = ('0' * 64)
}
$targets = @(Get-InstallTargets -Generated $generated -HomePath $testHome -Shell PowerShell -Client Codex)
if ($targets[0].Destination -ne (Join-Path $testHome '.agents/skills')) { throw 'Canonical skills must be installed before legacy copies are removed.' }
foreach ($item in $targets) { $null = Sync-ManagedDestination -Source $item.Source -Destination $item.Destination -Stamp 'migrate' }
if (-not (Test-Path (Join-Path $testHome '.agents/skills/example/SKILL.md'))) { throw 'Canonical skill is missing.' }
if (Test-Path (Join-Path $legacy 'skills/example/SKILL.md')) { throw 'Unmodified managed skill must not remain duplicated.' }
if (-not (Test-Path (Join-Path $legacy 'backups/migrate/skills/example/SKILL.md'))) { throw 'Removed legacy skill must have a backup.' }
foreach ($name in @('custom.md', 'foreign.md')) {
if (-not (Test-Path (Join-Path $legacy "skills/example/$name"))) { throw 'Modified and foreign legacy skills must be preserved.' }
}
foreach ($item in $targets) { $null = Sync-ManagedDestination -Source $item.Source -Destination $item.Destination -Stamp 'migrate-repeat' }
if (Test-Path (Join-Path $legacy 'backups/migrate-repeat')) { throw 'Skill migration must be idempotent.' }
Set-Content (Join-Path $source 'concurrency.txt') '__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__' -NoNewline
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'concurrency' -ClaudeConcurrency '7'
if ((Get-Content (Join-Path $destination 'concurrency.txt') -Raw) -ne '7') { throw 'Standalone concurrency placeholder must be resolved.' }
if ($Summary) { Write-Output 'Tests: PASS | managed install' } else { Write-Output 'PASS managed manifest: idempotent, stale removal, modified-file protection, foreign files preserved, dry run side-effect free, skill migration' }
} finally {
Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue
}
exit 0
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
set -euo pipefail
summary=false
while [ $# -gt 0 ]; do
case "$1" in
--summary) summary=true; shift ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
. "$root/scripts/lib/manifest.sh"
. "$root/scripts/lib/install-targets.sh"
work=$(mktemp -d)
cleanup() { rm -rf -- "$work"; }
trap cleanup EXIT
source_dir="$work/source"
destination="$work/destination"
mkdir -p "$source_dir"
printf 'A' > "$source_dir/a.md"
printf 'B' > "$source_dir/b.md"
printf 'C' > "$source_dir/c.md"
out1=$(sync_managed_destination "$source_dir" "$destination" run1 "$destination" bash bash false)
[ "$(printf '%s\n' "$out1" | grep -c '^CREATE')" -eq 3 ] || { printf 'Expected three CREATE lines on first install:\n%s\n' "$out1" >&2; exit 1; }
out2=$(sync_managed_destination "$source_dir" "$destination" run2 "$destination" bash bash false)
[ -z "$(printf '%s\n' "$out2" | grep -v '^UNCHANGED' | grep -v '^SOURCE ')" ] || { printf 'Second identical install was not a no-op:\n%s\n' "$out2" >&2; exit 1; }
[ ! -d "$destination/backups/run2" ] || { printf 'Idempotent install must not create a backup.\n' >&2; exit 1; }
printf 'mine' > "$destination/foreign.md"
rm -f "$source_dir/b.md" "$source_dir/c.md"
printf 'locally changed' > "$destination/c.md"
out3=$(sync_managed_destination "$source_dir" "$destination" run3 "$destination" bash bash false)
printf '%s\n' "$out3" | grep -q '^REMOVE .*b\.md$' || { printf 'Expected b.md to be removed:\n%s\n' "$out3" >&2; exit 1; }
[ ! -f "$destination/b.md" ] || { printf 'b.md should have been removed.\n' >&2; exit 1; }
printf '%s\n' "$out3" | grep -q 'WARN .*c\.md.*changed locally' || { printf 'Expected c.md to be flagged as locally modified:\n%s\n' "$out3" >&2; exit 1; }
[ -f "$destination/c.md" ] || { printf 'A locally modified stale file must not be removed.\n' >&2; exit 1; }
[ "$(cat "$destination/c.md")" = 'locally changed' ] || { printf 'c.md content must be preserved exactly.\n' >&2; exit 1; }
[ "$(cat "$destination/foreign.md")" = 'mine' ] || { printf 'A file this setup never installed must never be touched.\n' >&2; exit 1; }
[ -f "$destination/backups/run3/b.md" ] || { printf 'b.md should have been backed up before removal.\n' >&2; exit 1; }
[ -f "$destination/backups/run3/c.md" ] || { printf 'c.md should have been backed up even though it was left in place.\n' >&2; exit 1; }
rm -f "$source_dir/a.md"
before_dry=$(find "$destination" -type f | sort)
out_dry=$(sync_managed_destination "$source_dir" "$destination" dry "$destination" bash bash true)
after_dry=$(find "$destination" -type f | sort)
[ "$before_dry" = "$after_dry" ] || { printf 'A dry run must not change the destination at all.\n' >&2; exit 1; }
printf '%s\n' "$out_dry" | grep -q '^DRYRUN REMOVE .*a\.md$' || { printf 'Dry run should report the planned removal without performing it:\n%s\n' "$out_dry" >&2; exit 1; }
plugin_state=' [marketplaces.ponytail]
source_type = "git"
source = "https://github.com/DietrichGebert/ponytail.git"
[plugins."ponytail@ponytail"]
enabled = true
[plugins."disabled@market"]
enabled = false'
printf "approvals_reviewer = 'auto_review'\n\n[tui]\n" > "$source_dir/config.toml"
printf "\357\273\277model = 'old'\nmodel_reasoning_effort = 'high'\napprovals_reviewer = 'user'\n%s\n[other]\nvalue = true\n" "$plugin_state" > "$destination/config.toml"
original_config=$(cat "$destination/config.toml")
sync_managed_destination "$source_dir" "$destination" plugins-dry "" "" "" true > /dev/null
[ "$(cat "$destination/config.toml")" = "$original_config" ] || { printf 'Dry run changed plugin configuration.\n' >&2; exit 1; }
sync_managed_destination "$source_dir" "$destination" plugins "" "" "" false > /dev/null
merged_config=$(cat "$destination/config.toml")
merged_config_normalized=${merged_config//$'\r'/}
plugin_state_normalized=${plugin_state//$'\r'/}
[[ "$merged_config_normalized" == *"$plugin_state_normalized"* ]] || { printf 'Updating config.toml must preserve local marketplace and plugin tables, including disabled plugins.\n' >&2; exit 1; }
[[ "$merged_config" == *"model = 'old'"* && "$merged_config" == *"model_reasoning_effort = 'high'"* && "$merged_config" == *"approvals_reviewer = 'auto_review'"* && "$merged_config" != *"approvals_reviewer = 'user'"* && "$merged_config" != *'[other]'* ]] || { printf 'Updating config.toml must preserve local model settings and enforce automatic review.\n' >&2; exit 1; }
[ "$(printf '%s\n' "$merged_config" | grep -n "model = 'old'" | cut -d: -f1)" -lt "$(printf '%s\n' "$merged_config" | grep -n '^\[tui\]$' | cut -d: -f1)" ] || { printf 'Model must remain in TOML root table.\n' >&2; exit 1; }
[ "$(printf '%s\n' "$merged_config" | grep -n "model_reasoning_effort = 'high'" | cut -d: -f1)" -lt "$(printf '%s\n' "$merged_config" | grep -n '^\[tui\]$' | cut -d: -f1)" ] || { printf 'Model effort must remain in TOML root table.\n' >&2; exit 1; }
[ "$(cat "$destination/backups/plugins/config.toml")" = "$original_config" ] || { printf 'Original plugin configuration must be backed up.\n' >&2; exit 1; }
sync_managed_destination "$source_dir" "$destination" plugins-repeat "" "" "" false > /dev/null
[ "$(cat "$destination/config.toml")" = "$merged_config" ] && [ ! -d "$destination/backups/plugins-repeat" ] || { printf 'Preserving plugin configuration must be idempotent.\n' >&2; exit 1; }
out_summary=$(sync_managed_destination "$source_dir" "$destination" "summary" "" "" "" false true)
if printf '%s\n' "$out_summary" | grep -Eq '^(CREATE|UPDATE|UNCHANGED|BACKUP) '; then
printf 'Summary mode must not print per-file lines: %s\n' "$out_summary" >&2
exit 1
fi
if [ "$(printf '%s\n' "$out_summary" | grep -c '^SYNC .* unchanged')" -ne 1 ]; then
printf 'Summary mode must print exactly one SYNC tally line: %s\n' "$out_summary" >&2
exit 1
fi
generated="$work/generated"
package="$generated/codex-bash"
test_home="$work/home"
legacy="$test_home/.codex"
mkdir -p "$package/skills/example" "$legacy/skills/example"
printf 'managed skill' > "$package/skills/example/SKILL.md"
cp "$package/skills/example/SKILL.md" "$legacy/skills/example/SKILL.md"
printf 'customized' > "$legacy/skills/example/custom.md"
printf 'foreign' > "$legacy/skills/example/foreign.md"
declare -A legacy_manifest
# shellcheck disable=SC2034
legacy_manifest[skills/example/SKILL.md]=$(sha256_of_file "$legacy/skills/example/SKILL.md")
# shellcheck disable=SC2034
legacy_manifest[skills/example/custom.md]=$(printf '0%.0s' {1..64})
write_managed_manifest "$(manifest_path "$legacy")" legacy_manifest
mapfile -t targets < <(get_install_targets "$generated" "$test_home" bash codex)
[[ "${targets[0]}" = *"|$test_home/.agents/skills" ]] || { printf 'Canonical skills must be installed first.\n' >&2; exit 1; }
for target_pair in "${targets[@]}"; do
sync_managed_destination "${target_pair%%|*}" "${target_pair#*|}" migrate "" "" "" false > /dev/null
done
[ -f "$test_home/.agents/skills/example/SKILL.md" ] && [ ! -f "$legacy/skills/example/SKILL.md" ] && [ -f "$legacy/backups/migrate/skills/example/SKILL.md" ] || { printf 'Skill migration must remove and back up the managed duplicate.\n' >&2; exit 1; }
[ -f "$legacy/skills/example/custom.md" ] && [ -f "$legacy/skills/example/foreign.md" ] || { printf 'Skill migration must preserve modified and foreign files.\n' >&2; exit 1; }
for target_pair in "${targets[@]}"; do
sync_managed_destination "${target_pair%%|*}" "${target_pair#*|}" migrate-repeat "" "" "" false > /dev/null
done
[ ! -d "$legacy/backups/migrate-repeat" ] || { printf 'Skill migration must be idempotent.\n' >&2; exit 1; }
printf '__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__' > "$source_dir/concurrency.txt"
sync_managed_destination "$source_dir" "$destination" concurrency "" "" "" false false true 7 > /dev/null
[ "$(cat "$destination/concurrency.txt")" = 7 ] || { printf 'Standalone concurrency placeholder must be resolved.\n' >&2; exit 1; }
if [ "$summary" = true ]; then printf 'Tests: PASS | managed install\n'; else printf 'PASS managed manifest: idempotent, stale removal, modified-file protection, foreign files preserved, dry run side-effect free, skill migration\n'; fi
+66
View File
@@ -0,0 +1,66 @@
[CmdletBinding()]
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
function Write-Host { param($Object) if (-not $Summary) { Microsoft.PowerShell.Utility\Write-Host $Object } }
. (Join-Path $root 'scripts/lib/plugins.ps1')
$script:answers = [System.Collections.Generic.Queue[string]]::new()
$script:answers.Enqueue('1')
$script:answers.Enqueue('2')
$script:answers.Enqueue('done')
function Read-Host { param($Prompt) $answers.Dequeue() }
$result = Read-PluginToggleSelection -Names @('A','B','C') -Checked @($true, $false, $true)
if ($result[0] -ne $false) { throw 'Toggling checked entry 1 must uncheck it.' }
if ($result[1] -ne $true) { throw 'Toggling unchecked entry 2 must check it.' }
if ($result[2] -ne $true) { throw 'Entry 3 was never toggled and must stay unchanged.' }
$script:answers.Enqueue('')
$confirmed = @(Read-PluginToggleSelection -Names @('A') -Checked @($true))
if ($confirmed.Count -ne 1 -or -not $confirmed[0]) { throw 'Enter must confirm without changing the selection.' }
function Test-PluginOutput {
param([string]$Mode)
Write-Output 'download progress'
Write-Output 'WARN test warning'
$global:LASTEXITCODE = if ($Mode -eq 'fail') { 7 } else { 0 }
}
$compact = @(Invoke-PluginCommand -Command Test-PluginOutput -Arguments @('success') -Summary)
if ($compact.Count -ne 1 -or $compact[0] -ne 'WARN test warning') { throw 'Plugin summary must retain warnings and suppress progress.' }
$detailed = @(Invoke-PluginCommand -Command Test-PluginOutput -Arguments @('success'))
if ($detailed.Count -ne 2 -or $detailed[0] -ne 'download progress') { throw 'Detailed output must retain progress.' }
$diagnostics = [System.Collections.Generic.List[string]]::new()
$failed = $false
$env:AI_CONFIG_VERBOSE = $null
try { Invoke-PluginCommand -Command Test-PluginOutput -Arguments @('fail') -Summary | ForEach-Object { $diagnostics.Add("$_") } }
catch { $failed = $true }
$env:AI_CONFIG_VERBOSE = $null
if (-not $failed -or $diagnostics -notcontains 'download progress' -or $diagnostics -notcontains 'WARN test warning') { throw 'Plugin failure must retain full diagnostics and fail.' }
$global:LASTEXITCODE = 0
function codex { $script:observedCodexHome = $env:CODEX_HOME; $script:observedProfile = $env:USERPROFILE; '{"installed":[{"pluginId":"ponytail@ponytail"},{"pluginId":"i-have-adhd@i-have-adhd"},{"pluginId":"superpowers@openai-curated-remote"},{"pluginId":"context7@context7-marketplace"},{"pluginId":"caveman@thinkhome-caveman"}],"available":[]}' }
$previousCodexHome = $env:CODEX_HOME
$previousProfile = $env:USERPROFILE
$null = Get-InstalledPlugins -Client Codex -HomePath 'C:/temporary profile'
if ($observedCodexHome -ne (Join-Path 'C:/temporary profile' '.codex') -or $observedProfile -ne 'C:/temporary profile') { throw 'Plugin inspection must use the requested user home.' }
if ($env:CODEX_HOME -ne $previousCodexHome -or $env:USERPROFILE -ne $previousProfile) { throw 'Plugin inspection must restore the process environment.' }
$script:answers.Enqueue('done')
$selection = Select-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -Mode Update
if (@($selection.Selected | Where-Object { $_.codex_method -eq 'plugin' }).Count -ne 5) { throw 'All installed Codex plugins must start checked when updating.' }
$script:answers.Enqueue('4')
$script:answers.Enqueue('done')
$selection = Select-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -Mode Update
if (@($selection.Deselected | Where-Object { $_.name -eq 'Context7' }).Count -ne 1) { throw 'Only the explicitly unchecked installed plugin must be deselected.' }
$script:managedArguments = @()
function python { $script:managedArguments = @($args); $global:LASTEXITCODE = 0 }
$entry = [pscustomobject]@{ name = 'Test'; codex_method = 'plugin'; codex_marketplace = '-'; codex_plugin = 'test@market' }
Sync-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -HomePath 'C:/temporary home' -Update -Summary -Entries @($entry)
if ($managedArguments -notcontains 'sync' -or $managedArguments -notcontains '--update' -or $managedArguments -notcontains 'Test' -or $managedArguments -notcontains 'C:/temporary home') { throw 'Managed reconciliation must receive selection, home, and update mode.' }
Sync-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -Entries @()
if ($managedArguments[-1] -ne '--selected') { throw 'Empty selection must reach reconciliation for managed cleanup.' }
function Get-InstalledPlugins { throw 'Empty selection must not query installed plugins.' }
function Invoke-PluginCommand { throw 'Empty selection must not run plugin commands.' }
Install-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Both -Entries @()
Uninstall-DeselectedPlugins -Client Both -Entries @()
if ($Summary) { Write-Output 'Tests: PASS | plugin selection' } else { Write-Output 'PASS plugin toggle selection: toggle and confirm' }
exit 0
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
summary=false
while [ $# -gt 0 ]; do
case "$1" in
--summary) summary=true; shift ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
. "$root/scripts/lib/plugins.sh"
# shellcheck disable=SC2034
names=(A B C)
checked=(true false true)
toggle_log=$(mktemp)
trap 'rm -f -- "$toggle_log"' EXIT
read_plugin_toggle_selection names checked <<< $'1\n2\ndone' > "$toggle_log"
[ "$summary" = true ] || cat "$toggle_log"
[ "${checked[0]}" = false ] || { printf 'Toggling checked entry 1 must uncheck it.\n' >&2; exit 1; }
[ "${checked[1]}" = true ] || { printf 'Toggling unchecked entry 2 must check it.\n' >&2; exit 1; }
[ "${checked[2]}" = true ] || { printf 'Entry 3 was never toggled and must stay unchanged.\n' >&2; exit 1; }
read_plugin_toggle_selection names checked <<< '' > "$toggle_log"
[ "${checked[0]}" = false ] && [ "${checked[1]}" = true ]
test_plugin_output() {
printf 'download progress\nWARN test warning\n'
[ "$1" != fail ]
}
compact=$(summary=true run_plugin_command false test_plugin_output success)
[ "$compact" = 'WARN test warning' ] || { printf 'Plugin summary lost warnings or leaked progress.\n' >&2; exit 1; }
if diagnostics=$(summary=true run_plugin_command false test_plugin_output fail 2>&1); then
printf 'Plugin failure must fail.\n' >&2
exit 1
fi
[[ "$diagnostics" == *'download progress'* ]] && [[ "$diagnostics" == *'WARN test warning'* ]]
# shellcheck disable=SC2034
selected=()
plugin_installed() { printf 'Empty selection must not query installed plugins.\n' >&2; exit 1; }
run_plugin_command() { printf 'Empty selection must not run commands.\n' >&2; exit 1; }
install_configured_plugins "$root" both false false selected false
uninstall_deselected_plugins "$root" both false selected false
if read_plugin_toggle_selection names checked </dev/null > "$toggle_log" 2>&1; then
printf 'Closed input must fail instead of looping.\n' >&2
exit 1
fi
if [ "$summary" = true ]; then printf 'Tests: PASS | plugin selection\n'; else printf 'PASS plugin toggle selection: toggle and confirm\n'; fi
+29
View File
@@ -0,0 +1,29 @@
import importlib.util
from pathlib import Path
from tempfile import TemporaryDirectory
import unittest
SPEC = importlib.util.spec_from_file_location('prompt_inventory', Path(__file__).parent / 'lib/prompt-inventory.py')
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
class PromptInventoryTests(unittest.TestCase):
def test_counts_metadata_separately_and_reports_duplicate_names(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
metadata = 'name: "example"\ndescription: Example skill.\n'
for name in ('first', 'second'):
directory = root / name
directory.mkdir()
(directory / 'SKILL.md').write_text(f'---\n{metadata}---\n\nBody\n---\nMore body', encoding='utf-8-sig')
count, header_bytes, body_bytes, duplicates = MODULE.inventory([root, root / 'missing'])
self.assertEqual(count, 2)
self.assertEqual(header_bytes, 2 * len(metadata.encode()))
self.assertGreater(body_bytes, header_bytes)
self.assertEqual(duplicates, ['example'])
if __name__ == '__main__':
unittest.main()
+6
View File
@@ -0,0 +1,6 @@
[CmdletBinding()]
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
& python (Join-Path $PSScriptRoot 'test-quick-update.py')
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Output 'Tests: PASS | quick update selection'
+162
View File
@@ -0,0 +1,162 @@
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parent.parent.parent
HELPER = ROOT / 'scripts/lib/selection-state.py'
class SelectionTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.home = Path(self.temporary.name)
self.path = self.home / '.my-ai-configuration/selection.json'
def run_state(self, action, *arguments):
return subprocess.run([sys.executable, str(HELPER), action, '--home', str(self.home),
'--manifest', str(ROOT / 'adapters/plugins.tsv'), *arguments],
capture_output=True, text=True, timeout=10)
def save(self, *arguments):
result = self.run_state('write', '--shell', 'powershell', '--update-agents', 'false', '--flashbang', 'false', '--client', 'both', *arguments)
self.assertEqual(result.returncode, 0, result.stderr)
def test_missing_selection_fails_without_creating_state(self):
result = self.run_state('read')
self.assertNotEqual(result.returncode, 0)
self.assertIn('without Quick', result.stderr)
self.assertFalse(self.path.exists())
def test_selection_round_trip_and_last_successful_write(self):
self.save('--selected', 'Superpowers', 'Anthropic Frontend Design', '--deselected', 'Context7')
original = self.path.read_bytes()
state = json.loads(self.run_state('read').stdout)
self.assertEqual(state['shell'], 'powershell')
self.assertEqual(state['client'], 'both')
self.assertEqual(state['selected'], ['Superpowers', 'Anthropic Frontend Design'])
self.assertEqual(state['deselected'], ['Context7'])
self.assertEqual(self.path.read_bytes(), original)
self.assertIn('selected\tAnthropic Frontend Design', self.run_state('read', '--format', 'tsv').stdout)
self.save('--selected', 'Context7')
self.assertEqual(json.loads(self.run_state('read').stdout)['selected'], ['Context7'])
def test_empty_selection_does_not_select_everything(self):
self.save()
self.assertEqual(json.loads(self.run_state('read').stdout)['selected'], [])
def test_legacy_state_requires_normal_update_and_can_supply_defaults(self):
self.path.parent.mkdir()
self.path.write_text(json.dumps(dict(version=1, platform='windows', client='both', selected=[], deselected=[])))
result = self.run_state('read')
self.assertNotEqual(result.returncode, 0)
self.assertIn('without Quick', result.stderr)
migrated = json.loads(self.run_state('read', '--allow-legacy').stdout)
self.assertEqual(migrated['shell'], 'powershell')
self.assertFalse(migrated['update_agents'])
self.assertTrue(migrated['flashbang'])
def test_boolean_options_are_required_and_preserved(self):
self.save()
state = json.loads(self.run_state('read').stdout)
self.assertFalse(state['update_agents'])
self.assertFalse(state['flashbang'])
state['flashbang'] = 'false'
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()
result = self.run_state('write', '--shell', 'bash', '--update-agents', 'false', '--flashbang', 'true', '--client', 'codex', '--selected', 'Unknown')
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.path.read_bytes(), original)
def test_corrupt_or_conflicting_state_is_rejected(self):
self.path.parent.mkdir()
for state in ('{', '[]', json.dumps(dict(version=1, platform='windows', client='both',
selected=['Context7'], deselected=['Context7']))):
self.path.write_text(state)
self.assertNotEqual(self.run_state('read').returncode, 0)
def test_removed_plugins_are_ignored_and_new_plugins_not_selected(self):
self.save('--selected', 'Superpowers')
state = json.loads(self.path.read_text())
state['selected'].append('Removed plugin')
self.path.write_text(json.dumps(state))
self.assertEqual(json.loads(self.run_state('read').stdout)['selected'], ['Superpowers'])
def test_powershell_quick_preview_uses_saved_selection_without_writes(self):
shell = shutil.which('powershell') or shutil.which('pwsh')
if not shell:
self.skipTest('PowerShell unavailable')
environment = dict(os.environ, TEST_SELECTION_HOME=str(self.home), TEST_REPOSITORY_ROOT=str(ROOT))
save = subprocess.run([shell, '-NoProfile', '-Command',
"$ErrorActionPreference = 'Stop'; . (Join-Path $env:TEST_REPOSITORY_ROOT 'scripts/lib/selection-state.ps1'); "
"Save-UpdateSelection -HomePath $env:TEST_SELECTION_HOME -RepositoryRoot $env:TEST_REPOSITORY_ROOT "
"-Shell PowerShell -Client Both -UpdateAgents $false -Flashbang $false -Plugins @{ Selected = @([pscustomobject]@{name='Superpowers'}); Deselected = @() }"],
env=environment, capture_output=True, text=True, timeout=10)
self.assertEqual(save.returncode, 0, save.stdout + save.stderr)
original = self.path.read_bytes()
source = (ROOT / 'scripts/commands/update.ps1').read_text(encoding='utf-8-sig')
source = source.replace("[Environment]::GetFolderPath('UserProfile')", '$env:TEST_SELECTION_HOME')
with tempfile.NamedTemporaryFile(mode='w', suffix='.tmp.ps1', dir=ROOT / 'scripts',
encoding='utf-8', delete=False) as stream:
stream.write(source)
entry = Path(stream.name)
self.addCleanup(entry.unlink)
result = subprocess.run([shell, '-NoProfile', '-File', str(entry), '-Quick', '-DryRun'],
env=environment, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=90)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertNotIn('Select plugins', result.stdout)
self.assertIn('DRYRUN ensure codex plugin: Superpowers', result.stdout)
self.assertNotIn('DRYRUN ensure codex plugin: Context7', result.stdout)
self.assertEqual(self.path.read_bytes(), original)
self.assertFalse((self.home / '.codex').exists())
failed = subprocess.run([shell, '-NoProfile', '-File', str(entry), '-Quick'], env=environment,
stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=10)
self.assertNotEqual(failed.returncode, 0)
self.assertEqual(self.path.read_bytes(), original)
def test_bash_quick_preview_uses_saved_selection_without_writes(self):
shell = shutil.which('bash')
if os.name == 'nt':
candidate = Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe'
shell = str(candidate) if candidate.exists() else None
if not shell:
self.skipTest('Bash unavailable')
save = subprocess.run([shell, '-c',
'set -euo pipefail; root=$1; home_path=$2; shell=powershell; client=both; update_agents=false; flashbang=false; '
'selected_plugins=(Superpowers); deselected_plugins=(); '
'. "$root/scripts/lib/selection-state.sh"; save_update_selection',
'test', ROOT.as_posix(), self.home.as_posix()],
capture_output=True, text=True, timeout=10)
self.assertEqual(save.returncode, 0, save.stdout + save.stderr)
original = self.path.read_bytes()
environment = dict(os.environ, HOME=self.home.as_posix())
result = subprocess.run([shell, str(ROOT / 'scripts/commands/update.sh'), '--quick', '--dry-run'],
env=environment, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=90)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertNotIn('Select plugins', result.stdout)
self.assertIn('DRYRUN ensure codex plugin: Superpowers', result.stdout)
self.assertNotIn('DRYRUN ensure codex plugin: Context7', result.stdout)
self.assertEqual(self.path.read_bytes(), original)
self.assertFalse((self.home / '.codex').exists())
if __name__ == '__main__':
unittest.main(argv=[sys.argv[0]], verbosity=1)
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
case "${1:-}" in ''|--summary) ;; *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; esac
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
python3 "$root/scripts/tests/test-quick-update.py"
printf 'Tests: PASS | quick update selection\n'
+351
View File
@@ -0,0 +1,351 @@
import importlib.util
from array import array
from concurrent.futures import ThreadPoolExecutor
import os
from pathlib import Path
import re
from tempfile import TemporaryDirectory
from types import SimpleNamespace
import time
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parent.parent.parent
SPEC = importlib.util.spec_from_file_location('retrieval_server', ROOT / 'shared/retrieval/server.py')
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
def tokenize(text, **kwargs):
offsets = [match.span() for match in re.finditer(r'\S+', text)]
return {'offset_mapping': offsets, 'input_ids': list(range(len(offsets)))}
def embedding():
return array('f', [1.0] + [0.0] * (MODULE.EMBEDDING_DIMENSIONS - 1))
class RetrievalServerTests(unittest.TestCase):
def test_runtime_python_uses_adjacent_virtualenv(self):
with TemporaryDirectory() as temporary:
script = Path(temporary) / 'semantic-retrieval' / 'server.py'
runtime = script.parent / '.venv' / 'Scripts' / 'python.exe'
runtime.parent.mkdir(parents=True)
runtime.touch()
self.assertEqual(MODULE.runtime_python(script, 'C:/Python/python.exe', 'nt'), runtime.resolve())
def test_preferred_device_uses_cuda_with_cpu_fallback(self):
with patch.dict('sys.modules', torch=SimpleNamespace(cuda=SimpleNamespace(is_available=lambda: True))):
self.assertEqual(MODULE.preferred_device(), 'cuda')
with patch.dict('sys.modules', torch=SimpleNamespace(cuda=SimpleNamespace(is_available=lambda: False))):
self.assertEqual(MODULE.preferred_device(), 'cpu')
@unittest.skipUnless(os.environ.get('QWEN_TEST_MODEL_CACHE'), 'Set QWEN_TEST_MODEL_CACHE to run the real-model check.')
def test_real_models(self):
os.environ['HF_HOME'] = os.environ['QWEN_TEST_MODEL_CACHE']
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / 'authentication.md').write_text('Password reset tokens are created by create_reset_token. The reset_password endpoint sends the user a password reset email.', encoding='utf-8')
(root / 'weather.md').write_text('The weather service fetches temperature and rainfall forecasts for a city.', encoding='utf-8')
index = MODULE.Index(root, root / 'data')
try:
tokenizer = index.encoder().tokenizer
for source in ['alpha beta gamma ' * 450, 'Grüße 世界 🙂 ' * 300]:
windows = list(MODULE.chunks(source, 'sample.txt', tokenizer))
self.assertGreater(len(windows), 1)
self.assertTrue(all(len(tokenizer(value, add_special_tokens=False)['input_ids']) <= 400
for _, value in windows))
self.assertTrue(all('\ufffd' not in value for _, value in windows))
for query, expected in [('Where are password reset tokens created?', 'authentication.md'),
('Which service provides rainfall forecasts?', 'weather.md')]:
started = time.monotonic()
results = index.search(query, 2)
self.assertEqual(results[0]['path'], expected)
self.assertEqual(len(results), 2)
self.assertGreater(results[0]['score'], results[1]['score'])
print(f'Qwen real search: {expected}, {time.monotonic() - started:.2f}s, embedding device {index.encoder().device}, reranker device {index.reranker().device}', flush=True)
finally:
index.db.close()
def test_isolation_freshness_and_incremental_encoding(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
first, second = root / 'first', root / 'second'
first.mkdir()
second.mkdir()
file = first / 'a.md'
file.write_text('alpha', encoding='utf-8')
(second / 'a.md').write_text('other', encoding='utf-8')
indexes = [MODULE.Index(repository, root / 'data') for repository in (first, second)]
encoded = []
def encode(values, **kwargs):
self.assertEqual(kwargs.get('prompt_name'), 'query' if values == ['query'] else None)
encoded.extend(values)
return [embedding() for _ in values]
for index in indexes:
index.encoder = lambda: SimpleNamespace(encode=encode, tokenizer=tokenize)
index.rerank = lambda query, rows, limit: rows[:limit]
numpy = SimpleNamespace(dot=lambda a, b: sum(x * y for x, y in zip(a, b)),
frombuffer=lambda blob, **kwargs: array('f', blob), float32=None)
try:
with patch.dict('sys.modules', numpy=numpy):
self.assertEqual(indexes[0].search('query', 5)[0]['text'], 'alpha')
self.assertEqual(indexes[1].search('query', 5)[0]['text'], 'other')
encoded.clear()
with ThreadPoolExecutor(max_workers=1) as executor:
self.assertEqual(executor.submit(indexes[0].search, 'query', 5).result()[0]['text'], 'alpha')
self.assertEqual(encoded, [])
timestamp = file.stat()
file.write_text('bravo', encoding='utf-8')
os.utime(file, ns=(timestamp.st_atime_ns, timestamp.st_mtime_ns))
self.assertEqual(indexes[0].search('query', 5)[0]['text'], 'bravo')
file.rename(first / 'renamed.md')
self.assertEqual(indexes[0].search('query', 5)[0]['path'], 'renamed.md')
(first / 'renamed.md').unlink()
encoded.clear()
self.assertEqual(indexes[0].search('query', 5), [])
self.assertEqual(indexes[0].search('query', 5), [])
self.assertEqual(encoded, [])
self.assertEqual(indexes[1].search('query', 5)[0]['text'], 'other')
finally:
for index in indexes:
index.db.close()
def test_failed_refresh_preserves_previous_index(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / 'a.md').write_text('before', encoding='utf-8')
index = MODULE.Index(root, root / 'data')
index.encoder = lambda: SimpleNamespace(encode=lambda values, **kwargs: [embedding() for _ in values], tokenizer=tokenize)
try:
index.rebuild()
(root / 'a.md').write_text('after', encoding='utf-8')
(root / 'b.md').write_text('fail', encoding='utf-8')
def encode(values, **kwargs):
if any(value.endswith('\n\nfail') for value in values):
raise RuntimeError('encoder failed')
return [embedding() for _ in values]
index.encoder = lambda: SimpleNamespace(encode=encode, tokenizer=tokenize)
with self.assertRaisesRegex(RuntimeError, 'encoder failed'):
index.rebuild()
self.assertEqual(index.db.execute('select path, text from chunks').fetchall(), [('a.md', 'before')])
self.assertEqual(index.db.execute('select document from lexical').fetchall(),
[('Path: a.md\nSymbol: <module>\n\nbefore',)])
finally:
index.db.close()
def test_refresh_batches_embeddings_for_changed_files(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / 'first.md').write_text('first', encoding='utf-8')
(root / 'second.md').write_text('second', encoding='utf-8')
index = MODULE.Index(root, root / 'data')
encoded = []
def encode(values, **kwargs):
encoded.append(list(values))
return [embedding() for _ in values]
index.encoder = lambda: SimpleNamespace(encode=encode, tokenizer=tokenize)
try:
index.rebuild()
self.assertEqual(len(encoded), 1)
self.assertEqual(len(encoded[0]), 2)
finally:
index.db.close()
def test_scan_prunes_ignored_directories_and_warns_at_limit(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
index = MODULE.Index(root, root / 'data')
(root / 'visible.md').write_text('visible', encoding='utf-8')
for name in ('node_modules', '.git', 'generated', 'data'):
directory = root / name
directory.mkdir(exist_ok=True)
(directory / 'hidden.md').write_text('hidden', encoding='utf-8')
try:
self.assertEqual(index.files(), [(root / 'visible.md').resolve()])
filenames = ['visible.md'] * 2001
with patch.object(MODULE.os, 'walk', return_value=iter([(str(root), [], filenames)])), \
patch.object(MODULE.sys, 'stderr') as stderr:
self.assertEqual(len(index.files()), 2000)
self.assertIn('stopped scanning after 2000', stderr.write.call_args_list[0].args[0])
finally:
index.db.close()
def test_max_files_is_configurable_via_environment(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
index = MODULE.Index(root, root / 'data')
try:
with patch.dict(os.environ, {MODULE.MAX_FILES_ENV: '3'}):
self.assertEqual(MODULE.read_max_files(), 3)
with patch.dict(os.environ, {MODULE.MAX_FILES_ENV: 'not-a-number'}):
with self.assertRaises(ValueError):
MODULE.read_max_files()
with patch.dict(os.environ, {}, clear=True):
self.assertEqual(MODULE.read_max_files(), MODULE.DEFAULT_MAX_FILES)
(root / 'visible.md').write_text('visible', encoding='utf-8')
index.max_files = 3
filenames = ['visible.md'] * 5
with patch.object(MODULE.os, 'walk', return_value=iter([(str(root), [], filenames)])), \
patch.object(MODULE.sys, 'stderr'):
self.assertEqual(len(index.files()), 3)
finally:
index.db.close()
def test_daemon_address_is_deterministic_and_platform_specific(self):
with TemporaryDirectory() as temporary:
data_dir = Path(temporary)
first = MODULE.daemon_address(data_dir)
second = MODULE.daemon_address(data_dir)
self.assertEqual(first, second)
if os.name == 'nt':
self.assertTrue(first.startswith(r'\\.\pipe\ai-config-retrieval-'))
else:
self.assertTrue(first.startswith(str(data_dir)))
def test_shared_encoder_and_reranker_are_cached_module_singletons(self):
with patch.object(MODULE, '_shared_encoder', None), patch.object(MODULE, '_shared_reranker', None), \
patch.object(MODULE, 'preferred_device', lambda: 'cpu'):
sentinel = SimpleNamespace()
with patch.dict('sys.modules', sentence_transformers=SimpleNamespace(
SentenceTransformer=lambda *args, **kwargs: sentinel,
CrossEncoder=lambda *args, **kwargs: sentinel)):
self.assertIs(MODULE.shared_encoder(), sentinel)
self.assertIs(MODULE.shared_encoder(), MODULE.shared_encoder())
self.assertIs(MODULE.shared_reranker(), sentinel)
self.assertIs(MODULE.shared_reranker(), MODULE.shared_reranker())
def test_rerank_uses_cross_encoder_scores(self):
index = MODULE.Index.__new__(MODULE.Index)
class Reranker:
def predict(self, pairs, **kwargs):
self.pairs = pairs
return [0.1, 0.8, 0.5]
reranker = Reranker()
index.reranker = lambda: reranker
rows = [
{'path': 'a.md', 'symbol': 'A', 'score': 0.9, 'text': 'first'},
{'path': 'b.md', 'symbol': 'B', 'score': 0.8, 'text': 'second'},
{'path': 'c.md', 'symbol': 'C', 'score': 0.7, 'text': 'third'},
]
result = index.rerank('query', rows, 2)
self.assertEqual([item['path'] for item in result], ['b.md', 'c.md'])
self.assertEqual(reranker.pairs, [('query', 'Path: a.md\nSymbol: A\n\nfirst'),
('query', 'Path: b.md\nSymbol: B\n\nsecond'),
('query', 'Path: c.md\nSymbol: C\n\nthird')])
def test_token_windows_have_exact_overlap_and_no_redundant_tail(self):
for size, lengths in [(0, []), (400, [400]), (401, [400, 65]), (736, [400, 400]), (800, [400, 400, 128])]:
words = [f'token{number}' for number in range(size)]
result = list(MODULE.chunks(' '.join(words), 'plain.txt', tokenize))
windows = [content.split() for _, content in result]
self.assertEqual([len(window) for window in windows], lengths)
for first, second in zip(windows, windows[1:]):
self.assertEqual(first[-64:], second[:64])
if windows:
reconstructed = windows[0] + [word for window in windows[1:] for word in window[64:]]
self.assertEqual(reconstructed, words)
def test_python_symbols_include_decorators_and_restore_parent(self):
source = 'class Service:\n @cached\n def load(self):\n return 1\n\nvalue = 2\n\nasync def refresh():\n return value\n'
result = list(MODULE.sections(source, 'service.py'))
self.assertEqual([symbol for symbol, _ in result], ['Service', 'Service.load', '<module>', 'refresh'])
self.assertIn('@cached', result[1][1])
self.assertEqual(''.join(content for _, content in result), source)
broken = 'def incomplete(\n' + 'word ' * 450
self.assertEqual(len(list(MODULE.chunks(broken, 'broken.py', tokenize))), 2)
def test_markdown_sections_ignore_fenced_headings(self):
source = '# Setup\nIntro\n## Install\n```sh\n# not a heading\n```\nSteps\n## Run\nStart\n'
result = list(MODULE.chunks(source, 'guide.md', tokenize))
self.assertEqual([symbol for symbol, _ in result], ['Setup', 'Setup / Install', 'Setup / Run'])
self.assertIn('# not a heading', result[1][1])
def test_common_declarations_and_config_sections(self):
for path, source, symbols in [
('service.ts', 'export function load() {\n return 1;\n}\nexport const save = () => 2;\n', ['load', 'save']),
('service.cs', 'public class Service\n{\n public void Load()\n {\n }\n}\n', ['Service', 'Load']),
('setup.ps1', 'function Get-Result {\n return 1\n}\n', ['Get-Result']),
('config.toml', '[server]\nport = 1\n[client]\nport = 2\n', ['server', 'client']),
]:
self.assertEqual([symbol for symbol, _ in MODULE.sections(source, path)], symbols)
def test_rrf_rewards_agreement_and_breaks_ties_deterministically(self):
self.assertEqual(MODULE.reciprocal_rank_fusion([1, 2, 3], [4, 2, 3]), [2, 3, 1, 4])
self.assertEqual(MODULE.reciprocal_rank_fusion([], [4, 2]), [4, 2])
def test_hybrid_candidates_and_document_metadata(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
for number in range(110):
(root / f'{number:03}.md').write_text(f'# Symbol{number}\ncontent {number}', encoding='utf-8', newline='\n')
index = MODULE.Index(root, root / 'data')
documents = []
def encode(values, **kwargs):
if kwargs.get('prompt_name') != 'query':
documents.extend(values)
return [embedding() for _ in values]
pairs = []
index.encoder = lambda: SimpleNamespace(encode=encode, tokenizer=tokenize)
index.reranker = lambda: SimpleNamespace(predict=lambda values, **kwargs:
pairs.extend(values) or [float('109.md' in document) for _, document in values])
numpy = SimpleNamespace(dot=lambda a, b: 1.0,
frombuffer=lambda blob, **kwargs: array('f', blob), float32=None)
original_fusion = MODULE.reciprocal_rank_fusion
rankings = []
def fuse(*values):
rankings.extend(values)
return original_fusion(*values)
try:
with patch.dict('sys.modules', numpy=numpy), patch.object(MODULE, 'reciprocal_rank_fusion', fuse):
results = index.search('109.md Symbol109 content', 20)
self.assertEqual([len(ranking) for ranking in rankings], [50, 50])
self.assertEqual(len(pairs), 50)
self.assertEqual(len(results), 5)
self.assertEqual(results[0]['path'], '109.md')
self.assertEqual(results[0]['symbol'], 'Symbol109')
self.assertIn('Path: 109.md\nSymbol: Symbol109\n\n# Symbol109\ncontent 109', documents)
self.assertTrue(all(document in documents for _, document in pairs))
with patch.dict('sys.modules', numpy=numpy):
self.assertEqual(len(index.search('" OR * () : -')), 5)
self.assertEqual(len(index.search('***')), 5)
(root / '109.md').unlink()
index.search('Symbol109')
self.assertEqual(index.db.execute('select count(*) from lexical where lexical match ?', ('"Symbol109"',)).fetchone()[0], 0)
finally:
index.db.close()
def test_old_cache_is_preserved_but_not_reused(self):
with TemporaryDirectory() as temporary:
root = Path(temporary)
data = root / 'data'
data.mkdir()
identity = os.path.normcase(str(root.resolve())) + '\n' + MODULE.MODEL
old = data / (MODULE.hashlib.sha256(identity.encode()).hexdigest() + '.sqlite3')
old.write_bytes(b'old cache')
index = MODULE.Index(root, data)
try:
self.assertNotEqual(Path(index.db.execute('pragma database_list').fetchone()[2]), old)
self.assertEqual(old.read_bytes(), b'old cache')
finally:
index.db.close()
if __name__ == '__main__':
unittest.main()
+77
View File
@@ -0,0 +1,77 @@
import hashlib
import importlib.util
import io
import json
from contextlib import redirect_stdout
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parent.parent.parent
SPEC = importlib.util.spec_from_file_location('semantic_retrieval', ROOT / 'scripts/lib/semantic-retrieval.py')
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
class SemanticRetrievalTests(unittest.TestCase):
def test_sync_does_not_register_mcp(self):
with tempfile.TemporaryDirectory() as directory:
home = Path(directory) / 'home'
output = io.StringIO()
with redirect_stdout(output):
MODULE.sync(ROOT, home, ['codex', 'claude'], True, True, False)
self.assertNotIn('mcp', output.getvalue().lower())
def test_runtime_prefers_cuda_packages_for_nvidia_gpu(self):
with tempfile.TemporaryDirectory() as directory:
target = Path(directory)
python = MODULE.python_path(target / '.venv')
python.parent.mkdir(parents=True)
python.touch()
with patch.object(MODULE.shutil, 'which', return_value='nvidia-smi'), patch.object(MODULE.subprocess, 'run') as run:
MODULE.ensure_runtime(target, False)
self.assertEqual(run.call_args.args[0][-2:], ['--extra-index-url', MODULE.CUDA_INDEX])
def test_runtime_falls_back_to_cpu_packages_when_cuda_install_fails(self):
with tempfile.TemporaryDirectory() as directory:
target = Path(directory)
python = MODULE.python_path(target / '.venv')
python.parent.mkdir(parents=True)
python.touch()
failure = MODULE.subprocess.CalledProcessError(1, 'pip')
with patch.object(MODULE.shutil, 'which', return_value='nvidia-smi'), patch.object(MODULE.subprocess, 'run', side_effect=[failure, None]) as run:
MODULE.ensure_runtime(target, False)
self.assertEqual(run.call_count, 2)
self.assertNotIn('--extra-index-url', run.call_args.args[0])
def test_summary_dry_run_omits_details_without_writes(self):
with tempfile.TemporaryDirectory() as directory:
home = Path(directory) / 'home'
for enabled in (True, False):
output = io.StringIO()
with redirect_stdout(output):
MODULE.sync(ROOT, home, ['codex'], enabled, True, False, summary=True)
self.assertEqual(output.getvalue(), '')
self.assertFalse(home.exists())
def test_disable_removes_owned_runtime_without_cli_registration(self):
with tempfile.TemporaryDirectory() as directory:
home = Path(directory) / 'home'
target = home / '.my-ai-configuration/semantic-retrieval'
target.mkdir(parents=True)
for name in MODULE.FILES:
(target / name).write_text(name)
state_path = target.parent / 'semantic-retrieval.json'
state_path.write_text(json.dumps({'version': 1, 'clients': ['codex'], 'files': {
name: hashlib.sha256((target / name).read_bytes()).hexdigest() for name in MODULE.FILES}}))
MODULE.sync(ROOT, home, ['codex'], False, False, False)
self.assertFalse(target.exists())
self.assertFalse(state_path.exists())
if __name__ == '__main__':
unittest.main()
+191
View File
@@ -0,0 +1,191 @@
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parent.parent.parent
SHELLS = [
('powershell', shutil.which('powershell') or shutil.which('pwsh')),
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
if os.name == 'nt' else shutil.which('bash')),
]
class SessionStateHookTests(unittest.TestCase):
def test_git_identity_and_stale_verification(self):
with tempfile.TemporaryDirectory() as directory:
workspace = Path(directory)
env = dict(os.environ, GIT_AUTHOR_NAME='Test', GIT_AUTHOR_EMAIL='test@example.invalid', GIT_COMMITTER_NAME='Test', GIT_COMMITTER_EMAIL='test@example.invalid')
subprocess.run(['git', '-C', str(workspace), 'init', '-q'], check=True)
(workspace / 'file').write_text('one')
subprocess.run(['git', '-C', str(workspace), 'add', 'file'], check=True)
subprocess.run(['git', '-C', str(workspace), 'commit', '-qm', 'one'], check=True, env=env)
command = [sys.executable, str(ROOT / 'shared/hooks/scripts/session-state.py'), '--workspace', str(workspace)]
self.assertEqual(subprocess.run(command + ['--verified', 'check'], capture_output=True).returncode, 0)
state = json.loads((workspace / '.ai-session/state.json').read_text())
self.assertTrue(state['head'])
self.assertEqual(state['verification'][0]['head'], state['head'])
(workspace / 'file').write_text('two')
subprocess.run(['git', '-C', str(workspace), 'add', 'file'], check=True)
subprocess.run(['git', '-C', str(workspace), 'commit', '-qm', 'two'], check=True, env=env)
self.assertEqual(subprocess.run(command, capture_output=True).returncode, 0)
state = json.loads((workspace / '.ai-session/state.json').read_text())
self.assertTrue(state['verificationStale'])
self.assertTrue(state['verification'][0]['stale'])
def test_writer_migrates_legacy_state_and_requires_reset_for_new_goal(self):
with tempfile.TemporaryDirectory() as directory:
workspace = Path(directory)
state_file = workspace / '.ai-session/state.json'
state_file.parent.mkdir()
state_file.write_text(json.dumps({'goal': 'Original', 'changedFiles': ['a'],
'pending': ['b'], 'importantFindings': ['c']}))
command = [sys.executable, str(ROOT / 'shared/hooks/scripts/session-state.py'),
'--workspace', str(workspace)]
result = subprocess.run(command + ['--verified', 'passed'], capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr)
state = json.loads(state_file.read_text())
self.assertEqual(state['changed'], ['a'])
self.assertEqual(state['open'], ['b'])
self.assertEqual(state['decisions'], ['c'])
self.assertFalse({'changedFiles', 'pending', 'importantFindings'} & state.keys())
before = state_file.read_bytes()
result = subprocess.run(command + ['--goal', 'New'], capture_output=True)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(state_file.read_bytes(), before)
result = subprocess.run(command + ['--reset', '--goal', 'New'], capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr)
state = json.loads(state_file.read_text())
self.assertEqual(state['goal'], 'New')
self.assertEqual(state['verified'], [])
self.assertEqual(state['changed'], [])
state_file.write_text('{"goal":"New","verified":"invalid"}')
before = state_file.read_bytes()
result = subprocess.run(command + ['--open', 'next'], capture_output=True)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(state_file.read_bytes(), before)
def test_writers_preserve_partial_updates_and_escape_values(self):
for shell, executable in SHELLS:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=shell), tempfile.TemporaryDirectory() as directory:
base = Path(directory)
repository = base / 'repository'
shutil.copytree(ROOT / 'shared/hooks/scripts', repository / 'shared/hooks/scripts')
(repository / 'scripts').mkdir()
for filename in ('session-state.ps1', 'session-state.sh'):
shutil.copy(ROOT / 'scripts' / filename, repository / 'scripts' / filename)
workspace = base / 'workspace ü'
workspace.mkdir()
environment = dict(os.environ, TASK_WORKSPACE=str(workspace),
TASK_GOAL='Repair "quotes"\n' + 'x' * 300)
if shell == 'powershell':
prefix = [executable, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command']
commands = [
'& ./scripts/commands/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Goal $env:TASK_GOAL -Decisions @("keep", "line two") -Pending @("check")',
'& ./scripts/commands/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Verified @("passed")',
'& ./scripts/commands/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Pointer',
]
else:
prefix = [executable, '-c']
commands = [
'bash ./scripts/commands/session-state.sh --workspace "$TASK_WORKSPACE" --goal "$TASK_GOAL" --decisions keep "line two" --open check',
'bash ./scripts/commands/session-state.sh --workspace "$TASK_WORKSPACE" --verified passed',
'bash ./scripts/commands/session-state.sh --workspace "$TASK_WORKSPACE" --pointer',
]
for command in commands:
result = subprocess.run(prefix + [command], cwd=repository, capture_output=True,
encoding='utf-8', timeout=20, env=environment)
self.assertEqual(result.returncode, 0, result.stderr)
state_file = workspace / '.ai-session/state.json'
self.assertIn(str(state_file), result.stdout)
state = json.loads(state_file.read_text(encoding='utf-8-sig'))
self.assertEqual(state['goal'], environment['TASK_GOAL'])
self.assertEqual(state['decisions'], ['keep', 'line two'])
self.assertEqual(state['open'], ['check'])
self.assertEqual(state['verified'], ['passed'])
self.assertEqual(state['workspace'], str(workspace))
self.assertFalse((repository / '.ai-session').exists())
self.assertEqual([p.name for p in state_file.parent.iterdir()], ['state.json'])
def run_hook(self, shell, executable, hook, config, workspace, payload):
names = {'pointer': ('Show-SessionStatePointer.ps1', 'show-session-state-pointer.sh'),
'compact': ('Record-Compact.ps1', 'record-compact.sh')}
script = config / 'hooks/scripts' / names[hook][shell == 'bash']
command = ([executable, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', str(script)]
if shell == 'powershell' else [executable, str(script)])
result = subprocess.run(command, cwd=workspace, input=payload, capture_output=True,
encoding='utf-8', timeout=20)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stderr, '')
return result.stdout
def test_workspace_selection_and_read_only_state(self):
for shell, executable in SHELLS:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=shell), tempfile.TemporaryDirectory() as directory:
base = Path(directory)
config = base / 'installed config'
shutil.copytree(ROOT / 'shared/hooks/scripts', config / 'hooks/scripts')
workspace = base / 'working project'
selected = base / 'selected project ü'
empty = base / 'empty project'
for folder in (config, workspace, selected, empty):
folder.mkdir(exist_ok=True)
for folder in (config, workspace, selected):
(folder / '.ai-session').mkdir()
(folder / '.ai-session/state.json').write_text('private state content')
for hook in ('pointer', 'compact'):
for payload, expected in (
('', workspace),
('{}', workspace),
(json.dumps({'cwd': str(selected)}, ensure_ascii=False), selected),
(json.dumps({'cwd': str(empty)}), None),
('{"cwd":"relative/path"}', None),
('{"cwd":null}', None),
('{"cwd":12}', None),
('{"cwd":""}', None),
('{bad json', None),
('[]', None),
):
with self.subTest(hook=hook, payload=payload):
output = self.run_hook(shell, executable, hook, config, workspace, payload)
if expected is None:
self.assertEqual(output, '')
else:
self.assertIn(str(expected / '.ai-session/state.json'), output)
self.assertNotIn(str(config), output)
self.assertNotIn('private state content', output)
for folder in (config, workspace, selected):
self.assertEqual((folder / '.ai-session/state.json').read_text(), 'private state content')
self.assertFalse((empty / '.ai-session').exists())
events = (config / '.ai-session/telemetry.jsonl').read_text(encoding='utf-8-sig').splitlines()
self.assertEqual(len(events), 10)
self.assertTrue(all(json.loads(line)['event'] == 'compact' for line in events))
def test_missing_workspace_state_succeeds_without_config_fallback(self):
for shell, executable in SHELLS:
if not executable or not Path(executable).exists():
continue
with self.subTest(shell=shell), tempfile.TemporaryDirectory() as directory:
base = Path(directory)
config = base / 'config'
shutil.copytree(ROOT / 'shared/hooks/scripts', config / 'hooks/scripts')
(config / '.ai-session').mkdir()
(config / '.ai-session/state.json').write_text('{}')
workspace = base / 'workspace'
workspace.mkdir()
for hook in ('pointer', 'compact'):
self.assertEqual(self.run_hook(shell, executable, hook, config, workspace, ''), '')
self.assertEqual(list(workspace.iterdir()), [])
if __name__ == '__main__':
unittest.main()
+90
View File
@@ -0,0 +1,90 @@
[CmdletBinding()]
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$powerShellHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.ps1') -Raw
$bashHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.sh') -Raw
if ($powerShellHook -notmatch '\$HoldMs\s*=\s*250' -or $powerShellHook -notmatch '\$FadeMs\s*=\s*250' -or ([regex]::Matches($bashHook, 'default=250').Count -lt 2)) { throw 'Flashbang defaults must total 500 milliseconds on PowerShell and Bash.' }
$statusInput = '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"' + $root.Replace('\','\\') + '","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}'
$branch = & git -C $root branch --show-current
$buildSummaryOutput = & (Join-Path $root 'scripts/commands/build.ps1') -Summary
if (@($buildSummaryOutput | Where-Object { $_ -eq 'Build: PASS | 4 packages' }).Count -ne 1) { throw "build.ps1 -Summary must still print the PASS line: $($buildSummaryOutput -join '; ')" }
$statusOutput = ($statusInput | & (Join-Path $root 'shared/statusline/statusline.ps1')) -join "`n"
$plainStatusOutput = [regex]::Replace($statusOutput, [char]27 + '\[[0-9;]*m', '')
$expectedStatusOutput = "Test Model $([char]0x00B7) Review auto $([char]0x00B7) Effort high $([char]0x00B7) TestRepo @ $branch`nCtx 200k $([char]0x00B7) Used 9% $([char]0x00B7) Tokens 16.7k"
if ($buildSummaryOutput.Count -ne 1) { throw 'Build summary must contain only one success line.' }
if ($plainStatusOutput -ne $expectedStatusOutput) { throw 'PowerShell status line output is incorrect.' }
$sourceAgentCount = @(Get-ChildItem (Join-Path $root 'shared/agents') -Directory).Count
$sourceSkillCount = @(Get-ChildItem (Join-Path $root 'shared/skills') -Filter 'SKILL.md' -Recurse).Count
$ruleSkillCount = @(Import-Csv -LiteralPath (Join-Path $root 'adapters/rule-skills.tsv') -Delimiter ([char]9)).Count
foreach ($shell in @('powershell','bash')) {
foreach ($client in @('codex','claude')) {
$package = Join-Path $root "generated/$client-$shell"
$file = if ($client -eq 'codex') { 'config.toml' } else { 'settings.json' }
$content = Get-Content -LiteralPath (Join-Path $package $file) -Raw
if ($client -eq 'codex') {
foreach ($entry in @(Import-Csv (Join-Path $root 'adapters/rule-skills.tsv') -Delimiter ([char]9))) {
$rulePath = $entry.rule_file
if (Test-Path (Join-Path $root "shared/rules/$($entry.rule_file)") -PathType Container) { $rulePath = "$($entry.rule_file)/index.md" }
if ((Get-Content (Join-Path $package 'AGENTS.md') -Raw) -notmatch [regex]::Escape("- rules/$rulePath for $($entry.trigger).")) { throw "Codex rule loading is missing $($entry.rule_file) in $package" }
}
}
if ($client -eq 'claude') { $settings = $content | ConvertFrom-Json }
if (@(Get-ChildItem "$package/agents" -File).Count -ne $sourceAgentCount) { throw "Missing agents in $package" }
$expectedReviewerShellTool = if ($shell -eq 'powershell') { 'PowerShell' } else { 'Bash' }
if ($client -eq 'claude' -and (Get-Content (Join-Path $package 'agents/reviewer.md') -Raw) -notmatch "(?m)^tools: Read,Grep,Glob,$expectedReviewerShellTool\r?`$") { throw "Reviewer capability profile is missing in $package" }
$expectedSkillCount = if ($client -eq 'claude') { $sourceSkillCount + $ruleSkillCount } else { $sourceSkillCount }
if (@(Get-ChildItem "$package/skills" -Filter SKILL.md -Recurse).Count -ne $expectedSkillCount) { throw "Missing skills in $package" }
$expected = if ($shell -eq 'powershell') { '__POWERSHELL_COMMAND__ .*flashbang.ps1' } else { 'bash .*flashbang.sh' }
if ($content -notmatch $expected -or $content -match '__HOOK_|__POWERSHELL_HOOK_') { throw "Incorrect shell command in $package" }
if ($shell -eq 'powershell' -and $content -match 'WindowStyle\s+Hidden') { throw "PowerShell hook hides the terminal in $package" }
if ($client -eq 'codex') {
$events = @([regex]::Matches($content, '(?m)^\[\[hooks\.([^.\]]+)\]\]\s*$') | ForEach-Object { $_.Groups[1].Value })
if ($events.Count -ne 1 -or $events[0] -ne 'Stop' -or $content -match 'flashbang-if-input' -or $content -match '(?m)^async\s*=\s*true\s*$') { throw "Codex finish hook is incorrect in $package" }
if ($content -notmatch 'approvals_reviewer\s*=\s*"auto_review"') { throw "Codex auto review is missing in $package" }
if ($content -notmatch '(?m)^max_depth\s*=\s*1\s*$') { throw "Codex agent depth limit is missing in $package" }
if ($content -notmatch 'status_line\s*=\s*\["model", "reasoning", "approval-mode", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"\]') { throw "Codex status line is incorrect in $package" }
} elseif ((($settings.hooks.PSObject.Properties.Name | Sort-Object) -join ',') -ne 'PreCompact,SessionStart,Stop' -or $content -match 'flashbang-if-input' -or $settings.hooks.Stop[0].hooks[0].async) {
throw "Claude finish hook is incorrect in $package"
} elseif ($content -notmatch '"defaultMode"\s*:\s*"auto"') {
throw "Claude auto permission mode is missing in $package"
} elseif (-not $settings.sandbox.enabled -or $settings.sandbox.allowUnsandboxedCommands -ne $false -or $settings.sandbox.failIfUnavailable -ne $true) {
throw "Claude strict sandbox is missing in $package"
} elseif (-not (Test-Path -LiteralPath (Join-Path $package "statusline/statusline.$(if ($shell -eq 'powershell') { 'ps1' } else { 'sh' })")) -or $settings.statusLine.command -notmatch "statusline\.$(if ($shell -eq 'powershell') { 'ps1' } else { 'sh' })") {
throw "Claude status line is incorrect in $package"
}
$docFile = if ($client -eq 'codex') { 'AGENTS.md' } else { 'CLAUDE.md' }
$docContent = Get-Content -LiteralPath (Join-Path $package $docFile) -Raw
if (([regex]::Matches($docContent, [regex]::Escape('Apply instructions in this order'))).Count -ne 1) { throw "$client-$shell must embed the priority rule exactly once" }
if ($client -eq 'codex' -and $docContent -match 'Always load and apply `rules/general\.md`') { throw "$package must not still instruct loading general.md by path" }
if ($client -eq 'claude' -and (Test-Path (Join-Path $package 'rules/general.md'))) { throw 'Claude must not automatically load a second copy of general rules.' }
foreach ($splitRule in @('angular','wpf','ui-ux')) {
$referencesDir = if ($client -eq 'codex') { Join-Path $package "rules/$splitRule/references" } else { Join-Path $package "skills/rules/rules-$splitRule/references" }
if (-not (Test-Path $referencesDir) -or (Get-ChildItem $referencesDir -Filter '*.md').Count -eq 0) { throw "$package is missing reference files for $splitRule" }
}
}
}
foreach ($shellSelection in @('1','2')) {
foreach ($clientSelection in @('1','2','3')) {
$script:answers = [System.Collections.Generic.Queue[string]]::new()
$script:answers.Enqueue($shellSelection)
$script:answers.Enqueue($clientSelection)
function Read-Host { param($Prompt) $answers.Dequeue() }
$output = & (Join-Path $root 'scripts/commands/install.ps1') -DryRun 6>$null
if ($output -notcontains 'PASS install dry-run') { throw 'Dry-run did not finish.' }
$shell = if ($shellSelection -eq '1') { 'powershell' } else { 'bash' }
if (($output -join "`n") -notmatch "-$shell") { throw 'Incorrect selected package.' }
}
}
$doctorSummaryOutput = & (Join-Path $root 'scripts/commands/doctor.ps1') -Summary
if (@($doctorSummaryOutput | Where-Object { $_ -match '^PASS ' }).Count -ne 0) { throw 'Doctor summary mode must not print individual PASS lines.' }
if (@($doctorSummaryOutput | Where-Object { $_ -match '^Doctor: PASS \| \d+ checks$' }).Count -ne 1) { throw "Doctor summary mode must print one 'Doctor: PASS | N checks' line: $($doctorSummaryOutput -join '; ')" }
foreach ($entryPoint in @('install','update')) {
$preview = @(& (Join-Path $root "scripts/commands/$entryPoint.ps1") -Summary -DryRun -Client Both -Shell PowerShell)
if ($preview -match '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) ') { throw "$entryPoint summary leaked per-item output." }
if (@($preview | Where-Object { $_ -match ('^' + $entryPoint + ': PASS') }).Count -ne 1) { throw "$entryPoint summary is missing." }
}
$doctorSummaryOutput | Where-Object { $_ -match '^WARN ' }
if ($Summary) { Write-Output 'Tests: PASS | shell packages' } else { Write-Output 'PASS four shell packages and six PowerShell selections' }
exit 0
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env bash
set -euo pipefail
summary=false
while [ $# -gt 0 ]; do
case "$1" in
--summary) summary=true; shift ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
grep -Eq '\$HoldMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1"
grep -Eq '\$FadeMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1"
[ "$(grep -c 'default=250' "$root/shared/hooks/flashbang.sh")" -ge 2 ]
branch=$(git -C "$root" branch --show-current)
build_summary_output=$(bash "$root/scripts/commands/build.sh" --summary)
if ! printf '%s\n' "$build_summary_output" | grep -qx 'Build: PASS | 4 packages'; then
printf 'build.sh --summary must still print the PASS line: %s\n' "$build_summary_output" >&2
exit 1
fi
[ "$build_summary_output" = 'Build: PASS | 4 packages' ]
status_output=$(printf '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"%s","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}' "$root" | bash "$root/shared/statusline/statusline.sh")
plain_status_output=$(printf '%s' "$status_output" | sed -E $'s/\x1b\\[[0-9;]*m//g')
expected_status_output=$'Test Model \xc2\xb7 Review auto \xc2\xb7 Effort high \xc2\xb7 TestRepo @ '"$branch"$'\nCtx 200k \xc2\xb7 Used 9% \xc2\xb7 Tokens 16.7k'
[ "$plain_status_output" = "$expected_status_output" ]
source_agent_count=$(find "$root/shared/agents" -mindepth 1 -maxdepth 1 -type d | wc -l)
source_skill_count=$(find "$root/shared/skills" -name SKILL.md | wc -l)
rule_skill_count=$(($(wc -l < "$root/adapters/rule-skills.tsv") - 1))
for shell in powershell bash; do
for client in codex claude; do
package="$root/generated/$client-$shell"
file=config.toml
[ "$client" != claude ] || file=settings.json
test -f "$package/$file"
if [ "$client" = codex ]; then
while IFS=$'\t' read -r rule_file _ trigger; do
[ "$rule_file" = rule_file ] && continue
[ -n "$rule_file" ] || continue
rule_path=$rule_file
[ -d "$root/shared/rules/$rule_file" ] && rule_path="$rule_file/index.md"
grep -Fq -- "- rules/$rule_path for $trigger." "$package/AGENTS.md"
done < "$root/adapters/rule-skills.tsv"
fi
test "$(find "$package/agents" -type f | wc -l)" -eq "$source_agent_count"
expected_reviewer_shell_tool=Bash
[ "$shell" != powershell ] || expected_reviewer_shell_tool=PowerShell
if [ "$client" = claude ]; then grep -q "^tools: Read,Grep,Glob,$expected_reviewer_shell_tool\$" "$package/agents/reviewer.md"; fi
expected_skill_count=$source_skill_count
[ "$client" != claude ] || expected_skill_count=$((source_skill_count + rule_skill_count))
test "$(find "$package/skills" -name SKILL.md | wc -l)" -eq "$expected_skill_count"
if [ "$shell" = powershell ]; then
grep -q '__POWERSHELL_COMMAND__ .*flashbang.ps1' "$package/$file"
! grep -Eq 'WindowStyle[[:space:]]+Hidden' "$package/$file"
else
grep -q 'bash .*flashbang.sh' "$package/$file"
fi
if [ "$client" = codex ]; then
[ "$(sed -n 's/^\[\[hooks\.\([^].]*\)\]\]$/\1/p' "$package/$file")" = Stop ]
! grep -q 'flashbang-if-input' "$package/$file"
! grep -Eq '^async[[:space:]]*=[[:space:]]*true[[:space:]]*$' "$package/$file"
grep -q 'approvals_reviewer[[:space:]]*=[[:space:]]*"auto_review"' "$package/$file"
grep -Eq '^max_depth[[:space:]]*=[[:space:]]*1[[:space:]]*$' "$package/$file"
grep -Fq 'status_line = ["model", "reasoning", "approval-mode", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"]' "$package/$file"
else
python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); assert set(settings["hooks"]) == {"PreCompact", "SessionStart", "Stop"}' "$package/$file"
! grep -q 'flashbang-if-input' "$package/$file"
! grep -q '"async"[[:space:]]*:[[:space:]]*true' "$package/$file"
grep -q '"defaultMode"[[:space:]]*:[[:space:]]*"auto"' "$package/$file"
python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); assert settings["sandbox"] == {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True}' "$package/$file"
statusline_extension='sh'
[ "$shell" != powershell ] || statusline_extension=ps1
test -f "$package/statusline/statusline.$statusline_extension"
grep -q "statusline.$statusline_extension" "$package/$file"
fi
if grep -Eq '__HOOK_|__POWERSHELL_HOOK_' "$package/$file"; then exit 1; fi
doc_file=AGENTS.md
[ "$client" != claude ] || doc_file=CLAUDE.md
doc_content=$(cat "$package/$doc_file")
occurrences=$(printf '%s' "$doc_content" | grep -o 'Apply instructions in this order' | wc -l)
[ "$occurrences" -eq 1 ] || { printf '%s-%s must embed the priority rule exactly once\n' "$client" "$shell" >&2; exit 1; }
if [ "$client" = codex ] && printf '%s' "$doc_content" | grep -q 'Always load and apply `rules/general\.md`'; then
printf '%s must not still instruct loading general.md by path\n' "$package" >&2
exit 1
fi
if [ "$client" = claude ] && [ -f "$package/rules/general.md" ]; then
printf 'Claude must not automatically load a second copy of general rules.\n' >&2
exit 1
fi
for split_rule in angular wpf ui-ux; do
if [ "$client" = codex ]; then
references_dir="$package/rules/$split_rule/references"
else
references_dir="$package/skills/rules/rules-$split_rule/references"
fi
if [ ! -d "$references_dir" ] || [ -z "$(find "$references_dir" -maxdepth 1 -name '*.md' -print -quit)" ]; then
printf '%s is missing reference files for %s\n' "$package" "$split_rule" >&2
exit 1
fi
done
done
done
for shell_selection in 1 2; do
for client_selection in 1 2 3; do
output=$(printf '%s\n%s\n' "$shell_selection" "$client_selection" | bash "$root/scripts/commands/install.sh" --dry-run 2>&1) || { printf '%s\n' "$output" >&2; exit 1; }
printf '%s\n' "$output" | grep -E 'WARN|FAIL|WARNING|ERROR' || true
[[ "$output" == *'PASS install dry-run'* ]]
shell=powershell
[ "$shell_selection" != 2 ] || shell=bash
[[ "$output" == *"-$shell"* ]]
done
done
doctor_summary_output=$(bash "$root/scripts/commands/doctor.sh" --summary)
if printf '%s\n' "$doctor_summary_output" | grep -q '^PASS '; then
printf 'Doctor summary mode must not print individual PASS lines.\n' >&2
exit 1
fi
if ! printf '%s\n' "$doctor_summary_output" | grep -Eq '^Doctor: PASS \| [0-9]+ checks$'; then
printf "Doctor summary mode must print one 'Doctor: PASS | N checks' line: %s\n" "$doctor_summary_output" >&2
exit 1
fi
for entry_point in install update; do
preview=$(bash "$root/scripts/commands/$entry_point.sh" --summary --dry-run --client both --shell bash)
if printf '%s\n' "$preview" | grep -Eq '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) '; then
printf '%s summary leaked per-item output.\n' "$entry_point" >&2
exit 1
fi
printf '%s\n' "$preview" | grep -Eiq "^$entry_point: PASS"
done
printf '%s\n' "$doctor_summary_output" | grep '^WARN ' || true
if [ "$summary" = true ]; then printf 'Tests: PASS | shell packages\n'; else printf 'PASS four shell packages and six Bash selections\n'; fi
+37
View File
@@ -0,0 +1,37 @@
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parent.parent.parent
class TelemetryTests(unittest.TestCase):
def test_bash_record_disable_and_summary(self):
bash = shutil.which('bash')
if not bash:
self.skipTest('Bash unavailable')
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
script = root / 'scripts/commands/telemetry.sh'
script.parent.mkdir()
shutil.copy(ROOT / 'scripts/commands/telemetry.sh', script)
env = dict(os.environ, AI_CONFIG_TELEMETRY='1')
result = subprocess.run([bash, str(script), 'record', 'subagent-stop', '--role', 'reviewer', '--filesRead', '3', '--outcome', 'findings'], env=env, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
event = json.loads((root / '.ai-session/telemetry.jsonl').read_text().strip())
self.assertEqual(event['role'], 'reviewer')
self.assertEqual(event['filesRead'], 3)
self.assertNotIn('prompt', event)
disabled = dict(env, AI_CONFIG_TELEMETRY='0')
subprocess.run([bash, str(script), 'record', 'ignored'], env=disabled, check=True)
summary = subprocess.run([bash, str(script), 'summary'], env=env, capture_output=True, text=True, check=True)
self.assertIn('reviewer: runs 1 findings 1', summary.stdout)
if __name__ == '__main__':
unittest.main()