param(
  [string]$ProjectRoot = "",
  [string]$TaskFile = "task.json",
  [switch]$JsonOutput
)

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

if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
  $candidateRoots = @(
    [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\..")),
    [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\..\..\..\.."))
  )

  foreach ($candidateRoot in $candidateRoots | Select-Object -Unique) {
    if (Test-Path -LiteralPath (Join-Path $candidateRoot "task.json")) {
      $ProjectRoot = $candidateRoot
      break
    }
  }

  if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
    $ProjectRoot = $candidateRoots[0]
  }
}

$script:Findings = New-Object System.Collections.Generic.List[object]

function Get-ObjectPropertyValue {
  param(
    [object]$InputObject,
    [string]$Name,
    [object]$Default = $null
  )

  if ($null -eq $InputObject) {
    return $Default
  }

  $property = $InputObject.PSObject.Properties[$Name]
  if ($null -eq $property) {
    return $Default
  }

  return $property.Value
}

function ConvertTo-StringArray {
  param([object]$Value)

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

  if ($Value -is [string]) {
    if ([string]::IsNullOrWhiteSpace($Value)) {
      return @()
    }

    return @([string]$Value)
  }

  $items = @()
  foreach ($item in $Value) {
    if ($null -ne $item -and -not [string]::IsNullOrWhiteSpace([string]$item)) {
      $items += [string]$item
    }
  }

  return $items
}

function Add-Finding {
  param(
    [string]$Severity,
    [string]$Code,
    [string]$Message,
    [string]$TaskId = "",
    [string]$Field = ""
  )

  $script:Findings.Add([pscustomobject]@{
      severity = $Severity
      code = $Code
      message = $Message
      task_id = $TaskId
      field = $Field
    })
}

function Read-JsonFileOrNull {
  param([string]$Path)

  try {
    return (Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json)
  }
  catch {
    return $null
  }
}

function Get-TaskList {
  param([object]$TaskDocument)

  $tasks = Get-ObjectPropertyValue -InputObject $TaskDocument -Name "tasks" -Default $null
  if ($null -eq $tasks -or ($tasks -is [string]) -or (-not ($tasks -is [System.Collections.IEnumerable]))) {
    return @()
  }

  return @($tasks)
}

function Test-FileExists {
  param(
    [string]$Root,
    [string]$RelativePath
  )

  return (Test-Path -LiteralPath (Join-Path $Root $RelativePath))
}

function Test-IsFormalTaskQueue {
  param([object[]]$Tasks)

  foreach ($task in @($Tasks)) {
    $taskKind = [string](Get-ObjectPropertyValue -InputObject $task -Name "task_kind" -Default "")
    if (@("feature_research", "feature_spec", "feature_design", "feature_plan", "testcase_design", "feature_impl", "release", "archive") -contains $taskKind) {
      return $true
    }
  }

  return $false
}

function Test-ApprovedDecompositionExemption {
  param(
    [object]$Task,
    [string]$Scope
  )

  $exemption = Get-ObjectPropertyValue -InputObject $Task -Name "decomposition_exemption" -Default $null
  if ($null -eq $exemption) {
    return $false
  }

  $approved = [bool](Get-ObjectPropertyValue -InputObject $exemption -Name "user_approved" -Default $false)
  if (-not $approved) {
    return $false
  }

  $scopes = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $exemption -Name "scopes" -Default @()))
  if ($scopes.Count -eq 0) {
    return $true
  }

  return $scopes -contains $Scope
}

