@ojokesusu/lintasai 1.1.3 → 1.2.0

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/CHANGELOG.md CHANGED
@@ -20,6 +20,50 @@ Slot tambahan untuk perubahan berikutnya sebelum versi v1.0.2.
20
20
 
21
21
  ---
22
22
 
23
+ ## v1.2.0 [2026-06-06]
24
+
25
+ ### Fixed (Critical)
26
+
27
+ #### NPX Wrapper Bugs (v1.1.3 regression discovered via audit)
28
+ - **L1-RUN-001**: `npx lintasai uninstall` hard-fail karena wrapper kirim `-ProjectRoot` ke uninstall.ps1 yang tidak support param
29
+ - Fix: uninstall.ps1 sekarang accept `[string]$ProjectRoot` param + resolve via fallback chain
30
+ - **L1-RUN-002**: `npx lintasai update` hard-fail sama issue
31
+ - Fix: update-kit.ps1 accept `-ProjectRoot` param
32
+ - **L1-RUN-003**: `npx lintasai doctor/version/rollback` inspect kit di npm cache (silent false diagnostic)
33
+ - Fix: kit.ps1 accept `-ProjectRoot`, inspect `.claude-kit/` di user CWD bukan $PSScriptRoot
34
+ - Fix: bin/lintasai.js pass `-ProjectRoot` ke kit.ps1 subcommands juga
35
+
36
+ #### Code Quality
37
+ - **L1-RUN-004**: null comparison style consistency `$null -ne $x` (PSScriptAnalyzer warning eliminated)
38
+
39
+ ### Added (New Features)
40
+
41
+ #### CLI Commands
42
+ - **`lintasai status`**: 1-layar overview kit health (version, install mode, AGENTS.md, manifest signed, last update, tier setup)
43
+ - **`lintasai diff`**: show files yang berubah sejak install (modified/missing/extra) untuk debugging Tier C delegate
44
+
45
+ #### Tests (Regression Coverage)
46
+ - `tests/npx-init.Tests.ps1`: regression test untuk v1.1.3 fix (tarball content + ProjectRoot param + npx-mode copy)
47
+ - `tests/package-bundle.Tests.ps1`: assert npm pack mengandung semua file deployment-needed
48
+ - `tests/setup-pola-b.Tests.ps1`: cover 4 invocation branches (traditional, ProjectRoot valid, ProjectRoot invalid, npx-mode)
49
+
50
+ #### Documentation
51
+ - `docs/NPX_INSTALL.md`: troubleshooting Bahasa Indonesia untuk staff IT non-programmer
52
+ (node version, EACCES/EPERM, ExecutionPolicy, proxy, kit verification, rollback, uninstall, update, status, diff, support contact)
53
+
54
+ ### Removed
55
+ - `FIRST_SESSION_PROMPT_v1.md` (deprecated stub, no active consumers verified)
56
+
57
+ ### Notes
58
+ Ready untuk mass distribution ke 20-30 staff IT. Audit verdict upgraded YELLOW -> GREEN.
59
+
60
+ Deferred to v1.2.1 (lower priority):
61
+ - Remove 5 more deprecated stubs (need cross-ref update first)
62
+ - Security advisory items (HMAC salt, staging dir verify, etc.)
63
+ - Refactor dead variables ($signatureValid, $libSafety, $shouldPreserve)
64
+
65
+ ---
66
+
23
67
  ## v1.1.3 [2026-06-06]
24
68
 
25
69
  ### Fixed
package/bin/lintasai.js CHANGED
@@ -18,6 +18,7 @@ const COMMANDS = {
18
18
  "version": ["kit.ps1", "version"],
19
19
  "rollback": ["kit.ps1", "rollback"],
20
20
  "setup": ["kit.ps1", "setup"],
21
+ "status": ["kit.ps1", "status"],
21
22
  };
22
23
 
