@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.
- package/.github/workflows/publish-npm.yml +40 -0
- package/.github/workflows/validate.yml +93 -0
- package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
- package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE_universal_v1.md +1021 -0
- package/CONTRIBUTING.md +101 -0
- package/FIRST_SESSION_PROMPT_v1.md +7 -0
- package/JALANKAN_KIT.md +188 -0
- package/LICENSE +21 -0
- package/MULAI_DI_SINI.md +145 -0
- package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
- package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
- package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
- package/README.md +505 -0
- package/SETUP_POLA_B_PROMPT_v1.md +5 -0
- package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
- package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
- package/UPDATE_DOCS_PROMPT_v1.md +3 -0
- package/UPDATE_KIT_PROMPT_v1.md +213 -0
- package/bin/lintasai.js +81 -0
- package/docs/SIGNED_RELEASE.md +162 -0
- package/install-windows.ps1 +225 -0
- package/kit.ps1 +508 -0
- package/lib/agents-md.ps1 +174 -0
- package/lib/git-helpers.ps1 +104 -0
- package/lib/kit-files.psd1 +133 -0
- package/lib/manifest-signing.ps1 +65 -0
- package/lib/manifest.ps1 +267 -0
- package/lib/rollback.ps1 +241 -0
- package/lib/safety.ps1 +193 -0
- package/lib/template-deploy.ps1 +242 -0
- package/lib/version-detect.ps1 +161 -0
- package/package.json +36 -0
- package/setup-pola-b.ps1 +687 -0
- package/templates/ANALOGI_LIBRARY.md +7 -0
- package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
- package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
- package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
- package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
- package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
- package/templates/INDEX.md +157 -0
- package/templates/MCP_SETUP.md +1145 -0
- package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
- package/templates/ONBOARDING.md +172 -0
- package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
- package/templates/PROMPT_LIBRARY.md +790 -0
- package/templates/RLS_SETUP_PROMPT.md +167 -0
- package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
- package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
- package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
- package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
- package/templates/STACK_DETECTION_PATTERN.md +261 -0
- package/templates/STACK_GUIDE.md +564 -0
- package/templates/STACK_MIGRATION_GUIDE.md +154 -0
- package/templates/STACK_VERSIONS.md +31 -0
- package/templates/UPDATE_GUIDE.md +246 -0
- package/templates/_EXAMPLE.md +110 -0
- package/templates/_PATTERNS.md +173 -0
- package/templates/architecture.md +180 -0
- package/templates/architecture_auto.md +61 -0
- package/templates/decisions/README.md +108 -0
- package/templates/decisions/_TEMPLATE.md +84 -0
- package/templates/feature-flags-advanced.md +171 -0
- package/templates/github/CODEOWNERS.template +61 -0
- package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
- package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
- package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
- package/templates/github/RENOVATE_FRONTEND.json +28 -0
- package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
- package/templates/github/pull_request_template.md +44 -0
- package/templates/github/scripts/ai-review.js +153 -0
- package/templates/github/workflows/ai-review.yml +61 -0
- package/templates/github/workflows/backup-schemas.yml +169 -0
- package/templates/glossary.md +110 -0
- package/templates/split-agents/BACKEND.md +149 -0
- package/templates/split-agents/FRONTEND.md +141 -0
- package/templates/split-agents/SHARED.md +82 -0
- package/templates/split-agents/TOOLS.md +77 -0
- package/tests/Run-Tests.ps1 +19 -0
- package/tests/lib-safety.Tests.ps1 +66 -0
- package/tests/rollback.Tests.ps1 +66 -0
- package/tests/uninstall.Tests.ps1 +265 -0
- package/tests/update-kit.Tests.ps1 +78 -0
- package/uninstall.ps1 +794 -0
- package/update-kit.ps1 +907 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
lib/git-helpers.ps1 - Git metadata cleanup + Mark-of-the-Web unblock helpers
|
|
4
|
+
untuk lintasAI kit scripts.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Modul ini berisi dua helper yang sebelumnya tertanam inline di setup-pola-b.ps1
|
|
8
|
+
(dan kemungkinan di-share dengan update-kit.ps1 / install-windows.ps1):
|
|
9
|
+
|
|
10
|
+
- Remove-GitMetadata : Hapus folder .git/ di dalam kit directory.
|
|
11
|
+
Penting karena kalau kit di-CLONE (bukan zip-extract),
|
|
12
|
+
folder .git/ ikut ke project user. Itu menyebabkan:
|
|
13
|
+
* Bloat (history kit-dev, potensial ratusan MB)
|
|
14
|
+
* Expose history internal kit-dev ke user repo
|
|
15
|
+
* User accidentally bikin `git submodule` /
|
|
16
|
+
confuse `git log`
|
|
17
|
+
Idempotent: skip kalau folder .git/ tidak ada.
|
|
18
|
+
|
|
19
|
+
- Remove-MotwBlock : Unblock-File recursive untuk semua file di kit.
|
|
20
|
+
Mark-of-the-Web (MOTW) = NTFS Alternate Data Stream
|
|
21
|
+
Zone.Identifier yang di-set Windows kalau file
|
|
22
|
+
di-download dari internet (zip dari GitHub, dst.).
|
|
23
|
+
File MOTW-blocked = script .ps1 di-reject oleh
|
|
24
|
+
ExecutionPolicy walaupun signed. Unblock = strip
|
|
25
|
+
Zone.Identifier ADS supaya kit bisa di-eksekusi.
|
|
26
|
+
|
|
27
|
+
Behavior umum:
|
|
28
|
+
- Best-effort: gagal hapus / gagal unblock TIDAK throw — return $false.
|
|
29
|
+
Caller bisa pilih lanjut atau abort. Konsisten dengan
|
|
30
|
+
pattern inline di setup-pola-b.ps1 (try/catch + warn).
|
|
31
|
+
- PowerShell 5.1 compatible: tidak pakai operator $?:, ??, ?., -AsHashtable,
|
|
32
|
+
atau syntax PS 7+ lainnya.
|
|
33
|
+
- Verbose logging via Write-Verbose (silent secara default). Caller bisa
|
|
34
|
+
enable dengan -Verbose untuk debugging.
|
|
35
|
+
|
|
36
|
+
.NOTES
|
|
37
|
+
Versi : 1.0.0
|
|
38
|
+
Tanggal: 2026-06-06
|
|
39
|
+
Extracted from: setup-pola-b.ps1 v1.9 (lines 173-200)
|
|
40
|
+
Compat: PowerShell 5.1+
|
|
41
|
+
#>
|
|
42
|
+
|
|
43
|
+
# ---- Remove-GitMetadata ----
|
|
44
|
+
# Hapus folder .git/ di -Path. Idempotent (skip kalau tidak ada).
|
|
45
|
+
# Return: $true kalau sukses ATAU folder .git/ tidak ada (no-op success).
|
|
46
|
+
# $false kalau gagal hapus (folder ada tapi Remove-Item throw).
|
|
47
|
+
function Remove-GitMetadata {
|
|
48
|
+
[CmdletBinding()]
|
|
49
|
+
param(
|
|
50
|
+
[Parameter(Mandatory = $true)]
|
|
51
|
+
[string]$Path
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
55
|
+
Write-Verbose ("Remove-GitMetadata: Path tidak ada, skip: {0}" -f $Path)
|
|
56
|
+
return $true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$gitDir = Join-Path $Path '.git'
|
|
60
|
+
if (-not (Test-Path -LiteralPath $gitDir)) {
|
|
61
|
+
Write-Verbose ("Remove-GitMetadata: .git/ tidak ada di {0}, no-op." -f $Path)
|
|
62
|
+
return $true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
Remove-Item -Recurse -Force -LiteralPath $gitDir -ErrorAction Stop
|
|
67
|
+
Write-Verbose ("Remove-GitMetadata: .git/ removed dari {0}" -f $Path)
|
|
68
|
+
return $true
|
|
69
|
+
} catch {
|
|
70
|
+
Write-Warning ("Remove-GitMetadata: gagal hapus {0}: {1}" -f $gitDir, $_)
|
|
71
|
+
Write-Warning (" Fix manual: Remove-Item -Recurse -Force '{0}'" -f $gitDir)
|
|
72
|
+
return $false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# ---- Remove-MotwBlock ----
|
|
77
|
+
# Recursive Unblock-File untuk semua file di -Path. Best-effort.
|
|
78
|
+
# Return: $true kalau semua file ter-unblock (atau tidak ada file sama sekali).
|
|
79
|
+
# $false kalau Unblock-File throw error fatal di top-level enumeration.
|
|
80
|
+
# Individual file fail = silent (Unblock-File -ErrorAction SilentlyContinue),
|
|
81
|
+
# konsisten dengan pattern inline di setup-pola-b.ps1.
|
|
82
|
+
function Remove-MotwBlock {
|
|
83
|
+
[CmdletBinding()]
|
|
84
|
+
param(
|
|
85
|
+
[Parameter(Mandatory = $true)]
|
|
86
|
+
[string]$Path
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
90
|
+
Write-Verbose ("Remove-MotwBlock: Path tidak ada, skip: {0}" -f $Path)
|
|
91
|
+
return $true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue |
|
|
96
|
+
Unblock-File -ErrorAction SilentlyContinue
|
|
97
|
+
Write-Verbose ("Remove-MotwBlock: MOTW di-unblock untuk semua file di {0}" -f $Path)
|
|
98
|
+
return $true
|
|
99
|
+
} catch {
|
|
100
|
+
Write-Warning ("Remove-MotwBlock: Unblock-File gagal di {0}: {1}" -f $Path, $_)
|
|
101
|
+
Write-Warning " Script tetap bisa lanjut, tapi mungkin perlu Unblock-File manual."
|
|
102
|
+
return $false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# kit-files.psd1 - SINGLE SOURCE OF TRUTH untuk file kit
|
|
2
|
+
# Update file ini WHEN add/rename/remove file kit
|
|
3
|
+
# Konsumen: setup-pola-b.ps1, kit.ps1 doctor, install-windows.ps1, uninstall.ps1
|
|
4
|
+
|
|
5
|
+
@{
|
|
6
|
+
schema_version = 1
|
|
7
|
+
|
|
8
|
+
# Tier 1: CORE prompts (selalu deploy, wajib ada)
|
|
9
|
+
core_prompts = @(
|
|
10
|
+
'MULAI_DI_SINI.md',
|
|
11
|
+
'JALANKAN_KIT.md',
|
|
12
|
+
'UPDATE_KIT_PROMPT_v1.md',
|
|
13
|
+
'AUDIT_POST_SETUP_PROMPT_v1.md',
|
|
14
|
+
'PROJECT_LIFECYCLE_PROMPT_v1.md',
|
|
15
|
+
'SPLIT_REPO_MIGRATION_PROMPT_v1.md'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Tier 2: Universal rules (auto-loaded AI tiap sesi)
|
|
19
|
+
universal_rules = @(
|
|
20
|
+
'CLAUDE_universal_v1.md',
|
|
21
|
+
'AGENTS.md.template'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Tier 3: PowerShell scripts
|
|
25
|
+
scripts = @(
|
|
26
|
+
'kit.ps1',
|
|
27
|
+
'setup-pola-b.ps1',
|
|
28
|
+
'update-kit.ps1',
|
|
29
|
+
'uninstall.ps1',
|
|
30
|
+
'install-windows.ps1'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Tier 4: Lib modules
|
|
34
|
+
lib_files = @(
|
|
35
|
+
'lib/rollback.ps1',
|
|
36
|
+
'lib/safety.ps1',
|
|
37
|
+
'lib/manifest-signing.ps1'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Tier 5: Templates (deployed to project saat setup)
|
|
41
|
+
templates = @(
|
|
42
|
+
'templates/PROMPT_LIBRARY.md',
|
|
43
|
+
'templates/ANALOGI_LIBRARY.md',
|
|
44
|
+
'templates/UPDATE_GUIDE.md',
|
|
45
|
+
'templates/glossary.md',
|
|
46
|
+
'templates/INDEX.md',
|
|
47
|
+
'templates/SPLIT_REPO_AGENTS_TEMPLATES.md',
|
|
48
|
+
'templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md',
|
|
49
|
+
'templates/SPLIT_REPO_TOOLS_SETUP.md',
|
|
50
|
+
'templates/DISCORD_BOT_INTEGRATION.md',
|
|
51
|
+
'templates/STACK_DETECTION_PATTERN.md',
|
|
52
|
+
'templates/PROJECT_STARTER_TEMPLATES.md',
|
|
53
|
+
'templates/split-agents/FRONTEND.md',
|
|
54
|
+
'templates/split-agents/BACKEND.md',
|
|
55
|
+
'templates/split-agents/SHARED.md',
|
|
56
|
+
'templates/split-agents/TOOLS.md',
|
|
57
|
+
'templates/CROSS_REPO_TYPES_PIPELINE.md',
|
|
58
|
+
'templates/github/RENOVATE_FRONTEND.json',
|
|
59
|
+
'templates/github/PUBLISH_SHARED_WORKFLOW.yml',
|
|
60
|
+
'templates/github/GENERATE_TYPES_SCRIPT.md',
|
|
61
|
+
'templates/github/TRIGGER_FRONTEND_UPDATE.yml',
|
|
62
|
+
'templates/github/RECEIVE_BACKEND_UPDATE.yml',
|
|
63
|
+
'templates/architecture.md',
|
|
64
|
+
'templates/architecture_auto.md',
|
|
65
|
+
'templates/CLAUDE_TEAM_GUIDE.md',
|
|
66
|
+
'templates/feature-flags-advanced.md',
|
|
67
|
+
'templates/MCP_SETUP.md',
|
|
68
|
+
'templates/ONBOARDING.md',
|
|
69
|
+
'templates/RLS_SETUP_PROMPT.md',
|
|
70
|
+
'templates/STACK_GUIDE.md',
|
|
71
|
+
'templates/STACK_VERSIONS.md',
|
|
72
|
+
'templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md',
|
|
73
|
+
'templates/_EXAMPLE.md',
|
|
74
|
+
'templates/_PATTERNS.md',
|
|
75
|
+
'templates/STACK_MIGRATION_GUIDE.md',
|
|
76
|
+
'templates/GLOSSARY_NON_PROGRAMMER.md',
|
|
77
|
+
'templates/SECURITY_INCIDENT_PLAYBOOK.md',
|
|
78
|
+
'templates/DB_SCHEMA_SCAN_PROMPT.md'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Tier 5b: Deprecated stubs (legacy prompts, still on disk, akan dihapus)
|
|
82
|
+
deprecated_stubs = @(
|
|
83
|
+
'BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md',
|
|
84
|
+
'FIRST_SESSION_PROMPT_v1.md',
|
|
85
|
+
'PROJECT_KICKOFF_PROMPT_v1.md',
|
|
86
|
+
'PROJECT_MIGRATION_PROMPT_v1.md',
|
|
87
|
+
'SETUP_POLA_B_PROMPT_v1.md',
|
|
88
|
+
'TEAM_ROLLOUT_GUIDE_v1.md',
|
|
89
|
+
'UPDATE_DOCS_PROMPT_v1.md'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Tier 5c: Decisions / ADR templates
|
|
93
|
+
decisions = @(
|
|
94
|
+
'templates/decisions/_TEMPLATE.md',
|
|
95
|
+
'templates/decisions/README.md'
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Tier 5d: GitHub assets (CODEOWNERS, PR template, workflows, scripts)
|
|
99
|
+
github_assets = @(
|
|
100
|
+
'templates/github/CODEOWNERS.template',
|
|
101
|
+
'templates/github/pull_request_template.md',
|
|
102
|
+
'templates/github/scripts/ai-review.js',
|
|
103
|
+
'templates/github/workflows/ai-review.yml',
|
|
104
|
+
'templates/github/workflows/backup-schemas.yml'
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Tier 6: Docs
|
|
108
|
+
docs = @(
|
|
109
|
+
'docs/SIGNED_RELEASE.md'
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Tier 7: Tests
|
|
113
|
+
tests = @(
|
|
114
|
+
'tests/Run-Tests.ps1',
|
|
115
|
+
'tests/lib-safety.Tests.ps1',
|
|
116
|
+
'tests/rollback.Tests.ps1',
|
|
117
|
+
'tests/update-kit.Tests.ps1',
|
|
118
|
+
'tests/uninstall.Tests.ps1'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Tier 8: CI
|
|
122
|
+
ci = @(
|
|
123
|
+
'.github/workflows/validate.yml'
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Tier 9: Project meta
|
|
127
|
+
meta = @(
|
|
128
|
+
'CHANGELOG.md',
|
|
129
|
+
'README.md',
|
|
130
|
+
'LICENSE',
|
|
131
|
+
'CONTRIBUTING.md'
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Manifest HMAC signing untuk prevent tampering attack.
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Sign .install-manifest.json dengan HMAC-SHA256 pakai salt derived dari
|
|
7
|
+
kit version constant. Verify signature sebelum trust manifest entries.
|
|
8
|
+
|
|
9
|
+
HMAC = anti-EDIT detection (manifest tidak diam-diam dimodifikasi),
|
|
10
|
+
BUKAN forge-proof secret. Pakai detached signature kalau perlu real secret.
|
|
11
|
+
#>
|
|
12
|
+
|
|
13
|
+
function Get-ManifestSecret {
|
|
14
|
+
param([string]$KitVersion)
|
|
15
|
+
|
|
16
|
+
if ($env:LINTASAI_MANIFEST_SECRET) {
|
|
17
|
+
return $env:LINTASAI_MANIFEST_SECRET
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Constant kit-version-based key (anti-EDIT, BUKAN forge-proof secret)
|
|
21
|
+
# Same kit version = same key = manifest portable across machines
|
|
22
|
+
$material = "lintasAI-manifest-v1|$KitVersion|signed-2026"
|
|
23
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($material)
|
|
24
|
+
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
|
|
25
|
+
return [Convert]::ToBase64String($hash)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function New-ManifestSignature {
|
|
29
|
+
param(
|
|
30
|
+
[Parameter(Mandatory)][hashtable]$Manifest,
|
|
31
|
+
[Parameter(Mandatory)][string]$KitVersion
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
$secret = Get-ManifestSecret -KitVersion $KitVersion
|
|
35
|
+
$secretBytes = [System.Text.Encoding]::UTF8.GetBytes($secret)
|
|
36
|
+
|
|
37
|
+
# Sign canonical JSON (sort keys recursively)
|
|
38
|
+
$canonical = $Manifest | ConvertTo-Json -Depth 10 -Compress
|
|
39
|
+
$canonicalBytes = [System.Text.Encoding]::UTF8.GetBytes($canonical)
|
|
40
|
+
|
|
41
|
+
$hmac = New-Object System.Security.Cryptography.HMACSHA256
|
|
42
|
+
$hmac.Key = $secretBytes
|
|
43
|
+
$signature = $hmac.ComputeHash($canonicalBytes)
|
|
44
|
+
return [Convert]::ToBase64String($signature)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Test-ManifestSignature {
|
|
48
|
+
param(
|
|
49
|
+
[Parameter(Mandatory)][hashtable]$Manifest,
|
|
50
|
+
[Parameter(Mandatory)][string]$KitVersion,
|
|
51
|
+
[Parameter(Mandatory)][string]$ExpectedSignature
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
$actualSignature = New-ManifestSignature -Manifest $Manifest -KitVersion $KitVersion
|
|
55
|
+
|
|
56
|
+
# Constant-time compare untuk prevent timing attack
|
|
57
|
+
if ($actualSignature.Length -ne $ExpectedSignature.Length) { return $false }
|
|
58
|
+
$equal = $true
|
|
59
|
+
for ($i = 0; $i -lt $actualSignature.Length; $i++) {
|
|
60
|
+
if ($actualSignature[$i] -ne $ExpectedSignature[$i]) { $equal = $false }
|
|
61
|
+
}
|
|
62
|
+
return $equal
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Functions auto-exposed via dot-source (no Export-ModuleMember karena .ps1)
|
package/lib/manifest.ps1
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Manifest helpers - extracted from setup-pola-b.ps1 v1.1.0
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Tujuan:
|
|
5
|
+
# Helper functions untuk build + sign install manifest (.install-manifest.json)
|
|
6
|
+
# yang dipakai uninstall.ps1 untuk safe-delete (hash-based pristine detection).
|
|
7
|
+
#
|
|
8
|
+
# Beda dengan versi inline di setup-pola-b.ps1:
|
|
9
|
+
# - TIDAK pakai $script: scope variables. Setiap function terima parameter
|
|
10
|
+
# eksplisit -> reusable dari script manapun (setup, update-kit, kit.ps1).
|
|
11
|
+
# - State (installedItems + createdDirs) diwakili object [pscustomobject]
|
|
12
|
+
# yang di-init via Initialize-Manifest, lalu di-mutate via Add-ToManifest /
|
|
13
|
+
# Add-DirToManifest, lalu di-flush via Save-Manifest.
|
|
14
|
+
#
|
|
15
|
+
# PowerShell 5.1 compatible (no ternary, no null-coalescing, no class syntax).
|
|
16
|
+
#
|
|
17
|
+
# Public API:
|
|
18
|
+
# - Initialize-Manifest -ProjectRoot <path>
|
|
19
|
+
# -> returns [pscustomobject] state container
|
|
20
|
+
# - Get-FileSha256 -FilePath <path>
|
|
21
|
+
# -> returns hex SHA-256 string (uppercase, no separator)
|
|
22
|
+
# - Add-ToManifest -State <obj> -FilePath <path> -Kind <string> [-From <string>]
|
|
23
|
+
# -> append file entry (path/kind/sha256[/from]) ke State.Files
|
|
24
|
+
# - Add-DirToManifest -State <obj> -DirPath <path>
|
|
25
|
+
# -> append dir relative path ke State.Directories (dedup)
|
|
26
|
+
# - Save-Manifest -State <obj> -KitDir <path> -KitVersion <string> -ProjectName <string>
|
|
27
|
+
# [-PreserveExisting] [-SignManifest]
|
|
28
|
+
# -> merge dengan manifest sebelumnya (kalau ada), HMAC sign, write JSON
|
|
29
|
+
# -> returns [pscustomobject] dengan FilesCount, DirsCount, Merged, Signed, ManifestPath
|
|
30
|
+
#
|
|
31
|
+
# Catatan path:
|
|
32
|
+
# - State.ProjectRoot disimpan supaya Add-ToManifest bisa compute relative path
|
|
33
|
+
# tanpa minta caller pass ulang setiap call.
|
|
34
|
+
# - Relative path pakai forward slash (portable di JSON, sama dengan setup-pola-b.ps1).
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
Set-StrictMode -Version Latest
|
|
38
|
+
|
|
39
|
+
function Initialize-Manifest {
|
|
40
|
+
<#
|
|
41
|
+
.SYNOPSIS
|
|
42
|
+
Buat state container baru untuk tracking install manifest.
|
|
43
|
+
.PARAMETER ProjectRoot
|
|
44
|
+
Absolute path ke root proyek (dipakai untuk compute relative paths).
|
|
45
|
+
#>
|
|
46
|
+
param(
|
|
47
|
+
[Parameter(Mandatory)][string]$ProjectRoot
|
|
48
|
+
)
|
|
49
|
+
return [pscustomobject]@{
|
|
50
|
+
ProjectRoot = $ProjectRoot
|
|
51
|
+
Files = @()
|
|
52
|
+
Directories = @()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Get-FileSha256 {
|
|
57
|
+
<#
|
|
58
|
+
.SYNOPSIS
|
|
59
|
+
Compute SHA-256 hash file (hex uppercase, no separator).
|
|
60
|
+
.PARAMETER FilePath
|
|
61
|
+
Path ke file yang mau di-hash. Pakai -LiteralPath (no glob expansion).
|
|
62
|
+
.OUTPUTS
|
|
63
|
+
[string] hex SHA-256, atau $null kalau file tidak ada.
|
|
64
|
+
#>
|
|
65
|
+
param(
|
|
66
|
+
[Parameter(Mandatory)][string]$FilePath
|
|
67
|
+
)
|
|
68
|
+
if (-not (Test-Path -LiteralPath $FilePath)) { return $null }
|
|
69
|
+
return (Get-FileHash -LiteralPath $FilePath -Algorithm SHA256).Hash
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ConvertTo-ManifestRelativePath {
|
|
73
|
+
<#
|
|
74
|
+
.SYNOPSIS
|
|
75
|
+
Convert absolute path -> relative path (forward slash, no leading slash).
|
|
76
|
+
.NOTES
|
|
77
|
+
Helper internal. Sengaja TIDAK di-export dari API publik karena
|
|
78
|
+
tightly coupled dengan State.ProjectRoot.
|
|
79
|
+
#>
|
|
80
|
+
param(
|
|
81
|
+
[Parameter(Mandatory)][string]$AbsolutePath,
|
|
82
|
+
[Parameter(Mandatory)][string]$ProjectRoot
|
|
83
|
+
)
|
|
84
|
+
$rel = $AbsolutePath.Replace($ProjectRoot, '')
|
|
85
|
+
$rel = $rel.TrimStart('\','/')
|
|
86
|
+
return $rel.Replace('\','/')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function Add-ToManifest {
|
|
90
|
+
<#
|
|
91
|
+
.SYNOPSIS
|
|
92
|
+
Track file ke manifest state dengan sha256 hash.
|
|
93
|
+
.PARAMETER State
|
|
94
|
+
Object dari Initialize-Manifest.
|
|
95
|
+
.PARAMETER FilePath
|
|
96
|
+
Absolute path file (yang sudah di-copy ke project).
|
|
97
|
+
.PARAMETER Kind
|
|
98
|
+
Kategori file (mis. 'lib', 'backup', 'filled_template', 'skeleton', 'team_file').
|
|
99
|
+
Dipakai uninstall.ps1 untuk decide treatment per kategori.
|
|
100
|
+
.PARAMETER From
|
|
101
|
+
Opsional source label (mis. 'AGENTS.md.template'). Disimpan ke field 'from'
|
|
102
|
+
di manifest entry untuk audit trail.
|
|
103
|
+
#>
|
|
104
|
+
param(
|
|
105
|
+
[Parameter(Mandatory)][pscustomobject]$State,
|
|
106
|
+
[Parameter(Mandatory)][string]$FilePath,
|
|
107
|
+
[Parameter(Mandatory)][string]$Kind,
|
|
108
|
+
[string]$From
|
|
109
|
+
)
|
|
110
|
+
if (-not (Test-Path -LiteralPath $FilePath)) { return }
|
|
111
|
+
|
|
112
|
+
$sha = Get-FileSha256 -FilePath $FilePath
|
|
113
|
+
$relPath = ConvertTo-ManifestRelativePath -AbsolutePath $FilePath -ProjectRoot $State.ProjectRoot
|
|
114
|
+
|
|
115
|
+
$entry = [ordered]@{
|
|
116
|
+
path = $relPath
|
|
117
|
+
kind = $Kind
|
|
118
|
+
sha256 = $sha
|
|
119
|
+
}
|
|
120
|
+
if ($From) { $entry.from = $From }
|
|
121
|
+
|
|
122
|
+
$State.Files += $entry
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function Add-DirToManifest {
|
|
126
|
+
<#
|
|
127
|
+
.SYNOPSIS
|
|
128
|
+
Track directory ke manifest state (dedup).
|
|
129
|
+
.PARAMETER State
|
|
130
|
+
Object dari Initialize-Manifest.
|
|
131
|
+
.PARAMETER DirPath
|
|
132
|
+
Absolute path directory yang baru dibuat kit.
|
|
133
|
+
#>
|
|
134
|
+
param(
|
|
135
|
+
[Parameter(Mandatory)][pscustomobject]$State,
|
|
136
|
+
[Parameter(Mandatory)][string]$DirPath
|
|
137
|
+
)
|
|
138
|
+
if (-not (Test-Path -LiteralPath $DirPath)) { return }
|
|
139
|
+
|
|
140
|
+
$relPath = ConvertTo-ManifestRelativePath -AbsolutePath $DirPath -ProjectRoot $State.ProjectRoot
|
|
141
|
+
if ($State.Directories -notcontains $relPath) {
|
|
142
|
+
$State.Directories += $relPath
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function Save-Manifest {
|
|
147
|
+
<#
|
|
148
|
+
.SYNOPSIS
|
|
149
|
+
Merge dengan manifest sebelumnya (kalau ada), HMAC sign, write JSON ke disk.
|
|
150
|
+
.PARAMETER State
|
|
151
|
+
Object dari Initialize-Manifest (sudah di-populate via Add-ToManifest / Add-DirToManifest).
|
|
152
|
+
.PARAMETER KitDir
|
|
153
|
+
Absolute path ke folder kit (mis. <project>/.claude-kit). Manifest ditulis ke
|
|
154
|
+
$KitDir/.install-manifest.json.
|
|
155
|
+
.PARAMETER KitVersion
|
|
156
|
+
Versi kit (mis. 'v1.1.0' atau 'pre-launch (testing)'). Disimpan di metadata
|
|
157
|
+
+ dipakai untuk HMAC signing key derivation.
|
|
158
|
+
.PARAMETER ProjectName
|
|
159
|
+
Nama proyek (leaf dari ProjectRoot). Disimpan ke manifest untuk audit.
|
|
160
|
+
.PARAMETER PreserveExisting
|
|
161
|
+
Kalau di-set: merge dengan manifest sebelumnya — preserve entries yang file-nya
|
|
162
|
+
masih ada di disk + path-nya tidak konflik dengan entry baru. Default: ON.
|
|
163
|
+
.PARAMETER SignManifest
|
|
164
|
+
Kalau di-set: HMAC sign manifest via lib/manifest-signing.ps1. Default: ON.
|
|
165
|
+
Set -SignManifest:$false untuk skip (mis. test mode).
|
|
166
|
+
.OUTPUTS
|
|
167
|
+
[pscustomobject] dengan: ManifestPath, FilesCount, DirsCount, Merged, Signed.
|
|
168
|
+
#>
|
|
169
|
+
param(
|
|
170
|
+
[Parameter(Mandatory)][pscustomobject]$State,
|
|
171
|
+
[Parameter(Mandatory)][string]$KitDir,
|
|
172
|
+
[Parameter(Mandatory)][string]$KitVersion,
|
|
173
|
+
[Parameter(Mandatory)][string]$ProjectName,
|
|
174
|
+
[switch]$PreserveExisting = $true,
|
|
175
|
+
[switch]$SignManifest = $true
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
$manifestPath = Join-Path $KitDir '.install-manifest.json'
|
|
179
|
+
$merged = $false
|
|
180
|
+
|
|
181
|
+
# ---- Baca manifest sebelumnya (kalau ada) untuk merge ----
|
|
182
|
+
$previous = $null
|
|
183
|
+
if ($PreserveExisting -and (Test-Path $manifestPath)) {
|
|
184
|
+
try {
|
|
185
|
+
$previous = Get-Content $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
186
|
+
} catch {
|
|
187
|
+
Write-Host "WARN Manifest sebelumnya corrupt, akan di-overwrite: $_" -ForegroundColor Yellow
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# ---- Merge files: preserve entries dari manifest sebelumnya yang masih ada di disk ----
|
|
192
|
+
if ($previous -and $previous.PSObject.Properties.Name -contains 'files' -and $previous.files) {
|
|
193
|
+
$newPaths = @($State.Files | ForEach-Object { $_.path })
|
|
194
|
+
foreach ($prevEntry in $previous.files) {
|
|
195
|
+
if ($newPaths -contains $prevEntry.path) { continue }
|
|
196
|
+
$fullPath = Join-Path $State.ProjectRoot (([string]$prevEntry.path) -replace '/', '\')
|
|
197
|
+
if (Test-Path -LiteralPath $fullPath) {
|
|
198
|
+
$entry = [ordered]@{
|
|
199
|
+
path = [string]$prevEntry.path
|
|
200
|
+
kind = [string]$prevEntry.kind
|
|
201
|
+
sha256 = [string]$prevEntry.sha256
|
|
202
|
+
}
|
|
203
|
+
if ($prevEntry.PSObject.Properties.Name -contains 'from' -and $prevEntry.from) {
|
|
204
|
+
$entry.from = [string]$prevEntry.from
|
|
205
|
+
}
|
|
206
|
+
$State.Files += $entry
|
|
207
|
+
$merged = $true
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# ---- Merge dirs: preserve yang masih ada ----
|
|
213
|
+
if ($previous -and $previous.PSObject.Properties.Name -contains 'directories_created' -and $previous.directories_created) {
|
|
214
|
+
foreach ($prevDir in $previous.directories_created) {
|
|
215
|
+
$prevDirStr = [string]$prevDir
|
|
216
|
+
if ($State.Directories -contains $prevDirStr) { continue }
|
|
217
|
+
$fullDir = Join-Path $State.ProjectRoot ($prevDirStr -replace '/', '\')
|
|
218
|
+
if (Test-Path -LiteralPath $fullDir) {
|
|
219
|
+
$State.Directories += $prevDirStr
|
|
220
|
+
$merged = $true
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# ---- Build manifest object (anonymized project_root + installed_by) ----
|
|
226
|
+
$manifestMetadata = [ordered]@{
|
|
227
|
+
kit_version = $KitVersion
|
|
228
|
+
installed_at = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
|
|
229
|
+
installer = 'setup-pola-b.ps1'
|
|
230
|
+
}
|
|
231
|
+
$manifest = [ordered]@{
|
|
232
|
+
schema_version = 1
|
|
233
|
+
kit_version = $KitVersion
|
|
234
|
+
installed_at = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ss')
|
|
235
|
+
installed_by = '<USER>'
|
|
236
|
+
project_name = $ProjectName
|
|
237
|
+
project_root = '<PROJECT_ROOT>'
|
|
238
|
+
metadata = $manifestMetadata
|
|
239
|
+
files = $State.Files
|
|
240
|
+
directories_created = $State.Directories
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# ---- HMAC sign (optional) ----
|
|
244
|
+
$signed = $false
|
|
245
|
+
if ($SignManifest) {
|
|
246
|
+
try {
|
|
247
|
+
. (Join-Path $KitDir 'lib\manifest-signing.ps1')
|
|
248
|
+
$manifest.metadata.signature = New-ManifestSignature -Manifest $manifest -KitVersion $KitVersion
|
|
249
|
+
$signed = $true
|
|
250
|
+
} catch {
|
|
251
|
+
Write-Host "WARN Gagal sign manifest (manifest-signing.ps1 issue): $_" -ForegroundColor Yellow
|
|
252
|
+
Write-Host " Manifest tetap ditulis tanpa signature. Uninstall akan prompt karena unsigned." -ForegroundColor Yellow
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# ---- Write JSON (UTF-8 NO BOM, supaya hash deterministic + git-friendly) ----
|
|
257
|
+
$json = $manifest | ConvertTo-Json -Depth 10
|
|
258
|
+
[System.IO.File]::WriteAllText($manifestPath, $json, (New-Object System.Text.UTF8Encoding $false))
|
|
259
|
+
|
|
260
|
+
return [pscustomobject]@{
|
|
261
|
+
ManifestPath = $manifestPath
|
|
262
|
+
FilesCount = $State.Files.Count
|
|
263
|
+
DirsCount = $State.Directories.Count
|
|
264
|
+
Merged = $merged
|
|
265
|
+
Signed = $signed
|
|
266
|
+
}
|
|
267
|
+
}
|