extensions/specrew-speckit/scripts/scaffold-iteration-artifacts.ps1

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$SpecDirectory,

    [Parameter(Mandatory = $true)]
    [string]$IterationNumber,

    [switch]$DryRun,
    [switch]$PassThru
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Add-ScaffoldAction {
    param(
        [AllowEmptyCollection()]
        [Parameter(Mandatory = $true)]
        [System.Collections.ArrayList]$Actions,

        [Parameter(Mandatory = $true)]
        [string]$Action,

        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    $null = $Actions.Add([pscustomobject]@{
            Action = $Action
            Path   = $Path
        })
}

function Ensure-Directory {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [AllowEmptyCollection()]
        [Parameter(Mandatory = $true)]
        [System.Collections.ArrayList]$Actions
    )

    if (Test-Path -LiteralPath $Path) {
        Add-ScaffoldAction -Actions $Actions -Action 'preserved-directory' -Path $Path
        return
    }

    Add-ScaffoldAction -Actions $Actions -Action $(if ($DryRun) { 'would-create-directory' } else { 'created-directory' }) -Path $Path
    if (-not $DryRun) {
        New-Item -ItemType Directory -Path $Path -Force | Out-Null
    }
}

function Write-MissingFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]$TargetPath,

        [Parameter(Mandatory = $true)]
        [string]$Content,

        [AllowEmptyCollection()]
        [Parameter(Mandatory = $true)]
        [System.Collections.ArrayList]$Actions
    )

    if (Test-Path -LiteralPath $TargetPath) {
        Add-ScaffoldAction -Actions $Actions -Action 'preserved' -Path $TargetPath
        return
    }

    Add-ScaffoldAction -Actions $Actions -Action $(if ($DryRun) { 'would-create' } else { 'created' }) -Path $TargetPath
    if (-not $DryRun) {
        $parent = Split-Path -Parent $TargetPath
        if (-not (Test-Path -LiteralPath $parent)) {
            New-Item -ItemType Directory -Path $parent -Force | Out-Null
        }

        [System.IO.File]::WriteAllText($TargetPath, $Content, [System.Text.UTF8Encoding]::new($false))
    }
}

function Get-MarkdownContent {
    param([string]$Path)

    return @(Get-Content -LiteralPath $Path -Encoding UTF8)
}

function Get-MarkdownSectionTable {
    param(
        [string[]]$Lines,
        [string]$Heading
    )

    $headingPattern = '^#{2,3}\s+' + [regex]::Escape($Heading) + '\b'
    $startIndex = -1

    for ($index = 0; $index -lt $Lines.Count; $index++) {
        if ($Lines[$index] -match $headingPattern) {
            $startIndex = $index
            break
        }
    }

    if ($startIndex -lt 0) {
        return @()
    }

    $tableLines = New-Object System.Collections.Generic.List[string]
    for ($index = $startIndex + 1; $index -lt $Lines.Count; $index++) {
        $currentLine = $Lines[$index]
        if ($currentLine -match '^#{2,3}\s+') {
            break
        }

        if ($currentLine.Trim().StartsWith('|')) {
            $null = $tableLines.Add($currentLine)
        }
    }

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

    $headers = ($tableLines[0].Trim('|') -split '\|') | ForEach-Object { $_.Trim() }
    $rows = New-Object System.Collections.Generic.List[object]

    for ($rowIndex = 1; $rowIndex -lt $tableLines.Count; $rowIndex++) {
        $cells = ($tableLines[$rowIndex].Trim('|') -split '\|') | ForEach-Object { $_.Trim() }
        $isSeparator = $true

        foreach ($cell in $cells) {
            if ($cell -notmatch '^:?-{3,}:?$') {
                $isSeparator = $false
                break
            }
        }

        if ($isSeparator) {
            continue
        }

        $row = [ordered]@{}
        for ($cellIndex = 0; $cellIndex -lt $headers.Count; $cellIndex++) {
            $value = if ($cellIndex -lt $cells.Count) { $cells[$cellIndex] } else { '' }
            $row[$headers[$cellIndex]] = $value
        }

        $rows.Add([pscustomobject]$row)
    }

    return $rows.ToArray()
}

function Get-MarkdownMetadataValue {
    param(
        [string[]]$Lines,
        [string]$Label
    )

    $pattern = '^\*\*' + [regex]::Escape($Label) + '\*\*:\s*(.+?)\s*$'
    foreach ($line in $Lines) {
        if ($line -match $pattern) {
            return $Matches[1].Trim()
        }
    }

    return $null
}

