Payload/scripts/harness/harness-journal.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    Journal CLI for the harness event ledger (start / stop / instant / adjust).

.DESCRIPTION
    Appends accounting-style events to docs/harness/features/<slug>/journal/events.jsonl
    (schema harness.journal.event/v1). This is the source of truth for *what happened*.

    Fits the big picture (docs/kaden/harness-journal.md):
    - Journal = ledger (append-only; correcting entries via -Action adjust).
    - .review-loop.json = gate view (schema 1.2 projection). Pass -RebuildProjection to
      rebuild that view from the journal after a write.
    - Gate decisions still come from validate-review-loop against projection + artifacts -
      never from journal narrative alone.

    Prefer calling this (or emitters on harness-record-phase / harness-sensor) instead of
    hand-editing JSONL. See docs/kaden/harness-journal-blueprint.md.

.PARAMETER Summary
    Human-readable activity summary. Alias: -Note (agents often guess -Note; both are accepted).

.EXAMPLE
    .\scripts\harness-journal.ps1 -Feature harness-event-journal -Slice journal-gate-skeleton -Action start -ActivityType slice.phase -ActivityName RED -ActivityId act_demo -Summary 'RED start'

.EXAMPLE
    .\scripts\harness-journal.ps1 -Feature harness-event-journal -Slice journal-gate-skeleton -Action adjust -ActivityType blocker -ActivityName review-ready-blocked-evidence -AmendsEventId <event_id> -Summary 'Adjustment: attach original sensor paths'

.EXAMPLE
    .\scripts\harness-journal.ps1 -Feature harness-event-journal -Slice journal-gate-skeleton -Action stop -ActivityType slice.phase -ActivityName RED -ActivityId act_demo -Outcome PASS -RebuildProjection
#>

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

    [string]$Slice = '',

    [Parameter(Mandatory = $true)]
    [ValidateSet('start', 'stop', 'instant', 'adjust')]
    [string]$Action,

    [ValidateSet('pipeline.phase', 'slice.phase', 'review.cycle', 'sensor', 'validation', 'handoff', 'blocker', 'turn', 'artifact', 'other')]
    [string]$ActivityType = 'other',

    [string]$ActivityName = '',

    [string]$ActivityId = '',

    [ValidateSet('PASS', 'FAIL', 'BLOCKED', 'SKIPPED', 'PASS_WITH_FINDINGS')]
    [string]$Outcome = 'PASS',

    [ValidateSet(
        'ActivityStarted', 'ActivityStopped', 'ActivityAdjusted',
        'PhaseStatusChanged', 'SensorRunRecorded', 'GateCheckRun',
        'GateRelevantValidationRun', 'GateValidationPassed',
        'ReviewerCycleRecorded', 'HandoffRecorded',
        'HumanGateDecided', 'OwnershipChanged', 'BlockerDeclared', 'EmissionGapMarked',
        'TaskStarted', 'TaskHeartbeat', 'TaskTimedOut', 'TaskRecovered', 'TaskAbandoned',
        'ArtifactRegistered'
    )]
    [string]$Kind = 'ActivityStarted',

    [Alias('Note')]
    [string]$Summary = '',

    [string]$AmendsEventId = '',

    [string]$OccurredAt = '',

    [string]$FeatureRoot = '',

    [string]$RepoRoot = '',

    [ValidateSet('cursor', 'copilot', 'other')]
    [string]$ModelProvider = 'other',

    [string]$ModelSlug = 'unknown',

    [ValidateSet('primary', 'subagent')]
    [string]$ModelRole = 'primary',

    [string]$Agent = 'atdd-developer',

    [string]$ParentAgent = '',

    [string]$Tool = 'harness-script',

    [switch]$RebuildProjection,

    [switch]$AllowArbitraryFeatureRoot,

    [string]$SkipReasonCode = ''
)

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

$contractPath = Join-Path $PSScriptRoot 'lib\HarnessContract.psm1'
$journalPath = Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1'
$projectionPath = Join-Path $PSScriptRoot 'lib\HarnessGateProjection.psm1'
Import-Module $contractPath -Force
Import-Module $journalPath -Force
Import-Module $projectionPath -Force
Test-HarnessContractModuleReady

if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
    $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot
}
$featureRootPath = Get-HarnessFeatureRoot -RepoRoot $RepoRoot -Feature $Feature -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot

if ([string]::IsNullOrWhiteSpace($ActivityId)) {
    $ActivityId = New-HarnessJournalActivityId
}
if ([string]::IsNullOrWhiteSpace($ActivityName)) {
    $ActivityName = $Action
}

$actor = New-HarnessJournalActor -Agent $Agent -ParentAgent $(if ($ParentAgent) { $ParentAgent } else { $null }) `
    -Tool $Tool -Provider $ModelProvider -Slug $ModelSlug -Role $ModelRole

$sliceArg = if ([string]::IsNullOrWhiteSpace($Slice)) { $null } else { $Slice }
$evt = $null

switch ($Action) {
    'start' {
        $evt = Start-HarnessJournalActivity -FeatureRoot $featureRootPath -Feature $Feature -Slice $sliceArg `
            -ActivityId $ActivityId -ActivityType $ActivityType -ActivityName $ActivityName `
            -Actor $actor -Summary $Summary -OccurredAt $OccurredAt
    }
    'stop' {
        $stopPayload = $null
        if (-not [string]::IsNullOrWhiteSpace($SkipReasonCode)) {
            $stopPayload = [pscustomobject]@{ skip_reason_code = $SkipReasonCode }
        }
        $evt = Stop-HarnessJournalActivity -FeatureRoot $featureRootPath -Feature $Feature -Slice $sliceArg `
            -ActivityId $ActivityId -ActivityType $ActivityType -ActivityName $ActivityName `
            -Outcome $Outcome -Actor $actor -Summary $Summary -Payload $stopPayload -OccurredAt $OccurredAt
    }
    'instant' {
        $activity = New-HarnessJournalActivity -Id $ActivityId -Type $ActivityType -Name $ActivityName -Role 'instant'
        if ([string]::IsNullOrWhiteSpace($Summary)) {
            $Summary = "$ActivityName ($Kind)"
        }
        $evt = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $sliceArg `
            -Kind $Kind -Activity $activity -Actor $actor -Outcome $(if ($Kind -eq 'ActivityStarted') { $null } else { $Outcome }) `
            -Summary $Summary -OccurredAt $OccurredAt
    }
    'adjust' {
        if ([string]::IsNullOrWhiteSpace($AmendsEventId)) {
            throw '-AmendsEventId is required for -Action adjust'
        }
        if ([string]::IsNullOrWhiteSpace($Summary)) {
            $Summary = "adjusting entry for $AmendsEventId"
        }
        $activity = New-HarnessJournalActivity -Id $ActivityId -Type $ActivityType -Name $ActivityName -Role 'adjust'
        $evt = Add-HarnessJournalAdjustingEntry -FeatureRoot $featureRootPath -Feature $Feature -Slice $sliceArg `
            -AmendsEventId $AmendsEventId -Summary $Summary -Activity $activity -Actor $actor -OccurredAt $OccurredAt
    }
}

if ($RebuildProjection) {
    Update-HarnessGateProjectionFromJournal -FeatureRoot $featureRootPath -Feature $Feature -RepoRoot $RepoRoot -Write | Out-Null
}

$evt | ConvertTo-Json -Depth 10 -Compress