@ojokesusu/lintasai 1.1.3 → 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.
@@ -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,288 @@
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
+ # ---- 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
+
44
+ if (-not (Test-Path $script:SetupScript)) {
45
+ throw "setup-pola-b.ps1 not found at $script:SetupScript - tests assume layout tests/ sibling to setup-pola-b.ps1"
46
+ }
47
+
48
+ # ---- Helper: copy kit asli ke target folder (jadi .claude-kit di fake project) ----
49
+ function script:Copy-RealKit {
50
+ param(
51
+ [Parameter(Mandatory)][string]$Destination
52
+ )
53
+ if (-not (Test-Path -LiteralPath $Destination)) {
54
+ $null = New-Item -ItemType Directory -Path $Destination -Force
55
+ }
56
+ # Copy semua isi kit kecuali subfolder yang bisa bikin loop/lambat (kalau ada)
57
+ Get-ChildItem -Path $script:KitRepoRoot -Force | Where-Object {
58
+ $_.Name -notin @('.git', 'node_modules')
59
+ } | ForEach-Object {
60
+ $dest = Join-Path $Destination $_.Name
61
+ if ($_.PSIsContainer) {
62
+ Copy-Item -LiteralPath $_.FullName -Destination $dest -Recurse -Force
63
+ } else {
64
+ Copy-Item -LiteralPath $_.FullName -Destination $dest -Force
65
+ }
66
+ }
67
+ }
68
+
69
+ # ---- Helper: bikin file dummy supaya proyek "tidak hampir kosong" ----
70
+ # setup-pola-b.ps1 skip docs/ skeleton kalau project root hampir kosong (heuristic
71
+ # menghitung non-hidden file/dir). Test branch 1/2/4/5 mau real flow, jadi taruh
72
+ # 2 file dummy supaya nonHiddenFiles.Count > 1.
73
+ function script:Add-ProjectContent {
74
+ param([Parameter(Mandatory)][string]$Root)
75
+ [System.IO.File]::WriteAllText(
76
+ (Join-Path $Root 'README.md'),
77
+ "# Test Project`nDummy content untuk lewatin proyek-hampir-kosong heuristic.`n",
78
+ (New-Object System.Text.UTF8Encoding $false)
79
+ )
80
+ [System.IO.File]::WriteAllText(
81
+ (Join-Path $Root 'package.json'),
82
+ '{"name":"test","version":"0.0.1"}',
83
+ (New-Object System.Text.UTF8Encoding $false)
84
+ )
85
+ }
86
+
87
+ # ---- Helper: run setup-pola-b.ps1 di child PowerShell, capture stdout + exit ----
88
+ function script:Invoke-Setup {
89
+ param(
90
+ [Parameter(Mandatory)][string]$ScriptPath,
91
+ [string[]]$Args = @()
92
+ )
93
+ $pwshExe = (Get-Process -Id $PID).Path
94
+ if (-not $pwshExe) { $pwshExe = 'powershell.exe' }
95
+ $allArgs = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $ScriptPath) + $Args
96
+ $output = & $pwshExe @allArgs 2>&1 | Out-String
97
+ return [pscustomobject]@{
98
+ Output = $output
99
+ ExitCode = $LASTEXITCODE
100
+ }
101
+ }
102
+
103
+ # ---- Helper: cleanup folder kalau ada ----
104
+ function script:Remove-TestRoot {
105
+ param([Parameter(Mandatory)][string]$Path)
106
+ if (Test-Path -LiteralPath $Path) {
107
+ Remove-Item -Recurse -Force -LiteralPath $Path -ErrorAction SilentlyContinue
108
+ }
109
+ }
110
+ }
111
+
112
+ Describe "setup-pola-b.ps1 Branch 1: traditional invocation (no -ProjectRoot)" {
113
+ BeforeAll {
114
+ # Layout: <tmp>/<project>/.claude-kit/setup-pola-b.ps1
115
+ $script:Root1 = Join-Path $script:TempCanonical ("lintasAI-setup-test-traditional-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
116
+ $null = New-Item -ItemType Directory -Path $script:Root1 -Force
117
+ script:Add-ProjectContent -Root $script:Root1
118
+ $script:Kit1 = Join-Path $script:Root1 '.claude-kit'
119
+ script:Copy-RealKit -Destination $script:Kit1
120
+ $script:SetupInKit1 = Join-Path $script:Kit1 'setup-pola-b.ps1'
121
+ }
122
+
123
+ AfterAll {
124
+ script:Remove-TestRoot -Path $script:Root1
125
+ }
126
+
127
+ It "Resolves project root = parent of kit folder" {
128
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit1 -Args @('-Force', '-SkipTeamFiles')
129
+ $result.ExitCode | Should -Be 0
130
+ # Output harus mention root proyek sesuai $Root1.
131
+ $result.Output | Should -Match ([regex]::Escape($script:Root1))
132
+ # AGENTS.md harus dideploy di project root.
133
+ (Test-Path -LiteralPath (Join-Path $script:Root1 'AGENTS.md')) | Should -BeTrue
134
+ }
135
+
136
+ It "Does NOT print npx mode banner" {
137
+ # Traditional mode: tidak boleh ada string "[npx] Mode" karena ProjectRoot tidak di-pass.
138
+ # Re-run dengan -DryRun supaya tidak duplicate setup di same root.
139
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit1 -Args @('-Force', '-SkipTeamFiles', '-DryRun')
140
+ $result.Output | Should -Not -Match '\[npx\] Mode'
141
+ }
142
+ }
143
+
144
+ Describe "setup-pola-b.ps1 Branch 2: -ProjectRoot supplied (valid path)" {
145
+ BeforeAll {
146
+ # Layout: <tmp>/<project>/.claude-kit/setup-pola-b.ps1 (kit lives standard place)
147
+ # but we ALSO pass -ProjectRoot explicitly to exercise the npx-mode code path
148
+ # bahkan kalau path-nya same. Script harus treat sebagai npx mode.
149
+ $script:Root2 = Join-Path $script:TempCanonical ("lintasAI-setup-test-explicitroot-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
150
+ $null = New-Item -ItemType Directory -Path $script:Root2 -Force
151
+ script:Add-ProjectContent -Root $script:Root2
152
+ $script:Kit2 = Join-Path $script:Root2 '.claude-kit'
153
+ script:Copy-RealKit -Destination $script:Kit2
154
+ $script:SetupInKit2 = Join-Path $script:Kit2 'setup-pola-b.ps1'
155
+ }
156
+
157
+ AfterAll {
158
+ script:Remove-TestRoot -Path $script:Root2
159
+ }
160
+
161
+ It "Uses the explicit ProjectRoot path (no derivation)" {
162
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit2 -Args @(
163
+ '-Force', '-SkipTeamFiles', '-ProjectRoot', $script:Root2
164
+ )
165
+ $result.ExitCode | Should -Be 0
166
+ # Banner npx mode harus muncul (proves the branch executed).
167
+ $result.Output | Should -Match '\[npx\] Mode: explicit ProjectRoot'
168
+ # AGENTS.md harus ada di explicit root.
169
+ (Test-Path -LiteralPath (Join-Path $script:Root2 'AGENTS.md')) | Should -BeTrue
170
+ }
171
+ }
172
+
173
+ Describe "setup-pola-b.ps1 Branch 3: -ProjectRoot non-existent path" {
174
+ BeforeAll {
175
+ # Kit lives di TEMP/<kit-folder>/.claude-kit dengan parent project legit, tapi
176
+ # we pass -ProjectRoot ke path yang tidak ada -> Resolve-Path -ErrorAction Stop
177
+ # harus throw -> exit code non-zero, with clear error message (not silent).
178
+ $script:Root3 = Join-Path $script:TempCanonical ("lintasAI-setup-test-badroot-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
179
+ $null = New-Item -ItemType Directory -Path $script:Root3 -Force
180
+ script:Add-ProjectContent -Root $script:Root3
181
+ $script:Kit3 = Join-Path $script:Root3 '.claude-kit'
182
+ script:Copy-RealKit -Destination $script:Kit3
183
+ $script:SetupInKit3 = Join-Path $script:Kit3 'setup-pola-b.ps1'
184
+ # Path yang DIJAMIN tidak ada
185
+ $script:BadRoot = Join-Path $script:TempCanonical ("lintasAI-DOES-NOT-EXIST-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
186
+ }
187
+
188
+ AfterAll {
189
+ script:Remove-TestRoot -Path $script:Root3
190
+ script:Remove-TestRoot -Path $script:BadRoot # defensive (script harusnya tidak create)
191
+ }
192
+
193
+ It "Errors out (non-zero exit) with clear message" {
194
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit3 -Args @(
195
+ '-Force', '-SkipTeamFiles', '-ProjectRoot', $script:BadRoot
196
+ )
197
+ $result.ExitCode | Should -Not -Be 0
198
+ # Pesan harus include "Resolve-Path" / "not exist" / nama path yang tidak valid -
199
+ # apa pun yang membuktikan ini BUKAN silent fail.
200
+ # Resolve-Path throws "Cannot find path '...' because it does not exist."
201
+ $result.Output | Should -Match '(?i)(cannot find path|does not exist|not exist|tidak ditemukan|tidak ada)'
202
+ }
203
+
204
+ It "Does NOT create .claude-kit at the bogus path" {
205
+ # Script tidak boleh men-create folder di path yang tidak ada (defensive guard).
206
+ (Test-Path -LiteralPath $script:BadRoot) | Should -BeFalse
207
+ }
208
+ }
209
+
210
+ Describe "setup-pola-b.ps1 Branch 4: -ProjectRoot + kit at npm-cache-like path" {
211
+ BeforeAll {
212
+ # Layout simulasi npx:
213
+ # <tmp>\nodemod-test\@ojokesusu\lintasai\setup-pola-b.ps1 (kit di npm cache)
214
+ # <tmp>\<project-root>\ (target project, beda parent)
215
+ # Script harus detect mismatch ($KitDir tidak dalam $ProjectRoot/.claude-kit/)
216
+ # dan COPY isi kit ke $ProjectRoot/.claude-kit/.
217
+ $script:Root4 = Join-Path $script:TempCanonical ("lintasAI-setup-test-npx-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
218
+ $null = New-Item -ItemType Directory -Path $script:Root4 -Force
219
+ script:Add-ProjectContent -Root $script:Root4
220
+
221
+ # Kit DIPASANG di npm-cache-like location (BUKAN di $script:Root4/.claude-kit)
222
+ $script:NpmCacheRoot = Join-Path $script:TempCanonical ("nodemod-test-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
223
+ $script:KitInNpmCache = Join-Path $script:NpmCacheRoot '@ojokesusu\lintasai'
224
+ $null = New-Item -ItemType Directory -Path $script:KitInNpmCache -Force
225
+ script:Copy-RealKit -Destination $script:KitInNpmCache
226
+ $script:SetupInNpmCache = Join-Path $script:KitInNpmCache 'setup-pola-b.ps1'
227
+ }
228
+
229
+ AfterAll {
230
+ script:Remove-TestRoot -Path $script:Root4
231
+ script:Remove-TestRoot -Path $script:NpmCacheRoot
232
+ }
233
+
234
+ It "Detects npx mode and copies kit to <ProjectRoot>/.claude-kit/" {
235
+ # Pre-assert: target .claude-kit tidak ada sebelum jalan.
236
+ $targetKit = Join-Path $script:Root4 '.claude-kit'
237
+ (Test-Path -LiteralPath $targetKit) | Should -BeFalse
238
+
239
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInNpmCache -Args @(
240
+ '-Force', '-SkipTeamFiles', '-ProjectRoot', $script:Root4
241
+ )
242
+ $result.ExitCode | Should -Be 0
243
+ # Verifikasi log npx mode + copy
244
+ $result.Output | Should -Match '\[npx\] Mode: explicit ProjectRoot'
245
+ $result.Output | Should -Match '(?i)\[npx\] Copy kit'
246
+
247
+ # Hasil: .claude-kit di project root harus ada, berisi setup-pola-b.ps1 yang ke-copy
248
+ (Test-Path -LiteralPath $targetKit) | Should -BeTrue
249
+ (Test-Path -LiteralPath (Join-Path $targetKit 'setup-pola-b.ps1')) | Should -BeTrue
250
+ (Test-Path -LiteralPath (Join-Path $targetKit 'CHANGELOG.md')) | Should -BeTrue
251
+ (Test-Path -LiteralPath (Join-Path $targetKit 'lib\manifest.ps1')) | Should -BeTrue
252
+ }
253
+ }
254
+
255
+ Describe "setup-pola-b.ps1 manifest creation" {
256
+ BeforeAll {
257
+ # Reuse pattern Branch 1 (traditional) – setelah setup sukses, manifest harus
258
+ # terbuat di .claude-kit/.install-manifest.json. Pakai fresh root supaya hash
259
+ # & state-nya independent dari describe lain.
260
+ $script:Root5 = Join-Path $script:TempCanonical ("lintasAI-setup-test-manifest-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
261
+ $null = New-Item -ItemType Directory -Path $script:Root5 -Force
262
+ script:Add-ProjectContent -Root $script:Root5
263
+ $script:Kit5 = Join-Path $script:Root5 '.claude-kit'
264
+ script:Copy-RealKit -Destination $script:Kit5
265
+ $script:SetupInKit5 = Join-Path $script:Kit5 'setup-pola-b.ps1'
266
+ }
267
+
268
+ AfterAll {
269
+ script:Remove-TestRoot -Path $script:Root5
270
+ }
271
+
272
+ It "Creates .install-manifest.json after successful setup" {
273
+ $result = script:Invoke-Setup -ScriptPath $script:SetupInKit5 -Args @('-Force', '-SkipTeamFiles')
274
+ $result.ExitCode | Should -Be 0
275
+ $manifestPath = Join-Path $script:Kit5 '.install-manifest.json'
276
+ (Test-Path -LiteralPath $manifestPath) | Should -BeTrue
277
+ }
278
+
279
+ It "Manifest is valid JSON with expected top-level fields" {
280
+ $manifestPath = Join-Path $script:Kit5 '.install-manifest.json'
281
+ (Test-Path -LiteralPath $manifestPath) | Should -BeTrue
282
+ $json = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8
283
+ { $json | ConvertFrom-Json -ErrorAction Stop } | Should -Not -Throw
284
+ $obj = $json | ConvertFrom-Json
285
+ $obj.PSObject.Properties.Name | Should -Contain 'schema_version'
286
+ $obj.PSObject.Properties.Name | Should -Contain 'files'
287
+ }
288
+ }