src/Module.ps1

function ConvertTo-QuietDataLiteral {
    param($Value)
    if($null -eq $Value){return '$null'}
    if($Value -is [bool]){return $(if($Value){'$true'}else{'$false'})}
    if($Value -is [Collections.IDictionary]){
        $parts=foreach($key in @($Value.Keys|Sort-Object)){"'"+([string]$key).Replace("'","''")+"' = "+(ConvertTo-QuietDataLiteral $Value[$key])}
        return '@{'+($parts -join ';')+'}'
    }
    if($Value -is [Collections.IEnumerable] -and $Value -isnot [string]){
        $parts=foreach($item in $Value){ConvertTo-QuietDataLiteral $item}
        return '@('+($parts -join ',')+')'
    }
    return "'"+([string]$Value).Replace("'","''")+"'"
}
function Get-QuietModuleHash {
    param([Parameter(Mandatory)][string]$Path)
    $root=if(Test-Path -LiteralPath $Path -PathType Leaf){Split-Path -Parent $Path}else{$Path}
    $root=[IO.Path]::GetFullPath($root)
    $text=(@(Get-ChildItem -LiteralPath $root -Recurse -File|Where-Object Name -ne '.quietcli-validation.json'|Sort-Object FullName|ForEach-Object{
        [IO.Path]::GetRelativePath($root,$_.FullName)+':'+(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash
    })) -join [char]10
    [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($text)))
}
function New-QuietModule {
    [CmdletBinding(SupportsShouldProcess)]param([Parameter(Mandatory)][ValidatePattern('^[a-zA-Z0-9_.-]+$')][string]$Command,
        [Parameter(Mandatory)][string[]]$ContractPath,[string[]]$RecordId=@(),[string[]]$AssertionPath=@(),[string[]]$LiveTestPath=@(),
        [Parameter(Mandatory)][string]$OutputPath,[string]$Name,[string]$SourceRoot,[version]$Version='1.0.0')
    if(!$Name){$Name="FrenchExDev.QuietCli.$Command.PoSh"}
    if($Name -notmatch '^[a-zA-Z0-9_.-]+$'){throw 'Invalid module name.'}
    $destination=$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath)
    if(Test-Path -LiteralPath $destination){throw 'Generation never overwrites an existing directory.'}
    if(!$PSCmdlet.ShouldProcess($destination,'Generate explicit QuietCli module sources')){return}
    foreach($path in @($ContractPath)+@($AssertionPath)+@($LiveTestPath)){if(!(Test-Path -LiteralPath $path -PathType Leaf)){throw "Missing source: $path"}}
    $records=@(foreach($id in $RecordId){$r=Read-QuietRecord $id;if(!$r.Promotable){throw "Record $id has implicit, missing or masked dependencies."};$r})
        $allPaths=@($ContractPath|ForEach-Object {(Resolve-Path -LiteralPath $_).Path})+@($records|ForEach-Object {foreach($s in $_.Sources){foreach($copy in $s.Sources){$copy.Path}}})
    if(!$SourceRoot){
        $SourceRoot=Split-Path -Parent $allPaths[0]
        foreach($p in $allPaths){
            while(([IO.Path]::GetRelativePath($SourceRoot,$p)).StartsWith('..'+[IO.Path]::DirectorySeparatorChar)){
                $parent=Split-Path -Parent $SourceRoot;if(!$parent -or $parent -eq $SourceRoot){throw 'Provide a common SourceRoot for all declared files.'};$SourceRoot=$parent
            }
        }
    }
    $SourceRoot=[IO.Path]::GetFullPath($SourceRoot)
    $relativePath={
        param($p)
        $relative=[IO.Path]::GetRelativePath($SourceRoot,[IO.Path]::GetFullPath($p))
        if([IO.Path]::IsPathRooted($relative) -or $relative -eq '..' -or $relative.StartsWith('..'+[IO.Path]::DirectorySeparatorChar)){throw 'Declared source escapes SourceRoot.'}
        $relative.Replace([char]92,[char]47)
    }
    $staging=Join-Path ([IO.Path]::GetTempPath()) ('quietcli-module-'+[guid]::NewGuid().ToString('N'))
    try{
        foreach($dir in @('contracts','test','fixtures')){[void][IO.Directory]::CreateDirectory((Join-Path $staging $dir))}
        foreach($path in $ContractPath){
            $relative=& $relativePath (Resolve-Path -LiteralPath $path).Path
            $target=Join-Path $staging ('contracts/'+$relative)
            if(Test-Path $target){throw 'Duplicate contract path.'}
            [void][IO.Directory]::CreateDirectory((Split-Path -Parent $target));Copy-Item -LiteralPath $path -Destination $target
        }
        foreach($record in $records){
            foreach($s in $record.Sources){foreach($copy in $s.Sources){
                $target=Join-Path $staging ('contracts/'+(& $relativePath $copy.Path));[void][IO.Directory]::CreateDirectory((Split-Path -Parent $target))
                if(Test-Path $target){if((Get-FileHash $target -Algorithm SHA256).Hash -cne $copy.SHA256){throw "Source changed since RECORD: $($copy.Name)"}}
                else{if($copy.Bytes){[IO.File]::WriteAllBytes($target,[Convert]::FromBase64String($copy.Bytes))}else{[IO.File]::WriteAllText($target,$copy.Text,[Text.UTF8Encoding]::new($false))}}
            }}
            [IO.File]::WriteAllText((Join-Path $staging ('fixtures/'+$record.Id+'.json')),($record|ConvertTo-Json -Depth 64),[Text.UTF8Encoding]::new($false))
        }
        $assertions=@(foreach($p in $AssertionPath){$leaf=[IO.Path]::GetFileName($p);Copy-Item -LiteralPath $p -Destination (Join-Path $staging "test/$leaf");"test/$leaf"})
        $live=@(foreach($p in $LiveTestPath){$leaf=[IO.Path]::GetFileName($p);Copy-Item -LiteralPath $p -Destination (Join-Path $staging "test/$leaf");"test/$leaf"})
        $files=@($ContractPath|ForEach-Object {'contracts/'+(& $relativePath (Resolve-Path -LiteralPath $_).Path)})
        $factory='New-'+($Command -replace '[^a-zA-Z0-9]','')+'QuietContract'
        $quoted=(@($files|ForEach-Object {"'"+$_.Replace("'","''")+"'"})) -join ','
        $body=@'
# Generated from explicit, documented PowerShell contracts.
#requires -Version 7.6
#requires -PSEdition Core
$script:ContractFiles=@(__FILES__)
function __FACTORY__ {
    param($Invocation)
    foreach($relative in $script:ContractFiles){
        $candidate=& (Join-Path $PSScriptRoot $relative) $Invocation
        if($candidate -and $candidate.PSObject.TypeNames -contains 'QuietCli.Contract'){return $candidate}
        if($candidate -and $candidate.Outcome -ne 'Unsupported'){return $candidate}
    }
    New-QuietResult -Outcome Unsupported -Summary 'Arguments outside the documented module scope.' -Execution @{ExitCode=$null} -ErrorInfo @{Code='ArgumentsUnsupported'}
}
Register-QuietCommand -Command '__COMMAND__' -Factory (Get-Command __FACTORY__ -CommandType Function).ScriptBlock -ModuleId '__NAME__'
Export-ModuleMember -Function __FACTORY__
'@

        $body=$body.Replace('__FILES__',$quoted).Replace('__FACTORY__',$factory).Replace('__COMMAND__',$Command).Replace('__NAME__',$Name)
        [IO.File]::WriteAllText((Join-Path $staging "$Name.psm1"),$body,[Text.UTF8Encoding]::new($false))
        $guid=[guid]::new([Security.Cryptography.MD5]::HashData([Text.Encoding]::UTF8.GetBytes($Name)))
        $runtimeManifest=Import-PowerShellDataFile (Join-Path $script:QuietRoot 'FrenchExDev.QuietCli.PoSh.psd1')
        $manifestData=@{RootModule="$Name.psm1";ModuleVersion=[string]$Version;GUID=[string]$guid;Author=$runtimeManifest.Author;CompanyName=$runtimeManifest.CompanyName;Copyright=$runtimeManifest.Copyright;Description=('Documented '+$Command+' contracts for QuietCli.');PowerShellVersion='7.6'
            CompatiblePSEditions=@('Core');FunctionsToExport=@($factory);CmdletsToExport=@();AliasesToExport=@();VariablesToExport=@()
            RequiredModules=@(@{ModuleName='FrenchExDev.QuietCli.PoSh';ModuleVersion=[string]$runtimeManifest.ModuleVersion})
            PrivateData=@{PSData=@{Tags=@('QuietCli',$Command,'PowerShell7');ProjectUri=$runtimeManifest.PrivateData.PSData.ProjectUri;LicenseUri=$runtimeManifest.PrivateData.PSData.LicenseUri};QuietCli=@{SchemaVersion=2;Command=$Command;Contracts=$files;Assertions=$assertions;LiveTests=$live;Factory=$factory}}}
        $manifest=Join-Path $staging "$Name.psd1"
        [IO.File]::WriteAllText($manifest,(ConvertTo-QuietDataLiteral $manifestData),[Text.UTF8Encoding]::new($false))
        $readme="# $Name"+[Environment]::NewLine+"Requires QuietCli 2.0+ and PowerShell Core 7.6+. Sources declare documentation and supported arguments. Fixtures are observations; independent assertions are required. Run Test-QuietModule, then Install-QuietModule explicitly."
        [IO.File]::WriteAllText((Join-Path $staging 'README.md'),$readme,[Text.UTF8Encoding]::new($false))
        [void][IO.Directory]::CreateDirectory((Split-Path -Parent $destination));Move-Item -LiteralPath $staging -Destination $destination
        [pscustomobject]@{Path=(Join-Path $destination "$Name.psd1");Command=$Command;Hash=(Get-QuietModuleHash $destination);Validated=$false}
    }finally{if(Test-Path -LiteralPath $staging){Remove-Item -LiteralPath $staging -Recurse -Force}}
}
function Test-QuietModule {
    [CmdletBinding()]param([Parameter(Mandatory)][string]$Path,[switch]$IncludeLiveTests,[switch]$AsObject)
    $manifest=if(Test-Path -LiteralPath $Path -PathType Leaf){[IO.Path]::GetFullPath($Path)}else{@(Get-ChildItem -LiteralPath $Path -Filter '*.psd1' -File)[0].FullName}
    $root=Split-Path -Parent $manifest;$meta=(Import-PowerShellDataFile $manifest).PrivateData.QuietCli
    $failures=[Collections.Generic.List[string]]::new();$checks=0;$snapshot=$script:QuietCommands.Clone();$cases=[Collections.Generic.List[object]]::new()
    try{
        foreach($file in Get-ChildItem $root -Recurse -File|Where-Object Extension -in '.ps1','.psm1','.psd1'){
            $tokens=$null;$errors=$null;$null=[Management.Automation.Language.Parser]::ParseFile($file.FullName,[ref]$tokens,[ref]$errors)
            if($errors.Count){throw "Parse error in $($file.Name): $($errors[0].Message)"};$checks++
        }
        if(!$meta.Assertions.Count){throw 'Validation requires independent business assertions.'}
        $script:QuietCommands.Remove($meta.Command);Import-Module -Name $manifest -Force -ErrorAction Stop
        foreach($fixture in Get-ChildItem (Join-Path $root 'fixtures') -Filter '*.json' -File){
            $record=Get-Content $fixture.FullName -Raw|ConvertFrom-Json -AsHashtable -Depth 64
            Save-QuietEvent $record.Id record $meta.Command $record
            $actual=Invoke-QuietRecord -Id $record.Id -AsObject
            if($actual.Outcome -ne $record.Outcome){throw "Replay outcome mismatch: $($fixture.Name)"}
            $cases.Add(@{Record=$record;Actual=$actual});$checks++
        }
        $previous=$script:QuietReplay;$script:QuietReplay=$true
        try{foreach($test in $meta.Assertions){$null=Invoke-QuietCallback {param($p,$items) & $p $items} @((Join-Path $root $test),$cases.ToArray());$checks++}}
        finally{$script:QuietReplay=$previous}
        if($IncludeLiveTests){foreach($test in $meta.LiveTests){$null=Invoke-QuietCallback {param($p,$m) & $p $m} @((Join-Path $root $test),$manifest);$checks++}}
    }catch{$failures.Add($_.Exception.Message)}
    finally{$script:QuietCommands=$snapshot}
    $report=@{SchemaVersion=2;Hash=(Get-QuietModuleHash $root);Success=($failures.Count -eq 0);Checks=$checks;Failures=$failures.ToArray();LiveTestsRun=[bool]$IncludeLiveTests;PowerShell=$PSVersionTable.PSVersion.ToString();Platform=$PSVersionTable.OS}
    [IO.File]::WriteAllText((Join-Path $root '.quietcli-validation.json'),($report|ConvertTo-Json -Depth 20),[Text.UTF8Encoding]::new($false))
    $result=New-QuietResult -Outcome $(if($report.Success){'Success'}else{'Failed'}) -Data $report -NoRegister
    if($AsObject){$result}else{Out-QuietResult $result}
}
function Install-QuietModule {
    [CmdletBinding(SupportsShouldProcess)]param([Parameter(Mandatory)][string]$Path,[string]$InstallRoot)
    $root=if(Test-Path -LiteralPath $Path -PathType Leaf){Split-Path -Parent ([IO.Path]::GetFullPath($Path))}else{[IO.Path]::GetFullPath($Path)}
    $validationPath=Join-Path $root '.quietcli-validation.json'
    if(!(Test-Path $validationPath)){throw 'Run Test-QuietModule before installation.'}
    $report=Get-Content $validationPath -Raw|ConvertFrom-Json
    if(!$report.Success -or $report.Hash -cne (Get-QuietModuleHash $root)){throw 'Validation failed or module sources changed.'}
    $manifest=@(Get-ChildItem $root -Filter '*.psd1' -File)[0];$data=Import-PowerShellDataFile $manifest.FullName;$command=$data.PrivateData.QuietCli.Command
    $settings=Get-QuietConfiguration
    if(@($settings.Modules|Where-Object Command -eq $command).Count){throw "A module is already configured for $command."}
    if(!$InstallRoot){$InstallRoot=Join-Path $HOME '.quietcli/modules'}
    $target=Join-Path $InstallRoot ($manifest.BaseName+'/'+$data.ModuleVersion)
    if(Test-Path $target){throw 'Installation directory already exists.'}
    if(!$PSCmdlet.ShouldProcess($target,'Install validated QuietCli module')){return}
    [void][IO.Directory]::CreateDirectory((Split-Path -Parent $target));Copy-Item -LiteralPath $root -Destination $target -Recurse
    $installed=Join-Path $target $manifest.Name;$settings.Modules=@($settings.Modules)+@(@{Command=$command;Path=$installed;Hash=$report.Hash})
    $config=if($env:QUIETCLI_CONFIG_PATH){$env:QUIETCLI_CONFIG_PATH}else{Join-Path $HOME '.quietcli/config.json'}
    [void][IO.Directory]::CreateDirectory((Split-Path -Parent ([IO.Path]::GetFullPath($config))))
    $temporary=$config+'.'+[guid]::NewGuid().ToString('N')
    [IO.File]::WriteAllText($temporary,($settings|ConvertTo-Json -Depth 20),[Text.UTF8Encoding]::new($false));[IO.File]::Move($temporary,$config,$true)
    [pscustomobject]@{Command=$command;Path=$installed;Hash=$report.Hash}
}