BurningTimesAi/setup/setup_windows.ps1

332 lines
16 KiB
PowerShell
Raw Normal View History

# 너드나비스 조직 레포 - Windows PC 셋업
# 사용: PowerShell에서 실행
# .\setup_windows.ps1 ← 기본 셋업만
# .\setup_windows.ps1 -CreateShortcuts ← 기본 셋업 + 바탕화면 바로가기 3종 생성 (주의 아래)
# .\setup_windows.ps1 -NerdNavisRoot "C:\..." ← 레포 경로 지정
# .\setup_windows.ps1 -CreateShortcuts -ClaudeExePath "C:\Tools\claude\claude.exe"
# ← claude.exe 자동 탐지 실패 시 경로 수동 지정
#
# ⚠️ 바로가기 관련 주의 (2026-04-15 실증)
# Windows Store(MSIX) 버전 Claude 앱은 single-instance + WorkingDirectory 무시 특성이 있어
# 바로가기로 부서 세션을 분리할 수 없다. MSIX 앱 사용자는 앱 실행 후 **입력창 위 "폴더 칩"**
# 을 클릭해 부서 폴더를 선택하는 것이 정답이다. 바로가기 옵션은 non-MSIX 환경(CLI 등)에서만
# 유의미하므로 필요한 사람만 -CreateShortcuts 로 명시 선택할 것.
# 상세: memory/org/feedback_permissions_portability.md
param(
[string]$NerdNavisRoot = $(Resolve-Path (Join-Path $PSScriptRoot "..")).Path,
[string]$UnityRoot = "",
[string]$FrameworkRoot = "",
[string]$GiteaUrl = "https://burning.i234.me",
[string]$GiteaSsh = "ssh://git@burning.i234.me:30030",
[switch]$CreateShortcuts,
[string]$ClaudeExePath = ""
)
$ErrorActionPreference = "Stop"
# 경로 추정: NerdNavisRoot 드라이브 기반 동적 default (PC별 하드코딩 회피)
# 사용자가 -UnityRoot/-FrameworkRoot 명시하면 그 값 우선, 미명시면 NerdNavisRoot 드라이브의 \NerdNavis\* 패턴으로 추정
$rootDriveLetter = (Split-Path $NerdNavisRoot -Qualifier).TrimEnd(':')
if (-not $UnityRoot) { $UnityRoot = "${rootDriveLetter}:\NerdNavis\FilGoodBandits\DeckBuilding" }
if (-not $FrameworkRoot) { $FrameworkRoot = "${rootDriveLetter}:\NerdNavis\NerdNavis.Framework" }
Write-Host "=== 너드나비스 조직 레포 셋업 ==="
Write-Host "NerdNavisRoot: $NerdNavisRoot"
Write-Host "UnityRoot: $UnityRoot $(if (-not (Test-Path $UnityRoot)) { '(경로 미존재 - paths.local.json 생성 후 수동 수정 권장)' })"
Write-Host "FrameworkRoot: $FrameworkRoot $(if (-not (Test-Path $FrameworkRoot)) { '(경로 미존재 - paths.local.json 생성 후 수동 수정 권장)' })"
# 1. Git 확인
git --version | Out-Null
if (-not $?) { throw "Git이 설치되지 않았습니다." }
# 2. paths.local.json 생성
$pathsFile = Join-Path $NerdNavisRoot "paths.local.json"
if (-not (Test-Path $pathsFile)) {
$paths = [ordered]@{
"_description" = "로컬 환경 경로. 커밋 금지."
NERDNAVIS_ROOT = $NerdNavisRoot
UNITY_PROJECT_ROOT = $UnityRoot
FRAMEWORK_PKG_ROOT = $FrameworkRoot
TABLE_EXPORT_ROOT = (Join-Path $UnityRoot "Assets\ResWork\Table\Export")
GITEA_URL = $GiteaUrl
GITEA_SSH = $GiteaSsh
HOSTNAME = $env:COMPUTERNAME
}
# UTF-8 BOM 없음으로 명시 출력 (PowerShell 5.1의 기본 -Encoding utf8은 BOM 포함 + 한국어 깨짐 이슈 회피)
$jsonText = $paths | ConvertTo-Json -Depth 5
[System.IO.File]::WriteAllText($pathsFile, $jsonText, [System.Text.UTF8Encoding]::new($false))
Write-Host "paths.local.json 작성 완료 (UTF-8 no BOM): $pathsFile"
} else {
Write-Host "paths.local.json 이미 존재. 유지."
}
# 3. Claude 사용자 메모리 연결 (junction)
$claudeMemoryBase = "$env:USERPROFILE\.claude\projects"
$orgMemoryTarget = Join-Path $NerdNavisRoot "memory\org"
if (-not (Test-Path $orgMemoryTarget)) {
New-Item -ItemType Directory -Path $orgMemoryTarget | Out-Null
}
$hashDirs = @()
if (Test-Path $claudeMemoryBase) {
# Claude Code는 프로젝트 경로의 각 세그먼트를 '-'로 이어 해시 폴더명을 만든다
# (예: E:\NerdNavisAi → E--NerdNavisAi, C:\Users\PC\Documents\너드나비스 → C--Users-PC-Documents-너드나비스)
# NerdNavisRoot의 리프 이름·드라이브 prefix·관례적 키워드를 모두 포괄하도록 필터 확장
$rootLeaf = Split-Path $NerdNavisRoot -Leaf
$rootDrive = (Split-Path $NerdNavisRoot -Qualifier).TrimEnd(':')
$hashDirs = Get-ChildItem $claudeMemoryBase -Directory -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -like "*Documents*" -or
$_.Name -like "*너드나비스*" -or
$_.Name -like "*NerdNavis*" -or
$_.Name -like "*$rootLeaf*" -or
$_.Name -like "$rootDrive--*"
}
}
foreach ($d in $hashDirs) {
$memLink = Join-Path $d.FullName "memory"
if (Test-Path $memLink) {
$attr = (Get-Item $memLink -Force).Attributes
if (($attr -band [IO.FileAttributes]::ReparsePoint) -eq 0) {
# 실체 폴더. 백업 후 junction으로 교체
$bak = "$memLink.bak-$(Get-Date -Format yyyyMMddHHmmss)"
Rename-Item $memLink $bak
Write-Host "기존 memory 폴더 백업: $bak"
cmd /c mklink /J "`"$memLink`"" "`"$orgMemoryTarget`"" | Out-Null
Write-Host "Junction 생성: $memLink -> $orgMemoryTarget"
} else {
Write-Host "이미 junction/symlink. 유지: $memLink"
}
} else {
cmd /c mklink /J "`"$memLink`"" "`"$orgMemoryTarget`"" | Out-Null
Write-Host "Junction 생성: $memLink -> $orgMemoryTarget"
}
}
if ($hashDirs.Count -eq 0) {
Write-Warning "Claude 프로젝트 해시 폴더를 찾지 못했습니다. 수동 연결 필요."
}
feat(rules): C34 Live 증분 동기화 헌법급 신설 + worktree 격리 근원 해결 PD님 조직 생존급 선언 수용 — "이 문제가 해결되지 않으면 앞으로 우리 조직은 유지될 수 없어" / "철저히 검토해서 가능한 모든 수단을 써서 개선해". 헌법 제1원칙 ⑤(세션·PC 연속성) 근본 위협 판정. P25 → C34 승격 + 중앙 Junction 구조로 worktree 경계 무관 실시간 공유 복원. 집행 10종: - SKILL.md: C34 신설(14개 하위 조항) + P25 본문 삭제(C14-5-확장) + C16-1 .live/ junction 편입 + C31-1-E 표기 갱신 + 조직 핵심 자산 참조 갱신 - CLAUDE.md: 핵심 규칙 요약 28→29 + C34 추가, 프로젝트 규칙 요약 25→24 + P25 제거, 번호 구멍 목록 확장 - scripts/live_junction_ensure.sh: 신규 SessionStart hook (PowerShell New-Item Junction 우선, git-bash mklink 대비 신뢰성 우위 실증 반영) - setup/setup_windows.ps1·setup_macos.sh: 3.5 섹션 추가 - scripts/verify_setup.ps1: 2.5 Live junction 3축 검증 (실측 통과 확인) - .claude/settings.json: SessionStart hook 체인 최우선 삽입 - .gitignore: .live/* 제외 + README 예외 + 백업 제외 - 공유/조직공지/2026-04-18_C34_신설_worktree_격리_근원해결.md 신설 - 공유/조직공지/폐기_규칙_아카이브.md §13 P25 승격 6필드 기록 - memory/org/feedback_worktree_isolation.md + feedback_agent_path_boundary.md 신설 + MEMORY.md 인덱스 대화로그·PD 지시 로그 #39 완료 갱신 포함. Agent 경계 넘기 2차 사건 (개발팀장 Agent 절대 경로 하드코딩으로 레포 루트 유출) stash 이관 복구 + C34-11 Agent 경계 보호 조항 신설. 조직 전원 세션 1회 재시작 필요 — SessionStart hook이 live_junction_ensure.sh 자동 실행하여 junction 설치. verify_setup.ps1 통과 확인 후 작업 재개. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:59:41 +00:00
# 3.5. Live 증분 동기화 중앙 저장소 + Junction (C34, 2026-04-18 PD님 직접 지시)
# worktree 격리로 인한 .live/ 물리 분리 문제를 해결하기 위해
# $HOME\.claude\nerdnavis-live\ 중앙 디렉토리로 junction 연결한다.
# 헌법 제1원칙 ⑤ 세션·PC 연속성의 근원 보장 장치.
$centralLive = Join-Path $env:USERPROFILE ".claude\nerdnavis-live"
$localLive = Join-Path $NerdNavisRoot ".live"
$markerName = ".junction-marker"
$markerText = "nerdnavis-live central junction target (C34, 2026-04-18)"
if (-not (Test-Path $centralLive)) {
New-Item -ItemType Directory -Path $centralLive -Force | Out-Null
Write-Host "Live 중앙 저장소 생성: $centralLive"
}
$centralMarker = Join-Path $centralLive $markerName
if (-not (Test-Path $centralMarker)) {
[System.IO.File]::WriteAllText($centralMarker, $markerText, [System.Text.UTF8Encoding]::new($false))
}
if (Test-Path $localLive) {
$liveItem = Get-Item $localLive -Force
$isReparse = ($liveItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0
if (-not $isReparse) {
# 실체 디렉토리 — 백업 후 junction 전환 (C6-1 원본 보호)
$bak = "$localLive.bak-$(Get-Date -Format yyyyMMddHHmmss)"
# 기존 파일 중앙으로 복사 (기존 중앙 파일 덮어쓰기 안 함)
Get-ChildItem $localLive -File -ErrorAction SilentlyContinue | ForEach-Object {
$dst = Join-Path $centralLive $_.Name
if (-not (Test-Path $dst)) { Copy-Item $_.FullName $dst -Force }
}
Rename-Item $localLive $bak
Write-Host "기존 .live/ 백업: $bak"
New-Item -ItemType Junction -Path $localLive -Target $centralLive -Force | Out-Null
Write-Host "Live Junction 생성: $localLive -> $centralLive"
} else {
Write-Host "Live Junction 이미 존재. 유지: $localLive"
}
} else {
New-Item -ItemType Junction -Path $localLive -Target $centralLive -Force | Out-Null
Write-Host "Live Junction 생성: $localLive -> $centralLive"
}
feat(rules): C34 확장 — memory junction 중앙화 근원 해결 (옵션 A) PD님 직접 지적 수용 — "근본 해결이 아닌 임시 방편은 코어 룰 위반이야. C34와 동급의 생존성 이슈는 '권고' 수준이 아니었어. 옵션 A 방안대로 처리해." PM 자진 반성(C2·C3·C5·C29 위반 자인) + 옵션 A 집행. 규칙 확장: - SKILL.md C34 제목 개정 "Live 증분" → "PC 로컬 실시간 공유 중앙화 체계 (Live + memory)" + C34-1/3/14 개정 + C34-16 신설 (memory junction 특수 조항 5종: 실체 디렉토리 유지·sync 방향·Write 경로 선택·3층 백업· 정(正) 판정 규칙 A·B·C) - CLAUDE.md 요약 갱신 - 폐기_규칙_아카이브.md §14 신설 (C34 확장 이력 6필드) 스크립트 구현: - scripts/memory_junction_ensure.sh 신규 (SessionStart hook, Lock 추가, Windows junction reparse point 체크 선행) - scripts/sync_memory_repo_to_central.sh 신규 (SessionStart, unflushed 대피 + Lock race 방어) - scripts/sync_memory_central_to_repo.sh 신규 (post-commit hook) - scripts/sync_memory.sh 신규 (수동 비상) - scripts/rollback_memory_central.sh 신규 (C6-1 롤백 경로) - setup/setup_windows.ps1·setup_macos.sh 3.6 섹션 - scripts/verify_setup.ps1 2.6 섹션 3축 검증 - .claude/settings.json SessionStart hook 체인 확장 - scripts/git-hooks/post-commit 확장 감사관 3종 "C34/C16-1 동급 생존성 이슈 축소 보고 감지" 체크 신설 (pm-auditor 5-A · dev-auditor 6-A · plan-auditor 6-A). 조직공지·feedback·MEMORY.md 인덱스·Live 더미·대화로그 일괄 집행. 실측 검증 통과: 38개 worktree junction 중앙 연결 (10 신규 + 28 유지, 실패 0건). 스크립트 로직 결함(Windows junction bash `-L` 미인식) 수정. 조직 전원 세션 1회 재시작 필요 — SessionStart hook이 자동 마이그레이션. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:39:04 +00:00
# 3.6. memory/org/ 중앙 저장소 + Junction (C34-16, 2026-04-19 신설)
# Claude user memory junction 대상을 $HOME\.claude\nerdnavis-memory\로 변경.
# 레포 `memory/org/`는 git 추적 SOT로 실체 디렉토리 유지 + sync 스크립트가 양방향 동기화.
$centralMemory = Join-Path $env:USERPROFILE ".claude\nerdnavis-memory"
$memoryMarkerName = ".memory-junction-marker"
$memoryMarkerText = "nerdnavis-memory central (C34-16, 2026-04-19)"
if (-not (Test-Path $centralMemory)) {
New-Item -ItemType Directory -Path $centralMemory -Force | Out-Null
Write-Host "memory 중앙 저장소 생성: $centralMemory"
}
$centralMemoryMarker = Join-Path $centralMemory $memoryMarkerName
if (-not (Test-Path $centralMemoryMarker)) {
[System.IO.File]::WriteAllText($centralMemoryMarker, $memoryMarkerText, [System.Text.UTF8Encoding]::new($false))
}
# 초기 sync — 레포 memory/org → 중앙 (기존 중앙 파일 덮어쓰기 안 함)
$repoMemoryOrg = Join-Path $NerdNavisRoot "memory\org"
if (Test-Path $repoMemoryOrg) {
Get-ChildItem $repoMemoryOrg -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in @('.md','.json') } | ForEach-Object {
$dst = Join-Path $centralMemory $_.Name
if (-not (Test-Path $dst)) { Copy-Item $_.FullName $dst -Force }
}
Write-Host "memory 초기 sync 레포 → 중앙 완료"
}
# 모든 E--NerdNavisAi* 해시 폴더 순회하여 junction 중앙으로 재연결 (광범위 filter)
$allHashDirs = Get-ChildItem $claudeMemoryBase -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like "E--NerdNavisAi*" -or $_.Name -like "*NerdNavisAi*" }
foreach ($d in $allHashDirs) {
$memLink = Join-Path $d.FullName "memory"
$markerPath = Join-Path $memLink $memoryMarkerName
# 이미 중앙으로 연결된 경우 skip (sentinel 경유)
if (Test-Path $markerPath) { continue }
if (Test-Path $memLink) {
$item = Get-Item $memLink -Force
$isReparse = ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0
if (-not $isReparse) {
# 실체 디렉토리 — 내용 중앙으로 흡수 후 백업
Get-ChildItem $memLink -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in @('.md','.json') } | ForEach-Object {
$dst = Join-Path $centralMemory $_.Name
if (-not (Test-Path $dst)) { Copy-Item $_.FullName $dst -Force }
}
Rename-Item $memLink "$memLink.bak-$(Get-Date -Format yyyyMMddHHmmss)"
} else {
Remove-Item $memLink -Force -ErrorAction SilentlyContinue
}
}
try {
New-Item -ItemType Junction -Path $memLink -Target $centralMemory -Force | Out-Null
Write-Host "memory Junction 중앙 연결: $memLink -> $centralMemory"
} catch {
Write-Warning "memory Junction 생성 실패: $memLink"
}
}
# 4. .claude/settings.json 부서 동기화 (루트 SOT → 개발팀/기획팀 복제)
# Claude Code는 .claude/ 계층 auto-merge를 지원하지 않으므로 자식 디렉토리에서 세션 시작 시
# 루트 settings.json을 인식하지 못함. 이를 우회하기 위해 루트 settings.json을 부서 디렉토리로 복제.
$rootSettings = Join-Path $NerdNavisRoot ".claude\settings.json"
if (Test-Path $rootSettings) {
$deptPaths = @("개발팀", "기획팀")
foreach ($dept in $deptPaths) {
$deptClaudeDir = Join-Path $NerdNavisRoot "$dept\.claude"
$deptSettings = Join-Path $deptClaudeDir "settings.json"
if (-not (Test-Path $deptClaudeDir)) {
New-Item -ItemType Directory -Path $deptClaudeDir -Force | Out-Null
}
Copy-Item -Path $rootSettings -Destination $deptSettings -Force
Write-Host "settings.json 동기화: $dept/.claude/settings.json"
}
} else {
Write-Warning "루트 .claude/settings.json 미발견. 부서 동기화 생략."
}
# 5. (선택) 부서별 바탕화면 바로가기 3종 생성
# Claude Code 앱의 "New Session"은 현재 열린 프로젝트 컨텍스트 내에서 새 대화를 만드는 동작이라
# 루트에서 앱이 시작되면 부서 폴더 진입이 어려움. 각 부서 폴더를 WorkingDirectory로 지정한 바로가기로 우회.
if ($CreateShortcuts) {
Write-Host ""
Write-Host "=== 부서별 바탕화면 바로가기 생성 ==="
# 1) MSIX/Store 패키지 우선 탐지 (PD님 환경 표준)
$msixPkg = $null
try { $msixPkg = Get-AppxPackage -Name '*Claude*' -ErrorAction SilentlyContinue | Select-Object -First 1 } catch {}
$msixLaunchArg = $null
if ($msixPkg) {
# AppxManifest에서 AppId 추출
$manifestPath = Join-Path $msixPkg.InstallLocation 'AppxManifest.xml'
$appId = 'App'
if (Test-Path $manifestPath) {
try {
[xml]$m = Get-Content $manifestPath
$appIdNode = $m.Package.Applications.Application | Select-Object -First 1
if ($appIdNode -and $appIdNode.Id) { $appId = $appIdNode.Id }
} catch {}
}
$msixLaunchArg = "shell:AppsFolder\$($msixPkg.PackageFamilyName)!$appId"
Write-Host "Claude MSIX 패키지 탐지: $($msixPkg.PackageFamilyName) / AppId=$appId"
}
# 2) MSIX 미탐지 시 일반 claude.exe 탐색 (fallback)
if (-not $msixLaunchArg -and -not $ClaudeExePath) {
$candidatePaths = @(
"$env:LOCALAPPDATA\Programs\claude\claude.exe",
"$env:LOCALAPPDATA\AnthropicClaude\claude.exe",
"$env:LOCALAPPDATA\claude\claude.exe",
"$env:ProgramFiles\claude\claude.exe",
"${env:ProgramFiles(x86)}\claude\claude.exe",
"$env:LOCALAPPDATA\Programs\Claude\Claude.exe"
)
foreach ($cp in $candidatePaths) {
if (Test-Path $cp) { $ClaudeExePath = $cp; Write-Host "claude.exe 자동 탐지: $ClaudeExePath"; break }
}
if (-not $ClaudeExePath) {
$where = (Get-Command claude.exe -ErrorAction SilentlyContinue).Source
if ($where) { $ClaudeExePath = $where; Write-Host "claude.exe PATH에서 탐지: $ClaudeExePath" }
}
}
if (-not $msixLaunchArg -and (-not $ClaudeExePath -or -not (Test-Path $ClaudeExePath))) {
Write-Warning "Claude 실행 수단 탐지 실패 (MSIX·exe 모두). 바로가기 생성 생략."
Write-Warning "수동 지정: .\setup_windows.ps1 -CreateShortcuts -ClaudeExePath '<실제 경로>'"
} else {
$WshShell = New-Object -ComObject WScript.Shell
$desktop = [Environment]::GetFolderPath("Desktop")
$targets = @(
@{ Name = "너드나비스_총괄PM"; Dir = $NerdNavisRoot; Desc = "너드나비스 총괄PM 세션 (루트)" },
@{ Name = "너드나비스_개발팀"; Dir = (Join-Path $NerdNavisRoot "개발팀"); Desc = "너드나비스 개발팀 세션" },
@{ Name = "너드나비스_기획팀"; Dir = (Join-Path $NerdNavisRoot "기획팀"); Desc = "너드나비스 기획팀 세션" }
)
foreach ($t in $targets) {
if (-not (Test-Path $t.Dir)) {
Write-Warning "대상 디렉토리 없음. 건너뜀: $($t.Dir)"
continue
}
$shortcutPath = Join-Path $desktop "$($t.Name).lnk"
$sc = $WshShell.CreateShortcut($shortcutPath)
if ($msixLaunchArg) {
# MSIX 앱: explorer.exe shell:AppsFolder\... 방식
$sc.TargetPath = "explorer.exe"
$sc.Arguments = $msixLaunchArg
} else {
# 일반 exe 방식
$sc.TargetPath = $ClaudeExePath
$sc.IconLocation = "$ClaudeExePath,0"
}
$sc.WorkingDirectory = $t.Dir
$sc.Description = $t.Desc
$sc.Save()
Write-Host "바로가기 생성: $shortcutPath (WD=$($t.Dir))"
}
Write-Host ""
if ($msixLaunchArg) {
Write-Host "바로가기 3종 생성 완료 (MSIX 모드)."
Write-Host "※ MSIX 앱은 WorkingDirectory가 전달되지 않을 수 있음. 바로가기 더블클릭 후 실제 프로젝트 경로 확인 필수."
} else {
Write-Host "바로가기 3종 생성 완료."
}
}
}
Write-Host ""
Write-Host "셋업 완료. 'git pull'로 최신 상태 유지 권장."
Write-Host "※ .claude/settings.json 변경사항은 세션 재시작 후 적용됨."
if (-not $CreateShortcuts) {
Write-Host "※ 부서별 바탕화면 바로가기 필요 시: .\setup_windows.ps1 -CreateShortcuts"
}