codmap 0.0.3__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.
- codemap/__init__.py +10 -0
- codemap/apidiff.py +208 -0
- codemap/arch.py +190 -0
- codemap/cli.py +718 -0
- codemap/diagnostics.py +256 -0
- codemap/extract/__init__.py +10 -0
- codemap/extract/attrflow.py +230 -0
- codemap/extract/behavior.py +771 -0
- codemap/extract/dataflow.py +97 -0
- codemap/extract/dispatch.py +248 -0
- codemap/extract/griffe_extractor.py +496 -0
- codemap/extract/gsource.py +83 -0
- codemap/extract/roots.py +427 -0
- codemap/freshness.py +94 -0
- codemap/incremental.py +195 -0
- codemap/integrations/__init__.py +51 -0
- codemap/integrations/base.py +196 -0
- codemap/integrations/cocoindex.py +78 -0
- codemap/integrations/gate.py +58 -0
- codemap/integrations/gitnexus.py +93 -0
- codemap/integrations/registry.py +69 -0
- codemap/integrations/transport.py +46 -0
- codemap/model.py +178 -0
- codemap/provenance.py +248 -0
- codemap/query.py +1164 -0
- codemap/scope.py +212 -0
- codemap/serve/__init__.py +26 -0
- codemap/serve/_scip_pb2.py +100 -0
- codemap/serve/api_surface.py +60 -0
- codemap/serve/apidiff.py +83 -0
- codemap/serve/architecture.py +101 -0
- codemap/serve/audit.py +176 -0
- codemap/serve/check.py +80 -0
- codemap/serve/ctags.py +203 -0
- codemap/serve/impact.py +84 -0
- codemap/serve/livingdocs.py +174 -0
- codemap/serve/mcp_server.py +278 -0
- codemap/serve/mermaid.py +120 -0
- codemap/serve/pack.py +93 -0
- codemap/serve/rag.py +142 -0
- codemap/serve/review.py +197 -0
- codemap/serve/scip.py +183 -0
- codemap/serve/semantic.py +71 -0
- codemap/serve/server.py +43 -0
- codemap/serve/session.py +482 -0
- codemap/serve/subsystems.py +85 -0
- codemap/serve/vault.py +156 -0
- codemap/store.py +28 -0
- codemap/tomlio.py +59 -0
- codmap-0.0.3.dist-info/METADATA +245 -0
- codmap-0.0.3.dist-info/RECORD +55 -0
- codmap-0.0.3.dist-info/WHEEL +5 -0
- codmap-0.0.3.dist-info/entry_points.txt +2 -0
- codmap-0.0.3.dist-info/licenses/LICENSE +21 -0
- codmap-0.0.3.dist-info/top_level.txt +1 -0
codemap/serve/vault.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Obsidian-vault export — consumer B (DESIGN §4.1-B, M2.2; repo scope M6).
|
|
2
|
+
|
|
3
|
+
Renders the graph as a browsable knowledge base: one Markdown note per module and
|
|
4
|
+
per class/function, cross-linked with ``[[wikilinks]]``. Note names are the full
|
|
5
|
+
canonical id (unambiguous), so links resolve without collisions.
|
|
6
|
+
|
|
7
|
+
With a repo-scoped graph (``extract_repo``) the vault also carries provenance:
|
|
8
|
+
core-symbol notes get a **Used by** section grouped by root (tests/docs/…), each
|
|
9
|
+
consumer note a **Uses (core)** section, and every ``doc`` node its own note — so
|
|
10
|
+
the Obsidian graph shows the blast radius, not just the package interior.
|
|
11
|
+
|
|
12
|
+
``build_vault`` returns ``{relative_path: content}``; the CLI writes the tree.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections import defaultdict
|
|
18
|
+
|
|
19
|
+
from codemap.query import Query
|
|
20
|
+
|
|
21
|
+
_SYMBOL_KINDS = {"class", "function"}
|
|
22
|
+
_USE_EDGES = {"calls", "references", "imports"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_vault(query: Query) -> dict[str, str]:
|
|
26
|
+
"""Return ``{path: markdown}`` for the whole vault (index + module + symbol + doc notes)."""
|
|
27
|
+
graph = query.graph
|
|
28
|
+
known = set(graph.nodes)
|
|
29
|
+
by_module: dict[str, list] = defaultdict(list)
|
|
30
|
+
for node in graph.nodes.values():
|
|
31
|
+
if node.kind in _SYMBOL_KINDS:
|
|
32
|
+
by_module[node.id.rsplit(".", 1)[0]].append(node)
|
|
33
|
+
|
|
34
|
+
# outbound uses (source -> [(target, type)]) for consumer "Uses (core)" sections.
|
|
35
|
+
outbound: dict[str, list[tuple[str, str]]] = defaultdict(list)
|
|
36
|
+
doc_refs: dict[str, list[str]] = defaultdict(list)
|
|
37
|
+
for e in graph.edges:
|
|
38
|
+
if e.type in _USE_EDGES and e.target in known:
|
|
39
|
+
outbound[e.source].append((e.target, e.type))
|
|
40
|
+
if e.type == "references" and graph.nodes.get(e.source) \
|
|
41
|
+
and graph.nodes[e.source].kind == "doc" and e.target in known:
|
|
42
|
+
doc_refs[e.source].append(e.target)
|
|
43
|
+
|
|
44
|
+
modules = sorted(n.id for n in graph.nodes.values() if n.kind == "module")
|
|
45
|
+
docs = sorted(n.id for n in graph.nodes.values() if n.kind == "doc")
|
|
46
|
+
out: dict[str, str] = {"index.md": _index_note(graph.target, modules, docs)}
|
|
47
|
+
for module in modules:
|
|
48
|
+
node = graph.nodes[module]
|
|
49
|
+
out[f"{module}.md"] = _module_note(
|
|
50
|
+
node, sorted(by_module.get(module, []), key=lambda n: n.id),
|
|
51
|
+
outbound.get(module, []), known,
|
|
52
|
+
)
|
|
53
|
+
for node in graph.nodes.values():
|
|
54
|
+
if node.kind in _SYMBOL_KINDS:
|
|
55
|
+
out[f"{node.id}.md"] = _symbol_note(query, node, known)
|
|
56
|
+
for doc in docs:
|
|
57
|
+
out[f"{doc}.md"] = _doc_note(graph.nodes[doc], sorted(set(doc_refs.get(doc, []))), known)
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _link(node_id: str) -> str:
|
|
62
|
+
return f"[[{node_id}|{node_id.rsplit('.', 1)[-1]}]]"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _tags(node) -> str:
|
|
66
|
+
"""Kind + provenance-root tags, so Obsidian graph groups can colour by root."""
|
|
67
|
+
root = node.extras.get("root", "core")
|
|
68
|
+
parts = [f"#{node.kind}", f"#{root}"]
|
|
69
|
+
if node.is_deprecated:
|
|
70
|
+
parts.append("#deprecated")
|
|
71
|
+
return " ".join(parts)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _linked(i: str, known: set) -> str:
|
|
75
|
+
"""Wikilink when the target has its own note, else inline code (external base)."""
|
|
76
|
+
return f"- {_link(i)}" if i in known else f"- `{i}`"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _index_note(target: str, modules: list[str], docs: list[str]) -> str:
|
|
80
|
+
lines = [f"# {target} — code map", "", f"_{len(modules)} modules, {len(docs)} docs._", ""]
|
|
81
|
+
lines += [f"- {_link(m)}" for m in modules]
|
|
82
|
+
if docs:
|
|
83
|
+
lines += ["", "## Docs", ""] + [f"- [[{d}|{d.rsplit('/', 1)[-1]}]]" for d in docs]
|
|
84
|
+
return "\n".join(lines) + "\n"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _module_note(node, symbols: list, uses: list[tuple[str, str]], known: set) -> str:
|
|
88
|
+
module = node.id
|
|
89
|
+
root = node.extras.get("root", "core")
|
|
90
|
+
lines = [f"# `{module}`", "", _tags(node), ""]
|
|
91
|
+
classes = [n for n in symbols if n.kind == "class"]
|
|
92
|
+
funcs = [n for n in symbols if n.kind == "function" and "." not in n.id[len(module) + 1:]]
|
|
93
|
+
if classes:
|
|
94
|
+
lines += ["## Classes", ""] + [f"- {_link(c.id)}" for c in classes] + [""]
|
|
95
|
+
if funcs:
|
|
96
|
+
lines += ["## Functions", ""] + [f"- {_link(f.id)}" for f in funcs] + [""]
|
|
97
|
+
# consumer roots: show what core symbols this file reaches into (the impact link).
|
|
98
|
+
if root != "core" and uses:
|
|
99
|
+
core_targets = sorted({t for t, _ in uses})
|
|
100
|
+
lines += ["## Uses (core)", ""] + [_linked(t, known) for t in core_targets] + [""]
|
|
101
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _symbol_note(query: Query, node, known: set) -> str:
|
|
105
|
+
module = node.id.rsplit(".", 1)[0]
|
|
106
|
+
lines = [f"# `{node.id.rsplit('.', 1)[-1]}`", "", _tags(node), "", f"In {_link(module)}."]
|
|
107
|
+
if node.signature:
|
|
108
|
+
lines += ["", f"```python\n{node.signature}\n```"]
|
|
109
|
+
if node.docstring:
|
|
110
|
+
lines += ["", node.docstring.strip()]
|
|
111
|
+
|
|
112
|
+
if node.kind == "class":
|
|
113
|
+
_section(lines, "Inherits", query.bases(node.id), known)
|
|
114
|
+
_section(lines, "Subclasses", query.subclasses(node.id), known)
|
|
115
|
+
reg = node.extras.get("registry")
|
|
116
|
+
if reg:
|
|
117
|
+
lines += ["", f"**Registered as** `{reg.get('key')}`."]
|
|
118
|
+
if node.kind == "function":
|
|
119
|
+
_section(lines, "Calls", query.callees(node.id), known)
|
|
120
|
+
_section(lines, "Called by", query.callers(node.id), known)
|
|
121
|
+
_used_by_section(lines, query, node.id, known)
|
|
122
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _doc_note(node, targets: list[str], known: set) -> str:
|
|
126
|
+
lines = [f"# `{node.id}`", "", _tags(node), ""]
|
|
127
|
+
if targets:
|
|
128
|
+
lines += ["## References", ""] + [_linked(t, known) for t in targets]
|
|
129
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _used_by_section(lines: list, query: Query, node_id: str, known: set) -> None:
|
|
133
|
+
"""Inbound references grouped by root — the blast radius (repo-scoped graphs)."""
|
|
134
|
+
refs = query.references_to(node_id)
|
|
135
|
+
if not refs:
|
|
136
|
+
return
|
|
137
|
+
by_root: dict[str, set] = defaultdict(set)
|
|
138
|
+
for r in refs:
|
|
139
|
+
by_root[r["root"]].add(r["source"])
|
|
140
|
+
# core self-references are already covered by Calls/Called-by; highlight the rest.
|
|
141
|
+
external_roots = {r: s for r, s in by_root.items() if r != "core"}
|
|
142
|
+
if not external_roots:
|
|
143
|
+
return
|
|
144
|
+
total = sum(len(s) for s in external_roots.values())
|
|
145
|
+
lines += ["", f"## Used by ({total} outside core)", ""]
|
|
146
|
+
for root in sorted(external_roots):
|
|
147
|
+
for src in sorted(external_roots[root])[:20]:
|
|
148
|
+
lines.append(f"- {_link(src) if src in known else f'`{src}`'} _({root})_")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _section(lines: list, title: str, ids: list[str], known: set) -> None:
|
|
152
|
+
if not ids:
|
|
153
|
+
return
|
|
154
|
+
lines += ["", f"## {title}", ""]
|
|
155
|
+
for i in ids:
|
|
156
|
+
lines.append(_linked(i, known))
|
codemap/store.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Canonical JSON store (DESIGN §2.2, §4).
|
|
2
|
+
|
|
3
|
+
JSON is the canonical, portable, diffable source of truth. Query backends
|
|
4
|
+
(networkx/SQLite/Neo4j) are derived from it — not the other way round (DESIGN §4).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from codemap.model import Graph
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def save(graph: Graph, path: str | Path) -> None:
|
|
16
|
+
Path(path).write_text(
|
|
17
|
+
json.dumps(graph.to_dict(), ensure_ascii=False, indent=2) + "\n",
|
|
18
|
+
encoding="utf-8",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dumps(graph: Graph) -> str:
|
|
23
|
+
return json.dumps(graph.to_dict(), ensure_ascii=False, indent=2)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load(path: str | Path) -> Graph:
|
|
27
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
28
|
+
return Graph.from_dict(data)
|
codemap/tomlio.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""One place that reads TOML, and one rule about failing (R1-C27).
|
|
2
|
+
|
|
3
|
+
Three loaders — the architecture contract (`arch.py`), the integration gate
|
|
4
|
+
(`integrations/gate.py`) and the dead-code whitelist (`serve/audit.py`) — read the same
|
|
5
|
+
``codemap.toml``. Each had grown its own copy of:
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import tomllib
|
|
9
|
+
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
10
|
+
except (OSError, ValueError, ModuleNotFoundError):
|
|
11
|
+
return <empty>
|
|
12
|
+
|
|
13
|
+
which collapses three different situations into one silent empty result, and every caller
|
|
14
|
+
then renders that as *the user configured nothing*. It is not the same thing:
|
|
15
|
+
|
|
16
|
+
- **absent** — the user configured nothing. A legitimate, common answer.
|
|
17
|
+
- **unparseable** — the user configured something and it has a typo. `TOMLDecodeError`
|
|
18
|
+
subclasses `ValueError`, so one missing ``]`` turned ``codemap check`` from *exit 2, 14
|
|
19
|
+
imports point up the layer stack* into *"nothing to enforce"*, exit 0. A typo painted a
|
|
20
|
+
CI gate green.
|
|
21
|
+
- **no parser** — the interpreter cannot do this class of work at all (``tomllib`` is
|
|
22
|
+
stdlib from 3.11). Not per-file and not per-user: every TOML feature, off, everywhere.
|
|
23
|
+
|
|
24
|
+
The tolerance is deliberate and stays: reading a broken file must never raise, and must
|
|
25
|
+
never wedge a plain build. What changes is that the tool says which of the three happened,
|
|
26
|
+
so a caller can report *"I could not read your contract"* instead of *"you have no
|
|
27
|
+
contract"* — the same rule as `risk:"unknown"` never being rendered as `risk:"none"`.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
__all__ = ["read_toml"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_toml(path: Path) -> tuple[dict, str | None]:
|
|
38
|
+
"""Parse a TOML file → ``(data, error)``. Never raises.
|
|
39
|
+
|
|
40
|
+
``error`` is ``None`` when there is nothing to report — including when the file simply
|
|
41
|
+
is not there, which is an answer and not a failure. Otherwise ``data`` is ``{}`` and
|
|
42
|
+
``error`` is a human-readable reason naming the file and, where the parser gives one,
|
|
43
|
+
the position of the problem.
|
|
44
|
+
"""
|
|
45
|
+
if not path.is_file():
|
|
46
|
+
return {}, None
|
|
47
|
+
try:
|
|
48
|
+
import tomllib
|
|
49
|
+
except ModuleNotFoundError: # pragma: no cover - 3.10 and older
|
|
50
|
+
return {}, (f"cannot read {path.name}: this Python has no `tomllib` "
|
|
51
|
+
f"(added to the standard library in 3.11)")
|
|
52
|
+
try:
|
|
53
|
+
text = path.read_text(encoding="utf-8")
|
|
54
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
55
|
+
return {}, f"cannot read {path.name}: {exc}"
|
|
56
|
+
try:
|
|
57
|
+
return tomllib.loads(text), None
|
|
58
|
+
except ValueError as exc: # TOMLDecodeError subclasses ValueError
|
|
59
|
+
return {}, f"{path.name} is not valid TOML: {exc}"
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codmap
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Static analyzer that turns a package's source into a queryable code graph.
|
|
5
|
+
Author-email: kogriv <kogriv@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kogriv/codemap
|
|
8
|
+
Project-URL: Source, https://github.com/kogriv/codemap
|
|
9
|
+
Project-URL: Issues, https://github.com/kogriv/codemap/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/kogriv/codemap/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Documentation, https://github.com/kogriv/codemap/tree/main/docs
|
|
12
|
+
Keywords: static-analysis,code-graph,call-graph,dependency-graph,code-intelligence,ast,griffe,mcp,rag,scip
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: griffe>=2.0
|
|
29
|
+
Requires-Dist: networkx>=3.0
|
|
30
|
+
Requires-Dist: jedi>=0.19
|
|
31
|
+
Provides-Extra: mcp
|
|
32
|
+
Requires-Dist: mcp>=2.0; extra == "mcp"
|
|
33
|
+
Provides-Extra: scip
|
|
34
|
+
Requires-Dist: protobuf>=5.26; extra == "scip"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# codemap
|
|
38
|
+
|
|
39
|
+
**A static analyzer that turns a Python package's source into a queryable code graph.**
|
|
40
|
+
It reads source only — no runtime import — so it works on any package and stays decoupled from
|
|
41
|
+
the code it analyzes. One canonical, deterministic graph store → many renders: API surface,
|
|
42
|
+
dependency/architecture audit, RAG chunks, an Obsidian vault, mermaid diagrams, change-set review,
|
|
43
|
+
and a **SCIP index** for interop with Sourcegraph / Glean and other precise-code-intelligence tools.
|
|
44
|
+
|
|
45
|
+
[](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
|
|
46
|
+
|
|
47
|
+
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.12, **530 tests with no failures on
|
|
48
|
+
Python 3.11–3.14** ([in CI](docs/ci.md): the full suite including the dogfood pass, a determinism check, a
|
|
49
|
+
wheel smoke test, and ctags/SCIP interop against the real CLIs), warm serve surface with 31 ops (28 exposed
|
|
50
|
+
as MCP tools), and SCIP export. See **[DESIGN.md](DESIGN.md)** (product design &
|
|
51
|
+
v1 boundaries), **[BACKLOG.md](BACKLOG.md)** (roadmap), and **[research/](research/)** (tool landscape).
|
|
52
|
+
|
|
53
|
+
## Why it exists
|
|
54
|
+
|
|
55
|
+
Docs describe code and CLIs call code; without a parsed map of the code both are done blind. codemap
|
|
56
|
+
builds that map as **facts**: modules, classes, functions, the public API surface, import/inherit/
|
|
57
|
+
export edges, best-effort call edges, registry-family `implements` links, string-key column dataflow,
|
|
58
|
+
and per-call argument contracts — then answers questions over it.
|
|
59
|
+
|
|
60
|
+
Design principles: **source-only** (static `ast`/`griffe`, never imports the target), **deterministic**
|
|
61
|
+
(canonical sorted JSON, no timestamps — diffable), **CLI-AI-first** (JSON by default, stable exit
|
|
62
|
+
codes), **honest** (approximations are labeled, not hidden).
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
**Python 3.11+** — the range is measured on 3.11–3.14 in CI, not assumed ([docs/ci.md](docs/ci.md)).
|
|
67
|
+
|
|
68
|
+
> **The distribution is `codmap`; everything else is `codemap`.** `codemap` was already taken on
|
|
69
|
+
> PyPI, so you install `codmap` — and then the command, the import and this repository are all
|
|
70
|
+
> spelled `codemap`, as they always were.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install codmap # then: codemap build ./yourpkg
|
|
74
|
+
|
|
75
|
+
# optional: MCP server (`codemap serve --mcp`)
|
|
76
|
+
pip install 'codmap[mcp]'
|
|
77
|
+
|
|
78
|
+
# optional: SCIP export (`codemap export scip`)
|
|
79
|
+
pip install 'codmap[scip]'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Until the first release lands on PyPI, install straight from the repository instead:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install git+https://github.com/kogriv/codemap
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Working on codemap itself, from a clone:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv venv && uv pip install -e '.[mcp,scip]' # or: pip install -e '.[mcp,scip]'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Dependencies: `griffe` (structure), `networkx` (query backend), `jedi` (deep call resolution).
|
|
95
|
+
Optional extras: `mcp` (Model Context Protocol server), `scip` (protobuf, for SCIP export).
|
|
96
|
+
|
|
97
|
+
## Quickstart
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# build the canonical graph of a package
|
|
101
|
+
codemap build ./yourpkg -o graph.json
|
|
102
|
+
|
|
103
|
+
# repo-scoped: add consumers (tests/examples) + docs for blast-radius/impact
|
|
104
|
+
codemap build ./yourpkg --deep --mode full --consumer ./tests --docs ./docs -o graph.json
|
|
105
|
+
|
|
106
|
+
# ask about a symbol (JSON by default; --format text for humans)
|
|
107
|
+
codemap query analyze_zones --graph graph.json
|
|
108
|
+
|
|
109
|
+
# reports over the graph
|
|
110
|
+
codemap report architecture --graph graph.json # layers, coupling, god-objects, cycles
|
|
111
|
+
codemap report dependencies --graph graph.json
|
|
112
|
+
codemap report dead-code --graph graph.json
|
|
113
|
+
codemap report impact --symbol MyClass --graph graph.json
|
|
114
|
+
|
|
115
|
+
# change-set review straight from a diff → risk-sorted dossier
|
|
116
|
+
git diff | codemap review - --graph graph.json
|
|
117
|
+
|
|
118
|
+
# exports (see docs/export.md)
|
|
119
|
+
codemap export rag --graph graph.json -o chunks.jsonl
|
|
120
|
+
codemap export mermaid --graph graph.json --mkind class
|
|
121
|
+
codemap export vault --graph graph.json -o vault/
|
|
122
|
+
codemap export scip --graph graph.json -o index.scip # SCIP index (needs [scip] extra)
|
|
123
|
+
codemap export ctags --graph graph.json -o tags # universal-ctags tags file
|
|
124
|
+
|
|
125
|
+
# semantic (concept) search via an opt-in adapter → codemap symbols (needs the tool + opt-in)
|
|
126
|
+
codemap semantic "detect swing pivots" --build ./pkg --root pkg
|
|
127
|
+
|
|
128
|
+
# token-budgeted context pack — most relevant graph slice under N tokens (ranked; --seed to focus)
|
|
129
|
+
codemap pack --graph graph.json --budget 2000 --seed analyze_zones
|
|
130
|
+
|
|
131
|
+
# warm resident process — JSON requests over stdin/stdout (29 ops)
|
|
132
|
+
codemap serve --graph graph.json --source-root .
|
|
133
|
+
|
|
134
|
+
# …or expose the same surface as MCP tools for an AI-agent host (needs [mcp] extra)
|
|
135
|
+
codemap serve --graph graph.json --source-root . --mcp
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## What it answers
|
|
139
|
+
|
|
140
|
+
- **Structure & API** — public surface, signatures, docstrings, deprecation.
|
|
141
|
+
- **Dependencies & architecture** — import cycles, layers + direction/violations, coupling
|
|
142
|
+
(Ca/Ce/instability), god-objects & call-hubs, per-function complexity (cyclomatic / MI)
|
|
143
|
+
blended with structural coupling.
|
|
144
|
+
- **Impact / blast radius** — who uses X, across the whole repo (core + tests + docs).
|
|
145
|
+
- **Change review** — a diff → the symbols it touches, their callers, signature-change surface,
|
|
146
|
+
touched columns, cross-root consumers, risk rank.
|
|
147
|
+
- **Dispatch seams** — registry/factory families and the Protocol each impl satisfies.
|
|
148
|
+
- **Dataflow** — producers/consumers of a string-keyed DataFrame column.
|
|
149
|
+
- **Semantic search** (opt-in) — a concept query routed to an external adapter, with each fuzzy hit
|
|
150
|
+
resolved to the exact codemap symbol at its location (`codemap semantic`). See [docs/integrations.md](docs/integrations.md).
|
|
151
|
+
- **Context pack** — the most relevant slice of the graph under a token budget, ranked by importance or
|
|
152
|
+
by relevance to seed symbols (`codemap pack --budget N [--seed X]`). See [docs/pack.md](docs/pack.md).
|
|
153
|
+
- **Interop** — export the graph as a [SCIP](https://scip-code.org/) index (definitions + symbol
|
|
154
|
+
info + inherits/implements relationships) so Sourcegraph, Glean and other SCIP consumers can drive
|
|
155
|
+
go-to-definition, symbol search and type hierarchy over it. See [docs/export.md](docs/export.md).
|
|
156
|
+
|
|
157
|
+
## How it compares
|
|
158
|
+
|
|
159
|
+
codemap is **the precise structural leg for index-free AI agents** — it complements embeddings-RAG and
|
|
160
|
+
Repomix-style packing rather than competing with them. Its bet is to be the best *deterministic, diffable,
|
|
161
|
+
provenance-aware* graph in that slot, and to interoperate outward (SCIP, ctags) instead of locking the graph
|
|
162
|
+
away.
|
|
163
|
+
|
|
164
|
+
> **A code graph an agent can trust: source-only, deterministic, diffable — no index to go stale, no LSP to provision.**
|
|
165
|
+
|
|
166
|
+
The [research track](research/) measures this against the field hands-on, on a shared benchmark scope. The
|
|
167
|
+
[positioning doc](research/positioning.md) is the publication layer — the narrative and the numbers behind the
|
|
168
|
+
claims above; [comparison.md](research/comparison.md) is the coverage matrix that backs them.
|
|
169
|
+
|
|
170
|
+
Honesty is part of the bet: the call graph is a **measured lower bound**, not a guess. [docs/accuracy.md](docs/accuracy.md)
|
|
171
|
+
reports it — 100% precision / 100% decidable-recall on a hand-labeled suite, an openly-stated ~60% recall
|
|
172
|
+
against *all* true edges (the price of Python's dynamism), and a grep-vs-graph proof that the graph is ~2×
|
|
173
|
+
cheaper than grep for impact on unique names, tens of × on polymorphic ones, and no cheaper for locating a
|
|
174
|
+
symbol.
|
|
175
|
+
|
|
176
|
+
## Dogfooding
|
|
177
|
+
|
|
178
|
+
codemap is validated end-to-end against a real external package. Place a target repo as a sibling and
|
|
179
|
+
run the full flow against its package — e.g. `codemap build ../bquant/bquant` (point at the package
|
|
180
|
+
directory that holds `__init__.py`, not the repo root) — treating codemap purely as a third-party tool.
|
|
181
|
+
The `gaps/` directory records those dogfood runs: each is a pre-registered set of hypotheses, a run on
|
|
182
|
+
the live graph, findings, and the milestone that closed them.
|
|
183
|
+
|
|
184
|
+
## Documentation
|
|
185
|
+
|
|
186
|
+
- **[DESIGN.md](DESIGN.md)** — product design, the query catalog, v1 boundaries.
|
|
187
|
+
- **[docs/export.md](docs/export.md)** — export recipes: RAG, mermaid, Obsidian vault, SCIP + ctags interop.
|
|
188
|
+
- **[docs/accuracy.md](docs/accuracy.md)** — measured call-graph accuracy, the honest static ceiling, and
|
|
189
|
+
the grep-vs-graph value proof (both harnesses guarded in CI).
|
|
190
|
+
- **[docs/architecture-contracts.md](docs/architecture-contracts.md)** — declare the intended architecture
|
|
191
|
+
in `codemap.toml` and enforce it with `codemap check` (CI gate; codemap dogfoods its own).
|
|
192
|
+
- **[docs/api-diff.md](docs/api-diff.md)** — `codemap diff` two snapshots for added/removed/changed symbols
|
|
193
|
+
and API breaking-change detection (release gate + `review --base`).
|
|
194
|
+
- **[docs/integrations.md](docs/integrations.md)** — the opt-in router/adapter layer over external tools
|
|
195
|
+
(`codemap route` / `codemap semantic`); license policy; adding an integration.
|
|
196
|
+
- **[docs/dead-code.md](docs/dead-code.md)** — graded dead-code candidates (high/medium/low + provenance
|
|
197
|
+
reason) with a `[dead_code]` whitelist and `--min-confidence` filter.
|
|
198
|
+
- **[docs/pack.md](docs/pack.md)** — `codemap pack`: PageRank ranking + token-budgeted context slice for
|
|
199
|
+
AI agents (global importance or seed-focused relevance).
|
|
200
|
+
- **[docs/attribute-edges.md](docs/attribute-edges.md)** — `accesses` edges: who reads/writes a class field,
|
|
201
|
+
honest field-level `impact` (`accessors`; `unknown` vs `none`).
|
|
202
|
+
- **[docs/incremental.md](docs/incremental.md)** — `codemap build --incremental`: recompute only changed
|
|
203
|
+
modules (~12× faster on `--deep`), byte-identical on the fast tier.
|
|
204
|
+
- **[docs/test-mapping.md](docs/test-mapping.md)** — `codemap tests <symbol>`: which tests exercise a
|
|
205
|
+
symbol, as runnable pytest node ids, with the measured distance cutoff and an `unknown` that never
|
|
206
|
+
pretends to be "untested".
|
|
207
|
+
- **[docs/hard-python.md](docs/hard-python.md)** — what the extractor does with metaclasses, dynamic
|
|
208
|
+
classes, star imports, quoted annotations, `.pyi` stubs and symlinked trees; and the conditions where it
|
|
209
|
+
warns instead of answering.
|
|
210
|
+
- **[docs/provenance.md](docs/provenance.md)** — the `provenance` block: which tool, which tier, which
|
|
211
|
+
input tree built a graph; what stays in the sidecar; the schema-mismatch warning and `diff`'s
|
|
212
|
+
comparability check.
|
|
213
|
+
- **[docs/flat-layout.md](docs/flat-layout.md)** — flat module directories (sibling imports, no
|
|
214
|
+
`__init__.py`): labelled `resolution="flat"` edges, and the empty-import-graph warning that stops a
|
|
215
|
+
vacuous graph from reading as a clean one.
|
|
216
|
+
- **[research/blog/](research/blog/)** — **the build-story series**: field notes on building
|
|
217
|
+
codemap and measuring it against rival tools (EN + RU). See the section below.
|
|
218
|
+
- **[BACKLOG.md](BACKLOG.md)** — milestones M0–M18, the research track (R1), and deferred work.
|
|
219
|
+
- **[gaps/](gaps/)** — dogfood runs, coverage analysis, the living [axis register](gaps/dogfood_axes.md).
|
|
220
|
+
- **[research/](research/)** — survey of adjacent code-analysis tools and how codemap relates to each
|
|
221
|
+
(integrate / wrap / learn); source of the R1 capability roadmap. See
|
|
222
|
+
**[research/positioning.md](research/positioning.md)** for the publication-layer narrative and
|
|
223
|
+
**[research/comparison.md](research/comparison.md)** for the hands-on coverage matrix.
|
|
224
|
+
|
|
225
|
+
## Writing — the build-story series
|
|
226
|
+
|
|
227
|
+
Field notes on building codemap, and on measuring it honestly against the nearest rival
|
|
228
|
+
tools. Published here in the repo; every post exists in English and Russian.
|
|
229
|
+
**Index: [research/blog/](research/blog/README.md).**
|
|
230
|
+
|
|
231
|
+
| # | Post | |
|
|
232
|
+
|---|------|---|
|
|
233
|
+
| 0 | **A code graph an agent can trust** — what codemap is, the bet it makes, and the honest limits. | [EN](research/blog/00-a-code-graph-an-agent-can-trust.md) · [RU](research/blog/00-a-code-graph-an-agent-can-trust.ru.md) |
|
|
234
|
+
| 1 | **The competitor wasn't broken. We were.** — I nearly published that a rival's impact analysis was broken. The bug was my `PATH`. | [EN](research/blog/01-the-competitor-wasnt-broken.md) · [RU](research/blog/01-the-competitor-wasnt-broken.ru.md) |
|
|
235
|
+
| 2 | **The one that does more — and why that's fine.** — a 1.7 GB hybrid rival that proved the thesis instead of threatening it. | [EN](research/blog/02-the-one-that-does-more.md) · [RU](research/blog/02-the-one-that-does-more.ru.md) |
|
|
236
|
+
| 3 | **The competitor that does *less* — and that's why I take it.** — the emptiest coverage row was the most useful find. For its license, not its features. | [EN](research/blog/03-the-one-that-does-less.md) · [RU](research/blog/03-the-one-that-does-less.ru.md) |
|
|
237
|
+
| 4 | **My determinism test went red. The tool was fine.** — the input was moving under it, and the artifact could not say so. How the graph learned to name what built it. | [EN](research/blog/04-the-determinism-test-that-was-right.md) · [RU](research/blog/04-the-determinism-test-that-was-right.ru.md) |
|
|
238
|
+
|
|
239
|
+
New here? Read **1 → 0 → 2 → 3 → 4**. Every number in every post reproduces from a
|
|
240
|
+
[tool card](research/tools/) or the [comparison hub](research/comparison.md) — measurements,
|
|
241
|
+
not verdict.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
[MIT](LICENSE).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
codemap/__init__.py,sha256=jggl8TP4o82sWxwKfMz6t7IuvhIs8aIKaPrU5FLSgSo,254
|
|
2
|
+
codemap/apidiff.py,sha256=c1FwDsv2PoT36P43gvQuMOgIqRJTMqDh7nYkjC0QNhQ,8851
|
|
3
|
+
codemap/arch.py,sha256=Nvdv2_NskvCi9JzvB3LTH4cwRMsHhG4SnxTrxPxuMCY,7715
|
|
4
|
+
codemap/cli.py,sha256=9DFyAEUnFUpHOeVnavwTZ1FbEpxVohBTYKBLLE_3uJs,35177
|
|
5
|
+
codemap/diagnostics.py,sha256=rrupvLu01gcNEcZq5IPDqGfQz4Z56UcMPOfU0Fvn4YM,12550
|
|
6
|
+
codemap/freshness.py,sha256=x31ABcmxBIId2SK8bEmcnC2tXyAVvXU9uSFXLr2Zoy4,4171
|
|
7
|
+
codemap/incremental.py,sha256=z_3GdTgoKC4HhrnH-VroHI1GxGWprpfi905JKkWbhV4,9401
|
|
8
|
+
codemap/model.py,sha256=BCQkUCrdpEYuUcGDUEcOWyht2J_EoByGeCpkt8u3MIU,9956
|
|
9
|
+
codemap/provenance.py,sha256=evFZ1may3_xko1q5AK4FCbhvRyPPG2hy0PSP4bdlrvI,10017
|
|
10
|
+
codemap/query.py,sha256=o9EHZMWtXAU8KF_1A6TFChtMoCc_0fanFSctNg4MDdU,56669
|
|
11
|
+
codemap/scope.py,sha256=Q19Ff8uI-TJXpPX2PLaMPJcj7O7p6kaTdA2jrqOu4jM,8416
|
|
12
|
+
codemap/store.py,sha256=5Biq0Ymw3p-rkTrXVbHA-Uew6E1gN-DIPk7FZUyLwcY,739
|
|
13
|
+
codemap/tomlio.py,sha256=FdTcUsXNYTdik3xfoyYz89QGs7pkvSXOo483rfZlq9Q,2717
|
|
14
|
+
codemap/extract/__init__.py,sha256=F7p365zBRpdXyPydElj2m0FqiIRu6woPo4cudaut42c,380
|
|
15
|
+
codemap/extract/attrflow.py,sha256=-jL-0oh7cfvxOVuM1yJY-zd87Tu-aLQMSLqaJaVzJTU,10643
|
|
16
|
+
codemap/extract/behavior.py,sha256=3ngdIDZnW-gwoJc3ksuXRAmqMgvLTeOh_ajM7GATR50,35626
|
|
17
|
+
codemap/extract/dataflow.py,sha256=pMnh4ala7mMYMBnk2meo9ylZMbHMxiJe9LgmnKgOo2M,4865
|
|
18
|
+
codemap/extract/dispatch.py,sha256=oAll-vzgkCJ-oZCCbwkMs7y7VYJ4w-gfygLYAA7Tg_8,10563
|
|
19
|
+
codemap/extract/griffe_extractor.py,sha256=ytbEYVrg9vF8YYP0aZbbbgBXKcqrSYaVP96AZMYF-gw,21298
|
|
20
|
+
codemap/extract/gsource.py,sha256=WIGgRtJRZTa_ZsOxG_Xi71K_VSKqRtbD_It05gKPPAs,3570
|
|
21
|
+
codemap/extract/roots.py,sha256=GaTmwx1aoWqAMFATbpFddxFfQb5_hUT-Kw6ORZRFrDY,17839
|
|
22
|
+
codemap/integrations/__init__.py,sha256=lQyfJM8Iu1KJI--hAMRjSj9MayoDXwp58bCELY92QUg,1507
|
|
23
|
+
codemap/integrations/base.py,sha256=BoKb2Z5ak858A7NLHdTzVHgS4Bx_eJKsQ8w890lSIwM,8469
|
|
24
|
+
codemap/integrations/cocoindex.py,sha256=jguBSRQJJsOI9PblxLw7Rla0cY5EPVq9ZRNbf2nBqTo,3353
|
|
25
|
+
codemap/integrations/gate.py,sha256=0c85E3WVQQS0ZK2ehuAhM5KROj25wzMu8jZ-92-z5SQ,2249
|
|
26
|
+
codemap/integrations/gitnexus.py,sha256=rjWlJlr54ZV6RZQLEa0M_4U0T7IfkNyiXMLH20J82cs,4078
|
|
27
|
+
codemap/integrations/registry.py,sha256=wGawI8biedDhyS0bqvXnoYDmwMeubEfmAlgzrvTD7m4,3004
|
|
28
|
+
codemap/integrations/transport.py,sha256=NQWOFcCTvG-7X5KpoeJQYClTJqiC5o_8PuzsIRRpSuQ,1668
|
|
29
|
+
codemap/serve/__init__.py,sha256=wH8fuOAb7nC7zIK-vIO-wKKx3Mvjd8dDS4ak0U8AcAg,862
|
|
30
|
+
codemap/serve/_scip_pb2.py,sha256=Pmq1QXjz5zdIpruFbT1Ja6hjkGxoiFjP97ijEObud5M,17077
|
|
31
|
+
codemap/serve/api_surface.py,sha256=I4b1yI5egp8Hthy3rhdbibhqG3DYPZ1pJ_7fsuk9soQ,2013
|
|
32
|
+
codemap/serve/apidiff.py,sha256=SWNC43QSYTzoxnp-d0QbkWwomYKcZdIZeJKQ5rEkKJA,3324
|
|
33
|
+
codemap/serve/architecture.py,sha256=swnEQEib2BqKER8dIKkjiIK1p0XhFLkNU2dD4_YV1no,4561
|
|
34
|
+
codemap/serve/audit.py,sha256=hrSxX3a2H4zqO7IDHHPGLJK0SUBWp1gH6WKU0waSDIg,8491
|
|
35
|
+
codemap/serve/check.py,sha256=rDY5VVhhMl2a1Sdrcqhbe2kXn1aMqFgcKR8NhJzUY1g,3625
|
|
36
|
+
codemap/serve/ctags.py,sha256=Zw3VIG0rgEwsgDf1SJgwIJHpD4e_GbJGVlSObni9Res,7849
|
|
37
|
+
codemap/serve/impact.py,sha256=sBXppoFF9UVLWQMgrysVObT30hjzdFzCd6ZOcCMcX8I,3859
|
|
38
|
+
codemap/serve/livingdocs.py,sha256=otPvVvYzR-FIcA6orYoIz8PCDNg5djggXJShEIb2S4k,7038
|
|
39
|
+
codemap/serve/mcp_server.py,sha256=W-VJpKFEgUsmXUTy0ZgkIRZenkOBiyQLFMfl_ORN0VI,12882
|
|
40
|
+
codemap/serve/mermaid.py,sha256=7CZF1gyf_lucxOjC1IH6D76wWWl0iCD9ByCRrhELiuA,4558
|
|
41
|
+
codemap/serve/pack.py,sha256=kiZzSkISbXxd3bPjGkRA_U2Rcptryj_IuTWegh1uYX8,3876
|
|
42
|
+
codemap/serve/rag.py,sha256=62cimPFEu4B1uoM1tWf0acbvSdgx-NmXyqLVYjo5wfo,5539
|
|
43
|
+
codemap/serve/review.py,sha256=sSzgmFlXNNjGwB7GPWzSNrAURfqIwWDcohkxp56VCwE,8850
|
|
44
|
+
codemap/serve/scip.py,sha256=7d4hy9AobyMI7DtVvhyHJvkPRlr2K2Z87rKcWg7AOzg,7611
|
|
45
|
+
codemap/serve/semantic.py,sha256=FmHipgggSMHrx1SodvJWK5uE84y6gNFmj2b8mhkjKRw,3376
|
|
46
|
+
codemap/serve/server.py,sha256=JBbLMCdTC2zp1B6oLryShoACDkXwprsM3Phoym_fbts,1589
|
|
47
|
+
codemap/serve/session.py,sha256=aKxKylqrjqL4qIqnv5MAReDaUrkdz6FVSIvdhIm-95M,21794
|
|
48
|
+
codemap/serve/subsystems.py,sha256=BzSo_dacXT5qM1OBxYe6fURriaf9W4Cbo1ndUFiVclA,3609
|
|
49
|
+
codemap/serve/vault.py,sha256=V0DQRxX1VxN3b6_ZjKFY01guh8VPuCSuKBY_1XMuYdU,6568
|
|
50
|
+
codmap-0.0.3.dist-info/licenses/LICENSE,sha256=YmrVnKUwYfMJ7uKkDNJoZ1OxIv69wSo_OV5XxPcBrSU,1063
|
|
51
|
+
codmap-0.0.3.dist-info/METADATA,sha256=vOFiB2NGBAHypzQibXxkB_5KlHpoy2hjzsyx30o6Rl8,14540
|
|
52
|
+
codmap-0.0.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
53
|
+
codmap-0.0.3.dist-info/entry_points.txt,sha256=P73zdmjeFsibIiyUP1n3h2WeNAviIGsEkve74a5NXmU,45
|
|
54
|
+
codmap-0.0.3.dist-info/top_level.txt,sha256=y3cIjxmjvwNiAspXYGejsYO5jxkQ4UXIEgLe3iHpL8g,8
|
|
55
|
+
codmap-0.0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kogriv
|
|
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
|
+
codemap
|