autoforge-ai 0.1.9 → 0.1.10

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.
@@ -90,13 +90,13 @@ Use browser automation tools:
90
90
 
91
91
  - Navigate to the app in a real browser
92
92
  - Interact like a human user (click, type, scroll)
93
- - Take screenshots at each step
93
+ - Take screenshots at each step (use inline screenshots only -- do NOT save screenshot files to disk)
94
94
  - Verify both functionality AND visual appearance
95
95
 
96
96
  **DO:**
97
97
 
98
98
  - Test through the UI with clicks and keyboard input
99
- - Take screenshots to verify visual appearance
99
+ - Take screenshots to verify visual appearance (inline only, never save to disk)
100
100
  - Check for console errors in browser
101
101
  - Verify complete user workflows end-to-end
102
102
 
@@ -194,6 +194,8 @@ Before context fills up:
194
194
 
195
195
  Use Playwright MCP tools (`browser_*`) for UI verification. Key tools: `navigate`, `click`, `type`, `fill_form`, `take_screenshot`, `console_messages`, `network_requests`. All tools have auto-wait built in.
196
196
 
197
+ **Screenshot rule:** Always use inline mode (base64). NEVER save screenshots as files to disk.
198
+
197
199
  Test like a human user with mouse and keyboard. Use `browser_console_messages` to detect errors. Don't bypass UI with JavaScript evaluation.
198
200
 
199
201
  ---
@@ -31,14 +31,14 @@ For the feature returned:
31
31
  1. Read and understand the feature's verification steps
32
32
  2. Navigate to the relevant part of the application
33
33
  3. Execute each verification step using browser automation
34
- 4. Take screenshots to document the verification
34
+ 4. Take screenshots to document the verification (inline only -- do NOT save to disk)
35
35
  5. Check for console errors
36
36
 
37
37
  Use browser automation tools:
38
38
 
39
39
  **Navigation & Screenshots:**
40
40
  - browser_navigate - Navigate to a URL
41
- - browser_take_screenshot - Capture screenshot (use for visual verification)
41
+ - browser_take_screenshot - Capture screenshot (inline mode only -- never save to disk)
42
42
  - browser_snapshot - Get accessibility tree snapshot
43
43
 
44
44
  **Element Interaction:**
@@ -79,7 +79,7 @@ A regression has been introduced. You MUST fix it:
79
79
 
80
80
  4. **Verify the fix:**
81
81
  - Run through all verification steps again
82
- - Take screenshots confirming the fix
82
+ - Take screenshots confirming the fix (inline only, never save to disk)
83
83
 
84
84
  5. **Mark as passing after fix:**
