# 너드나비스 조직 레포 - 신 PC 셋팅 검증 스크립트 (Windows / PowerShell) # # 목적: `setup/setup_windows.ps1` 실행 후, 본 PC가 조직 레포를 정상 사용할 수 있는 상태인지 # 파일 존재·OS 동작(reparse point)·실행 결과(JSON 파싱) 3축으로 검증. # # 사용: PowerShell에서 # .\scripts\verify_setup.ps1 # 또는 (상세 출력) .\scripts\verify_setup.ps1 -Verbose # # Exit code: 0 = OK, 1 = 결함 발견 (stderr에 항목 나열) [CmdletBinding()] param() $ErrorActionPreference = "Stop" $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $fail = @() $warn = @() function Check([string]$name, [bool]$ok, [string]$detail) { if ($ok) { Write-Host "[OK] $name — $detail" } else { Write-Host "[FAIL] $name — $detail" -ForegroundColor Red $script:fail += "$name :: $detail" } } function Warn([string]$name, [string]$detail) { Write-Host "[WARN] $name — $detail" -ForegroundColor Yellow $script:warn += "$name :: $detail" } Write-Host "=== 너드나비스 셋팅 검증 ===" Write-Host "RepoRoot: $repoRoot" Write-Host "" # 1. paths.local.json 실파일·파싱·필수 키 (2026-04-18 worktree fallback 추가) $pathsFile = Join-Path $repoRoot "paths.local.json" if (-not (Test-Path $pathsFile)) { # worktree 감지 — 공통 git dir 기반 메인 레포 루트 추론 (C34-15 worktree 안전성) try { $gitCommon = (& git -C $repoRoot rev-parse --git-common-dir 2>$null) | Out-String $gitCommon = $gitCommon.Trim() if ($gitCommon -and (Test-Path $gitCommon)) { $mainRoot = Split-Path (Resolve-Path $gitCommon).Path -Parent $altPaths = Join-Path $mainRoot "paths.local.json" if (Test-Path $altPaths) { $pathsFile = $altPaths Warn "paths.local.json worktree fallback" "본 worktree 없음 → 메인 레포 경유: $pathsFile" } } } catch {} } $paths = $null if (Test-Path $pathsFile) { try { $raw = Get-Content $pathsFile -Raw -Encoding UTF8 $paths = $raw | ConvertFrom-Json Check "paths.local.json 존재·JSON 파싱" $true $pathsFile } catch { Check "paths.local.json 존재·JSON 파싱" $false "JSON 파싱 실패: $_" } } else { Check "paths.local.json 존재" $false "파일 없음(메인 레포도). setup_windows.ps1 선행 필요: $pathsFile" } $required = @("NERDNAVIS_ROOT","UNITY_PROJECT_ROOT","FRAMEWORK_PKG_ROOT","TABLE_EXPORT_ROOT") if ($paths) { foreach ($k in $required) { $v = $paths.$k $ok = [bool]$v Check "paths.local.json 키 [$k]" $ok ("값=" + ($v -as [string])) } # NERDNAVIS_ROOT 실제 디렉토리 존재 여부 if ($paths.NERDNAVIS_ROOT) { Check "NERDNAVIS_ROOT 디렉토리 존재" (Test-Path $paths.NERDNAVIS_ROOT) $paths.NERDNAVIS_ROOT } # Unity·Framework·TableExport는 경고 수준 (존재하지 않아도 일부 작업은 가능) foreach ($k in @("UNITY_PROJECT_ROOT","FRAMEWORK_PKG_ROOT","TABLE_EXPORT_ROOT")) { $v = $paths.$k if ($v -and -not (Test-Path $v)) { Warn "$k 디렉토리 미존재" "$v (해당 부서 작업 시 필요)" } } } # 2. Claude 사용자 메모리 junction 검증 $orgMemoryTarget = Join-Path $repoRoot "memory\org" Check "memory/org 실체 존재" (Test-Path $orgMemoryTarget) $orgMemoryTarget $claudeMemoryBase = "$env:USERPROFILE\.claude\projects" if (Test-Path $claudeMemoryBase) { $found = $false $junctionOk = $false foreach ($d in (Get-ChildItem $claudeMemoryBase -Directory -ErrorAction SilentlyContinue)) { $memLink = Join-Path $d.FullName "memory" if (Test-Path $memLink) { $found = $true $item = Get-Item $memLink -Force $isReparse = ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 if ($isReparse) { # 타깃이 memory\org 인지 확인 (memory 자체가 memory\org를 가리킴) $target = (Get-Item $memLink -Force).Target if (-not $target) { $target = (& cmd /c "dir `"$($item.Parent.FullName)`" 2>&1" | Out-String) } $pointsToOrg = $target -like "*memory\org*" -or $target -like "*memory/org*" if ($pointsToOrg) { Check "junction [$($d.Name)/memory]" $true "-> memory\org (reparse OK)" $junctionOk = $true } else { Warn "junction 타깃 확인 불가" "[$($d.Name)/memory] target=$target (수동 확인 권장)" } } else { Check "junction [$($d.Name)/memory]" $false "reparse point 아님 (실체 폴더). setup 재실행 필요" } } } if (-not $found) { Check "Claude 프로젝트 해시 폴더 내 memory 링크" $false "$claudeMemoryBase 에서 memory 링크를 찾지 못함. setup_windows.ps1 재실행 필요" } elseif (-not $junctionOk) { Warn "junction 상태" "타깃 검증에 성공한 링크가 없음. 수동 확인 권장" } # MEMORY.md 로드 가능 여부 (junction 통해 읽기) $memoryMdCandidates = Get-ChildItem $claudeMemoryBase -Directory -ErrorAction SilentlyContinue | ForEach-Object { Join-Path $_.FullName "memory\MEMORY.md" } | Where-Object { Test-Path $_ } if ($memoryMdCandidates.Count -gt 0) { Check "MEMORY.md junction 경유 읽기" $true ($memoryMdCandidates[0]) } else { Warn "MEMORY.md 읽기" "junction 경유로 MEMORY.md 를 찾지 못함" } } else { Warn "Claude 메모리 베이스" "$claudeMemoryBase 미존재. Claude Code 첫 실행 전일 가능성" } # 2.5. Live 증분 동기화 Junction 3축 검증 (C34, 2026-04-18 신설) # 축 1: 중앙 저장소 실체 / 축 2: reparse point / 축 3: marker 경유 읽기 $centralLive = Join-Path $env:USERPROFILE ".claude\nerdnavis-live" $localLive = Join-Path $repoRoot ".live" $markerName = ".junction-marker" Check "Live 중앙 저장소 실체 존재" (Test-Path $centralLive) $centralLive if (Test-Path $localLive) { $liveItem = Get-Item $localLive -Force $isReparse = ($liveItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 if ($isReparse) { Check "Live .live/ Junction reparse 실체" $true "reparse point OK" # 축 3: marker 경유 읽기 (A→B worktree 동기화 가능성 실증) $markerPath = Join-Path $localLive $markerName Check "Live Junction marker 경유 읽기" (Test-Path $markerPath) $markerPath } else { Check "Live .live/ Junction reparse 실체" $false "실체 디렉토리 (setup 재실행 필요)" } } else { Warn "Live .live/ 존재" "$localLive 미존재 (세션 1회 시작 시 hook 자동 생성)" } # 2.6. memory/org/ 중앙 저장소 + Junction 3축 검증 (C34-16, 2026-04-19 신설) $centralMemory = Join-Path $env:USERPROFILE ".claude\nerdnavis-memory" $memoryMarker = ".memory-junction-marker" Check "memory 중앙 저장소 실체 존재" (Test-Path $centralMemory) $centralMemory if (Test-Path $claudeMemoryBase) { $centralCount = 0 $repoRootCount = 0 $otherCount = 0 foreach ($d in (Get-ChildItem $claudeMemoryBase -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "E--NerdNavisAi*" })) { $memLink = Join-Path $d.FullName "memory" if (Test-Path (Join-Path $memLink $memoryMarker)) { $centralCount += 1 } elseif (Test-Path $memLink) { $repoRootCount += 1 } else { $otherCount += 1 } } if ($centralCount -gt 0 -and $repoRootCount -eq 0) { Check "memory junction 전원 중앙 연결" $true "$centralCount 건 (부재 $otherCount)" } elseif ($centralCount -gt 0) { Warn "memory junction 타깃 혼재" "중앙 $centralCount / 레포 루트 $repoRootCount / 부재 $otherCount — setup 재실행 권장" } else { Warn "memory junction 중앙 미연결" "레포 루트 $repoRootCount / 부재 $otherCount — C34-16 setup 재실행 필요" } # unflushed 중앙 대피본 감지 $conflictDirs = Get-ChildItem "$env:USERPROFILE\.claude" -Directory -Filter "nerdnavis-memory.conflict-*" -ErrorAction SilentlyContinue if ($conflictDirs.Count -gt 0) { Warn "memory unflushed 중앙 대피본 잔류" "$($conflictDirs.Count) 건 — 수동 병합 후 정리 필요: $($conflictDirs[0].FullName)" } } # 2.7. audit 중앙 저장소 + Junction 3종 검증 (C34-17, 2026-04-20 #48 I-1 부분 집행 — verify_setup 2.7 단독) $centralAudit = Join-Path $env:USERPROFILE ".claude\nerdnavis-audit" $auditMarker = ".junction-marker" Check "audit 중앙 저장소 실체 존재" (Test-Path $centralAudit) $centralAudit if (Test-Path $centralAudit) { Check "audit 중앙 최상위 marker" (Test-Path (Join-Path $centralAudit $auditMarker)) "$centralAudit\$auditMarker" $auditSubs = @("auditor_calls", "warning_ignored", "bypass_log") $subOk = 0 $subMissing = @() foreach ($sub in $auditSubs) { $subDir = Join-Path $centralAudit $sub $subMarkerPath = Join-Path $subDir $auditMarker if ((Test-Path $subDir) -and (Test-Path $subMarkerPath)) { $subOk += 1 } else { $subMissing += $sub } } if ($subOk -eq 3) { Check "audit 중앙 하위 3종 marker" $true "auditor_calls / warning_ignored / bypass_log" } else { Warn "audit 중앙 하위 marker 결손" "정상 $subOk/3 — 부재: $($subMissing -join ', ') — audit_junction_ensure.sh 재실행 필요" } # Junction 연결 검증 ($HOME/.claude/.nerdnavis_* → 중앙 하위) $junctionMap = @{ ".nerdnavis_auditor_calls" = "auditor_calls" ".nerdnavis_warning_ignored" = "warning_ignored" ".nerdnavis_bypass_log" = "bypass_log" } $linkedCount = 0 $unlinkedList = @() foreach ($localName in $junctionMap.Keys) { $localPath = Join-Path $env:USERPROFILE ".claude\$localName" $markerViaJunction = Join-Path $localPath $auditMarker if (Test-Path $markerViaJunction) { $linkedCount += 1 } else { $unlinkedList += $localName } } if ($linkedCount -eq 3) { Check "audit junction 3종 연결" $true "전수 정상" } elseif ($linkedCount -gt 0) { Warn "audit junction 부분 연결" "정상 $linkedCount/3 — 미연결: $($unlinkedList -join ', ')" } else { Warn "audit junction 미연결" "세션 재시작 후 audit_junction_ensure.sh 자동 실행 필요" } } # 3. 경로 추상화 적용 여부 (CLAUDE.md 계열에 구 하드코딩 경로 잔존 확인) $hardcodePatterns = @("C:/Users/PC/Documents", "D:/NerdNavis/FilGoodBandits", "D:/NerdNavis/NerdNavis.Framework") $scanTargets = @( (Join-Path $repoRoot "CLAUDE.md"), (Join-Path $repoRoot ".claude\skills") ) | Where-Object { Test-Path $_ } $hits = @() foreach ($t in $scanTargets) { $files = if ((Get-Item $t).PSIsContainer) { Get-ChildItem $t -Recurse -File -Include *.md } else { @(Get-Item $t) } foreach ($f in $files) { $content = Get-Content $f.FullName -Raw -ErrorAction SilentlyContinue foreach ($p in $hardcodePatterns) { if ($content -match [regex]::Escape($p)) { $hits += "$($f.FullName) :: '$p'" } } } } if ($hits.Count -eq 0) { Check "CLAUDE.md 계열 경로 추상화" $true "하드코딩 구 경로 미발견" } else { Warn "CLAUDE.md 계열 경로 추상화" ("잔존 " + $hits.Count + "건 — 변수화 후보") foreach ($h in $hits) { Write-Host " - $h" -ForegroundColor DarkYellow } } # 4. .gitignore 핵심 규칙 (paths.local.json 무시) $gi = Join-Path $repoRoot ".gitignore" if (Test-Path $gi) { $giText = Get-Content $gi -Raw Check ".gitignore paths.local.json 제외" ($giText -match "paths\.local\.json") ".gitignore 라인 확인됨" } else { Check ".gitignore 존재" $false ".gitignore 파일 없음" } # 5. settings.json (조직 공용 승인) 존재 $settings = Join-Path $repoRoot ".claude\settings.json" Check ".claude/settings.json (조직 공용 승인)" (Test-Path $settings) $settings # 결과 Write-Host "" Write-Host "=== 결과 요약 ===" Write-Host "PASS: 상기 [OK] 표기" if ($warn.Count -gt 0) { Write-Host "WARN: $($warn.Count)건 — 블로커 아님" -ForegroundColor Yellow } if ($fail.Count -gt 0) { Write-Host "FAIL: $($fail.Count)건 — 아래 항목 해결 필요" -ForegroundColor Red foreach ($f in $fail) { Write-Host " - $f" -ForegroundColor Red } exit 1 } Write-Host "셋팅 검증 통과. 작업 착수 가능." -ForegroundColor Green exit 0