Public/Get-AITDebate.ps1
|
# Copyright (c) 2026 Jeffrey Snover. All rights reserved. # Licensed under the MIT License. See LICENSE file in the project root. function Get-AITDebate { <# .SYNOPSIS Lists and filters debate sessions from the AI Triad data store. .DESCRIPTION Reads debate JSON files and returns typed AITDebate objects. Supports filtering by ID, topic text, audience, protocol, source type, debater, model, origin (gui/cli), phase, date range, and adaptive staging. .PARAMETER Id Filter by debate ID (exact match or wildcard). .PARAMETER Topic Filter by topic text (substring match, case-insensitive). .PARAMETER Audience Filter by audience type. .PARAMETER Protocol Filter by debate protocol. .PARAMETER SourceType Filter by source type (topic, document, situations, url). .PARAMETER Debater Filter to debates that include this speaker. .PARAMETER Model Filter by AI model used. .PARAMETER Origin Filter by origin (gui or cli). .PARAMETER Phase Filter by debate phase (opening, debate, synthesis, complete). .PARAMETER AdaptiveStaging Filter to debates with adaptive staging enabled. .PARAMETER HasSynthesis Filter to debates that have a synthesis entry. .PARAMETER HasDiagnostics Filter to debates that have a diagnostics file. .PARAMETER HasHarvest Filter to debates that have a harvest file. .PARAMETER After Filter to debates created after this date. .PARAMETER Before Filter to debates created before this date. .PARAMETER MinRounds Filter to debates with at least this many statement rounds. .PARAMETER Latest Return only the N most recent debates. .PARAMETER IncludeTranscript Include the full transcript array on each result (large; off by default). .EXAMPLE Get-AITDebate .EXAMPLE Get-AITDebate -Topic "regulation" -Audience policymakers .EXAMPLE Get-AITDebate -AdaptiveStaging -HasSynthesis -Latest 5 .EXAMPLE Get-AITDebate -Origin cli -After "2026-05-01" | Format-Table Id, Title, Phase, Rounds .EXAMPLE Get-AITDebate -Debater cassandra -Protocol deliberation #> [CmdletBinding()] param( [string]$Id, [string]$Topic, [ValidateSet('policymakers', 'technical_researchers', 'industry_leaders', 'academic_community', 'general_public')] [string]$Audience, [ValidateSet('structured', 'socratic', 'deliberation')] [string]$Protocol, [ValidateSet('topic', 'document', 'situations', 'url')] [string]$SourceType, [ValidateSet('prometheus', 'sentinel', 'cassandra')] [string]$Debater, [ValidateScript({ Test-AIModelId $_ })] [ArgumentCompleter({ param($cmd, $param, $word) $script:ValidModelIds | Where-Object { $_ -like "$word*" } })] [string]$Model, [ValidateSet('gui', 'cli')] [string]$Origin, [string]$Phase, [switch]$AdaptiveStaging, [switch]$HasSynthesis, [switch]$HasDiagnostics, [switch]$HasHarvest, [DateTime]$After, [DateTime]$Before, [ValidateRange(1, 100)] [int]$MinRounds, [ValidateRange(1, 1000)] [int]$Latest, [switch]$IncludeTranscript ) Set-StrictMode -Version Latest $DebatesDir = Get-DebatesDir if (-not (Test-Path $DebatesDir)) { Write-Warning "Debates directory not found: $DebatesDir" return } $Files = Get-ChildItem $DebatesDir -Filter 'debate-*.json' -Recurse | Where-Object { $_.Name -notmatch 'diagnostics|harvest|transcript' } if ($Files.Count -eq 0) { Write-Verbose "No debate files found in $DebatesDir" return } $Results = [System.Collections.Generic.List[object]]::new() foreach ($File in $Files) { try { $Raw = Get-Content $File.FullName -Raw | ConvertFrom-Json } catch { Write-Verbose "Skipping malformed file: $($File.Name)" continue } # Extract metadata $DebateId = $Raw.id $Title = $Raw.title $TopicText = if ($Raw.PSObject.Properties['topic'] -and $Raw.topic.PSObject.Properties['original']) { $Raw.topic.original } elseif ($Raw.PSObject.Properties['topic'] -and $Raw.topic -is [string]) { $Raw.topic } else { '' } $CreatedAt = if ($Raw.PSObject.Properties['created_at']) { [DateTime]$Raw.created_at } else { $File.CreationTime } $UpdatedAt = if ($Raw.PSObject.Properties['updated_at']) { [DateTime]$Raw.updated_at } else { $File.LastWriteTime } $DebatePhase = if ($Raw.PSObject.Properties['phase']) { $Raw.phase } else { 'unknown' } $Aud = if ($Raw.PSObject.Properties['audience']) { $Raw.audience } else { '' } $Proto = if ($Raw.PSObject.Properties['protocol_id']) { $Raw.protocol_id } else { '' } $SrcType = if ($Raw.PSObject.Properties['source_type']) { $Raw.source_type } else { '' } $SrcRef = if ($Raw.PSObject.Properties['source_ref']) { $Raw.source_ref } else { '' } $Debaters = if ($Raw.PSObject.Properties['active_povers']) { @($Raw.active_povers) } else { @() } $Temp = if ($Raw.PSObject.Properties['debate_temperature']) { $Raw.debate_temperature } else { 0.0 } # Origin and model $OriginMode = 'unknown' $DebateModel = '' if ($Raw.PSObject.Properties['origin'] -and $null -ne $Raw.origin) { if ($Raw.origin.PSObject.Properties['mode']) { $OriginMode = $Raw.origin.mode } if ($Raw.origin.PSObject.Properties['model']) { $DebateModel = $Raw.origin.model } } # Adaptive staging $IsAdaptive = $false $PacingVal = '' if ($Raw.PSObject.Properties['adaptive_staging'] -and $null -ne $Raw.adaptive_staging) { if ($Raw.adaptive_staging.PSObject.Properties['enabled']) { $IsAdaptive = [bool]$Raw.adaptive_staging.enabled } if ($Raw.adaptive_staging.PSObject.Properties['pacing']) { $PacingVal = $Raw.adaptive_staging.pacing } } # Transcript stats $Transcript = if ($Raw.PSObject.Properties['transcript']) { @($Raw.transcript) } else { @() } $StmtCount = @($Transcript | Where-Object { $_.type -eq 'statement' }).Count $IntCount = @($Transcript | Where-Object { $_.type -eq 'intervention' }).Count $HasSynth = @($Transcript | Where-Object { $_.type -eq 'synthesis' }).Count -gt 0 # Round count: count distinct rounds from statements (3 speakers per round) $RoundCount = if ($Debaters.Count -gt 0 -and $StmtCount -gt 0) { [Math]::Ceiling($StmtCount / $Debaters.Count) } else { 0 } # Companion files $BaseName = $File.BaseName -replace '^debate-', '' $Dir = $File.DirectoryName $DiagExists = Test-Path (Join-Path $Dir "*$BaseName*diagnostics*") $HarvestExists = Test-Path (Join-Path $Dir "*$BaseName*harvest*") # ── Apply filters ────────────────────────────────────────────────── if ($Id -and $DebateId -notlike $Id) { continue } if ($Topic -and $TopicText -notmatch [regex]::Escape($Topic)) { continue } if ($Audience -and $Aud -ne $Audience) { continue } if ($Protocol -and $Proto -ne $Protocol) { continue } if ($SourceType -and $SrcType -ne $SourceType) { continue } if ($Debater -and $Debater -notin $Debaters) { continue } if ($Model -and $DebateModel -ne $Model) { continue } if ($Origin -and $OriginMode -ne $Origin) { continue } if ($Phase -and $DebatePhase -ne $Phase) { continue } if ($PSBoundParameters.ContainsKey('AdaptiveStaging') -and $AdaptiveStaging -ne $IsAdaptive) { continue } if ($PSBoundParameters.ContainsKey('HasSynthesis') -and $HasSynthesis -ne $HasSynth) { continue } if ($PSBoundParameters.ContainsKey('HasDiagnostics') -and $HasDiagnostics -ne $DiagExists) { continue } if ($PSBoundParameters.ContainsKey('HasHarvest') -and $HasHarvest -ne $HarvestExists) { continue } if ($After -and $CreatedAt -lt $After) { continue } if ($Before -and $CreatedAt -gt $Before) { continue } if ($MinRounds -and $RoundCount -lt $MinRounds) { continue } # ── Build result ─────────────────────────────────────────────────── $Obj = [AITDebate]::new() $Obj.Id = $DebateId $Obj.Title = $Title $Obj.Topic = $TopicText $Obj.CreatedAt = $CreatedAt $Obj.UpdatedAt = $UpdatedAt $Obj.Phase = $DebatePhase $Obj.Audience = $Aud $Obj.Protocol = $Proto $Obj.SourceType = $SrcType $Obj.SourceRef = $SrcRef $Obj.Debaters = $Debaters $Obj.Temperature = $Temp $Obj.Model = $DebateModel $Obj.Origin = $OriginMode $Obj.AdaptiveStaging = $IsAdaptive $Obj.Pacing = $PacingVal $Obj.TranscriptCount = $Transcript.Count $Obj.Rounds = $RoundCount $Obj.Statements = $StmtCount $Obj.Interventions = $IntCount $Obj.HasSynthesis = $HasSynth $Obj.HasDiagnostics = $DiagExists $Obj.HasHarvest = $HarvestExists $Obj.FilePath = $File.FullName if ($IncludeTranscript) { $Obj | Add-Member -NotePropertyName 'Transcript' -NotePropertyValue $Transcript -Force } $Results.Add($Obj) } # Sort by CreatedAt descending (newest first) $Sorted = $Results | Sort-Object CreatedAt -Descending if ($Latest) { $Sorted | Select-Object -First $Latest } else { $Sorted } } |