125 lines
3.5 KiB
PowerShell
125 lines
3.5 KiB
PowerShell
# Changelog sync guard: compare Godot source/config mtimes against this session and changelog.
|
|
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
|
[Console]::InputEncoding = $utf8
|
|
[Console]::OutputEncoding = $utf8
|
|
|
|
function Write-EmptyHookResult {
|
|
[Console]::Out.WriteLine('{}')
|
|
}
|
|
|
|
$null = [Console]::In.ReadToEnd()
|
|
|
|
# sessionStart sets this variable outside agent mode.
|
|
if ($env:CURSOR_SKIP_CHANGELOG) {
|
|
Write-EmptyHookResult
|
|
exit 0
|
|
}
|
|
|
|
$cursorDir = Split-Path -Parent $PSScriptRoot
|
|
$repoRoot = Split-Path -Parent $cursorDir
|
|
$changelog = Join-Path $cursorDir 'changelog\changelog-headlines.md'
|
|
$ackFile = Join-Path $cursorDir 'changelog\.changelog-ack'
|
|
$sessionMarker = Join-Path $cursorDir 'changelog\.session-start'
|
|
|
|
if (-not (Test-Path -LiteralPath $changelog -PathType Leaf)) {
|
|
Write-EmptyHookResult
|
|
exit 0
|
|
}
|
|
|
|
$changelogMtime = (Get-Item -LiteralPath $changelog).LastWriteTimeUtc
|
|
if ((Test-Path -LiteralPath $ackFile -PathType Leaf) -and
|
|
(Get-Item -LiteralPath $ackFile).LastWriteTimeUtc -ge $changelogMtime) {
|
|
Write-EmptyHookResult
|
|
exit 0
|
|
}
|
|
|
|
$comparisonMtime = $changelogMtime
|
|
if ((Test-Path -LiteralPath $sessionMarker -PathType Leaf)) {
|
|
$sessionMtime = (Get-Item -LiteralPath $sessionMarker).LastWriteTimeUtc
|
|
if ($sessionMtime -gt $comparisonMtime) {
|
|
$comparisonMtime = $sessionMtime
|
|
}
|
|
}
|
|
|
|
$includedNames = @('project.godot')
|
|
$includedExtensions = @(
|
|
'.gd',
|
|
'.tscn',
|
|
'.tres',
|
|
'.res',
|
|
'.gdshader',
|
|
'.cfg',
|
|
'.json',
|
|
'.cs',
|
|
'.csproj',
|
|
'.py',
|
|
'.ps1',
|
|
'.sh'
|
|
)
|
|
$excludedDirectoryNames = @(
|
|
'.git',
|
|
'.cursor',
|
|
'.godot',
|
|
'node_modules',
|
|
'__pycache__',
|
|
'venv',
|
|
'.venv',
|
|
'dist',
|
|
'build',
|
|
'out',
|
|
'bin',
|
|
'obj',
|
|
'export',
|
|
'exports',
|
|
'coverage',
|
|
'tmp',
|
|
'temp'
|
|
)
|
|
|
|
$newerFile = $null
|
|
$rootItem = Get-Item -LiteralPath $repoRoot
|
|
$directories = New-Object 'System.Collections.Generic.Stack[System.IO.DirectoryInfo]'
|
|
$directories.Push($rootItem)
|
|
|
|
while ($directories.Count -gt 0 -and -not $newerFile) {
|
|
$directory = $directories.Pop()
|
|
try {
|
|
$entries = Get-ChildItem -LiteralPath $directory.FullName -Force -ErrorAction Stop
|
|
} catch {
|
|
continue
|
|
}
|
|
|
|
foreach ($entry in $entries) {
|
|
if ($entry.PSIsContainer) {
|
|
$isReparsePoint = ($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0
|
|
if (-not $isReparsePoint -and $excludedDirectoryNames -notcontains $entry.Name) {
|
|
$directories.Push($entry)
|
|
}
|
|
continue
|
|
}
|
|
|
|
$isIncluded = ($includedNames -contains $entry.Name) -or
|
|
($includedExtensions -contains $entry.Extension)
|
|
if ($isIncluded -and $entry.LastWriteTimeUtc -gt $comparisonMtime) {
|
|
$newerFile = $entry
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($newerFile) {
|
|
$rootPrefix = $repoRoot.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar
|
|
$relativePath = if ($newerFile.FullName.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
$newerFile.FullName.Substring($rootPrefix.Length)
|
|
} else {
|
|
$newerFile.Name
|
|
}
|
|
$message = "[Hook] Godot source/config is newer than the changelog (e.g. $relativePath). Run dev-changelog Skill operation A now: add a fragment under .cursor/changelog/entries/ and rebuild the changelog views."
|
|
$result = [ordered]@{ followup_message = $message }
|
|
[Console]::Out.WriteLine(($result | ConvertTo-Json -Compress -Depth 3))
|
|
} else {
|
|
Write-EmptyHookResult
|
|
}
|
|
|
|
exit 0
|