mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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...'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user