dev-guru 0.1.0__tar.gz → 0.2.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/CHANGELOG.md +93 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/PKG-INFO +20 -6
- {dev_guru-0.1.0 → dev_guru-0.2.0}/README.md +17 -4
- {dev_guru-0.1.0 → dev_guru-0.2.0}/pyproject.toml +2 -1
- dev_guru-0.2.0/src/dev_guru/cli.py +347 -0
- dev_guru-0.2.0/src/dev_guru/client/inprocess.py +277 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/client/negotiation.py +7 -3
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/client/port.py +53 -1
- dev_guru-0.2.0/src/dev_guru/diagram/__init__.py +308 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/errors.py +2 -0
- dev_guru-0.2.0/src/dev_guru/present/__init__.py +333 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/reports/assets/explorer.html +1 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/reports/assets/explorer.js +315 -93
- dev_guru-0.2.0/src/dev_guru/reports/pages.py +89 -0
- dev_guru-0.2.0/src/dev_guru/reports/profiles.py +49 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/sdk.py +1 -1
- dev_guru-0.2.0/src/dev_guru/service.py +88 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/version.py +1 -1
- {dev_guru-0.1.0 → dev_guru-0.2.0}/tests/test_client.py +15 -5
- dev_guru-0.2.0/tests/test_diagram.py +125 -0
- dev_guru-0.2.0/tests/test_facade_features.py +109 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/tests/test_negotiation.py +7 -2
- {dev_guru-0.1.0 → dev_guru-0.2.0}/tests/test_packaging.py +10 -1
- dev_guru-0.2.0/tests/test_present.py +119 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/tests/test_reports.py +36 -0
- dev_guru-0.1.0/CHANGELOG.md +0 -49
- dev_guru-0.1.0/src/dev_guru/cli.py +0 -149
- dev_guru-0.1.0/src/dev_guru/client/inprocess.py +0 -122
- dev_guru-0.1.0/src/dev_guru/service.py +0 -53
- {dev_guru-0.1.0 → dev_guru-0.2.0}/.gitignore +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/LICENSE +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/__init__.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/__main__.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/client/__init__.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/reports/__init__.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/reports/assets/explorer.css +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/reports/model.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/src/dev_guru/reports/render.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/tests/conftest.py +0 -0
- {dev_guru-0.1.0 → dev_guru-0.2.0}/tests/test_cli.py +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Dev Guru are documented here.
|
|
4
|
+
|
|
5
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) — [SemVer](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## [0.2.0] — 2026-11-03
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Runs through the Ananke app facade** (`ananke-app/1`, needs Ananke 0.3.13):
|
|
18
|
+
- `dev explore` builds the graph in an idempotent Ananke run, records the run in the report manifest
|
|
19
|
+
(`ananke_run`), and registers the HTML back into that run as an artifact with its inputs;
|
|
20
|
+
- a failed build is a failed run, with its id kept as evidence.
|
|
21
|
+
- **`dev sources`**: source-neutral capability discovery (internal folders, private Git, troves) with an offline
|
|
22
|
+
picker page. Works with Dex switched off.
|
|
23
|
+
- **`dev learning review`**: an offline review page for an Ananke learning candidate, showing the gate status,
|
|
24
|
+
evaluation, review, provenance and a diff per destination.
|
|
25
|
+
|
|
26
|
+
- **`dev diagram`**: an animated SVG of the relationships a document states (arrow lines, Mermaid flowcharts, From/To
|
|
27
|
+
tables). Every edge cites its file and line, and an edited edge that fails its citation is drawn as a hypothesis.
|
|
28
|
+
Guide: [Diagrams from documents](docs/guides/diagrams.md).
|
|
29
|
+
- **`dev present`**: Markdown slides become one offline, accessible HTML deck:
|
|
30
|
+
- `lantern` and `paper` themes, responsive columns, step-by-step lists, speaker notes, keyboard navigation;
|
|
31
|
+
- bar charts as accessible SVG;
|
|
32
|
+
- embedded local images that require alt text; raw HTML is shown as text;
|
|
33
|
+
- `--check` verifies a deck. Guide: [Presentations](docs/guides/presentations.md).
|
|
34
|
+
- **Publication profiles** for `dev explore` (`--profile public`): removes source, docstrings, string defaults in
|
|
35
|
+
signatures and the Git revision, so a report is safe to publish. The manifest records the profile.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## [0.1.1] — 2026-11-02
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **The constellation is a 3D force-directed graph.** Imports are springs, files repel each other and packages pull
|
|
44
|
+
their files together. Drag to orbit, scroll to zoom, drag a file to pull the graph, double-click to fly to a file;
|
|
45
|
+
it turns slowly when idle. Shaded bodies, depth fog, a star field and pulses along lit imports, with labels that
|
|
46
|
+
never overlap. Drawn on a plain canvas inside the report: no WebGL, no library, no network. With reduced motion it
|
|
47
|
+
settles before the first frame and stays still.
|
|
48
|
+
- Docs: the client-port design is now a plain concept page, [How Dev Guru talks to Ananke](docs/concepts/ananke-client.md).
|
|
49
|
+
PyPI links on the README, the docs home and the site.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- `.trivyignore` added, so the release workflow's Trivy gate can run.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## [0.1.0] — 2026-11-02
|
|
58
|
+
|
|
59
|
+
The first release.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **`dev explore`**: Ananke builds the code graph, and Dev Guru writes one interactive, offline HTML explorer plus a
|
|
64
|
+
manifest.
|
|
65
|
+
- Six views: constellation, flow (package or file level), impact rings, calls, hotspots and a narrated tour.
|
|
66
|
+
- Around them: search, a command palette, a source inspector, a light and dark theme, and URL-shareable state.
|
|
67
|
+
- Explicit downloads of the view state and of analysis requests.
|
|
68
|
+
- **Report safety.**
|
|
69
|
+
- Everything is inlined, and the page sets a CSP with `connect-src 'none'`.
|
|
70
|
+
- JSON is embedded with `<`, `>`, `&` and U+2028/U+2029 escaped; untrusted text is set only with
|
|
71
|
+
`textContent`.
|
|
72
|
+
- Templates are filled in a single pass, so source code containing the template's placeholders stays data.
|
|
73
|
+
- A manifest records the renderer, the Dev Guru and Ananke versions, the input digest and the git revision.
|
|
74
|
+
- **View model `dev-guru/explorer@1`**: packages, files, symbols with source snippets, resolved import, call and
|
|
75
|
+
inheritance edges, insights (most depended-on, largest, entry points, import cycles) and stated limitations.
|
|
76
|
+
Deterministic.
|
|
77
|
+
- **SDK** `GuruClient(workspace).explore()` / `.export()`, sharing one service layer with the CLI.
|
|
78
|
+
- **`dev doctor`**: version and capability negotiation with Ananke (`ananke-plexus>=0.3.12,<0.4`) over one explicit
|
|
79
|
+
transport, with stable error codes.
|
|
80
|
+
- **Docs site** with a live demo (Dev Guru exploring its own source) and the "Lantern" theme.
|
|
81
|
+
- **Release gates:**
|
|
82
|
+
- acceptance on the built wheel, including real-Chrome report journeys and a dogfood run on Dev Guru's own source;
|
|
83
|
+
- a distribution scan (gitleaks, pip-audit, GuardDog, Bandit);
|
|
84
|
+
- Trivy and build provenance.
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- The command is **`dev`** (it was `devguru` in the unreleased scaffold).
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- Import cycles in Dev Guru itself, found by exploring it: `dev_guru` now exposes `GuruClient` lazily, and
|
|
93
|
+
`client.inprocess` imports `negotiation` directly.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dev-guru
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
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 first, offline HTML reports, later a local web UI.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Neo-Pragnya/Dev-Guru
|
|
6
6
|
Project-URL: Repository, https://github.com/Neo-Pragnya/Dev-Guru
|
|
@@ -22,7 +22,8 @@ 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.13
|
|
26
|
+
Requires-Dist: markdown-it-py<5,>=3
|
|
26
27
|
Requires-Dist: pydantic<3,>=2.11
|
|
27
28
|
Requires-Dist: rich>=13
|
|
28
29
|
Requires-Dist: typer<1,>=0.16
|
|
@@ -42,10 +43,13 @@ Description-Content-Type: text/markdown
|
|
|
42
43
|
|
|
43
44
|
# Dev Guru
|
|
44
45
|
|
|
46
|
+
[](https://pypi.org/project/dev-guru/)
|
|
47
|
+
[](https://pypi.org/project/dev-guru/)
|
|
48
|
+
|
|
45
49
|
**See your code. Understand it. Change it with confidence.**
|
|
46
50
|
|
|
47
51
|
Point Dev Guru at a Python project and get **one offline HTML explorer**:
|
|
48
|
-
- a
|
|
52
|
+
- a 3D constellation of files you can orbit, where imports pull and packages cluster;
|
|
49
53
|
- the flow of dependencies;
|
|
50
54
|
- the blast radius of a change;
|
|
51
55
|
- who calls what, and where the hotspots are;
|
|
@@ -61,14 +65,14 @@ dev explore path/to/project -o explorer.html --open
|
|
|
61
65
|
|
|
62
66
|

|
|
63
67
|
|
|
64
|
-
**[Documentation and a live demo →](https://neo-pragnya.github.io/Dev-Guru/)**: the site's hero runs the real
|
|
68
|
+
**[Documentation and a live demo →](https://neo-pragnya.github.io/Dev-Guru/)** · **[Dev Guru on PyPI →](https://pypi.org/project/dev-guru/)**: the site's hero runs the real
|
|
65
69
|
report of Dev Guru exploring its own source.
|
|
66
70
|
|
|
67
71
|
## What's in a report
|
|
68
72
|
|
|
69
73
|
| View | Question it answers |
|
|
70
74
|
| --- | --- |
|
|
71
|
-
| ✦ Constellation | What's here?
|
|
75
|
+
| ✦ Constellation | What's here? A 3D force-directed graph: drag to orbit, hover to light up imports |
|
|
72
76
|
| ⇶ Flow | What depends on what? Entry side to foundations; cycles glow rose |
|
|
73
77
|
| ◎ Impact | What breaks if this changes? Blast-radius rings over importers and callers |
|
|
74
78
|
| ƒ Calls | Who calls in, and what goes out? A symbol-level ego network |
|
|
@@ -79,6 +83,16 @@ Plus search (`/`), a command palette (`Ctrl`/`⌘` `K`), a source inspector, a l
|
|
|
79
83
|
URLs. Reports are **offline and view-only**, treat source as text, and carry a manifest with the input digest and the
|
|
80
84
|
Git revision. See [report safety](docs/concepts/report-safety.md).
|
|
81
85
|
|
|
86
|
+
## More than the explorer
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
dev present talk.md # Markdown slides → one offline, accessible HTML deck
|
|
90
|
+
dev diagram docs/arch.md # an animated SVG of what a document states, every edge cited
|
|
91
|
+
dev explore . --profile public # a report safe to publish: no source, docstrings or Git revision
|
|
92
|
+
dev sources # capabilities from your folders, private Git and Dex Trove
|
|
93
|
+
dev learning review NAME # review what Ananke learned before it becomes reusable
|
|
94
|
+
```
|
|
95
|
+
|
|
82
96
|
## From Python
|
|
83
97
|
|
|
84
98
|
```python
|
|
@@ -98,7 +112,7 @@ with GuruClient(workspace="path/to/project") as guru:
|
|
|
98
112
|
| [Dex Trove](https://github.com/Neo-Pragnya/Dex-Trove) | Optional capability memory: curated skills, agents, instructions and workflows |
|
|
99
113
|
|
|
100
114
|
Dev Guru never analyses code itself: every domain operation goes through one client port to Ananke, and Dex is never
|
|
101
|
-
required. See [Architecture](docs/concepts/architecture.md) and [
|
|
115
|
+
required. See [Architecture](docs/concepts/architecture.md) and [How Dev Guru talks to Ananke](docs/concepts/ananke-client.md).
|
|
102
116
|
|
|
103
117
|
## Quality gates
|
|
104
118
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# Dev Guru
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/dev-guru/)
|
|
4
|
+
[](https://pypi.org/project/dev-guru/)
|
|
5
|
+
|
|
3
6
|
**See your code. Understand it. Change it with confidence.**
|
|
4
7
|
|
|
5
8
|
Point Dev Guru at a Python project and get **one offline HTML explorer**:
|
|
6
|
-
- a
|
|
9
|
+
- a 3D constellation of files you can orbit, where imports pull and packages cluster;
|
|
7
10
|
- the flow of dependencies;
|
|
8
11
|
- the blast radius of a change;
|
|
9
12
|
- who calls what, and where the hotspots are;
|
|
@@ -19,14 +22,14 @@ dev explore path/to/project -o explorer.html --open
|
|
|
19
22
|
|
|
20
23
|

|
|
21
24
|
|
|
22
|
-
**[Documentation and a live demo →](https://neo-pragnya.github.io/Dev-Guru/)**: the site's hero runs the real
|
|
25
|
+
**[Documentation and a live demo →](https://neo-pragnya.github.io/Dev-Guru/)** · **[Dev Guru on PyPI →](https://pypi.org/project/dev-guru/)**: the site's hero runs the real
|
|
23
26
|
report of Dev Guru exploring its own source.
|
|
24
27
|
|
|
25
28
|
## What's in a report
|
|
26
29
|
|
|
27
30
|
| View | Question it answers |
|
|
28
31
|
| --- | --- |
|
|
29
|
-
| ✦ Constellation | What's here?
|
|
32
|
+
| ✦ Constellation | What's here? A 3D force-directed graph: drag to orbit, hover to light up imports |
|
|
30
33
|
| ⇶ Flow | What depends on what? Entry side to foundations; cycles glow rose |
|
|
31
34
|
| ◎ Impact | What breaks if this changes? Blast-radius rings over importers and callers |
|
|
32
35
|
| ƒ Calls | Who calls in, and what goes out? A symbol-level ego network |
|
|
@@ -37,6 +40,16 @@ Plus search (`/`), a command palette (`Ctrl`/`⌘` `K`), a source inspector, a l
|
|
|
37
40
|
URLs. Reports are **offline and view-only**, treat source as text, and carry a manifest with the input digest and the
|
|
38
41
|
Git revision. See [report safety](docs/concepts/report-safety.md).
|
|
39
42
|
|
|
43
|
+
## More than the explorer
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dev present talk.md # Markdown slides → one offline, accessible HTML deck
|
|
47
|
+
dev diagram docs/arch.md # an animated SVG of what a document states, every edge cited
|
|
48
|
+
dev explore . --profile public # a report safe to publish: no source, docstrings or Git revision
|
|
49
|
+
dev sources # capabilities from your folders, private Git and Dex Trove
|
|
50
|
+
dev learning review NAME # review what Ananke learned before it becomes reusable
|
|
51
|
+
```
|
|
52
|
+
|
|
40
53
|
## From Python
|
|
41
54
|
|
|
42
55
|
```python
|
|
@@ -56,7 +69,7 @@ with GuruClient(workspace="path/to/project") as guru:
|
|
|
56
69
|
| [Dex Trove](https://github.com/Neo-Pragnya/Dex-Trove) | Optional capability memory: curated skills, agents, instructions and workflows |
|
|
57
70
|
|
|
58
71
|
Dev Guru never analyses code itself: every domain operation goes through one client port to Ananke, and Dex is never
|
|
59
|
-
required. See [Architecture](docs/concepts/architecture.md) and [
|
|
72
|
+
required. See [Architecture](docs/concepts/architecture.md) and [How Dev Guru talks to Ananke](docs/concepts/ananke-client.md).
|
|
60
73
|
|
|
61
74
|
## Quality gates
|
|
62
75
|
|
|
@@ -28,7 +28,8 @@ 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.13,<0.4",
|
|
32
|
+
"markdown-it-py>=3,<5",
|
|
32
33
|
"pydantic>=2.11,<3",
|
|
33
34
|
"rich>=13",
|
|
34
35
|
"typer>=0.16,<1",
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""The ``dev`` command. CLI and SDK share one service layer (dev_guru.service)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import platform
|
|
7
|
+
import webbrowser
|
|
8
|
+
from importlib import metadata
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.markup import escape
|
|
15
|
+
|
|
16
|
+
from dev_guru import errors, service
|
|
17
|
+
from dev_guru.client import TRANSPORTS, connect
|
|
18
|
+
from dev_guru.version import __version__
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(
|
|
21
|
+
name="dev",
|
|
22
|
+
help="Dev Guru: understand, improve and verify your software, on the Ananke Plexus harness.",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
add_completion=False,
|
|
25
|
+
)
|
|
26
|
+
console = Console()
|
|
27
|
+
err = Console(stderr=True)
|
|
28
|
+
TransportOpt = Annotated[str, typer.Option(help=f"Harness transport: {', '.join(TRANSPORTS)}")]
|
|
29
|
+
JsonOpt = Annotated[bool, typer.Option("--json", help="Machine-readable output")]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _version(value: bool) -> None:
|
|
33
|
+
if value:
|
|
34
|
+
console.print(f"dev-guru {__version__}")
|
|
35
|
+
raise typer.Exit()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.callback()
|
|
39
|
+
def main(
|
|
40
|
+
version: Annotated[
|
|
41
|
+
bool,
|
|
42
|
+
typer.Option(
|
|
43
|
+
"--version", callback=_version, is_eager=True, help="Show the version and exit"
|
|
44
|
+
),
|
|
45
|
+
] = False,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Dev Guru: understand, improve and verify your software, on the Ananke Plexus harness."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _fail(exc: errors.GuruError, as_json: bool, code: int) -> None:
|
|
51
|
+
if as_json:
|
|
52
|
+
console.print_json(json.dumps({"ok": False, "error": exc.to_dict()}))
|
|
53
|
+
else:
|
|
54
|
+
err.print(f"[red]✗ {exc.code}: {exc.message}[/red]")
|
|
55
|
+
if exc.hint:
|
|
56
|
+
err.print(f" [dim]{exc.hint}[/dim]")
|
|
57
|
+
raise typer.Exit(code) from exc
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _optional(distribution: str) -> str | None:
|
|
61
|
+
try:
|
|
62
|
+
return metadata.version(distribution)
|
|
63
|
+
except metadata.PackageNotFoundError:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command()
|
|
68
|
+
def doctor(transport: TransportOpt = "in-process", as_json: JsonOpt = False) -> None:
|
|
69
|
+
"""Check the Ananke harness: installed version, supported range and the capabilities Dev Guru needs.
|
|
70
|
+
|
|
71
|
+
Exits 1 when the harness is missing or incompatible, 2 for an unknown transport.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
info = connect(transport).info()
|
|
75
|
+
except errors.GuruError as exc:
|
|
76
|
+
_fail(exc, as_json, 2)
|
|
77
|
+
return
|
|
78
|
+
dex = _optional("dex-trove")
|
|
79
|
+
report = {
|
|
80
|
+
"ok": info.compatible,
|
|
81
|
+
"dev_guru": __version__,
|
|
82
|
+
"python": platform.python_version(),
|
|
83
|
+
"harness": info.model_dump(),
|
|
84
|
+
"optional": {"dex-trove": dex},
|
|
85
|
+
}
|
|
86
|
+
if as_json:
|
|
87
|
+
console.print_json(json.dumps(report))
|
|
88
|
+
else:
|
|
89
|
+
mark = "[green]✓[/green]" if info.compatible else "[red]✗[/red]"
|
|
90
|
+
console.print(
|
|
91
|
+
f"dev-guru {__version__} · Python {report['python']} · transport {info.transport}"
|
|
92
|
+
)
|
|
93
|
+
console.print(
|
|
94
|
+
f"{mark} ananke-plexus {info.ananke_version or 'not installed'} (supported {info.supported_range})"
|
|
95
|
+
)
|
|
96
|
+
for cap, present in info.capabilities.items():
|
|
97
|
+
console.print(f" {'✓' if present else '✗'} {cap}")
|
|
98
|
+
for reason in info.reasons:
|
|
99
|
+
console.print(f" [red]{reason}[/red]")
|
|
100
|
+
console.print(f" [dim]dex-trove: {dex or 'not installed (optional)'}[/dim]")
|
|
101
|
+
if not info.compatible:
|
|
102
|
+
raise typer.Exit(1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.command()
|
|
106
|
+
def explore(
|
|
107
|
+
workspace: Annotated[Path, typer.Argument(help="Project to explore")] = Path("."),
|
|
108
|
+
output: Annotated[
|
|
109
|
+
Path, typer.Option("--output", "-o", help="Report file (a manifest is written next to it)")
|
|
110
|
+
] = Path("dev-guru-explorer.html"),
|
|
111
|
+
name: Annotated[str | None, typer.Option(help="Project name shown in the report")] = None,
|
|
112
|
+
open_browser: Annotated[
|
|
113
|
+
bool, typer.Option("--open", help="Open the report when it's written")
|
|
114
|
+
] = False,
|
|
115
|
+
transport: TransportOpt = "in-process",
|
|
116
|
+
profile: Annotated[
|
|
117
|
+
str,
|
|
118
|
+
typer.Option(
|
|
119
|
+
help="private (everything) or public (no source, docstrings or Git revision: safe to publish)"
|
|
120
|
+
),
|
|
121
|
+
] = "private",
|
|
122
|
+
as_json: JsonOpt = False,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Explore a codebase: Ananke builds the graph, Dev Guru writes an interactive, offline HTML explorer.
|
|
125
|
+
|
|
126
|
+
The report is one file with no network access: architecture constellation, dependency flow, impact rings,
|
|
127
|
+
call graph, hotspots, a guided tour, search and a source inspector.
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
client = connect(transport)
|
|
131
|
+
result = service.explore(workspace, client=client, name=name, profile=profile)
|
|
132
|
+
page, sidecar = service.export_html(result, output, client=client)
|
|
133
|
+
except errors.GuruError as exc:
|
|
134
|
+
_fail(exc, as_json, 1)
|
|
135
|
+
return
|
|
136
|
+
project = result.model["project"]
|
|
137
|
+
summary = {
|
|
138
|
+
"ok": True,
|
|
139
|
+
"report": str(page),
|
|
140
|
+
"manifest": str(sidecar),
|
|
141
|
+
"input_digest": result.manifest["input_digest"],
|
|
142
|
+
"project": project,
|
|
143
|
+
"insights": {k: len(v) for k, v in result.model["insights"].items()},
|
|
144
|
+
}
|
|
145
|
+
if as_json:
|
|
146
|
+
console.print_json(json.dumps(summary))
|
|
147
|
+
else:
|
|
148
|
+
console.print(
|
|
149
|
+
f"[green]✓[/green] {project['files']} files · {project['symbols']} symbols · "
|
|
150
|
+
f"{project['imports']} imports · {project['calls']} calls → {page}"
|
|
151
|
+
)
|
|
152
|
+
cycles = len(result.model["insights"]["cycles"])
|
|
153
|
+
if cycles:
|
|
154
|
+
console.print(f" [yellow]{cycles} import cycle(s) found; see the Flow view[/yellow]")
|
|
155
|
+
console.print(f" [dim]manifest {sidecar} · {result.manifest['input_digest'][:19]}…[/dim]")
|
|
156
|
+
if open_browser:
|
|
157
|
+
webbrowser.open(page.resolve().as_uri())
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@app.command()
|
|
161
|
+
def present(
|
|
162
|
+
slides: Annotated[
|
|
163
|
+
Path, typer.Argument(help="Markdown slides (separate slides with a line of ---)")
|
|
164
|
+
],
|
|
165
|
+
output: Annotated[Path | None, typer.Option("--output", "-o", help="Deck file")] = None,
|
|
166
|
+
theme: Annotated[
|
|
167
|
+
str | None, typer.Option(help="lantern (dark) or paper (light); default from front matter")
|
|
168
|
+
] = None,
|
|
169
|
+
check_only: Annotated[
|
|
170
|
+
bool, typer.Option("--check", help="Check an existing HTML deck instead of building")
|
|
171
|
+
] = False,
|
|
172
|
+
open_browser: Annotated[
|
|
173
|
+
bool, typer.Option("--open", help="Open the deck when it's written")
|
|
174
|
+
] = False,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Turn Markdown slides into one offline, accessible HTML deck (arrow keys, N for notes, print to PDF).
|
|
177
|
+
|
|
178
|
+
Raw HTML is shown as text, images must be local with alt text, charts come from ```chart JSON blocks, and the
|
|
179
|
+
page can't make network requests. --check verifies a built deck.
|
|
180
|
+
"""
|
|
181
|
+
from dev_guru import present as deck
|
|
182
|
+
|
|
183
|
+
if check_only:
|
|
184
|
+
problems = deck.check(slides.read_text(encoding="utf-8"))
|
|
185
|
+
for p in problems:
|
|
186
|
+
err.print(f"[red]✗[/red] {p}")
|
|
187
|
+
if problems:
|
|
188
|
+
raise typer.Exit(1)
|
|
189
|
+
console.print(f"[green]✓[/green] {slides}: offline and accessible checks pass")
|
|
190
|
+
return
|
|
191
|
+
try:
|
|
192
|
+
page = deck.build(slides.read_text(encoding="utf-8"), slides.parent, theme=theme)
|
|
193
|
+
except (deck.DeckError, OSError) as exc:
|
|
194
|
+
err.print(
|
|
195
|
+
f"[red]✗ {escape(str(exc))}[/red]"
|
|
196
|
+
) # the message quotes Markdown like 
|
|
197
|
+
raise typer.Exit(2) from exc
|
|
198
|
+
problems = deck.check(page)
|
|
199
|
+
if (
|
|
200
|
+
problems
|
|
201
|
+
): # a bug in the renderer, not the author's input: never write a deck that fails its own check
|
|
202
|
+
err.print("[red]✗ built deck failed its checks: " + "; ".join(problems) + "[/red]")
|
|
203
|
+
raise typer.Exit(1)
|
|
204
|
+
out = output or slides.with_suffix(".html")
|
|
205
|
+
out.write_text(page, encoding="utf-8")
|
|
206
|
+
n = page.count('class="slide"')
|
|
207
|
+
console.print(f"[green]✓[/green] {n} slides → {out}")
|
|
208
|
+
if open_browser:
|
|
209
|
+
webbrowser.open(out.resolve().as_uri())
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@app.command()
|
|
213
|
+
def sources(
|
|
214
|
+
query: Annotated[str, typer.Argument(help="Words to match (optional)")] = "",
|
|
215
|
+
workspace: Annotated[
|
|
216
|
+
Path, typer.Option("--workspace", "-w", help="Project whose sources to read")
|
|
217
|
+
] = Path("."),
|
|
218
|
+
html_out: Annotated[
|
|
219
|
+
Path | None, typer.Option("--html", help="Also write an offline picker page")
|
|
220
|
+
] = None,
|
|
221
|
+
transport: TransportOpt = "in-process",
|
|
222
|
+
as_json: JsonOpt = False,
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Browse capabilities from every source the project configures (internal folders, private Git, troves).
|
|
225
|
+
|
|
226
|
+
Source-neutral: Dev Guru asks Ananke's provider port; Dex Trove is just one optional source.
|
|
227
|
+
"""
|
|
228
|
+
from dev_guru.reports import pages
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
found = connect(transport).discover_capabilities(workspace, query)
|
|
232
|
+
except errors.GuruError as exc:
|
|
233
|
+
_fail(exc, as_json, 1)
|
|
234
|
+
return
|
|
235
|
+
rows = [c.model_dump() for c in found]
|
|
236
|
+
if html_out is not None:
|
|
237
|
+
html_out.write_text(pages.sources_page(rows), encoding="utf-8")
|
|
238
|
+
if as_json:
|
|
239
|
+
console.print_json(json.dumps(rows))
|
|
240
|
+
return
|
|
241
|
+
for c in found:
|
|
242
|
+
console.print(f"{c.identity}@{c.version} [dim]{escape(c.summary[:80])}[/dim]")
|
|
243
|
+
console.print(f"{len(found)} capabilities" + (f" · picker: {html_out}" if html_out else ""))
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
learning_app = typer.Typer(
|
|
247
|
+
help="Review what Ananke learned before it becomes reusable memory.", no_args_is_help=True
|
|
248
|
+
)
|
|
249
|
+
app.add_typer(learning_app, name="learning")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@learning_app.command("review")
|
|
253
|
+
def learning_review(
|
|
254
|
+
name: Annotated[str, typer.Argument(help="Candidate name (see `ananke learning list`)")],
|
|
255
|
+
workspace: Annotated[Path, typer.Option("--workspace", "-w")] = Path("."),
|
|
256
|
+
output: Annotated[
|
|
257
|
+
Path | None, typer.Option("--output", "-o", help="Review page (default <name>-review.html)")
|
|
258
|
+
] = None,
|
|
259
|
+
open_browser: Annotated[bool, typer.Option("--open")] = False,
|
|
260
|
+
transport: TransportOpt = "in-process",
|
|
261
|
+
as_json: JsonOpt = False,
|
|
262
|
+
) -> None:
|
|
263
|
+
"""A review page for a learning candidate: gate status, evaluation, review, provenance and per-destination diff.
|
|
264
|
+
|
|
265
|
+
Reviewing changes nothing: approve with `ananke learning review` and promote with `ananke learning promote`.
|
|
266
|
+
"""
|
|
267
|
+
from dev_guru.reports import pages
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
cand = connect(transport).learning_candidate(workspace, name)
|
|
271
|
+
except errors.GuruError as exc:
|
|
272
|
+
_fail(exc, as_json, 1)
|
|
273
|
+
return
|
|
274
|
+
out = output or Path(f"{name}-review.html")
|
|
275
|
+
out.write_text(pages.review_page(cand.model_dump()), encoding="utf-8")
|
|
276
|
+
if as_json:
|
|
277
|
+
console.print_json(json.dumps(cand.model_dump() | {"page": str(out)}))
|
|
278
|
+
else:
|
|
279
|
+
state = (
|
|
280
|
+
"[green]ready to promote[/green]"
|
|
281
|
+
if not cand.problems
|
|
282
|
+
else "[yellow]" + escape("; ".join(cand.problems)) + "[/yellow]"
|
|
283
|
+
)
|
|
284
|
+
console.print(f"{name}: {state} → {out}")
|
|
285
|
+
if open_browser:
|
|
286
|
+
webbrowser.open(out.resolve().as_uri())
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@app.command()
|
|
290
|
+
def diagram(
|
|
291
|
+
document: Annotated[Path, typer.Argument(help="An approved Markdown document")],
|
|
292
|
+
output: Annotated[
|
|
293
|
+
Path | None, typer.Option("--output", "-o", help="SVG file (default <document>.svg)")
|
|
294
|
+
] = None,
|
|
295
|
+
model_out: Annotated[
|
|
296
|
+
Path | None, typer.Option("--model", help="Also write the editable model (JSON)")
|
|
297
|
+
] = None,
|
|
298
|
+
from_model: Annotated[
|
|
299
|
+
Path | None, typer.Option("--from-model", help="Render an edited model instead")
|
|
300
|
+
] = None,
|
|
301
|
+
static: Annotated[bool, typer.Option("--static", help="No animation")] = False,
|
|
302
|
+
as_json: JsonOpt = False,
|
|
303
|
+
) -> None:
|
|
304
|
+
"""An animated SVG of the relationships a document states, every one cited (file and line).
|
|
305
|
+
|
|
306
|
+
Reads arrow lines (A -> B: label), Mermaid flowcharts and From/To tables, never prose, so nothing is invented.
|
|
307
|
+
Edited relationships whose citation doesn't hold up are drawn as hypotheses (dashed, "?").
|
|
308
|
+
"""
|
|
309
|
+
from dev_guru import diagram as dg
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
text = document.read_text(encoding="utf-8")
|
|
313
|
+
model = (
|
|
314
|
+
json.loads(from_model.read_text(encoding="utf-8"))
|
|
315
|
+
if from_model
|
|
316
|
+
else dg.extract(text, document.name)
|
|
317
|
+
)
|
|
318
|
+
model = dg.verify(model, text)
|
|
319
|
+
except (dg.DiagramError, OSError, ValueError) as exc:
|
|
320
|
+
err.print(f"[red]✗ {escape(str(exc))}[/red]")
|
|
321
|
+
raise typer.Exit(2) from exc
|
|
322
|
+
svg = dg.render(model, animate=not static)
|
|
323
|
+
problems = dg.svg_problems(svg)
|
|
324
|
+
if problems: # never write an unsafe SVG, whatever the input
|
|
325
|
+
err.print("[red]✗ " + "; ".join(problems) + "[/red]")
|
|
326
|
+
raise typer.Exit(1)
|
|
327
|
+
out = output or document.with_suffix(".svg")
|
|
328
|
+
out.write_text(svg, encoding="utf-8")
|
|
329
|
+
if model_out:
|
|
330
|
+
model_out.write_text(
|
|
331
|
+
json.dumps(model, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
|
332
|
+
)
|
|
333
|
+
hyp = sum(1 for r in model["relations"] if r["status"] != "cited")
|
|
334
|
+
summary = {
|
|
335
|
+
"svg": str(out),
|
|
336
|
+
"entities": len(model["entities"]),
|
|
337
|
+
"relations": len(model["relations"]),
|
|
338
|
+
"hypotheses": hyp,
|
|
339
|
+
}
|
|
340
|
+
if as_json:
|
|
341
|
+
console.print_json(json.dumps(summary))
|
|
342
|
+
else:
|
|
343
|
+
console.print(
|
|
344
|
+
f"[green]✓[/green] {len(model['relations']) - hyp} cited relationships, {summary['entities']} entities"
|
|
345
|
+
+ (f", [yellow]{hyp} hypotheses[/yellow]" if hyp else "")
|
|
346
|
+
+ f" → {out}"
|
|
347
|
+
)
|