qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$sourceRoot = Join-Path $projectRoot 'src'
$subsystems = @('app', 'cli', 'core', 'messaging', 'napcat', 'native', 'requests', 'storage', 'transport')

$rootFiles = @(Get-ChildItem -LiteralPath $sourceRoot -File -Filter '*.rs' | ForEach-Object Name | Sort-Object)
if (($rootFiles -join ',') -ne 'lib.rs,main.rs') {
    throw 'Source root must contain only the library boundary and binary entry point.'
}
$library = Get-Content -LiteralPath (Join-Path $sourceRoot 'lib.rs') -Raw
$declared = @([regex]::Matches($library, '(?m)^pub mod ([a-z_]+);') | ForEach-Object { $_.Groups[1].Value } | Sort-Object)
if (($declared -join ',') -ne (($subsystems | Sort-Object) -join ',')) {
    throw 'Library subsystem declarations differ from the documented architecture.'
}
if ($library -match 'pub use|allow\(unsafe_code\)') {
    throw 'Do not restore root compatibility aliases or widen the unsafe boundary.'
}

foreach ($subsystem in @('core', 'native', 'storage', 'transport', 'napcat')) {
    $files = Get-ChildItem -LiteralPath (Join-Path $sourceRoot $subsystem) -Recurse -File -Filter '*.rs'
    foreach ($file in $files) {
        if ($file.Name -eq 'tests.rs' -or $file.Directory.Name -eq 'tests') { continue }
        $body = Get-Content -LiteralPath $file.FullName -Raw
        # Unit-only integration fixtures may exercise higher-level composition.
        $production = ($body -split '#\[cfg\(test\)\]', 2)[0]
        $grouped = @([regex]::Matches($production, '(?s)\buse\s+crate::\{(.*?)\};') | ForEach-Object { $_.Groups[1].Value })
        $groupedUpper = @($grouped | Where-Object { $_ -match '(?:^|,)\s*(app|cli)\b' }).Count -gt 0
        if ($production -match '\bcrate::app\b|\bcrate::cli\b' -or $groupedUpper) {
            throw "Reverse dependency into application/CLI: $subsystem/$($file.Name)"
        }
        $groupedMessaging = @($grouped | Where-Object { $_ -match '(?:^|,)\s*messaging\b' }).Count -gt 0
        if ($subsystem -eq 'transport' -and ($production -match '\bcrate::messaging\b' -or $groupedMessaging)) {
            throw "Transport depends on a messaging use case: $($file.Name)"
        }
    }
}

$allowedUnsafe = Join-Path $sourceRoot 'native/mod.rs'
foreach ($file in Get-ChildItem -LiteralPath $sourceRoot -Recurse -File -Filter '*.rs') {
    if ((Get-Content -LiteralPath $file.FullName -Raw) -match '(?s)#!?\[allow\([^\]]*\bunsafe_code\b' -and $file.FullName -ne $allowedUnsafe) {
        throw 'Unsafe allowance must remain on the audited native capability declarations only.'
    }
}
$nativeBoundary = Get-Content -LiteralPath $allowedUnsafe -Raw
if ($nativeBoundary -notmatch '(?s)#\[allow\(unsafe_code\)\][^\r\n]*\s+pub mod job;' -or
    $nativeBoundary -notmatch '(?s)#\[allow\(unsafe_code\)\][^\r\n]*\s+pub\(crate\) mod credential_lock;' -or
    [regex]::Matches($nativeBoundary, '(?s)#!?\[allow\([^\]]*\bunsafe_code\b').Count -ne 2) {
    throw 'The audited unsafe allowances must apply only to native::job and native::credential_lock.'
}
if ((Get-Content -LiteralPath (Join-Path $sourceRoot 'app/mod.rs')).Count -gt 200) {
    throw 'Application composition entry exceeds its review budget; extract a focused use case.'
}
Write-Output 'Architecture boundaries: OK'