contextlake 2.2.0__tar.gz → 2.3.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.
- {contextlake-2.2.0/src/contextlake.egg-info → contextlake-2.3.0}/PKG-INFO +26 -10
- {contextlake-2.2.0 → contextlake-2.3.0}/README.md +25 -9
- {contextlake-2.2.0 → contextlake-2.3.0}/pyproject.toml +4 -4
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/__init__.py +1 -1
- contextlake-2.3.0/src/contextlake/kb/arch/__init__.py +1 -0
- contextlake-2.3.0/src/contextlake/kb/arch/resolve.py +44 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/commands.py +7 -6
- contextlake-2.3.0/src/contextlake/kb/static/app.css +137 -0
- contextlake-2.3.0/src/contextlake/kb/static/app.js +557 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/visualize.py +126 -377
- {contextlake-2.2.0 → contextlake-2.3.0/src/contextlake.egg-info}/PKG-INFO +26 -10
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake.egg-info/SOURCES.txt +4 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/LICENSE +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/setup.cfg +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/__main__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/cli.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/config.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/core.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/config.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/connectors/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/connectors/atlassian.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/connectors/common.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/connectors/figma.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/connectors/gitlab.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/connectors/orchestrate.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/base.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/builtin.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/hybrid.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/index.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/ollama.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/openai.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/embeddings/store.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/ids.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/llm/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/llm/base.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/llm/builtin.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/llm/ollama.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/llm/openai.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/manifest.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/mcp_client.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/model.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/parse.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/references.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/security.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/server.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/state.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/static/cytoscape.min.js +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/steer/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/steer/generate.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/steer/skills.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/store/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/store/base.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/store/shards.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/store/sqlite_store.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/wiki/__init__.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/wiki/council.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/kb/wiki/generate.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/logging_setup.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/metrics.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/safety.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake/style.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake.egg-info/dependency_links.txt +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake.egg-info/entry_points.txt +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake.egg-info/requires.txt +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/src/contextlake.egg-info/top_level.txt +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_branches.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_cli_overrides.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_clone.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_config.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_logging.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_metrics.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_orchestration.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_resilience.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_safety.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_style.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_update.py +0 -0
- {contextlake-2.2.0 → contextlake-2.3.0}/tests/test_verify_fetch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contextlake
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A local context layer for AI tools: mirror your repositories, index them into a knowledge graph, and serve it over MCP.
|
|
5
5
|
Author-email: Sayak Sarkar <sayak.bugsmith@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -44,13 +44,20 @@ Requires-Dist: llama-cpp-python>=0.2; extra == "llm-local"
|
|
|
44
44
|
Requires-Dist: huggingface_hub>=0.20; extra == "llm-local"
|
|
45
45
|
Dynamic: license-file
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
<p align="center">
|
|
48
|
+
<img src="docs/branding/glyph.svg" alt="" width="76" height="76">
|
|
49
|
+
</p>
|
|
50
|
+
<h1 align="center">contextlake</h1>
|
|
51
|
+
<p align="center"><em>All your real context, in one local lake.</em></p>
|
|
48
52
|
|
|
49
|
-
>
|
|
53
|
+
<p align="center">
|
|
54
|
+
<a href="https://github.com/sayak-sarkar/contextlake/actions/workflows/ci.yml"><img src="https://github.com/sayak-sarkar/contextlake/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
55
|
+
<a href="https://pypi.org/project/contextlake/"><img src="https://img.shields.io/pypi/v/contextlake?color=137A8B" alt="PyPI"></a>
|
|
56
|
+
<img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+">
|
|
57
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT">
|
|
58
|
+
</p>
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-

|
|
53
|
-

