Initial commit
This commit is contained in:
136
.claude/hooks/check-javadoc.ps1
Normal file
136
.claude/hooks/check-javadoc.ps1
Normal file
@@ -0,0 +1,136 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
src/main/java의 추가·수정 소스에 한글 Javadoc이 붙어 있는지 확인하는 PostToolUse hook.
|
||||
|
||||
.DESCRIPTION
|
||||
`.agents/skills/verify-mcp-server-change/SKILL.md`의 "필수 한글 소스 주석" 규칙을 지침이 아니라
|
||||
결정적 검사로 강제한다. 최상위 type 선언과 indent 4의 method/constructor 선언 바로 위에
|
||||
Javadoc(`*/`로 끝나는 블록)이 있는지만 본다. 파일을 고치지 않으며 build에도 관여하지 않는다.
|
||||
|
||||
Hook 모드: Claude Code가 stdin으로 넘긴 JSON에서 tool_input.file_path를 읽는다.
|
||||
누락이 있으면 stderr로 알리고 exit 2로 Claude에게 되돌린다.
|
||||
|
||||
수동 모드: -Path 로 파일 또는 디렉터리를 직접 검사한다. 예)
|
||||
pwsh .claude/hooks/check-javadoc.ps1 -Path src/main/java
|
||||
#>
|
||||
param(
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-MissingJavadoc {
|
||||
param([string]$File)
|
||||
|
||||
$lines = Get-Content -LiteralPath $File -Encoding utf8
|
||||
$findings = @()
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$line = $lines[$i]
|
||||
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||||
|
||||
$kind = $null
|
||||
|
||||
# 최상위 type 선언 (indent 0). 중첩 type은 skill 규칙의 강제 대상이 아니다.
|
||||
if ($line -match '^(?:(?:public|final|abstract|sealed|non-sealed)\s+)*(class|interface|record|enum)\s+\w') {
|
||||
$kind = 'type'
|
||||
}
|
||||
# 정확히 indent 4인 선언만 본다. 8칸 이상은 method 본문이나 이어지는 인자 목록이다.
|
||||
elseif ($line -match '^ {4}\S') {
|
||||
$body = $line.Substring(4)
|
||||
# 중첩 type 선언은 건너뛴다.
|
||||
if ($body -match '^(?:(?:public|protected|private|static|final|abstract|sealed|non-sealed)\s+)*(class|interface|record|enum)\s+\w') {
|
||||
continue
|
||||
}
|
||||
# compact record constructor: `public Protocol {`
|
||||
if ($body -match '^(public|protected|private)\s+\w+\s*\{\s*$') {
|
||||
$kind = 'method'
|
||||
}
|
||||
# method/constructor: `(` 앞에 토큰이 둘 이상이라 enum 상수(`NAME(...)`)와 구분된다.
|
||||
elseif ($body -match '^([^(){};=]*\S)\s*\(' -and $matches[1] -match '\s' -and
|
||||
($body -match '\{\s*$' -or $body -match ';\s*$' -or $body -match '\(\s*$')) {
|
||||
$kind = 'method'
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $kind) { continue }
|
||||
|
||||
# 선언 위로 올라가며 Javadoc 블록의 끝(`*/`)을 찾는다.
|
||||
# 빈 줄, annotation, 그리고 선언보다 깊게 들여쓴 줄(여러 줄 annotation의 이어지는 인자)은 건너뛴다.
|
||||
$declIndent = $line.Length - $line.TrimStart(' ').Length
|
||||
$hasJavadoc = $false
|
||||
$j = $i - 1
|
||||
while ($j -ge 0) {
|
||||
$prev = $lines[$j]
|
||||
$trimmed = $prev.Trim()
|
||||
if ($trimmed -match '\*/$') { $hasJavadoc = $true; break }
|
||||
$prevIndent = $prev.Length - $prev.TrimStart(' ').Length
|
||||
if ($trimmed -eq '' -or $trimmed -match '^@\w' -or $prevIndent -gt $declIndent) { $j--; continue }
|
||||
break
|
||||
}
|
||||
|
||||
if (-not $hasJavadoc) {
|
||||
$findings += [pscustomobject]@{
|
||||
File = $File
|
||||
Line = $i + 1
|
||||
Kind = $kind
|
||||
Text = $line.Trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
return $findings
|
||||
}
|
||||
|
||||
function Test-Target {
|
||||
param([string]$Candidate)
|
||||
if ([string]::IsNullOrWhiteSpace($Candidate)) { return $false }
|
||||
if ($Candidate -notmatch '\.java$') { return $false }
|
||||
return ($Candidate -replace '\\', '/') -match 'src/main/java/'
|
||||
}
|
||||
|
||||
# --- 대상 수집 ---
|
||||
$targets = @()
|
||||
if ($Path) {
|
||||
if (Test-Path -LiteralPath $Path -PathType Container) {
|
||||
$targets = Get-ChildItem -LiteralPath $Path -Recurse -Filter *.java |
|
||||
ForEach-Object { $_.FullName } | Where-Object { Test-Target $_ }
|
||||
}
|
||||
elseif (Test-Target $Path) {
|
||||
$targets = @($Path)
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$raw = [Console]::In.ReadToEnd()
|
||||
if ([string]::IsNullOrWhiteSpace($raw)) { exit 0 }
|
||||
$event = $raw | ConvertFrom-Json
|
||||
$file = $event.tool_input.file_path
|
||||
if (Test-Target $file) { $targets = @($file) }
|
||||
}
|
||||
catch {
|
||||
# Hook은 편집 흐름을 막지 않는다. 입력을 못 읽으면 조용히 통과시킨다.
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
if ($targets.Count -eq 0) { exit 0 }
|
||||
|
||||
$all = @()
|
||||
foreach ($t in $targets) {
|
||||
if (Test-Path -LiteralPath $t) { $all += Get-MissingJavadoc -File $t }
|
||||
}
|
||||
|
||||
if ($all.Count -eq 0) {
|
||||
if ($Path) { Write-Host "javadoc ok: $($targets.Count) file(s)" }
|
||||
exit 0
|
||||
}
|
||||
|
||||
$report = ($all | ForEach-Object {
|
||||
" {0}:{1} [{2}] {3}" -f (Resolve-Path -LiteralPath $_.File -Relative), $_.Line, $_.Kind, $_.Text
|
||||
}) -join "`n"
|
||||
|
||||
[Console]::Error.WriteLine(
|
||||
"한글 Javadoc 누락 ($($all.Count)건). " +
|
||||
".agents/skills/verify-mcp-server-change/SKILL.md의 '필수 한글 소스 주석' 규칙에 따라 " +
|
||||
"역할·처리 단계·협력 객체를 설명하는 주석을 선언 바로 위에 추가하세요.`n$report")
|
||||
exit 2
|
||||
Reference in New Issue
Block a user