autoforge-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.claude/commands/check-code.md +32 -0
  2. package/.claude/commands/checkpoint.md +40 -0
  3. package/.claude/commands/create-spec.md +613 -0
  4. package/.claude/commands/expand-project.md +234 -0
  5. package/.claude/commands/gsd-to-autoforge-spec.md +10 -0
  6. package/.claude/commands/review-pr.md +75 -0
  7. package/.claude/templates/app_spec.template.txt +331 -0
  8. package/.claude/templates/coding_prompt.template.md +265 -0
  9. package/.claude/templates/initializer_prompt.template.md +354 -0
  10. package/.claude/templates/testing_prompt.template.md +146 -0
  11. package/.env.example +64 -0
  12. package/LICENSE.md +676 -0
  13. package/README.md +423 -0
  14. package/agent.py +444 -0
  15. package/api/__init__.py +10 -0
  16. package/api/database.py +536 -0
  17. package/api/dependency_resolver.py +449 -0
  18. package/api/migration.py +156 -0
  19. package/auth.py +83 -0
  20. package/autoforge_paths.py +315 -0
  21. package/autonomous_agent_demo.py +293 -0
  22. package/bin/autoforge.js +3 -0
  23. package/client.py +607 -0
  24. package/env_constants.py +27 -0
  25. package/examples/OPTIMIZE_CONFIG.md +230 -0
  26. package/examples/README.md +531 -0
  27. package/examples/org_config.yaml +172 -0
  28. package/examples/project_allowed_commands.yaml +139 -0
  29. package/lib/cli.js +791 -0
  30. package/mcp_server/__init__.py +1 -0
  31. package/mcp_server/feature_mcp.py +988 -0
  32. package/package.json +53 -0
  33. package/parallel_orchestrator.py +1800 -0
  34. package/progress.py +247 -0
  35. package/prompts.py +427 -0
  36. package/pyproject.toml +17 -0
  37. package/rate_limit_utils.py +132 -0
  38. package/registry.py +614 -0
  39. package/requirements-prod.txt +14 -0
  40. package/security.py +959 -0
  41. package/server/__init__.py +17 -0
  42. package/server/main.py +261 -0
  43. package/server/routers/__init__.py +32 -0
  44. package/server/routers/agent.py +177 -0
  45. package/server/routers/assistant_chat.py +327 -0
  46. package/server/routers/devserver.py +309 -0
  47. package/server/routers/expand_project.py +239 -0
  48. package/server/routers/features.py +746 -0
  49. package/server/routers/filesystem.py +514 -0
  50. package/server/routers/projects.py +524 -0
  51. package/server/routers/schedules.py +356 -0
  52. package/server/routers/settings.py +127 -0
  53. package/server/routers/spec_creation.py +357 -0
  54. package/server/routers/terminal.py +453 -0
  55. package/server/schemas.py +593 -0
  56. package/server/services/__init__.py +36 -0
  57. package/server/services/assistant_chat_session.py +496 -0
  58. package/server/services/assistant_database.py +304 -0
  59. package/server/services/chat_constants.py +57 -0
  60. package/server/services/dev_server_manager.py +557 -0
  61. package/server/services/expand_chat_session.py +399 -0
  62. package/server/services/process_manager.py +657 -0
  63. package/server/services/project_config.py +475 -0
  64. package/server/services/scheduler_service.py +683 -0
  65. package/server/services/spec_chat_session.py +502 -0
  66. package/server/services/terminal_manager.py +756 -0
  67. package/server/utils/__init__.py +1 -0
  68. package/server/utils/process_utils.py +134 -0
  69. package/server/utils/project_helpers.py +32 -0
  70. package/server/utils/validation.py +54 -0
  71. package/server/websocket.py +903 -0
  72. package/start.py +456 -0
  73. package/ui/dist/assets/index-8W_wmZzz.js +168 -0
  74. package/ui/dist/assets/index-B47Ubhox.css +1 -0
  75. package/ui/dist/assets/vendor-flow-CVNK-_lx.js +7 -0
  76. package/ui/dist/assets/vendor-query-BUABzP5o.js +1 -0
  77. package/ui/dist/assets/vendor-radix-DTNNCg2d.js +45 -0
  78. package/ui/dist/assets/vendor-react-qkC6yhPU.js +1 -0
  79. package/ui/dist/assets/vendor-utils-COeKbHgx.js +2 -0
  80. package/ui/dist/assets/vendor-xterm-DP_gxef0.js +16 -0
  81. package/ui/dist/index.html +23 -0
  82. package/ui/dist/ollama.png +0 -0
  83. package/ui/dist/vite.svg +6 -0
  84. package/ui/package.json +57 -0
