refactor: remove unused rate-limit helpers and improve PowerShell scripts

- Remove unused _parse_rate_limit_headers() and _format_rate_limit_error()
  from src/specify_cli/__init__.py (56 lines of dead code)
- Add GENRELEASES_DIR override support to PowerShell release script with
  comprehensive safety checks (parity with bash script)
- Remove redundant shared-file update calls from PowerShell agent context
  script (AMP_FILE, KIRO_FILE, BOB_FILE, FORGE_FILE all resolve to AGENTS.md)
- Update test docstring to accurately reflect Forge's {{parameters}} token

Changes align PowerShell scripts with bash equivalents and reduce maintenance
burden by removing dead code.
This commit is contained in:
ericnoam
2026-04-02 22:43:47 +02:00
parent 053ee8a2aa
commit 59c4212a49
4 changed files with 58 additions and 65 deletions

View File

@@ -20,6 +20,10 @@
Comma or space separated subset of script types to build (default: both)
Valid scripts: sh, ps
.PARAMETER GenReleasesDir
Output directory for build artifacts (default: .genreleases in current directory)
Can also be set via GENRELEASES_DIR environment variable
.EXAMPLE
.\create-release-packages.ps1 -Version v0.2.0
@@ -28,6 +32,12 @@
.EXAMPLE
.\create-release-packages.ps1 -Version v0.2.0 -Agents claude -Scripts ps
.EXAMPLE
$env:GENRELEASES_DIR = "$env:TEMP/releases"; .\create-release-packages.ps1 -Version v0.2.0
.EXAMPLE
.\create-release-packages.ps1 -Version v0.2.0 -GenReleasesDir "$env:TEMP/releases"
#>
param(
@@ -38,7 +48,10 @@ param(
[string]$Agents = "",
[Parameter(Mandatory=$false)]
[string]$Scripts = ""
[string]$Scripts = "",
[Parameter(Mandatory=$false)]
[string]$GenReleasesDir = ""
)
$ErrorActionPreference = "Stop"
@@ -51,8 +64,49 @@ if ($Version -notmatch '^v\d+\.\d+\.\d+$') {
Write-Host "Building release packages for $Version"
# Create and use .genreleases directory for all build artifacts
$GenReleasesDir = ".genreleases"
# Resolve output directory: parameter > env var > default
if ([string]::IsNullOrEmpty($GenReleasesDir)) {
$GenReleasesDir = if ($env:GENRELEASES_DIR) { $env:GENRELEASES_DIR } else { ".genreleases" }
}
# Safety check: refuse empty output directory
if ([string]::IsNullOrWhiteSpace($GenReleasesDir)) {
Write-Error "Output directory must not be empty"
exit 1
}
# Safety check: refuse paths containing '..' segments (path traversal)
if ($GenReleasesDir -match '\.\.') {
Write-Error "Refusing to use output directory containing '..' path segments: $GenReleasesDir"
exit 1
}
# Convert to absolute path for safety checks
$GenReleasesDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($GenReleasesDir)
# Safety check: refuse to delete critical paths
$repoRoot = (Resolve-Path ".").Path
$forbiddenPaths = @(
$repoRoot,
(Join-Path $repoRoot ".git"),
(Join-Path $repoRoot "scripts"),
(Join-Path $repoRoot "templates"),
(Join-Path $repoRoot "src"),
$HOME,
[System.IO.Path]::GetTempPath().TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar),
[System.IO.Path]::GetPathRoot($repoRoot) # Root directory (e.g., C:\ or /)
)
foreach ($forbidden in $forbiddenPaths) {
if ($GenReleasesDir -eq $forbidden) {
Write-Error "Refusing to use '$GenReleasesDir' as output directory (safety check failed)"
exit 1
}
}
Write-Host "Output directory: $GenReleasesDir"
# Create and clean output directory
if (Test-Path $GenReleasesDir) {
Remove-Item -Path $GenReleasesDir -Recurse -Force -ErrorAction SilentlyContinue
}

View File

@@ -461,9 +461,6 @@ function Update-AllExistingAgents {
if (-not (Update-IfNew -FilePath $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }
if (-not (Update-IfNew -FilePath $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }
if (-not (Update-IfNew -FilePath $AGENTS_FILE -AgentName 'Codex/opencode/Amp/Kiro/Bob/Pi/Forge')) { $ok = $false }
if (-not (Update-IfNew -FilePath $AMP_FILE -AgentName 'Amp')) { $ok = $false }
if (-not (Update-IfNew -FilePath $KIRO_FILE -AgentName 'Kiro CLI')) { $ok = $false }
if (-not (Update-IfNew -FilePath $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }
if (-not (Update-IfNew -FilePath $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }
if (-not (Update-IfNew -FilePath $JUNIE_FILE -AgentName 'Junie')) { $ok = $false }
if (-not (Update-IfNew -FilePath $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }
@@ -478,7 +475,6 @@ function Update-AllExistingAgents {
if (-not (Update-IfNew -FilePath $KIMI_FILE -AgentName 'Kimi Code')) { $ok = $false }
if (-not (Update-IfNew -FilePath $TRAE_FILE -AgentName 'Trae')) { $ok = $false }
if (-not (Update-IfNew -FilePath $IFLOW_FILE -AgentName 'iFlow CLI')) { $ok = $false }
if (-not (Update-IfNew -FilePath $FORGE_FILE -AgentName 'Forge')) { $ok = $false }
if (-not $found) {
Write-Info 'No existing agent files found, creating default Claude file...'

View File

@@ -67,63 +67,6 @@ def _github_auth_headers(cli_token: str | None = None) -> dict:
token = _github_token(cli_token)
return {"Authorization": f"Bearer {token}"} if token else {}
def _parse_rate_limit_headers(headers: httpx.Headers) -> dict:
"""Extract and parse GitHub rate-limit headers."""
info = {}
# Standard GitHub rate-limit headers
if "X-RateLimit-Limit" in headers:
info["limit"] = headers.get("X-RateLimit-Limit")
if "X-RateLimit-Remaining" in headers:
info["remaining"] = headers.get("X-RateLimit-Remaining")
if "X-RateLimit-Reset" in headers:
reset_epoch = int(headers.get("X-RateLimit-Reset", "0"))
if reset_epoch:
reset_time = datetime.fromtimestamp(reset_epoch, tz=timezone.utc)
info["reset_epoch"] = reset_epoch
info["reset_time"] = reset_time
info["reset_local"] = reset_time.astimezone()
# Retry-After header (seconds or HTTP-date)
if "Retry-After" in headers:
retry_after = headers.get("Retry-After")
try:
info["retry_after_seconds"] = int(retry_after)
except ValueError:
# HTTP-date format - not implemented, just store as string
info["retry_after"] = retry_after
return info
def _format_rate_limit_error(status_code: int, headers: httpx.Headers, url: str) -> str:
"""Format a user-friendly error message with rate-limit information."""
rate_info = _parse_rate_limit_headers(headers)
lines = [f"GitHub API returned status {status_code} for {url}"]
lines.append("")
if rate_info:
lines.append("[bold]Rate Limit Information:[/bold]")
if "limit" in rate_info:
lines.append(f" • Rate Limit: {rate_info['limit']} requests/hour")
if "remaining" in rate_info:
lines.append(f" • Remaining: {rate_info['remaining']}")
if "reset_local" in rate_info:
reset_str = rate_info["reset_local"].strftime("%Y-%m-%d %H:%M:%S %Z")
lines.append(f" • Resets at: {reset_str}")
if "retry_after_seconds" in rate_info:
lines.append(f" • Retry after: {rate_info['retry_after_seconds']} seconds")
lines.append("")
# Add troubleshooting guidance
lines.append("[bold]Troubleshooting Tips:[/bold]")
lines.append(" • If you're on a shared CI or corporate environment, you may be rate-limited.")
lines.append(" • Consider using a GitHub token via --github-token or the GH_TOKEN/GITHUB_TOKEN")
lines.append(" environment variable to increase rate limits.")
lines.append(" • Authenticated requests have a limit of 5,000/hour vs 60/hour for unauthenticated.")
return "\n".join(lines)
def _build_agent_config() -> dict[str, dict[str, Any]]:
"""Derive AGENT_CONFIG from INTEGRATION_REGISTRY."""
from .integrations import INTEGRATION_REGISTRY

View File

@@ -15,7 +15,7 @@ Per-agent invariants verified
• File count matches the number of source templates
• Extension is correct: .toml (TOML agents), .agent.md (copilot), .md (rest)
• No unresolved placeholders remain ({SCRIPT}, {ARGS}, __AGENT__)
• Argument token is correct: {{args}} for TOML agents, $ARGUMENTS for others
• Argument token is correct: {{args}} for TOML agents, {{parameters}} for Forge, $ARGUMENTS for other non-TOML agents
• Path rewrites applied: scripts/ → .specify/scripts/ etc.
• TOML files have "description" and "prompt" fields
• Markdown files have parseable YAML frontmatter