function Get-OwnedSurfaceKinds {
  param([string[]]$OwnedPaths)

  $hasBackend = $false
  $hasFrontend = $false

  foreach ($rawPath in @($OwnedPaths)) {
    $path = $rawPath.Replace('/', '\').ToLowerInvariant()
    if ($path -match '(^|\\)(apps\\api|backend|server|services?)($|\\)') {
      $hasBackend = $true
    }

    if ($path -match '(^|\\)(apps\\admin|apps\\web|apps\\mobile|frontend|web|mobile|ui)($|\\)') {
      $hasFrontend = $true
    }
  }

  return [pscustomobject]@{
    backend = $hasBackend
    frontend = $hasFrontend
  }
}

function Get-DomainMatches {
  param([object]$Task)

  $description = [string](Get-ObjectPropertyValue -InputObject $Task -Name "description" -Default "")
  $steps = ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name "steps" -Default @())
  $requirementIds = ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name "requirement_ids" -Default @())
  $haystack = (($description + " " + ($steps -join " ") + " " + ($requirementIds -join " ")).ToLowerInvariant())

  $domainMatchesList = @()
  $patterns = @(
    @{ name = "order"; pattern = '(订单|order)' },
    @{ name = "payment"; pattern = '(支付|payment)' },
    @{ name = "inventory"; pattern = '(库存|inventory)' },
    @{ name = "rbac"; pattern = '(rbac|角色权限|权限角色|role[ -]?based access)' }
  )

  foreach ($entry in $patterns) {
    if ($haystack -match $entry["pattern"]) {
      $domainMatchesList += [string]$entry["name"]
    }
  }

  return @($domainMatchesList | Select-Object -Unique)
}

function Test-RequiredSourceCaseIds {
  param(
    [object]$ParentObject,
    [string]$PropertyName,
    [string]$TaskId,
    [string]$FieldBase
  )

  $target = Get-ObjectPropertyValue -InputObject $ParentObject -Name $PropertyName -Default $null
  if ($null -eq $target) {
    Add-Finding -Severity "error" -Code "missing_field" -Message "任务缺少 $FieldBase.$PropertyName。" -TaskId $TaskId -Field "$FieldBase.$PropertyName"
    return
  }

  $sourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $target -Name "source_case_ids" -Default @()))
  if ($sourceCaseIds.Count -eq 0) {
    Add-Finding -Severity "error" -Code "empty_source_case_ids" -Message "任务缺少 $FieldBase.$PropertyName.source_case_ids。" -TaskId $TaskId -Field "$FieldBase.$PropertyName.source_case_ids"
  }
}

function Test-RequiredBusinessGoalFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $requiredStringFields = @(
    "business_goal",
    "problem_statement",
    "not_doing_impact",
    "business_priority",
    "business_priority_reason"
  )

  foreach ($fieldName in $requiredStringFields) {
    $value = [string](Get-ObjectPropertyValue -InputObject $Task -Name $fieldName -Default "")
    if ([string]::IsNullOrWhiteSpace($value)) {
      Add-Finding -Severity "error" -Code "missing_business_goal_field" -Message "正式任务缺少 $fieldName。" -TaskId $TaskId -Field $fieldName
    }
  }

  $requiredArrayFields = @("beneficiary", "success_metrics")
  foreach ($fieldName in $requiredArrayFields) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name $fieldName -Default @()))
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_business_goal_field" -Message "正式任务缺少 $fieldName。" -TaskId $TaskId -Field $fieldName
    }
  }
}

function Test-RequiredProductSpecSummary {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $productSpecSummary = Get-ObjectPropertyValue -InputObject $Task -Name "product_spec_summary" -Default $null
  if ($null -eq $productSpecSummary) {
    Add-Finding -Severity "error" -Code "missing_product_spec_summary" -Message "正式任务缺少 product_spec_summary。" -TaskId $TaskId -Field "product_spec_summary"
    return
  }

  $requiredArrayFields = @(
    "user_roles",
    "user_scenarios",
    "main_flow",
    "exception_flows",
    "inputs",
    "outputs",
    "state_changes",
    "permission_rules",
    "boundary_conditions",
    "non_goals",
    "existing_behavior_relation"
  )

  foreach ($fieldName in $requiredArrayFields) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $productSpecSummary -Name $fieldName -Default @()))
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_product_spec_field" -Message "正式任务缺少 product_spec_summary.$fieldName。" -TaskId $TaskId -Field "product_spec_summary.$fieldName"
    }
  }
}

function Test-RequiredDomainModelFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  foreach ($fieldName in @("domain_entities", "state_model", "business_rules", "invariants", "domain_logic_locations")) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name $fieldName -Default @()))
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_domain_field" -Message "正式任务缺少 $fieldName。" -TaskId $TaskId -Field $fieldName
    }
  }

  $domainModelSummary = Get-ObjectPropertyValue -InputObject $Task -Name "domain_model_summary" -Default $null
  if ($null -eq $domainModelSummary) {
    Add-Finding -Severity "error" -Code "missing_domain_summary" -Message "正式任务缺少 domain_model_summary。" -TaskId $TaskId -Field "domain_model_summary"
    return
  }

  foreach ($fieldName in @("entities", "state_transitions", "business_rules", "invariants", "existing_logic_locations")) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $domainModelSummary -Name $fieldName -Default @()))
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_domain_summary_field" -Message "正式任务缺少 domain_model_summary.$fieldName。" -TaskId $TaskId -Field "domain_model_summary.$fieldName"
    }
  }
}

function Test-RequiredAcceptanceFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $acceptance = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name "acceptance" -Default @()))
  if ($acceptance.Count -eq 0) {
    Add-Finding -Severity "error" -Code "missing_acceptance" -Message "正式任务缺少 acceptance 条目。" -TaskId $TaskId -Field "acceptance"
  }
}

function Test-RequiredArchitectureBoundaryFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $ownedPaths = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name "owned_paths" -Default @()))
  if ($ownedPaths.Count -eq 0) {
    Add-Finding -Severity "error" -Code "missing_boundary_field" -Message "正式任务缺少 owned_paths。" -TaskId $TaskId -Field "owned_paths"
  }

  foreach ($fieldName in @("forbidden_paths", "allowed_dependencies")) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name $fieldName -Default @()))
    if ($items.Count -eq 0 -and $fieldName -eq "forbidden_paths") {
      Add-Finding -Severity "error" -Code "missing_boundary_field" -Message "正式任务缺少 forbidden_paths。" -TaskId $TaskId -Field $fieldName
    }
  }
}

function Test-RequiredCoreContractFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  foreach ($fieldName in @("id", "title", "description", "task_kind", "type", "priority", "passes", "context_files", "owned_paths", "forbidden_paths", "required_truth_sources", "produces_artifacts", "acceptance", "test_command")) {
    $value = Get-ObjectPropertyValue -InputObject $Task -Name $fieldName -Default $null
    $isEmptyArray = ($value -is [System.Collections.IEnumerable] -and $value -isnot [string] -and @($value).Count -eq 0)
    $isEmptyString = ($value -is [string] -and [string]::IsNullOrWhiteSpace([string]$value))
    if ($null -eq $value -or $isEmptyArray -or $isEmptyString) {
      Add-Finding -Severity "error" -Code "missing_core_contract_field" -Message "正式任务缺少 $fieldName。" -TaskId $TaskId -Field $fieldName
    }
  }

}

function Test-RequiredHighRiskFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $taskKind = [string](Get-ObjectPropertyValue -InputObject $Task -Name "task_kind" -Default "")
  if (@("feature_impl", "release") -notcontains $taskKind) {
    return
  }

  foreach ($fieldName in @("depth", "risk", "human_approval_required", "rollback_plan_required", "security_review_required", "migration_review_required")) {
    $value = Get-ObjectPropertyValue -InputObject $Task -Name $fieldName -Default $null
    if ($null -eq $value -or ($value -is [string] -and [string]::IsNullOrWhiteSpace([string]$value))) {
      Add-Finding -Severity "error" -Code "missing_high_risk_field" -Message "高风险任务缺少 $fieldName。" -TaskId $TaskId -Field $fieldName
    }
  }
}

function Test-RequiredImpactAnalysisFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $taskKind = [string](Get-ObjectPropertyValue -InputObject $Task -Name "task_kind" -Default "")
  $depth = [string](Get-ObjectPropertyValue -InputObject $Task -Name "depth" -Default "")
  $requiresImpact = (@("feature_impl", "release") -contains $taskKind) -or (@("deep", "critical") -contains $depth)
  if (-not $requiresImpact) {
    return
  }

  $impactAnalysisRequired = Get-ObjectPropertyValue -InputObject $Task -Name "impact_analysis_required" -Default $null
  if ($impactAnalysisRequired -ne $true) {
    Add-Finding -Severity "error" -Code "missing_impact_requirement" -Message "需要影响面分析的任务缺少 impact_analysis_required=true。" -TaskId $TaskId -Field "impact_analysis_required"
  }

  $impactSummary = Get-ObjectPropertyValue -InputObject $Task -Name "impact_analysis_summary" -Default $null
  if ($null -eq $impactSummary) {
    Add-Finding -Severity "error" -Code "missing_impact_summary" -Message "需要影响面分析的任务缺少 impact_analysis_summary。" -TaskId $TaskId -Field "impact_analysis_summary"
    return
  }

  foreach ($fieldName in @(
      "relevant_files",
      "call_chain",
      "data_flow",
      "state_flow",
      "affected_apis",
      "affected_ui_user_paths",
      "affected_tests",
      "compatibility_risks",
      "security_permission_risks",
      "performance_risks",
      "concurrency_transaction_risks",
      "migration_risks",
      "rollback_considerations",
      "owned_paths_coverage"
    )) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $impactSummary -Name $fieldName -Default @()))
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_impact_summary_field" -Message "impact_analysis_summary 缺少 $fieldName。" -TaskId $TaskId -Field "impact_analysis_summary.$fieldName"
    }
  }
}

function Test-RequiredAcceptanceCaseArrays {
  param(
    [object]$QaContract,
    [string]$TaskId
  )

  foreach ($fieldName in @("positive_cases", "negative_cases", "boundary_cases", "regression_cases", "permission_cases", "security_cases")) {
    $items = @(Get-ObjectPropertyValue -InputObject $QaContract -Name $fieldName -Default @())
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_acceptance_case_group" -Message "feature_impl 缺少 qa_contract.$fieldName。" -TaskId $TaskId -Field "qa_contract.$fieldName"
      continue
    }

    foreach ($item in @($items)) {
      $acceptanceText = [string](Get-ObjectPropertyValue -InputObject $item -Name "acceptance" -Default "")
      if ([string]::IsNullOrWhiteSpace($acceptanceText)) {
        Add-Finding -Severity "error" -Code "missing_acceptance_case_mapping" -Message "qa_contract.$fieldName 缺少 acceptance 映射。" -TaskId $TaskId -Field "qa_contract.$fieldName.acceptance"
      }

      $sourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $item -Name "source_case_ids" -Default @()))
      if ($sourceCaseIds.Count -eq 0) {
        Add-Finding -Severity "error" -Code "missing_acceptance_case_mapping" -Message "qa_contract.$fieldName 缺少 source_case_ids。" -TaskId $TaskId -Field "qa_contract.$fieldName.source_case_ids"
      }

      $verification = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $item -Name "verification" -Default @()))
      if ($verification.Count -eq 0) {
        Add-Finding -Severity "error" -Code "missing_acceptance_case_mapping" -Message "qa_contract.$fieldName 缺少 verification。" -TaskId $TaskId -Field "qa_contract.$fieldName.verification"
      }
    }
  }
}

function Test-RequiredTestStrategyFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $taskKind = [string](Get-ObjectPropertyValue -InputObject $Task -Name "task_kind" -Default "")
  if (@("feature_impl", "release") -notcontains $taskKind) {
    return
  }

  $requiredFlag = Get-ObjectPropertyValue -InputObject $Task -Name "test_strategy_required" -Default $null
  if ($requiredFlag -ne $true) {
    Add-Finding -Severity "error" -Code "missing_test_strategy_flag" -Message "实现任务缺少 test_strategy_required=true。" -TaskId $TaskId -Field "test_strategy_required"
  }

  $testPlanSummary = Get-ObjectPropertyValue -InputObject $Task -Name "test_plan_summary" -Default $null
  if ($null -eq $testPlanSummary) {
    Add-Finding -Severity "error" -Code "missing_test_plan_summary" -Message "实现任务缺少 test_plan_summary。" -TaskId $TaskId -Field "test_plan_summary"
    return
  }

  foreach ($fieldName in @("acceptance_to_test_mapping", "positive_cases", "negative_cases", "boundary_cases", "regression_cases", "permission_security_cases", "affected_tests", "verification_commands", "evidence_plan")) {
    $items = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $testPlanSummary -Name $fieldName -Default @()))
    if ($items.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_test_plan_field" -Message "test_plan_summary 缺少 $fieldName。" -TaskId $TaskId -Field "test_plan_summary.$fieldName"
    }
  }
}

