chore(tooling): add AI security review exporter
This commit is contained in:
parent
bc2b658570
commit
1495067501
4 changed files with 538 additions and 0 deletions
19
agents/antigravity/done/TASK-2026-08-18-consolidation.md
Normal file
19
agents/antigravity/done/TASK-2026-08-18-consolidation.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# TASK-2026-08-18-консолидация-рабочего-пространства
|
||||||
|
|
||||||
|
**Дата:** 2026-08-18
|
||||||
|
**От:** владелец проекта (через Claude)
|
||||||
|
**Статус:** in-progress
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
Склонировать 17 репозиториев ochenstarik-ui в E:\Agent projects и проверить структуру agents/ в каждом.
|
||||||
|
|
||||||
|
## Репозитории
|
||||||
|
business-platform, kagent, hermes-config-backup, server-monitor-manager,
|
||||||
|
randomayzer, trade-signal-platform, trading-knowledge-base,
|
||||||
|
finance-telegram-bot, agent-control-center, agent-control-center-server,
|
||||||
|
lightweight-server, safetest-platform, agent-engineering-quality-system,
|
||||||
|
hermes-lossless-context-layer, hermes-task-inbox, singbox-ai-router,
|
||||||
|
trade-signal-bot-legacy
|
||||||
|
|
||||||
|
## Отчёт
|
||||||
|
E:\Agent projects\done\2026-08-18-консолидация-рабочего-пространства.md
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Task Done: Developer Tooling — Add Security Review Exporter
|
||||||
|
|
||||||
|
**Status:** DONE
|
||||||
|
**Assigned to:** Antigravity (@orchestrator)
|
||||||
|
**Date:** 2026-08-18
|
||||||
|
**Base Commit:** `bc2b658`
|
||||||
|
|
||||||
|
## Accomplished
|
||||||
|
1. Created `tools/export-review.ps1` supporting:
|
||||||
|
- Full snapshot mode (`git archive HEAD`) with `REVIEW_CONTEXT.md` injection.
|
||||||
|
- Diff mode (`-Diff`, `-Base <SHA>`) with `REVIEW_DIFF.patch`, `REVIEW_CHANGED_FILES.txt`, changed source files in tree structure, all tests in `tests/*`, `prisma/schema.prisma`, `package.json`, `docs/*`.
|
||||||
|
- Security safety check against tracked secrets (`.env`, `*.pem`, `*.key`, `credentials.json`, `secrets.json`).
|
||||||
|
- Dirty worktree handling with warning and `-RequireClean` strict guard.
|
||||||
|
- Output summary table with file count, size, commit SHA, mode.
|
||||||
|
- `-CopyPrompt` to automatically put Russian reviewer prompt into Windows clipboard.
|
||||||
|
- Compatible with Windows PowerShell 5.1 and PowerShell 7+, handles paths with spaces.
|
||||||
|
2. Created user documentation in `docs/AI_REVIEW_EXPORT.md`.
|
||||||
|
3. Verified both FULL and DIFF exports, tested `-RequireClean`, tested Windows PowerShell 5.1 and PowerShell 7.
|
||||||
91
docs/AI_REVIEW_EXPORT.md
Normal file
91
docs/AI_REVIEW_EXPORT.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Randomayzer AI Security Review Exporter
|
||||||
|
|
||||||
|
`tools/export-review.ps1` — утилита для создания автономных snapshot и diff пакетов репозитория для внешних AI security reviewers, у которых нет прямого доступа к GitHub репозиторию или локальной файловой системе.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Возможности
|
||||||
|
|
||||||
|
- **Full Snapshot Mode (по умолчанию)**: Создает полный ZIP-архив репозитория на основе `git archive HEAD`.
|
||||||
|
- **Diff Mode (`-Diff`)**: Создает компактный пакет изменений между базовым коммитом (`-Base`) и `HEAD`, включая патч, список измененных файлов, измененные исходники, полный набор тестов, схему базы данных и документацию.
|
||||||
|
- **Review Context (`REVIEW_CONTEXT.md`)**: В каждый архив автоматически встраивается файл метаданных с полным SHA, веткой, временной меткой, состоянием working tree и статистикой `git log`.
|
||||||
|
- **Secret Safety Check**: Проверяет tracked-файлы на наличие потенциальных секретов (`.env`, `*.pem`, `*.key`, `credentials.json`, `secrets.json`) и блокирует экспорт при обнаружении.
|
||||||
|
- **Dirty Worktree Warning / Guard**: Предупреждает о наличии незакоммиченных изменений (снапшот собирается строго из `HEAD`) или прерывает выполнение при флаге `-RequireClean`.
|
||||||
|
- **Zero External Dependencies**: Работает на Windows PowerShell 5.1 и PowerShell 7+ с использованием стандартных API .NET (`System.IO.Compression`). Сторонние архиваторы (7-Zip, WinRAR) не требуются.
|
||||||
|
- **Корректная работа с пробелами в путях** (например, `E:\Agent projects\Randomayzer`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Быстрый старт
|
||||||
|
|
||||||
|
Запуск из корня проекта:
|
||||||
|
|
||||||
|
### Полный снимок репозитория (Full Snapshot)
|
||||||
|
```powershell
|
||||||
|
.\tools\export-review.ps1
|
||||||
|
```
|
||||||
|
Архив сохраняется по умолчанию в папку `Desktop\Randomayzer Reviews\randomayzer-review-<shortSHA>.zip`.
|
||||||
|
|
||||||
|
### Diff последнего коммита (`HEAD^` vs `HEAD`)
|
||||||
|
```powershell
|
||||||
|
.\tools\export-review.ps1 -Diff
|
||||||
|
```
|
||||||
|
Создает архив `randomayzer-diff-review-<shortSHA>.zip`, содержащий:
|
||||||
|
- `REVIEW_CONTEXT.md`
|
||||||
|
- `REVIEW_DIFF.patch`
|
||||||
|
- `REVIEW_CHANGED_FILES.txt`
|
||||||
|
- Измененные файлы в структуре каталогов проекта
|
||||||
|
- Полный набор тестов `tests/*`
|
||||||
|
- Схему `prisma/schema.prisma`, `package.json`, `.env.example`, документацию `docs/*`
|
||||||
|
|
||||||
|
### Diff от определенного коммита
|
||||||
|
```powershell
|
||||||
|
.\tools\export-review.ps1 -Diff -Base b5467f6
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Параметры
|
||||||
|
|
||||||
|
| Параметр | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| `-Diff` | Switch | Включает режим формирования diff-пакета вместо полного снимка. |
|
||||||
|
| `-Base <SHA>` | String | Базовый коммит для сравнения в режиме `-Diff` (по умолчанию `HEAD^`). |
|
||||||
|
| `-OutputDir <Path>` | String | Пользовательский путь для сохранения ZIP-файлов (по умолчанию `Desktop\Randomayzer Reviews\`). |
|
||||||
|
| `-RequireClean` | Switch | Завершает работу с ошибкой, если в working tree есть незакоммиченные файлы. |
|
||||||
|
| `-AllowSensitiveTrackedFiles` | Switch | Отключает блокировку экспорта при обнаружении подозрительных tracked-файлов. |
|
||||||
|
| `-CopyPrompt` | Switch | Автоматически копирует в буфер обмена Windows текст задания для security reviewer. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Примеры использования
|
||||||
|
|
||||||
|
### Экспорт со строгой проверкой чистоты рабочей директории
|
||||||
|
```powershell
|
||||||
|
.\tools\export-review.ps1 -RequireClean
|
||||||
|
```
|
||||||
|
|
||||||
|
### Экспорт в пользовательскую директорию с копированием промпта
|
||||||
|
```powershell
|
||||||
|
.\tools\export-review.ps1 -Diff -Base bc2b658 -OutputDir "D:\Audits" -CopyPrompt
|
||||||
|
```
|
||||||
|
|
||||||
|
После выполнения в буфере обмена будет готов стандартизированный промпт:
|
||||||
|
> «В приложенном архиве snapshot Randomayzer на commit `<SHA>`.
|
||||||
|
> Используй архив как source of truth.
|
||||||
|
> Не используй GitHub HEAD вместо него.
|
||||||
|
> Выполни ранее выданное security review задание.»
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Гарантии безопасности архива
|
||||||
|
|
||||||
|
1. **Изоляция от локального мусора**: В ZIP-архив **никогда не попадают**:
|
||||||
|
- `.git`
|
||||||
|
- `node_modules`
|
||||||
|
- `.next` сборки и кэш
|
||||||
|
- Локальные незакоммиченные файлы
|
||||||
|
- Локальные `.env`, `.env.local`
|
||||||
|
- IDE-файлы (`.vscode`, `.idea`)
|
||||||
|
2. **Точность снимка**: Файлы извлекаются напрямую из Git-объектов коммита (`git archive` для Full или `git show HEAD:<path>` для Diff), поэтому локальные изменения в рабочей директории не искажают снимок.
|
||||||
|
3. **Защита от утечки секретов**: Сканирование имен файлов блокирует экспорт при случайном попадании приватных ключей или файлов конфигурации в индекс Git.
|
||||||
410
tools/export-review.ps1
Normal file
410
tools/export-review.ps1
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports Randomayzer repository snapshots and diff packages for AI Security Reviewers.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Creates clean, deterministic ZIP archives of tracked repository code at a specific commit.
|
||||||
|
Guarantees that node_modules, local .env files, untracked artifacts, and IDE caches are never included.
|
||||||
|
Includes automated REVIEW_CONTEXT.md, diffs, and verification metrics.
|
||||||
|
|
||||||
|
.PARAMETER Diff
|
||||||
|
Generate a diff review package between Base commit and HEAD instead of a full snapshot.
|
||||||
|
|
||||||
|
.PARAMETER Base
|
||||||
|
Base commit for diff mode (defaults to HEAD^).
|
||||||
|
|
||||||
|
.PARAMETER OutputDir
|
||||||
|
Output directory for the generated ZIP file (defaults to Desktop\Randomayzer Reviews\).
|
||||||
|
|
||||||
|
.PARAMETER RequireClean
|
||||||
|
If specified, aborts the export if the git working tree has uncommitted changes.
|
||||||
|
|
||||||
|
.PARAMETER AllowSensitiveTrackedFiles
|
||||||
|
Bypasses the safety abort when potentially sensitive file patterns are detected in tracked git files.
|
||||||
|
|
||||||
|
.PARAMETER CopyPrompt
|
||||||
|
Copies the standardized security reviewer instructions to the Windows clipboard upon completion.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\tools\export-review.ps1
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\tools\export-review.ps1 -Diff
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\tools\export-review.ps1 -Diff -Base bc2b658 -CopyPrompt
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\tools\export-review.ps1 -OutputDir "D:\Audits" -RequireClean
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Diff,
|
||||||
|
[string]$Base,
|
||||||
|
[string]$OutputDir,
|
||||||
|
[switch]$RequireClean,
|
||||||
|
[switch]$AllowSensitiveTrackedFiles,
|
||||||
|
[switch]$CopyPrompt
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# 1. Resolve Git repository root and metadata
|
||||||
|
$gitRootRaw = git rev-parse --show-toplevel 2>$null
|
||||||
|
if (-not $gitRootRaw -or $LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Not inside a git repository or git is unavailable."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$gitRoot = [System.IO.Path]::GetFullPath($gitRootRaw.Trim())
|
||||||
|
|
||||||
|
$headSha = (git rev-parse HEAD 2>$null).Trim()
|
||||||
|
$shortSha = (git rev-parse --short HEAD 2>$null).Trim()
|
||||||
|
$branch = (git rev-parse --abbrev-ref HEAD 2>$null).Trim()
|
||||||
|
|
||||||
|
$remoteUrl = (git config --get remote.origin.url 2>$null)
|
||||||
|
if (-not $remoteUrl) {
|
||||||
|
$remoteUrl = "https://github.com/ochenstarik-ui/randomayzer"
|
||||||
|
} else {
|
||||||
|
$remoteUrl = $remoteUrl.Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Output directory resolution (relative to current PowerShell location)
|
||||||
|
if (-not $OutputDir) {
|
||||||
|
$desktopPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)
|
||||||
|
$OutputDir = [System.IO.Path]::Combine($desktopPath, "Randomayzer Reviews")
|
||||||
|
} else {
|
||||||
|
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
|
||||||
|
$OutputDir = [System.IO.Path]::Combine((Get-Location).Path, $OutputDir)
|
||||||
|
}
|
||||||
|
$OutputDir = [System.IO.Path]::GetFullPath($OutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $OutputDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Dirty worktree check
|
||||||
|
$statusOutput = git status --porcelain
|
||||||
|
$isDirty = [bool]($statusOutput -and ($statusOutput.Trim().Length -gt 0))
|
||||||
|
|
||||||
|
if ($isDirty) {
|
||||||
|
if ($RequireClean) {
|
||||||
|
Write-Error "Working tree contains uncommitted changes. -RequireClean was specified. Aborting export."
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Warning "Working tree contains uncommitted changes. Snapshot represents committed HEAD only."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$dirtyText = if ($isDirty) { "YES" } else { "NO" }
|
||||||
|
|
||||||
|
# 4. Secret safety check on tracked files
|
||||||
|
$trackedFiles = git ls-tree -r --name-only HEAD
|
||||||
|
$sensitiveMatches = @()
|
||||||
|
$sensitiveRegex = '(?i)(^|/|\\)(\.env(\.(?!example$).*)?|\.env$|.*\.pem$|.*\.key$|credentials\.json$|secrets\.json$|.*\.p12$|.*\.pfx$)'
|
||||||
|
|
||||||
|
foreach ($file in $trackedFiles) {
|
||||||
|
if ($file -match $sensitiveRegex -and $file -notmatch '(?i)\.env\.example') {
|
||||||
|
$sensitiveMatches += $file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sensitiveMatches.Count -gt 0) {
|
||||||
|
Write-Warning "=================================================="
|
||||||
|
Write-Warning "POTENTIALLY SENSITIVE FILES DETECTED IN TRACKED GIT:"
|
||||||
|
foreach ($sf in $sensitiveMatches) {
|
||||||
|
Write-Warning " - $sf"
|
||||||
|
}
|
||||||
|
Write-Warning "=================================================="
|
||||||
|
if (-not $AllowSensitiveTrackedFiles) {
|
||||||
|
Write-Error "Export aborted to prevent secret leakage. Pass -AllowSensitiveTrackedFiles to override."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure .NET compression assemblies are loaded
|
||||||
|
Add-Type -AssemblyName System.IO.Compression
|
||||||
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||||
|
|
||||||
|
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss zzz")
|
||||||
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
|
||||||
|
# 5. Export generation
|
||||||
|
if (-not $Diff) {
|
||||||
|
# ── FULL SNAPSHOT MODE ──
|
||||||
|
$modeName = "FULL"
|
||||||
|
$zipFileName = "randomayzer-review-$shortSha.zip"
|
||||||
|
$zipFilePath = [System.IO.Path]::Combine($OutputDir, $zipFileName)
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $zipFilePath) {
|
||||||
|
Remove-Item -LiteralPath $zipFilePath -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build clean archive directly from git HEAD
|
||||||
|
Push-Location $gitRoot
|
||||||
|
try {
|
||||||
|
git archive HEAD --format=zip --output="$zipFilePath"
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "git archive failed with exit code $LASTEXITCODE"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate metadata context
|
||||||
|
$gitLogStat = (git log -1 --stat HEAD) -join "`n"
|
||||||
|
|
||||||
|
$reviewContextLines = @(
|
||||||
|
"# Randomayzer Review Snapshot",
|
||||||
|
"",
|
||||||
|
"- **Full commit SHA:** $headSha",
|
||||||
|
"- **Short SHA:** $shortSha",
|
||||||
|
"- **Branch:** $branch",
|
||||||
|
"- **Generated at:** $timestamp",
|
||||||
|
"- **Repository:** $remoteUrl",
|
||||||
|
"- **Snapshot mode:** FULL",
|
||||||
|
"- **Working tree dirty at export time:** $dirtyText",
|
||||||
|
"",
|
||||||
|
"## Source of Truth",
|
||||||
|
"The files in this archive are the exact tracked repository snapshot for the commit shown above.",
|
||||||
|
"",
|
||||||
|
"## Reviewer Instructions",
|
||||||
|
"Do not use GitHub HEAD as source of truth.",
|
||||||
|
"Review the attached snapshot.",
|
||||||
|
"",
|
||||||
|
"## Recent Commit Log",
|
||||||
|
'```',
|
||||||
|
$gitLogStat,
|
||||||
|
'```'
|
||||||
|
)
|
||||||
|
$reviewContext = $reviewContextLines -join "`n"
|
||||||
|
|
||||||
|
# Inject REVIEW_CONTEXT.md into ZIP
|
||||||
|
$archive = [System.IO.Compression.ZipFile]::Open($zipFilePath, [System.IO.Compression.ZipArchiveMode]::Update)
|
||||||
|
try {
|
||||||
|
$existingEntry = $archive.GetEntry("REVIEW_CONTEXT.md")
|
||||||
|
if ($existingEntry) {
|
||||||
|
$existingEntry.Delete()
|
||||||
|
}
|
||||||
|
$entry = $archive.CreateEntry("REVIEW_CONTEXT.md", [System.IO.Compression.CompressionLevel]::Optimal)
|
||||||
|
$stream = $entry.Open()
|
||||||
|
try {
|
||||||
|
$bytes = $utf8NoBom.GetBytes($reviewContext)
|
||||||
|
$stream.Write($bytes, 0, $bytes.Length)
|
||||||
|
} finally {
|
||||||
|
$stream.Dispose()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$archive.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
# ── DIFF MODE ──
|
||||||
|
$modeName = "DIFF"
|
||||||
|
$zipFileName = "randomayzer-diff-review-$shortSha.zip"
|
||||||
|
$zipFilePath = [System.IO.Path]::Combine($OutputDir, $zipFileName)
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $zipFilePath) {
|
||||||
|
Remove-Item -LiteralPath $zipFilePath -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $Base) {
|
||||||
|
$Base = "HEAD^"
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseShaRaw = git rev-parse --verify "$Base" 2>$null
|
||||||
|
if (-not $baseShaRaw -or $LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Invalid base commit: '$Base'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$baseSha = $baseShaRaw.Trim()
|
||||||
|
$baseShortSha = (git rev-parse --short $baseSha 2>$null).Trim()
|
||||||
|
|
||||||
|
# Generate diff and list of changed files
|
||||||
|
$diffPatch = (git diff $baseSha HEAD) -join "`n"
|
||||||
|
$changedFiles = git diff --name-only $baseSha HEAD
|
||||||
|
$changedFilesList = @()
|
||||||
|
if ($changedFiles) {
|
||||||
|
$changedFilesList = @($changedFiles) | Where-Object { $_ -and $_.Trim().Length -gt 0 }
|
||||||
|
}
|
||||||
|
$changedFilesText = ($changedFilesList -join "`n")
|
||||||
|
|
||||||
|
# Determine files to include in Diff package:
|
||||||
|
# 1. Changed files present in HEAD (exclude deleted files)
|
||||||
|
# 2. All tracked test files (tests/*)
|
||||||
|
# 3. Security, config, and documentation context files
|
||||||
|
$trackedHeadFiles = git ls-tree -r --name-only HEAD
|
||||||
|
$filesToInclude = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
|
|
||||||
|
foreach ($cf in $changedFilesList) {
|
||||||
|
if ($trackedHeadFiles -contains $cf) {
|
||||||
|
$filesToInclude.Add($cf) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add all tracked test files for test integrity review
|
||||||
|
foreach ($tf in $trackedHeadFiles) {
|
||||||
|
if ($tf -like "tests/*" -or $tf -like "test/*") {
|
||||||
|
$filesToInclude.Add($tf) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add schema, dependencies, env.example, and documentation
|
||||||
|
$contextPatterns = @(
|
||||||
|
"package.json",
|
||||||
|
"package-lock.json",
|
||||||
|
"prisma/schema.prisma",
|
||||||
|
".env.example",
|
||||||
|
"README.md",
|
||||||
|
"AGENTS.md",
|
||||||
|
"GEMINI.md",
|
||||||
|
"docs/*"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($pattern in $contextPatterns) {
|
||||||
|
foreach ($hf in $trackedHeadFiles) {
|
||||||
|
if ($hf -like $pattern) {
|
||||||
|
# Security filter
|
||||||
|
if ($hf -match $sensitiveRegex -and $hf -notmatch '(?i)\.env\.example') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$filesToInclude.Add($hf) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stage files in temporary directory
|
||||||
|
$tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), ("randomayzer_export_" + [System.Guid]::NewGuid().ToString("N")))
|
||||||
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
Push-Location $gitRoot
|
||||||
|
try {
|
||||||
|
foreach ($relPath in $filesToInclude) {
|
||||||
|
$gitRelPath = $relPath -replace '\\', '/'
|
||||||
|
$destPath = [System.IO.Path]::Combine($tempDir, ($relPath -replace '/', [System.IO.Path]::DirectorySeparatorChar))
|
||||||
|
$destParent = [System.IO.Path]::GetDirectoryName($destPath)
|
||||||
|
if (-not (Test-Path -LiteralPath $destParent)) {
|
||||||
|
New-Item -ItemType Directory -Path $destParent -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract exact binary content from HEAD
|
||||||
|
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
|
$processInfo.FileName = "git"
|
||||||
|
$processInfo.Arguments = "show `"HEAD:$gitRelPath`""
|
||||||
|
$processInfo.WorkingDirectory = $gitRoot
|
||||||
|
$processInfo.UseShellExecute = $false
|
||||||
|
$processInfo.RedirectStandardOutput = $true
|
||||||
|
$processInfo.RedirectStandardError = $true
|
||||||
|
$processInfo.CreateNoWindow = $true
|
||||||
|
|
||||||
|
$process = [System.Diagnostics.Process]::Start($processInfo)
|
||||||
|
$outputStream = [System.IO.File]::Create($destPath)
|
||||||
|
try {
|
||||||
|
$process.StandardOutput.BaseStream.CopyTo($outputStream)
|
||||||
|
} finally {
|
||||||
|
$outputStream.Dispose()
|
||||||
|
}
|
||||||
|
$process.WaitForExit()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write diff artifacts
|
||||||
|
[System.IO.File]::WriteAllText([System.IO.Path]::Combine($tempDir, "REVIEW_DIFF.patch"), $diffPatch, $utf8NoBom)
|
||||||
|
[System.IO.File]::WriteAllText([System.IO.Path]::Combine($tempDir, "REVIEW_CHANGED_FILES.txt"), $changedFilesText, $utf8NoBom)
|
||||||
|
|
||||||
|
# Generate REVIEW_CONTEXT.md
|
||||||
|
$gitLogStat = (git log --stat "$baseSha..HEAD") -join "`n"
|
||||||
|
|
||||||
|
$reviewContextLines = @(
|
||||||
|
"# Randomayzer Review Snapshot (Diff Mode)",
|
||||||
|
"",
|
||||||
|
"- **Full commit SHA:** $headSha",
|
||||||
|
"- **Short SHA:** $shortSha",
|
||||||
|
"- **Branch:** $branch",
|
||||||
|
"- **Generated at:** $timestamp",
|
||||||
|
"- **Repository:** $remoteUrl",
|
||||||
|
"- **Snapshot mode:** DIFF",
|
||||||
|
"- **Base commit:** $baseSha ($baseShortSha)",
|
||||||
|
"- **Target commit:** $headSha ($shortSha)",
|
||||||
|
"- **Working tree dirty at export time:** $dirtyText",
|
||||||
|
"",
|
||||||
|
"## Source of Truth",
|
||||||
|
"The files in this archive are the exact tracked repository snapshot for the commit shown above.",
|
||||||
|
"This archive contains the diff against base commit, changed files, relevant test suites, schema definitions, and project documentation.",
|
||||||
|
"",
|
||||||
|
"## Reviewer Instructions",
|
||||||
|
"Do not use GitHub HEAD as source of truth.",
|
||||||
|
"Review the attached snapshot.",
|
||||||
|
"- Check `REVIEW_DIFF.patch` for the complete patch.",
|
||||||
|
"- Check `REVIEW_CHANGED_FILES.txt` for the list of modified files.",
|
||||||
|
"- Inspect the included source and test files.",
|
||||||
|
"",
|
||||||
|
"## Recent Commit Log ($baseShortSha..$shortSha)",
|
||||||
|
'```',
|
||||||
|
$gitLogStat,
|
||||||
|
'```'
|
||||||
|
)
|
||||||
|
$reviewContext = $reviewContextLines -join "`n"
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText([System.IO.Path]::Combine($tempDir, "REVIEW_CONTEXT.md"), $reviewContext, $utf8NoBom)
|
||||||
|
|
||||||
|
# Compress staged directory
|
||||||
|
[System.IO.Compression.ZipFile]::CreateFromDirectory($tempDir, $zipFilePath, [System.IO.Compression.CompressionLevel]::Optimal, $false)
|
||||||
|
} finally {
|
||||||
|
if (Test-Path -LiteralPath $tempDir) {
|
||||||
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 6. Verify Archive & Display Summary
|
||||||
|
$zip = [System.IO.Compression.ZipFile]::OpenRead($zipFilePath)
|
||||||
|
$fileCount = $zip.Entries.Count
|
||||||
|
$zip.Dispose()
|
||||||
|
|
||||||
|
$fileInfo = Get-Item -LiteralPath $zipFilePath
|
||||||
|
$sizeBytes = $fileInfo.Length
|
||||||
|
$sizeFormatted = if ($sizeBytes -ge 1MB) {
|
||||||
|
"{0:N2} MB ({1:N0} bytes)" -f ($sizeBytes / 1MB), $sizeBytes
|
||||||
|
} elseif ($sizeBytes -ge 1KB) {
|
||||||
|
"{0:N2} KB ({1:N0} bytes)" -f ($sizeBytes / 1KB), $sizeBytes
|
||||||
|
} else {
|
||||||
|
"{0} bytes" -f $sizeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "==================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " Randomayzer Review Package Exported Successfully " -ForegroundColor Cyan
|
||||||
|
Write-Host "==================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ("Created: " + $zipFilePath) -ForegroundColor Green
|
||||||
|
Write-Host ("Commit: " + $headSha + " (" + $shortSha + ")")
|
||||||
|
Write-Host ("Mode: " + $modeName)
|
||||||
|
Write-Host ("Files: " + $fileCount)
|
||||||
|
Write-Host ("Size: " + $sizeFormatted)
|
||||||
|
Write-Host "==================================================" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 7. Optional Clipboard Prompt
|
||||||
|
if ($CopyPrompt) {
|
||||||
|
$clipboardPromptLines = @(
|
||||||
|
"В приложенном архиве snapshot Randomayzer на commit $headSha.",
|
||||||
|
"Используй архив как source of truth.",
|
||||||
|
"Не используй GitHub HEAD вместо него.",
|
||||||
|
"Выполни ранее выданное security review задание."
|
||||||
|
)
|
||||||
|
$clipboardPrompt = $clipboardPromptLines -join "`n"
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Get-Command Set-Clipboard -ErrorAction SilentlyContinue) {
|
||||||
|
Set-Clipboard -Value $clipboardPrompt
|
||||||
|
Write-Host "Reviewer prompt copied to clipboard." -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
|
Write-Warning "Set-Clipboard is not available in this environment."
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Could not copy prompt to clipboard: $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue