@ojokesusu/lintasai 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.github/workflows/publish-npm.yml +40 -0
  2. package/.github/workflows/validate.yml +93 -0
  3. package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
  4. package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
  5. package/CHANGELOG.md +313 -0
  6. package/CLAUDE_universal_v1.md +1021 -0
  7. package/CONTRIBUTING.md +101 -0
  8. package/FIRST_SESSION_PROMPT_v1.md +7 -0
  9. package/JALANKAN_KIT.md +188 -0
  10. package/LICENSE +21 -0
  11. package/MULAI_DI_SINI.md +145 -0
  12. package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
  13. package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
  14. package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
  15. package/README.md +505 -0
  16. package/SETUP_POLA_B_PROMPT_v1.md +5 -0
  17. package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
  18. package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
  19. package/UPDATE_DOCS_PROMPT_v1.md +3 -0
  20. package/UPDATE_KIT_PROMPT_v1.md +213 -0
  21. package/bin/lintasai.js +81 -0
  22. package/docs/SIGNED_RELEASE.md +162 -0
  23. package/install-windows.ps1 +225 -0
  24. package/kit.ps1 +508 -0
  25. package/lib/agents-md.ps1 +174 -0
  26. package/lib/git-helpers.ps1 +104 -0
  27. package/lib/kit-files.psd1 +133 -0
  28. package/lib/manifest-signing.ps1 +65 -0
  29. package/lib/manifest.ps1 +267 -0
  30. package/lib/rollback.ps1 +241 -0
  31. package/lib/safety.ps1 +193 -0
  32. package/lib/template-deploy.ps1 +242 -0
  33. package/lib/version-detect.ps1 +161 -0
  34. package/package.json +36 -0
  35. package/setup-pola-b.ps1 +687 -0
  36. package/templates/ANALOGI_LIBRARY.md +7 -0
  37. package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
  38. package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
  39. package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
  40. package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
  41. package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
  42. package/templates/INDEX.md +157 -0
  43. package/templates/MCP_SETUP.md +1145 -0
  44. package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
  45. package/templates/ONBOARDING.md +172 -0
  46. package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
  47. package/templates/PROMPT_LIBRARY.md +790 -0
  48. package/templates/RLS_SETUP_PROMPT.md +167 -0
  49. package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
  50. package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
  51. package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
  52. package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
  53. package/templates/STACK_DETECTION_PATTERN.md +261 -0
  54. package/templates/STACK_GUIDE.md +564 -0
  55. package/templates/STACK_MIGRATION_GUIDE.md +154 -0
  56. package/templates/STACK_VERSIONS.md +31 -0
  57. package/templates/UPDATE_GUIDE.md +246 -0
  58. package/templates/_EXAMPLE.md +110 -0
  59. package/templates/_PATTERNS.md +173 -0
  60. package/templates/architecture.md +180 -0
  61. package/templates/architecture_auto.md +61 -0
  62. package/templates/decisions/README.md +108 -0
  63. package/templates/decisions/_TEMPLATE.md +84 -0
  64. package/templates/feature-flags-advanced.md +171 -0
  65. package/templates/github/CODEOWNERS.template +61 -0
  66. package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
  67. package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
  68. package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
  69. package/templates/github/RENOVATE_FRONTEND.json +28 -0
  70. package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
  71. package/templates/github/pull_request_template.md +44 -0
  72. package/templates/github/scripts/ai-review.js +153 -0
  73. package/templates/github/workflows/ai-review.yml +61 -0
  74. package/templates/github/workflows/backup-schemas.yml +169 -0
  75. package/templates/glossary.md +110 -0
  76. package/templates/split-agents/BACKEND.md +149 -0
  77. package/templates/split-agents/FRONTEND.md +141 -0
  78. package/templates/split-agents/SHARED.md +82 -0
  79. package/templates/split-agents/TOOLS.md +77 -0
  80. package/tests/Run-Tests.ps1 +19 -0
  81. package/tests/lib-safety.Tests.ps1 +66 -0
  82. package/tests/rollback.Tests.ps1 +66 -0
  83. package/tests/uninstall.Tests.ps1 +265 -0
  84. package/tests/update-kit.Tests.ps1 +78 -0
  85. package/uninstall.ps1 +794 -0
  86. package/update-kit.ps1 +907 -0
package/uninstall.ps1 ADDED
@@ -0,0 +1,794 @@
1
+ <#
2
+ .SYNOPSIS
3
+ uninstall.ps1 - Hapus lintasAI dari proyek dengan AMAN (manifest-based diff)
4
+
5
+ .DESCRIPTION
6
+ Script ini baca .claude-kit/.install-manifest.json (manifest yang dibuat setup-pola-b.ps1)
7
+ lalu compare hash sha256 tiap file kit-template vs file di disk:
8
+
9
+ PRISTINE (hash match) -> auto-delete, file persis sama dengan yang kit copy waktu install.
10
+ MODIFIED (hash beda) -> SKIP by default (user sudah edit). -AllowModified -> backup ke
11
+ .pre-uninstall-<timestamp>.bak lalu hapus. (-Force = alias deprecated)
12
+ SYMLINK (reparse pt) -> SKIP selalu. Symlink / junction tidak pernah di-auto-delete
13
+ supaya tidak bocorin isi file di luar project ke .bak.
14
+ BLOCKED (out of root)-> SKIP. Manifest entry yang resolve ke luar project ditolak
15
+ (proteksi path traversal). Lihat juga -AllowProjectRootMismatch.
16
+ LOCKED (hash gagal) -> SKIP. File ke-lock editor/AV; user diminta tutup editor.
17
+ MISSING (file gone) -> skip silent (user mungkin sudah hapus manual).
18
+
19
+ Direktori yang dibuat saat install (docs/, .github/, dst.) hanya di-delete kalau EMPTY
20
+ setelah file dihapus. Project file kamu di sana TETAP aman.
21
+
22
+ AGENTS.md default SKIP delete (heavy customization expected). Pakai -DeleteAgents untuk hapus.
23
+
24
+ Default uninstall TIDAK self-delete folder .claude-kit\ -- script tidak bisa hapus folder
25
+ yang sedang dia jalankan dari sana. Instruksi 1-langkah ditampilkan di akhir.
26
+
27
+ .PARAMETER DryRun
28
+ Tampilkan rencana hapus tanpa eksekusi (RECOMMENDED first run).
29
+ .PARAMETER AllowModified
30
+ Hapus juga file yang MODIFIED (dengan backup .bak). Default = skip modified.
31
+ Flag ini menggantikan -Force (deprecated) untuk bypass modified-file check.
32
+ .PARAMETER Force
33
+ [DEPRECATED] Backward-compat alias untuk -AllowModified. Akan menampilkan
34
+ deprecation warning. Pakai -AllowModified untuk explicit intent.
35
+ .PARAMETER DeleteAgents
36
+ Ikut hapus AGENTS.md di project root. Default = skip (user heavy-edit).
37
+ .PARAMETER KeepKit
38
+ Default-nya script memang TIDAK self-delete folder .claude-kit\ (tidak bisa saat running),
39
+ tapi instruksi manual ditampilkan. Pakai -KeepKit untuk SUPPRESS instruksi tersebut kalau
40
+ kamu memang mau retain folder kit (mis. lanjut pakai kit, cuma cleanup file deploy).
41
+ .PARAMETER Yes
42
+ Auto-confirm prompt Y/N (skip Read-Host). Untuk CI/automation. Manual user: tidak perlu.
43
+ PAKAI cuma kalau sudah lihat dry-run.
44
+ .PARAMETER AllowProjectRootMismatch
45
+ Override hard-fail kalau manifest project_root TIDAK match lokasi sekarang (folder di-rename
46
+ / di-move setelah install). Tanpa flag ini, script abort untuk mencegah delete ke salah lokasi.
47
+
48
+ .NOTES
49
+ Versi : 1.0.1
50
+ Tanggal: 2026-06-03
51
+ Run : .\.claude-kit\uninstall.ps1 -DryRun (lihat rencana dulu)
52
+ Atau : .\.claude-kit\uninstall.ps1 (eksekusi konservatif)
53
+ Atau : .\.claude-kit\uninstall.ps1 -AllowModified (hapus juga modified, dengan backup)
54
+ Legacy : .\.claude-kit\uninstall.ps1 -Force (DEPRECATED alias untuk -AllowModified)
55
+ Lebih ringkas: .\.claude-kit\kit.ps1 uninstall
56
+ #>
57
+
58
+ [CmdletBinding()]
59
+ param(
60
+ [switch]$DryRun,
61
+ [switch]$AllowModified,
62
+ [switch]$Force,
63
+ [switch]$DeleteAgents,
64
+ [switch]$KeepKit,
65
+ [switch]$Yes,
66
+ [switch]$AllowProjectRootMismatch
67
+ )
68
+
69
+ # ---- Deprecation handling: -Force jadi alias backward-compat untuk -AllowModified ----
70
+ # Sebelumnya -Force overloaded (modified bypass + signature bypass). Pisahkan supaya
71
+ # intent jelas. -Force masih bekerja untuk backward-compat tapi warn user.
72
+ if ($Force) {
73
+ Write-Host ''
74
+ Write-Host '[DEPRECATED] -Force flag. Pakai -AllowModified.' -ForegroundColor Yellow
75
+ Write-Host ' -Force masih bekerja sebagai alias untuk backward-compat,' -ForegroundColor Yellow
76
+ Write-Host ' tapi akan dihapus di versi mendatang. Ganti pemanggilan script kamu.' -ForegroundColor Yellow
77
+ Write-Host ''
78
+ if (-not $AllowModified) { $AllowModified = $true }
79
+ }
80
+
81
+ $ErrorActionPreference = 'Stop'
82
+
83
+ # ---- Resolve folder kit (tempat script ini berada) ----
84
+ $KitDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { (Get-Location).Path }
85
+ $ProjectRoot = Split-Path -Parent $KitDir
86
+ $ProjectName = Split-Path -Leaf $ProjectRoot
87
+ $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
88
+
89
+ # ---- Load shared safety helpers (Resolve-SafeProjectPath, Test-PathHasReparsePoint) ----
90
+ # Dot-source supaya helper jalan di scope script ini (perlu akses $ProjectRoot &
91
+ # $script:ProjectRootCanonical). Import-Module .ps1 tidak supported di PS 5.1 dan
92
+ # function di module punya scope sendiri (helper tidak akan lihat $ProjectRoot caller).
93
+ . (Join-Path $PSScriptRoot 'lib\safety.ps1')
94
+
95
+ # ---- Validasi posisi script: script HARUS di dalam folder bernama .claude-kit ----
96
+ # Kalau bukan, user mungkin salah jalankan dari folder lain. Fail-aman dengan pesan jelas.
97
+ $kitFolderName = Split-Path -Leaf $KitDir
98
+ if ($kitFolderName -ne '.claude-kit') {
99
+ Write-Host ''
100
+ Write-Host 'STOP: Script ini tidak berada di dalam folder .claude-kit\.' -ForegroundColor Red
101
+ Write-Host " Lokasi script sekarang: $KitDir" -ForegroundColor Red
102
+ Write-Host ''
103
+ Write-Host 'Kemungkinan:' -ForegroundColor Yellow
104
+ Write-Host ' (A) Kamu jalankan dari folder SALAH (proyek ini belum pernah install lintasAI).' -ForegroundColor Yellow
105
+ Write-Host ' -> Cek di File Explorer: buka root proyek, lihat ada folder .claude-kit\ atau tidak.' -ForegroundColor Yellow
106
+ Write-Host ' (B) Folder kit di-rename dari .claude-kit\ ke nama lain.' -ForegroundColor Yellow
107
+ Write-Host ' -> Rename balik jadi .claude-kit, lalu ulangi.' -ForegroundColor Yellow
108
+ Write-Host ''
109
+ Write-Host 'TIDAK ADA satu pun file proyek kamu yang disentuh script ini. Aman.' -ForegroundColor Green
110
+ exit 1
111
+ }
112
+
113
+ # ---- Cek manifest ----
114
+ $manifestPath = Join-Path $KitDir '.install-manifest.json'
115
+ if (-not (Test-Path $manifestPath)) {
116
+ Write-Host ''
117
+ Write-Host 'STOP: Tidak bisa lanjut karena file pencatat install hilang.' -ForegroundColor Red
118
+ Write-Host ''
119
+ Write-Host 'Apa yang terjadi:' -ForegroundColor White
120
+ Write-Host ' Setiap kali kit ter-install, dia bikin catatan kecil:' -ForegroundColor White
121
+ Write-Host " $manifestPath" -ForegroundColor DarkGray
122
+ Write-Host ' Catatan ini berisi DAFTAR file kit + sidik-jari (hash) tiap file.' -ForegroundColor White
123
+ Write-Host ' Tanpa catatan ini, script TIDAK BERANI hapus apapun karena' -ForegroundColor White
124
+ Write-Host ' takut salah hapus file proyek kamu (BUKAN file kit).' -ForegroundColor White
125
+ Write-Host ''
126
+ Write-Host 'Kenapa hilang? Tiga kemungkinan:' -ForegroundColor Yellow
127
+ Write-Host ' 1. Kit kamu versi LAMA (sebelum fitur catatan ini ada).' -ForegroundColor Yellow
128
+ Write-Host ' 2. Install dulu cuma preview (pakai -DryRun) = catatan tidak ditulis.' -ForegroundColor Yellow
129
+ Write-Host ' 3. File catatan ter-hapus / kena git revert / setup gagal di tengah.' -ForegroundColor Yellow
130
+ Write-Host ''
131
+ Write-Host 'Recovery (pilih salah satu):' -ForegroundColor Cyan
132
+ Write-Host ' A. Re-run setup: .\.claude-kit\setup-pola-b.ps1 -Force' -ForegroundColor Cyan
133
+ Write-Host ' (akan re-generate manifest dari file kit yang ada di project).' -ForegroundColor Cyan
134
+ Write-Host ' B. Hapus manual sambil pelan-pelan:' -ForegroundColor Cyan
135
+ Write-Host ' 1. Buka README.md, cari section "Kalau manifest TIDAK ADA".' -ForegroundColor Cyan
136
+ Write-Host ' 2. Di sana ada DAFTAR FILE yang kit install. Cek satu-satu.' -ForegroundColor Cyan
137
+ Write-Host ' 3. Hapus file dari list itu (yang BELUM kamu edit).' -ForegroundColor Cyan
138
+ Write-Host ' 4. Terakhir hapus folder .claude-kit\ sendiri.' -ForegroundColor Cyan
139
+ exit 1
140
+ }
141
+
142
+ # ---- Load manifest signing helpers (HMAC verify) ----
143
+ # Dot-source supaya New-ManifestSignature + Test-ManifestSignature available di scope ini.
144
+ $signingLib = Join-Path $KitDir 'lib\manifest-signing.ps1'
145
+ $signingLibOk = $false
146
+ if (Test-Path -LiteralPath $signingLib) {
147
+ try {
148
+ . $signingLib
149
+ $signingLibOk = $true
150
+ } catch {
151
+ Write-Host "WARN Gagal load lib\manifest-signing.ps1: $_" -ForegroundColor Yellow
152
+ Write-Host " Manifest signature verification akan di-skip (legacy fallback)." -ForegroundColor Yellow
153
+ }
154
+ }
155
+
156
+ # ---- Helper: deep-convert PSCustomObject (from ConvertFrom-Json) ke nested hashtable ----
157
+ # Diperlukan supaya hashtable-based signing function (Clone + Remove + ConvertTo-Json) bisa
158
+ # jalan ulang dengan output identik dengan saat manifest di-write (waktu setup-pola-b.ps1).
159
+ function ConvertTo-HashtableDeep {
160
+ param($Object)
161
+ if ($null -eq $Object) { return $null }
162
+ if ($Object -is [System.Collections.IDictionary]) {
163
+ $ht = [ordered]@{}
164
+ foreach ($k in $Object.Keys) { $ht[$k] = ConvertTo-HashtableDeep -Object $Object[$k] }
165
+ return $ht
166
+ }
167
+ if ($Object -is [System.Management.Automation.PSCustomObject]) {
168
+ $ht = [ordered]@{}
169
+ foreach ($prop in $Object.PSObject.Properties) {
170
+ $ht[$prop.Name] = ConvertTo-HashtableDeep -Object $prop.Value
171
+ }
172
+ return $ht
173
+ }
174
+ if ($Object -is [System.Collections.IEnumerable] -and -not ($Object -is [string])) {
175
+ $arr = @()
176
+ foreach ($item in $Object) { $arr += ,(ConvertTo-HashtableDeep -Object $item) }
177
+ return ,$arr
178
+ }
179
+ return $Object
180
+ }
181
+
182
+ # ---- Parse manifest ----
183
+ try {
184
+ $manifest = Get-Content $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
185
+ } catch {
186
+ Write-Host ''
187
+ Write-Host 'ERROR: Manifest tidak bisa di-parse (corrupt JSON):' -ForegroundColor Red
188
+ Write-Host " Path : $manifestPath" -ForegroundColor Red
189
+ Write-Host " Error: $_" -ForegroundColor Red
190
+ Write-Host ''
191
+ Write-Host 'Saran:' -ForegroundColor Yellow
192
+ Write-Host " 1. Rename file ke .install-manifest.json.corrupt-$Timestamp.bak supaya bisa di-debug." -ForegroundColor Yellow
193
+ Write-Host ' 2. Re-run setup-pola-b.ps1 -Force untuk regenerate manifest dari file yang ada.' -ForegroundColor Yellow
194
+ Write-Host ' 3. Atau lihat README section "Kalau manifest TIDAK ADA" untuk fallback manual.' -ForegroundColor Yellow
195
+ exit 1
196
+ }
197
+
198
+ # ---- Verify manifest HMAC signature (prevent tampering) ----
199
+ # Manifest unsigned (legacy / older kit) -> warn + prompt continue.
200
+ # Manifest signature INVALID -> warn + prompt continue (default ABORT kalau interactive).
201
+ # Manifest signature VALID -> silent, continue normally.
202
+ if ($signingLibOk) {
203
+ $expectedSig = $null
204
+ if ($manifest.metadata -and $manifest.metadata.signature) {
205
+ $expectedSig = [string]$manifest.metadata.signature
206
+ }
207
+
208
+ if (-not $expectedSig) {
209
+ Write-Host ''
210
+ Write-Host '[!] Manifest tidak ber-signature (kit versi lama / install pre-HMAC).' -ForegroundColor Yellow
211
+ if (-not $Force -and -not $Yes) {
212
+ $legacyChoice = 'n'
213
+ try {
214
+ $legacyChoice = Read-Host 'Lanjut uninstall dengan manifest unsigned? (y/N)'
215
+ } catch {
216
+ Write-Host 'INFO: NonInteractive shell. Pakai -Yes atau -Force untuk override.' -ForegroundColor Yellow
217
+ $legacyChoice = 'n'
218
+ }
219
+ if ($legacyChoice -notmatch '^[Yy]') {
220
+ throw 'Uninstall aborted: unsigned manifest (user declined to proceed)'
221
+ }
222
+ }
223
+ } else {
224
+ try {
225
+ $manifestHt = ConvertTo-HashtableDeep -Object $manifest
226
+ # Clone + drop signature field sebelum re-sign (signature dihitung pada manifest WITHOUT signature)
227
+ $manifestCopy = ConvertTo-HashtableDeep -Object $manifest
228
+ if ($manifestCopy.metadata -is [System.Collections.IDictionary]) {
229
+ $null = $manifestCopy.metadata.Remove('signature')
230
+ }
231
+ $kitVerForVerify = ''
232
+ if ($manifest.metadata -and $manifest.metadata.kit_version) {
233
+ $kitVerForVerify = [string]$manifest.metadata.kit_version
234
+ } elseif ($manifest.kit_version) {
235
+ $kitVerForVerify = [string]$manifest.kit_version
236
+ }
237
+ $valid = Test-ManifestSignature -Manifest $manifestCopy -KitVersion $kitVerForVerify -ExpectedSignature $expectedSig
238
+ if (-not $valid) {
239
+ Write-Warning '[!] Manifest signature INVALID. Manifest mungkin di-tamper.'
240
+ if (-not $Force) {
241
+ $choice = 'n'
242
+ try {
243
+ $choice = Read-Host 'Lanjut uninstall dengan manifest unsigned/tampered? (y/N)'
244
+ } catch {
245
+ Write-Host 'INFO: NonInteractive shell. Pakai -Force untuk override.' -ForegroundColor Yellow
246
+ $choice = 'n'
247
+ }
248
+ if ($choice -ne 'y') { throw 'Uninstall aborted: untrusted manifest' }
249
+ }
250
+ }
251
+ $null = $manifestHt # reserved for downstream tooling kalau perlu hashtable view
252
+ } catch {
253
+ if ($_.Exception.Message -match 'Uninstall aborted') { throw }
254
+ Write-Host "WARN Manifest signature verification gagal jalan: $_" -ForegroundColor Yellow
255
+ Write-Host ' Lanjut dengan asumsi manifest trusted (legacy fallback).' -ForegroundColor Yellow
256
+ }
257
+ }
258
+ }
259
+
260
+ # ---- Validate schema_version (forward-compat guard) ----
261
+ $schemaVersion = [int]([string]$manifest.schema_version)
262
+ if ($schemaVersion -ne 1) {
263
+ Write-Host ''
264
+ Write-Host "ERROR: Schema version manifest = $schemaVersion (script ini cuma kenal schema_version=1)." -ForegroundColor Red
265
+ Write-Host ' Manifest mungkin dari kit versi lebih baru dari script uninstall ini.' -ForegroundColor Red
266
+ Write-Host ' Update kit ke versi terbaru (.\.claude-kit\kit.ps1 update) atau gunakan uninstall.ps1 versi sama.' -ForegroundColor Red
267
+ exit 1
268
+ }
269
+
270
+ # ---- Sanity check: project_root manifest match folder kit saat ini? ----
271
+ # Default HARD-FAIL untuk mencegah manifest dari project lain delete file di project ini.
272
+ # Override pakai -AllowProjectRootMismatch (mis. legitimate rename folder).
273
+ $manifestProjectRoot = [string]$manifest.project_root
274
+ if ($manifestProjectRoot -and $manifestProjectRoot -ne $ProjectRoot -and $manifestProjectRoot -ne '<PROJECT_ROOT>') {
275
+ Write-Host ''
276
+ if (-not $AllowProjectRootMismatch) {
277
+ Write-Host 'ABORT: Project root di manifest TIDAK match lokasi sekarang.' -ForegroundColor Red
278
+ Write-Host " Manifest installed di : $manifestProjectRoot" -ForegroundColor Red
279
+ Write-Host " Lokasi sekarang : $ProjectRoot" -ForegroundColor Red
280
+ Write-Host ''
281
+ Write-Host 'Kemungkinan:' -ForegroundColor Yellow
282
+ Write-Host ' (A) Folder proyek di-rename / di-move setelah install.' -ForegroundColor Yellow
283
+ Write-Host ' (B) Folder .claude-kit\ di-copy dari proyek lain (manifest milik proyek lain).' -ForegroundColor Yellow
284
+ Write-Host ''
285
+ Write-Host 'Untuk proceed:' -ForegroundColor Cyan
286
+ Write-Host ' - Kalau (A): jalankan dengan -AllowProjectRootMismatch supaya pakai lokasi sekarang.' -ForegroundColor Cyan
287
+ Write-Host ' - Kalau (B): JANGAN proceed. Manifest entries kemungkinan punya path proyek lain.' -ForegroundColor Cyan
288
+ Write-Host ' Hapus manual sesuai daftar file di README section "Kalau manifest TIDAK ADA".' -ForegroundColor Cyan
289
+ exit 1
290
+ } else {
291
+ Write-Host 'PERINGATAN: project_root mismatch di-override via -AllowProjectRootMismatch.' -ForegroundColor Yellow
292
+ Write-Host " Manifest installed di : $manifestProjectRoot" -ForegroundColor Yellow
293
+ Write-Host " Lokasi sekarang : $ProjectRoot" -ForegroundColor Yellow
294
+ Write-Host ' Script pakai lokasi sekarang ($ProjectRoot) sebagai base.' -ForegroundColor Yellow
295
+ Write-Host ''
296
+ }
297
+ }
298
+
299
+ # ---- Setup canonical project root (consumed by lib/safety.ps1 helpers) ----
300
+ # Helpers Resolve-SafeProjectPath & Test-PathHasReparsePoint baca $script:ProjectRootCanonical
301
+ # sebagai base containment check. WAJIB di-set sebelum panggil helper apapun.
302
+ $script:ProjectRootCanonical = [System.IO.Path]::GetFullPath($ProjectRoot)
303
+ if (-not $script:ProjectRootCanonical.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
304
+ $script:ProjectRootCanonical += [System.IO.Path]::DirectorySeparatorChar
305
+ }
306
+
307
+ # ---- Header ----
308
+ Write-Host ''
309
+ Write-Host '================================================================' -ForegroundColor Cyan
310
+ Write-Host ' lintasAI uninstall - safe diff-based delete' -ForegroundColor Cyan
311
+ Write-Host '================================================================' -ForegroundColor Cyan
312
+ Write-Host "Proyek : $ProjectName ($ProjectRoot)"
313
+ Write-Host "Kit version : $($manifest.kit_version)"
314
+ Write-Host "Installed at : $($manifest.installed_at) by $($manifest.installed_by)"
315
+ Write-Host "Files tracked : $((@($manifest.files)).Count)"
316
+ Write-Host "Dirs tracked : $((@($manifest.directories_created)).Count)"
317
+
318
+ # Flag aktif (collected + bulleted, bukan repeated "Mode" lines)
319
+ $flags = @()
320
+ if ($DryRun) { $flags += 'DRY-RUN (preview saja, tidak ada yang dihapus)' }
321
+ if ($AllowModified) { $flags += '-AllowModified (modified files akan di-backup + hapus)' }
322
+ if ($Force -and -not $AllowModified) { $flags += '-Force (DEPRECATED, alias untuk -AllowModified)' }
323
+ if ($DeleteAgents) { $flags += '-DeleteAgents (AGENTS.md ikut dievaluasi)' }
324
+ if ($KeepKit) { $flags += '-KeepKit (instruksi self-delete .claude-kit\ disuppress)' }
325
+ if ($Yes) { $flags += '-Yes (auto-confirm Y, skip prompt)' }
326
+ if ($AllowProjectRootMismatch) { $flags += '-AllowProjectRootMismatch (project_root manifest override)' }
327
+ if ($flags.Count -gt 0) {
328
+ Write-Host 'Flag aktif :'
329
+ foreach ($f in $flags) { Write-Host " - $f" -ForegroundColor Yellow }
330
+ } else {
331
+ Write-Host 'Flag aktif : (default - konservatif, skip modified)' -ForegroundColor DarkGray
332
+ }
333
+ Write-Host ''
334
+
335
+ # ---- Classify each file: pristine / modified / symlink / blocked / locked / missing ----
336
+ $pristine = @()
337
+ $modified = @()
338
+ $symlinked = @()
339
+ $blocked = @()
340
+ $locked = @()
341
+ $missing = @()
342
+ $skipped = @()
343
+ $backups = @() # kind='backup' entries — preserved + reported, never auto-deleted
344
+
345
+ foreach ($item in @($manifest.files)) {
346
+ $relPath = [string]$item.path
347
+ $itemKind = [string]$item.kind
348
+
349
+ # Skip AGENTS.md by default (heavy customization expected)
350
+ if ($relPath -eq 'AGENTS.md' -and -not $DeleteAgents) {
351
+ $skipped += @{ item = $item; reason = 'AGENTS.md skipped (pakai -DeleteAgents untuk override)' }
352
+ continue
353
+ }
354
+
355
+ # Backup files (kind='backup') — preserved + reported, never auto-deleted
356
+ if ($itemKind -eq 'backup') {
357
+ $backups += @{ item = $item }
358
+ continue
359
+ }
360
+
361
+ # Path containment check (block traversal + absolute paths).
362
+ # Resolve-SafeProjectPath throws on reject (security boundary). Catch supaya satu entry
363
+ # bermasalah tidak abort seluruh uninstall - tetap append ke $blocked + lanjut entry berikutnya.
364
+ $fullPath = $null
365
+ try {
366
+ $fullPath = Resolve-SafeProjectPath -RelPath $relPath -Label "file '$relPath'"
367
+ } catch {
368
+ $blocked += @{ item = $item; reason = "path escapes project root (rejected: $($_.Exception.Message))" }
369
+ continue
370
+ }
371
+ if ($null -eq $fullPath) {
372
+ $blocked += @{ item = $item; reason = 'path escapes project root (rejected, lihat REJECT log di atas)' }
373
+ continue
374
+ }
375
+
376
+ if (-not (Test-Path -LiteralPath $fullPath)) {
377
+ $missing += @{ item = $item; path = $fullPath }
378
+ continue
379
+ }
380
+
381
+ # Reparse point check (junction / symlink)
382
+ if (Test-PathHasReparsePoint -FullPath $fullPath) {
383
+ $symlinked += @{ item = $item; path = $fullPath }
384
+ continue
385
+ }
386
+
387
+ # Hash compute — guarded with try/catch supaya 1 file locked tidak abort seluruh script
388
+ try {
389
+ $currentHash = (Get-FileHash -LiteralPath $fullPath -Algorithm SHA256 -ErrorAction Stop).Hash
390
+ } catch {
391
+ $locked += @{ item = $item; path = $fullPath; reason = $_.Exception.Message }
392
+ continue
393
+ }
394
+
395
+ if ($currentHash -eq $item.sha256) {
396
+ $pristine += @{ item = $item; path = $fullPath }
397
+ } else {
398
+ $modified += @{ item = $item; path = $fullPath; current_sha = $currentHash }
399
+ }
400
+ }
401
+
402
+ # ---- Print plan ----
403
+ Write-Host '--- RENCANA HAPUS ---' -ForegroundColor Cyan
404
+
405
+ if ($pristine.Count -gt 0) {
406
+ Write-Host ''
407
+ Write-Host ("[PRISTINE] {0} file (hash match, AUTO-DELETE):" -f $pristine.Count) -ForegroundColor Green
408
+ foreach ($p in $pristine) {
409
+ Write-Host (" - {0}" -f $p.item.path)
410
+ }
411
+ }
412
+
413
+ if ($modified.Count -gt 0) {
414
+ Write-Host ''
415
+ if ($AllowModified) {
416
+ Write-Host ("[MODIFIED] {0} file (user-edited, BACKUP + DELETE karena -AllowModified):" -f $modified.Count) -ForegroundColor Yellow
417
+ } else {
418
+ Write-Host ("[MODIFIED] {0} file (user-edited, SKIP - pakai -AllowModified untuk hapus dengan backup):" -f $modified.Count) -ForegroundColor Yellow
419
+ }
420
+ foreach ($m in $modified) {
421
+ Write-Host (" [edit] {0}" -f $m.item.path) -ForegroundColor Yellow
422
+ }
423
+ }
424
+
425
+ if ($symlinked.Count -gt 0) {
426
+ Write-Host ''
427
+ Write-Host ("[SYMLINK] {0} file (junction/symlink terdeteksi, SKIP selalu - cek manual):" -f $symlinked.Count) -ForegroundColor Magenta
428
+ foreach ($s in $symlinked) {
429
+ Write-Host (" [link] {0}" -f $s.item.path) -ForegroundColor Magenta
430
+ }
431
+ }
432
+
433
+ if ($locked.Count -gt 0) {
434
+ Write-Host ''
435
+ Write-Host ("[LOCKED] {0} file (hash gagal - mungkin di-buka editor/AV):" -f $locked.Count) -ForegroundColor DarkYellow
436
+ foreach ($l in $locked) {
437
+ Write-Host (" [lock] {0}" -f $l.item.path) -ForegroundColor DarkYellow
438
+ Write-Host (" reason: {0}" -f $l.reason) -ForegroundColor DarkGray
439
+ }
440
+ Write-Host ' HINT: Tutup file di editor (VS Code/Notepad++/Notepad), lalu re-run.' -ForegroundColor DarkYellow
441
+ }
442
+
443
+ if ($blocked.Count -gt 0) {
444
+ Write-Host ''
445
+ Write-Host ("[BLOCKED] {0} file (path escape ke luar project - DITOLAK, lihat REJECT log di atas):" -f $blocked.Count) -ForegroundColor Red
446
+ foreach ($b in $blocked) {
447
+ Write-Host (" [reject] {0}" -f $b.item.path) -ForegroundColor Red
448
+ }
449
+ }
450
+
451
+ if ($missing.Count -gt 0) {
452
+ Write-Host ''
453
+ Write-Host ("[MISSING] {0} file (sudah tidak ada, skip):" -f $missing.Count) -ForegroundColor DarkGray
454
+ foreach ($m in $missing) {
455
+ Write-Host (" - {0}" -f $m.item.path) -ForegroundColor DarkGray
456
+ }
457
+ Write-Host (' INFO: kalau curiga file di-rename, cek manual di project tree.') -ForegroundColor DarkGray
458
+ }
459
+
460
+ if ($skipped.Count -gt 0) {
461
+ Write-Host ''
462
+ Write-Host ("[SKIPPED] {0} file:" -f $skipped.Count) -ForegroundColor Cyan
463
+ foreach ($s in $skipped) {
464
+ Write-Host (" - {0} ({1})" -f $s.item.path, $s.reason) -ForegroundColor Cyan
465
+ }
466
+ }
467
+
468
+ if ($backups.Count -gt 0) {
469
+ Write-Host ''
470
+ Write-Host ("[BACKUPS] {0} file backup pre-setup ditemukan (PRESERVED, hapus manual kalau mau):" -f $backups.Count) -ForegroundColor Cyan
471
+ foreach ($b in $backups) {
472
+ Write-Host (" - {0} (dari setup -Force re-run sebelumnya)" -f $b.item.path) -ForegroundColor Cyan
473
+ }
474
+ }
475
+
476
+ # Dirs plan
477
+ Write-Host ''
478
+ Write-Host '[DIRS] Direktori dari manifest (cek empty sebelum hapus):' -ForegroundColor DarkCyan
479
+ foreach ($d in @($manifest.directories_created)) {
480
+ Write-Host (" - {0}" -f $d) -ForegroundColor DarkCyan
481
+ }
482
+
483
+ # Self-delete plan
484
+ Write-Host ''
485
+ if ($KeepKit) {
486
+ Write-Host '[KIT] Folder .claude-kit\ DIPERTAHANKAN (-KeepKit aktif - instruksi self-delete tidak akan ditampilkan)' -ForegroundColor Cyan
487
+ } else {
488
+ Write-Host '[KIT] Folder .claude-kit\ harus kamu hapus MANUAL (script tidak bisa self-delete saat running, instruksi di akhir).' -ForegroundColor Cyan
489
+ }
490
+
491
+ # ---- DRY-RUN summary + exit ----
492
+ if ($DryRun) {
493
+ Write-Host ''
494
+ Write-Host '--- RINGKASAN DRY-RUN ---' -ForegroundColor Cyan
495
+ Write-Host (" Aman dihapus (pristine) : $($pristine.Count) file") -ForegroundColor Green
496
+ if ($modified.Count -gt 0) {
497
+ if ($AllowModified) {
498
+ Write-Host (" User-edit + backup : $($modified.Count) file (karena -AllowModified)") -ForegroundColor Yellow
499
+ } else {
500
+ Write-Host (" User-edit (DILEWATI) : $($modified.Count) file - pakai -AllowModified kalau mau ikut hapus") -ForegroundColor Yellow
501
+ }
502
+ }
503
+ if ($symlinked.Count -gt 0) {
504
+ Write-Host (" Symlink/junction : $($symlinked.Count) file (SKIP selalu)") -ForegroundColor Magenta
505
+ }
506
+ if ($locked.Count -gt 0) {
507
+ Write-Host (" Locked (cek editor) : $($locked.Count) file") -ForegroundColor DarkYellow
508
+ }
509
+ if ($blocked.Count -gt 0) {
510
+ Write-Host (" BLOCKED (path traversal) : $($blocked.Count) file - manifest mungkin di-tamper") -ForegroundColor Red
511
+ }
512
+ Write-Host (" Sudah hilang : $($missing.Count) file") -ForegroundColor DarkGray
513
+ Write-Host (" Sengaja dilewati : $($skipped.Count) file (mis. AGENTS.md)") -ForegroundColor Cyan
514
+ if ($backups.Count -gt 0) {
515
+ Write-Host (" Backup pre-setup : $($backups.Count) file (preserved)") -ForegroundColor Cyan
516
+ }
517
+ Write-Host ''
518
+ Write-Host 'Yakin dengan rencana di atas?' -ForegroundColor White
519
+ Write-Host ' - Lanjut hapus beneran : jalankan ulang TANPA -DryRun' -ForegroundColor White
520
+ Write-Host ' - Batal : tutup window ini, tidak ada yang berubah' -ForegroundColor White
521
+ if ($modified.Count -gt 0 -and -not $AllowModified) {
522
+ Write-Host ' - Modified ikut : jalankan dengan -AllowModified (akan dibackup .bak dulu)' -ForegroundColor White
523
+ }
524
+ Write-Host ''
525
+ Write-Host 'Catatan: plan ini adalah SNAPSHOT saat dry-run. Kalau kamu edit file setelah ini' -ForegroundColor DarkGray
526
+ Write-Host ' dan sebelum run eksekusi, ulangi -DryRun untuk lihat plan terbaru.' -ForegroundColor DarkGray
527
+ exit 0
528
+ }
529
+
530
+ # ---- Confirm sebelum execute ----
531
+ Write-Host ''
532
+ $forceSuffix = ''
533
+ if ($AllowModified) { $forceSuffix = " + $($modified.Count) modified (with backup)" }
534
+ Write-Host ("Total file akan dihapus: {0} pristine{1}" -f $pristine.Count, $forceSuffix) -ForegroundColor Cyan
535
+ if ($Yes) {
536
+ Write-Host 'Auto-confirm via -Yes (skip Read-Host).' -ForegroundColor Cyan
537
+ $confirm = 'Y'
538
+ } else {
539
+ try {
540
+ $confirm = Read-Host 'Lanjut hapus? (Y/N)'
541
+ } catch {
542
+ Write-Host ''
543
+ Write-Host 'INFO: Terminal sekarang tidak bisa nerima jawaban Y/N interaktif.' -ForegroundColor Yellow
544
+ Write-Host ' (umumnya terjadi kalau dijalankan dari VSCode tab Output, CI, atau script wrapper).' -ForegroundColor Yellow
545
+ Write-Host ''
546
+ Write-Host 'Solusi:' -ForegroundColor Cyan
547
+ Write-Host ' A. Buka PowerShell terpisah (Start Menu -> ketik PowerShell -> Enter), cd ke project, lalu ulangi.' -ForegroundColor Cyan
548
+ Write-Host ' B. Sudah yakin dengan rencana? Jalankan ulang dengan tambah -Yes di akhir.' -ForegroundColor Cyan
549
+ Write-Host ' CONTOH: .\.claude-kit\uninstall.ps1 -Yes' -ForegroundColor Cyan
550
+ Write-Host ' (sama dengan jawab YA tanpa ditanya. PAKAI cuma kalau sudah lihat dry-run.)' -ForegroundColor Cyan
551
+ exit 0
552
+ }
553
+ }
554
+ if ($confirm -notmatch '^[Yy]') {
555
+ Write-Host 'Dibatalkan oleh user. Tidak ada file yang dihapus.' -ForegroundColor Yellow
556
+ exit 0
557
+ }
558
+
559
+ # ---- Execute: delete pristine files (with re-hash before delete = TOCTOU close) ----
560
+ Write-Host ''
561
+ Write-Host '--- EKSEKUSI ---' -ForegroundColor Cyan
562
+ $deletedCount = 0
563
+ $errorCount = 0
564
+ $rehashSkipped = 0
565
+ foreach ($p in $pristine) {
566
+ # Re-hash right before delete: kalau user edit file antara plan & confirm, skip.
567
+ try {
568
+ $rehash = (Get-FileHash -LiteralPath $p.path -Algorithm SHA256 -ErrorAction Stop).Hash
569
+ } catch {
570
+ Write-Host ("LOCK {0}: {1}" -f $p.item.path, $_.Exception.Message) -ForegroundColor DarkYellow
571
+ Write-Host ' HINT: Tutup file di editor (VS Code/Notepad++/Notepad), lalu re-run.' -ForegroundColor DarkYellow
572
+ $errorCount++
573
+ continue
574
+ }
575
+ if ($rehash -ne $p.item.sha256) {
576
+ Write-Host ("SKIP {0}: file berubah sejak plan dibuat (kemungkinan user edit setelah dry-run)" -f $p.item.path) -ForegroundColor Yellow
577
+ $rehashSkipped++
578
+ continue
579
+ }
580
+ # TOCTOU close: re-check reparse point IMMEDIATELY before Remove-Item.
581
+ # Attacker bisa race-swap file ke junction antara plan phase dan execute phase.
582
+ # Re-check di sini menutup window itu (best-effort, tidak atomic 100% tapi sangat menyempit window).
583
+ if (Test-PathHasReparsePoint -FullPath $p.path) {
584
+ Write-Host ("SKIP {0}: TOCTOU - jadi reparse point setelah plan, tidak dihapus" -f $p.item.path) -ForegroundColor Magenta
585
+ $rehashSkipped++
586
+ continue
587
+ }
588
+ $itemInfo = Get-Item -Force -LiteralPath $p.path -ErrorAction SilentlyContinue
589
+ if ($itemInfo -and $itemInfo.LinkType) {
590
+ Write-Host ("SKIP {0}: TOCTOU - sekarang symlink/junction (LinkType={1}), tidak dihapus" -f $p.item.path, $itemInfo.LinkType) -ForegroundColor Magenta
591
+ $rehashSkipped++
592
+ continue
593
+ }
594
+ try {
595
+ Remove-Item -LiteralPath $p.path -Force -ErrorAction Stop
596
+ Write-Host ("DEL {0}" -f $p.item.path) -ForegroundColor Green
597
+ $deletedCount++
598
+ } catch {
599
+ Write-Host ("FAIL {0}: {1}" -f $p.item.path, $_.Exception.Message) -ForegroundColor Red
600
+ Write-Host ' HINT: Tutup file di editor (VS Code/Notepad++/Notepad), lalu re-run.' -ForegroundColor DarkYellow
601
+ $errorCount++
602
+ }
603
+ }
604
+
605
+ # ---- Execute: backup + delete modified files (kalau -AllowModified) ----
606
+ if ($AllowModified -and $modified.Count -gt 0) {
607
+ foreach ($m in $modified) {
608
+ # Defense: derive bakPath dari validated $m.path (bukan $m.item.path raw)
609
+ $bakPath = "$($m.path).pre-uninstall-$Timestamp.bak"
610
+ # Defense: validate bakPath also stays inside project root (defense-in-depth)
611
+ try {
612
+ $bakResolved = [System.IO.Path]::GetFullPath($bakPath)
613
+ } catch {
614
+ Write-Host ("FAIL {0}: backup path tidak valid" -f $m.item.path) -ForegroundColor Red
615
+ $errorCount++
616
+ continue
617
+ }
618
+ if (-not $bakResolved.StartsWith($script:ProjectRootCanonical, [System.StringComparison]::OrdinalIgnoreCase)) {
619
+ Write-Host ("FAIL {0}: backup path escape project root (REJECT)" -f $m.item.path) -ForegroundColor Red
620
+ $errorCount++
621
+ continue
622
+ }
623
+ # Re-check: file still exists + still MODIFIED candidate (TOCTOU close)
624
+ if (-not (Test-Path -LiteralPath $m.path)) {
625
+ Write-Host ("SKIP {0}: file hilang sejak plan dibuat" -f $m.item.path) -ForegroundColor Yellow
626
+ continue
627
+ }
628
+ # TOCTOU close: re-check reparse point IMMEDIATELY before Copy-Item + Remove-Item.
629
+ # Attacker bisa race-swap file ke junction antara plan phase dan execute phase, yang
630
+ # bisa bocorin isi file di luar project ke .bak ATAU bikin delete redirect ke luar.
631
+ if (Test-PathHasReparsePoint -FullPath $m.path) {
632
+ Write-Host ("SKIP {0}: TOCTOU - jadi reparse point setelah plan, tidak di-backup/hapus" -f $m.item.path) -ForegroundColor Magenta
633
+ continue
634
+ }
635
+ $itemInfoM = Get-Item -Force -LiteralPath $m.path -ErrorAction SilentlyContinue
636
+ if ($itemInfoM -and $itemInfoM.LinkType) {
637
+ Write-Host ("SKIP {0}: TOCTOU - sekarang symlink/junction (LinkType={1}), tidak di-backup/hapus" -f $m.item.path, $itemInfoM.LinkType) -ForegroundColor Magenta
638
+ continue
639
+ }
640
+ try {
641
+ Copy-Item -LiteralPath $m.path -Destination $bakPath -Force -ErrorAction Stop
642
+ Remove-Item -LiteralPath $m.path -Force -ErrorAction Stop
643
+ Write-Host ("BAK {0} -> {1}" -f $m.item.path, (Split-Path -Leaf $bakPath)) -ForegroundColor Yellow
644
+ $deletedCount++
645
+ } catch {
646
+ Write-Host ("FAIL {0}: {1}" -f $m.item.path, $_.Exception.Message) -ForegroundColor Red
647
+ Write-Host ' HINT: Tutup file di editor (VS Code/Notepad++/Notepad), lalu re-run.' -ForegroundColor DarkYellow
648
+ # Kalau .bak terbuat tapi Remove gagal, kasih info supaya user tahu state filesystem
649
+ if (Test-Path -LiteralPath $bakPath) {
650
+ Write-Host (" NOTE: backup ada di {0} (file original tetap di tempat, hapus manual kalau perlu)" -f (Split-Path -Leaf $bakPath)) -ForegroundColor DarkGray
651
+ }
652
+ $errorCount++
653
+ }
654
+ }
655
+ }
656
+
657
+ # ---- Try remove empty directories (deepest first supaya nested directories aman) ----
658
+ # Skip reparse-point dirs (junction redirect) untuk defense-in-depth.
659
+ $dirsSorted = @($manifest.directories_created) | Sort-Object -Descending @{
660
+ Expression = { ($_.ToString() -split '[\\/]').Count }
661
+ }
662
+ $dirDeleted = 0
663
+ $dirsMissing = @()
664
+ foreach ($d in $dirsSorted) {
665
+ $dStr = [string]$d
666
+ # Resolve-SafeProjectPath throws on reject; catch supaya satu dir bermasalah tidak abort loop.
667
+ $fullDir = $null
668
+ try {
669
+ $fullDir = Resolve-SafeProjectPath -RelPath $dStr -Label "dir '$dStr'"
670
+ } catch {
671
+ # Path traversal in dir entry — already logged by Resolve-SafeProjectPath
672
+ $errorCount++
673
+ continue
674
+ }
675
+ if ($null -eq $fullDir) {
676
+ # Path traversal in dir entry — already logged by Resolve-SafeProjectPath
677
+ $errorCount++
678
+ continue
679
+ }
680
+ if (-not (Test-Path -LiteralPath $fullDir)) {
681
+ $dirsMissing += $dStr
682
+ continue
683
+ }
684
+ # Reparse-point guard: jangan ikuti junction yang mungkin redirect ke luar.
685
+ if (Test-PathHasReparsePoint -FullPath $fullDir) {
686
+ Write-Host ("KEEP {0}\ (reparse point terdeteksi, tidak diikuti)" -f $dStr) -ForegroundColor Magenta
687
+ continue
688
+ }
689
+ $entries = @(Get-ChildItem -LiteralPath $fullDir -Force -ErrorAction SilentlyContinue)
690
+ # Whitelist: file system noise (Windows/mac) yang clearly bukan user-intent
691
+ $systemNoise = @('desktop.ini', 'Thumbs.db', '.DS_Store')
692
+ $realEntries = @($entries | Where-Object { $_.Name -notin $systemNoise })
693
+ if ($realEntries.Count -eq 0) {
694
+ # Cleanup noise sebelum RMDIR
695
+ foreach ($noise in $entries) {
696
+ if ($noise.Name -in $systemNoise) {
697
+ try { Remove-Item -LiteralPath $noise.FullName -Force -ErrorAction Stop } catch { }
698
+ }
699
+ }
700
+ try {
701
+ Remove-Item -LiteralPath $fullDir -Force -ErrorAction Stop
702
+ Write-Host ("RMDIR {0}\" -f $dStr) -ForegroundColor Green
703
+ $dirDeleted++
704
+ } catch {
705
+ Write-Host ("FAIL rmdir {0}: {1}" -f $dStr, $_.Exception.Message) -ForegroundColor Red
706
+ $errorCount++
707
+ }
708
+ } else {
709
+ # Print breakdown isi yang tersisa (max 5) supaya user audit
710
+ $sample = @($realEntries | Select-Object -First 5 -ExpandProperty Name)
711
+ $sampleStr = $sample -join ', '
712
+ if ($realEntries.Count -gt 5) { $sampleStr += ', ...' }
713
+ Write-Host ("KEEP {0}\ ({1} file/dir user tersisa: {2})" -f $dStr, $realEntries.Count, $sampleStr) -ForegroundColor Cyan
714
+ }
715
+ }
716
+
717
+ # ---- REASSURANCE FIRST (sebelum self-delete instruction supaya user tenang dulu) ----
718
+ Write-Host ''
719
+ Write-Host '=== FILE PROYEK KAMU - STATUS ===' -ForegroundColor Green
720
+ Write-Host 'Yang AMAN (tidak disentuh sama sekali oleh script ini):' -ForegroundColor Green
721
+ foreach ($d in @($manifest.directories_created)) {
722
+ $dStr = [string]$d
723
+ $fullDir = Join-Path $ProjectRoot ($dStr -replace '/', '\')
724
+ if (Test-Path -LiteralPath $fullDir) {
725
+ $userFiles = @(Get-ChildItem -LiteralPath $fullDir -Force -ErrorAction SilentlyContinue)
726
+ if ($userFiles.Count -gt 0) {
727
+ Write-Host (" - {0}\ : {1} file/folder milikmu, AMAN" -f $dStr, $userFiles.Count) -ForegroundColor Green
728
+ }
729
+ }
730
+ }
731
+ if ($modified.Count -gt 0 -and -not $AllowModified) {
732
+ Write-Host (" - {0} file kit yang sudah kamu edit (modified) TETAP ADA di tempatnya." -f $modified.Count) -ForegroundColor Green
733
+ }
734
+ if ($skipped.Count -gt 0) {
735
+ Write-Host (' - AGENTS.md (kalau ada) TETAP ADA - default skip karena user edit.') -ForegroundColor Green
736
+ }
737
+ Write-Host 'Verifikasi: jalankan `git status` di project root - file project tidak boleh muncul sebagai deleted.' -ForegroundColor DarkGray
738
+
739
+ # ---- Self-delete instructions (kecuali -KeepKit) ----
740
+ if (-not $KeepKit) {
741
+ Write-Host ''
742
+ Write-Host '=== LANGKAH TERAKHIR (manual) ===' -ForegroundColor Cyan
743
+ Write-Host 'Folder .claude-kit\ TIDAK BISA dihapus oleh script ini (karena script ada di dalamnya).' -ForegroundColor Cyan
744
+ Write-Host ''
745
+ Write-Host 'Cara hapus:' -ForegroundColor White
746
+ Write-Host ' 1. TUTUP semua VSCode / editor yang sedang buka file di .claude-kit\ (cegah Access Denied).' -ForegroundColor White
747
+ Write-Host ' 2. Buka PowerShell baru di root proyek (folder INDUK dari .claude-kit\).' -ForegroundColor White
748
+ Write-Host ' 3. Copy-paste perintah berikut PERSIS:' -ForegroundColor White
749
+ Write-Host ''
750
+ Write-Host " Remove-Item -Recurse -Force '$KitDir'" -ForegroundColor Yellow
751
+ Write-Host ''
752
+ Write-Host ' 4. Cek folder .claude-kit\ sudah hilang dari File Explorer.' -ForegroundColor White
753
+ Write-Host ''
754
+ Write-Host 'Kalau muncul error "Access Denied": tutup VSCode, restart PowerShell, ulangi langkah 3.' -ForegroundColor DarkGray
755
+ }
756
+
757
+ # ---- Summary ----
758
+ Write-Host ''
759
+ Write-Host '================================================================' -ForegroundColor Green
760
+ Write-Host ' lintasAI uninstall - SELESAI' -ForegroundColor Green
761
+ Write-Host '================================================================' -ForegroundColor Green
762
+ Write-Host ("File dihapus : {0}" -f $deletedCount)
763
+ Write-Host ("Direktori dihapus : {0}" -f $dirDeleted)
764
+ if ($rehashSkipped -gt 0) {
765
+ Write-Host ("Re-hash mismatch : {0} (file berubah sejak plan, tidak dihapus)" -f $rehashSkipped) -ForegroundColor Yellow
766
+ }
767
+ if ($modified.Count -gt 0 -and -not $AllowModified) {
768
+ Write-Host ("Modified disimpan : {0} (run dengan -AllowModified kalau mau ikut hapus)" -f $modified.Count) -ForegroundColor Yellow
769
+ }
770
+ if ($symlinked.Count -gt 0) {
771
+ Write-Host ("Symlink dilewati : {0} (cek manual)" -f $symlinked.Count) -ForegroundColor Magenta
772
+ }
773
+ if ($locked.Count -gt 0) {
774
+ Write-Host ("Locked dilewati : {0} (tutup editor, re-run)" -f $locked.Count) -ForegroundColor DarkYellow
775
+ }
776
+ if ($blocked.Count -gt 0) {
777
+ Write-Host ("BLOCKED (traversal): {0} - manifest mungkin di-tamper, audit manual" -f $blocked.Count) -ForegroundColor Red
778
+ }
779
+ if ($dirsMissing.Count -gt 0) {
780
+ Write-Host ("Dir tracked hilang: {0} (mungkin di-rename: {1})" -f $dirsMissing.Count, ($dirsMissing -join ', ')) -ForegroundColor Yellow
781
+ }
782
+ if ($backups.Count -gt 0) {
783
+ Write-Host ("Backup pre-setup : {0} (preserved, hapus manual kalau mau)" -f $backups.Count) -ForegroundColor Cyan
784
+ }
785
+ if ($skipped.Count -gt 0) {
786
+ Write-Host ("Skipped : {0} (AGENTS.md / lain-lain)" -f $skipped.Count) -ForegroundColor Cyan
787
+ }
788
+ if ($errorCount -gt 0) {
789
+ Write-Host ("ERROR : {0} (cek log di atas)" -f $errorCount) -ForegroundColor Red
790
+ }
791
+ Write-Host ''
792
+
793
+ if ($errorCount -gt 0) { exit 1 }
794
+ exit 0