function Normalize-MarkdownCell {
    param([AllowNull()][string]$Value)

    if ($null -eq $Value) {
        return ''
    }

    return $Value.Trim().Trim('`')
}

function Convert-ToRepoRelativePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$BasePath,

        [Parameter(Mandatory = $true)]
        [string]$TargetPath
    )

    return ([System.IO.Path]::GetRelativePath($BasePath, $TargetPath)).Replace('\', '/')
}

function Get-DefaultRequirementRefsForGate {
    param(
        [Parameter(Mandatory = $true)]
        [string]$GateId
    )

    switch ($GateId) {
        'dead-field' { return @('FR-011', 'FR-027', 'FR-030') }
        'anti-pattern' { return @('FR-011', 'FR-028', 'FR-030') }
        'test-integrity' { return @('FR-011', 'FR-029', 'FR-030') }
        'stack-tooling-evidence' { return @('FR-011') }
        'quality-lens-review' { return @('FR-011', 'FR-012') }
        'concurrency-correctness-review' { return @('FR-011', 'FR-012', 'FR-015') }
        'resiliency-semantics-review' { return @('FR-011', 'FR-012', 'FR-015') }
        'retry-idempotency-review' { return @('FR-011', 'FR-012', 'FR-015') }
        default { return @('FR-011') }
    }
}

function Resolve-QualityEvidenceSource {
    param(
        [AllowNull()][string]$Value,
        [Parameter(Mandatory = $true)]
        [string]$FeatureId,
        [Parameter(Mandatory = $true)]
        [string]$IterationNumber,
        [Parameter(Mandatory = $true)]
        [string]$FindingsRef,
        [Parameter(Mandatory = $true)]
        [string]$EvidenceRef
    )

    $normalized = Normalize-MarkdownCell $Value
    if ([string]::IsNullOrWhiteSpace($normalized)) {
        return $EvidenceRef
    }

    $resolved = $normalized.Replace('specs/<feature>/iterations/<NNN>/quality/mechanical-findings.json', $FindingsRef)
    $resolved = $resolved.Replace('specs/<feature>/iterations/<NNN>/quality/quality-evidence.md', $EvidenceRef)
    $resolved = $resolved.Replace('<feature>', $FeatureId)
    $resolved = $resolved.Replace('<NNN>', $IterationNumber)
    return $resolved
}

function Get-DefaultQualityGateRows {
    return @(
        [pscustomobject]@{ 'Required Quality Gate' = 'dead-field'; Category = 'mechanical'; 'Evidence Source' = 'specs/<feature>/iterations/<NNN>/quality/mechanical-findings.json' }
        [pscustomobject]@{ 'Required Quality Gate' = 'anti-pattern'; Category = 'mechanical'; 'Evidence Source' = 'specs/<feature>/iterations/<NNN>/quality/mechanical-findings.json' }
        [pscustomobject]@{ 'Required Quality Gate' = 'test-integrity'; Category = 'mechanical'; 'Evidence Source' = 'specs/<feature>/iterations/<NNN>/quality/mechanical-findings.json' }
        [pscustomobject]@{ 'Required Quality Gate' = 'stack-tooling-evidence'; Category = 'tooling'; 'Evidence Source' = 'specs/<feature>/iterations/<NNN>/quality/quality-evidence.md' }
        [pscustomobject]@{ 'Required Quality Gate' = 'quality-lens-review'; Category = 'manual-evidence'; 'Evidence Source' = 'specs/<feature>/iterations/<NNN>/quality/quality-evidence.md' }
    )
}

