scripts/auto-detection.ps1

<#
.SYNOPSIS
    Multi-developer signal detection for Specrew multi-session recommendations (F-051 Iteration 2b).
#>


Set-StrictMode -Version Latest

. (Join-Path $PSScriptRoot 'internal\session-config.ps1')
. (Join-Path $PSScriptRoot 'internal\session-management.ps1')

function Get-SpecrewRecentGitAuthorEmails {
    param(
        [Parameter(Mandatory = $true)][string]$ProjectRoot,
        [int]$SinceDays = 90
    )

    $since = ('{0} days ago' -f $SinceDays)
    $output = @(& git -C $ProjectRoot log --since=$since --format='%ae' 2>$null)
    if ($LASTEXITCODE -ne 0) { return @() }

    return @($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim().ToLowerInvariant() } | Select-Object -Unique)
}

function Get-SpecrewActiveMachineFingerprintCount {
    param([Parameter(Mandatory = $true)][string]$ProjectRoot)

    $sessions = @(Read-ActiveSessions -ProjectRoot $ProjectRoot)
    return @($sessions | ForEach-Object { [string]$_['machine_fingerprint'] } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique).Count
}

function Get-SpecrewConcurrentWriteSignalCount {
    param([Parameter(Mandatory = $true)][string]$ProjectRoot)

    $stateFiles = @(
        Join-Path $ProjectRoot '.specrew/start-context.json'
        Join-Path $ProjectRoot '.specrew/last-start-prompt.md'
        Join-Path $ProjectRoot '.squad/identity/now.md'
        Join-Path $ProjectRoot '.squad/decisions.md'
        Join-Path $ProjectRoot '.squad/active-features.yml'
    ) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | ForEach-Object { Get-Item -LiteralPath $_ }

    if (@($stateFiles).Count -lt 2) { return 0 }

    $ordered = @($stateFiles | Sort-Object LastWriteTimeUtc)
    $signals = 0
    for ($i = 1; $i -lt $ordered.Count; $i++) {
        $delta = ($ordered[$i].LastWriteTimeUtc - $ordered[$i - 1].LastWriteTimeUtc).Duration()
        if ($delta.TotalSeconds -le 60) { $signals++ }
    }

    return $signals
}

function Get-SpecrewBranchFanoutCount {
    param([Parameter(Mandatory = $true)][string]$ProjectRoot)

    $branches = @(& git -C $ProjectRoot for-each-ref --format='%(refname:short)' refs/heads 2>$null)
    if ($LASTEXITCODE -ne 0) { return 0 }

    return @($branches | Where-Object { $_ -match '^\d{3}[-_]' } | Select-Object -Unique).Count
}

function Get-SpecrewMultiDeveloperSignals {
    param(
        [Parameter(Mandatory = $true)][string]$ProjectRoot,
        [int]$SinceDays = 90
    )

    $authors = @(Get-SpecrewRecentGitAuthorEmails -ProjectRoot $ProjectRoot -SinceDays $SinceDays)
    $machineCount = Get-SpecrewActiveMachineFingerprintCount -ProjectRoot $ProjectRoot
    $writeSignals = Get-SpecrewConcurrentWriteSignalCount -ProjectRoot $ProjectRoot
    $branchFanout = Get-SpecrewBranchFanoutCount -ProjectRoot $ProjectRoot
    $sessionMode = Get-SessionMode -ProjectRoot $ProjectRoot

    # Genuine multi-developer signals attribute activity to DISTINCT actors: multiple unique
    # git authors, multiple active-session machine fingerprints, or a fan-out of numbered
    # feature branches. Close-together shared-state writes ($writeSignals) CANNOT attribute
    # writes to distinct actors -- a single session's bootstrap writes start-context.json +
    # last-start-prompt.md (+ decisions.md) within ~1s, which always trips a write signal on a
    # brand-new single-developer project (FR-012). So $writeSignals only CORROBORATES a genuine
    # distinct-actor signal; it must never trigger the recommendation on its own.
    $distinctActorSignal = ($authors.Count -ge 2) -or ($machineCount -ge 2) -or ($branchFanout -ge 3)
    $hasSignals = $distinctActorSignal
    $suppressed = $sessionMode -eq 'multi'
    $reasonParts = New-Object System.Collections.Generic.List[string]
    if ($authors.Count -ge 2) { $reasonParts.Add(('{0} unique git authors' -f $authors.Count)) | Out-Null }
    if ($machineCount -ge 2) { $reasonParts.Add(('{0} active machines' -f $machineCount)) | Out-Null }
    if ($branchFanout -ge 3) { $reasonParts.Add(('{0} feature branches' -f $branchFanout)) | Out-Null }
    if ($distinctActorSignal -and $writeSignals -ge 1) { $reasonParts.Add(('{0} close-together shared-state writes' -f $writeSignals)) | Out-Null }

    $recommendation = $null
    if ($hasSignals -and -not $suppressed) {
        $detail = if ($reasonParts.Count -gt 0) { $reasonParts -join ', ' } else { 'multi-developer activity' }
        $recommendation = "Multiple developers detected ($detail). Consider enabling multi-session mode: ``specrew config set session_mode multi``"
    }

    return [pscustomobject]@{
        schema_version             = 'v1'
        session_mode               = $sessionMode
        unique_git_author_count    = $authors.Count
        unique_machine_count       = $machineCount
        concurrent_write_count     = $writeSignals
        branch_fanout_count        = $branchFanout
        has_multi_developer_signal = $hasSignals
        recommendation_suppressed  = $suppressed
        recommendation_message     = $recommendation
        summary                    = if ($reasonParts.Count -gt 0) { $reasonParts -join '; ' } else { 'no multi-developer signals' }
    }
}

function Get-SpecrewMultiDeveloperRecommendation {
    param([Parameter(Mandatory = $true)][string]$ProjectRoot)

    $signals = Get-SpecrewMultiDeveloperSignals -ProjectRoot $ProjectRoot
    if ([string]::IsNullOrWhiteSpace([string]$signals.recommendation_message)) {
        return $null
    }

    return $signals.recommendation_message
}