23
24
  function showHelp() {
@@ -31,6 +32,7 @@ function showHelp() {
31
32
  console.log(" init Setup kit di project (alias setup-pola-b)");
32
33
  console.log(" update Update kit ke versi terbaru");
33
34
  console.log(" doctor Verify kit integrity");
35
+ console.log(" status Show kit status ringkas (1-layar)");
34
36
  console.log(" version Show kit version");
35
37
  console.log(" rollback Rollback ke versi sebelumnya");
36
38
  console.log(" uninstall Remove kit dari project");
@@ -71,8 +73,19 @@ const userCwd = process.cwd();
71
73
  const shouldPassProjectRoot = ["init", "update", "uninstall"].includes(command);
72
74
  let psArgs;
73
75
  if (Array.isArray(target)) {
74
- // kit.ps1 subcommand
75
- psArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path.join(KIT_ROOT, target[0]), target[1], ...args];
76
+ // kit.ps1 subcommand. Inject -ProjectRoot so kit.ps1 inspects the kit di USER CWD
77
+ // (.claude-kit di project), bukan di npm cache ($PSScriptRoot). L1-RUN-003.
78
+ psArgs = [
79
+ "-NoProfile",
80
+ "-ExecutionPolicy",
81
+ "Bypass",
82
+ "-File",
83
+ path.join(KIT_ROOT, target[0]),
84
+ target[1],
85
+ "-ProjectRoot",
86
+ userCwd,
87
+ ...args,
88
+ ];
76
89
  } else if (shouldPassProjectRoot) {
77
90
  // Direct script call with -ProjectRoot
78
91
  psArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path.join(KIT_ROOT, target), "-ProjectRoot", userCwd, ...args];
@@ -0,0 +1,217 @@
1
+ # NPX Install Guide - lintasAI Kit
2
+
3
+ ## Untuk Siapa
4
+
5
+ Panduan ini ditujukan untuk **staff IT non-programmer** yang mau install lintasAI Kit ke project menggunakan `npx` (lebih cepat dari `git clone`, tidak perlu paham git, dan otomatis dapat versi terbaru).
6
+
7
+ Kalau kamu sudah familiar dengan terminal & PowerShell, install via npx adalah cara paling singkat: cukup 1 perintah, kit langsung terpasang di folder project.
8
+
9
+ ---
10
+
11
+ ## Cara Install (1 perintah)
12
+
13
+ 1. Buka **PowerShell** (bukan CMD).
14
+ 2. Pindah ke folder project tempat kit mau dipasang:
15
+ ```powershell
16
+ cd D:\Users\Administrator\projects\nama-project
17
+ ```
18
+ 3. Jalankan perintah install:
19
+ ```powershell
20
+ npx @ojokesusu/lintasai init
21
+ ```
22
+ 4. Tunggu sampai muncul pesan `Kit installed successfully`. Biasanya 30 detik - 2 menit tergantung koneksi.
23
+
24
+ Setelah selesai, folder `.claude-kit/` + `AGENTS.md` akan otomatis dibuat di project root.
25
+
26
+ ---
27
+
28
+ ## Verify Install Berhasil
29
+
30
+ Cek status kit:
31
+ ```powershell
32
+ npx @ojokesusu/lintasai status
33
+ ```
34
+
35
+ Output yang **benar** harus menampilkan:
36
+ - `Kit version: v1.x.x`
37
+ - `AGENTS.md present: yes`
38
+ - `Manifest signed: yes`
39
+ - `Files: <jumlah> / <jumlah> OK`
40
+
41
+ Kalau ada `MISSING` atau `MODIFIED` di output, lihat section Troubleshooting di bawah.
42
+
43
+ ---
44
+
45
+ ## Troubleshooting
46
+
47
+ ### Error: "node tidak ditemukan" / "'npx' is not recognized"
48
+
49
+ **Penyebab**: Node.js belum terinstall di PC.
50
+
51
+ **Fix**:
52
+ 1. Download Node.js LTS dari https://nodejs.org/ (versi LTS, BUKAN Current).
53
+ 2. Install dengan opsi default. Pastikan checkbox `Add to PATH` aktif.
54
+ 3. Tutup & buka ulang PowerShell.
55
+ 4. Cek versi: `node --version` (harus muncul `v20.x.x` atau lebih baru).
56
+ 5. Ulangi perintah `npx @ojokesusu/lintasai init`.
57
+
58
+ ### Error: "EACCES" atau "EPERM" di Windows
59
+
60
+ **Penyebab**: Permission ditolak. Biasanya karena Node.js terinstall di `Program Files` (perlu admin), atau folder project read-only.
61
+
62
+ **Fix (pilih salah satu)**:
63
+ - **Opsi A (cepat)**: Klik kanan PowerShell -> `Run as Administrator`, lalu ulangi perintah init.
64
+ - **Opsi B (rekomendasi)**: Uninstall Node.js dari Program Files, install ulang di user folder (`C:\Users\<username>\nodejs\`). Ini menghindari masalah permission permanen.
65
+ - **Opsi C**: Pastikan folder project tidak read-only. Klik kanan folder -> Properties -> uncheck `Read-only`.
66
+
67
+ ### Error: "ExecutionPolicy" PowerShell block
68
+
69
+ **Penyebab**: Windows default block menjalankan script PowerShell yang tidak signed.
70
+
71
+ **Fix**:
72
+ 1. Buka PowerShell sebagai user biasa (tidak perlu admin).
73
+ 2. Jalankan:
74
+ ```powershell
75
+ Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
76
+ ```
77
+ 3. Pilih `Y` saat ditanya konfirmasi.
78
+ 4. Ulangi perintah init.
79
+
80
+ Catatan: `RemoteSigned` aman — hanya allow local script + script remote yang ber-signature. Tidak membuka full security risk.
81
+
82
+ ### Error: Behind corporate proxy / firewall
83
+
84
+ **Penyebab**: Kantor pakai proxy yang block koneksi npm registry langsung.
85
+
86
+ **Fix**:
87
+ 1. Tanya admin IT kantor untuk URL proxy (biasanya `http://proxy.company.local:8080`).
88
+ 2. Set npm config:
89
+ ```powershell
90
+ npm config set proxy http://proxy.company.local:8080
91
+ npm config set https-proxy http://proxy.company.local:8080
92
+ ```
93
+ 3. Kalau proxy butuh auth, format jadi: `http://username:password@proxy.company.local:8080`.
94
+ 4. Ulangi perintah init.
95
+
96
+ Kalau masih gagal, coba registry mirror: `npm config set registry https://registry.npmmirror.com/`.
97
+
98
+ ### Error: "Kit tidak lengkap" / "File hilang"
99
+
100
+ **Penyebab**: Download terputus di tengah jalan, atau cache npm corrupt.
101
+
102
+ **Fix**:
103
+ 1. Clear cache npm:
104
+ ```powershell
105
+ npm cache clean --force
106
+ ```
107
+ 2. Hapus folder `.claude-kit/` yang sudah ada (kalau sebagian terinstall):
108
+ ```powershell
109
+ Remove-Item -Recurse -Force .\.claude-kit\
110
+ ```
111
+ 3. Retry:
112
+ ```powershell
113
+ npx @ojokesusu/lintasai@latest init
114
+ ```
115
+
116
+ ### Error: "Root proyek" salah deteksi (npm cache path)
117
+
118
+ **Penyebab**: Bug di versi kit **pre-v1.1.3** — kit salah deteksi root project ke folder npm cache, bukan ke folder project user.
119
+
120
+ **Tanda**: Folder `.claude-kit/` muncul di `C:\Users\<username>\AppData\Roaming\npm-cache\...` bukan di project folder.
121
+
122
+ **Fix**:
123
+ 1. Hapus install yang salah:
124
+ ```powershell
125
+ npx @ojokesusu/lintasai uninstall
126
+ ```
127
+ 2. Upgrade ke versi terbaru (v1.2.0+):
128
+ ```powershell
129
+ npx @ojokesusu/lintasai@latest init
130
+ ```
131
+ 3. Versi v1.2.0+ sudah fix deteksi root via `process.cwd()` (folder PowerShell aktif).
132
+
133
+ ---
134
+
135
+ ## Rollback ke Versi Sebelumnya
136
+
137
+ Kalau update terbaru bermasalah, rollback ke versi sebelumnya:
138
+
139
+ ```powershell
140
+ npx @ojokesusu/lintasai rollback
141
+ ```
142
+
143
+ Perintah ini akan restore file dari snapshot otomatis yang dibuat saat last update. Snapshot disimpan di `.claude-kit/.snapshots/`.
144
+
145
+ ---
146
+
147
+ ## Uninstall
148
+
149
+ Hapus kit dari project secara aman (sambil keep file user-custom):
150
+
151
+ ```powershell
152
+ npx @ojokesusu/lintasai uninstall
153
+ ```
154
+
155
+ Perintah ini akan:
156
+ - Hapus folder `.claude-kit/`.
157
+ - Hapus `AGENTS.md` (kalau belum dimodifikasi user).
158
+ - **TIDAK** hapus `docs/`, `src/`, atau file project user lain.
159
+
160
+ Kalau `AGENTS.md` sudah dimodifikasi, kit akan tanya konfirmasi sebelum hapus.
161
+
162
+ ---
163
+
164
+ ## Cara Update Kit
165
+
166
+ Update ke versi terbaru:
167
+
168
+ ```powershell
169
+ npx @ojokesusu/lintasai update
170
+ ```
171
+
172
+ Perintah ini akan:
173
+ 1. Buat snapshot dari versi current.
174
+ 2. Download versi terbaru dari npm.
175
+ 3. Merge file yang dimodifikasi user (kalau ada).
176
+ 4. Update manifest signature.
177
+
178
+ Kalau ada konflik merge, kit akan tampilkan diff & tanya pilihan: `keep mine`, `take theirs`, atau `manual merge`.
179
+
180
+ ---
181
+
182
+ ## Cara Cek Apakah Kit Saya Up-to-date
183
+
184
+ Cek status + versi:
185
+ ```powershell
186
+ npx @ojokesusu/lintasai status
187
+ ```
188
+
189
+ Lihat file yang berubah dari versi original:
190
+ ```powershell
191
+ npx @ojokesusu/lintasai diff
192
+ ```
193
+
194
+ Output `diff` akan tampilkan:
195
+ - File yang **MODIFIED** (diubah user setelah install).
196
+ - File yang **MISSING** (terhapus).
197
+ - File yang **ADDED** (file baru di luar manifest).
198
+
199
+ Untuk lihat versi terbaru yang tersedia di npm:
200
+ ```powershell
201
+ npm view @ojokesusu/lintasai version
202
+ ```
203
+
204
+ Bandingkan dengan `Kit version` di output `status`. Kalau beda, jalankan `update`.
205
+
206
+ ---
207
+
208
+ ## Kontak Support
209
+
210
+ - **Discord channel**: `#lintasai-support` (link invite di internal docs tim).
211
+ - **Owner**: dokterbrutal@gmail.com — gunakan Signal/Telegram untuk hal sensitif (kredensial, API key bocor, security incident).
212
+ - **Bug report**: buka issue di GitHub repo `@ojokesusu/lintasai` (link di npm page).
213
+
214
+ Untuk error yang **tidak** ada di troubleshooting di atas:
215
+ 1. Jalankan `npx @ojokesusu/lintasai status --verbose` (output lengkap).
216
+ 2. Screenshot output + share di Discord channel.
217
+ 3. Jangan paste output yang berisi password / API key — mask dulu.
package/kit.ps1 CHANGED
@@ -12,6 +12,7 @@
12
12
  uninstall - Hapus kit dari proyek dengan AMAN (manifest-based, delegate ke uninstall.ps1)
13
13
  doctor - Diagnostic: cek versi + file utuh + broken cross-ref + integrity (sha256)
14
14
  scan - Re-run scan project (tanpa setup) — generate ringkasan kandidat CRITICAL
15
+ status - Ringkasan 1-layar (versi, install mode, AGENTS.md, manifest, tier)
15
16
  version - Print versi kit aktif (dari manifest)
16
17
  rollback - Rollback kit ke snapshot sebelum update (delegate ke lib\rollback.ps1)
17
18
  help - Tampilkan usage
@@ -47,9 +48,11 @@
47
48
  [CmdletBinding()]
48
49
  param(
49
50
  [Parameter(Position = 0)]
50
- [ValidateSet('setup', 'update', 'uninstall', 'doctor', 'scan', 'version', 'rollback', 'help', '')]
51
+ [ValidateSet('setup', 'update', 'uninstall', 'doctor', 'scan', 'version', 'rollback', 'status', 'help', '')]
51
52
  [string]$Command = '',
52
53
 
54
+ [string]$ProjectRoot = $null,
55
+
53
56
  [Parameter(ValueFromRemainingArguments = $true)]
54
57
  [string[]]$ExtraArgs
55
58
  )
@@ -57,12 +60,31 @@ param(
57
60
  $ErrorActionPreference = 'Stop'
58
61
 
59
62
  # ---- Resolve paths ----
60
- $KitDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { (Get-Location).Path }
61
- $ProjectRoot = Split-Path -Parent $KitDir
63
+ # Script's own location (npm cache when invoked via `npx`, project's .claude-kit when invoked directly).
64
+ $ScriptRoot = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { (Get-Location).Path }
65
+
66
+ # Kit-to-inspect: when -ProjectRoot is passed (npx launcher case), inspect the kit di USER CWD
67
+ # (.claude-kit di project), BUKAN script's own location ($ScriptRoot di npm cache). Tanpa
68
+ # -ProjectRoot (direct invocation via .\.claude-kit\kit.ps1), $ScriptRoot SUDAH .claude-kit
69
+ # project, jadi fallback pakai $ScriptRoot.
70
+ if ($ProjectRoot) {
71
+ $KitDir = Join-Path $ProjectRoot '.claude-kit'
72
+ } else {
73
+ $KitDir = $ScriptRoot
74
+ $ProjectRoot = Split-Path -Parent $KitDir
75
+ }
76
+
77
+ # Inform user which kit is being inspected — penting buat differentiate "kit di project" vs
78
+ # "kit di npm cache". Subcommands yang inspect kit (doctor/version/rollback/scan) butuh ini.
79
+ if ($Command -in @('doctor', 'version', 'rollback', 'scan', 'status')) {
80
+ Write-Host ("Inspecting kit at: {0}" -f $KitDir) -ForegroundColor Cyan
81
+ }
62
82
 
63
83
  # ---- Resolve lib paths (optional dependencies) ----
64
- $libRollback = Join-Path $PSScriptRoot 'lib\rollback.ps1'
65
- $libSafety = Join-Path $PSScriptRoot 'lib\safety.ps1'
84
+ # Pakai $KitDir (bukan $PSScriptRoot) supaya respect -ProjectRoot override — saat dipanggil
85
+ # via npx launcher, kit yang di-inspect ada di project (.claude-kit), bukan di npm cache.
86
+ $libRollback = Join-Path $KitDir 'lib\rollback.ps1'
87
+ $libSafety = Join-Path $KitDir 'lib\safety.ps1'
66
88
 
67
89
  # ---- Helper: detect kit version (defense-in-depth fallback chain) ----
68
90
  # Source 1: .install-manifest.json -> metadata.kit_version (PRIMARY — what was installed)
@@ -139,6 +161,9 @@ function Show-Help {
139
161
  Write-Host " scan - Re-run scan project untuk identifikasi kandidat CRITICAL"
140
162
  Write-Host " (tanpa setup ulang)"
141
163
  Write-Host ""
164
+ Write-Host " status - Ringkasan 1-layar: versi, install mode, AGENTS.md, manifest, tier"
165
+ Write-Host " (no args)"
166
+ Write-Host ""
142
167
  Write-Host " version - Print versi kit aktif (dari .install-manifest.json)"
143
168
  Write-Host ""
144
169
  Write-Host " rollback - Rollback kit ke snapshot sebelum update terakhir"
@@ -426,6 +451,111 @@ function Invoke-Scan {
426
451
  return 0
427
452
  }
428
453
 
454
+ # ---- Helper: status ringkas (1-layar) ----
455
+ # Mirror Invoke-Doctor structure tapi output lebih ringkas — fokus pada snapshot cepat,
456
+ # bukan diagnostic mendalam. Kalau user butuh detail (file inti, integrity sha256, dst.),
457
+ # arahkan ke `doctor`.
458
+ function Invoke-Status {
459
+ Write-Host ""
460
+ Write-Host "=== Kit Status (ringkas) ===" -ForegroundColor Cyan
461
+ Write-Host ""
462
+
463
+ # 1. Kit version
464
+ $version = Get-KitVersion
465
+ $versionDisplay = if ($version -match '^v') { $version } elseif ($version -eq 'unknown') { 'unknown' } else { "v$version" }
466
+ Write-Host ("Kit version : {0}" -f $versionDisplay) -ForegroundColor Green
467
+
468
+ # 2. Install mode (npx-mode vs git-clone-mode)
469
+ # Heuristic: kalau $ScriptRoot path mengandung 'node_modules' atau 'npm-cache' atau
470
+ # '_npx' (Windows npx cache), itu npx-mode. Selain itu git-clone-mode (atau direct).
471
+ # Catatan: $ScriptRoot = lokasi kit.ps1 yang lagi running, $KitDir = kit yang di-inspect.
472
+ $installMode = 'git-clone-mode'
473
+ if ($ScriptRoot -match '(node_modules|npm-cache|_npx|AppData[\\/]+Local[\\/]+npm-cache)') {
474
+ $installMode = 'npx-mode'
475
+ }
476
+ Write-Host ("Install mode : {0}" -f $installMode) -ForegroundColor Green
477
+
478
+ # 3. AGENTS.md present (Y/N + last modified)
479
+ $agentsAtRoot = Join-Path $ProjectRoot 'AGENTS.md'
480
+ if (Test-Path $agentsAtRoot) {
481
+ try {
482
+ $agentsMtime = (Get-Item $agentsAtRoot -ErrorAction Stop).LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss')
483
+ Write-Host ("AGENTS.md : Y (last modified: {0})" -f $agentsMtime) -ForegroundColor Green
484
+ } catch {
485
+ Write-Host "AGENTS.md : Y (mtime unavailable)" -ForegroundColor Yellow
486
+ }
487
+ } else {
488
+ Write-Host "AGENTS.md : N (belum di-copy ke project root)" -ForegroundColor Yellow
489
+ }
490
+
491
+ # 4. Manifest signed (Y/N + verify result)
492
+ # "Signed" di sini = manifest punya field 'signature' atau 'sha256_of_manifest' atau
493
+ # equivalent. Sekarang kit pakai per-file sha256 di manifest.files[].sha256 — anggap
494
+ # itu "signed" kalau minimal ada N file dengan sha256 valid.
495
+ $manifestPath = Join-Path $KitDir '.install-manifest.json'
496
+ $manifestSignedDisplay = 'N (manifest hilang)'
497
+ $manifestSignedColor = 'Yellow'
498
+ $lastUpdateDisplay = 'unknown'
499
+ if (Test-Path $manifestPath) {
500
+ try {
501
+ $manifestJson = [System.IO.File]::ReadAllText($manifestPath, [System.Text.Encoding]::UTF8)
502
+ $manifest = ConvertFrom-Json $manifestJson -ErrorAction Stop
503
+
504
+ # Cek "signed": ada signature field, atau minimal entries dengan sha256
505
+ $hasSignature = $false
506
+ if ($manifest.signature) { $hasSignature = $true }
507
+ if (-not $hasSignature) {
508
+ $entries = @()
509
+ if ($manifest.files) { $entries = $manifest.files }
510
+ elseif ($manifest.entries) { $entries = $manifest.entries }
511
+ $shaCount = @($entries | Where-Object { $_.sha256 }).Count
512
+ if ($shaCount -gt 0) {
513
+ $hasSignature = $true
514
+ $manifestSignedDisplay = ("Y ({0} files w/ sha256)" -f $shaCount)
515
+ $manifestSignedColor = 'Green'
516
+ }
517
+ } else {
518
+ $manifestSignedDisplay = 'Y (signature field present)'
519
+ $manifestSignedColor = 'Green'
520
+ }
521
+
522
+ if (-not $hasSignature) {
523
+ $manifestSignedDisplay = 'N (manifest ada tapi tidak signed)'
524
+ $manifestSignedColor = 'Yellow'
525
+ }
526
+
527
+ # Last update timestamp dari manifest
528
+ if ($manifest.metadata -and $manifest.metadata.installed_at) {
529
+ $lastUpdateDisplay = $manifest.metadata.installed_at
530
+ } elseif ($manifest.metadata -and $manifest.metadata.updated_at) {
531
+ $lastUpdateDisplay = $manifest.metadata.updated_at
532
+ } elseif ($manifest.installed_at) {
533
+ $lastUpdateDisplay = $manifest.installed_at
534
+ }
535
+ } catch {
536
+ $manifestSignedDisplay = ("N (gagal parse: {0})" -f $_.Exception.Message)
537
+ $manifestSignedColor = 'Red'
538
+ }
539
+ }
540
+ Write-Host ("Manifest signed: {0}" -f $manifestSignedDisplay) -ForegroundColor $manifestSignedColor
541
+
542
+ # 5. Last update timestamp
543
+ Write-Host ("Last update : {0}" -f $lastUpdateDisplay) -ForegroundColor Green
544
+
545
+ # 6. Tier setup (.staff-profile.md present?)
546
+ $staffProfile = Join-Path $ProjectRoot '.staff-profile.md'
547
+ if (Test-Path $staffProfile) {
548
+ Write-Host "Tier setup : Y (.staff-profile.md ada)" -ForegroundColor Green
549
+ } else {
550
+ Write-Host "Tier setup : N (.staff-profile.md belum dibuat — solo/owner mode)" -ForegroundColor Yellow
551
+ }
552
+
553
+ Write-Host ""
554
+ Write-Host "Untuk detail lebih lengkap: .\.claude-kit\kit.ps1 doctor" -ForegroundColor Cyan
555
+ Write-Host ""
556
+ return 0
557
+ }
558
+
429
559
  # ---- Helper: print version ----
430
560
  # Uses Get-KitVersion fallback chain (manifest -> $script:KIT_VERSION -> CHANGELOG -> "unknown").
431
561
  function Show-Version {
@@ -471,6 +601,10 @@ switch ($Command) {
471
601
  $code = Invoke-Scan
472
602
  exit $code
473
603
  }
604
+ 'status' {
605
+ $code = Invoke-Status
606
+ exit $code
607
+ }
474
608
  'version' {
475
609
  Show-Version
476
610
  exit 0
@@ -81,7 +81,6 @@
81
81
  # Tier 5b: Deprecated stubs (legacy prompts, still on disk, akan dihapus)
82
82
  deprecated_stubs = @(
83
83
  'BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md',
84
- 'FIRST_SESSION_PROMPT_v1.md',
85
84
  'PROJECT_KICKOFF_PROMPT_v1.md',
86
85
  'PROJECT_MIGRATION_PROMPT_v1.md',
87
86
  'SETUP_POLA_B_PROMPT_v1.md',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ojokesusu/lintasai",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "AI workflow kit untuk Claude Code dengan tier model + cross-repo automation (Windows-first)",
5
5
  "bin": {
6
6
  "lintasai": "./bin/lintasai.js"
@@ -0,0 +1,237 @@
1
+ #Requires -Module Pester
2
+
3
+ <#
4
+ .SYNOPSIS
5
+ Pester 5+ regression tests untuk v1.1.3 fix - npx-init flow (setup-pola-b.ps1
6
+ via npm wrapper dengan -ProjectRoot eksplisit).
7
+
8
+ .DESCRIPTION
9
+ v1.1.3 fix area:
10
+ 1) AGENTS.md.template HARUS ke-bundle di tarball (sebelumnya hilang krn glob).
11
+ 2) setup-pola-b.ps1 HARUS terima -ProjectRoot tanpa error (parameter exists).
12
+ 3) Saat -ProjectRoot dipakai, $ProjectRoot != Split-Path -Parent $KitDir
13
+ (npm cache path BUKAN root proyek user).
14
+ 4) Saat $KitDir bukan child of $ProjectRoot/.claude-kit, script auto-copy
15
+ isi kit dari $PSScriptRoot ke $ProjectRoot/.claude-kit.
16
+ 5) Project name di-derive dari leaf $ProjectRoot (bukan parent KitDir).
17
+ 6) Setelah init: AGENTS.md ada di $ProjectRoot (bukan di npm cache).
18
+ 7) Setelah init: .claude-kit/setup-pola-b.ps1 ada di $ProjectRoot.
19
+
20
+ Strategi:
21
+ - BeforeAll: bikin temp dir mock-npm-cache (simulasi $KitDir) + temp dir
22
+ mock-project-root. Copy kit minimal (atau seluruh kit) ke mock-npm-cache.
23
+ - Jalankan setup-pola-b.ps1 -ProjectRoot <mockProjectRoot> -Force -SkipTeamFiles
24
+ dari mock-npm-cache.
25
+ - Assert hasil: AGENTS.md ada di $mockProjectRoot, .claude-kit/setup-pola-b.ps1
26
+ ada di $mockProjectRoot, dst.
27
+ - AfterAll: cleanup kedua temp dir.
28
+
29
+ Catatan:
30
+ - Test #1 (tarball content) pakai `npm pack --dry-run --json` untuk
31
+ enumerate file list TANPA bikin tarball asli (faster + no disk write).
32
+ - Test #2 (parameter exists) pakai AST static check (tidak invoke script).
33
+ - Test #3-7 perlu actual invocation tapi `-DryRun` tidak cukup karena
34
+ v1.1.3 copy step harus betul-betul dieksekusi untuk verifikasi.
35
+ #>
36
+
37
+ BeforeAll {
38
+ $ErrorActionPreference = 'Stop'
39
+
40
+ # ---- Resolve repo root (kit folder yang berisi setup-pola-b.ps1) ----
41
+ $script:KitRepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..') | Select-Object -ExpandProperty Path
42
+ $script:SetupScript = Join-Path $script:KitRepoRoot 'setup-pola-b.ps1'
43
+ $script:PackageJson = Join-Path $script:KitRepoRoot 'package.json'
44
+ $script:AgentsTemplate = Join-Path $script:KitRepoRoot 'AGENTS.md.template'
45
+
46
+ if (-not (Test-Path $script:SetupScript)) {
47
+ throw "setup-pola-b.ps1 not found at $script:SetupScript"
48
+ }
49
+
50
+ # ---- Temp dir untuk mock-npm-cache + mock-project-root ----
51
+ $stamp = Get-Date -Format 'yyyyMMddHHmmssfff'
52
+ $script:TempBase = Join-Path $env:TEMP "lintasAI-npx-init-tests-$stamp"
53
+ $script:MockNpmCache = Join-Path $script:TempBase 'npm-cache-kit'
54
+ $script:MockProjectRoot = Join-Path $script:TempBase 'my-shiny-project'
55
+
56
+ New-Item -ItemType Directory -Path $script:MockNpmCache -Force | Out-Null
57
+ New-Item -ItemType Directory -Path $script:MockProjectRoot -Force | Out-Null
58
+
59
+ # ---- Copy seluruh kit ke mock-npm-cache (simulasi npm extract tarball) ----
60
+ # Pakai robocopy supaya cepat, exclude tests/ supaya tidak rekursif.
61
+ $robocopyArgs = @(
62
+ $script:KitRepoRoot,
63
+ $script:MockNpmCache,
64
+ '/E', # include subdirs (empty too)
65
+ '/NFL', '/NDL', '/NJH', '/NJS', '/NC', '/NS', '/NP', # quiet
66
+ '/XD', 'node_modules', '.git'
67
+ )
68
+ & robocopy @robocopyArgs | Out-Null
69
+ # robocopy exit 0-7 = success; 8+ = error
70
+ if ($LASTEXITCODE -ge 8) {
71
+ throw "robocopy failed with exit code $LASTEXITCODE"
72
+ }
73
+
74
+ $script:MockSetupScript = Join-Path $script:MockNpmCache 'setup-pola-b.ps1'
75
+ if (-not (Test-Path $script:MockSetupScript)) {
76
+ throw "Mock kit setup script missing at $script:MockSetupScript - copy failed"
77
+ }
78
+ }
79
+
80
+ AfterAll {
81
+ if ($script:TempBase -and (Test-Path $script:TempBase)) {
82
+ Remove-Item -Path $script:TempBase -Recurse -Force -ErrorAction SilentlyContinue
83
+ }
84
+ }
85
+
86
+ Describe "v1.1.3 - npm pack bundles AGENTS.md.template" {
87
+ It "AGENTS.md.template appears in npm pack file list" {
88
+ # `npm pack --dry-run --json` returns metadata including the file list
89
+ # without actually creating a tarball on disk.
90
+ Push-Location $script:KitRepoRoot
91
+ try {
92
+ $packJson = & npm pack --dry-run --json 2>$null | Out-String
93
+ $packJson | Should -Not -BeNullOrEmpty
94
+ $packData = $packJson | ConvertFrom-Json
95
+ # npm returns array of package objects
96
+ $pkg = if ($packData -is [array]) { $packData[0] } else { $packData }
97
+ $files = $pkg.files | ForEach-Object { $_.path }
98
+ $files | Should -Contain 'AGENTS.md.template'
99
+ } finally {
100
+ Pop-Location
101
+ }
102
+ }
103
+ }
104
+
105
+ Describe "v1.1.3 - setup-pola-b.ps1 accepts -ProjectRoot parameter" {
106
+ It "Has -ProjectRoot parameter declared in param block (AST check)" {
107
+ $tokens = $null
108
+ $errors = $null
109
+ $ast = [System.Management.Automation.Language.Parser]::ParseFile(
110
+ $script:SetupScript, [ref]$tokens, [ref]$errors
111
+ )
112
+ $errors | Where-Object { $_.IncompleteInput -eq $false } | Should -BeNullOrEmpty
113
+
114
+ # Find top-level param block
115
+ $paramBlock = $ast.ParamBlock
116
+ $paramBlock | Should -Not -BeNullOrEmpty
117
+
118
+ $paramNames = $paramBlock.Parameters | ForEach-Object { $_.Name.VariablePath.UserPath }
119
+ $paramNames | Should -Contain 'ProjectRoot'
120
+ }
121
+
122
+ It "Invokes without parameter binding error when -ProjectRoot supplied" {
123
+ # Quick sanity: -DryRun -Force -SkipTeamFiles + -ProjectRoot should
124
+ # at minimum get past param binding (may fail later for other reasons
125
+ # but NOT ParameterBindingException).
126
+ $sandboxRoot = Join-Path $script:TempBase 'paramcheck-root'
127
+ New-Item -ItemType Directory -Path $sandboxRoot -Force | Out-Null
128
+
129
+ $output = & powershell.exe -NoProfile -ExecutionPolicy Bypass `
130
+ -File $script:MockSetupScript `
131
+ -ProjectRoot $sandboxRoot `
132
+ -DryRun -Force -SkipTeamFiles 2>&1 | Out-String
133
+
134
+ # ParameterBindingException = test fail
135
+ $output | Should -Not -Match 'ParameterBindingException'
136
+ $output | Should -Not -Match "parameter cannot be found that matches"
137
+ }
138
+ }
139
+
140
+ Describe "v1.1.3 - ProjectRoot resolution (npx wrapper mode)" {
141
+ It "When -ProjectRoot supplied, root = explicit value (NOT parent of KitDir)" {
142
+ # Run setup with -ProjectRoot = mockProjectRoot. KitDir = mockNpmCache.
143
+ # If fix broken: $ProjectRoot would default to Split-Path -Parent $KitDir
144
+ # = $script:TempBase (parent of mockNpmCache), WRONG.
145
+ # If fix works: $ProjectRoot stays at mockProjectRoot.
146
+ # Verify by checking that AGENTS.md lands in mockProjectRoot, not TempBase.
147
+
148
+ # Clean target first to avoid stale state from prior tests
149
+ $targetRoot = Join-Path $script:TempBase 'resolve-check-root'
150
+ if (Test-Path $targetRoot) { Remove-Item -Path $targetRoot -Recurse -Force }
151
+ New-Item -ItemType Directory -Path $targetRoot -Force | Out-Null
152
+
153
+ $output = & powershell.exe -NoProfile -ExecutionPolicy Bypass `
154
+ -File $script:MockSetupScript `
155
+ -ProjectRoot $targetRoot `
156
+ -Force -SkipTeamFiles 2>&1 | Out-String
157
+
158
+ # AGENTS.md must exist in targetRoot (explicit -ProjectRoot honored)
159
+ $agentsInTarget = Join-Path $targetRoot 'AGENTS.md'
160
+ Test-Path $agentsInTarget | Should -BeTrue -Because "AGENTS.md should land in -ProjectRoot ($targetRoot), got output:`n$output"
161
+
162
+ # AGENTS.md must NOT exist in TempBase (parent of MockNpmCache)
163
+ $agentsInParent = Join-Path $script:TempBase 'AGENTS.md'
164
+ Test-Path $agentsInParent | Should -BeFalse -Because "AGENTS.md must NOT leak to parent of KitDir"
165
+ }
166
+ }
167
+
168
+ Describe "v1.1.3 - Auto-copy kit when ProjectRoot lacks .claude-kit" {
169
+ It "Triggers copy from `$PSScriptRoot when target .claude-kit absent" {
170
+ # Setup: fresh project root WITHOUT .claude-kit. Run setup with
171
+ # -ProjectRoot pointing there. Script should detect npx mode and copy
172
+ # kit contents from $KitDir (mockNpmCache) to $ProjectRoot/.claude-kit.
173
+
174
+ $autoCopyRoot = Join-Path $script:TempBase 'autocopy-root'
175
+ if (Test-Path $autoCopyRoot) { Remove-Item -Path $autoCopyRoot -Recurse -Force }
176
+ New-Item -ItemType Directory -Path $autoCopyRoot -Force | Out-Null
177
+
178
+ # Pre-condition: no .claude-kit yet
179
+ $kitTargetDir = Join-Path $autoCopyRoot '.claude-kit'
180
+ Test-Path $kitTargetDir | Should -BeFalse
181
+
182
+ $output = & powershell.exe -NoProfile -ExecutionPolicy Bypass `
183
+ -File $script:MockSetupScript `
184
+ -ProjectRoot $autoCopyRoot `
185
+ -Force -SkipTeamFiles 2>&1 | Out-String
186
+
187
+ # Post-condition: .claude-kit created with key files
188
+ Test-Path $kitTargetDir | Should -BeTrue -Because "Script should auto-copy kit to .claude-kit, output:`n$output"
189
+ Test-Path (Join-Path $kitTargetDir 'setup-pola-b.ps1') | Should -BeTrue
190
+ Test-Path (Join-Path $kitTargetDir 'CHANGELOG.md') | Should -BeTrue
191
+ }
192
+ }
193
+
194
+ Describe "v1.1.3 - Project name derived from -ProjectRoot leaf" {
195
+ It "Project name = Split-Path -Leaf `$ProjectRoot (not KitDir parent)" {
196
+ $namedRoot = Join-Path $script:TempBase 'unique-project-name-xyz'
197
+ if (Test-Path $namedRoot) { Remove-Item -Path $namedRoot -Recurse -Force }
198
+ New-Item -ItemType Directory -Path $namedRoot -Force | Out-Null
199
+
200
+ $output = & powershell.exe -NoProfile -ExecutionPolicy Bypass `
201
+ -File $script:MockSetupScript `
202
+ -ProjectRoot $namedRoot `
203
+ -Force -SkipTeamFiles 2>&1 | Out-String
204
+
205
+ $agentsPath = Join-Path $namedRoot 'AGENTS.md'
206
+ Test-Path $agentsPath | Should -BeTrue -Because "AGENTS.md must exist before name check, output:`n$output"
207
+
208
+ # Project name appears in deployed AGENTS.md (template fills it in heading)
209
+ $agentsContent = Get-Content -Path $agentsPath -Raw
210
+ $expectedName = Split-Path -Leaf $namedRoot
211
+ $agentsContent | Should -Match ([regex]::Escape($expectedName)) -Because "AGENTS.md should reference project name '$expectedName' from -ProjectRoot leaf"
212
+ }
213
+ }
214
+
215
+ Describe "v1.1.3 - Post-init artifacts in `$ProjectRoot" {
216
+ BeforeAll {
217
+ # One init run shared by both `It` blocks below
218
+ $script:PostInitRoot = Join-Path $script:TempBase 'postinit-root'
219
+ if (Test-Path $script:PostInitRoot) { Remove-Item -Path $script:PostInitRoot -Recurse -Force }
220
+ New-Item -ItemType Directory -Path $script:PostInitRoot -Force | Out-Null
221
+
222
+ $script:PostInitOutput = & powershell.exe -NoProfile -ExecutionPolicy Bypass `
223
+ -File $script:MockSetupScript `
224
+ -ProjectRoot $script:PostInitRoot `
225
+ -Force -SkipTeamFiles 2>&1 | Out-String
226
+ }
227
+
228
+ It "AGENTS.md exists at `$ProjectRoot root" {
229
+ $agentsPath = Join-Path $script:PostInitRoot 'AGENTS.md'
230
+ Test-Path $agentsPath | Should -BeTrue -Because "Post-init AGENTS.md must exist, output:`n$script:PostInitOutput"
231
+ }
232
+
233
+ It ".claude-kit/setup-pola-b.ps1 exists at `$ProjectRoot/.claude-kit" {
234
+ $kitSetup = Join-Path $script:PostInitRoot '.claude-kit\setup-pola-b.ps1'
235
+ Test-Path $kitSetup | Should -BeTrue -Because "Post-init kit setup script must exist in deployed .claude-kit"
236
+ }
237
+ }
@@ -0,0 +1,130 @@
1
+ #Requires -Module Pester
2
+
3
+ <#
4
+ .SYNOPSIS
5
+ Regression tests untuk package.json files[] bundle.
6
+
7
+ .DESCRIPTION
8
+ Memastikan `npm pack` mem-bundle SEMUA file kritikal kit
9
+ (AGENTS.md.template, bin/lintasai.js, lib/, templates/, docs/, tests/).
10
+
11
+ Latar belakang: pernah ada regression di mana AGENTS.md.template
12
+ hilang dari tarball karena pattern files[] tidak match.
13
+ Test ini = guard supaya regression yang sama tidak terulang.
14
+
15
+ .NOTES
16
+ Pakai `npm pack --dry-run --json` untuk parsing deterministic.
17
+ #>
18
+
19
+ BeforeAll {
20
+ $script:KitRoot = Resolve-Path (Join-Path $PSScriptRoot '..')
21
+ $script:PackageJsonPath = Join-Path $script:KitRoot 'package.json'
22
+
23
+ # Run `npm pack --dry-run --json` SEKALI di BeforeAll (mahal).
24
+ Push-Location $script:KitRoot
25
+ try {
26
+ $rawOutput = & npm pack --dry-run --json 2>&1
27
+ $exitCode = $LASTEXITCODE
28
+ } finally {
29
+ Pop-Location
30
+ }
31
+
32
+ if ($exitCode -ne 0) {
33
+ throw "npm pack --dry-run gagal (exit $exitCode). Output: $rawOutput"
34
+ }
35
+
36
+ # npm pack --json kadang campur stderr ke stdout; ambil JSON array/object di akhir.
37
+ $jsonText = ($rawOutput | Out-String).Trim()
38
+ # Cari posisi '[' atau '{' pertama yang masuk akal — npm pack --json balikin array.
39
+ $jsonStart = $jsonText.IndexOf('[')
40
+ if ($jsonStart -lt 0) { $jsonStart = $jsonText.IndexOf('{') }
41
+ if ($jsonStart -gt 0) {
42
+ $jsonText = $jsonText.Substring($jsonStart)
43
+ }
44
+
45
+ $script:PackInfo = $jsonText | ConvertFrom-Json
46
+ # npm pack --json returns array; ambil entry pertama.
47
+ if ($script:PackInfo -is [array]) {
48
+ $script:PackInfo = $script:PackInfo[0]
49
+ }
50
+
51
+ # File list (relative path di dalam tarball).
52
+ $script:PackFiles = @($script:PackInfo.files | ForEach-Object { $_.path })
53
+ }
54
+
55
+ Describe "package.json structural integrity" {
56
+ It "package.json adalah valid JSON" {
57
+ { Get-Content -Raw -Path $script:PackageJsonPath | ConvertFrom-Json } | Should -Not -Throw
58
+ }
59
+
60
+ It "package.json punya files[] array" {
61
+ $pkg = Get-Content -Raw -Path $script:PackageJsonPath | ConvertFrom-Json
62
+ $pkg.files | Should -Not -BeNullOrEmpty
63
+ $pkg.files.Count | Should -BeGreaterThan 0
64
+ }
65
+
66
+ It "files[] include pattern untuk AGENTS.md.template (explicit atau via wildcard)" {
67
+ $pkg = Get-Content -Raw -Path $script:PackageJsonPath | ConvertFrom-Json
68
+ $patterns = @($pkg.files)
69
+
70
+ # Match jika ada salah satu: explicit "AGENTS.md.template", wildcard "*.template", atau wildcard "*.md".
71
+ $hasExplicit = $patterns -contains 'AGENTS.md.template'
72
+ $hasTemplateWildcard = $patterns -contains '*.template'
73
+ $hasMdWildcard = $patterns -contains '*.md'
74
+
75
+ ($hasExplicit -or $hasTemplateWildcard -or $hasMdWildcard) | Should -BeTrue `
76
+ -Because "AGENTS.md.template harus ter-cover oleh pattern di files[] (explicit atau wildcard)"
77
+ }
78
+ }
79
+
80
+ Describe "npm pack --dry-run bundle coverage" {
81
+ It "Tarball memuat AGENTS.md.template" {
82
+ $script:PackFiles | Should -Contain 'AGENTS.md.template' `
83
+ -Because "AGENTS.md.template adalah file kritikal kit; tanpa ini setup Pola B di project gagal"
84
+ }
85
+
86
+ It "Tarball memuat bin/lintasai.js" {
87
+ $script:PackFiles | Should -Contain 'bin/lintasai.js' `
88
+ -Because "Entry point CLI; tanpa ini `npx lintasai` tidak jalan"
89
+ }
90
+
91
+ It "Tarball memuat file di lib/" {
92
+ $libFiles = @($script:PackFiles | Where-Object { $_ -like 'lib/*' })
93
+ $libFiles.Count | Should -BeGreaterThan 0 -Because "lib/ harus ada di tarball"
94
+ }
95
+
96
+ It "Tarball memuat file di templates/" {
97
+ $templateFiles = @($script:PackFiles | Where-Object { $_ -like 'templates/*' })
98
+ $templateFiles.Count | Should -BeGreaterThan 0 -Because "templates/ harus ada di tarball"
99
+ }
100
+
101
+ It "Tarball memuat file di docs/" {
102
+ $docsFiles = @($script:PackFiles | Where-Object { $_ -like 'docs/*' })
103
+ $docsFiles.Count | Should -BeGreaterThan 0 -Because "docs/ harus ada di tarball"
104
+ }
105
+
106
+ It "Tarball memuat file di tests/" {
107
+ $testFiles = @($script:PackFiles | Where-Object { $_ -like 'tests/*' })
108
+ $testFiles.Count | Should -BeGreaterThan 0 -Because "tests/ harus ada di tarball (untuk konsumen yang mau re-verify)"
109
+ }
110
+ }
111
+
112
+ Describe "Tarball size & file count sanity" {
113
+ It "Tarball size < 2 MB" {
114
+ # npm pack --json balikin `size` (unpacked) dan `unpackedSize`. Pakai size (tarball compressed) kalau ada.
115
+ $size = if ($null -ne $script:PackInfo.size) { $script:PackInfo.size } else { $script:PackInfo.unpackedSize }
116
+ $size | Should -Not -BeNullOrEmpty
117
+ $size | Should -BeLessThan (2 * 1024 * 1024) `
118
+ -Because "Kit harus stay lightweight (<2MB). Saat ini: $([math]::Round($size/1024,1)) KB"
119
+ }
120
+
121
+ It "Tarball entryCount > 50 (sanity check, bukan empty bundle)" {
122
+ $entryCount = if ($null -ne $script:PackInfo.entryCount) {
123
+ $script:PackInfo.entryCount
124
+ } else {
125
+ $script:PackFiles.Count
126
+ }
127
+ $entryCount | Should -BeGreaterThan 50 `
128
+ -Because "Kit punya banyak file (templates, docs, lib, tests). <50 = something missing. Actual: $entryCount"
129
+ }
130
+ }
@@ -0,0 +1,280 @@
1
+ #Requires -Module Pester
2
+
3
+ <#
4
+ .SYNOPSIS
5
+ Pester 5+ tests untuk setup-pola-b.ps1 (ProjectRoot resolution + npx mode copy).
6
+
7
+ .DESCRIPTION
8
+ Empat invocation branches yang di-test:
9
+ 1. No -ProjectRoot, kit at <tmp>/.claude-kit/setup-pola-b.ps1
10
+ -> project = parent of kit (traditional Pola B mode).
11
+ 2. -ProjectRoot supplied (valid path)
12
+ -> project = that explicit path (no derivation).
13
+ 3. -ProjectRoot non-existent path
14
+ -> script errors out clearly (not silent fail). Resolve-Path -ErrorAction Stop
15
+ melempar terminating error; test assert exit code non-zero.
16
+ 4. -ProjectRoot supplied + kit at npm-cache-like path (TEMP\nodemod-test\@ojokesusu\lintasai)
17
+ -> npx mode auto-detect, kit di-COPY ke $ProjectRoot/.claude-kit/.
18
+
19
+ Plus:
20
+ 5. Manifest sanity: setelah setup sukses, .install-manifest.json terbuat.
21
+
22
+ Strategi:
23
+ - Real kit (parent dari folder tests/) jadi source-of-truth file.
24
+ - Per-test fake project root di $env:TEMP dengan layout sesuai branch.
25
+ - Branch 1, 2, 4, 5: pakai -Force -SkipTeamFiles untuk skip prompt + faster run.
26
+ - Branch 3: pakai path yang sengaja tidak ada, assert exit != 0 + pesan error.
27
+ - Test memanggil script di child PowerShell -NoProfile -NonInteractive supaya
28
+ env caller tidak ke-pollute (Set-StrictMode di lib/manifest.ps1 dll).
29
+ #>
30
+
31
+ BeforeAll {
32
+ # ---- Resolve repo root (kit folder asli, parent dari tests/) ----
33
+ $script:KitRepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..') | Select-Object -ExpandProperty Path
34
+ $script:SetupScript = Join-Path $script:KitRepoRoot 'setup-pola-b.ps1'
35
+
36
+ if (-not (Test-Path $script:SetupScript)) {
37
+ throw "setup-pola-b.ps1 not found at $script:SetupScript - tests assume layout tests/ sibling to setup-pola-b.ps1"
38
+ }
39
+
40
+ # ---- Helper: copy kit asli ke target folder (jadi .claude-kit di fake project) ----
41
+ function script:Copy-RealKit {
42
+ param(
43
+ [Parameter(Mandatory)][string]$Destination
44
+ )
45
+ if (-not (Test-Path -LiteralPath $Destination)) {
46
+ $null = New-Item -ItemType Directory -Path $Destination -Force
47
+ }
48
+ # Copy semua isi kit kecuali subfolder yang bisa bikin loop/lambat (kalau ada)
49
+ Get-ChildItem -Path $script:KitRepoRoot -Force | Where-Object {
50
+ $_.Name -notin @('.git', 'node_modules')
51
+ } | ForEach-Object {
52
+ $dest = Join-Path $Destination $_.Name
53
+ if ($_.PSIsContainer) {
54
+ Copy-Item -LiteralPath $_.FullName -Destination $dest -Recurse -Force
55
+ } else {
56
+ Copy-Item -LiteralPath $_.FullName -Destination $dest -Force
57
+ }
58
+ }
59
+ }
60
+
61
+ # ---- Helper: bikin file dummy supaya proyek "tidak hampir kosong" ----
62
+ # setup-pola-b.ps1 skip docs/ skeleton kalau project root hampir kosong (heuristic
63
+ # menghitung non-hidden file/dir). Test branch 1/2/4/5 mau real flow, jadi taruh
64
+ # 2 file dummy supaya nonHiddenFiles.Count > 1.
65
+ function script:Add-ProjectContent {
66
+ param([Parameter(Mandatory)][string]$Root)
67
+ [System.IO.File]::WriteAllText(
68
+ (Join-Path $Root 'README.md'),
69
+ "# Test Project`nDummy content untuk lewatin proyek-hampir-kosong heuristic.`n",
70
+ (New-Object System.Text.UTF8Encoding $false)
71
+ )
72
+ [System.IO.File]::WriteAllText(
73
+ (Join-Path $Root 'package.json'),
74
+ '{"name":"test","version":"0.0.1"}',
75
+ (New-Object System.Text.UTF8Encoding $false)
76
+ )
77
+ }
78
+
79
+ # ---- Helper: run setup-pola-b.ps1 di child PowerShell, capture stdout + exit ----
80
+ function script:Invoke-Setup {
81
+ param(
82
+ [Parameter(Mandatory)][string]$ScriptPath,
83
+ [string[]]$Args = @()
84
+ )
85
+ $pwshExe = (Get-Process -Id $PID).Path
86
+ if (-not $pwshExe) { $pwshExe = 'powershell.exe' }
87
+ $allArgs = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $ScriptPath) + $Args
88
+ $output = & $pwshExe @allArgs 2>&1 | Out-String
89
+ return [pscustomobject]@{
90
+ Output = $output
91
+ ExitCode = $LASTEXITCODE
92
+ }
93
+ }
94
+
95
+ # ---- Helper: cleanup folder kalau ada ----
96
+ function script:Remove-TestRoot {
97
+ param([Parameter(Mandatory)][string]$Path)
98
+ if (Test-Path -LiteralPath $Path) {
99
+ Remove-Item -Recurse -Force -LiteralPath $Path -ErrorAction SilentlyContinue
100
+ }
101
+ }
102
+ }
103
+
104
+ Describe "setup-pola-b.ps1 Branch 1: traditional invocation (no -ProjectRoot)" {
105
+ BeforeAll {
106
+ # Layout: <tmp>/<project>/.claude-kit/setup-pola-b.ps1
107
+ $script:Root1 = Join-Path $env:TEMP ("lintasAI-setup-test-traditional-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
108
+ $null = New-Item -ItemType Directory -Path $script:Root1 -Force
109
+ script:Add-ProjectContent -Root $script:Root1
110
+ $script:Kit1 = Join-Path $script:Root1 '.claude-kit'
111
+ script:Copy-RealKit -Destination $script:Kit1
112
+ $script:SetupInKit1 = Join-Path $script:Kit1 'setup-pola-b.ps1'
113
+ }
114
+
115
+ AfterAll {
116
+ script:Remove-TestRoot -Path $script:Root1
117
+ }
118
+
119
+ It "Resolves project root = parent of kit folder" {
120
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit1 -Args @('-Force', '-SkipTeamFiles')
121
+ $result.ExitCode | Should -Be 0
122
+ # Output harus mention root proyek sesuai $Root1.
123
+ $result.Output | Should -Match ([regex]::Escape($script:Root1))
124
+ # AGENTS.md harus dideploy di project root.
125
+ (Test-Path -LiteralPath (Join-Path $script:Root1 'AGENTS.md')) | Should -BeTrue
126
+ }
127
+
128
+ It "Does NOT print npx mode banner" {
129
+ # Traditional mode: tidak boleh ada string "[npx] Mode" karena ProjectRoot tidak di-pass.
130
+ # Re-run dengan -DryRun supaya tidak duplicate setup di same root.
131
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit1 -Args @('-Force', '-SkipTeamFiles', '-DryRun')
132
+ $result.Output | Should -Not -Match '\[npx\] Mode'
133
+ }
134
+ }
135
+
136
+ Describe "setup-pola-b.ps1 Branch 2: -ProjectRoot supplied (valid path)" {
137
+ BeforeAll {
138
+ # Layout: <tmp>/<project>/.claude-kit/setup-pola-b.ps1 (kit lives standard place)
139
+ # but we ALSO pass -ProjectRoot explicitly to exercise the npx-mode code path
140
+ # bahkan kalau path-nya same. Script harus treat sebagai npx mode.
141
+ $script:Root2 = Join-Path $env:TEMP ("lintasAI-setup-test-explicitroot-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
142
+ $null = New-Item -ItemType Directory -Path $script:Root2 -Force
143
+ script:Add-ProjectContent -Root $script:Root2
144
+ $script:Kit2 = Join-Path $script:Root2 '.claude-kit'
145
+ script:Copy-RealKit -Destination $script:Kit2
146
+ $script:SetupInKit2 = Join-Path $script:Kit2 'setup-pola-b.ps1'
147
+ }
148
+
149
+ AfterAll {
150
+ script:Remove-TestRoot -Path $script:Root2
151
+ }
152
+
153
+ It "Uses the explicit ProjectRoot path (no derivation)" {
154
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit2 -Args @(
155
+ '-Force', '-SkipTeamFiles', '-ProjectRoot', $script:Root2
156
+ )
157
+ $result.ExitCode | Should -Be 0
158
+ # Banner npx mode harus muncul (proves the branch executed).
159
+ $result.Output | Should -Match '\[npx\] Mode: explicit ProjectRoot'
160
+ # AGENTS.md harus ada di explicit root.
161
+ (Test-Path -LiteralPath (Join-Path $script:Root2 'AGENTS.md')) | Should -BeTrue
162
+ }
163
+ }
164
+
165
+ Describe "setup-pola-b.ps1 Branch 3: -ProjectRoot non-existent path" {
166
+ BeforeAll {
167
+ # Kit lives di TEMP/<kit-folder>/.claude-kit dengan parent project legit, tapi
168
+ # we pass -ProjectRoot ke path yang tidak ada -> Resolve-Path -ErrorAction Stop
169
+ # harus throw -> exit code non-zero, with clear error message (not silent).
170
+ $script:Root3 = Join-Path $env:TEMP ("lintasAI-setup-test-badroot-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
171
+ $null = New-Item -ItemType Directory -Path $script:Root3 -Force
172
+ script:Add-ProjectContent -Root $script:Root3
173
+ $script:Kit3 = Join-Path $script:Root3 '.claude-kit'
174
+ script:Copy-RealKit -Destination $script:Kit3
175
+ $script:SetupInKit3 = Join-Path $script:Kit3 'setup-pola-b.ps1'
176
+ # Path yang DIJAMIN tidak ada
177
+ $script:BadRoot = Join-Path $env:TEMP ("lintasAI-DOES-NOT-EXIST-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
178
+ }
179
+
180
+ AfterAll {
181
+ script:Remove-TestRoot -Path $script:Root3
182
+ script:Remove-TestRoot -Path $script:BadRoot # defensive (script harusnya tidak create)
183
+ }
184
+
185
+ It "Errors out (non-zero exit) with clear message" {
186
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit3 -Args @(
187
+ '-Force', '-SkipTeamFiles', '-ProjectRoot', $script:BadRoot
188
+ )
189
+ $result.ExitCode | Should -Not -Be 0
190
+ # Pesan harus include "Resolve-Path" / "not exist" / nama path yang tidak valid -
191
+ # apa pun yang membuktikan ini BUKAN silent fail.
192
+ # Resolve-Path throws "Cannot find path '...' because it does not exist."
193
+ $result.Output | Should -Match '(?i)(cannot find path|does not exist|not exist|tidak ditemukan|tidak ada)'
194
+ }
195
+
196
+ It "Does NOT create .claude-kit at the bogus path" {
197
+ # Script tidak boleh men-create folder di path yang tidak ada (defensive guard).
198
+ (Test-Path -LiteralPath $script:BadRoot) | Should -BeFalse
199
+ }
200
+ }
201
+
202
+ Describe "setup-pola-b.ps1 Branch 4: -ProjectRoot + kit at npm-cache-like path" {
203
+ BeforeAll {
204
+ # Layout simulasi npx:
205
+ # <tmp>\nodemod-test\@ojokesusu\lintasai\setup-pola-b.ps1 (kit di npm cache)
206
+ # <tmp>\<project-root>\ (target project, beda parent)
207
+ # Script harus detect mismatch ($KitDir tidak dalam $ProjectRoot/.claude-kit/)
208
+ # dan COPY isi kit ke $ProjectRoot/.claude-kit/.
209
+ $script:Root4 = Join-Path $env:TEMP ("lintasAI-setup-test-npx-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
210
+ $null = New-Item -ItemType Directory -Path $script:Root4 -Force
211
+ script:Add-ProjectContent -Root $script:Root4
212
+
213
+ # Kit DIPASANG di npm-cache-like location (BUKAN di $script:Root4/.claude-kit)
214
+ $script:NpmCacheRoot = Join-Path $env:TEMP ("nodemod-test-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
215
+ $script:KitInNpmCache = Join-Path $script:NpmCacheRoot '@ojokesusu\lintasai'
216
+ $null = New-Item -ItemType Directory -Path $script:KitInNpmCache -Force
217
+ script:Copy-RealKit -Destination $script:KitInNpmCache
218
+ $script:SetupInNpmCache = Join-Path $script:KitInNpmCache 'setup-pola-b.ps1'
219
+ }
220
+
221
+ AfterAll {
222
+ script:Remove-TestRoot -Path $script:Root4
223
+ script:Remove-TestRoot -Path $script:NpmCacheRoot
224
+ }
225
+
226
+ It "Detects npx mode and copies kit to <ProjectRoot>/.claude-kit/" {
227
+ # Pre-assert: target .claude-kit tidak ada sebelum jalan.
228
+ $targetKit = Join-Path $script:Root4 '.claude-kit'
229
+ (Test-Path -LiteralPath $targetKit) | Should -BeFalse
230
+
231
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInNpmCache -Args @(
232
+ '-Force', '-SkipTeamFiles', '-ProjectRoot', $script:Root4
233
+ )
234
+ $result.ExitCode | Should -Be 0
235
+ # Verifikasi log npx mode + copy
236
+ $result.Output | Should -Match '\[npx\] Mode: explicit ProjectRoot'
237
+ $result.Output | Should -Match '(?i)\[npx\] Copy kit'
238
+
239
+ # Hasil: .claude-kit di project root harus ada, berisi setup-pola-b.ps1 yang ke-copy
240
+ (Test-Path -LiteralPath $targetKit) | Should -BeTrue
241
+ (Test-Path -LiteralPath (Join-Path $targetKit 'setup-pola-b.ps1')) | Should -BeTrue
242
+ (Test-Path -LiteralPath (Join-Path $targetKit 'CHANGELOG.md')) | Should -BeTrue
243
+ (Test-Path -LiteralPath (Join-Path $targetKit 'lib\manifest.ps1')) | Should -BeTrue
244
+ }
245
+ }
246
+
247
+ Describe "setup-pola-b.ps1 manifest creation" {
248
+ BeforeAll {
249
+ # Reuse pattern Branch 1 (traditional) – setelah setup sukses, manifest harus
250
+ # terbuat di .claude-kit/.install-manifest.json. Pakai fresh root supaya hash
251
+ # & state-nya independent dari describe lain.
252
+ $script:Root5 = Join-Path $env:TEMP ("lintasAI-setup-test-manifest-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
253
+ $null = New-Item -ItemType Directory -Path $script:Root5 -Force
254
+ script:Add-ProjectContent -Root $script:Root5
255
+ $script:Kit5 = Join-Path $script:Root5 '.claude-kit'
256
+ script:Copy-RealKit -Destination $script:Kit5
257
+ $script:SetupInKit5 = Join-Path $script:Kit5 'setup-pola-b.ps1'
258
+ }
259
+
260
+ AfterAll {
261
+ script:Remove-TestRoot -Path $script:Root5
262
+ }
263
+
264
+ It "Creates .install-manifest.json after successful setup" {
265
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit5 -Args @('-Force', '-SkipTeamFiles')
266
+ $result.ExitCode | Should -Be 0
267
+ $manifestPath = Join-Path $script:Kit5 '.install-manifest.json'
268
+ (Test-Path -LiteralPath $manifestPath) | Should -BeTrue
269
+ }
270
+
271
+ It "Manifest is valid JSON with expected top-level fields" {
272
+ $manifestPath = Join-Path $script:Kit5 '.install-manifest.json'
273
+ (Test-Path -LiteralPath $manifestPath) | Should -BeTrue
274
+ $json = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8
275
+ { $json | ConvertFrom-Json -ErrorAction Stop } | Should -Not -Throw
276
+ $obj = $json | ConvertFrom-Json
277
+ $obj.PSObject.Properties.Name | Should -Contain 'schema_version'
278
+ $obj.PSObject.Properties.Name | Should -Contain 'files'
279
+ }
280
+ }
package/uninstall.ps1 CHANGED
@@ -63,9 +63,20 @@ param(
63
63
  [switch]$DeleteAgents,
64
64
  [switch]$KeepKit,
65
65
  [switch]$Yes,
66
- [switch]$AllowProjectRootMismatch
66
+ [switch]$AllowProjectRootMismatch,
67
+ [string]$ProjectRoot = $null
67
68
  )
68
69
 
70
+ # ---- Resolve $ProjectRoot early (param-driven, fallback to script location) ----
71
+ # Kalau user pass -ProjectRoot pakai itu (untuk smoke test / CI). Kalau tidak, derive dari
72
+ # $PSScriptRoot (script ada di .claude-kit\, parent = project root).
73
+ if (-not $ProjectRoot) {
74
+ $ProjectRoot = Split-Path -Parent $PSScriptRoot
75
+ } else {
76
+ $ProjectRoot = (Resolve-Path $ProjectRoot -ErrorAction Stop).Path
77
+ }
78
+ Write-Host "Root proyek : $ProjectRoot"
79
+
69
80
  # ---- Deprecation handling: -Force jadi alias backward-compat untuk -AllowModified ----
70
81
  # Sebelumnya -Force overloaded (modified bypass + signature bypass). Pisahkan supaya
71
82
  # intent jelas. -Force masih bekerja untuk backward-compat tapi warn user.
@@ -81,8 +92,8 @@ if ($Force) {
81
92
  $ErrorActionPreference = 'Stop'
82
93
 
83
94
  # ---- Resolve folder kit (tempat script ini berada) ----
95
+ # $ProjectRoot sudah di-resolve di awal (param-driven). Jangan override.
84
96
  $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
97
  $ProjectName = Split-Path -Leaf $ProjectRoot
87
98
  $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
88
99
 
package/update-kit.ps1 CHANGED
@@ -70,11 +70,22 @@ param(
70
70
  [switch]$DryRun,
71
71
  [switch]$AllowUntrustedRepo,
72
72
  [switch]$Force,
73
- [switch]$AllowUnsignedTag
73
+ [switch]$AllowUnsignedTag,
74
+ [string]$ProjectRoot = $null
74
75
  )
75
76
 
76
77
  $ErrorActionPreference = 'Stop'
77
78
 
79
+ # ---- Resolve $ProjectRoot early (param-driven, fallback to script location) ----
80
+ # Kalau user pass -ProjectRoot pakai itu (untuk smoke test / CI). Kalau tidak, derive dari
81
+ # $PSScriptRoot (script ada di .claude-kit\, parent = project root).
82
+ if (-not $ProjectRoot) {
83
+ $ProjectRoot = Split-Path -Parent $PSScriptRoot
84
+ } else {
85
+ $ProjectRoot = (Resolve-Path $ProjectRoot -ErrorAction Stop).Path
86
+ }
87
+ Write-Host "Root proyek : $ProjectRoot"
88
+
78
89
  # ---- Deprecation handling: -Force jadi alias backward-compat untuk -AllowUntrustedRepo ----
79
90
  # Sebelumnya -Force overloaded (GPG bypass + RepoUrl bypass). GPG bypass sekarang lewat
80
91
  # -AllowUnsignedTag; -Force narrowed jadi alias RepoUrl-allowlist-bypass saja, dengan warn.
@@ -89,9 +100,11 @@ if ($Force) {
89
100
  }
90
101
 
91
102
  # ---- Resolve paths (do this FIRST, sebelum Move-Item rename folder) ----
103
+ # $ProjectRoot (PascalCase) sudah di-resolve dari param di awal. $projectRoot (camelCase)
104
+ # di-alias supaya legacy code di bawah tetap jalan tanpa rename massal.
92
105
  $kitDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { (Get-Location).Path }
93
106
  $kitFolderName = Split-Path -Leaf $kitDir
94
- $projectRoot = Split-Path -Parent $kitDir
107
+ $projectRoot = $ProjectRoot
95
108
  $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
96
109
  $backupDir = "$kitDir.backup-$timestamp"
97
110
 
@@ -1,7 +0,0 @@
1
- # DEPRECATED (lintasAI v1.0.0 republish 2026-06-04)
2
-
3
- File ini deprecated. First-session workflow sudah jadi bagian dari JALANKAN_KIT.md.
4
-
5
- -> Pakai [JALANKAN_KIT.md](JALANKAN_KIT.md)
6
-
7
- Content lama bisa dicek di git history sebelum 2026-06-04.