@ojokesusu/lintasai 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.github/workflows/publish-npm.yml +40 -0
  2. package/.github/workflows/validate.yml +93 -0
  3. package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
  4. package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
  5. package/CHANGELOG.md +313 -0
  6. package/CLAUDE_universal_v1.md +1021 -0
  7. package/CONTRIBUTING.md +101 -0
  8. package/FIRST_SESSION_PROMPT_v1.md +7 -0
  9. package/JALANKAN_KIT.md +188 -0
  10. package/LICENSE +21 -0
  11. package/MULAI_DI_SINI.md +145 -0
  12. package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
  13. package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
  14. package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
  15. package/README.md +505 -0
  16. package/SETUP_POLA_B_PROMPT_v1.md +5 -0
  17. package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
  18. package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
  19. package/UPDATE_DOCS_PROMPT_v1.md +3 -0
  20. package/UPDATE_KIT_PROMPT_v1.md +213 -0
  21. package/bin/lintasai.js +81 -0
  22. package/docs/SIGNED_RELEASE.md +162 -0
  23. package/install-windows.ps1 +225 -0
  24. package/kit.ps1 +508 -0
  25. package/lib/agents-md.ps1 +174 -0
  26. package/lib/git-helpers.ps1 +104 -0
  27. package/lib/kit-files.psd1 +133 -0
  28. package/lib/manifest-signing.ps1 +65 -0
  29. package/lib/manifest.ps1 +267 -0
  30. package/lib/rollback.ps1 +241 -0
  31. package/lib/safety.ps1 +193 -0
  32. package/lib/template-deploy.ps1 +242 -0
  33. package/lib/version-detect.ps1 +161 -0
  34. package/package.json +36 -0
  35. package/setup-pola-b.ps1 +687 -0
  36. package/templates/ANALOGI_LIBRARY.md +7 -0
  37. package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
  38. package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
  39. package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
  40. package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
  41. package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
  42. package/templates/INDEX.md +157 -0
  43. package/templates/MCP_SETUP.md +1145 -0
  44. package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
  45. package/templates/ONBOARDING.md +172 -0
  46. package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
  47. package/templates/PROMPT_LIBRARY.md +790 -0
  48. package/templates/RLS_SETUP_PROMPT.md +167 -0
  49. package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
  50. package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
  51. package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
  52. package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
  53. package/templates/STACK_DETECTION_PATTERN.md +261 -0
  54. package/templates/STACK_GUIDE.md +564 -0
  55. package/templates/STACK_MIGRATION_GUIDE.md +154 -0
  56. package/templates/STACK_VERSIONS.md +31 -0
  57. package/templates/UPDATE_GUIDE.md +246 -0
  58. package/templates/_EXAMPLE.md +110 -0
  59. package/templates/_PATTERNS.md +173 -0
  60. package/templates/architecture.md +180 -0
  61. package/templates/architecture_auto.md +61 -0
  62. package/templates/decisions/README.md +108 -0
  63. package/templates/decisions/_TEMPLATE.md +84 -0
  64. package/templates/feature-flags-advanced.md +171 -0
  65. package/templates/github/CODEOWNERS.template +61 -0
  66. package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
  67. package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
  68. package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
  69. package/templates/github/RENOVATE_FRONTEND.json +28 -0
  70. package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
  71. package/templates/github/pull_request_template.md +44 -0
  72. package/templates/github/scripts/ai-review.js +153 -0
  73. package/templates/github/workflows/ai-review.yml +61 -0
  74. package/templates/github/workflows/backup-schemas.yml +169 -0
  75. package/templates/glossary.md +110 -0
  76. package/templates/split-agents/BACKEND.md +149 -0
  77. package/templates/split-agents/FRONTEND.md +141 -0
  78. package/templates/split-agents/SHARED.md +82 -0
  79. package/templates/split-agents/TOOLS.md +77 -0
  80. package/tests/Run-Tests.ps1 +19 -0
  81. package/tests/lib-safety.Tests.ps1 +66 -0
  82. package/tests/rollback.Tests.ps1 +66 -0
  83. package/tests/uninstall.Tests.ps1 +265 -0
  84. package/tests/update-kit.Tests.ps1 +78 -0
  85. package/uninstall.ps1 +794 -0
  86. package/update-kit.ps1 +907 -0
@@ -0,0 +1,265 @@
1
+ #Requires -Module Pester
2
+
3
+ <#
4
+ .SYNOPSIS
5
+ Pester 5+ tests untuk uninstall.ps1 (manifest-based safe uninstall).
6
+
7
+ .DESCRIPTION
8
+ Tiga area uji:
9
+ 1) DryRun mode - fresh install harus exit 0, classification PRISTINE/MODIFIED/MISSING
10
+ terlihat, AGENTS.md di-skip secara default.
11
+ 2) Manifest gone - project tanpa manifest harus warn / abort dengan exit non-zero,
12
+ TIDAK menghapus apa pun.
13
+ 3) Param surface - -AllowModified harus exist sebagai parameter (AST static check).
14
+
15
+ Strategi:
16
+ - Build fake project root di $env:TEMP dengan struktur:
17
+ <fakeRoot>\
18
+ AGENTS.md (akan masuk manifest, default skipped uninstall)
19
+ docs\README.md (PRISTINE - hash match)
20
+ docs\edited.md (MODIFIED - hash mismatch)
21
+ docs\gone.md (MISSING - di-list manifest tapi sudah dihapus)
22
+ .claude-kit\
23
+ uninstall.ps1 (copy dari kit asli)
24
+ lib\safety.ps1, manifest-signing.ps1 (dependencies)
25
+ .install-manifest.json (di-craft + HMAC-signed di BeforeAll)
26
+
27
+ - Jalankan uninstall.ps1 -DryRun -Yes dari fake project, capture stdout + exit code.
28
+ - Assert keyword di output + exit code.
29
+
30
+ Tests TIDAK menjalankan delete sungguhan (selalu -DryRun) untuk safety.
31
+ #>
32
+
33
+ BeforeAll {
34
+ # ---- Resolve repo root (kit folder yang berisi uninstall.ps1) ----
35
+ $script:KitRepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..') | Select-Object -ExpandProperty Path
36
+ $script:UninstallScript = Join-Path $script:KitRepoRoot 'uninstall.ps1'
37
+ $script:SigningLib = Join-Path $script:KitRepoRoot 'lib\manifest-signing.ps1'
38
+ $script:SafetyLib = Join-Path $script:KitRepoRoot 'lib\safety.ps1'
39
+
40
+ if (-not (Test-Path $script:UninstallScript)) {
41
+ throw "uninstall.ps1 not found at $script:UninstallScript - tests assume layout tests/ sibling to uninstall.ps1"
42
+ }
43
+
44
+ # ---- Helper: build fake project (root + .claude-kit + files) ----
45
+ function script:New-FakeProject {
46
+ param(
47
+ [Parameter(Mandatory)][string]$Root,
48
+ [switch]$WithManifest = $true,
49
+ [switch]$IncludeAgents = $true,
50
+ [switch]$IncludeMissingEntry,
51
+ [switch]$IncludeModifiedEntry,
52
+ [string]$KitVersion = '1.0.0'
53
+ )
54
+
55
+ # Bersihkan kalau ada sisa run sebelumnya
56
+ if (Test-Path -LiteralPath $Root) {
57
+ Remove-Item -Recurse -Force -LiteralPath $Root -ErrorAction SilentlyContinue
58
+ }
59
+ $null = New-Item -ItemType Directory -Path $Root -Force
60
+ $kitDir = Join-Path $Root '.claude-kit'
61
+ $kitLib = Join-Path $kitDir 'lib'
62
+ $null = New-Item -ItemType Directory -Path $kitDir -Force
63
+ $null = New-Item -ItemType Directory -Path $kitLib -Force
64
+
65
+ # Copy script + lib supaya dot-source di uninstall.ps1 bisa nemu file-nya
66
+ Copy-Item -LiteralPath $script:UninstallScript -Destination (Join-Path $kitDir 'uninstall.ps1') -Force
67
+ Copy-Item -LiteralPath $script:SafetyLib -Destination (Join-Path $kitLib 'safety.ps1') -Force
68
+ Copy-Item -LiteralPath $script:SigningLib -Destination (Join-Path $kitLib 'manifest-signing.ps1') -Force
69
+
70
+ # ---- Tulis file project yang akan ditrack manifest ----
71
+ $docsDir = Join-Path $Root 'docs'
72
+ $null = New-Item -ItemType Directory -Path $docsDir -Force
73
+
74
+ # PRISTINE candidate
75
+ $pristineRel = 'docs/README.md'
76
+ $pristineFull = Join-Path $Root 'docs\README.md'
77
+ $pristineContent = "# Pristine test`nhash should match`n"
78
+ [System.IO.File]::WriteAllText($pristineFull, $pristineContent, (New-Object System.Text.UTF8Encoding $false))
79
+ $pristineHash = (Get-FileHash -LiteralPath $pristineFull -Algorithm SHA256).Hash
80
+
81
+ $files = @()
82
+ $files += [ordered]@{ path = $pristineRel; kind = 'file'; sha256 = $pristineHash }
83
+
84
+ # AGENTS.md (default skipped during uninstall)
85
+ if ($IncludeAgents) {
86
+ $agentsFull = Join-Path $Root 'AGENTS.md'
87
+ $agentsContent = "# AGENTS`n"
88
+ [System.IO.File]::WriteAllText($agentsFull, $agentsContent, (New-Object System.Text.UTF8Encoding $false))
89
+ $agentsHash = (Get-FileHash -LiteralPath $agentsFull -Algorithm SHA256).Hash
90
+ $files += [ordered]@{ path = 'AGENTS.md'; kind = 'file'; sha256 = $agentsHash }
91
+ }
92
+
93
+ # MODIFIED candidate (hash mismatch — file di-edit setelah "install")
94
+ if ($IncludeModifiedEntry) {
95
+ $modFull = Join-Path $Root 'docs\edited.md'
96
+ [System.IO.File]::WriteAllText($modFull, "ORIGINAL", (New-Object System.Text.UTF8Encoding $false))
97
+ $origHash = (Get-FileHash -LiteralPath $modFull -Algorithm SHA256).Hash
98
+ # Sekarang ubah isi -> hash on disk beda dari yang tercatat di manifest
99
+ [System.IO.File]::WriteAllText($modFull, "EDITED BY USER", (New-Object System.Text.UTF8Encoding $false))
100
+ $files += [ordered]@{ path = 'docs/edited.md'; kind = 'file'; sha256 = $origHash }
101
+ }
102
+
103
+ # MISSING candidate (entry ada di manifest tapi file tidak pernah di-create di disk)
104
+ if ($IncludeMissingEntry) {
105
+ $files += [ordered]@{
106
+ path = 'docs/gone.md'
107
+ kind = 'file'
108
+ sha256 = 'a' * 64
109
+ }
110
+ }
111
+
112
+ $dirsTracked = @('docs')
113
+
114
+ if ($WithManifest) {
115
+ $manifest = [ordered]@{
116
+ schema_version = 1
117
+ kit_version = $KitVersion
118
+ installed_at = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ss')
119
+ installed_by = '<USER>'
120
+ project_name = (Split-Path -Leaf $Root)
121
+ project_root = '<PROJECT_ROOT>' # uninstall.ps1 treats this as "match anything"
122
+ metadata = [ordered]@{
123
+ kit_version = $KitVersion
124
+ installed_at = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
125
+ installer = 'setup-pola-b.ps1'
126
+ }
127
+ files = $files
128
+ directories_created = $dirsTracked
129
+ }
130
+
131
+ # HMAC sign manifest (dot-source signing lib sekali di scope helper)
132
+ . $script:SigningLib
133
+ try {
134
+ $sig = New-ManifestSignature -Manifest $manifest -KitVersion $KitVersion
135
+ $manifest.metadata.signature = $sig
136
+ } catch {
137
+ # Kalau signing gagal, tetap tulis (uninstall.ps1 akan treat sebagai unsigned,
138
+ # tests pakai -Yes untuk bypass prompt unsigned).
139
+ }
140
+
141
+ $json = $manifest | ConvertTo-Json -Depth 10
142
+ $manifestPath = Join-Path $kitDir '.install-manifest.json'
143
+ [System.IO.File]::WriteAllText($manifestPath, $json, (New-Object System.Text.UTF8Encoding $false))
144
+ }
145
+
146
+ return [pscustomobject]@{
147
+ Root = $Root
148
+ KitDir = $kitDir
149
+ Uninstall = (Join-Path $kitDir 'uninstall.ps1')
150
+ Manifest = (Join-Path $kitDir '.install-manifest.json')
151
+ }
152
+ }
153
+
154
+ # ---- Helper: run uninstall.ps1 in fresh powershell, capture stdout + exit ----
155
+ function script:Invoke-Uninstall {
156
+ param(
157
+ [Parameter(Mandatory)][string]$ScriptPath,
158
+ [string[]]$Args = @()
159
+ )
160
+ $pwshExe = (Get-Process -Id $PID).Path
161
+ if (-not $pwshExe) { $pwshExe = 'powershell.exe' }
162
+ $allArgs = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $ScriptPath) + $Args
163
+ # Capture stdout+stderr ke string, jangan throw walau exit != 0
164
+ $output = & $pwshExe @allArgs 2>&1 | Out-String
165
+ return [pscustomobject]@{
166
+ Output = $output
167
+ ExitCode = $LASTEXITCODE
168
+ }
169
+ }
170
+ }
171
+
172
+ Describe "uninstall.ps1 DryRun mode" {
173
+ BeforeAll {
174
+ $script:FakeRoot1 = Join-Path $env:TEMP ("lintasAI-uninstall-test-dryrun-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
175
+ $script:Fake1 = script:New-FakeProject -Root $script:FakeRoot1 -WithManifest -IncludeAgents `
176
+ -IncludeMissingEntry -IncludeModifiedEntry
177
+ }
178
+
179
+ AfterAll {
180
+ if (Test-Path -LiteralPath $script:FakeRoot1) {
181
+ Remove-Item -Recurse -Force -LiteralPath $script:FakeRoot1 -ErrorAction SilentlyContinue
182
+ }
183
+ }
184
+
185
+ It "Exit 0 untuk fresh install" {
186
+ $result = script:Invoke-Uninstall -ScriptPath $script:Fake1.Uninstall -Args @('-DryRun', '-Yes')
187
+ $result.ExitCode | Should -Be 0
188
+ }
189
+
190
+ It "Classification per file shown" {
191
+ $result = script:Invoke-Uninstall -ScriptPath $script:Fake1.Uninstall -Args @('-DryRun', '-Yes')
192
+ # Output harus berisi setidaknya satu dari label classification.
193
+ # Test PRISTINE (file hash-match) + MODIFIED (kita craft) + MISSING (entry tanpa file).
194
+ $result.Output | Should -Match '\[PRISTINE\]'
195
+ $result.Output | Should -Match '\[MODIFIED\]'
196
+ $result.Output | Should -Match '\[MISSING\]'
197
+ }
198
+
199
+ It "AGENTS.md skipped by default" {
200
+ $result = script:Invoke-Uninstall -ScriptPath $script:Fake1.Uninstall -Args @('-DryRun', '-Yes')
201
+ # uninstall.ps1 string: "AGENTS.md skipped (pakai -DeleteAgents untuk override)"
202
+ # Bisa muncul di section [SKIPPED] atau di REASSURANCE FIRST block.
203
+ $result.Output | Should -Match 'AGENTS\.md'
204
+ $result.Output | Should -Match '(?i)(skip|skipped|TETAP ADA|DeleteAgents)'
205
+ }
206
+ }
207
+
208
+ Describe "uninstall.ps1 manifest verification" {
209
+ BeforeAll {
210
+ $script:FakeRoot2 = Join-Path $env:TEMP ("lintasAI-uninstall-test-nomanifest-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
211
+ # Build project structure TANPA manifest -- uninstall harus abort.
212
+ $script:Fake2 = script:New-FakeProject -Root $script:FakeRoot2 -WithManifest:$false `
213
+ -IncludeAgents
214
+ }
215
+
216
+ AfterAll {
217
+ if (Test-Path -LiteralPath $script:FakeRoot2) {
218
+ Remove-Item -Recurse -Force -LiteralPath $script:FakeRoot2 -ErrorAction SilentlyContinue
219
+ }
220
+ }
221
+
222
+ It "Should handle no-manifest gracefully" {
223
+ # uninstall.ps1 dengan manifest gone: print "STOP: Tidak bisa lanjut karena file pencatat install hilang."
224
+ # lalu exit 1. Test: exit code non-zero + output mengandung STOP / hilang / manifest.
225
+ $result = script:Invoke-Uninstall -ScriptPath $script:Fake2.Uninstall -Args @('-DryRun', '-Yes')
226
+ $result.ExitCode | Should -Not -Be 0
227
+ $result.Output | Should -Match '(?i)(STOP|manifest|catatan|hilang|abort)'
228
+ }
229
+ }
230
+
231
+ Describe "uninstall.ps1 -AllowModified flag" {
232
+ It "Should accept -AllowModified parameter" {
233
+ # Verifikasi via AST: parse script + cari ParameterAst dengan nama 'AllowModified'.
234
+ # Tidak panggil script (cukup static check) supaya test cepat & deterministic.
235
+ $tokens = $null
236
+ $errors = $null
237
+ $ast = [System.Management.Automation.Language.Parser]::ParseFile(
238
+ $script:UninstallScript, [ref]$tokens, [ref]$errors
239
+ )
240
+ $errors | Should -BeNullOrEmpty -Because 'uninstall.ps1 must parse cleanly'
241
+
242
+ $params = $ast.FindAll({
243
+ param($node)
244
+ $node -is [System.Management.Automation.Language.ParameterAst]
245
+ }, $true)
246
+ $paramNames = $params | ForEach-Object { $_.Name.VariablePath.UserPath }
247
+ $paramNames | Should -Contain 'AllowModified'
248
+ }
249
+
250
+ It "Should not break -DryRun when -AllowModified is set" {
251
+ # Sanity: jalankan dengan -AllowModified -DryRun, exit 0 (preview saja).
252
+ $rootMod = Join-Path $env:TEMP ("lintasAI-uninstall-test-allowmod-{0}" -f ([guid]::NewGuid().ToString('N').Substring(0,8)))
253
+ try {
254
+ $fakeMod = script:New-FakeProject -Root $rootMod -WithManifest -IncludeAgents -IncludeModifiedEntry
255
+ $result = script:Invoke-Uninstall -ScriptPath $fakeMod.Uninstall `
256
+ -Args @('-DryRun', '-Yes', '-AllowModified')
257
+ $result.ExitCode | Should -Be 0
258
+ $result.Output | Should -Match '(?i)AllowModified'
259
+ } finally {
260
+ if (Test-Path -LiteralPath $rootMod) {
261
+ Remove-Item -Recurse -Force -LiteralPath $rootMod -ErrorAction SilentlyContinue
262
+ }
263
+ }
264
+ }
265
+ }
@@ -0,0 +1,78 @@
1
+ #Requires -Module Pester
2
+
3
+ BeforeAll {
4
+ $script:scriptPath = Join-Path $PSScriptRoot '..\update-kit.ps1'
5
+ if (-not (Test-Path $script:scriptPath)) {
6
+ throw "update-kit.ps1 tidak ditemukan di $script:scriptPath"
7
+ }
8
+
9
+ # Baca raw content sekali untuk pattern checks.
10
+ $script:scriptContent = Get-Content -Path $script:scriptPath -Raw -Encoding UTF8
11
+
12
+ # Parse AST sekali untuk param-block introspection.
13
+ $tokens = $null
14
+ $errors = $null
15
+ $script:scriptAst = [System.Management.Automation.Language.Parser]::ParseFile(
16
+ $script:scriptPath, [ref]$tokens, [ref]$errors
17
+ )
18
+
19
+ # Ambil top-level param block (kalau ada).
20
+ $script:paramBlock = $script:scriptAst.ParamBlock
21
+ if (-not $script:paramBlock) {
22
+ # Fallback: cari ParamBlockAst di seluruh AST (script bisa pakai function-level param).
23
+ $script:paramBlock = $script:scriptAst.Find({
24
+ param($node) $node -is [System.Management.Automation.Language.ParamBlockAst]
25
+ }, $true)
26
+ }
27
+
28
+ # Helper: cek param name ada di paramBlock.
29
+ function script:Test-ParamExists {
30
+ param([string]$Name)
31
+ if (-not $script:paramBlock) { return $false }
32
+ foreach ($p in $script:paramBlock.Parameters) {
33
+ if ($p.Name.VariablePath.UserPath -ieq $Name) { return $true }
34
+ }
35
+ return $false
36
+ }
37
+ }
38
+
39
+ Describe "update-kit.ps1 parameter validation" {
40
+ It "Should have -AllowUnsignedTag parameter (via AST)" {
41
+ Test-ParamExists -Name 'AllowUnsignedTag' | Should -BeTrue
42
+ }
43
+
44
+ It "Should have -AllowUntrustedRepo parameter (via AST)" {
45
+ Test-ParamExists -Name 'AllowUntrustedRepo' | Should -BeTrue
46
+ }
47
+ }
48
+
49
+ Describe "GPG verify-tag logic" {
50
+ It "Should reference 'describe --exact-match' atau 'ls-remote.*tags' untuk resolve tag name" {
51
+ $hasDescribe = $script:scriptContent -match 'describe --exact-match'
52
+ $hasLsRemote = $script:scriptContent -match 'ls-remote.*tags'
53
+ ($hasDescribe -or $hasLsRemote) | Should -BeTrue
54
+ }
55
+
56
+ It "Should call 'verify-tag' (bukan verify-commit)" {
57
+ ($script:scriptContent -match 'verify-tag') | Should -BeTrue
58
+ }
59
+
60
+ It "Should NOT use 'verify-commit' (separation: tag signing, bukan commit signing)" {
61
+ ($script:scriptContent -match 'verify-commit') | Should -BeFalse
62
+ }
63
+ }
64
+
65
+ Describe "Version detection" {
66
+ It "Should define Get-LatestChangelogEntry function" {
67
+ $fnAst = $script:scriptAst.Find({
68
+ param($node)
69
+ $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
70
+ $node.Name -ieq 'Get-LatestChangelogEntry'
71
+ }, $true)
72
+ $fnAst | Should -Not -BeNullOrEmpty
73
+ }
74
+
75
+ It "Should have version comparison logic (Sudah versi terbaru / sama / equal)" {
76
+ ($script:scriptContent -match 'Sudah versi terbaru|sama|equal') | Should -BeTrue
77
+ }
78
+ }