function Test-AcceptanceTestMatrix {
  param(
    [object]$QaContract,
    [string]$TaskId,
    [string]$FieldPrefix = "qa_contract"
  )

  $matrix = @(Get-ObjectPropertyValue -InputObject $QaContract -Name "acceptance_test_matrix" -Default @())
  if ($matrix.Count -eq 0) {
    Add-Finding -Severity "error" -Code "missing_acceptance_matrix" -Message "任务缺少 qa_contract.acceptance_test_matrix。" -TaskId $TaskId -Field "$FieldPrefix.acceptance_test_matrix"
    return
  }

  foreach ($item in @($matrix)) {
    $acceptanceText = [string](Get-ObjectPropertyValue -InputObject $item -Name "acceptance" -Default "")
    if ([string]::IsNullOrWhiteSpace($acceptanceText)) {
      Add-Finding -Severity "error" -Code "missing_acceptance_matrix_entry" -Message "acceptance_test_matrix 缺少 acceptance。" -TaskId $TaskId -Field "$FieldPrefix.acceptance_test_matrix.acceptance"
    }

    $verification = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $item -Name "verification" -Default @()))
    if ($verification.Count -eq 0) {
      Add-Finding -Severity "error" -Code "missing_acceptance_matrix_entry" -Message "acceptance_test_matrix 缺少 verification。" -TaskId $TaskId -Field "$FieldPrefix.acceptance_test_matrix.verification"
    }
  }
}

function Test-ReleaseQaContractFields {
  param(
    [object]$Task,
    [string]$TaskId
  )

  $qaContract = Get-ObjectPropertyValue -InputObject $Task -Name "qa_contract" -Default $null
  if ($null -eq $qaContract) {
    Add-Finding -Severity "error" -Code "missing_qa_contract" -Message "release 缺少 qa_contract。" -TaskId $TaskId -Field "qa_contract"
    return
  }

  foreach ($fieldName in @("positive_cases", "negative_cases", "boundary_cases", "regression_cases", "permission_cases", "security_cases", "risk_checks", "acceptance_validation")) {
    $value = Get-ObjectPropertyValue -InputObject $qaContract -Name $fieldName -Default $null
    $isEmptyArray = ($value -is [System.Collections.IEnumerable] -and $value -isnot [string] -and @($value).Count -eq 0)
    if ($null -eq $value -or $isEmptyArray) {
      Add-Finding -Severity "error" -Code "missing_release_acceptance_field" -Message "release 缺少 qa_contract.$fieldName。" -TaskId $TaskId -Field "qa_contract.$fieldName"
    }
  }

  $blockedIfMissing = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $qaContract -Name "blocked_if_missing" -Default @()))
  if ($blockedIfMissing.Count -eq 0) {
    Add-Finding -Severity "error" -Code "missing_release_acceptance_field" -Message "release 缺少 qa_contract.blocked_if_missing。" -TaskId $TaskId -Field "qa_contract.blocked_if_missing"
  }

  Test-RequiredAcceptanceCaseArrays -QaContract $qaContract -TaskId $TaskId
  Test-AcceptanceTestMatrix -QaContract $qaContract -TaskId $TaskId
}

