private/Write-WayforgeOpencodeAdapter.ps1
|
function Write-WayforgeOpencodeAdapter { <# .SYNOPSIS Generates an opencode plugin (.opencode/plugins/wayforge-gate.js) that blocks a tool call by delegating to the shared gate shim. .DESCRIPTION opencode runs the plugin under Bun with no build step. The plugin's `tool.execute.before` hook runs gate.ps1 and throws (opencode's documented block mechanism) when the gate denies (non-zero exit). #> param([string] $ProjectRoot, [string] $WorkflowName) $set = Get-WayforgeGateSet -Root $ProjectRoot -WorkflowName $WorkflowName $stages = Get-WayforgeStages -Gates $set.Gates if (-not $stages['pre-tool']) { return $null } $dir = Join-Path $ProjectRoot '.opencode/plugins' if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $path = Join-Path $dir 'wayforge-gate.js' $content = @' // Generated by PSWayforge (Sync-WayforgeHarness -Harness opencode). Do not edit. // Blocks a tool call by delegating to the shared Wayforge gate shim. import { spawnSync } from "node:child_process"; export const WayforgeGate = async ({ directory }) => { const root = directory ? String(directory).replace(/\\/g, "/").replace(/\/$/, "") + "/" : ""; const script = root + ".workflow/hooks/gate.ps1"; return { "tool.execute.before": async (input, output) => { const payload = JSON.stringify({ tool_name: input.tool, tool_input: output.args }); const r = spawnSync("pwsh", ["-NoProfile", "-File", script, "-Stage", "pre-tool", "-AsHook", "opencode"], { input: payload, encoding: "utf8" }); if (r.status !== 0) { const reason = ((r.stdout || "") + (r.stderr || "")).trim(); throw new Error(reason || `Wayforge gate blocked this tool (exit ${r.status}).`); } }, }; }; '@ Set-Content -Path $path -Value $content -Encoding utf8NoBOM return $path } |