??# kb_sanitize_hashtable_v1.0.ps1 # Objet : supprimer/neutraliser les s?quences '@{' r?siduelles dans la KB canonique. # Port?e minimale : NE modifie QUE les champs texte ; pas d'autres remplacements. # R?gles : PS 5.1 compatible, ASCII-only dans le code, SAFE-WRITE (staging + .tmp + .bak), UTF-8 BOM. [CmdletBinding()] param( [switch]$Preview, [switch]$Execute, [string]$Root = "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry", [string]$StageRoot = "C:\Temp_Gouvernance" ) function Ensure-Dir([string]$Path){ if(-not(Test-Path -LiteralPath $Path)){ New-Item -ItemType Directory -Force -Path $Path | Out-Null } } function Ensure-Parent([string]$Target){ $p=Split-Path -Parent $Target; if($p){ Ensure-Dir $p } } function Get-NowIso(){ (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssK") } function Read-JsonSansFooter([string]$Path){ $sr=New-Object IO.StreamReader($Path) try{ $L=New-Object 'System.Collections.Generic.List[string]' while(-not $sr.EndOfStream){ $line=$sr.ReadLine() if($line -match '^\s*---\s*DOC-VERSION-FOOTER'){ break } $L.Add($line)|Out-Null } ($L -join "`n") } finally { $sr.Dispose() } } function Write-SafeText([string]$Target,[string]$Content,[string]$StageRoot){ Ensure-Dir $StageRoot; Ensure-Parent $Target $tmp=Join-Path $StageRoot ("write_" + [IO.Path]::GetRandomFileName()) $utf8=New-Object Text.UTF8Encoding($true) # UTF-8 avec BOM [IO.File]::WriteAllText($tmp,$Content,$utf8) $bak=$null if(Test-Path -LiteralPath $Target){ $bak=$Target+"."+(Get-Date -f yyyyMMdd_HHmmss)+".bak" Copy-Item -LiteralPath $Target -Destination $bak -Force } $tmpR="$Target.tmp" Copy-Item -LiteralPath $tmp -Destination $tmpR -Force Move-Item -LiteralPath $tmpR -Destination $Target -Force Remove-Item -LiteralPath $tmp -Force return $bak } # Cibles $KbPath = Join-Path $Root "bug_kb\BUG_KB.json.txt" if(-not (Test-Path -LiteralPath $KbPath)){ Write-Host "[ERR] KB absente: $KbPath"; exit 2 } # Lecture sans footer + parse $raw = Get-Content -LiteralPath $KbPath -Raw $head = ($raw -split "(?m)^--- DOC-VERSION-FOOTER")[0] $pat = '@\{' $badBefore = ([regex]::Matches($head,$pat)).Count try { $kb = $head | ConvertFrom-Json -ErrorAction Stop } catch { Write-Host "[ERR] KB JSON invalide: $($_.Exception.Message)"; exit 3 } if(-not $kb.entries){ $kb | Add-Member -Name entries -MemberType NoteProperty -Value @() } # Sanitize: remplacer '@{' par '(' dans tous les champs texte connus function San([string]$s){ if($null -eq $s){ return "" }; return ($s -replace '@\{','(') } $clean = New-Object System.Collections.ArrayList foreach($e in $kb.entries){ $tags = @() if($e.tags){ foreach($t in $e.tags){ $tags += (San ([string]$t)) } } $seenIn=@() if($e.seen_in_threads){ if($e.seen_in_threads -is [string]){ $seenIn=@(San ([string]$e.seen_in_threads)) } else { $seenIn=@($e.seen_in_threads | ForEach-Object { San ([string]$_) }) } } $null = $clean.Add([ordered]@{ id = San ([string]$e.id) title = San ([string]$e.title) blocking = [bool]$e.blocking workaround = San ([string]$e.workaround) note = San ([string]$e.note) fix = San ([string]$e.fix) tags = @($tags) seen_in_threads = @($seenIn) last_seen = [string]$e.last_seen }) } $out = [ordered]@{ entries = @($clean); updated = Get-NowIso } $json = ($out | ConvertTo-Json -Depth 6 -Compress) $badAfter = ([regex]::Matches($json,$pat)).Count $footer = "`r`n`r`n--- DOC-VERSION-FOOTER ---`r`nGenerated: $($out.updated)`r`nPolicy: TXT-ONLY v1.0; SAFE-WRITE v1.1; GOV_SCRIPT_GATE v1.3`r`nSource: KB_SANITIZE_HASHTABLE_v1.0`r`n" if($Preview -or (-not $Execute)){ Write-Host "== PREVIEW :: KB SANITIZE @{} ==" Write-Host ("Entries={0} '@{{' before={1} after_if_written={2}" -f $clean.Count, $badBefore, $badAfter) Write-Host ("Will write -> {0} out-json-bytes?{1:n0}" -f $KbPath, ([Text.Encoding]::UTF8.GetByteCount($json))) Write-Host "No write performed (Preview)." exit 0 } $bak = Write-SafeText -Target $KbPath -Content ($json + $footer) -StageRoot $StageRoot $bakMsg = $bak; if(-not $bakMsg){ $bakMsg = "" } Write-Host ("[OK] KB sanitiz?e (@{{) et r??crite: {0}" -f $KbPath) Write-Host ("Backup: {0}" -f $bakMsg)