@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/update-kit.ps1 ADDED
@@ -0,0 +1,907 @@
1
+ <#
2
+ .SYNOPSIS
3
+ update-kit.ps1 - Update kit lintasAI di proyek ke versi terbaru via re-clone fresh.
4
+
5
+ .DESCRIPTION
6
+ Workflow atomic (zero half-state):
7
+ 1. Backup .claude-kit/ lama → .claude-kit.backup-<timestamp>
8
+ 2. Clone fresh dari GitHub ke .claude-kit/ baru
9
+ 3. Hapus .git/ internal supaya tidak konflik dengan git proyek user
10
+ 4. Re-run setup-pola-b.ps1 -Force (anti-overwrite preserve docs/ existing)
11
+ 5. Tampilkan diff CHANGELOG lama vs baru + action items
12
+
13
+ Cara B (re-clone fresh) sesuai README — paling clean, no merge conflict
14
+ possible, atomic operation.
15
+
16
+ .PARAMETER NoBackup
17
+ Skip backup .claude-kit/ lama (irreversible). Default: backup aktif.
18
+
19
+ .PARAMETER RepoUrl
20
+ Override GitHub URL (untuk fork private kamu sendiri).
21
+ Default: https://github.com/ojokesusu/lintasAI.git
22
+
23
+ .PARAMETER Branch
24
+ Branch yang di-clone. Default: main.
25
+
26
+ .PARAMETER DryRun
27
+ Preview tindakan tanpa eksekusi nyata.
28
+
29
+ .PARAMETER AllowUntrustedRepo
30
+ Bypass RepoUrl allowlist check (kalau pakai fork private / mirror tidak
31
+ terdaftar di $allowedRepoUrls). Tanpa flag ini, RepoUrl di luar allowlist
32
+ akan prompt user (interactive) atau abort (non-interactive).
33
+ UNSAFE — pakai cuma kalau yakin sumber tepercaya atau di CI.
34
+
35
+ .PARAMETER Force
36
+ [DEPRECATED] Backward-compat alias untuk -AllowUntrustedRepo (RepoUrl
37
+ allowlist bypass). Akan menampilkan deprecation warning. Pakai
38
+ -AllowUntrustedRepo untuk explicit intent.
39
+ CATATAN: -Force TIDAK lagi bypass GPG verify-tag. Untuk GPG bypass pakai
40
+ -AllowUnsignedTag (separation of concerns).
41
+
42
+ .PARAMETER AllowUnsignedTag
43
+ Bypass GPG tag signature verification (fail-closed by default).
44
+ Default: false — kalau tag tidak signed atau pubkey owner tidak ter-import,
45
+ update DI-ABORT (no interactive prompt). Pakai flag ini cuma kalau owner
46
+ belum publish signed release / kamu sengaja jalanin dari fork tanpa key.
47
+ Detail setup pubkey owner: docs/SIGNED_RELEASE.md.
48
+
49
+ .NOTES
50
+ Versi : 1.0
51
+ Tanggal: 2026-06-01
52
+ Kit :
53
+ Run : .\.claude-kit\update-kit.ps1
54
+ Atau : powershell -ExecutionPolicy Bypass -File .\.claude-kit\update-kit.ps1
55
+
56
+ Jalankan dari root proyek (folder yang ada .claude-kit/ di dalamnya).
57
+
58
+ Catatan keamanan:
59
+ - Script tidak modify file di luar .claude-kit/. docs/ + .github/ proyek user AMAN
60
+ (anti-overwrite dari setup-pola-b.ps1).
61
+ - Kalau git clone gagal (network/auth issue), backup akan di-rollback otomatis
62
+ untuk recovery.
63
+ #>
64
+
65
+ [CmdletBinding()]
66
+ param(
67
+ [switch]$NoBackup,
68
+ [string]$RepoUrl = 'https://github.com/ojokesusu/lintasAI.git',
69
+ [string]$Branch = 'main',
70
+ [switch]$DryRun,
71
+ [switch]$AllowUntrustedRepo,
72
+ [switch]$Force,
73
+ [switch]$AllowUnsignedTag
74
+ )
75
+
76
+ $ErrorActionPreference = 'Stop'
77
+
78
+ # ---- Deprecation handling: -Force jadi alias backward-compat untuk -AllowUntrustedRepo ----
79
+ # Sebelumnya -Force overloaded (GPG bypass + RepoUrl bypass). GPG bypass sekarang lewat
80
+ # -AllowUnsignedTag; -Force narrowed jadi alias RepoUrl-allowlist-bypass saja, dengan warn.
81
+ if ($Force) {
82
+ Write-Host ''
83
+ Write-Host '[DEPRECATED] -Force flag. Pakai -AllowUntrustedRepo.' -ForegroundColor Yellow
84
+ Write-Host ' -Force masih bekerja sebagai alias untuk RepoUrl-allowlist bypass,' -ForegroundColor Yellow
85
+ Write-Host ' tapi akan dihapus di versi mendatang. Ganti pemanggilan script kamu.' -ForegroundColor Yellow
86
+ Write-Host ' Untuk GPG bypass, pakai -AllowUnsignedTag (bukan -Force lagi).' -ForegroundColor Yellow
87
+ Write-Host ''
88
+ if (-not $AllowUntrustedRepo) { $AllowUntrustedRepo = $true }
89
+ }
90
+
91
+ # ---- Resolve paths (do this FIRST, sebelum Move-Item rename folder) ----
92
+ $kitDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { (Get-Location).Path }
93
+ $kitFolderName = Split-Path -Leaf $kitDir
94
+ $projectRoot = Split-Path -Parent $kitDir
95
+ $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
96
+ $backupDir = "$kitDir.backup-$timestamp"
97
+
98
+ # === UPDATE STRATEGY ENHANCEMENT (v1.0.0+) ===
99
+ # 4 helper functions + hook integration. See section 4.5 di CLAUDE_universal_v1.md.
100
+
101
+
102
+ # ---- Section: Get-LatestChangelogEntry ----
103
+ # Baca .claude-kit/CHANGELOG.md, ambil entry version paling atas (versi terbaru).
104
+ # Konvensi CHANGELOG: heading "## vX.Y.Z" atau "## [vX.Y.Z]" di awal baris.
105
+ # Return hashtable: @{ Version = 'v1.2.0'; Body = '...isi entry...' }
106
+ # Kalau gagal parse, return $null (caller harus handle).
107
+ function Get-LatestChangelogEntry {
108
+ param(
109
+ [string]$ChangelogPath = ".\.claude-kit\CHANGELOG.md"
110
+ )
111
+
112
+ try {
113
+ if (-not (Test-Path $ChangelogPath)) {
114
+ Write-Host "[WARN] CHANGELOG.md tidak ketemu di $ChangelogPath" -ForegroundColor Yellow
115
+ return $null
116
+ }
117
+
118
+ # Baca raw biar BOM-safe; -Encoding UTF8 di PS 5.1 sudah handle BOM otomatis.
119
+ $lines = Get-Content -Path $ChangelogPath -Encoding UTF8 -ErrorAction Stop
120
+
121
+ $latestVersion = $null
122
+ $bodyLines = New-Object System.Collections.Generic.List[string]
123
+ $inLatest = $false
124
+
125
+ # Pattern fleksibel: "## v1.2.0", "## [v1.2.0]", "## v1.2.0 — 2026-06-04"
126
+ $versionPattern = '^##\s*\[?(v\d+\.\d+\.\d+)\]?'
127
+
128
+ foreach ($line in $lines) {
129
+ if ($line -match $versionPattern) {
130
+ if ($null -eq $latestVersion) {
131
+ # Heading versi pertama yang ketemu = versi terbaru (CHANGELOG urut desc).
132
+ $latestVersion = $Matches[1]
133
+ $inLatest = $true
134
+ continue
135
+ } else {
136
+ # Heading versi kedua = stop, body entry pertama udah komplit.
137
+ break
138
+ }
139
+ }
140
+
141
+ if ($inLatest) {
142
+ $bodyLines.Add($line) | Out-Null
143
+ }
144
+ }
145
+
146
+ if ($null -eq $latestVersion) {
147
+ Write-Host "[WARN] Tidak ada heading versi (## vX.Y.Z) di CHANGELOG" -ForegroundColor Yellow
148
+ return $null
149
+ }
150
+
151
+ $body = ($bodyLines -join "`n").Trim()
152
+
153
+ return @{
154
+ Version = $latestVersion
155
+ Body = $body
156
+ }
157
+ }
158
+ catch {
159
+ Write-Host "[ERROR] Gagal parse CHANGELOG: $($_.Exception.Message)" -ForegroundColor Red
160
+ return $null
161
+ }
162
+ }
163
+
164
+
165
+ # ---- Section: Classify-UpdateTier ----
166
+ # Klasifikasi tier dari isi CHANGELOG entry. Return string: "Tier 1" / "Tier 2" / "Tier 3" / "Tier 4".
167
+ # Urutan cek (paling spesifik dulu): SCAN-REQUIRED -> BREAKING -> Tier 2 keywords -> Tier 1 default.
168
+ function Classify-UpdateTier {
169
+ param(
170
+ [Parameter(Mandatory = $true)]
171
+ [string]$EntryBody
172
+ )
173
+
174
+ try {
175
+ if ([string]::IsNullOrWhiteSpace($EntryBody)) {
176
+ # Entry kosong dianggap minor patch.
177
+ return "Tier 1"
178
+ }
179
+
180
+ # Cek label eksplisit lebih dulu (paling spesifik).
181
+ if ($EntryBody -match '\[SCAN-REQUIRED\]') {
182
+ return "Tier 4"
183
+ }
184
+
185
+ if ($EntryBody -match '\[BREAKING\]') {
186
+ return "Tier 3"
187
+ }
188
+
189
+ # Tier 2 = fitur/aturan baru. Keyword spesifik biar tidak false-positive.
190
+ $tier2Keywords = @(
191
+ 'tambah section',
192
+ 'fitur baru',
193
+ 'aturan AI',
194
+ 'aturan baru',
195
+ 'panduan baru',
196
+ 'section baru',
197
+ 'rule baru',
198
+ 'tambah fitur',
199
+ 'tambah aturan',
200
+ 'tambah panduan'
201
+ )
202
+
203
+ foreach ($kw in $tier2Keywords) {
204
+ # -match case-insensitive by default di PS.
205
+ if ($EntryBody -match [regex]::Escape($kw)) {
206
+ return "Tier 2"
207
+ }
208
+ }
209
+
210
+ # Default: Tier 1 (typo, fix kecil, perbaikan ringan).
211
+ return "Tier 1"
212
+ }
213
+ catch {
214
+ Write-Host "[WARN] Classify error, fallback Tier 1: $($_.Exception.Message)" -ForegroundColor Yellow
215
+ return "Tier 1"
216
+ }
217
+ }
218
+
219
+
220
+ # ---- Section: Format-UpdateSummary ----
221
+ # Compose ringkasan ramah staff non-programmer, pakai analogi tools digital populer.
222
+ # Return string multi-line siap di-Write-Host.
223
+ function Format-UpdateSummary {
224
+ param(
225
+ [Parameter(Mandatory = $true)]
226
+ [string]$Tier,
227
+ [Parameter(Mandatory = $true)]
228
+ [hashtable]$Entry
229
+ )
230
+
231
+ $version = $Entry.Version
232
+ $body = $Entry.Body
233
+
234
+ # Map tier -> analogi + action staff.
235
+ $analogi = ""
236
+ $action = ""
237
+
238
+ switch ($Tier) {
239
+ "Tier 1" {
240
+ $analogi = "Tier 1 (kayak WhatsApp minor update, 2.23.10 -> 2.23.11)"
241
+ $action = "Action: udah selesai. Tinggal pakai biasa, ga ada yang berubah cara kerjanya."
242
+ }
243
+ "Tier 2" {
244
+ $analogi = "Tier 2 (kayak iPhone iOS 17.3 -> 17.4 minor, ada fitur baru)"
245
+ $action = "Action: AI bakal otomatis pakai aturan/fitur baru sesi berikutnya. Restart chat = aman."
246
+ }
247
+ "Tier 3" {
248
+ $analogi = "Tier 3 BREAKING (kayak iPhone iOS 16 -> iOS 17 major, backup wajib)"
249
+ $action = "Action: BACA migration notes di CHANGELOG, jalanin PS commands yang tertera. Backup udah otomatis di .bak."
250
+ }
251
+ "Tier 4" {
252
+ $analogi = "Tier 4 SCAN-REQUIRED (kayak Tokopedia ganti algoritma kategori, perlu re-mapping)"
253
+ $action = "Action: paste ulang isi JALANKAN_KIT.md ke Claude Code. AI bakal re-scan project & re-bootstrap."
254
+ }
255
+ default {
256
+ $analogi = "Unknown tier — treat as Tier 1 (paling aman)"
257
+ $action = "Action: pakai biasa, monitor sesi berikutnya."
258
+ }
259
+ }
260
+
261
+ # Build summary string.
262
+ $sb = New-Object System.Text.StringBuilder
263
+ [void]$sb.AppendLine("")
264
+ [void]$sb.AppendLine("============================================================")
265
+ [void]$sb.AppendLine(" Kit Update Summary — $version")
266
+ [void]$sb.AppendLine("============================================================")
267
+ [void]$sb.AppendLine("Klasifikasi: $analogi")
268
+ [void]$sb.AppendLine("")
269
+ [void]$sb.AppendLine($action)
270
+ [void]$sb.AppendLine("")
271
+ [void]$sb.AppendLine("--- CHANGELOG entry (verbatim) ---")
272
+ if ([string]::IsNullOrWhiteSpace($body)) {
273
+ [void]$sb.AppendLine("(entry kosong)")
274
+ } else {
275
+ # Trim biar tidak kebanyakan whitespace.
276
+ [void]$sb.AppendLine($body)
277
+ }
278
+ [void]$sb.AppendLine("============================================================")
279
+
280
+ return $sb.ToString()
281
+ }
282
+
283
+
284
+ # ---- Section: Invoke-BackupCleanup ----
285
+ # Bersihin .bak files di project root:
286
+ # 1) Hapus yang > 30 hari old.
287
+ # 2) Per "base filename" (mis. AGENTS.md.backup-*), keep max 3 yang terbaru.
288
+ # Aman: kalau folder gak ada / gak ada .bak, return 0 tanpa error.
289
+ function Invoke-BackupCleanup {
290
+ param(
291
+ [string]$ProjectRoot = ".",
292
+ [int]$MaxAgeDays = 30,
293
+ [int]$KeepLatest = 3
294
+ )
295
+
296
+ try {
297
+ if (-not (Test-Path $ProjectRoot)) {
298
+ Write-Host "[WARN] Project root tidak ada: $ProjectRoot" -ForegroundColor Yellow
299
+ return 0
300
+ }
301
+
302
+ # Tangkep dua pola backup: *.bak DAN *.backup-* (sesuai existing AGENTS.md.backup-YYYYMMDD-HHMMSS).
303
+ $backupFiles = @()
304
+ $backupFiles += Get-ChildItem -Path $ProjectRoot -Filter "*.bak" -File -ErrorAction SilentlyContinue
305
+ $backupFiles += Get-ChildItem -Path $ProjectRoot -Filter "*.backup-*" -File -ErrorAction SilentlyContinue
306
+
307
+ if (-not $backupFiles -or $backupFiles.Count -eq 0) {
308
+ Write-Host "Cleanup: tidak ada backup file ditemukan." -ForegroundColor DarkGray
309
+ return 0
310
+ }
311
+
312
+ $removed = 0
313
+ $cutoff = (Get-Date).AddDays(-1 * $MaxAgeDays)
314
+
315
+ # --- Step 1: hapus yang udah > MaxAgeDays ---
316
+ foreach ($f in $backupFiles) {
317
+ if ($f.LastWriteTime -lt $cutoff) {
318
+ try {
319
+ Remove-Item -Path $f.FullName -Force -ErrorAction Stop
320
+ $removed++
321
+ }
322
+ catch {
323
+ Write-Host "[WARN] Gagal hapus $($f.Name): $($_.Exception.Message)" -ForegroundColor Yellow
324
+ }
325
+ }
326
+ }
327
+
328
+ # --- Step 2: per base-name, keep latest N saja ---
329
+ # "Base name" = nama file sebelum suffix .bak / .backup-*. Contoh:
330
+ # AGENTS.md.backup-20260601-160733 -> base "AGENTS.md"
331
+ # docs.md.bak -> base "docs.md"
332
+ $remaining = @()
333
+ $remaining += Get-ChildItem -Path $ProjectRoot -Filter "*.bak" -File -ErrorAction SilentlyContinue
334
+ $remaining += Get-ChildItem -Path $ProjectRoot -Filter "*.backup-*" -File -ErrorAction SilentlyContinue
335
+
336
+ $groups = $remaining | Group-Object -Property {
337
+ $name = $_.Name
338
+ if ($name -match '^(.+?)\.backup-') { return $Matches[1] }
339
+ if ($name -match '^(.+?)\.bak$') { return $Matches[1] }
340
+ return $name
341
+ }
342
+
343
+ foreach ($grp in $groups) {
344
+ if ($grp.Count -le $KeepLatest) { continue }
345
+
346
+ # Sort desc by LastWriteTime, ambil yang setelah index KeepLatest = excess (paling lama).
347
+ $sorted = $grp.Group | Sort-Object -Property LastWriteTime -Descending
348
+ $excess = $sorted | Select-Object -Skip $KeepLatest
349
+
350
+ foreach ($f in $excess) {
351
+ try {
352
+ Remove-Item -Path $f.FullName -Force -ErrorAction Stop
353
+ $removed++
354
+ }
355
+ catch {
356
+ Write-Host "[WARN] Gagal hapus $($f.Name): $($_.Exception.Message)" -ForegroundColor Yellow
357
+ }
358
+ }
359
+ }
360
+
361
+ Write-Host "Cleanup: removed $removed old backup(s) (> $MaxAgeDays hari atau di luar latest-$KeepLatest)." -ForegroundColor DarkGray
362
+ return $removed
363
+ }
364
+ catch {
365
+ Write-Host "[ERROR] Backup cleanup gagal total: $($_.Exception.Message)" -ForegroundColor Red
366
+ return 0
367
+ }
368
+ }
369
+
370
+ # === END UPDATE STRATEGY ENHANCEMENT (functions) ===
371
+
372
+
373
+ # ---- Validasi posisi ----
374
+ if ($kitFolderName -ne '.claude-kit') {
375
+ Write-Host "PERINGATAN: Folder kit ini bernama '$kitFolderName', bukan '.claude-kit'." -ForegroundColor Yellow
376
+ Write-Host " Script update-kit.ps1 dirancang untuk Pola B (.claude-kit/ di root proyek)." -ForegroundColor Yellow
377
+ Write-Host " Tetap lanjut? Sebagian fitur (rename atomic) mungkin tidak presisi." -ForegroundColor Yellow
378
+ }
379
+
380
+ Write-Host ""
381
+ Write-Host "=== Update Kit lintasAI ===" -ForegroundColor Cyan
382
+ Write-Host "Kit folder : $kitDir"
383
+ Write-Host "Project root : $projectRoot"
384
+ Write-Host "Repo URL : $RepoUrl"
385
+ Write-Host "Branch : $Branch"
386
+ Write-Host "Backup : $(if ($NoBackup) { 'DISABLED (-NoBackup)' } else { $backupDir })"
387
+ if ($DryRun) {
388
+ Write-Host "Mode : DRY-RUN (tidak ada perubahan file)" -ForegroundColor Yellow
389
+ }
390
+ Write-Host ""
391
+
392
+ # ---- Pre-check: git installed ----
393
+ try {
394
+ $gitVersion = & git --version 2>$null
395
+ Write-Host "OK Git terdeteksi: $gitVersion" -ForegroundColor Green
396
+ } catch {
397
+ Write-Host "ERROR: git tidak terinstall atau tidak di PATH." -ForegroundColor Red
398
+ Write-Host " Install dari https://git-scm.com/ lalu buka PowerShell baru." -ForegroundColor Red
399
+ exit 1
400
+ }
401
+
402
+ # ---- Pre-check: RepoUrl allowlist (anti-supply-chain) ----
403
+ # Cegah update dari fork/mirror tak dikenal yang bisa inject kode jahat.
404
+ # Kalau user override $RepoUrl ke URL di luar allowlist, harus konfirm eksplisit.
405
+ $allowedRepoUrls = @(
406
+ 'https://github.com/ojokesusu/lintasAI.git'
407
+ )
408
+ if ($RepoUrl -notin $allowedRepoUrls) {
409
+ Write-Warning "RepoUrl '$RepoUrl' BUKAN di allowlist. Default: github.com/ojokesusu/lintasAI."
410
+ Write-Warning "Detail setup repo tepercaya: docs/SIGNED_RELEASE.md"
411
+ if (-not $AllowUntrustedRepo) {
412
+ $choice = try { Read-Host "Lanjut clone dari URL tidak terdaftar? (y/N)" } catch { "n" }
413
+ if ($choice -ne 'y') { throw "Update aborted: untrusted RepoUrl (pakai -AllowUntrustedRepo untuk bypass)" }
414
+ Write-Warning "User bypass allowlist (UNSAFE)"
415
+ } else {
416
+ Write-Warning "-AllowUntrustedRepo aktif: skip allowlist check (UNSAFE)"
417
+ }
418
+ }
419
+
420
+ # ---- Pre-check: detect current kit version (dari .install-manifest.json) ----
421
+ $manifestPath = Join-Path $kitDir '.install-manifest.json'
422
+ $currentVersion = $null
423
+ $manifestPresent = $false
424
+ if (Test-Path $manifestPath) {
425
+ $manifestPresent = $true
426
+ try {
427
+ $manifestRaw = Get-Content -Path $manifestPath -Raw -Encoding UTF8 -ErrorAction Stop
428
+ $manifestObj = $manifestRaw | ConvertFrom-Json -ErrorAction Stop
429
+ if ($manifestObj -and $manifestObj.metadata -and $manifestObj.metadata.kit_version) {
430
+ $currentVersion = [string]$manifestObj.metadata.kit_version
431
+ }
432
+ } catch {
433
+ Write-Host "WARN Gagal parse .install-manifest.json: $($_.Exception.Message)" -ForegroundColor Yellow
434
+ }
435
+ }
436
+
437
+ $canCheckRemote = [bool]$currentVersion
438
+ if ($currentVersion) {
439
+ Write-Host "OK Versi sekarang (manifest): $currentVersion" -ForegroundColor Green
440
+ } elseif ($manifestPresent) {
441
+ Write-Host "WARN manifest ada tapi metadata.kit_version kosong — skip version check." -ForegroundColor Yellow
442
+ $currentVersion = 'unknown'
443
+ } else {
444
+ Write-Host "INFO .install-manifest.json tidak ada — asumsi fresh install, skip version check." -ForegroundColor Cyan
445
+ $currentVersion = 'unknown'
446
+ }
447
+
448
+ # ---- Pre-check: query remote latest tag via git ls-remote ----
449
+ # Hanya dijalankan kalau ada manifest + kit_version (artinya ada baseline buat dibandingin).
450
+ if ($canCheckRemote -and -not $DryRun) {
451
+ Write-Host ""
452
+ Write-Host "Cek versi terbaru di remote..." -ForegroundColor Cyan
453
+
454
+ $latestVersion = $null
455
+ $lsRemoteOk = $false
456
+ try {
457
+ $oldEA = $ErrorActionPreference
458
+ $ErrorActionPreference = 'Continue'
459
+
460
+ # Jangan pakai 2>&1 di native exe (PS 5.1 promote stderr ke ErrorRecord).
461
+ # stderr akan auto ke console; kita cuma butuh stdout.
462
+ $remoteRefs = & git ls-remote --tags $RepoUrl
463
+ $lsExit = $LASTEXITCODE
464
+
465
+ $ErrorActionPreference = $oldEA
466
+
467
+ if ($lsExit -eq 0 -and $remoteRefs) {
468
+ $lsRemoteOk = $true
469
+
470
+ # Parse tags: format "<sha>\trefs/tags/vX.Y.Z" (optional ^{} dereference suffix).
471
+ $semverRegex = 'refs/tags/(v\d+\.\d+\.\d+)(?:\^\{\})?$'
472
+ $tagList = New-Object System.Collections.Generic.List[string]
473
+ foreach ($refLine in $remoteRefs) {
474
+ if ($refLine -match $semverRegex) {
475
+ $tagList.Add($Matches[1]) | Out-Null
476
+ }
477
+ }
478
+
479
+ if ($tagList.Count -gt 0) {
480
+ # Sort version-descending: strip leading 'v', cast ke [version], desc.
481
+ $sorted = $tagList | Sort-Object -Unique | Sort-Object -Property @{
482
+ Expression = { [version]($_ -replace '^v','') }
483
+ Descending = $true
484
+ }
485
+ $latestVersion = @($sorted)[0]
486
+ }
487
+ }
488
+ } catch {
489
+ $ErrorActionPreference = $oldEA
490
+ Write-Host "WARN git ls-remote exception: $($_.Exception.Message)" -ForegroundColor Yellow
491
+ }
492
+
493
+ if (-not $lsRemoteOk) {
494
+ Write-Host "WARN Gagal query remote tag (network/auth issue?)." -ForegroundColor Yellow
495
+ $ans = $null
496
+ try {
497
+ $ans = Read-Host "Lanjut update tanpa cek versi? [y/N]"
498
+ } catch {
499
+ # Non-interactive host -> default 'n'.
500
+ $ans = 'n'
501
+ }
502
+ if ([string]::IsNullOrWhiteSpace($ans)) { $ans = 'n' }
503
+ if ($ans -notmatch '^[Yy]') {
504
+ Write-Host "Dibatalkan oleh user." -ForegroundColor Yellow
505
+ exit 0
506
+ }
507
+ Write-Host "Lanjut update tanpa cek versi (atas konfirmasi user)..." -ForegroundColor Yellow
508
+ } elseif ([string]::IsNullOrWhiteSpace($latestVersion)) {
509
+ Write-Host "WARN Tag remote tidak ditemukan/parse-able — fall back: lanjut update dengan warning." -ForegroundColor Yellow
510
+ } else {
511
+ # Normalize: kit_version di manifest mungkin "1.0.0" tanpa 'v', remote tag pakai 'vX.Y.Z'.
512
+ $currentNorm = ($currentVersion -replace '^v','').Trim()
513
+ $latestNorm = ($latestVersion -replace '^v','').Trim()
514
+
515
+ if ($currentNorm -eq $latestNorm) {
516
+ Write-Host "[OK] Sudah versi terbaru ($currentVersion). Tidak ada update." -ForegroundColor Green
517
+ exit 0
518
+ }
519
+
520
+ # Compare semver-aware: kalau current >= latest, treat as up-to-date juga.
521
+ try {
522
+ $cv = [version]$currentNorm
523
+ $lv = [version]$latestNorm
524
+ if ($cv -ge $lv) {
525
+ Write-Host "[OK] Versi lokal ($currentVersion) >= remote latest ($latestVersion). Tidak ada update." -ForegroundColor Green
526
+ exit 0
527
+ }
528
+ } catch {
529
+ # Cast gagal -> fallback ke string compare di atas; lanjut update.
530
+ }
531
+
532
+ Write-Host "[INFO] Update tersedia: $currentVersion -> $latestVersion" -ForegroundColor Cyan
533
+ }
534
+ }
535
+
536
+ # ---- Step 1: Backup existing .claude-kit/ ----
537
+ if (-not $NoBackup) {
538
+ if ($DryRun) {
539
+ Write-Host "[DRY] Akan backup: $kitDir -> $backupDir" -ForegroundColor Yellow
540
+ } else {
541
+ Write-Host ""
542
+ Write-Host "Step 1: Backup .claude-kit/ lama..." -ForegroundColor Cyan
543
+ try {
544
+ Move-Item -Path $kitDir -Destination $backupDir -Force
545
+ Write-Host "OK Backup: $backupDir" -ForegroundColor Green
546
+ } catch {
547
+ Write-Host "ERROR: Backup gagal: $_" -ForegroundColor Red
548
+ Write-Host " Kemungkinan ada file yang di-lock (editor open, antivirus scan)." -ForegroundColor Red
549
+ exit 1
550
+ }
551
+ }
552
+ } else {
553
+ Write-Host "Step 1: Skip backup (-NoBackup aktif)" -ForegroundColor Yellow
554
+ # Tanpa backup, hapus langsung (irreversible)
555
+ if (-not $DryRun) {
556
+ try {
557
+ Remove-Item -Path $kitDir -Recurse -Force
558
+ Write-Host "OK Kit lama dihapus." -ForegroundColor Green
559
+ } catch {
560
+ Write-Host "ERROR: Gagal hapus kit lama: $_" -ForegroundColor Red
561
+ exit 1
562
+ }
563
+ }
564
+ }
565
+
566
+ # ---- Step 2: Clone fresh ke .claude-kit/ baru ----
567
+ Write-Host ""
568
+ Write-Host "Step 2: Clone fresh dari GitHub..." -ForegroundColor Cyan
569
+ if ($DryRun) {
570
+ Write-Host "[DRY] git clone --depth 1 -b $Branch $RepoUrl '$kitDir'" -ForegroundColor Yellow
571
+ } else {
572
+ Push-Location $projectRoot
573
+ # CRITICAL FIX (v1.0.0): wrap git clone di try/catch dengan ErrorActionPreference='Continue'
574
+ # supaya $LASTEXITCODE bisa di-cek tanpa stderr promote ke terminating error.
575
+ # Bug sebelumnya: $ErrorActionPreference='Stop' di scope file + 2>&1 di native exe
576
+ # bikin stderr 'fatal:' jadi terminating error yang bypass cek $LASTEXITCODE → rollback gak jalan.
577
+ $cloneOk = $false
578
+ $cloneErrorMsg = ''
579
+ try {
580
+ # Local ErrorActionPreference=Continue supaya native stderr tidak terminating
581
+ $oldErrorAction = $ErrorActionPreference
582
+ $ErrorActionPreference = 'Continue'
583
+
584
+ # JANGAN pakai 2>&1 di native exe (PS 5.1 promote stderr lines ke ErrorRecord).
585
+ # Stderr akan auto-ditampilkan ke console oleh native exe.
586
+ # Pakai try/catch + cek $LASTEXITCODE untuk control flow.
587
+ & git clone --depth 1 -b $Branch $RepoUrl '.claude-kit'
588
+ $cloneExitCode = $LASTEXITCODE
589
+
590
+ $ErrorActionPreference = $oldErrorAction
591
+
592
+ if ($cloneExitCode -eq 0) {
593
+ $cloneOk = $true
594
+ Write-Host "OK Clone selesai." -ForegroundColor Green
595
+ } else {
596
+ $cloneErrorMsg = "git clone exit code: $cloneExitCode (cek pesan error di atas)"
597
+ }
598
+ } catch {
599
+ $cloneErrorMsg = "git clone exception: $_"
600
+ $ErrorActionPreference = $oldErrorAction
601
+ } finally {
602
+ Pop-Location
603
+ }
604
+
605
+ if (-not $cloneOk) {
606
+ Write-Host ""
607
+ Write-Host "ERROR: $cloneErrorMsg" -ForegroundColor Red
608
+
609
+ # Rollback: restore backup (CRITICAL: must run kalau clone gagal)
610
+ if (-not $NoBackup -and (Test-Path $backupDir)) {
611
+ Write-Host ""
612
+ Write-Host "ROLLBACK: restore backup folder..." -ForegroundColor Yellow
613
+ try {
614
+ # Hapus folder partial .claude-kit kalau ada (clone gagal kadang bikin folder kosong)
615
+ if (Test-Path $kitDir) {
616
+ Remove-Item -Path $kitDir -Recurse -Force -ErrorAction SilentlyContinue
617
+ }
618
+ Move-Item -Path $backupDir -Destination $kitDir -Force
619
+ Write-Host "OK Backup restored. Kit lama aktif lagi di $kitDir" -ForegroundColor Green
620
+ } catch {
621
+ Write-Host "GAGAL rollback: $_" -ForegroundColor Red
622
+ Write-Host " MANUAL restore: Move-Item '$backupDir' '$kitDir'" -ForegroundColor Yellow
623
+ }
624
+ } else {
625
+ Write-Host "TIDAK ada backup untuk restore (-NoBackup aktif atau backup folder tidak ada)." -ForegroundColor Yellow
626
+ }
627
+ exit 1
628
+ }
629
+ }
630
+
631
+ # ---- Step 2b: Verify tag signature (GPG) sebelum hapus .git/ ----
632
+ # BUG FIX: sebelumnya call `git verify-tag $Branch` di mana $Branch = "main".
633
+ # verify-tag butuh TAG name, bukan branch name -> selalu fail -> fall through
634
+ # ke prompt y/N = security theater. Sekarang resolve tag eksak dari HEAD dulu,
635
+ # fallback ke latest tag remote, baru call verify-tag dengan nama tag betulan.
636
+ # FAIL-CLOSED: kalau verify gagal -> throw. Bypass cuma via -AllowUnsignedTag
637
+ # (bukan -Force; separation of concerns: -Force = allowlist, -AllowUnsignedTag = GPG).
638
+ $signatureValid = $false
639
+ if (-not $DryRun -and (Test-Path (Join-Path $kitDir '.git'))) {
640
+ Write-Host ""
641
+ Write-Host "Step 2b: Verify tag signature (GPG)..." -ForegroundColor Cyan
642
+
643
+ # --- Resolve actual tag NAME pointing to cloned HEAD ---
644
+ # `--depth 1 -b main` clone biasanya checkout commit yang juga ditag (latest release).
645
+ # `git describe --exact-match --tags HEAD` return tag name persis kalau HEAD == tag commit,
646
+ # otherwise exit non-zero. Itu signal yang kita mau (no fuzzy match).
647
+ $resolvedTag = $null
648
+ $oldEA = $ErrorActionPreference
649
+ $ErrorActionPreference = 'Continue'
650
+ try {
651
+ $describeOut = & git -C $kitDir describe --exact-match --tags HEAD 2>$null
652
+ $describeExit = $LASTEXITCODE
653
+ if ($describeExit -eq 0 -and $describeOut) {
654
+ $resolvedTag = ($describeOut | Select-Object -First 1).ToString().Trim()
655
+ Write-Host " Tag di HEAD (describe --exact-match): $resolvedTag" -ForegroundColor DarkGray
656
+ }
657
+ } catch {
658
+ # Swallow — fallback path di bawah.
659
+ }
660
+ $ErrorActionPreference = $oldEA
661
+
662
+ # --- Fallback: latest tag dari ls-remote (kalau HEAD bukan tag commit) ---
663
+ if (-not $resolvedTag) {
664
+ Write-Host " HEAD bukan exact tag — fallback ke latest tag via ls-remote..." -ForegroundColor DarkGray
665
+ $oldEA = $ErrorActionPreference
666
+ $ErrorActionPreference = 'Continue'
667
+ try {
668
+ $lsTags = & git ls-remote --tags $RepoUrl 2>$null
669
+ if ($LASTEXITCODE -eq 0 -and $lsTags) {
670
+ $semverRegex = 'refs/tags/(v\d+\.\d+\.\d+)(?:\^\{\})?$'
671
+ $fallbackTagList = New-Object System.Collections.Generic.List[string]
672
+ foreach ($refLine in $lsTags) {
673
+ if ($refLine -match $semverRegex) {
674
+ $fallbackTagList.Add($Matches[1]) | Out-Null
675
+ }
676
+ }
677
+ if ($fallbackTagList.Count -gt 0) {
678
+ $sortedFallback = $fallbackTagList | Sort-Object -Unique | Sort-Object -Property @{
679
+ Expression = { [version]($_ -replace '^v','') }
680
+ Descending = $true
681
+ }
682
+ $resolvedTag = @($sortedFallback)[0]
683
+ Write-Host " Latest tag remote: $resolvedTag" -ForegroundColor DarkGray
684
+
685
+ # Pastikan tag ada di local clone object DB (--depth 1 -b main biasanya tidak fetch tags lain).
686
+ # Fetch tag spesifik dulu supaya verify-tag bisa baca object-nya.
687
+ & git -C $kitDir fetch --depth 1 origin "refs/tags/${resolvedTag}:refs/tags/${resolvedTag}" 2>$null | Out-Null
688
+ }
689
+ }
690
+ } catch {
691
+ # Swallow — handled di bawah kalau $resolvedTag tetap null.
692
+ }
693
+ $ErrorActionPreference = $oldEA
694
+ }
695
+
696
+ if (-not $resolvedTag) {
697
+ # Tidak ada tag sama sekali — FAIL-CLOSED kecuali user explicit bypass.
698
+ if ($AllowUnsignedTag) {
699
+ Write-Warning "Tidak ada tag GPG yang bisa di-resolve, tapi -AllowUnsignedTag aktif. Skip verify (UNSAFE)."
700
+ } else {
701
+ throw "Update aborted: tidak ada tag yang bisa di-resolve dari HEAD maupun ls-remote (release belum di-tag?). Pakai -AllowUnsignedTag untuk bypass (UNSAFE). Setup pubkey: docs/SIGNED_RELEASE.md"
702
+ }
703
+ } else {
704
+ # --- Verify-tag dengan tag NAME (bukan branch) ---
705
+ $oldEA = $ErrorActionPreference
706
+ $ErrorActionPreference = 'Continue'
707
+ $verifyOutput = & git -C $kitDir verify-tag $resolvedTag 2>&1
708
+ $verifyExit = $LASTEXITCODE
709
+ $ErrorActionPreference = $oldEA
710
+
711
+ if ($verifyExit -eq 0) {
712
+ Write-Host "[OK] Tag $resolvedTag GPG signature verified" -ForegroundColor Green
713
+ $signatureValid = $true
714
+ } else {
715
+ Write-Warning "[FAIL] Tag $resolvedTag bukan GPG-signed valid (atau pubkey owner tidak ter-import)"
716
+ Write-Host "Detail: $verifyOutput"
717
+ Write-Host "Setup pubkey owner: docs/SIGNED_RELEASE.md" -ForegroundColor Yellow
718
+ if ($AllowUnsignedTag) {
719
+ Write-Warning "Bypass via -AllowUnsignedTag aktif: lanjut tanpa verifikasi (UNSAFE)."
720
+ $signatureValid = $false
721
+ } else {
722
+ # FAIL-CLOSED: tidak ada prompt y/N. Kalau mau bypass harus explicit flag.
723
+ throw "Update aborted: GPG verify-tag $resolvedTag gagal. Pakai -AllowUnsignedTag untuk bypass (UNSAFE)."
724
+ }
725
+ }
726
+ }
727
+ }
728
+
729
+ # ---- Step 3: Hapus .git/ internal ----
730
+ $gitInternal = Join-Path $kitDir '.git'
731
+ Write-Host ""
732
+ Write-Host "Step 3: Hapus .git/ internal supaya tidak konflik dengan git proyek..." -ForegroundColor Cyan
733
+ if ($DryRun) {
734
+ Write-Host "[DRY] Remove-Item $gitInternal -Recurse -Force" -ForegroundColor Yellow
735
+ } elseif (Test-Path $gitInternal) {
736
+ try {
737
+ Remove-Item -Path $gitInternal -Recurse -Force
738
+ Write-Host "OK .git/ internal dihapus." -ForegroundColor Green
739
+ } catch {
740
+ Write-Host "WARN Gagal hapus .git/: $_" -ForegroundColor Yellow
741
+ Write-Host " Bisa nested git issue. Hapus manual: Remove-Item '$gitInternal' -Recurse -Force" -ForegroundColor Yellow
742
+ }
743
+ }
744
+
745
+ # ---- Step 4: Re-run setup-pola-b.ps1 ----
746
+ $setupScript = Join-Path $kitDir 'setup-pola-b.ps1'
747
+ Write-Host ""
748
+ Write-Host "Step 4: Re-run setup-pola-b.ps1 (anti-overwrite untuk docs/ existing)..." -ForegroundColor Cyan
749
+ if ($DryRun) {
750
+ Write-Host ("[DRY] Call: '{0}' -Force" -f $setupScript) -ForegroundColor Yellow
751
+ } elseif (Test-Path $setupScript) {
752
+ try {
753
+ & $setupScript -Force
754
+ } catch {
755
+ Write-Host "WARN setup-pola-b.ps1 error: $_" -ForegroundColor Yellow
756
+ Write-Host " File kit sudah ter-copy, tapi setup mungkin gak komplit." -ForegroundColor Yellow
757
+ Write-Host " Run manual: .\.claude-kit\setup-pola-b.ps1 -Force" -ForegroundColor Yellow
758
+ }
759
+ } else {
760
+ Write-Host "WARN setup-pola-b.ps1 tidak ada di kit baru — skip." -ForegroundColor Yellow
761
+ }
762
+
763
+ # ---- Step 5: Detect new version + diff CHANGELOG ----
764
+ if (-not $DryRun) {
765
+ $newChangelog = Join-Path $kitDir 'CHANGELOG.md'
766
+ $newVersion = 'unknown'
767
+ if (Test-Path $newChangelog) {
768
+ $match = Select-String -Path $newChangelog -Pattern '## \[(\d+\.\d+\.\d+)\]' -List -ErrorAction SilentlyContinue
769
+ if ($match) {
770
+ $newVersion = $match.Matches[0].Groups[1].Value
771
+ }
772
+ }
773
+
774
+ Write-Host ""
775
+ Write-Host "=== Update selesai ===" -ForegroundColor Cyan
776
+ Write-Host "Versi lama : v$currentVersion"
777
+ Write-Host "Versi baru : v$newVersion"
778
+
779
+ if ($currentVersion -ne $newVersion -and $newVersion -ne 'unknown') {
780
+ Write-Host ""
781
+ Write-Host "Update v$currentVersion -> v$newVersion sukses!" -ForegroundColor Green
782
+
783
+ # ---- Scan CHANGELOG entry v$newVersion untuk label [BREAKING] / [SCAN-REQUIRED] ----
784
+ $breakingFound = $false
785
+ $scanRequiredFound = $false
786
+ if (Test-Path $newChangelog) {
787
+ $clContent = Get-Content $newChangelog -Raw -Encoding UTF8
788
+ # Ambil bagian dari "## [<newVersion>]" sampai "## [" berikutnya (atau EOF)
789
+ $pattern = "(?ms)## \[$([regex]::Escape($newVersion))\].*?(?=^## \[|\Z)"
790
+ $entryMatch = [regex]::Match($clContent, $pattern)
791
+ if ($entryMatch.Success) {
792
+ $entryText = $entryMatch.Value
793
+ if ($entryText -match '\[BREAKING\]') { $breakingFound = $true }
794
+ if ($entryText -match '\[SCAN-REQUIRED\]') { $scanRequiredFound = $true }
795
+ }
796
+ }
797
+
798
+ if ($breakingFound -or $scanRequiredFound) {
799
+ Write-Host ""
800
+ Write-Host "================================================================" -ForegroundColor Red
801
+ Write-Host " PERHATIAN: VERSI INI ADA PERUBAHAN PENTING" -ForegroundColor Red
802
+ Write-Host "================================================================" -ForegroundColor Red
803
+ if ($breakingFound) {
804
+ Write-Host " [BREAKING] Ada perubahan yang tidak backward-compatible." -ForegroundColor Red
805
+ Write-Host " Baca CHANGELOG ENTRY v$newVersion sebelum lanjut kerja." -ForegroundColor Red
806
+ }
807
+ if ($scanRequiredFound) {
808
+ Write-Host " [SCAN-REQUIRED] Wajib regenerate docs/ supaya kompatibel." -ForegroundColor Red
809
+ Write-Host " Re-paste isi .claude-kit\PROJECT_LIFECYCLE_PROMPT_v1.md (Stage B: Bootstrap Docs)" -ForegroundColor Red
810
+ Write-Host " ke Claude Code untuk regenerate docs lama." -ForegroundColor Red
811
+ }
812
+ Write-Host "================================================================" -ForegroundColor Red
813
+ }
814
+
815
+ Write-Host ""
816
+ Write-Host "Action items recommended:" -ForegroundColor Cyan
817
+ Write-Host " 1. Baca CHANGELOG entry [v$newVersion]:"
818
+ Write-Host " $newChangelog"
819
+ Write-Host " 2. Verify file baru di docs/ + .github/ (kalau ada di release notes)."
820
+ Write-Host " 3. Update AGENTS.md di root proyek:"
821
+ Write-Host " Ganti 'Versi kit di .claude-kit/: vX.Y.Z' -> v$newVersion"
822
+ if (-not $breakingFound -and -not $scanRequiredFound) {
823
+ Write-Host " 4. Tidak ada label [BREAKING]/[SCAN-REQUIRED] — docs/ kamu AMAN, gak perlu scan ulang."
824
+ Write-Host " 5. Kalau CHANGELOG sebut workflow change di JALANKAN_KIT.md:"
825
+ Write-Host " Re-paste isi .claude-kit\JALANKAN_KIT.md ke Claude Code."
826
+ } else {
827
+ Write-Host " 4. WAJIB ikuti instruksi PERHATIAN di atas sebelum kerja lanjut."
828
+ }
829
+ } elseif ($currentVersion -eq $newVersion) {
830
+ Write-Host ""
831
+ Write-Host "Tidak ada perubahan versi (v$currentVersion). Update mungkin cuma minor patch/refinement." -ForegroundColor Cyan
832
+ Write-Host "Cek CHANGELOG untuk detail."
833
+ }
834
+
835
+ if (-not $NoBackup -and (Test-Path $backupDir)) {
836
+ Write-Host ""
837
+ Write-Host "Backup lama tersimpan di:" -ForegroundColor Cyan
838
+ Write-Host " $backupDir"
839
+ Write-Host ""
840
+ Write-Host "Hapus backup kalau sudah yakin update sukses:" -ForegroundColor Cyan
841
+ Write-Host " Remove-Item '$backupDir' -Recurse -Force"
842
+ }
843
+ }
844
+
845
+ Write-Host ""
846
+
847
+ # === Tier classification + backup cleanup hooks ===
848
+ # - $KitPath : path .claude-kit/ di project (default ".\.claude-kit")
849
+ # Kalau nama beda, sesuaikan parameter pemanggilan di bawah.
850
+
851
+ try {
852
+ Write-Host ""
853
+ Write-Host "[*] Klasifikasi tier update dari CHANGELOG..." -ForegroundColor Cyan
854
+
855
+ # Resolve changelog path defensif (kalau $KitPath belum di-set, fallback default).
856
+ $changelogPath = ".\.claude-kit\CHANGELOG.md"
857
+ if ($KitPath) {
858
+ $candidate = Join-Path $KitPath "CHANGELOG.md"
859
+ if (Test-Path $candidate) { $changelogPath = $candidate }
860
+ }
861
+
862
+ $entry = Get-LatestChangelogEntry -ChangelogPath $changelogPath
863
+
864
+ if ($null -ne $entry) {
865
+ $tier = Classify-UpdateTier -EntryBody $entry.Body
866
+ $summary = Format-UpdateSummary -Tier $tier -Entry $entry
867
+
868
+ # Warna terminal sesuai tier (visual cue cepat buat staff).
869
+ $color = "Green"
870
+ switch ($tier) {
871
+ "Tier 1" { $color = "Green" }
872
+ "Tier 2" { $color = "Cyan" }
873
+ "Tier 3" { $color = "Yellow" }
874
+ "Tier 4" { $color = "Magenta" }
875
+ }
876
+
877
+ Write-Host $summary -ForegroundColor $color
878
+ } else {
879
+ Write-Host "[WARN] Skip tier classification — CHANGELOG tidak bisa di-parse." -ForegroundColor Yellow
880
+ }
881
+ }
882
+ catch {
883
+ # Jangan gagalkan update cuma karena classification error.
884
+ Write-Host "[WARN] Tier classification error (non-fatal): $($_.Exception.Message)" -ForegroundColor Yellow
885
+ }
886
+
887
+ # [B] Backup cleanup — paling akhir, sebelum exit.
888
+ try {
889
+ Write-Host ""
890
+ Write-Host "[*] Bersih-bersih backup lama..." -ForegroundColor Cyan
891
+
892
+ $rootForCleanup = "."
893
+ if ($ProjectRoot) { $rootForCleanup = $ProjectRoot }
894
+
895
+ [void](Invoke-BackupCleanup -ProjectRoot $rootForCleanup -MaxAgeDays 30 -KeepLatest 3)
896
+ }
897
+ catch {
898
+ Write-Host "[WARN] Backup cleanup non-fatal error: $($_.Exception.Message)" -ForegroundColor Yellow
899
+ }
900
+
901
+ # =============================================================================
902
+ # END ENHANCEMENT
903
+ # =============================================================================
904
+ # === END hooks ===
905
+
906
+ Write-Host "OK update-kit.ps1 selesai." -ForegroundColor Green
907
+ exit 0