function Test-FormalTaskQueueStructure {
  param(
    [string]$Root,
    [object[]]$Tasks
  )

  $taskIdSet = @{}
  foreach ($task in @($Tasks)) {
    $taskId = [string](Get-ObjectPropertyValue -InputObject $task -Name "id" -Default "")
    if (-not [string]::IsNullOrWhiteSpace($taskId)) {
      $taskIdSet[$taskId] = $true
    }
  }

  foreach ($requiredTaskId in @("ANALYSIS-001", "TESTCASE-001", "PLAN-001")) {
    if (-not $taskIdSet.ContainsKey($requiredTaskId)) {
      Add-Finding -Severity "error" -Code "missing_phase_task" -Message "正式任务队列缺少必需 phase：$requiredTaskId。" -Field "tasks"
    }
  }

  $featureImplTasks = @($Tasks | Where-Object { [string](Get-ObjectPropertyValue -InputObject $_ -Name "task_kind" -Default "") -eq "feature_impl" })
  $releaseTasks = @($Tasks | Where-Object { [string](Get-ObjectPropertyValue -InputObject $_ -Name "task_kind" -Default "") -eq "release" })

  if (($featureImplTasks.Count + $releaseTasks.Count) -gt 0) {
    $requiredTestingArtifacts = @(
      "docs\testing\ACCEPTANCE_CRITERIA.md",
      "docs\testing\NATURAL_LANGUAGE_TEST_CASES.md",
      "docs\testing\TRACEABILITY_MATRIX.md",
      "docs\testing\TEST_DATA_MATRIX.md",
      "docs\testing\REGRESSION_PLAN.md",
      "docs\testing\verify-matrix.md"
    )

    foreach ($relativePath in $requiredTestingArtifacts) {
      if (-not (Test-FileExists -Root $Root -RelativePath $relativePath)) {
        Add-Finding -Severity "error" -Code "missing_testing_artifact" -Message "正式任务队列缺少测试真相源：$relativePath。" -Field $relativePath
      }
    }
  }

  foreach ($task in $featureImplTasks) {
    $taskId = [string](Get-ObjectPropertyValue -InputObject $task -Name "id" -Default "")
    $qaContract = Get-ObjectPropertyValue -InputObject $task -Name "qa_contract" -Default $null
    if ($null -eq $qaContract) {
      Add-Finding -Severity "error" -Code "missing_qa_contract" -Message "feature_impl 缺少 qa_contract。" -TaskId $taskId -Field "qa_contract"
    }
    else {
      Test-RequiredTestStrategyFields -Task $task -TaskId $taskId
      $requiredLayers = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $qaContract -Name "required_layers" -Default @()))
      foreach ($layer in @("unit_or_component", "contract_or_api", "story_full_chain", "affected_regression")) {
        if ($requiredLayers -notcontains $layer) {
          Add-Finding -Severity "error" -Code "missing_required_layer" -Message "feature_impl 缺少 qa_contract.required_layers[$layer]。" -TaskId $taskId -Field "qa_contract.required_layers"
        }
      }
      Test-RequiredAcceptanceCaseArrays -QaContract $qaContract -TaskId $taskId
      Test-AcceptanceTestMatrix -QaContract $qaContract -TaskId $taskId

      $developmentValidation = Get-ObjectPropertyValue -InputObject $qaContract -Name "development_validation" -Default $null
      if ($null -eq $developmentValidation) {
        Add-Finding -Severity "error" -Code "missing_development_validation" -Message "feature_impl 缺少 qa_contract.development_validation。" -TaskId $taskId -Field "qa_contract.development_validation"
      }

      $acceptanceValidation = Get-ObjectPropertyValue -InputObject $qaContract -Name "acceptance_validation" -Default $null
      if ($null -eq $acceptanceValidation) {
        Add-Finding -Severity "error" -Code "missing_acceptance_validation" -Message "feature_impl 缺少 qa_contract.acceptance_validation。" -TaskId $taskId -Field "qa_contract.acceptance_validation"
      }

      $tddContract = Get-ObjectPropertyValue -InputObject $qaContract -Name "tdd_contract" -Default $null
      if ($null -eq $tddContract) {
        Add-Finding -Severity "error" -Code "missing_tdd_contract" -Message "feature_impl 缺少 qa_contract.tdd_contract。" -TaskId $taskId -Field "qa_contract.tdd_contract"
      }
      else {
        $red = Get-ObjectPropertyValue -InputObject $tddContract -Name "red" -Default $null
        if ($null -eq $red) {
          Add-Finding -Severity "error" -Code "missing_tdd_red" -Message "feature_impl 缺少 qa_contract.tdd_contract.red。" -TaskId $taskId -Field "qa_contract.tdd_contract.red"
        }
        else {
          $sourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $red -Name "source_case_ids" -Default @()))
          if ($sourceCaseIds.Count -eq 0) {
            Add-Finding -Severity "error" -Code "empty_source_case_ids" -Message "feature_impl 缺少 qa_contract.tdd_contract.red.source_case_ids。" -TaskId $taskId -Field "qa_contract.tdd_contract.red.source_case_ids"
          }
        }

        foreach ($requiredField in @("green", "refactor_guard")) {
          if ($null -eq (Get-ObjectPropertyValue -InputObject $tddContract -Name $requiredField -Default $null)) {
            Add-Finding -Severity "error" -Code "missing_tdd_field" -Message "feature_impl 缺少 qa_contract.tdd_contract.$requiredField。" -TaskId $taskId -Field "qa_contract.tdd_contract.$requiredField"
          }
        }
      }

      Test-RequiredSourceCaseIds -ParentObject $qaContract -PropertyName "story_full_chain" -TaskId $taskId -FieldBase "qa_contract"
      if ($null -ne $acceptanceValidation) {
        $acceptanceSourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $acceptanceValidation -Name "source_case_ids" -Default @()))
        if ($acceptanceSourceCaseIds.Count -eq 0) {
          Add-Finding -Severity "error" -Code "empty_source_case_ids" -Message "feature_impl 缺少 qa_contract.acceptance_validation.source_case_ids。" -TaskId $taskId -Field "qa_contract.acceptance_validation.source_case_ids"
        }
      }
    }

    $requirementIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $task -Name "requirement_ids" -Default @()))
    if ($requirementIds.Count -gt 3 -and -not (Test-ApprovedDecompositionExemption -Task $task -Scope "requirement_span")) {
      Add-Finding -Severity "error" -Code "oversized_requirement_span" -Message "单个 feature_impl 默认不得覆盖超过 3 条主 requirement_ids。" -TaskId $taskId -Field "requirement_ids"
    }

    $ownedPaths = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $task -Name "owned_paths" -Default @()))
    $surfaceKinds = Get-OwnedSurfaceKinds -OwnedPaths $ownedPaths
    if ($surfaceKinds.backend -and $surfaceKinds.frontend -and -not (Test-ApprovedDecompositionExemption -Task $task -Scope "mixed_surface")) {
      Add-Finding -Severity "error" -Code "mixed_frontend_backend_story" -Message "feature_impl 同时覆盖前端与后端路径；请拆分为独立 story 或改为明确的 release 收口。" -TaskId $taskId -Field "owned_paths"
    }

    $domains = @(Get-DomainMatches -Task $task)
    if ($domains.Count -gt 1 -and -not (Test-ApprovedDecompositionExemption -Task $task -Scope "domain_bundle")) {
      Add-Finding -Severity "error" -Code "combined_high_risk_domains" -Message ("feature_impl 同时覆盖高风险主域：{0}。订单、支付、库存、RBAC 必须拆独立任务。" -f ($domains -join ", ")) -TaskId $taskId -Field "description"
    }
  }

  foreach ($task in $releaseTasks) {
    $taskId = [string](Get-ObjectPropertyValue -InputObject $task -Name "id" -Default "")
    Test-RequiredTestStrategyFields -Task $task -TaskId $taskId
    Test-ReleaseQaContractFields -Task $task -TaskId $taskId
  }

  foreach ($task in @($Tasks)) {
    $taskId = [string](Get-ObjectPropertyValue -InputObject $task -Name "id" -Default "")
    $taskKind = [string](Get-ObjectPropertyValue -InputObject $task -Name "task_kind" -Default "")
    if (@("feature_research", "feature_spec", "feature_design", "feature_plan", "testcase_design", "feature_impl", "release", "archive") -contains $taskKind) {
      Test-RequiredCoreContractFields -Task $task -TaskId $taskId
      Test-RequiredBusinessGoalFields -Task $task -TaskId $taskId
      Test-RequiredAcceptanceFields -Task $task -TaskId $taskId
      Test-RequiredArchitectureBoundaryFields -Task $task -TaskId $taskId
      Test-RequiredHighRiskFields -Task $task -TaskId $taskId
      Test-RequiredImpactAnalysisFields -Task $task -TaskId $taskId
      if (@("feature_research", "feature_spec", "feature_design", "feature_plan", "testcase_design", "feature_impl", "release") -contains $taskKind) {
        Test-RequiredProductSpecSummary -Task $task -TaskId $taskId
        Test-RequiredDomainModelFields -Task $task -TaskId $taskId
      }
    }
  }
}

$resolvedProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path
$taskPath = Join-Path $resolvedProjectRoot $TaskFile

if (-not (Test-Path -LiteralPath $taskPath)) {
  Add-Finding -Severity "error" -Code "missing_task_file" -Message "缺少 task.json：$TaskFile。" -Field $TaskFile
}
else {
  $taskDocument = Read-JsonFileOrNull -Path $taskPath
  if ($null -eq $taskDocument) {
    Add-Finding -Severity "error" -Code "invalid_task_json" -Message "task.json 不是合法 JSON。" -Field $TaskFile
  }
  else {
    $rawTasks = Get-ObjectPropertyValue -InputObject $taskDocument -Name "tasks" -Default @()
    $tasks = @($rawTasks)
    if ($tasks.Count -eq 0) {
      Add-Finding -Severity "error" -Code "empty_task_queue" -Message "task.json 不包含可校验任务。" -Field "tasks"
    }
    elseif (Test-IsFormalTaskQueue -Tasks $tasks) {
      Test-FormalTaskQueueStructure -Root $resolvedProjectRoot -Tasks $tasks
    }
    else {
      $smokeLikeTasks = @($tasks | Where-Object {
          @("smoke", "harness") -contains [string](Get-ObjectPropertyValue -InputObject $_ -Name "task_kind" -Default "")
        })
      if ($smokeLikeTasks.Count -eq 0) {
        Add-Finding -Severity "error" -Code "non_formal_task_queue" -Message "task.json 存在任务，但既不是正式任务队列，也不是合法 smoke/harness 队列。" -Field "tasks"
      }
    }
  }
}

