codegraph-network 0.1.0__py3-none-any.whl

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.
codegraph/tools.py ADDED
@@ -0,0 +1,99 @@
1
+ """One definition of the codegraph tool surface, shared by the MCP server and the CLI.
2
+
3
+ Keeping the signature, the argument spec and the handler in one place is the only way the two
4
+ front ends cannot drift apart.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class Param:
13
+ name: str
14
+ type: str # "str" | "int" | "bool"
15
+ default: object = None
16
+ help: str = ""
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Tool:
21
+ name: str
22
+ summary: str # what the agent should use it for
23
+ params: tuple[Param, ...] = field(default_factory=tuple)
24
+
25
+ def defaults(self) -> dict:
26
+ return {p.name: p.default for p in self.params}
27
+
28
+ def usage(self) -> str:
29
+ parts = [p.name if p.default is None else f"[{p.name}]" for p in self.params]
30
+ return f"codegraph {self.name} " + " ".join(parts)
31
+
32
+
33
+ def _p(name, type_, default=None, help_=""):
34
+ return Param(name, type_, default, help_)
35
+
36
+
37
+ TOOLS: tuple[Tool, ...] = (
38
+ Tool("repo_map", "Ranked overview of the repo. Start here instead of reading whole files.",
39
+ (_p("prefix", "str", "", "directory prefix to zoom into"),
40
+ _p("max_tokens", "int", 1500),
41
+ _p("focus", "str", "", "symbols, words or paths, comma separated"))),
42
+ Tool("search", "Find symbols by name, split words or docstring; by meaning too when the embed extra is installed.",
43
+ (_p("query", "str", None), _p("limit", "int", 10))),
44
+ Tool("outline", "Symbol tree of a file, without any code.",
45
+ (_p("file", "str", None),)),
46
+ Tool("context", "Best first call for a symbol: source, what it uses, who uses it.",
47
+ (_p("symbol", "str", None), _p("max_tokens", "int", 1500))),
48
+ Tool("related", "Closest symbols to one symbol, direct and multi-hop, ranked.",
49
+ (_p("symbol", "str", None), _p("max_tokens", "int", 800))),
50
+ Tool("similar", "Symbols that mean the same thing as this one, even with no call between them "
51
+ "(needs the embed extra).",
52
+ (_p("symbol", "str", None), _p("limit", "int", 10))),
53
+ Tool("source", "Exact source of one symbol.",
54
+ (_p("symbol", "str", None),)),
55
+ Tool("callers", "Who calls / uses / inherits from this symbol.",
56
+ (_p("symbol", "str", None), _p("depth", "int", 1), _p("limit", "int", 30))),
57
+ Tool("callees", "What this symbol calls.",
58
+ (_p("symbol", "str", None), _p("depth", "int", 1), _p("limit", "int", 30))),
59
+ Tool("impact", "Blast radius of a change, grouped by file. Use before editing.",
60
+ (_p("symbol", "str", None), _p("depth", "int", 3))),
61
+ Tool("deps", "What a file imports and which files import it.",
62
+ (_p("file", "str", None),)),
63
+ Tool("stats", "Index size: files, symbols, edges by confidence, imports, communities."),
64
+ Tool("report", "Whole-repo overview: key symbols, communities, surprising connections. "
65
+ "Also writes .codegraph/GRAPH_REPORT.md and a browsable graph.html.",
66
+ (_p("max_tokens", "int", 1500),)),
67
+ Tool("review", "Review a change: symbols the git diff touches, their blast radius, missing tests, "
68
+ "files that usually change with it.",
69
+ (_p("base", "str", "", "branch/commit to diff against (default: where this branch left main)"),
70
+ _p("max_tokens", "int", 1500))),
71
+ Tool("reindex", "Re-scan the repo incrementally. Use after large external edits.",
72
+ (_p("force", "bool", False),)),
73
+ )
74
+
75
+ TOOLS_BY_NAME = {t.name: t for t in TOOLS}
76
+
77
+ # extra CLI spellings, e.g. `codegraph map` for `repo_map`
78
+ CLI_ALIASES = {"repo_map": ("map",), "review": ("prs", "pr")}
79
+ QUERY_TOOLS = tuple(t for t in TOOLS if t.name not in ("reindex",))
80
+
81
+ # CLI flag aliases: param name -> (long flag, short flag or None).
82
+ # Anything not listed here is exposed under its own parameter name.
83
+ CLI_FLAGS = {
84
+ "max_tokens": ("tokens", "-t"),
85
+ "focus": ("focus", "-f"),
86
+ "depth": ("depth", "-d"),
87
+ }
88
+
89
+
90
+ def make_callable(tool: Tool, graph, reindex_fn=None):
91
+ """Bind a Tool spec to a Graph, producing the callable used by both front ends."""
92
+ def call(**kwargs):
93
+ return getattr(graph, tool.name)(**kwargs)
94
+ call.__name__ = tool.name
95
+ call.__doc__ = tool.summary
96
+ if tool.name == "reindex":
97
+ assert reindex_fn is not None
98
+ call = reindex_fn
99
+ return call
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: codegraph-network
3
+ Version: 0.1.0
4
+ Summary: Graph-based code understanding for LLM agents: fewer tokens, better answers
5
+ Author: Oviing
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Oviing/codegraph
8
+ Project-URL: Issues, https://github.com/Oviing/codegraph/issues
9
+ Keywords: code-graph,tree-sitter,mcp,llm,static-analysis
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: tree-sitter>=0.23
23
+ Requires-Dist: tree-sitter-language-pack
24
+ Requires-Dist: mcp>=1.2
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8; extra == "dev"
27
+ Requires-Dist: ruff>=0.6; extra == "dev"
28
+ Provides-Extra: embed
29
+ Requires-Dist: fastembed>=0.4; extra == "embed"
30
+ Requires-Dist: numpy>=1.24; extra == "embed"
31
+ Provides-Extra: bench
32
+ Requires-Dist: pyyaml>=6; extra == "bench"
33
+ Provides-Extra: pdf
34
+ Requires-Dist: pypdf>=4; extra == "pdf"
35
+ Dynamic: license-file
36
+
37
+ # codegraph
38
+
39
+ Graph-based code understanding for LLM agents. Parses a repo once with tree-sitter (no LLM, no cost),
40
+ stores symbols and relations in SQLite, and serves compact, token-budgeted answers over MCP.
41
+
42
+ Instead of reading whole files, the agent asks the graph.
43
+
44
+ ## Install
45
+
46
+ pip install codegraph-network # provides the `codegraph` command
47
+ pip install "codegraph-network[embed]" # optional: semantic search, see "Hybrid search"
48
+
49
+ From a checkout:
50
+
51
+ pip install -e ".[dev]" # drop [dev] if you don't need pytest/ruff
52
+ pip install -e ".[embed]" # optional: semantic search (fastembed + numpy), see "Hybrid search"
53
+
54
+ ## Use with Claude Code
55
+
56
+ claude mcp add codegraph -- codegraph -r /path/to/repo serve
57
+
58
+ Or in any MCP client config:
59
+
60
+ {"mcpServers": {"codegraph": {"command": "codegraph", "args": ["-r", "/path/to/repo", "serve"]}}}
61
+
62
+ The index lives in `<repo>/.codegraph/graph.db` and updates incrementally on every server start;
63
+ `reindex` re-scans after edits made outside the session. `.gitignore` it.
64
+
65
+ ## Tools
66
+
67
+ | Tool | Use it to |
68
+ |---|---|
69
+ | `repo_map` | PageRank-ranked overview within a token budget; `focus=` personalizes it to what you're working on |
70
+ | `search` | Find symbols by name, words (camel/snake split) or docstring; with the embed extra also by meaning, each hit tagged `[lexical]` / `[semantic]` / `[both]` |
71
+ | `context` | Best first call: source + signatures it uses + who uses it, in one reply |
72
+ | `source` | Exact source of one symbol |
73
+ | `outline` | Symbol tree of a file, without code |
74
+ | `callers` / `callees` | Walk the call/inherit graph (depth N) |
75
+ | `impact` | Blast radius of a change, grouped by file |
76
+ | `related` | Closest symbols to one symbol (direct + multi-hop), ranked by personalized PageRank; each line says how it is related (`uses`, `used by`, `tested by`, `tests`, `key`, `similar`) |
77
+ | `similar` | Symbols that mean the same thing as this one even with no call between them (needs the embed extra) |
78
+ | `file_deps` | What a file imports and what imports it |
79
+ | `stats` | Index size, edges by confidence, communities |
80
+ | `report` | Key symbols, communities, surprising connections; writes `.codegraph/GRAPH_REPORT.md` and a browsable `graph.html` |
81
+ | `review` (`prs`) | What the git diff touches: blast radius per changed symbol, missing tests, files that usually change with it |
82
+ | `reindex` | Re-scan after big external edits |
83
+
84
+ The MCP server and the CLI are both generated from one tool registry (`src/codegraph/tools.py`), so
85
+ they cannot drift apart. The CLI mirrors the tools: `codegraph map`, `codegraph context Store.resolve`,
86
+ `codegraph impact parse`, ...
87
+
88
+ ## Languages
89
+
90
+ Python, TypeScript/TSX, JavaScript, Go, Rust, C/C++.
91
+
92
+ Beyond code, also parsed statically (no LLM): Markdown (headings become `section` symbols; `` `code spans` `` link
93
+ to the symbols they name, relative links count as imports), SQL (`CREATE TABLE/VIEW/FUNCTION/PROCEDURE`, with
94
+ `FROM`/`JOIN`/`REFERENCES` links), and PDF pages (`pip install -e ".[pdf]"`). Prose and SQL mentions link only
95
+ when the name is unambiguous.
96
+
97
+ ## Edge confidence
98
+
99
+ Every edge records how it was resolved: `extracted` (one target named by the source itself: same file, an import,
100
+ `self.x()`), `inferred` (a name match by proximity) or `ambiguous` (several equally plausible targets). `callers`,
101
+ `callees` and `context` tag anything that is not `extracted`; `impact` marks it with `?`. Treat tagged edges as
102
+ upper bounds.
103
+
104
+ ## Scripting and ignoring
105
+
106
+ `codegraph <tool>` exits 0 on an answer, 3 when a name matches several symbols, 4 when it matches none.
107
+ `.codegraphignore` (gitignore-style: `#` comments, `dir/`, `/anchored`, `*.glob`, `!negation`) excludes paths
108
+ from the index, on top of the built-in skips (`node_modules`, `venv`, `dist`, hidden directories, ...).
109
+
110
+ ## Installing into assistants and git
111
+
112
+ codegraph install [claude|cursor|gemini|agents|all] # skill, MCP config, guidance block; --uninstall to undo
113
+ codegraph hook install # re-index after commit, checkout, merge, rebase
114
+
115
+ `install` is idempotent and merges into existing files (guidance lives between `<!-- codegraph:start/end -->`
116
+ markers; JSON configs keep their other entries). For Claude Code it also adds a `Glob|Grep` hint hook
117
+ (`--no-hooks` to skip). Git hooks are appended to existing ones and run the indexer in the background.
118
+
119
+ ## How it works
120
+
121
+ Indexing is incremental in two layers. Files are re-parsed only when their hash changes, and edge
122
+ resolution is scoped: a change re-resolves only the raw references whose name, or whose source file,
123
+ is affected. Parsing runs across processes and writes are batched; PageRank and community detection
124
+ warm-start from the previous run. On a 3,091-file / 59k-symbol / 79k-edge corpus (8 cores) a full index takes
125
+ ~7.6s (was ~22s single-process), changing one file ~2s, and a no-op reindex ~0.3s. PageRank is the one step
126
+ that always walks the whole graph; whether it converged is reported by `codegraph index` rather than assumed.
127
+
128
+ Edge resolution is name-based, using the strongest evidence available:
129
+
130
+ - `self.x()` / `this.x()` link only to methods of the enclosing class, its bases and its subclasses
131
+ (no match => inherited from an external base => no edge).
132
+ - `obj.x()` links only if a candidate is in the same file, an imported file or the same directory.
133
+ - bare `x()` prefers same file, then imported files, then same directory, then anywhere.
134
+ - a bare call to a Python builtin, or to a name imported from a module outside the repo
135
+ (`from typing import cast`), links to nothing — otherwise `isinstance(...)` would attach thousands
136
+ of phantom edges to whatever repo method happens to share the name.
137
+ - Targets defined in more than 4 places are dropped as too ambiguous.
138
+
139
+ ## Hybrid search and hidden links
140
+
141
+ Everything above is exact and structural. A second layer adds *recall* for relationships that are not
142
+ spelled out in code. It never touches `callers` / `callees` / `impact` or global PageRank: those stay
143
+ structural. Soft links live in their own tables (`links`, `file_links`) and enter the personalized
144
+ PageRank behind `context` / `related` / `repo_map(focus=)` at a lower weight than a call edge
145
+ (`LINK_WEIGHT` in `queries.py`).
146
+
147
+ | Link | Source | Needs |
148
+ |---|---|---|
149
+ | `tests` | `test_foo.py` / `foo.test.ts` / `foo_test.go` -> the file named after it, and `test_bar` -> `bar` inside that pair; test callers are labelled `tested by` | nothing |
150
+ | `key` | the same string literal used in matching roles in different files: route path (`@app.get("/x")` vs `requests.get("/x")`, express vs `fetch`), event name (`emit` vs `on`), config key (`getenv`, `config.get`). Table-driven: add a row to `PATTERNS` in `keys.py`. Keys shared by more than 8 symbols and generic events (`error`, `data`, ...) are ignored | nothing |
151
+ | co-change | `git log --name-only` (last 1000 commits, commits over 30 files ignored, pairs need 3+ co-changes); shown by `deps` and used by `repo_map`'s ranking. No git or no history: no edges | git |
152
+ | `similar` | top-5 nearest neighbours by embedding cosine >= 0.85, skipping pairs already linked by a call/inheritance, overloads, and test-to-test pairs | embed extra |
153
+
154
+ **Semantic search** is optional: `pip install codegraph[embed]` (fastembed + numpy). Each function / method /
155
+ class / type is embedded from its name as words, qualified name, signature and doc line with a small local
156
+ ONNX model (`BAAI/bge-small-en-v1.5`); there is no network access at query time, the model files are
157
+ downloaded once at the first index. Vectors are cached in SQLite by content hash, so only changed symbols are
158
+ ever re-embedded and a no-change reindex stays near-instant. `search` fuses FTS5 and vector rankings with
159
+ reciprocal rank fusion (semantic hits in test files count half). Without the extra, or with
160
+ `CODEGRAPH_EMBED=off`, nothing changes: the output is the plain lexical search, byte for byte.
161
+
162
+ Providers are pluggable: `CODEGRAPH_EMBED=package.module:factory` loads any object with a `name`,
163
+ `embed_passages(list[str])` and `embed_query(str)` returning L2-normalised float32 vectors (see
164
+ `tests/fake_embed.py`); `CODEGRAPH_EMBED_MODEL` picks another fastembed model.
165
+
166
+ ### Measured (fastembed bge-small, Apple silicon, CPU)
167
+
168
+ | | Flask (Python) | ky (TypeScript) |
169
+ |---|---|---|
170
+ | files / symbols | 88 / 1,723 | 100 / 11,427 |
171
+ | cold index, no embeddings | 0.4 s | 0.9 s |
172
+ | first-time embedding (one-off, then cached) | 27 s / 1,624 symbols | 58 s / 7,389 symbols |
173
+ | no-change reindex | 0.06 s | 0.04 s |
174
+ | edges (structural) | 1,011 calls, 74 inherits | 2,915 calls, 8 inherits |
175
+ | soft links | 70 tests, 52 key, 353 similar, 39 co-change | 3 key, 1,261 similar, 40 co-change |
176
+
177
+ `codegraph bench tasks.yaml` replays your own `(task, expected symbols)` list and reports, per task and in
178
+ total, recall@k of lexical-only vs hybrid search and the tokens an agent reads through the graph vs reading
179
+ the files that contain the expected symbols (`pip install codegraph[bench]` for YAML; JSON works without).
180
+ On 7 Flask and 5 ky natural-language tasks written for this README: hybrid recall 8/9 and 3/5, lexical-only
181
+ 1/9 and 1/5. The lexical baseline is the existing search, which needs every query word to match, so it
182
+ scores ~0 on questions phrased unlike the code by construction. Treat the gap as "what the semantic layer adds
183
+ for descriptive queries", not as a general accuracy figure.
184
+
185
+ The similarity threshold (0.85) was set on codegraph's own code: bge-small cosines are high in general
186
+ (median nearest-neighbour 0.80 here); real near-duplicates (`make_repo` in two test files, `pagerank` vs
187
+ `personalized_pagerank`) start around 0.85, and below it the pairs are mostly generically similar test names.
188
+
189
+ ## Tests
190
+
191
+ pytest # 232 tests
192
+ pytest tests/test_edges.py -rA # per-language edge precision/recall report
193
+
194
+ `tests/fixtures/<lang>/expected.json` holds hand-verified edges per language. Each fixture lists
195
+ `true` edges (must all be found) and `known_ambiguous` ones (accepted false positives from not
196
+ tracking receiver types, each with a stated reason). Anything the tool emits that is in neither list
197
+ fails the test, so a new kind of false positive cannot slip in unnoticed.
198
+
199
+ ## Known limits
200
+
201
+ - Remaining false positives: `obj.x()` where `obj`'s type is unknown and an imported file defines a
202
+ method called `x` (e.g. `Path(p).resolve()` vs your own `resolve`). Type-aware indexers (SCIP) fix this.
203
+ - Dynamic dispatch, reflection and macros are mostly invisible.
204
+ - Semantic search is as good as a small local model: it can miss (`jsonify` for "turn python objects into a
205
+ json response" in Flask) and it ranks by meaning of the name/signature/doc line, not of the body. Variables
206
+ and TypeScript interface fields are not embedded.
207
+ - String keys match on identical literals only (no `/users/{id}` vs `/users/1`), and only for Python and
208
+ JS/TS call shapes listed in `keys.py`. `os.environ["X"]` and `process.env.X` are not recognised yet.
209
+ - Co-change uses the last 1000 commits and treats renames as new files.
210
+ - Object-literal properties in JS/TS (`{ json: body }`) are indexed as methods by the parser, which inflates
211
+ TypeScript indexes (ky: 11k symbols for 100 files) and puts noise in semantic results.
212
+ - Go/Rust/C++ import resolution is heuristic. Notably a stdlib import can collide with a local
213
+ directory of the same name: `net/http` matches an `internal/http` directory, because there is no
214
+ `go.mod` to tell the module prefix apart. Python imports are exact (see `py_lookup` in `store.py`).
215
+
216
+ ## Next steps
217
+
218
+ - Type-aware resolution via SCIP/LSIF indexers for exact call edges
219
+ - Optional one-line LLM summaries per symbol, generated once at index time
220
+ - Git-aware ranking by recency (co-change is in; "recently changed" is not)
@@ -0,0 +1,25 @@
1
+ codegraph/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ codegraph/bench.py,sha256=-2_kV76N8gjM58mW5Qb3tvMGGxfB-XEj_MsJTg7-t8s,3880
3
+ codegraph/cli.py,sha256=08n_GNkr4jEKKy6f_6Bfnft8M3lIENy5VzgjYqtXnj8,4877
4
+ codegraph/community.py,sha256=HtsQK58zDfUkySLE1mvuEoup1GR9RIIwh7UoY4iRDnw,3737
5
+ codegraph/docs.py,sha256=YHzg24v9tviiz5PqSG_nqHsnQhPntVgk8ABx2pmarcI,6126
6
+ codegraph/embed.py,sha256=LVryGlwSkktpWgPJzVtRFYJny4yWWu3AtuF-RQXruyo,9891
7
+ codegraph/indexer.py,sha256=ySizCFui6JFMgE00SoPChpTh4l0bm20XokyMWZc5hnU,7825
8
+ codegraph/install.py,sha256=ldx78xs6M2eiJZFnxxMoAY9mBdih7RfUWoDfjg2Qads,9821
9
+ codegraph/keys.py,sha256=OcDYsTxst8Jg_hcSNucuk-jxoTbLAuSgVbPm3bNbV7E,4149
10
+ codegraph/links.py,sha256=sn7bZIxf0fLFv7BjoK7V2opMVW1BuwQNaXsMSQprI04,9846
11
+ codegraph/parser.py,sha256=Q4mtOCaqyZm-b0fnPNH9fa6sA39tejmZmLag__RZa-w,19619
12
+ codegraph/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ codegraph/queries.py,sha256=CTt6TpRfWOcXgA4wwXIJJYs6-Vu8r14UUt-WdqepXpc,30829
14
+ codegraph/rank.py,sha256=FUJ-W9ogQPnXngNxCBHD-58ke6sXBTlZlb1xETuC6-g,4963
15
+ codegraph/report.py,sha256=xjoQ7KYKo-6hjDsmA6wykbeX2Yi0uFbVZknUeRYYS_E,9853
16
+ codegraph/review.py,sha256=yh5hNWtnsppc40hidL8Kb4kzSb9gugO5MJrIxY-37kM,6380
17
+ codegraph/server.py,sha256=lTmdhnHE-r2aLmNwWKu_svHBZ5XJ1j7higQDoT9W-TY,3094
18
+ codegraph/store.py,sha256=rOxweItAg1jMvb8D6W_rt4TTZSVkUOrj2tjurW_OVqY,30847
19
+ codegraph/tools.py,sha256=UNsiuta7TFaC1AgGMTDCOHb5jV0Czvz-5kh7nXoUB70,4338
20
+ codegraph_network-0.1.0.dist-info/licenses/LICENSE,sha256=2g3-GEuR7lJgSNW_8hDmQ67ottLWLC7TB7pWmiVYcdQ,1063
21
+ codegraph_network-0.1.0.dist-info/METADATA,sha256=b7tTVDFxSGgadSmqUUyFteXKf0MR-TTrI4iQWu9APr8,13153
22
+ codegraph_network-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
23
+ codegraph_network-0.1.0.dist-info/entry_points.txt,sha256=3f2dJK7oR3dBzP21qRk_KuQa6Li8MVbVXeKcx3UjQ6c,49
24
+ codegraph_network-0.1.0.dist-info/top_level.txt,sha256=RqBj9sPbifZTb9aeHtnbxTgKJvfHIQdmYE1Brv8Wdkg,10
25
+ codegraph_network-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codegraph = codegraph.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oviing
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 @@
1
+ codegraph