grapheinstein 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grapheinstein-0.1.0/LICENSE +21 -0
- grapheinstein-0.1.0/PKG-INFO +148 -0
- grapheinstein-0.1.0/README.md +96 -0
- grapheinstein-0.1.0/pyproject.toml +115 -0
- grapheinstein-0.1.0/setup.cfg +4 -0
- grapheinstein-0.1.0/src/grapheinstein/__init__.py +3 -0
- grapheinstein-0.1.0/src/grapheinstein/__main__.py +6 -0
- grapheinstein-0.1.0/src/grapheinstein/api.py +195 -0
- grapheinstein-0.1.0/src/grapheinstein/cli.py +938 -0
- grapheinstein-0.1.0/src/grapheinstein/core/__init__.py +1 -0
- grapheinstein-0.1.0/src/grapheinstein/core/cache.py +287 -0
- grapheinstein-0.1.0/src/grapheinstein/core/explain.py +344 -0
- grapheinstein-0.1.0/src/grapheinstein/core/graph.py +1008 -0
- grapheinstein-0.1.0/src/grapheinstein/core/index.py +440 -0
- grapheinstein-0.1.0/src/grapheinstein/core/match.py +192 -0
- grapheinstein-0.1.0/src/grapheinstein/core/merge.py +204 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/__init__.py +25 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/docs.py +198 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/extract.py +298 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/llm_enrich.py +387 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/llm_ollama.py +322 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/media_av.py +163 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/media_link.py +173 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/media_ocr.py +106 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/pdf.py +103 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/queries/__init__.py +141 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/registry.py +156 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/resolve.py +310 -0
- grapheinstein-0.1.0/src/grapheinstein/core/parsers/resolve_docs.py +145 -0
- grapheinstein-0.1.0/src/grapheinstein/core/path.py +593 -0
- grapheinstein-0.1.0/src/grapheinstein/core/query.py +706 -0
- grapheinstein-0.1.0/src/grapheinstein/core/references.py +85 -0
- grapheinstein-0.1.0/src/grapheinstein/core/visualize.py +107 -0
- grapheinstein-0.1.0/src/grapheinstein/serve/__init__.py +50 -0
- grapheinstein-0.1.0/src/grapheinstein/serve/app.py +161 -0
- grapheinstein-0.1.0/src/grapheinstein/utils.py +624 -0
- grapheinstein-0.1.0/src/grapheinstein.egg-info/PKG-INFO +148 -0
- grapheinstein-0.1.0/src/grapheinstein.egg-info/SOURCES.txt +40 -0
- grapheinstein-0.1.0/src/grapheinstein.egg-info/dependency_links.txt +1 -0
- grapheinstein-0.1.0/src/grapheinstein.egg-info/entry_points.txt +2 -0
- grapheinstein-0.1.0/src/grapheinstein.egg-info/requires.txt +30 -0
- grapheinstein-0.1.0/src/grapheinstein.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joe Zanini
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grapheinstein
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local-first project knowledge graph CLI
|
|
5
|
+
Author-email: Joe Zanini <jozanini@users.noreply.github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Joezanini/grapheinstein
|
|
8
|
+
Project-URL: Repository, https://github.com/Joezanini/grapheinstein
|
|
9
|
+
Project-URL: Issues, https://github.com/Joezanini/grapheinstein/issues
|
|
10
|
+
Keywords: cli,knowledge-graph,code-analysis,local-first,offline,tree-sitter
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: typer>=0.12
|
|
25
|
+
Requires-Dist: networkx>=3.2
|
|
26
|
+
Requires-Dist: pathspec>=0.12
|
|
27
|
+
Requires-Dist: rich>=13.7
|
|
28
|
+
Requires-Dist: loguru>=0.7
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Requires-Dist: tree-sitter>=0.23
|
|
31
|
+
Requires-Dist: tree-sitter-python>=0.23
|
|
32
|
+
Requires-Dist: tree-sitter-javascript>=0.23
|
|
33
|
+
Requires-Dist: tree-sitter-typescript>=0.23
|
|
34
|
+
Requires-Dist: tree-sitter-java>=0.23
|
|
35
|
+
Requires-Dist: tree-sitter-go>=0.23
|
|
36
|
+
Requires-Dist: tree-sitter-rust>=0.23
|
|
37
|
+
Requires-Dist: tree-sitter-cpp>=0.23
|
|
38
|
+
Requires-Dist: tree-sitter-sql>=0.3
|
|
39
|
+
Requires-Dist: pymupdf>=1.24
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
42
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
43
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
44
|
+
Provides-Extra: media
|
|
45
|
+
Requires-Dist: pytesseract>=0.3.10; extra == "media"
|
|
46
|
+
Requires-Dist: Pillow>=10.0; extra == "media"
|
|
47
|
+
Requires-Dist: faster-whisper>=1.0; extra == "media"
|
|
48
|
+
Provides-Extra: serve
|
|
49
|
+
Requires-Dist: fastapi>=0.115; extra == "serve"
|
|
50
|
+
Requires-Dist: uvicorn[standard]>=0.30; extra == "serve"
|
|
51
|
+
Dynamic: license-file
|
|
52
|
+
|
|
53
|
+
# Grapheinstein
|
|
54
|
+
|
|
55
|
+
Local-first CLI that maps a project folder into a portable `graph.json` knowledge graph for AI agents.
|
|
56
|
+
|
|
57
|
+
Offline by design — indexing, explain, path, and query use the local filesystem and optional local models (e.g. Ollama). No cloud APIs required.
|
|
58
|
+
|
|
59
|
+
Schema version: **6.0.0**.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install grapheinstein
|
|
65
|
+
# Optional local HTTP API:
|
|
66
|
+
# pip install 'grapheinstein[serve]'
|
|
67
|
+
# Optional OCR / audio extras:
|
|
68
|
+
# pip install 'grapheinstein[media]'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### From source
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m venv .venv
|
|
75
|
+
source .venv/bin/activate
|
|
76
|
+
pip install -e ".[dev]"
|
|
77
|
+
# Optional local HTTP API:
|
|
78
|
+
# pip install -e ".[dev,serve]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick start
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Create a commented starter config (~/.grapheinstein/config.yaml by default)
|
|
85
|
+
grapheinstein init
|
|
86
|
+
# or: grapheinstein init --output ./grapheinstein.yaml
|
|
87
|
+
|
|
88
|
+
# Index a project (default command)
|
|
89
|
+
grapheinstein /path/to/project --output graph.json
|
|
90
|
+
|
|
91
|
+
# Explicit index with project config
|
|
92
|
+
grapheinstein index /path/to/project -o graph.json --config ./grapheinstein.yaml
|
|
93
|
+
|
|
94
|
+
# Explain / path / query over an existing graph
|
|
95
|
+
grapheinstein explain "authentication" -i graph.json -o explain.json --no-summary
|
|
96
|
+
grapheinstein path "login" "database" -i graph.json --no-llm-explain
|
|
97
|
+
grapheinstein query "How does auth work?" -i graph.json -o sub.json --no-answer
|
|
98
|
+
|
|
99
|
+
# Console summary / status
|
|
100
|
+
grapheinstein visualize --input graph.json
|
|
101
|
+
grapheinstein status --output graph.json
|
|
102
|
+
|
|
103
|
+
# Optional local HTTP (requires: pip install 'grapheinstein[serve]')
|
|
104
|
+
grapheinstein serve --port 8000
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Agent integration
|
|
108
|
+
|
|
109
|
+
Agents and Cursor slash-commands should call the Python API (same semantics as the CLI):
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from grapheinstein.api import index, query
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Optional HTTP: `POST /index` and `POST /query` via `grapheinstein serve` (loopback by default).
|
|
116
|
+
|
|
117
|
+
See [docs/agent-integration.md](docs/agent-integration.md) for copy-paste examples and CLI ↔ Python ↔ HTTP parity.
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
Config precedence: CLI flags > `--config` file > `~/.grapheinstein/config.yaml` > built-ins.
|
|
122
|
+
|
|
123
|
+
Key settings (see `grapheinstein init` for a full commented template):
|
|
124
|
+
|
|
125
|
+
| Key | Purpose |
|
|
126
|
+
|-----|---------|
|
|
127
|
+
| `ignored_patterns` | Extra gitignore-style paths beyond `.gitignore` |
|
|
128
|
+
| `embedding_model` | Local model for embeddings |
|
|
129
|
+
| `llm_model` | Local model for enrichment / answers |
|
|
130
|
+
| `max_file_size` | Skip parsing files larger than this (bytes; default 10 MiB) |
|
|
131
|
+
| `cache_dir` | Local cache for ASTs / chunks / embeddings (default `~/.grapheinstein/cache`) |
|
|
132
|
+
|
|
133
|
+
Re-indexing an unchanged project reuses the cache under `cache_dir` for faster runs.
|
|
134
|
+
|
|
135
|
+
## Validation
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pytest
|
|
139
|
+
ruff check src tests
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Feature quickstarts live under `specs/*/quickstart.md` (e.g. `specs/011-config-cache-init/quickstart.md`).
|
|
143
|
+
|
|
144
|
+
## Contributing
|
|
145
|
+
|
|
146
|
+
Community contributions are welcome — bug fixes, docs, tests, and features that fit the project’s local-first CLI goals.
|
|
147
|
+
|
|
148
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, validation, and how to propose a change.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Grapheinstein
|
|
2
|
+
|
|
3
|
+
Local-first CLI that maps a project folder into a portable `graph.json` knowledge graph for AI agents.
|
|
4
|
+
|
|
5
|
+
Offline by design — indexing, explain, path, and query use the local filesystem and optional local models (e.g. Ollama). No cloud APIs required.
|
|
6
|
+
|
|
7
|
+
Schema version: **6.0.0**.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install grapheinstein
|
|
13
|
+
# Optional local HTTP API:
|
|
14
|
+
# pip install 'grapheinstein[serve]'
|
|
15
|
+
# Optional OCR / audio extras:
|
|
16
|
+
# pip install 'grapheinstein[media]'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### From source
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m venv .venv
|
|
23
|
+
source .venv/bin/activate
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
# Optional local HTTP API:
|
|
26
|
+
# pip install -e ".[dev,serve]"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Create a commented starter config (~/.grapheinstein/config.yaml by default)
|
|
33
|
+
grapheinstein init
|
|
34
|
+
# or: grapheinstein init --output ./grapheinstein.yaml
|
|
35
|
+
|
|
36
|
+
# Index a project (default command)
|
|
37
|
+
grapheinstein /path/to/project --output graph.json
|
|
38
|
+
|
|
39
|
+
# Explicit index with project config
|
|
40
|
+
grapheinstein index /path/to/project -o graph.json --config ./grapheinstein.yaml
|
|
41
|
+
|
|
42
|
+
# Explain / path / query over an existing graph
|
|
43
|
+
grapheinstein explain "authentication" -i graph.json -o explain.json --no-summary
|
|
44
|
+
grapheinstein path "login" "database" -i graph.json --no-llm-explain
|
|
45
|
+
grapheinstein query "How does auth work?" -i graph.json -o sub.json --no-answer
|
|
46
|
+
|
|
47
|
+
# Console summary / status
|
|
48
|
+
grapheinstein visualize --input graph.json
|
|
49
|
+
grapheinstein status --output graph.json
|
|
50
|
+
|
|
51
|
+
# Optional local HTTP (requires: pip install 'grapheinstein[serve]')
|
|
52
|
+
grapheinstein serve --port 8000
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Agent integration
|
|
56
|
+
|
|
57
|
+
Agents and Cursor slash-commands should call the Python API (same semantics as the CLI):
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from grapheinstein.api import index, query
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Optional HTTP: `POST /index` and `POST /query` via `grapheinstein serve` (loopback by default).
|
|
64
|
+
|
|
65
|
+
See [docs/agent-integration.md](docs/agent-integration.md) for copy-paste examples and CLI ↔ Python ↔ HTTP parity.
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Config precedence: CLI flags > `--config` file > `~/.grapheinstein/config.yaml` > built-ins.
|
|
70
|
+
|
|
71
|
+
Key settings (see `grapheinstein init` for a full commented template):
|
|
72
|
+
|
|
73
|
+
| Key | Purpose |
|
|
74
|
+
|-----|---------|
|
|
75
|
+
| `ignored_patterns` | Extra gitignore-style paths beyond `.gitignore` |
|
|
76
|
+
| `embedding_model` | Local model for embeddings |
|
|
77
|
+
| `llm_model` | Local model for enrichment / answers |
|
|
78
|
+
| `max_file_size` | Skip parsing files larger than this (bytes; default 10 MiB) |
|
|
79
|
+
| `cache_dir` | Local cache for ASTs / chunks / embeddings (default `~/.grapheinstein/cache`) |
|
|
80
|
+
|
|
81
|
+
Re-indexing an unchanged project reuses the cache under `cache_dir` for faster runs.
|
|
82
|
+
|
|
83
|
+
## Validation
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pytest
|
|
87
|
+
ruff check src tests
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Feature quickstarts live under `specs/*/quickstart.md` (e.g. `specs/011-config-cache-init/quickstart.md`).
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
Community contributions are welcome — bug fixes, docs, tests, and features that fit the project’s local-first CLI goals.
|
|
95
|
+
|
|
96
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, validation, and how to propose a change.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "grapheinstein"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local-first project knowledge graph CLI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Joe Zanini", email = "jozanini@users.noreply.github.com" },
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"cli",
|
|
18
|
+
"knowledge-graph",
|
|
19
|
+
"code-analysis",
|
|
20
|
+
"local-first",
|
|
21
|
+
"offline",
|
|
22
|
+
"tree-sitter",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"Environment :: Console",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Software Development",
|
|
34
|
+
"Topic :: Utilities",
|
|
35
|
+
]
|
|
36
|
+
dependencies = [
|
|
37
|
+
"typer>=0.12",
|
|
38
|
+
"networkx>=3.2",
|
|
39
|
+
"pathspec>=0.12",
|
|
40
|
+
"rich>=13.7",
|
|
41
|
+
"loguru>=0.7",
|
|
42
|
+
"pyyaml>=6.0",
|
|
43
|
+
"tree-sitter>=0.23",
|
|
44
|
+
"tree-sitter-python>=0.23",
|
|
45
|
+
"tree-sitter-javascript>=0.23",
|
|
46
|
+
"tree-sitter-typescript>=0.23",
|
|
47
|
+
"tree-sitter-java>=0.23",
|
|
48
|
+
"tree-sitter-go>=0.23",
|
|
49
|
+
"tree-sitter-rust>=0.23",
|
|
50
|
+
"tree-sitter-cpp>=0.23",
|
|
51
|
+
"tree-sitter-sql>=0.3",
|
|
52
|
+
"pymupdf>=1.24",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.optional-dependencies]
|
|
56
|
+
dev = ["pytest>=8.0", "httpx>=0.27", "ruff>=0.8"]
|
|
57
|
+
media = [
|
|
58
|
+
"pytesseract>=0.3.10",
|
|
59
|
+
"Pillow>=10.0",
|
|
60
|
+
"faster-whisper>=1.0",
|
|
61
|
+
]
|
|
62
|
+
serve = [
|
|
63
|
+
"fastapi>=0.115",
|
|
64
|
+
"uvicorn[standard]>=0.30",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[project.scripts]
|
|
68
|
+
grapheinstein = "grapheinstein.cli:app"
|
|
69
|
+
|
|
70
|
+
[project.urls]
|
|
71
|
+
Homepage = "https://github.com/Joezanini/grapheinstein"
|
|
72
|
+
Repository = "https://github.com/Joezanini/grapheinstein"
|
|
73
|
+
Issues = "https://github.com/Joezanini/grapheinstein/issues"
|
|
74
|
+
|
|
75
|
+
[tool.setuptools.packages.find]
|
|
76
|
+
where = ["src"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = ["tests"]
|
|
80
|
+
pythonpath = ["src"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
target-version = "py311"
|
|
84
|
+
line-length = 100
|
|
85
|
+
src = ["src", "tests"]
|
|
86
|
+
exclude = [
|
|
87
|
+
".venv",
|
|
88
|
+
"venv",
|
|
89
|
+
"build",
|
|
90
|
+
"dist",
|
|
91
|
+
"tests/fixtures",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint]
|
|
95
|
+
select = [
|
|
96
|
+
"E", # pycodestyle errors
|
|
97
|
+
"W", # pycodestyle warnings
|
|
98
|
+
"F", # Pyflakes
|
|
99
|
+
"I", # isort
|
|
100
|
+
"UP", # pyupgrade
|
|
101
|
+
"B", # flake8-bugbear
|
|
102
|
+
]
|
|
103
|
+
ignore = [
|
|
104
|
+
"E501", # line length: keep readable long strings/CLI help without noisy failures
|
|
105
|
+
"B008", # function calls in defaults (Typer / FastAPI patterns)
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
[tool.ruff.lint.per-file-ignores]
|
|
109
|
+
"tests/**" = [
|
|
110
|
+
"B017", # pytest.raises(Exception) used intentionally in a few contracts
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[tool.ruff.format]
|
|
114
|
+
quote-style = "double"
|
|
115
|
+
indent-style = "space"
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Public Python API for agent / slash-command integration.
|
|
2
|
+
|
|
3
|
+
Importing this module MUST NOT require FastAPI or Uvicorn.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from dataclasses import asdict, dataclass, is_dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from grapheinstein.core.cache import CacheStore
|
|
14
|
+
from grapheinstein.core.graph import GraphError, GraphStats, load_artifact
|
|
15
|
+
from grapheinstein.core.index import MediaExtrasError, index_project
|
|
16
|
+
from grapheinstein.core.parsers import LanguageError, parse_languages_csv
|
|
17
|
+
from grapheinstein.core.query import (
|
|
18
|
+
EmptyCorpusError,
|
|
19
|
+
NoEvidenceError,
|
|
20
|
+
QueryError,
|
|
21
|
+
run_query,
|
|
22
|
+
)
|
|
23
|
+
from grapheinstein.utils import ConfigError, load_config, setup_logging
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"IndexResult",
|
|
27
|
+
"index",
|
|
28
|
+
"query",
|
|
29
|
+
"stats_to_dict",
|
|
30
|
+
"ConfigError",
|
|
31
|
+
"GraphError",
|
|
32
|
+
"MediaExtrasError",
|
|
33
|
+
"QueryError",
|
|
34
|
+
"EmptyCorpusError",
|
|
35
|
+
"NoEvidenceError",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class IndexResult:
|
|
41
|
+
"""Result of a successful API index call."""
|
|
42
|
+
|
|
43
|
+
output_path: Path
|
|
44
|
+
stats: GraphStats | dict[str, Any]
|
|
45
|
+
artifact: dict[str, Any] | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _stats_as_dict(stats: GraphStats | dict[str, Any]) -> dict[str, Any]:
|
|
49
|
+
if isinstance(stats, dict):
|
|
50
|
+
return dict(stats)
|
|
51
|
+
if is_dataclass(stats):
|
|
52
|
+
return asdict(stats)
|
|
53
|
+
return {
|
|
54
|
+
"total_nodes": getattr(stats, "total_nodes", 0),
|
|
55
|
+
"graph_path": str(getattr(stats, "graph_path", "")),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _normalize_languages(
|
|
60
|
+
languages: str | Sequence[str] | None,
|
|
61
|
+
) -> list[str] | None:
|
|
62
|
+
if languages is None:
|
|
63
|
+
return None
|
|
64
|
+
if isinstance(languages, str):
|
|
65
|
+
try:
|
|
66
|
+
return list(parse_languages_csv(languages))
|
|
67
|
+
except LanguageError as exc:
|
|
68
|
+
raise ConfigError(str(exc)) from exc
|
|
69
|
+
try:
|
|
70
|
+
return list(languages)
|
|
71
|
+
except TypeError as exc:
|
|
72
|
+
raise ConfigError("languages must be a string or sequence of strings") from exc
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def index(
|
|
76
|
+
project_path: str | Path,
|
|
77
|
+
*,
|
|
78
|
+
output: str | Path | None = None,
|
|
79
|
+
config: str | Path | None = None,
|
|
80
|
+
languages: str | Sequence[str] | None = None,
|
|
81
|
+
include_docs: bool = False,
|
|
82
|
+
include_pdfs: bool = False,
|
|
83
|
+
transcribe_media: bool = False,
|
|
84
|
+
enrich_llm: bool = False,
|
|
85
|
+
llm_model: str | None = None,
|
|
86
|
+
llm_base_url: str | None = None,
|
|
87
|
+
embedding_model: str | None = None,
|
|
88
|
+
compress: bool = False,
|
|
89
|
+
versioned: bool = False,
|
|
90
|
+
include_artifact: bool = False,
|
|
91
|
+
show_progress: bool = False,
|
|
92
|
+
) -> IndexResult:
|
|
93
|
+
"""
|
|
94
|
+
Index a project folder into a portable graph (CLI `index` semantics).
|
|
95
|
+
|
|
96
|
+
Raises FileNotFoundError, NotADirectoryError, OSError, ConfigError,
|
|
97
|
+
GraphError, MediaExtrasError on hard failures — never returns an empty success.
|
|
98
|
+
"""
|
|
99
|
+
languages_override = _normalize_languages(languages)
|
|
100
|
+
cfg = load_config(
|
|
101
|
+
config_path=Path(config).expanduser() if config is not None else None,
|
|
102
|
+
output_override=Path(output).expanduser() if output is not None else None,
|
|
103
|
+
languages_override=languages_override,
|
|
104
|
+
llm_model_override=llm_model,
|
|
105
|
+
llm_base_url_override=llm_base_url,
|
|
106
|
+
embedding_model_override=embedding_model,
|
|
107
|
+
compress_override=True if compress else None,
|
|
108
|
+
versioned_override=True if versioned else None,
|
|
109
|
+
)
|
|
110
|
+
setup_logging(cfg.log_level)
|
|
111
|
+
output_path = Path(cfg.output)
|
|
112
|
+
|
|
113
|
+
written, stats = index_project(
|
|
114
|
+
Path(project_path),
|
|
115
|
+
output_path,
|
|
116
|
+
languages=list(cfg.languages),
|
|
117
|
+
include_docs=include_docs,
|
|
118
|
+
include_pdfs=include_pdfs,
|
|
119
|
+
transcribe_media=transcribe_media,
|
|
120
|
+
enrich_llm=enrich_llm,
|
|
121
|
+
llm_model=cfg.llm_model,
|
|
122
|
+
llm_base_url=cfg.llm_base_url,
|
|
123
|
+
llm_confidence_threshold=cfg.llm_confidence_threshold,
|
|
124
|
+
compress=cfg.compress,
|
|
125
|
+
versioned=cfg.versioned,
|
|
126
|
+
ignored_patterns=list(cfg.ignored_patterns),
|
|
127
|
+
max_file_size=cfg.max_file_size,
|
|
128
|
+
cache_dir=cfg.cache_dir,
|
|
129
|
+
embedding_model=cfg.embedding_model,
|
|
130
|
+
show_progress=show_progress,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
artifact: dict[str, Any] | None = None
|
|
134
|
+
if include_artifact:
|
|
135
|
+
artifact = load_artifact(written)
|
|
136
|
+
|
|
137
|
+
return IndexResult(output_path=written, stats=stats, artifact=artifact)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def query(
|
|
141
|
+
question: str,
|
|
142
|
+
*,
|
|
143
|
+
input: str | Path,
|
|
144
|
+
output: str | Path | None = None,
|
|
145
|
+
config: str | Path | None = None,
|
|
146
|
+
k: int | None = None,
|
|
147
|
+
hops: int | None = None,
|
|
148
|
+
match_threshold: float | None = None,
|
|
149
|
+
no_answer: bool = False,
|
|
150
|
+
llm_model: str | None = None,
|
|
151
|
+
llm_base_url: str | None = None,
|
|
152
|
+
embedding_model: str | None = None,
|
|
153
|
+
) -> dict[str, Any]:
|
|
154
|
+
"""
|
|
155
|
+
Answer a natural-language question over a graph (CLI `query` semantics).
|
|
156
|
+
|
|
157
|
+
Returns the query-answer JSON envelope (schema_version 1.0.0).
|
|
158
|
+
"""
|
|
159
|
+
input_path = Path(input).expanduser()
|
|
160
|
+
if output is not None:
|
|
161
|
+
output_path = Path(output).expanduser()
|
|
162
|
+
else:
|
|
163
|
+
output_path = input_path.parent / "subgraph.json"
|
|
164
|
+
|
|
165
|
+
cfg = load_config(
|
|
166
|
+
config_path=Path(config).expanduser() if config is not None else None,
|
|
167
|
+
llm_model_override=llm_model,
|
|
168
|
+
llm_base_url_override=llm_base_url,
|
|
169
|
+
embedding_model_override=embedding_model,
|
|
170
|
+
query_k_override=k,
|
|
171
|
+
query_hops_override=hops,
|
|
172
|
+
query_match_threshold_override=match_threshold,
|
|
173
|
+
)
|
|
174
|
+
setup_logging(cfg.log_level)
|
|
175
|
+
|
|
176
|
+
result = run_query(
|
|
177
|
+
question,
|
|
178
|
+
input_path,
|
|
179
|
+
output_path,
|
|
180
|
+
k=cfg.query_k,
|
|
181
|
+
hops=cfg.query_hops,
|
|
182
|
+
match_threshold=cfg.query_match_threshold,
|
|
183
|
+
node_cap=cfg.query_node_cap,
|
|
184
|
+
want_answer=not no_answer,
|
|
185
|
+
llm_model=cfg.llm_model,
|
|
186
|
+
llm_base_url=cfg.llm_base_url,
|
|
187
|
+
embedding_model=cfg.embedding_model,
|
|
188
|
+
cache=CacheStore(cfg.cache_dir),
|
|
189
|
+
)
|
|
190
|
+
return dict(result.answer_envelope)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def stats_to_dict(stats: GraphStats | dict[str, Any]) -> dict[str, Any]:
|
|
194
|
+
"""Serialize index stats for HTTP / agents."""
|
|
195
|
+
return _stats_as_dict(stats)
|