diff --git a/legacy/tools/compatibility/Get-FrozenManifestProjection.ps1 b/legacy/tools/compatibility/Get-FrozenManifestProjection.ps1 new file mode 100644 index 0000000..aaed424 --- /dev/null +++ b/legacy/tools/compatibility/Get-FrozenManifestProjection.ps1 @@ -0,0 +1,123 @@ +#requires -Version 7.2 + +<# +.SYNOPSIS +Shared frozen-source digest helpers for the compatibility retirement packets. + +.DESCRIPTION +Every retirement packet freezes a set of source inputs and hashes them into a +snapshot identity. Both the generator (New-*RetirementPacket.ps1) and the +verifier (Test-*RetirementPacket.ps1) must compute that identity byte for byte +identically, so the computation lives here once and is dot-sourced by both. +Duplicating it in the mirror pair is what lets the two silently drift. + +Why a projection instead of the whole registry file +--------------------------------------------------- +Packets used to freeze the whole of orchestration/integrations.json. That file +also carries the toolchainDigests arrays, which are re-pinned whenever ANY +pinned Rust source file changes. So editing one unrelated Rust file invalidated +six packets at once and forced all of them to be regenerated in the same change +- 112 files, past the review-tool ceiling, which meant the governance mechanism +was blocking review of the very code it governs. + +That coupling was never meaningful. A packet's staleness claim is about the +registry state of the capability it retires - is the replacement still declared, +with the same implementation, contract, effects and status - not about the +digests of source files it does not name. Freezing a projection over exactly +the integration entries the retirement concerns, plus the manifest's policy +header, keeps the claim the packet actually makes and drops the coupling it +never needed. + +Canonicalisation +---------------- +Deterministic by construction, and identical on both sides of the mirror: + 1. Select the named integration entries. Missing or duplicated ids are a hard + error - an empty projection would be a silent governance hole. + 2. Order them by id with an ordinal (culture-invariant) sort, so the digest + does not depend on the registry's file order or on the host locale. + 3. Emit { schema, policy, integrations } via ConvertTo-Json -Depth 40 + -Compress. Depth 40 matches the deepest packet writer; -Compress removes + insignificant whitespace so formatting-only edits to the registry do not + move the digest. + 4. SHA-256 over the UTF8 bytes, lowercase hex - the same shape as the + Get-FileHash digests it sits beside in a frozen set. + +Frozen-set entries +------------------ +An entry is either a repository-relative file path, or a projection token: + + manifest-projection:#[,...] + +Root resolution is shared too: crates/ and orchestration/ paths resolve against +the pipeline repository root, everything else against the PowerShell root +(legacy/). +#> + +Set-StrictMode -Version Latest + +function Get-FrozenDigestSha256 { + param([Parameter(Mandatory = $true)][AllowEmptyString()][string]$Text) + return ([Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($Text)))).ToLowerInvariant() +} + +function Resolve-FrozenSourcePath { + param( + [Parameter(Mandatory = $true)][string]$Relative, + [Parameter(Mandatory = $true)][string]$RepoRoot, + [Parameter(Mandatory = $true)][string]$PipelineRepoRoot + ) + $root = if ($Relative.StartsWith('crates/') -or $Relative.StartsWith('orchestration/')) { $PipelineRepoRoot } else { $RepoRoot } + return (Join-Path $root $Relative) +} + +function Get-FrozenManifestProjection { + param( + [Parameter(Mandatory = $true)][string]$ManifestPath, + [Parameter(Mandatory = $true)][string[]]$IntegrationIds + ) + if ($IntegrationIds.Count -eq 0) { throw "manifest projection requires at least one integration id" } + $manifest = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json + $ordered = [string[]]@($IntegrationIds) + [Array]::Sort($ordered, [StringComparer]::Ordinal) + $selected = @(foreach ($id in $ordered) { + $matched = @($manifest.integrations | Where-Object { $_.id -eq $id }) + if ($matched.Count -ne 1) { + throw "manifest projection requires exactly one '$id' integration in $ManifestPath (found $($matched.Count))" + } + $matched[0] + }) + $projection = [ordered]@{ + schema = "code-intel-frozen-manifest-projection.v1" + policy = $manifest.policy + integrations = $selected + } + return Get-FrozenDigestSha256 (ConvertTo-Json -InputObject $projection -Depth 40 -Compress) +} + +function Get-FrozenSourceDigest { + param( + [Parameter(Mandatory = $true)][string]$Entry, + [Parameter(Mandatory = $true)][string]$RepoRoot, + [Parameter(Mandatory = $true)][string]$PipelineRepoRoot + ) + if ($Entry.StartsWith('manifest-projection:')) { + $spec = $Entry.Substring('manifest-projection:'.Length) + $parts = @($spec -split '#', 2) + if ($parts.Count -ne 2) { throw "malformed frozen projection entry: $Entry" } + $ids = @($parts[1] -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_.Length -gt 0 }) + if ([string]::IsNullOrWhiteSpace($parts[0]) -or $ids.Count -eq 0) { throw "malformed frozen projection entry: $Entry" } + return Get-FrozenManifestProjection -ManifestPath (Resolve-FrozenSourcePath $parts[0] $RepoRoot $PipelineRepoRoot) -IntegrationIds $ids + } + return (Get-FileHash -LiteralPath (Resolve-FrozenSourcePath $Entry $RepoRoot $PipelineRepoRoot) -Algorithm SHA256).Hash.ToLowerInvariant() +} + +function Get-FrozenSourceIdentity { + param( + [Parameter(Mandatory = $true)][string[]]$FrozenSet, + [Parameter(Mandatory = $true)][string]$RepoRoot, + [Parameter(Mandatory = $true)][string]$PipelineRepoRoot + ) + return Get-FrozenDigestSha256 ((@($FrozenSet | ForEach-Object { + Get-FrozenSourceDigest -Entry $_ -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot + })) -join "`n") +} diff --git a/legacy/tools/compatibility/New-CodeNexusDirectRetirementPacket.ps1 b/legacy/tools/compatibility/New-CodeNexusDirectRetirementPacket.ps1 index fe443bd..d9648fc 100644 --- a/legacy/tools/compatibility/New-CodeNexusDirectRetirementPacket.ps1 +++ b/legacy/tools/compatibility/New-CodeNexusDirectRetirementPacket.ps1 @@ -76,14 +76,24 @@ foreach ($testName in $b05Tests) { if ($LASTEXITCODE -ne 0) { throw "B05 targeted fallback test failed: $testName" } } +# Frozen source set. Test-CodeNexusDirectRetirementPacket.ps1 repeats this list +# and both sides hash it through the same dot-sourced helper, so the mirror pair +# cannot drift. +# +# The registry input is a canonical projection over exactly the integrations +# this retirement concerns - localization.codenexus-lite, the participant being +# retired, and provider.codenexus-adapt, the replacement - plus the manifest +# policy header, not the whole of orchestration/integrations.json. E04's +# staleness claim is about the registry state of the capability it retires; it +# is not about the toolchainDigests of unrelated Rust sources that happen to be +# pinned in the same file, which are re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") $snapshotInputs = @( "run-code-intel.ps1", "Invoke-CodeNexusLite.ps1", "crates/code-intel-cli/src/codenexus_adapter.rs", - "crates/code-intel-cli/src/survival_scan.rs", "orchestration/integrations.json" + "crates/code-intel-cli/src/survival_scan.rs", + "manifest-projection:orchestration/integrations.json#localization.codenexus-lite,provider.codenexus-adapt" ) -$snapshotIdentity = Get-Sha256Text (($snapshotInputs | ForEach-Object { - $root = if ($_.StartsWith('crates/') -or $_.StartsWith('orchestration/')) { $PipelineRepoRoot } else { $RepoRoot } - (Get-FileHash -LiteralPath (Join-Path $root $_) -Algorithm SHA256).Hash.ToLowerInvariant() -}) -join "`n") +$snapshotIdentity = Get-FrozenSourceIdentity -FrozenSet $snapshotInputs -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot $retirementId = "retire-codenexus-direct-branch" $branchId = "run-code-intel.codenexus-lite.direct" $replacementId = "provider.codenexus-adapt" diff --git a/legacy/tools/compatibility/New-HospitalRetirementPacket.ps1 b/legacy/tools/compatibility/New-HospitalRetirementPacket.ps1 index b388151..0928347 100644 --- a/legacy/tools/compatibility/New-HospitalRetirementPacket.ps1 +++ b/legacy/tools/compatibility/New-HospitalRetirementPacket.ps1 @@ -7,7 +7,17 @@ function W([string]$p,[object]$v){[IO.File]::WriteAllText($p,($v|ConvertTo-Json $tests=@('provider_quota_precedes_missing_current_graph_and_is_replay_stable','precedence_matrix_matches_the_legacy_stable_diagnoses_and_fails_closed','missing_or_non_admitted_authority_is_rejected_and_enrichment_never_overrides_it','conflicting_or_provider_injected_modalities_fail_closed_independent_of_input_order','markdown_is_a_rebuildable_view_and_cannot_change_the_machine_verdict','a09_seeded_path_executes_hospital_through_a01_and_rejects_snapshot_mismatch','legacy_facade_and_rust_execute_the_same_fixture_with_stable_machine_parity');foreach($n in $tests){& cargo test -q -p code-intel --test hospital_diagnosis $n -- --exact|Out-Null;if($LASTEXITCODE-ne0){throw "B09 test failed $n"}} $boundary=& pwsh -NoLogo -NoProfile -File (Join-Path $RepoRoot "tools\compatibility\Test-HospitalRetirementBoundary.ps1")|ConvertFrom-Json;$runPath=Join-Path $RepoRoot "run-code-intel.ps1";$run=[IO.File]::ReadAllText($runPath);$base=$run.Replace("`r`n","`n").Replace("`r","`n") $functions='(?s)function New-HospitalProtocol \{.*?(?=\nfunction Get-CodeIntelSentruxStep)';$call='(?s)\$hospitalReport = New-CodeIntelHospitalReport .*?Convert-HospitalReportToMarkdown \$hospitalReport \| Set-Content -LiteralPath \$hospitalMarkdownPath -Encoding UTF8\n';$matches=@([regex]::Match($base,$functions),[regex]::Match($base,$call))|Sort-Object Index;if($matches.Where({-not$_.Success}).Count){throw "bounded E08 branch absent"} -$frozen=@('run-code-intel.ps1','crates/code-intel-cli/src/hospital_diagnosis.rs','orchestration/integrations.json','crates/code-intel-cli/src/run_commit.rs','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/src/doctor_adapter.rs');$digests=@{};foreach($p in $frozen){$digests[$p]=(Get-FileHash (Join-Path $(if($p.StartsWith('crates/')-or$p.StartsWith('orchestration/')){$PipelineRepoRoot}else{$RepoRoot}) $p) -Algorithm SHA256).Hash.ToLowerInvariant()};$snapshot=S (($frozen|ForEach-Object{$digests[$_]})-join"`n") +# Frozen source set. Test-HospitalRetirementPacket.ps1 repeats this list and both +# sides hash it through the same dot-sourced helper, so the mirror pair cannot +# drift. The registry input is a canonical projection over exactly the +# integration this retirement concerns - diagnosis.hospital, the replacement +# capability and the registry participant - plus the manifest policy header, not +# the whole of orchestration/integrations.json. E08's staleness claim is about +# the registry state of the capability it retires; it is not about the +# toolchainDigests of unrelated Rust sources that happen to be pinned in the +# same file, which are re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$frozen=@('run-code-intel.ps1','crates/code-intel-cli/src/hospital_diagnosis.rs','manifest-projection:orchestration/integrations.json#diagnosis.hospital','crates/code-intel-cli/src/run_commit.rs','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/src/doctor_adapter.rs');$digests=@{};foreach($p in $frozen){$digests[$p]=Get-FrozenSourceDigest -Entry $p -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot};$snapshot=S (($frozen|ForEach-Object{$digests[$_]})-join"`n") $rid="retire-hospital-branch";$bid="run-code-intel.hospital.embedded-diagnosis-render";$rep="diagnosis.hospital";$callPath="run-code-intel.ps1::$bid";$expiry=$EvaluatedAt+2592000 function E([string]$n,[string]$c,[object]$d){$v=[ordered]@{schema="code-intel-compatibility-retirement-evidence.v1";snapshotIdentity=$snapshot;id="e08.$n";evidenceClass=$c;retirementId=$rid;legacyBranchId=$bid;replacementCapabilityId=$rep;details=$d};$r="evidence/$n.json";W (Join-Path $OutDir $r) $v;Ref "code-intel-compatibility-retirement-evidence.v1" "compatibility.retirement-evidence" $r} $atom=E "replacement-atom" "replacement_atom" ([ordered]@{outcome="blocked";status="pending_facade_route";capability=$rep;b09RegistryAvailable=$true;normalFacadeUsesB09=$boundary.normalFacadeUsesB09;blocker="normal facade still executes embedded Hospital authority"}) @@ -35,5 +45,5 @@ $ticket=[ordered]@{schema="code-intel-compatibility-retirement-ticket-template.v W (Join-Path $OutDir "compatibility-retirement-ticket.json") $ticket;& $CodeIntel compatibility retirement-ticket lint --ticket (Join-Path $OutDir "compatibility-retirement-ticket.json") --evaluated-at $EvaluatedAt|Out-Null;if($LASTEXITCODE-ne0){throw "E01 lint failed"};$ticketRef=Ref "code-intel-compatibility-retirement-ticket-template.v1" "compatibility.retirement-ticket-template" "compatibility-retirement-ticket.json";$ticketDecl=($registryJson.integrations|Where-Object id -eq "compatibility.retirement-ticket-template").capabilityDeclaration $e01=[ordered]@{schema="code-intel-capability-request.v1";capability="compatibility.retirement-ticket-template";contractVersion=1;implementation=$ticketDecl.implementation;snapshot=$gate.snapshot;options=[ordered]@{evaluatedAt=$EvaluatedAt};inputs=@($ticketRef,$manifestRef,$decisionRef,$diffRef);effectPolicy=[ordered]@{allowedEffects=$ticketDecl.allowedEffects}};W (Join-Path $OutDir "e01-request.json") $e01 $o=@(& $CodeIntel capability exec compatibility.retirement-ticket-template --request (Join-Path $OutDir "e01-request.json") --out (Join-Path $OutDir "e01-out") --artifact-root $OutDir 2>&1);$x=$LASTEXITCODE;$txt=$o-join"`n";[IO.File]::WriteAllText((Join-Path $OutDir "e01-stderr.txt"),$txt,[Text.UTF8Encoding]::new($false));if($x-ne65-or$txt-notmatch"ticket requires an approved E00 decision"){throw "E01 must validate patch then reject blocked E00: $x $txt"} -foreach($p in $frozen){if((Get-FileHash (Join-Path $(if($p.StartsWith('crates/')-or$p.StartsWith('orchestration/')){$PipelineRepoRoot}else{$RepoRoot}) $p) -Algorithm SHA256).Hash.ToLowerInvariant()-ne$digests[$p]){throw "E08 changed frozen path: $p"}} +foreach($p in $frozen){if((Get-FrozenSourceDigest -Entry $p -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot)-ne$digests[$p]){throw "E08 changed frozen path: $p"}} $status=[ordered]@{schema="code-intel-compatibility-retirement-execution-status.v1";retirementId=$rid;decision="blocked";deletionExecuted=$false;retired=$false;liveEmbeddedAuthority=$true;normalFacadeUsesB09=$false;blockers=@($decision.blockers);gainLedgerProjection=$decision.gainLedgerProjection;boundary="B09 is registered but the normal facade still executes embedded Hospital authority; E08 has no deletion authority and excludes publication, index, and doctor."};W (Join-Path $OutDir "status.json") $status;$status|ConvertTo-Json -Depth 10 -Compress diff --git a/legacy/tools/compatibility/New-IndexRetirementPacket.ps1 b/legacy/tools/compatibility/New-IndexRetirementPacket.ps1 index 584adff..1013b75 100644 --- a/legacy/tools/compatibility/New-IndexRetirementPacket.ps1 +++ b/legacy/tools/compatibility/New-IndexRetirementPacket.ps1 @@ -7,8 +7,18 @@ function W([string]$p,[object]$v){[IO.File]::WriteAllText($p,($v|ConvertTo-Json $tests=@('complete_and_staged_side_by_side_indexes_only_complete_run','forged_marker_manifest_and_artifact_bindings_are_diagnosed_and_rejected','incomplete_and_legacy_runs_are_diagnostic_only_and_newer_invalid_does_not_win','rebuild_and_incremental_are_byte_equivalent_and_stably_sorted','production_cli_writes_the_registered_committed_only_schema');foreach($n in $tests){& cargo test -q -p code-intel --test artifact_index $n -- --exact|Out-Null;if($LASTEXITCODE-ne0){throw "A08 test failed $n"}} $facade=& pwsh -NoLogo -NoProfile -File (Join-Path $RepoRoot "scripts/tests/test-artifact-index-contract.ps1")|ConvertFrom-Json;if($facade.productionSchema-ne"code-intel-artifact-index.v1"){throw "A08 facade contract failed"} $boundary=& pwsh -NoLogo -NoProfile -File (Join-Path $RepoRoot "tools\compatibility\Test-IndexRetirementBoundary.ps1")|ConvertFrom-Json;$indexPath=Join-Path $RepoRoot "update-code-intel-index.ps1";$source=[IO.File]::ReadAllText($indexPath);$base=$source.Replace("`r`n","`n").Replace("`r","`n");$pattern='(?s)function Read-JsonFile \{.*\z';$match=[regex]::Match($base,$pattern);if(-not$match.Success){throw "E10 branch absent"} -$frozen=@('update-code-intel-index.ps1','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/tests/artifact_index.rs','crates/code-intel-cli/src/run_commit.rs','orchestration/integrations.json','orchestration/retirements/e05-publication/status.json','orchestration/retirements/e05-publication/gate-out/compatibility-retirement-decision.json');function FrozenPath([string]$p){if($p-like'crates/*'-or$p-like'orchestration/*'){Join-Path $PipelineRepoRoot $p}else{Join-Path $RepoRoot $p}} -$digests=@{};foreach($p in $frozen){$digests[$p]=(Get-FileHash (FrozenPath $p)-Algorithm SHA256).Hash.ToLowerInvariant()};$snapshot=S (($frozen|ForEach-Object{$digests[$_]})-join"`n") +# Frozen source set. Test-IndexRetirementPacket.ps1 repeats this list and both +# sides hash it through the same dot-sourced helper, so the mirror pair cannot +# drift. The registry input is a canonical projection over exactly the +# integration this retirement concerns - artifact.index-committed-only, the +# replacement capability and the registry participant - plus the manifest policy +# header, not the whole of orchestration/integrations.json. E10's staleness +# claim is about the registry state of the capability it retires; it is not +# about the toolchainDigests of unrelated Rust sources that happen to be pinned +# in the same file, which are re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$frozen=@('update-code-intel-index.ps1','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/tests/artifact_index.rs','crates/code-intel-cli/src/run_commit.rs','manifest-projection:orchestration/integrations.json#artifact.index-committed-only','orchestration/retirements/e05-publication/status.json','orchestration/retirements/e05-publication/gate-out/compatibility-retirement-decision.json') +$digests=@{};foreach($p in $frozen){$digests[$p]=Get-FrozenSourceDigest -Entry $p -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot};$snapshot=S (($frozen|ForEach-Object{$digests[$_]})-join"`n") $rid="retire-index-branch";$bid="update-code-intel-index.legacy-compatibility-traversal";$rep="artifact.index-committed-only";$call="update-code-intel-index.ps1::$bid";$expiry=$EvaluatedAt+2592000 function E([string]$n,[string]$c,[object]$d){$v=[ordered]@{schema="code-intel-compatibility-retirement-evidence.v1";snapshotIdentity=$snapshot;id="e10.$n";evidenceClass=$c;retirementId=$rid;legacyBranchId=$bid;replacementCapabilityId=$rep;details=$d};$r="evidence/$n.json";W (Join-Path $OutDir $r) $v;Ref "code-intel-compatibility-retirement-evidence.v1" "compatibility.retirement-evidence" $r} $atom=E "replacement-atom" "replacement_atom" ([ordered]@{outcome="passed";status="production_ready";capability=$rep;publicNormalUsesA08=$boundary.publicNormalUsesA08;normalSchema="code-intel-artifact-index.v1"}) @@ -29,4 +39,4 @@ $gate=[ordered]@{schema="code-intel-capability-request.v1";capability="compatibi $baseLines=@($base-split"`n");$oldStart=@($base.Substring(0,$match.Index)-split"`n").Count;$lines=@($baseLines[($oldStart-1)..($baseLines.Count-1)]);$hunk=[ordered]@{addedLines=@();deletedLines=$lines;newLines=0;newStart=$oldStart;oldLines=$lines.Count;oldStart=$oldStart};$result=@($baseLines[0..($oldStart-2)])-join"`n";$files=@([ordered]@{baseBlobSha256=(S $base);baseText=$base;hunks=@($hunk);path="update-code-intel-index.ps1";resultBlobSha256=(S $result);resultText=$result});$diff=[ordered]@{schema="code-intel-compatibility-retirement-deletion-diff.v1";snapshotIdentity=$snapshot;retirementId=$rid;legacyBranchId=$bid;affectedFiles=@("update-code-intel-index.ps1");deletionsOnly=$true;summary="One-hunk deletion removes only explicit legacy index traversal; A08 normal route and E05 publication branch are excluded.";patch=[ordered]@{algorithm="replayable-delete-only-v1";sha256=(S (ConvertTo-Json -InputObject $files -Depth 40 -Compress));files=$files}};W (Join-Path $OutDir "compatibility-retirement-deletion-diff.json") $diff;$diffRef=Ref "code-intel-compatibility-retirement-deletion-diff.v1" "compatibility.retirement-deletion-diff" "compatibility-retirement-deletion-diff.json";$decisionRef=Ref "code-intel-compatibility-retirement-decision.v1" "compatibility.retirement-decision" "gate-out/compatibility-retirement-decision.json" $ticket=[ordered]@{schema="code-intel-compatibility-retirement-ticket-template.v1";snapshotIdentity=$snapshot;ticketId="ticket-e10-retire-index-branch";retirementId=$rid;legacyBranch=[ordered]@{capabilityId="artifact.index.legacy-powershell";branchId=$bid;callPath=$call};replacement=[ordered]@{capabilityId=$rep;dependencies=@("compatibility.retire-publication-branch")};affectedFiles=@("update-code-intel-index.ps1");evidence=[ordered]@{golden=$gold;contract=$contract;effects=$effects;usage=$usage;rollbackRehearsal=$rollback;deletionDiff=$diffRef};source=[ordered]@{retirementDecision=$decisionRef;retirementManifest=$manifestRef};owner="executor-index";verifier="independent-verifier-required";observationExpiry=$expiry;status="draft";authorityBoundary="template_only_no_approval_or_deletion_authority"};W (Join-Path $OutDir "compatibility-retirement-ticket.json") $ticket;& $CodeIntel compatibility retirement-ticket lint --ticket (Join-Path $OutDir "compatibility-retirement-ticket.json") --evaluated-at $EvaluatedAt|Out-Null;if($LASTEXITCODE-ne0){throw "E01 lint failed"};$ticketRef=Ref "code-intel-compatibility-retirement-ticket-template.v1" "compatibility.retirement-ticket-template" "compatibility-retirement-ticket.json";$ticketDecl=($registryJson.integrations|Where-Object id -eq "compatibility.retirement-ticket-template").capabilityDeclaration $e01=[ordered]@{schema="code-intel-capability-request.v1";capability="compatibility.retirement-ticket-template";contractVersion=1;implementation=$ticketDecl.implementation;snapshot=$requestSnapshot;options=[ordered]@{evaluatedAt=$EvaluatedAt};inputs=@($ticketRef,$manifestRef,$decisionRef,$diffRef);effectPolicy=[ordered]@{allowedEffects=$ticketDecl.allowedEffects}};W (Join-Path $OutDir "e01-request.json") $e01;$o=@(& $CodeIntel capability exec compatibility.retirement-ticket-template --request (Join-Path $OutDir "e01-request.json") --out (Join-Path $OutDir "e01-out") --artifact-root $OutDir 2>&1);$x=$LASTEXITCODE;$txt=$o-join"`n";[IO.File]::WriteAllText((Join-Path $OutDir "e01-stderr.txt"),$txt,[Text.UTF8Encoding]::new($false));if($x-ne65-or$txt-notmatch"ticket requires an approved E00 decision"){throw "E01 boundary failed"} -foreach($p in $frozen){if((Get-FileHash (Join-Path $(if($p.StartsWith('crates/')-or$p.StartsWith('orchestration/')){$PipelineRepoRoot}else{$RepoRoot}) $p)-Algorithm SHA256).Hash.ToLowerInvariant()-ne$digests[$p]){throw "E10 changed frozen path $p"}};$status=[ordered]@{schema="code-intel-compatibility-retirement-execution-status.v1";retirementId=$rid;decision="blocked";publicNormalUsesA08=$true;publicLegacyRouteReachable=$true;legacyOutputDiagnosticOnly=$true;legacyCanWriteAuthoritativeIndex=$false;e05Decision="blocked";deletionExecuted=$false;retired=$false;blockers=@($decision.blockers);gainLedgerProjection=$decision.gainLedgerProjection;boundary="A08 owns normal authoritative indexing, but E05, observation, and independent approval remain blocked; no deletion authority exists."};W (Join-Path $OutDir "status.json") $status;$status|ConvertTo-Json -Depth 10 -Compress +foreach($p in $frozen){if((Get-FrozenSourceDigest -Entry $p -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot)-ne$digests[$p]){throw "E10 changed frozen path $p"}};$status=[ordered]@{schema="code-intel-compatibility-retirement-execution-status.v1";retirementId=$rid;decision="blocked";publicNormalUsesA08=$true;publicLegacyRouteReachable=$true;legacyOutputDiagnosticOnly=$true;legacyCanWriteAuthoritativeIndex=$false;e05Decision="blocked";deletionExecuted=$false;retired=$false;blockers=@($decision.blockers);gainLedgerProjection=$decision.gainLedgerProjection;boundary="A08 owns normal authoritative indexing, but E05, observation, and independent approval remain blocked; no deletion authority exists."};W (Join-Path $OutDir "status.json") $status;$status|ConvertTo-Json -Depth 10 -Compress diff --git a/legacy/tools/compatibility/New-NativeCodeRetirementPacket.ps1 b/legacy/tools/compatibility/New-NativeCodeRetirementPacket.ps1 index e091eb7..a8990fb 100644 --- a/legacy/tools/compatibility/New-NativeCodeRetirementPacket.ps1 +++ b/legacy/tools/compatibility/New-NativeCodeRetirementPacket.ps1 @@ -76,15 +76,24 @@ foreach ($testName in $dagTests) { $registryAudit = & $CodeIntel orchestrate --action Validate --manifest (Join-Path $PipelineRepoRoot "orchestration/integrations.json") --json | ConvertFrom-Json if (-not $registryAudit.ok -or -not $registryAudit.registryAudit.ok) { throw "B07 registry audit failed" } +# Frozen source set. Test-NativeCodeRetirementPacket.ps1 repeats this list and +# both sides hash it through the same dot-sourced helper, so the mirror pair +# cannot drift. +# +# The registry input is a canonical projection over exactly the integration this +# retirement concerns - evidence.native-code, the replacement capability and the +# registry participant - plus the manifest policy header, not the whole of +# orchestration/integrations.json. E07's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") $snapshotInputs = @( "run-code-intel.ps1", "crates/code-intel-cli/src/native_code_evidence.rs", "crates/code-intel-cli/tests/native_code_evidence.rs", "crates/code-intel-cli/tests/dag_run.rs", - "orchestration/integrations.json" + "manifest-projection:orchestration/integrations.json#evidence.native-code" ) -$snapshotIdentity = Get-Sha256Text (($snapshotInputs | ForEach-Object { - $root = if ($_.StartsWith('crates/') -or $_.StartsWith('orchestration/')) { $PipelineRepoRoot } else { $RepoRoot } - (Get-FileHash -LiteralPath (Join-Path $root $_) -Algorithm SHA256).Hash.ToLowerInvariant() -}) -join "`n") +$snapshotIdentity = Get-FrozenSourceIdentity -FrozenSet $snapshotInputs -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot $retirementId = "retire-native-code-branch" $branchId = "run-code-intel.native-code.embedded" $replacementId = "evidence.native-code" diff --git a/legacy/tools/compatibility/New-ProviderPreflightRetirementPacket.ps1 b/legacy/tools/compatibility/New-ProviderPreflightRetirementPacket.ps1 index 8febc05..d03f313 100644 --- a/legacy/tools/compatibility/New-ProviderPreflightRetirementPacket.ps1 +++ b/legacy/tools/compatibility/New-ProviderPreflightRetirementPacket.ps1 @@ -53,7 +53,20 @@ $baseText = $legacyMatch.Value.Replace("`r`n","`n").Replace("`r","`n") $resultText = "" $deletedLines = @($baseText -split "`n") -$snapshotIdentity = Get-TextSha ((@($liveHash, (Get-TextSha $baseText), ((Get-FileHash (Join-Path $RepoRoot "Invoke-RepowiseProviderProbe.ps1") -Algorithm SHA256).Hash.ToLowerInvariant()), ((Get-FileHash (Join-Path $PipelineRepoRoot "orchestration\integrations.json") -Algorithm SHA256).Hash.ToLowerInvariant())) -join "`n")) +# Frozen source set. Test-ProviderPreflightRetirementPacket.ps1 mirrors this +# composition exactly and both sides compute the registry component through the +# same dot-sourced helper, so the mirror pair cannot drift. +# +# The fourth component is a canonical projection over exactly the integration +# this retirement concerns - provider.repowise-adapt, the replacement capability +# - plus the manifest policy header, not the whole of +# orchestration/integrations.json. E03's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$registryProjection = Get-FrozenManifestProjection -ManifestPath (Join-Path $PipelineRepoRoot "orchestration\integrations.json") -IntegrationIds @("provider.repowise-adapt") +$snapshotIdentity = Get-TextSha ((@($liveHash, (Get-TextSha $baseText), ((Get-FileHash (Join-Path $RepoRoot "Invoke-RepowiseProviderProbe.ps1") -Algorithm SHA256).Hash.ToLowerInvariant()), $registryProjection) -join "`n")) $retirementId = "retire-provider-preflight-branch" $branchId = "run-code-intel.provider-preflight.test-wrapper" $replacementId = "provider.repowise-adapt" diff --git a/legacy/tools/compatibility/New-RecommenderRetirementPacket.ps1 b/legacy/tools/compatibility/New-RecommenderRetirementPacket.ps1 index a8af1b0..2166953 100644 --- a/legacy/tools/compatibility/New-RecommenderRetirementPacket.ps1 +++ b/legacy/tools/compatibility/New-RecommenderRetirementPacket.ps1 @@ -65,13 +65,20 @@ $providerPreflightPresent = $runText -match 'Invoke-RepowiseProviderProbe\.ps1' if (-not $legacyInlineAbsent) { throw "legacy inline recommender is still present" } if (-not $providerPreflightPresent) { throw "unrelated provider-preflight branch changed; E02 refuses to proceed" } -$inputDigests = @( - "run-code-intel.ps1", "OpenSpec-Detector.ps1", "Invoke-WorkflowRecommendation.ps1", "orchestration/integrations.json" -) | ForEach-Object { - $root = if ($_.StartsWith('crates/') -or $_.StartsWith('orchestration/')) { $PipelineRepoRoot } else { $RepoRoot } - (Get-FileHash -LiteralPath (Join-Path $root $_) -Algorithm SHA256).Hash.ToLowerInvariant() -} -$snapshotIdentity = Get-Sha256Text ($inputDigests -join "`n") +# Frozen source set. Test-RecommenderRetirementPacket.ps1 repeats this list and +# both sides hash it through the same dot-sourced helper, so the mirror pair +# cannot drift. The registry input is a canonical projection over exactly the +# integration this retirement concerns - advisory.workflow-recommend, the +# replacement capability - plus the manifest policy header, not the whole of +# orchestration/integrations.json. E02's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$frozen = @( + "run-code-intel.ps1", "OpenSpec-Detector.ps1", "Invoke-WorkflowRecommendation.ps1", + "manifest-projection:orchestration/integrations.json#advisory.workflow-recommend" +) +$snapshotIdentity = Get-FrozenSourceIdentity -FrozenSet $frozen -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot $retirementId = "retire-recommender-branch" $branchId = "run-code-intel.workflow-recommender.inline" $replacementId = "advisory.workflow-recommend" diff --git a/legacy/tools/compatibility/Test-CodeNexusDirectRetirementPacket.ps1 b/legacy/tools/compatibility/Test-CodeNexusDirectRetirementPacket.ps1 index 797f98e..1ab0b17 100644 --- a/legacy/tools/compatibility/Test-CodeNexusDirectRetirementPacket.ps1 +++ b/legacy/tools/compatibility/Test-CodeNexusDirectRetirementPacket.ps1 @@ -9,18 +9,11 @@ Set-StrictMode -Version Latest # stayed behind (orchestration/, crates/) live one level above it $PipelineRepoRoot = Split-Path -Parent $RepoRoot $ErrorActionPreference = "Stop" -function Resolve-SnapshotInput([string]$Relative) { - $root = if ($Relative.StartsWith('crates/') -or $Relative.StartsWith('orchestration/')) { $PipelineRepoRoot } else { $RepoRoot } - Join-Path $root $Relative -} function Read-Packet([string]$Relative) { $path = Join-Path $PacketRoot $Relative if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "packet file missing: $Relative" } return Get-Content -LiteralPath $path -Raw | ConvertFrom-Json } -function Get-Sha256Text([string]$Text) { - return ([Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($Text)))).ToLowerInvariant() -} $branch = "run-code-intel.codenexus-lite.direct" $replacement = "provider.codenexus-adapt" @@ -30,13 +23,24 @@ $manifest = Read-Packet "compatibility-retirement-manifest.json" $decision = Read-Packet "gate-out/compatibility-retirement-decision.json" $diff = Read-Packet "compatibility-retirement-deletion-diff.json" $status = Read-Packet "status.json" +# Staleness. This must mirror New-CodeNexusDirectRetirementPacket.ps1's frozen +# set exactly; the digest and root resolution are computed by the shared helper +# both scripts dot-source, so the two lists are the only thing that has to match. +# +# The registry input is a canonical projection over exactly the integrations +# this retirement concerns — localization.codenexus-lite, the participant being +# retired, and provider.codenexus-adapt, the replacement — plus the manifest +# policy header, not the whole of orchestration/integrations.json. E04's +# staleness claim is about the registry state of the capability it retires; it +# is not about the toolchainDigests of unrelated Rust sources that happen to be +# pinned in the same file, which are re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") $snapshotInputs = @( "run-code-intel.ps1", "Invoke-CodeNexusLite.ps1", "crates/code-intel-cli/src/codenexus_adapter.rs", - "crates/code-intel-cli/src/survival_scan.rs", "orchestration/integrations.json" + "crates/code-intel-cli/src/survival_scan.rs", + "manifest-projection:orchestration/integrations.json#localization.codenexus-lite,provider.codenexus-adapt" ) -$currentSnapshotIdentity = Get-Sha256Text (($snapshotInputs | ForEach-Object { - (Get-FileHash -LiteralPath (Resolve-SnapshotInput $_) -Algorithm SHA256).Hash.ToLowerInvariant() -}) -join "`n") +$currentSnapshotIdentity = Get-FrozenSourceIdentity -FrozenSet $snapshotInputs -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot if ($manifest.snapshotIdentity -ne $currentSnapshotIdentity -or $ticket.snapshotIdentity -ne $currentSnapshotIdentity -or $decision.snapshotIdentity -ne $currentSnapshotIdentity -or $diff.snapshotIdentity -ne $currentSnapshotIdentity) { throw "E04 packet is stale relative to its frozen source set" diff --git a/legacy/tools/compatibility/Test-HospitalRetirementPacket.ps1 b/legacy/tools/compatibility/Test-HospitalRetirementPacket.ps1 index 93e7dfd..da1cab5 100644 --- a/legacy/tools/compatibility/Test-HospitalRetirementPacket.ps1 +++ b/legacy/tools/compatibility/Test-HospitalRetirementPacket.ps1 @@ -3,7 +3,6 @@ Set-StrictMode -Version Latest;$ErrorActionPreference="Stop" # $RepoRoot lands on legacy/ after the PowerShell move; assets that # stayed behind (orchestration/, crates/) live one level above it $PipelineRepoRoot = Split-Path -Parent $RepoRoot -function RS([string]$r){Join-Path $(if($r.StartsWith('crates/')-or$r.StartsWith('orchestration/')){$PipelineRepoRoot}else{$RepoRoot}) $r} function J([string]$r){$p=Join-Path $PacketRoot $r;if(-not(Test-Path $p -PathType Leaf)){throw "missing $r"};Get-Content $p -Raw|ConvertFrom-Json} function S([string]$t){([Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($t)))).ToLowerInvariant()} $t=J "compatibility-retirement-ticket.json";$m=J "compatibility-retirement-manifest.json";$d=J "compatibility-retirement-deletion-diff.json";$g=J "gate-out/compatibility-retirement-decision.json";$s=J "status.json" @@ -18,7 +17,18 @@ $canonical=@($d.patch.files);if((S (ConvertTo-Json -InputObject $canonical -Dept $baseLines=@($base-split"`n");$replayed=for($i=1;$i-le$baseLines.Count;$i++){if(-not@($file.hunks|Where-Object{$i-ge$_.oldStart-and$i-lt($_.oldStart+$_.oldLines)}).Count){$baseLines[$i-1]}};$replayed=$replayed-join"`n";if($replayed-ne$result){throw "E08 patch replay mismatch"} if($base-notmatch'function New-HospitalProtocol'-or$base-notmatch'\$hospitalReport = New-CodeIntelHospitalReport'-or$result-match'function New-HospitalProtocol'-or$result-match'\$hospitalReport = New-CodeIntelHospitalReport'){throw "E08 patch does not remove bounded Hospital authority"} foreach($h in @($file.hunks)){if($h.newLines-ne0-or@($h.addedLines).Count-ne0-or$h.oldLines-le0){throw "E08 patch is not deletion-only"};$joined=@($h.deletedLines)-join"`n";if($joined-match'run-complete\.json|update-code-intel-index|doctor_adapter|run_commit|artifact_index'){throw "E08 patch touches excluded ownership"}} -$frozen=@('run-code-intel.ps1','crates/code-intel-cli/src/hospital_diagnosis.rs','orchestration/integrations.json','crates/code-intel-cli/src/run_commit.rs','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/src/doctor_adapter.rs');$snapshot=S (($frozen|ForEach-Object{(Get-FileHash (RS $_) -Algorithm SHA256).Hash.ToLowerInvariant()})-join"`n");if($snapshot-ne$m.snapshotIdentity){throw "E08 snapshot drift"} +# Staleness. This must mirror New-HospitalRetirementPacket.ps1's $frozen set +# exactly; the digest and root resolution are computed by the shared helper both +# scripts dot-source, so the two lists are the only thing that has to match. +# The registry input is a canonical projection over exactly the integration this +# retirement concerns — diagnosis.hospital, the replacement capability and the +# registry participant — plus the manifest policy header, not the whole of +# orchestration/integrations.json. E08's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$frozen=@('run-code-intel.ps1','crates/code-intel-cli/src/hospital_diagnosis.rs','manifest-projection:orchestration/integrations.json#diagnosis.hospital','crates/code-intel-cli/src/run_commit.rs','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/src/doctor_adapter.rs');$snapshot=Get-FrozenSourceIdentity -FrozenSet $frozen -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot;if($snapshot-ne$m.snapshotIdentity){throw "E08 snapshot drift"} $e=@(Get-ChildItem (Join-Path $PacketRoot evidence) -Filter *.json -File|ForEach-Object{Get-Content $_.FullName -Raw|ConvertFrom-Json});if($e.Count-ne12){throw "E08 must have twelve evidence objects"};if(@($e|Where-Object{$_.legacyBranchId-ne$b-or$_.replacementCapabilityId-ne$r}).Count){throw "E08 evidence identity mismatch"} $gold=$e|Where-Object evidenceClass -eq golden_parity;$contract=$e|Where-Object evidenceClass -eq contract_parity;$effects=$e|Where-Object evidenceClass -eq effect_parity;$rollback=$e|Where-Object evidenceClass -eq rollback_execution if($gold.details.sameUntrustedAuthoritativeFixture-ne$true-or$gold.details.machineParity-ne$true-or$gold.details.executedTestCount-ne7){throw "E08 golden parity incomplete"} diff --git a/legacy/tools/compatibility/Test-IndexRetirementPacket.ps1 b/legacy/tools/compatibility/Test-IndexRetirementPacket.ps1 index aa8cc66..df319d2 100644 --- a/legacy/tools/compatibility/Test-IndexRetirementPacket.ps1 +++ b/legacy/tools/compatibility/Test-IndexRetirementPacket.ps1 @@ -1,16 +1,26 @@ [CmdletBinding()]param([Parameter(Mandatory=$true)][string]$PacketRoot,[string]$RepoRoot=(Split-Path (Split-Path $PSScriptRoot -Parent) -Parent)) Set-StrictMode -Version Latest;$ErrorActionPreference="Stop";function J([string]$r){$p=Join-Path $PacketRoot $r;if(-not(Test-Path $p -PathType Leaf)){throw "missing $r"};Get-Content $p -Raw|ConvertFrom-Json};function S([string]$t){([Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($t)))).ToLowerInvariant()} $t=J "compatibility-retirement-ticket.json";$m=J "compatibility-retirement-manifest.json";$d=J "compatibility-retirement-deletion-diff.json";$g=J "gate-out/compatibility-retirement-decision.json";$s=J "status.json";$b="update-code-intel-index.legacy-compatibility-traversal";$c="update-code-intel-index.ps1::$b";$r="artifact.index-committed-only" -# Staleness. This must mirror New-IndexRetirementPacket.ps1's $frozen set and -# root resolution exactly. Without it E10 could be stale in six of its seven -# frozen inputs and still pass: the only other freshness signal is the rollback -# evidence on line 10, which rehashes update-code-intel-index.ps1 alone. That is -# how #48's edits to orchestration/integrations.json went undetected here while -# E04, E07 and E08 correctly went red on the same change. +# Staleness. This must mirror New-IndexRetirementPacket.ps1's $frozen set +# exactly; the digest and root resolution are computed by the shared helper both +# scripts dot-source, so the two lists are the only thing that has to match. +# Without this guard E10 could be stale in six of its seven frozen inputs and +# still pass: the only other freshness signal is the rollback evidence on line +# 10, which rehashes update-code-intel-index.ps1 alone. That is how #48's edits +# to the registry went undetected here while E04, E07 and E08 correctly went red +# on the same change. +# +# The registry input is a canonical projection over exactly the integration this +# retirement concerns — artifact.index-committed-only, the replacement capability +# and the registry participant — plus the manifest policy header, not the whole +# of orchestration/integrations.json. E10's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. $PipelineRepoRoot=Split-Path -Parent $RepoRoot -function FrozenPath([string]$p){if($p-like'crates/*'-or$p-like'orchestration/*'){Join-Path $PipelineRepoRoot $p}else{Join-Path $RepoRoot $p}} -$frozen=@('update-code-intel-index.ps1','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/tests/artifact_index.rs','crates/code-intel-cli/src/run_commit.rs','orchestration/integrations.json','orchestration/retirements/e05-publication/status.json','orchestration/retirements/e05-publication/gate-out/compatibility-retirement-decision.json') -$snapshot=S (($frozen|ForEach-Object{(Get-FileHash (FrozenPath $_) -Algorithm SHA256).Hash.ToLowerInvariant()})-join"`n") +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$frozen=@('update-code-intel-index.ps1','crates/code-intel-cli/src/artifact_index.rs','crates/code-intel-cli/tests/artifact_index.rs','crates/code-intel-cli/src/run_commit.rs','manifest-projection:orchestration/integrations.json#artifact.index-committed-only','orchestration/retirements/e05-publication/status.json','orchestration/retirements/e05-publication/gate-out/compatibility-retirement-decision.json') +$snapshot=Get-FrozenSourceIdentity -FrozenSet $frozen -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot foreach($a in @($t,$m,$d,$g)){if($a.snapshotIdentity-ne$snapshot){throw "E10 packet is stale relative to its frozen source set"}} if($t.legacyBranch.branchId-ne$b-or$t.legacyBranch.callPath-ne$c-or$m.approvalSubject.legacyBranch.callPath-ne$c-or$t.replacement.capabilityId-ne$r){throw "E10 identity mismatch"};if(@($t.affectedFiles).Count-ne1-or$t.affectedFiles[0]-ne"update-code-intel-index.ps1"){throw "E10 escaped file boundary"};if($d.patch.algorithm-ne"replayable-delete-only-v1"-or@($d.patch.files).Count-ne1-or@($d.patch.files[0].hunks).Count-ne1){throw "E10 patch shape invalid"} $f=$d.patch.files[0];if((S $f.baseText)-ne$f.baseBlobSha256-or(S $f.resultText)-ne$f.resultBlobSha256-or(S (ConvertTo-Json -InputObject @($d.patch.files)-Depth 40 -Compress))-ne$d.patch.sha256){throw "E10 patch hash invalid"};$h=$f.hunks[0];if($h.newLines-ne0-or@($h.addedLines).Count-ne0-or$h.oldLines-le0){throw "E10 patch is not deletion only"};if($f.baseText-notmatch'function Read-JsonFile'-or$f.resultText-match'function Read-JsonFile'-or$f.resultText-notmatch'"artifact", "index"'-or$f.resultText-notmatch'exit 0'){throw "E10 branch deletion or A08 preservation invalid"} diff --git a/legacy/tools/compatibility/Test-NativeCodeRetirementPacket.ps1 b/legacy/tools/compatibility/Test-NativeCodeRetirementPacket.ps1 index cb53989..44c9890 100644 --- a/legacy/tools/compatibility/Test-NativeCodeRetirementPacket.ps1 +++ b/legacy/tools/compatibility/Test-NativeCodeRetirementPacket.ps1 @@ -9,18 +9,11 @@ Set-StrictMode -Version Latest # stayed behind (orchestration/, crates/) live one level above it $PipelineRepoRoot = Split-Path -Parent $RepoRoot $ErrorActionPreference = "Stop" -function Resolve-SnapshotInput([string]$Relative) { - $root = if ($Relative.StartsWith('crates/') -or $Relative.StartsWith('orchestration/')) { $PipelineRepoRoot } else { $RepoRoot } - Join-Path $root $Relative -} function Read-Packet([string]$Relative) { $path = Join-Path $PacketRoot $Relative if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "packet file missing: $Relative" } return Get-Content -LiteralPath $path -Raw | ConvertFrom-Json } -function Get-Sha256Text([string]$Text) { - return ([Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($Text)))).ToLowerInvariant() -} $branch = "run-code-intel.native-code.embedded" $replacement = "evidence.native-code" @@ -30,14 +23,24 @@ $manifest = Read-Packet "compatibility-retirement-manifest.json" $decision = Read-Packet "gate-out/compatibility-retirement-decision.json" $diff = Read-Packet "compatibility-retirement-deletion-diff.json" $status = Read-Packet "status.json" +# Staleness. This must mirror New-NativeCodeRetirementPacket.ps1's frozen set +# exactly; the digest and root resolution are computed by the shared helper both +# scripts dot-source, so the two lists are the only thing that has to match. +# +# The registry input is a canonical projection over exactly the integration this +# retirement concerns — evidence.native-code, the replacement capability and the +# registry participant — plus the manifest policy header, not the whole of +# orchestration/integrations.json. E07's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") $snapshotInputs = @( "run-code-intel.ps1", "crates/code-intel-cli/src/native_code_evidence.rs", "crates/code-intel-cli/tests/native_code_evidence.rs", "crates/code-intel-cli/tests/dag_run.rs", - "orchestration/integrations.json" + "manifest-projection:orchestration/integrations.json#evidence.native-code" ) -$currentSnapshotIdentity = Get-Sha256Text (($snapshotInputs | ForEach-Object { - (Get-FileHash -LiteralPath (Resolve-SnapshotInput $_) -Algorithm SHA256).Hash.ToLowerInvariant() -}) -join "`n") +$currentSnapshotIdentity = Get-FrozenSourceIdentity -FrozenSet $snapshotInputs -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot if ($manifest.snapshotIdentity -ne $currentSnapshotIdentity -or $ticket.snapshotIdentity -ne $currentSnapshotIdentity -or $decision.snapshotIdentity -ne $currentSnapshotIdentity -or $diff.snapshotIdentity -ne $currentSnapshotIdentity) { throw "E07 packet is stale relative to its frozen source set" diff --git a/legacy/tools/compatibility/Test-ProviderPreflightRetirementPacket.ps1 b/legacy/tools/compatibility/Test-ProviderPreflightRetirementPacket.ps1 index d456b79..1e23e3a 100644 --- a/legacy/tools/compatibility/Test-ProviderPreflightRetirementPacket.ps1 +++ b/legacy/tools/compatibility/Test-ProviderPreflightRetirementPacket.ps1 @@ -22,11 +22,21 @@ $file=$diff.patch.files[0]; $hunk=$file.hunks[0] # drift guard at all. The second component is taken from the packet's own # baseText by design — that text is frozen history and cannot drift — so the # guard is carried by the three live components. +# +# The fourth component is a canonical projection over exactly the integration +# this retirement concerns — provider.repowise-adapt, the replacement capability +# — plus the manifest policy header, not the whole of +# orchestration/integrations.json. E03's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. Generator and verifier compute the +# projection through the same dot-sourced helper so the two cannot drift. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") $snapshotIdentity = Get-TextSha ((@( (Get-FileHash -LiteralPath (Join-Path $RepoRoot "run-code-intel.ps1") -Algorithm SHA256).Hash.ToLowerInvariant(), (Get-TextSha $file.baseText), (Get-FileHash -LiteralPath (Join-Path $RepoRoot "Invoke-RepowiseProviderProbe.ps1") -Algorithm SHA256).Hash.ToLowerInvariant(), - (Get-FileHash -LiteralPath (Join-Path $PipelineRepoRoot "orchestration/integrations.json") -Algorithm SHA256).Hash.ToLowerInvariant() + (Get-FrozenManifestProjection -ManifestPath (Join-Path $PipelineRepoRoot "orchestration/integrations.json") -IntegrationIds @("provider.repowise-adapt")) )) -join "`n") foreach($artifact in @($ticket,$manifest,$decision,$diff)){if($artifact.snapshotIdentity-ne$snapshotIdentity){throw "E03 packet is stale relative to its frozen source set"}} if($file.path-ne"run-code-intel.ps1"-or$file.baseText-notmatch'test-code-intel-provider\.ps1'-or$file.baseText-match'Invoke-RepowiseProviderProbe\.ps1'-or$file.resultText-ne""-or$hunk.newLines-ne0-or@($hunk.addedLines).Count-ne0){throw "E03 diff is not a pure deletion of the historical direct wrapper block"} diff --git a/legacy/tools/compatibility/Test-RecommenderRetirementPacket.ps1 b/legacy/tools/compatibility/Test-RecommenderRetirementPacket.ps1 index 76f6caf..1335580 100644 --- a/legacy/tools/compatibility/Test-RecommenderRetirementPacket.ps1 +++ b/legacy/tools/compatibility/Test-RecommenderRetirementPacket.ps1 @@ -16,26 +16,32 @@ function Read-Json([string]$RelativePath) { if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "packet file is missing: $RelativePath" } return Get-Content -LiteralPath $path -Raw | ConvertFrom-Json } -function Get-Sha256Text([string]$Text) { - ([Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($Text)))).ToLowerInvariant() -} -function FrozenPath([string]$p) { - if ($p -like 'crates/*' -or $p -like 'orchestration/*') { Join-Path $PipelineRepoRoot $p } else { Join-Path $RepoRoot $p } -} $ticket = Read-Json "compatibility-retirement-ticket.json" $manifest = Read-Json "compatibility-retirement-manifest.json" $decision = Read-Json "gate-out/compatibility-retirement-decision.json" $diff = Read-Json "compatibility-retirement-deletion-diff.json" -# Staleness. This must mirror New-RecommenderRetirementPacket.ps1's -# $inputDigests set and root resolution exactly. E02 previously had no drift -# guard of any kind: it could pass with every frozen input changed, so a green -# result said nothing about whether the packet still described the live tree. -$frozen = @("run-code-intel.ps1", "OpenSpec-Detector.ps1", "Invoke-WorkflowRecommendation.ps1", "orchestration/integrations.json") -$snapshotIdentity = Get-Sha256Text ((($frozen | ForEach-Object { - (Get-FileHash -LiteralPath (FrozenPath $_) -Algorithm SHA256).Hash.ToLowerInvariant() -})) -join "`n") +# Staleness. This must mirror New-RecommenderRetirementPacket.ps1's $frozen set +# exactly; the digest and root resolution are computed by the shared helper both +# scripts dot-source, so the two lists are the only thing that has to match. +# E02 previously had no drift guard of any kind: it could pass with every frozen +# input changed, so a green result said nothing about whether the packet still +# described the live tree. +# +# The registry input is a canonical projection over exactly the integration this +# retirement concerns - advisory.workflow-recommend, the replacement capability - +# plus the manifest policy header, not the whole of +# orchestration/integrations.json. E02's staleness claim is about the registry +# state of the capability it retires; it is not about the toolchainDigests of +# unrelated Rust sources that happen to be pinned in the same file, which are +# re-pinned on every routine source edit. +. (Join-Path $PSScriptRoot "Get-FrozenManifestProjection.ps1") +$frozen = @( + "run-code-intel.ps1", "OpenSpec-Detector.ps1", "Invoke-WorkflowRecommendation.ps1", + "manifest-projection:orchestration/integrations.json#advisory.workflow-recommend" +) +$snapshotIdentity = Get-FrozenSourceIdentity -FrozenSet $frozen -RepoRoot $RepoRoot -PipelineRepoRoot $PipelineRepoRoot foreach ($artifact in @($ticket, $manifest, $decision, $diff)) { if ($artifact.snapshotIdentity -ne $snapshotIdentity) { throw "E02 packet is stale relative to its frozen source set" } } diff --git a/orchestration/retirements/e02-recommender/compatibility-retirement-deletion-diff.json b/orchestration/retirements/e02-recommender/compatibility-retirement-deletion-diff.json index 9023631..e2d006e 100644 --- a/orchestration/retirements/e02-recommender/compatibility-retirement-deletion-diff.json +++ b/orchestration/retirements/e02-recommender/compatibility-retirement-deletion-diff.json @@ -1 +1 @@ -{"schema":"code-intel-compatibility-retirement-deletion-diff.v1","snapshotIdentity":"562efc4fede0c2ce2407cb1ef2d28561b0a8a0edd3c6e5d3937285f3178c701d","retirementId":"retire-recommender-branch","legacyBranchId":"run-code-intel.workflow-recommender.inline","affectedFiles":["run-code-intel.ps1"],"deletionsOnly":true,"summary":"Proposed deletion is limited to the retired inline recommender adapter markers; provider-preflight and all other branches are excluded. Summary is non-authoritative.","patch":{"algorithm":"replayable-delete-only-v1","sha256":"36f7433bef1f198e37d0cdccd602753df5fb261b0f466714a01feb72751f734d","files":[{"baseBlobSha256":"a17f6761c104b0b3965b31bb3e2f8083bfaf28218fa3a9fc23ef6ea3b917ddd0","baseText":"#requires -Version 7.2\n\nparam(\n [string]$Repo = \"\",\n [string]$RepoPath = \"\",\n\n [string]$Config = \"\",\n\n [ValidateSet(\"auto\", \"windows\", \"macos\", \"linux\")]\n [string]$Platform = \"auto\",\n\n [ValidateSet(\"lite\", \"normal\", \"full\")]\n [string]$Mode = \"normal\",\n\n [string]$Language = \"\",\n\n [string]$ArtifactRoot = \"\",\n [string]$SentruxPath = \"\",\n [string]$RepowiseWorkspaceRoot = \"\",\n [string]$RepowiseShadowRoot = \"\",\n [string[]]$RepowiseScopePaths = @(),\n [string[]]$RepowiseRootFiles = @(),\n [int]$RepowiseTimeoutSeconds = 600,\n [string]$RepowiseProvider = \"\",\n [string]$RepowiseModel = \"\",\n [string]$RepowiseReasoning = \"\",\n [string]$ModelRoutingResult = \"\",\n [string]$ModelInventoryResult = \"\",\n [string]$ModelExecutableHandle = \"\",\n [string]$ModelPromptFile = \"\",\n [string]$ModelEndpoint = \"\",\n [ValidateSet(\"\", \"openai\", \"anthropic\", \"ollama\")]\n [string]$ModelProtocol = \"\",\n [string]$ModelCredentialEnvName = \"\",\n [ValidateRange(1, 3600)]\n [int]$ModelTimeoutSeconds = 300,\n [ValidateSet(\"json\", \"jsonl\")]\n [string]$ModelResponseFormat = \"json\",\n [string]$ModelAdapterRequest = \"\",\n [string]$ModelAdapterArtifactRoot = \"\",\n [string]$RuntimeCiEvidenceRequest = \"\",\n [string]$RuntimeCiEvidenceArtifactRoot = \"\",\n [string]$RepowiseAdapterRequest = \"\",\n [string]$RepowiseAdapterArtifactRoot = \"\",\n [long]$RepowiseAdapterEvaluatedAt = 0,\n [long]$RepowiseAdapterMaxAgeSeconds = 0,\n [string]$GraphAdapterRequest = \"\",\n [string]$GraphAdapterArtifactRoot = \"\",\n [long]$GraphAdapterEvaluatedAt = 0,\n [long]$GraphAdapterMaxAgeSeconds = 0,\n [string]$SentruxAdapterRequest = \"\",\n [string]$SentruxAdapterArtifactRoot = \"\",\n [long]$SentruxAdapterEvaluatedAt = 0,\n [long]$SentruxAdapterMaxAgeSeconds = 0,\n [string]$CodeNexusAdapterRequest = \"\",\n [string]$CodeNexusAdapterArtifactRoot = \"\",\n [long]$CodeNexusAdapterEvaluatedAt = 0,\n [long]$CodeNexusAdapterMaxAgeSeconds = 0,\n [string]$SurvivalScanRequest = \"\",\n [string]$SurvivalScanArtifactRoot = \"\",\n [string]$RunCommitSourceRoot = \"\",\n [string]$RunCommitAuthorityRoot = \"\",\n [string]$RunCommitManifestRef = \"\",\n [string]$RunCommitFinalName = \"\",\n [string[]]$InventoryExclude = @(),\n\n [switch]$DagCoordinate,\n\n [switch]$SaveSentruxBaseline,\n [switch]$AutoSaveMissingSentruxBaseline,\n [switch]$SkipRepowise,\n [switch]$RepowiseDocs,\n [switch]$AllowRepowiseShadowMutation,\n [switch]$SkipRepomix,\n [ValidateSet(\"xml\", \"markdown\", \"json\", \"plain\")]\n [string]$RepomixStyle = \"markdown\",\n [switch]$RepomixCompress,\n [switch]$SkipSentrux,\n[switch]$SkipSentruxCheck,\n[switch]$SkipSentruxGate,\n[switch]$RequireUnderstandGraph,\n[switch]$SkipGitHubResearch,\n[switch]$WorkspaceAdd,\n[switch]$SkipOpenSpec,\n[switch]$AutoOpenSpec,\n[ValidateSet(\"auto\", \"enabled\", \"disabled\")]\n[string]$ProactiveSkillSuggestions = \"auto\",\n[ValidateSet(\"auto\", \"ask\", \"enabled\", \"disabled\")]\n[string]$AutomaticPullRequests = \"auto\",\n[string]$BugSkill = \"\"\n)\n\nSet-StrictMode -Version Latest\n$ErrorActionPreference = \"Stop\"\n\n$platformModule = Join-Path (Join-Path $PSScriptRoot \"tools\") \"code-intel-platform.psm1\"\nImport-Module $platformModule -Force\n$followUpAutomationModule = Join-Path (Join-Path $PSScriptRoot \"tools\") \"code-intel-follow-up-automation.psm1\"\nImport-Module $followUpAutomationModule -Force\n$effectivePlatform = Get-CodeIntelPlatform -Platform $Platform\n$codeIntelPaths = Get-CodeIntelPaths -Platform $effectivePlatform -Root (Split-Path -Parent $PSScriptRoot)\n$rustExecutableName = if ($effectivePlatform -eq \"windows\") { \"code-intel.exe\" } else { \"code-intel\" }\n$defaultRustCli = Join-Path (Split-Path -Parent $PSScriptRoot) (Join-Path \"target/debug\" $rustExecutableName)\n\n[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()\n$OutputEncoding = [System.Text.UTF8Encoding]::new()\n$env:PYTHONIOENCODING = \"utf-8\"\n$env:PYTHONUTF8 = \"1\"\n$env:TERM = \"xterm\"\n$env:NO_COLOR = \"1\"\n$env:RICH_FORCE_TERMINAL = \"0\"\n\nif (-not [string]::IsNullOrWhiteSpace($ModelInventoryResult)) {\n if ([string]::IsNullOrWhiteSpace($ModelRoutingResult) -or\n [string]::IsNullOrWhiteSpace($ModelPromptFile) -or\n [string]::IsNullOrWhiteSpace($ModelAdapterArtifactRoot)) {\n throw \"Model request synthesis requires inventory, routing, prompt, and adapter artifact root\"\n }\n $synthesisScript = Join-Path $PSScriptRoot \"New-ModelAdapterRequest.ps1\"\n $delegateScript = Join-Path $PSScriptRoot \"Invoke-ModelChannelDelegate.ps1\"\n if (-not (Test-Path -LiteralPath $synthesisScript -PathType Leaf) -or -not (Test-Path -LiteralPath $delegateScript -PathType Leaf)) {\n throw \"Model request synthesis or delegate implementation is missing\"\n }\n New-Item -ItemType Directory -Force -Path $ModelAdapterArtifactRoot | Out-Null\n $synthesizedRequest = Join-Path ([IO.Path]::GetFullPath($ModelAdapterArtifactRoot)) \"model-adapter-request.v2.json\"\n $synthesisParameters = @{\n Inventory = $ModelInventoryResult\n Routing = $ModelRoutingResult\n PromptFile = $ModelPromptFile\n OutputPath = $synthesizedRequest\n TimeoutSeconds = $ModelTimeoutSeconds\n ResponseFormat = $ModelResponseFormat\n }\n if (-not [string]::IsNullOrWhiteSpace($ModelExecutableHandle)) { $synthesisParameters.ExecutableHandle = $ModelExecutableHandle }\n if (-not [string]::IsNullOrWhiteSpace($ModelEndpoint)) { $synthesisParameters.Endpoint = $ModelEndpoint }\n if (-not [string]::IsNullOrWhiteSpace($ModelProtocol)) { $synthesisParameters.Protocol = $ModelProtocol }\n if (-not [string]::IsNullOrWhiteSpace($ModelCredentialEnvName)) { $synthesisParameters.CredentialEnvName = $ModelCredentialEnvName }\n & $synthesisScript @synthesisParameters | Out-Null\n & $delegateScript -Request $synthesizedRequest -ArtifactRoot $ModelAdapterArtifactRoot\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($ModelAdapterRequest)) {\n if ([string]::IsNullOrWhiteSpace($ModelAdapterArtifactRoot)) { throw \"Model adapter facade requires an artifact root\" }\n $delegateScript = Join-Path $PSScriptRoot \"Invoke-ModelChannelDelegate.ps1\"\n if (-not (Test-Path -LiteralPath $delegateScript -PathType Leaf)) { throw \"Model channel delegate is missing: $delegateScript\" }\n & $delegateScript -Request $ModelAdapterRequest -ArtifactRoot $ModelAdapterArtifactRoot\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($RepowiseAdapterRequest)) {\n if ([string]::IsNullOrWhiteSpace($RepowiseAdapterArtifactRoot) -or\n $RepowiseAdapterEvaluatedAt -lt 0 -or\n $RepowiseAdapterMaxAgeSeconds -le 0) {\n throw \"Repowise adapter facade requires artifact root, non-negative evaluated-at, and positive max-age\"\n }\n $rustCli = $defaultRustCli\n if (-not (Test-Path -LiteralPath $rustCli -PathType Leaf)) {\n throw \"Repowise adapter binary is missing: $rustCli\"\n }\n & $rustCli provider repowise-adapt `\n --request $RepowiseAdapterRequest `\n --artifact-root $RepowiseAdapterArtifactRoot `\n --evaluated-at $RepowiseAdapterEvaluatedAt `\n --max-age-seconds $RepowiseAdapterMaxAgeSeconds\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($GraphAdapterRequest)) {\n if ([string]::IsNullOrWhiteSpace($GraphAdapterArtifactRoot) -or\n $GraphAdapterEvaluatedAt -lt 0 -or\n $GraphAdapterMaxAgeSeconds -le 0) {\n throw \"Graph adapter facade requires artifact root, non-negative evaluated-at, and positive max-age\"\n }\n $rustCli = $defaultRustCli\n if (-not (Test-Path -LiteralPath $rustCli -PathType Leaf)) {\n throw \"Graph adapter binary is missing: $rustCli\"\n }\n & $rustCli provider graph-adapt `\n --request $GraphAdapterRequest `\n --artifact-root $GraphAdapterArtifactRoot `\n --evaluated-at $GraphAdapterEvaluatedAt `\n --max-age-seconds $GraphAdapterMaxAgeSeconds\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($SentruxAdapterRequest)) {\n if ([string]::IsNullOrWhiteSpace($SentruxAdapterArtifactRoot) -or\n $SentruxAdapterEvaluatedAt -lt 0 -or\n $SentruxAdapterMaxAgeSeconds -le 0) {\n throw \"Sentrux adapter facade requires artifact root, non-negative evaluated-at, and positive max-age\"\n }\n $rustCli = $defaultRustCli\n if (-not (Test-Path -LiteralPath $rustCli -PathType Leaf)) { throw \"Sentrux adapter binary is missing: $rustCli\" }\n & $rustCli provider sentrux-adapt --request $SentruxAdapterRequest --artifact-root $SentruxAdapterArtifactRoot --evaluated-at $SentruxAdapterEvaluatedAt --max-age-seconds $SentruxAdapterMaxAgeSeconds\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($CodeNexusAdapterRequest)) {\n if ([string]::IsNullOrWhiteSpace($CodeNexusAdapterArtifactRoot) -or\n $CodeNexusAdapterEvaluatedAt -lt 0 -or\n $CodeNexusAdapterMaxAgeSeconds -le 0) {\n throw \"CodeNexus adapter facade requires artifact root, non-negative evaluated-at, and positive max-age\"\n }\n $rustCli = $defaultRustCli\n if (-not (Test-Path -LiteralPath $rustCli -PathType Leaf)) {\n throw \"CodeNexus adapter binary is missing: $rustCli\"\n }\n & $rustCli provider codenexus-adapt `\n --request $CodeNexusAdapterRequest `\n --artifact-root $CodeNexusAdapterArtifactRoot `\n --evaluated-at $CodeNexusAdapterEvaluatedAt `\n --max-age-seconds $CodeNexusAdapterMaxAgeSeconds\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($SurvivalScanRequest)) {\n if ([string]::IsNullOrWhiteSpace($SurvivalScanArtifactRoot)) {\n throw \"Repository survival scan facade requires an artifact root\"\n }\n $rustCli = $defaultRustCli\n if (-not (Test-Path -LiteralPath $rustCli -PathType Leaf)) {\n throw \"Repository survival scan binary is missing: $rustCli\"\n }\n & $rustCli repository survival-scan `\n --request $SurvivalScanRequest `\n --artifact-root $SurvivalScanArtifactRoot\n exit $LASTEXITCODE\n}\n\nif (-not [string]::IsNullOrWhiteSpace($RunCommitManifestRef)) {\n if ([string]::IsNullOrWhiteSpace($RunCommitSourceRoot) -or\n [string]::IsNullOrWhiteSpace($RunCommitAuthorityRoot) -or\n [string]::IsNullOrWhiteSpace($RunCommitFinalName)) {\n throw \"Run commit facade requires source root, authority root, manifest Artifact Ref, and final name\"\n }\n $rustCli = $defaultRustCli\n if (-not (Test-Path -LiteralPath $rustCli -PathType Leaf)) {\n throw \"Run commit binary is missing: $rustCli\"\n }\n & $rustCli run commit `\n --source-root $RunCommitSourceRoot `\n --authority-root $RunCommitAuthorityRoot `\n --manifest-ref $RunCommitManifestRef `\n --final-name $RunCommitFinalName\n exit $LASTEXITCODE\n}\n\nfunction Resolve-Repo {\n param([string]$Path)\n\n $item = Get-Item -LiteralPath $Path -ErrorAction Stop\n if (-not $item.PSIsContainer) {\n throw \"Repo path is not a directory: $Path\"\n }\n return $item.FullName\n}\n\nfunction Find-RepoConfigByPath {\n param([object]$ReposConfig, [string]$ResolvedRepoPath)\n\n if ($null -eq $ReposConfig -or [string]::IsNullOrWhiteSpace($ResolvedRepoPath)) { return $null }\n $normalizedRepoPath = [System.IO.Path]::TrimEndingDirectorySeparator($ResolvedRepoPath)\n foreach ($entry in $ReposConfig.PSObject.Properties) {\n $configuredPath = Get-JsonProperty $entry.Value \"path\"\n if ([string]::IsNullOrWhiteSpace([string]$configuredPath)) { continue }\n try {\n $resolvedConfiguredPath = Resolve-Repo ([string]$configuredPath)\n }\n catch {\n continue\n }\n $normalizedConfiguredPath = [System.IO.Path]::TrimEndingDirectorySeparator($resolvedConfiguredPath)\n if ([string]::Equals($normalizedConfiguredPath, $normalizedRepoPath, [System.StringComparison]::OrdinalIgnoreCase)) {\n return $entry.Value\n }\n }\n return $null\n}\n\nfunction Test-CommandAvailable {\n param([string]$Name)\n return [bool](Get-Command $Name -ErrorAction SilentlyContinue)\n}\n\n# Git config keys that name a program Git will execute, pinned empty so a\n# scanned repository's own .git/config cannot supply one. `core.fsmonitor`\n# runs on ordinary read commands like `git status`, before any gate in this\n# pipeline has looked at the repository. Mirrors\n# crates/code-intel-cli/src/hardened_git.rs.\n$script:GitHardening = @(\n \"-c\", \"core.fsmonitor=\",\n \"-c\", \"core.hooksPath=\",\n \"-c\", \"core.sshCommand=\",\n \"-c\", \"diff.external=\",\n \"-c\", \"core.pager=\"\n)\n\nfunction Test-GitRepository {\nparam([string]$Path)\n\nif (-not (Test-CommandAvailable \"git\")) { return $false }\n$output = & git @script:GitHardening -C $Path rev-parse --is-inside-work-tree 2>$null\nreturn ($LASTEXITCODE -eq 0 -and [string]$output -eq \"true\")\n}\n\n# Workflow recommendations are owned by the standalone advisory atom in OpenSpec-Detector.ps1.\n\nfunction Get-JsonProperty {\n param(\n [object]$Object,\n [string]$Name\n )\n\n if ($null -eq $Object) { return $null }\n $prop = $Object.PSObject.Properties[$Name]\n if ($null -eq $prop) { return $null }\n return $prop.Value\n}\n\nfunction Resolve-ConfigString {\n param(\n [string]$Value,\n [object]$RepoConfig,\n [object]$ConfigData,\n [string]$Name,\n [string[]]$EnvNames = @(),\n [string]$Default = \"\"\n )\n\n if (-not [string]::IsNullOrWhiteSpace($Value)) { return $Value }\n\n $repoValue = Get-JsonProperty $RepoConfig $Name\n if (-not [string]::IsNullOrWhiteSpace([string]$repoValue)) { return [string]$repoValue }\n\n $globalValue = Get-JsonProperty $ConfigData $Name\n if (-not [string]::IsNullOrWhiteSpace([string]$globalValue)) { return [string]$globalValue }\n\n foreach ($envName in $EnvNames) {\n $envValue = [Environment]::GetEnvironmentVariable($envName, \"Process\")\n if ([string]::IsNullOrWhiteSpace($envValue)) {\n $envValue = [Environment]::GetEnvironmentVariable($envName, \"User\")\n }\n if (-not [string]::IsNullOrWhiteSpace($envValue)) { return $envValue }\n }\n\n return $Default\n}\n\nfunction Normalize-RepowiseProvider {\n param([string]$Provider)\n if ([string]::IsNullOrWhiteSpace($Provider)) { return \"mock\" }\n $normalized = $Provider.Trim()\n if ($normalized -ieq \"ccw\") { return \"codex_cli\" }\n return $normalized\n}\n\nfunction Get-RepowiseProviderArgs {\n param(\n [string]$Provider,\n [string]$Model,\n [string]$Reasoning\n )\n\n $args = @(\"--provider\", $Provider)\n if (-not [string]::IsNullOrWhiteSpace($Model)) { $args += @(\"--model\", $Model) }\n if (-not [string]::IsNullOrWhiteSpace($Reasoning)) { $args += @(\"--reasoning\", $Reasoning) }\n return $args\n}\n\nfunction Get-DefaultArtifactRoot {\n return (Get-CodeIntelArtifactRoot -Platform $effectivePlatform)\n}\n\nfunction Get-DefaultShadowRoot {\n return (Get-CodeIntelShadowRoot -Platform $effectivePlatform)\n}\n\nfunction Resolve-ChildPath {\n param(\n [string]$Base,\n [string]$Path\n )\n\n if ([string]::IsNullOrWhiteSpace($Path)) { return $Base }\n if ([System.IO.Path]::IsPathRooted($Path)) { return (Resolve-Repo $Path) }\n return Resolve-Repo (Join-Path $Base $Path)\n}\n\nfunction Invoke-LoggedStep {\n param(\n [string]$Name,\n [scriptblock]$Body\n )\n\n $started = Get-Date\n $entry = [ordered]@{\n name = $Name\n startedAt = $started.ToString(\"o\")\n status = \"running\"\n exitCode = $null\n output = \"\"\n error = \"\"\n finishedAt = $null\n durationMs = $null\n }\n\n try {\n $global:LASTEXITCODE = 0\n $previousErrorActionPreference = $ErrorActionPreference\n try {\n $ErrorActionPreference = \"Continue\"\n $output = & $Body 2>&1\n }\n finally {\n $ErrorActionPreference = $previousErrorActionPreference\n }\n $entry.output = ($output | ForEach-Object { $_.ToString() } | Out-String).Trim()\n if ($global:LASTEXITCODE -ne 0) {\n throw \"Command exited with code $global:LASTEXITCODE\"\n }\n $entry.status = \"passed\"\n $entry.exitCode = 0\n }\n catch {\n $entry.status = \"failed\"\n if ($global:LASTEXITCODE -ne 0) {\n $entry.exitCode = $global:LASTEXITCODE\n }\n else {\n $entry.exitCode = 1\n }\n $entry.error = $_.Exception.Message\n if ([string]::IsNullOrWhiteSpace([string]$entry.output)) {\n $entry.output = ($_ | Out-String).Trim()\n }\n }\n finally {\n $finished = Get-Date\n $entry.finishedAt = $finished.ToString(\"o\")\n $entry.durationMs = [int]($finished - $started).TotalMilliseconds\n }\n\n return [pscustomobject]$entry\n}\n\nfunction Convert-OptionalRepowiseTimeout {\n param([object]$Step)\n\n if ($null -eq $Step) { return $Step }\n $blob = (([string]$Step.error) + \"`n\" + ([string]$Step.output)).ToLowerInvariant()\n if ([string]$Step.status -eq \"failed\" -and [string]$Step.name -like \"repowise*\" -and $blob -match \"timed out after\") {\n $Step.status = \"skipped\"\n $Step.exitCode = $null\n $Step.output = \"Optional Repowise step skipped after timeout. $($Step.error)\"\n $Step.error = \"\"\n }\n return $Step\n}\n\nfunction Get-RelativePathSafe {\n param(\n [string]$Base,\n [string]$Path\n )\n\n try {\n return [System.IO.Path]::GetRelativePath($Base, $Path)\n }\n catch {\n try {\n $baseFull = [System.IO.Path]::GetFullPath($Base)\n $pathFull = [System.IO.Path]::GetFullPath($Path)\n if (-not $baseFull.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {\n $baseFull = $baseFull + [System.IO.Path]::DirectorySeparatorChar\n }\n if ((Test-Path -LiteralPath $pathFull -PathType Container) -and -not $pathFull.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {\n $pathFull = $pathFull + [System.IO.Path]::DirectorySeparatorChar\n }\n $relative = ([uri]$baseFull).MakeRelativeUri([uri]$pathFull).ToString()\n $relative = [uri]::UnescapeDataString($relative).Replace(\"/\", [System.IO.Path]::DirectorySeparatorChar)\n if ([string]::IsNullOrWhiteSpace($relative)) { return \".\" }\n return $relative\n }\n catch {\n return $Path\n }\n }\n}\n\nfunction Get-StepFailureCategory {\n param([object]$Step)\n\n $name = [string]$Step.name\n $status = [string]$Step.status\n $blob = (([string]$Step.error) + \"`n\" + ([string]$Step.output)).ToLowerInvariant()\n\n if ($name -eq \"understand graph\" -and ($status -eq \"failed\" -or $status -eq \"manual_required\")) {\n return \"graph_missing\"\n }\n if ($name -like \"sentrux*\" -and ($status -eq \"failed\" -or $status -eq \"manual_required\")) {\n return \"sentrux_fail\"\n }\n if (($name -like \"repowise*\" -or $name -eq \"provider preflight\") -and $blob -match \"rate_limit|quota|usage limit exceeded|error code: 429|too many requests|provider_quota\") {\n return \"provider_quota\"\n }\n if (($name -like \"repowise*\" -or $name -eq \"provider preflight\") -and $blob -match \"provider_unavailable|model_not_found|not_found_error|error code: 404|status code: 404\") {\n return \"provider_unavailable\"\n }\n if (($name -like \"repowise*\" -or $name -eq \"provider preflight\") -and $blob -match \"config_error|authentication_error|invalid api key|not authorized|token not match\") {\n return \"config_error\"\n }\n if ($status -eq \"failed\") {\n return \"local_tool_error\"\n }\n return $null\n}\n\nfunction Get-CodeIntelEffectiveFailedSteps {\n param(\n [object[]]$FailedSteps,\n [int]$BlockingSentruxDebt\n )\n\n return @($FailedSteps | Where-Object {\n $category = [string](Get-StepFailureCategory $_)\n $category -ne \"sentrux_fail\" -or $BlockingSentruxDebt -gt 0\n })\n}\n\nfunction Test-GitHubSolutionResearchRequired {\nparam([object]$FailureCounts)\n\n if ($null -eq $FailureCounts) { return $false }\n if ($FailureCounts.localToolError -gt 0) { return $true }\n if ($FailureCounts.sentruxFail -gt 0) { return $true }\n if ($FailureCounts.providerQuota -gt 0) { return $true }\n\n return $false\n}\n\nfunction Complete-NodeLintHygieneStep {\n param(\n [System.Collections.Specialized.OrderedDictionary]$Step,\n [datetime]$Started\n )\n\n $finished = Get-Date\n $Step[\"finishedAt\"] = $finished.ToString(\"o\")\n $Step[\"durationMs\"] = [int]($finished - $Started).TotalMilliseconds\n return [pscustomobject]$Step\n}\n\nfunction Get-NodeLintHygieneStep {\n param(\n [string]$RepoPath,\n [bool]$RgAvailable\n )\n\n $started = Get-Date\n $step = [ordered]@{\n name = \"node lint hygiene\"\n startedAt = $started.ToString(\"o\")\n status = \"skipped\"\n exitCode = $null\n output = \"\"\n error = \"\"\n finishedAt = \"\"\n durationMs = 0\n }\n\n try {\n $packageJson = Join-Path $RepoPath \"package.json\"\n if (-not (Test-Path -LiteralPath $packageJson -PathType Leaf)) {\n $step[\"output\"] = \"No package.json found.\"\n return (Complete-NodeLintHygieneStep -Step $step -Started $started)\n }\n\n $package = Get-Content -LiteralPath $packageJson -Raw | ConvertFrom-Json\n $scripts = Get-JsonProperty $package \"scripts\"\n $lintScript = [string](Get-JsonProperty $scripts \"lint\")\n if ([string]::IsNullOrWhiteSpace($lintScript) -or $lintScript -notmatch \"\\beslint\\b\") {\n $step[\"output\"] = \"No root ESLint lint script detected.\"\n return (Complete-NodeLintHygieneStep -Step $step -Started $started)\n }\n\n if (-not $RgAvailable) {\n $step[\"output\"] = \"rg unavailable; skip static ESLint asset-boundary check.\"\n return (Complete-NodeLintHygieneStep -Step $step -Started $started)\n }\n\n $rgArgs = @(\n \"--files\",\n \"--hidden\",\n \"--no-ignore\",\n \"-g\", \"!**/.git/**\",\n \"-g\", \"!**/node_modules/**\",\n \"-g\", \"!**/dist/**\",\n \"-g\", \"!**/build/**\",\n $RepoPath\n )\n $repoFiles = @(& rg @rgArgs 2>$null)\n $global:LASTEXITCODE = 0\n $normalizedFiles = @($repoFiles | ForEach-Object { ([string]$_).Replace(\"\\\", \"/\") })\n\n $assetPatterns = New-Object System.Collections.Generic.List[string]\n if (@($normalizedFiles | Where-Object { $_ -match \"(^|/)apps/[^/]+/public/charting_library/\" } | Select-Object -First 1).Count -gt 0) {\n $assetPatterns.Add(\"apps/*/public/charting_library/**\")\n }\n if (@($normalizedFiles | Where-Object { $_ -match \"(^|/)apps/[^/]+/public/datafeeds/\" } | Select-Object -First 1).Count -gt 0) {\n $assetPatterns.Add(\"apps/*/public/datafeeds/**\")\n }\n if (@($normalizedFiles | Where-Object { $_ -match \"(^|/)packages/[^/]+/vendor/\" } | Select-Object -First 1).Count -gt 0) {\n $assetPatterns.Add(\"packages/*/vendor/**\")\n }\n if (@($normalizedFiles | Where-Object { $_ -match \"(^|/)vendor/\" } | Select-Object -First 1).Count -gt 0) {\n $assetPatterns.Add(\"vendor/**\")\n }\n\n if ($assetPatterns.Count -eq 0) {\n $step[\"status\"] = \"passed\"\n $step[\"exitCode\"] = 0\n $step[\"output\"] = \"Root ESLint lint script detected; no known generated/vendor static asset directories found.\"\n return (Complete-NodeLintHygieneStep -Step $step -Started $started)\n }\n\n $configNames = @(\"eslint.config.js\", \"eslint.config.mjs\", \"eslint.config.cjs\", \".eslintignore\", \".eslintrc\", \".eslintrc.json\", \".eslintrc.js\", \".eslintrc.cjs\")\n $configFiles = @($configNames | ForEach-Object {\n $candidate = Join-Path $RepoPath $_\n if (Test-Path -LiteralPath $candidate -PathType Leaf) { $candidate }\n })\n if ($configFiles.Count -eq 0) {\n $step[\"status\"] = \"manual_required\"\n $step[\"exitCode\"] = 0\n $step[\"output\"] = \"Root lint script uses ESLint and known generated/vendor static asset dirs exist, but no root ESLint config or ignore file was found. Add ignores for: $($assetPatterns -join ', '), then run root lint before push.\"\n return (Complete-NodeLintHygieneStep -Step $step -Started $started)\n }\n\n $configText = (($configFiles | ForEach-Object { Get-Content -LiteralPath $_ -Raw }) -join [Environment]::NewLine).Replace(\"\\\", \"/\")\n $missing = New-Object System.Collections.Generic.List[string]\n foreach ($pattern in $assetPatterns) {\n $covered = $false\n if ($pattern -eq \"apps/*/public/charting_library/**\") {\n $covered = ($configText -match \"charting_library|apps/\\*/public|\\*\\*/public|public/\\*\\*\")\n }\n elseif ($pattern -eq \"apps/*/public/datafeeds/**\") {\n $covered = ($configText -match \"datafeeds|apps/\\*/public|\\*\\*/public|public/\\*\\*\")\n }\n elseif ($pattern -eq \"packages/*/vendor/**\" -or $pattern -eq \"vendor/**\") {\n $covered = ($configText -match \"vendor\")\n }\n\n if (-not $covered) {\n $missing.Add($pattern)\n }\n }\n\n if ($missing.Count -gt 0) {\n $step[\"status\"] = \"manual_required\"\n $step[\"exitCode\"] = 0\n $step[\"output\"] = \"Root lint script uses ESLint and known generated/vendor static asset dirs exist, but ignore coverage appears incomplete for: $($missing -join ', '). Add explicit ESLint ignores or run root lint before push.\"\n }\n else {\n $step[\"status\"] = \"passed\"\n $step[\"exitCode\"] = 0\n $step[\"output\"] = \"Root ESLint lint script has ignore coverage for known generated/vendor static asset dirs: $($assetPatterns -join ', ').\"\n }\n }\n catch {\n $step[\"status\"] = \"manual_required\"\n $step[\"exitCode\"] = 0\n $step[\"output\"] = \"Node lint hygiene check could not complete. Run root lint before push and inspect generated/vendor asset ignores.\"\n $step[\"error\"] = $_.Exception.Message\n }\n finally {\n $finished = Get-Date\n $step[\"finishedAt\"] = $finished.ToString(\"o\")\n $step[\"durationMs\"] = [int]($finished - $started).TotalMilliseconds\n }\n\n return (Complete-NodeLintHygieneStep -Step $step -Started $started)\n}\n\nfunction New-GitHubSolutionResearchNotApplicable {\n return [ordered]@{\n status = \"not_applicable\"\n required = $false\n path = \"\"\n markdown = \"\"\n reason = \"No blocker category requires GitHub solution research.\"\n candidates = 0\n queries = 0\n evidenceLinks = @()\n exitCriteria = @(\"GitHub research is not required for clean, graph-missing, governance-only, or surgery-plan-only scans.\")\n }\n}\n\nfunction Join-StatusNames {\n param(\n [object[]]$Items,\n [string]$Empty = \"none\"\n )\n\n if ($Items.Count -eq 0) { return $Empty }\n return (($Items | ForEach-Object { \"$($_.name)=$($_.status)\" }) -join \"; \")\n}\n\nfunction Read-JsonFileSafe {\n param([string]$Path)\n\n if ([string]::IsNullOrWhiteSpace($Path) -or -not (Test-Path -LiteralPath $Path -PathType Leaf)) {\n return $null\n }\n try {\n return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json\n }\n catch {\n return $null\n }\n}\n\nfunction Get-CodeEvidenceLanguage {\n param([string]$Extension)\n\n switch ($Extension.ToLowerInvariant()) {\n \".ps1\" { return \"powershell\" }\n \".psm1\" { return \"powershell\" }\n \".py\" { return \"python\" }\n \".js\" { return \"javascript\" }\n \".jsx\" { return \"javascript\" }\n \".mjs\" { return \"javascript\" }\n \".cjs\" { return \"javascript\" }\n \".ts\" { return \"typescript\" }\n \".tsx\" { return \"typescript\" }\n \".rs\" { return \"rust\" }\n \".go\" { return \"go\" }\n \".java\" { return \"java\" }\n \".cs\" { return \"csharp\" }\n default { return \"text\" }\n }\n}\n\nfunction New-CodeEvidenceNativeSymbol {\n param(\n [string]$RelativePath,\n [string]$Language,\n [int]$LineNumber,\n [string]$Kind,\n [string]$Name\n )\n\n return [ordered]@{\n id = \"$RelativePath#$Kind`:$Name\"\n kind = $Kind\n name = $Name\n file = $RelativePath\n startLine = $LineNumber\n endLine = $LineNumber\n language = $Language\n confidence = 0.55\n source = \"native-minimal\"\n }\n}\n\nfunction Get-CodeEvidencePowerShellSymbol {\n param([string]$Line)\n\n if ($Line -match '^\\s*function\\s+([A-Za-z0-9_\\-:]+)') {\n return [ordered]@{ kind = \"function\"; name = $Matches[1] }\n }\n return $null\n}\n\nfunction Get-CodeEvidencePythonSymbol {\n param([string]$Line)\n\n if ($Line -match '^\\s*(def|class)\\s+([A-Za-z_][A-Za-z0-9_]*)') {\n $kind = if ($Matches[1] -eq \"class\") { \"class\" } else { \"function\" }\n return [ordered]@{ kind = $kind; name = $Matches[2] }\n }\n return $null\n}\n\nfunction Get-CodeEvidenceJavaScriptSymbol {\n param([string]$Line)\n\n if ($Line -match '^\\s*(export\\s+)?(async\\s+)?function\\s+([A-Za-z_$][A-Za-z0-9_$]*)') {\n return [ordered]@{ kind = \"function\"; name = $Matches[3] }\n }\n if ($Line -match '^\\s*(export\\s+)?(const|let|var)\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*=\\s*(async\\s*)?(\\([^)]*\\)|[A-Za-z_$][A-Za-z0-9_$]*)\\s*=>') {\n return [ordered]@{ kind = \"function\"; name = $Matches[3] }\n }\n if ($Line -match '^\\s*(export\\s+)?(class|interface)\\s+([A-Za-z_$][A-Za-z0-9_$]*)') {\n return [ordered]@{ kind = $Matches[2]; name = $Matches[3] }\n }\n return $null\n}\n\nfunction Get-CodeEvidenceRustSymbol {\n param([string]$Line)\n\n if ($Line -match '^\\s*(pub\\s+)?(async\\s+)?fn\\s+([A-Za-z_][A-Za-z0-9_]*)') {\n return [ordered]@{ kind = \"function\"; name = $Matches[3] }\n }\n return $null\n}\n\nfunction Get-CodeEvidenceGoSymbol {\n param([string]$Line)\n\n if ($Line -match '^\\s*func\\s+(\\([^)]+\\)\\s*)?([A-Za-z_][A-Za-z0-9_]*)') {\n return [ordered]@{ kind = \"function\"; name = $Matches[2] }\n }\n return $null\n}\n\nfunction Get-CodeEvidenceJavaSymbol {\n param([string]$Line)\n\n if ($Line -match '^\\s*(public|private|protected)?\\s*(class|interface|enum)\\s+([A-Za-z_][A-Za-z0-9_]*)') {\n return [ordered]@{ kind = $Matches[2]; name = $Matches[3] }\n }\n return $null\n}\n\nfunction Get-CodeEvidenceSymbolCandidate {\n param(\n [string]$Language,\n [string]$Line\n )\n\n switch ($Language) {\n \"powershell\" { return Get-CodeEvidencePowerShellSymbol $Line }\n \"python\" { return Get-CodeEvidencePythonSymbol $Line }\n \"javascript\" { return Get-CodeEvidenceJavaScriptSymbol $Line }\n \"typescript\" { return Get-CodeEvidenceJavaScriptSymbol $Line }\n \"rust\" { return Get-CodeEvidenceRustSymbol $Line }\n \"go\" { return Get-CodeEvidenceGoSymbol $Line }\n \"java\" { return Get-CodeEvidenceJavaSymbol $Line }\n default { return $null }\n }\n}\n\nfunction Get-CodeEvidenceSymbols {\n param(\n [string]$RelativePath,\n [string]$Language,\n [string[]]$Lines\n )\n\n $symbols = New-Object System.Collections.Generic.List[object]\n for ($i = 0; $i -lt $Lines.Count; $i++) {\n $candidate = Get-CodeEvidenceSymbolCandidate -Language $Language -Line ([string]$Lines[$i])\n if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate[\"name\"])) {\n continue\n }\n\n $symbols.Add((New-CodeEvidenceNativeSymbol `\n -RelativePath $RelativePath `\n -Language $Language `\n -LineNumber ($i + 1) `\n -Kind ([string]$candidate[\"kind\"]) `\n -Name ([string]$candidate[\"name\"])))\n }\n return $symbols.ToArray()\n}\n\nfunction Get-CodeEvidenceImports {\nparam(\n[string]$RelativePath,\n[string]$Language,\n[string[]]$Lines\n )\n\n $imports = New-Object System.Collections.Generic.List[object]\n for ($i = 0; $i -lt $Lines.Count; $i++) {\n $line = [string]$Lines[$i]\n $target = \"\"\n if ($Language -in @(\"javascript\", \"typescript\") -and $line -match 'from\\s+[\"'']([^\"'']+)[\"'']') {\n $target = $Matches[1]\n } elseif ($Language -in @(\"javascript\", \"typescript\") -and $line -match 'require\\([\"'']([^\"'']+)[\"'']\\)') {\n $target = $Matches[1]\n } elseif ($Language -eq \"python\" -and $line -match '^\\s*(from|import)\\s+([A-Za-z0-9_\\.]+)') {\n $target = $Matches[2]\n } elseif ($Language -eq \"rust\" -and $line -match '^\\s*use\\s+([^;]+);') {\n $target = $Matches[1].Trim()\n } elseif ($Language -eq \"go\" -and $line -match '^\\s*import\\s+[\"'']([^\"'']+)[\"'']') {\n $target = $Matches[1]\n } elseif ($line -match '^\\s*#include\\s+[<\"]([^>\"]+)[>\"]') {\n $target = $Matches[1]\n }\n\n if (-not [string]::IsNullOrWhiteSpace($target)) {\n $imports.Add([ordered]@{\n file = $RelativePath\n line = $i + 1\n target = $target\n language = $Language\n confidence = 0.6\n source = \"native-minimal\"\n })\n }\n }\nreturn $imports.ToArray()\n}\n\nfunction New-AgentCodeSliceRanking {\nparam(\n[object[]]$Files,\n[object[]]$Symbols,\n[object[]]$Imports\n)\n\n$symbolsByFile = @{}\nforeach ($symbol in @($Symbols)) {\n$file = [string]$symbol.file\nif ([string]::IsNullOrWhiteSpace($file)) { continue }\nif (-not $symbolsByFile.ContainsKey($file)) {\n$symbolsByFile[$file] = New-Object System.Collections.Generic.List[object]\n}\n$symbolsByFile[$file].Add($symbol)\n}\n\n$importsByFile = @{}\nforeach ($import in @($Imports)) {\n$file = [string]$import.file\nif ([string]::IsNullOrWhiteSpace($file)) { continue }\nif (-not $importsByFile.ContainsKey($file)) {\n$importsByFile[$file] = New-Object System.Collections.Generic.List[object]\n}\n$importsByFile[$file].Add($import)\n}\n\n$rankedFiles = New-Object System.Collections.Generic.List[object]\nforeach ($file in @($Files)) {\n$path = [string]$file.path\nif ([string]::IsNullOrWhiteSpace($path)) { continue }\n\n$reasons = New-Object System.Collections.Generic.List[string]\n$score = 0\nif ($path -match '(^|/)(index|main|app|server|cli)\\.') {\n$reasons.Add(\"entrypoint\")\n$score += 40\n}\nif ($path -match '(test|spec)\\.' -or $path -match '(^|/)(tests?|spec)/') {\n$reasons.Add(\"test\")\n$score += 35\n}\nif ($symbolsByFile.ContainsKey($path) -and $symbolsByFile[$path].Count -gt 0) {\n$reasons.Add(\"symbols\")\n$score += [Math]::Min(20, 5 * $symbolsByFile[$path].Count)\n}\nif ($importsByFile.ContainsKey($path) -and $importsByFile[$path].Count -gt 0) {\n$reasons.Add(\"imports\")\n$score += [Math]::Min(15, 5 * $importsByFile[$path].Count)\n}\nif ($score -eq 0) {\n$reasons.Add(\"inventory\")\n$score = 1\n}\n\n$rankedFiles.Add([ordered]@{\npath = $path\nlanguage = [string]$file.language\nscore = $score\nreasons = @($reasons.ToArray())\nsymbols = if ($symbolsByFile.ContainsKey($path)) { @($symbolsByFile[$path] | ForEach-Object { $_.name }) } else { @() }\nimports = if ($importsByFile.ContainsKey($path)) { @($importsByFile[$path] | ForEach-Object { $_.target }) } else { @() }\n})\n}\n\n$ordered = @($rankedFiles.ToArray() | Sort-Object -Property @{ Expression = \"score\"; Descending = $true }, @{ Expression = \"path\"; Descending = $false })\nreturn [ordered]@{\nschema = \"agent-code-slice-ranking.v1\"\nstrategy = \"native-evidence-default\"\nfiles = $ordered\n}\n}\n\nfunction Write-CodeEvidenceAgentSlices {\nparam(\n[string]$AgentDir,\n[string]$SliceDir,\n[object[]]$Files,\n[object[]]$Symbols,\n[object[]]$Imports,\n[object]$CocoOutcome\n)\n\n$ranking = New-AgentCodeSliceRanking -Files $Files -Symbols $Symbols -Imports $Imports\n$rankingPath = Join-Path $AgentDir \"ranking.json\"\n$ranking | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $rankingPath -Encoding UTF8\n\n$agentIndexPath = Join-Path $AgentDir \"index.md\"\n@(\n\"# Agent Code Map\",\n\"\",\n\"## Status\",\n\"- Code Evidence Layer: ok\",\n\"- Native minimal layer: enabled\",\n\"- Ranking: [ranking.json](ranking.json)\",\n\"- Native retrieval slice: [native-retrieval](slices/native-retrieval.md)\",\n\"- cocoindex-code adapter: $($CocoOutcome.status) ($($CocoOutcome.reasonCode))\",\n\"\",\n\"## Full Dumps\",\n\"- [files](../full/files.json)\",\n\"- [symbols](../full/symbols.json)\",\n\"- [chunks](../full/chunks.json)\",\n\"- [symbol chunks](../full/symbol-chunks.json)\",\n\"- [imports](../full/imports.json)\",\n\"\",\n\"## Slices\",\n\"- [native retrieval](slices/native-retrieval.md)\",\n\"- [entrypoints](slices/entrypoints.md)\",\n\"- [tests](slices/tests.md)\",\n\"- [risk hotspots](slices/risk-hotspots.md)\"\n) | Set-Content -LiteralPath $agentIndexPath -Encoding UTF8\n\n$topRanked = @($ranking.files | Select-Object -First 20)\n@(\n\"# Native Retrieval Slice\",\n\"\",\n\"- Strategy: native-evidence-default\",\n\"- Source: Code Evidence files/symbols/imports only\",\n\"\",\n\"## Ranked Files\"\n) + @($topRanked | ForEach-Object {\n\"- $($_.path) score=$($_.score) reasons=$(@($_.reasons) -join ',')\"\n}) | Set-Content -LiteralPath (Join-Path $SliceDir \"native-retrieval.md\") -Encoding UTF8\n\n$entrypoints = @($Files | Where-Object { $_.path -match '(^|/)(index|main|app|server|cli)\\.' } | Select-Object -First 20)\n@(\"# Entrypoints\", \"\") + @($entrypoints | ForEach-Object { \"- $($_.path) ($($_.language))\" }) | Set-Content -LiteralPath (Join-Path $SliceDir \"entrypoints.md\") -Encoding UTF8\n\n$tests = @($Files | Where-Object { $_.path -match '(test|spec)\\.' -or $_.path -match '(^|/)(tests?|spec)/' } | Select-Object -First 30)\n@(\"# Tests\", \"\") + @($tests | ForEach-Object { \"- $($_.path) ($($_.language))\" }) | Set-Content -LiteralPath (Join-Path $SliceDir \"tests.md\") -Encoding UTF8\n\n@(\n\"# Risk Hotspots\",\n\"\",\n\"- Native minimal layer does not calculate complexity.\",\n\"- Treat file-sized chunks as fallback evidence until structural chunking is enabled.\",\n\"- cocoindex-code adapter outcome: $($CocoOutcome.status) ($($CocoOutcome.reasonCode)).\"\n) | Set-Content -LiteralPath (Join-Path $SliceDir \"risk-hotspots.md\") -Encoding UTF8\n\nreturn [ordered]@{\nagentIndex = $agentIndexPath\nranking = $rankingPath\nnativeRetrieval = Join-Path $SliceDir \"native-retrieval.md\"\n}\n}\n\nfunction New-CodeEvidenceLayer {\nparam(\n[string]$RepoPath,\n[string]$RunDir,\n[object[]]$Files,\n[object]$CodeEvidenceConfig = $null\n)\n\n$root = Join-Path $RunDir \"code-evidence\"\n$fullDir = Join-Path $root \"merged\\full\"\n$agentDir = Join-Path $root \"merged\\agent\"\n$sliceDir = Join-Path $agentDir \"slices\"\n$adapterDir = Join-Path $root \"adapters\\cocoindex-code\"\nforeach ($dir in @($fullDir, $agentDir, $sliceDir, $adapterDir)) {\nNew-Item -ItemType Directory -Force -Path $dir | Out-Null\n}\n\n$fileRows = New-Object System.Collections.Generic.List[object]\n$symbols = New-Object System.Collections.Generic.List[object]\n$chunks = New-Object System.Collections.Generic.List[object]\n$symbolChunks = New-Object System.Collections.Generic.List[object]\n$imports = New-Object System.Collections.Generic.List[object]\n\nforeach ($file in @($Files)) {\n$fileText = [string]$file\nif ([string]::IsNullOrWhiteSpace($fileText)) { continue }\n$fullPath = if ([System.IO.Path]::IsPathRooted($fileText)) { $fileText } else { Join-Path $RepoPath $fileText }\nif (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) { continue }\n\n$relativePath = (Get-RelativePathSafe $RepoPath $fullPath).Replace(\"\\\", \"/\")\n$extension = [System.IO.Path]::GetExtension($fullPath)\n$language = Get-CodeEvidenceLanguage -Extension $extension\n$content = Get-Content -LiteralPath $fullPath -Raw -ErrorAction SilentlyContinue\n if ($null -eq $content) { $content = \"\" }\n $lines = if ([string]::IsNullOrEmpty($content)) { @() } else { @($content -split \"`r?`n\") }\n $lines = @($lines)\n $contentBytes = [System.Text.Encoding]::UTF8.GetBytes($content)\n $hashBytes = [System.Security.Cryptography.SHA256]::HashData($contentBytes)\n$hash = [System.BitConverter]::ToString($hashBytes).Replace(\"-\", \"\").ToLowerInvariant()\n\n$fileRows.Add([ordered]@{\npath = $relativePath\nlanguage = $language\nbytes = $contentBytes.Length\nlines = $lines.Count\ntextHash = $hash\nsource = \"native-minimal\"\n})\n\n$fileSymbols = @(Get-CodeEvidenceSymbols -RelativePath $relativePath -Language $language -Lines $lines)\nforeach ($symbol in $fileSymbols) { $symbols.Add($symbol) }\n\n$chunkId = \"$relativePath#file\"\n$chunks.Add([ordered]@{\nid = $chunkId\nfile = $relativePath\nstartLine = 1\nendLine = [Math]::Max(1, $lines.Count)\nkind = \"file\"\ncontainsSymbols = @($fileSymbols | ForEach-Object { $_.id })\ntextHash = $hash\nsource = \"native-minimal\"\n})\n\nforeach ($symbol in $fileSymbols) {\n$symbolChunks.Add([ordered]@{\nsymbolId = $symbol.id\nchunkId = $chunkId\nrelation = \"contained_by\"\nconfidence = 0.55\n})\n}\n\nforeach ($import in @(Get-CodeEvidenceImports -RelativePath $relativePath -Language $language -Lines $lines)) {\n$imports.Add($import)\n}\n}\n\n$fileRowsArray = @($fileRows.ToArray())\n$symbolsArray = @($symbols.ToArray())\n$chunksArray = @($chunks.ToArray())\n$symbolChunksArray = @($symbolChunks.ToArray())\n$importsArray = @($imports.ToArray())\n\n([ordered]@{ schema = \"code-evidence-files.v1\"; files = $fileRowsArray }) | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $fullDir \"files.json\") -Encoding UTF8\n([ordered]@{ schema = \"code-evidence-symbols.v1\"; symbols = $symbolsArray }) | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $fullDir \"symbols.json\") -Encoding UTF8\n([ordered]@{ schema = \"code-evidence-chunks.v1\"; chunks = $chunksArray }) | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $fullDir \"chunks.json\") -Encoding UTF8\n([ordered]@{ schema = \"code-evidence-symbol-chunks.v1\"; mappings = $symbolChunksArray }) | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $fullDir \"symbol-chunks.json\") -Encoding UTF8\n([ordered]@{ schema = \"code-evidence-imports.v1\"; imports = $importsArray }) | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $fullDir \"imports.json\") -Encoding UTF8\n\n# R07 reviewed retirement: this compatibility artifact is a static tombstone.\n# There is intentionally no configuration lookup, executable discovery, or provider invocation.\n$cocoOutcome = [ordered]@{\nschema = \"code-evidence-adapter-outcome.v1\"\nadapter = \"cocoindex-code\"\nenabled = $false\nrequired = $false\nstatus = \"skipped\"\nfatal = $false\nreasonCode = \"reviewed_deletion\"\nreason = \"cocoindex-code is a reviewed retirement tombstone; legacy configuration cannot restore discovery or invocation.\"\ncommand = \"\"\n}\n\n$cocoOutcomePath = Join-Path $adapterDir \"outcome.json\"\n$cocoOutcome | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $cocoOutcomePath -Encoding UTF8\n\n$scorecard = [ordered]@{\nschema = \"code-evidence-scorecard.v1\"\nstatus = \"ok\"\nnativeMinimal = $true\nadapters = @($cocoOutcome)\nmetrics = [ordered]@{\nfiles = $fileRowsArray.Count\nsymbols = $symbolsArray.Count\nchunks = $chunksArray.Count\nimports = $importsArray.Count\nsymbolContainmentRate = if ($symbolsArray.Count -gt 0) { 1.0 } else { $null }\nfallbackChunkRate = 1.0\n}\n}\n$scorecardPath = Join-Path $root \"merged\\scorecard.json\"\n$scorecard | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $scorecardPath -Encoding UTF8\n$scorecardMarkdownPath = Join-Path $root \"merged\\scorecard.md\"\n@(\n\"# Code Evidence Scorecard\",\n\"\",\n\"- Status: ok\",\n\"- Native minimal: true\",\n\"- Files: $($fileRowsArray.Count)\",\n\"- Symbols: $($symbolsArray.Count)\",\n\"- Chunks: $($chunksArray.Count)\",\n\"- Imports: $($importsArray.Count)\",\n\"- cocoindex-code: $($cocoOutcome.status) ($($cocoOutcome.reasonCode))\"\n) | Set-Content -LiteralPath $scorecardMarkdownPath -Encoding UTF8\n\n$agentSlices = Write-CodeEvidenceAgentSlices `\n-AgentDir $agentDir `\n-SliceDir $sliceDir `\n-Files $fileRowsArray `\n-Symbols $symbolsArray `\n-Imports $importsArray `\n-CocoOutcome $cocoOutcome\n\nreturn [ordered]@{\nschema = \"code-evidence-summary.v1\"\nstatus = \"ok\"\nfatal = $false\nroot = $root\nagentIndex = $agentSlices.agentIndex\nscorecard = $scorecardPath\nscorecardMarkdown = $scorecardMarkdownPath\nfiles = $fileRowsArray.Count\nsymbols = $symbolsArray.Count\nchunks = $chunksArray.Count\nimports = $importsArray.Count\nadapters = @($cocoOutcome)\n}\n}\n\nfunction ConvertTo-NullableDouble {\n param([object]$Value)\n\n if ($null -eq $Value) { return $null }\n try {\n return [double]$Value\n }\n catch {\n return $null\n }\n}\n\nfunction Get-SentruxMetricPair {\n param(\n [string]$Output,\n [string]$Label\n )\n\n if ([string]::IsNullOrWhiteSpace($Output)) { return $null }\n $pattern = [regex]::Escape($Label) + \":\\s+([0-9.]+)\\s+[^\\r\\n0-9.]+\\s+([0-9.]+)\"\n $match = [regex]::Match($Output, $pattern)\n if (-not $match.Success) { return $null }\n\n return [ordered]@{\n before = ConvertTo-NullableDouble $match.Groups[1].Value\n after = ConvertTo-NullableDouble $match.Groups[2].Value\n }\n}\n\nfunction New-SentruxMetricDelta {\n param(\n [string]$Name,\n [object]$Before,\n [object]$After,\n [ValidateSet(\"higher_is_better\", \"lower_is_better\")]\n [string]$Polarity = \"lower_is_better\"\n )\n\n $beforeValue = ConvertTo-NullableDouble $Before\n $afterValue = ConvertTo-NullableDouble $After\n $delta = $null\n $direction = \"unknown\"\n $regressed = $false\n\n if ($null -ne $beforeValue -and $null -ne $afterValue) {\n $delta = $afterValue - $beforeValue\n if ([math]::Abs($delta) -lt 0.000001) {\n $direction = \"stable\"\n }\n elseif ($delta -gt 0) {\n $direction = \"up\"\n }\n else {\n $direction = \"down\"\n }\n\n if ($Polarity -eq \"higher_is_better\") {\n $regressed = $delta -lt 0\n }\n else {\n $regressed = $delta -gt 0\n }\n }\n\n return [ordered]@{\n name = $Name\n before = $beforeValue\n after = $afterValue\n delta = $delta\n direction = $direction\n polarity = $Polarity\n regressed = $regressed\n }\n}\n\nfunction Test-SentruxGateNoDegradation {\n param([string]$GateOutput)\n\n return (-not [string]::IsNullOrWhiteSpace($GateOutput) -and $GateOutput -match \"No degradation detected\")\n}\n\nfunction Resolve-SentruxMetricRegressions {\n param(\n [object[]]$Metrics,\n [bool]$NoDegradation\n )\n\n foreach ($metric in @($Metrics)) {\n if ($null -eq $metric) {\n continue\n }\n\n $rawRegressed = [bool]$metric.regressed\n $gateAccepted = $NoDegradation -and $rawRegressed\n $metric | Add-Member -NotePropertyName rawRegressed -NotePropertyValue $rawRegressed -Force\n $metric | Add-Member -NotePropertyName gateAccepted -NotePropertyValue $gateAccepted -Force\n if ($gateAccepted) {\n $metric.regressed = $false\n }\n $metric\n }\n}\n\nfunction New-SentruxInsight {\n param(\n [string]$RepoName,\n [string]$TargetPath,\n [string]$BaselinePath,\n [object[]]$Steps\n )\n\n $gateStep = @($Steps | Where-Object { $_.name -like \"sentrux gate*\" } | Select-Object -Last 1)\n $checkStep = @($Steps | Where-Object { $_.name -eq \"sentrux check\" } | Select-Object -First 1)\n $rulesPath = if ([string]::IsNullOrWhiteSpace($TargetPath)) { \"\" } else { Join-Path (Join-Path $TargetPath \".sentrux\") \"rules.toml\" }\n $baseline = Read-JsonFileSafe $BaselinePath\n $gateOutput = if ($gateStep.Count -gt 0) { [string]$gateStep[0].output } else { \"\" }\n $noDegradation = Test-SentruxGateNoDegradation $gateOutput\n\n $qualityPair = Get-SentruxMetricPair $gateOutput \"Quality\"\n $couplingPair = Get-SentruxMetricPair $gateOutput \"Coupling\"\n $cyclesPair = Get-SentruxMetricPair $gateOutput \"Cycles\"\n $godFilesPair = Get-SentruxMetricPair $gateOutput \"God files\"\n $distance = $null\n $distanceMatch = [regex]::Match($gateOutput, \"Distance from Main Sequence:\\s+([0-9.]+)\")\n if ($distanceMatch.Success) {\n $distance = ConvertTo-NullableDouble $distanceMatch.Groups[1].Value\n }\n\n $scan = [ordered]@{}\n $resolveMatch = [regex]::Match($gateOutput, \"\\[resolve\\]\\s+([0-9]+)\\s+resolved,\\s+([0-9]+)\\s+unresolved\")\n if ($resolveMatch.Success) {\n $scan[\"resolvedImports\"] = [int]$resolveMatch.Groups[1].Value\n $scan[\"unresolvedImports\"] = [int]$resolveMatch.Groups[2].Value\n }\n $graphMatch = [regex]::Match($gateOutput, \"\\[build_graphs\\]\\s+([0-9]+)\\s+files.*\\|\\s+([0-9]+)\\s+import,\\s+([0-9]+)\\s+call,\\s+([0-9]+)\\s+inherit edges\")\n if ($graphMatch.Success) {\n $scan[\"files\"] = [int]$graphMatch.Groups[1].Value\n $scan[\"importEdges\"] = [int]$graphMatch.Groups[2].Value\n $scan[\"callEdges\"] = [int]$graphMatch.Groups[3].Value\n $scan[\"inheritEdges\"] = [int]$graphMatch.Groups[4].Value\n }\n\n $metrics = @()\n if ($null -ne $qualityPair) {\n $metrics += [pscustomobject](New-SentruxMetricDelta \"quality\" $qualityPair[\"before\"] $qualityPair[\"after\"] \"higher_is_better\")\n }\n if ($null -ne $couplingPair) {\n $metrics += [pscustomobject](New-SentruxMetricDelta \"coupling\" $couplingPair[\"before\"] $couplingPair[\"after\"] \"lower_is_better\")\n }\n if ($null -ne $cyclesPair) {\n $metrics += [pscustomobject](New-SentruxMetricDelta \"cycles\" $cyclesPair[\"before\"] $cyclesPair[\"after\"] \"lower_is_better\")\n }\n if ($null -ne $godFilesPair) {\n $metrics += [pscustomobject](New-SentruxMetricDelta \"god_files\" $godFilesPair[\"before\"] $godFilesPair[\"after\"] \"lower_is_better\")\n }\n $metrics = @(Resolve-SentruxMetricRegressions -Metrics $metrics -NoDegradation $noDegradation)\n\n $regressions = @($metrics | Where-Object { $_.regressed })\n $nextActions = @()\n $codeNexusHints = @()\n\n if ([string]::IsNullOrWhiteSpace($TargetPath)) {\n $nextActions += \"Sentrux target was not resolved; inspect pipeline configuration.\"\n }\n elseif (-not (Test-Path -LiteralPath $BaselinePath -PathType Leaf)) {\n $nextActions += \"Create an intentional Sentrux baseline for this scope before using it as a gate.\"\n }\n elseif ($gateStep.Count -gt 0 -and $gateStep[0].status -eq \"failed\") {\n $nextActions += \"Inspect the Sentrux gate output before saving any new baseline.\"\n }\n elseif ($regressions.Count -gt 0) {\n $nextActions += \"Investigate regressed structural metrics before accepting this change.\"\n }\n else {\n $nextActions += \"No structural regression detected for this scope.\"\n }\n\n if (-not [string]::IsNullOrWhiteSpace($rulesPath) -and -not (Test-Path -LiteralPath $rulesPath -PathType Leaf)) {\n $nextActions += \"Add .sentrux/rules.toml when this scope needs explicit architecture boundary rules.\"\n }\n\n if (@($regressions | Where-Object { $_.name -in @(\"coupling\", \"cycles\") }).Count -gt 0) {\n $codeNexusHints += \"Use CodeNexus impact/context on symbols in newly coupled modules.\"\n $codeNexusHints += \"Suggested query: gitnexus query `\"cross module import dependency cycle`\" --repo $RepoName\"\n }\n elseif (@($regressions | Where-Object { $_.name -eq \"quality\" }).Count -gt 0) {\n $codeNexusHints += \"Use CodeNexus query to locate the flow behind the quality drop.\"\n $codeNexusHints += \"Suggested query: gitnexus query `\"complex hotspot structural regression`\" --repo $RepoName\"\n }\n else {\n $codeNexusHints += \"If a future gate regresses, start with CodeNexus context/impact on the changed files.\"\n }\n\n return [ordered]@{\n targetPath = $TargetPath\n baselinePath = $BaselinePath\n baselineExists = (-not [string]::IsNullOrWhiteSpace($BaselinePath) -and (Test-Path -LiteralPath $BaselinePath -PathType Leaf))\n rulesPath = $rulesPath\n rulesExists = (-not [string]::IsNullOrWhiteSpace($rulesPath) -and (Test-Path -LiteralPath $rulesPath -PathType Leaf))\n checkStatus = if ($checkStep.Count -gt 0) { $checkStep[0].status } else { \"not_run\" }\n gateStatus = if ($gateStep.Count -gt 0) { $gateStep[0].status } else { \"not_run\" }\n noDegradation = $noDegradation\n metrics = $metrics\n baseline = [ordered]@{\n qualitySignal = ConvertTo-NullableDouble (Get-JsonProperty $baseline \"quality_signal\")\n couplingScore = ConvertTo-NullableDouble (Get-JsonProperty $baseline \"coupling_score\")\n cycleCount = ConvertTo-NullableDouble (Get-JsonProperty $baseline \"cycle_count\")\n complexFnCount = ConvertTo-NullableDouble (Get-JsonProperty $baseline \"complex_fn_count\")\n crossModuleEdges = ConvertTo-NullableDouble (Get-JsonProperty $baseline \"cross_module_edges\")\n totalImportEdges = ConvertTo-NullableDouble (Get-JsonProperty $baseline \"total_import_edges\")\n }\n distanceFromMainSequence = $distance\n scan = $scan\n regressions = $regressions\n nextActions = $nextActions\n codeNexusHints = $codeNexusHints\n }\n}\n\nfunction Get-StepMatch {\n param(\n [object[]]$Steps,\n [string]$Pattern,\n [switch]$Last\n )\n\n $matches = @($Steps | Where-Object { [string]$_.name -like $Pattern })\n if ($matches.Count -eq 0) { return $null }\n if ($Last) { return $matches[-1] }\n return $matches[0]\n}\n\nfunction Get-StepScore {\n param([object]$Step)\n\n if ($null -eq $Step) { return 0 }\n switch ([string]$Step.status) {\n \"passed\" { return 100 }\n default { return 0 }\n }\n}\n\nfunction Get-FailureCount {\n param(\n [object]$FailureCounts,\n [string]$Name\n )\n\n if ($FailureCounts -is [System.Collections.IDictionary] -and $FailureCounts.Contains($Name)) {\n return [int]$FailureCounts[$Name]\n }\n if ($null -ne $FailureCounts -and $null -ne $FailureCounts.PSObject.Properties[$Name]) {\n return [int]$FailureCounts.$Name\n }\n\n return 0\n}\n\nfunction Get-FirstLine {\n param([string]$Text)\n\n if ([string]::IsNullOrWhiteSpace($Text)) { return \"\" }\n return (($Text -split \"\\r?\\n\") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1)\n}\n\nfunction New-QualityDimension {\n param(\n [string]$Name,\n [int]$Score,\n [string]$Status,\n [string]$Evidence\n )\n\n return [ordered]@{\n name = $Name\n score = [math]::Max(0, [math]::Min(100, $Score))\n status = $Status\n evidence = $Evidence\n }\n}\n\nfunction New-Modality {\n param(\n [string]$Name,\n [string]$Role,\n [object]$Step,\n [int]$Confidence,\n [string]$Artifact,\n [string]$Finding,\n [string]$Limit\n )\n\n $status = if ($Finding -eq \"not generated\" -and [string]::IsNullOrWhiteSpace($Artifact)) {\n \"missing\"\n }\n elseif ($null -ne $Step) {\n [string]$Step.status\n }\n elseif (-not [string]::IsNullOrWhiteSpace($Artifact)) {\n \"generated\"\n }\n else {\n \"not_run\"\n }\n return [ordered]@{\n name = $Name\n role = $Role\n status = $status\n confidence = [math]::Max(0, [math]::Min(100, $Confidence))\n artifact = $Artifact\n finding = $Finding\n limit = $Limit\n durationMs = if ($null -eq $Step -or $null -eq $Step.durationMs) { $null } else { [int]$Step.durationMs }\n }\n}\n\nfunction New-HospitalProtocol {\n param(\n [string]$Name,\n [string]$Status,\n [string]$Command,\n [string]$ExitCriteria\n )\n\n return [ordered]@{\n name = $Name\n status = $Status\n command = $Command\n exit_criteria = $ExitCriteria\n }\n}\n\nfunction New-StateTransition {\n param(\n [string]$From,\n [string]$To,\n [string]$Guard,\n [bool]$Pass\n )\n\n return [ordered]@{\n from = $From\n to = $To\n guard = $Guard\n pass = $Pass\n }\n}\n\nfunction New-HospitalStateMachine {\n param(\n [object]$FailureCounts,\n [bool]$RulesExists,\n [string]$GateStatus,\n [string]$CheckStatus,\n [int]$FailingWhatIfCount,\n [string]$Disposition,\n [string]$NextProtocol,\n [bool]$StructuralEvidenceComplete = $true,\n [string]$SurgeryTarget = \"\",\n [string]$CurrentTopHotspot = \"\"\n )\n\n # Keep this guard self-contained because the state-machine seam is also\n # extracted independently by the regression harness.\n $providerQuotaCount = 0\n $providerUnavailableCount = 0\n $configErrorCount = 0\n if ($FailureCounts -is [System.Collections.IDictionary] -and $FailureCounts.Contains(\"providerQuota\")) {\n $providerQuotaCount = [int]$FailureCounts[\"providerQuota\"]\n }\n elseif ($null -ne $FailureCounts -and $null -ne $FailureCounts.PSObject.Properties[\"providerQuota\"]) {\n $providerQuotaCount = [int]$FailureCounts.providerQuota\n }\n if ($FailureCounts -is [System.Collections.IDictionary] -and $FailureCounts.Contains(\"providerUnavailable\")) {\n $providerUnavailableCount = [int]$FailureCounts[\"providerUnavailable\"]\n }\n elseif ($null -ne $FailureCounts -and $null -ne $FailureCounts.PSObject.Properties[\"providerUnavailable\"]) {\n $providerUnavailableCount = [int]$FailureCounts.providerUnavailable\n }\n if ($FailureCounts -is [System.Collections.IDictionary] -and $FailureCounts.Contains(\"configError\")) {\n $configErrorCount = [int]$FailureCounts[\"configError\"]\n }\n elseif ($null -ne $FailureCounts -and $null -ne $FailureCounts.PSObject.Properties[\"configError\"]) {\n $configErrorCount = [int]$FailureCounts.configError\n }\n\n $toolsOk = ([int]$FailureCounts.localToolError -eq 0)\n $providerAvailable = ($providerQuotaCount -eq 0 -and $providerUnavailableCount -eq 0 -and $configErrorCount -eq 0)\n $graphOk = ([int]$FailureCounts.graphMissing -eq 0)\n $sentruxOk = ([int]$FailureCounts.sentruxFail -eq 0 -and $RulesExists -and $GateStatus -eq \"passed\" -and $CheckStatus -eq \"passed\")\n $surgeryDebtCleared = ($StructuralEvidenceComplete -and $FailingWhatIfCount -eq 0)\n\n # surgery_plan -> post_op: the surgery target has actually been treated\n # (it no longer shows up as the current top hotspot) and sentrux confirms\n # the governed scope is clean, so it is safe to move on to post-op review.\n $surgeryTargetResolved = (-not [string]::IsNullOrWhiteSpace($SurgeryTarget) -and\n -not [string]::IsNullOrWhiteSpace($CurrentTopHotspot) -and\n ($SurgeryTarget -ne $CurrentTopHotspot))\n $surgeryToPostOpOk = ($sentruxOk -and $StructuralEvidenceComplete -and $surgeryTargetResolved)\n $postOpOk = ($toolsOk -and $providerAvailable -and $graphOk -and $sentruxOk -and $surgeryDebtCleared -and $surgeryTargetResolved)\n\n $currentState = switch ($NextProtocol) {\n \"triage\" { \"triage\" }\n \"diagnose\" { \"diagnose\" }\n \"govern\" { \"govern\" }\n \"surgery_plan\" { \"surgery_plan\" }\n \"post_op\" { if ($Disposition -eq \"discharge_ready\") { \"discharge_ready\" } else { \"post_op\" } }\n default { \"triage\" }\n }\n\n return [ordered]@{\n schema = \"code-intel-hospital-state-machine.v1\"\n current_state = $currentState\n disposition = $Disposition\n next_protocol = $NextProtocol\n states = @(\"triage\", \"diagnose\", \"govern\", \"surgery_plan\", \"post_op\", \"discharge_ready\")\n transitions = @(\n (New-StateTransition \"triage\" \"diagnose\" \"local toolchain is available\" $toolsOk)\n (New-StateTransition \"diagnose\" \"govern\" \"architecture graph exists or graph absence is accepted\" $graphOk)\n (New-StateTransition \"govern\" \"surgery_plan\" \"rules and gate pass, but what-if still has planned debt\" ($sentruxOk -and -not $surgeryDebtCleared))\n (New-StateTransition \"govern\" \"post_op\" \"rules and gate pass, no planned surgery debt remains\" ($sentruxOk -and $surgeryDebtCleared))\n (New-StateTransition \"surgery_plan\" \"post_op\" \"sentrux gate/check pass and the surgery target no longer appears as the current top hotspot\" $surgeryToPostOpOk)\n (New-StateTransition \"post_op\" \"discharge_ready\" \"post-op verification passes with no regressions\" $postOpOk)\n )\n guards = [ordered]@{\n tools_ok = $toolsOk\n provider_available = $providerAvailable\n graph_ok = $graphOk\n rules_exists = $RulesExists\n sentrux_check = $CheckStatus\n sentrux_gate = $GateStatus\n sentrux_ok = $sentruxOk\n failing_what_if = $FailingWhatIfCount\n structural_evidence_complete = $StructuralEvidenceComplete\n surgery_debt_cleared = $surgeryDebtCleared\n surgery_target = $SurgeryTarget\n current_top_hotspot = $CurrentTopHotspot\n surgery_target_resolved = $surgeryTargetResolved\n surgery_to_post_op_ok = $surgeryToPostOpOk\n post_op_ok = $postOpOk\n }\n }\n}\n\nfunction Read-JsonPathIfExists {\n param([string]$Path)\n\n if ([string]::IsNullOrWhiteSpace($Path)) { return $null }\n if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }\n\n return Read-JsonFileSafe $Path\n}\n\nfunction Get-SourceAnchorText {\n param([object]$SourceAnchor)\n\n if ($null -eq $SourceAnchor) { return \"\" }\n if ($SourceAnchor -is [string]) { return [string]$SourceAnchor }\n if ($null -ne $SourceAnchor.label) { return [string]$SourceAnchor.label }\n if ($null -ne $SourceAnchor.path) { return [string]$SourceAnchor.path }\n\n return [string]$SourceAnchor\n}\n\nfunction New-CodeIntelSurgeryPlan {\n param(\n [object]$Hospital,\n [string]$RepoPath,\n [string]$SentruxTargetPath,\n [string]$HotspotsPath,\n [string]$WhatIfPath,\n [string]$CodeNexusPath\n )\n\n $hotspots = Read-JsonPathIfExists $HotspotsPath\n $whatIf = Read-JsonPathIfExists $WhatIfPath\n $codeNexus = Read-JsonPathIfExists $CodeNexusPath\n\n $primaryFunction = $null\n if ($null -ne $hotspots -and $null -ne $hotspots.functions -and @($hotspots.functions).Count -gt 0) {\n $primaryFunction = $hotspots.functions[0]\n }\n $primaryFile = $null\n if ($null -ne $hotspots -and $null -ne $hotspots.files -and @($hotspots.files).Count -gt 0) {\n $primaryFile = $hotspots.files[0]\n }\n $primaryScenario = $null\n $failingScenarios = @()\n if ($null -ne $whatIf -and $null -ne $whatIf.scenarios) {\n $failingScenarios = @($whatIf.scenarios | Where-Object { -not $_.pass })\n if ($failingScenarios.Count -gt 0) { $primaryScenario = $failingScenarios[0] }\n }\n $contextFile = $null\n if ($null -ne $codeNexus -and $null -ne $codeNexus.files -and @($codeNexus.files).Count -gt 0) {\n $contextFile = $codeNexus.files[0]\n }\n\n $targetFile = if ($null -ne $primaryFunction) { [string]$primaryFunction.file } elseif ($null -ne $primaryFile) { [string]$primaryFile.path } else { \"\" }\n $targetName = if ($null -ne $primaryFunction) { [string]$primaryFunction.name } elseif ($null -ne $primaryFile) { [string]$primaryFile.path } else { \"\" }\n $targetAnchor = if ($null -ne $primaryFunction) { Get-SourceAnchorText $primaryFunction.sourceAnchor } elseif ($null -ne $primaryFile) { Get-SourceAnchorText $primaryFile.sourceAnchor } else { \"\" }\n $targetComplexity = if ($null -ne $primaryFunction) { [int]$primaryFunction.complexity } elseif ($null -ne $primaryFile) { [int]$primaryFile.maxComplexity } else { $null }\n $scenarioName = if ($null -ne $primaryScenario) { [string]$primaryScenario.name } else { \"\" }\n $scenarioAction = if ($null -ne $primaryScenario) { [string]$primaryScenario.action } else { \"\" }\n $status = if ([string]$Hospital.triage.next_protocol -eq \"surgery_plan\" -or\n ([string]$Hospital.triage.disposition -eq \"admit\" -and -not [string]::IsNullOrWhiteSpace($targetFile))) {\n \"planned\"\n }\n else {\n \"not_required\"\n }\n\n return [ordered]@{\n schema = \"code-intel-surgery-plan.v1\"\n status = $status\n repo = $RepoPath\n scope = $SentruxTargetPath\n admission = [ordered]@{\n disposition = $Hospital.triage.disposition\n diagnosis = $Hospital.triage.primary_diagnosis\n reason = $Hospital.triage.admission_reason\n }\n primary_target = [ordered]@{\n file = $targetFile\n name = $targetName\n source_anchor = $targetAnchor\n complexity = $targetComplexity\n scenario = $scenarioName\n scenario_action = $scenarioAction\n codenexus_file = if ($null -ne $contextFile) { [string]$contextFile.path } else { \"\" }\n }\n operating_plan = @(\n \"Open the primary target and its CodeNexus context before editing.\",\n \"Reduce the selected hotspot by extraction, boundary clarification, or testable decomposition.\",\n \"Do not raise Sentrux thresholds to make the surgery pass.\",\n \"Add or update the smallest test that proves the behavior stayed intact.\"\n )\n verification = @(\n \"Invoke-SentruxAgentTool.ps1 check_rules `\"$SentruxTargetPath`\"\",\n \"Invoke-SentruxAgentTool.ps1 session_end `\"$SentruxTargetPath`\"\",\n \"scripts/tests/test-code-intel-pipeline.ps1 -RepoPath `\"$RepoPath`\" -SentruxPath `\"$((Get-RelativePathSafe $RepoPath $SentruxTargetPath) -replace '\\\\', '/')`\" -SkipRepowise -Mode normal\"\n )\n discharge_criteria = $Hospital.triage.discharge_criteria\n evidence = [ordered]@{\n hotspots = $HotspotsPath\n what_if = $WhatIfPath\n codenexus = $CodeNexusPath\n failing_scenarios = @($failingScenarios | Select-Object -First 5)\n }\n }\n}\n\nfunction Convert-SurgeryPlanToMarkdown {\n param([object]$Plan)\n\n $lines = @(\n \"# Code Intel Surgery Plan\",\n \"\",\n \"- Status: $($Plan.status)\",\n \"- Repo: $($Plan.repo)\",\n \"- Scope: $($Plan.scope)\",\n \"- Diagnosis: $($Plan.admission.diagnosis)\",\n \"- Admission reason: $($Plan.admission.reason)\",\n \"\",\n \"## Primary Target\",\n \"- File: $($Plan.primary_target.file)\",\n \"- Symbol: $($Plan.primary_target.name)\",\n \"- Anchor: $($Plan.primary_target.source_anchor)\",\n \"- Complexity: $($Plan.primary_target.complexity)\",\n \"- Scenario: $($Plan.primary_target.scenario)\",\n \"- Action: $($Plan.primary_target.scenario_action)\",\n \"- CodeNexus file: $($Plan.primary_target.codenexus_file)\",\n \"\",\n \"## Operating Plan\"\n )\n foreach ($item in @($Plan.operating_plan)) {\n $lines += \"- $item\"\n }\n $lines += \"\"\n $lines += \"## Verification\"\n foreach ($item in @($Plan.verification)) {\n $lines += \"- ``$item``\"\n }\n $lines += \"\"\n $lines += \"## Discharge Criteria\"\n foreach ($item in @($Plan.discharge_criteria)) {\n $lines += \"- $item\"\n }\n return $lines\n}\n\nfunction Get-HospitalDiagnosis {\n param(\n [object]$FailureCounts,\n [bool]$RulesExists,\n [int]$FailingWhatIfCount\n )\n\n $providerQuotaCount = Get-FailureCount $FailureCounts \"providerQuota\"\n $providerUnavailableCount = Get-FailureCount $FailureCounts \"providerUnavailable\"\n $configErrorCount = Get-FailureCount $FailureCounts \"configError\"\n\n if ($FailureCounts.localToolError -gt 0) {\n return [ordered]@{ severity = \"red\"; primaryDiagnosis = \"local tool failure\" }\n }\n if ($providerQuotaCount -gt 0) {\n return [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"provider quota exhausted\" }\n }\n if ($providerUnavailableCount -gt 0) {\n return [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"provider unavailable\" }\n }\n if ($configErrorCount -gt 0) {\n return [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"provider configuration error\" }\n }\n if ($FailureCounts.sentruxFail -gt 0) {\n return [ordered]@{ severity = \"red\"; primaryDiagnosis = \"architecture gate failure\" }\n }\n if ($FailureCounts.graphMissing -gt 0) {\n return [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"architecture graph missing\" }\n }\n if (-not $RulesExists) {\n return [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"ungoverned structural scope\" }\n }\n if ($FailingWhatIfCount -gt 0) {\n return [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"known modernization debt\" }\n }\n\n return [ordered]@{ severity = \"green\"; primaryDiagnosis = \"clean snapshot\" }\n}\n\nfunction Get-HospitalNextProtocol {\n param(\n [object]$FailureCounts,\n [bool]$RulesExists,\n [int]$FailingWhatIfCount,\n [object]$GitHubResearch\n )\n\n $providerQuotaCount = Get-FailureCount $FailureCounts \"providerQuota\"\n $providerUnavailableCount = Get-FailureCount $FailureCounts \"providerUnavailable\"\n $configErrorCount = Get-FailureCount $FailureCounts \"configError\"\n\n if ($FailureCounts.localToolError -gt 0) { return \"triage\" }\n if ($providerQuotaCount -gt 0) { return \"triage\" }\n if ($providerUnavailableCount -gt 0) { return \"triage\" }\n if ($configErrorCount -gt 0) { return \"triage\" }\n if ($null -ne $GitHubResearch -and [bool]$GitHubResearch.required) { return \"github_solution_research\" }\n if ($FailureCounts.graphMissing -gt 0) { return \"diagnose\" }\n if (-not $RulesExists) { return \"govern\" }\n if ($FailingWhatIfCount -gt 0) { return \"surgery_plan\" }\n\n return \"post_op\"\n}\n\nfunction Get-HospitalAdmissionReason {\n param([string]$PrimaryDiagnosis)\n\n switch ($PrimaryDiagnosis) {\n \"clean snapshot\" { return \"No active inpatient issue; ready for discharge after post-op verification.\" }\n \"architecture graph missing\" { return \"Admit for diagnostic imaging: Understand graph is missing or stale.\" }\n \"ungoverned structural scope\" { return \"Admit for governance: rules are missing for the selected scope.\" }\n \"known modernization debt\" { return \"Admit for planned surgery: what-if scenarios show debt that should be scheduled, not ignored.\" }\n \"architecture gate failure\" { return \"Admit for structural treatment: Sentrux gate or rules failed.\" }\n \"provider quota exhausted\" { return \"Admit for triage: provider quota prevented complete evidence collection.\" }\n \"provider unavailable\" { return \"Admit for triage: the configured upstream provider route or model was unavailable.\" }\n \"provider configuration error\" { return \"Admit for triage: provider credentials, endpoint, or model configuration must be corrected.\" }\n \"structural evidence incomplete\" { return \"Admit for diagnosis: required structural summaries are incomplete.\" }\n \"local tool failure\" { return \"Admit for triage: local toolchain failed before diagnosis can be trusted.\" }\n default { return \"Admit until the next protocol clears the diagnosis.\" }\n }\n}\n\nfunction Get-HospitalTreatmentPlan {\n param(\n [object]$FailureCounts,\n [bool]$RulesExists,\n [int]$FailingWhatIfCount,\n [string]$UnderstandCommand,\n [string]$TopContextFile\n )\n\n $providerQuotaCount = Get-FailureCount $FailureCounts \"providerQuota\"\n $providerUnavailableCount = Get-FailureCount $FailureCounts \"providerUnavailable\"\n $configErrorCount = Get-FailureCount $FailureCounts \"configError\"\n\n $treatment = @()\n if ($FailureCounts.localToolError -gt 0) { $treatment += \"Fix local tool errors before interpreting architecture signals.\" }\n if ($providerQuotaCount -gt 0) { $treatment += \"Restore provider quota or use a complete local evidence path before interpreting the result.\" }\n if ($providerUnavailableCount -gt 0) { $treatment += \"Verify the provider model catalog and route availability; keep local index-only evidence available.\" }\n if ($configErrorCount -gt 0) { $treatment += \"Correct provider endpoint, model, or credential configuration before retrying provider-backed docs.\" }\n if ($FailureCounts.graphMissing -gt 0) { $treatment += \"Refresh Understand graph with: $UnderstandCommand\" }\n if (-not $RulesExists) { $treatment += \"Add .sentrux/rules.toml for the chosen scope.\" }\n if ($FailingWhatIfCount -gt 0) { $treatment += \"Use what-if failures as the tightening roadmap; start with the first failing scenario.\" }\n if (-not [string]::IsNullOrWhiteSpace($TopContextFile)) { $treatment += \"Start CodeNexus review at $TopContextFile.\" }\n if ($treatment.Count -eq 0) { $treatment += \"Keep this artifact as the current clean snapshot and compare the next session against it.\" }\n\n return $treatment\n}\n\nfunction New-HospitalDecisionBlock {\n param(\n [object]$FailureCounts,\n [bool]$RulesExists,\n [string]$GateStatus,\n [string]$CheckStatus,\n [int]$FailingWhatIfCount,\n [string]$UnderstandCommand,\n [string]$TopContextFile,\n [bool]$StructuralEvidenceComplete = $false,\n [string]$SurgeryTarget = \"\",\n [string]$CurrentTopHotspot = \"\",\n [object]$GitHubResearch\n )\n\n $diagnosis = Get-HospitalDiagnosis $FailureCounts $RulesExists $FailingWhatIfCount\n $nextProtocol = Get-HospitalNextProtocol $FailureCounts $RulesExists $FailingWhatIfCount $GitHubResearch\n $sentruxVerified = ($RulesExists -and $GateStatus -eq \"passed\" -and $CheckStatus -eq \"passed\")\n if ($diagnosis.severity -eq \"green\" -and -not $sentruxVerified) {\n $hasExplicitFailure = ($GateStatus -eq \"failed\" -or $CheckStatus -eq \"failed\")\n $diagnosis = [ordered]@{\n severity = if ($hasExplicitFailure) { \"red\" } else { \"amber\" }\n primaryDiagnosis = if ($hasExplicitFailure) { \"architecture gate failure\" } else { \"architecture verification incomplete\" }\n }\n $nextProtocol = \"govern\"\n }\n elseif ($diagnosis.severity -eq \"green\" -and -not $StructuralEvidenceComplete) {\n $diagnosis = [ordered]@{ severity = \"amber\"; primaryDiagnosis = \"structural evidence incomplete\" }\n $nextProtocol = \"diagnose\"\n }\n $postOpResolved = (-not [string]::IsNullOrWhiteSpace($SurgeryTarget) -and\n -not [string]::IsNullOrWhiteSpace($CurrentTopHotspot) -and\n $SurgeryTarget -ne $CurrentTopHotspot)\n $disposition = if ($diagnosis.severity -ne \"green\") {\n \"admit\"\n }\n elseif ($sentruxVerified -and $StructuralEvidenceComplete -and $postOpResolved) {\n \"discharge_ready\"\n }\n else {\n \"observe\"\n }\n $admissionReason = Get-HospitalAdmissionReason $diagnosis.primaryDiagnosis\n $dischargeCriteria = @(\n \"failure category counters are zero\",\n \"Sentrux check and gate pass for the governed scope\",\n \"hospital triage status is green or explicitly accepted for observation\",\n \"session_end reports no quality regression after Agent edits\"\n )\n if ($null -ne $GitHubResearch -and [bool]$GitHubResearch.required) {\n $dischargeCriteria += \"GitHub evidence linked or GitHub evidence insufficiency recorded in github-solution-research artifacts\"\n }\n $treatment = Get-HospitalTreatmentPlan $FailureCounts $RulesExists $FailingWhatIfCount $UnderstandCommand $TopContextFile\n if (-not $sentruxVerified) {\n $treatment = @($treatment) + \"Obtain passing Sentrux check and gate evidence before discharge.\"\n }\n\n $stateMachine = New-HospitalStateMachine `\n -FailureCounts $FailureCounts `\n -RulesExists $RulesExists `\n -GateStatus $GateStatus `\n -CheckStatus $CheckStatus `\n -FailingWhatIfCount $FailingWhatIfCount `\n -Disposition $disposition `\n -NextProtocol $nextProtocol `\n -StructuralEvidenceComplete $StructuralEvidenceComplete `\n -SurgeryTarget $SurgeryTarget `\n -CurrentTopHotspot $CurrentTopHotspot\n\n return [ordered]@{\n severity = $diagnosis.severity\n primaryDiagnosis = $diagnosis.primaryDiagnosis\n nextProtocol = $nextProtocol\n disposition = $disposition\n admissionReason = $admissionReason\n dischargeCriteria = $dischargeCriteria\n treatment = $treatment\n stateMachine = $stateMachine\n }\n}\n\nfunction New-HospitalFindings {\n param(\n [int]$InventoryFiles,\n [object]$SentruxFileDetailsSummary,\n [string]$TopFunction,\n [string]$TopModule,\n [object]$ResolvedRatio,\n [int]$ResolvedImports,\n [int]$UnresolvedImports,\n [int]$ExcludedFiles\n )\n\n $findings = @()\n if ($InventoryFiles -gt 0) { $findings += \"X-ray inventory found $InventoryFiles files.\" }\n if ($null -ne $SentruxFileDetailsSummary) { $findings += \"CT structural scan found $($SentruxFileDetailsSummary.files) files and $($SentruxFileDetailsSummary.functions) functions.\" }\n if (-not [string]::IsNullOrWhiteSpace($TopFunction)) { $findings += \"Top surgical hotspot: $TopFunction.\" }\n if (-not [string]::IsNullOrWhiteSpace($TopModule)) { $findings += \"Top module hotspot: $TopModule.\" }\n if ($ResolvedRatio -ne $null) { $findings += \"Import resolution ratio is $ResolvedRatio% ($ResolvedImports resolved, $UnresolvedImports unresolved).\" }\n if ($ExcludedFiles -gt 0) { $findings += \"$ExcludedFiles files were quarantined from governed source metrics.\" }\n return $findings\n}\n\nfunction New-HospitalModalities {\n param(\n [object]$InventoryStep,\n [object]$UnderstandStep,\n [object]$RepowiseStep,\n [object]$SentruxCheckStep,\n [object]$SentruxGateStep,\n [int]$GraphScore,\n [int]$MemoryScore,\n [int]$MriScore,\n [string]$MriStatus,\n [int]$CtScore,\n [string]$CtStatus,\n [int]$PetScore,\n [string]$PetStatus,\n [int]$GovernanceScore,\n [string]$RunDir,\n [string]$RepoPath,\n [int]$InventoryFiles,\n [object]$SentruxDsmSummary,\n [object]$SentruxFileDetailsSummary,\n [object]$CodeNexusContextSummary,\n [object]$SentruxWhatIfSummary,\n [object]$RuntimeCiSummary,\n [string]$GovernanceArtifact,\n [string]$GovernanceFinding\n )\n\n $xrayFinding = if ($InventoryFiles -gt 0) { \"$InventoryFiles files inventoried\" } else { \"no inventory\" }\n $ctArtifact = if ($CtStatus -eq \"available\") { [string]$SentruxDsmSummary.path } else { \"\" }\n $ctFinding = if ($CtStatus -eq \"available\") { \"$($SentruxDsmSummary.modules) modules, $($SentruxFileDetailsSummary.functions) functions\" } else { \"not generated\" }\n $mriArtifact = if ($MriStatus -eq \"available\") { [string]$CodeNexusContextSummary.path } else { \"\" }\n $mriFinding = if ($MriStatus -eq \"available\") { \"$($CodeNexusContextSummary.files) files, $($CodeNexusContextSummary.references) references\" } else { \"not generated\" }\n $petArtifact = if ($null -ne $RuntimeCiSummary) { [string]$RuntimeCiSummary.path } elseif ($PetStatus -eq \"available\") { [string]$SentruxWhatIfSummary.path } else { \"\" }\n $petFinding = if ($null -ne $RuntimeCiSummary) { \"runtime/CI health=$($RuntimeCiSummary.health); freshness=$($RuntimeCiSummary.freshness); completeness=$($RuntimeCiSummary.completeness)\" } elseif ($PetStatus -eq \"available\") { \"$($SentruxWhatIfSummary.failing) failing what-if scenarios\" } else { \"not generated\" }\n $petLimitation = if ($null -ne $RuntimeCiSummary) { \"Provider-neutral runtime/CI evidence is cited; provider logs remain outside this report.\" } else { \"No live runtime trace is captured yet.\" }\n $chartFinding = if ($null -ne $RepowiseStep) { [string]$RepowiseStep.status } else { \"not run\" }\n\n return @(\n (New-Modality \"xray\" \"fast file inventory and repo surface\" $InventoryStep (Get-StepScore $InventoryStep) (Join-Path $RunDir \"files.txt\") $xrayFinding \"Sees files, not semantic impact.\")\n (New-Modality \"anatomy\" \"Understand Anything architecture graph\" $UnderstandStep $GraphScore (Join-Path (Join-Path $RepoPath \".understand-anything\") \"knowledge-graph.json\") (Get-FirstLine ([string]$UnderstandStep.output)) \"Requires a prebuilt graph from the Understand tool.\")\n (New-Modality \"ct\" \"Sentrux DSM, hotspots, and structural slices\" $SentruxGateStep $CtScore $ctArtifact $ctFinding \"Static structure is not runtime truth.\")\n (New-Modality \"mri\" \"CodeNexus context and impact localization\" $null $MriScore $mriArtifact $mriFinding \"Lite mode is local evidence, not a full semantic backend.\")\n (New-Modality \"pet\" \"runtime/CI evidence with test gaps, evolution, and what-if fallback\" $null $PetScore $petArtifact $petFinding $petLimitation)\n (New-Modality \"chart\" \"Repowise long-term project memory\" $RepowiseStep $MemoryScore \"\" $chartFinding \"Provider quota and index freshness can limit semantic memory.\")\n (New-Modality \"governance\" \"rules, gate, and session safety rails\" $SentruxCheckStep $GovernanceScore $GovernanceArtifact $GovernanceFinding \"Rules only protect boundaries that have been encoded.\")\n )\n}\n\nfunction New-HospitalQualityDimensions {\n param(\n [int]$SourceCoverageScore,\n [string]$SourceScopeStatus,\n [int]$InventoryFiles,\n [int]$ScanFiles,\n [int]$GraphScore,\n [object]$UnderstandStep,\n [int]$ResolutionScore,\n [string]$ImportResolutionStatus,\n [int]$ResolvedImports,\n [int]$UnresolvedImports,\n [int]$PollutionScore,\n [string]$PollutionStatus,\n [int]$ExcludedFiles,\n [int]$GovernanceScore,\n [string]$GovernanceStatus,\n [string]$GovernanceEvidence,\n [int]$MriScore,\n [string]$LocalizationStatus,\n [string]$TopContextFile,\n [int]$MemoryScore,\n [string]$MemoryStatus,\n [string]$MemoryEvidence\n )\n\n return @(\n (New-QualityDimension \"source_coverage\" $SourceCoverageScore $SourceScopeStatus \"inventory=$InventoryFiles; sentrux_scan=$ScanFiles\")\n (New-QualityDimension \"graph_freshness\" $GraphScore ([string]$UnderstandStep.status) (Get-FirstLine ([string]$UnderstandStep.output)))\n (New-QualityDimension \"import_resolution\" $ResolutionScore $ImportResolutionStatus \"resolved=$ResolvedImports; unresolved=$UnresolvedImports\")\n (New-QualityDimension \"pollution_control\" $PollutionScore $PollutionStatus \"excluded=$ExcludedFiles\")\n (New-QualityDimension \"governance\" $GovernanceScore $GovernanceStatus $GovernanceEvidence)\n (New-QualityDimension \"localization\" $MriScore $LocalizationStatus \"top_file=$TopContextFile\")\n (New-QualityDimension \"memory\" $MemoryScore $MemoryStatus $MemoryEvidence)\n )\n}\n\nfunction Read-HospitalArtifactFile {\n param([object]$Summary)\n\n if ($null -eq $Summary) { return $null }\n\n $path = [string]$Summary.path\n if ([string]::IsNullOrWhiteSpace($path)) { return $null }\n if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { return $null }\n\n return Read-JsonFileSafe $path\n}\n\nfunction Read-HospitalArtifacts {\n param(\n [object]$SentruxDsmSummary,\n [object]$SentruxFileDetailsSummary,\n [object]$SentruxHotspotsSummary,\n [object]$SentruxEvolutionSummary,\n [object]$SentruxWhatIfSummary,\n [object]$CodeNexusContextSummary\n )\n\n return [ordered]@{\n dsm = Read-HospitalArtifactFile $SentruxDsmSummary\n file_details = Read-HospitalArtifactFile $SentruxFileDetailsSummary\n hotspots = Read-HospitalArtifactFile $SentruxHotspotsSummary\n evolution = Read-HospitalArtifactFile $SentruxEvolutionSummary\n what_if = Read-HospitalArtifactFile $SentruxWhatIfSummary\n codenexus = Read-HospitalArtifactFile $CodeNexusContextSummary\n }\n}\n\nfunction New-HospitalMeasurements {\n param(\n [object]$InventoryStep,\n [object]$SentruxInsight,\n [object]$DsmObject\n )\n\n $inventoryFiles = 0\n $inventoryMatch = [regex]::Match([string]$InventoryStep.output, \"files=([0-9]+)\")\n if ($inventoryMatch.Success) { $inventoryFiles = [int]$inventoryMatch.Groups[1].Value }\n\n $scan = if ($null -ne $SentruxInsight -and $null -ne $SentruxInsight[\"scan\"]) { $SentruxInsight[\"scan\"] } else { @{} }\n $scanFiles = if ($scan.Contains(\"files\")) { [int]$scan[\"files\"] } else { 0 }\n $unresolvedImports = if ($scan.Contains(\"unresolvedImports\")) { [int]$scan[\"unresolvedImports\"] } else { 0 }\n $resolvedImports = if ($scan.Contains(\"resolvedImports\")) { [int]$scan[\"resolvedImports\"] } else { 0 }\n $totalImports = $resolvedImports + $unresolvedImports\n $resolvedRatio = if ($totalImports -gt 0) { [math]::Round(($resolvedImports * 100.0) / $totalImports, 1) } else { $null }\n $dsmScope = $null\n if ($DsmObject -is [System.Collections.IDictionary] -and $DsmObject.Contains(\"scope\")) {\n $dsmScope = $DsmObject[\"scope\"]\n }\n elseif ($null -ne $DsmObject -and $null -ne $DsmObject.PSObject.Properties[\"scope\"]) {\n $dsmScope = $DsmObject.scope\n }\n\n $excludedFilesValue = $null\n $hasPollutionEvidence = $false\n if ($dsmScope -is [System.Collections.IDictionary] -and $dsmScope.Contains(\"excluded_files\")) {\n $excludedFilesValue = $dsmScope[\"excluded_files\"]\n $hasPollutionEvidence = ($null -ne $excludedFilesValue)\n }\n elseif ($null -ne $dsmScope -and $null -ne $dsmScope.PSObject.Properties[\"excluded_files\"]) {\n $excludedFilesValue = $dsmScope.excluded_files\n $hasPollutionEvidence = ($null -ne $excludedFilesValue)\n }\n\n $excludedFiles = if ($hasPollutionEvidence) { [int]$excludedFilesValue } else { 0 }\n $sourceScopeStatus = if ($inventoryFiles -gt 0 -and $scanFiles -gt 0) { \"measured\" } else { \"unknown\" }\n $pollutionStatus = if (-not $hasPollutionEvidence) { \"unknown\" } elseif ($excludedFiles -gt 0) { \"quarantined\" } else { \"clean\" }\n\n return [ordered]@{\n inventory_files = $inventoryFiles\n scan_files = $scanFiles\n unresolved_imports = $unresolvedImports\n resolved_imports = $resolvedImports\n resolved_ratio = $resolvedRatio\n excluded_files = $excludedFiles\n source_scope_status = $sourceScopeStatus\n pollution_status = $pollutionStatus\n }\n}\n\nfunction Get-ImportResolutionScore {\n param([object]$ResolvedRatio)\n\n if ($null -eq $ResolvedRatio) { return 0 }\n if ($ResolvedRatio -ge 75) { return 100 }\n if ($ResolvedRatio -ge 50) { return 75 }\n if ($ResolvedRatio -ge 25) { return 50 }\n\n return 30\n}\n\nfunction Get-SourceCoverageScore {\n param(\n [int]$ScanFiles,\n [int]$InventoryFiles\n )\n\n if ($ScanFiles -le 0 -or $InventoryFiles -le 0) { return 0 }\n\n return [int][math]::Round([math]::Min(100.0, ($ScanFiles * 100.0) / $InventoryFiles))\n}\n\nfunction New-HospitalScoreBlock {\n param(\n [object]$SentruxInsight,\n [object]$Measurements,\n [object]$UnderstandStep,\n [object]$RepowiseStep,\n [object]$SentruxCheckStep,\n [object]$SentruxGateStep,\n [object]$SentruxDsmObject,\n [object]$SentruxFileDetailsObject,\n [object]$SentruxEvolutionObject,\n [object]$SentruxWhatIfObject,\n [object]$CodeNexusContextObject,\n [object]$RuntimeCiSummary\n )\n\n $rulesExists = [bool]$SentruxInsight[\"rulesExists\"]\n $rulesScore = if ($rulesExists) { 100 } else { 45 }\n $gateScore = Get-StepScore $SentruxGateStep\n $checkScore = Get-StepScore $SentruxCheckStep\n $graphScore = Get-StepScore $UnderstandStep\n $memoryScore = Get-StepScore $RepowiseStep\n $mriStatus = if ($null -ne $CodeNexusContextObject) { \"available\" } else { \"missing\" }\n $ctStatus = if ($null -ne $SentruxDsmObject -and $null -ne $SentruxFileDetailsObject) { \"available\" } else { \"missing\" }\n $petStatus = if ($null -ne $RuntimeCiSummary) { if ([string]$RuntimeCiSummary.health -eq \"unknown\") { \"unknown\" } else { \"available\" } } elseif ($null -ne $SentruxWhatIfObject -and $null -ne $SentruxEvolutionObject) { \"available\" } else { \"missing\" }\n $mriScore = if ($mriStatus -eq \"available\") { 100 } else { 0 }\n $ctScore = if ($ctStatus -eq \"available\") { 100 } else { 0 }\n $petScore = if ($null -ne $RuntimeCiSummary) { switch ([string]$RuntimeCiSummary.health) { \"green\" { 100 } \"red\" { 0 } default { 30 } } } elseif ($petStatus -eq \"available\") { 70 } else { 0 }\n $resolutionScore = Get-ImportResolutionScore $Measurements.resolved_ratio\n $pollutionStatus = [string]$Measurements.pollution_status\n $pollutionScore = if ($pollutionStatus -eq \"unknown\") { 0 } elseif ($Measurements.excluded_files -gt 0) { 100 } else { 80 }\n $governanceScore = [int][math]::Round(($rulesScore + $gateScore + $checkScore) / 3.0)\n $diagnosticScore = [int][math]::Round(($ctScore + $mriScore + $graphScore + $memoryScore) / 4.0)\n $overallScore = [int][math]::Round(($diagnosticScore + $governanceScore + $resolutionScore + $pollutionScore) / 4.0)\n $governanceArtifact = if ($rulesExists) { [string]$SentruxInsight[\"rulesPath\"] } else { \"\" }\n $resolvedRatio = $Measurements.resolved_ratio\n\n return [ordered]@{\n rules_exists = $rulesExists\n gate_status = [string]$SentruxInsight[\"gateStatus\"]\n check_status = [string]$SentruxInsight[\"checkStatus\"]\n graph_score = $graphScore\n memory_score = $memoryScore\n mri_score = $mriScore\n mri_status = $mriStatus\n ct_score = $ctScore\n ct_status = $ctStatus\n pet_score = $petScore\n pet_status = $petStatus\n resolution_score = $resolutionScore\n pollution_score = $pollutionScore\n governance_score = $governanceScore\n diagnostic_score = $diagnosticScore\n overall_score = $overallScore\n source_coverage_score = Get-SourceCoverageScore $Measurements.scan_files $Measurements.inventory_files\n import_resolution_status = if ($null -eq $resolvedRatio) { \"unknown\" } else { \"$resolvedRatio%\" }\n pollution_status = $pollutionStatus\n governance_status = if ($rulesExists) { \"rules_present\" } else { \"rules_missing\" }\n governance_artifact = $governanceArtifact\n governance_finding = \"rules=$($SentruxInsight['rulesExists']); gate=$($SentruxInsight['gateStatus']); check=$($SentruxInsight['checkStatus'])\"\n governance_evidence = \"gate=$($SentruxInsight['gateStatus']); check=$($SentruxInsight['checkStatus'])\"\n localization_status = $mriStatus\n memory_status = if ($null -ne $RepowiseStep) { [string]$RepowiseStep.status } else { \"not_run\" }\n memory_evidence = if ($null -ne $RepowiseStep) { Get-FirstLine ([string]$RepowiseStep.output) } else { \"\" }\n }\n}\n\nfunction New-HospitalEvidenceBlock {\n param(\n [object]$HotspotsObject,\n [object]$WhatIfObject,\n [object]$CodeNexusContextSummary\n )\n\n $failingWhatIf = @()\n if ($null -ne $WhatIfObject -and $null -ne $WhatIfObject.scenarios) {\n $failingWhatIf = @($WhatIfObject.scenarios | Where-Object { -not $_.pass })\n }\n\n $topFunction = \"\"\n if ($null -ne $HotspotsObject -and $null -ne $HotspotsObject.functions -and @($HotspotsObject.functions).Count -gt 0) {\n $topFunction = \"{0} in {1} (cc={2})\" -f $HotspotsObject.functions[0].name, $HotspotsObject.functions[0].file, $HotspotsObject.functions[0].complexity\n }\n\n $topModule = \"\"\n if ($null -ne $HotspotsObject -and $null -ne $HotspotsObject.modules -and @($HotspotsObject.modules).Count -gt 0) {\n $topModule = \"{0} (risk={1})\" -f $HotspotsObject.modules[0].name, $HotspotsObject.modules[0].risk\n }\n\n return [ordered]@{\n failing_what_if = $failingWhatIf\n top_function = $topFunction\n top_module = $topModule\n top_context_file = if ($null -ne $CodeNexusContextSummary) { [string]$CodeNexusContextSummary.topFile } else { \"\" }\n }\n}\n\nfunction New-HospitalProtocolBlock {\n param(\n [bool]$RulesExists,\n [int]$FailingWhatIfCount\n )\n\n $governProtocolStatus = if ($RulesExists) { \"active\" } else { \"needs_rules\" }\n $surgeryProtocolStatus = if ($FailingWhatIfCount -gt 0) { \"available\" } else { \"low_risk\" }\n\n return @(\n (New-HospitalProtocol \"triage\" \"available\" \"run-code-intel.ps1 -RepoPath -Mode lite\" \"Classify provider/tool/graph/Sentrux failure bucket and choose next protocol.\")\n (New-HospitalProtocol \"diagnose\" \"available\" \"run-code-intel.ps1 -RepoPath -Mode normal\" \"Produce summary.md, hospital.md, sentrux artifacts, and codenexus context.\")\n (New-HospitalProtocol \"govern\" $governProtocolStatus \"sentrux check ; sentrux gate \" \"Rules pass and gate reports no degradation.\")\n (New-HospitalProtocol \"surgery_plan\" $surgeryProtocolStatus \"read sentrux-what-if.json and codenexus-context.json\" \"Choose one hotspot, one boundary, and one verification command before editing.\")\n (New-HospitalProtocol \"post_op\" \"available\" \"Invoke-SentruxAgentTool.ps1 session_end \" \"Signal does not drop, rules pass, and touched hotspot is lower risk.\")\n )\n}\n\nfunction Get-PreviousSurgeryTarget {\n param([string]$RunDir)\n\n if ([string]::IsNullOrWhiteSpace($RunDir)) { return \"\" }\n $repoArtifactRoot = Split-Path -Parent $RunDir\n if ([string]::IsNullOrWhiteSpace($repoArtifactRoot) -or -not (Test-Path -LiteralPath $repoArtifactRoot -PathType Container)) { return \"\" }\n\n $currentName = Split-Path -Leaf $RunDir\n $previousRun = Get-ChildItem -LiteralPath $repoArtifactRoot -Directory -ErrorAction SilentlyContinue |\n Where-Object { $_.Name -ne $currentName } |\n Sort-Object Name -Descending |\n Select-Object -First 1\n if ($null -eq $previousRun) { return \"\" }\n\n $previousPlanPath = Join-Path $previousRun.FullName \"surgery-plan.json\"\n if (-not (Test-Path -LiteralPath $previousPlanPath -PathType Leaf)) { return \"\" }\n\n $previousPlan = Read-JsonFileSafe $previousPlanPath\n if ($null -eq $previousPlan -or $null -eq $previousPlan.primary_target) { return \"\" }\n if ([string]::IsNullOrWhiteSpace([string]$previousPlan.primary_target.name)) { return \"\" }\n\n return \"$($previousPlan.primary_target.name) in $($previousPlan.primary_target.file)\"\n}\n\nfunction New-CodeIntelHospitalReport {\n param(\n [string]$RepoPath,\n [string]$Mode,\n [string]$RunDir,\n [string]$ReportPath,\n [string]$SummaryPath,\n [string]$UnderstandingPath,\n [object[]]$Steps,\n [object]$FailureCounts,\n [object]$SentruxInsight,\n [object]$SentruxDsmSummary,\n [object]$SentruxFileDetailsSummary,\n [object]$SentruxHotspotsSummary,\n[object]$SentruxEvolutionSummary,\n[object]$SentruxWhatIfSummary,\n[object]$CodeNexusContextSummary,\n[object]$RuntimeCiSummary,\n[string]$UnderstandCommand,\n[object]$ToolState,\n[object]$GitHubResearch\n)\n\n $gitStep = Get-StepMatch $Steps \"git status\"\n $inventoryStep = Get-StepMatch $Steps \"rg file inventory\"\n $understandStep = Get-StepMatch $Steps \"understand graph\"\n $repowiseStep = Get-StepMatch $Steps \"repowise*\" -Last\n $sentruxCheckStep = Get-StepMatch $Steps \"sentrux check\"\n $sentruxGateStep = Get-StepMatch $Steps \"sentrux gate*\" -Last\n\n $artifacts = Read-HospitalArtifacts $SentruxDsmSummary $SentruxFileDetailsSummary $SentruxHotspotsSummary $SentruxEvolutionSummary $SentruxWhatIfSummary $CodeNexusContextSummary\n $structuralEvidenceComplete = ($null -ne $artifacts.dsm -and\n $null -ne $artifacts.file_details -and\n $null -ne $artifacts.hotspots -and\n $null -ne $artifacts.evolution -and\n $null -ne $artifacts.what_if)\n $measurements = New-HospitalMeasurements $inventoryStep $SentruxInsight $artifacts.dsm\n $scores = New-HospitalScoreBlock `\n -SentruxInsight $SentruxInsight `\n -Measurements $measurements `\n -UnderstandStep $understandStep `\n -RepowiseStep $repowiseStep `\n -SentruxCheckStep $sentruxCheckStep `\n -SentruxGateStep $sentruxGateStep `\n -SentruxDsmObject $artifacts.dsm `\n -SentruxFileDetailsObject $artifacts.file_details `\n -SentruxEvolutionObject $artifacts.evolution `\n -SentruxWhatIfObject $artifacts.what_if `\n -CodeNexusContextObject $artifacts.codenexus `\n -RuntimeCiSummary $RuntimeCiSummary\n $evidence = New-HospitalEvidenceBlock $artifacts.hotspots $artifacts.what_if $CodeNexusContextSummary\n\n $currentTopHotspot = \"\"\n if ($null -ne $artifacts.hotspots -and $null -ne $artifacts.hotspots.functions -and @($artifacts.hotspots.functions).Count -gt 0) {\n $topFn = $artifacts.hotspots.functions[0]\n $currentTopHotspot = \"$($topFn.name) in $($topFn.file)\"\n }\n $surgeryTarget = Get-PreviousSurgeryTarget $RunDir\n\n $decision = New-HospitalDecisionBlock `\n -FailureCounts $FailureCounts `\n -RulesExists $scores.rules_exists `\n -GateStatus $scores.gate_status `\n -CheckStatus $scores.check_status `\n -FailingWhatIfCount @($evidence.failing_what_if).Count `\n -UnderstandCommand $UnderstandCommand `\n -TopContextFile $evidence.top_context_file `\n -StructuralEvidenceComplete $structuralEvidenceComplete `\n -SurgeryTarget $surgeryTarget `\n -CurrentTopHotspot $currentTopHotspot `\n -GitHubResearch $GitHubResearch\n\n $findings = New-HospitalFindings `\n -InventoryFiles $measurements.inventory_files `\n -SentruxFileDetailsSummary $SentruxFileDetailsSummary `\n -TopFunction $evidence.top_function `\n -TopModule $evidence.top_module `\n -ResolvedRatio $measurements.resolved_ratio `\n -ResolvedImports $measurements.resolved_imports `\n -UnresolvedImports $measurements.unresolved_imports `\n -ExcludedFiles $measurements.excluded_files\n\n $modalities = New-HospitalModalities `\n -InventoryStep $inventoryStep `\n -UnderstandStep $understandStep `\n -RepowiseStep $repowiseStep `\n -SentruxCheckStep $sentruxCheckStep `\n -SentruxGateStep $sentruxGateStep `\n -GraphScore $scores.graph_score `\n -MemoryScore $scores.memory_score `\n -MriScore $scores.mri_score `\n -MriStatus $scores.mri_status `\n -CtScore $scores.ct_score `\n -CtStatus $scores.ct_status `\n -PetScore $scores.pet_score `\n -PetStatus $scores.pet_status `\n -GovernanceScore $scores.governance_score `\n -RunDir $RunDir `\n -RepoPath $RepoPath `\n -InventoryFiles $measurements.inventory_files `\n -SentruxDsmSummary $SentruxDsmSummary `\n -SentruxFileDetailsSummary $SentruxFileDetailsSummary `\n -CodeNexusContextSummary $CodeNexusContextSummary `\n -SentruxWhatIfSummary $SentruxWhatIfSummary `\n -RuntimeCiSummary $RuntimeCiSummary `\n -GovernanceArtifact $scores.governance_artifact `\n -GovernanceFinding $scores.governance_finding\n\n $quality = New-HospitalQualityDimensions `\n -SourceCoverageScore $scores.source_coverage_score `\n -SourceScopeStatus $measurements.source_scope_status `\n -InventoryFiles $measurements.inventory_files `\n -ScanFiles $measurements.scan_files `\n -GraphScore $scores.graph_score `\n -UnderstandStep $understandStep `\n -ResolutionScore $scores.resolution_score `\n -ImportResolutionStatus $scores.import_resolution_status `\n -ResolvedImports $measurements.resolved_imports `\n -UnresolvedImports $measurements.unresolved_imports `\n -PollutionScore $scores.pollution_score `\n -PollutionStatus $scores.pollution_status `\n -ExcludedFiles $measurements.excluded_files `\n -GovernanceScore $scores.governance_score `\n -GovernanceStatus $scores.governance_status `\n -GovernanceEvidence $scores.governance_evidence `\n -MriScore $scores.mri_score `\n -LocalizationStatus $scores.localization_status `\n -TopContextFile $evidence.top_context_file `\n -MemoryScore $scores.memory_score `\n -MemoryStatus $scores.memory_status `\n -MemoryEvidence $scores.memory_evidence\n\n $protocols = New-HospitalProtocolBlock $scores.rules_exists @($evidence.failing_what_if).Count\n\n return [ordered]@{\n schema = \"code-intel-hospital.v1\"\n generatedAt = (Get-Date).ToString(\"o\")\n repo = $RepoPath\n mode = $Mode\n artifacts = [ordered]@{\n runDir = $RunDir\n report = $ReportPath\n summary = $SummaryPath\n understanding = $UnderstandingPath\n runtime_ci = if ($null -ne $RuntimeCiSummary) { [string]$RuntimeCiSummary.path } else { \"\" }\n github_solution_research = if ($null -ne $GitHubResearch) { [string]$GitHubResearch.path } else { \"\" }\n github_solution_research_markdown = if ($null -ne $GitHubResearch) { [string]$GitHubResearch.markdown } else { \"\" }\n }\n triage = [ordered]@{\n status = $decision.severity\n disposition = $decision.disposition\n primary_diagnosis = $decision.primaryDiagnosis\n overall_score = $scores.overall_score\n next_protocol = $decision.nextProtocol\n research_status = if ($null -ne $GitHubResearch) { [string]$GitHubResearch.status } else { \"not_applicable\" }\n research_required = if ($null -ne $GitHubResearch) { [bool]$GitHubResearch.required } else { $false }\n exit_criteria = if ($null -ne $GitHubResearch) { @($GitHubResearch.exitCriteria) } else { @() }\n admission_reason = $decision.admissionReason\n discharge_criteria = $decision.dischargeCriteria\n }\n state_machine = $decision.stateMachine\n modalities = $modalities\n policies = [ordered]@{\n admission = [ordered]@{\n admit_when = @(\n \"local toolchain fails\",\n \"architecture graph is missing\",\n \"Sentrux rules are missing\",\n \"Sentrux check or gate fails\",\n \"what-if reports planned modernization debt\"\n )\n current_reason = $decision.admissionReason\n }\n discharge = [ordered]@{\n criteria = $decision.dischargeCriteria\n current_state = $decision.stateMachine.current_state\n }\n }\n report_quality = [ordered]@{\n overall_score = $scores.overall_score\n diagnostic_score = $scores.diagnostic_score\n governance_score = $scores.governance_score\n dimensions = $quality\n }\n diagnosis = [ordered]@{\n findings = $findings\n impression = $decision.primaryDiagnosis\n risk = $decision.severity\n evidence = [ordered]@{\n top_function = $evidence.top_function\n top_module = $evidence.top_module\n top_context_file = $evidence.top_context_file\n failing_what_if = @($evidence.failing_what_if | Select-Object -First 5)\n }\n }\n treatment = [ordered]@{\n plan = $decision.treatment\n follow_up = @(\n \"Rerun normal mode after code changes.\",\n \"Compare hospital-report.json overall_score and Sentrux quality signal.\",\n \"Use session_start/session_end around Agent edits.\"\n )\n }\n protocols = $protocols\n tools = $ToolState\n }\n}\n\nfunction Convert-HospitalReportToMarkdown {\n param([object]$Hospital)\n\n $lines = @(\n \"# Code Intel Hospital Report\",\n \"\",\n \"- Repo: $($Hospital.repo)\",\n \"- Mode: $($Hospital.mode)\",\n \"- Status: $($Hospital.triage.status)\",\n \"- Disposition: $($Hospital.triage.disposition)\",\n \"- Primary diagnosis: $($Hospital.triage.primary_diagnosis)\",\n \"- Admission reason: $($Hospital.triage.admission_reason)\",\n\"- Overall score: $($Hospital.triage.overall_score)\",\n\"- Next protocol: $($Hospital.triage.next_protocol)\",\n\"- Research status: $($Hospital.triage.research_status)\",\n\"- Research required: $($Hospital.triage.research_required)\",\n\"- Current state: $($Hospital.state_machine.current_state)\",\n\"\",\n\"## Imaging Modalities\"\n)\nif ($null -ne $Hospital.triage.exit_criteria -and @($Hospital.triage.exit_criteria).Count -gt 0) {\n $lines += \"\"\n $lines += \"## Exit Criteria\"\n foreach ($criterion in @($Hospital.triage.exit_criteria)) {\n $lines += \"- $criterion\"\n }\n}\nforeach ($item in @($Hospital.modalities)) {\n $lines += \"- $($item.name): $($item.status), confidence=$($item.confidence), finding=$($item.finding)\"\n }\n $lines += \"\"\n $lines += \"## Report Quality\"\n foreach ($dimension in @($Hospital.report_quality.dimensions)) {\n $lines += \"- $($dimension.name): $($dimension.score) ($($dimension.status)) - $($dimension.evidence)\"\n }\n $lines += \"\"\n $lines += \"## Diagnosis\"\n foreach ($finding in @($Hospital.diagnosis.findings)) {\n $lines += \"- $finding\"\n }\n $lines += \"\"\n $lines += \"## Treatment\"\n foreach ($item in @($Hospital.treatment.plan)) {\n $lines += \"- $item\"\n }\n if ($null -ne $Hospital.surgery_plan) {\n $lines += \"\"\n $lines += \"## Surgery Plan\"\n $lines += \"- Status: $($Hospital.surgery_plan.status)\"\n $lines += \"- Report: $($Hospital.surgery_plan.path)\"\n $lines += \"- Markdown: $($Hospital.surgery_plan.markdown)\"\n $lines += \"- Primary target: $($Hospital.surgery_plan.primary_target)\"\n }\n $lines += \"\"\n $lines += \"## Discharge Criteria\"\n foreach ($item in @($Hospital.triage.discharge_criteria)) {\n $lines += \"- $item\"\n }\n $lines += \"\"\n $lines += \"## State Machine\"\n foreach ($transition in @($Hospital.state_machine.transitions)) {\n $lines += \"- $($transition.from) -> $($transition.to): pass=$($transition.pass), guard=$($transition.guard)\"\n }\n $lines += \"\"\n $lines += \"## Protocols\"\n foreach ($protocol in @($Hospital.protocols)) {\n $lines += \"- $($protocol.name): $($protocol.status) - $($protocol.exit_criteria)\"\n }\n return $lines\n}\n\nfunction Get-CodeIntelSentruxStep {\n param(\n [object[]]$Steps,\n [string]$NamePattern,\n [switch]$Last\n )\n\n $matches = @($Steps | Where-Object { [string]$_.name -like $NamePattern })\n if ($matches.Count -eq 0) { return $null }\n if ($Last) { return $matches[-1] }\n return $matches[0]\n}\n\nfunction Get-CodeIntelBoundedExcerpt {\n param(\n [string]$Text,\n [int]$MaxLength = 500\n )\n\n if ([string]::IsNullOrWhiteSpace($Text)) { return \"\" }\n $singleLine = (($Text -split \"`r?`n\") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 8) -join \" | \"\n if ($singleLine.Length -le $MaxLength) { return $singleLine }\n return $singleLine.Substring(0, $MaxLength)\n}\n\nfunction New-CodeIntelSentruxTarget {\n param(\n [ValidateSet(\"resolved\", \"unresolved\", \"aggregate\", \"not_applicable\")]\n [string]$Status,\n [string]$File = \"\",\n [string]$Symbol = \"\"\n )\n\n $target = [ordered]@{ status = $Status }\n if (-not [string]::IsNullOrWhiteSpace($File)) { $target[\"file\"] = $File }\n if (-not [string]::IsNullOrWhiteSpace($Symbol)) { $target[\"symbol\"] = $Symbol }\n return $target\n}\n\nfunction New-CodeIntelSentruxRecord {\n param(\n [string]$Id,\n [string]$Kind,\n [string]$Source,\n [string]$SourceStep,\n [string]$RawOutputPath,\n [string]$Stdout,\n [object]$Target,\n [string]$Metric = \"\",\n [Nullable[int]]$Value = $null,\n [Nullable[int]]$Threshold = $null,\n [Nullable[int]]$Before = $null,\n [Nullable[int]]$After = $null\n )\n\n $record = [ordered]@{\n id = $Id\n kind = $Kind\n source = $Source\n source_step = $SourceStep\n provenance = \"stdout\"\n raw_output_path = $RawOutputPath\n stdout_excerpt = Get-CodeIntelBoundedExcerpt $Stdout\n parsed_at = (Get-Date).ToString(\"o\")\n target = $Target\n }\n if (-not [string]::IsNullOrWhiteSpace($Metric)) { $record[\"metric\"] = $Metric }\n if ($null -ne $Value) { $record[\"value\"] = [int]$Value }\n if ($null -ne $Threshold) { $record[\"threshold\"] = [int]$Threshold }\n if ($null -ne $Before) { $record[\"before\"] = [int]$Before }\n if ($null -ne $After) { $record[\"after\"] = [int]$After }\n return $record\n}\n\nfunction Get-CodeIntelObjectValue {\n param(\n [object]$Object,\n [string]$Name\n )\n\n if ($null -eq $Object) { return $null }\n if ($Object -is [System.Collections.IDictionary] -and $Object.Contains($Name)) {\n return $Object[$Name]\n }\n return Get-JsonProperty $Object $Name\n}\n\nfunction New-CodeIntelSentruxConflict {\n param(\n [object]$Authoritative,\n [object]$Conflicting,\n [string]$ConflictingSource,\n [string]$RawPointer\n )\n\n if ($null -eq $Authoritative -or $null -eq $Conflicting) { return $null }\n $authoritativeValue = ConvertTo-NullableDouble (Get-CodeIntelObjectValue $Authoritative \"value\")\n $conflictingValue = ConvertTo-NullableDouble (Get-CodeIntelObjectValue $Conflicting \"complexity\")\n if ($null -eq $authoritativeValue -or $null -eq $conflictingValue) { return $null }\n if ([int]$authoritativeValue -eq [int]$conflictingValue) { return $null }\n\n $conflictingId = \"{0}:max_cc:{1}:{2}\" -f $ConflictingSource, [string](Get-CodeIntelObjectValue $Conflicting \"file\"), [string](Get-CodeIntelObjectValue $Conflicting \"name\")\n return [ordered]@{\n kind = \"metric_conflict\"\n authoritative_record_id = [string](Get-CodeIntelObjectValue $Authoritative \"id\")\n conflicting_record_id = $conflictingId\n metric = \"cyclomatic_complexity\"\n authoritative_value = [int]$authoritativeValue\n conflicting_value = [int]$conflictingValue\n authoritative_source = [string](Get-CodeIntelObjectValue $Authoritative \"source\")\n conflicting_source = $ConflictingSource\n raw_output_path = $RawPointer\n stdout_excerpt = Get-CodeIntelBoundedExcerpt (\"{0} {1} (cc={2})\" -f [string](Get-CodeIntelObjectValue $Conflicting \"name\"), [string](Get-CodeIntelObjectValue $Conflicting \"file\"), [string](Get-CodeIntelObjectValue $Conflicting \"complexity\"))\n parsed_at = (Get-Date).ToString(\"o\")\n resolution = \"authoritative_stdout_wins\"\n }\n}\n\nfunction New-CodeIntelSentruxFailures {\n param(\n [object[]]$Steps,\n [string]$OutputPath = \"\",\n [string]$HotspotsPath = \"\",\n [string]$FileDetailsPath = \"\"\n )\n\n $checkStep = Get-CodeIntelSentruxStep -Steps $Steps -NamePattern \"sentrux check\"\n $gateStep = Get-CodeIntelSentruxStep -Steps $Steps -NamePattern \"sentrux gate*\" -Last\n $records = [System.Collections.Generic.List[object]]::new()\n $parserNotes = [System.Collections.Generic.List[string]]::new()\n $parserErrors = [System.Collections.Generic.List[string]]::new()\n\n if ($null -ne $checkStep) {\n $checkStatus = [string]$checkStep.status\n $checkText = (([string]$checkStep.output) + \"`n\" + ([string]$checkStep.error)).Trim()\n if ($checkStatus -eq \"failed\" -or $checkStatus -eq \"manual_required\") {\n $namedMatches = @([regex]::Matches($checkText, \"(?im)(?[^\\s:()]+(?:\\.ps1|\\.psm1|\\.ts|\\.tsx|\\.js|\\.jsx|\\.py|\\.rs|\\.go|\\.cs|\\.java|\\.kt|\\.v)):(?[A-Za-z_][A-Za-z0-9_.:-]*)\\s*\\(cc=(?\\d+)\\)\"))\n if ($namedMatches.Count -gt 0) {\n foreach ($match in $namedMatches) {\n $file = [string]$match.Groups[\"file\"].Value\n $symbol = [string]$match.Groups[\"symbol\"].Value\n $value = [int]$match.Groups[\"cc\"].Value\n $records.Add((New-CodeIntelSentruxRecord `\n -Id (\"check:max_cc:{0}:{1}\" -f $file, $symbol) `\n -Kind \"max_cc\" `\n -Source \"sentrux check\" `\n -SourceStep \"sentrux check\" `\n -RawOutputPath \"report.json#/steps/sentrux check/output\" `\n -Stdout $checkText `\n -Metric \"cyclomatic_complexity\" `\n -Value $value `\n -Threshold 70 `\n -Target (New-CodeIntelSentruxTarget -Status \"resolved\" -File $file -Symbol $symbol)))\n }\n }\n elseif ($checkText -match \"(?i)max[_ -]?cc|cyclomatic|complex\") {\n $value = $null\n $valueMatch = [regex]::Match($checkText, \"(?i)(?:max[_ -]?cc|cc|cyclomatic[^0-9]*)(?:\\D+)(?\\d+)\")\n if ($valueMatch.Success) { $value = [int]$valueMatch.Groups[\"cc\"].Value }\n $records.Add((New-CodeIntelSentruxRecord `\n -Id \"check:max_cc:unresolved\" `\n -Kind \"max_cc\" `\n -Source \"sentrux check\" `\n -SourceStep \"sentrux check\" `\n -RawOutputPath \"report.json#/steps/sentrux check/output\" `\n -Stdout $checkText `\n -Metric \"cyclomatic_complexity\" `\n -Value $value `\n -Threshold 70 `\n -Target (New-CodeIntelSentruxTarget -Status \"unresolved\")))\n }\n else {\n $parserErrors.Add(\"sentrux check failed but stdout did not match known max_cc formats.\")\n }\n }\n }\n\n if ($null -ne $gateStep) {\n $gateStatus = [string]$gateStep.status\n $gateText = (([string]$gateStep.output) + \"`n\" + ([string]$gateStep.error)).Trim()\n if ($gateStatus -eq \"failed\" -or $gateStatus -eq \"manual_required\") {\n $gateMatches = @([regex]::Matches($gateText, \"(?im)(?