|
|
60
|
+
> **A local context layer for your AI tools — your repositories mirrored, indexed into a knowledge graph, and served over MCP, so agents work from real source instead of guessing.**
|
|
54
61
|
|
|
55
62
|
You have access to dozens — maybe hundreds — of repositories scattered across a
|
|
56
63
|
GitLab group and its subgroups. You want them all on your laptop, in the same
|
|
@@ -135,6 +142,11 @@ The tool carries no credentials of its own — auth rides on `glab` — so
|
|
|
135
142
|
`.contextlake.ini` holds only non-secret settings and is gitignored by default. The
|
|
136
143
|
full option reference is in [docs/usage.md](docs/usage.md).
|
|
137
144
|
|
|
145
|
+
> **Behind a slow / TLS-inspecting corporate proxy** (e.g. Zscaler) where `glab`'s API
|
|
146
|
+
> calls time out, set `GITLAB_TOKEN` (a `read_api` token) — contextlake then enumerates
|
|
147
|
+
> projects via its own HTTP client, which tolerates the slow DNS where `glab`'s short
|
|
148
|
+
> dial timeout fails.
|
|
149
|
+
|
|
138
150
|
## Usage
|
|
139
151
|
|
|
140
152
|
Run commands as `contextlake <command>` — full per-command docs are in
|
|
@@ -150,7 +162,8 @@ Run commands as `contextlake <command>` — full per-command docs are in
|
|
|
150
162
|
| `update` | Pull updates for local repos (skips only repos with a dirty working tree) |
|
|
151
163
|
| `branches` | Switch each repo to its most active branch |
|
|
152
164
|
| `verify` | Check the local mirror matches GitLab (drift, orphans, nesting) |
|
|
153
|
-
| `sync` | The full pipeline: fetch → clone → update → branches → verify |
|
|
165
|
+
| `sync` | The full pipeline: fetch → clone → update → branches → verify → audit |
|
|
166
|
+
| `audit` | Repo health & age: empty/README-only repos + creation & last-commit dates (JSON + CSV) |
|
|
154
167
|
| `bootstrap` | **Turnkey**: sync + index + connect + embed + wiki + steer |
|
|
155
168
|
| `index` | Build the code/dependency graph (`--workspace`, incremental, `--watch`) |
|
|
156
169
|
| `connect` | Link repos to Atlassian / Figma / GitLab sources |
|
|
@@ -160,9 +173,10 @@ Run commands as `contextlake <command>` — full per-command docs are in
|
|
|
160
173
|
| `steer` | Write editor steering — `AGENTS.md`, `.mcp.json`, `.windsurfrules`, skills |
|
|
161
174
|
| `serve` | Expose the graph over MCP (`--transport stdio`/`http`) |
|
|
162
175
|
| `query` | Search the index (`--kind`, `--repo`, `--limit`, `--as-of <commit>`) |
|
|
176
|
+
| `graph` | Visualize the graph — offline interactive HTML / DOT / Mermaid / JSON (`--overview`, `--serve`) |
|
|
163
177
|
| `doctor` | Check the knowledge-layer environment (SQLite FTS5, git/glab, store, embeddings) |
|
|
164
178
|
|
|
165
|
-
The first
|
|
179
|
+
The first eight are the core sync (detailed below); the rest are the optional
|
|
166
180
|
**[knowledge layer](#knowledge-layer)**. Run any command with `--config` (sync INI)
|
|
167
181
|
and, for the knowledge layer, `--config`/`--kb-config` pointing at your `kb.toml`.
|
|
168
182
|
|
|
@@ -194,8 +208,10 @@ Beyond mirroring, an optional layer (`contextlake.kb`) turns your repos into a
|
|
|
194
208
|
**knowledge graph** and serves it to AI tools over **MCP** — so Claude Code, Windsurf,
|
|
195
209
|
or Kiro can answer *"where is `X` defined?"* or *"who calls `Y`?"* instead of grepping.
|
|
196
210
|
It can also link repos to their Atlassian / Figma / GitLab items, add semantic search,
|
|
197
|
-
write a curated wiki,
|
|
198
|
-
|
|
211
|
+
write a curated wiki, **visualize the graph** (`contextlake graph` → an offline, interactive
|
|
212
|
+
HTML — fleet overview, a symbol's neighbourhood, or a single repo), and generate per-tool
|
|
213
|
+
steering files + a skills library. Most of it needs no model; the rest works with a local
|
|
214
|
+
Ollama or any OpenAI-compatible endpoint.
|
|
199
215
|
|
|
200
216
|
One command sets it all up:
|
|
201
217
|
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/branding/glyph.svg" alt="" width="76" height="76">
|
|
3
|
+
</p>
|
|
4
|
+
<h1 align="center">contextlake</h1>
|
|
5
|
+
<p align="center"><em>All your real context, in one local lake.</em></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/sayak-sarkar/contextlake/actions/workflows/ci.yml"><img src="https://github.com/sayak-sarkar/contextlake/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
9
|
+
<a href="https://pypi.org/project/contextlake/"><img src="https://img.shields.io/pypi/v/contextlake?color=137A8B" alt="PyPI"></a>
|
|
10
|
+
<img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+">
|
|
11
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT">
|
|
12
|
+
</p>
|
|
2
13
|
|
|
3
14
|
> **A local context layer for your AI tools — your repositories mirrored, indexed into a knowledge graph, and served over MCP, so agents work from real source instead of guessing.**
|
|
4
15
|
|
|
5
|
-
[](https://github.com/sayak-sarkar/contextlake/actions/workflows/ci.yml)
|
|
6
|
-

|
|
7
|
-

|
|
8
|
-
|
|
9
16
|
You have access to dozens — maybe hundreds — of repositories scattered across a
|
|
10
17
|
GitLab group and its subgroups. You want them all on your laptop, in the same
|
|
11
18
|
shape they have on GitLab, each sitting on the branch where the real work is
|
|
@@ -89,6 +96,11 @@ The tool carries no credentials of its own — auth rides on `glab` — so
|
|
|
89
96
|
`.contextlake.ini` holds only non-secret settings and is gitignored by default. The
|
|
90
97
|
full option reference is in [docs/usage.md](docs/usage.md).
|
|
91
98
|
|
|
99
|
+
> **Behind a slow / TLS-inspecting corporate proxy** (e.g. Zscaler) where `glab`'s API
|
|
100
|
+
> calls time out, set `GITLAB_TOKEN` (a `read_api` token) — contextlake then enumerates
|
|
101
|
+
> projects via its own HTTP client, which tolerates the slow DNS where `glab`'s short
|
|
102
|
+
> dial timeout fails.
|
|
103
|
+
|
|
92
104
|
## Usage
|
|
93
105
|
|
|
94
106
|
Run commands as `contextlake <command>` — full per-command docs are in
|
|
@@ -104,7 +116,8 @@ Run commands as `contextlake <command>` — full per-command docs are in
|
|
|
104
116
|
| `update` | Pull updates for local repos (skips only repos with a dirty working tree) |
|
|
105
117
|
| `branches` | Switch each repo to its most active branch |
|
|
106
118
|
| `verify` | Check the local mirror matches GitLab (drift, orphans, nesting) |
|
|
107
|
-
| `sync` | The full pipeline: fetch → clone → update → branches → verify |
|
|
119
|
+
| `sync` | The full pipeline: fetch → clone → update → branches → verify → audit |
|
|
120
|
+
| `audit` | Repo health & age: empty/README-only repos + creation & last-commit dates (JSON + CSV) |
|
|
108
121
|
| `bootstrap` | **Turnkey**: sync + index + connect + embed + wiki + steer |
|
|
109
122
|
| `index` | Build the code/dependency graph (`--workspace`, incremental, `--watch`) |
|
|
110
123
|
| `connect` | Link repos to Atlassian / Figma / GitLab sources |
|
|
@@ -114,9 +127,10 @@ Run commands as `contextlake <command>` — full per-command docs are in
|
|
|
114
127
|
| `steer` | Write editor steering — `AGENTS.md`, `.mcp.json`, `.windsurfrules`, skills |
|
|
115
128
|
| `serve` | Expose the graph over MCP (`--transport stdio`/`http`) |
|
|
116
129
|
| `query` | Search the index (`--kind`, `--repo`, `--limit`, `--as-of <commit>`) |
|
|
130
|
+
| `graph` | Visualize the graph — offline interactive HTML / DOT / Mermaid / JSON (`--overview`, `--serve`) |
|
|
117
131
|
| `doctor` | Check the knowledge-layer environment (SQLite FTS5, git/glab, store, embeddings) |
|
|
118
132
|
|
|
119
|
-
The first
|
|
133
|
+
The first eight are the core sync (detailed below); the rest are the optional
|
|
120
134
|
**[knowledge layer](#knowledge-layer)**. Run any command with `--config` (sync INI)
|
|
121
135
|
and, for the knowledge layer, `--config`/`--kb-config` pointing at your `kb.toml`.
|
|
122
136
|
|
|
@@ -148,8 +162,10 @@ Beyond mirroring, an optional layer (`contextlake.kb`) turns your repos into a
|
|
|
148
162
|
**knowledge graph** and serves it to AI tools over **MCP** — so Claude Code, Windsurf,
|
|
149
163
|
or Kiro can answer *"where is `X` defined?"* or *"who calls `Y`?"* instead of grepping.
|
|
150
164
|
It can also link repos to their Atlassian / Figma / GitLab items, add semantic search,
|
|
151
|
-
write a curated wiki,
|
|
152
|
-
|
|
165
|
+
write a curated wiki, **visualize the graph** (`contextlake graph` → an offline, interactive
|
|
166
|
+
HTML — fleet overview, a symbol's neighbourhood, or a single repo), and generate per-tool
|
|
167
|
+
steering files + a skills library. Most of it needs no model; the rest works with a local
|
|
168
|
+
Ollama or any OpenAI-compatible endpoint.
|
|
153
169
|
|
|
154
170
|
One command sets it all up:
|
|
155
171
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "contextlake"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.0"
|
|
8
8
|
description = "A local context layer for AI tools: mirror your repositories, index them into a knowledge graph, and serve it over MCP."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -72,9 +72,9 @@ package-dir = { "" = "src" }
|
|
|
72
72
|
where = ["src"]
|
|
73
73
|
|
|
74
74
|
[tool.setuptools.package-data]
|
|
75
|
-
# Vendored
|
|
76
|
-
# Without this the wheel silently omits
|
|
77
|
-
"contextlake.kb" = ["static/*.js"]
|
|
75
|
+
# Vendored cytoscape.js + the extracted app.css/app.js for the offline
|
|
76
|
+
# `contextlake graph` HTML. Without this the wheel silently omits them.
|
|
77
|
+
"contextlake.kb" = ["static/*.js", "static/*.css"]
|
|
78
78
|
|
|
79
79
|
[tool.pytest.ini_options]
|
|
80
80
|
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Architecture-level resolution & diagram export over the code graph."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Repo-level architecture resolution from the code knowledge graph.
|
|
2
|
+
|
|
3
|
+
The **trustworthy** cross-repo signal is the *package two-hop*: repo A
|
|
4
|
+
**publishes** a package that repo B **depends_on** → B depends on A. Raw
|
|
5
|
+
cross-repo ``imports`` edges are dominated by import-star artifacts (global
|
|
6
|
+
``module`` nodes like ``System``/``xunit`` shared across the fleet), so they are
|
|
7
|
+
deliberately NOT used here. The result is **inferred** (a manifest-derived,
|
|
8
|
+
likely-undercount signal), never presented as ground truth.
|
|
9
|
+
|
|
10
|
+
Stdlib-only; one SQL query against the shared store.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..store.base import Store
|
|
19
|
+
|
|
20
|
+
# dependent_repo --depends_on--> publisher_repo, weighted by shared package count.
|
|
21
|
+
_TWO_HOP = """
|
|
22
|
+
SELECT dep.dep_repo, pub.pub_repo, COUNT(DISTINCT pub.pkg) AS shared
|
|
23
|
+
FROM (SELECT np.repo_id AS pub_repo, e.dst AS pkg FROM edges e
|
|
24
|
+
JOIN nodes np ON np.node_id = e.src WHERE e.relation = 'publishes') pub
|
|
25
|
+
JOIN (SELECT nd.repo_id AS dep_repo, e.dst AS pkg FROM edges e
|
|
26
|
+
JOIN nodes nd ON nd.node_id = e.src WHERE e.relation = 'depends_on') dep
|
|
27
|
+
ON pub.pkg = dep.pkg
|
|
28
|
+
WHERE pub.pub_repo != dep.dep_repo
|
|
29
|
+
GROUP BY dep.dep_repo, pub.pub_repo
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def repo_dependency_edges(store: Store) -> list[dict]:
|
|
34
|
+
"""Real repo→repo dependencies via the package two-hop (``publishes ⨝ depends_on``).
|
|
35
|
+
|
|
36
|
+
Each edge is ``dependent --depends_on--> publisher``, ``weight`` = number of
|
|
37
|
+
shared packages, marked ``INFERRED`` (manifest-derived, a likely undercount —
|
|
38
|
+
not every dependency declares/publishes a package). Far smaller and far more
|
|
39
|
+
trustworthy than the raw cross-repo ``imports`` edges.
|
|
40
|
+
"""
|
|
41
|
+
rows = store.conn.execute(_TWO_HOP).fetchall()
|
|
42
|
+
return [{"src": dep, "dst": pub, "relation": "depends_on",
|
|
43
|
+
"confidence": "INFERRED", "weight": shared}
|
|
44
|
+
for dep, pub, shared in rows]
|
|
@@ -840,12 +840,13 @@ def cmd_graph(args) -> int:
|
|
|
840
840
|
# is findable); neighbourhood/repo views stay bounded at 500.
|
|
841
841
|
max_nodes = getattr(args, "max_nodes", None) or (5000 if overview else 500)
|
|
842
842
|
|
|
843
|
+
meta: dict = {}
|
|
843
844
|
if overview:
|
|
844
|
-
nodes, edges = viz.overview_subgraph(store, max_nodes=max_nodes)
|
|
845
|
-
meta
|
|
845
|
+
nodes, edges = viz.overview_subgraph(store, max_nodes=max_nodes, meta=meta)
|
|
846
|
+
meta["mode"] = "overview"
|
|
846
847
|
elif getattr(args, "repo", None) and not _has_seed(args):
|
|
847
|
-
nodes, edges = viz.repo_subgraph(store, args.repo, max_nodes=max_nodes)
|
|
848
|
-
meta
|
|
848
|
+
nodes, edges = viz.repo_subgraph(store, args.repo, max_nodes=max_nodes, meta=meta)
|
|
849
|
+
meta.update(mode="repo", repo=args.repo)
|
|
849
850
|
else:
|
|
850
851
|
seeds = viz.seed_ids_from_args(store, args)
|
|
851
852
|
if not seeds:
|
|
@@ -855,8 +856,8 @@ def cmd_graph(args) -> int:
|
|
|
855
856
|
nodes, edges = viz.extract_subgraph(
|
|
856
857
|
store, seeds, hops=hops, max_nodes=max_nodes, max_fanout=max_fanout,
|
|
857
858
|
relation=getattr(args, "relation", None),
|
|
858
|
-
direction=getattr(args, "direction", None) or "both")
|
|
859
|
-
meta
|
|
859
|
+
direction=getattr(args, "direction", None) or "both", meta=meta)
|
|
860
|
+
meta.update(mode="neighborhood", seed_ids=seeds, hops=hops)
|
|
860
861
|
|
|
861
862
|
payload = viz.to_payload(nodes, edges, meta)
|
|
862
863
|
cdn = getattr(args, "cdn", False)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
|
|
2
|
+
:root{
|
|
3
|
+
--deepwater:#0E2A33;--lake:#137A8B;--current:#2BB3A3;--mist:#EAF4F4;
|
|
4
|
+
--shore:#D7C5A0;--sun:#E7B53C;
|
|
5
|
+
--bg:#f5fafb;--surface:#ffffff;--surface-2:#eef6f7;--line:#dce8ea;--line-2:#c6d8db;
|
|
6
|
+
--text:#0E2A33;--muted:#5b7177;--subtle:#8aa2a6;--brand:#137A8B;--brand-h:#0f6473;
|
|
7
|
+
--accent:#2BB3A3;--focus:#2BB3A3;--canvas-label:#0E2A33;--surface-solid:#ffffff;
|
|
8
|
+
--faded-opacity:0.08;
|
|
9
|
+
--cy-bg:radial-gradient(120% 90% at 50% -10%,#ffffff 0%,#f1fafb 45%,#e3f1f2 100%);
|
|
10
|
+
--e1:0 1px 2px rgba(14,42,51,.07);--e2:0 4px 14px -4px rgba(14,42,51,.18);
|
|
11
|
+
--rail:288px;--inspect:0px;--bar:48px;--status:30px;
|
|
12
|
+
--ff:"Inter",system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
|
13
|
+
--ff-d:"Space Grotesk",var(--ff);--dur:.16s;--ease:cubic-bezier(.2,.6,.2,1);
|
|
14
|
+
}
|
|
15
|
+
body[data-theme="dark"]{
|
|
16
|
+
--bg:#0a1f26;--surface:#102e38;--surface-2:#16414e;--line:#1d4d5b;--line-2:#286675;
|
|
17
|
+
--text:#EAF4F4;--muted:#9bbcc2;--subtle:#6f969e;--brand:#2BB3A3;--brand-h:#3fc6b6;
|
|
18
|
+
--canvas-label:#EAF4F4;--surface-solid:#102e38;
|
|
19
|
+
--faded-opacity:0.16;
|
|
20
|
+
--cy-bg:radial-gradient(120% 90% at 50% -10%,#103039 0%,#0c252d 45%,#081b21 100%);
|
|
21
|
+
--e1:0 1px 2px rgba(0,0,0,.4);--e2:0 6px 18px -6px rgba(0,0,0,.55);
|
|
22
|
+
}
|
|
23
|
+
body[data-sidebar="collapsed"]{--rail:0px}
|
|
24
|
+
body[data-inspect="open"]{--inspect:340px}
|
|
25
|
+
*{box-sizing:border-box}
|
|
26
|
+
html,body{margin:0;height:100%;color:var(--text);background:var(--bg);font-family:var(--ff)}
|
|
27
|
+
#app{display:grid;height:100%;grid-template-columns:var(--rail) 1fr var(--inspect);
|
|
28
|
+
grid-template-rows:var(--bar) 1fr var(--status);
|
|
29
|
+
grid-template-areas:"top top top" "side cy inspect" "status status status";
|
|
30
|
+
transition:grid-template-columns var(--dur) var(--ease)}
|
|
31
|
+
#topbar{grid-area:top;display:flex;align-items:center;gap:10px;padding:0 12px;
|
|
32
|
+
background:var(--surface);border-bottom:1px solid var(--line);z-index:5}
|
|
33
|
+
.glyph{width:26px;height:26px;border-radius:7px;display:block;box-shadow:var(--e1)}
|
|
34
|
+
.wm{font-family:var(--ff-d);font-size:16px;font-weight:600;letter-spacing:-.01em;color:var(--text)}
|
|
35
|
+
.wm .l{color:var(--brand)}
|
|
36
|
+
#mode{font-size:11px;color:var(--muted);padding:2px 9px;border:1px solid var(--line);
|
|
37
|
+
border-radius:999px;background:var(--surface-2);text-transform:capitalize}
|
|
38
|
+
.grow{flex:1}
|
|
39
|
+
.tsearch{position:relative;display:flex;align-items:center;width:260px;max-width:34vw}
|
|
40
|
+
.tsearch .si{position:absolute;left:9px;width:14px;height:14px;color:var(--subtle)}
|
|
41
|
+
#search{width:100%;padding:7px 10px 7px 28px;font-size:12px;border:1px solid var(--line);
|
|
42
|
+
border-radius:8px;background:var(--surface-2);color:var(--text);
|
|
43
|
+
transition:border-color var(--dur),box-shadow var(--dur)}
|
|
44
|
+
#search:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(43,179,163,.25);
|
|
45
|
+
background:var(--surface)}
|
|
46
|
+
#panel{grid-area:side;overflow-y:auto;background:var(--surface);
|
|
47
|
+
border-right:1px solid var(--line);padding:14px;font-size:12px;min-width:0}
|
|
48
|
+
body[data-sidebar="collapsed"] #panel{padding:0;border:0;overflow:hidden}
|
|
49
|
+
.sgroup{margin-bottom:16px}
|
|
50
|
+
.sgroup h2{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;
|
|
51
|
+
color:var(--muted);margin:0 0 8px}
|
|
52
|
+
.row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
|
53
|
+
#viewmodes{margin-bottom:9px}
|
|
54
|
+
#viewmodes[hidden]{display:none}
|
|
55
|
+
.seg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;
|
|
56
|
+
background:var(--surface-2)}
|
|
57
|
+
.segbtn{font:inherit;font-size:12px;padding:6px 13px;border:0;background:transparent;
|
|
58
|
+
color:var(--muted);cursor:pointer;transition:background var(--dur),color var(--dur)}
|
|
59
|
+
.segbtn+.segbtn{border-left:1px solid var(--line)}
|
|
60
|
+
.segbtn.on{background:var(--brand);color:#fff;font-weight:500}
|
|
61
|
+
.segbtn:not(.on):hover{background:var(--surface);color:var(--text)}
|
|
62
|
+
.tog{display:flex;align-items:center;gap:6px;margin-top:9px;font-size:11px;
|
|
63
|
+
color:var(--muted);cursor:pointer}
|
|
64
|
+
.tog[hidden]{display:none}
|
|
65
|
+
.tog input{accent-color:var(--brand);cursor:pointer;margin:0}
|
|
66
|
+
.tog .cnt{color:var(--subtle);font-variant-numeric:tabular-nums}
|
|
67
|
+
label{font-size:11px;color:var(--muted)}
|
|
68
|
+
select,.btn,.ibtn{font:inherit;font-size:12px;border:1px solid var(--line);border-radius:8px;
|
|
69
|
+
background:var(--surface);color:var(--text);cursor:pointer;
|
|
70
|
+
transition:background var(--dur),border-color var(--dur),box-shadow var(--dur)}
|
|
71
|
+
select{padding:6px 8px}
|
|
72
|
+
.btn{padding:6px 11px;display:inline-flex;align-items:center;gap:6px}
|
|
73
|
+
.ibtn{padding:6px;width:30px;height:30px;display:inline-flex;align-items:center;
|
|
74
|
+
justify-content:center}
|
|
75
|
+
.ibtn svg,.btn svg{width:15px;height:15px}
|
|
76
|
+
.btn:hover,.ibtn:hover{background:var(--surface-2);border-color:var(--line-2)}
|
|
77
|
+
.btn:active,.ibtn:active{transform:translateY(1px)}
|
|
78
|
+
.btn.primary{background:var(--brand);border-color:var(--brand);color:#fff;box-shadow:var(--e1)}
|
|
79
|
+
.btn.primary:hover{background:var(--brand-h);border-color:var(--brand-h)}
|
|
80
|
+
:focus-visible{outline:none;box-shadow:0 0 0 2px var(--surface),0 0 0 4px var(--focus)}
|
|
81
|
+
#legend,#edgelegend{display:flex;flex-wrap:wrap;gap:5px}
|
|
82
|
+
.lg{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:999px;
|
|
83
|
+
border:1px solid var(--line);background:var(--surface);color:var(--text);cursor:pointer;
|
|
84
|
+
user-select:none;font-size:11px;transition:background var(--dur),border-color var(--dur)}
|
|
85
|
+
.lg:hover{background:var(--surface-2);border-color:var(--line-2)}
|
|
86
|
+
.lg i{width:9px;height:9px;border-radius:50%;display:inline-block}
|
|
87
|
+
.lg.rel i{border-radius:1px;width:12px;height:3px}
|
|
88
|
+
.lg .cnt{color:var(--subtle);font-variant-numeric:tabular-nums;font-size:10px}
|
|
89
|
+
.lg.off{opacity:.4}
|
|
90
|
+
.lg.off .lbl{text-decoration:line-through}
|
|
91
|
+
#cy{grid-area:cy;min-width:0;min-height:0;position:relative;background:var(--cy-bg)}
|
|
92
|
+
#info{grid-area:inspect;overflow-y:auto;background:var(--surface);
|
|
93
|
+
border-left:1px solid var(--line);padding:14px;font-size:12px;display:none}
|
|
94
|
+
body[data-inspect="open"] #info{display:block}
|
|
95
|
+
#info h2{font-size:14px;margin:0 0 2px;color:var(--text);word-break:break-all}
|
|
96
|
+
#info dl{display:grid;grid-template-columns:auto 1fr;gap:4px 10px;margin:9px 0 0;
|
|
97
|
+
border-top:1px solid var(--line);padding-top:9px}
|
|
98
|
+
#info dt{color:var(--brand);font-weight:500;font-size:11px}
|
|
99
|
+
#info dd{margin:0;word-break:break-all;font-size:11px}
|
|
100
|
+
#info .hint{color:var(--subtle);margin-top:9px;font-style:italic}
|
|
101
|
+
#info .edge-flow{font-size:11px;color:var(--muted);margin:5px 0 2px;word-break:break-all}
|
|
102
|
+
#info .trust{display:flex;align-items:center;gap:6px;margin:8px 0 2px;font-size:11px}
|
|
103
|
+
#info .trust .dot{width:9px;height:9px;border-radius:50%;display:inline-block}
|
|
104
|
+
#info .trust .blurb{color:var(--muted)}
|
|
105
|
+
#info .copy-prov{margin-top:10px}
|
|
106
|
+
#info .conns{margin-top:11px;border-top:1px solid var(--line);padding-top:9px}
|
|
107
|
+
#info .conns h3{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;
|
|
108
|
+
color:var(--muted);margin:0 0 7px}
|
|
109
|
+
#info .conns .cc{color:var(--subtle);font-weight:500}
|
|
110
|
+
#info .conns ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
|
|
111
|
+
#info .conns li{display:flex;align-items:center;gap:6px;font-size:11px;line-height:1.3}
|
|
112
|
+
#info .conns .rdot{width:8px;height:8px;border-radius:50%;flex:none}
|
|
113
|
+
#info .conns .rl{color:var(--muted);white-space:nowrap;flex:none}
|
|
114
|
+
#info .conns .rn{color:var(--brand);cursor:pointer;word-break:break-all;
|
|
115
|
+
text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color var(--dur)}
|
|
116
|
+
#info .conns .rn:hover{text-decoration-color:currentColor}
|
|
117
|
+
#info .conns .rmore{color:var(--brand);font-style:italic;cursor:pointer}
|
|
118
|
+
#info .conns .rmore:hover{text-decoration:underline}
|
|
119
|
+
.rel-chip{display:inline-block;padding:2px 9px;border-radius:999px;color:#fff;
|
|
120
|
+
font-size:12px;font-weight:600}
|
|
121
|
+
#statusbar{grid-area:status;display:flex;align-items:center;gap:10px;padding:0 12px;
|
|
122
|
+
background:var(--surface);border-top:1px solid var(--line);font-size:11px;color:var(--muted)}
|
|
123
|
+
#statusbar .l{color:var(--brand)}
|
|
124
|
+
.trunc{color:var(--sun);font-weight:600;display:none}
|
|
125
|
+
.trunc.show{display:inline}
|
|
126
|
+
#tip{position:absolute;z-index:40;pointer-events:none;display:none;background:var(--deepwater);
|
|
127
|
+
color:var(--mist);font-size:11px;padding:4px 8px;border-radius:6px;max-width:42ch;
|
|
128
|
+
box-shadow:var(--e2)}
|
|
129
|
+
#empty{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;
|
|
130
|
+
justify-content:center;gap:6px;color:var(--muted);text-align:center;padding:20px}
|
|
131
|
+
#empty.show{display:flex}
|
|
132
|
+
#empty .et{font-size:14px;color:var(--text);font-weight:600}
|
|
133
|
+
#empty code{background:var(--surface-2);padding:1px 5px;border-radius:4px;font-size:11px}
|
|
134
|
+
#cy.loading::after{content:"";position:absolute;top:0;left:0;height:2px;width:28%;
|
|
135
|
+
background:var(--accent);animation:ld 1s var(--ease) infinite}
|
|
136
|
+
@keyframes ld{0%{left:-28%}100%{left:100%}}
|
|
137
|
+
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
|