@@ -0,0 +1,132 @@
1
+ """
2
+ Rate Limit Utilities
3
+ ====================
4
+
5
+ Shared utilities for detecting and handling API rate limits.
6
+ Used by both agent.py (production) and test_rate_limit_utils.py (tests).
7
+ """
8
+
9
+ import random
10
+ import re
11
+ from typing import Optional
12
+
13
+ # Regex patterns for rate limit detection (used in both exception messages and response text)
14
+ # These patterns use word boundaries to avoid false positives like "PR #429" or "please wait while I..."
15
+ RATE_LIMIT_REGEX_PATTERNS = [
16
+ r"\brate[_\s]?limit", # "rate limit", "rate_limit", "ratelimit"
17
+ r"\btoo\s+many\s+requests", # "too many requests"
18
+ r"\bhttp\s*429\b", # "http 429", "http429"
19
+ r"\bstatus\s*429\b", # "status 429", "status429"
20
+ r"\berror\s*429\b", # "error 429", "error429"
21
+ r"\b429\s+too\s+many", # "429 too many"
22
+ r"\b(?:server|api|system)\s+(?:is\s+)?overloaded\b", # "server is overloaded", "api overloaded"
23
+ r"\bquota\s*exceeded\b", # "quota exceeded"
24
+ ]
25
+
26
+ # Compiled regex for efficient matching
27
+ _RATE_LIMIT_REGEX = re.compile(
28
+ "|".join(RATE_LIMIT_REGEX_PATTERNS),
29
+ re.IGNORECASE
30
+ )
31
+
32
+
33
+ def parse_retry_after(error_message: str) -> Optional[int]:
34
+ """
35
+ Extract retry-after seconds from various error message formats.
36
+
37
+ Handles common formats:
38
+ - "Retry-After: 60"
39
+ - "retry after 60 seconds"
40
+ - "try again in 5 seconds"
41
+ - "30 seconds remaining"
42
+
43
+ Args:
44
+ error_message: The error message to parse
45
+
46
+ Returns:
47
+ Seconds to wait, or None if not parseable.
48
+ """
49
+ # Patterns require explicit "seconds" or "s" unit, OR no unit at all (end of string/sentence)
50
+ # This prevents matching "30 minutes" or "1 hour" since those have non-seconds units
51
+ patterns = [
52
+ r"retry.?after[:\s]+(\d+)\s*(?:seconds?|s\b)", # Requires seconds unit
53
+ r"retry.?after[:\s]+(\d+)(?:\s*$|\s*[,.])", # Or end of string/sentence
54
+ r"try again in\s+(\d+)\s*(?:seconds?|s\b)", # Requires seconds unit
55
+ r"try again in\s+(\d+)(?:\s*$|\s*[,.])", # Or end of string/sentence
56
+ r"(\d+)\s*seconds?\s*(?:remaining|left|until)",
57
+ ]
58
+
59
+ for pattern in patterns:
60
+ match = re.search(pattern, error_message, re.IGNORECASE)
61
+ if match:
62
+ return int(match.group(1))
63
+
64
+ return None
65
+
66
+
67
+ def is_rate_limit_error(error_message: str) -> bool:
68
+ """
69
+ Detect if an error message indicates a rate limit.
70
+
71
+ Uses regex patterns with word boundaries to avoid false positives
72
+ like "PR #429", "please wait while I...", or "Node v14.29.0".
73
+
74
+ Args:
75
+ error_message: The error message to check
76
+
77
+ Returns:
78
+ True if the message indicates a rate limit, False otherwise.
79
+ """
80
+ return bool(_RATE_LIMIT_REGEX.search(error_message))
81
+
82
+
83
+ def calculate_rate_limit_backoff(retries: int) -> int:
84
+ """
85
+ Calculate exponential backoff with jitter for rate limits.
86
+
87
+ Base formula: min(15 * 2^retries, 3600)
88
+ Jitter: adds 0-30% random jitter to prevent thundering herd.
89
+ Base sequence: ~15-20s, ~30-40s, ~60-78s, ~120-156s, ...
90
+
91
+ The lower starting delay (15s vs 60s) allows faster recovery from
92
+ transient rate limits, while jitter prevents synchronized retries
93
+ when multiple agents hit limits simultaneously.
94
+
95
+ Args:
96
+ retries: Number of consecutive rate limit retries (0-indexed)
97
+
98
+ Returns:
99
+ Delay in seconds (clamped to 1-3600 range, with jitter)
100
+ """
101
+ base = int(min(max(15 * (2 ** retries), 1), 3600))
102
+ jitter = random.uniform(0, base * 0.3)
103
+ return int(base + jitter)
104
+
105
+
106
+ def calculate_error_backoff(retries: int) -> int:
107
+ """
108
+ Calculate linear backoff for non-rate-limit errors.
109
+
110
+ Formula: min(30 * retries, 300) - caps at 5 minutes
111
+ Sequence: 30s, 60s, 90s, 120s, ... 300s
112
+
113
+ Args:
114
+ retries: Number of consecutive error retries (1-indexed)
115
+
116
+ Returns:
117
+ Delay in seconds (clamped to 1-300 range)
118
+ """
119
+ return min(max(30 * retries, 1), 300)
120
+
121
+
122
+ def clamp_retry_delay(delay_seconds: int) -> int:
123
+ """
124
+ Clamp a retry delay to a safe range (1-3600 seconds).
125
+
126
+ Args:
127
+ delay_seconds: The raw delay value
128
+
129
+ Returns:
130
+ Delay clamped to 1-3600 seconds
131
+ """
132
+ return min(max(delay_seconds, 1), 3600)