# check-changelog.ps1 — Claude Code Stop hook (PowerShell 版) # # 检查源文件是否比 changelog-headlines.md 更新。如果更新了且本次会话确实有 # 文件编辑操作(通过 transcript_path 确认),提醒用户调用 dev-changelog Skill。 # # 豁免条件(任一满足则静默退出): # 1. changelog-headlines.md 不存在 # 2. .changelog-ack 文件足够新(本会话已通过 checklist 收尾项 Z 确认过) # 3. stop_hook_active = true(Claude Code 正在从 Stop hook 里再次调用 Claude,避免递归) # 4. transcript_path 中没有 Write/Edit 工具调用(无编辑证据,防跨会话 mtime 误触发) # 5. 没有源文件比 changelog 更新 $input = [Console]::In.ReadToEnd() # === 豁免 1:stop_hook_active 防递归 === $stopHookActive = $false try { $jsonData = $input | ConvertFrom-Json $stopHookActive = $jsonData.stop_hook_active -eq $true } catch {} if ($stopHookActive) { Write-Output '{}' exit 0 } $changelog = ".claude\changelog\changelog-headlines.md" # === 豁免 2:changelog 文件不存在 === if (-not (Test-Path $changelog)) { Write-Output '{}' exit 0 } $clItem = Get-Item $changelog $clMtime = $clItem.LastWriteTime.ToFileTime() # === 豁免 3:ack 标记文件足够新 === $ackFile = ".claude\changelog\.changelog-ack" if (Test-Path $ackFile) { $ackItem = Get-Item $ackFile $ackMtime = $ackItem.LastWriteTime.ToFileTime() if ($ackMtime -ge $clMtime) { Write-Output '{}' exit 0 } } # === 豁免 4:transcript 中无编辑证据 === $transcriptPath = "" try { $jsonData = $input | ConvertFrom-Json $transcriptPath = $jsonData.transcript_path } catch {} $hasEditEvidence = $false if ($transcriptPath -and (Test-Path $transcriptPath)) { $content = Get-Content $transcriptPath -Raw if ($content -match '"name"\s*:\s*"(Write|Edit|MultiEdit|NotebookEdit)"') { $hasEditEvidence = $true } } if (-not $hasEditEvidence) { # transcript 不可用或无编辑证据,从保守角度静默 Write-Output '{}' exit 0 } # === 源目录配置:若项目未自定义则默认扫描整个仓库根(排除常见生成物/依赖)=== $srcDir = "." # === 核心检查:是否有源文件比 changelog 更新 === $sourceExtensions = @("*.py", "*.ts", "*.tsx", "*.js", "*.jsx", "*.css", "*.cs", "*.go", "*.rs", "*.java") $excludePatterns = @( "*\node_modules\*", "*\.next\*", "*\__pycache__\*", "*\venv\*", "*\.venv\*", "*\target\*", "*\bin\*", "*\obj\*", "*\.claude\*", "*\.cursor\*", "*\.git\*" ) $newerFile = $null foreach ($ext in $sourceExtensions) { $files = Get-ChildItem -Path $srcDir -Filter $ext -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $item = $_ $exclude = $false foreach ($pattern in $excludePatterns) { if ($item.FullName -like $pattern) { $exclude = $true break } } -not $exclude } | Where-Object { $_.LastWriteTime.ToFileTime() -gt $clMtime } if ($files) { $newerFile = $files | Select-Object -First 1 break } } if ($newerFile) { $fname = $newerFile.Name $msg = "[Hook] Source file updated (e.g. $fname) but changelog not synced. Run dev-changelog Skill operation A NOW to write all three changelog layers." Write-Output '{"decision":"block","reason":"' + $msg + '"}' } else { Write-Output '{}' } exit 0