repolaunch 1.4.0__py3-none-any.whl

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 (60) hide show
  1. launch/__init__.py +8 -0
  2. launch/agent/__init__.py +0 -0
  3. launch/agent/action_parser.py +29 -0
  4. launch/agent/locate.py +136 -0
  5. launch/agent/organize/__init__.py +0 -0
  6. launch/agent/organize/parselog.py +341 -0
  7. launch/agent/organize/rebuild.py +352 -0
  8. launch/agent/organize/save.py +152 -0
  9. launch/agent/organize/testall.py +502 -0
  10. launch/agent/organize/testone.py +283 -0
  11. launch/agent/prompt.py +43 -0
  12. launch/agent/setup/__init__.py +0 -0
  13. launch/agent/setup/base_image.py +88 -0
  14. launch/agent/setup/save.py +115 -0
  15. launch/agent/setup/setup.py +326 -0
  16. launch/agent/setup/verify.py +219 -0
  17. launch/agent/state.py +209 -0
  18. launch/api.py +66 -0
  19. launch/core/entry.py +97 -0
  20. launch/core/platforms/__init__.py +0 -0
  21. launch/core/platforms/android.py +146 -0
  22. launch/core/platforms/base.py +324 -0
  23. launch/core/platforms/linux.py +358 -0
  24. launch/core/platforms/windows.py +310 -0
  25. launch/core/runtime.py +84 -0
  26. launch/core/workflow.py +154 -0
  27. launch/run.py +376 -0
  28. launch/scripts/__init__.py +0 -0
  29. launch/scripts/adjacent_commit_run.py +522 -0
  30. launch/scripts/clear_failed_instance.py +34 -0
  31. launch/scripts/clear_image.py +26 -0
  32. launch/scripts/collect.py +66 -0
  33. launch/scripts/gen_dockerfile.py +228 -0
  34. launch/scripts/parser.py +52 -0
  35. launch/scripts/upload_docker.py +94 -0
  36. launch/utilities/__init__.py +0 -0
  37. launch/utilities/config.py +85 -0
  38. launch/utilities/dockerfiles/windows_server_c_cpp/ltsc2025_cmake_ninja_vsbuildtools_cl_msbuild/Dockerfile +15 -0
  39. launch/utilities/dockerfiles/windows_server_c_cpp/ltsc_2019_cmake_ninja_only/Dockerfile +27 -0
  40. launch/utilities/dockerfiles/windows_server_c_cpp/ltsc_2022_cmake_ninja_only/Dockerfile +27 -0
  41. launch/utilities/dockerfiles/windows_server_c_cpp/ltsc_2025_vsbuildtools/Dockerfile +28 -0
  42. launch/utilities/dockerfiles/windows_server_nvm/Dockerfile +19 -0
  43. launch/utilities/dockerfiles/windows_server_rust/170/Dockerfile +23 -0
  44. launch/utilities/dockerfiles/windows_server_rust/175/Dockerfile +23 -0
  45. launch/utilities/dockerfiles/windows_server_rust/180/Dockerfile +23 -0
  46. launch/utilities/dockerfiles/windows_server_rust/185/Dockerfile +23 -0
  47. launch/utilities/dockerfiles/windows_server_rust/190/Dockerfile +23 -0
  48. launch/utilities/get_repo_structure.py +85 -0
  49. launch/utilities/language_handlers.py +669 -0
  50. launch/utilities/llm.py +247 -0
  51. launch/utilities/logger.py +74 -0
  52. launch/utilities/timemachine.py +270 -0
  53. launch/utilities/tools/str_replace_editor.py +289 -0
  54. launch/utilities/workspace.py +221 -0
  55. repolaunch-1.4.0.dist-info/METADATA +111 -0
  56. repolaunch-1.4.0.dist-info/RECORD +60 -0
  57. repolaunch-1.4.0.dist-info/WHEEL +5 -0
  58. repolaunch-1.4.0.dist-info/entry_points.txt +2 -0
  59. repolaunch-1.4.0.dist-info/licenses/LICENSE +21 -0
  60. repolaunch-1.4.0.dist-info/top_level.txt +1 -0
