shims/editor.ps1

#Requires -Version 7
<#
    Context isolation for code editors, from any launcher.

    WHY THIS EXISTS

    Open-DevCode already passes --user-data-dir, so an editor opened through it
    gets its own sign-ins. The trouble is everything that does NOT go through
    it: a shortcut somebody made by hand, `code .` typed in a terminal, "Open
    with" from the file explorer, an npm script, an agent. All of those land on
    the shared profile, and a GitHub sign-in there logs you out everywhere else.

    Sitting in PATH is the only position every one of those callers passes
    through. Same reasoning, and the same three entry points, as the supabase
    guard next door.

    ONE SCRIPT, MANY EDITORS

    The editor's name arrives in DEVCTX_SHIM_EDITOR, set by the .cmd or the
    POSIX sibling that called us -- not as a parameter, because the argument
    stream belongs to the caller and must reach the editor untouched. `code
    --wait COMMIT_EDITMSG` is git's editor; anything that reorders, swallows or
    re-quotes an argument there breaks committing.

    CONTRACT

    Adds flags, never removes any, and delegates unchanged whenever it is not
    certain: no context owning the folder, no editor found, an unreadable
    module, an unexpected error. The behaviour without this shim is the
    behaviour when it hesitates -- which is also why the failure mode is
    "isolation you did not get" rather than "editor that will not start".

    Synchronous on purpose. Open-DevCode detaches through `start`, which is
    right for a shortcut and catastrophic here: git would see its editor return
    immediately and commit an empty message.

    Exit code is the editor's.

    DEVCTX_SHIM_TRACE=1 prints, on stderr, what was decided and why. "My editor
    opened on the wrong account" has no answer otherwise: the flags are injected
    where nobody can see them, and two contexts can be indistinguishable from
    the outside. stderr, so it never contaminates a caller parsing stdout --
    `code --list-extensions` is read by scripts.

    The trace names a context and prints paths. It never prints an environment
    variable, an argument of the command, or anything from the vault: a trace
    gets pasted into an issue.
#>


Set-StrictMode -Off
$ErrorActionPreference = 'Stop'

$ShimDir = $PSScriptRoot
$Arguments = @($args)
$EditorName = $env:DEVCTX_SHIM_EDITOR
if (-not $EditorName) { $EditorName = 'code' }

# --- delegation -------------------------------------------------------------

function Resolve-RealExe {
    # Duplicated from the module rather than imported, deliberately: this has to
    # work when the module is missing or broken, which is exactly when
    # delegating matters most.
    $here = $ShimDir.TrimEnd('\', '/')
    Get-Command $EditorName -CommandType Application -All -ErrorAction SilentlyContinue |
        Where-Object { (Split-Path $_.Source -Parent).TrimEnd('\', '/') -ne $here } |
        Select-Object -First 1 -ExpandProperty Source
}

$RealExe = Resolve-RealExe

function Write-Trace {
    param([string]$Message)
    if ($env:DEVCTX_SHIM_TRACE) { [Console]::Error.WriteLine(" [devctx:$EditorName] $Message") }
}

function Invoke-Real {
    param([string[]]$Final = $Arguments, [string]$Motif = 'delegation nue')
    if (-not $RealExe) {
        Write-Error "$EditorName introuvable dans le PATH (hors shims)."
        exit 127
    }
    Write-Trace "$Motif -> $RealExe"
    & $RealExe @Final
    exit $LASTEXITCODE
}

# --- decide -----------------------------------------------------------------

$finalArguments = $Arguments

try {
    $module = Import-Module (Join-Path $PSScriptRoot '..' 'DevContext.psd1') -Force -PassThru -ErrorAction Stop

    # LE DOSSIER DECIDE, JAMAIS LA SESSION -- et ici le dossier est celui qu'on
    # OUVRE, pas celui d'ou l'on tape. Ouvrir un projet client depuis un dossier
    # perso doit charger l'identite client, sans quoi ce shim reproduit
    # exactement le bug qu'il existe pour corriger.
    $target = & $module { param($a, $w) Resolve-CtxEditorTargetPath -Arguments $a -WorkingDirectory $w } $Arguments $PWD.Path
    Write-Trace "cible : $target"

    $manifest = & $module { param($p) Resolve-DevContextForPath -Path $p } $target
    if (-not $manifest) { Invoke-Real -Motif 'aucun contexte ne possede ce dossier' }

    $contextName = & $module { param($m) Get-CtxProp $m 'name' } $manifest
    $contextDir = & $module { param($n) Get-CtxPath $n } $contextName

    $editor = & $module { param($n) Get-CtxEditorFacts | Where-Object Name -eq $n | Select-Object -First 1 } $EditorName
    if (-not $editor) { Invoke-Real -Motif "editeur '$EditorName' non reconnu" }

    $caps = & $module { param($e) Get-CtxEditorCapabilitiesCached -Editor $e } $editor
    Write-Trace "contexte : $contextName | profil isole : $($caps.UserDataDir) | extensions isolees : $($caps.ExtensionsDir) ($($caps.Method))"

    $finalArguments = & $module {
        param($c, $d, $p, $a) Resolve-CtxEditorArguments -Capabilities $c -ContextDir $d -ProfileName $p -Arguments $a
    } $caps $contextDir $editor.Profile $Arguments

    # L'environnement du contexte, pour le terminal integre et pour l'assistant
    # qui y tourne : sans GH_CONFIG_DIR, `gh` repart sur le dernier compte
    # connecte de la machine. C'est un BONUS -- l'isolation du profil, elle, est
    # deja acquise ci-dessus. Un coffre verrouille ne doit pas empecher
    # l'editeur de s'ouvrir.
    try { & $module { param($n) Use-DevContext -Name $n -NoCd } $contextName 6>$null | Out-Null }
    catch { Write-Trace "identite non chargee : $($_.Exception.Message)" }
}
catch {
    Write-Trace "erreur inattendue, delegation : $($_.Exception.Message)"
    Invoke-Real
}

Invoke-Real -Final $finalArguments -Motif 'lancement isole'