refactor(shell): use powershell and bash package names
This commit is contained in:
1 parent
9a2a606410
commit
6a0c4abb65
11 files changed
+108
-87
No files matched your search
+9
-9
@@ -18,26 +18,26 @@ function Result($state, $message) {
|
||||
}
|
||||
|
||||
foreach ($tool in @('codex','claude')) { if (Get-Command $tool -ErrorAction SilentlyContinue) { $version = & $tool --version 2>&1 | Select-Object -First 1; Result 'PASS' "$tool available ($version)" } else { Result 'WARN' "$tool unavailable" } }
|
||||
if ($PSVersionTable.PSVersion.Major -ge 6 -and -not $IsWindows) { if (Get-Command jq -ErrorAction SilentlyContinue) { Result 'PASS' 'jq available for Claude status line' } else { Result 'WARN' 'jq unavailable; Claude status line is disabled' } }
|
||||
if (Get-Command jq -ErrorAction SilentlyContinue) { Result 'PASS' 'jq available for Claude Bash status line' } else { Result 'WARN' 'jq unavailable; Claude Bash status line is disabled' }
|
||||
|
||||
$sourceAgentCount = @(Get-ChildItem (Join-Path $root 'shared/agents') -Directory -ErrorAction SilentlyContinue).Count
|
||||
$sourceSkillCount = @(Get-ChildItem (Join-Path $root 'shared/skills') -Filter 'SKILL.md' -Recurse -ErrorAction SilentlyContinue).Count
|
||||
$ruleSkillManifestPath = Join-Path $root 'adapters/claude/rule-skills.tsv'
|
||||
$ruleSkillCount = if (Test-Path -LiteralPath $ruleSkillManifestPath) { @(Import-Csv -LiteralPath $ruleSkillManifestPath -Delimiter ([char]9)).Count } else { 0 }
|
||||
|
||||
foreach ($platform in @('windows','linux')) {
|
||||
if (Test-Path (Join-Path $root "generated/codex-$platform/AGENTS.md")) { Result 'PASS' "generated output present ($platform)" } else { Result 'FAIL' "generated output missing ($platform); run build" }
|
||||
if ((Test-Path (Join-Path $root "generated/codex-$platform/hooks/scripts/Validate-CommandSafety.ps1")) -and (Test-Path (Join-Path $root "generated/codex-$platform/hooks/scripts/validate-command-safety.sh")) -and (Test-Path (Join-Path $root "generated/claude-$platform/hooks/scripts/Validate-CommandSafety.ps1")) -and (Test-Path (Join-Path $root "generated/claude-$platform/hooks/scripts/validate-command-safety.sh"))) { Result 'PASS' "generated hooks present ($platform)" } else { Result 'FAIL' "generated hooks missing ($platform); run build" }
|
||||
foreach ($shell in @('powershell','bash')) {
|
||||
if (Test-Path (Join-Path $root "generated/codex-$shell/AGENTS.md")) { Result 'PASS' "generated output present ($shell)" } else { Result 'FAIL' "generated output missing ($shell); run build" }
|
||||
if ((Test-Path (Join-Path $root "generated/codex-$shell/hooks/scripts/Validate-CommandSafety.ps1")) -and (Test-Path (Join-Path $root "generated/codex-$shell/hooks/scripts/validate-command-safety.sh")) -and (Test-Path (Join-Path $root "generated/claude-$shell/hooks/scripts/Validate-CommandSafety.ps1")) -and (Test-Path (Join-Path $root "generated/claude-$shell/hooks/scripts/validate-command-safety.sh"))) { Result 'PASS' "generated hooks present ($shell)" } else { Result 'FAIL' "generated hooks missing ($shell); run build" }
|
||||
foreach ($client in @('codex','claude')) {
|
||||
$package = Join-Path $root "generated/$client-$platform"
|
||||
$package = Join-Path $root "generated/$client-$shell"
|
||||
if (-not (Test-Path $package)) { continue }
|
||||
$agentCount = @(Get-ChildItem (Join-Path $package 'agents') -File -ErrorAction SilentlyContinue).Count
|
||||
if ($agentCount -eq $sourceAgentCount) { Result 'PASS' "$client-$platform agent count matches source ($agentCount)" } else { Result 'FAIL' "$client-$platform agent count $agentCount does not match source ($sourceAgentCount)" }
|
||||
if ($agentCount -eq $sourceAgentCount) { Result 'PASS' "$client-$shell agent count matches source ($agentCount)" } else { Result 'FAIL' "$client-$shell agent count $agentCount does not match source ($sourceAgentCount)" }
|
||||
$expectedSkillCount = if ($client -eq 'claude') { $sourceSkillCount + $ruleSkillCount } else { $sourceSkillCount }
|
||||
$skillCount = @(Get-ChildItem (Join-Path $package 'skills') -Filter 'SKILL.md' -Recurse -ErrorAction SilentlyContinue).Count
|
||||
if ($skillCount -eq $expectedSkillCount) { Result 'PASS' "$client-$platform skill count matches source ($skillCount)" } else { Result 'FAIL' "$client-$platform skill count $skillCount does not match source ($expectedSkillCount)" }
|
||||
$placeholderHits = @(Get-ChildItem $package -File -Recurse | Where-Object { ((Get-Content -LiteralPath $_.FullName -Raw) -replace '__AI_CONFIG_ROOT__', '') -cmatch '__[A-Z0-9_]+__' })
|
||||
if ($placeholderHits.Count -eq 0) { Result 'PASS' "$client-$platform has no unresolved placeholders" } else { Result 'FAIL' "$client-$platform has unresolved placeholders: $($placeholderHits.FullName -join ', ')" }
|
||||
if ($skillCount -eq $expectedSkillCount) { Result 'PASS' "$client-$shell skill count matches source ($skillCount)" } else { Result 'FAIL' "$client-$shell skill count $skillCount does not match source ($expectedSkillCount)" }
|
||||
$placeholderHits = @(Get-ChildItem $package -File -Recurse | Where-Object { ((Get-Content -LiteralPath $_.FullName -Raw) -replace '__AI_CONFIG_ROOT__|__POWERSHELL_COMMAND__', '') -cmatch '__[A-Z0-9_]+__' })
|
||||
if ($placeholderHits.Count -eq 0) { Result 'PASS' "$client-$shell has no unresolved placeholders" } else { Result 'FAIL' "$client-$shell has unresolved placeholders: $($placeholderHits.FullName -join ', ')" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -29,7 +29,7 @@ result() {
|
||||
for tool in codex claude; do
|
||||
if command -v "$tool" >/dev/null; then result PASS "$tool available ($($tool --version 2>&1 | head -n 1))"; else result WARN "$tool unavailable"; fi
|
||||
done
|
||||
command -v jq >/dev/null && result PASS 'jq available for Claude status line' || result WARN 'jq unavailable; Claude status line is disabled'
|
||||
command -v jq >/dev/null && result PASS 'jq available for Claude Bash status line' || result WARN 'jq unavailable; Claude Bash status line is disabled'
|
||||
|
||||
source_agent_count=$(find "$root/shared/agents" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
source_skill_count=$(find "$root/shared/skills" -name SKILL.md 2>/dev/null | wc -l)
|
||||
@@ -37,20 +37,20 @@ rule_skill_manifest="$root/adapters/claude/rule-skills.tsv"
|
||||
rule_skill_count=0
|
||||
[ -f "$rule_skill_manifest" ] && rule_skill_count=$(($(wc -l < "$rule_skill_manifest") - 1))
|
||||
|
||||
for platform in windows linux; do
|
||||
[ -f "$root/generated/codex-$platform/AGENTS.md" ] && result PASS "generated output present ($platform)" || result FAIL "generated output missing ($platform); run build"
|
||||
[ -f "$root/generated/codex-$platform/hooks/scripts/Validate-CommandSafety.ps1" ] && [ -f "$root/generated/codex-$platform/hooks/scripts/validate-command-safety.sh" ] && [ -f "$root/generated/claude-$platform/hooks/scripts/Validate-CommandSafety.ps1" ] && [ -f "$root/generated/claude-$platform/hooks/scripts/validate-command-safety.sh" ] && result PASS "generated hooks present ($platform)" || result FAIL "generated hooks missing ($platform); run build"
|
||||
for shell in powershell bash; do
|
||||
[ -f "$root/generated/codex-$shell/AGENTS.md" ] && result PASS "generated output present ($shell)" || result FAIL "generated output missing ($shell); run build"
|
||||
[ -f "$root/generated/codex-$shell/hooks/scripts/Validate-CommandSafety.ps1" ] && [ -f "$root/generated/codex-$shell/hooks/scripts/validate-command-safety.sh" ] && [ -f "$root/generated/claude-$shell/hooks/scripts/Validate-CommandSafety.ps1" ] && [ -f "$root/generated/claude-$shell/hooks/scripts/validate-command-safety.sh" ] && result PASS "generated hooks present ($shell)" || result FAIL "generated hooks missing ($shell); run build"
|
||||
for client in codex claude; do
|
||||
package="$root/generated/$client-$platform"
|
||||
package="$root/generated/$client-$shell"
|
||||
[ -d "$package" ] || continue
|
||||
agent_count=$(find "$package/agents" -type f 2>/dev/null | wc -l)
|
||||
if [ "$agent_count" -eq "$source_agent_count" ]; then result PASS "$client-$platform agent count matches source ($agent_count)"; else result FAIL "$client-$platform agent count $agent_count does not match source ($source_agent_count)"; fi
|
||||
if [ "$agent_count" -eq "$source_agent_count" ]; then result PASS "$client-$shell agent count matches source ($agent_count)"; else result FAIL "$client-$shell agent count $agent_count does not match source ($source_agent_count)"; fi
|
||||
expected_skill_count=$source_skill_count
|
||||
[ "$client" != claude ] || expected_skill_count=$((source_skill_count + rule_skill_count))
|
||||
skill_count=$(find "$package/skills" -name SKILL.md 2>/dev/null | wc -l)
|
||||
if [ "$skill_count" -eq "$expected_skill_count" ]; then result PASS "$client-$platform skill count matches source ($skill_count)"; else result FAIL "$client-$platform skill count $skill_count does not match source ($expected_skill_count)"; fi
|
||||
leftover_tokens=$(grep -RohE '__[A-Z0-9_]+__' "$package" 2>/dev/null | sort -u | grep -v '^__AI_CONFIG_ROOT__$' || true)
|
||||
if [ -n "$leftover_tokens" ]; then result FAIL "$client-$platform has unresolved placeholders"; else result PASS "$client-$platform has no unresolved placeholders"; fi
|
||||
if [ "$skill_count" -eq "$expected_skill_count" ]; then result PASS "$client-$shell skill count matches source ($skill_count)"; else result FAIL "$client-$shell skill count $skill_count does not match source ($expected_skill_count)"; fi
|
||||
leftover_tokens=$(grep -RohE '__[A-Z0-9_]+__' "$package" 2>/dev/null | sort -u | grep -Ev '^(__AI_CONFIG_ROOT__|__POWERSHELL_COMMAND__)$' || true)
|
||||
if [ -n "$leftover_tokens" ]; then result FAIL "$client-$shell has unresolved placeholders"; else result PASS "$client-$shell has no unresolved placeholders"; fi
|
||||
done
|
||||
done
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
function Read-InstallPlatform {
|
||||
param([string]$Platform)
|
||||
if ($Platform) { return $Platform }
|
||||
return Read-InteractiveSingle -Title 'Select shell' -Names @('PowerShell','Bash') -Values @('Windows','Linux')
|
||||
function Read-InstallShell {
|
||||
param([string]$Shell)
|
||||
if ($Shell) { return $Shell }
|
||||
return Read-InteractiveSingle -Title 'Select shell' -Names @('PowerShell','Bash') -Values @('PowerShell','Bash')
|
||||
}
|
||||
function Read-InstallClient {
|
||||
param([string]$Client)
|
||||
@@ -13,14 +13,14 @@ function Read-InteractiveSingle {
|
||||
param([string]$Title,[string[]]$Names,[string[]]$Values)
|
||||
if ([Console]::IsInputRedirected -or $env:CI -eq 'true' -or $env:AI_CONFIG_NO_INTERACTIVE -eq '1') {
|
||||
Write-Host $Title; for($i=0;$i -lt $Names.Count;$i++){ Write-Host ('{0}) {1}' -f ($i+1),$Names[$i]) }
|
||||
do { $answer=Read-Host 'Selection' } while($answer -notmatch ('^[1-{0}]$' -f $Names.Count)); return $Values[[int]$answer-1]
|
||||
do { $answer=Read-Host 'Selection'; if ($null -eq $answer -or ([Console]::IsInputRedirected -and $answer -eq '')) { throw 'Selection input ended before a choice was provided.' } } while($answer -notmatch ('^[1-{0}]$' -f $Names.Count)); return $Values[[int]$answer-1]
|
||||
}
|
||||
$index=0
|
||||
while($true) {
|
||||
Clear-InteractiveScreen; Write-Host $Title
|
||||
for($i=0;$i -lt $Names.Count;$i++){ $prefix=if($i -eq $index){'>'}else{' '}; if($i -eq $index -and $env:NO_COLOR -ne '1'){Write-Host ('{0} {1}' -f $prefix,$Names[$i]) -ForegroundColor Cyan}else{Write-Host ('{0} {1}' -f $prefix,$Names[$i])} }
|
||||
Write-Host 'Up/Down Navigate Enter Confirm'; $key=[Console]::ReadKey($true)
|
||||
if($key.Key -eq 'Up'){ $index=($index+$Names.Count-1)%$Names.Count } elseif($key.Key -eq 'Down'){ $index=($index+1)%$Names.Count } elseif($key.Key -eq 'Enter'){ Clear-InteractiveScreen; return $Values[$index] }
|
||||
if($key.Key -eq 'UpArrow'){ $index=($index+$Names.Count-1)%$Names.Count } elseif($key.Key -eq 'DownArrow'){ $index=($index+1)%$Names.Count } elseif($key.Key -eq 'Enter'){ Clear-InteractiveScreen; return $Values[$index] }
|
||||
}
|
||||
}
|
||||
function Get-InstallDestinations {
|
||||
@@ -28,8 +28,8 @@ function Get-InstallDestinations {
|
||||
$destinations=@(); if($Client -in @('Codex','Both')){$destinations+=Join-Path $HomePath '.codex'}; if($Client -in @('Claude','Both')){$destinations+=Join-Path $HomePath '.claude'}; return $destinations
|
||||
}
|
||||
function Get-InstallTargets {
|
||||
param([Parameter(Mandatory=$true)][string]$Generated,[Parameter(Mandatory=$true)][string]$HomePath,[Parameter(Mandatory=$true)][string]$Platform,[ValidateSet('Codex','Claude','Both')][Parameter(Mandatory=$true)][string]$Client)
|
||||
$targets=@(); if($Client -in @('Codex','Both')){$targets+=@{Source=(Join-Path $Generated "codex-$Platform");Destination=(Join-Path $HomePath '.codex')};$targets+=@{Source=(Join-Path $Generated "codex-$Platform/skills");Destination=(Join-Path $HomePath '.agents/skills')}}; if($Client -in @('Claude','Both')){$targets+=@{Source=(Join-Path $Generated "claude-$Platform");Destination=(Join-Path $HomePath '.claude')};}; return $targets
|
||||
param([Parameter(Mandatory=$true)][string]$Generated,[Parameter(Mandatory=$true)][string]$HomePath,[Parameter(Mandatory=$true)][string]$Shell,[ValidateSet('Codex','Claude','Both')][Parameter(Mandatory=$true)][string]$Client)
|
||||
$targets=@(); if($Client -in @('Codex','Both')){$targets+=@{Source=(Join-Path $Generated "codex-$($Shell.ToLowerInvariant())");Destination=(Join-Path $HomePath '.codex')};$targets+=@{Source=(Join-Path $Generated "codex-$($Shell.ToLowerInvariant())/skills");Destination=(Join-Path $HomePath '.agents/skills')}}; if($Client -in @('Claude','Both')){$targets+=@{Source=(Join-Path $Generated "claude-$($Shell.ToLowerInvariant())");Destination=(Join-Path $HomePath '.claude')};}; return $targets
|
||||
}
|
||||
function Test-AnyManifestPresent { param([Parameter(Mandatory=$true)][string[]]$Destinations); return [bool](@($Destinations|Where-Object{Test-Path(Get-ManagedManifestPath $_)}).Count) }
|
||||
function Test-AllManifestsPresent { param([Parameter(Mandatory=$true)][string[]]$Destinations); return [bool](@($Destinations|Where-Object{-not(Test-Path(Get-ManagedManifestPath $_))}).Count -eq 0) }
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
read_install_platform() {
|
||||
local platform=$1
|
||||
if [ -n "$platform" ]; then
|
||||
case "$platform" in
|
||||
windows|Windows) printf 'windows\n'; return 0 ;;
|
||||
linux|Linux) printf 'linux\n'; return 0 ;;
|
||||
*) printf 'Unknown --platform value: %s (expected windows or linux)\n' "$platform" >&2; return 1 ;;
|
||||
read_install_shell() {
|
||||
local shell=$1
|
||||
if [ -n "$shell" ]; then
|
||||
case "$shell" in
|
||||
powershell|PowerShell) printf 'powershell\n'; return 0 ;;
|
||||
bash|Bash) printf 'bash\n'; return 0 ;;
|
||||
*) printf 'Unknown --shell value: %s (expected powershell or bash)\n' "$shell" >&2; return 1 ;;
|
||||
esac
|
||||
fi
|
||||
read_single_selection 'Select shell' 'PowerShell' 'windows' 'Bash' 'linux'
|
||||
read_single_selection 'Select shell' 'PowerShell' 'powershell' 'Bash' 'bash'
|
||||
}
|
||||
|
||||
read_install_client() {
|
||||
@@ -25,12 +25,30 @@ read_install_client() {
|
||||
read_single_selection 'Select client' 'Codex' 'codex' 'Claude' 'claude' 'Both' 'both'
|
||||
}
|
||||
|
||||
clear_interactive() { [ -t 0 ] && [ "${CI:-}" != true ] && [ "${AI_CONFIG_NO_INTERACTIVE:-}" != 1 ] && printf '\033[2J\033[H'; }
|
||||
clear_interactive() { [ -t 0 ] && [ "${CI:-}" != true ] && [ "${AI_CONFIG_NO_INTERACTIVE:-}" != 1 ] && printf '\033[2J\033[H' >&2; }
|
||||
read_single_selection() {
|
||||
local title=$1; shift; local labels=() values=() label value index=0 key
|
||||
while [ $# -gt 0 ]; do labels+=("$1"); values+=("$2"); shift 2; done
|
||||
if [ ! -t 0 ] || [ "${CI:-}" = true ] || [ "${AI_CONFIG_NO_INTERACTIVE:-}" = 1 ]; then printf '%s\n' "$title" >&2; for ((index=0;index<${#labels[@]};index++)); do printf '%d) %s\n' "$((index+1))" "${labels[$index]}" >&2; done; while read -r -p 'Selection ' value; do [[ "$value" =~ ^[1-9][0-9]*$ ]] && [ "$value" -le "${#labels[@]}" ] && { printf '%s\n' "${values[$((value-1))]}"; return; }; done; return 1; fi
|
||||
local selected=0; while :; do clear_interactive; printf '%s\n' "$title"; for ((index=0;index<${#labels[@]};index++)); do [ "$index" -eq "$selected" ] && printf '> %s\n' "${labels[$index]}" || printf ' %s\n' "${labels[$index]}"; done; printf '↑/↓ Navigate Enter Confirm\n'; IFS= read -rsn1 key || return 1; case "$key" in $'\x1b') read -rsn2 key; case "$key" in '[A') selected=$(( (selected+${#labels[@]}-1)%${#labels[@]} ));; '[B') selected=$(( (selected+1)%${#labels[@]} ));; esac;; '') clear_interactive; printf '%s\n' "${values[$selected]}"; return;; esac; done
|
||||
local selected=0
|
||||
while :; do
|
||||
clear_interactive
|
||||
printf '%s\n' "$title" >&2
|
||||
for ((index=0;index<${#labels[@]};index++)); do
|
||||
if [ "$index" -eq "$selected" ]; then printf '> %s\n' "${labels[$index]}" >&2; else printf ' %s\n' "${labels[$index]}" >&2; fi
|
||||
done
|
||||
printf 'Up/Down Navigate Enter Confirm\n' >&2
|
||||
IFS= read -rsn1 key || return 1
|
||||
case "$key" in
|
||||
$'\x1b')
|
||||
read -rsn2 key || return 1
|
||||
case "$key" in
|
||||
'[A') selected=$(( (selected+${#labels[@]}-1)%${#labels[@]} ));;
|
||||
'[B') selected=$(( (selected+1)%${#labels[@]} ));;
|
||||
esac;;
|
||||
'') clear_interactive; printf '%s\n' "${values[$selected]}"; return;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# get_install_destinations <home> <client> -- prints one destination per line
|
||||
@@ -40,16 +58,16 @@ get_install_destinations() {
|
||||
case "$client" in claude|both) printf '%s/.claude\n' "$home_path" ;; esac
|
||||
}
|
||||
|
||||
# get_install_targets <generated> <home> <platform> <client> -- prints "source|dest" per line
|
||||
# get_install_targets <generated> <home> <shell> <client> -- prints "source|dest" per line
|
||||
get_install_targets() {
|
||||
local generated=$1 home_path=$2 platform=$3 client=$4
|
||||
local generated=$1 home_path=$2 shell=$3 client=$4
|
||||
case "$client" in
|
||||
codex|both)
|
||||
printf '%s/codex-%s|%s/.codex\n' "$generated" "$platform" "$home_path"
|
||||
printf '%s/codex-%s/skills|%s/.agents/skills\n' "$generated" "$platform" "$home_path"
|
||||
printf '%s/codex-%s|%s/.codex\n' "$generated" "$shell" "$home_path"
|
||||
printf '%s/codex-%s/skills|%s/.agents/skills\n' "$generated" "$shell" "$home_path"
|
||||
;;
|
||||
esac
|
||||
case "$client" in claude|both) printf '%s/claude-%s|%s/.claude\n' "$generated" "$platform" "$home_path" ;; esac
|
||||
case "$client" in claude|both) printf '%s/claude-%s|%s/.claude\n' "$generated" "$shell" "$home_path" ;; esac
|
||||
}
|
||||
|
||||
# any_manifest_present <destination>... -- true (0) if at least one has a manifest
|
||||
|
||||
@@ -3,21 +3,21 @@ param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$generated = Join-Path $root 'generated'
|
||||
if (-not (Test-Path (Join-Path $generated 'claude-linux/CLAUDE.md'))) { throw 'Generated output is missing; run build first.' }
|
||||
if (-not (Test-Path (Join-Path $generated 'claude-bash/CLAUDE.md'))) { throw 'Generated output is missing; run build first.' }
|
||||
function Measure-Tree([string]$path) {
|
||||
if (-not (Test-Path $path)) { return 0 }
|
||||
return [int64]((Get-ChildItem $path -File -Recurse | ForEach-Object { $_.Length } | Measure-Object -Sum).Sum)
|
||||
}
|
||||
function Tokens([int64]$bytes) { return [int][Math]::Ceiling($bytes / 4.0) }
|
||||
$claude = (Get-Item (Join-Path $generated 'claude-linux/CLAUDE.md')).Length
|
||||
$codex = (Get-Item (Join-Path $generated 'codex-linux/AGENTS.md')).Length
|
||||
$skills = Measure-Tree (Join-Path $generated 'claude-linux/skills')
|
||||
$claude = (Get-Item (Join-Path $generated 'claude-bash/CLAUDE.md')).Length
|
||||
$codex = (Get-Item (Join-Path $generated 'codex-bash/AGENTS.md')).Length
|
||||
$skills = Measure-Tree (Join-Path $generated 'claude-bash/skills')
|
||||
$triggers = (Get-Item (Join-Path $root 'adapters/claude/rule-skills.tsv')).Length
|
||||
$lazyItem = Get-ChildItem (Join-Path $root 'shared/rules') -File -Recurse | Sort-Object Length -Descending | Select-Object -First 1
|
||||
$lazy = if ($lazyItem) { [int64]$lazyItem.Length } else { 0 }
|
||||
$baseline = Join-Path $root 'adapters/prompt-budget-baseline.json'
|
||||
$old = if (Test-Path $baseline) { Get-Content $baseline -Raw | ConvertFrom-Json } else { $null }
|
||||
$rows = @([pscustomobject]@{Name='Claude permanent context';Bytes=$claude;Tokens=(Tokens $claude)},[pscustomobject]@{Name='Codex permanent context';Bytes=$codex;Tokens=(Tokens $codex)},[pscustomobject]@{Name='Claude skill metadata';Bytes=$skills;Tokens=(Tokens $skills)},[pscustomobject]@{Name='Rule trigger metadata';Bytes=$triggers;Tokens=(Tokens $triggers)},[pscustomobject]@{Name='Largest lazy rule';Bytes=$lazy;Tokens=(Tokens $lazy)})
|
||||
$rows = @([pscustomobject]@{Name='Claude permanent context';Bytes=$claude;Tokens=(Tokens $claude)},[pscustomobject]@{Name='Codex permanent context';Bytes=$codex;Tokens=(Tokens $codex)},[pscustomobject]@{Name='Claude skill files (lazy)';Bytes=$skills;Tokens=(Tokens $skills)},[pscustomobject]@{Name='Rule trigger metadata';Bytes=$triggers;Tokens=(Tokens $triggers)},[pscustomobject]@{Name='Largest lazy rule';Bytes=$lazy;Tokens=(Tokens $lazy)})
|
||||
$fail = $false
|
||||
if ($old) { foreach ($row in $rows | Select-Object -First 2) { $previous = [double]$old.($row.Name); if ($previous -gt 0 -and $row.Bytes -gt $previous * 1.15) { $fail = $true } } }
|
||||
if ($Summary) { Write-Output 'Prompt Budget'; $rows | ForEach-Object { Write-Output ('{0,-28} {1,8} bytes {2,6} tokens' -f $_.Name,$_.Bytes,$_.Tokens) } } else { $rows | ForEach-Object { Write-Output ('{0}: {1} bytes / {2} tokens' -f $_.Name,$_.Bytes,$_.Tokens) } }
|
||||
|
||||
@@ -4,11 +4,12 @@ summary=false
|
||||
for arg in "$@"; do case "$arg" in --summary) summary=true;; *) printf 'Unknown argument: %s\n' "$arg" >&2; exit 1;; esac; done
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
generated="$root/generated"
|
||||
[ -f "$generated/claude-linux/CLAUDE.md" ] || { printf 'Generated output is missing; run build first.\n' >&2; exit 1; }
|
||||
[ -f "$generated/claude-bash/CLAUDE.md" ] || { printf 'Generated output is missing; run build first.\n' >&2; exit 1; }
|
||||
bytes() { wc -c < "$1" | tr -d ' '; }
|
||||
tree_bytes() { find "$1" -type f -printf '%s\n' 2>/dev/null | awk '{s+=$1} END {print s+0}'; }
|
||||
tokens() { awk -v b="$1" 'BEGIN { printf "%d", (b + 3) / 4 }'; }
|
||||
claude=$(bytes "$generated/claude-linux/CLAUDE.md"); codex=$(bytes "$generated/codex-linux/AGENTS.md"); skills=$(tree_bytes "$generated/claude-linux/skills"); triggers=$(bytes "$root/adapters/claude/rule-skills.tsv"); lazy=$(find "$root/shared/rules" -type f -printf '%s %p\n' | sort -nr | awk 'NR==1 {print $1+0}'); fail=false
|
||||
claude=$(bytes "$generated/claude-bash/CLAUDE.md"); codex=$(bytes "$generated/codex-bash/AGENTS.md"); skills=$(tree_bytes "$generated/claude-bash/skills"); triggers=$(bytes "$root/adapters/claude/rule-skills.tsv"); lazy=$(find "$root/shared/rules" -type f -printf '%s %p\n' | sort -nr | awk 'NR==1 {print $1+0}'); fail=false
|
||||
if [ -f "$root/adapters/prompt-budget-baseline.tsv" ]; then while IFS=$'\t' read -r name old; do current=$([ "$name" = Claude ] && printf '%s' "$claude" || printf '%s' "$codex"); awk -v n="$current" -v o="$old" 'BEGIN { exit !(o > 0 && n > o * 1.15) }' && fail=true; done < "$root/adapters/prompt-budget-baseline.tsv"; fi
|
||||
if [ "$summary" = true ]; then printf 'Prompt Budget\nClaude permanent context %8s bytes %6s tokens\nCodex permanent context %8s bytes %6s tokens\nClaude skill metadata %8s bytes %6s tokens\nRule trigger metadata %8s bytes %6s tokens\nLargest lazy rule %8s bytes %6s tokens\n' "$claude" "$(tokens "$claude")" "$codex" "$(tokens "$codex")" "$skills" "$(tokens "$skills")" "$triggers" "$(tokens "$triggers")" "$lazy" "$(tokens "$lazy")"; else printf 'Prompt budget: Claude %s bytes, Codex %s bytes, skills %s bytes, triggers %s bytes, largest lazy rule %s bytes\n' "$claude" "$codex" "$skills" "$triggers" "$lazy"; fi
|
||||
if [ "$summary" = true ]; then printf 'Prompt Budget\nClaude permanent context %8s bytes %6s tokens\nCodex permanent context %8s bytes %6s tokens\nClaude skill files (lazy) %8s bytes %6s tokens\nRule trigger metadata %8s bytes %6s tokens\nLargest lazy rule %8s bytes %6s tokens\n' "$claude" "$(tokens "$claude")" "$codex" "$(tokens "$codex")" "$skills" "$(tokens "$skills")" "$triggers" "$(tokens "$triggers")" "$lazy" "$(tokens "$lazy")"; else printf 'Prompt budget: Claude %s bytes, Codex %s bytes, skills %s bytes, triggers %s bytes, largest lazy rule %s bytes\n' "$claude" "$codex" "$skills" "$triggers" "$lazy"; fi
|
||||
$fail && { printf 'Prompt budget: permanent context increased by more than 15 percent.\n' >&2; exit 1; }
|
||||
exit 0
|
||||
@@ -4,7 +4,7 @@ $ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -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 Windows and Linux.' }
|
||||
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/build.ps1') -Summary
|
||||
@@ -17,33 +17,33 @@ if ($plainStatusOutput -ne $expectedStatusOutput) { throw 'PowerShell status lin
|
||||
$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/claude/rule-skills.tsv') -Delimiter ([char]9)).Count
|
||||
foreach ($platform in @('windows','linux')) {
|
||||
foreach ($shell in @('powershell','bash')) {
|
||||
foreach ($client in @('codex','claude')) {
|
||||
$package = Join-Path $root "generated/$client-$platform"
|
||||
$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 'claude') { $settings = $content | ConvertFrom-Json }
|
||||
if (@(Get-ChildItem "$package/agents" -File).Count -ne $sourceAgentCount) { throw "Missing agents 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 ($platform -eq 'windows') { 'powershell .*flashbang.ps1' } else { 'bash .*flashbang.sh' }
|
||||
if ($content -notmatch $expected -or $content -match '__HOOK_|__WINDOWS_HOOK_') { throw "Incorrect platform command in $package" }
|
||||
if ($platform -eq 'windows' -and $content -match 'WindowStyle\s+Hidden') { throw "Windows hook hides the terminal 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 'status_line\s*=\s*\["model", "reasoning", "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).Count -ne 1 -or $settings.hooks.PSObject.Properties.Name -ne 'Stop' -or $content -match 'flashbang-if-input' -or $settings.hooks.Stop[0].hooks[0].async) {
|
||||
} 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 (Test-Path -LiteralPath (Join-Path $package "statusline/statusline.$(if ($platform -eq 'windows') { 'ps1' } else { 'sh' })")) -or $settings.statusLine.command -notmatch "statusline\.$(if ($platform -eq 'windows') { 'ps1' } else { 'sh' })") {
|
||||
} 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-$platform must embed the priority rule exactly once" }
|
||||
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')) {
|
||||
@@ -52,27 +52,27 @@ foreach ($platform in @('windows','linux')) {
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($platformSelection in @('1','2')) {
|
||||
foreach ($shellSelection in @('1','2')) {
|
||||
foreach ($clientSelection in @('1','2','3')) {
|
||||
$script:answers = [System.Collections.Generic.Queue[string]]::new()
|
||||
$script:answers.Enqueue($platformSelection)
|
||||
$script:answers.Enqueue($shellSelection)
|
||||
$script:answers.Enqueue($clientSelection)
|
||||
function Read-Host { param($Prompt) $answers.Dequeue() }
|
||||
$output = & "$PSScriptRoot/install.ps1" -DryRun 6>$null
|
||||
if ($output -notcontains 'PASS install dry-run') { throw 'Dry-run did not finish.' }
|
||||
$platform = if ($platformSelection -eq '1') { 'windows' } else { 'linux' }
|
||||
if (($output -join "`n") -notmatch "-$platform") { throw 'Incorrect selected package.' }
|
||||
$shell = if ($shellSelection -eq '1') { 'powershell' } else { 'bash' }
|
||||
if (($output -join "`n") -notmatch "-$shell") { throw 'Incorrect selected package.' }
|
||||
}
|
||||
}
|
||||
$doctorSummaryOutput = & (Join-Path $root 'scripts/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 = @(& "$PSScriptRoot/$entryPoint.ps1" -Summary -DryRun -Client Both -Platform Windows)
|
||||
$preview = @(& "$PSScriptRoot/$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 | platform packages' } else { Write-Output 'PASS four platform packages and six PowerShell selections' }
|
||||
if ($Summary) { Write-Output 'Tests: PASS | shell packages' } else { Write-Output 'PASS four shell packages and six PowerShell selections' }
|
||||
exit 0
|
||||
|
||||
Executable → Regular
+15
-15
@@ -25,9 +25,9 @@ expected_status_output=$'Test Model \xc2\xb7 Effort high \xc2\xb7 TestRepo @ '"$
|
||||
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/claude/rule-skills.tsv") - 1))
|
||||
for platform in windows linux; do
|
||||
for shell in powershell bash; do
|
||||
for client in codex claude; do
|
||||
package="$root/generated/$client-$platform"
|
||||
package="$root/generated/$client-$shell"
|
||||
file=config.toml
|
||||
[ "$client" != claude ] || file=settings.json
|
||||
test -f "$package/$file"
|
||||
@@ -35,8 +35,8 @@ for platform in windows linux; do
|
||||
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 [ "$platform" = windows ]; then
|
||||
grep -q 'powershell .*flashbang.ps1' "$package/$file"
|
||||
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"
|
||||
@@ -48,21 +48,21 @@ for platform in windows linux; do
|
||||
grep -q 'approvals_reviewer[[:space:]]*=[[:space:]]*"auto_review"' "$package/$file"
|
||||
grep -Fq 'status_line = ["model", "reasoning", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"]' "$package/$file"
|
||||
else
|
||||
[ "$(awk '/^ "hooks": \{/{inside=1; next} inside && /^ \}/{inside=0} inside && /^ "[^"]+": \[$/{gsub(/^ "|": \[$/, ""); print}' "$package/$file")" = Stop ]
|
||||
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"
|
||||
statusline_extension=sh
|
||||
[ "$platform" != windows ] || statusline_extension=ps1
|
||||
[ "$shell" != powershell ] || statusline_extension=ps1
|
||||
test -f "$package/statusline/statusline.$statusline_extension"
|
||||
grep -q "statusline.$statusline_extension" "$package/$file"
|
||||
fi
|
||||
if grep -Eq '__HOOK_|__WINDOWS_HOOK_' "$package/$file"; then exit 1; 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" "$platform" >&2; exit 1; }
|
||||
[ "$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
|
||||
@@ -84,14 +84,14 @@ for platform in windows linux; do
|
||||
done
|
||||
done
|
||||
done
|
||||
for platform_selection in 1 2; do
|
||||
for shell_selection in 1 2; do
|
||||
for client_selection in 1 2 3; do
|
||||
output=$(printf '%s\n%s\n' "$platform_selection" "$client_selection" | bash "$root/scripts/install.sh" --dry-run 2>&1) || { printf '%s\n' "$output" >&2; exit 1; }
|
||||
output=$(printf '%s\n%s\n' "$shell_selection" "$client_selection" | bash "$root/scripts/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'* ]]
|
||||
platform=windows
|
||||
[ "$platform_selection" != 2 ] || platform=linux
|
||||
[[ "$output" == *"-$platform"* ]]
|
||||
shell=powershell
|
||||
[ "$shell_selection" != 2 ] || shell=bash
|
||||
[[ "$output" == *"-$shell"* ]]
|
||||
done
|
||||
done
|
||||
doctor_summary_output=$(bash "$root/scripts/doctor.sh" --summary)
|
||||
@@ -104,7 +104,7 @@ if ! printf '%s\n' "$doctor_summary_output" | grep -Eq '^Doctor: PASS \| [0-9]+
|
||||
exit 1
|
||||
fi
|
||||
for entry_point in install update; do
|
||||
preview=$(bash "$root/scripts/$entry_point.sh" --summary --dry-run --client both --platform linux)
|
||||
preview=$(bash "$root/scripts/$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
|
||||
@@ -112,4 +112,4 @@ for entry_point in install update; do
|
||||
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 | platform packages\n'; else printf 'PASS four platform packages and six Bash selections\n'; fi
|
||||
if [ "$summary" = true ]; then printf 'Tests: PASS | shell packages\n'; else printf 'PASS four shell packages and six Bash selections\n'; fi
|
||||
Reference in new issue
Block a user