private/Write-WayforgePiAdapter.ps1
|
function Write-WayforgePiAdapter { <# .SYNOPSIS Generates a pi extension (.pi/extensions/wayforge-gate.ts) that blocks a tool call by delegating to the shared gate shim. .DESCRIPTION pi loads the extension via jiti (no build). The `tool_call` handler runs gate.ps1 and returns { block: true, reason } when the gate denies (non-zero exit). Project extensions load only after the project is trusted. #> 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 '.pi/extensions' if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $path = Join-Path $dir 'wayforge-gate.ts' $content = @' // Generated by PSWayforge (Sync-WayforgeHarness -Harness pi). Do not edit. // Blocks a tool call by delegating to the shared Wayforge gate shim. import { spawnSync } from "node:child_process"; import { join } from "node:path"; export default function (pi: any) { pi.on("tool_call", async (event: any, ctx: any) => { const cwd = ctx && ctx.cwd ? ctx.cwd : process.cwd(); const script = join(cwd, ".workflow", "hooks", "gate.ps1"); const payload = JSON.stringify({ tool_name: event.toolName, tool_input: event.input }); const r = spawnSync("pwsh", ["-NoProfile", "-File", script, "-Stage", "pre-tool", "-AsHook", "pi"], { input: payload, encoding: "utf8" }); if (r.status !== 0) { const reason = ((r.stdout || "") + (r.stderr || "")).trim(); return { block: true, reason: reason || `Wayforge gate blocked this tool (exit ${r.status}).` }; } }); } '@ Set-Content -Path $path -Value $content -Encoding utf8NoBOM return $path } |