rote-cli 0.8.0__tar.gz → 0.9.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.
Files changed (53) hide show
  1. {rote_cli-0.8.0 → rote_cli-0.9.0}/PKG-INFO +7 -1
  2. {rote_cli-0.8.0 → rote_cli-0.9.0}/pyproject.toml +8 -0
  3. {rote_cli-0.8.0 → rote_cli-0.9.0}/skills/rote-graduate/SKILL.md +13 -0
  4. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/__init__.py +1 -1
  5. rote_cli-0.9.0/src/rote/__main__.py +12 -0
  6. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/__init__.py +28 -11
  7. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/_common.py +47 -0
  8. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/_ts_common.py +573 -0
  9. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/cloudflare.py +146 -15
  10. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/dbos.py +29 -16
  11. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/dbos_ts.py +27 -2
  12. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/inngest.py +27 -2
  13. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/cli.py +379 -66
  14. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/__init__.py +4 -0
  15. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/empirical.py +31 -5
  16. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/scorecard.py +66 -0
  17. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/graduator/__init__.py +103 -4
  18. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/graduator/drivers/__init__.py +22 -0
  19. rote_cli-0.9.0/src/rote/graduator/drivers/_fs_tools.py +280 -0
  20. rote_cli-0.9.0/src/rote/graduator/drivers/anthropic_api.py +466 -0
  21. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/graduator/drivers/claude.py +197 -41
  22. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/graduator/drivers/codex.py +13 -2
  23. rote_cli-0.9.0/src/rote/graduator/drivers/openai_api.py +431 -0
  24. rote_cli-0.9.0/src/rote/graduator/events.py +255 -0
  25. rote_cli-0.9.0/src/rote/mcp/__init__.py +48 -0
  26. rote_cli-0.9.0/src/rote/mcp/_runtime_helper.py +247 -0
  27. rote_cli-0.9.0/src/rote/mcp/auth.py +166 -0
  28. rote_cli-0.9.0/src/rote/mcp/registry.py +124 -0
  29. rote_cli-0.9.0/src/rote/mcp/tokens.py +246 -0
  30. rote_cli-0.8.0/src/rote/graduator/drivers/anthropic_api.py +0 -423
  31. {rote_cli-0.8.0 → rote_cli-0.9.0}/.gitignore +0 -0
  32. {rote_cli-0.8.0 → rote_cli-0.9.0}/LICENSE +0 -0
  33. {rote_cli-0.8.0 → rote_cli-0.9.0}/README.md +0 -0
  34. {rote_cli-0.8.0 → rote_cli-0.9.0}/skills/rote-graduate/references/crystallization-heuristics.md +0 -0
  35. {rote_cli-0.8.0 → rote_cli-0.9.0}/skills/rote-graduate/references/eval-estimates.md +0 -0
  36. {rote_cli-0.8.0 → rote_cli-0.9.0}/skills/rote-graduate/references/ir-schema.md +0 -0
  37. {rote_cli-0.8.0 → rote_cli-0.9.0}/skills/rote-graduate/references/llm-judge-extraction.md +0 -0
  38. {rote_cli-0.8.0 → rote_cli-0.9.0}/skills/rote-graduate/references/node-kinds.md +0 -0
  39. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/_py_common.py +0 -0
  40. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/python.py +0 -0
  41. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/adapters/temporal.py +0 -0
  42. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/estimate.py +0 -0
  43. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/pricing.py +0 -0
  44. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/priors.py +0 -0
  45. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/sidecar.py +0 -0
  46. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/eval/tokens.py +0 -0
  47. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/graduator/update.py +0 -0
  48. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/ir.py +0 -0
  49. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/serve/__init__.py +0 -0
  50. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/serve/backends.py +0 -0
  51. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/serve/registry.py +0 -0
  52. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/serve/server.py +0 -0
  53. {rote_cli-0.8.0 → rote_cli-0.9.0}/src/rote/skill_source.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rote-cli
3
- Version: 0.8.0
3
+ Version: 0.9.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
@@ -32,11 +32,17 @@ Requires-Dist: dbos>=2.26; extra == 'dev'
32
32
  Requires-Dist: fastmcp>=3.4.2; extra == 'dev'
33
33
  Requires-Dist: httpx>=0.28; extra == 'dev'
34
34
  Requires-Dist: mypy>=1.10; extra == 'dev'
35
+ Requires-Dist: openai>=2.45; extra == 'dev'
35
36
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
36
37
  Requires-Dist: pytest>=8.0; extra == 'dev'
37
38
  Requires-Dist: ruff>=0.6; extra == 'dev'
38
39
  Requires-Dist: temporalio>=1.7; extra == 'dev'
