param( [ValidateSet('ip', 'storage', 'billing', 'governance', 'all')] [string]$Group = 'all', [switch]$Apply ) $ErrorActionPreference = 'Stop' function Write-Section([string]$Title) { Write-Host "" Write-Host "==== $Title ====" -ForegroundColor Cyan } function Invoke-GitAdd([string]$Name, [string[]]$Paths) { Write-Section "stage group: $Name" if (-not $Paths -or $Paths.Count -eq 0) { Write-Host "No paths configured. Skip." return } if (-not $Apply) { Write-Host "Dry run only. These paths would be staged:" $Paths | ForEach-Object { Write-Host " $_" } return } git add -- @Paths if ($LASTEXITCODE -ne 0) { throw "git add failed: $Name" } } function Test-ForbiddenStagedFiles { $forbiddenPatterns = @( '^docs/', '^data/', '^tmp/', '^test-results/', '^playwright-report/', '^blob-report/', '^coverage/', '^cloud-functions/deployable/', '^\.env($|\.)', '^deploy\.ps1$', '^src/video/', '\.xlsx$', '\.xls$' ) $staged = git diff --cached --name-only $bad = @() foreach ($file in $staged) { foreach ($pattern in $forbiddenPatterns) { if ($file -match $pattern) { $bad += $file break } } } if ($bad.Count -gt 0) { Write-Section "forbidden staged files" $bad | Sort-Object -Unique | ForEach-Object { Write-Host " $_" -ForegroundColor Red } Write-Host "" Write-Host "Abort. Run: git restore --staged " -ForegroundColor Yellow exit 1 } } function Show-CredentialReminder { Write-Section "credential reminder" Write-Host "Before commit, manually verify there are no real tokens/API keys in:" Write-Host " cloud-functions/06-voiceManager.js" Write-Host " cloud-functions/11-jimengManager.js" Write-Host " cloud-functions/13-douyinInsightManager.js" Write-Host " cloud-functions/08-proxyHub.js" Write-Host " cloud-functions/12-douyinManager.js" } $groups = @{ ip = @( 'src/app/pages/ip-operator', 'src/app/models/ip-operator.model.ts', 'src/app/services/ip-account-*', 'src/app/services/ip-operator-*', 'src/app/services/ip-content-production-flow*', 'src/app/services/ip-publish-*', 'src/app/services/ip-script-*', 'src/app/services/ip-topic-script-generator*', 'src/app/services/reference-video-prompt-planner*', 'src/app/services/retrospective.service*', 'src/app/services/viral-analysis.service*', 'e2e/ip-operator-*', 'src/app/app.ts', 'src/app/app.html', 'src/app/app.spec.ts', 'src/app/components/app-sidebar/app-sidebar.component.ts', 'src/app/models/app-tab.model.ts' ) storage = @( 'cloud-functions/_session.js', 'cloud-functions/_session.spec.js', 'cloud-functions/_parseClassStore.js', 'cloud-functions/13-douyinInsightManager.js', 'cloud-functions/13-douyinInsightManager.spec.js', 'cloud-functions/14-systemStorageManager.js', 'cloud-functions/15-fileAssetManager.js', 'cloud-functions/09-uploadManager.js', 'scripts/build-cloud-functions.mjs', 'scripts/smoke-cloud-functions.mjs', 'scripts/validation/cloud-functions-readiness.mjs', 'scripts/validation/parse-storage-postdeploy.mjs', 'scripts/validation/storage-governance-postdeploy.mjs', 'scripts/validation/storage-sensitive-policy.mjs', 'src/app/services/cloud-session-storage*', 'src/app/services/file-asset*', 'src/app/services/storage-governance*', 'src/app/services/system-storage-migration*', 'src/app/services/douyin-insight*', 'src/app/services/douyin-api*' ) billing = @( 'cloud-functions/10-authCreditManager.js', 'cloud-functions/11-jimengManager.js', 'cloud-functions/auth-credit-deploy.md', 'src/app/services/auth-credit*', 'src/app/services/jimeng*', 'src/app/services/cost-estimator*', 'src/app/pages/account/user-center.component*', 'src/app/pages/pipelines/image-to-video', 'src/app/pages/pipelines/image-generation/image-generation.component.ts', 'src/app/pages/pipelines/action-transfer/action-transfer.component.ts', 'src/app/pages/pipelines/asset-remix/asset-remix.component.ts', 'src/app/pages/pipelines/topic-to-video/topic-to-video.component.ts' ) governance = @( 'AGENTS.md', '.githooks/pre-commit', '.gitignore', 'README.md', 'package.json', 'angular.json', 'cloud-functions/DEPLOY.md', 'scripts/git-stage-safe.ps1', 'scripts/validation/ip-operator-cleanup-dry-run.mjs', 'scripts/validation/ip-operator-cleanup-soft-delete.mjs', 'scripts/validation/ip-operator-cleanup-physical-delete.mjs', 'scripts/validation/videoworkflow-parse-cleanup.mjs', 'scripts/validation/ip-operator-scenario-seed.js', 'scripts/validation/jimeng-billing-policy.mjs' ) } Write-Section "current git status" git status --short if ($Group -eq 'all') { Invoke-GitAdd 'ip' $groups.ip Invoke-GitAdd 'storage' $groups.storage Invoke-GitAdd 'billing' $groups.billing Invoke-GitAdd 'governance' $groups.governance } else { Invoke-GitAdd $Group $groups[$Group] } if ($Apply) { Test-ForbiddenStagedFiles Write-Section "staged files" git diff --cached --name-status } else { Write-Section "usage" Write-Host "Dry run group: powershell -ExecutionPolicy Bypass -File scripts/git-stage-safe.ps1 -Group ip" Write-Host "Stage group: powershell -ExecutionPolicy Bypass -File scripts/git-stage-safe.ps1 -Group ip -Apply" Write-Host "Stage all: powershell -ExecutionPolicy Bypass -File scripts/git-stage-safe.ps1 -Group all -Apply" } Show-CredentialReminder