85
85
  ```
@@ -110,7 +110,7 @@ A regression has been introduced. You MUST fix it:
110
110
  All interaction tools have **built-in auto-wait** -- no manual timeouts needed.
111
111
 
112
112
  - `browser_navigate` - Navigate to URL
113
- - `browser_take_screenshot` - Capture screenshot
113
+ - `browser_take_screenshot` - Capture screenshot (inline only, never save to disk)
114
114
  - `browser_snapshot` - Get accessibility tree
115
115
  - `browser_click` - Click elements
116
116
  - `browser_type` - Type text
package/client.py CHANGED
@@ -446,6 +446,9 @@ def create_client(
446
446
  mcp_servers["playwright"] = {
447
447
  "command": "npx",
448
448
  "args": playwright_args,
449
+ "env": {
450
+ "NODE_COMPILE_CACHE": "", # Disable V8 compile caching to prevent .node file accumulation in %TEMP%
451
+ },
449
452
  }
450
453
 
451
454
  # Build environment overrides for API endpoint configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autoforge-ai",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Autonomous coding agent with web UI - build complete apps with AI",
5
5
  "license": "AGPL-3.0",
6
6
  "bin": {
@@ -846,7 +846,7 @@ class ParallelOrchestrator:
846
846
  "encoding": "utf-8",
847
847
  "errors": "replace",
848
848
  "cwd": str(self.project_dir), # Run from project dir so CLI creates .claude/ in project
849
- "env": {**os.environ, "PYTHONUNBUFFERED": "1"},
849
+ "env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
850
850
  }
851
851
  if sys.platform == "win32":
852
852
  popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
@@ -909,7 +909,7 @@ class ParallelOrchestrator:
909
909
  "encoding": "utf-8",
910
910
  "errors": "replace",
911
911
  "cwd": str(self.project_dir), # Run from project dir so CLI creates .claude/ in project
912
- "env": {**os.environ, "PYTHONUNBUFFERED": "1"},
912
+ "env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
913
913
  }
914
914
  if sys.platform == "win32":
915
915
  popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
@@ -1013,7 +1013,7 @@ class ParallelOrchestrator:
1013
1013
  "encoding": "utf-8",
1014
1014
  "errors": "replace",
1015
1015
  "cwd": str(self.project_dir), # Run from project dir so CLI creates .claude/ in project
1016
- "env": {**os.environ, "PYTHONUNBUFFERED": "1"},
1016
+ "env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
1017
1017
  }
1018
1018
  if sys.platform == "win32":
1019
1019
  popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
@@ -1074,7 +1074,7 @@ class ParallelOrchestrator:
1074
1074
  "encoding": "utf-8",
1075
1075
  "errors": "replace",
1076
1076
  "cwd": str(AUTOFORGE_ROOT),
1077
- "env": {**os.environ, "PYTHONUNBUFFERED": "1"},
1077
+ "env": {**os.environ, "PYTHONUNBUFFERED": "1", "NODE_COMPILE_CACHE": ""},
1078
1078
  }
1079
1079
  if sys.platform == "win32":
1080
1080
  popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
@@ -1160,6 +1160,19 @@ class ParallelOrchestrator:
1160
1160
  debug_log.log("CLEANUP", f"Error killing process tree for {agent_type} agent", error=str(e))
1161
1161
  self._on_agent_complete(feature_id, proc.returncode, agent_type, proc)
1162
1162
 
1163
+ def _run_inter_session_cleanup(self):
1164
+ """Run lightweight cleanup between agent sessions.
1165
+
1166
+ Removes stale temp files and project screenshots to prevent
1167
+ disk space accumulation during long overnight runs.
1168
+ """
1169
+ try:
1170
+ from temp_cleanup import cleanup_project_screenshots, cleanup_stale_temp
1171
+ cleanup_stale_temp()
1172
+ cleanup_project_screenshots(self.project_dir)
1173
+ except Exception as e:
1174
+ debug_log.log("CLEANUP", f"Inter-session cleanup failed (non-fatal): {e}")
1175
+
1163
1176
  def _signal_agent_completed(self):
1164
1177
  """Signal that an agent has completed, waking the main loop.
1165
1178
 
@@ -1235,6 +1248,8 @@ class ParallelOrchestrator:
1235
1248
  pid=proc.pid,
1236
1249
  feature_id=feature_id,
1237
1250
  status=status)
1251
+ # Run lightweight cleanup between sessions
1252
+ self._run_inter_session_cleanup()
1238
1253
  # Signal main loop that an agent slot is available
1239
1254
  self._signal_agent_completed()
1240
1255
  return
@@ -1301,6 +1316,8 @@ class ParallelOrchestrator:
1301
1316
  else:
1302
1317
  print(f"Feature #{feature_id} {status}", flush=True)
1303
1318
 
1319
+ # Run lightweight cleanup between sessions
1320
+ self._run_inter_session_cleanup()
1304
1321
  # Signal main loop that an agent slot is available
1305
1322
  self._signal_agent_completed()
1306
1323
 
package/server/main.py CHANGED
@@ -61,6 +61,17 @@ UI_DIST_DIR = ROOT_DIR / "ui" / "dist"
61
61
  @asynccontextmanager
62
62
  async def lifespan(app: FastAPI):
63
63
  """Lifespan context manager for startup and shutdown."""
64
+ # Startup - clean up stale temp files (Playwright profiles, .node cache, etc.)
65
+ try:
66
+ from temp_cleanup import cleanup_stale_temp
67
+ stats = cleanup_stale_temp()
68
+ if stats["dirs_deleted"] > 0 or stats["files_deleted"] > 0:
69
+ mb_freed = stats["bytes_freed"] / (1024 * 1024)
70
+ logger.info("Startup temp cleanup: %d dirs, %d files, %.1f MB freed",
71
+ stats["dirs_deleted"], stats["files_deleted"], mb_freed)
72
+ except Exception as e:
73
+ logger.warning("Startup temp cleanup failed (non-fatal): %s", e)
74
+
64
75
  # Startup - clean up orphaned lock files from previous runs
65
76
  cleanup_orphaned_locks()
66
77
  cleanup_orphaned_devserver_locks()
@@ -410,6 +410,7 @@ class AgentProcessManager:
410
410
  **os.environ,
411
411
  "PYTHONUNBUFFERED": "1",
412
412
  "PLAYWRIGHT_HEADLESS": "true" if playwright_headless else "false",
413
+ "NODE_COMPILE_CACHE": "", # Disable V8 compile caching to prevent .node file accumulation in %TEMP%
413
414
  **api_env,
414
415
  }
415
416
 
package/temp_cleanup.py CHANGED
@@ -37,11 +37,12 @@ DIR_PATTERNS = [
37
37
  "mongodb-memory-server*", # MongoDB Memory Server binaries
38
38
  "ng-*", # Angular CLI temp directories
39
39
  "scoped_dir*", # Chrome/Chromium temp directories
40
+ "node-compile-cache", # Node.js V8 compile cache directory
40
41
  ]
41
42
 
42
43
  # File patterns to clean up (glob patterns)
43
44
  FILE_PATTERNS = [
44
- ".78912*.node", # Node.js native module cache (major space consumer, ~7MB each)
45
+ ".[0-9a-f]*.node", # Node.js/V8 compile cache files (~7MB each, varying hex prefixes)
45
46
  "claude-*-cwd", # Claude CLI working directory temp files
46
47
  "mat-debug-*.log", # Material/Angular debug logs
47
48
  ]
@@ -122,6 +123,54 @@ def cleanup_stale_temp(max_age_seconds: int = MAX_AGE_SECONDS) -> dict:
122
123
  return stats
123
124
 
124
125
 
126
+ def cleanup_project_screenshots(project_dir: Path, max_age_seconds: int = 300) -> dict:
127
+ """
128
+ Clean up stale screenshot files from the project root.
129
+
130
+ Playwright browser verification can leave .png files in the project
131
+ directory. This removes them after they've aged out (default 5 minutes).
132
+
133
+ Args:
134
+ project_dir: Path to the project directory.
135
+ max_age_seconds: Maximum age in seconds before a screenshot is deleted.
136
+ Defaults to 5 minutes (300 seconds).
137
+
138
+ Returns:
139
+ Dictionary with cleanup statistics (files_deleted, bytes_freed, errors).
140
+ """
141
+ cutoff_time = time.time() - max_age_seconds
142
+ stats: dict = {"files_deleted": 0, "bytes_freed": 0, "errors": []}
143
+
144
+ screenshot_patterns = [
145
+ "feature*-*.png",
146
+ "screenshot-*.png",
147
+ "step-*.png",
148
+ ]
149
+
150
+ for pattern in screenshot_patterns:
151
+ for item in project_dir.glob(pattern):
152
+ if not item.is_file():
153
+ continue
154
+ try:
155
+ mtime = item.stat().st_mtime
156
+ if mtime < cutoff_time:
157
+ size = item.stat().st_size
158
+ item.unlink(missing_ok=True)
159
+ if not item.exists():
160
+ stats["files_deleted"] += 1
161
+ stats["bytes_freed"] += size
162
+ logger.debug(f"Deleted project screenshot: {item}")
163
+ except Exception as e:
164
+ stats["errors"].append(f"Failed to delete {item}: {e}")
165
+ logger.debug(f"Failed to delete screenshot {item}: {e}")
166
+
167
+ if stats["files_deleted"] > 0:
168
+ mb_freed = stats["bytes_freed"] / (1024 * 1024)
169
+ logger.info(f"Screenshot cleanup: {stats['files_deleted']} files, {mb_freed:.1f} MB freed")
170
+
171
+ return stats
172
+
173
+
125
174
  def _get_dir_size(path: Path) -> int:
126
175
  """Get total size of a directory in bytes."""
127
176
  total = 0