@ojokesusu/lintasai 1.2.0 → 1.2.1

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,32 @@ Slot tambahan untuk perubahan berikutnya sebelum versi v1.0.2.
20
20
 
21
21
  ---
22
22
 
23
+ ## v1.2.1 [2026-06-06]
24
+
25
+ ### Fixed (Critical Follow-up to v1.2.0)
26
+
27
+ #### Uninstall Command via NPX
28
+ - `npx lintasai uninstall` previously failed karena uninstall.ps1 ignore -ProjectRoot param dan resolve via manifest project_root override
29
+ - Fix: When -ProjectRoot explicitly supplied via $PSBoundParameters, honor it as ground truth (npx mode)
30
+ - Manifest mismatch detection only runs ketika -ProjectRoot NOT supplied + -AllowProjectRootMismatch not set
31
+
32
+ #### Diff Command
33
+ - `npx lintasai diff` previously failed (bug di new Invoke-Diff function)
34
+ - Fix: Rewrite to use Get-FileHash + manifest comparison cleanly with proper $ProjectRoot scoping
35
+
36
+ #### Pester Tests Alignment
37
+ - 3 failing tests di setup-pola-b.Tests.ps1 adjusted untuk match actual script behavior
38
+
39
+ #### Code Quality
40
+ - L1-RUN-004: null comparison style ($null -ne $x) consistency across all .ps1 files
41
+
42
+ ### Notes
43
+ - ALL 7 commands sekarang exit 0 (verified): init, version, doctor, status, diff, update, uninstall
44
+ - Pester 100% pass
45
+ - Distribution Verdict: GREEN
46
+
47
+ ---
48
+
23
49
  ## v1.2.0 [2026-06-06]
24
50
 
25
51
  ### Fixed (Critical)
package/bin/lintasai.js CHANGED
@@ -19,6 +19,7 @@ const COMMANDS = {
19
19
  "rollback": ["kit.ps1", "rollback"],
20
20
  "setup": ["kit.ps1", "setup"],
21
21
  "status": ["kit.ps1", "status"],
22
+ "diff": ["kit.ps1", "diff"],
22
23
  };
23
24
 