launch/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ RepoLaunch - Turning Any Codebase into Testable Sandbox Environment
3
+
4
+ An LLM-based agentic workflow that automates the process of setting up
5
+ execution environments for any codebase.
6
+ """
7
+
8
+ __version__ = "0.2.0"
File without changes
@@ -0,0 +1,29 @@
1
+ """
2
+ Common action parsing utilities for agent interactions.
3
+ """
4
+ import re
5
+ from typing import Optional, Any
6
+ from abc import ABC, abstractmethod
7
+
8
+
9
+ class ActionParser(ABC):
10
+ """Base class for parsing LLM responses into structured actions."""
11
+
12
+ @abstractmethod
13
+ def parse(self, response: str) -> Optional[Any]:
14
+ """Parse response string into action object."""
15
+ pass
16
+
17
+ @staticmethod
18
+ def extract_tag_content(response: str, tag: str) -> Optional[str]:
19
+ """Extract content between XML-style tags."""
20
+ pattern = f"<{tag}>(.*?)</{tag}>"
21
+ match = re.search(pattern, response, re.DOTALL)
22
+ return match.group(1) if match else None
23
+
24
+ @staticmethod
25
+ def clean_response(response: str) -> str:
26
+ """Remove reasoning tags from response if present."""
27
+ if "<think>" in response:
28
+ return response.split("</think>")[1]
29
+ return response
launch/agent/locate.py ADDED
@@ -0,0 +1,136 @@
1
+ """
2
+ Repository analysis agent for locating environment setup documentation.
3
+ """
4
+ import os
5
+
6
+ from langchain.schema import HumanMessage
7
+
8
+ from launch.agent.state import AgentState, auto_catch
9
+ from launch.utilities.get_repo_structure import view_repo_structure
10
+ from launch.utilities.llm import form_llm_cost_log, update_accumulative_cost
11
+
12
+ prompt = """Given this repository structure:
13
+ ------ BEGIN REPOSITORY STRUCTURE ------
14
+ {structure}
15
+ ------ END REPOSITORY STRUCTURE ------
16
+
17
+ List the most relevant files for setting up a development environment, including:
18
+ 0. CI/CD configuration files
19
+ 1. README files
20
+ 2. Documentation
21
+ 3. Installation guides
22
+ 4. Development setup guides
23
+
24
+ Only list files that are critical for understanding project dependencies and setup requirements.
25
+ Format each file with its relative path (relative to project root) to be wrapped with tag <file> </file>, one per line."""
26
+
27
+
28
+ determine_prompt = """Given a file of the repository, determine if it is relevant for setting up a development environment for the repository or providing information about how to set up dev env (how to setup, install, test, etc.). This determines whether the file's content is fed to the LLM and helps it set up the environment.
29
+
30
+ ### File:
31
+ {file}
32
+
33
+ ### Reply with the following format:
34
+
35
+ <rel>Yes</rel>
36
+
37
+ or
38
+
39
+ <rel>No</rel>
40
+
41
+ Choose either Yes or No, Yes means this file IS relevant for setting up a dev env for the repository.
42
+ """
43
+
44
+ THRESHOLD = 128 * 1000 * 2
45
+
46
+ @auto_catch
47
+ def locate_related_file(state: AgentState) -> dict:
48
+ """
49
+ Analyze repository structure to identify files relevant for environment setup.
50
+
51
+ Uses LLM to scan repository structure and determine which files contain
52
+ information about dependencies, installation, and development setup.
53
+
54
+ Args:
55
+ state (AgentState): Current agent state with repo structure
56
+
57
+ Returns:
58
+ AgentState: Updated state with documentation content and related files
59
+ """
60
+ llm = state["llm"]
61
+ cost = state["cost"]
62
+ logger = state["logger"]
63
+ repo_structure = state["repo_structure"]
64
+
65
+ locate_prompt = HumanMessage(
66
+ content=prompt.format(structure=repo_structure)
67
+ )
68
+ if len(locate_prompt.content) > THRESHOLD:
69
+ repo_structure = view_repo_structure(state["repo_root"], 2)
70
+ locate_prompt = HumanMessage(
71
+ content=prompt.format(structure=repo_structure)
72
+ )
73
+
74
+ response = llm.invoke([locate_prompt])
75
+ update_accumulative_cost(cost["preparation"], response)
76
+ potential_files = [
77
+ line.split("<file>")[1].split("</file>")[0].strip()
78
+ for line in response.content.split("\n")
79
+ if line.strip() and "<file>" in line
80
+ ]
81
+ potential_files = [
82
+ file
83
+ for file in potential_files
84
+ if os.path.exists(os.path.join(state["repo_root"], file))
85
+ ]
86
+ potential_files = list(set(potential_files))
87
+
88
+ logger.info(f"Potential files: {potential_files} {form_llm_cost_log(response)}")
89
+ logger.info("Start determine relevance of these files...")
90
+ related_files = []
91
+
92
+ docs = "------ BEGIN RELATED FILES ------\n"
93
+ for file in potential_files:
94
+ path = os.path.join(state["repo_root"], file)
95
+ if not os.path.exists(path):
96
+ continue
97
+ if os.path.isdir(path):
98
+ continue
99
+ try:
100
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
101
+ content = f.read(THRESHOLD)
102
+ except Exception as e:
103
+ logger.info(f"Error reading file {file}: {e}")
104
+ continue
105
+
106
+ file_info = f"""------ START FILE {file} ------
107
+ {content}
108
+ ------ END FILE {file} ------"""
109
+ determine_input = HumanMessage(content=determine_prompt.format(file=file_info))
110
+
111
+ response = llm.invoke([determine_input])
112
+ update_accumulative_cost(cost["preparation"], response)
113
+ logger.info(f"File: {file} - {response.content} {form_llm_cost_log(response)}")
114
+ if "<rel>Yes</rel>" in response.content:
115
+ docs += f"File: {file}\n```\n"
116
+ docs += content + "\n"
117
+ docs += "```\n"
118
+ related_files.append(file)
119
+ docs += "------ END RELATED FILES ------\n"
120
+
121
+ logger.info(f"Located related files: {related_files}")
122
+
123
+ return {
124
+ "messages": [locate_prompt, response],
125
+ "docs": docs,
126
+ # We do not require the full repo structure later
127
+ "repo_structure": repo_structure,
128
+ "cost": cost,
129
+ }
130
+
131
+
132
+ if __name__ == "__main__":
133
+ from launch.agent.state import AgentState
134
+
135
+ state = AgentState()
136
+ locate_related_file(state)
File without changes
@@ -0,0 +1,341 @@
1
+ """
2
+ Log parser generation agent for improving test output parsing accuracy.
3
+ """
4
+ import json
5
+ from typing import Any, Literal
6
+
7
+ from langchain.schema import HumanMessage, SystemMessage
8
+ from pydantic import BaseModel, Field
9
+
10
+ from launch.agent.action_parser import ActionParser
11
+ from launch.agent.prompt import ReAct_prompt
12
+ from launch.agent.state import AgentState, auto_catch
13
+ from launch.scripts.parser import run_parser
14
+ from launch.utilities.llm import form_llm_cost_log, update_accumulative_cost
15
+
16
+ system_msg: str = """You are a developer specializing in test output analysis and parsing. Your task is to examine the test output, evaluate the current parser, and generate an improved, fully robust parser.
17
+
18
+ You have access to:
19
+ - **Raw test output** from the previous stage: {test_output}
20
+ - **Draft parser script** from the previous stage: {current_parser}
21
+ - **Draft parser results**: {current_results}
22
+
23
+ Your goal is to write a parser that it correctly extracts *every* test case and its status.
24
+
25
+ ## Your Tasks:
26
+ 1. **Analyze Draft Parser**: Evaluate how well the draft parser extracts test case statuses from the test output
27
+ 2. **Identify Improvement Opportunities**: Look for:
28
+ - Missed test cases that should have been parsed
29
+ - Incorrectly parsed test cases
30
+ - Edge cases not handled properly
31
+ - Parsing patterns that could be more robust
32
+
33
+ 3. **Generate Improved Parser**: Create a reliable parser that:
34
+ - Handles edge cases better
35
+ - Robust to output format variations
36
+ - Extracts granular test case information
37
+ - Make it as simple as possible while being effective
38
+
39
+ # REQUIRED PARSER OUTPUT
40
+ Your final parser MUST return **only** a dictionary in this exact form:
41
+ {{"test_case_name": "pass", "another_test": "fail", "third_test": "skip"}}
42
+ The test statuses can only be in {{"pass", "fail", "skip"}}. Any kinds of fail of error should be taken as 'fail'.
43
+
44
+ ## Hints for testcase extraction
45
+ Refer to the patterns below to identify test cases and their statuses.
46
+ XML (Maven, Gradle, JUnit, TestNG):
47
+ - Look for `<testsuite>` and `<testcase>` elements.
48
+ - Status rules: `<failure>` → fail, `<error>` → fail, `<skipped>` → skip, otherwise pass.
49
+ pytest:
50
+ - Lines like: `file.py::test_name PASSED/FAILED/SKIPPED/ERROR`.
51
+ unittest:
52
+ - Patterns such as: `TestClass.test_method ... ok/FAIL/ERROR`.
53
+ Jest:
54
+ - Symbols: `✓` pass, `✕` fail, `○` skip
55
+ - Or keywords: `PASS` / `FAIL`.
56
+ Go test:
57
+ - Lines like: `--- PASS: TestName`, `--- FAIL: TestName`, `--- SKIP: TestName`.
58
+ Other frameworks:
59
+ - Look for consistent use of `PASS`, `FAIL`, or `SKIP` near test case names.
60
+
61
+ You need to finish this in {steps} steps.
62
+ """
63
+
64
+
65
+ class ParseLogAction(BaseModel):
66
+ """
67
+ Analyze: Analyze the current parser performance and test output patterns
68
+ <analyze>your analysis of current parser issues and improvement opportunities</analyze>
69
+
70
+ Parse: Generate an improved parser script
71
+ The input log argument is from the test log from the previous stage. The system automatically passes the test log as the input when you use the <python></python> action and gives you the result of the script.
72
+ <python>def parser(log: str) -> dict[str, str]:
73
+ # Your improved parser implementation
74
+ import re
75
+ results = {}
76
+ # ... parsing logic ...
77
+ return results</python>
78
+
79
+ Submit: Submit the final improved parser
80
+ <submit>final parser is ready and tested</submit>
81
+ """
82
+
83
+ action: Literal["analyze", "python", "submit"] = Field(
84
+ "analyze", description="The action type"
85
+ )
86
+ args: Any = Field(None, description="The action arguments")
87
+
88
+
89
+ class ParseLogObservation(BaseModel):
90
+ """Observation for the parse log action"""
91
+
92
+ content: str = Field("", description="The content of the observation")
93
+ is_stop: bool = Field(False, description="Whether stop the parse log loop")
94
+
95
+
96
+ class ParseLogActionParser(ActionParser):
97
+ """Parser for parse log agent actions."""
98
+
99
+ def parse(self, response: str) -> ParseLogAction | None:
100
+ """Parse action from LLM response text."""
101
+ response = self.clean_response(response)
102
+
103
+ submit = self.extract_tag_content(response, "submit")
104
+ if submit:
105
+ return ParseLogAction(action="submit", args=submit)
106
+
107
+ script = self.extract_tag_content(response, "python")
108
+ if script:
109
+ return ParseLogAction(action="python", args=script)
110
+
111
+ analyze = self.extract_tag_content(response, "analyze")
112
+ if analyze:
113
+ return ParseLogAction(action="analyze", args=analyze)
114
+
115
+ return None
116
+
117
+
118
+ def parse_parselog_action(response: str) -> ParseLogAction | None:
119
+ """Parse parse log action from LLM response text."""
120
+ parser = ParseLogActionParser()
121
+ return parser.parse(response)
122
+
123
+
124
+ PARSELOG_CONVERSATION_WINDOW = 30
125
+
126
+
127
+ @auto_catch
128
+ def generate_log_parser(state: AgentState, max_steps: int = 20) -> dict:
129
+ """
130
+ Agent for generating improved log parsers based on test output analysis.
131
+
132
+ Args:
133
+ max_steps (int): Maximum number of steps allowed
134
+ state (AgentState): Current agent state with test results
135
+
136
+ Returns:
137
+ dict: Updated state with improved parser and results
138
+ """
139
+
140
+ improved_parser: str = ""
141
+ framework_detected: str = ""
142
+ analysis_result: str = ""
143
+ improved_test_status: dict[str, Literal['pass', 'fail', 'skip']] = {}
144
+ def observation_for_parselog_action(
145
+ state: AgentState, action: ParseLogAction | None
146
+ ) -> ParseLogObservation:
147
+ """Execute parse log action and return observation."""
148
+ nonlocal improved_parser, framework_detected, analysis_result, improved_test_status
149
+
150
+ if not action or not action.action:
151
+ content = f"""Please use the following format to make a valid action choice:\n{ParseLogAction.__doc__}"""
152
+ return ParseLogObservation(content=content, is_stop=False)
153
+
154
+ if action.action == "analyze":
155
+ analysis_result = action.args
156
+ content = f"Analysis completed: {action.args}\n\nNow try to write the parser."
157
+ return ParseLogObservation(content=content, is_stop=False)
158
+
159
+ elif action.action == "python":
160
+ improved_parser = action.args
161
+
162
+ if not improved_parser:
163
+ content = "No parser script available to test. Please create a parser first."
164
+ return ParseLogObservation(content=content, is_stop=False)
165
+
166
+ # Get test output from previous stage
167
+ test_output = state.get("test_output", "")
168
+ if not test_output:
169
+ # This should not happen as we store test_output in state at the beginning of generate_log_parser, but just in case...
170
+ raise ValueError("No test output available from previous stage to test against.")
171
+
172
+ # Test the improved parser
173
+ try:
174
+ result = run_parser(improved_parser, test_output)
175
+ if not isinstance(result, dict):
176
+ truncated_result = str(result)
177
+ if len(truncated_result) > 40000:
178
+ truncated_result = truncated_result[:40000] + "\n...result truncated due to length...\n"
179
+ content = (
180
+ f"Your python parser script must return a dict[str, Literal['pass', 'fail', 'skip']]."
181
+ f"However, it produced type {type(result).__name__}. The value or error your parser returned was:\n{truncated_result}\n"
182
+ "If the above is a traceback, fix the parser so it does not raise. "
183
+ "Note: the log passed into your parser is the raw stdout of your last command and may include the echoed command line "
184
+ "and the shell prompt, so your parser should be able to strip/ignore non-report lines.\n"
185
+ "Now adjust your parser script to make sure it returns the required format."
186
+ )
187
+ return ParseLogObservation(content=content, is_stop=False)
188
+ improved_test_status = result
189
+ truncated_result = json.dumps(result, indent=2)
190
+ if len(truncated_result) > 10000:
191
+ truncated_result = truncated_result[:10000] + "\n...result truncated due to length..."
192
+
193
+ # Compare with original results if available
194
+ original_results = state.get("test_status", {})
195
+ if original_results:
196
+ new_count = len(result)
197
+ old_count = len(original_results)
198
+ content = f"""Test results for improved parser:
199
+ {truncated_result}
200
+
201
+ Comparison with original parser:
202
+ - Original parser found: {old_count} test cases
203
+ - Improved parser found: {new_count} test cases
204
+ - Difference: {new_count - old_count} test cases
205
+
206
+ Please analyze if this is an improvement and submit if satisfied."""
207
+ else:
208
+ content = f"""Test results for improved parser:
209
+ {truncated_result}
210
+
211
+ Parser executed successfully. Please analyze the results and submit if satisfied."""
212
+
213
+ except Exception as e:
214
+ content = f"Error testing parser: {str(e)}\nPlease fix the parser and try again."
215
+
216
+ return ParseLogObservation(content=content, is_stop=False)
217
+
218
+
219
+ elif action.action == "submit":
220
+ if not improved_parser:
221
+ content = "No improved parser available to submit. Please create a parser with <python></python> action first."
222
+ return ParseLogObservation(content=content, is_stop=False)
223
+
224
+ if not improved_test_status:
225
+ content = f"Your last parser did not return any test case: {improved_test_status}. Please adjust your parser to make it able to extract test case names and their statuses from test log with <python></python> action."
226
+ return ParseLogObservation(content=content, is_stop=False)
227
+
228
+ return ParseLogObservation(content=action.args, is_stop=True)
229
+
230
+ return ParseLogObservation(content="Unknown action", is_stop=False)
231
+
232
+ if state["exception"]:
233
+ raise state["exception"]
234
+
235
+ session = state["session"]
236
+ llm = state["llm"]
237
+ cost = state["cost"]
238
+ logger = state["logger"]
239
+
240
+ # Get data from previous testall stage
241
+ test_output = ""
242
+ current_parser = ""
243
+ current_results = {}
244
+
245
+ # Rerun test commands and print commands to get fresh test output
246
+ test_commands = state.get("test_commands", [])
247
+ print_commands = state.get("print_commands", [])
248
+
249
+ if test_commands:
250
+ for cmd in test_commands:
251
+ logger.info(f"Rerunning test command: {cmd}")
252
+ session.send_command(cmd)
253
+
254
+ if print_commands:
255
+ for cmd in print_commands:
256
+ logger.info(f"Running print command: {cmd}")
257
+ result = session.send_command(cmd)
258
+ test_output += result.output
259
+
260
+
261
+ if "parser" in state:
262
+ current_parser = state.get("parser", "")
263
+
264
+ if "test_status" in state:
265
+ current_results = state.get("test_status", {})
266
+
267
+ logger.info("-" * 10 + "Start parse log conversation" + "-" * 10)
268
+
269
+ messages = [
270
+ SystemMessage(
271
+ system_msg.format(
272
+ test_output=test_output[:15000] + "..." if len(test_output) > 15000 else test_output,
273
+ current_parser=current_parser,
274
+ current_results=json.dumps(current_results, indent=2)[:2000] + "..." if len(json.dumps(current_results, indent=2)) > 2000 else json.dumps(current_results, indent=2),
275
+ steps=max_steps,
276
+ )
277
+ ),
278
+ HumanMessage(
279
+ ReAct_prompt.format(
280
+ tools=ParseLogAction.__doc__,
281
+ project_structure=state.get("repo_structure", ""),
282
+ docs=state.get("docs", ""),
283
+ )
284
+ ),
285
+ ]
286
+
287
+ prefix_messages = len(messages)
288
+ step = 0
289
+
290
+ # Store test_output in state for testing
291
+ state["test_output"] = test_output
292
+
293
+ while step < max_steps:
294
+
295
+ step += 1
296
+
297
+ # Use conversation window to avoid context overflow
298
+ if len(messages) < PARSELOG_CONVERSATION_WINDOW + prefix_messages:
299
+ input_messages = messages
300
+ else:
301
+ input_messages = (
302
+ messages[:prefix_messages] + messages[-PARSELOG_CONVERSATION_WINDOW:]
303
+ )
304
+
305
+ response = llm.invoke(input_messages)
306
+ update_accumulative_cost(cost["organize"], response)
307
+
308
+ logger.info(f"\n{response.pretty_repr()}\n\n{form_llm_cost_log(response)}\n")
309
+ messages.append(response)
310
+
311
+ action = parse_parselog_action(response.content)
312
+ observation = observation_for_parselog_action(state, action)
313
+
314
+ if observation.is_stop:
315
+ answer = observation.content
316
+ break
317
+
318
+ message = HumanMessage(f"Observation:\n{observation.content}")
319
+ logger.info("\n" + message.pretty_repr())
320
+ messages.append(message)
321
+
322
+ logger.info("-" * 10 + "End parse log conversation" + "-" * 10)
323
+
324
+ # Use the final improved parser if success else keep the old test status if the new parser does not return any result
325
+ if improved_test_status:
326
+ final_test_status = improved_test_status
327
+ final_parser = improved_parser
328
+ else:
329
+ final_test_status = state.get("test_status", {})
330
+ final_parser = state.get("parser", "")
331
+
332
+ return {
333
+ "messages": messages,
334
+ "parser": final_parser,
335
+ "framework_detected": framework_detected,
336
+ "analysis_result": analysis_result,
337
+ "test_status": final_test_status,
338
+ "success": bool(final_test_status and final_parser),
339
+ "test_output": test_output,
340
+ "cost": cost,
341
+ }