rote-cli 0.1.0__tar.gz → 0.4.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.
- {rote_cli-0.1.0 → rote_cli-0.4.0}/PKG-INFO +76 -18
- {rote_cli-0.1.0 → rote_cli-0.4.0}/README.md +75 -17
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/__init__.py +1 -1
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/adapters/__init__.py +7 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/adapters/_common.py +58 -1
- rote_cli-0.4.0/src/rote/adapters/_ts_common.py +338 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/adapters/cloudflare.py +193 -294
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/adapters/dbos.py +37 -17
- rote_cli-0.4.0/src/rote/adapters/dbos_ts.py +906 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/adapters/temporal.py +6 -5
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/cli.py +17 -5
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/ir.py +85 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/serve/backends.py +34 -9
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/serve/registry.py +10 -6
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/serve/server.py +28 -3
- {rote_cli-0.1.0 → rote_cli-0.4.0}/.gitignore +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/LICENSE +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/pyproject.toml +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/skills/rote-graduate/SKILL.md +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/skills/rote-graduate/references/crystallization-heuristics.md +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/skills/rote-graduate/references/ir-schema.md +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/skills/rote-graduate/references/llm-judge-extraction.md +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/skills/rote-graduate/references/node-kinds.md +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/graduator/__init__.py +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/graduator/drivers/__init__.py +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/graduator/drivers/anthropic_api.py +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/graduator/drivers/claude.py +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/graduator/drivers/codex.py +0 -0
- {rote_cli-0.1.0 → rote_cli-0.4.0}/src/rote/serve/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rote-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Graduate fuzzy AI skills into deterministic, reliable workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/trevhud/rote
|
|
6
6
|
Project-URL: Repository, https://github.com/trevhud/rote
|
|
@@ -59,8 +59,13 @@ applies a structured graduation rubric, and emits:
|
|
|
59
59
|
- and runtime code for your durable execution engine of choice.
|
|
60
60
|
|
|
61
61
|
```sh
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
|
|
63
|
+
|
|
64
|
+
# Default target is DBOS — durable execution as a plain Python library,
|
|
65
|
+
# no orchestrator to run, SQLite for dev / Postgres for prod:
|
|
66
|
+
rote graduate ./examples/bdr-outreach/skill --out ./graduated/
|
|
67
|
+
|
|
68
|
+
# Or target Temporal (Python) or Cloudflare Workflows (TypeScript):
|
|
64
69
|
rote graduate ./examples/bdr-outreach/skill --runtime temporal --out ./graduated/
|
|
65
70
|
rote graduate ./examples/bdr-outreach/skill --runtime cloudflare --out ./graduated/
|
|
66
71
|
```
|
|
@@ -140,6 +145,10 @@ The graduator independently:
|
|
|
140
145
|
After the agent finishes, a runtime adapter consumes the IR and emits
|
|
141
146
|
the target runtime's native code shape:
|
|
142
147
|
|
|
148
|
+
- The **DBOS** adapter (the default) emits a single `main.py` — one
|
|
149
|
+
`@DBOS.workflow` DAG plus a `@DBOS.step` per node, checkpointing to
|
|
150
|
+
SQLite or Postgres. `python main.py` *is* the runtime; there is no
|
|
151
|
+
orchestrator to deploy.
|
|
143
152
|
- The **Temporal** adapter emits `workflow.py` (the orchestration
|
|
144
153
|
class with `@workflow.defn` and signal handlers for the HITL gates)
|
|
145
154
|
and `activities.py` (one `@activity.defn` per node, lazy-importing
|
|
@@ -150,6 +159,14 @@ the target runtime's native code shape:
|
|
|
150
159
|
`signatures/*.ts` (Zod schemas + Anthropic SDK calls) and the
|
|
151
160
|
supporting `wrangler.jsonc` / `package.json` / `tsconfig.json`. The
|
|
152
161
|
output is `wrangler deploy`-ready.
|
|
162
|
+
- The **DBOS (TypeScript)** adapter (`--runtime dbos-ts`) emits a
|
|
163
|
+
Node.js app for DBOS Transact: `src/main.ts` registers one durable
|
|
164
|
+
workflow (`DBOS.registerWorkflow`) running the DAG waves and one
|
|
165
|
+
`DBOS.registerStep` per node, with `DBOS.recv(...)` parking each
|
|
166
|
+
HITL gate durably in Postgres until `DBOS.send(...)` resumes it.
|
|
167
|
+
Zero-orchestrator like the Python DBOS target — `node dist/main.js`
|
|
168
|
+
*is* the runtime — but note the TS SDK is Postgres-only (no SQLite
|
|
169
|
+
mode; `npx dbos postgres start` covers local dev).
|
|
153
170
|
|
|
154
171
|
None of the emitted code references an MCP runtime, in either
|
|
155
172
|
language — the agent's crystallization step replaces tool calls with
|
|
@@ -242,9 +259,43 @@ agent-driven — so the same IR always produces byte-identical output.
|
|
|
242
259
|
|
|
243
260
|
## Quickstart
|
|
244
261
|
|
|
245
|
-
###
|
|
262
|
+
### Use from Claude Code (recommended)
|
|
263
|
+
|
|
264
|
+
`rote` ships as a Claude Code plugin, so you can graduate a skill
|
|
265
|
+
without leaving Claude or touching Python tooling:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
/plugin marketplace add trevhud/rote
|
|
269
|
+
/plugin install rote@rote
|
|
270
|
+
```
|
|
246
271
|
|
|
247
|
-
|
|
272
|
+
Then say "graduate this skill" (or run `/rote:graduate` directly).
|
|
273
|
+
The plugin confirms the source skill directory, asks which runtime you
|
|
274
|
+
want (Temporal, Cloudflare Workflows, or DBOS), runs the CLI via
|
|
275
|
+
[uv](https://docs.astral.sh/uv/) in the background, and reports the
|
|
276
|
+
emitted pipeline. A second skill, `/rote:serve`, wires graduated
|
|
277
|
+
pipelines up as MCP tools so Claude can trigger the deployed workflows.
|
|
278
|
+
|
|
279
|
+
Prefer a terminal? The same thing is one `uvx` command:
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
uvx --from rote-cli rote graduate ./my-skill --runtime dbos --out ./graduated
|
|
283
|
+
|
|
284
|
+
# or straight from GitHub for unreleased changes:
|
|
285
|
+
uvx --from git+https://github.com/trevhud/rote rote graduate \
|
|
286
|
+
./my-skill --runtime dbos --out ./graduated
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
> **Naming note:** the `rote` package on PyPI is an unrelated
|
|
290
|
+
> memoization library that also installs `import rote`, so the two
|
|
291
|
+
> can't share an environment. This project's distribution is
|
|
292
|
+
> `rote-cli` while the CLI command and import name stay `rote` —
|
|
293
|
+
> hence `uvx --from rote-cli rote ...`. See
|
|
294
|
+
> [docs/releasing.md](docs/releasing.md).
|
|
295
|
+
|
|
296
|
+
### Install from source (development)
|
|
297
|
+
|
|
298
|
+
Clone and install in editable mode:
|
|
248
299
|
|
|
249
300
|
```sh
|
|
250
301
|
git clone https://github.com/trevhud/rote.git
|
|
@@ -263,11 +314,12 @@ The repository includes a real BDR outreach skill in
|
|
|
263
314
|
`examples/bdr-outreach/skill/`. Graduate it:
|
|
264
315
|
|
|
265
316
|
```sh
|
|
266
|
-
rote graduate examples/bdr-outreach/skill
|
|
267
|
-
--runtime temporal \
|
|
268
|
-
--out /tmp/bdr-graduated
|
|
317
|
+
rote graduate examples/bdr-outreach/skill --out /tmp/bdr-graduated
|
|
269
318
|
```
|
|
270
319
|
|
|
320
|
+
This targets DBOS by default; pass `--runtime temporal` or
|
|
321
|
+
`--runtime cloudflare` for the other adapters.
|
|
322
|
+
|
|
271
323
|
By default `rote` auto-detects an available agent driver in this
|
|
272
324
|
order: `claude` (Claude Code CLI) → `codex` (Codex CLI) → `api`
|
|
273
325
|
(Anthropic SDK with `ANTHROPIC_API_KEY`). Override with `--agent`:
|
|
@@ -286,10 +338,12 @@ The output directory is structured as:
|
|
|
286
338
|
│ ├── signatures/*.py # typed LLM-judge signatures
|
|
287
339
|
│ ├── evals/*.jsonl # seed eval examples
|
|
288
340
|
│ └── graduation-report.md # human-readable summary
|
|
289
|
-
└── runtime/
|
|
290
|
-
├── workflow.
|
|
291
|
-
├──
|
|
292
|
-
|
|
341
|
+
└── runtime/dbos/ # produced by the adapter
|
|
342
|
+
├── main.py # @DBOS.workflow + one @DBOS.step per node
|
|
343
|
+
├── extracted/*.py # copied deterministic functions
|
|
344
|
+
├── signatures/*.py # generated Pydantic + vendor-SDK judges
|
|
345
|
+
├── dbos-config.yaml
|
|
346
|
+
└── README.md # how to run, signal HITL gates, deploy
|
|
293
347
|
```
|
|
294
348
|
|
|
295
349
|
### Render an IR without re-running the agent
|
|
@@ -298,6 +352,7 @@ If you already have a `pipeline.yaml` (hand-written or from a previous
|
|
|
298
352
|
graduation), `rote emit` runs just the adapter step:
|
|
299
353
|
|
|
300
354
|
```sh
|
|
355
|
+
rote emit /path/to/pipeline.yaml --out /tmp/emitted/ # dbos (default)
|
|
301
356
|
rote emit /path/to/pipeline.yaml --runtime temporal --out /tmp/emitted/
|
|
302
357
|
```
|
|
303
358
|
|
|
@@ -359,10 +414,12 @@ Opus's extra reasoning earns its cost.
|
|
|
359
414
|
## Status
|
|
360
415
|
|
|
361
416
|
`rote` is **pre-1.0**. The end-to-end flow works on the BDR example
|
|
362
|
-
and the test suite covers each layer (
|
|
363
|
-
plus
|
|
364
|
-
|
|
365
|
-
|
|
417
|
+
and the test suite covers each layer (231 tests in the fast suite,
|
|
418
|
+
plus 5 slow tests that run the emitted code against real runtimes:
|
|
419
|
+
a DBOS runtime over SQLite, Temporal's time-skipping test server, the
|
|
420
|
+
emitted Cloudflare TypeScript compiled against real
|
|
421
|
+
`@cloudflare/workers-types` and driven through both HITL gates via
|
|
422
|
+
`wrangler dev`, and the MCP server over a real stdio transport).
|
|
366
423
|
Run `pytest tests/` for the fast suite; `pytest tests/ -m slow` for
|
|
367
424
|
the toolchain-dependent integration tests.
|
|
368
425
|
|
|
@@ -372,6 +429,7 @@ the toolchain-dependent integration tests.
|
|
|
372
429
|
| Temporal adapter | working (validated with mocked-activities e2e test) |
|
|
373
430
|
| Cloudflare Workflows adapter | working (validated with `tsc --noEmit` over the real emitted output) |
|
|
374
431
|
| DBOS adapter | working (validated against a real DBOS runtime over SQLite in the e2e test) |
|
|
432
|
+
| DBOS TypeScript adapter (`dbos-ts`) | working (validated with `tsc --noEmit` over the real emitted output and a live run on the DBOS TS runtime against Docker Postgres) |
|
|
375
433
|
| Graduator orchestrator | working |
|
|
376
434
|
| `rote graduate` / `rote emit` CLI commands | working |
|
|
377
435
|
| `claude` driver | working |
|
|
@@ -379,8 +437,8 @@ the toolchain-dependent integration tests.
|
|
|
379
437
|
| `codex` driver | stub (`is_available` works; `run` not implemented) |
|
|
380
438
|
| Inngest / Restate adapters | planned |
|
|
381
439
|
| Real implementations of the extracted modules | the agent produces stubs that raise `NotImplementedError`; humans fill them in with real API client code |
|
|
382
|
-
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all
|
|
383
|
-
| Distribution via PyPI |
|
|
440
|
+
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all four adapters (Temporal, Cloudflare, DBOS, DBOS-TS) thread real payloads through the DAG, validated in the runtime e2e tests |
|
|
441
|
+
| Distribution via PyPI | published as [`rote-cli`](https://pypi.org/project/rote-cli/) (`pip install rote-cli`); tag-driven Trusted Publishing releases — see [docs/releasing.md](docs/releasing.md) |
|
|
384
442
|
|
|
385
443
|
The project explicitly **does not** depend on `claude-agent-sdk`.
|
|
386
444
|
Anthropic's terms of service forbid third-party agents built on the
|
|
@@ -13,8 +13,13 @@ applies a structured graduation rubric, and emits:
|
|
|
13
13
|
- and runtime code for your durable execution engine of choice.
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
|
|
17
|
+
|
|
18
|
+
# Default target is DBOS — durable execution as a plain Python library,
|
|
19
|
+
# no orchestrator to run, SQLite for dev / Postgres for prod:
|
|
20
|
+
rote graduate ./examples/bdr-outreach/skill --out ./graduated/
|
|
21
|
+
|
|
22
|
+
# Or target Temporal (Python) or Cloudflare Workflows (TypeScript):
|
|
18
23
|
rote graduate ./examples/bdr-outreach/skill --runtime temporal --out ./graduated/
|
|
19
24
|
rote graduate ./examples/bdr-outreach/skill --runtime cloudflare --out ./graduated/
|
|
20
25
|
```
|
|
@@ -94,6 +99,10 @@ The graduator independently:
|
|
|
94
99
|
After the agent finishes, a runtime adapter consumes the IR and emits
|
|
95
100
|
the target runtime's native code shape:
|
|
96
101
|
|
|
102
|
+
- The **DBOS** adapter (the default) emits a single `main.py` — one
|
|
103
|
+
`@DBOS.workflow` DAG plus a `@DBOS.step` per node, checkpointing to
|
|
104
|
+
SQLite or Postgres. `python main.py` *is* the runtime; there is no
|
|
105
|
+
orchestrator to deploy.
|
|
97
106
|
- The **Temporal** adapter emits `workflow.py` (the orchestration
|
|
98
107
|
class with `@workflow.defn` and signal handlers for the HITL gates)
|
|
99
108
|
and `activities.py` (one `@activity.defn` per node, lazy-importing
|
|
@@ -104,6 +113,14 @@ the target runtime's native code shape:
|
|
|
104
113
|
`signatures/*.ts` (Zod schemas + Anthropic SDK calls) and the
|
|
105
114
|
supporting `wrangler.jsonc` / `package.json` / `tsconfig.json`. The
|
|
106
115
|
output is `wrangler deploy`-ready.
|
|
116
|
+
- The **DBOS (TypeScript)** adapter (`--runtime dbos-ts`) emits a
|
|
117
|
+
Node.js app for DBOS Transact: `src/main.ts` registers one durable
|
|
118
|
+
workflow (`DBOS.registerWorkflow`) running the DAG waves and one
|
|
119
|
+
`DBOS.registerStep` per node, with `DBOS.recv(...)` parking each
|
|
120
|
+
HITL gate durably in Postgres until `DBOS.send(...)` resumes it.
|
|
121
|
+
Zero-orchestrator like the Python DBOS target — `node dist/main.js`
|
|
122
|
+
*is* the runtime — but note the TS SDK is Postgres-only (no SQLite
|
|
123
|
+
mode; `npx dbos postgres start` covers local dev).
|
|
107
124
|
|
|
108
125
|
None of the emitted code references an MCP runtime, in either
|
|
109
126
|
language — the agent's crystallization step replaces tool calls with
|
|
@@ -196,9 +213,43 @@ agent-driven — so the same IR always produces byte-identical output.
|
|
|
196
213
|
|
|
197
214
|
## Quickstart
|
|
198
215
|
|
|
199
|
-
###
|
|
216
|
+
### Use from Claude Code (recommended)
|
|
217
|
+
|
|
218
|
+
`rote` ships as a Claude Code plugin, so you can graduate a skill
|
|
219
|
+
without leaving Claude or touching Python tooling:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
/plugin marketplace add trevhud/rote
|
|
223
|
+
/plugin install rote@rote
|
|
224
|
+
```
|
|
200
225
|
|
|
201
|
-
|
|
226
|
+
Then say "graduate this skill" (or run `/rote:graduate` directly).
|
|
227
|
+
The plugin confirms the source skill directory, asks which runtime you
|
|
228
|
+
want (Temporal, Cloudflare Workflows, or DBOS), runs the CLI via
|
|
229
|
+
[uv](https://docs.astral.sh/uv/) in the background, and reports the
|
|
230
|
+
emitted pipeline. A second skill, `/rote:serve`, wires graduated
|
|
231
|
+
pipelines up as MCP tools so Claude can trigger the deployed workflows.
|
|
232
|
+
|
|
233
|
+
Prefer a terminal? The same thing is one `uvx` command:
|
|
234
|
+
|
|
235
|
+
```sh
|
|
236
|
+
uvx --from rote-cli rote graduate ./my-skill --runtime dbos --out ./graduated
|
|
237
|
+
|
|
238
|
+
# or straight from GitHub for unreleased changes:
|
|
239
|
+
uvx --from git+https://github.com/trevhud/rote rote graduate \
|
|
240
|
+
./my-skill --runtime dbos --out ./graduated
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
> **Naming note:** the `rote` package on PyPI is an unrelated
|
|
244
|
+
> memoization library that also installs `import rote`, so the two
|
|
245
|
+
> can't share an environment. This project's distribution is
|
|
246
|
+
> `rote-cli` while the CLI command and import name stay `rote` —
|
|
247
|
+
> hence `uvx --from rote-cli rote ...`. See
|
|
248
|
+
> [docs/releasing.md](docs/releasing.md).
|
|
249
|
+
|
|
250
|
+
### Install from source (development)
|
|
251
|
+
|
|
252
|
+
Clone and install in editable mode:
|
|
202
253
|
|
|
203
254
|
```sh
|
|
204
255
|
git clone https://github.com/trevhud/rote.git
|
|
@@ -217,11 +268,12 @@ The repository includes a real BDR outreach skill in
|
|
|
217
268
|
`examples/bdr-outreach/skill/`. Graduate it:
|
|
218
269
|
|
|
219
270
|
```sh
|
|
220
|
-
rote graduate examples/bdr-outreach/skill
|
|
221
|
-
--runtime temporal \
|
|
222
|
-
--out /tmp/bdr-graduated
|
|
271
|
+
rote graduate examples/bdr-outreach/skill --out /tmp/bdr-graduated
|
|
223
272
|
```
|
|
224
273
|
|
|
274
|
+
This targets DBOS by default; pass `--runtime temporal` or
|
|
275
|
+
`--runtime cloudflare` for the other adapters.
|
|
276
|
+
|
|
225
277
|
By default `rote` auto-detects an available agent driver in this
|
|
226
278
|
order: `claude` (Claude Code CLI) → `codex` (Codex CLI) → `api`
|
|
227
279
|
(Anthropic SDK with `ANTHROPIC_API_KEY`). Override with `--agent`:
|
|
@@ -240,10 +292,12 @@ The output directory is structured as:
|
|
|
240
292
|
│ ├── signatures/*.py # typed LLM-judge signatures
|
|
241
293
|
│ ├── evals/*.jsonl # seed eval examples
|
|
242
294
|
│ └── graduation-report.md # human-readable summary
|
|
243
|
-
└── runtime/
|
|
244
|
-
├── workflow.
|
|
245
|
-
├──
|
|
246
|
-
|
|
295
|
+
└── runtime/dbos/ # produced by the adapter
|
|
296
|
+
├── main.py # @DBOS.workflow + one @DBOS.step per node
|
|
297
|
+
├── extracted/*.py # copied deterministic functions
|
|
298
|
+
├── signatures/*.py # generated Pydantic + vendor-SDK judges
|
|
299
|
+
├── dbos-config.yaml
|
|
300
|
+
└── README.md # how to run, signal HITL gates, deploy
|
|
247
301
|
```
|
|
248
302
|
|
|
249
303
|
### Render an IR without re-running the agent
|
|
@@ -252,6 +306,7 @@ If you already have a `pipeline.yaml` (hand-written or from a previous
|
|
|
252
306
|
graduation), `rote emit` runs just the adapter step:
|
|
253
307
|
|
|
254
308
|
```sh
|
|
309
|
+
rote emit /path/to/pipeline.yaml --out /tmp/emitted/ # dbos (default)
|
|
255
310
|
rote emit /path/to/pipeline.yaml --runtime temporal --out /tmp/emitted/
|
|
256
311
|
```
|
|
257
312
|
|
|
@@ -313,10 +368,12 @@ Opus's extra reasoning earns its cost.
|
|
|
313
368
|
## Status
|
|
314
369
|
|
|
315
370
|
`rote` is **pre-1.0**. The end-to-end flow works on the BDR example
|
|
316
|
-
and the test suite covers each layer (
|
|
317
|
-
plus
|
|
318
|
-
|
|
319
|
-
|
|
371
|
+
and the test suite covers each layer (231 tests in the fast suite,
|
|
372
|
+
plus 5 slow tests that run the emitted code against real runtimes:
|
|
373
|
+
a DBOS runtime over SQLite, Temporal's time-skipping test server, the
|
|
374
|
+
emitted Cloudflare TypeScript compiled against real
|
|
375
|
+
`@cloudflare/workers-types` and driven through both HITL gates via
|
|
376
|
+
`wrangler dev`, and the MCP server over a real stdio transport).
|
|
320
377
|
Run `pytest tests/` for the fast suite; `pytest tests/ -m slow` for
|
|
321
378
|
the toolchain-dependent integration tests.
|
|
322
379
|
|
|
@@ -326,6 +383,7 @@ the toolchain-dependent integration tests.
|
|
|
326
383
|
| Temporal adapter | working (validated with mocked-activities e2e test) |
|
|
327
384
|
| Cloudflare Workflows adapter | working (validated with `tsc --noEmit` over the real emitted output) |
|
|
328
385
|
| DBOS adapter | working (validated against a real DBOS runtime over SQLite in the e2e test) |
|
|
386
|
+
| DBOS TypeScript adapter (`dbos-ts`) | working (validated with `tsc --noEmit` over the real emitted output and a live run on the DBOS TS runtime against Docker Postgres) |
|
|
329
387
|
| Graduator orchestrator | working |
|
|
330
388
|
| `rote graduate` / `rote emit` CLI commands | working |
|
|
331
389
|
| `claude` driver | working |
|
|
@@ -333,8 +391,8 @@ the toolchain-dependent integration tests.
|
|
|
333
391
|
| `codex` driver | stub (`is_available` works; `run` not implemented) |
|
|
334
392
|
| Inngest / Restate adapters | planned |
|
|
335
393
|
| Real implementations of the extracted modules | the agent produces stubs that raise `NotImplementedError`; humans fill them in with real API client code |
|
|
336
|
-
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all
|
|
337
|
-
| Distribution via PyPI |
|
|
394
|
+
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all four adapters (Temporal, Cloudflare, DBOS, DBOS-TS) thread real payloads through the DAG, validated in the runtime e2e tests |
|
|
395
|
+
| Distribution via PyPI | published as [`rote-cli`](https://pypi.org/project/rote-cli/) (`pip install rote-cli`); tag-driven Trusted Publishing releases — see [docs/releasing.md](docs/releasing.md) |
|
|
338
396
|
|
|
339
397
|
The project explicitly **does not** depend on `claude-agent-sdk`.
|
|
340
398
|
Anthropic's terms of service forbid third-party agents built on the
|
|
@@ -48,12 +48,19 @@ def _dbos_adapter_factory() -> Adapter:
|
|
|
48
48
|
return DbosAdapter()
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _dbos_ts_adapter_factory() -> Adapter:
|
|
52
|
+
from rote.adapters.dbos_ts import DbosTsAdapter
|
|
53
|
+
|
|
54
|
+
return DbosTsAdapter()
|
|
55
|
+
|
|
56
|
+
|
|
51
57
|
#: Name → factory. Keep the values as zero-arg callables so adapters can
|
|
52
58
|
#: lazy-import their heavy dependencies.
|
|
53
59
|
ADAPTERS: dict[str, Callable[[], Adapter]] = {
|
|
54
60
|
"temporal": _temporal_adapter_factory,
|
|
55
61
|
"cloudflare": _cloudflare_adapter_factory,
|
|
56
62
|
"dbos": _dbos_adapter_factory,
|
|
63
|
+
"dbos-ts": _dbos_ts_adapter_factory,
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
|
|
@@ -13,9 +13,63 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import re
|
|
16
|
+
from pathlib import Path
|
|
16
17
|
|
|
17
18
|
from rote.ir import Node, Pipeline, parse_input_ref
|
|
18
19
|
|
|
20
|
+
# ───────── Prose → safe code comment/docstring ─────────
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def safe_docstring_line(text: str, fallback: str = "") -> str:
|
|
24
|
+
"""First line of ``text``, escaped so it cannot break out of a docstring.
|
|
25
|
+
|
|
26
|
+
Node ``description`` is prose (it legitimately contains quotes and
|
|
27
|
+
backslashes), yet adapters splice its first line straight into a
|
|
28
|
+
``\"\"\"…\"\"\"`` Python docstring / block comment. An unescaped
|
|
29
|
+
``\"\"\"`` — or a trailing backslash that escapes the closing quote —
|
|
30
|
+
lets a crafted pipeline.yaml close the docstring early and inject
|
|
31
|
+
code. Escaping backslashes and double-quotes neutralizes both without
|
|
32
|
+
charset-restricting prose at the IR layer.
|
|
33
|
+
"""
|
|
34
|
+
stripped = text.strip() if text else ""
|
|
35
|
+
first = stripped.splitlines()[0] if stripped else fallback
|
|
36
|
+
return first.replace("\\", "\\\\").replace('"', '\\"')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def safe_block_comment_line(text: str, fallback: str = "") -> str:
|
|
40
|
+
"""First line of ``text``, safe to splice into a ``/* … */`` block comment.
|
|
41
|
+
|
|
42
|
+
The TypeScript adapter emits ``description`` first-lines into JSDoc
|
|
43
|
+
comments. An unescaped ``*/`` would close the comment early and let a
|
|
44
|
+
crafted pipeline.yaml inject code after it; neutralize the sequence by
|
|
45
|
+
inserting a space (``*/`` → ``* /``). Newlines are already dropped by
|
|
46
|
+
taking the first line.
|
|
47
|
+
"""
|
|
48
|
+
stripped = text.strip() if text else ""
|
|
49
|
+
first = stripped.splitlines()[0] if stripped else fallback
|
|
50
|
+
return first.replace("*/", "* /")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ───────── Output-path containment ─────────
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def resolve_within(base: Path, *parts: str) -> Path:
|
|
57
|
+
"""Join ``parts`` onto ``base`` and assert the result stays inside it.
|
|
58
|
+
|
|
59
|
+
Defense-in-depth for emitted-file paths. IR validation already pins
|
|
60
|
+
``node.id`` to an identifier, but this guard means any future field
|
|
61
|
+
that reaches a filename cannot escape the output directory via an
|
|
62
|
+
absolute segment or ``..`` traversal — it fails loudly at emit time.
|
|
63
|
+
"""
|
|
64
|
+
base_resolved = base.resolve()
|
|
65
|
+
target = base_resolved.joinpath(*parts).resolve()
|
|
66
|
+
if target != base_resolved and base_resolved not in target.parents:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"Refusing to write outside output directory: {target} is not within {base_resolved}"
|
|
69
|
+
)
|
|
70
|
+
return target
|
|
71
|
+
|
|
72
|
+
|
|
19
73
|
# ───────── Case conversion ─────────
|
|
20
74
|
|
|
21
75
|
|
|
@@ -42,7 +96,10 @@ def _pipeline_hash(pipeline: Pipeline) -> str:
|
|
|
42
96
|
name) so a regenerated pipeline becomes a new type. Old in-flight
|
|
43
97
|
workflows continue on the old code; new workflows use the new code.
|
|
44
98
|
"""
|
|
45
|
-
|
|
99
|
+
# Hash the full validated contents: node wiring, inputs, retries, and
|
|
100
|
+
# edges all participate, so any regeneration that changes behavior gets
|
|
101
|
+
# a new workflow type — name/version/counts alone miss rewires.
|
|
102
|
+
payload = pipeline.model_dump_json(by_alias=True, exclude_none=True)
|
|
46
103
|
return hashlib.sha256(payload.encode()).hexdigest()[:8]
|
|
47
104
|
|
|
48
105
|
|