@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.
- package/.github/workflows/publish-npm.yml +40 -0
- package/.github/workflows/validate.yml +93 -0
- package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
- package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE_universal_v1.md +1021 -0
- package/CONTRIBUTING.md +101 -0
- package/FIRST_SESSION_PROMPT_v1.md +7 -0
- package/JALANKAN_KIT.md +188 -0
- package/LICENSE +21 -0
- package/MULAI_DI_SINI.md +145 -0
- package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
- package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
- package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
- package/README.md +505 -0
- package/SETUP_POLA_B_PROMPT_v1.md +5 -0
- package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
- package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
- package/UPDATE_DOCS_PROMPT_v1.md +3 -0
- package/UPDATE_KIT_PROMPT_v1.md +213 -0
- package/bin/lintasai.js +81 -0
- package/docs/SIGNED_RELEASE.md +162 -0
- package/install-windows.ps1 +225 -0
- package/kit.ps1 +508 -0
- package/lib/agents-md.ps1 +174 -0
- package/lib/git-helpers.ps1 +104 -0
- package/lib/kit-files.psd1 +133 -0
- package/lib/manifest-signing.ps1 +65 -0
- package/lib/manifest.ps1 +267 -0
- package/lib/rollback.ps1 +241 -0
- package/lib/safety.ps1 +193 -0
- package/lib/template-deploy.ps1 +242 -0
- package/lib/version-detect.ps1 +161 -0
- package/package.json +36 -0
- package/setup-pola-b.ps1 +687 -0
- package/templates/ANALOGI_LIBRARY.md +7 -0
- package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
- package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
- package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
- package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
- package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
- package/templates/INDEX.md +157 -0
- package/templates/MCP_SETUP.md +1145 -0
- package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
- package/templates/ONBOARDING.md +172 -0
- package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
- package/templates/PROMPT_LIBRARY.md +790 -0
- package/templates/RLS_SETUP_PROMPT.md +167 -0
- package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
- package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
- package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
- package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
- package/templates/STACK_DETECTION_PATTERN.md +261 -0
- package/templates/STACK_GUIDE.md +564 -0
- package/templates/STACK_MIGRATION_GUIDE.md +154 -0
- package/templates/STACK_VERSIONS.md +31 -0
- package/templates/UPDATE_GUIDE.md +246 -0
- package/templates/_EXAMPLE.md +110 -0
- package/templates/_PATTERNS.md +173 -0
- package/templates/architecture.md +180 -0
- package/templates/architecture_auto.md +61 -0
- package/templates/decisions/README.md +108 -0
- package/templates/decisions/_TEMPLATE.md +84 -0
- package/templates/feature-flags-advanced.md +171 -0
- package/templates/github/CODEOWNERS.template +61 -0
- package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
- package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
- package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
- package/templates/github/RENOVATE_FRONTEND.json +28 -0
- package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
- package/templates/github/pull_request_template.md +44 -0
- package/templates/github/scripts/ai-review.js +153 -0
- package/templates/github/workflows/ai-review.yml +61 -0
- package/templates/github/workflows/backup-schemas.yml +169 -0
- package/templates/glossary.md +110 -0
- package/templates/split-agents/BACKEND.md +149 -0
- package/templates/split-agents/FRONTEND.md +141 -0
- package/templates/split-agents/SHARED.md +82 -0
- package/templates/split-agents/TOOLS.md +77 -0
- package/tests/Run-Tests.ps1 +19 -0
- package/tests/lib-safety.Tests.ps1 +66 -0
- package/tests/rollback.Tests.ps1 +66 -0
- package/tests/uninstall.Tests.ps1 +265 -0
- package/tests/update-kit.Tests.ps1 +78 -0
- package/uninstall.ps1 +794 -0
- 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
|