2026-04-15 01:00:03 +00:00
|
|
|
|
# 너드나비스 조직 레포 - 신 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 실파일·파싱·필수 키
|
|
|
|
|
|
$pathsFile = Join-Path $repoRoot "paths.local.json"
|
|
|
|
|
|
$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 첫 실행 전일 가능성"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 12:59:41 +00:00
|
|
|
|
# 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 자동 생성)"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 01:00:03 +00:00
|
|
|
|
# 3. 경로 추상화 적용 여부 (CLAUDE.md 계열에 구 하드코딩 경로 잔존 확인)
|
|
|
|
|
|
$hardcodePatterns = @("C:/Users/PC/Documents", "D:/NerdNavis/FilGoodBandits", "D:/NerdNavis/NerdNavis.Framework")
|
|
|
|
|
|
$scanTargets = @(
|
|
|
|
|
|
(Join-Path $repoRoot "CLAUDE.md"),
|
2026-04-16 08:23:49 +00:00
|
|
|
|
(Join-Path $repoRoot ".claude\skills")
|
2026-04-15 01:00:03 +00:00
|
|
|
|
) | 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
|