function Get-QualityEvidenceContent {
    param(
        [Parameter(Mandatory = $true)]
        [string]$PlanPath,
        [Parameter(Mandatory = $true)]
        [string]$FeatureId,
        [Parameter(Mandatory = $true)]
        [string]$IterationNumber,
        [Parameter(Mandatory = $true)]
        [string]$FindingsRef,
        [Parameter(Mandatory = $true)]
        [string]$EvidenceRef,
        [Parameter(Mandatory = $true)]
        [string]$ReviewedAt
    )

    $planLines = if (Test-Path -LiteralPath $PlanPath -PathType Leaf) {
        @(Get-MarkdownContent -Path $PlanPath)
    }
    else {
        @()
    }

    $profileRef = Normalize-MarkdownCell (Get-MarkdownMetadataValue -Lines $planLines -Label 'Inferred Quality Profile')
    if ([string]::IsNullOrWhiteSpace($profileRef)) {
        $profileRef = 'quality-profile.pending'
    }

    $presetRefs = Normalize-MarkdownCell (Get-MarkdownMetadataValue -Lines $planLines -Label 'Selected preset ref or explicit custom composition')
    if ([string]::IsNullOrWhiteSpace($presetRefs)) {
        $presetRefs = '(pending preset selection)'
    }

    $gateRows = @(Get-MarkdownSectionTable -Lines $planLines -Heading 'Required Quality Gates')
    if ($gateRows.Count -eq 0) {
        $gateRows = @(Get-DefaultQualityGateRows)
    }
    $lines = [System.Collections.Generic.List[string]]::new()
    $null = $lines.Add("# Quality Evidence: Iteration $IterationNumber")
    $null = $lines.Add('')
    $null = $lines.Add(('**Profile Ref**: `' + $profileRef + '`'))
    $null = $lines.Add(('**Preset Refs**: ' + $presetRefs))
    $null = $lines.Add(('**Findings Ref**: `' + $FindingsRef + '`'))
    $null = $lines.Add('**Reviewed By**: Reviewer (pending)')
    $null = $lines.Add(('**Reviewed At**: ' + $ReviewedAt))
    $null = $lines.Add('')
    $null = $lines.Add('## Gate Matrix')
    $null = $lines.Add('')
    $null = $lines.Add('| Gate | Requirement | Evidence Source | Status | Exception |')
    $null = $lines.Add('| --- | --- | --- | --- | --- |')

    foreach ($gateRow in $gateRows) {
        $gateId = Normalize-MarkdownCell ([string]$gateRow.'Required Quality Gate')
        if ([string]::IsNullOrWhiteSpace($gateId)) {
            continue
        }

        $requirementRefs = (Get-DefaultRequirementRefsForGate -GateId $gateId) -join ', '
        $evidenceSource = Resolve-QualityEvidenceSource `
            -Value ([string]$gateRow.'Evidence Source') `
            -FeatureId $FeatureId `
            -IterationNumber $IterationNumber `
            -FindingsRef $FindingsRef `
            -EvidenceRef $EvidenceRef

        $null = $lines.Add(('| `{0}` | {1} | `{2}` | `planned` | `—` |' -f $gateId, $requirementRefs, $evidenceSource))
    }

    return ($lines -join [Environment]::NewLine) + [Environment]::NewLine
}

function Get-MechanicalFindingsScaffoldJson {
    param(
        [Parameter(Mandatory = $true)]
        [string]$FeatureRef,
        [Parameter(Mandatory = $true)]
        [string]$IterationRef,
        [Parameter(Mandatory = $true)]
        [string]$GeneratedAt
    )

    return ([pscustomobject][ordered]@{
            schemaVersion = 'v1'
            featureRef    = $FeatureRef
            iterationRef  = $IterationRef
            generatedAt   = $GeneratedAt
            generator     = [pscustomobject][ordered]@{
                name    = 'specrew-iteration-scaffold'
                version = '1.0.0'
            }
            findings      = @()
        } | ConvertTo-Json -Depth 8)
}

function Test-PhaseTwoQualityArtifactScaffold {
    param(
        [AllowEmptyCollection()]
        [string[]]$PlanLines,
        [string]$QualityContractPath
    )

    $phaseTwoPattern = 'hardening-gate\.md|trap-reapplication\.md|quality[\\/]+lenses'
    $planText = ($PlanLines -join [Environment]::NewLine)
    if ($planText -match $phaseTwoPattern) {
        return $true
    }

    if (Test-Path -LiteralPath $QualityContractPath -PathType Leaf) {
        $contractText = Get-Content -LiteralPath $QualityContractPath -Raw -Encoding UTF8
        return $contractText -match $phaseTwoPattern
    }

    return $false
}

function Get-HardeningGateContent {
    param(
        [Parameter(Mandatory = $true)]
        [string]$FeatureRef,
        [Parameter(Mandatory = $true)]
        [string]$IterationRef,
        [Parameter(Mandatory = $true)]
        [string]$IterationNumber,
        [Parameter(Mandatory = $true)]
        [string]$ReviewedAt
    )

    $lines = [System.Collections.Generic.List[string]]::new()
    $null = $lines.Add("# Hardening Gate: Iteration $IterationNumber")
    $null = $lines.Add('')
    $null = $lines.Add('**Schema**: v1')
    $null = $lines.Add('**Gate ID**: `pre-implementation-hardening`')
    $null = $lines.Add(('**Feature Ref**: `' + $FeatureRef + '`'))
    $null = $lines.Add(('**Iteration Ref**: `' + $IterationRef + '`'))
    $null = $lines.Add('**Requested Review Class**: `strongest-available`')
    $null = $lines.Add('**Effective Review Class**: `(pending hardening review)`')
    $null = $lines.Add('**Overall Verdict**: `blocked`')
    $null = $lines.Add('**Approval Ref**: `—`')
    $null = $lines.Add('**Reviewed By**: Reviewer (pending)')
    $null = $lines.Add(('**Reviewed At**: ' + $ReviewedAt))
    $null = $lines.Add('')
    $null = $lines.Add('## Concern Review')
    $null = $lines.Add('')
    $null = $lines.Add('| Concern | Category | Status | Blocking | Rationale | Approval |')
    $null = $lines.Add('| --- | --- | --- | --- | --- | --- |')
    $null = $lines.Add('| `security-surface` | `security` | `tbd` | `true` | Scaffolded placeholder. Review trust boundaries, privilege changes, and sensitive flows before implementation proceeds. | `—` |')
    $null = $lines.Add('| `error-handling-expectations` | `error-handling` | `tbd` | `true` | Scaffolded placeholder. Record expected failure semantics and incomplete-state handling before implementation proceeds. | `—` |')
    $null = $lines.Add('| `retry-idempotency-requirements` | `retry-idempotency` | `tbd` | `true` | Scaffolded placeholder. Confirm whether retries/idempotency are required or explicitly not applicable. | `—` |')
    $null = $lines.Add('| `test-integrity-targets` | `test-integrity` | `tbd` | `true` | Scaffolded placeholder. Tie negative-path expectations to observable test evidence before implementation proceeds. | `—` |')
    $null = $lines.Add('| `operational-resilience-concerns` | `operational` | `tbd` | `true` | Scaffolded placeholder. Review runtime resilience, fallback, and operator-facing failure signals before implementation proceeds. | `—` |')
    $null = $lines.Add('')
    $null = $lines.Add('## Notes')
    $null = $lines.Add('')
    $null = $lines.Add('- This artifact was scaffolded before the hardening review ran.')
    $null = $lines.Add('- Replace placeholder statuses with reviewed outcomes before marking implementation readiness.')
    return ($lines -join [Environment]::NewLine) + [Environment]::NewLine
}

function Get-TrapReapplicationContent {
    param(
        [Parameter(Mandatory = $true)]
        [string]$IterationNumber,
        [Parameter(Mandatory = $true)]
        [string]$RecordedAt
    )

    $lines = [System.Collections.Generic.List[string]]::new()
    $null = $lines.Add("# Trap Reapplication: Iteration $IterationNumber")
    $null = $lines.Add('')
    $null = $lines.Add('**Schema**: v1')
    $null = $lines.Add('**Scan ID**: `trap-reapplication.pending`')
    $null = $lines.Add(('**Recorded At**: ' + $RecordedAt))
    $null = $lines.Add('')
    $null = $lines.Add('## Scan Log')
    $null = $lines.Add('')
    $null = $lines.Add('| Trap Ref | Scan Scope | Result | Matches |')
    $null = $lines.Add('| --- | --- | --- | --- |')
    $null = $lines.Add('| `(pending trap refs)` | `(pending scan scope)` | `skipped-with-rationale` | Scaffolded placeholder. Known-trap reapplication has not run yet. |')
    $null = $lines.Add('')
    $null = $lines.Add('## Notes')
    $null = $lines.Add('')
    $null = $lines.Add('- Replace the placeholder row with concrete trap scan evidence once reapplication runs.')
    return ($lines -join [Environment]::NewLine) + [Environment]::NewLine
}

function Get-PlanTaskIds {
    param(
        [Parameter(Mandatory = $true)]
        [string]$PlanPath
    )

    if (-not (Test-Path -LiteralPath $PlanPath)) {
        return @()
    }

    $planLines = @(Get-MarkdownContent -Path $PlanPath)
    $taskRows = @(Get-MarkdownSectionTable -Lines $planLines -Heading 'Tasks')
    $seen = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
    $taskIds = New-Object System.Collections.Generic.List[string]

    foreach ($taskRow in $taskRows) {
        $taskId = [string]$taskRow.Task
        if ([string]::IsNullOrWhiteSpace($taskId)) {
            continue
        }

        $taskId = $taskId.Trim()
        if ($seen.Add($taskId)) {
            $null = $taskIds.Add($taskId)
        }
    }

    return $taskIds.ToArray()
}

function Get-BaselineRef {
    param(
        [Parameter(Mandatory = $true)]
        [string]$SpecDirectory
    )

    $projectRoot = Split-Path -Parent $SpecDirectory
    $defaultBaselineRef = 'iteration-baseline'
    $configPath = Join-Path $projectRoot '.specrew\iteration-config.yml'

    if (Test-Path -LiteralPath $configPath -PathType Leaf) {
        foreach ($line in Get-Content -LiteralPath $configPath -Encoding UTF8) {
            if ($line -match '^\s{2}baseline_ref:\s*"?([^"#]+?)"?\s*$') {
                $defaultBaselineRef = $Matches[1].Trim()
                break
            }
        }
    }

    $gitCommand = Get-Command -Name 'git' -ErrorAction SilentlyContinue
    if ($null -eq $gitCommand) {
        return $defaultBaselineRef
    }

    $headRef = @(& git -C $projectRoot rev-parse HEAD 2>$null)
    if ($LASTEXITCODE -eq 0 -and $headRef.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($headRef[0])) {
        return [string]$headRef[0]
    }

    return $defaultBaselineRef
}

$resolvedSpecDirectory = [System.IO.Path]::GetFullPath($SpecDirectory)
$projectRoot = Split-Path -Parent (Split-Path -Parent $resolvedSpecDirectory)
$iterationsRoot = Join-Path $resolvedSpecDirectory 'iterations'
$iterationDirectory = Join-Path $iterationsRoot $IterationNumber
$planPath = Join-Path $iterationDirectory 'plan.md'
$statePath = Join-Path $iterationDirectory 'state.md'
$driftLogPath = Join-Path $iterationDirectory 'drift-log.md'
$timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
$actions = [System.Collections.ArrayList]::new()

if (-not (Test-Path -LiteralPath $resolvedSpecDirectory)) {
    throw "Spec directory '$resolvedSpecDirectory' does not exist."
}

Ensure-Directory -Path $iterationsRoot -Actions $actions
Ensure-Directory -Path $iterationDirectory -Actions $actions

$taskIds = @(Get-PlanTaskIds -PlanPath $planPath)
$tasksRemaining = if ($taskIds.Count -gt 0) { $taskIds -join ', ' } else { '(populate from plan.md)' }
$baselineRef = Get-BaselineRef -SpecDirectory $resolvedSpecDirectory

$stateContent = @"
# Iteration State: $IterationNumber
 
**Schema**: v1
**Last Completed Task**: (none)
**Tasks Remaining**: $tasksRemaining
**In Progress**: (none)
**Baseline Ref**: $baselineRef
**Updated**: $timestamp
 
## Execution Summary
 
- Execution has not started yet.
- This artifact was scaffolded before task execution so resume state can be updated after each task.
 
## Notes
 
- Update this file after each task completes.
- Keep task identifiers aligned to plan.md.
 
<!-- >>> specrew-managed escalation-state >>> -->
## Repair Escalation
 
- **Status**: inactive
- **Artifact**: (none)
- **Gate**: (none)
- **Failure Count**: 0
- **Current Tier**: efficiency
- **Current Owner**: (none)
- **Locked Out Agents**: (none)
- **Last Escalated**: (none)
- **Resolved At**: (none)
- **Notes**: (none)
<!-- <<< specrew-managed escalation-state <<< -->
"@


$driftLogContent = @"
# Drift Log: Iteration $IterationNumber
 
**Schema**: v1
 
## Summary
 
**Total drift events**: 0
**Resolution rate**: 100% (0/0 resolved)
**Specification drift**: None detected
 
## Events
 
No specification drift detected during Iteration $IterationNumber execution to date.
 
### Resolution Strategies (Unused)
 
The following resolution strategies remain available if drift is detected later in execution:
- **spec-updated**: Update the spec to reflect implementation choice
- **implementation-reverted**: Revert implementation to match spec
- **deferred**: Mark drift as deferred to next iteration
- **human-decision**: Escalate to Alon for resolution
 
### Notes
 
- This artifact was scaffolded before review starts so drift can be logged immediately when detected.
- Replace the zero-drift summary with real counts when the first drift event is recorded.
"@


Write-MissingFile -TargetPath $statePath -Content $stateContent -Actions $actions
Write-MissingFile -TargetPath $driftLogPath -Content $driftLogContent -Actions $actions

$planLines = if (Test-Path -LiteralPath $planPath -PathType Leaf) {
    @(Get-MarkdownContent -Path $planPath)
}
else {
    @()
}

$qualityContractPath = Join-Path $resolvedSpecDirectory 'contracts\quality-governance-artifacts.md'
$qualityGateRows = @(Get-MarkdownSectionTable -Lines $planLines -Heading 'Required Quality Gates')
$hasQualityEvidenceContract = $qualityGateRows.Count -gt 0 -or (Test-Path -LiteralPath $qualityContractPath -PathType Leaf)
$phaseTwoQualityArtifactsRequired = Test-PhaseTwoQualityArtifactScaffold -PlanLines $planLines -QualityContractPath $qualityContractPath
if ($hasQualityEvidenceContract -or $phaseTwoQualityArtifactsRequired) {
    $qualityDirectory = Join-Path $iterationDirectory 'quality'
    $featureId = Split-Path -Leaf $resolvedSpecDirectory
    $featureRef = Convert-ToRepoRelativePath -BasePath $projectRoot -TargetPath (Join-Path $resolvedSpecDirectory 'spec.md')
    $iterationRef = Convert-ToRepoRelativePath -BasePath $projectRoot -TargetPath $iterationDirectory
    $hardeningGatePath = Join-Path $qualityDirectory 'hardening-gate.md'
    $lensesDirectory = Join-Path $qualityDirectory 'lenses'
    $trapReapplicationPath = Join-Path $qualityDirectory 'trap-reapplication.md'
    $qualityEvidencePath = Join-Path $qualityDirectory 'quality-evidence.md'
    $mechanicalFindingsPath = Join-Path $qualityDirectory 'mechanical-findings.json'
    $qualityEvidenceRef = Convert-ToRepoRelativePath -BasePath $projectRoot -TargetPath $qualityEvidencePath
    $mechanicalFindingsRef = Convert-ToRepoRelativePath -BasePath $projectRoot -TargetPath $mechanicalFindingsPath

    Ensure-Directory -Path $qualityDirectory -Actions $actions
    if ($phaseTwoQualityArtifactsRequired) {
        Ensure-Directory -Path $lensesDirectory -Actions $actions
        Write-MissingFile -TargetPath $hardeningGatePath -Content (Get-HardeningGateContent `
                -FeatureRef $featureRef `
                -IterationRef $iterationRef `
                -IterationNumber $IterationNumber `
                -ReviewedAt $timestamp) -Actions $actions
        Write-MissingFile -TargetPath $trapReapplicationPath -Content (Get-TrapReapplicationContent `
                -IterationNumber $IterationNumber `
                -RecordedAt $timestamp) -Actions $actions
    }

    if ($hasQualityEvidenceContract) {
        Write-MissingFile -TargetPath $qualityEvidencePath -Content (Get-QualityEvidenceContent `
                -PlanPath $planPath `
                -FeatureId $featureId `
                -IterationNumber $IterationNumber `
                -FindingsRef $mechanicalFindingsRef `
                -EvidenceRef $qualityEvidenceRef `
                -ReviewedAt $timestamp) -Actions $actions
        Write-MissingFile -TargetPath $mechanicalFindingsPath -Content (Get-MechanicalFindingsScaffoldJson `
                -FeatureRef $featureRef `
                -IterationRef $iterationRef `
                -GeneratedAt $timestamp) -Actions $actions
    }
}

if ($PassThru) {
    $actions
    return
}

$actions | Select-Object Action, Path | Format-Table -AutoSize
Write-Host ("Iteration artifact scaffold {0} for {1}" -f ($(if ($DryRun) { 'previewed' } else { 'completed' }), $iterationDirectory)) -ForegroundColor Green
exit 0