39
40
  Requires-Dist: types-pyyaml; extra == 'dev'
41
+ Provides-Extra: mcp
42
+ Requires-Dist: fastmcp>=3.4.2; extra == 'mcp'
43
+ Requires-Dist: httpx>=0.28; extra == 'mcp'
44
+ Provides-Extra: openai-api
45
+ Requires-Dist: openai>=2.45; extra == 'openai-api'
40
46
  Provides-Extra: serve
41
47
  Requires-Dist: fastmcp>=3.4.2; extra == 'serve'
42
48
  Requires-Dist: httpx>=0.28; extra == 'serve'
@@ -50,6 +50,9 @@ temporal = [
50
50
  api = [
51
51
  "anthropic>=0.89",
52
52
  ]
53
+ openai-api = [
54
+ "openai>=2.45",
55
+ ]
53
56
  dbos = [
54
57
  "dbos>=2.26",
55
58
  ]
@@ -57,6 +60,10 @@ serve = [
57
60
  "fastmcp>=3.4.2",
58
61
  "httpx>=0.28",
59
62
  ]
63
+ mcp = [
64
+ "fastmcp>=3.4.2",
65
+ "httpx>=0.28",
66
+ ]
60
67
  dev = [
61
68
  "pytest>=8.0",
62
69
  "pytest-asyncio>=0.23",
@@ -65,6 +72,7 @@ dev = [
65
72
  "types-pyyaml",
66
73
  "temporalio>=1.7",
67
74
  "anthropic>=0.89",
75
+ "openai>=2.45",
68
76
  "dbos>=2.26",
69
77
  "fastmcp>=3.4.2",
70
78
  "httpx>=0.28",
@@ -46,6 +46,19 @@ re-deriving a known-fixed procedure at runtime is waste.
46
46
 
47
47
  ## Pipeline
48
48
 
49
+ **Progress markers.** At the START of each Phase N, rewrite
50
+ `progress.ndjson` in the work directory so it holds one JSON line per
51
+ phase entered so far, in order:
52
+
53
+ ```
54
+ {"phase": 1, "name": "Intake"}
55
+ {"phase": 2, "name": "Node Classification"}
56
+ ```
57
+
58
+ Rewrite the whole file each time (append the new phase's line to the
59
+ ones already there). This file is machine-read for live progress
60
+ display; it is not a deliverable and never affects the IR.
61
+
49
62
  ### Phase 1: Intake
50
63
 
51
64
  Read the target directory exhaustively:
@@ -1,3 +1,3 @@
1
1
  """rote — graduate fuzzy AI skills into deterministic workflows."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.9.0"
@@ -0,0 +1,12 @@
1
+ """``python -m rote`` — the same CLI as the ``rote`` console script.
2
+
3
+ Exists so generated configs can invoke rote through a known
4
+ interpreter (``sys.executable -m rote``) instead of guessing at PATH —
5
+ the eval harness's ``headersHelper`` wiring depends on this.
6
+ """
7
+
8
+ import sys
9
+
10
+ from rote.cli import main
11
+
12
+ sys.exit(main())
@@ -31,7 +31,17 @@ class Adapter(Protocol):
31
31
  #: Options a factory may receive (forwarded from ``get_adapter``). Factories
32
32
  #: swallow unknown keys via ``**options`` — mirrors the driver-registry
33
33
  #: convention — so a runtime that doesn't support an option just ignores it.
34
- #: ``external_backend`` ("mcp" | "api") is understood by the DBOS adapter.
34
+ #: ``external_backend`` ("mcp" | "api") is understood by the DBOS, DBOS-TS,
35
+ #: and Inngest adapters.
36
+
37
+
38
+ def _validated_backend(options: dict[str, Any]) -> Literal["mcp", "api"] | None:
39
+ backend = options.get("external_backend")
40
+ if backend is None:
41
+ return None
42
+ if backend not in ("mcp", "api"):
43
+ raise ValueError(f"external_backend must be 'mcp' or 'api', got {backend!r}")
44
+ return cast(Literal["mcp", "api"], backend)
35
45
 
36
46
 
37
47
  def _temporal_adapter_factory(**options: Any) -> Adapter:
@@ -43,32 +53,39 @@ def _temporal_adapter_factory(**options: Any) -> Adapter:
43
53
 
44
54
 
45
55
  def _cloudflare_adapter_factory(**options: Any) -> Adapter:
46
- from rote.adapters.cloudflare import CloudflareAdapter
56
+ from rote.adapters.cloudflare import CloudflareAdapter, CloudflareAdapterConfig
47
57
 
48
- return CloudflareAdapter()
58
+ backend = _validated_backend(options)
59
+ if backend is None:
60
+ return CloudflareAdapter()
61
+ return CloudflareAdapter(CloudflareAdapterConfig(external_backend=backend))
49
62
 
50
63
 
51
64
  def _dbos_adapter_factory(**options: Any) -> Adapter:
52
65
  from rote.adapters.dbos import DbosAdapter, DbosAdapterConfig
53
66
 
54
- backend = options.get("external_backend")
67
+ backend = _validated_backend(options)
55
68
  if backend is None:
56
69
  return DbosAdapter()
57
- if backend not in ("mcp", "api"):
58
- raise ValueError(f"external_backend must be 'mcp' or 'api', got {backend!r}")
59
- return DbosAdapter(DbosAdapterConfig(external_backend=cast(Literal["mcp", "api"], backend)))
70
+ return DbosAdapter(DbosAdapterConfig(external_backend=backend))
60
71
 
61
72
 
62
73
  def _dbos_ts_adapter_factory(**options: Any) -> Adapter:
63
- from rote.adapters.dbos_ts import DbosTsAdapter
74
+ from rote.adapters.dbos_ts import DbosTsAdapter, DbosTsAdapterConfig
64
75
 
65
- return DbosTsAdapter()
76
+ backend = _validated_backend(options)
77
+ if backend is None:
78
+ return DbosTsAdapter()
79
+ return DbosTsAdapter(DbosTsAdapterConfig(external_backend=backend))
66
80
 
67
81
 
68
82
  def _inngest_adapter_factory(**options: Any) -> Adapter:
69
- from rote.adapters.inngest import InngestAdapter
83
+ from rote.adapters.inngest import InngestAdapter, InngestAdapterConfig
70
84
 
71
- return InngestAdapter()
85
+ backend = _validated_backend(options)
86
+ if backend is None:
87
+ return InngestAdapter()
88
+ return InngestAdapter(InngestAdapterConfig(external_backend=backend))
72
89
 
73
90
 
74
91
  def _python_adapter_factory(**options: Any) -> Adapter:
@@ -194,6 +194,27 @@ class EmitWriter:
194
194
  )
195
195
 
196
196
 
197
+ class EmitResult(dict[str, Path]):
198
+ """An adapter emit mapping that also carries preserved-conflict paths.
199
+
200
+ It *is* the ``label -> written path`` dict every ``emit()`` has always
201
+ returned (the path is the ``.new`` sibling when a user-edited file was
202
+ preserved), so existing consumers that iterate it keep working
203
+ unchanged. The extra :attr:`preserved` attribute exposes the
204
+ :class:`EmitWriter`'s ``rel -> .new path`` map directly, for callers
205
+ (the cloud runner) that want the conflict set without sniffing ``.new``
206
+ suffixes off the values.
207
+ """
208
+
209
+ def __init__(
210
+ self,
211
+ written: dict[str, Path] | None = None,
212
+ preserved: dict[str, Path] | None = None,
213
+ ) -> None:
214
+ super().__init__(written or {})
215
+ self.preserved: dict[str, Path] = dict(preserved or {})
216
+
217
+
197
218
  # ───────── Case conversion ─────────
198
219
 
199
220
 
@@ -238,6 +259,32 @@ def _pipeline_hash(pipeline: Pipeline) -> str:
238
259
  return hashlib.sha256(payload.encode()).hexdigest()[:8]
239
260
 
240
261
 
262
+ def workflow_class_name(pipeline: Pipeline) -> str:
263
+ """The generated workflow class name: ``<PascalName>Workflow``.
264
+
265
+ The Cloudflare and Temporal adapters both name their emitted
266
+ workflow class this way; centralizing it here keeps the manifest the
267
+ cloud consumes and the emitted TypeScript from drifting apart.
268
+ """
269
+ return f"{_to_pascal_case(pipeline.name)}Workflow"
270
+
271
+
272
+ def pipeline_identity(pipeline: Pipeline) -> dict[str, str]:
273
+ """Stable identity fields for a pipeline, shared by emitters and manifests.
274
+
275
+ Returns ``{name, version, pipeline_hash, class_name}`` — the same four
276
+ values the Cloudflare adapter bakes into the workflow.ts header and
277
+ writes into ``manifest.json`` for the cloud runner. Single source of
278
+ truth so the two can't disagree.
279
+ """
280
+ return {
281
+ "name": pipeline.name,
282
+ "version": pipeline.version,
283
+ "pipeline_hash": _pipeline_hash(pipeline),
284
+ "class_name": workflow_class_name(pipeline),
285
+ }
286
+
287
+
241
288
  # ───────── Duration strings ─────────
242
289
 
243
290
  _IR_DURATION_RE = re.compile(r"^(\d+(?:\.\d+)?)(ms|s|m|h|d)$")