?param([string]$FromFile,[switch]$Execute,[switch]$NoBootpack) $ErrorActionPreference='Stop' $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $kb = Join-Path $root "bug_kb\BUG_KB.json.txt" $boot = Join-Path $root "bootpack\bootpack.txt" $logs = Join-Path $root "logs" $thr = Join-Path $root "threads_archive" $stage= "C:\Temp_Gouvernance" foreach($d in @($stage,$logs,$thr)){ if(!(Test-Path $d)){ New-Item -ItemType Directory -Force -Path $d|Out-Null } } $Utf8NoBom = New-Object Text.UTF8Encoding($false) function Read-KbObj([string]$Path){ if(!(Test-Path $Path)){ return [pscustomobject]@{ entries=@(); updated=(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK') } } $t = Get-Content -LiteralPath $Path -Raw $m='--- DOC-VERSION-FOOTER ---' $i=$t.IndexOf($m); if($i -ge 0){ $t=$t.Substring(0,$i).Trim() } try { $o = $t | ConvertFrom-Json } catch { return [pscustomobject]@{ entries=@(); updated=(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK') } } if($o -and $o.entries){ if($o.entries -is [array]){ return $o } else { $o.entries=@($o.entries); return $o } } return [pscustomobject]@{ entries=@(); updated=(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK') } } function Write-SafeTxt([string]$Dest,[string]$Body,[string]$SourceTag){ $iso=(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK') $policy='TXT-ONLY v1.0; SAFE-WRITE v1.1; GOV_SCRIPT_GATE v1.3' if(!(Test-Path (Split-Path $Dest -Parent))){ New-Item -ItemType Directory -Force -Path (Split-Path $Dest -Parent)|Out-Null } if(!(Test-Path $stage)){ New-Item -ItemType Directory -Force -Path $stage|Out-Null } $tmpLocal = Join-Path $stage ([IO.Path]::GetFileName($Dest)) [IO.File]::WriteAllText($tmpLocal,$Body,$Utf8NoBom) $sha=[Security.Cryptography.SHA256]::Create() try{ $bytes=[IO.File]::ReadAllBytes($tmpLocal); $h=[BitConverter]::ToString($sha.ComputeHash($bytes)).Replace('-','').ToLower() } finally{ $sha.Dispose() } [IO.File]::AppendAllText($tmpLocal,"`r`n`r`n--- DOC-VERSION-FOOTER ---`r`nGenerated: $iso`r`nSHA-256: $h`r`nPolicy: $policy`r`nSource: KB_QUICKLOG_v1.2.1`r`n",$Utf8NoBom) $tmp="$Dest.tmp"; Copy-Item $tmpLocal $tmp -Force if(Test-Path $Dest){ Copy-Item $Dest ($Dest+".bak_"+(Get-Date -Format 'yyyyMMdd_HHmmss')) -Force } Move-Item $tmp $Dest -Force } function Parse-AutoFile([string]$Path){ $map=@{} $lines = Get-Content -LiteralPath $Path -ErrorAction Stop foreach($ln in $lines){ if($ln -match '^\s*([^:]+)\s*:\s*(.*)$'){ $map[$matches[1].Trim()]=$matches[2] } } $title = if([string]::IsNullOrWhiteSpace($map['Title'])){ "Untitled KB entry" } else { $map['Title'] } $blocking = ($map['Blocking'] -match 'true|1|yes') $rootcause = $map['RootCause']; $workaround=$map['Workaround']; $fix=$map['Fix'] $tags = @(); if($map['Tags']){ $tmp=$map['Tags'].Split(','); foreach($t in $tmp){ $t=$t.Trim(); if($t){ $tags += $t } } } $seen = @(); if($map['SeenInThreads']){ $tmp=$map['SeenInThreads'].Split(','); foreach($s in $tmp){ $s=$s.Trim(); if($s){ $seen += $s } } } return [pscustomobject]@{ title=$title; blocking=$blocking; note=$rootcause; workaround=$workaround; fix=$fix; tags=$tags; seen_in_threads=$seen } } function Slugify([string]$s){ return (($s.ToLower() -replace '[^a-z0-9]+','-').Trim('-')) } function Update-Bootpack-BugJsonStream([string]$BootPath,[string]$JsonString){ if(!(Test-Path $BootPath)){ return } $tmp="$BootPath.tmp" $sr = New-Object IO.StreamReader($BootPath,[Text.UTF8Encoding]::new($false),$true,4096) $sw = New-Object IO.StreamWriter($tmp,[Text.UTF8Encoding]::new($false)) try{ $inKb=$false; $wrote=$false while(-not $sr.EndOfStream){ $line=$sr.ReadLine() if(-not $inKb){ if($line -match '^\[BUG_KB_JSON\]\s*$'){ $inKb=$true; $sw.WriteLine($line); $sw.WriteLine($JsonString); $wrote=$true while(-not $sr.EndOfStream){ $posLine=$sr.ReadLine() if($posLine -match '^\['){ $sw.WriteLine($posLine); $inKb=$false; break } } } else { $sw.WriteLine($line) } } else { if($line -match '^\['){ $sw.WriteLine($line); $inKb=$false } } } if(-not $wrote){ $sw.WriteLine('[BUG_KB_JSON]'); $sw.WriteLine($JsonString) } } finally{ $sr.Close(); $sw.Close() } if(Test-Path $BootPath){ Copy-Item $BootPath ($BootPath+'.bak_'+(Get-Date -Format 'yyyyMMdd_HHmmss')) -Force } Move-Item $tmp $BootPath -Force } if(-not $FromFile -or -not (Test-Path $FromFile)){ Write-Error "Missing -FromFile"; exit 1 } $kbObj = Read-KbObj $kb $before = ($kbObj | ConvertTo-Json -Depth 20) $entry = Parse-AutoFile $FromFile $slug = Slugify $entry.title $now = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK') $found=$false foreach($e in $kbObj.entries){ if($e.title){ $eslug = Slugify([string]$e.title) if($eslug -eq $slug){ if($entry.blocking){ $e.blocking=$true } if($entry.note){ $e.note=$entry.note } if($entry.workaround){ $e.workaround=$entry.workaround } if($entry.fix){ $e.fix=$entry.fix } if($entry.tags){ $e.tags = @([string[]]($e.tags + $entry.tags | Select-Object -Unique)) } if($entry.seen_in_threads){ $e.seen_in_threads = @([string[]]($e.seen_in_threads + $entry.seen_in_threads | Select-Object -Unique)) } $e.last_seen=$now $found=$true; break } } } if(-not $found){ $new=[pscustomobject]@{ id="KB-AUTO-"+$slug+"-"+(Get-Date -Format 'yyyyMMdd_HHmmss') title=$entry.title; blocking=$entry.blocking; note=$entry.note; workaround=$entry.workaround; fix=$entry.fix tags=$entry.tags; seen_in_threads=$entry.seen_in_threads; last_seen=$now } $kbObj.entries += ,$new } $kbObj.updated=$now $kbJson = ($kbObj | ConvertTo-Json -Depth 20) if($before -eq $kbJson){ Write-Host "[KB] no-op (unchanged)"; exit 0 } if(-not $Execute){ Write-Host "[PREVIEW] would update KB + (bootpack unless -NoBootpack). Use -Execute to apply."; exit 0 } Write-SafeTxt -Dest $kb -Body $kbJson -SourceTag "KB_QUICKLOG_v1.2.1" # Bootpack: injecter **JSON pur** (sans footer) $srch='--- DOC-VERSION-FOOTER ---'; $idx=$kbJson.IndexOf($srch); if($idx -ge 0){ $json=$kbJson.Substring(0,$idx).Trim() } else { $json=$kbJson } if(-not $NoBootpack){ Update-Bootpack-BugJsonStream -BootPath $boot -JsonString $json } # Archive + Log $day=(Get-Date -Format 'yyyy-MM-dd'); $t=(Get-Date -Format 'HHmmss') $archDir = Join-Path (Join-Path $thr $day) $t; if(!(Test-Path $archDir)){ New-Item -ItemType Directory -Force -Path $archDir | Out-Null } [IO.File]::WriteAllText((Join-Path $archDir 'kb_quicklog_snapshot.txt'),"FromFile: $FromFile`r`nUpdated: $($kbObj.updated)`r`n",(New-Object Text.UTF8Encoding($false))) $log = Join-Path $logs 'registry_activity.log' $old=@(); if(Test-Path $log){ $old += Get-Content -LiteralPath $log -ErrorAction SilentlyContinue } $old += ("[{0}] kb_quicklog_v1.2.1: from={1}" -f (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK'),$FromFile) [IO.File]::WriteAllText($log, ($old -join "`r`n"), (New-Object Text.UTF8Encoding($false))) Write-Host "[KB] updated -> $kb"