diff --git a/doc/history/2026-09-18-11차.md b/doc/history/2026-09-18-11차.md
index 45b511d..3487b06 100644
--- a/doc/history/2026-09-18-11차.md
+++ b/doc/history/2026-09-18-11차.md
@@ -127,6 +127,63 @@ A-1 에서 값으로 확인한 것 — 지운 파일은 "삭제되어 건너뜀
- A-4(ZIP)는 아직 정하지 않았다.
- 매뉴얼 · 웹 문서의 변경분 추출 설명(새 폴더 · 안내 파일)은 E-2 에서 함께 고친다.
+## B-1. 배포 묶음을 다시 만들고 꼭 필요한 파일을 가림 (2026-09-22, 완료)
+
+### 고치기 전 — 매뉴얼이 가리키는 Release 는 09-07 것
+
+| 무엇 | 값 |
+|---|---|
+| `bin\Release\net48` | 65개 · 96MB. `MergeHub.dll` 09-07 14:11, 셸 09-07 13:41 (플랫폼은 그 뒤 09-16 에 다시 배포됨) |
+| 옛 Release 로 켜면 | 오류 없이 켜지지만 **본 탭이 3개**(지금은 4개 — 폴더 비교 탭이 없음). 지금 소스의 형식 142개 가운데 **74개가 옛 dll 에 없다** — 폴더 비교(`FolderView` · `FolderComparer`), 3분할(`ThreeWayAligner`), 병합 저장 판단(`MergeSavePlan`), 단축키(`ShortcutMap`), 변경분 안내(`ExportReport` · `ExportPlan`), 커밋 수정 판단(`CommitSelection` · `CommitReword`) 등 6차 이후 것 (`e11-b1-old.ps1`, 이름 대조) |
+
+### 무엇이 필요한지 가린 길
+
+1. **참조로** — 셸 · MergeHub 에서 시작해 dll 이 부르는 dll 을 따라갔다. 어디서도 닿지 않는 것 7개: `GLWpfControl` · `OpenTK` · `SkiaSharp.Views.WPF` · `SkiaSharp.Views.Desktop.Common` · `LiveChartsCore.SkiaSharpView.WPF` · `Microsoft.Extensions.Options` · `…Options.ConfigurationExtensions`. 플랫폼 dll 안에 이 이름이 글자로도 없다(XAML 로 부르는 것도 아님).
+2. **플랫폼이 쓰는 것** — `UbisamBase.Core` 가 `SkiaSharp` · `LiveChartsCore` · `LiveChartsCore.SkiaSharpView` 를 **직접 참조**한다(`Charting/ChartTheme.cs`, 테마를 바꿀 때 `UiSettingsService` 가 부름). MergeHub 는 안 쓰지만 **플랫폼 몫이라 남긴다**(사용자 결정 2026-09-22 — 빼면 약 8MB 가 되지만 플랫폼이 바뀔 때마다 다시 재야 한다).
+3. **셸이 켜질 때 하는 일** — 셸은 폴더의 `*.dll` 을 모두 `Assembly.LoadFrom` 으로 열어 `IAppSetup` 을 찾는다(`AppSetupLoader.cs:16`). 쓰지 않는 dll 도 이름 순서상 `MergeHub.dll` 앞이면 켤 때마다 읽힌다 — 빼면 그만큼 덜 읽는다. `Lang` 폴더는 없으면 플랫폼이 만든다(`LanguageService.cs`), 지금은 비어 있다.
+4. **다른 OS · CPU** — 맥용 dylib 3개(17.5MB), 리눅스 · 맥용 libgit2 9개(약 19MB), ARM 용(Skia · HarfBuzz 11MB, libgit2). 이 PC 에서 셸은 **64비트**로 뜬다. x86 은 32비트 Windows 를 위해 남긴다.
+5. **지워 보고 켜서** — 통째 복사본(65개 · 97MB)과 줄인 묶음(41개 · 44MB)을 `%TEMP%` 에 따로 두고 켰다. 둘 다 오류 없이 켜지고 본 탭 4개가 모두 열렸다(UIA 로만 이동, `e11-b1-run.ps1`).
+
+### 빈 폴더에서 병합 한 길 (`e11-b1-merge.ps1` — 10차 E-1 의 병합 길 그대로, 실행 파일만 묶음으로)
+
+| 무엇 | 결과 |
+|---|---|
+| 실행 파일 | `%TEMP%\mergehub-beta\UbisamBase.Shell.exe` (줄인 묶음) |
+| 병합 | `feature/theirs → feature/mine` 충돌 24건 (머지 주석만 다른 2건 자동) |
+| 풀기 | 파일 단위 15 · 줄 단위 7 · 파일 지우기 · 남기기 · 누른 Take 18번 = 줄 고른 로그 18줄 |
+| 병합 커밋 | `647f1f8` 부모 둘 · 본문 "충돌 해결 26개" · 줄끝 · BOM · 인코딩 바뀐 파일 0 · 충돌 표식 0 · 워킹트리 깨끗 |
+| 두 탭 | Git 탭에서 고른 커밋을 파일 비교 탭이 따라감(10차 B-1) |
+| 걸린 시간 | 304초 |
+
+### 만든 것
+
+| 자리 | 한 일 |
+|---|---|
+| `scripts\make-bundle.ps1` (새) | Release 빌드(vswhere 로 MSBuild 를 찾음, `-NoBuild` 로 건너뜀) → 뺄 것 24개를 **까닭과 함께 한 표**에서 빼고 → `-Out`(기본 `%TEMP%\mergehub-beta`)에 옮기고 → 꼭 있어야 할 것(셸 · Core · MergeHub · LibGit2Sharp · git2 x64)을 확인하고 → 묶음 안에 `배포묶음.txt`(파일 · 크기 · 까닭, 뺀 것 · 까닭, 만든 시각 · 커밋)를 쓴다. **다른 파일이 든 폴더는 비우지 않고 멈춘다**(`배포묶음.txt` 가 있는 폴더 · 빈 폴더만) — 남의 파일을 넣어 둔 폴더로 확인함 |
+| 매뉴얼(`doc/web/index.html` 켜기와 탭 구성) | "Release 로 다시 빌드해야 최신" · 다른 PC 에는 스크립트로 묶음을 만들어 폴더째 복사 · `배포묶음.txt` 설명 |
+
+- 스크립트로 만든 묶음이 손으로 만들어 시험한 묶음과 **파일 목록이 같고**, 다시 켜도 오류 없이 네 탭이 열렸다.
+- Release 빌드 경고 0 · 오류 0. 제품 코드 변경 없음(스크립트 · 문서만).
+
+### 배포 묶음 (41개 · 43.5MB)
+
+| 묶음 | 파일 | 크기 | 까닭 |
+|---|---|---|---|
+| 셸 · 플랫폼 | `UbisamBase.Shell.exe` · `.exe.config` · `UbisamBase.Core.dll` | 0.7MB | 켜는 것 · .NET 4.8 · 버전 맞춤 · 플랫폼 |
+| MergeHub | `MergeHub.dll` | 0.6MB | 셸이 폴더의 dll 을 훑어 찾는다 |
+| Git | `LibGit2Sharp.dll` · `lib\win32\x64\git2-*.dll` · `lib\win32\x86\git2-*.dll` | 4.0MB | Git 조작 · 네이티브 |
+| MVVM · 인코딩 | `CommunityToolkit.Mvvm.dll` · `System.Text.Encoding.CodePages.dll` | 0.8MB | 플랫폼 · MergeHub · CP949 판별 |
+| 플랫폼 설정 · DI | `Microsoft.Extensions.*` 11개 | 0.5MB | 플랫폼 |
+| .NET 보강 | `System.*` 9개 · `Microsoft.Bcl.AsyncInterfaces` | 1.1MB | 플랫폼 · Json |
+| 차트(플랫폼 몫) | `LiveChartsCore` 둘 · `SkiaSharp` 둘 · `HarfBuzzSharp` · 네이티브 6개(루트 · x64 · x86) | 35.8MB | 플랫폼 Core 가 참조(`ChartTheme`) |
+
+### 남은 것
+
+- 묶음은 `%TEMP%` 에 있다. 다른 PC 에 줄 자리(공유 폴더 · 문서 폴더)는 베타 때 정한다.
+- 지시서의 "다섯 탭" 은 지금 본 탭 4개(폴더 비교 · 파일 비교 · Git · 설정)다 — 지시서 글이 옛 것.
+- 웹 문서 발행(아티팩트)은 E-2 에서.
+- 플랫폼 배포 폴더(`D:\UbisamPlatform\bin`)는 **손대지 않았다**. 플랫폼 README 가 "UbisamBase.Shell 을 Release 로 빌드하면 이 폴더를 갱신" 한다고 하므로, 플랫폼을 새로 배포하면 이 스크립트를 다시 돌려야 한다.
+
## C-1. 있는 [커밋 수정] 을 다시 내 보고 바로잡음 (2026-09-18, 완료 — 8 만 이번 범위 밖)
### 고치기 전에 여덟 가지를 화면에서 다시 냈다
diff --git a/doc/history/changelog.md b/doc/history/changelog.md
index 29217f9..cafc11d 100644
--- a/doc/history/changelog.md
+++ b/doc/history/changelog.md
@@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
+| 2026-09-22 | **11차 B-1** — 배포 묶음. 매뉴얼이 가리키던 Release 출력이 09-07 것이라 폴더 비교 탭 등 6차 이후가 없었다(형식 142개 중 74개 없음). Release 로 다시 빌드하고, 셸 · MergeHub 에서 참조를 따라가 **어디서도 닿지 않는 dll 7개**(OpenTK · GLWpfControl · SkiaSharp.Views · LiveCharts WPF · Extensions.Options 둘)와 맥 · 리눅스 · ARM 용 네이티브 · pdb · 빈 Lang 을 뺀 묶음을 만든다 — 97MB → 43.5MB. 플랫폼 Core 가 직접 참조하는 LiveCharts · Skia 는 사용자 결정대로 남김. 새 `scripts\make-bundle.ps1` 이 빌드 · 묶기 · `배포묶음.txt`(무엇이 왜 들었는지)를 하고, 남의 파일이 든 폴더는 비우지 않는다. 빈 폴더에서 켜 네 탭 · 병합 한 길 끝까지. 제품 코드 변경 없음 | 아래 |
| 2026-09-22 | **11차 C-2** — 마지막이 아닌 커밋의 메시지를 바꾼다(기획안 "커밋 우클릭 → 이름 변경", 사용자 결정 좁은 범위). 이력 줄 우클릭 [메시지 바꾸기...] — MergeHub 첫 우클릭 메뉴이고, 우클릭한 줄을 먼저 고른다. 여러 줄 창과 확인 창("이 커밋과 그 뒤 N개가 새 해시로 다시 써집니다")을 거쳐, 고른 커밋 메시지만 갈고 뒤 커밋은 트리 · 작성자 · 작성 시각 그대로 다시 쓴다. 바꾸기 전 자리를 `refs/original/…` 에 남기고 알림 · 로그에 옛 → 새 HEAD. 올린 커밋 · 사이 병합 · 다른 브랜치도 품음 · 태그 · 작업 중 · 커밋 안 한 변경 · 다른 브랜치에만 있는 커밋이면 까닭을 알림으로 말하고 막는다. 화면에서 새 메시지가 끝 줄바꿈 없이 저장되는 것을 찾아 git `-m` 처럼 다듬게 함. 하네스 MergeSaveTest +40(272건) | 아래 |
| 2026-09-18 | **11차 C-1 (마무리)** — [커밋 수정] 을 누르기 전에 **먼저 묻는다**(사용자 결정): 스테이징해 둔 파일이 있으면 "파일 N개도 이 커밋에 들어갑니다", 이미 올린 커밋이면 "origin/… 에 이미 올라가 있습니다 · 고치면 다음 Push 가 거부됩니다". 둘 다면 두 줄을 한 창에. [아니오] 면 커밋도 메시지 칸도 그대로 둔다. 막지 않는 까닭은 앞선 커밋 수가 마지막 fetch 시점 기준이라 확실치 않아서다. 하네스 MergeSaveTest +7(232건) | 아래 |
| 2026-09-18 | **11차 C-1 (일부)** — [커밋 수정]의 여덟 가지를 화면에서 재현한 뒤 넷을 고침: [마지막 메시지]가 이력 맨 위(딴 브랜치일 수 있음) 대신 **HEAD 커밋** 메시지를 읽고, 고쳐 써도 **원래 작성자 · 작성 시각을 지키며**(git 과 같게), 병합 · rebase · cherry-pick · revert **진행 중에는 단추를 끄고**(예전엔 눌러서 실패), 알림에 **"옛 해시 → 새 해시 · 파일 N개 포함"**. 하네스 MergeSaveTest +15(225건). 스테이징 파일 처리(2)와 이미 올린 커밋(6)은 사용자 결정 대기 | 아래 |
diff --git a/doc/web/index.html b/doc/web/index.html
index 7c50307..c60fe99 100644
--- a/doc/web/index.html
+++ b/doc/web/index.html
@@ -302,7 +302,8 @@ figcaption{font-size:.82rem;color:var(--ink-3);margin-top:6px}
켜기와 탭 구성
-
MergeHub 는 dll 이라 혼자 실행되지 않는다. UbisamBase.Shell.exe 를 켜면 셸이 MergeHub 를 읽어 탭을 만든다. 개발 PC 에서는 src\MergeHub\bin\Release\net48\UbisamBase.Shell.exe 다.
+
MergeHub 는 dll 이라 혼자 실행되지 않는다. UbisamBase.Shell.exe 를 켜면 셸이 MergeHub 를 읽어 탭을 만든다. 개발 PC 에서는 src\MergeHub\bin\Release\net48\UbisamBase.Shell.exe 다 — Release 로 다시 빌드해야 최신이다.
+
다른 PC 에 줄 때는 scripts\make-bundle.ps1 로 배포 묶음(41개 · 약 44MB)을 만들어 폴더째 복사한다. 설치는 없다. 묶음 안의 배포묶음.txt 에 들어 있는 파일과 그 까닭, 뺀 파일과 그 까닭이 적혀 있다. 기본 위치는 %TEMP%\mergehub-beta 이고 -Out 으로 바꾼다.
| 탭 | 하는 일 |
| 폴더 비교 | 두 폴더에서 어느 파일이 다른지 찾는다. 파일을 두 번 누르면 파일 비교 탭으로 넘어간다. |
diff --git a/doc/진행상황.md b/doc/진행상황.md
index 3b971a6..23c1da2 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -343,7 +343,7 @@
| 상태 | 항목 |
|---|---|
-| 대기 | B-1 배포 폴더(`bin\Release\net48`)가 09-07 것 — 다시 만들고 꼭 필요한 파일 가리기(96MB) · 빈 폴더에서 병합 한 길 |
+| **완료** | B-1 배포 묶음 — 옛 Release(09-07)는 본 탭 3개 · 지금 형식 142개 중 74개가 없었다. Release 다시 빌드(경고 0 · 오류 0) · 참조를 따라가 **65개 97MB → 41개 43.5MB**(안 닿는 dll 7개 · 맥/리눅스/ARM 용 · pdb · 빈 Lang 뺌, 플랫폼 Core 가 참조하는 차트 몫은 사용자 결정대로 남김) · `scripts\make-bundle.ps1` 이 묶음과 `배포묶음.txt`(파일 · 크기 · 까닭) 를 만든다 · 빈 폴더(`%TEMP%\mergehub-beta`)에서 켜 네 탭 · 병합 한 길 끝까지(`647f1f8`, 줄끝 · BOM 바뀐 파일 0) · 매뉴얼 실행 위치 문장 고침 |
| 대기 | B-2 D 드라이브가 없는 PC · 설정 파일이 없는 첫 실행 (`D:\UbisamConfig` 고정, 플랫폼) |
### C. 커밋 메시지 수정
diff --git a/scripts/make-bundle.ps1 b/scripts/make-bundle.ps1
new file mode 100644
index 0000000..b84a22d
--- /dev/null
+++ b/scripts/make-bundle.ps1
@@ -0,0 +1,159 @@
+# MergeHub 배포 묶음 만들기 (11차 B-1, 2026-09-22)
+#
+# Release 로 빌드한 출력(src\MergeHub\bin\Release\net48)을 통째로 옮기면 97MB · 65개다.
+# 그 가운데 Windows PC 에서 MergeHub 를 켜는 데 쓰지 않는 것을 빼고 새 폴더에 옮긴 뒤,
+# 무엇이 왜 들어갔는지를 묶음 안의 "배포묶음.txt" 에 적는다.
+#
+# 빼는 것은 아래 $Excluded 에 까닭과 함께 적는다. 무엇을 넣을지 바꾸려면 여기만 고친다.
+# 뺀 묶음으로 빈 폴더에서 켜 네 탭 · 병합 한 길(C1Repo)을 끝까지 해 본 결과는
+# doc\history\2026-09-18-11차.md 의 B-1 에 있다.
+#
+# 쓰는 법:
+# powershell -ExecutionPolicy Bypass -File scripts\make-bundle.ps1
+# powershell -ExecutionPolicy Bypass -File scripts\make-bundle.ps1 -Out D:\배포\MergeHub_베타 -NoBuild
+#
+# 셸과 플랫폼 dll 은 빌드할 때 플랫폼 배포 폴더(D:\UbisamPlatform\bin)에서 복사되어 온다.
+# 플랫폼을 새로 배포했다면 이 스크립트를 다시 돌려야 묶음에 반영된다.
+
+param(
+ # 묶음을 만들 폴더. 이미 있으면 이 스크립트가 만든 묶음일 때만 비우고 다시 채운다.
+ [string]$Out = (Join-Path $env:TEMP "mergehub-beta"),
+ # 빌드를 건너뛰고 지금 있는 Release 출력으로 묶는다.
+ [switch]$NoBuild
+)
+
+$ErrorActionPreference = "Stop"
+$root = Split-Path -Parent $PSScriptRoot
+$project = Join-Path $root "src\MergeHub\MergeHub.csproj"
+$release = Join-Path $root "src\MergeHub\bin\Release\net48"
+$marker = "배포묶음.txt"
+
+# ---- 빼는 것 (까닭) --------------------------------------------------------
+# 경로는 묶음 폴더 기준. 끝이 \ 이면 폴더 통째.
+$Excluded = [ordered]@{
+ "GLWpfControl.dll" = "어디서도 참조하지 않음 (SkiaSharp.Views.WPF 만 부르고, 그것도 안 쓰임)"
+ "OpenTK.dll" = "어디서도 참조하지 않음 (GLWpfControl 만 부름)"
+ "SkiaSharp.Views.WPF.dll" = "어디서도 참조하지 않음 (LiveCharts WPF 컨트롤용)"
+ "SkiaSharp.Views.Desktop.Common.dll" = "어디서도 참조하지 않음"
+ "LiveChartsCore.SkiaSharpView.WPF.dll" = "어디서도 참조하지 않음 (플랫폼 Core 는 WPF 컨트롤이 아니라 LiveChartsCore 만 부름)"
+ "Microsoft.Extensions.Options.dll" = "어디서도 참조하지 않음"
+ "Microsoft.Extensions.Options.ConfigurationExtensions.dll" = "어디서도 참조하지 않음"
+ "libSkiaSharp.dylib" = "맥용"
+ "libHarfBuzzSharp.dylib" = "맥용"
+ "arm64\" = "ARM PC 용 Skia · HarfBuzz (사내 PC 는 x64)"
+ "lib\linux-arm\" = "리눅스용 libgit2"
+ "lib\linux-arm64\" = "리눅스용 libgit2"
+ "lib\linux-musl-arm\" = "리눅스용 libgit2"
+ "lib\linux-musl-arm64\" = "리눅스용 libgit2"
+ "lib\linux-musl-x64\" = "리눅스용 libgit2"
+ "lib\linux-ppc64le\" = "리눅스용 libgit2"
+ "lib\linux-x64\" = "리눅스용 libgit2"
+ "lib\osx-arm64\" = "맥용 libgit2"
+ "lib\osx-x64\" = "맥용 libgit2"
+ "lib\win32\arm64\" = "ARM PC 용 libgit2 (사내 PC 는 x64)"
+ "MergeHub.pdb" = "디버그 심볼"
+ "LibGit2Sharp.dll.config" = "Mono 용 dll 대응표 - .NET Framework 는 lib\win32 를 스스로 찾는다"
+ "Lang\" = "비어 있음 - 없으면 켤 때 플랫폼(LanguageService)이 만든다"
+ "README.txt" = "플랫폼 배포 폴더 안내문 - 묶음에는 맞지 않아 배포묶음.txt 로 바꿈"
+}
+
+# ---- 넣는 것이 왜 필요한지 (배포묶음.txt 에 적음) -------------------------
+function Why([string]$path) {
+ # switch 는 걸리는 규칙을 모두 돌려준다 - 위에 적은 것(더 좁은 것) 하나만 쓴다.
+ @(Reason $path)[0]
+}
+function Reason([string]$path) {
+ switch -Regex ($path) {
+ '^UbisamBase\.Shell\.exe$' { "셸 - 이것을 켠다" }
+ '^UbisamBase\.Shell\.exe\.config$' { "셸 설정 - .NET 4.8 · 어셈블리 버전 맞춤" }
+ '^UbisamBase\.Core\.dll$' { "플랫폼" }
+ '^MergeHub\.dll$' { "MergeHub - 셸이 폴더의 dll 을 훑어 찾는다" }
+ '^LibGit2Sharp\.dll$' { "Git 조작" }
+ '^lib\\win32\\' { "Git 네이티브(libgit2)" }
+ '^CommunityToolkit\.Mvvm\.dll$' { "MVVM (플랫폼 · MergeHub)" }
+ '^System\.Text\.Encoding\.CodePages' { "CP949 판별 (MergeHub)" }
+ '^Microsoft\.Extensions\.' { "플랫폼 설정 · DI" }
+ '^(LiveChartsCore|SkiaSharp|HarfBuzzSharp)' { "플랫폼 Core 가 참조하는 차트 (ChartTheme)" }
+ '^(x64|x86)\\|^lib(SkiaSharp|HarfBuzzSharp)\.dll$' { "차트 네이티브 (플랫폼 몫)" }
+ '^(System|Microsoft\.Bcl)\.' { "플랫폼 · Json 이 쓰는 .NET 보강" }
+ default { "(까닭 미상 - 확인 필요)" }
+ }
+}
+
+# ---- 1. 빌드 ----------------------------------------------------------------
+if (-not $NoBuild) {
+ $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
+ if (-not (Test-Path $vswhere)) { throw "vswhere 가 없습니다: $vswhere (-NoBuild 로 지금 있는 Release 출력을 쓸 수 있습니다)" }
+ $msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1
+ if (-not $msbuild) { throw "MSBuild 를 찾지 못했습니다." }
+ Write-Host "Release 빌드: $project"
+ & $msbuild $project -p:Configuration=Release -v:m -nologo
+ if ($LASTEXITCODE -ne 0) { throw "빌드 실패 (종료 코드 $LASTEXITCODE)" }
+}
+if (-not (Test-Path (Join-Path $release "MergeHub.dll"))) { throw "Release 출력이 없습니다: $release" }
+
+# ---- 2. 묶음 폴더 준비 - 이 스크립트가 만든 것만 비운다 ---------------------
+if (Test-Path $Out) {
+ $items = @(Get-ChildItem $Out -Force)
+ if ($items.Count -gt 0 -and -not (Test-Path (Join-Path $Out $marker))) {
+ throw "'$Out' 에 다른 파일이 있습니다. 이 스크립트가 만든 묶음($marker 있음)이거나 빈 폴더여야 합니다."
+ }
+ $items | Remove-Item -Recurse -Force
+} else {
+ New-Item -ItemType Directory -Path $Out | Out-Null
+}
+
+# ---- 3. 옮기기 --------------------------------------------------------------
+$files = Get-ChildItem $release -Recurse -File | ForEach-Object { $_.FullName.Substring($release.Length + 1) }
+$kept = @(); $dropped = @()
+foreach ($rel in $files) {
+ $reason = $null
+ foreach ($key in $Excluded.Keys) {
+ if (($key.EndsWith("\") -and $rel.StartsWith($key, [StringComparison]::OrdinalIgnoreCase)) -or
+ ($rel -ieq $key)) { $reason = $Excluded[$key]; break }
+ }
+ if ($reason) { $dropped += [pscustomobject]@{ Path = $rel; Why = $reason }; continue }
+
+ $dest = Join-Path $Out $rel
+ New-Item -ItemType Directory -Path (Split-Path $dest) -Force | Out-Null
+ Copy-Item (Join-Path $release $rel) $dest
+ $kept += [pscustomobject]@{ Path = $rel; Size = (Get-Item $dest).Length; Why = (Why $rel) }
+}
+
+# 꼭 있어야 하는 것
+foreach ($must in "UbisamBase.Shell.exe", "UbisamBase.Shell.exe.config", "UbisamBase.Core.dll", "MergeHub.dll", "LibGit2Sharp.dll") {
+ if (-not (Test-Path (Join-Path $Out $must))) { throw "묶음에 $must 가 없습니다." }
+}
+if (-not (Get-ChildItem (Join-Path $Out "lib\win32\x64") -Filter "git2-*.dll" -ErrorAction SilentlyContinue)) { throw "묶음에 lib\win32\x64\git2-*.dll 이 없습니다." }
+
+# ---- 4. 배포묶음.txt --------------------------------------------------------
+$total = ($kept | Measure-Object Size -Sum).Sum
+$commit = try { (& git -C $root rev-parse --short HEAD 2>$null) } catch { "-" }
+$dll = Get-Item (Join-Path $Out "MergeHub.dll")
+$shell = Get-Item (Join-Path $Out "UbisamBase.Shell.exe")
+$mb = { param($b) "{0:N1}MB" -f ($b / 1MB) }
+
+$lines = @()
+$lines += "MergeHub 배포 묶음"
+$lines += "=================="
+$lines += ""
+$lines += "UbisamBase.Shell.exe 를 켠다. 설치 없이 폴더째 복사해 쓴다."
+$lines += "설정 · 로그는 D:\UbisamConfig 에 쓴다(플랫폼)."
+$lines += ""
+$lines += "만든 시각 : " + (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
+$lines += "MergeHub : " + $dll.LastWriteTime.ToString("yyyy-MM-dd HH:mm") + " (커밋 $commit)"
+$lines += "셸 · 플랫폼 : " + $shell.LastWriteTime.ToString("yyyy-MM-dd HH:mm") + " (D:\UbisamPlatform\bin 에서 온 것)"
+$lines += ("파일 {0}개 · {1}" -f $kept.Count, (& $mb $total))
+$lines += ""
+$lines += "[ 들어 있는 것 ]"
+foreach ($k in $kept | Sort-Object Path) { $lines += (" {0,-58} {1,9} {2}" -f $k.Path, (& $mb $k.Size), $k.Why) }
+$lines += ""
+$lines += "[ 뺀 것 ] (Release 출력에는 있음)"
+foreach ($d in $dropped | Sort-Object Path) { $lines += (" {0,-58} {1}" -f $d.Path, $d.Why) }
+$lines += ""
+$lines += "이 파일은 scripts\make-bundle.ps1 이 만든다. 이 파일이 있어야 다음에 같은 폴더를 다시 채운다."
+[IO.File]::WriteAllLines((Join-Path $Out $marker), $lines, (New-Object Text.UTF8Encoding($true)))
+
+Write-Host ("묶음: {0} · 파일 {1}개 · {2} (뺀 것 {3}개)" -f $Out, $kept.Count, (& $mb $total), $dropped.Count)
+$unknown = @($kept | Where-Object { $_.Why -like "(까닭 미상*" })
+if ($unknown.Count) { Write-Warning ("까닭을 모르는 파일: " + (($unknown | ForEach-Object { $_.Path }) -join ", ")) }