claude init inited
This commit is contained in:
108
.claude/hooks/check-changelog.ps1
Normal file
108
.claude/hooks/check-changelog.ps1
Normal file
@@ -0,0 +1,108 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user