24
25
  function showHelp() {
package/kit.ps1 CHANGED
@@ -48,7 +48,7 @@
48
48
  [CmdletBinding()]
49
49
  param(
50
50
  [Parameter(Position = 0)]
51
- [ValidateSet('setup', 'update', 'uninstall', 'doctor', 'scan', 'version', 'rollback', 'status', 'help', '')]
51
+ [ValidateSet('setup', 'update', 'uninstall', 'doctor', 'scan', 'version', 'rollback', 'status', 'diff', 'help', '')]
52
52
  [string]$Command = '',
53
53
 
54
54
  [string]$ProjectRoot = $null,
@@ -76,7 +76,7 @@ if ($ProjectRoot) {
76
76
 
77
77
  # Inform user which kit is being inspected — penting buat differentiate "kit di project" vs
78
78
  # "kit di npm cache". Subcommands yang inspect kit (doctor/version/rollback/scan) butuh ini.
79
- if ($Command -in @('doctor', 'version', 'rollback', 'scan', 'status')) {
79
+ if ($Command -in @('doctor', 'version', 'rollback', 'scan', 'status', 'diff')) {
80
80
  Write-Host ("Inspecting kit at: {0}" -f $KitDir) -ForegroundColor Cyan
81
81
  }
82
82
 
@@ -260,7 +260,7 @@ function Invoke-Doctor {
260
260
  $manifest = $null
261
261
  }
262
262
 
263
- if ($manifest -ne $null) {
263
+ if ($null -ne $manifest) {
264
264
  $pristine = 0
265
265
  $modifiedList = @()
266
266
  $integrityMissing = 0
@@ -562,6 +562,41 @@ function Show-Version {
562
562
  Write-Host (Get-KitVersion)
563
563
  }
564
564
 
565
+ # ---- Helper: diff vs manifest ----
566
+ # Compare current file hashes vs sha256 di .install-manifest.json. Tampilkan file yang
567
+ # modified atau missing sejak install. Berguna buat user yang mau cek apakah ada yang
568
+ # tweaked file kit secara manual sebelum jalankan `update` atau `uninstall`.
569
+ function Invoke-Diff {
570
+ param([string]$ProjectRootResolved)
571
+ $kitDir = Join-Path $ProjectRootResolved ".claude-kit"
572
+ $manifestPath = Join-Path $kitDir ".install-manifest.json"
573
+ if (-not (Test-Path $manifestPath)) {
574
+ Write-Warning "Tidak ada manifest di $manifestPath. Kit belum di-install atau corrupt."
575
+ return 1
576
+ }
577
+ $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
578
+ $modified = @()
579
+ $missing = @()
580
+ foreach ($entry in $manifest.files) {
581
+ $filePath = Join-Path $ProjectRootResolved $entry.path
582
+ if (-not (Test-Path $filePath)) {
583
+ $missing += $entry.path
584
+ } else {
585
+ $currentHash = (Get-FileHash -Algorithm SHA256 -Path $filePath).Hash.ToLower()
586
+ if ($currentHash -ne $entry.sha256.ToLower()) {
587
+ $modified += $entry.path
588
+ }
589
+ }
590
+ }
591
+ Write-Host "=== Kit Diff (vs manifest) ==="
592
+ Write-Host "Modified ($($modified.Count)):"
593
+ $modified | ForEach-Object { Write-Host " M $_" }
594
+ Write-Host ""
595
+ Write-Host "Missing ($($missing.Count)):"
596
+ $missing | ForEach-Object { Write-Host " - $_" }
597
+ return 0
598
+ }
599
+
565
600
  # ---- Router ----
566
601
  switch ($Command) {
567
602
  'setup' {
@@ -605,6 +640,10 @@ switch ($Command) {
605
640
  $code = Invoke-Status
606
641
  exit $code
607
642
  }
643
+ 'diff' {
644
+ $code = Invoke-Diff -ProjectRootResolved $ProjectRoot
645
+ exit $code
646
+ }
608
647
  'version' {
609
648
  Show-Version
610
649
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ojokesusu/lintasai",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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"
@@ -33,6 +33,14 @@ BeforeAll {
33
33
  $script:KitRepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..') | Select-Object -ExpandProperty Path
34
34
  $script:SetupScript = Join-Path $script:KitRepoRoot 'setup-pola-b.ps1'
35
35
 
36
+ # ---- Canonical TEMP (long form) ----
37
+ # $env:TEMP sering return 8.3 short path (mis. C:\Users\ADMINI~1\AppData\Local\Temp\2).
38
+ # Setup script pakai $PSScriptRoot (long form) untuk derive ProjectRoot di traditional
39
+ # mode, dan Resolve-Path tidak expand 8.3 -> long. Akibatnya: output script pakai long
40
+ # form, regex test pakai short form -> tidak match. Pakai (Get-Item).FullName supaya
41
+ # canonical long form di semua test path.
42
+ $script:TempCanonical = (Get-Item -LiteralPath $env:TEMP).FullName
43
+
36
44
  if (-not (Test-Path $script:SetupScript)) {
37
45
  throw "setup-pola-b.ps1 not found at $script:SetupScript - tests assume layout tests/ sibling to setup-pola-b.ps1"
38
46
  }
@@ -104,7 +112,7 @@ BeforeAll {
104
112
  Describe "setup-pola-b.ps1 Branch 1: traditional invocation (no -ProjectRoot)" {
105
113
  BeforeAll {
106
114
  # 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)))
115
+ $script:Root1 = Join-Path $script:TempCanonical ("lintasAI-setup-test-traditional-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
108
116
  $null = New-Item -ItemType Directory -Path $script:Root1 -Force
109
117
  script:Add-ProjectContent -Root $script:Root1
110
118
  $script:Kit1 = Join-Path $script:Root1 '.claude-kit'
@@ -138,7 +146,7 @@ Describe "setup-pola-b.ps1 Branch 2: -ProjectRoot supplied (valid path)" {
138
146
  # Layout: <tmp>/<project>/.claude-kit/setup-pola-b.ps1 (kit lives standard place)
139
147
  # but we ALSO pass -ProjectRoot explicitly to exercise the npx-mode code path
140
148
  # 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)))
149
+ $script:Root2 = Join-Path $script:TempCanonical ("lintasAI-setup-test-explicitroot-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
142
150
  $null = New-Item -ItemType Directory -Path $script:Root2 -Force
143
151
  script:Add-ProjectContent -Root $script:Root2
144
152
  $script:Kit2 = Join-Path $script:Root2 '.claude-kit'
@@ -167,14 +175,14 @@ Describe "setup-pola-b.ps1 Branch 3: -ProjectRoot non-existent path" {
167
175
  # Kit lives di TEMP/<kit-folder>/.claude-kit dengan parent project legit, tapi
168
176
  # we pass -ProjectRoot ke path yang tidak ada -> Resolve-Path -ErrorAction Stop
169
177
  # 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)))
178
+ $script:Root3 = Join-Path $script:TempCanonical ("lintasAI-setup-test-badroot-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
171
179
  $null = New-Item -ItemType Directory -Path $script:Root3 -Force
172
180
  script:Add-ProjectContent -Root $script:Root3
173
181
  $script:Kit3 = Join-Path $script:Root3 '.claude-kit'
174
182
  script:Copy-RealKit -Destination $script:Kit3
175
183
  $script:SetupInKit3 = Join-Path $script:Kit3 'setup-pola-b.ps1'
176
184
  # 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)))
185
+ $script:BadRoot = Join-Path $script:TempCanonical ("lintasAI-DOES-NOT-EXIST-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
178
186
  }
179
187
 
180
188
  AfterAll {
@@ -206,12 +214,12 @@ Describe "setup-pola-b.ps1 Branch 4: -ProjectRoot + kit at npm-cache-like path"
206
214
  # <tmp>\<project-root>\ (target project, beda parent)
207
215
  # Script harus detect mismatch ($KitDir tidak dalam $ProjectRoot/.claude-kit/)
208
216
  # 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)))
217
+ $script:Root4 = Join-Path $script:TempCanonical ("lintasAI-setup-test-npx-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
210
218
  $null = New-Item -ItemType Directory -Path $script:Root4 -Force
211
219
  script:Add-ProjectContent -Root $script:Root4
212
220
 
213
221
  # 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)))
222
+ $script:NpmCacheRoot = Join-Path $script:TempCanonical ("nodemod-test-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
215
223
  $script:KitInNpmCache = Join-Path $script:NpmCacheRoot '@ojokesusu\lintasai'
216
224
  $null = New-Item -ItemType Directory -Path $script:KitInNpmCache -Force
217
225
  script:Copy-RealKit -Destination $script:KitInNpmCache
@@ -249,7 +257,7 @@ Describe "setup-pola-b.ps1 manifest creation" {
249
257
  # Reuse pattern Branch 1 (traditional) – setelah setup sukses, manifest harus
250
258
  # terbuat di .claude-kit/.install-manifest.json. Pakai fresh root supaya hash
251
259
  # & 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)))
260
+ $script:Root5 = Join-Path $script:TempCanonical ("lintasAI-setup-test-manifest-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
253
261
  $null = New-Item -ItemType Directory -Path $script:Root5 -Force
254
262
  script:Add-ProjectContent -Root $script:Root5
255
263
  $script:Kit5 = Join-Path $script:Root5 '.claude-kit'
package/uninstall.ps1 CHANGED
@@ -68,12 +68,13 @@ param(
68
68
  )
69
69
 
70
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 {
71
+ # Kalau user pass -ProjectRoot via param (npx wrapper / smoke test / CI), TRUST it sebagai
72
+ # ground truth -- $PSBoundParameters check supaya kita honor explicit param meski empty string.
73
+ # Kalau tidak di-supply, derive dari $PSScriptRoot (script ada di .claude-kit\, parent = project root).
74
+ if ($PSBoundParameters.ContainsKey('ProjectRoot') -and $ProjectRoot) {
76
75
  $ProjectRoot = (Resolve-Path $ProjectRoot -ErrorAction Stop).Path
76
+ } else {
77
+ $ProjectRoot = Split-Path -Parent $PSScriptRoot
77
78
  }
78
79
  Write-Host "Root proyek : $ProjectRoot"
79
80
 
@@ -281,8 +282,12 @@ if ($schemaVersion -ne 1) {
281
282
  # ---- Sanity check: project_root manifest match folder kit saat ini? ----
282
283
  # Default HARD-FAIL untuk mencegah manifest dari project lain delete file di project ini.
283
284
  # Override pakai -AllowProjectRootMismatch (mis. legitimate rename folder).
285
+ #
286
+ # GUARD: Kalau -ProjectRoot di-supply EXPLICIT via param (npx/CI/wrapper mode), TRUST it
287
+ # sebagai ground truth -- skip mismatch check (caller sudah explicit override).
284
288
  $manifestProjectRoot = [string]$manifest.project_root
285
- if ($manifestProjectRoot -and $manifestProjectRoot -ne $ProjectRoot -and $manifestProjectRoot -ne '<PROJECT_ROOT>') {
289
+ $projectRootSupplied = $PSBoundParameters.ContainsKey('ProjectRoot')
290
+ if (-not $projectRootSupplied -and $manifestProjectRoot -and $manifestProjectRoot -ne $ProjectRoot -and $manifestProjectRoot -ne '<PROJECT_ROOT>') {
286
291
  Write-Host ''
287
292
  if (-not $AllowProjectRootMismatch) {
288
293
  Write-Host 'ABORT: Project root di manifest TIDAK match lokasi sekarang.' -ForegroundColor Red
@@ -305,6 +310,12 @@ if ($manifestProjectRoot -and $manifestProjectRoot -ne $ProjectRoot -and $manife
305
310
  Write-Host ' Script pakai lokasi sekarang ($ProjectRoot) sebagai base.' -ForegroundColor Yellow
306
311
  Write-Host ''
307
312
  }
313
+ } elseif ($projectRootSupplied -and $manifestProjectRoot -and $manifestProjectRoot -ne $ProjectRoot -and $manifestProjectRoot -ne '<PROJECT_ROOT>') {
314
+ Write-Host ''
315
+ Write-Host 'INFO: -ProjectRoot di-supply explicit, manifest project_root override di-skip.' -ForegroundColor DarkGray
316
+ Write-Host " Manifest installed di : $manifestProjectRoot" -ForegroundColor DarkGray
317
+ Write-Host " Param -ProjectRoot : $ProjectRoot (ground truth)" -ForegroundColor DarkGray
318
+ Write-Host ''
308
319
  }
309
320
 
310
321
  # ---- Setup canonical project root (consumed by lib/safety.ps1 helpers) ----