private/Write-WayforgeKimiAdapter.ps1
|
function Write-WayforgeKimiAdapter { <# .SYNOPSIS Generates a Kimi Code config snippet (.workflow/harness/kimi.config.toml) for the user to add to their global ~/.kimi-code/config.toml. .DESCRIPTION Kimi reads hooks and permission rules ONLY from the global ~/.kimi-code/config.toml — there is no project-scoped hooks file. Rather than mutate the user's global environment, this emits the exact TOML to add (PreToolUse hook + forbid-derived permission.rules) and warns. #> param([string] $ProjectRoot, [string] $WorkflowName) $set = Get-WayforgeGateSet -Root $ProjectRoot -WorkflowName $WorkflowName $stages = Get-WayforgeStages -Gates $set.Gates # The PreToolUse hook is the source of truth: it evaluates the complete # Wayforge model (on/when/severity/all forbid dimensions). Static permission # rules are emitted only as fail-closed defense-in-depth, and only for forbids # Kimi can represent EXACTLY: unconditional (when: always), blocking, pre-tool # path guardrails. Anything conditional or non-pre-tool stays hook-only, so we # never broaden a gate's semantics into an always-active project denial. $rules = [System.Collections.Generic.List[string]]::new() foreach ($gate in $set.Gates) { $on = @(Get-WayforgeField $gate 'on') $when = Get-WayforgeField $gate 'when' 'always' $severity = Get-WayforgeField $gate 'severity' 'block' if (('pre-tool' -notin $on) -or ($when -ne 'always') -or ($severity -ne 'block')) { continue } $forbid = Get-WayforgeField (Get-WayforgeField $gate 'check') 'forbid' if ($forbid) { # Like Claude, Kimi's Edit(path) rule covers file-editing tools; paths # map to Edit and commands to Bash. foreach ($p in @(Get-WayforgeField $forbid 'path' | Where-Object { $_ })) { $rule = "Edit($p)" if (-not $rules.Contains($rule)) { $rules.Add($rule) | Out-Null } } foreach ($c in @(Get-WayforgeField $forbid 'command' | Where-Object { $_ })) { $rule = "Bash($c)" if (-not $rules.Contains($rule)) { $rules.Add($rule) | Out-Null } } } } $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('# Kimi Code enforcement, generated by PSWayforge.') [void]$sb.AppendLine('# Kimi reads hooks ONLY from the GLOBAL ~/.kimi-code/config.toml') [void]$sb.AppendLine('# (Windows: %USERPROFILE%\.kimi-code\config.toml). Add the entries below to') [void]$sb.AppendLine('# that file. The command path is relative to the project Kimi runs in.') [void]$sb.AppendLine() if ($stages['pre-tool']) { [void]$sb.AppendLine('[[hooks]]') [void]$sb.AppendLine('event = "PreToolUse"') [void]$sb.AppendLine('matcher = "Bash|Edit|Write"') [void]$sb.AppendLine('command = "pwsh -NoProfile -File .workflow/hooks/gate.ps1 -Stage pre-tool -AsHook kimi"') [void]$sb.AppendLine('timeout = 60') [void]$sb.AppendLine() } foreach ($rule in $rules) { [void]$sb.AppendLine('[[permission.rules]]') [void]$sb.AppendLine('decision = "deny"') [void]$sb.AppendLine("pattern = ""$rule""") [void]$sb.AppendLine('scope = "project"') [void]$sb.AppendLine() } $dir = Join-Path $ProjectRoot '.workflow/harness' if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $path = Join-Path $dir 'kimi.config.toml' Set-Content -Path $path -Value $sb.ToString() -Encoding utf8NoBOM Write-Warning "Kimi hooks are global-only. Add the entries in '$path' to your ~/.kimi-code/config.toml to enable Kimi enforcement." return $path } |