diff --git a/scripts/lib/manifest.ps1 b/scripts/lib/manifest.ps1 index 09350a1..2590b33 100644 --- a/scripts/lib/manifest.ps1 +++ b/scripts/lib/manifest.ps1 @@ -86,7 +86,7 @@ function Sync-ManagedDestination { if ($relative -eq 'config.toml' -and (Test-Path -LiteralPath $target)) { $localConfig = [System.IO.File]::ReadAllText($target) $pluginTables = [regex]::Matches($localConfig, '(?m)^[ \t]*\[(?:plugins|marketplaces)(?:\.|\])[\s\S]*?(?=^[ \t]*\[|\z)') - $rootConfig = [regex]::Split($localConfig, '(?m)^[ \t]*\[', 2)[0] + $rootConfig = ([regex]::Split($localConfig, '(?m)^[ \t]*\[', 2)[0]).TrimStart([char]0xFEFF) $localSettings = [regex]::Matches($rootConfig, '(?m)^[ \t]*(?:model|model_reasoning_effort)[ \t]*=.*$') if ($localSettings.Count -gt 0) { $content = [System.Text.Encoding]::UTF8.GetString($newBytes).TrimEnd([char[]]"`r`n") @@ -95,7 +95,10 @@ function Sync-ManagedDestination { $content = [regex]::Replace($content, "(?m)^[ \t]*$([regex]::Escape($name))[ \t]*=.*(?:\r?\n|$)", '') } $preservedSettings = ($localSettings | ForEach-Object { $_.Value.TrimEnd([char[]]"`r`n") }) -join "`n" - $newBytes = [System.Text.Encoding]::UTF8.GetBytes("$content`n$preservedSettings`n") + $firstTable = [regex]::Match($content, '(?m)^[ \t]*\[') + if ($firstTable.Success) { $content = $content.Insert($firstTable.Index, "$preservedSettings`n") } + else { $content = "$content`n$preservedSettings" } + $newBytes = [System.Text.Encoding]::UTF8.GetBytes("$content`n") } if ($pluginTables.Count -gt 0) { $content = [System.Text.Encoding]::UTF8.GetString($newBytes).TrimEnd([char[]]"`r`n") diff --git a/scripts/test-managed-install.ps1 b/scripts/test-managed-install.ps1 index 21a4093..7e55c99 100644 --- a/scripts/test-managed-install.ps1 +++ b/scripts/test-managed-install.ps1 @@ -54,15 +54,16 @@ try { [plugins."disabled@market"] enabled = false '@ - [System.IO.File]::WriteAllText((Join-Path $source 'config.toml'), "model = 'new'`nmodel_reasoning_effort = 'low'`napprovals_reviewer = 'auto_review'`n") - [System.IO.File]::WriteAllText($configPath, "model = 'old'`nmodel_reasoning_effort = 'high'`napprovals_reviewer = 'user'`n$pluginState`n[other]`nvalue = true`n") + [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("model = 'new'") -or $mergedConfig.Contains("model_reasoning_effort = 'low'") -or $mergedConfig.Contains("approvals_reviewer = 'user'") -or $mergedConfig.Contains('[other]')) { throw 'Updating config.toml must preserve local model settings and enforce automatic review.' } + 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.' }