Payload/scripts/merge-harness-to-kaden.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Surgically merge matured harness surfaces from this child into a local Kaden clone. .DESCRIPTION Layout contract (post scripts/harness/ restructure): | Source (this repo) | Kaden destination | |---------------------------------|-----------------------------------------------------------| | scripts/harness/** | templates/scripts/harness/** AND scripts/harness/** | | pwsh -File scripts/harness-invoke.ps1 | templates/scripts/ + scripts/ (stable entry point) | | scripts/verify-local.ps1 | templates/scripts/ + scripts/ (stable entry point) | | scripts/lift|merge-*.ps1 | templates/scripts/ + scripts/ (promote tooling) | | docs/kaden/** | docs/kaden/** | | tests/harness/** | tests/harness/** | | selected .github skills/evals | .github/... | Removes obsolete flat copies under templates/scripts/ and scripts/ that moved into scripts/harness/ (harness-*.ps1, lib/, lint-*.mjs, etc.). Prefer UTF-8 Evidence-First inject on template agents over wholesale agent copy. Does NOT commit. Does NOT delete attribution/README files. .EXAMPLE .\scripts\merge-harness-to-kaden.ps1 -TemplateRoot $env:USERPROFILE\Repos_local\kaden #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)] [string]$TemplateRoot, [string]$SourceRoot = '' ) $ErrorActionPreference = 'Stop' $utf8NoBom = New-Object System.Text.UTF8Encoding $false if ([string]::IsNullOrWhiteSpace($SourceRoot)) { $SourceRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path } $TemplateRoot = (Resolve-Path $TemplateRoot).Path $EfBullet = '- Follow [Evidence-First Work Ethic](../skills/evidence-first-work-ethic/SKILL.md) - truth over pleasing; inspect before asserting; verify before claiming done; block instead of guessing.' function New-HarnessLiftDirectory { [CmdletBinding(SupportsShouldProcess = $true)] param([Parameter(Mandatory = $true)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { if ($PSCmdlet.ShouldProcess($Path, 'Create directory')) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } } } function Write-Utf8File([string]$Path, [string]$Content) { [IO.File]::WriteAllText($Path, $Content, $utf8NoBom) } function Copy-HarnessLiftFile { [CmdletBinding(SupportsShouldProcess = $true)] param([string]$Rel, [string]$DestRel = '') if ([string]::IsNullOrWhiteSpace($DestRel)) { $DestRel = $Rel } $src = Join-Path $SourceRoot $Rel $dst = Join-Path $TemplateRoot $DestRel if (-not (Test-Path -LiteralPath $src)) { Write-Warning "Skip missing: $Rel" return } if (-not $PSCmdlet.ShouldProcess($dst, "Copy $Rel")) { return } New-HarnessLiftDirectory (Split-Path $dst -Parent) Copy-Item -LiteralPath $src -Destination $dst -Force Write-Output "OK file $Rel -> $DestRel" } function Copy-HarnessLiftDirectory { [CmdletBinding(SupportsShouldProcess = $true)] param([string]$Rel, [string]$DestRel = '') if ([string]::IsNullOrWhiteSpace($DestRel)) { $DestRel = $Rel } $src = Join-Path $SourceRoot $Rel $dst = Join-Path $TemplateRoot $DestRel if (-not (Test-Path -LiteralPath $src)) { Write-Warning "Skip missing dir: $Rel" return } if (-not $PSCmdlet.ShouldProcess($dst, "Copy dir $Rel")) { return } New-HarnessLiftDirectory (Split-Path $dst -Parent) if (Test-Path -LiteralPath $dst) { Remove-Item -LiteralPath $dst -Recurse -Force } Copy-Item -LiteralPath $src -Destination $dst -Recurse -Force Write-Output "OK dir $Rel -> $DestRel" } function Remove-ObsoleteFlatHarnessFile { [CmdletBinding(SupportsShouldProcess = $true)] param([Parameter(Mandatory = $true)][string]$Rel) $dst = Join-Path $TemplateRoot $Rel if (-not (Test-Path -LiteralPath $dst)) { return } if ($PSCmdlet.ShouldProcess($dst, 'Remove obsolete flat harness copy')) { Remove-Item -LiteralPath $dst -Force -Recurse Write-Output "RM obsolete $Rel" } } function Add-HarnessEvidenceFirstConstraint { [CmdletBinding(SupportsShouldProcess = $true)] param([Parameter(Mandatory = $true)][string]$AgentPath) $text = [IO.File]::ReadAllText($AgentPath) if ($text -match 'Evidence-First Work Ethic') { Write-Output "skip EF (present): $(Split-Path $AgentPath -Leaf)" return } $patterns = @( '(?m)^(## Constraints(?: and Boundaries)?\r?\n\r?\n)', '(?m)^(## Constraints(?: and Boundaries)?\r?\n)', '(?m)^(## Operating Rules\r?\n\r?\n)', '(?m)^(## Operating Rules\r?\n)', '(?m)^(## Do / Don''t\r?\n\r?\n)', '(?m)^(## Do / Don''t\r?\n)' ) $updated = $false foreach ($pat in $patterns) { if ($text -match $pat) { $text = [regex]::Replace($text, $pat, ('$1' + $EfBullet + "`r`n"), 1) $updated = $true break } } if (-not $updated) { Write-Warning "No Constraints heading: $(Split-Path $AgentPath -Leaf)" return } if (-not $PSCmdlet.ShouldProcess($AgentPath, 'Inject Evidence-First Constraints')) { return } Write-Utf8File $AgentPath $text Write-Output "OK EF inject: $(Split-Path $AgentPath -Leaf)" } Write-Output "Source: $SourceRoot" Write-Output "Kaden: $TemplateRoot" # --- Skills (mac ahead) --- foreach ($skill in @( 'evidence-first-work-ethic', 'repo-agent-basics', 'artifact-paths', 'review-loop-contract', 'harness-operator-guide', 'agent-evals', 'agent-skill-authoring', 'sub-agent-communication-protocol', 'agentic-spending-governance', 'model-routing', 'code-review' )) { Copy-HarnessLiftDirectory ".github\skills\$skill" } Copy-HarnessLiftFile '.github\agents\feature-orchestrator.agent.md' # --- Evals (fixtures only; skip captures / last-eval runtime) --- New-HarnessLiftDirectory (Join-Path $TemplateRoot '.github\evals\skills') foreach ($ev in @('evidence-first-work-ethic', 'repo-agent-basics', 'artifact-paths')) { Copy-HarnessLiftDirectory ".github\evals\skills\$ev" } Copy-HarnessLiftFile '.github\evals\README.md' # --- Instructions / copilot / prompts --- Copy-HarnessLiftFile '.github\instructions\evidence-first.instructions.md' Copy-HarnessLiftFile '.github\copilot-instructions.md' foreach ($p in @('feature-start.prompt.md', 'feature-resume.prompt.md', 'agent-eval.prompt.md')) { Copy-HarnessLiftFile ".github\prompts\$p" } # --- Templates (hooks + review-loop) --- New-HarnessLiftDirectory (Join-Path $TemplateRoot '.github\templates') New-HarnessLiftDirectory (Join-Path $TemplateRoot 'templates') Copy-HarnessLiftFile '.github\templates\cursor-hooks.json' Copy-HarnessLiftFile '.github\templates\cursor-hooks.json' 'templates\cursor-hooks.json' Copy-HarnessLiftFile '.github\templates\copilot-hooks.json' Copy-HarnessLiftFile '.github\templates\copilot-hooks.json' 'templates\copilot-hooks.json' Copy-HarnessLiftFile '.github\templates\review-loop.json' 'templates\review-loop.json' Copy-HarnessLiftFile '.github\templates\harness-requirements.json' 'templates\harness-requirements.json' Copy-HarnessLiftFile '.github\templates\review-loop.json' '.github\templates\review-loop.json' Copy-HarnessLiftFile '.github\templates\harness-requirements.json' '.github\templates\harness-requirements.json' Copy-HarnessLiftFile '.github\templates\handoff-slice.md' 'templates\handoff-slice.md' Copy-HarnessLiftFile '.github\templates\handoff-slice.md' # --- Kaden operator docs (child-safe folder; does not overwrite child docs root) --- Copy-HarnessLiftDirectory 'docs\kaden' 'docs\kaden' # ADR 0003 lives in the Kaden clone only; copy if the child still has it. Copy-HarnessLiftFile 'docs\adr\0003-harness-event-journal-and-gate-projection.md' # --- Rules (flat in template; pull maps to 00-from-template) --- $rulesDir = Join-Path $TemplateRoot '.cursor\rules' New-HarnessLiftDirectory $rulesDir $ruleMap = @{ 'evidence-first-work-ethic.mdc' = 'evidence-first-work-ethic.mdc' 'core-philosophy.mdc' = 'core-philosophy.mdc' 'agent-ecosystem.mdc' = 'agent-ecosystem.mdc' 'agentic-spending-governance.mdc' = 'agentic-spending-governance.mdc' 'local-verify-gate.mdc' = 'local-verify-gate.mdc' 'development-workflow.mdc' = 'development-workflow.mdc' } foreach ($kv in $ruleMap.GetEnumerator()) { Copy-HarnessLiftFile ".cursor\rules\00-from-template\$($kv.Key)" ".cursor\rules\$($kv.Value)" } # --- Harness core tree (read-only in children) --- Copy-HarnessLiftDirectory 'scripts\harness' 'templates\scripts\harness' Copy-HarnessLiftDirectory 'scripts\harness' 'scripts\harness' # --- Stable entry points + promote tooling (scripts/ root) --- foreach ($f in @( 'harness-invoke.ps1', 'verify-local.ps1', 'lift-harness-to-kaden.ps1', 'merge-harness-to-kaden.ps1' )) { Copy-HarnessLiftFile "scripts\$f" "templates\scripts\$f" Copy-HarnessLiftFile "scripts\$f" "scripts\$f" } # --- Obsolete flat copies (pre-restructure layout) --- $obsoleteFlat = @( 'harness-dispatch-preflight.ps1', 'harness-health.ps1', 'harness-journal.ps1', 'harness-own-slice-status.ps1', 'harness-own-slice.ps1', 'harness-persist-quality.ps1', 'harness-pretooluse-guard.ps1', 'harness-record-handoff.ps1', 'harness-record-phase.ps1', 'harness-record-review.ps1', 'harness-record-slice-verdict.ps1', 'harness-record-task-step.ps1', 'harness-record-task.ps1', 'harness-relay.ps1', 'harness-run-slice-tests.ps1', 'harness-sensor.ps1', 'harness-session-start.ps1', 'harness-step.ps1', 'harness-stop-gate.ps1', 'harness-timeline.ps1', 'harness-turn-start.ps1', 'harness-turn-stop.ps1', 'init-harness-feature.ps1', 'validate-review-loop.ps1', 'setup-harness-hooks.ps1', 'pull-ai-base.ps1', 'setup-ai-overlays.ps1', 'sync-cursor-ai.ps1', 'run-agent-eval.ps1', 'run-agent-eval.mjs', 'lint-powershell.ps1', 'lint-tests.mjs', 'lint-review-loop.mjs', 'format-review-loop.mjs', 'run-lint-staged.mjs', 'run-npm-script.mjs', 'resolve-node-for-git.sh', 'stryker-run.mjs', 'stryker-diff.mjs', 'stryker-scope.mjs', 'parse-mutation-report.mjs', 'lib' ) foreach ($leaf in $obsoleteFlat) { Remove-ObsoleteFlatHarnessFile "templates\scripts\$leaf" Remove-ObsoleteFlatHarnessFile "scripts\$leaf" } # --- Child PowerShellLintPaths: exclude harness/ as vendor --- $childLintPaths = @' # Executable scope for scripts/harness/lint-powershell.ps1 - copy to repo root and adapt. # # Only paths under LintRoots are scanned. This avoids node_modules, vendored # trees, and repo-wide -Recurse over ".". # # Child repos: harness core lives under scripts/harness/ (and tests/harness/). # ExcludeDirectoryNames includes "harness" so PSScriptAnalyzer skips read-only # harness scripts. The harness source repo (mac-test-suite) intentionally does # NOT exclude harness - it lints the core. @{ LintRoots = @( 'scripts' 'tests/manual' 'hooks' '.githooks' 'certs' ) ExcludeDirectoryNames = @( 'node_modules' '.venv' 'wip' 'ideas' 'output' '__pycache__' '.pytest_cache' 'harness' ) } '@ $childLintRel = 'templates\PowerShellLintPaths.psd1' $childLintDst = Join-Path $TemplateRoot $childLintRel if ($PSCmdlet.ShouldProcess($childLintDst, 'Write child PowerShellLintPaths with harness exclusion')) { New-HarnessLiftDirectory (Split-Path $childLintDst -Parent) Write-Utf8File $childLintDst $childLintPaths Write-Output "OK file $childLintRel (child harness exclusion)" } # --- Pester --- Copy-HarnessLiftDirectory 'tests\harness' # --- Agents: UTF-8-safe EF inject (never robocopy child agents) --- $agentDir = Join-Path $TemplateRoot '.github\agents' if (Test-Path -LiteralPath $agentDir) { Get-ChildItem -LiteralPath $agentDir -Filter '*.agent.md' | ForEach-Object { Add-HarnessEvidenceFirstConstraint $_.FullName } } # Feature-orchestrator: ownership autonomy + journal (surgical, keep UTF-8 body) $orch = Join-Path $agentDir 'feature-orchestrator.agent.md' if (Test-Path -LiteralPath $orch) { $t = [IO.File]::ReadAllText($orch) if ($t -notmatch 'do not intercept that chain') { $oldLine = '- After every subagent: confirm artifacts on disk, append `.review-loop.json`, update `docs/harness/features/<slug>/handoff-<slice>.md`, emit a status table to the human.' $newBlock = @( '- After every subagent: confirm artifacts on disk, append journal start/stop and `.review-loop.json` via harness scripts, update `docs/harness/features/<slug>/handoff-<slice>.md`, emit a status table to the human.', '- **Visible progress is not a human approval gate.** The operator observes; they approve COMMIT and PR - not RED->GREEN or reviewer hops.', '- **`@atdd-developer` slice ownership** chains RED -> pre-commit reviewers until `review_ready` without per-hop human approval - do not intercept that chain.', '- **Never** black-box a full slice inside a nested Task without returning status - but do not ask the human to "approve GREEN" between steps.' ) -join "`r`n" if ($t.Contains($oldLine)) { $t = $t.Replace($oldLine, $newBlock) } elseif ($t -match '(?m)^- After every subagent:.*\r?\n') { $t = [regex]::Replace($t, '(?m)^- After every subagent:.*\r?\n', ($newBlock + "`r`n"), 1) } else { Write-Warning 'feature-orchestrator: could not find After every subagent line; ownership bullets not merged' } } if ($PSCmdlet.ShouldProcess($orch, 'Write feature-orchestrator ownership merge')) { Write-Utf8File $orch $t Write-Output 'OK feature-orchestrator ownership/journal merge' } } # atdd-developer: journal obligation in Constraints (UTF-8 template body) $atdd = Join-Path $agentDir 'atdd-developer.agent.md' if (Test-Path -LiteralPath $atdd) { $t = [IO.File]::ReadAllText($atdd) if ($t -notmatch 'harness-journal\.ps1|harness-turn-start') { $journalLine = '- Journal via harness DNA: `pwsh -File scripts/harness-invoke.ps1 harness-turn-start.ps1` / `harness-turn-stop.ps1` (and recorders); see [harness-journal.md](../../docs/kaden/harness-journal.md).' if ($t -match '(?m)^(- Follow \[Evidence-First Work Ethic\].*)\r?\n') { $t = [regex]::Replace($t, '(?m)^(- Follow \[Evidence-First Work Ethic\].*\r?\n)', ('$1' + $journalLine + "`r`n"), 1) Write-Utf8File $atdd $t Write-Output 'OK atdd-developer journal obligation' } else { Write-Warning 'atdd-developer: Evidence-First line not found for journal insert' } } } # Sync Cursor overlays in template $sync = Join-Path $TemplateRoot 'scripts\harness\sync-cursor-ai.ps1' if (-not (Test-Path -LiteralPath $sync)) { $sync = Join-Path $TemplateRoot 'scripts\sync-cursor-ai.ps1' } if (Test-Path -LiteralPath $sync) { if ($PSCmdlet.ShouldProcess($sync, 'Run sync-cursor-ai.ps1')) { & $sync -RepoRoot $TemplateRoot } } Write-Output '' Write-Output 'Merge complete (no commit). Preserved: .cursor/README.md, AGENTS-ATTRIBUTION.md (not overwritten).' Write-Output ('Next: review git status in {0}' -f $TemplateRoot) Write-Output 'Then patch README / getting-started paths if needed, and in children:' Write-Output ' .\scripts\harness\pull-ai-base.ps1 -TemplateRoot <path-to-kaden>' Write-Output ' (first-time: also copy templates/pwsh -File scripts/harness-invoke.ps1 + verify-local.ps1 to scripts/)' |