Added to Git

This commit is contained in:
Vadim Flowed
2026-05-10 19:01:57 +03:00
parent d08c2c4d8a
commit 1c7f9161f1
44 changed files with 2902 additions and 0 deletions
@@ -0,0 +1,178 @@
param(
[Parameter(Mandatory = $true)]
[string]$PackagePath,
[Parameter(Mandatory = $true)]
[string]$TargetRoot,
[Parameter(Mandatory = $false)]
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
function Normalize-RelativePath {
param([string]$Path)
$value = $Path -replace "\\", "/"
$value = $value.Trim()
while ($value.StartsWith("./")) {
$value = $value.Substring(2)
}
$value = $value.TrimStart("/")
return $value.TrimEnd("/")
}
function Test-PathMatchesPrefix {
param(
[string]$RelativePath,
[string]$Prefix
)
$pathValue = Normalize-RelativePath -Path $RelativePath
$prefixValue = Normalize-RelativePath -Path $Prefix
if ([string]::IsNullOrWhiteSpace($prefixValue)) { return $false }
return $pathValue -eq $prefixValue -or $pathValue.StartsWith("$prefixValue/")
}
function Test-IsAllowed {
param(
[string]$RelativePath,
[string[]]$AllowedPaths
)
foreach ($allowed in $AllowedPaths) {
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $allowed) {
return $true
}
}
return $false
}
function Test-IsBlocked {
param(
[string]$RelativePath,
[string[]]$BlockedPaths
)
foreach ($blocked in $BlockedPaths) {
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $blocked) {
return $true
}
}
return $false
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
function Get-TargetFileHash {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return "" }
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
$resolvedPackage = Resolve-Path -LiteralPath $PackagePath
$packageItem = Get-Item -LiteralPath $resolvedPackage.Path
$packageRoot = if ($packageItem.PSIsContainer) { $packageItem.FullName } else { Split-Path -Parent $packageItem.FullName }
$packageManifestPath = if ($packageItem.PSIsContainer) { Join-Path $packageRoot "package-manifest.json" } else { $packageItem.FullName }
if (-not (Test-Path -LiteralPath $packageManifestPath)) {
throw "Package manifest not found: $packageManifestPath"
}
if (-not (Test-Path -LiteralPath $TargetRoot)) {
throw "Target root not found: $TargetRoot"
}
$targetRootPath = (Resolve-Path -LiteralPath $TargetRoot).Path
$packageManifest = Get-Content -LiteralPath $packageManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
$allowedPaths = @($packageManifest.system_paths | ForEach-Object { [string]$_ })
$blockedPaths = @($packageManifest.excluded_paths + $packageManifest.preserve_paths | ForEach-Object { [string]$_ })
$operations = New-Object System.Collections.Generic.List[object]
foreach ($file in @($packageManifest.files)) {
$relative = Normalize-RelativePath -Path ([string]$file.path)
if (-not (Test-IsAllowed -RelativePath $relative -AllowedPaths $allowedPaths)) {
throw "Package file is outside system allowlist: $relative"
}
if (Test-IsBlocked -RelativePath $relative -BlockedPaths $blockedPaths) {
throw "Package file targets a preserved/excluded path: $relative"
}
$source = Join-Path $packageRoot ($relative -replace "/", "\")
if (-not (Test-Path -LiteralPath $source)) {
throw "Package file is missing: $relative"
}
$sourceHash = (Get-FileHash -LiteralPath $source -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sourceHash -ne ([string]$file.sha256).ToLowerInvariant()) {
throw "Checksum mismatch for package file: $relative"
}
$target = Join-Path $targetRootPath ($relative -replace "/", "\")
$targetHash = Get-TargetFileHash -Path $target
if ($targetHash -eq $sourceHash) {
continue
}
$action = if (Test-Path -LiteralPath $target) { "update" } else { "create" }
$operations.Add([ordered]@{
action = $action
path = $relative
source = $source
target = $target
sha256 = $sourceHash
})
}
if ($DryRun) {
Write-Output "Dry run: $($operations.Count) file operation(s) would be applied."
foreach ($operation in $operations) {
Write-Output "$($operation.action): $($operation.path)"
}
return
}
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff")
$hotUpdateDir = Join-Path $targetRootPath "workspace/.hot-update"
$backupRoot = Join-Path $hotUpdateDir "backups/$timestamp"
New-Directory -Path $backupRoot
$backupEntries = New-Object System.Collections.Generic.List[object]
foreach ($operation in $operations) {
if ($operation.action -eq "update") {
$backupPath = Join-Path $backupRoot ($operation.path -replace "/", "\")
New-Directory -Path (Split-Path -Parent $backupPath)
Copy-Item -LiteralPath $operation.target -Destination $backupPath -Force
$backupEntries.Add([ordered]@{
path = $operation.path
backup_path = ("workspace/.hot-update/backups/$timestamp/" + $operation.path)
original_sha256 = (Get-FileHash -LiteralPath $backupPath -Algorithm SHA256).Hash.ToLowerInvariant()
})
}
New-Directory -Path (Split-Path -Parent $operation.target)
Copy-Item -LiteralPath $operation.source -Destination $operation.target -Force
}
$applyLog = [ordered]@{
applied_at = (Get-Date).ToUniversalTime().ToString("o")
package_name = [string]$packageManifest.package_name
version = [string]$packageManifest.version
dry_run = $false
operations = $operations
backups = $backupEntries
}
$backupManifestPath = Join-Path $backupRoot "backup-manifest.json"
$applyLog | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $backupManifestPath -Encoding UTF8
$applyLogPath = Join-Path $hotUpdateDir "last-apply.json"
$applyLog | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $applyLogPath -Encoding UTF8
Write-Output "Applied $($operations.Count) file operation(s)."
Write-Output "Backup: $backupRoot"
@@ -0,0 +1,146 @@
param(
[Parameter(Mandatory = $false)]
[string]$OutputDir = "dist/wireframe-system",
[Parameter(Mandatory = $false)]
[string]$ManifestPath = "wireframe-system.manifest.json"
)
$ErrorActionPreference = "Stop"
function Get-RepoRoot {
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
}
function Normalize-RelativePath {
param([string]$Path)
$value = $Path -replace "\\", "/"
$value = $value.Trim()
while ($value.StartsWith("./")) {
$value = $value.Substring(2)
}
$value = $value.TrimStart("/")
return $value.TrimEnd("/")
}
function Test-PathMatchesPrefix {
param(
[string]$RelativePath,
[string]$Prefix
)
$pathValue = Normalize-RelativePath -Path $RelativePath
$prefixValue = Normalize-RelativePath -Path $Prefix
if ([string]::IsNullOrWhiteSpace($prefixValue)) { return $false }
return $pathValue -eq $prefixValue -or $pathValue.StartsWith("$prefixValue/")
}
function Test-IsExcluded {
param(
[string]$RelativePath,
[string[]]$ExcludedPaths
)
foreach ($excluded in $ExcludedPaths) {
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $excluded) {
return $true
}
}
return $false
}
function Get-RelativePath {
param(
[string]$Root,
[string]$Path
)
$rootPath = (Resolve-Path -LiteralPath $Root).Path
if (-not $rootPath.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
$rootPath += [System.IO.Path]::DirectorySeparatorChar
}
$pathValue = (Resolve-Path -LiteralPath $Path).Path
$rootUri = New-Object System.Uri($rootPath)
$pathUri = New-Object System.Uri($pathValue)
$relativeUri = $rootUri.MakeRelativeUri($pathUri)
return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace("/", "\")
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
$root = Get-RepoRoot
$manifestFullPath = Join-Path $root $ManifestPath
if (-not (Test-Path -LiteralPath $manifestFullPath)) {
throw "System manifest not found: $manifestFullPath"
}
$manifest = Get-Content -LiteralPath $manifestFullPath -Raw -Encoding UTF8 | ConvertFrom-Json
$packageName = [string]$manifest.package_name
$version = [string]$manifest.version
if ([string]::IsNullOrWhiteSpace($packageName) -or [string]::IsNullOrWhiteSpace($version)) {
throw "System manifest must include package_name and version."
}
$excludedPaths = @($manifest.excluded_paths | ForEach-Object { [string]$_ })
$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path $root $OutputDir }
New-Directory -Path $outputRoot
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff")
$packageRoot = Join-Path $outputRoot "$packageName-$version-$timestamp"
New-Directory -Path $packageRoot
foreach ($relativePath in @($manifest.system_paths)) {
$relative = Normalize-RelativePath -Path ([string]$relativePath)
if (Test-IsExcluded -RelativePath $relative -ExcludedPaths $excludedPaths) {
throw "System path is excluded and cannot be packaged: $relative"
}
$source = Join-Path $root $relative
if (-not (Test-Path -LiteralPath $source)) {
throw "System path listed in manifest does not exist: $relative"
}
$destination = Join-Path $packageRoot $relative
$destinationParent = Split-Path -Parent $destination
if (-not [string]::IsNullOrWhiteSpace($destinationParent)) {
New-Directory -Path $destinationParent
}
Copy-Item -LiteralPath $source -Destination $destination -Recurse -Force
}
$files = New-Object System.Collections.Generic.List[object]
Get-ChildItem -LiteralPath $packageRoot -Recurse -File -Force |
Sort-Object FullName |
ForEach-Object {
$relative = (Get-RelativePath -Root $packageRoot -Path $_.FullName) -replace "\\", "/"
$hash = Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256
$files.Add([ordered]@{
path = $relative
sha256 = $hash.Hash.ToLowerInvariant()
length = $_.Length
})
}
$packageManifest = [ordered]@{
package_name = $packageName
version = $version
exported_at = (Get-Date).ToUniversalTime().ToString("o")
source_manifest = $ManifestPath
default_artifact_dir = [string]$manifest.default_artifact_dir
system_paths = @($manifest.system_paths)
preserve_paths = @($manifest.preserve_paths)
excluded_paths = @($manifest.excluded_paths)
files = $files
}
$packageManifestPath = Join-Path $packageRoot "package-manifest.json"
$packageManifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $packageManifestPath -Encoding UTF8
Write-Output "Exported $($files.Count) system file(s)."
Write-Output $packageRoot
@@ -0,0 +1,70 @@
param(
[Parameter(Mandatory = $false)]
[string]$WorkspaceDir = "workspace",
[Parameter(Mandatory = $false)]
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen",
[Parameter(Mandatory = $false)]
[string]$LegacyArtifactDir = "artifacts/wireframe-gen"
)
$ErrorActionPreference = "Stop"
function Get-RepoRoot {
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
}
function Resolve-RepoPath {
param(
[string]$Root,
[string]$Path
)
if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
return Join-Path $Root $Path
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
function Test-DirectoryHasFiles {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return $false }
return $null -ne (Get-ChildItem -LiteralPath $Path -Force -Recurse -File | Select-Object -First 1)
}
$root = Get-RepoRoot
$workspacePath = Resolve-RepoPath -Root $root -Path $WorkspaceDir
$artifactPath = Resolve-RepoPath -Root $root -Path $ArtifactDir
$legacyArtifactPath = Resolve-RepoPath -Root $root -Path $LegacyArtifactDir
New-Directory -Path $workspacePath
New-Directory -Path $artifactPath
New-Directory -Path (Join-Path $workspacePath "system-feedback/bug-reports")
New-Directory -Path (Join-Path $workspacePath ".hot-update/backups")
$targetHadFiles = Test-DirectoryHasFiles -Path $artifactPath
$legacyExists = Test-Path -LiteralPath $legacyArtifactPath
$copiedLegacy = $false
if ($legacyExists -and -not $targetHadFiles) {
Get-ChildItem -LiteralPath $legacyArtifactPath -Force | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination $artifactPath -Recurse -Force
}
$copiedLegacy = $true
}
if (-not (Test-DirectoryHasFiles -Path $artifactPath)) {
$initArtifactsScript = Join-Path $root "scripts/wireframe/init-artifacts.ps1"
powershell -NoProfile -ExecutionPolicy Bypass -File $initArtifactsScript -ArtifactDir $artifactPath | Out-Null
}
Write-Output "Workspace initialized: $workspacePath"
Write-Output "Artifact dir: $artifactPath"
if ($copiedLegacy) {
Write-Output "Copied legacy artifacts from $legacyArtifactPath. Legacy artifacts were not deleted."
}
@@ -0,0 +1,162 @@
param(
[Parameter(Mandatory = $true)]
[string]$Title,
[Parameter(Mandatory = $true)]
[string]$Area,
[Parameter(Mandatory = $true)]
[string]$Expected,
[Parameter(Mandatory = $true)]
[string]$Actual,
[Parameter(Mandatory = $false)]
[ValidateSet("low", "medium", "high", "critical")]
[string]$Severity = "medium",
[Parameter(Mandatory = $false)]
[string]$TriggerContext = "Not specified",
[Parameter(Mandatory = $false)]
[string[]]$ReproSteps = @(),
[Parameter(Mandatory = $false)]
[string]$Impact = "",
[Parameter(Mandatory = $false)]
[string]$Workaround = "",
[Parameter(Mandatory = $false)]
[string]$DepersonalizationNotes = "No client names, copied client documents, Figma content, credentials, or private identifiers are included.",
[Parameter(Mandatory = $false)]
[switch]$PrivacyConfirmed,
[Parameter(Mandatory = $false)]
[string]$WorkspaceDir = "workspace",
[Parameter(Mandatory = $false)]
[string]$SystemManifestPath = "wireframe-system.manifest.json"
)
$ErrorActionPreference = "Stop"
function Get-RepoRoot {
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
}
function Resolve-RepoPath {
param(
[string]$Root,
[string]$Path
)
if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
return Join-Path $Root $Path
}
function New-Slug {
param([string]$Value)
$slug = $Value.ToLowerInvariant() -replace "[^a-z0-9]+", "-"
$slug = $slug.Trim("-")
if ([string]::IsNullOrWhiteSpace($slug)) { return "bug-report" }
if ($slug.Length -gt 48) { return $slug.Substring(0, 48).Trim("-") }
return $slug
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
$root = Get-RepoRoot
$workspacePath = Resolve-RepoPath -Root $root -Path $WorkspaceDir
$reportRoot = Join-Path $workspacePath "system-feedback/bug-reports"
New-Directory -Path $reportRoot
$systemVersion = "unknown"
$manifestPath = Resolve-RepoPath -Root $root -Path $SystemManifestPath
if (Test-Path -LiteralPath $manifestPath) {
$manifest = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
if ($manifest.PSObject.Properties.Name -contains "version") {
$systemVersion = [string]$manifest.version
}
}
$createdAt = (Get-Date).ToUniversalTime()
$id = "BUG-" + $createdAt.ToString("yyyyMMdd-HHmmss-fff")
$reportDir = Join-Path $reportRoot ($id + "-" + (New-Slug -Value $Title))
New-Directory -Path $reportDir
New-Directory -Path (Join-Path $reportDir "sanitized-snippets")
$report = [ordered]@{
id = $id
title = $Title
created_at = $createdAt.ToString("o")
system_version = $systemVersion
area = $Area
severity = $Severity
trigger_context = $TriggerContext
actual_behavior = $Actual
expected_behavior = $Expected
repro_steps = @($ReproSteps)
impact = $Impact
workaround = $Workaround
depersonalization_notes = $DepersonalizationNotes
privacy_confirmed = [bool]$PrivacyConfirmed
}
$jsonPath = Join-Path $reportDir "report.json"
$report | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $jsonPath -Encoding UTF8
$stepsText = if (@($ReproSteps).Count -gt 0) {
(@($ReproSteps) | ForEach-Object { "- $_" }) -join "`n"
}
else {
"- Not provided"
}
$markdown = @"
# $id - $Title
- Created at: $($report.created_at)
- System version: $systemVersion
- Area: $Area
- Severity: $Severity
- Privacy confirmed: $([bool]$PrivacyConfirmed)
## Trigger Context
$TriggerContext
## Actual Behavior
$Actual
## Expected Behavior
$Expected
## Reproduction Steps
$stepsText
## Impact
$Impact
## Workaround
$Workaround
## Depersonalization Notes
$DepersonalizationNotes
## Sanitized Attachments
Place only manually cleaned snippets in sanitized-snippets/. Do not attach source client documents, copied Figma content, credentials, private names, or identifiers.
"@
$markdownPath = Join-Path $reportDir "report.md"
Set-Content -LiteralPath $markdownPath -Value $markdown -Encoding UTF8
Set-Content -LiteralPath (Join-Path $reportDir "sanitized-snippets/.gitkeep") -Value "" -Encoding UTF8
Write-Output "Bug report created: $reportDir"
Write-Output $jsonPath
Write-Output $markdownPath