robopilot 1.1.0__tar.gz
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.
- robopilot-1.1.0/LICENSE +21 -0
- robopilot-1.1.0/PKG-INFO +207 -0
- robopilot-1.1.0/README.md +174 -0
- robopilot-1.1.0/pyproject.toml +66 -0
- robopilot-1.1.0/setup.cfg +4 -0
- robopilot-1.1.0/src/robopilot/__init__.py +0 -0
- robopilot-1.1.0/src/robopilot/apply/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/apply/apply_plan.py +218 -0
- robopilot-1.1.0/src/robopilot/apply_plan/__init__.py +13 -0
- robopilot-1.1.0/src/robopilot/apply_plan/plan.py +216 -0
- robopilot-1.1.0/src/robopilot/apply_preview/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/apply_preview/preview.py +156 -0
- robopilot-1.1.0/src/robopilot/debugger/__init__.py +6 -0
- robopilot-1.1.0/src/robopilot/debugger/log_analyzer.py +198 -0
- robopilot-1.1.0/src/robopilot/deps/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/deps/analyzer.py +440 -0
- robopilot-1.1.0/src/robopilot/detector/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/detector/project_detector.py +349 -0
- robopilot-1.1.0/src/robopilot/diff/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/diff/spec_diff.py +189 -0
- robopilot-1.1.0/src/robopilot/generator/__init__.py +2 -0
- robopilot-1.1.0/src/robopilot/generator/project_generator.py +106 -0
- robopilot-1.1.0/src/robopilot/generator/project_spec.py +78 -0
- robopilot-1.1.0/src/robopilot/generator/task_classifier.py +55 -0
- robopilot-1.1.0/src/robopilot/generator/template_registry.py +177 -0
- robopilot-1.1.0/src/robopilot/generator/templates.py +541 -0
- robopilot-1.1.0/src/robopilot/graph/__init__.py +6 -0
- robopilot-1.1.0/src/robopilot/graph/mermaid_generator.py +34 -0
- robopilot-1.1.0/src/robopilot/history/__init__.py +15 -0
- robopilot-1.1.0/src/robopilot/history/journal.py +226 -0
- robopilot-1.1.0/src/robopilot/inspector/__init__.py +6 -0
- robopilot-1.1.0/src/robopilot/inspector/project_inspector.py +287 -0
- robopilot-1.1.0/src/robopilot/main.py +1294 -0
- robopilot-1.1.0/src/robopilot/migration/__init__.py +31 -0
- robopilot-1.1.0/src/robopilot/migration/plan_diff.py +177 -0
- robopilot-1.1.0/src/robopilot/migration/plan_validator.py +142 -0
- robopilot-1.1.0/src/robopilot/migration/preview.py +267 -0
- robopilot-1.1.0/src/robopilot/migration/ros1_to_ros2.py +579 -0
- robopilot-1.1.0/src/robopilot/planner/__init__.py +26 -0
- robopilot-1.1.0/src/robopilot/planner/base.py +30 -0
- robopilot-1.1.0/src/robopilot/planner/llm_planner.py +127 -0
- robopilot-1.1.0/src/robopilot/planner/openai_client.py +67 -0
- robopilot-1.1.0/src/robopilot/planner/prompts.py +59 -0
- robopilot-1.1.0/src/robopilot/planner/provider_config.py +43 -0
- robopilot-1.1.0/src/robopilot/planner/rule_based_planner.py +40 -0
- robopilot-1.1.0/src/robopilot/refiner/__init__.py +6 -0
- robopilot-1.1.0/src/robopilot/refiner/llm_refiner.py +126 -0
- robopilot-1.1.0/src/robopilot/refiner/spec_refiner.py +172 -0
- robopilot-1.1.0/src/robopilot/repair/__init__.py +9 -0
- robopilot-1.1.0/src/robopilot/repair/repair_suggester.py +269 -0
- robopilot-1.1.0/src/robopilot/report/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/report/project_report.py +92 -0
- robopilot-1.1.0/src/robopilot/rollback/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/rollback/rollback.py +155 -0
- robopilot-1.1.0/src/robopilot/ros1/__init__.py +5 -0
- robopilot-1.1.0/src/robopilot/ros1/inspector.py +403 -0
- robopilot-1.1.0/src/robopilot/spec/__init__.py +13 -0
- robopilot-1.1.0/src/robopilot/spec/io.py +178 -0
- robopilot-1.1.0/src/robopilot/spec/validator.py +66 -0
- robopilot-1.1.0/src/robopilot/utils/__init__.py +2 -0
- robopilot-1.1.0/src/robopilot/utils/file_ops.py +30 -0
- robopilot-1.1.0/src/robopilot.egg-info/PKG-INFO +207 -0
- robopilot-1.1.0/src/robopilot.egg-info/SOURCES.txt +89 -0
- robopilot-1.1.0/src/robopilot.egg-info/dependency_links.txt +1 -0
- robopilot-1.1.0/src/robopilot.egg-info/entry_points.txt +2 -0
- robopilot-1.1.0/src/robopilot.egg-info/requires.txt +8 -0
- robopilot-1.1.0/src/robopilot.egg-info/top_level.txt +1 -0
- robopilot-1.1.0/tests/test_apply_plan.py +228 -0
- robopilot-1.1.0/tests/test_apply_plan_execution.py +224 -0
- robopilot-1.1.0/tests/test_apply_preview.py +165 -0
- robopilot-1.1.0/tests/test_dependency_analyzer.py +271 -0
- robopilot-1.1.0/tests/test_history.py +197 -0
- robopilot-1.1.0/tests/test_llm_refiner.py +201 -0
- robopilot-1.1.0/tests/test_log_analyzer.py +33 -0
- robopilot-1.1.0/tests/test_mermaid_generator.py +46 -0
- robopilot-1.1.0/tests/test_migration_plan_diff.py +194 -0
- robopilot-1.1.0/tests/test_migration_plan_validate.py +129 -0
- robopilot-1.1.0/tests/test_migration_preview.py +219 -0
- robopilot-1.1.0/tests/test_planner.py +298 -0
- robopilot-1.1.0/tests/test_project_detector.py +219 -0
- robopilot-1.1.0/tests/test_project_generator.py +74 -0
- robopilot-1.1.0/tests/test_project_inspector.py +127 -0
- robopilot-1.1.0/tests/test_project_report.py +125 -0
- robopilot-1.1.0/tests/test_repair_suggester.py +124 -0
- robopilot-1.1.0/tests/test_rollback.py +151 -0
- robopilot-1.1.0/tests/test_ros1_inspector.py +211 -0
- robopilot-1.1.0/tests/test_ros1_to_ros2_migration_plan.py +238 -0
- robopilot-1.1.0/tests/test_spec_diff.py +177 -0
- robopilot-1.1.0/tests/test_spec_refiner.py +129 -0
- robopilot-1.1.0/tests/test_spec_workflow.py +79 -0
- robopilot-1.1.0/tests/test_task_classifier.py +35 -0
robopilot-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 J1angJJ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
robopilot-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robopilot
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: No-ROS-required static engineering toolchain for ROS-style projects.
|
|
5
|
+
Author: J1angJJ
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/J1angJJ/RoboPilot
|
|
8
|
+
Project-URL: Repository, https://github.com/J1angJJ/RoboPilot
|
|
9
|
+
Project-URL: Issues, https://github.com/J1angJJ/RoboPilot/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/J1angJJ/RoboPilot/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: robotics,ros,ros1,ros2,developer-tools,code-generation,static-analysis,migration
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Requires-Python: <3.12,>=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: typer>=0.12.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
|
+
Provides-Extra: llm
|
|
31
|
+
Requires-Dist: openai>=1.0.0; extra == "llm"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# RoboPilot
|
|
35
|
+
|
|
36
|
+
[English](README.md) | [中文](README.zh-CN.md)
|
|
37
|
+
|
|
38
|
+
[](https://github.com/J1angJJ/RoboPilot/actions/workflows/tests.yml)
|
|
39
|
+
|
|
40
|
+
RoboPilot is a no-ROS-required static engineering toolchain for ROS-style projects.
|
|
41
|
+
|
|
42
|
+
It helps robotics learners and developers plan, refine, validate, generate, inspect, update, roll back, document, and review ROS/ROS2-style project structure without installing ROS, ROS2, catkin, colcon, simulator runtimes, or robot hardware.
|
|
43
|
+
|
|
44
|
+
## What RoboPilot Does
|
|
45
|
+
|
|
46
|
+
- Creates and validates `ProjectSpec` files from robotics tasks.
|
|
47
|
+
- Renders deterministic ROS-style Python package skeletons.
|
|
48
|
+
- Refines and diffs specs before generation.
|
|
49
|
+
- Previews, exports, applies, backs up, rolls back, and journals safe project updates.
|
|
50
|
+
- Inspects projects and exports read-only reports.
|
|
51
|
+
- Detects RoboPilot, ROS1, ROS2, mixed, non-ROS, and unknown project types.
|
|
52
|
+
- Statically inspects ROS1 catkin packages.
|
|
53
|
+
- Analyzes declared and detected dependencies.
|
|
54
|
+
- Builds static ROS1-to-ROS2 migration plans, validates/diffs them, and previews file-level migration work.
|
|
55
|
+
- Provides small offline utilities for robotics error logs and Mermaid workflow graphs.
|
|
56
|
+
- Optionally uses an LLM only to produce or refine validated `ProjectSpec` data.
|
|
57
|
+
|
|
58
|
+
RoboPilot does not run ROS, ROS2, launch files, generated code, `catkin_make`, or `colcon`.
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
Supported Python versions for this release line are Python 3.10 and 3.11. Package metadata declares `>=3.10,<3.12`; Python 3.12 and 3.13 are not claimed until the test suite passes there.
|
|
63
|
+
|
|
64
|
+
Install from source for now:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python -m venv .venv
|
|
68
|
+
.venv\Scripts\activate
|
|
69
|
+
python -m pip install -U pip
|
|
70
|
+
pip install -e ".[dev]"
|
|
71
|
+
robopilot --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
After PyPI release:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install robopilot
|
|
78
|
+
robopilot --help
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
On Windows, if pytest has temporary directory permission issues:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m pytest --basetemp=".pytest_tmp" -p no:cacheprovider
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Core Workflows
|
|
88
|
+
|
|
89
|
+
Spec-first generation:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
robopilot plan --name demo_detector --task "Create an object detection pipeline" --output robopilot.yaml
|
|
93
|
+
robopilot validate --spec robopilot.yaml
|
|
94
|
+
robopilot generate --spec robopilot.yaml
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Iterative spec review:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
robopilot refine --spec robopilot.yaml --instruction "Add a tracker node after the detector" --output refined.yaml
|
|
101
|
+
robopilot diff --old robopilot.yaml --new refined.yaml
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Safe project update loop:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
robopilot apply-preview --spec refined.yaml --project outputs/demo_detector
|
|
108
|
+
robopilot apply-plan --spec refined.yaml --project outputs/demo_detector --output apply_plan.yaml
|
|
109
|
+
robopilot apply --plan apply_plan.yaml
|
|
110
|
+
robopilot apply --plan apply_plan.yaml --confirm
|
|
111
|
+
robopilot history --project outputs/demo_detector
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Static project review:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
robopilot inspect examples/generated_projects/demo_detector
|
|
118
|
+
robopilot repair-suggest examples/generated_projects/demo_detector
|
|
119
|
+
robopilot report examples/generated_projects/demo_detector --output report.md
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
ROS-style static analysis:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
robopilot detect path/to/project
|
|
126
|
+
robopilot inspect-ros1 path/to/ros1_package
|
|
127
|
+
robopilot deps path/to/project
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
ROS1 to ROS2 migration planning:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
robopilot migrate-plan --from path/to/ros1_package --to ros2 --output migration_plan.yaml
|
|
134
|
+
robopilot migrate-plan-validate --plan migration_plan.yaml
|
|
135
|
+
robopilot migrate-preview --plan migration_plan.yaml --project path/to/ros1_package
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Documentation
|
|
139
|
+
|
|
140
|
+
- [Command Reference](docs/command_reference.md)
|
|
141
|
+
- [Workflows](docs/workflows.md)
|
|
142
|
+
- [Architecture](docs/architecture.md)
|
|
143
|
+
- [Developer Setup](docs/developer_setup.md)
|
|
144
|
+
- [Testing](docs/testing.md)
|
|
145
|
+
- [Release Process](docs/release_process.md)
|
|
146
|
+
- [PyPI Publishing](docs/pypi_publish.md)
|
|
147
|
+
- [Compatibility](docs/compatibility.md)
|
|
148
|
+
- [Known Limitations](docs/known_limitations.md)
|
|
149
|
+
- [Stability Policy](docs/stability_policy.md)
|
|
150
|
+
- [Demo Script](docs/demo_script.md)
|
|
151
|
+
- [v1.0.0 Scope](docs/v1_scope.md)
|
|
152
|
+
- [Changelog](CHANGELOG.md)
|
|
153
|
+
- [Roadmap](roadmap.md)
|
|
154
|
+
|
|
155
|
+
## Safety Model
|
|
156
|
+
|
|
157
|
+
RoboPilot is designed around static analysis and explicit review:
|
|
158
|
+
|
|
159
|
+
- Default planning, validation, diff, inspection, report, detection, dependency, and migration commands are read-only.
|
|
160
|
+
- `apply` is dry-run by default and writes only with `--confirm`.
|
|
161
|
+
- Confirmed updates write only files listed in a validated apply plan.
|
|
162
|
+
- Existing files are backed up before updates.
|
|
163
|
+
- `rollback` is dry-run by default and restores only files from RoboPilot backup directories.
|
|
164
|
+
- Migration planning, validation, diff, and preview do not modify source projects.
|
|
165
|
+
- Optional LLM paths are limited to `ProjectSpec` planning/refinement and must pass validation before generation or apply workflows.
|
|
166
|
+
|
|
167
|
+
## Example Output
|
|
168
|
+
|
|
169
|
+
A static generated demo project is committed at:
|
|
170
|
+
|
|
171
|
+
```txt
|
|
172
|
+
examples/generated_projects/demo_detector/
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Transient generated projects should go under `outputs/`, which is intentionally ignored by git.
|
|
176
|
+
|
|
177
|
+
## Project Status
|
|
178
|
+
|
|
179
|
+
Current release line: `v1.1.0`.
|
|
180
|
+
|
|
181
|
+
RoboPilot's no-ROS-required static engineering workflow remains the stable v1 baseline:
|
|
182
|
+
|
|
183
|
+
```txt
|
|
184
|
+
plan -> refine -> diff -> validate -> generate
|
|
185
|
+
-> apply-preview -> apply-plan -> apply -> rollback -> history
|
|
186
|
+
-> inspect -> repair-suggest -> report
|
|
187
|
+
-> detect -> inspect-ros1 -> deps
|
|
188
|
+
-> migrate-plan -> migrate-plan-validate -> migrate-plan-diff -> migrate-preview
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
Run tests:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
python -m pytest
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Windows fallback:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
python -m pytest --basetemp=".pytest_tmp" -p no:cacheprovider
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# RoboPilot
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
[](https://github.com/J1angJJ/RoboPilot/actions/workflows/tests.yml)
|
|
6
|
+
|
|
7
|
+
RoboPilot is a no-ROS-required static engineering toolchain for ROS-style projects.
|
|
8
|
+
|
|
9
|
+
It helps robotics learners and developers plan, refine, validate, generate, inspect, update, roll back, document, and review ROS/ROS2-style project structure without installing ROS, ROS2, catkin, colcon, simulator runtimes, or robot hardware.
|
|
10
|
+
|
|
11
|
+
## What RoboPilot Does
|
|
12
|
+
|
|
13
|
+
- Creates and validates `ProjectSpec` files from robotics tasks.
|
|
14
|
+
- Renders deterministic ROS-style Python package skeletons.
|
|
15
|
+
- Refines and diffs specs before generation.
|
|
16
|
+
- Previews, exports, applies, backs up, rolls back, and journals safe project updates.
|
|
17
|
+
- Inspects projects and exports read-only reports.
|
|
18
|
+
- Detects RoboPilot, ROS1, ROS2, mixed, non-ROS, and unknown project types.
|
|
19
|
+
- Statically inspects ROS1 catkin packages.
|
|
20
|
+
- Analyzes declared and detected dependencies.
|
|
21
|
+
- Builds static ROS1-to-ROS2 migration plans, validates/diffs them, and previews file-level migration work.
|
|
22
|
+
- Provides small offline utilities for robotics error logs and Mermaid workflow graphs.
|
|
23
|
+
- Optionally uses an LLM only to produce or refine validated `ProjectSpec` data.
|
|
24
|
+
|
|
25
|
+
RoboPilot does not run ROS, ROS2, launch files, generated code, `catkin_make`, or `colcon`.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
Supported Python versions for this release line are Python 3.10 and 3.11. Package metadata declares `>=3.10,<3.12`; Python 3.12 and 3.13 are not claimed until the test suite passes there.
|
|
30
|
+
|
|
31
|
+
Install from source for now:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m venv .venv
|
|
35
|
+
.venv\Scripts\activate
|
|
36
|
+
python -m pip install -U pip
|
|
37
|
+
pip install -e ".[dev]"
|
|
38
|
+
robopilot --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After PyPI release:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install robopilot
|
|
45
|
+
robopilot --help
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
On Windows, if pytest has temporary directory permission issues:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python -m pytest --basetemp=".pytest_tmp" -p no:cacheprovider
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Core Workflows
|
|
55
|
+
|
|
56
|
+
Spec-first generation:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
robopilot plan --name demo_detector --task "Create an object detection pipeline" --output robopilot.yaml
|
|
60
|
+
robopilot validate --spec robopilot.yaml
|
|
61
|
+
robopilot generate --spec robopilot.yaml
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Iterative spec review:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
robopilot refine --spec robopilot.yaml --instruction "Add a tracker node after the detector" --output refined.yaml
|
|
68
|
+
robopilot diff --old robopilot.yaml --new refined.yaml
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Safe project update loop:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
robopilot apply-preview --spec refined.yaml --project outputs/demo_detector
|
|
75
|
+
robopilot apply-plan --spec refined.yaml --project outputs/demo_detector --output apply_plan.yaml
|
|
76
|
+
robopilot apply --plan apply_plan.yaml
|
|
77
|
+
robopilot apply --plan apply_plan.yaml --confirm
|
|
78
|
+
robopilot history --project outputs/demo_detector
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Static project review:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
robopilot inspect examples/generated_projects/demo_detector
|
|
85
|
+
robopilot repair-suggest examples/generated_projects/demo_detector
|
|
86
|
+
robopilot report examples/generated_projects/demo_detector --output report.md
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
ROS-style static analysis:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
robopilot detect path/to/project
|
|
93
|
+
robopilot inspect-ros1 path/to/ros1_package
|
|
94
|
+
robopilot deps path/to/project
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
ROS1 to ROS2 migration planning:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
robopilot migrate-plan --from path/to/ros1_package --to ros2 --output migration_plan.yaml
|
|
101
|
+
robopilot migrate-plan-validate --plan migration_plan.yaml
|
|
102
|
+
robopilot migrate-preview --plan migration_plan.yaml --project path/to/ros1_package
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
- [Command Reference](docs/command_reference.md)
|
|
108
|
+
- [Workflows](docs/workflows.md)
|
|
109
|
+
- [Architecture](docs/architecture.md)
|
|
110
|
+
- [Developer Setup](docs/developer_setup.md)
|
|
111
|
+
- [Testing](docs/testing.md)
|
|
112
|
+
- [Release Process](docs/release_process.md)
|
|
113
|
+
- [PyPI Publishing](docs/pypi_publish.md)
|
|
114
|
+
- [Compatibility](docs/compatibility.md)
|
|
115
|
+
- [Known Limitations](docs/known_limitations.md)
|
|
116
|
+
- [Stability Policy](docs/stability_policy.md)
|
|
117
|
+
- [Demo Script](docs/demo_script.md)
|
|
118
|
+
- [v1.0.0 Scope](docs/v1_scope.md)
|
|
119
|
+
- [Changelog](CHANGELOG.md)
|
|
120
|
+
- [Roadmap](roadmap.md)
|
|
121
|
+
|
|
122
|
+
## Safety Model
|
|
123
|
+
|
|
124
|
+
RoboPilot is designed around static analysis and explicit review:
|
|
125
|
+
|
|
126
|
+
- Default planning, validation, diff, inspection, report, detection, dependency, and migration commands are read-only.
|
|
127
|
+
- `apply` is dry-run by default and writes only with `--confirm`.
|
|
128
|
+
- Confirmed updates write only files listed in a validated apply plan.
|
|
129
|
+
- Existing files are backed up before updates.
|
|
130
|
+
- `rollback` is dry-run by default and restores only files from RoboPilot backup directories.
|
|
131
|
+
- Migration planning, validation, diff, and preview do not modify source projects.
|
|
132
|
+
- Optional LLM paths are limited to `ProjectSpec` planning/refinement and must pass validation before generation or apply workflows.
|
|
133
|
+
|
|
134
|
+
## Example Output
|
|
135
|
+
|
|
136
|
+
A static generated demo project is committed at:
|
|
137
|
+
|
|
138
|
+
```txt
|
|
139
|
+
examples/generated_projects/demo_detector/
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Transient generated projects should go under `outputs/`, which is intentionally ignored by git.
|
|
143
|
+
|
|
144
|
+
## Project Status
|
|
145
|
+
|
|
146
|
+
Current release line: `v1.1.0`.
|
|
147
|
+
|
|
148
|
+
RoboPilot's no-ROS-required static engineering workflow remains the stable v1 baseline:
|
|
149
|
+
|
|
150
|
+
```txt
|
|
151
|
+
plan -> refine -> diff -> validate -> generate
|
|
152
|
+
-> apply-preview -> apply-plan -> apply -> rollback -> history
|
|
153
|
+
-> inspect -> repair-suggest -> report
|
|
154
|
+
-> detect -> inspect-ros1 -> deps
|
|
155
|
+
-> migrate-plan -> migrate-plan-validate -> migrate-plan-diff -> migrate-preview
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
Run tests:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python -m pytest
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Windows fallback:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python -m pytest --basetemp=".pytest_tmp" -p no:cacheprovider
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "robopilot"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = "No-ROS-required static engineering toolchain for ROS-style projects."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10,<3.12"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "J1angJJ" }
|
|
10
|
+
]
|
|
11
|
+
keywords = [
|
|
12
|
+
"robotics",
|
|
13
|
+
"ros",
|
|
14
|
+
"ros1",
|
|
15
|
+
"ros2",
|
|
16
|
+
"developer-tools",
|
|
17
|
+
"code-generation",
|
|
18
|
+
"static-analysis",
|
|
19
|
+
"migration"
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 5 - Production/Stable",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Intended Audience :: Education",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Topic :: Software Development :: Code Generators",
|
|
31
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
32
|
+
"Topic :: Scientific/Engineering"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
dependencies = [
|
|
36
|
+
"typer>=0.12.0",
|
|
37
|
+
"rich>=13.0.0"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8.0.0"
|
|
43
|
+
]
|
|
44
|
+
llm = [
|
|
45
|
+
"openai>=1.0.0"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
robopilot = "robopilot.main:app"
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/J1angJJ/RoboPilot"
|
|
53
|
+
Repository = "https://github.com/J1angJJ/RoboPilot"
|
|
54
|
+
Issues = "https://github.com/J1angJJ/RoboPilot/issues"
|
|
55
|
+
Changelog = "https://github.com/J1angJJ/RoboPilot/blob/main/CHANGELOG.md"
|
|
56
|
+
|
|
57
|
+
[build-system]
|
|
58
|
+
requires = ["setuptools>=77.0.0", "wheel"]
|
|
59
|
+
build-backend = "setuptools.build_meta"
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.packages.find]
|
|
62
|
+
where = ["src"]
|
|
63
|
+
|
|
64
|
+
[tool.pytest.ini_options]
|
|
65
|
+
pythonpath = ["src"]
|
|
66
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Safely apply a previously exported apply plan."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from robopilot.apply_plan.plan import (
|
|
11
|
+
apply_plan_from_preview,
|
|
12
|
+
load_apply_plan,
|
|
13
|
+
validate_apply_plan,
|
|
14
|
+
)
|
|
15
|
+
from robopilot.apply_preview.preview import preview_apply
|
|
16
|
+
from robopilot.generator.project_generator import render_project_files
|
|
17
|
+
from robopilot.history.journal import record_history_entry
|
|
18
|
+
from robopilot.spec.io import load_spec
|
|
19
|
+
from robopilot.spec.validator import validate_spec
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
SAFETY_NOTE = (
|
|
23
|
+
"RoboPilot apply validates the plan, re-runs apply-preview, refuses stale "
|
|
24
|
+
"plans and conflicts, and only writes files listed in files_to_create or "
|
|
25
|
+
"files_to_update when --confirm is provided."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class ApplySummary:
|
|
31
|
+
"""Summary of a dry-run or confirmed apply operation."""
|
|
32
|
+
|
|
33
|
+
plan_path: str
|
|
34
|
+
project_path: str
|
|
35
|
+
dry_run: bool
|
|
36
|
+
files_created: tuple[str, ...]
|
|
37
|
+
files_updated: tuple[str, ...]
|
|
38
|
+
files_kept: tuple[str, ...]
|
|
39
|
+
backups_created: tuple[str, ...]
|
|
40
|
+
skipped_files: tuple[str, ...]
|
|
41
|
+
conflicts: tuple[str, ...]
|
|
42
|
+
safety_note: str
|
|
43
|
+
|
|
44
|
+
def to_dict(self) -> dict[str, object]:
|
|
45
|
+
"""Return a stable JSON-serializable representation."""
|
|
46
|
+
return {
|
|
47
|
+
"plan_path": self.plan_path,
|
|
48
|
+
"project_path": self.project_path,
|
|
49
|
+
"dry_run": self.dry_run,
|
|
50
|
+
"files_created": list(self.files_created),
|
|
51
|
+
"files_updated": list(self.files_updated),
|
|
52
|
+
"files_kept": list(self.files_kept),
|
|
53
|
+
"backups_created": list(self.backups_created),
|
|
54
|
+
"skipped_files": list(self.skipped_files),
|
|
55
|
+
"conflicts": list(self.conflicts),
|
|
56
|
+
"safety_note": self.safety_note,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def apply_from_plan(plan_path: Path, *, confirm: bool = False) -> ApplySummary:
|
|
61
|
+
"""Dry-run or apply a saved plan after conservative safety checks."""
|
|
62
|
+
plan = load_apply_plan(plan_path)
|
|
63
|
+
validation = validate_apply_plan(plan)
|
|
64
|
+
if not validation.is_valid:
|
|
65
|
+
raise ValueError("Invalid apply plan: " + "; ".join(validation.errors))
|
|
66
|
+
|
|
67
|
+
spec_path = Path(_string_field(plan, "spec_path"))
|
|
68
|
+
project_path = Path(_string_field(plan, "project_path"))
|
|
69
|
+
spec = load_spec(spec_path)
|
|
70
|
+
spec_validation = validate_spec(spec)
|
|
71
|
+
if not spec_validation.is_valid:
|
|
72
|
+
raise ValueError("Invalid ProjectSpec: " + "; ".join(spec_validation.errors))
|
|
73
|
+
|
|
74
|
+
fresh_preview = preview_apply(spec_path, project_path)
|
|
75
|
+
fresh_plan = apply_plan_from_preview(fresh_preview)
|
|
76
|
+
_ensure_plan_is_fresh(plan, fresh_plan)
|
|
77
|
+
|
|
78
|
+
conflicts = tuple(_list_field(plan, "conflicts"))
|
|
79
|
+
if confirm and conflicts:
|
|
80
|
+
raise ValueError("Refusing to apply because conflicts are present.")
|
|
81
|
+
|
|
82
|
+
files_to_create = tuple(_validate_relative_paths(_list_field(plan, "files_to_create")))
|
|
83
|
+
files_to_update = tuple(_validate_relative_paths(_list_field(plan, "files_to_update")))
|
|
84
|
+
files_to_keep = tuple(_validate_relative_paths(_list_field(plan, "files_to_keep")))
|
|
85
|
+
expected_files = {
|
|
86
|
+
path.as_posix(): content for path, content in render_project_files(spec).items()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_ensure_planned_files_are_expected(files_to_create + files_to_update, expected_files)
|
|
90
|
+
|
|
91
|
+
if not confirm:
|
|
92
|
+
return ApplySummary(
|
|
93
|
+
plan_path=str(plan_path),
|
|
94
|
+
project_path=str(project_path),
|
|
95
|
+
dry_run=True,
|
|
96
|
+
files_created=(),
|
|
97
|
+
files_updated=(),
|
|
98
|
+
files_kept=files_to_keep,
|
|
99
|
+
backups_created=(),
|
|
100
|
+
skipped_files=tuple(sorted(files_to_create + files_to_update + files_to_keep + conflicts)),
|
|
101
|
+
conflicts=conflicts,
|
|
102
|
+
safety_note="Dry run only. No files were modified. " + SAFETY_NOTE,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
created: list[str] = []
|
|
106
|
+
updated: list[str] = []
|
|
107
|
+
backups: list[str] = []
|
|
108
|
+
backup_root = project_path / ".robopilot_backups" / _timestamp()
|
|
109
|
+
|
|
110
|
+
for relative_path in files_to_create:
|
|
111
|
+
target = project_path / relative_path
|
|
112
|
+
if target.exists():
|
|
113
|
+
raise ValueError(f"Refusing to create existing file: {relative_path}")
|
|
114
|
+
_write_expected_file(target, expected_files[relative_path])
|
|
115
|
+
created.append(relative_path)
|
|
116
|
+
|
|
117
|
+
for relative_path in files_to_update:
|
|
118
|
+
target = project_path / relative_path
|
|
119
|
+
if not target.is_file():
|
|
120
|
+
raise ValueError(f"Refusing to update missing file: {relative_path}")
|
|
121
|
+
backup_path = backup_root / relative_path
|
|
122
|
+
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
shutil.copy2(target, backup_path)
|
|
124
|
+
backups.append(backup_path.relative_to(project_path).as_posix())
|
|
125
|
+
_write_expected_file(target, expected_files[relative_path])
|
|
126
|
+
updated.append(relative_path)
|
|
127
|
+
|
|
128
|
+
summary = ApplySummary(
|
|
129
|
+
plan_path=str(plan_path),
|
|
130
|
+
project_path=str(project_path),
|
|
131
|
+
dry_run=False,
|
|
132
|
+
files_created=tuple(sorted(created)),
|
|
133
|
+
files_updated=tuple(sorted(updated)),
|
|
134
|
+
files_kept=files_to_keep,
|
|
135
|
+
backups_created=tuple(sorted(backups)),
|
|
136
|
+
skipped_files=tuple(sorted(files_to_keep + conflicts)),
|
|
137
|
+
conflicts=conflicts,
|
|
138
|
+
safety_note=SAFETY_NOTE,
|
|
139
|
+
)
|
|
140
|
+
record_history_entry(
|
|
141
|
+
project_path=project_path,
|
|
142
|
+
operation="apply",
|
|
143
|
+
plan_path=str(plan_path),
|
|
144
|
+
dry_run=False,
|
|
145
|
+
success=True,
|
|
146
|
+
files_created=summary.files_created,
|
|
147
|
+
files_updated=summary.files_updated,
|
|
148
|
+
files_kept=summary.files_kept,
|
|
149
|
+
conflicts=summary.conflicts,
|
|
150
|
+
skipped_files=summary.skipped_files,
|
|
151
|
+
summary=(
|
|
152
|
+
f"Applied plan with {len(summary.files_created)} files created "
|
|
153
|
+
f"and {len(summary.files_updated)} files updated."
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
return summary
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _ensure_plan_is_fresh(
|
|
160
|
+
saved_plan: dict[str, object],
|
|
161
|
+
fresh_plan: dict[str, object],
|
|
162
|
+
) -> None:
|
|
163
|
+
compared_fields = (
|
|
164
|
+
"spec_path",
|
|
165
|
+
"project_path",
|
|
166
|
+
"package_name",
|
|
167
|
+
"selected_template",
|
|
168
|
+
"files_to_create",
|
|
169
|
+
"files_to_update",
|
|
170
|
+
"files_to_keep",
|
|
171
|
+
"conflicts",
|
|
172
|
+
"missing_project",
|
|
173
|
+
)
|
|
174
|
+
for field in compared_fields:
|
|
175
|
+
if saved_plan.get(field) != fresh_plan.get(field):
|
|
176
|
+
raise ValueError(f"Refusing to apply stale plan: {field} changed.")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _ensure_planned_files_are_expected(
|
|
180
|
+
relative_paths: tuple[str, ...],
|
|
181
|
+
expected_files: dict[str, str],
|
|
182
|
+
) -> None:
|
|
183
|
+
for relative_path in relative_paths:
|
|
184
|
+
if relative_path not in expected_files:
|
|
185
|
+
raise ValueError(f"Refusing to modify unexpected file: {relative_path}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _write_expected_file(path: Path, content: str) -> None:
|
|
189
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
path.write_text(content, encoding="utf-8")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _string_field(plan: dict[str, object], field: str) -> str:
|
|
194
|
+
value = plan.get(field)
|
|
195
|
+
if not isinstance(value, str):
|
|
196
|
+
raise ValueError(f"{field} must be a string.")
|
|
197
|
+
return value
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _list_field(plan: dict[str, object], field: str) -> tuple[str, ...]:
|
|
201
|
+
value = plan.get(field)
|
|
202
|
+
if not isinstance(value, list):
|
|
203
|
+
raise ValueError(f"{field} must be a list.")
|
|
204
|
+
return tuple(str(item) for item in value)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _validate_relative_paths(paths: tuple[str, ...]) -> tuple[str, ...]:
|
|
208
|
+
validated: list[str] = []
|
|
209
|
+
for raw_path in paths:
|
|
210
|
+
path = Path(raw_path)
|
|
211
|
+
if path.is_absolute() or ".." in path.parts:
|
|
212
|
+
raise ValueError(f"Refusing unsafe relative path: {raw_path}")
|
|
213
|
+
validated.append(path.as_posix())
|
|
214
|
+
return tuple(validated)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _timestamp() -> str:
|
|
218
|
+
return datetime.now().strftime("%Y%m%d_%H%M%S")
|