$errors = @($script:Findings | Where-Object { $_.severity -eq "error" })
$warnings = @($script:Findings | Where-Object { $_.severity -eq "warning" })
$status = if ($errors.Count -gt 0) { "fail" } elseif ($warnings.Count -gt 0) { "warn" } else { "pass" }
$result = @{
  status = $status
  error_count = $errors.Count
  warning_count = $warnings.Count
  findings = @($script:Findings.ToArray())
}

if ($JsonOutput) {
  $result | ConvertTo-Json -Depth 8
}
else {
  if ($status -eq "fail") {
    Write-Output "Task structure validation failed."
  }
  elseif ($status -eq "warn") {
    Write-Output "Task structure validation passed with warnings."
  }
  else {
    Write-Output "Task structure validation passed."
  }

  foreach ($finding in $script:Findings.ToArray()) {
    $taskLabel = if ([string]::IsNullOrWhiteSpace([string]$finding.task_id)) { "" } else { " task=$($finding.task_id)" }
    $fieldLabel = if ([string]::IsNullOrWhiteSpace([string]$finding.field)) { "" } else { " field=$($finding.field)" }
    Write-Output "- [$($finding.severity)] $($finding.code)$taskLabel$fieldLabel :: $($finding.message)"
  }
}

if ($status -eq "fail") {
  exit 1
}

exit 0
