@ojokesusu/lintasai 1.1.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ojokesusu/lintasai",
3
- "version": "1.1.2",
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"
@@ -8,12 +8,18 @@
8
8
  "files": [
9
9
  "bin/",
10
10
  "*.ps1",
11
+ "*.psm1",
11
12
  "lib/",
12
13
  "templates/",
13
14
  "docs/",
14
15
  "tests/",
15
16
  "*.md",
16
- ".github/"
17
+ "*.template",
18
+ "AGENTS.md.template",
19
+ ".github/",
20
+ "decisions/",
21
+ "LICENSE",
22
+ ".gitignore"
17
23
  ],
18
24
  "scripts": {
19
25
  "test": "echo \"Pester tests run via ./tests/Run-Tests.ps1\" && exit 0"
package/setup-pola-b.ps1 CHANGED
@@ -38,7 +38,8 @@
38
38
  param(
39
39
  [switch]$Force,
40
40
  [switch]$DryRun,
41
- [switch]$SkipTeamFiles
41
+ [switch]$SkipTeamFiles,
42
+ [string]$ProjectRoot = $null
42
43
  )
43
44
 
44
45
  $ErrorActionPreference = 'Stop'
@@ -46,6 +47,50 @@ $ErrorActionPreference = 'Stop'
46
47
  # ---- Resolve folder kit (tempat script ini berada) ----
47
48
  $KitDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { (Get-Location).Path }
48
49
 
50
+ # ---- Resolve ProjectRoot (npm wrapper vs traditional invocation) ----
51
+ # Traditional: kit lives at <project>/.claude-kit/, $ProjectRoot = parent of $KitDir.
52
+ # NPX wrapper: $ProjectRoot passed explicitly; kit source at $KitDir (npm cache) must
53
+ # be COPIED to $ProjectRoot/.claude-kit/ before setup continues.
54
+ $script:__lintasAI_NpxMode = $false
55
+ if (-not $ProjectRoot) {
56
+ # Traditional invocation - kit lives at <project>/.claude-kit/, parent is project
57
+ $ProjectRoot = Split-Path -Parent $KitDir
58
+ } else {
59
+ # NPX wrapper invocation - $ProjectRoot passed explicitly
60
+ Write-Host "[npx] Mode: explicit ProjectRoot = $ProjectRoot" -ForegroundColor Cyan
61
+ $script:__lintasAI_NpxMode = $true
62
+ }
63
+ $ProjectRoot = (Resolve-Path $ProjectRoot -ErrorAction Stop).Path
64
+
65
+ # ---- NPX mode: copy kit from $KitDir (npm cache) to $ProjectRoot/.claude-kit/ ----
66
+ # Detection: if $KitDir is NOT already inside $ProjectRoot/.claude-kit/, we need to copy.
67
+ $expectedKitDir = Join-Path $ProjectRoot '.claude-kit'
68
+ $kitDirResolved = (Resolve-Path $KitDir -ErrorAction Stop).Path
69
+ $expectedKitDirNormalized = $expectedKitDir.TrimEnd('\','/')
70
+ $kitDirNormalized = $kitDirResolved.TrimEnd('\','/')
71
+ if ($script:__lintasAI_NpxMode -and ($kitDirNormalized -ne $expectedKitDirNormalized)) {
72
+ Write-Host "[npx] Copy kit: $KitDir -> $expectedKitDir" -ForegroundColor Cyan
73
+ if (-not $DryRun) {
74
+ if (-not (Test-Path $expectedKitDir)) {
75
+ New-Item -ItemType Directory -Path $expectedKitDir -Force | Out-Null
76
+ }
77
+ # Robocopy or Copy-Item: use Copy-Item for cross-platform-ish behavior
78
+ Get-ChildItem -Path $KitDir -Force | ForEach-Object {
79
+ $destPath = Join-Path $expectedKitDir $_.Name
80
+ if ($_.PSIsContainer) {
81
+ Copy-Item -Path $_.FullName -Destination $destPath -Recurse -Force
82
+ } else {
83
+ Copy-Item -Path $_.FullName -Destination $destPath -Force
84
+ }
85
+ }
86
+ Write-Host "[npx] Kit copied successfully." -ForegroundColor Green
87
+ } else {
88
+ Write-Host "[DRY] [npx] Would copy kit contents from $KitDir to $expectedKitDir" -ForegroundColor Yellow
89
+ }
90
+ # Pivot $KitDir to the deployed location so rest of script operates on project copy
91
+ $KitDir = $expectedKitDir
92
+ }
93
+
49
94
  # ---- Dot-source library helpers (heavy-lifting) ----
50
95
  # Order penting: agents-md.ps1 dot-source SEBELUM manifest.ps1.
51
96
  # agents-md.ps1 punya guard `if ($script:__lintasAI_AgentsMdLoaded)` (anti-redefine).
@@ -130,7 +175,11 @@ if ($kitFolderName -ne '.claude-kit') {
130
175
  }
131
176
 
132
177
  # ---- Resolve root proyek (parent dari folder kit) ----
133
- $ProjectRoot = Split-Path -Parent $KitDir
178
+ # NOTE: $ProjectRoot sudah di-resolve di atas (param eksplisit atau Split-Path -Parent $KitDir).
179
+ # Re-derive di sini HANYA kalau belum di-set (defensif). Skip kalau npx mode (sudah eksplisit).
180
+ if (-not $ProjectRoot) {
181
+ $ProjectRoot = Split-Path -Parent $KitDir
182
+ }
134
183
  $ProjectName = Split-Path -Leaf $ProjectRoot
135
184
  $Today = Get-Date -Format 'yyyy-MM-dd'
136
185
  $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
@@ -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
+ }