dev-guru 0.2.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.
- {dev_guru-0.2.0 → dev_guru-0.4.0}/CHANGELOG.md +51 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/PKG-INFO +16 -5
- {dev_guru-0.2.0 → dev_guru-0.4.0}/README.md +13 -2
- {dev_guru-0.2.0 → dev_guru-0.4.0}/pyproject.toml +2 -2
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/cli.py +37 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/client/inprocess.py +53 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/client/negotiation.py +9 -3
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/client/port.py +27 -1
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/errors.py +11 -0
- dev_guru-0.4.0/src/dev_guru/gateway/__init__.py +5 -0
- dev_guru-0.4.0/src/dev_guru/gateway/server.py +390 -0
- dev_guru-0.4.0/src/dev_guru/gateway/static/app.css +73 -0
- dev_guru-0.4.0/src/dev_guru/gateway/static/app.js +405 -0
- dev_guru-0.4.0/src/dev_guru/gateway/static/favicon.svg +1 -0
- dev_guru-0.4.0/src/dev_guru/gateway/static/index.html +37 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/render.py +3 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/version.py +1 -1
- dev_guru-0.4.0/tests/test_gateway.py +647 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_negotiation.py +3 -3
- {dev_guru-0.2.0 → dev_guru-0.4.0}/.gitignore +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/LICENSE +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/__init__.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/__main__.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/client/__init__.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/diagram/__init__.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/present/__init__.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/__init__.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/assets/explorer.css +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/assets/explorer.html +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/assets/explorer.js +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/model.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/pages.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/reports/profiles.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/sdk.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/src/dev_guru/service.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/conftest.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_cli.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_client.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_diagram.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_facade_features.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_packaging.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_present.py +0 -0
- {dev_guru-0.2.0 → dev_guru-0.4.0}/tests/test_reports.py +0 -0
|
@@ -10,6 +10,57 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) — [SemVer](ht
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [0.4.0] — 2026-11-03
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **A web interface in `dev serve`.** Overview, runs, a code view and capabilities from your sources, as plain files in
|
|
18
|
+
the package: no Node and no build step. Runs are shown live, survive a tab reload and a lost connection, name any
|
|
19
|
+
events no longer kept, and stop only when you press Cancel. It works from the keyboard and on a phone. Pages that
|
|
20
|
+
don't exist yet say so and name the commands that work today. Guide: [The local gateway](docs/guides/gateway.md).
|
|
21
|
+
- `GET /api/v1/runs`, the runs in a workspace.
|
|
22
|
+
- Real-Chrome journeys for the interface, on a temporary workspace they build themselves (`make web-journeys`, and part
|
|
23
|
+
of the acceptance suite).
|
|
24
|
+
- [ROADMAP.md](ROADMAP.md): what's released and what's planned.
|
|
25
|
+
- Dependabot for `uv` and GitHub Actions, with a seven-day cooldown.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Needs Ananke 0.3.19 or newer, for its fixes to path traversal and vulnerable dependencies.
|
|
30
|
+
- CI grants `security-events: write` only to the jobs that upload findings.
|
|
31
|
+
- Releases are created with `gh release` rather than a third-party action.
|
|
32
|
+
- CI installs Trivy with retries and a pinned SHA-256 rather than through a third-party action, so one failed DNS lookup can't block a release.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- The run console could stop without showing a run's final event, if it read between the run's final state and that event.
|
|
37
|
+
|
|
38
|
+
- The gateway answered a login token or CSRF token that wasn't ASCII with a `500`. It's now the usual refusal. No request
|
|
39
|
+
got through either way.
|
|
40
|
+
|
|
41
|
+
### Security
|
|
42
|
+
|
|
43
|
+
- Reviewed two Semgrep findings as false positives and marked each in the code with its reason. Reviewed findings no
|
|
44
|
+
longer reappear as new alerts when a line moves.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## [0.3.0] — 2026-11-03
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **`dev serve`, a local gateway to Ananke.** It listens on loopback only. A single-use link signs in one browser, and
|
|
53
|
+
changes also need a same-origin `Origin`, a CSRF token and a JSON body. Workspaces are named by opaque id, and no route
|
|
54
|
+
takes a command or path to run. It starts and cancels runs, reads events and artifacts, previews workflows and serves
|
|
55
|
+
the code graph. There's no web interface yet. Guide: [The local gateway](docs/guides/gateway.md).
|
|
56
|
+
- The client port gains run, event, artifact, plan and code graph operations, so the gateway reaches Ananke only through it.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- Needs Ananke 0.3.17 or newer, for its facade that's safe across threads.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
13
64
|
## [0.2.0] — 2026-11-03
|
|
14
65
|
|
|
15
66
|
### Added
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dev-guru
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Dev Guru: understand, improve, verify and learn from your software. A local-first developer product on the Ananke Plexus harness: Python SDK and CLI
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Dev Guru: understand, improve, verify and learn from your software. A local-first developer product on the Ananke Plexus harness: Python SDK and CLI, offline HTML reports and a local web interface.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Neo-Pragnya/Dev-Guru
|
|
6
6
|
Project-URL: Repository, https://github.com/Neo-Pragnya/Dev-Guru
|
|
7
7
|
Project-URL: Issues, https://github.com/Neo-Pragnya/Dev-Guru/issues
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
23
|
Classifier: Typing :: Typed
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
|
-
Requires-Dist: ananke-plexus<0.4,>=0.3.
|
|
25
|
+
Requires-Dist: ananke-plexus<0.4,>=0.3.19
|
|
26
26
|
Requires-Dist: markdown-it-py<5,>=3
|
|
27
27
|
Requires-Dist: pydantic<3,>=2.11
|
|
28
28
|
Requires-Dist: rich>=13
|
|
@@ -90,6 +90,7 @@ dev present talk.md # Markdown slides → one offline, accessib
|
|
|
90
90
|
dev diagram docs/arch.md # an animated SVG of what a document states, every edge cited
|
|
91
91
|
dev explore . --profile public # a report safe to publish: no source, docstrings or Git revision
|
|
92
92
|
dev sources # capabilities from your folders, private Git and Dex Trove
|
|
93
|
+
dev serve # a local web interface: live runs, the code graph, your capabilities
|
|
93
94
|
dev learning review NAME # review what Ananke learned before it becomes reusable
|
|
94
95
|
```
|
|
95
96
|
|
|
@@ -103,11 +104,20 @@ with GuruClient(workspace="path/to/project") as guru:
|
|
|
103
104
|
guru.export(result, output="explorer.html")
|
|
104
105
|
```
|
|
105
106
|
|
|
107
|
+
## In your browser
|
|
108
|
+
|
|
109
|
+
`dev serve` opens a web interface to what Ananke computes. It shows what's connected, starts and follows runs (which
|
|
110
|
+
survive a reload or a lost connection, and stop only when you press Cancel), browses the code graph, and searches your
|
|
111
|
+
capabilities. It listens only on your computer, only the link it prints gets in, and it needs no Node: everything comes
|
|
112
|
+
from the [Dev Guru package on PyPI](https://pypi.org/project/dev-guru/). See [The local gateway](docs/guides/gateway.md).
|
|
113
|
+
|
|
114
|
+
What's coming next, including code maps, flows, ERDs and CVE views, is in the [roadmap](ROADMAP.md).
|
|
115
|
+
|
|
106
116
|
## How it fits together
|
|
107
117
|
|
|
108
118
|
| Repository | Role |
|
|
109
119
|
| --- | --- |
|
|
110
|
-
| **Dev Guru** (this repo) | The product: CLI and SDK, report view models and renderers,
|
|
120
|
+
| **Dev Guru** (this repo) | The product: CLI and SDK, report view models and renderers, a local web interface |
|
|
111
121
|
| [Ananke Plexus](https://github.com/Neo-Pragnya/Ananke-Plexus) | The harness: code graph, analysis, workflows, policy, runs and evidence |
|
|
112
122
|
| [Dex Trove](https://github.com/Neo-Pragnya/Dex-Trove) | Optional capability memory: curated skills, agents, instructions and workflows |
|
|
113
123
|
|
|
@@ -118,7 +128,8 @@ required. See [Architecture](docs/concepts/architecture.md) and [How Dev Guru ta
|
|
|
118
128
|
|
|
119
129
|
```bash
|
|
120
130
|
make verify # ruff, ruff format, strict mypy, pytest
|
|
121
|
-
make acceptance-wheel # capability scenarios on the built wheel, including real-Chrome
|
|
131
|
+
make acceptance-wheel # capability scenarios on the built wheel, including real-Chrome journeys
|
|
132
|
+
make web-journeys # just the web interface, in real Chrome, on a temporary workspace
|
|
122
133
|
make scan # gitleaks, pip-audit, GuardDog and Bandit on dist/
|
|
123
134
|
```
|
|
124
135
|
|
|
@@ -47,6 +47,7 @@ dev present talk.md # Markdown slides → one offline, accessib
|
|
|
47
47
|
dev diagram docs/arch.md # an animated SVG of what a document states, every edge cited
|
|
48
48
|
dev explore . --profile public # a report safe to publish: no source, docstrings or Git revision
|
|
49
49
|
dev sources # capabilities from your folders, private Git and Dex Trove
|
|
50
|
+
dev serve # a local web interface: live runs, the code graph, your capabilities
|
|
50
51
|
dev learning review NAME # review what Ananke learned before it becomes reusable
|
|
51
52
|
```
|
|
52
53
|
|
|
@@ -60,11 +61,20 @@ with GuruClient(workspace="path/to/project") as guru:
|
|
|
60
61
|
guru.export(result, output="explorer.html")
|
|
61
62
|
```
|
|
62
63
|
|
|
64
|
+
## In your browser
|
|
65
|
+
|
|
66
|
+
`dev serve` opens a web interface to what Ananke computes. It shows what's connected, starts and follows runs (which
|
|
67
|
+
survive a reload or a lost connection, and stop only when you press Cancel), browses the code graph, and searches your
|
|
68
|
+
capabilities. It listens only on your computer, only the link it prints gets in, and it needs no Node: everything comes
|
|
69
|
+
from the [Dev Guru package on PyPI](https://pypi.org/project/dev-guru/). See [The local gateway](docs/guides/gateway.md).
|
|
70
|
+
|
|
71
|
+
What's coming next, including code maps, flows, ERDs and CVE views, is in the [roadmap](ROADMAP.md).
|
|
72
|
+
|
|
63
73
|
## How it fits together
|
|
64
74
|
|
|
65
75
|
| Repository | Role |
|
|
66
76
|
| --- | --- |
|
|
67
|
-
| **Dev Guru** (this repo) | The product: CLI and SDK, report view models and renderers,
|
|
77
|
+
| **Dev Guru** (this repo) | The product: CLI and SDK, report view models and renderers, a local web interface |
|
|
68
78
|
| [Ananke Plexus](https://github.com/Neo-Pragnya/Ananke-Plexus) | The harness: code graph, analysis, workflows, policy, runs and evidence |
|
|
69
79
|
| [Dex Trove](https://github.com/Neo-Pragnya/Dex-Trove) | Optional capability memory: curated skills, agents, instructions and workflows |
|
|
70
80
|
|
|
@@ -75,7 +85,8 @@ required. See [Architecture](docs/concepts/architecture.md) and [How Dev Guru ta
|
|
|
75
85
|
|
|
76
86
|
```bash
|
|
77
87
|
make verify # ruff, ruff format, strict mypy, pytest
|
|
78
|
-
make acceptance-wheel # capability scenarios on the built wheel, including real-Chrome
|
|
88
|
+
make acceptance-wheel # capability scenarios on the built wheel, including real-Chrome journeys
|
|
89
|
+
make web-journeys # just the web interface, in real Chrome, on a temporary workspace
|
|
79
90
|
make scan # gitleaks, pip-audit, GuardDog and Bandit on dist/
|
|
80
91
|
```
|
|
81
92
|
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dev-guru"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
description = "Dev Guru: understand, improve, verify and learn from your software. A local-first developer product on the Ananke Plexus harness: Python SDK and CLI
|
|
8
|
+
description = "Dev Guru: understand, improve, verify and learn from your software. A local-first developer product on the Ananke Plexus harness: Python SDK and CLI, offline HTML reports and a local web interface."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
# Dev Guru is a client of Ananke: the harness owns analysis, execution and evidence (docs/architecture.md).
|
|
29
29
|
# Keep this range in step with SUPPORTED_ANANKE in src/dev_guru/client/negotiation.py (tests/test_packaging.py).
|
|
30
30
|
dependencies = [
|
|
31
|
-
"ananke-plexus>=0.3.
|
|
31
|
+
"ananke-plexus>=0.3.19,<0.4",
|
|
32
32
|
"markdown-it-py>=3,<5",
|
|
33
33
|
"pydantic>=2.11,<3",
|
|
34
34
|
"rich>=13",
|
|
@@ -286,6 +286,43 @@ def learning_review(
|
|
|
286
286
|
webbrowser.open(out.resolve().as_uri())
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
@app.command()
|
|
290
|
+
def serve(
|
|
291
|
+
workspaces: Annotated[
|
|
292
|
+
list[Path] | None, typer.Argument(help="Projects the browser may reach (default: here)")
|
|
293
|
+
] = None,
|
|
294
|
+
port: Annotated[int, typer.Option(help="Local port (default: any free one)")] = 0,
|
|
295
|
+
open_browser: Annotated[bool, typer.Option("--open/--no-open", help="Open the browser")] = True,
|
|
296
|
+
transport: TransportOpt = "in-process",
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Start the local web gateway. It only listens on this computer, and only the printed link gets in."""
|
|
299
|
+
from dev_guru.gateway import Gateway
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
client = connect(transport)
|
|
303
|
+
client.require_compatible() # type: ignore[attr-defined]
|
|
304
|
+
gateway = Gateway(workspaces or [Path.cwd()], client, port=port)
|
|
305
|
+
except errors.GuruError as exc:
|
|
306
|
+
_fail(exc, False, 2)
|
|
307
|
+
except OSError as exc:
|
|
308
|
+
_fail(
|
|
309
|
+
errors.GuruError(errors.INVALID_INPUT, f"can't listen on port {port}: {exc}"), False, 2
|
|
310
|
+
)
|
|
311
|
+
# printed plainly: rich would wrap a long link, and this link is the way in
|
|
312
|
+
print(
|
|
313
|
+
f"Dev Guru is listening on this computer only. Open this link, which works once:\n{gateway.login_url}",
|
|
314
|
+
flush=True,
|
|
315
|
+
)
|
|
316
|
+
if open_browser:
|
|
317
|
+
webbrowser.open(gateway.login_url)
|
|
318
|
+
try:
|
|
319
|
+
gateway.serve_forever()
|
|
320
|
+
except KeyboardInterrupt:
|
|
321
|
+
pass
|
|
322
|
+
finally:
|
|
323
|
+
gateway.shutdown()
|
|
324
|
+
|
|
325
|
+
|
|
289
326
|
@app.command()
|
|
290
327
|
def diagram(
|
|
291
328
|
document: Annotated[Path, typer.Argument(help="An approved Markdown document")],
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections import Counter
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -87,6 +88,58 @@ class InProcessAnanke:
|
|
|
87
88
|
raise errors.GuruError(errors.HARNESS_INCOMPATIBLE, exc.message) from exc
|
|
88
89
|
return facade
|
|
89
90
|
|
|
91
|
+
def _call(self, workspace: Path, work: Callable[[Any], Any]) -> Any:
|
|
92
|
+
"""Run one facade call, turning what Ananke refuses into a typed error that keeps its code."""
|
|
93
|
+
from ananke.plexus.app import AppError
|
|
94
|
+
|
|
95
|
+
facade = self._facade(self._root(workspace))
|
|
96
|
+
try:
|
|
97
|
+
return work(facade)
|
|
98
|
+
except AppError as exc:
|
|
99
|
+
raise errors.HarnessRefused(exc.code, exc.message, retryable=exc.retryable) from exc
|
|
100
|
+
|
|
101
|
+
def harness_capabilities(self, workspace: Path) -> dict[str, Any]:
|
|
102
|
+
return dict(self._call(workspace, lambda f: f.capabilities()))
|
|
103
|
+
|
|
104
|
+
def code_view(self, workspace: Path, **filters: Any) -> dict[str, Any]:
|
|
105
|
+
return dict(self._call(workspace, lambda f: f.code_view(**filters)))
|
|
106
|
+
|
|
107
|
+
def prepare_plan(self, workspace: Path, draft: dict[str, Any]) -> dict[str, Any]:
|
|
108
|
+
return dict(self._call(workspace, lambda f: f.preview_workflow(draft)))
|
|
109
|
+
|
|
110
|
+
def submit_run(
|
|
111
|
+
self, workspace: Path, kind: str, parameters: dict[str, Any], idempotency_key: str
|
|
112
|
+
) -> dict[str, Any]:
|
|
113
|
+
return dict(
|
|
114
|
+
self._call(
|
|
115
|
+
workspace,
|
|
116
|
+
lambda f: f.submit_run(
|
|
117
|
+
f.prepare_run(kind, parameters), idempotency_key, start=False
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def drive_run(self, workspace: Path, run_id: str) -> dict[str, Any]:
|
|
123
|
+
return dict(self._call(workspace, lambda f: f.drive(run_id)))
|
|
124
|
+
|
|
125
|
+
def get_run(self, workspace: Path, run_id: str) -> dict[str, Any]:
|
|
126
|
+
return dict(self._call(workspace, lambda f: f.get_run(run_id)))
|
|
127
|
+
|
|
128
|
+
def run_history(self, workspace: Path) -> dict[str, Any]:
|
|
129
|
+
return dict(self._call(workspace, lambda f: f.evidence_view()))
|
|
130
|
+
|
|
131
|
+
def run_events(self, workspace: Path, run_id: str, after: int = 0) -> dict[str, Any]:
|
|
132
|
+
return dict(self._call(workspace, lambda f: f.stream_events(run_id, after)))
|
|
133
|
+
|
|
134
|
+
def cancel_run(self, workspace: Path, run_id: str) -> dict[str, Any]:
|
|
135
|
+
return dict(self._call(workspace, lambda f: f.cancel_run(run_id)))
|
|
136
|
+
|
|
137
|
+
def run_artifacts(self, workspace: Path, run_id: str) -> list[dict[str, Any]]:
|
|
138
|
+
return list(self._call(workspace, lambda f: f.list_artifacts(run_id)))
|
|
139
|
+
|
|
140
|
+
def read_artifact(self, workspace: Path, run_id: str, name: str) -> bytes:
|
|
141
|
+
return bytes(self._call(workspace, lambda f: f.read_artifact(run_id, name)))
|
|
142
|
+
|
|
90
143
|
def graph_data(self, workspace: Path) -> GraphData:
|
|
91
144
|
"""Run Ananke's graph build through the app facade and read the snapshot artifact it produced."""
|
|
92
145
|
info = self.require_compatible()
|
|
@@ -11,10 +11,8 @@ import importlib
|
|
|
11
11
|
import re
|
|
12
12
|
from importlib import metadata
|
|
13
13
|
|
|
14
|
-
SUPPORTED_ANANKE = ">=0.3.
|
|
15
|
-
_MIN = (0, 3, 13)
|
|
14
|
+
SUPPORTED_ANANKE = ">=0.3.19,<0.4" # keep in step with pyproject.toml (tests/test_packaging.py)
|
|
16
15
|
APP_PROTOCOL = "ananke-app/1" # the app facade protocol this Dev Guru speaks
|
|
17
|
-
_BELOW = (0, 4, 0)
|
|
18
16
|
|
|
19
17
|
# Capability id -> (module, attribute path). Required ones are what this release of Dev Guru calls.
|
|
20
18
|
CAPABILITIES: dict[str, tuple[str, str]] = {
|
|
@@ -36,6 +34,12 @@ def parse_version(text: str) -> tuple[int, int, int] | None:
|
|
|
36
34
|
return (int(m.group(1)), int(m.group(2)), int(m.group(3))) if m else None
|
|
37
35
|
|
|
38
36
|
|
|
37
|
+
_MIN, _BELOW = (
|
|
38
|
+
tuple(int(part) for part in bound.split("."))
|
|
39
|
+
for bound in re.fullmatch(r">=([\d.]+),<([\d.]+)", SUPPORTED_ANANKE).groups() # type: ignore[union-attr]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
39
43
|
def supported(version: str) -> bool:
|
|
40
44
|
parsed = parse_version(version)
|
|
41
45
|
return parsed is not None and _MIN <= parsed < _BELOW
|
|
@@ -53,6 +57,8 @@ def probe(capabilities: dict[str, tuple[str, str]] = CAPABILITIES) -> dict[str,
|
|
|
53
57
|
found: dict[str, bool] = {}
|
|
54
58
|
for cap, (module, attr_path) in capabilities.items():
|
|
55
59
|
try:
|
|
60
|
+
# False positive: module names come from the fixed CAPABILITIES table above, never from a request or a file.
|
|
61
|
+
# nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
|
|
56
62
|
obj: object = importlib.import_module(module)
|
|
57
63
|
for part in attr_path.split("."):
|
|
58
64
|
obj = getattr(obj, part)
|
|
@@ -9,7 +9,7 @@ silently swapped for another (docs/concepts/ananke-client.md).
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Protocol, runtime_checkable
|
|
12
|
+
from typing import Any, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
14
|
from pydantic import BaseModel, Field
|
|
15
15
|
|
|
@@ -125,6 +125,32 @@ class AnankeClient(Protocol):
|
|
|
125
125
|
|
|
126
126
|
def learning_candidate(self, workspace: Path, name: str) -> LearningCandidate: ...
|
|
127
127
|
|
|
128
|
+
def harness_capabilities(self, workspace: Path) -> dict[str, Any]: ...
|
|
129
|
+
|
|
130
|
+
def code_view(self, workspace: Path, **filters: Any) -> dict[str, Any]: ...
|
|
131
|
+
|
|
132
|
+
def prepare_plan(self, workspace: Path, draft: dict[str, Any]) -> dict[str, Any]: ...
|
|
133
|
+
|
|
134
|
+
def submit_run(
|
|
135
|
+
self, workspace: Path, kind: str, parameters: dict[str, Any], idempotency_key: str
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""Record a run without starting it. The same key and request return the same run."""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
def drive_run(self, workspace: Path, run_id: str) -> dict[str, Any]: ...
|
|
141
|
+
|
|
142
|
+
def get_run(self, workspace: Path, run_id: str) -> dict[str, Any]: ...
|
|
143
|
+
|
|
144
|
+
def run_history(self, workspace: Path) -> dict[str, Any]: ...
|
|
145
|
+
|
|
146
|
+
def run_events(self, workspace: Path, run_id: str, after: int = 0) -> dict[str, Any]: ...
|
|
147
|
+
|
|
148
|
+
def cancel_run(self, workspace: Path, run_id: str) -> dict[str, Any]: ...
|
|
149
|
+
|
|
150
|
+
def run_artifacts(self, workspace: Path, run_id: str) -> list[dict[str, Any]]: ...
|
|
151
|
+
|
|
152
|
+
def read_artifact(self, workspace: Path, run_id: str, name: str) -> bytes: ...
|
|
153
|
+
|
|
128
154
|
|
|
129
155
|
__all__ = [
|
|
130
156
|
"AnankeClient",
|
|
@@ -10,6 +10,7 @@ UNSUPPORTED_TRANSPORT = "UNSUPPORTED_TRANSPORT"
|
|
|
10
10
|
WORKSPACE_INVALID = "WORKSPACE_INVALID"
|
|
11
11
|
HARNESS_FAILED = "HARNESS_FAILED"
|
|
12
12
|
INVALID_INPUT = "INVALID_INPUT"
|
|
13
|
+
HARNESS_REFUSED = "HARNESS_REFUSED"
|
|
13
14
|
CODES = frozenset(
|
|
14
15
|
{
|
|
15
16
|
HARNESS_MISSING,
|
|
@@ -19,6 +20,7 @@ CODES = frozenset(
|
|
|
19
20
|
WORKSPACE_INVALID,
|
|
20
21
|
HARNESS_FAILED,
|
|
21
22
|
INVALID_INPUT,
|
|
23
|
+
HARNESS_REFUSED,
|
|
22
24
|
}
|
|
23
25
|
)
|
|
24
26
|
|
|
@@ -44,3 +46,12 @@ class GuruError(Exception):
|
|
|
44
46
|
"hint": self.hint,
|
|
45
47
|
"run_id": self.run_id,
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class HarnessRefused(GuruError):
|
|
52
|
+
"""Ananke refused a request. ``harness_code`` is its own stable code, kept so a client can branch on it."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, harness_code: str, message: str, *, retryable: bool = False) -> None:
|
|
55
|
+
super().__init__(HARNESS_REFUSED, message)
|
|
56
|
+
self.harness_code = harness_code
|
|
57
|
+
self.retryable = retryable
|